balboa_worldwide_app 1.2.5 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/bwa_client +1 -1
- data/bin/bwa_mqtt_bridge +339 -8
- data/lib/bwa/client.rb +62 -21
- data/lib/bwa/discovery.rb +2 -1
- data/lib/bwa/logger.rb +55 -0
- data/lib/bwa/message.rb +38 -8
- data/lib/bwa/messages/configuration.rb +4 -0
- data/lib/bwa/messages/control_configuration.rb +1 -0
- data/lib/bwa/messages/control_configuration_request.rb +19 -1
- data/lib/bwa/messages/ready.rb +4 -0
- data/lib/bwa/messages/set_temperature.rb +1 -0
- data/lib/bwa/messages/set_temperature_scale.rb +1 -0
- data/lib/bwa/messages/set_time.rb +1 -0
- data/lib/bwa/messages/status.rb +9 -2
- data/lib/bwa/messages/toggle_item.rb +17 -7
- data/lib/bwa/proxy.rb +3 -2
- data/lib/bwa/server.rb +7 -6
- data/lib/bwa/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f1f61d30556c0536efad6d27c229cefc0bccd1293a4dc491230a3e68cfddca13
|
4
|
+
data.tar.gz: 3014c3c575a53cceaa2f9c2903b1977480bffa5cc0db5e44f08bce8fc8671e17
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3ff3043cbbdefd778ee2fbea74ce46ad8c73860f8e541c9035911219b6d6048203f817a2f129bac16678334d4f02cddea806814357d13710c4c76fc3e1987b5e
|
7
|
+
data.tar.gz: bb1d5468380c06c7bc38c34cf984cd9c4550fb3a3f1314f1b85617df0cff003ece26ce4603746f59f0e00950ae8244aaf0f477149f3b693ab67b8f6ff30f8602
|
data/bin/bwa_client
CHANGED
data/bin/bwa_mqtt_bridge
CHANGED
@@ -3,9 +3,12 @@
|
|
3
3
|
require 'mqtt'
|
4
4
|
require 'sd_notify'
|
5
5
|
require 'set'
|
6
|
+
require 'json'
|
6
7
|
|
8
|
+
require 'bwa/logger'
|
7
9
|
require 'bwa/client'
|
8
10
|
require 'bwa/discovery'
|
11
|
+
require 'bwa/version'
|
9
12
|
|
10
13
|
class MQTTBridge
|
11
14
|
def initialize(mqtt_uri, bwa, device_id: "bwa", base_topic: "homie")
|
@@ -17,9 +20,36 @@ class MQTTBridge
|
|
17
20
|
@attributes = {}
|
18
21
|
@things = Set.new
|
19
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
|
+
|
20
45
|
publish_basic_attributes
|
46
|
+
publish_filtercycles
|
47
|
+
|
48
|
+
# Home Assistant MQTT Discovery Section
|
49
|
+
publish_hass_discovery
|
21
50
|
|
22
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})"
|
23
53
|
SdNotify.ready
|
24
54
|
|
25
55
|
bwa_thread = Thread.new do
|
@@ -28,7 +58,6 @@ class MQTTBridge
|
|
28
58
|
message = @bwa.poll
|
29
59
|
next if message.is_a?(BWA::Messages::Ready)
|
30
60
|
|
31
|
-
puts message.inspect unless message.is_a?(BWA::Messages::Status)
|
32
61
|
case message
|
33
62
|
when BWA::Messages::ControlConfiguration
|
34
63
|
publish("spa/$type", message.model)
|
@@ -37,18 +66,29 @@ class MQTTBridge
|
|
37
66
|
publish_pump(i + 1, speed) if speed != 0
|
38
67
|
end
|
39
68
|
message.lights.each_with_index do |exists, i|
|
40
|
-
publish_thing(
|
69
|
+
publish_thing(:light, i + 1) if exists
|
41
70
|
end
|
42
71
|
message.aux.each_with_index do |exists, i|
|
43
|
-
publish_thing(
|
72
|
+
publish_thing(:aux, i + 1) if exists
|
44
73
|
end
|
45
74
|
publish_mister if message.mister
|
46
75
|
publish_blower(message.blower) if message.blower != 0
|
47
76
|
publish_circpump if message.circ_pump
|
48
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)
|
49
88
|
when BWA::Messages::Status
|
50
89
|
@bwa.request_control_info unless @bwa.last_control_configuration
|
51
90
|
@bwa.request_control_info2 unless @bwa.last_control_configuration2
|
91
|
+
@bwa.request_filter_configuration unless @bwa.last_filter_configuration
|
52
92
|
|
53
93
|
# make sure time is in sync
|
54
94
|
now = Time.now
|
@@ -59,8 +99,10 @@ class MQTTBridge
|
|
59
99
|
|
60
100
|
# allow a skew of 1 minute, since the seconds will always be off
|
61
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"
|
62
103
|
@bwa.set_time(now.hour, now.min, message.twenty_four_hour_time)
|
63
104
|
end
|
105
|
+
publish_attribute("spa/hold", message.hold)
|
64
106
|
publish_attribute("spa/priming", message.priming)
|
65
107
|
publish_attribute("spa/heatingmode", message.heating_mode)
|
66
108
|
publish_attribute("spa/temperaturescale", message.temperature_scale)
|
@@ -72,11 +114,13 @@ class MQTTBridge
|
|
72
114
|
publish_attribute("spa/settemperature", message.set_temperature)
|
73
115
|
publish_attribute("spa/settemperature/$unit", "º#{message.temperature_scale.to_s[0].upcase}")
|
74
116
|
if message.temperature_scale == :celsius
|
75
|
-
publish_attribute("spa/currenttemperature/$format",
|
117
|
+
publish_attribute("spa/currenttemperature/$format", "0:42")
|
76
118
|
publish_attribute("spa/settemperature/$format", message.temperature_range == :high ? "26:40" : "10:26")
|
119
|
+
publish_hass_discovery_settemp(:celsius)
|
77
120
|
else
|
78
|
-
publish_attribute("spa/currenttemperature/$format",
|
79
|
-
publish_attribute("spa/settemperature/$format", message.temperature_range == :high ? "80:
|
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)
|
80
124
|
end
|
81
125
|
publish_attribute("spa/filter1", message.filter[0])
|
82
126
|
publish_attribute("spa/filter2", message.filter[1])
|
@@ -96,14 +140,19 @@ class MQTTBridge
|
|
96
140
|
|
97
141
|
# Tell systemd we are still alive and kicking. Ignored if systemd not in use.
|
98
142
|
SdNotify.watchdog
|
143
|
+
|
99
144
|
end
|
100
145
|
end
|
101
146
|
end
|
102
147
|
end
|
103
148
|
|
104
149
|
@mqtt.get do |topic, value|
|
105
|
-
|
150
|
+
BWA.logger.warn "from mqtt: #{value.inspect} at #{topic}"
|
106
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')
|
107
156
|
when "spa/heatingmode/set"
|
108
157
|
next @bwa.toggle_heating_mode if value == 'toggle'
|
109
158
|
next unless %w{ready rest}.include?(value)
|
@@ -135,11 +184,14 @@ class MQTTBridge
|
|
135
184
|
@bwa.set_blower(value.to_i)
|
136
185
|
when "spa/settemperature/set"
|
137
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)
|
138
189
|
end
|
139
190
|
end
|
140
191
|
end
|
141
192
|
|
142
193
|
def publish(topic, value)
|
194
|
+
BWA.logger.debug " to mqtt: #{topic}: #{value}"
|
143
195
|
@mqtt.publish("#{@base_topic}/#{topic}", value, true)
|
144
196
|
end
|
145
197
|
|
@@ -154,6 +206,216 @@ class MQTTBridge
|
|
154
206
|
@mqtt.subscribe("#{@base_topic}/#{topic}")
|
155
207
|
end
|
156
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
|
+
|
157
419
|
def publish_basic_attributes
|
158
420
|
publish("$homie", "4.0.0")
|
159
421
|
publish("$name", "BWA Spa")
|
@@ -164,6 +426,11 @@ class MQTTBridge
|
|
164
426
|
publish("spa/$type", "spa")
|
165
427
|
publish_nodes
|
166
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
|
+
|
167
434
|
publish("spa/priming/$name", "Is the pump priming")
|
168
435
|
publish("spa/priming/$datatype", "boolean")
|
169
436
|
|
@@ -216,6 +483,7 @@ class MQTTBridge
|
|
216
483
|
subscribe("spa/pump#{i}/set")
|
217
484
|
|
218
485
|
@things << "pump#{i}"
|
486
|
+
publish_hass_discovery_pumps(i, speeds)
|
219
487
|
publish_nodes
|
220
488
|
end
|
221
489
|
|
@@ -226,6 +494,7 @@ class MQTTBridge
|
|
226
494
|
subscribe("spa/#{type}#{i}/set")
|
227
495
|
|
228
496
|
@things << "#{type}#{i}"
|
497
|
+
publish_hass_discovery_things(type, i)
|
229
498
|
publish_nodes
|
230
499
|
end
|
231
500
|
|
@@ -258,8 +527,68 @@ class MQTTBridge
|
|
258
527
|
publish_nodes
|
259
528
|
end
|
260
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
|
+
|
261
590
|
def publish_nodes
|
262
|
-
publish("spa/$properties", (["priming,heatingmode,temperaturescale,24htime,heating,temperaturerange,currenttemperature,settemperature,filter1,filter2"] + @things.to_a).join(','))
|
591
|
+
publish("spa/$properties", (["hold,priming,heatingmode,temperaturescale,24htime,heating,temperaturerange,currenttemperature,settemperature,filter1,filter2"] + @things.to_a).join(','))
|
263
592
|
end
|
264
593
|
end
|
265
594
|
|
@@ -268,6 +597,7 @@ mqtt_uri = ARGV.shift
|
|
268
597
|
if ARGV.empty?
|
269
598
|
spas = BWA::Discovery.discover
|
270
599
|
if spas.empty?
|
600
|
+
BWA.logger.fatal "Could not find spa!"
|
271
601
|
$stderr.puts "Could not find spa!"
|
272
602
|
exit 1
|
273
603
|
end
|
@@ -279,5 +609,6 @@ end
|
|
279
609
|
spa = BWA::Client.new(spa_ip)
|
280
610
|
|
281
611
|
spa.request_configuration
|
612
|
+
spa.request_filter_configuration
|
282
613
|
|
283
614
|
MQTTBridge.new(mqtt_uri, spa)
|
data/lib/bwa/client.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'uri'
|
2
2
|
|
3
|
+
require 'bwa/logger'
|
3
4
|
require 'bwa/message'
|
4
5
|
|
5
6
|
module BWA
|
@@ -10,7 +11,7 @@ module BWA
|
|
10
11
|
uri = URI.parse(uri)
|
11
12
|
if uri.scheme == 'tcp'
|
12
13
|
require 'socket'
|
13
|
-
@io = TCPSocket.new(uri.host, uri.port ||
|
14
|
+
@io = TCPSocket.new(uri.host, uri.port || 4257)
|
14
15
|
elsif uri.scheme == 'telnet' || uri.scheme == 'rfc2217'
|
15
16
|
require 'net/telnet/rfc2217'
|
16
17
|
@io = Net::Telnet::RFC2217.new("Host" => uri.host, "Port" => uri.port || 23, "baud" => 115200)
|
@@ -20,6 +21,7 @@ module BWA
|
|
20
21
|
@io = CCutrer::SerialPort.new(uri.path, baud: 115200)
|
21
22
|
@queue = []
|
22
23
|
end
|
24
|
+
@src = 0x0a
|
23
25
|
@buffer = ""
|
24
26
|
end
|
25
27
|
|
@@ -43,7 +45,7 @@ module BWA
|
|
43
45
|
end
|
44
46
|
|
45
47
|
if message.is_a?(Messages::Ready) && (msg = @queue&.shift)
|
46
|
-
|
48
|
+
BWA.logger.debug "wrote: #{BWA.raw2str(msg)}" unless BWA.verbosity < 1 && msg[3..4] == Messages::ControlConfigurationRequest::MESSAGE_TYPE
|
47
49
|
@io.write(msg)
|
48
50
|
end
|
49
51
|
@last_status = message.dup if message.is_a?(Messages::Status)
|
@@ -62,35 +64,35 @@ module BWA
|
|
62
64
|
end
|
63
65
|
|
64
66
|
def send_message(message)
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
full_message = "\x7e#{full_message}#{checksum.chr}\x7e".force_encoding(Encoding::ASCII_8BIT)
|
67
|
+
message.src = @src
|
68
|
+
BWA.logger.info " to spa: #{message.inspect}" unless BWA.verbosity < 1 && message.is_a?(Messages::ControlConfigurationRequest)
|
69
|
+
full_message = message.serialize
|
69
70
|
if @queue
|
70
71
|
@queue.push(full_message)
|
71
72
|
else
|
73
|
+
BWA.logger.debug "wrote: #{BWA.raw2str(full_message)}" unless BWA.verbosity < 1 && message.is_a?(Messages::ControlConfigurationRequest)
|
72
74
|
@io.write(full_message)
|
73
75
|
end
|
74
76
|
end
|
75
77
|
|
76
78
|
def request_configuration
|
77
|
-
send_message(
|
79
|
+
send_message(Messages::ConfigurationRequest.new)
|
78
80
|
end
|
79
81
|
|
80
82
|
def request_control_info2
|
81
|
-
send_message(
|
83
|
+
send_message(Messages::ControlConfigurationRequest.new(2))
|
82
84
|
end
|
83
85
|
|
84
86
|
def request_control_info
|
85
|
-
send_message(
|
87
|
+
send_message(Messages::ControlConfigurationRequest.new(1))
|
86
88
|
end
|
87
89
|
|
88
90
|
def request_filter_configuration
|
89
|
-
send_message(
|
91
|
+
send_message(Messages::ControlConfigurationRequest.new(3))
|
90
92
|
end
|
91
93
|
|
92
94
|
def toggle_item(item)
|
93
|
-
send_message(
|
95
|
+
send_message(Messages::ToggleItem.new(item))
|
94
96
|
end
|
95
97
|
|
96
98
|
def toggle_pump(i)
|
@@ -102,11 +104,15 @@ module BWA
|
|
102
104
|
end
|
103
105
|
|
104
106
|
def toggle_mister
|
105
|
-
toggle_item(
|
107
|
+
toggle_item(:mister)
|
106
108
|
end
|
107
109
|
|
108
110
|
def toggle_blower
|
109
|
-
toggle_item(
|
111
|
+
toggle_item(:blower)
|
112
|
+
end
|
113
|
+
|
114
|
+
def toggle_hold
|
115
|
+
toggle_item(:hold)
|
110
116
|
end
|
111
117
|
|
112
118
|
def set_pump(i, desired)
|
@@ -143,22 +149,57 @@ module BWA
|
|
143
149
|
end
|
144
150
|
end
|
145
151
|
|
146
|
-
|
147
|
-
|
152
|
+
def set_hold(desired)
|
153
|
+
return unless last_status
|
154
|
+
return if last_status.hold == desired
|
155
|
+
toggle_hold
|
156
|
+
end
|
157
|
+
|
158
|
+
# high range is 80-106 for F, 26-40 for C (by 0.5)
|
159
|
+
# low range is 50-99 for F, 10-26 for C (by 0.5)
|
148
160
|
def set_temperature(desired)
|
161
|
+
return unless last_status
|
162
|
+
return if last_status.set_temperature == desired
|
163
|
+
|
149
164
|
desired *= 2 if last_status && last_status.temperature_scale == :celsius || desired < 50
|
150
|
-
send_message(
|
165
|
+
send_message(Messages::SetTemperature.new(desired.round))
|
151
166
|
end
|
152
167
|
|
153
168
|
def set_time(hour, minute, twenty_four_hour_time = false)
|
154
|
-
hour
|
155
|
-
send_message("\x0a\xbf\x21".force_encoding(Encoding::ASCII_8BIT) + hour.chr + minute.chr)
|
169
|
+
send_message(Messages::SetTime.new(hour, minute, twenty_four_hour_time))
|
156
170
|
end
|
157
171
|
|
158
172
|
def set_temperature_scale(scale)
|
159
173
|
raise ArgumentError, "scale must be :fahrenheit or :celsius" unless %I{fahrenheit :celsius}.include?(scale)
|
160
|
-
|
161
|
-
|
174
|
+
send_message(Messages::SetTemperatureScale.new(scale))
|
175
|
+
end
|
176
|
+
|
177
|
+
def set_filtercycles(changedItem, changedValue)
|
178
|
+
#changedItem - String name of item that was changed
|
179
|
+
#changedValue - String value of the item that changed
|
180
|
+
if @last_filter_configuration
|
181
|
+
messagedata = if changedItem == "filter1hour" then changedValue.to_i.chr else @last_filter_configuration.filter1_hour.chr end
|
182
|
+
messagedata += if changedItem == "filter1minute" then changedValue.to_i.chr else @last_filter_configuration.filter1_minute.chr end
|
183
|
+
messagedata += if changedItem == "filter1durationhours" then changedValue.to_i.chr else @last_filter_configuration.filter1_duration_hours.chr end
|
184
|
+
messagedata += if changedItem == "filter1durationminutes" then changedValue.to_i.chr else @last_filter_configuration.filter1_duration_minutes.chr end
|
185
|
+
|
186
|
+
#The filter2 start hour is merged with the filter2 enable (who thought that was a good idea?) The high order bit of the byte is a flag
|
187
|
+
#to indicate this so we have to do a bit of different processing to do that
|
188
|
+
#Get the filter 2 start hour
|
189
|
+
starthour = if changedItem == "filter2hour" then changedValue.to_i else @last_filter_configuration.filter2_hour end
|
190
|
+
#Check to see if we want filter 2 enabled (either because it changed or from the current configuration)
|
191
|
+
#If it is something that changed, we have to convert to boolean, if it is from the current config it already is a boolean
|
192
|
+
starthour |= 0x80 if (if changedItem == "filter2enabled" then (changedValue == "true" ? true : false) else @last_filter_configuration.filter2_enabled end)
|
193
|
+
|
194
|
+
messagedata += starthour.chr
|
195
|
+
|
196
|
+
messagedata += if changedItem == "filter2minute" then changedValue.to_i.chr else @last_filter_configuration.filter2_minute.chr end
|
197
|
+
messagedata += if changedItem == "filter2durationhours" then changedValue.to_i.chr else @last_filter_configuration.filter2_duration_hours.chr end
|
198
|
+
messagedata += if changedItem == "filter2durationminutes" then changedValue.to_i.chr else @last_filter_configuration.filter2_duration_minutes.chr end
|
199
|
+
|
200
|
+
send_message("\x0a\xbf\x23".force_encoding(Encoding::ASCII_8BIT) + messagedata)
|
201
|
+
end
|
202
|
+
request_filter_configuration
|
162
203
|
end
|
163
204
|
|
164
205
|
def toggle_temperature_range
|
@@ -172,7 +213,7 @@ module BWA
|
|
172
213
|
end
|
173
214
|
|
174
215
|
def toggle_heating_mode
|
175
|
-
toggle_item(
|
216
|
+
toggle_item(:heating_mode)
|
176
217
|
end
|
177
218
|
|
178
219
|
HEATING_MODES = %I{ready rest ready_in_rest}.freeze
|
data/lib/bwa/discovery.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'socket'
|
2
|
+
require 'bwa/logger'
|
2
3
|
|
3
4
|
module BWA
|
4
5
|
class Discovery
|
@@ -34,7 +35,7 @@ module BWA
|
|
34
35
|
data, addr = socket.recvfrom(32)
|
35
36
|
next unless data == 'Discovery: Who is out there?'
|
36
37
|
ip = addr.last
|
37
|
-
|
38
|
+
BWA.logger.info "Advertising to #{ip}"
|
38
39
|
socket.sendmsg(msg, 0, Socket.sockaddr_in(addr[1], ip))
|
39
40
|
end
|
40
41
|
end
|
data/lib/bwa/logger.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module BWA
|
4
|
+
# This module logs to stdout by default, or you can provide a logger as BWA.logger.
|
5
|
+
# If using default logger, set LOG_LEVEL in the environment to control logging.
|
6
|
+
#
|
7
|
+
# Log levels are:
|
8
|
+
#
|
9
|
+
# FATAL - fatal errors
|
10
|
+
# ERROR - handled errors
|
11
|
+
# WARN - problems while parsing known messages
|
12
|
+
# INFO - unrecognized messages
|
13
|
+
# DEBUG - all messages
|
14
|
+
#
|
15
|
+
# Certain very frequent messages are suppressed by default even in DEBUG mode.
|
16
|
+
# Set LOG_VERBOSITY to one of the following levels to see these:
|
17
|
+
#
|
18
|
+
# 0 - default
|
19
|
+
# 1 - show status messages
|
20
|
+
# 2 - show ready and nothing-to-send messages
|
21
|
+
#
|
22
|
+
class << self
|
23
|
+
attr_writer :logger, :verbosity
|
24
|
+
|
25
|
+
def logger
|
26
|
+
@logger ||= Logger.new(STDOUT).tap do |log|
|
27
|
+
STDOUT.sync = true
|
28
|
+
log.level = ENV.fetch("LOG_LEVEL","WARN")
|
29
|
+
log.formatter = proc do |severity, datetime, progname, msg|
|
30
|
+
"#{severity[0..0]}, #{msg2logstr(msg)}\n"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def verbosity
|
36
|
+
@verbosity ||= ENV.fetch("LOG_VERBOSITY", "0").to_i
|
37
|
+
@verbosity
|
38
|
+
end
|
39
|
+
|
40
|
+
def msg2logstr(msg)
|
41
|
+
case msg
|
42
|
+
when ::String
|
43
|
+
msg
|
44
|
+
when ::Exception
|
45
|
+
"#{ msg.message } (#{ msg.class })\n#{ msg.backtrace.join("\n") if msg.backtrace }"
|
46
|
+
else
|
47
|
+
msg.inspect
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def raw2str(data)
|
52
|
+
data.unpack("H*").first.gsub!(/(..)/, "\\1 ").chop!
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/lib/bwa/message.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'bwa/logger'
|
1
2
|
require 'bwa/crc'
|
2
3
|
|
3
4
|
module BWA
|
@@ -11,6 +12,8 @@ module BWA
|
|
11
12
|
end
|
12
13
|
|
13
14
|
class Message
|
15
|
+
attr_accessor :src
|
16
|
+
|
14
17
|
class Unrecognized < Message
|
15
18
|
end
|
16
19
|
|
@@ -20,11 +23,39 @@ module BWA
|
|
20
23
|
@messages << klass
|
21
24
|
end
|
22
25
|
|
26
|
+
# Ignore (parse and throw away) messages of these types.
|
27
|
+
IGNORED_MESSAGES = [
|
28
|
+
"\xbf\x00".force_encoding(Encoding::ASCII_8BIT), # request for new clients
|
29
|
+
"\xbf\xe1".force_encoding(Encoding::ASCII_8BIT),
|
30
|
+
"\xbf\x07".force_encoding(Encoding::ASCII_8BIT), # nothing to send
|
31
|
+
]
|
32
|
+
|
33
|
+
# Don't log messages of these types, even in DEBUG mode.
|
34
|
+
# They are very frequent and would swamp the logs.
|
35
|
+
def common_messages
|
36
|
+
@COMMON_MESSAGES ||= begin
|
37
|
+
msgs = []
|
38
|
+
msgs += [
|
39
|
+
Messages::Status::MESSAGE_TYPE,
|
40
|
+
"\xbf\xe1".force_encoding(Encoding::ASCII_8BIT),
|
41
|
+
] unless BWA.verbosity >= 1
|
42
|
+
msgs += [
|
43
|
+
"\xbf\x00".force_encoding(Encoding::ASCII_8BIT),
|
44
|
+
"\xbf\xe1".force_encoding(Encoding::ASCII_8BIT),
|
45
|
+
Messages::Ready::MESSAGE_TYPE,
|
46
|
+
"\xbf\x07".force_encoding(Encoding::ASCII_8BIT),
|
47
|
+
] unless BWA.verbosity >= 2
|
48
|
+
msgs
|
49
|
+
end
|
50
|
+
@COMMON_MESSAGES
|
51
|
+
end
|
52
|
+
|
23
53
|
def parse(data)
|
24
54
|
offset = -1
|
25
55
|
message_type = length = message_class = nil
|
26
56
|
loop do
|
27
57
|
offset += 1
|
58
|
+
# Not enough data for a full message; return and hope for more
|
28
59
|
return nil if data.length - offset < 5
|
29
60
|
|
30
61
|
# Keep scanning until message start char
|
@@ -52,18 +83,15 @@ module BWA
|
|
52
83
|
break
|
53
84
|
end
|
54
85
|
|
55
|
-
|
56
|
-
|
86
|
+
message_type = data.slice(offset + 3, 2)
|
87
|
+
BWA.logger.debug "discarding invalid data prior to message #{BWA.raw2str(data[0...offset])}" unless offset == 0
|
88
|
+
BWA.logger.debug " read: #{BWA.raw2str(data.slice(offset, length + 2))}" unless common_messages.include?(message_type)
|
57
89
|
|
58
90
|
src = data[offset + 2].ord
|
59
|
-
message_type = data.slice(offset + 3, 2)
|
60
91
|
klass = @messages.find { |k| k::MESSAGE_TYPE == message_type }
|
61
92
|
|
62
|
-
|
63
|
-
return [nil, offset + length + 2] if
|
64
|
-
"\xbf\x00".force_encoding(Encoding::ASCII_8BIT),
|
65
|
-
"\xbf\xe1".force_encoding(Encoding::ASCII_8BIT),
|
66
|
-
"\xbf\x07".force_encoding(Encoding::ASCII_8BIT)].include?(message_type)
|
93
|
+
# Ignore these message types
|
94
|
+
return [nil, offset + length + 2] if IGNORED_MESSAGES.include?(message_type)
|
67
95
|
|
68
96
|
if klass
|
69
97
|
valid_length = if klass::MESSAGE_LENGTH.respond_to?(:include?)
|
@@ -73,6 +101,7 @@ module BWA
|
|
73
101
|
end
|
74
102
|
raise InvalidMessage.new("Unrecognized data length (#{length}) for message #{klass}", data) unless valid_length
|
75
103
|
else
|
104
|
+
BWA.logger.info "Unrecognized message type #{BWA.raw2str(message_type)}: #{BWA.raw2str(data.slice(offset, length + 2))}"
|
76
105
|
klass = Unrecognized
|
77
106
|
end
|
78
107
|
|
@@ -80,6 +109,7 @@ module BWA
|
|
80
109
|
message.parse(data.slice(offset + 5, length - 5))
|
81
110
|
message.instance_variable_set(:@raw_data, data.slice(offset, length + 2))
|
82
111
|
message.instance_variable_set(:@src, src)
|
112
|
+
BWA.logger.debug "from spa: #{message.inspect}" unless common_messages.include?(message_type)
|
83
113
|
[message, offset + length + 2]
|
84
114
|
end
|
85
115
|
|
@@ -7,14 +7,32 @@ module BWA
|
|
7
7
|
attr_accessor :type
|
8
8
|
|
9
9
|
def initialize(type = 1)
|
10
|
+
super()
|
10
11
|
self.type = type
|
11
12
|
end
|
12
13
|
|
13
14
|
def parse(data)
|
14
|
-
self.type = data
|
15
|
+
self.type = case data
|
16
|
+
when "\x02\x00\x00"; 1
|
17
|
+
when "\x00\x00\x01"; 2
|
18
|
+
when "\x01\x00\x00"; 3
|
19
|
+
else 0
|
20
|
+
end
|
15
21
|
end
|
16
22
|
|
23
|
+
def serialize
|
24
|
+
data = case type
|
25
|
+
when 1; "\x02\x00\x00"
|
26
|
+
when 2; "\x00\x00\x01"
|
27
|
+
when 3; "\x01\x00\x00"
|
28
|
+
else "\x00\x00\x00"
|
29
|
+
end
|
30
|
+
super(data)
|
31
|
+
end
|
17
32
|
|
33
|
+
def inspect
|
34
|
+
"#<BWA::Messages::ControlConfigurationRequest #{type}>"
|
35
|
+
end
|
18
36
|
end
|
19
37
|
end
|
20
38
|
end
|
data/lib/bwa/messages/ready.rb
CHANGED
data/lib/bwa/messages/status.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
module BWA
|
2
2
|
module Messages
|
3
3
|
class Status < Message
|
4
|
-
attr_accessor :
|
4
|
+
attr_accessor :hold,
|
5
|
+
:priming,
|
5
6
|
:heating_mode,
|
6
7
|
:temperature_scale,
|
7
8
|
:twenty_four_hour_time,
|
@@ -23,6 +24,7 @@ module BWA
|
|
23
24
|
|
24
25
|
def initialize
|
25
26
|
@src = 0xff
|
27
|
+
self.hold = false
|
26
28
|
self.priming = false
|
27
29
|
self.heating_mode = :ready
|
28
30
|
@temperature_scale = :fahrenheit
|
@@ -40,6 +42,9 @@ module BWA
|
|
40
42
|
end
|
41
43
|
|
42
44
|
def parse(data)
|
45
|
+
flags = data[0].ord
|
46
|
+
self.hold = (flags & 0x05 != 0 )
|
47
|
+
|
43
48
|
flags = data[1].ord
|
44
49
|
self.priming = (flags & 0x01 == 0x01)
|
45
50
|
flags = data[5].ord
|
@@ -88,6 +93,7 @@ module BWA
|
|
88
93
|
|
89
94
|
def serialize
|
90
95
|
data = "\x00" * 24
|
96
|
+
data[0] = (hold ? 0x05 : 0x00).chr
|
91
97
|
data[1] = (priming ? 0x01 : 0x00).chr
|
92
98
|
data[5] = (case heating_mode
|
93
99
|
when :ready; 0x00
|
@@ -118,7 +124,7 @@ module BWA
|
|
118
124
|
data[2] = (current_temperature ? (current_temperature * 2).to_i : 0xff).chr
|
119
125
|
data[20] = (set_temperature * 2).to_i.chr
|
120
126
|
else
|
121
|
-
data[2] = (current_temperature
|
127
|
+
data[2] = (current_temperature.to_i || 0xff).chr
|
122
128
|
data[20] = set_temperature.to_i.chr
|
123
129
|
end
|
124
130
|
|
@@ -154,6 +160,7 @@ module BWA
|
|
154
160
|
result = "#<BWA::Messages::Status "
|
155
161
|
items = []
|
156
162
|
|
163
|
+
items << "hold" if hold
|
157
164
|
items << "priming" if priming
|
158
165
|
items << self.class.format_time(hour, minute, twenty_four_hour_time)
|
159
166
|
items << "#{current_temperature || '--'}/#{set_temperature}º#{temperature_scale.to_s[0].upcase}"
|
@@ -7,6 +7,7 @@ module BWA
|
|
7
7
|
attr_accessor :item
|
8
8
|
|
9
9
|
def initialize(item = nil)
|
10
|
+
super()
|
10
11
|
self.item = item
|
11
12
|
end
|
12
13
|
|
@@ -14,6 +15,8 @@ module BWA
|
|
14
15
|
self.item = case data[0].ord
|
15
16
|
when 0x04; :pump1
|
16
17
|
when 0x05; :pump2
|
18
|
+
when 0x0c; :blower
|
19
|
+
when 0x0e; :mister
|
17
20
|
when 0x11; :light1
|
18
21
|
when 0x3c; :hold
|
19
22
|
when 0x50; :temperature_range
|
@@ -24,13 +27,20 @@ module BWA
|
|
24
27
|
|
25
28
|
def serialize
|
26
29
|
data = "\x00\x00"
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
30
|
+
if item.is_a? Integer
|
31
|
+
data[0] = item.chr
|
32
|
+
else
|
33
|
+
data[0] = (case item
|
34
|
+
when :pump1; 0x04
|
35
|
+
when :pump2; 0x05
|
36
|
+
when :blower; 0x0c
|
37
|
+
when :mister; 0x0e
|
38
|
+
when :light1; 0x11
|
39
|
+
when :hold; 0x3c
|
40
|
+
when :temperature_range; 0x50
|
41
|
+
when :heating_mode; 0x51
|
42
|
+
end).chr
|
43
|
+
end
|
34
44
|
super(data)
|
35
45
|
end
|
36
46
|
|
data/lib/bwa/proxy.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'socket'
|
2
|
+
require 'bwa/logger'
|
2
3
|
require 'bwa/message'
|
3
4
|
|
4
5
|
module BWA
|
@@ -44,9 +45,9 @@ module BWA
|
|
44
45
|
leftover_data = leftover_data[(data_length + 2)..-1] || ''
|
45
46
|
begin
|
46
47
|
message = Message.parse(data)
|
47
|
-
|
48
|
+
BWA.logger.info "#{tag}: #{message.inspect}"
|
48
49
|
rescue InvalidMessage => e
|
49
|
-
|
50
|
+
BWA.logger.info "#{tag}: #{e}"
|
50
51
|
end
|
51
52
|
socket2.send(data, 0)
|
52
53
|
end
|
data/lib/bwa/server.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'socket'
|
2
|
+
require 'bwa/logger'
|
2
3
|
require 'bwa/message'
|
3
4
|
|
4
5
|
module BWA
|
@@ -26,7 +27,7 @@ module BWA
|
|
26
27
|
end
|
27
28
|
|
28
29
|
def run_client(socket)
|
29
|
-
|
30
|
+
BWA.logger.info "Received connection from #{socket.remote_address.inspect}"
|
30
31
|
|
31
32
|
send_status(socket)
|
32
33
|
loop do
|
@@ -35,8 +36,8 @@ module BWA
|
|
35
36
|
break if data.empty?
|
36
37
|
begin
|
37
38
|
message = Message.parse(data)
|
38
|
-
|
39
|
-
|
39
|
+
BWA.logger.info BWA.raw2str(message.raw_data)
|
40
|
+
BWA.logger.info message.inspect
|
40
41
|
|
41
42
|
case message
|
42
43
|
when Messages::ConfigurationRequest
|
@@ -64,8 +65,8 @@ module BWA
|
|
64
65
|
end
|
65
66
|
end
|
66
67
|
rescue BWA::InvalidMessage => e
|
67
|
-
|
68
|
-
|
68
|
+
BWA.logger.warn e.message
|
69
|
+
BWA.logger.warn BWA.raw2str(e.raw_data)
|
69
70
|
end
|
70
71
|
else
|
71
72
|
send_status(socket)
|
@@ -74,7 +75,7 @@ module BWA
|
|
74
75
|
end
|
75
76
|
|
76
77
|
def send_status(socket)
|
77
|
-
|
78
|
+
BWA.logger.info "sending #{@status.inspect}"
|
78
79
|
socket.send(@status.serialize, 0)
|
79
80
|
end
|
80
81
|
|
data/lib/bwa/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: balboa_worldwide_app
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Cody Cutrer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-10
|
11
|
+
date: 2021-11-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: digest-crc
|
@@ -123,6 +123,7 @@ files:
|
|
123
123
|
- lib/bwa/client.rb
|
124
124
|
- lib/bwa/crc.rb
|
125
125
|
- lib/bwa/discovery.rb
|
126
|
+
- lib/bwa/logger.rb
|
126
127
|
- lib/bwa/message.rb
|
127
128
|
- lib/bwa/messages/configuration.rb
|
128
129
|
- lib/bwa/messages/configuration_request.rb
|