balboa_worldwide_app 1.2.5 → 1.3.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/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
|