balboa_worldwide_app 2.0.4 → 2.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: 64fc310dcf147c2786084dcd1fd792942a46d4af3a9e3a32caed05aafd3af2db
4
- data.tar.gz: 881215be0b0f7ec906c0c1fa36f8866abe16e0dd748d906ddaa324c9b46f7af9
3
+ metadata.gz: 1cac0db450913aecd0a1027147202f01278e8828fce2b1fc86541940a37782f3
4
+ data.tar.gz: 67e5d9a8f211bb61f7b4c3d783486d03e78eeecac1da6983d43116be0f87d545
5
5
  SHA512:
6
- metadata.gz: 54ddd001789885a8134d2cc0d479c5506a51a000394fb4cf6f268d49f8dfa90f216468dcbe1fbc0f947568ea52ccab55975a65d4605e23330e8d8a5dc5feeb62
7
- data.tar.gz: 06a454791a33e646ecc8e10c7362c978e138d4a82ea404466ef5207ad2cc2624ae65ecadd425744306922aa18b008a2f3c9b2dd1003896da7be50d6ab3089831
6
+ metadata.gz: aed0becca04006dc4288ccd8bf5134d8387edfd51c47444753b57681184acc95d3244a1df506d5cbceb9997ff440550f09907c9f334b3050e1ffe7b31af02eee
7
+ data.tar.gz: c2870fb8c3d23915c172db962483fb00928562aabc76ed503d9b4641e69f6cc100fe7a64d627cd86883fe76b0650a73c68a62473e4449d74782cc89247fc6f1f
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
@@ -139,11 +140,27 @@ class MQTTBridge
139
140
  end
140
141
  allow_toggle_or_speed = lambda do |value|
141
142
  next value if value == "toggle"
143
+ next true if value == "true"
144
+ next false if value == "false"
142
145
 
143
146
  value.to_i if value.match?(/^\d+$/)
144
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
145
154
 
146
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
147
164
  spa.property("hold",
148
165
  "Hold",
149
166
  :boolean,
@@ -155,6 +172,8 @@ class MQTTBridge
155
172
  @bwa.hold = value
156
173
  end
157
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"])
158
177
  spa.property("heating-mode",
159
178
  "Heating Mode",
160
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,9 +167,11 @@ 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)
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)
166
175
  times.times do
167
176
  toggle_pump(index)
168
177
  sleep(0.1)
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.4"
4
+ VERSION = "2.1.0"
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.4
4
+ version: 2.1.0
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-12 00:00:00.000000000 Z
11
+ date: 2022-01-24 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