balboa_worldwide_app 1.2.2 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9b3690c43f61e80376391c61d447f3e8430ae844ffc5d380afa9bb90636095bd
4
- data.tar.gz: 284a4856cac4830956a8e944572ce724f0bbeba98efa3cba0a879d932193bc23
3
+ metadata.gz: f1f61d30556c0536efad6d27c229cefc0bccd1293a4dc491230a3e68cfddca13
4
+ data.tar.gz: 3014c3c575a53cceaa2f9c2903b1977480bffa5cc0db5e44f08bce8fc8671e17
5
5
  SHA512:
6
- metadata.gz: 7a82e78b7f1ea26b74fe71117e9ca9c549708851f39a62c176650faf12c972f32658a541edac023ccd81def26fa6c2e8844adb3bf8b0f95bc4720ecf11d44e9c
7
- data.tar.gz: 00ffc61f1eb37ac4025e03e3a0412835e1211f2faa82d46dc6115de3a13ede7e4526fe3527bd9d04b9dfb8873ccfe566992e7a8a6955adca55161a6be2d45dd3
6
+ metadata.gz: 3ff3043cbbdefd778ee2fbea74ce46ad8c73860f8e541c9035911219b6d6048203f817a2f129bac16678334d4f02cddea806814357d13710c4c76fc3e1987b5e
7
+ data.tar.gz: bb1d5468380c06c7bc38c34cf984cd9c4550fb3a3f1314f1b85617df0cff003ece26ce4603746f59f0e00950ae8244aaf0f477149f3b693ab67b8f6ff30f8602
data/bin/bwa_client CHANGED
@@ -27,7 +27,7 @@ if ARGV.empty?
27
27
  $stderr.puts "Could not find spa!"
28
28
  exit 1
29
29
  end
30
- spa_ip = spas.first.first
30
+ spa_ip = 'tcp://' + spas.first.first + '/'
31
31
  else
32
32
  spa_ip = ARGV[0]
33
33
  end
data/bin/bwa_mqtt_bridge CHANGED
@@ -1,9 +1,14 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'mqtt'
4
+ require 'sd_notify'
5
+ require 'set'
6
+ require 'json'
4
7
 
8
+ require 'bwa/logger'
5
9
  require 'bwa/client'
6
10
  require 'bwa/discovery'
11
+ require 'bwa/version'
7
12
 
8
13
  class MQTTBridge
9
14
  def initialize(mqtt_uri, bwa, device_id: "bwa", base_topic: "homie")
@@ -15,7 +20,37 @@ class MQTTBridge
15
20
  @attributes = {}
16
21
  @things = Set.new
17
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
+
18
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
19
54
 
20
55
  bwa_thread = Thread.new do
21
56
  loop do
@@ -23,7 +58,6 @@ class MQTTBridge
23
58
  message = @bwa.poll
24
59
  next if message.is_a?(BWA::Messages::Ready)
25
60
 
26
- puts message.inspect unless message.is_a?(BWA::Messages::Status)
27
61
  case message
28
62
  when BWA::Messages::ControlConfiguration
29
63
  publish("spa/$type", message.model)
@@ -32,18 +66,29 @@ class MQTTBridge
32
66
  publish_pump(i + 1, speed) if speed != 0
33
67
  end
34
68
  message.lights.each_with_index do |exists, i|
35
- publish_thing("light", i + 1) if exists
69
+ publish_thing(:light, i + 1) if exists
36
70
  end
37
71
  message.aux.each_with_index do |exists, i|
38
- publish_thing("aux", i + 1) if exists
72
+ publish_thing(:aux, i + 1) if exists
39
73
  end
40
74
  publish_mister if message.mister
41
75
  publish_blower(message.blower) if message.blower != 0
42
76
  publish_circpump if message.circ_pump
43
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)
44
88
  when BWA::Messages::Status
45
89
  @bwa.request_control_info unless @bwa.last_control_configuration
46
90
  @bwa.request_control_info2 unless @bwa.last_control_configuration2
91
+ @bwa.request_filter_configuration unless @bwa.last_filter_configuration
47
92
 
48
93
  # make sure time is in sync
49
94
  now = Time.now
@@ -54,8 +99,10 @@ class MQTTBridge
54
99
 
55
100
  # allow a skew of 1 minute, since the seconds will always be off
56
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"
57
103
  @bwa.set_time(now.hour, now.min, message.twenty_four_hour_time)
58
104
  end
105
+ publish_attribute("spa/hold", message.hold)
59
106
  publish_attribute("spa/priming", message.priming)
