balboa_worldwide_app 1.3.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/exe/bwa_client +41 -0
- data/exe/bwa_mqtt_bridge +394 -0
- data/{bin → exe}/bwa_proxy +3 -2
- data/{bin → exe}/bwa_server +3 -2
- data/lib/balboa_worldwide_app.rb +3 -1
- data/lib/bwa/client.rb +121 -91
- data/lib/bwa/crc.rb +3 -1
- data/lib/bwa/discovery.rb +18 -17
- data/lib/bwa/logger.rb +9 -7
- data/lib/bwa/message.rb +67 -53
- data/lib/bwa/messages/configuration.rb +3 -1
- data/lib/bwa/messages/configuration_request.rb +3 -1
- data/lib/bwa/messages/control_configuration.rb +12 -9
- data/lib/bwa/messages/control_configuration_request.rb +13 -11
- data/lib/bwa/messages/filter_cycles.rb +50 -22
- data/lib/bwa/messages/ready.rb +3 -1
- data/lib/bwa/messages/{set_temperature.rb → set_target_temperature.rb} +5 -3
- data/lib/bwa/messages/set_temperature_scale.rb +5 -3
- data/lib/bwa/messages/set_time.rb +4 -2
- data/lib/bwa/messages/status.rb +51 -44
- data/lib/bwa/messages/toggle_item.rb +29 -27
- data/lib/bwa/proxy.rb +17 -18
- data/lib/bwa/server.rb +16 -14
- data/lib/bwa/version.rb +3 -1
- metadata +70 -24
- data/bin/bwa_client +0 -43
- data/bin/bwa_mqtt_bridge +0 -614
    
        data/bin/bwa_mqtt_bridge
    DELETED
    
    | @@ -1,614 +0,0 @@ | |
| 1 | 
            -
            #!/usr/bin/env ruby
         | 
| 2 | 
            -
             | 
| 3 | 
            -
            require 'mqtt'
         | 
| 4 | 
            -
            require 'sd_notify'
         | 
| 5 | 
            -
            require 'set'
         | 
| 6 | 
            -
            require 'json'
         | 
| 7 | 
            -
             | 
| 8 | 
            -
            require 'bwa/logger'
         | 
| 9 | 
            -
            require 'bwa/client'
         | 
| 10 | 
            -
            require 'bwa/discovery'
         | 
| 11 | 
            -
            require 'bwa/version'
         | 
| 12 | 
            -
             | 
| 13 | 
            -
            class MQTTBridge
         | 
| 14 | 
            -
              def initialize(mqtt_uri, bwa, device_id: "bwa", base_topic: "homie")
         | 
| 15 | 
            -
                @base_topic = "#{base_topic}/#{device_id}"
         | 
| 16 | 
            -
                @mqtt = MQTT::Client.new(mqtt_uri)
         | 
| 17 | 
            -
                @mqtt.set_will("#{@base_topic}/$state", "lost", true)
         | 
| 18 | 
            -
                @mqtt.connect
         | 
| 19 | 
            -
                @bwa = bwa
         | 
| 20 | 
            -
                @attributes = {}
         | 
| 21 | 
            -
                @things = Set.new
         | 
| 22 | 
            -
             | 
| 23 | 
            -
                hass_discovery_topic = "homeassistant/"
         | 
| 24 | 
            -
                @ha_binary_path = hass_discovery_topic + "binary_sensor/"
         | 
| 25 | 
            -
                @ha_sensor_path = hass_discovery_topic + "sensor/"
         | 
| 26 | 
            -
                @ha_switch_path = hass_discovery_topic + "switch/"
         | 
| 27 | 
            -
                @ha_selects_path = hass_discovery_topic + "select/"
         | 
| 28 | 
            -
                @ha_number_path = hass_discovery_topic + "number/"
         | 
| 29 | 
            -
             | 
| 30 | 
            -
                @hass_device = {
         | 
| 31 | 
            -
                  device: {
         | 
| 32 | 
            -
                    identifiers: [device_id],
         | 
| 33 | 
            -
                    sw_version: BWA::VERSION,
         | 
| 34 | 
            -
                    name: "BWA Link"
         | 
| 35 | 
            -
                  }
         | 
| 36 | 
            -
                }
         | 
| 37 | 
            -
             | 
| 38 | 
            -
                # bwa availability topics (works for all devices)
         | 
| 39 | 
            -
                @hass_availability = {
         | 
| 40 | 
            -
                  availability_topic: "#{@base_topic}/$state",
         | 
| 41 | 
            -
                  payload_available: "ready",
         | 
| 42 | 
            -
                  payload_not_available: "lost"
         | 
| 43 | 
            -
                }
         | 
| 44 | 
            -
             | 
| 45 | 
            -
                publish_basic_attributes
         | 
| 46 | 
            -
                publish_filtercycles
         | 
| 47 | 
            -
             | 
| 48 | 
            -
                # Home Assistant MQTT Discovery Section
         | 
| 49 | 
            -
                publish_hass_discovery
         | 
| 50 | 
            -
             | 
| 51 | 
            -
                # Tell systemd we've started up OK. Ignored if systemd not in use.
         | 
| 52 | 
            -
                BWA.logger.warn "Balboa MQTT Bridge running (version #{BWA::VERSION})"
         | 
| 53 | 
            -
                SdNotify.ready
         | 
| 54 | 
            -
             | 
| 55 | 
            -
                bwa_thread = Thread.new do
         | 
| 56 | 
            -
                  loop do
         | 
| 57 | 
            -
                    begin
         | 
| 58 | 
            -
                      message = @bwa.poll
         | 
| 59 | 
            -
                      next if message.is_a?(BWA::Messages::Ready)
         | 
| 60 | 
            -
             | 
| 61 | 
            -
                      case message
         | 
| 62 | 
            -
                      when BWA::Messages::ControlConfiguration
         | 
| 63 | 
            -
                        publish("spa/$type", message.model)
         | 
| 64 | 
            -
                      when BWA::Messages::ControlConfiguration2
         | 
