balboa_worldwide_app 1.3.0 → 2.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/exe/bwa_client +41 -0
- data/exe/bwa_mqtt_bridge +399 -0
- data/{bin → exe}/bwa_proxy +3 -2
- data/{bin → exe}/bwa_server +3 -2
- data/lib/balboa_worldwide_app.rb +3 -1
- data/lib/bwa/client.rb +123 -91
- data/lib/bwa/crc.rb +3 -1
- data/lib/bwa/discovery.rb +18 -17
- data/lib/bwa/logger.rb +9 -7
- data/lib/bwa/message.rb +67 -53
- data/lib/bwa/messages/configuration.rb +3 -1
- data/lib/bwa/messages/configuration_request.rb +3 -1
- data/lib/bwa/messages/control_configuration.rb +12 -9
- data/lib/bwa/messages/control_configuration_request.rb +13 -11
- data/lib/bwa/messages/filter_cycles.rb +50 -22
- data/lib/bwa/messages/ready.rb +3 -1
- data/lib/bwa/messages/{set_temperature.rb → set_target_temperature.rb} +5 -3
- data/lib/bwa/messages/set_temperature_scale.rb +5 -3
- data/lib/bwa/messages/set_time.rb +4 -2
- data/lib/bwa/messages/status.rb +53 -46
- data/lib/bwa/messages/toggle_item.rb +29 -27
- data/lib/bwa/proxy.rb +17 -18
- data/lib/bwa/server.rb +16 -14
- data/lib/bwa/version.rb +3 -1
- metadata +76 -24
- data/bin/bwa_client +0 -43
- data/bin/bwa_mqtt_bridge +0 -614
data/lib/bwa/client.rb
CHANGED
@@ -1,28 +1,59 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "forwardable"
|
4
|
+
require "uri"
|
5
|
+
|
6
|
+
require "bwa/logger"
|
7
|
+
require "bwa/message"
|
5
8
|
|
6
9
|
module BWA
|
7
10
|
class Client
|
8
|
-
|
11
|
+
extend Forwardable
|
12
|
+
|
13
|
+
attr_reader :status, :control_configuration, :configuration, :filter_cycles
|
14
|
+
|
15
|
+
delegate model: :control_configuration
|
16
|
+
delegate %i[hold
|
17
|
+
hold?
|
18
|
+
priming
|
19
|
+
priming?
|
20
|
+
heating_mode
|
21
|
+
temperature_scale
|
22
|
+
twenty_four_hour_time
|
23
|
+
twenty_four_hour_time?
|
24
|
+
heating
|
25
|
+
heating?
|
26
|
+
temperature_range
|
27
|
+
current_temperature
|
28
|
+
target_temperature
|
29
|
+
circulation_pump
|
30
|
+
blower
|
31
|
+
mister
|
32
|
+
pumps
|
33
|
+
lights
|
34
|
+
aux] => :status
|
9
35
|
|
10
36
|
def initialize(uri)
|
11
37
|
uri = URI.parse(uri)
|
12
|
-
|
13
|
-
|
38
|
+
case uri.scheme
|
39
|
+
when "tcp"
|
40
|
+
require "socket"
|
14
41
|
@io = TCPSocket.new(uri.host, uri.port || 4257)
|
15
|
-
|
16
|
-
require
|
17
|
-
@io = Net::Telnet::RFC2217.new("Host" => uri.host, "Port" => uri.port || 23, "baud" =>
|
42
|
+
when "telnet", "rfc2217"
|
43
|
+
require "net/telnet/rfc2217"
|
44
|
+
@io = Net::Telnet::RFC2217.new("Host" => uri.host, "Port" => uri.port || 23, "baud" => 115_200)
|
18
45
|
@queue = []
|
19
46
|
else
|
20
|
-
require
|
21
|
-
@io = CCutrer::SerialPort.new(uri.path, baud:
|
47
|
+
require "ccutrer-serialport"
|
48
|
+
@io = CCutrer::SerialPort.new(uri.path, baud: 115_200)
|
22
49
|
@queue = []
|
23
50
|
end
|
24
51
|
@src = 0x0a
|
25
|
-
@buffer = ""
|
52
|
+
@buffer = +""
|
53
|
+
end
|
54
|
+
|
55
|
+
def full_configuration?
|
56
|
+
status && control_configuration && configuration && filter_cycles
|
26
57
|
end
|
27
58
|
|
28
59
|
def poll
|
@@ -45,18 +76,20 @@ module BWA
|
|
45
76
|
end
|
46
77
|
|
47
78
|
if message.is_a?(Messages::Ready) && (msg = @queue&.shift)
|
48
|
-
|
79
|
+
unless BWA.verbosity < 1 && msg[3..4] == Messages::ControlConfigurationRequest::MESSAGE_TYPE
|
80
|
+
BWA.logger.debug "wrote: #{BWA.raw2str(msg)}"
|
81
|
+
end
|
49
82
|
@io.write(msg)
|
50
83
|
end
|
51
|
-
@
|
52
|
-
@
|
53
|
-
@
|
54
|
-
@
|
84
|
+
@status = message.dup if message.is_a?(Messages::Status)
|
85
|
+
@filter_cycles = message.dup if message.is_a?(Messages::FilterCycles)
|
86
|
+
@control_configuration = message.dup if message.is_a?(Messages::ControlConfiguration)
|
87
|
+
@configuration = message.dup if message.is_a?(Messages::ControlConfiguration2)
|
55
88
|
message
|
56
89
|
end
|
57
90
|
|
58
91
|
def messages_pending?
|
59
|
-
|
92
|
+
!!@io.wait_readable(0)
|
60
93
|
end
|
61
94
|
|
62
95
|
def drain_message_queue
|
@@ -65,12 +98,16 @@ module BWA
|
|
65
98
|
|
66
99
|
def send_message(message)
|
67
100
|
message.src = @src
|
68
|
-
BWA.logger.info " to spa: #{message.inspect}" unless BWA.verbosity < 1 && message.is_a?(Messages::ControlConfigurationRequest)
|
69
101
|
full_message = message.serialize
|
102
|
+
unless BWA.verbosity < 1 && message.is_a?(Messages::ControlConfigurationRequest)
|
103
|
+
BWA.logger.info " to spa: #{message.inspect}"
|
104
|
+
end
|
70
105
|
if @queue
|
71
106
|
@queue.push(full_message)
|
72
107
|
else
|
73
|
-
|
108
|
+
unless BWA.verbosity < 1 && message.is_a?(Messages::ControlConfigurationRequest)
|
109
|
+
BWA.logger.debug "wrote: #{BWA.raw2str(full_message)}"
|
110
|
+
end
|
74
111
|
@io.write(full_message)
|
75
112
|
end
|
76
113
|
end
|
@@ -95,12 +132,16 @@ module BWA
|
|
95
132
|
send_message(Messages::ToggleItem.new(item))
|
96
133
|
end
|
97
134
|
|
98
|
-
def toggle_pump(
|
99
|
-
toggle_item(
|
135
|
+
def toggle_pump(index)
|
136
|
+
toggle_item(index + 0x04)
|
100
137
|
end
|
101
138
|
|
102
|
-
def toggle_light(
|
103
|
-
toggle_item(
|
139
|
+
def toggle_light(index)
|
140
|
+
toggle_item(index + 0x11)
|
141
|
+
end
|
142
|
+
|
143
|
+
def toggle_aux(index)
|
144
|
+
toggle_item(index + 0x16)
|
104
145
|
end
|
105
146
|
|
106
147
|
def toggle_mister
|
@@ -115,90 +156,79 @@ module BWA
|
|
115
156
|
toggle_item(:hold)
|
116
157
|
end
|
117
158
|
|
118
|
-
def set_pump(
|
119
|
-
return unless
|
120
|
-
|
159
|
+
def set_pump(index, desired)
|
160
|
+
return unless status && configuration
|
161
|
+
|
162
|
+
desired = 0 if desired == false
|
163
|
+
desired = 1 if desired == true
|
164
|
+
times = (desired - status.pumps[index]) % (configuration.pumps[index] + 1)
|
121
165
|
times.times do
|
122
|
-
toggle_pump(
|
166
|
+
toggle_pump(index)
|
123
167
|
sleep(0.1)
|
124
168
|
end
|
125
169
|
end
|
126
170
|
|
127
|
-
%
|
171
|
+
%i[light aux].each do |type|
|
172
|
+
suffix = "s" if type == :light
|
128
173
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
129
|
-
def set_#{type}(
|
130
|
-
return unless
|
131
|
-
return if
|
132
|
-
|
174
|
+
def set_#{type}(index, desired)
|
175
|
+
return unless status
|
176
|
+
return if status.#{type}#{suffix}[index] == desired
|
177
|
+
|
178
|
+
toggle_#{type}(index)
|
133
179
|
end
|
134
180
|
RUBY
|
135
181
|
end
|
136
182
|
|
137
|
-
def
|
138
|
-
return unless
|
139
|
-
return if
|
183
|
+
def mister=(desired)
|
184
|
+
return unless status
|
185
|
+
return if status.mister == desired
|
186
|
+
|
140
187
|
toggle_mister
|
141
188
|
end
|
142
189
|
|
143
|
-
def
|
144
|
-
return unless
|
145
|
-
|
190
|
+
def blower=(desired)
|
191
|
+
return unless status && configuration
|
192
|
+
|
193
|
+
desired = 0 if desired == false
|
194
|
+
desired = 1 if desired == true
|
195
|
+
times = (desired - status.blower) % (configuration.blower + 1)
|
146
196
|
times.times do
|
147
197
|
toggle_blower
|
148
198
|
sleep(0.1)
|
149
199
|
end
|
150
200
|
end
|
151
201
|
|
152
|
-
def
|
153
|
-
return unless
|
154
|
-
return if
|
202
|
+
def hold=(desired)
|
203
|
+
return unless status
|
204
|
+
return if status.hold == desired
|
205
|
+
|
155
206
|
toggle_hold
|
156
207
|
end
|
157
208
|
|
158
209
|
# high range is 80-106 for F, 26-40 for C (by 0.5)
|
159
210
|
# low range is 50-99 for F, 10-26 for C (by 0.5)
|
160
|
-
def
|
161
|
-
return unless
|
162
|
-
return if
|
211
|
+
def target_temperature=(desired)
|
212
|
+
return unless status
|
213
|
+
return if status.target_temperature == desired
|
163
214
|
|
164
|
-
desired *= 2 if
|
165
|
-
send_message(Messages::
|
215
|
+
desired *= 2 if (status && status.temperature_scale == :celsius) || desired < 50
|
216
|
+
send_message(Messages::SetTargetTemperature.new(desired.round))
|
166
217
|
end
|
167
218
|
|
168
|
-
def set_time(hour, minute, twenty_four_hour_time
|
219
|
+
def set_time(hour, minute, twenty_four_hour_time: false)
|
169
220
|
send_message(Messages::SetTime.new(hour, minute, twenty_four_hour_time))
|
170
221
|
end
|
171
222
|
|
172
|
-
def
|
173
|
-
raise ArgumentError, "scale must be :fahrenheit or :celsius" unless %I
|
223
|
+
def temperature_scale=(scale)
|
224
|
+
raise ArgumentError, "scale must be :fahrenheit or :celsius" unless %I[fahrenheit celsius].include?(scale)
|
225
|
+
|
174
226
|
send_message(Messages::SetTemperatureScale.new(scale))
|
175
227
|
end
|
176
228
|
|
177
|
-
def
|
178
|
-
|
179
|
-
|
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
|
229
|
+
def update_filter_cycles(new_filter_cycles)
|
230
|
+
send_message(new_filter_cycles)
|
231
|
+
@filter_cycles = new_filter_cycles.dup
|
202
232
|
request_filter_configuration
|
203
233
|
end
|
204
234
|
|
@@ -206,9 +236,10 @@ module BWA
|
|
206
236
|
toggle_item(0x50)
|
207
237
|
end
|
208
238
|
|
209
|
-
def
|
210
|
-
return unless
|
211
|
-
return if
|
239
|
+
def temperature_range=(desired)
|
240
|
+
return unless status
|
241
|
+
return if status.temperature_range == desired
|
242
|
+
|
212
243
|
toggle_temperature_range
|
213
244
|
end
|
214
245
|
|
@@ -216,19 +247,20 @@ module BWA
|
|
216
247
|
toggle_item(:heating_mode)
|
217
248
|
end
|
218
249
|
|
219
|
-
HEATING_MODES = %I
|
220
|
-
def
|
221
|
-
raise ArgumentError, "heating_mode must be :ready or :rest" unless %I
|
222
|
-
return unless
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
250
|
+
HEATING_MODES = %I[ready rest ready_in_rest].freeze
|
251
|
+
def heating_mode=(desired)
|
252
|
+
raise ArgumentError, "heating_mode must be :ready or :rest" unless %I[ready rest].include?(desired)
|
253
|
+
return unless status
|
254
|
+
|
255
|
+
times = if (status.heating_mode == :ready && desired == :rest) ||
|
256
|
+
(status.heating_mode == :rest && desired == :ready) ||
|
257
|
+
(status.heating_mode == :ready_in_rest && desired == :rest)
|
258
|
+
1
|
259
|
+
elsif status.heating_mode == :ready_in_rest && desired == :ready
|
260
|
+
2
|
261
|
+
else
|
262
|
+
0
|
263
|
+
end
|
232
264
|
times.times { toggle_heating_mode }
|
233
265
|
end
|
234
266
|
end
|
data/lib/bwa/crc.rb
CHANGED
data/lib/bwa/discovery.rb
CHANGED
@@ -1,27 +1,27 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "socket"
|
4
|
+
require "bwa/logger"
|
3
5
|
|
4
6
|
module BWA
|
5
7
|
class Discovery
|
6
8
|
class << self
|
7
|
-
def discover(timeout = 5, exhaustive
|
9
|
+
def discover(timeout = 5, exhaustive: false)
|
8
10
|
socket = UDPSocket.new
|
9
11
|
socket.bind("0.0.0.0", 0)
|
10
12
|
socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_BROADCAST, true)
|
11
|
-
socket.sendmsg("Discovery: Who is out there?", 0, Socket.sockaddr_in(
|
13
|
+
socket.sendmsg("Discovery: Who is out there?", 0, Socket.sockaddr_in(30_303, "255.255.255.255"))
|
12
14
|
spas = {}
|
13
15
|
loop do
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
else
|
24
|
-
break
|
16
|
+
break unless socket.wait_readable(timeout)
|
17
|
+
|
18
|
+
msg, ip = socket.recvfrom(64)
|
19
|
+
ip = ip[2]
|
20
|
+
name, mac = msg.split("\r\n")
|
21
|
+
name.strip!
|
22
|
+
if mac.start_with?("00-15-27-")
|
23
|
+
spas[ip] = name
|
24
|
+
break unless exhaustive
|
25
25
|
end
|
26
26
|
end
|
27
27
|
spas
|
@@ -29,11 +29,12 @@ module BWA
|
|
29
29
|
|
30
30
|
def advertise
|
31
31
|
socket = UDPSocket.new
|
32
|
-
socket.bind("0.0.0.0",
|
32
|
+
socket.bind("0.0.0.0", 30_303)
|
33
33
|
msg = "BWGSPA\r\n00-15-27-00-00-01\r\n"
|
34
34
|
loop do
|
35
35
|
data, addr = socket.recvfrom(32)
|
36
|
-
next unless data ==
|
36
|
+
next unless data == "Discovery: Who is out there?"
|
37
|
+
|
37
38
|
ip = addr.last
|
38
39
|
BWA.logger.info "Advertising to #{ip}"
|
39
40
|
socket.sendmsg(msg, 0, Socket.sockaddr_in(addr[1], ip))
|
data/lib/bwa/logger.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "logger"
|
2
4
|
|
3
5
|
module BWA
|
4
6
|
# This module logs to stdout by default, or you can provide a logger as BWA.logger.
|
@@ -23,10 +25,10 @@ module BWA
|
|
23
25
|
attr_writer :logger, :verbosity
|
24
26
|
|
25
27
|
def logger
|
26
|
-
@logger ||= Logger.new(
|
27
|
-
|
28
|
-
log.level = ENV.fetch("LOG_LEVEL","WARN")
|
29
|
-
log.formatter = proc do |severity,
|
28
|
+
@logger ||= Logger.new($stdout).tap do |log|
|
29
|
+
$stdout.sync = true
|
30
|
+
log.level = ENV.fetch("LOG_LEVEL", "WARN")
|
31
|
+
log.formatter = proc do |severity, _datetime, _progname, msg|
|
30
32
|
"#{severity[0..0]}, #{msg2logstr(msg)}\n"
|
31
33
|
end
|
32
34
|
end
|
@@ -42,14 +44,14 @@ module BWA
|
|
42
44
|
when ::String
|
43
45
|
msg
|
44
46
|
when ::Exception
|
45
|
-
"#{
|
47
|
+
"#{msg.message} (#{msg.class})\n#{msg.backtrace&.join("\n")}"
|
46
48
|
else
|
47
49
|
msg.inspect
|
48
50
|
end
|
49
51
|
end
|
50
52
|
|
51
53
|
def raw2str(data)
|
52
|
-
data.
|
54
|
+
data.unpack1("H*").gsub!(/(..)/, "\\1 ").chop!
|
53
55
|
end
|
54
56
|
end
|
55
57
|
end
|
data/lib/bwa/message.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bwa/logger"
|
4
|
+
require "bwa/crc"
|
3
5
|
|
4
6
|
module BWA
|
5
7
|
class InvalidMessage < RuntimeError
|
@@ -19,53 +21,58 @@ module BWA
|
|
19
21
|
|
20
22
|
class << self
|
21
23
|
def inherited(klass)
|
24
|
+
super
|
25
|
+
|
22
26
|
@messages ||= []
|
23
27
|
@messages << klass
|
24
28
|
end
|
25
29
|
|
26
30
|
# Ignore (parse and throw away) messages of these types.
|
27
31
|
IGNORED_MESSAGES = [
|
28
|
-
"\xbf\x00".force_encoding(Encoding::ASCII_8BIT),
|
29
|
-
"\xbf\xe1".force_encoding(Encoding::ASCII_8BIT),
|
30
|
-
"\xbf\x07".force_encoding(Encoding::ASCII_8BIT)
|
31
|
-
]
|
32
|
+
(+"\xbf\x00").force_encoding(Encoding::ASCII_8BIT), # request for new clients
|
33
|
+
(+"\xbf\xe1").force_encoding(Encoding::ASCII_8BIT),
|
34
|
+
(+"\xbf\x07").force_encoding(Encoding::ASCII_8BIT) # nothing to send
|
35
|
+
].freeze
|
32
36
|
|
33
37
|
# Don't log messages of these types, even in DEBUG mode.
|
34
38
|
# They are very frequent and would swamp the logs.
|
35
39
|
def common_messages
|
36
|
-
@
|
40
|
+
@common_messages ||= begin
|
37
41
|
msgs = []
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
42
|
+
unless BWA.verbosity >= 1
|
43
|
+
msgs += [
|
44
|
+
Messages::Status::MESSAGE_TYPE,
|
45
|
+
(+"\xbf\xe1").force_encoding(Encoding::ASCII_8BIT)
|
46
|
+
]
|
47
|
+
end
|
48
|
+
unless BWA.verbosity >= 2
|
49
|
+
msgs += [
|
50
|
+
(+"\xbf\x00").force_encoding(Encoding::ASCII_8BIT),
|
51
|
+
(+"\xbf\xe1").force_encoding(Encoding::ASCII_8BIT),
|
52
|
+
Messages::Ready::MESSAGE_TYPE,
|
53
|
+
(+"\xbf\x07").force_encoding(Encoding::ASCII_8BIT)
|
54
|
+
]
|
55
|
+
end
|
48
56
|
msgs
|
49
57
|
end
|
50
|
-
@COMMON_MESSAGES
|
51
58
|
end
|
52
59
|
|
53
60
|
def parse(data)
|
54
61
|
offset = -1
|
55
|
-
message_type = length =
|
62
|
+
message_type = length = nil
|
56
63
|
loop do
|
57
64
|
offset += 1
|
58
65
|
# Not enough data for a full message; return and hope for more
|
59
66
|
return nil if data.length - offset < 5
|
60
67
|
|
61
68
|
# Keep scanning until message start char
|
62
|
-
next unless data[offset] ==
|
69
|
+
next unless data[offset] == "~"
|
63
70
|
|
64
71
|
# Read length (safe since we have at least 5 chars)
|
65
72
|
length = data[offset + 1].ord
|
66
73
|
|
67
74
|
# No message is this short or this long; keep scanning
|
68
|
-
next if length < 5
|
75
|
+
next if (length < 5) || (length >= "~".ord)
|
69
76
|
|
70
77
|
# don't have enough data for what this message wants;
|
71
78
|
# return and hope for more (yes this might cause a
|
@@ -74,7 +81,7 @@ module BWA
|
|
74
81
|
return nil if length + 2 > data.length - offset
|
75
82
|
|
76
83
|
# Not properly terminated; keep scanning
|
77
|
-
next unless data[offset + length + 1] ==
|
84
|
+
next unless data[offset + length + 1] == "~"
|
78
85
|
|
79
86
|
# Not a valid checksum; keep scanning
|
80
87
|
next unless CRC.checksum(data.slice(offset + 1, length - 1)) == data[offset + length].ord
|
@@ -84,8 +91,11 @@ module BWA
|
|
84
91
|
end
|
85
92
|
|
86
93
|
message_type = data.slice(offset + 3, 2)
|
87
|
-
BWA.logger.debug "discarding invalid data prior to message #{BWA.raw2str(data[0...offset])}" unless offset
|
88
|
-
|
94
|
+
BWA.logger.debug "discarding invalid data prior to message #{BWA.raw2str(data[0...offset])}" unless offset.zero?
|
95
|
+
unless common_messages.include?(message_type)
|
96
|
+
BWA.logger.debug " read: #{BWA.raw2str(data.slice(offset,
|
97
|
+
length + 2))}"
|
98
|
+
end
|
89
99
|
|
90
100
|
src = data[offset + 2].ord
|
91
101
|
klass = @messages.find { |k| k::MESSAGE_TYPE == message_type }
|
@@ -95,13 +105,18 @@ module BWA
|
|
95
105
|
|
96
106
|
if klass
|
97
107
|
valid_length = if klass::MESSAGE_LENGTH.respond_to?(:include?)
|
98
|
-
|
99
|
-
|
100
|
-
|
108
|
+
klass::MESSAGE_LENGTH.include?(length - 5)
|
109
|
+
else
|
110
|
+
length - 5 == klass::MESSAGE_LENGTH
|
111
|
+
end
|
112
|
+
unless valid_length
|
113
|
+
raise InvalidMessage.new("Unrecognized data length (#{length}) for message #{klass}",
|
114
|
+
data)
|
101
115
|
end
|
102
|
-
raise InvalidMessage.new("Unrecognized data length (#{length}) for message #{klass}", data) unless valid_length
|
103
116
|
else
|
104
|
-
BWA.logger.info
|
117
|
+
BWA.logger.info(
|
118
|
+
"Unrecognized message type #{BWA.raw2str(message_type)}: #{BWA.raw2str(data.slice(offset, length + 2))}"
|
119
|
+
)
|
105
120
|
klass = Unrecognized
|
106
121
|
end
|
107
122
|
|
@@ -113,53 +128,52 @@ module BWA
|
|
113
128
|
[message, offset + length + 2]
|
114
129
|
end
|
115
130
|
|
116
|
-
def format_time(hour, minute, twenty_four_hour_time
|
131
|
+
def format_time(hour, minute, twenty_four_hour_time: true)
|
117
132
|
if twenty_four_hour_time
|
118
|
-
|
133
|
+
format("%02d:%02d", hour, minute)
|
119
134
|
else
|
120
135
|
print_hour = hour % 12
|
121
|
-
print_hour = 12 if print_hour
|
122
|
-
|
136
|
+
print_hour = 12 if print_hour.zero?
|
137
|
+
format("%d:%02d%s", print_hour, minute, hour >= 12 ? "PM" : "AM")
|
123
138
|
end
|
124
|
-
"#{print_hour}:#{"%02d" % minute}#{am_pm}"
|
125
139
|
end
|
126
140
|
|
127
|
-
def format_duration(
|
128
|
-
"
|
141
|
+
def format_duration(minutes)
|
142
|
+
format("%d:%02d", minutes / 60, minutes % 60)
|
129
143
|
end
|
130
144
|
end
|
131
145
|
|
132
|
-
attr_reader :raw_data
|
146
|
+
attr_reader :raw_data
|
133
147
|
|
134
148
|
def initialize
|
135
149
|
# most messages we're sending come from this address
|
136
150
|
@src = 0x0a
|
137
151
|
end
|
138
152
|
|
139
|
-
def parse(_data)
|
140
|
-
end
|
153
|
+
def parse(_data); end
|
141
154
|
|
142
155
|
def serialize(message = "")
|
143
156
|
length = message.length + 5
|
144
|
-
full_message = "#{length.chr}#{src.chr}#{self.class::MESSAGE_TYPE}#{message}"
|
157
|
+
full_message = (+"#{length.chr}#{src.chr}#{self.class::MESSAGE_TYPE}#{message}")
|
158
|
+
.force_encoding(Encoding::ASCII_8BIT)
|
145
159
|
checksum = CRC.checksum(full_message)
|
146
|
-
"\x7e#{full_message}#{checksum.chr}\x7e".force_encoding(Encoding::ASCII_8BIT)
|
160
|
+
(+"\x7e#{full_message}#{checksum.chr}\x7e").force_encoding(Encoding::ASCII_8BIT)
|
147
161
|
end
|
148
162
|
|
149
163
|
def inspect
|
150
|
-
"#<#{self.class.name} #{raw_data.
|
164
|
+
"#<#{self.class.name} #{raw_data.unpack1("H*")}>"
|
151
165
|
end
|
152
166
|
end
|
153
167
|
end
|
154
168
|
|
155
|
-
require
|
156
|
-
require
|
157
|
-
require
|
158
|
-
require
|
159
|
-
require
|
160
|
-
require
|
161
|
-
require
|
162
|
-
require
|
163
|
-
require
|
164
|
-
require
|
165
|
-
require
|
169
|
+
require "bwa/messages/configuration"
|
170
|
+
require "bwa/messages/configuration_request"
|
171
|
+
require "bwa/messages/control_configuration"
|
172
|
+
require "bwa/messages/control_configuration_request"
|
173
|
+
require "bwa/messages/filter_cycles"
|
174
|
+
require "bwa/messages/ready"
|
175
|
+
require "bwa/messages/set_target_temperature"
|
176
|
+
require "bwa/messages/set_temperature_scale"
|
177
|
+
require "bwa/messages/set_time"
|
178
|
+
require "bwa/messages/status"
|
179
|
+
require "bwa/messages/toggle_item"
|
@@ -1,7 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module BWA
|
2
4
|
module Messages
|
3
5
|
class Configuration < Message
|
4
|
-
MESSAGE_TYPE = "\xbf\x94".force_encoding(Encoding::ASCII_8BIT)
|
6
|
+
MESSAGE_TYPE = (+"\xbf\x94").force_encoding(Encoding::ASCII_8BIT)
|
5
7
|
MESSAGE_LENGTH = 25
|
6
8
|
|
7
9
|
def inspect
|
@@ -1,7 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module BWA
|
2
4
|
module Messages
|
3
5
|
class ConfigurationRequest < Message
|
4
|
-
MESSAGE_TYPE = "\xbf\x04".force_encoding(Encoding::ASCII_8BIT)
|
6
|
+
MESSAGE_TYPE = (+"\xbf\x04").force_encoding(Encoding::ASCII_8BIT)
|
5
7
|
MESSAGE_LENGTH = 0
|
6
8
|
|
7
9
|
def inspect
|