60
107
  publish_attribute("spa/heatingmode", message.heating_mode)
61
108
  publish_attribute("spa/temperaturescale", message.temperature_scale)
@@ -67,11 +114,13 @@ class MQTTBridge
67
114
  publish_attribute("spa/settemperature", message.set_temperature)
68
115
  publish_attribute("spa/settemperature/$unit", "º#{message.temperature_scale.to_s[0].upcase}")
69
116
  if message.temperature_scale == :celsius
70
- publish_attribute("spa/currenttemperature/$format", message.temperature_range == :high ? "26:40" : "10:26")
117
+ publish_attribute("spa/currenttemperature/$format", "0:42")
71
118
  publish_attribute("spa/settemperature/$format", message.temperature_range == :high ? "26:40" : "10:26")
119
+ publish_hass_discovery_settemp(:celsius)
72
120
  else
73
- publish_attribute("spa/currenttemperature/$format", message.temperature_range == :high ? "80:104" : "26:40")
74
- publish_attribute("spa/settemperature/$format", message.temperature_range == :high ? "80:104" : "26:40")
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)
75
124
  end
76
125
  publish_attribute("spa/filter1", message.filter[0])
77
126
  publish_attribute("spa/filter2", message.filter[1])
@@ -88,14 +137,22 @@ class MQTTBridge
88
137
  (0..1).each do |i|
89
138
  publish_attribute("spa/aux#{i + 1}", message.lights[i]) if @bwa.last_control_configuration2&.aux&.[](i)
90
139
  end
140
+
141
+ # Tell systemd we are still alive and kicking. Ignored if systemd not in use.
142
+ SdNotify.watchdog
143
+
91
144
  end
92
145
  end
93
146
  end
94
147
  end
95
148
 
96
149
  @mqtt.get do |topic, value|
97
- puts "got #{value.inspect} at #{topic}"
150
+ BWA.logger.warn "from mqtt: #{value.inspect} at #{topic}"
98
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')
99
156
  when "spa/heatingmode/set"
100
157
  next @bwa.toggle_heating_mode if value == 'toggle'
101
158
  next unless %w{ready rest}.include?(value)
@@ -127,11 +184,14 @@ class MQTTBridge
127
184
  @bwa.set_blower(value.to_i)
128
185
  when "spa/settemperature/set"
129
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)
130
189
  end
131
190
  end
132
191
  end
133
192
 
134
193
  def publish(topic, value)
194
+ BWA.logger.debug " to mqtt: #{topic}: #{value}"
135
195
  @mqtt.publish("#{@base_topic}/#{topic}", value, true)
136
196
  end
137
197
 
@@ -146,8 +206,218 @@ class MQTTBridge
146
206
  @mqtt.subscribe("#{@base_topic}/#{topic}")
147
207
  end
148
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
+
149
419
  def publish_basic_attributes
150
- publish("$homie", "v4.0.0")
420
+ publish("$homie", "4.0.0")
151
421
  publish("$name", "BWA Spa")
152
422
  publish("$state", "init")
153
423
  publish("$nodes", "spa")
@@ -156,6 +426,11 @@ class MQTTBridge
156
426
  publish("spa/$type", "spa")
157
427
  publish_nodes
158
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
+
159
434
  publish("spa/priming/$name", "Is the pump priming")
160
435
  publish("spa/priming/$datatype", "boolean")
161
436
 
@@ -208,6 +483,7 @@ class MQTTBridge
208
483
  subscribe("spa/pump#{i}/set")
209
484
 
210
485
  @things << "pump#{i}"
486
+ publish_hass_discovery_pumps(i, speeds)
211
487
  publish_nodes
212
488
  end
213
489
 
@@ -218,6 +494,7 @@ class MQTTBridge
218
494
  subscribe("spa/#{type}#{i}/set")
219
495
 
220
496
  @things << "#{type}#{i}"
497
+ publish_hass_discovery_things(type, i)
221
498
  publish_nodes
222
499
  end
223
500
 
@@ -250,8 +527,68 @@ class MQTTBridge
250
527
  publish_nodes
251
528
  end
252
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
+
253
590
  def publish_nodes
254
- 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(','))
255
592
  end
256
593
  end
257
594
 
@@ -260,6 +597,7 @@ mqtt_uri = ARGV.shift
260
597
  if ARGV.empty?
261
598
  spas = BWA::Discovery.discover
262
599
  if spas.empty?
