balboa_worldwide_app 2.0.5 → 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d9bc9287c6234b41b96d8b5e30ef26ce5226e7377f8358822ebaf3ee49630258
4
- data.tar.gz: fec9af69f1b103002352ed18a72ce59d1a46ad93b7268165362f654eb5d0e175
3
+ metadata.gz: 34269811a73d4aeca5f876a55bf71bc1f746f902b0a6dcba3cc16be77c0165e7
4
+ data.tar.gz: b07b0bc9e215cf9826b7596b1455a5c3f67a39fbc7ed664edef89899ef905abc
5
5
  SHA512:
6
- metadata.gz: 258986f1a4a395434bbebf0ee568516675fd98d05c7bf6ed2e5715c6ce6ec7cdda5bbe43c49f59a72eaea128f9d0e27fcb2606036f4c71065023f3b42d1318b4
7
- data.tar.gz: d3b3b6affea3961cc2b2b423b2b6e6b22db84842a9430ba306e27980e7c423993c707106aada7c6a7cda619e417c697472e816ffa3542b20ab377987641ac620
6
+ metadata.gz: 6de18fffd922c20a5e0f5d4eb44306055e21652e7b0dc3fc01e1c7b03cc09b96d7e6daa2643c17204da8c950eac2736cd57293e298eabd6ca2fae708e91488b5
7
+ data.tar.gz: e2efa4b162ca612ca1f9044ce9e539055f0e1cf63bcd5c5d4bd691d182d5853948be09f8da4f061bbb493491a271a5354846fa1a4bbe2dd4612e68f7f806ec48
data/exe/bwa_mqtt_bridge CHANGED
@@ -95,6 +95,7 @@ class MQTTBridge
95
95
  property = @homie["spa"][prop.to_s.tr("_", "-")]
96
96
  property.value = @bwa.public_send(prop)
97
97
  end
98
+ @homie["spa"]["notification"].value = @bwa.notification || "none"
98
99
  2.times do |i|
99
100
  @homie["filter-cycle#{i + 1}"]["running"].value = @bwa.status.filter_cycles[i]
100
101
  end
@@ -144,8 +145,22 @@ class MQTTBridge
144
145
 
145
146
  value.to_i if value.match?(/^\d+$/)
146
147
  end
148
+ allowed_items = BWA::Messages::ToggleItem::ITEMS.keys.map(&:to_s)
149
+ allow_toggles = lambda do |value|
150
+ next unless allowed_items.include?(value)
151
+
152
+ value
153
+ end
147
154
 
148
155
  @homie.node("spa", "Hot Tub", @bwa.model) do |spa|