| 65 | 
            -
                        message.pumps.each_with_index do |speed, i|
         | 
| 66 | 
            -
                          publish_pump(i + 1, speed) if speed != 0
         | 
| 67 | 
            -
                        end
         | 
| 68 | 
            -
                        message.lights.each_with_index do |exists, i|
         | 
| 69 | 
            -
                          publish_thing(:light, i + 1) if exists
         | 
| 70 | 
            -
                        end
         | 
| 71 | 
            -
                        message.aux.each_with_index do |exists, i|
         | 
| 72 | 
            -
                          publish_thing(:aux, i + 1) if exists
         | 
| 73 | 
            -
                        end
         | 
| 74 | 
            -
                        publish_mister if message.mister
         | 
| 75 | 
            -
                        publish_blower(message.blower) if message.blower != 0
         | 
| 76 | 
            -
                        publish_circpump if message.circ_pump
         | 
| 77 | 
            -
                        publish("$state", "ready")
         | 
| 78 | 
            -
                      when BWA::Messages::FilterCycles
         | 
| 79 | 
            -
                        publish_attribute("spa/filter1hour",message.filter1_hour)
         | 
| 80 | 
            -
                        publish_attribute("spa/filter1minute",message.filter1_minute)
         | 
| 81 | 
            -
                        publish_attribute("spa/filter1durationhours",message.filter1_duration_hours)
         | 
| 82 | 
            -
                        publish_attribute("spa/filter1durationminutes",message.filter1_duration_minutes)
         | 
| 83 | 
            -
                        publish_attribute("spa/filter2enabled",message.filter2_enabled)
         | 
| 84 | 
            -
                        publish_attribute("spa/filter2hour",message.filter2_hour)
         | 
| 85 | 
            -
                        publish_attribute("spa/filter2minute",message.filter2_minute)
         | 
| 86 | 
            -
                        publish_attribute("spa/filter2durationhours",message.filter2_duration_hours)
         | 
| 87 | 
            -
                        publish_attribute("spa/filter2durationminutes",message.filter2_duration_minutes)
         | 
| 88 | 
            -
                      when BWA::Messages::Status
         | 
| 89 | 
            -
                        @bwa.request_control_info unless @bwa.last_control_configuration
         | 
| 90 | 
            -
                        @bwa.request_control_info2 unless @bwa.last_control_configuration2
         | 
| 91 | 
            -
                        @bwa.request_filter_configuration unless @bwa.last_filter_configuration 
         | 
| 92 | 
            -
             | 
| 93 | 
            -
                        # make sure time is in sync
         | 
| 94 | 
            -
                        now = Time.now
         | 
| 95 | 
            -
                        now_minutes = now.hour * 60 + now.min
         | 
| 96 | 
            -
                        spa_minutes = message.hour * 60 + message.minute
         | 
| 97 | 
            -
                        # check the difference in both directions
         | 
| 98 | 
            -
                        diff = [(spa_minutes - now_minutes) % 1440, 1440 - (spa_minutes - now_minutes) % 1440].min
         | 
| 99 | 
            -
             | 
| 100 | 
            -
                        # allow a skew of 1 minute, since the seconds will always be off
         | 
| 101 | 
            -
                        if diff > 1
         | 
| 102 | 
            -
                          BWA.logger.info "Spa time #{"%02d:%02d" % [message.hour, message.minute]}, actually #{"%02d:%02d" % [now.hour, now.min]}; correcting difference of #{diff} min"
         | 
| 103 | 
            -
                          @bwa.set_time(now.hour, now.min, message.twenty_four_hour_time)
         | 
| 104 | 
            -
                        end
         | 
| 105 | 
            -
                        publish_attribute("spa/hold", message.hold)
         | 
| 106 | 
            -
                        publish_attribute("spa/priming", message.priming)
         | 
| 107 | 
            -
                        publish_attribute("spa/heatingmode", message.heating_mode)
         | 
| 108 | 
            -
                        publish_attribute("spa/temperaturescale", message.temperature_scale)
         | 
| 109 | 
            -
                        publish_attribute("spa/24htime", message.twenty_four_hour_time)
         | 
| 110 | 
            -
                        publish_attribute("spa/heating", message.heating)
         | 
| 111 | 
            -
                        publish_attribute("spa/temperaturerange", message.temperature_range)
         | 
| 112 | 
            -
                        publish_attribute("spa/currenttemperature", message.current_temperature)
         | 
| 113 | 
            -
                        publish_attribute("spa/currenttemperature/$unit", "º#{message.temperature_scale.to_s[0].upcase}")
         | 
| 114 | 
            -
                        publish_attribute("spa/settemperature", message.set_temperature)
         | 
| 115 | 
            -
                        publish_attribute("spa/settemperature/$unit", "º#{message.temperature_scale.to_s[0].upcase}")
         | 
| 116 | 
            -
                        if message.temperature_scale == :celsius
         | 
| 117 | 
            -
                          publish_attribute("spa/currenttemperature/$format", "0:42")
         | 
| 118 | 
            -
                          publish_attribute("spa/settemperature/$format", message.temperature_range == :high ? "26:40" : "10:26")
         | 
| 119 | 
            -
                          publish_hass_discovery_settemp(:celsius)
         | 
| 120 | 
            -
                        else
         | 
| 121 | 
            -
                          publish_attribute("spa/currenttemperature/$format", "32:108")
         | 
| 122 | 
            -
                          publish_attribute("spa/settemperature/$format", message.temperature_range == :high ? "80:106" : "50:99")
         | 
| 123 | 
            -
                          publish_hass_discovery_settemp(:fahrenheit)
         | 
| 124 | 
            -
                        end
         | 
| 125 | 
            -
                        publish_attribute("spa/filter1", message.filter[0])
         | 
| 126 | 
            -
                        publish_attribute("spa/filter2", message.filter[1])
         | 
| 127 | 
            -
             | 
| 128 | 
            -
                        publish_attribute("spa/circpump", message.circ_pump) if @bwa.last_control_configuration2&.circ_pump
         | 