600
+ BWA.logger.fatal "Could not find spa!"
263
601
  $stderr.puts "Could not find spa!"
264
602
  exit 1
265
603
  end
@@ -271,5 +609,6 @@ end
271
609
  spa = BWA::Client.new(spa_ip)
272
610
 
273
611
  spa.request_configuration
612
+ spa.request_filter_configuration
274
613
 
275
614
  MQTTBridge.new(mqtt_uri, spa)
data/lib/bwa/client.rb CHANGED
@@ -1,3 +1,6 @@
1
+ require 'uri'
2
+
3
+ require 'bwa/logger'
1
4
  require 'bwa/message'
2
5
 
3
6
  module BWA
@@ -8,7 +11,7 @@ module BWA
8
11
  uri = URI.parse(uri)
9
12
  if uri.scheme == 'tcp'
10
13
  require 'socket'
11
- @io = TCPSocket.new(uri.host, uri.port || 4217)
14
+ @io = TCPSocket.new(uri.host, uri.port || 4257)
12
15
  elsif uri.scheme == 'telnet' || uri.scheme == 'rfc2217'
13
16
  require 'net/telnet/rfc2217'
14
17
  @io = Net::Telnet::RFC2217.new("Host" => uri.host, "Port" => uri.port || 23, "baud" => 115200)
@@ -18,6 +21,7 @@ module BWA
18
21
  @io = CCutrer::SerialPort.new(uri.path, baud: 115200)
19
22
  @queue = []
20
23
  end
24
+ @src = 0x0a
21
25
  @buffer = ""
22
26
  end
23
27
 
@@ -41,7 +45,7 @@ module BWA
41
45
  end
42
46
 
43
47
  if message.is_a?(Messages::Ready) && (msg = @queue&.shift)
44
- puts "wrote #{msg.unpack('H*').first}"
48
+ BWA.logger.debug "wrote: #{BWA.raw2str(msg)}" unless BWA.verbosity < 1 && msg[3..4] == Messages::ControlConfigurationRequest::MESSAGE_TYPE
45
49
  @io.write(msg)
46
50
  end
47
51
  @last_status = message.dup if message.is_a?(Messages::Status)
@@ -60,35 +64,35 @@ module BWA
60
64
  end
61
65
 
62
66
  def send_message(message)
63
- length = message.length + 2
64
- full_message = "#{length.chr}#{message}".force_encoding(Encoding::ASCII_8BIT)
65
- checksum = CRC.checksum(full_message)
66
- 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
67
70
  if @queue
68
71
  @queue.push(full_message)
69
72
  else
73
+ BWA.logger.debug "wrote: #{BWA.raw2str(full_message)}" unless BWA.verbosity < 1 && message.is_a?(Messages::ControlConfigurationRequest)
70
74
  @io.write(full_message)
71
75
  end
72
76
  end
73
77
 
74
78
  def request_configuration
75
- send_message("\x0a\xbf\x04")
79
+ send_message(Messages::ConfigurationRequest.new)
76
80
  end
77
81
 
78
82
  def request_control_info2
79
- send_message("\x0a\xbf\x22\x00\x00\x01")
83
+ send_message(Messages::ControlConfigurationRequest.new(2))
80
84
  end
81
85
 
82
86
  def request_control_info
83
- send_message("\x0a\xbf\x22\x02\x00\x00")
87
+ send_message(Messages::ControlConfigurationRequest.new(1))
84
88
  end
85
89
 
86
90
  def request_filter_configuration
87
- send_message("\x0a\xbf\x22\x01\x00\x00")
91
+ send_message(Messages::ControlConfigurationRequest.new(3))
88
92
  end
89
93
 
90
94
  def toggle_item(item)
91
- send_message("\x0a\xbf\x11#{item.chr}\x00")
95
+ send_message(Messages::ToggleItem.new(item))
92
96
  end
93
97
 
94
98
  def toggle_pump(i)
@@ -100,11 +104,15 @@ module BWA
100
104
  end
101
105
 
102
106
  def toggle_mister
103
- toggle_item(0x0e)
107
+ toggle_item(:mister)
104
108
  end
105
109
 
106
110
  def toggle_blower
107
- toggle_item(0x0c)
111
+ toggle_item(:blower)
112
+ end
113
+
114
+ def toggle_hold
115
+ toggle_item(:hold)
108
116
  end
109
117
 
110
118
  def set_pump(i, desired)
@@ -141,22 +149,57 @@ module BWA
141
149
  end
142
150
  end
143
151
 
