balboa_worldwide_app 1.2.5 → 2.0.2

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