| 129 | 
            -
                        publish_attribute("spa/blower", message.blower) if @bwa.last_control_configuration2&.blower.to_i != 0
         | 
| 130 | 
            -
                        publish_attribute("spa/mister", message.mister) if @bwa.last_control_configuration2&.mister
         | 
| 131 | 
            -
                        (0..5).each do |i|
         | 
| 132 | 
            -
                          publish_attribute("spa/pump#{i + 1}", message.pumps[i]) if @bwa.last_control_configuration2&.pumps&.[](i).to_i != 0
         | 
| 133 | 
            -
                        end
         | 
| 134 | 
            -
                        (0..1).each do |i|
         | 
| 135 | 
            -
                          publish_attribute("spa/light#{i + 1}", message.lights[i]) if @bwa.last_control_configuration2&.lights&.[](i)
         | 
| 136 | 
            -
                        end
         | 
| 137 | 
            -
                        (0..1).each do |i|
         | 
| 138 | 
            -
                          publish_attribute("spa/aux#{i + 1}", message.lights[i]) if @bwa.last_control_configuration2&.aux&.[](i)
         | 
| 139 | 
            -
                        end
         | 
| 140 | 
            -
             | 
| 141 | 
            -
                        # Tell systemd we are still alive and kicking. Ignored if systemd not in use.
         | 
| 142 | 
            -
                        SdNotify.watchdog
         | 
| 143 | 
            -
             | 
| 144 | 
            -
                      end
         | 
| 145 | 
            -
                    end
         | 
| 146 | 
            -
                  end
         | 
| 147 | 
            -
                end
         | 
| 148 | 
            -
             | 
| 149 | 
            -
                @mqtt.get do |topic, value|
         | 
| 150 | 
            -
                  BWA.logger.warn "from mqtt: #{value.inspect} at #{topic}"
         | 
| 151 | 
            -
                  case topic[@base_topic.length + 1..-1]
         | 
| 152 | 
            -
                  when "spa/hold/set"
         | 
| 153 | 
            -
                    next @bwa.toggle_hold if value == 'toggle'
         | 
| 154 | 
            -
                    next unless %w{true false}.include?(value)
         | 
| 155 | 
            -
                    @bwa.set_hold(value == 'true')
         | 
| 156 | 
            -
                  when "spa/heatingmode/set"
         | 
| 157 | 
            -
                    next @bwa.toggle_heating_mode if value == 'toggle'
         | 
| 158 | 
            -
                    next unless %w{ready rest}.include?(value)
         | 
| 159 | 
            -
                    @bwa.set_heating_mode(value.to_sym)
         | 
| 160 | 
            -
                  when "spa/temperaturescale/set"
         | 
| 161 | 
            -
                    next unless %w{fahrenheit celsius}.include?(value)
         | 
| 162 | 
            -
                    @bwa.set_temperature_scale(value.to_sym)
         | 
| 163 | 
            -
                  when "spa/24htime/set"
         | 
| 164 | 
            -
                    next unless %w{true false}.include?(value)
         | 
| 165 | 
            -
                    now = Time.now
         | 
| 166 | 
            -
                    @bwa.set_time(now.hour, now.min, value == 'true')
         | 
| 167 | 
            -
                  when "spa/temperaturerange/set"
         | 
| 168 | 
            -
                    next @bwa.toggle_temperature_range if value == 'toggle'
         | 
| 169 | 
            -
                    next unless %w{low high}.include?(value)
         | 
| 170 | 
            -
                    @bwa.set_temperature_range(value.to_sym)
         | 
| 171 | 
            -
                  when %r{^spa/pump([1-6])/set$}
         | 
| 172 | 
            -
                    next @bwa.toggle_pump($1.to_i) if value == 'toggle'
         | 
| 173 | 
            -
                    @bwa.set_pump($1.to_i, value.to_i)
         | 
| 174 | 
            -
                  when %r{^spa/(light|aux)([12])/set$}
         | 
| 175 | 
            -
                    next @bwa.send(:"toggle_#{$1}", $2.to_i) if value == 'toggle'
         | 
| 176 | 
            -
                    next unless %w{true false}.include?(value)
         | 
| 177 | 
            -
                    @bwa.send(:"set_#{$1}", $2.to_i, value == 'true')
         | 
| 178 | 
            -
                  when "spa/mister/set"
         | 
| 179 | 
            -
                    next @bwa.toggle_mister if value == 'toggle'
         | 
| 180 | 
            -
                    next unless %w{true false}.include?(value)
         | 
| 181 | 
            -
                    @bwa.set_mister(value == 'true')
         | 
| 182 | 
            -
                  when "spa/blower/set"
         | 
| 183 | 
            -
                    next @bwa.toggle_blower if value == 'toggle'
         | 
| 184 | 
            -
                    @bwa.set_blower(value.to_i)
         | 
| 185 | 
            -
                  when "spa/settemperature/set"
         | 
| 186 | 
            -
                    @bwa.set_temperature(value.to_f)
         | 
| 187 | 
            -
                  when %r{^spa/(filter[12](hour|minute|durationhours|durationminutes|enabled))/set$}
         | 
| 188 | 
            -
                    @bwa.set_filtercycles($1, value)
         | 
| 189 | 
            -
                  end
         | 
| 190 | 
            -
                end
         | 
| 191 | 
            -
              end
         | 
| 192 | 
            -
             | 
| 193 | 
            -
              def publish(topic, value)
         | 
| 194 | 
            -
                BWA.logger.debug "  to mqtt: #{topic}: #{value}"
         | 
| 195 | 
            -
                @mqtt.publish("#{@base_topic}/#{topic}", value, true)
         | 
| 196 | 
            -
              end
         | 
| 197 | 
            -
             | 
| 198 | 
            -
              def publish_attribute(attr, value)
         | 
| 199 | 
            -
                if !@attributes.key?(attr) || @attributes[attr] != value
         | 
