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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7daff60d63c09a23f5628f69102fa722745c343d5c3641d7d87f09e47be7549a
4
- data.tar.gz: b70859344d26e0f7a54b3a8ffea6389637683c5b5f31de5a0defb8bb52176c0a
3
+ metadata.gz: 474d2a26cc4dca552bcb29ff22aa486903d73b7707c40d9c160e77eb2baed5fb
4
+ data.tar.gz: f085680c104a2d42e1755cf63a44538a376e875820c12468f0fb955c39f69f07
5
5
  SHA512:
6
- metadata.gz: '085e33a524d9410cbd069592804a716448031eece734bf5d2209318ef387ef9b2cda6fe941f4956681015889ca70f6f4429805eabfbc56e0bd23322262c3f850'
7
- data.tar.gz: 749b99bc33e6df19dec5f276206a62556310be025daec5b8c71f061f8fd45510d7a6f58f201655f8b9cfc0bfaf826cf7c7b5b3d5b43755bbedc44ed87bdd70b5
6
+ metadata.gz: d6f16d2ae55926e145489f3b61d9df9be692e2a5a3d21920721ef657301d1e099fe28381e8c7eea9711efd2b314079b818e2f33bbda66234423c3be4c0ccacd5
7
+ data.tar.gz: d7a6d5640c2902c03e22a6f4b11f447b3f4e3b6c9da757070b7db363b8dbb4e1b77ecfbce1553779f68b077e5ac2eac98f06d5ca7c7abafc9e4433459b211e81
@@ -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
- if message.hour != now.hour || message.minute != now.min
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 unless %w{ready rest ready_in_rest}.include?(value)
63
- # @bwa.set_heating_mode(value.to_sym)
64
- when "temperaturescale"
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
- # @bwa.set_temperature_scale(value.to_sym)
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
- # @bwa.set_temperature_range(value.to_sym)
74
- when %r{^spa/(pump[12])/set}
75
- @bwa.send(:"set_#{$1}", value.to_i)
76
- when "spa/light1/set"
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.set_light1(value == 'true')
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
- publish("spa/$properties", "priming,heatingmode,temperaturescale,24htime,heating,temperaturerange,circpump,pump1,pump2,light1,currenttemperature,settemperature")
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("$state", "ready")
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
@@ -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(host, port = 4257)
8
- if host =~ %r{^/dev}
9
- require 'serialport'
10
- @io = SerialPort.open(host, "baud" => 115200)
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 'socket'
14
- @io = TCPSocket.new(host, port)
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
- while message.nil?
21
- begin
22
- message = Message.parse(@io)
23
- if message.is_a?(Messages::Ready) && (msg = @queue.shift)
24
- @io.write(msg)
25
- end
26
- rescue BWA::InvalidMessage => e
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(args)
71
- send_message("\x0a\xbf\x11#{args}")
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 toggle_light1
75
- toggle_item("\x11\x00")
96
+ def toggle_mister
97
+ toggle_item(0x0e)
76
98
  end
77
99
 
78
- def toggle_pump1
79
- toggle_item("\x04\x00")
100
+ def toggle_blower
101
+ toggle_item(0x0c)
80
102
  end
81
103
 
82
- def toggle_pump2
83
- toggle_item("\x05\x00")
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
- (1..2).each do |i|
113
+ %w{light aux}.each do |type|
87
114
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
88
- def set_pump#{i}(desired)
89
- return unless last_status
90
- times = (desired - last_status.pump#{i}) % 3
91
- times.times do
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 set_light1(desired)
123
+ def set_mister(desired)
100
124
  return unless last_status
101
- return if last_status.light1 == desired
102
- toggle_light1
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
@@ -17,48 +17,36 @@ module BWA
17
17
  @messages << klass
18
18
  end
19
19
 
20
- def parse(io)
21
- io = StringIO.new(io) if io.is_a?(String)
22
- data = ''
23
- # skim through until a start-of-message indicator
24
- until data[0] == '~'
25
- data = io.read(1)
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.concat(io.read(1))
29
- length = data[-1].ord
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
- if length < 5
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[5..-2])
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
@@ -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
- :pump1, :pump2,
13
- :light1,
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.pump1 = self.pump2 = 0
29
- self.light1 = false
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
- self.pump1 = flags & 0x03
50
- self.pump2 = (flags / 4) & 0x03
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
- self.light1 = (flags & 0x03 == 0x03)
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 << "pump1=#{pump1}" unless pump1 == 0
142
- items << "pump2=#{pump2}" unless pump2 == 0
143
- items << "light1" if light1
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
@@ -1,3 +1,3 @@
1
1
  module BWA
2
- VERSION = '1.0.0'
2
+ VERSION = '1.1.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.0.0
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-05-12 00:00:00.000000000 Z
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: serialport
28
+ name: mqtt
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 1.3.1
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: 1.3.1
40
+ version: 0.5.0
41
41
  - !ruby/object:Gem::Dependency
42
- name: mqtt
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.5.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.5.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