156
+ spa.property("command",
157
+ "Send a command to the tub",
158
+ :enum,
159
+ format: %w[normal_operation clear_notification],
160
+ retained: false,
161
+ non_standard_value_check: allow_toggles) do |value|
162
+ @bwa.toggle_item(value.to_sym)
163
+ end
149
164
  spa.property("hold",
150
165
  "Hold",
151
166
  :boolean,
@@ -157,6 +172,8 @@ class MQTTBridge
157
172
  @bwa.hold = value
158
173
  end
159
174
  spa.property("priming", "Priming", :boolean, @bwa.priming, hass: { binary_sensor: { icon: "mdi:fast-forward" } })
175
+ spa.property("notification", "Notification", :enum, @bwa.notification || "none",
176
+ format: BWA::Messages::Status::NOTIFICATIONS.values.compact + ["none"])
160
177
  spa.property("heating-mode",
161
178
  "Heating Mode",
162
179
  :enum,
data/lib/bwa/client.rb CHANGED
@@ -17,6 +17,7 @@ module BWA
17
17
  hold?
18
18
  priming
19
19
  priming?
20
+ notification
20
21
  heating_mode
21
22
  temperature_scale
22
23
  twenty_four_hour_time
@@ -64,9 +65,15 @@ module BWA
64
65
  @buffer = @buffer[bytes_read..-1] if bytes_read
65
66
  method = @io.respond_to?(:readpartial) ? :readpartial : :read
66
67
  unless message
68
+ # one EOF is just serial ports saying they have no data;
69
+ # several EOFs in a row is the file is dead and gone
70
+ eofs = 0
67
71
  begin
68
72
  @buffer.concat(@io.__send__(method, 64 * 1024))
69
73
  rescue EOFError
74
+ eofs += 1
75
+ raise if eofs == 5
76
+
70
77
  @io.wait_readable
71
78
  retry
72
79
  end
@@ -160,12 +167,14 @@ module BWA
160
167
  return unless status && configuration
161
168
 
162
169
  desired = 0 if desired == false
163
- desired = configuration.pumps[index] if desired == true
164
- desired = [desired, configuration.pumps[index]].min
165
- times = (desired - status.pumps[index]) % (configuration.pumps[index] + 1)
166
- times.times do
170
+ max_pump_speed = configuration.pumps[index]
171
+ desired = max_pump_speed if desired == true
172
+ desired = [desired, max_pump_speed].min
173
+ current_pump_speed = [status.pumps[index], max_pump_speed].min
174
+ times = (desired - current_pump_speed) % (max_pump_speed + 1)
175
+ times.times do |i|
167
176
  toggle_pump(index)
168
- sleep(0.1)
177
+ sleep(0.1) unless i == times - 1
169
178
  end
170
179
  end
171
180
 
@@ -195,9 +204,9 @@ module BWA
195
204
  desired = configuration.blower if desired == true
196
205
  desired = [desired, configuration.blower].min
197
206
  times = (desired - status.blower) % (configuration.blower + 1)
198
- times.times do
207
+ times.times do |i|
199
208
  toggle_blower
200
- sleep(0.1)
209
+ sleep(0.1) unless i == times - 1
201
210
  end
202
211
  end
203
212
 
@@ -263,7 +272,10 @@ module BWA
263
272
  else
264
273
  0
265
274
  end
266
- times.times { toggle_heating_mode }
275
+ times.times do |i|
276
+ toggle_heating_mode
277
+ sleep(0.1) unless i == times - 1
278
+ end
267
279
  end
268
280
  end
269
281
  end
data/lib/bwa/message.rb CHANGED
@@ -27,36 +27,6 @@ module BWA
27
27
  @messages << klass
28
28
  end
29
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
-
60
30
  def parse(data)
61
31
  offset = -1
62
32
  message_type = length = nil
@@ -92,17 +62,10 @@ module BWA
92
62
 
93
63
  message_type = data.slice(offset + 3, 2)
94
64
  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
99
65
 
100
66
  src = data[offset + 2].ord
101
67
  klass = @messages.find { |k| k::MESSAGE_TYPE == message_type }
102
68
 
103
- # Ignore these message types
104
- return [nil, offset + length + 2] if IGNORED_MESSAGES.include?(message_type)
105
-
106
69
  if klass
107
70
  valid_length = if klass::MESSAGE_LENGTH.respond_to?(:include?)
108
71
  klass::MESSAGE_LENGTH.include?(length - 5)
@@ -110,6 +73,8 @@ module BWA
110
73
  length - 5 == klass::MESSAGE_LENGTH
111
74
  end
112
75
  unless valid_length
76
+ BWA.logger.debug " read: #{BWA.raw2str(data.slice(offset, length + 2))}"
77
+
113
78
  raise InvalidMessage.new("Unrecognized data length (#{length}) for message #{klass}",
114
79
  data)
115
80
  end
@@ -124,7 +89,10 @@ module BWA
124
89
  message.parse(data.slice(offset + 5, length - 5))
125
90
  message.instance_variable_set(:@raw_data, data.slice(offset, length + 2))
126
91
  message.instance_variable_set(:@src, src)
127
- BWA.logger.debug "from spa: #{message.inspect}" unless common_messages.include?(message_type)
92
+ if message.log?
93
+ BWA.logger.debug " read: #{BWA.raw2str(data.slice(offset, length + 2))}"
94
+ BWA.logger.info "from spa: #{message.inspect}"
95
+ end
128
96
  [message, offset + length + 2]
129
97
  end
130
98
 
@@ -150,14 +118,17 @@ module BWA
150
118
  @src = 0x0a
151
119
  end
152
120
 
121
+ def log?
122
+ true
123
+ end
124
+
153
125
  def parse(_data); end
154
126
 
155
127
  def serialize(message = "")
156
128
  length = message.length + 5
157
- full_message = (+"#{length.chr}#{src.chr}#{self.class::MESSAGE_TYPE}#{message}")
158
- .force_encoding(Encoding::ASCII_8BIT)
129
+ full_message = "#{length.chr}#{src.chr}#{self.class::MESSAGE_TYPE}#{message}"
159
130
  checksum = CRC.checksum(full_message)
160
- (+"\x7e#{full_message}#{checksum.chr}\x7e").force_encoding(Encoding::ASCII_8BIT)
131
+ "\x7e#{full_message}#{checksum.chr}\x7e".b
161
132
  end
162
133
 
163
134
  def inspect
@@ -170,7 +141,10 @@ require "bwa/messages/configuration"
170
141
  require "bwa/messages/configuration_request"
171
142
  require "bwa/messages/control_configuration"
172
143
  require "bwa/messages/control_configuration_request"
144
+ require "bwa/messages/error"
173
145
  require "bwa/messages/filter_cycles"
146
+ require "bwa/messages/new_client_clear_to_send"
147
+ require "bwa/messages/nothing_to_send"
174
148
  require "bwa/messages/ready"
175
149
  require "bwa/messages/set_target_temperature"
176
150
  require "bwa/messages/set_temperature_scale"
@@ -3,7 +3,7 @@
3
3
  module BWA
4
4
  module Messages
5
5
  class Configuration < Message
6
- MESSAGE_TYPE = (+"\xbf\x94").force_encoding(Encoding::ASCII_8BIT)
6
+ MESSAGE_TYPE = "\xbf\x94".b
7
7
  MESSAGE_LENGTH = 25
8
8
 
9
9
  def inspect
@@ -3,7 +3,7 @@
3
3
  module BWA
4
4
  module Messages
5
5
  class ConfigurationRequest < Message
6
- MESSAGE_TYPE = (+"\xbf\x04").force_encoding(Encoding::ASCII_8BIT)
6
+ MESSAGE_TYPE = "\xbf\x04".b
7
7
  MESSAGE_LENGTH = 0
8
8
 
9
9
  def inspect
@@ -3,7 +3,7 @@
3
3
  module BWA
4
4
  module Messages
5
5
  class ControlConfiguration < Message
6
- MESSAGE_TYPE = (+"\xbf\x24").force_encoding(Encoding::ASCII_8BIT)
6
+ MESSAGE_TYPE = "\xbf\x24".b
7
7
  MESSAGE_LENGTH = 21
8
8
 
9
9
  attr_accessor :model, :version
@@ -25,7 +25,7 @@ module BWA
25
25
  end
26
26
 
27
27
  class ControlConfiguration2 < Message
28
- MESSAGE_TYPE = (+"\xbf\x2e").force_encoding(Encoding::ASCII_8BIT)
28
+ MESSAGE_TYPE = "\xbf\x2e".b
29
29
  MESSAGE_LENGTH = 6
30
30
 
31
31
  attr_accessor :pumps, :lights, :circulation_pump, :blower, :mister, :aux
@@ -3,7 +3,7 @@
3
3
  module BWA
4
4
  module Messages
5
5
  class ControlConfigurationRequest < Message
6
- MESSAGE_TYPE = (+"\xbf\x22").force_encoding(Encoding::ASCII_8BIT)
6
+ MESSAGE_TYPE = "\xbf\x22".b
7
7
  MESSAGE_LENGTH = 3
8
8
 
9
9
  attr_accessor :type
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BWA
4
+ module Messages
5
+ class Error < Message
6
+ MESSAGE_TYPE = "\xbf\xe1".b
7
+ MESSAGE_LENGTH = 1
8
+
9
+ def log?
10
+ BWA.verbosity >= 1
11
+ end
12
+
13
+ def inspect
14
+ "#<BWA::Messages::Error>"
15
+ end
16
+ end
17
+ end
18
+ end
@@ -7,7 +7,7 @@ module BWA
7
7
  :cycle2_enabled, :cycle2_start_hour, :cycle2_start_minute, :cycle2_duration
8
8
  alias_method :cycle2_enabled?, :cycle2_enabled
9
9
 
10
- MESSAGE_TYPE = (+"\xbf\x23").force_encoding(Encoding::ASCII_8BIT)
10
+ MESSAGE_TYPE = "\xbf\x23".b
11
11
  MESSAGE_LENGTH = 8
12
12
 
13
13
  def parse(data)
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BWA
4
+ module Messages
5
+ class NewClientClearToSend < Message
6
+ MESSAGE_TYPE = "\xbf\0".b
7
+ MESSAGE_LENGTH = 0
8
+
9
+ def log?
10
+ BWA.verbosity >= 2
11
+ end
12
+
13
+ def inspect
14
+ "#<BWA::Messages::NewClientClearToSend>"
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module BWA
4
+ module Messages
5
+ class NothingToSend < Message
6
+ MESSAGE_TYPE = "\xbf\x07".b
7
+ MESSAGE_LENGTH = 0
8
+
9
+ def log?
10
+ BWA.verbosity >= 2
11
+ end
12
+
13
+ def inspect
14
+ "#<BWA::Messages::NothingToSend>"
15
+ end
16
+ end
17
+ end
18
+ end
@@ -3,9 +3,13 @@
3
3
  module BWA
4
4
  module Messages
5
5
  class Ready < Message
6
- MESSAGE_TYPE = (+"\xbf\06").force_encoding(Encoding::ASCII_8BIT)
6
+ MESSAGE_TYPE = "\xbf\06".b
7
7
  MESSAGE_LENGTH = 0
8
8
 
9
+ def log?
10
+ BWA.verbosity >= 2
11
+ end
12
+
9
13
  def inspect
10
14
  "#<BWA::Messages::Ready>"
11
15
  end
@@ -3,7 +3,7 @@
3
3
  module BWA
4
4
  module Messages
5
5
  class SetTargetTemperature < Message
6
- MESSAGE_TYPE = (+"\xbf\x20").force_encoding(Encoding::ASCII_8BIT)
6
+ MESSAGE_TYPE = "\xbf\x20".b
7
7
  MESSAGE_LENGTH = 1
8
8
 
9
9
  attr_accessor :temperature
@@ -3,7 +3,7 @@
3
3
  module BWA
4
4
  module Messages
5
5
  class SetTemperatureScale < Message
6
- MESSAGE_TYPE = (+"\xbf\x27").force_encoding(Encoding::ASCII_8BIT)
6
+ MESSAGE_TYPE = "\xbf\x27".b
7
7
  MESSAGE_LENGTH = 2
8
8
 
9
9
  attr_accessor :scale
@@ -3,7 +3,7 @@
3
3
  module BWA
4
4
  module Messages
5
5
  class SetTime < Message
6
- MESSAGE_TYPE = (+"\xbf\x21").force_encoding(Encoding::ASCII_8BIT)
6
+ MESSAGE_TYPE = "\xbf\x21".b
7
7
  MESSAGE_LENGTH = 2
8
8
 
9
9
  attr_accessor :hour, :minute, :twenty_four_hour_time
@@ -5,6 +5,7 @@ module BWA
5
5
  class Status < Message
6
6
  attr_accessor :hold,
7
7
  :priming,
8
+ :notification,
8
9
  :heating_mode,
9
10
  :twenty_four_hour_time,
10
11
  :filter_cycles,
@@ -25,16 +26,24 @@ module BWA
25
26
  alias_method :twenty_four_hour_time?, :twenty_four_hour_time
26
27
  alias_method :heating?, :heating
27
28
 
28
- MESSAGE_TYPE = (+"\xaf\x13").force_encoding(Encoding::ASCII_8BIT)
29
+ MESSAGE_TYPE = "\xaf\x13".b
29
30
  # additional features have been added in later versions
30
31
  MESSAGE_LENGTH = (24..32).freeze
31
32
 
33
+ NOTIFICATIONS = {
34
+ 0x00 => nil,
35
+ 0x0a => :ph,
36
+ 0x04 => :filter,
37
+ 0x09 => :sanitizer
38
+ }.freeze
39
+
32
40
  def initialize
33
41
  super
34
42
 
35
43
  @src = 0xff
36
44
  self.hold = false
37
45
  self.priming = false
46
+ self.notification = nil
38
47
  self.heating_mode = :ready
39
48
  @temperature_scale = :fahrenheit
40
49
  self.twenty_four_hour_time = false
@@ -50,18 +59,22 @@ module BWA
50
59
  self.target_temperature = 100
51
60
  end
52
61
 
62
+ def log?
63
+ BWA.verbosity >= 1
64
+ end
65
+
53
66
  def parse(data)
54
67
  flags = data[0].ord
55
68
  self.hold = (flags & 0x05 != 0)
56
69
 
57
- flags = data[1].ord
58
- self.priming = (flags & 0x01 == 0x01)
70
+ self.priming = data[1].ord == 0x01
59
71
  flags = data[5].ord
60
72
  self.heating_mode = case flags & 0x03
61
73
  when 0x00 then :ready
62
74
  when 0x01 then :rest
63
75
  when 0x02 then :ready_in_rest
64
76
  end
77
+ self.notification = data[1].ord == 0x03 && NOTIFICATIONS[data[6].ord]
65
78
  flags = data[9].ord
66
79
  self.temperature_scale = (flags & 0x01 == 0x01) ? :celsius : :fahrenheit
67
80
  self.twenty_four_hour_time = (flags & 0x02 == 0x02)
@@ -104,10 +117,17 @@ module BWA
104
117
  def serialize
105
118
  data = "\x00" * 24
106
119
  data[0] = (hold ? 0x05 : 0x00).chr
107
- data[1] = (priming ? 0x01 : 0x00).chr
120
+ data[1] = if priming
121
+ 0x01
122
+ elsif notification
123
+ 0x04
124
+ else
125
+ 0x00
126
+ end.chr
108
127
  data[5] = { ready: 0x00,
109
128
  rest: 0x01,
110
129
  ready_in_rest: 0x02 }[heating_mode].chr
130
+ data[6] = NOTIFICATIONS.invert[notification].chr
111
131
  flags = 0
112
132
  flags |= 0x01 if temperature_scale == :celsius
113
133
  flags |= 0x02 if twenty_four_hour_time
@@ -169,6 +189,7 @@ module BWA
169
189
 
170
190
  items << "hold" if hold
171
191
  items << "priming" if priming
192
+ items << "notification=#{notification}" if notification
172
193
  items << self.class.format_time(hour, minute, twenty_four_hour_time: twenty_four_hour_time)
173
194
  items << "#{current_temperature || "--"}/#{target_temperature}°#{temperature_scale.to_s[0].upcase}"
174
195
  items << "filter_cycles=#{filter_cycles.inspect}"
@@ -3,7 +3,7 @@
3
3
  module BWA
4
4
  module Messages
5
5
  class ToggleItem < Message
6
- MESSAGE_TYPE = (+"\xbf\x11").force_encoding(Encoding::ASCII_8BIT)
6
+ MESSAGE_TYPE = "\xbf\x11".b
7
7
  MESSAGE_LENGTH = 2
8
8
  ITEMS = {
9
9
  normal_operation: 0x01,
@@ -32,13 +32,22 @@ module BWA
32
32
  self.item = item
33
33
  end
34
34
 
35
+ def log?
36
+ return true if BWA.verbosity >= 2
37
+ # dunno why we receive this, but somebody is spamming the bus
38
+ # trying to toggle an item we don't know
39
+ return false if item == 0 # rubocop:disable Style/NumericPredicate could be a symbol
40
+
41
+ true
42
+ end
43
+
35
44
  def parse(data)
36
45
  self.item = ITEMS.invert[data[0].ord] || data[0].ord
37
46
  end
38
47
 
39
48
  def serialize
40
49
  data = +"\x00\x00"
41
- data[0] = if item.is_a? Integer
50
+ data[0] = if item.is_a?(Integer)
42
51
  item.chr
43
52
  else
44
53
  ITEMS[item].chr
data/lib/bwa/proxy.rb CHANGED
@@ -25,7 +25,7 @@ module BWA
25
25
  end
26
26
 
27
27
  def shuffle_messages(socket1, socket2, tag)
28
- leftover_data = (+"").force_encoding(Encoding::ASCII_8BIT)
28
+ leftover_data = "".b
29
29
  loop do
30
30
  if leftover_data.length < 2 || leftover_data.length < leftover_data[1].ord + 2
31
31
  begin
data/lib/bwa/server.rb CHANGED
@@ -20,9 +20,9 @@ module BWA
20
20
 
21
21
  def send_message(socket, message)
22
22
  length = message.length + 2
23
- full_message = (+"#{length.chr}#{message}").force_encoding(Encoding::ASCII_8BIT)
23
+ full_message = "#{length.chr}#{message}"
24
24
  checksum = CRC.checksum(full_message)
25
- socket.send((+"\x7e#{full_message}#{checksum.chr}\x7e").force_encoding(Encoding::ASCII_8BIT), 0)
25
+ socket.send("\x7e#{full_message}#{checksum.chr}\x7e".b, 0)
26
26
  end
27
27
 
28
28
  def run_client(socket)
data/lib/bwa/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BWA
4
- VERSION = "2.0.5"
4
+ VERSION = "2.1.1"
5
5
  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: 2.0.5
4
+ version: 2.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cody Cutrer
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-12-13 00:00:00.000000000 Z
11
+ date: 2022-02-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ccutrer-serialport
@@ -180,7 +180,10 @@ files:
180
180
  - lib/bwa/messages/configuration_request.rb
181
181
  - lib/bwa/messages/control_configuration.rb
182
182
  - lib/bwa/messages/control_configuration_request.rb
183
+ - lib/bwa/messages/error.rb
183
184
  - lib/bwa/messages/filter_cycles.rb
185
+ - lib/bwa/messages/new_client_clear_to_send.rb
186
+ - lib/bwa/messages/nothing_to_send.rb
184
187
  - lib/bwa/messages/ready.rb
185
188
  - lib/bwa/messages/set_target_temperature.rb
186
189
  - lib/bwa/messages/set_temperature_scale.rb