| 200 | 
            -
                  publish(attr, value.to_s)
         | 
| 201 | 
            -
                  @attributes[attr] = value
         | 
| 202 | 
            -
                end
         | 
| 203 | 
            -
              end
         | 
| 204 | 
            -
             | 
| 205 | 
            -
              def subscribe(topic)
         | 
| 206 | 
            -
                @mqtt.subscribe("#{@base_topic}/#{topic}")
         | 
| 207 | 
            -
              end
         | 
| 208 | 
            -
             | 
| 209 | 
            -
              def publish_hass_discovery()
         | 
| 210 | 
            -
                #Priming
         | 
| 211 | 
            -
                priming_config = {
         | 
| 212 | 
            -
                  name: "Hot Tub Priming",
         | 
| 213 | 
            -
                  state_topic: "#{@base_topic}/spa/priming",
         | 
| 214 | 
            -
                  device_class: "running",
         | 
| 215 | 
            -
                  unique_id: "spa_priming",
         | 
| 216 | 
            -
                  icon: "mdi:fast-forward"
         | 
| 217 | 
            -
                }
         | 
| 218 | 
            -
                @mqtt.publish(@ha_binary_path + "priming/config", priming_config
         | 
| 219 | 
            -
                  .merge(@hass_device)
         | 
| 220 | 
            -
                  .merge(@hass_availability)
         | 
| 221 | 
            -
                  .to_json, true)
         | 
| 222 | 
            -
             | 
| 223 | 
            -
                #Circulation Pump
         | 
| 224 | 
            -
                circ_config = {
         | 
| 225 | 
            -
                  name: "Hot Tub Circulation Pump",
         | 
| 226 | 
            -
                  state_topic: "#{@base_topic}/spa/circpump",
         | 
| 227 | 
            -
                  device_class: "running",
         | 
| 228 | 
            -
                  unique_id: "spa_circpump",
         | 
| 229 | 
            -
                  icon: "mdi:sync"
         | 
| 230 | 
            -
                }
         | 
| 231 | 
            -
                @mqtt.publish(@ha_binary_path + "circpump/config", circ_config
         | 
| 232 | 
            -
                  .merge(@hass_device)
         | 
| 233 | 
            -
                  .merge(@hass_availability)
         | 
| 234 | 
            -
                  .to_json, true)
         | 
| 235 | 
            -
             | 
| 236 | 
            -
                # Filter 1 Cycle Running
         | 
| 237 | 
            -
                filter1_config = {
         | 
| 238 | 
            -
                  name: "Hot Tub Filter 1 Cycle Running",
         | 
| 239 | 
            -
                  state_topic: "#{@base_topic}/spa/filter1",
         | 
| 240 | 
            -
                  device_class: "running",
         | 
| 241 | 
            -
                  unique_id: "spa_filtercycle1",
         | 
| 242 | 
            -
                  icon: "mdi:air-filter"
         | 
| 243 | 
            -
                }
         | 
| 244 | 
            -
                @mqtt.publish(@ha_binary_path + "filter1/config", filter1_config
         | 
| 245 | 
            -
                  .merge(@hass_device)
         | 
| 246 | 
            -
                  .merge(@hass_availability)
         | 
| 247 | 
            -
                  .to_json, true)
         | 
| 248 | 
            -
             | 
| 249 | 
            -
                # Filter 2 Cycle Running
         | 
| 250 | 
            -
                filter2_config = {
         | 
| 251 | 
            -
                  name: "Hot Tub Filter 2 Cycle Running",
         | 
| 252 | 
            -
                  state_topic: "#{@base_topic}/spa/filter2",
         | 
| 253 | 
            -
                  device_class: "running",
         | 
| 254 | 
            -
                  unique_id: "spa_filtercycle2",
         | 
| 255 | 
            -
                  icon: "mdi:air-filter"
         | 
| 256 | 
            -
                }
         | 
| 257 | 
            -
                @mqtt.publish(@ha_binary_path + "filter2/config", filter2_config
         | 
| 258 | 
            -
                  .merge(@hass_device)
         | 
| 259 | 
            -
                  .merge(@hass_availability)
         | 
| 260 | 
            -
                  .to_json, true)
         | 
| 261 | 
            -
             | 
| 262 | 
            -
                # Heater Running
         | 
| 263 | 
            -
                heater_config = {
         | 
| 264 | 
            -
                  name: "Hot Tub Heater",
         | 
| 265 | 
            -
                  state_topic: "#{@base_topic}/spa/heating",
         | 
| 266 | 
            -
                  device_class: "running",
         | 
| 267 | 
            -
                  unique_id: "spa_heating",
         | 
| 268 | 
            -
                  icon: "mdi:hot-tub"
         | 
| 269 | 
            -
                }
         | 
| 270 | 
            -
                @mqtt.publish(@ha_binary_path + "heating/config", heater_config
         | 
| 271 | 
            -
                  .merge(@hass_device)
         | 
| 272 | 
            -
                  .merge(@hass_availability)
         | 
| 273 | 
            -
                  .to_json, true)
         | 
| 274 | 
            -
             | 
| 275 | 
            -
                # Heating Mode
         | 
| 276 | 
            -
                heatingmode_config = {
         | 
| 277 | 
            -
                  name: "Hot Tub Heating Mode",
         | 
| 278 | 
            -
                  state_topic: "#{@base_topic}/spa/heatingmode",
         | 
| 279 | 
            -
                  command_topic: "#{@base_topic}/spa/heatingmode/set",
         | 
| 280 | 
            -
                  unique_id: "spa_heating_mode",
         | 
| 281 | 
            -
                  options: ["ready","rest","ready_in_rest"],
         | 
| 282 | 
            -
                  icon: "mdi:cog-play"
         | 
| 283 | 
            -
                }
         | 
| 284 | 
            -
                @mqtt.publish(@ha_selects_path + "heatingmode/config", heatingmode_config
         | 
| 285 | 
            -
                  .merge(@hass_device)
         | 
| 286 | 
            -
                  .merge(@hass_availability)
         | 
| 287 | 
            -
                  .to_json, true)
         | 