144
- # high range is 80-104 for F, 26-40 for C (by 0.5)
145
- # low range is 50-80 for F, 10-26 for C (by 0.5)
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)
146
160
  def set_temperature(desired)
161
+ return unless last_status
162
+ return if last_status.set_temperature == desired
163
+
147
164
  desired *= 2 if last_status && last_status.temperature_scale == :celsius || desired < 50
148
- send_message("\x0a\xbf\x20#{desired.round.chr}")
165
+ send_message(Messages::SetTemperature.new(desired.round))
149
166
  end
150
167
 
151
168
  def set_time(hour, minute, twenty_four_hour_time = false)
152
- hour |= 0x80 if twenty_four_hour_time
153
- 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))
154
170
  end
155
171
 
156
172
  def set_temperature_scale(scale)
157
173
  raise ArgumentError, "scale must be :fahrenheit or :celsius" unless %I{fahrenheit :celsius}.include?(scale)
158
- arg = scale == :fahrenheit ? 0 : 1
159
- send_message("\x0a\xbf\x27\x01".force_encoding(Encoding::ASCII_8BIT) + arg.chr)
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
160
203
  end
161
204
 
162
205
  def toggle_temperature_range
@@ -170,7 +213,7 @@ module BWA
170
213
  end
171
214
 
172
215
  def toggle_heating_mode
173
- toggle_item(0x51)
216
+ toggle_item(:heating_mode)
174
217
  end
175
218
 
176
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
- puts "Advertising to #{ip}"
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,40 +23,75 @@ 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
 
61
+ # Keep scanning until message start char
30
62
  next unless data[offset] == '~'
63
+
64
+ # Read length (safe since we have at least 5 chars)
31
65
  length = data[offset + 1].ord
32
- # impossible message
33
- next if length < 5
66
+
67
+ # No message is this short or this long; keep scanning
68
+ next if length < 5 or length >= '~'.ord
34
69
 
35
70
  # don't have enough data for what this message wants;
36
- # it could be garbage on the line so keep scanning
37
- next if length + 2 > data.length - offset
71
+ # return and hope for more (yes this might cause a
72
+ # delay, but the protocol is very chatty so it won't
73
+ # be long)
74
+ return nil if length + 2 > data.length - offset
38
75
 
76
+ # Not properly terminated; keep scanning
39
77
  next unless data[offset + length + 1] == '~'
40
78
 
79
+ # Not a valid checksum; keep scanning
41
80
  next unless CRC.checksum(data.slice(offset + 1, length - 1)) == data[offset + length].ord
81
+
82
+ # Got a valid message!
42
83
  break
43
84
  end
44
85
 
45
- puts "discarding invalid data prior to message #{data[0...offset].unpack('H*').first}" unless offset == 0
46
- #puts "read #{data.slice(offset, length + 2).unpack('H*').first}"
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)
47
89
 
48
90
  src = data[offset + 2].ord
49
- message_type = data.slice(offset + 3, 2)
50
91
  klass = @messages.find { |k| k::MESSAGE_TYPE == message_type }
51
92
 
