balboa_worldwide_app 1.3.0 → 2.0.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/exe/bwa_client +41 -0
- data/exe/bwa_mqtt_bridge +394 -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 +121 -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 +51 -44
- 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 +70 -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,77 @@ 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
|
+
times = (desired - status.blower) % (configuration.blower + 1)
|
146
194
|
times.times do
|
147
195
|
toggle_blower
|
148
196
|
sleep(0.1)
|
149
197
|
end
|
150
198
|
end
|
151
199
|
|
152
|
-
def
|
153
|
-
return unless
|
154
|
-
return if
|
200
|
+
def hold=(desired)
|
201
|
+
return unless status
|
202
|
+
return if status.hold == desired
|
203
|
+
|
155
204
|
toggle_hold
|
156
205
|
end
|
157
206
|
|
158
207
|
# high range is 80-106 for F, 26-40 for C (by 0.5)
|
159
208
|
# low range is 50-99 for F, 10-26 for C (by 0.5)
|
160
|
-
def
|
161
|
-
return unless
|
162
|
-
return if
|
209
|
+
def target_temperature=(desired)
|
210
|
+
return unless status
|
211
|
+
return if status.target_temperature == desired
|
163
212
|
|
164
|
-
desired *= 2 if
|
165
|
-
send_message(Messages::
|
213
|
+
desired *= 2 if (status && status.temperature_scale == :celsius) || desired < 50
|
214
|
+
send_message(Messages::SetTargetTemperature.new(desired.round))
|
166
215
|
end
|
167
216
|
|
168
|
-
def set_time(hour, minute, twenty_four_hour_time
|
217
|
+
def set_time(hour, minute, twenty_four_hour_time: false)
|
169
218
|
send_message(Messages::SetTime.new(hour, minute, twenty_four_hour_time))
|
170
219
|
end
|
171
220
|
|
172
|
-
def
|
173
|
-
raise ArgumentError, "scale must be :fahrenheit or :celsius" unless %I
|
221
|
+
def temperature_scale=(scale)
|
222
|
+
raise ArgumentError, "scale must be :fahrenheit or :celsius" unless %I[fahrenheit celsius].include?(scale)
|
223
|
+
|
174
224
|
send_message(Messages::SetTemperatureScale.new(scale))
|
175
225
|
end
|
176
226
|
|
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
|
227
|
+
def update_filter_cycles(new_filter_cycles)
|
228
|
+
send_message(new_filter_cycles)
|
229
|
+
@filter_cycles = new_filter_cycles.dup
|
202
230
|
request_filter_configuration
|
203
231
|
end
|
204
232
|
|
@@ -206,9 +234,10 @@ module BWA
|
|
206
234
|
toggle_item(0x50)
|
207
235
|
end
|
208
236
|
|
209
|
-
def
|
210
|
-
return unless
|
211
|
-
return if
|
237
|
+
def temperature_range=(desired)
|
238
|
+
return unless status
|
239
|
+
return if status.temperature_range == desired
|
240
|
+
|
212
241
|
toggle_temperature_range
|
213
242
|
end
|
214
243
|
|
@@ -216,19 +245,20 @@ module BWA
|
|
216
245
|
toggle_item(:heating_mode)
|
217
246
|
end
|
218
247
|
|
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
|
-
|
248
|
+
HEATING_MODES = %I[ready rest ready_in_rest].freeze
|
249
|
+
def heating_mode=(desired)
|
250
|
+
raise ArgumentError, "heating_mode must be :ready or :rest" unless %I[ready rest].include?(desired)
|
251
|
+
return unless status
|
252
|
+
|
253
|
+
times = if (status.heating_mode == :ready && desired == :rest) ||
|
254
|
+
(status.heating_mode == :rest && desired == :ready) ||
|
255
|
+
(status.heating_mode == :ready_in_rest && desired == :rest)
|
256
|
+
1
|
257
|
+
elsif status.heating_mode == :ready_in_rest && desired == :ready
|
258
|
+
2
|
259
|
+
else
|
260
|
+
0
|
261
|
+
end
|
232
262
|
times.times { toggle_heating_mode }
|
233
263
|
end
|
234
264
|
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
|