| 288 | 
            -
             | 
| 289 | 
            -
                # Temperature Range
         | 
| 290 | 
            -
                temperaturerange_config = {
         | 
| 291 | 
            -
                  name: "Hot Tub Temperature Range",
         | 
| 292 | 
            -
                  state_topic: "#{@base_topic}/spa/temperaturerange",
         | 
| 293 | 
            -
                  command_topic: "#{@base_topic}/spa/temperaturerange/set",
         | 
| 294 | 
            -
                  unique_id: "spa_temperaturerange_range",
         | 
| 295 | 
            -
                  options: ["high","low"],
         | 
| 296 | 
            -
                  icon: "mdi:thermometer-lines"
         | 
| 297 | 
            -
                }
         | 
| 298 | 
            -
                @mqtt.publish(@ha_selects_path + "temperaturerange/config", temperaturerange_config
         | 
| 299 | 
            -
                  .merge(@hass_device)
         | 
| 300 | 
            -
                  .merge(@hass_availability)
         | 
| 301 | 
            -
                  .to_json, true)
         | 
| 302 | 
            -
             | 
| 303 | 
            -
                # Temperature Scale
         | 
| 304 | 
            -
                temperaturescale_config = {
         | 
| 305 | 
            -
                  name: "Hot Tub Temperature Scale",
         | 
| 306 | 
            -
                  state_topic: "#{@base_topic}/spa/temperaturescale",
         | 
| 307 | 
            -
                  unique_id: "spa_temperaturescale"
         | 
| 308 | 
            -
                }
         | 
| 309 | 
            -
                @mqtt.publish(@ha_sensor_path + "temperaturescale/config", temperaturescale_config
         | 
| 310 | 
            -
                  .merge(@hass_device)
         | 
| 311 | 
            -
                  .merge(@hass_availability)
         | 
| 312 | 
            -
                  .to_json, true)
         | 
| 313 | 
            -
             | 
| 314 | 
            -
                # Current Temperature
         | 
| 315 | 
            -
                currenttemperature_config = {
         | 
| 316 | 
            -
                  name: "Hot Tub Current Temperature",
         | 
| 317 | 
            -
                  state_topic: "#{@base_topic}/spa/currenttemperature",
         | 
| 318 | 
            -
                  unique_id: "spa_currenttemperature",
         | 
| 319 | 
            -
                  device_class: "temperature"
         | 
| 320 | 
            -
                }
         | 
| 321 | 
            -
                @mqtt.publish(@ha_sensor_path + "currenttemperature/config", currenttemperature_config
         | 
| 322 | 
            -
                  .merge(@hass_device)
         | 
| 323 | 
            -
                  .merge(@hass_availability)
         | 
| 324 | 
            -
                  .to_json, true)
         | 
| 325 | 
            -
             | 
| 326 | 
            -
                # Set Temperature - Assuming fahrenheit first
         | 
| 327 | 
            -
                settemperature_config = {
         | 
| 328 | 
            -
                  name: "Hot Tub Set Temperature",
         | 
| 329 | 
            -
                  unique_id: "spa_settemperature",
         | 
| 330 | 
            -
                  state_topic: "#{@base_topic}/spa/settemperature",
         | 
| 331 | 
            -
                  command_topic: "#{@base_topic}/spa/settemperature/set",
         | 
| 332 | 
            -
                  min: 50,
         | 
| 333 | 
            -
                  max: 106,
         | 
| 334 | 
            -
                  unit_of_measurement: "°F",
         | 
| 335 | 
            -
                  icon: "mdi:thermometer"
         | 
| 336 | 
            -
                }
         | 
| 337 | 
            -
                @mqtt.publish(@ha_number_path + "settemperature/config", settemperature_config
         | 
| 338 | 
            -
                  .merge(@hass_device)
         | 
| 339 | 
            -
                  .merge(@hass_availability)
         | 
| 340 | 
            -
                  .to_json, true)
         | 
| 341 | 
            -
              end
         | 
| 342 | 
            -
             | 
| 343 | 
            -
              def publish_hass_discovery_settemp(scale)
         | 
| 344 | 
            -
                return if scale == @last_scale
         | 
| 345 | 
            -
                @last_scale = scale
         | 
| 346 | 
            -
             | 
| 347 | 
            -
                # Update temp ranges when scale changes
         | 
| 348 | 
            -
                settemperature_config = {
         | 
| 349 | 
            -
                  name: "Hot Tub Set Temperature",
         | 
| 350 | 
            -
                  unique_id: "spa_settemperature",
         | 
| 351 | 
            -
                  state_topic: "#{@base_topic}/spa/settemperature",
         | 
| 352 | 
            -
                  command_topic: "#{@base_topic}/spa/settemperature/set",
         | 
| 353 | 
            -
                  min: 50,
         | 
| 354 | 
            -
                  max: 106,
         | 
| 355 | 
            -
                  unit_of_measurement: "°F",
         | 
| 356 | 
            -
                  icon: "mdi:thermometer"
         | 
| 357 | 
            -
                }
         | 
| 358 | 
            -
             | 
| 359 | 
            -
                if scale == :celsius
         | 
| 360 | 
            -
                  settemperature_config[:min] = 10
         | 
| 361 | 
            -
                  settemperature_config[:max] = 40
         | 
| 362 | 
            -
                  settemperature_config[:unit_of_measurement] = "°C"
         | 
| 363 | 
            -
                else
         | 
| 364 | 
            -
                  settemperature_config[:min] = 50
         | 
| 365 | 
            -
                  settemperature_config[:max] = 106
         | 
| 366 | 
            -
                  settemperature_config[:unit_of_measurement] = "°F"
         | 
| 367 | 
            -
                end
         | 
| 368 | 
            -
             | 
| 369 | 
            -
                @mqtt.publish(@ha_number_path + "settemperature/config", settemperature_config
         | 
| 370 | 
            -
                  .merge(@hass_device)
         | 
| 371 | 
            -
                  .merge(@hass_availability)
         | 
| 372 | 
            -
                  .to_json, true)
         | 