52
-
53
- return [nil, offset + length + 2] if [
54
- "\xbf\x00".force_encoding(Encoding::ASCII_8BIT),
55
- "\xbf\xe1".force_encoding(Encoding::ASCII_8BIT),
56
- "\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)
57
95
 
58
96
  if klass
59
97
  valid_length = if klass::MESSAGE_LENGTH.respond_to?(:include?)
@@ -63,6 +101,7 @@ module BWA
63
101
  end
64
102
  raise InvalidMessage.new("Unrecognized data length (#{length}) for message #{klass}", data) unless valid_length
65
103
  else
104
+ BWA.logger.info "Unrecognized message type #{BWA.raw2str(message_type)}: #{BWA.raw2str(data.slice(offset, length + 2))}"
66
105
  klass = Unrecognized
67
106
  end
68
107
 
@@ -70,6 +109,7 @@ module BWA
70
109
  message.parse(data.slice(offset + 5, length - 5))
71
110
  message.instance_variable_set(:@raw_data, data.slice(offset, length + 2))
72
111
  message.instance_variable_set(:@src, src)
112
+ BWA.logger.debug "from spa: #{message.inspect}" unless common_messages.include?(message_type)
73
113
  [message, offset + length + 2]
74
114
  end
75
115
 
@@ -3,6 +3,10 @@ module BWA
3
3
  class Configuration < Message
4
4
  MESSAGE_TYPE = "\xbf\x94".force_encoding(Encoding::ASCII_8BIT)
5
5
  MESSAGE_LENGTH = 25
6
+
7
+ def inspect
8
+ "#<BWA::Messages::Configuration>"
9
+ end
6
10
  end
7
11
  end
8
12
  end
@@ -7,6 +7,7 @@ module BWA
7
7
  attr_accessor :model, :version
8
8
 
9
9
  def initialize
10
+ super
10
11
  @model = ''
11
12
  @version = 0
12
13
  end
@@ -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 == "\x02\x00\x00" ? 1 : 2
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
@@ -3,6 +3,10 @@ module BWA
3
3
  class Ready < Message
4
4
  MESSAGE_TYPE = "\xbf\06".force_encoding(Encoding::ASCII_8BIT)
5
5
  MESSAGE_LENGTH = 0
6
+
7
+ def inspect
8
+ "#<BWA::Messages::Ready>"
9
+ end
6
10
  end
7
11
  end
8
12
  end
@@ -7,6 +7,7 @@ module BWA
7
7
  attr_accessor :temperature
8
8
 
9
9
  def initialize(temperature = nil)
10
+ super()
10
11
  self.temperature = temperature
11
12
  end
12
13
 
@@ -7,6 +7,7 @@ module BWA
7
7
  attr_accessor :scale
8
8
 
9
9
  def initialize(scale = nil)
10
+ super()
10
11
  self.scale = scale
11
12
  end
12
13
 
@@ -7,6 +7,7 @@ module BWA
7
7
  attr_accessor :hour, :minute, :twenty_four_hour_time
8
8
 
9
9
  def initialize(hour = nil, minute = nil, twenty_four_hour_time = nil)
10
+ super()
10
11
  self.hour, self.minute, self.twenty_four_hour_time = hour, minute, twenty_four_hour_time
11
12
  end
12
13
 
@@ -1,7 +1,8 @@
1
1
  module BWA
2
2
  module Messages
3
3
  class Status < Message
4
- attr_accessor :priming,
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
@@ -81,13 +86,14 @@ module BWA
81
86
  self.current_temperature = nil if self.current_temperature == 0xff
82
87
  self.set_temperature = data[20].ord
83
88
  if temperature_scale == :celsius
84
- self.current_temperature /= 2.0
85
- self.set_temperature /= 2.0
89
+ self.current_temperature /= 2.0 if self.current_temperature
90
+ self.set_temperature /= 2.0 if self.set_temperature
86
91
  end
87
92
  end
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&.to_i || 0xff).chr
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
- data[0] = (case setting
28
- when :pump1; 0x04
29
- when :pump2; 0x05
30
- when :light1; 0x11
31
- when :temperature_range; 0x50
32
- when :heating_mode; 0x51
33
- end).chr
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
- puts "#{tag}: #{message.inspect}"
48
+ BWA.logger.info "#{tag}: #{message.inspect}"
48
49
  rescue InvalidMessage => e
49
- puts "#{tag}: #{e}"
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
- puts "Received connection from #{socket.remote_address.inspect}"
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
- puts message.raw_data.unpack("H*").first.scan(/[0-9a-f]{2}/).join(' ')
39
- puts message.inspect
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
- puts e.message
68
- puts e.raw_data.unpack("H*").first.scan(/[0-9a-f]{2}/).join(' ')
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
- puts "sending #{@status.inspect}"
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
@@ -1,3 +1,3 @@
1
1
  module BWA
2
- VERSION = '1.2.2'
2
+ VERSION = '1.3.0'
3
3
  end
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.2.2
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-03-15 00:00:00.000000000 Z
11
+ date: 2021-11-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: digest-crc
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: 1.0.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: sd_notify
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.1.1
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.1.1
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: byebug
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -109,6 +123,7 @@ files:
109
123
  - lib/bwa/client.rb
110
124
  - lib/bwa/crc.rb
111
125
  - lib/bwa/discovery.rb
126
+ - lib/bwa/logger.rb
112
127
  - lib/bwa/message.rb
113
128
  - lib/bwa/messages/configuration.rb
114
129
  - lib/bwa/messages/configuration_request.rb
@@ -143,7 +158,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
143
158
  - !ruby/object:Gem::Version
144
159
  version: '0'
145
160
  requirements: []
146
- rubygems_version: 3.0.3
161
+ rubygems_version: 3.1.2
147
162
  signing_key:
148
163
  specification_version: 4
149
164
  summary: Library for communication with Balboa Water Group's WiFi module or RS-485