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 +4 -4
- data/exe/bwa_mqtt_bridge +19 -0
- data/lib/bwa/client.rb +12 -3
- data/lib/bwa/message.rb +15 -41
- data/lib/bwa/messages/configuration.rb +1 -1
- data/lib/bwa/messages/configuration_request.rb +1 -1
- data/lib/bwa/messages/control_configuration.rb +2 -2
- data/lib/bwa/messages/control_configuration_request.rb +1 -1
- data/lib/bwa/messages/error.rb +18 -0
- data/lib/bwa/messages/filter_cycles.rb +1 -1
- data/lib/bwa/messages/new_client_clear_to_send.rb +18 -0
- data/lib/bwa/messages/nothing_to_send.rb +18 -0
- data/lib/bwa/messages/ready.rb +5 -1
- data/lib/bwa/messages/set_target_temperature.rb +1 -1
- data/lib/bwa/messages/set_temperature_scale.rb +1 -1
- data/lib/bwa/messages/set_time.rb +1 -1
- data/lib/bwa/messages/status.rb +25 -4
- data/lib/bwa/messages/toggle_item.rb +11 -2
- data/lib/bwa/proxy.rb +1 -1
- data/lib/bwa/server.rb +2 -2
- data/lib/bwa/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1cac0db450913aecd0a1027147202f01278e8828fce2b1fc86541940a37782f3
|
4
|
+
data.tar.gz: 67e5d9a8f211bb61f7b4c3d783486d03e78eeecac1da6983d43116be0f87d545
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
164
|
-
desired =
|
165
|
-
|
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
|
-
|
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 =
|
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
|
-
|
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 ControlConfiguration < Message
|
6
|
-
MESSAGE_TYPE =
|
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 =
|
28
|
+
MESSAGE_TYPE = "\xbf\x2e".b
|
29
29
|
MESSAGE_LENGTH = 6
|
30
30
|
|
31
31
|
attr_accessor :pumps, :lights, :circulation_pump, :blower, :mister, :aux
|
@@ -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 =
|
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
|
data/lib/bwa/messages/ready.rb
CHANGED
@@ -3,9 +3,13 @@
|
|
3
3
|
module BWA
|
4
4
|
module Messages
|
5
5
|
class Ready < Message
|
6
|
-
MESSAGE_TYPE =
|
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
|
data/lib/bwa/messages/status.rb
CHANGED
@@ -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 =
|
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
|
-
|
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] =
|
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 =
|
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?
|
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
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 =
|
23
|
+
full_message = "#{length.chr}#{message}"
|
24
24
|
checksum = CRC.checksum(full_message)
|
25
|
-
socket.send(
|
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
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
|
+
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:
|
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
|