| 373 | 
            -
              end
         | 
| 374 | 
            -
             | 
| 375 | 
            -
              def publish_hass_discovery_pumps(i, speeds)
         | 
| 376 | 
            -
                pump_config = {
         | 
| 377 | 
            -
                  name: "Hot Tub Pump #{i}",
         | 
| 378 | 
            -
                  unique_id: "spa_pump#{i}",
         | 
| 379 | 
            -
                  state_topic: "#{@base_topic}/spa/pump#{i}",
         | 
| 380 | 
            -
                  command_topic: "#{@base_topic}/spa/pump#{i}/set",
         | 
| 381 | 
            -
                  payload_on: "toggle",
         | 
| 382 | 
            -
                  payload_off: "toggle",
         | 
| 383 | 
            -
                  state_on: "2",
         | 
| 384 | 
            -
                  state_off: "0",
         | 
| 385 | 
            -
                  icon: "mdi:chart-bubble"
         | 
| 386 | 
            -
                }
         | 
| 387 | 
            -
                @mqtt.publish(@ha_switch_path + "pump#{i}/config", pump_config
         | 
| 388 | 
            -
                  .merge(@hass_device)
         | 
| 389 | 
            -
                  .merge(@hass_availability)
         | 
| 390 | 
            -
                  .to_json, true)
         | 
| 391 | 
            -
              end
         | 
| 392 | 
            -
             | 
| 393 | 
            -
              def publish_hass_discovery_things(type, i)
         | 
| 394 | 
            -
                #Things - Lights and such
         | 
| 395 | 
            -
                thing_config = {
         | 
| 396 | 
            -
                  name: "Hot Tub #{type} #{i}",
         | 
| 397 | 
            -
                  unique_id: "spa_#{type}#{i}",
         | 
| 398 | 
            -
                  state_topic: "#{@base_topic}/spa/#{type}#{i}",
         | 
| 399 | 
            -
                  command_topic: "#{@base_topic}/spa/#{type}#{i}/set",
         | 
| 400 | 
            -
                  payload_on: "true",
         | 
| 401 | 
            -
                  payload_off: "false",
         | 
| 402 | 
            -
                  state_on: "true",
         | 
| 403 | 
            -
                  state_off: "false"
         | 
| 404 | 
            -
                }
         | 
| 405 | 
            -
                case type
         | 
| 406 | 
            -
                when :light
         | 
| 407 | 
            -
                  thing_config[:icon] = "mdi:car-parking-lights"
         | 
| 408 | 
            -
                when :mister
         | 
| 409 | 
            -
                  thing_config[:icon] = "mdi:sprinkler-fire"
         | 
| 410 | 
            -
                when :blower
         | 
| 411 | 
            -
                  thing_config[:icon] = "mdi:chart-bubble"
         | 
| 412 | 
            -
                end
         | 
| 413 | 
            -
                @mqtt.publish(@ha_switch_path+ "#{type}#{i}/config", thing_config
         | 
| 414 | 
            -
                  .merge(@hass_device)
         | 
| 415 | 
            -
                  .merge(@hass_availability)
         | 
| 416 | 
            -
                  .to_json, true)
         | 
| 417 | 
            -
              end
         | 
| 418 | 
            -
             | 
| 419 | 
            -
              def publish_basic_attributes
         | 
| 420 | 
            -
                publish("$homie", "4.0.0")
         | 
| 421 | 
            -
                publish("$name", "BWA Spa")
         | 
| 422 | 
            -
                publish("$state", "init")
         | 
| 423 | 
            -
                publish("$nodes", "spa")
         | 
| 424 | 
            -
             | 
| 425 | 
            -
                publish("spa/$name", "BWA Spa")
         | 
| 426 | 
            -
                publish("spa/$type", "spa")
         | 
| 427 | 
            -
                publish_nodes
         | 
| 428 | 
            -
             | 
| 429 | 
            -
                publish("spa/hold/$name", "In Hold mode")
         | 
| 430 | 
            -
                publish("spa/hold/$datatype", "boolean")
         | 
| 431 | 
            -
                publish("spa/hold/$settable", "true")
         | 
| 432 | 
            -
                subscribe("spa/hold/set")
         | 
| 433 | 
            -
             | 
| 434 | 
            -
                publish("spa/priming/$name", "Is the pump priming")
         | 
| 435 | 
            -
                publish("spa/priming/$datatype", "boolean")
         | 
| 436 | 
            -
             | 
| 437 | 
            -
                publish("spa/heatingmode/$name", "Current heating mode")
         | 
| 438 | 
            -
                publish("spa/heatingmode/$datatype", "enum")
         | 
| 439 | 
            -
                publish("spa/heatingmode/$format", "ready,rest,ready_in_rest")
         | 
| 440 | 
            -
                publish("spa/heatingmode/$settable", "true")
         | 
| 441 | 
            -
                subscribe("spa/heatingmode/set")
         | 
| 442 | 
            -
             | 
| 443 | 
            -
                publish("spa/temperaturescale/$name", "Temperature scale")
         | 
| 444 | 
            -
                publish("spa/temperaturescale/$datatype", "enum")
         | 
| 445 | 
            -
                publish("spa/temperaturescale/$format", "fahrenheit,celsius")
         | 
| 446 | 
            -
                publish("spa/temperaturescale/$settable", "true")
         | 
| 447 | 
            -
                subscribe("spa/temperaturescale/set")
         | 
| 448 | 
            -
             | 
| 449 | 
            -
                publish("spa/24htime/$name", "Clock is 24 hour time")
         | 
| 450 | 
            -
                publish("spa/24htime/$datatype", "boolean")
         | 
| 451 | 
            -
                publish("spa/24htime/$settable", "true")
         | 
| 452 | 
            -
                subscribe("spa/24htime/set")
         | 
| 453 | 
            -
             | 
