balboa_worldwide_app 1.0.0 → 1.1.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_mqtt_bridge +126 -39
- data/lib/bwa/client.rb +107 -38
- data/lib/bwa/message.rb +27 -39
- data/lib/bwa/messages/control_configuration.rb +62 -0
- data/lib/bwa/messages/status.rb +34 -10
- data/lib/bwa/version.rb +1 -1
- metadata +22 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 474d2a26cc4dca552bcb29ff22aa486903d73b7707c40d9c160e77eb2baed5fb
|
4
|
+
data.tar.gz: f085680c104a2d42e1755cf63a44538a376e875820c12468f0fb955c39f69f07
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d6f16d2ae55926e145489f3b61d9df9be692e2a5a3d21920721ef657301d1e099fe28381e8c7eea9711efd2b314079b818e2f33bbda66234423c3be4c0ccacd5
|
7
|
+
data.tar.gz: d7a6d5640c2902c03e22a6f4b11f447b3f4e3b6c9da757070b7db363b8dbb4e1b77ecfbce1553779f68b077e5ac2eac98f06d5ca7c7abafc9e4433459b211e81
|
data/bin/bwa_mqtt_bridge
CHANGED
@@ -13,6 +13,7 @@ class MQTTBridge
|
|
13
13
|
@mqtt.connect
|
14
14
|
@bwa = bwa
|
15
15
|
@attributes = {}
|
16
|
+
@things = Set.new
|
16
17
|
|
17
18
|
publish_basic_attributes
|
18
19
|
|
@@ -22,11 +23,37 @@ class MQTTBridge
|
|
22
23
|
message = @bwa.poll
|
23
24
|
next if message.is_a?(BWA::Messages::Ready)
|
24
25
|
|
26
|
+
puts message.inspect unless message.is_a?(BWA::Messages::Status)
|
25
27
|
case message
|
28
|
+
when BWA::Messages::ControlConfiguration
|
29
|
+
publish("spa/$type", message.model)
|
30
|
+
when BWA::Messages::ControlConfiguration2
|
31
|
+
message.pumps.each_with_index do |speed, i|
|
32
|
+
publish_pump(i + 1, speed) if speed != 0
|
33
|
+
end
|
34
|
+
message.lights.each_with_index do |exists, i|
|
35
|
+
publish_thing("light", i + 1) if exists
|
36
|
+
end
|
37
|
+
message.aux.each_with_index do |exists, i|
|
38
|
+
publish_thing("aux", i + 1) if exists
|
39
|
+
end
|
40
|
+
publish_mister if message.mister
|
41
|
+
publish_blower(message.blower) if message.blower != 0
|
42
|
+
publish_circpump if message.circ_pump
|
43
|
+
publish("$state", "ready")
|
26
44
|
when BWA::Messages::Status
|
45
|
+
@bwa.request_control_info unless @bwa.last_control_configuration
|
46
|
+
@bwa.request_control_info2 unless @bwa.last_control_configuration2
|
47
|
+
|
27
48
|
# make sure time is in sync
|
28
49
|
now = Time.now
|
29
|
-
|
50
|
+
now_minutes = now.hour * 60 + now.min
|
51
|
+
spa_minutes = message.hour * 60 + message.minute
|
52
|
+
# check the difference in both directions
|
53
|
+
diff = [(spa_minutes - now_minutes) % 1440, 1440 - (spa_minutes - now_minutes) % 1440].min
|
54
|
+
|
55
|
+
# allow a skew of 1 minute, since the seconds will always be off
|
56
|
+
if diff > 1
|
30
57
|
@bwa.set_time(now.hour, now.min, message.twenty_four_hour_time)
|
31
58
|
end
|
32
59
|
publish_attribute("spa/priming", message.priming)
|
@@ -35,10 +62,6 @@ class MQTTBridge
|
|
35
62
|
publish_attribute("spa/24htime", message.twenty_four_hour_time)
|
36
63
|
publish_attribute("spa/heating", message.heating)
|
37
64
|
publish_attribute("spa/temperaturerange", message.temperature_range)
|
38
|
-
publish_attribute("spa/circpump", message.circ_pump)
|
39
|
-
publish_attribute("spa/pump1", message.pump1)
|
40
|
-
publish_attribute("spa/pump2", message.pump2)
|
41
|
-
publish_attribute("spa/light1", message.light1)
|
42
65
|
publish_attribute("spa/currenttemperature", message.current_temperature)
|
43
66
|
publish_attribute("spa/currenttemperature/$unit", "º#{message.temperature_scale.to_s[0].upcase}")
|
44
67
|
publish_attribute("spa/settemperature", message.set_temperature)
|
@@ -50,6 +73,21 @@ class MQTTBridge
|
|
50
73
|
publish_attribute("spa/currenttemperature/$format", message.temperature_range == :high ? "80:104" : "26:40")
|
51
74
|
publish_attribute("spa/settemperature/$format", message.temperature_range == :high ? "80:104" : "26:40")
|
52
75
|
end
|
76
|
+
publish_attribute("spa/filter1", message.filter[0])
|
77
|
+
publish_attribute("spa/filter2", message.filter[1])
|
78
|
+
|
79
|
+
publish_attribute("spa/circpump", message.circ_pump) if @bwa.last_control_configuration2&.circ_pump
|
80
|
+
publish_attribute("spa/blower", message.blower) if @bwa.last_control_configuration2&.blower.to_i != 0
|
81
|
+
publish_attribute("spa/mister", message.mister) if @bwa.last_control_configuration2&.mister
|
82
|
+
(0..5).each do |i|
|
83
|
+
publish_attribute("spa/pump#{i + 1}", message.pumps[i]) if @bwa.last_control_configuration2&.pumps&.[](i).to_i != 0
|
84
|
+
end
|
85
|
+
(0..1).each do |i|
|
86
|
+
publish_attribute("spa/light#{i + 1}", message.lights[i]) if @bwa.last_control_configuration2&.lights&.[](i)
|
87
|
+
end
|
88
|
+
(0..1).each do |i|
|
89
|
+
publish_attribute("spa/aux#{i + 1}", message.lights[i]) if @bwa.last_control_configuration2&.aux&.[](i)
|
90
|
+
end
|
53
91
|
end
|
54
92
|
end
|
55
93
|
end
|
@@ -58,24 +96,35 @@ class MQTTBridge
|
|
58
96
|
@mqtt.get do |topic, value|
|
59
97
|
puts "got #{value.inspect} at #{topic}"
|
60
98
|
case topic[@base_topic.length + 1..-1]
|
61
|
-
when "heatingmode"
|
62
|
-
next
|
63
|
-
|
64
|
-
|
99
|
+
when "spa/heatingmode/set"
|
100
|
+
next @bwa.toggle_heating_mode if value == 'toggle'
|
101
|
+
next unless %w{ready rest}.include?(value)
|
102
|
+
@bwa.set_heating_mode(value.to_sym)
|
103
|
+
when "spa/temperaturescale/set"
|
65
104
|
next unless %w{fahrenheit celsius}.include?(value)
|
66
|
-
|
105
|
+
@bwa.set_temperature_scale(value.to_sym)
|
67
106
|
when "spa/24htime/set"
|
68
107
|
next unless %w{true false}.include?(value)
|
69
108
|
now = Time.now
|
70
109
|
@bwa.set_time(now.hour, now.min, value == 'true')
|
71
|
-
when "temperaturerange"
|
110
|
+
when "spa/temperaturerange/set"
|
111
|
+
next @bwa.toggle_temperature_range if value == 'toggle'
|
72
112
|
next unless %w{low high}.include?(value)
|
73
|
-
|
74
|
-
when %r{^spa/(
|
75
|
-
@bwa.
|
76
|
-
|
113
|
+
@bwa.set_temperature_range(value.to_sym)
|
114
|
+
when %r{^spa/pump([1-6])/set$}
|
115
|
+
next @bwa.toggle_pump($1.to_i) if value == 'toggle'
|
116
|
+
@bwa.set_pump($1.to_i, value.to_i)
|
117
|
+
when %r{^spa/(light|aux)([12])/set$}
|
118
|
+
next @bwa.send(:"toggle_#{$1}", $2.to_i) if value == 'toggle'
|
77
119
|
next unless %w{true false}.include?(value)
|
78
|
-
@bwa.
|
120
|
+
@bwa.send(:"set_#{$1}", $2.to_i, value == 'true')
|
121
|
+
when "spa/mister/set"
|
122
|
+
next @bwa.toggle_mister if value == 'toggle'
|
123
|
+
next unless %w{true false}.include?(value)
|
124
|
+
@bwa.set_mister(value == 'true')
|
125
|
+
when "spa/blower/set"
|
126
|
+
next @bwa.toggle_blower if value == 'toggle'
|
127
|
+
@bwa.set_blower(value.to_i)
|
79
128
|
when "spa/settemperature/set"
|
80
129
|
@bwa.set_temperature(value.to_i)
|
81
130
|
end
|
@@ -105,7 +154,7 @@ class MQTTBridge
|
|
105
154
|
|
106
155
|
publish("spa/$name", "BWA Spa")
|
107
156
|
publish("spa/$type", "spa")
|
108
|
-
|
157
|
+
publish_nodes
|
109
158
|
|
110
159
|
publish("spa/priming/$name", "Is the pump priming")
|
111
160
|
publish("spa/priming/$datatype", "boolean")
|
@@ -136,26 +185,6 @@ class MQTTBridge
|
|
136
185
|
publish("spa/temperaturerange/$settable", "true")
|
137
186
|
subscribe("spa/temperaturerange/set")
|
138
187
|
|
139
|
-
publish("spa/circpump/$name", "Circ pump is currently running")
|
140
|
-
publish("spa/circpump/$datatype", "boolean")
|
141
|
-
|
142
|
-
publish("spa/pump1/$name", "Pump 1 speed")
|
143
|
-
publish("spa/pump1/$datatype", "integer")
|
144
|
-
publish("spa/pump1/$format", "0:2")
|
145
|
-
publish("spa/pump1/$settable", "true")
|
146
|
-
subscribe("spa/pump1/set")
|
147
|
-
|
148
|
-
publish("spa/pump2/$name", "Pump 2 speed")
|
149
|
-
publish("spa/pump2/$datatype", "integer")
|
150
|
-
publish("spa/pump2/$format", "0:2")
|
151
|
-
publish("spa/pump2/$settable", "true")
|
152
|
-
subscribe("spa/pump2/set")
|
153
|
-
|
154
|
-
publish("spa/light1/$name", "Light 1")
|
155
|
-
publish("spa/light1/$datatype", "boolean")
|
156
|
-
publish("spa/light1/$settable", "true")
|
157
|
-
subscribe("spa/light1/set")
|
158
|
-
|
159
188
|
publish("spa/currenttemperature/$name", "Current temperature")
|
160
189
|
publish("spa/currenttemperature/$datatype", "integer")
|
161
190
|
|
@@ -164,7 +193,65 @@ class MQTTBridge
|
|
164
193
|
publish("spa/settemperature/$settable", "true")
|
165
194
|
subscribe("spa/settemperature/set")
|
166
195
|
|
167
|
-
publish("
|
196
|
+
publish("spa/filter1/$name", "Filter cycle 1 is currently running")
|
197
|
+
publish("spa/filter1/$datatype", "boolean")
|
198
|
+
|
199
|
+
publish("spa/filter2/$name", "Filter cycle 2 is currently running")
|
200
|
+
publish("spa/filter2/$datatype", "boolean")
|
201
|
+
end
|
202
|
+
|
203
|
+
def publish_pump(i, speeds)
|
204
|
+
publish("spa/pump#{i}/$name", "Pump #{i} speed")
|
205
|
+
publish("spa/pump#{i}/$datatype", "integer")
|
206
|
+
publish("spa/pump#{i}/$format", "0:#{speeds}")
|
207
|
+
publish("spa/pump#{i}/$settable", "true")
|
208
|
+
subscribe("spa/pump#{i}/set")
|
209
|
+
|
210
|
+
@things << "pump#{i}"
|
211
|
+
publish_nodes
|
212
|
+
end
|
213
|
+
|
214
|
+
def publish_thing(type, i)
|
215
|
+
publish("spa/#{type}#{i}/$name", "#{type} #{i}")
|
216
|
+
publish("spa/#{type}#{i}/$datatype", "boolean")
|
217
|
+
publish("spa/#{type}#{i}/$settable", "true")
|
218
|
+
subscribe("spa/#{type}#{i}/set")
|
219
|
+
|
220
|
+
@things << "#{type}#{i}"
|
221
|
+
publish_nodes
|
222
|
+
end
|
223
|
+
|
224
|
+
def publish_mister
|
225
|
+
publish("spa/mister/$name", type)
|
226
|
+
publish("spa/mister/$datatype", "boolean")
|
227
|
+
publish("spa/mister/$settable", "true")
|
228
|
+
subscribe("spa/mister/set")
|
229
|
+
|
230
|
+
@things << "mister"
|
231
|
+
publish_nodes
|
232
|
+
end
|
233
|
+
|
234
|
+
def publish_blower(speeds)
|
235
|
+
publish("spa/blower/$name", "Blower speed")
|
236
|
+
publish("spa/blower/$datatype", "integer")
|
237
|
+
publish("spa/blower/$format", "0:#{speeds}")
|
238
|
+
publish("spa/blower/$settable", "true")
|
239
|
+
subscribe("spa/blower/set")
|
240
|
+
|
241
|
+
@things << "blower"
|
242
|
+
publish_nodes
|
243
|
+
end
|
244
|
+
|
245
|
+
def publish_circpump
|
246
|
+
publish("spa/circpump/$name", "Circ pump is currently running")
|
247
|
+
publish("spa/circpump/$datatype", "boolean")
|
248
|
+
@things << "circpump"
|
249
|
+
|
250
|
+
publish_nodes
|
251
|
+
end
|
252
|
+
|
253
|
+
def publish_nodes
|
254
|
+
publish("spa/$properties", (["priming,heatingmode,temperaturescale,24htime,heating,temperaturerange,currenttemperature,settemperature,filter1,filter2"] + @things.to_a).join(','))
|
168
255
|
end
|
169
256
|
end
|
170
257
|
|
@@ -176,7 +263,7 @@ if ARGV.empty?
|
|
176
263
|
$stderr.puts "Could not find spa!"
|
177
264
|
exit 1
|
178
265
|
end
|
179
|
-
spa_ip = spas.first.first
|
266
|
+
spa_ip = "tcp://#{spas.first.first}/"
|
180
267
|
else
|
181
268
|
spa_ip = ARGV[0]
|
182
269
|
end
|
data/lib/bwa/client.rb
CHANGED
@@ -2,36 +2,46 @@ require 'bwa/message'
|
|
2
2
|
|
3
3
|
module BWA
|
4
4
|
class Client
|
5
|
-
attr_reader :last_status, :last_filter_configuration
|
5
|
+
attr_reader :last_status, :last_control_configuration, :last_control_configuration2, :last_filter_configuration
|
6
6
|
|
7
|
-
def initialize(
|
8
|
-
|
9
|
-
|
10
|
-
|
7
|
+
def initialize(uri)
|
8
|
+
uri = URI.parse(uri)
|
9
|
+
if uri.scheme == 'tcp'
|
10
|
+
require 'socket'
|
11
|
+
@io = TCPSocket.new(uri.host, uri.port || 4217)
|
12
|
+
elsif uri.scheme == 'telnet' || uri.scheme == 'rfc2217'
|
13
|
+
require 'net/telnet/rfc2217'
|
14
|
+
@io = Net::Telnet::RFC2217.new("Host" => uri.host, "Port" => uri.port || 23, "baud" => 115200)
|
11
15
|
@queue = []
|
12
16
|
else
|
13
|
-
require '
|
14
|
-
@io =
|
17
|
+
require 'serialport'
|
18
|
+
@io = SerialPort.open(uri.path, "baud" => 115200)
|
19
|
+
@queue = []
|
15
20
|
end
|
21
|
+
@buffer = ""
|
16
22
|
end
|
17
23
|
|
18
24
|
def poll
|
19
|
-
message = nil
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
unless e.message =~ /Incorrect data length/
|
28
|
-
puts e.message
|
29
|
-
puts e.raw_data.unpack("H*").first.scan(/[0-9a-f]{2}/).join(' ')
|
30
|
-
end
|
25
|
+
message = bytes_read = nil
|
26
|
+
loop do
|
27
|
+
message, bytes_read = Message.parse(@buffer)
|
28
|
+
# discard how much we read
|
29
|
+
@buffer = @buffer[bytes_read..-1] if bytes_read
|
30
|
+
unless message
|
31
|
+
@buffer.concat(@io.readpartial(64 * 1024))
|
32
|
+
next
|
31
33
|
end
|
34
|
+
break
|
35
|
+
end
|
36
|
+
|
37
|
+
if message.is_a?(Messages::Ready) && (msg = @queue&.shift)
|
38
|
+
puts "wrote #{msg.unpack('H*').first}"
|
39
|
+
@io.write(msg)
|
32
40
|
end
|
33
41
|
@last_status = message.dup if message.is_a?(Messages::Status)
|
34
42
|
@last_filter_configuration = message.dup if message.is_a?(Messages::FilterCycles)
|
43
|
+
@last_control_configuration = message.dup if message.is_a?(Messages::ControlConfiguration)
|
44
|
+
@last_control_configuration2 = message.dup if message.is_a?(Messages::ControlConfiguration2)
|
35
45
|
message
|
36
46
|
end
|
37
47
|
|
@@ -59,6 +69,10 @@ module BWA
|
|
59
69
|
send_message("\x0a\xbf\x04")
|
60
70
|
end
|
61
71
|
|
72
|
+
def request_control_info2
|
73
|
+
send_message("\x0a\xbf\x22\x00\x00\x01")
|
74
|
+
end
|
75
|
+
|
62
76
|
def request_control_info
|
63
77
|
send_message("\x0a\xbf\x22\x02\x00\x00")
|
64
78
|
end
|
@@ -67,39 +81,58 @@ module BWA
|
|
67
81
|
send_message("\x0a\xbf\x22\x01\x00\x00")
|
68
82
|
end
|
69
83
|
|
70
|
-
def toggle_item(
|
71
|
-
send_message("\x0a\xbf\x11#{
|
84
|
+
def toggle_item(item)
|
85
|
+
send_message("\x0a\xbf\x11#{item.chr}\x00")
|
86
|
+
end
|
87
|
+
|
88
|
+
def toggle_pump(i)
|
89
|
+
toggle_item(i + 3)
|
90
|
+
end
|
91
|
+
|
92
|
+
def toggle_light(i)
|
93
|
+
toggle_item(i + 0x10)
|
72
94
|
end
|
73
95
|
|
74
|
-
def
|
75
|
-
toggle_item(
|
96
|
+
def toggle_mister
|
97
|
+
toggle_item(0x0e)
|
76
98
|
end
|
77
99
|
|
78
|
-
def
|
79
|
-
toggle_item(
|
100
|
+
def toggle_blower
|
101
|
+
toggle_item(0x0c)
|
80
102
|
end
|
81
103
|
|
82
|
-
def
|
83
|
-
|
104
|
+
def set_pump(i, desired)
|
105
|
+
return unless last_status && last_control_configuration2
|
106
|
+
times = (desired - last_status.pumps[i - 1]) % (last_control_configuration2.pumps[i - 1] + 1)
|
107
|
+
times.times do
|
108
|
+
toggle_pump(i)
|
109
|
+
sleep(0.1)
|
110
|
+
end
|
84
111
|
end
|
85
112
|
|
86
|
-
|
113
|
+
%w{light aux}.each do |type|
|
87
114
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
toggle_pump#{i}
|
93
|
-
sleep(0.1)
|
115
|
+
def set_#{type}(i, desired)
|
116
|
+
return unless last_status
|
117
|
+
return if last_status.#{type}s[i - 1] == desired
|
118
|
+
toggle_#{type}(i)
|
94
119
|
end
|
95
|
-
end
|
96
120
|
RUBY
|
97
121
|
end
|
98
122
|
|
99
|
-
def
|
123
|
+
def set_mister(desired)
|
100
124
|
return unless last_status
|
101
|
-
return if last_status.
|
102
|
-
|
125
|
+
return if last_status.mister == desired
|
126
|
+
toggle_mister
|
127
|
+
end
|
128
|
+
|
129
|
+
def set_blower(desired)
|
130
|
+
return unless last_status && last_control_configuration2
|
131
|
+
times = (desired - last_status.blower) % (last_control_configuration2.blower + 1)
|
132
|
+
times.times do
|
133
|
+
toggle_blower
|
134
|
+
sleep(0.1)
|
135
|
+
end
|
103
136
|
end
|
104
137
|
|
105
138
|
# high range is 80-104 for F, 26-40 for C (by 0.5)
|
@@ -113,5 +146,41 @@ module BWA
|
|
113
146
|
hour |= 0x80 if twenty_four_hour_time
|
114
147
|
send_message("\x0a\xbf\x21".force_encoding(Encoding::ASCII_8BIT) + hour.chr + minute.chr)
|
115
148
|
end
|
149
|
+
|
150
|
+
def set_temperature_scale(scale)
|
151
|
+
raise ArgumentError, "scale must be :fahrenheit or :celsius" unless %I{fahrenheit :celsius}.include?(scale)
|
152
|
+
arg = scale == :fahrenheit ? 0 : 1
|
153
|
+
send_message("\x0a\xbf\x27\x01".force_encoding(Encoding::ASCII_8BIT) + arg.chr)
|
154
|
+
end
|
155
|
+
|
156
|
+
def toggle_temperature_range
|
157
|
+
toggle_item(0x50)
|
158
|
+
end
|
159
|
+
|
160
|
+
def set_temperature_range(desired)
|
161
|
+
return unless last_status
|
162
|
+
return if last_status.temperature_range == desired
|
163
|
+
toggle_temperature_range
|
164
|
+
end
|
165
|
+
|
166
|
+
def toggle_heating_mode
|
167
|
+
toggle_item(0x51)
|
168
|
+
end
|
169
|
+
|
170
|
+
HEATING_MODES = %I{ready rest ready_in_rest}.freeze
|
171
|
+
def set_heating_mode(desired)
|
172
|
+
raise ArgumentError, "heating_mode must be :ready or :rest" unless %I{ready rest}.include?(desired)
|
173
|
+
return unless last_status
|
174
|
+
times = if last_status.heating_mode == :ready && desired == :rest ||
|
175
|
+
last_status.heating_mode == :rest && desired == :ready ||
|
176
|
+
last_status.heating_mode == :ready_in_rest && desired == :rest
|
177
|
+
1
|
178
|
+
elsif last_status.heating_mode == :ready_in_rest && desired == :ready
|
179
|
+
2
|
180
|
+
else
|
181
|
+
0
|
182
|
+
end
|
183
|
+
times.times { toggle_heating_mode }
|
184
|
+
end
|
116
185
|
end
|
117
186
|
end
|
data/lib/bwa/message.rb
CHANGED
@@ -17,48 +17,36 @@ module BWA
|
|
17
17
|
@messages << klass
|
18
18
|
end
|
19
19
|
|
20
|
-
def parse(
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
data
|
20
|
+
def parse(data)
|
21
|
+
offset = -1
|
22
|
+
message_type = length = message_class = nil
|
23
|
+
loop do
|
24
|
+
offset += 1
|
25
|
+
return nil if data.length - offset < 5
|
26
|
+
|
27
|
+
next unless data[offset] == '~'
|
28
|
+
length = data[offset + 1].ord
|
29
|
+
# impossible message
|
30
|
+
next if length < 5
|
31
|
+
|
32
|
+
# don't have enough data for what this message wants;
|
33
|
+
# it could be garbage on the line so keep scanning
|
34
|
+
next if length + 2 > data.length - offset
|
35
|
+
|
36
|
+
next unless data[offset + length + 1] == '~'
|
37
|
+
|
38
|
+
next unless CRC.checksum(data.slice(offset + 1, length - 1)) == data[offset + length].ord
|
39
|
+
break
|
26
40
|
end
|
27
41
|
|
28
|
-
data.
|
29
|
-
|
42
|
+
puts "discarding invalid data prior to message #{data[0...offset].unpack('H*').first}" unless offset == 0
|
43
|
+
#puts "read #{data.slice(offset, length + 2).unpack('H*').first}"
|
30
44
|
|
31
|
-
|
32
|
-
bytes = data.bytes
|
33
|
-
bytes.shift
|
34
|
-
bytes.reverse.each { |byte| io.ungetbyte(byte) }
|
35
|
-
raise InvalidMessage.new("Message has bogus length: #{length}", data)
|
36
|
-
end
|
37
|
-
|
38
|
-
data.concat(io.read(length))
|
39
|
-
if data.length != length + 2
|
40
|
-
data.bytes.reverse.each { |b| io.ungetbyte(b) }
|
41
|
-
raise InvalidMessage.new("Incorrect data length (received #{data.length - 2}, expected #{length})", data)
|
42
|
-
end
|
43
|
-
|
44
|
-
message_type = data[2..4]
|
45
|
+
message_type = data.slice(offset + 2, 3)
|
45
46
|
klass = @messages.find { |k| k::MESSAGE_TYPE == message_type }
|
46
47
|
|
47
|
-
unless data[-1] == '~'
|
48
|
-
bytes = data.bytes
|
49
|
-
bytes.shift
|
50
|
-
bytes.reverse.each { |b| io.ungetbyte(b) }
|
51
|
-
raise InvalidMessage.new("Missing trailing message indicator", data)
|
52
|
-
end
|
53
|
-
|
54
|
-
unless CRC.checksum(data[1...-2]) == data[-2].ord
|
55
|
-
bytes = data.bytes
|
56
|
-
bytes.shift
|
57
|
-
bytes.reverse.each { |b| io.ungetbyte(b) }
|
58
|
-
raise InvalidMessage.new("Invalid checksum", data)
|
59
|
-
end
|
60
48
|
|
61
|
-
return nil if [
|
49
|
+
return [nil, offset + length + 2] if [
|
62
50
|
"\xfe\xbf\x00".force_encoding(Encoding::ASCII_8BIT),
|
63
51
|
"\x10\xbf\xe1".force_encoding(Encoding::ASCII_8BIT),
|
64
52
|
"\x10\xbf\x07".force_encoding(Encoding::ASCII_8BIT)].include?(message_type)
|
@@ -67,9 +55,9 @@ module BWA
|
|
67
55
|
raise InvalidMessage.new("Unrecognized data length (#{length}) for message #{klass}", data) unless length - 5 == klass::MESSAGE_LENGTH
|
68
56
|
|
69
57
|
message = klass.new
|
70
|
-
message.parse(data
|
71
|
-
message.instance_variable_set(:@raw_data, data)
|
72
|
-
message
|
58
|
+
message.parse(data.slice(offset + 5, length - 5))
|
59
|
+
message.instance_variable_set(:@raw_data, data.slice(offset, length + 2))
|
60
|
+
[message, offset + length + 2]
|
73
61
|
end
|
74
62
|
|
75
63
|
def format_time(hour, minute, twenty_four_hour_time = true)
|
@@ -3,6 +3,22 @@ module BWA
|
|
3
3
|
class ControlConfiguration < Message
|
4
4
|
MESSAGE_TYPE = "\x0a\xbf\x24".force_encoding(Encoding::ASCII_8BIT)
|
5
5
|
MESSAGE_LENGTH = 21
|
6
|
+
|
7
|
+
attr_accessor :model, :version
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@model = ''
|
11
|
+
@version = 0
|
12
|
+
end
|
13
|
+
|
14
|
+
def parse(data)
|
15
|
+
self.version = "V#{data[2].ord}.#{data[3].ord}"
|
16
|
+
self.model = data[4..11].strip
|
17
|
+
end
|
18
|
+
|
19
|
+
def inspect
|
20
|
+
"#<BWA::Messages::ControlConfiguration #{model} #{version}>"
|
21
|
+
end
|
6
22
|
end
|
7
23
|
end
|
8
24
|
end
|
@@ -12,6 +28,52 @@ module BWA
|
|
12
28
|
class ControlConfiguration2 < Message
|
13
29
|
MESSAGE_TYPE = "\x0a\xbf\x2e".force_encoding(Encoding::ASCII_8BIT)
|
14
30
|
MESSAGE_LENGTH = 6
|
31
|
+
|
32
|
+
attr_accessor :pumps, :lights, :circ_pump, :blower, :mister, :aux
|
33
|
+
|
34
|
+
def initialize
|
35
|
+
self.pumps = Array.new(6, 0)
|
36
|
+
self.lights = Array.new(2, false)
|
37
|
+
self.circ_pump = false
|
38
|
+
self.blower = 0
|
39
|
+
self.mister = false
|
40
|
+
self.aux = Array.new(2, false)
|
41
|
+
end
|
42
|
+
|
43
|
+
def parse(data)
|
44
|
+
flags = data[0].ord
|
45
|
+
pumps[0] = flags & 0x03
|
46
|
+
pumps[1] = (flags >> 2) & 0x03
|
47
|
+
pumps[2] = (flags >> 4) & 0x03
|
48
|
+
pumps[3] = (flags >> 6) & 0x03
|
49
|
+
flags = data[1].ord
|
50
|
+
pumps[4] = flags & 0x03
|
51
|
+
pumps[5] = (flags >> 6) & 0x03
|
52
|
+
flags = data[2].ord
|
53
|
+
lights[0] = (flags & 0x03 != 0)
|
54
|
+
lights[1] = ((flags >> 6) & 0x03 != 0)
|
55
|
+
flags = data[3].ord
|
56
|
+
self.blower = flags & 0x03
|
57
|
+
self.circ_pump = ((flags >> 6) & 0x03 != 0)
|
58
|
+
flags = data[4].ord
|
59
|
+
self.mister = (flags & 0x30 != 0)
|
60
|
+
aux[0] = (flags & 0x01 != 0)
|
61
|
+
aux[1] = (flags & 0x02 != 0)
|
62
|
+
end
|
63
|
+
|
64
|
+
def inspect
|
65
|
+
result = "#<BWA::Messages::ControlConfiguration2 "
|
66
|
+
items = []
|
67
|
+
|
68
|
+
items << "pumps=#{pumps.inspect}"
|
69
|
+
items << "lights=#{lights.inspect}"
|
70
|
+
items << "circ_pump" if circ_pump
|
71
|
+
items << "blower=#{blower}" if blower != 0
|
72
|
+
items << "mister" if mister
|
73
|
+
items << "aux=#{aux.inspect}"
|
74
|
+
|
75
|
+
result << items.join(' ') << ">"
|
76
|
+
end
|
15
77
|
end
|
16
78
|
end
|
17
79
|
end
|
data/lib/bwa/messages/status.rb
CHANGED
@@ -5,12 +5,16 @@ module BWA
|
|
5
5
|
:heating_mode,
|
6
6
|
:temperature_scale,
|
7
7
|
:twenty_four_hour_time,
|
8
|
+
:filter,
|
8
9
|
:heating,
|
9
10
|
:temperature_range,
|
10
11
|
:hour, :minute,
|
11
12
|
:circ_pump,
|
12
|
-
:
|
13
|
-
:
|
13
|
+
:blower,
|
14
|
+
:pumps,
|
15
|
+
:lights,
|
16
|
+
:mister,
|
17
|
+
:aux,
|
14
18
|
:current_temperature, :set_temperature
|
15
19
|
|
16
20
|
MESSAGE_TYPE = "\xff\xaf\x13".force_encoding(Encoding::ASCII_8BIT)
|
@@ -21,12 +25,15 @@ module BWA
|
|
21
25
|
self.heating_mode = :ready
|
22
26
|
@temperature_scale = :fahrenheit
|
23
27
|
self.twenty_four_hour_time = false
|
28
|
+
self.filter = Array.new(2, false)
|
24
29
|
self.heating = false
|
25
30
|
self.temperature_range = :high
|
26
31
|
self.hour = self.minute = 0
|
27
32
|
self.circ_pump = false
|
28
|
-
self.
|
29
|
-
self.
|
33
|
+
self.pumps = Array.new(6, 0)
|
34
|
+
self.lights = Array.new(2, false)
|
35
|
+
self.mister = false
|
36
|
+
self.aux = Array.new(2, false)
|
30
37
|
self.set_temperature = 100
|
31
38
|
end
|
32
39
|
|
@@ -42,16 +49,30 @@ module BWA
|
|
42
49
|
flags = data[9].ord
|
43
50
|
self.temperature_scale = (flags & 0x01 == 0x01) ? :celsius : :fahrenheit
|
44
51
|
self.twenty_four_hour_time = (flags & 0x02 == 0x02)
|
52
|
+
filter[0] = (flags & 0x04 != 0)
|
53
|
+
filter[1] = (flags & 0x08 != 0)
|
45
54
|
flags = data[10].ord
|
46
55
|
self.heating = (flags & 0x30 != 0)
|
47
56
|
self.temperature_range = (flags & 0x04 == 0x04) ? :high : :low
|
48
57
|
flags = data[11].ord
|
49
|
-
|
50
|
-
|
58
|
+
pumps[0] = flags & 0x03
|
59
|
+
pumps[1] = (flags >> 2) & 0x03
|
60
|
+
pumps[2] = (flags >> 4) & 0x03
|
61
|
+
pumps[3] = (flags >> 6) & 0x03
|
62
|
+
flags = data[12].ord
|
63
|
+
pumps[4] = flags & 0x03
|
64
|
+
pumps[5] = (flags >> 2) & 0x03
|
65
|
+
|
51
66
|
flags = data[13].ord
|
52
67
|
self.circ_pump = (flags & 0x02 == 0x02)
|
68
|
+
self.blower = (flags & 0x0C == 0x0C)
|
53
69
|
flags = data[14].ord
|
54
|
-
|
70
|
+
lights[0] = (flags & 0x03 != 0)
|
71
|
+
lights[1] = ((flags >> 2) & 0x03 != 0)
|
72
|
+
flags = data[15].ord
|
73
|
+
self.mister = (flags & 0x01 == 0x01)
|
74
|
+
aux[0] = (flags & 0x08 != 0)
|
75
|
+
aux[1] = (flags & 0x10 != 0)
|
55
76
|
self.hour = data[3].ord
|
56
77
|
self.minute = data[4].ord
|
57
78
|
self.current_temperature = data[2].ord
|
@@ -134,13 +155,16 @@ module BWA
|
|
134
155
|
items << "priming" if priming
|
135
156
|
items << self.class.format_time(hour, minute, twenty_four_hour_time)
|
136
157
|
items << "#{current_temperature || '--'}/#{set_temperature}º#{temperature_scale.to_s[0].upcase}"
|
158
|
+
items << "filter=#{filter.inspect}"
|
137
159
|
items << heating_mode
|
138
160
|
items << "heating" if heating
|
139
161
|
items << temperature_range
|
140
162
|
items << "circ_pump" if circ_pump
|
141
|
-
items << "
|
142
|
-
items << "
|
143
|
-
items << "
|
163
|
+
items << "blower" if blower
|
164
|
+
items << "pumps=#{pumps.inspect}"
|
165
|
+
items << "lights=#{lights.inspect}"
|
166
|
+
items << "aux=#{aux.inspect}"
|
167
|
+
items << "mister" if mister
|
144
168
|
|
145
169
|
result << items.join(' ') << ">"
|
146
170
|
end
|
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.1.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: 2020-
|
11
|
+
date: 2020-06-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: digest-crc
|
@@ -25,33 +25,47 @@ dependencies:
|
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0.4'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: mqtt
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: 0.5.0
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
40
|
+
version: 0.5.0
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
|
-
name:
|
42
|
+
name: net-telnet-rfc2217
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: 0.
|
47
|
+
version: 0.0.3
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: 0.
|
54
|
+
version: 0.0.3
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: serialport
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.3.1
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 1.3.1
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
70
|
name: byebug
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|