| 454 | 
            -
                publish("spa/heating/$name", "Heater is currently running")
         | 
| 455 | 
            -
                publish("spa/heating/$datatype", "boolean")
         | 
| 456 | 
            -
             | 
| 457 | 
            -
                publish("spa/temperaturerange/$name", "Current temperature range")
         | 
| 458 | 
            -
                publish("spa/temperaturerange/$datatype", "enum")
         | 
| 459 | 
            -
                publish("spa/temperaturerange/$format", "high,low")
         | 
| 460 | 
            -
                publish("spa/temperaturerange/$settable", "true")
         | 
| 461 | 
            -
                subscribe("spa/temperaturerange/set")
         | 
| 462 | 
            -
             | 
| 463 | 
            -
                publish("spa/currenttemperature/$name", "Current temperature")
         | 
| 464 | 
            -
                publish("spa/currenttemperature/$datatype", "float")
         | 
| 465 | 
            -
             | 
| 466 | 
            -
                publish("spa/settemperature/$name", "Set Temperature")
         | 
| 467 | 
            -
                publish("spa/settemperature/$datatype", "float")
         | 
| 468 | 
            -
                publish("spa/settemperature/$settable", "true")
         | 
| 469 | 
            -
                subscribe("spa/settemperature/set")
         | 
| 470 | 
            -
             | 
| 471 | 
            -
                publish("spa/filter1/$name", "Filter cycle 1 is currently running")
         | 
| 472 | 
            -
                publish("spa/filter1/$datatype", "boolean")
         | 
| 473 | 
            -
             | 
| 474 | 
            -
                publish("spa/filter2/$name", "Filter cycle 2 is currently running")
         | 
| 475 | 
            -
                publish("spa/filter2/$datatype", "boolean")
         | 
| 476 | 
            -
              end
         | 
| 477 | 
            -
             | 
| 478 | 
            -
              def publish_pump(i, speeds)
         | 
| 479 | 
            -
                publish("spa/pump#{i}/$name", "Pump #{i} speed")
         | 
| 480 | 
            -
                publish("spa/pump#{i}/$datatype", "integer")
         | 
| 481 | 
            -
                publish("spa/pump#{i}/$format", "0:#{speeds}")
         | 
| 482 | 
            -
                publish("spa/pump#{i}/$settable", "true")
         | 
| 483 | 
            -
                subscribe("spa/pump#{i}/set")
         | 
| 484 | 
            -
             | 
| 485 | 
            -
                @things << "pump#{i}"
         | 
| 486 | 
            -
                publish_hass_discovery_pumps(i, speeds)
         | 
| 487 | 
            -
                publish_nodes
         | 
| 488 | 
            -
              end
         | 
| 489 | 
            -
             | 
| 490 | 
            -
              def publish_thing(type, i)
         | 
| 491 | 
            -
                publish("spa/#{type}#{i}/$name", "#{type} #{i}")
         | 
| 492 | 
            -
                publish("spa/#{type}#{i}/$datatype", "boolean")
         | 
| 493 | 
            -
                publish("spa/#{type}#{i}/$settable", "true")
         | 
| 494 | 
            -
                subscribe("spa/#{type}#{i}/set")
         | 
| 495 | 
            -
             | 
| 496 | 
            -
                @things << "#{type}#{i}"
         | 
| 497 | 
            -
                publish_hass_discovery_things(type, i)
         | 
| 498 | 
            -
                publish_nodes
         | 
| 499 | 
            -
              end
         | 
| 500 | 
            -
             | 
| 501 | 
            -
              def publish_mister
         | 
| 502 | 
            -
                publish("spa/mister/$name", type)
         | 
| 503 | 
            -
                publish("spa/mister/$datatype", "boolean")
         | 
| 504 | 
            -
                publish("spa/mister/$settable", "true")
         | 
| 505 | 
            -
                subscribe("spa/mister/set")
         | 
| 506 | 
            -
             | 
| 507 | 
            -
                @things << "mister"
         | 
| 508 | 
            -
                publish_nodes
         | 
| 509 | 
            -
              end
         | 
| 510 | 
            -
             | 
| 511 | 
            -
              def publish_blower(speeds)
         | 
| 512 | 
            -
                publish("spa/blower/$name", "Blower speed")
         | 
| 513 | 
            -
                publish("spa/blower/$datatype", "integer")
         | 
| 514 | 
            -
                publish("spa/blower/$format", "0:#{speeds}")
         | 
| 515 | 
            -
                publish("spa/blower/$settable", "true")
         | 
| 516 | 
            -
                subscribe("spa/blower/set")
         | 
| 517 | 
            -
             | 
| 518 | 
            -
                @things << "blower"
         | 
| 519 | 
            -
                publish_nodes
         | 
| 520 | 
            -
              end
         | 
| 521 | 
            -
             | 
| 522 | 
            -
              def publish_circpump
         | 
| 523 | 
            -
                publish("spa/circpump/$name", "Circ pump is currently running")
         | 
| 524 | 
            -
                publish("spa/circpump/$datatype", "boolean")
         | 
| 525 | 
            -
                @things << "circpump"
         | 
| 526 | 
            -
             | 
| 527 | 
            -
                publish_nodes
         | 
| 528 | 
            -
              end
         | 
| 529 | 
            -
             | 
| 530 | 
            -
              def publish_filtercycles
         | 
| 531 | 
            -
                publish("spa/filter1hour/$name", "Filter Cycle 1 Start Hour")
         | 
| 532 | 
            -
                publish("spa/filter1hour/$datatype", "integer")
         | 
| 533 | 
            -
                publish("spa/filter1hour/$format", "0:24")
         | 
| 534 | 
            -
                publish("spa/filter1hour/$settable", "true")
         | 
| 535 | 
            -
             | 
| 536 | 
            -
                publish("spa/filter1minute/$name", "Filter Cycle 1 Start Minutes")
         | 
| 537 | 
            -
                publish("spa/filter1minute/$datatype", "integer")
         | 
| 538 | 
            -
                publish("spa/filter1minute/$format", "0:59")
         | 
| 539 | 
            -
                publish("spa/filter1minute/$settable", "true")
         | 
| 540 | 
            -
             | 
| 541 | 
            -
                publish("spa/filter1durationhours/$name", "Filter Cycle 1 Duration Hours")
         | 
| 542 | 
            -
                publish("spa/filter1durationhours/$datatype", "integer")
         | 
| 543 | 
            -
                publish("spa/filter1durationhours/$format", "0:24")
         | 
| 544 | 
            -
                publish("spa/filter1durationhours/$settable", "true")
         | 
| 545 | 
            -
             | 
| 546 | 
            -
                publish("spa/filter1durationminutes/$name", "Filter Cycle 1 Duration Minutes")
         | 
| 547 | 
            -
                publish("spa/filter1durationminutes/$datatype", "integer")
         | 
| 548 | 
            -
                publish("spa/filter1durationminutes/$format", "0:59")
         | 
| 549 | 
            -
                publish("spa/filter1durationminutes/$settable", "true")
         | 
| 550 | 
            -
             | 
| 551 | 
            -
                publish("spa/filter2enabled/$name", "Filter Cycle 2 Enabled")
         | 
| 552 | 
            -
                publish("spa/filter2enabled/$datatype", "boolean")
         | 
| 553 | 
            -
                publish("spa/filter2enabled/$settable", "true")
         | 
| 554 | 
            -
             | 
| 555 | 
            -
                publish("spa/filter2hour/$name", "Filter Cycle 2 Start Hour")
         | 
| 556 | 
            -
                publish("spa/filter2hour/$datatype", "integer")
         | 
| 557 | 
            -
                publish("spa/filter2hour/$format", "0:24")
         | 
| 558 | 
            -
                publish("spa/filter2hour/$settable", "true")
         | 
| 559 | 
            -
             | 
| 560 | 
            -
                publish("spa/filter2minute/$name", "Filter Cycle 2 Start Minutes")
         | 
| 561 | 
            -
                publish("spa/filter2minute/$datatype", "integer")
         | 
| 562 | 
            -
                publish("spa/filter2minute/$format", "0:59")
         | 
| 563 | 
            -
                publish("spa/filter2minute/$settable", "true")
         | 
| 564 | 
            -
             | 
| 565 | 
            -
                publish("spa/filter2durationhours/$name", "Filter Cycle 2 Duration Hours")
         | 
| 566 | 
            -
                publish("spa/filter2durationhours/$datatype", "integer")
         | 
| 567 | 
            -
                publish("spa/filter2durationhours/$format", "0:24")
         | 
| 568 | 
            -
                publish("spa/filter2durationhours/$settable", "true")
         | 
| 569 | 
            -
             | 
| 570 | 
            -
                publish("spa/filter2durationminutes/$name", "Filter Cycle 2 Duration Minutes")
         | 
| 571 | 
            -
                publish("spa/filter2durationminutes/$datatype", "integer")
         | 
| 572 | 
            -
                publish("spa/filter2durationminutes/$format", "0:59")
         | 
| 573 | 
            -
                publish("spa/filter2durationminutes/$settable", "true")
         | 
| 574 | 
            -
             | 
| 575 | 
            -
                subscribe("spa/filter1hour/set")
         | 
| 576 | 
            -
                subscribe("spa/filter1minute/set")
         | 
| 577 | 
            -
                subscribe("spa/filter1durationhours/set")
         | 
| 578 | 
            -
                subscribe("spa/filter1durationminutes/set")
         | 
| 579 | 
            -
                subscribe("spa/filter2enabled/set")
         | 
| 580 | 
            -
                subscribe("spa/filter2hour/set")
         | 
| 581 | 
            -
                subscribe("spa/filter2minute/set")
         | 
| 582 | 
            -
                subscribe("spa/filter2durationhours/set")
         | 
| 583 | 
            -
                subscribe("spa/filter2durationminutes/set")
         | 
| 584 | 
            -
             | 
| 585 | 
            -
                @things.merge(["filter1hour,filter1minute,filter1durationhours,filter1durationminutes,filter2enabled,filter2hour,filter2minute,filter2durationhours,filter2durationminutes"])
         | 
| 586 | 
            -
             | 
| 587 | 
            -
                publish_nodes
         | 
| 588 | 
            -
              end
         | 
| 589 | 
            -
             | 
| 590 | 
            -
              def publish_nodes
         | 
| 591 | 
            -
                publish("spa/$properties", (["hold,priming,heatingmode,temperaturescale,24htime,heating,temperaturerange,currenttemperature,settemperature,filter1,filter2"] + @things.to_a).join(','))
         | 
| 592 | 
            -
              end
         | 
| 593 | 
            -
            end
         | 
| 594 | 
            -
             | 
| 595 | 
            -
            mqtt_uri = ARGV.shift
         | 
| 596 | 
            -
             | 
| 597 | 
            -
            if ARGV.empty?
         | 
| 598 | 
            -
              spas = BWA::Discovery.discover
         | 
| 599 | 
            -
              if spas.empty?
         | 
| 600 | 
            -
                BWA.logger.fatal "Could not find spa!"
         | 
| 601 | 
            -
                $stderr.puts "Could not find spa!"
         | 
| 602 | 
            -
                exit 1
         | 
| 603 | 
            -
              end
         | 
| 604 | 
            -
              spa_ip = "tcp://#{spas.first.first}/"
         | 
| 605 | 
            -
            else
         | 
| 606 | 
            -
              spa_ip = ARGV[0]
         | 
| 607 | 
            -
            end
         | 
| 608 | 
            -
             | 
| 609 | 
            -
            spa = BWA::Client.new(spa_ip)
         | 
| 610 | 
            -
             | 
| 611 | 
            -
            spa.request_configuration
         | 
| 612 | 
            -
            spa.request_filter_configuration
         | 
| 613 | 
            -
             | 
| 614 | 
            -
            MQTTBridge.new(mqtt_uri, spa)
         |