waterfurnace_aurora 0.2.2 → 0.3.3
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/aurora_fetch +1 -1
- data/exe/aurora_mock +1 -1
- data/exe/aurora_monitor +56 -12
- data/exe/aurora_mqtt_bridge +70 -49
- data/lib/aurora/abc_client.rb +35 -13
- data/lib/aurora/core_ext/string.rb +10 -0
- data/lib/aurora/iz2_zone.rb +18 -42
- data/lib/aurora/modbus/slave.rb +1 -2
- data/lib/aurora/registers.rb +34 -26
- data/lib/aurora/thermostat.rb +57 -0
- data/lib/aurora/version.rb +1 -1
- data/lib/aurora.rb +1 -0
- metadata +8 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c3127c610617c1008b2de42b4b03fc89867d12233548fc0baf6ea0ebb5ea08e8
|
4
|
+
data.tar.gz: '0190fccfece31159a9c5acdd14b648521c32e7b64f7cb596daeb3548b882393a'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5d8ae25889bcea9d6f6e7adfe8e242da729bc5172102688d4790c20a594e094f2aa27c78dfc7104895109bf4dc1636e357e6a03f60cb20918085f95fb8059211
|
7
|
+
data.tar.gz: 72682b009c4f19ff335d1b19759ccd813130148e9d5cfabfb9c915c88a497dbcc3290b2a36e1ef173e513843fd9e19827080236c092fd311618f88ac128e3de4
|
data/exe/aurora_fetch
CHANGED
@@ -18,7 +18,7 @@ args = if uri.scheme == "telnet" || uri.scheme == "rfc2217"
|
|
18
18
|
end
|
19
19
|
|
20
20
|
client = ModBus::RTUClient.new(*args)
|
21
|
-
client.
|
21
|
+
client.logger = Logger.new($stdout, :debug)
|
22
22
|
slave = client.with_slave(1)
|
23
23
|
|
24
24
|
registers = slave.holding_registers[ARGV[1].to_i]
|
data/exe/aurora_mock
CHANGED
@@ -21,7 +21,7 @@ args = if uri.scheme == "telnet" || uri.scheme == "rfc2217"
|
|
21
21
|
port = ARGV[1]&.to_i || 502
|
22
22
|
|
23
23
|
server1 = ModBus::RTUServer.new(*args)
|
24
|
-
server1.
|
24
|
+
server1.logger = Logger.new($stdout, :debug)
|
25
25
|
# AID Tool queries slave 1, AWL queries slave 2; just use both
|
26
26
|
slave1 = server1.with_slave(1)
|
27
27
|
slave2 = server1.with_slave(2)
|
data/exe/aurora_monitor
CHANGED
@@ -3,11 +3,39 @@
|
|
3
3
|
|
4
4
|
require "aurora"
|
5
5
|
require "ccutrer-serialport"
|
6
|
+
require "logger"
|
7
|
+
require "optparse"
|
6
8
|
require "socket"
|
7
9
|
require "uri"
|
8
10
|
|
11
|
+
diff_only = debug_modbus = ignore_awl_heartbeat = ignore_sensors = false
|
12
|
+
|
13
|
+
OptionParser.new do |opts|
|
14
|
+
opts.banner = "Usage: aurora_monitor /path/to/serial/port [options]"
|
15
|
+
|
16
|
+
opts.on("-q", "--quiet",
|
17
|
+
"Enables quiet mode (--diff-only, --ignore-awl-heartbeat, --ignore-sensors) to ease in deciphering new registers") do # rubocop:disable Layout/LineLength
|
18
|
+
diff_only = true
|
19
|
+
ignore_awl_heartbeat = true
|
20
|
+
ignore_sensors = true
|
21
|
+
end
|
22
|
+
opts.on("--diff-only", "Only show registers if they've changed from their previous value") { diff_only = true }
|
23
|
+
opts.on("--debug-modbus", "Print actual protocol bytes") { debug_modbus = true }
|
24
|
+
opts.on("--ignore-awl-heartbeat", "Don't print AWL heartbeat requests") { ignore_awl_heartbeat = true }
|
25
|
+
opts.on("--ignore-sensors", "Don't print sensor registers (i.e. because they change a lot)") { ignore_sensors = true }
|
26
|
+
opts.on("-h", "--help", "Prints this help") do
|
27
|
+
puts opts
|
28
|
+
exit
|
29
|
+
end
|
30
|
+
end.parse!
|
31
|
+
|
9
32
|
uri = URI.parse(ARGV[0])
|
10
33
|
|
34
|
+
last_registers = {}
|
35
|
+
|
36
|
+
SENSOR_REGISTERS = [16, 19, 20, 740, 900, 1109, 1105, 1106, 1107, 1108, 1110, 1111, 1114, 1117, 1134, 1147, 1149, 1151,
|
37
|
+
1153, 1165].freeze
|
38
|
+
|
11
39
|
args = if uri.scheme == "telnet" || uri.scheme == "rfc2217"
|
12
40
|
require "net/telnet/rfc2217"
|
13
41
|
[Net::Telnet::RFC2217.new("Host" => uri.host,
|
@@ -20,23 +48,39 @@ args = if uri.scheme == "telnet" || uri.scheme == "rfc2217"
|
|
20
48
|
|
21
49
|
server = ModBus::RTUServer.new(*args)
|
22
50
|
server.promiscuous = true
|
23
|
-
server.
|
51
|
+
server.logger = Logger.new($stdout)
|
52
|
+
server.logger.level = :debug if debug_modbus
|
53
|
+
|
54
|
+
diff_and_print = lambda do |registers|
|
55
|
+
registers = registers.slice(*(registers.keys - SENSOR_REGISTERS)) if ignore_sensors
|
56
|
+
next puts Aurora.print_registers(registers) unless diff_only
|
57
|
+
|
58
|
+
new_registers = last_registers.merge(registers)
|
59
|
+
diff = Aurora.diff_registers(last_registers, new_registers)
|
60
|
+
unless diff.empty?
|
61
|
+
puts "#{Time.now} ===== read"
|
62
|
+
puts Aurora.print_registers(diff)
|
63
|
+
end
|
64
|
+
last_registers = new_registers
|
65
|
+
end
|
24
66
|
|
25
67
|
server.request_callback = lambda { |uid, func, req|
|
26
68
|
if func == 68
|
27
|
-
puts "===== no idea to #{uid}: #{req.inspect}"
|
69
|
+
puts "#{Time.now} ===== no idea to #{uid}: #{req.inspect}" unless diff_only
|
28
70
|
elsif func == 67
|
29
|
-
puts "===== write discontiguous registers to #{uid}:"
|
71
|
+
puts "#{Time.now} ===== write discontiguous registers to #{uid}:"
|
30
72
|
registers = req.map { |p| [p[:addr], p[:val]] }.to_h
|
31
73
|
puts Aurora.print_registers(registers)
|
32
74
|
elsif func == 16
|
33
|
-
puts "===== write multiple registers to #{uid}:"
|
34
75
|
registers = Range.new(req[:addr], req[:addr] + req[:quant] - 1).zip(req[:val]).to_h
|
76
|
+
next if ignore_awl_heartbeat && registers == { 460 => 102, 461 => 0, 462 => 5 }
|
77
|
+
|
78
|
+
puts "#{Time.now} ===== write multiple registers to #{uid}:"
|
35
79
|
puts Aurora.print_registers(registers)
|
36
80
|
elsif [3, 65, 66].include?(func)
|
37
81
|
# no output
|
38
82
|
else
|
39
|
-
puts "**** new func #{func}"
|
83
|
+
puts "#{Time.now} **** new func #{func}"
|
40
84
|
end
|
41
85
|
}
|
42
86
|
|
@@ -46,9 +90,9 @@ server.response_callback = lambda { |uid, func, res, req|
|
|
46
90
|
puts "wrong number of results"
|
47
91
|
next
|
48
92
|
end
|
49
|
-
puts "===== read registers from #{uid}"
|
93
|
+
puts "#{Time.now} ===== read registers from #{uid}" unless diff_only
|
50
94
|
registers = Range.new(req[:addr], req[:addr] + req[:quant], true).to_a.zip(res).to_h
|
51
|
-
|
95
|
+
diff_and_print.call(registers)
|
52
96
|
elsif func == 65 && res.is_a?(Array) && req
|
53
97
|
register_list = []
|
54
98
|
req.each { |params| register_list.concat(Range.new(params[:addr], params[:addr] + params[:quant], true).to_a) }
|
@@ -56,21 +100,21 @@ server.response_callback = lambda { |uid, func, res, req|
|
|
56
100
|
puts "wrong number of results"
|
57
101
|
next
|
58
102
|
end
|
59
|
-
puts "===== read multiple register ranges from #{uid}"
|
103
|
+
puts "#{Time.now} ===== read multiple register ranges from #{uid}" unless diff_only
|
60
104
|
result = register_list.zip(res).to_h
|
61
|
-
|
105
|
+
diff_and_print.call(result)
|
62
106
|
elsif func == 66 && res.is_a?(Array) && req
|
63
107
|
unless req.length == res.length
|
64
108
|
puts "wrong number of results"
|
65
109
|
next
|
66
110
|
end
|
67
|
-
puts "===== read discontiguous registers from #{uid}"
|
111
|
+
puts "#{Time.now} ===== read discontiguous registers from #{uid}" unless diff_only
|
68
112
|
registers = req.zip(res).to_h
|
69
|
-
|
113
|
+
diff_and_print.call(registers)
|
70
114
|
elsif [16, 67, 68].include?(func)
|
71
115
|
# no output
|
72
116
|
else
|
73
|
-
puts "**** new func #{func}"
|
117
|
+
puts "#{Time.now} **** new func #{func}"
|
74
118
|
end
|
75
119
|
}
|
76
120
|
|
data/exe/aurora_mqtt_bridge
CHANGED
@@ -5,6 +5,7 @@ require "aurora"
|
|
5
5
|
require "homie-mqtt"
|
6
6
|
require "ccutrer-serialport"
|
7
7
|
require "uri"
|
8
|
+
require "aurora/core_ext/string"
|
8
9
|
|
9
10
|
uri = URI.parse(ARGV[0])
|
10
11
|
mqtt_uri = ARGV[1]
|
@@ -28,7 +29,7 @@ class MQTTBridge
|
|
28
29
|
@homie = homie
|
29
30
|
@mutex = Mutex.new
|
30
31
|
|
31
|
-
@homie.
|
32
|
+
@homie.out_of_band_topic_proc = lambda do |topic, value|
|
32
33
|
@mutex.synchronize do
|
33
34
|
case topic
|
34
35
|
when /\$modbus$/
|
@@ -48,7 +49,7 @@ class MQTTBridge
|
|
48
49
|
registers.merge!(@abc.modbus_slave.read_multiple_holding_registers(*subquery))
|
49
50
|
end
|
50
51
|
result = Aurora.print_registers(registers)
|
51
|
-
@homie.mqtt.publish("#{@
|
52
|
+
@homie.mqtt.publish("#{@homie.topic}/$modbus/response", result, retain: false, qos: 1)
|
52
53
|
when %r{\$modbus/(\d+)$}
|
53
54
|
register = Regexp.last_match(1).to_i
|
54
55
|
value = case value
|
@@ -61,47 +62,46 @@ class MQTTBridge
|
|
61
62
|
end
|
62
63
|
end
|
63
64
|
rescue StandardError => e
|
64
|
-
|
65
|
-
end
|
65
|
+
logger.error("failed processing message: #{e}\n#{e.backtrace}")
|
66
|
+
end
|
66
67
|
|
68
|
+
@abc.refresh
|
67
69
|
publish_basic_attributes
|
68
70
|
|
69
71
|
loop do
|
70
72
|
begin
|
71
73
|
@mutex.synchronize do
|
72
74
|
@abc.refresh
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
75
|
+
%i[compressor_speed
|
76
|
+
current_mode
|
77
|
+
dhw_water_temperature
|
78
|
+
entering_air_temperature
|
79
|
+
entering_water_temperature
|
80
|
+
fan_speed
|
81
|
+
leaving_air_temperature
|
82
|
+
leaving_water_temperature
|
83
|
+
outdoor_temperature
|
84
|
+
relative_humidity
|
85
|
+
waterflow
|
86
|
+
fp1
|
87
|
+
fp2
|
88
|
+
compressor_watts
|
89
|
+
blower_watts
|
90
|
+
aux_heat_watts
|
91
|
+
loop_pump_watts
|
92
|
+
total_watts].each do |property|
|
93
|
+
@homie_abc[property.to_s.tr("_", "-")].value = @abc.public_send(property)
|
94
|
+
end
|
86
95
|
|
87
|
-
@abc.
|
88
|
-
homie_zone = @homie["zone#{
|
89
|
-
homie_zone
|
90
|
-
|
91
|
-
|
92
|
-
homie_zone["current-fan-mode"].value = z.current_fan_mode
|
93
|
-
homie_zone["fan-intermittent-on"].value = z.fan_intermittent_on
|
94
|
-
homie_zone["fan-intermittent-off"].value = z.fan_intermittent_off
|
95
|
-
homie_zone["priority"].value = z.priority
|
96
|
-
homie_zone["size"].value = z.size
|
97
|
-
homie_zone["normalized-size"].value = z.normalized_size
|
98
|
-
homie_zone["ambient-temperature"].value = z.ambient_temperature
|
99
|
-
homie_zone["heating-target-temperature"].value = z.heating_target_temperature
|
100
|
-
homie_zone["cooling-target-temperature"].value = z.cooling_target_temperature
|
96
|
+
@abc.zones.each_with_index do |z, idx|
|
97
|
+
homie_zone = @homie["zone#{idx + 1}"]
|
98
|
+
homie_zone.each do |property|
|
99
|
+
property.value = z.public_send(property.id.tr("-", "_"))
|
100
|
+
end
|
101
101
|
end
|
102
102
|
end
|
103
103
|
rescue => e
|
104
|
-
|
104
|
+
logger.error("got garbage: #{e}; #{e.backtrace}")
|
105
105
|
exit 1
|
106
106
|
end
|
107
107
|
sleep(5)
|
@@ -129,34 +129,46 @@ class MQTTBridge
|
|
129
129
|
node.property("waterflow", "Waterflow", :float, unit: "gpm")
|
130
130
|
node.property("fp1", "FP1 Sensor", :float, @abc.fp1, unit: "ºF")
|
131
131
|
node.property("fp2", "FP2 Sensor", :float, @abc.fp2, unit: "ºF")
|
132
|
+
%i[compressor blower aux_heat loop_pump total].each do |component|
|
133
|
+
component = "#{component}_watts"
|
134
|
+
node.property(component.tr("_", "-"), component.tr("_", " ").titleize, :integer,
|
135
|
+
@abc.public_send(component), unit: "W")
|
136
|
+
end
|
132
137
|
end
|
133
138
|
|
134
|
-
@abc.
|
135
|
-
|
139
|
+
@abc.zones.each_with_index do |zone, i|
|
140
|
+
type = zone.is_a?(Aurora::IZ2Zone) ? "IntelliZone 2 Zone" : "Thermostat"
|
141
|
+
@homie.node("zone#{i + 1}", "Zone #{i + 1}", type) do |node|
|
136
142
|
allowed_modes = %w[off auto cool heat]
|
137
|
-
allowed_modes << "eheat" if
|
143
|
+
allowed_modes << "eheat" if i.zero?
|
138
144
|
node.property("target-mode", "Target Heating/Cooling Mode", :enum, zone.target_mode,
|
139
145
|
format: allowed_modes) do |value, property|
|
140
146
|
@mutex.synchronize { property.value = zone.target_mode = value.to_sym }
|
141
147
|
end
|
142
|
-
|
143
|
-
|
144
|
-
|
148
|
+
if zone.respond_to?(:current_mode) # TODO: implement for non-IZ2
|
149
|
+
node.property("current-mode", "Current Heating/Cooling Mode Requested", :enum, zone.current_mode,
|
150
|
+
format: %w[standby h1 h2 h3 c1 c2])
|
151
|
+
end
|
152
|
+
node.property("target-fan-mode", "Target Fan Mode", :enum, zone.target_fan_mode,
|
145
153
|
format: %w[auto continuous intermittent]) do |value, property|
|
146
154
|
@mutex.synchronize { property.value = zone.target_fan_mode = value.to_sym }
|
147
155
|
end
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
156
|
+
if zone.respond_to?(:current_fan_mode) # TODO: implement for non-IZ2
|
157
|
+
node.property("current-fan-mode", "Current Fan Status", :boolean, zone.current_fan_mode)
|
158
|
+
node.property("fan-intermittent-on", "Fan Intermittent Mode On Duration", :enum, zone.fan_intermittent_on,
|
159
|
+
unit: "M", format: %w[0 5 10 15 20]) do |value, property|
|
160
|
+
@mutex.synchronize { property.value = zone.fan_intermittent_on = value.to_i }
|
161
|
+
end
|
162
|
+
node.property("fan-intermittent-off", "Fan Intermittent Mode Off Duration", :enum, zone.fan_intermittent_on,
|
163
|
+
unit: "M", format: %w[0 5 10 15 20 25 30 35 40]) do |value, property|
|
164
|
+
@mutex.synchronize { property.value = zone.fan_intermittent_on = value.to_i }
|
165
|
+
end
|
152
166
|
end
|
153
|
-
|
154
|
-
|
155
|
-
|
167
|
+
if zone.is_a?(Aurora::IZ2Zone)
|
168
|
+
node.property("priority", "Zone Priority", :enum, zone.priority, format: %w[economy comfort])
|
169
|
+
node.property("size", "Size", :enum, zone.size, format: %w[0 25 45 70])
|
170
|
+
node.property("normalized-size", "Normalized Size", :integer, zone.normalized_size, unit: "%", format: 0..100)
|
156
171
|
end
|
157
|
-
node.property("priority", "Zone Priority", :enum, zone.priority, format: %w[economy comfort])
|
158
|
-
node.property("size", "Size", :enum, zone.size, format: %w[0 25 45 70])
|
159
|
-
node.property("normalized-size", "Normalized Size", :integer, zone.normalized_size, unit: "%", format: 0..100)
|
160
172
|
node.property("ambient-temperature", "Ambient Temperature", :float, zone.ambient_temperature, unit: "ºF")
|
161
173
|
node.property("heating-target-temperature", "Heating Target Temperature", :integer,
|
162
174
|
zone.heating_target_temperature, unit: "ºF", format: 40..90) do |value, property|
|
@@ -176,4 +188,13 @@ class MQTTBridge
|
|
176
188
|
end
|
177
189
|
end
|
178
190
|
|
179
|
-
|
191
|
+
log_level = ARGV.include?("--debug") ? :debug : :warn
|
192
|
+
logger = Logger.new($stdout)
|
193
|
+
logger.level = log_level
|
194
|
+
slave.logger = logger
|
195
|
+
|
196
|
+
device = "aurora-#{abc.serial_number}"
|
197
|
+
homie = MQTT::Homie::Device.new(device, "Aurora MQTT Bridge", mqtt: mqtt_uri)
|
198
|
+
homie.logger = logger
|
199
|
+
|
200
|
+
MQTTBridge.new(abc, homie)
|
data/lib/aurora/abc_client.rb
CHANGED
@@ -3,7 +3,8 @@
|
|
3
3
|
module Aurora
|
4
4
|
class ABCClient
|
5
5
|
attr_reader :modbus_slave,
|
6
|
-
:
|
6
|
+
:serial_number,
|
7
|
+
:zones,
|
7
8
|
:current_mode,
|
8
9
|
:fan_speed,
|
9
10
|
:entering_air_temperature,
|
@@ -16,26 +17,42 @@ module Aurora
|
|
16
17
|
:compressor_speed,
|
17
18
|
:outdoor_temperature,
|
18
19
|
:fp1,
|
19
|
-
:fp2
|
20
|
+
:fp2,
|
21
|
+
:compressor_watts,
|
22
|
+
:blower_watts,
|
23
|
+
:aux_heat_watts,
|
24
|
+
:loop_pump_watts,
|
25
|
+
:total_watts
|
20
26
|
|
21
27
|
def initialize(modbus_slave)
|
22
28
|
@modbus_slave = modbus_slave
|
23
29
|
@modbus_slave.read_retry_timeout = 15
|
24
30
|
@modbus_slave.read_retries = 2
|
31
|
+
registers_array = @modbus_slave.holding_registers[105...110]
|
32
|
+
registers = registers_array.each_with_index.map { |r, i| [i + 105, r] }.to_h
|
33
|
+
@serial_number = Aurora.transform_registers(registers)[105]
|
25
34
|
iz2_zone_count = @modbus_slave.holding_registers[483]
|
26
|
-
|
35
|
+
# TODO: better detect IZ2/Non-IZ2
|
36
|
+
@zones = if iz2_zone_count > 1
|
37
|
+
(0...iz2_zone_count).map { |i| IZ2Zone.new(self, i + 1) }
|
38
|
+
else
|
39
|
+
[Thermostat.new(self)]
|
40
|
+
end
|
27
41
|
end
|
28
42
|
|
29
43
|
def refresh
|
30
|
-
registers_to_read = [19..20, 30, 344, 740..741, 900, 1110..1111, 1114, 1117, 3027, 31_003]
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
44
|
+
registers_to_read = [19..20, 30, 344, 740..741, 900, 1110..1111, 1114, 1117, 1147..1153, 1165, 3027, 31_003]
|
45
|
+
if zones.first.is_a?(IZ2Zone)
|
46
|
+
zones.each_with_index do |_z, i|
|
47
|
+
base1 = 21_203 + i * 9
|
48
|
+
base2 = 31_007 + i * 3
|
49
|
+
base3 = 31_200 + i * 3
|
50
|
+
registers_to_read << (base1..(base1 + 1))
|
51
|
+
registers_to_read << (base2..(base2 + 2))
|
52
|
+
registers_to_read << base3
|
53
|
+
end
|
54
|
+
else
|
55
|
+
registers_to_read << 745..747
|
39
56
|
end
|
40
57
|
|
41
58
|
registers = @modbus_slave.holding_registers[*registers_to_read]
|
@@ -54,6 +71,11 @@ module Aurora
|
|
54
71
|
@fp1 = registers[19]
|
55
72
|
@fp2 = registers[20]
|
56
73
|
@locked_out = registers[1117]
|
74
|
+
@compressor_watts = registers[1147]
|
75
|
+
@blower_watts = registers[1149]
|
76
|
+
@aux_heat_watts = registers[1151]
|
77
|
+
@loop_pump_watts = registers[1165]
|
78
|
+
@total_watts = registers[1153]
|
57
79
|
|
58
80
|
outputs = registers[30]
|
59
81
|
@current_mode = if outputs.include?(:lockout)
|
@@ -72,7 +94,7 @@ module Aurora
|
|
72
94
|
:standby
|
73
95
|
end
|
74
96
|
|
75
|
-
|
97
|
+
zones.each do |z|
|
76
98
|
z.refresh(registers)
|
77
99
|
end
|
78
100
|
end
|
data/lib/aurora/iz2_zone.rb
CHANGED
@@ -1,22 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "aurora/thermostat"
|
4
|
+
|
3
5
|
module Aurora
|
4
|
-
class IZ2Zone
|
6
|
+
class IZ2Zone < Thermostat
|
5
7
|
attr_reader :zone_number,
|
6
|
-
:target_mode,
|
7
8
|
:current_mode,
|
8
|
-
:target_fan_mode,
|
9
9
|
:current_fan_mode,
|
10
10
|
:fan_intermittent_on,
|
11
11
|
:fan_intermittent_off,
|
12
12
|
:priority,
|
13
|
-
:size, :normalized_size
|
14
|
-
:ambient_temperature,
|
15
|
-
:cooling_target_temperature,
|
16
|
-
:heating_target_temperature
|
13
|
+
:size, :normalized_size
|
17
14
|
|
18
15
|
def initialize(abc, zone_number)
|
19
|
-
|
16
|
+
super(abc)
|
20
17
|
@zone_number = zone_number
|
21
18
|
end
|
22
19
|
|
@@ -42,68 +39,47 @@ module Aurora
|
|
42
39
|
end
|
43
40
|
|
44
41
|
def target_mode=(value)
|
45
|
-
|
46
|
-
return unless value
|
42
|
+
return unless (raw_value = Aurora::HEATING_MODE.invert[value])
|
47
43
|
|
48
|
-
@abc.modbus_slave.holding_registers[21_202 + (zone_number - 1) * 9] =
|
49
|
-
@target_mode =
|
44
|
+
@abc.modbus_slave.holding_registers[21_202 + (zone_number - 1) * 9] = raw_value
|
45
|
+
@target_mode = value
|
50
46
|
end
|
51
47
|
|
52
48
|
def target_fan_mode=(value)
|
53
|
-
|
54
|
-
return unless value
|
49
|
+
return unless (raw_value = Aurora::FAN_MODE.invert[value])
|
55
50
|
|
56
|
-
@abc.modbus_slave.holding_registers[21_205 + (zone_number - 1) * 9] =
|
57
|
-
|
58
|
-
Aurora.transform_registers(registers)
|
59
|
-
@target_fan_mode = registers.first.last[:fan]
|
51
|
+
@abc.modbus_slave.holding_registers[21_205 + (zone_number - 1) * 9] = raw_value
|
52
|
+
@target_fan_mode = value
|
60
53
|
end
|
61
54
|
|
62
55
|
def fan_intermittent_on=(value)
|
63
56
|
return unless value >= 0 && value <= 25 && (value % 5).zero?
|
64
57
|
|
65
58
|
@abc.modbus_slave.holding_registers[21_206 + (zone_number - 1) * 9] = value
|
66
|
-
|
67
|
-
Aurora.transform_registers(registers)
|
68
|
-
@fan_intermittent_on = registers.first.last[:on_time]
|
59
|
+
@fan_intermittent_on = value
|
69
60
|
end
|
70
61
|
|
71
62
|
def fan_intermittent_off=(value)
|
72
63
|
return unless value >= 0 && value <= 40 && (value % 5).zero?
|
73
64
|
|
74
65
|
@abc.modbus_slave.holding_registers[21_207 + (zone_number - 1) * 9] = value
|
75
|
-
|
76
|
-
Aurora.transform_registers(registers)
|
77
|
-
@fan_intermittent_on = registers.first.last[:off_time]
|
66
|
+
@fan_intermittent_off = value
|
78
67
|
end
|
79
68
|
|
80
69
|
def heating_target_temperature=(value)
|
81
70
|
return unless value >= 40 && value <= 90
|
82
71
|
|
83
|
-
|
84
|
-
@abc.modbus_slave.holding_registers[21_203 + (zone_number - 1) * 9] =
|
85
|
-
|
86
|
-
base = 31_008 + (zone_number - 1) * 3
|
87
|
-
registers = @abc.modbus_slave.read_multiple_holding_registers(base..(base + 1))
|
88
|
-
Aurora.transform_registers(registers)
|
89
|
-
registers[base + 1][:heating_target_temperature]
|
72
|
+
raw_value = (value * 10).to_i
|
73
|
+
@abc.modbus_slave.holding_registers[21_203 + (zone_number - 1) * 9] = raw_value
|
74
|
+
@heating_target_temperature = value
|
90
75
|
end
|
91
76
|
|
92
77
|
def cooling_target_temperature=(value)
|
93
78
|
return unless value >= 54 && value <= 99
|
94
79
|
|
95
|
-
|
80
|
+
raw_value = (value * 10).to_i
|
96
81
|
@abc.modbus_slave.holding_registers[21_204 + (zone_number - 1) * 9] = value
|
97
|
-
|
98
|
-
registers = @abc.modbus_slave.read_multiple_holding_registers(31_008 + (zone_number - 1) * 3)
|
99
|
-
Aurora.transform_registers(registers)
|
100
|
-
registers.first.last[:cooling_target_temperature]
|
101
|
-
end
|
102
|
-
|
103
|
-
def inspect
|
104
|
-
"#<Aurora::IZ2Zone #{(instance_variables - [:@abc]).map do |iv|
|
105
|
-
"#{iv}=#{instance_variable_get(iv).inspect}"
|
106
|
-
end.join(', ')}>"
|
82
|
+
@cooling_target_temperature = raw_value
|
107
83
|
end
|
108
84
|
end
|
109
85
|
end
|
data/lib/aurora/modbus/slave.rb
CHANGED
@@ -17,7 +17,7 @@ module Aurora
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def holding_registers
|
20
|
-
WFProxy.new(self, :holding_register)
|
20
|
+
@holding_registers ||= WFProxy.new(self, :holding_register)
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
@@ -33,7 +33,6 @@ module Aurora
|
|
33
33
|
def read_rtu_response(io)
|
34
34
|
# Read the slave_id and function code
|
35
35
|
msg = read(io, 2)
|
36
|
-
log logging_bytes(msg)
|
37
36
|
|
38
37
|
function_code = msg.getbyte(1)
|
39
38
|
case function_code
|
data/lib/aurora/registers.rb
CHANGED
@@ -52,7 +52,6 @@ module Aurora
|
|
52
52
|
end
|
53
53
|
|
54
54
|
def to_string(registers, idx, length)
|
55
|
-
puts "converting #{idx} of length #{length}"
|
56
55
|
(idx...(idx + length)).map do |i|
|
57
56
|
(registers[i] >> 8).chr + (registers[i] & 0xff).chr
|
58
57
|
end.join.sub(/[ \0]+$/, "")
|
@@ -323,7 +322,8 @@ module Aurora
|
|
323
322
|
REGISTER_CONVERTERS = {
|
324
323
|
TO_HUNDREDTHS => [2, 3, 807, 813, 816, 817, 819, 820, 825, 828],
|
325
324
|
method(:dipswitch_settings) => [4, 33],
|
326
|
-
TO_TENTHS => [19, 20, 401, 567, 740, 745, 746, 900, 1105, 1106, 1107, 1108, 1110, 1111, 1114, 1117, 1134, 1136,
|
325
|
+
TO_TENTHS => [19, 20, 401, 567, 740, 745, 746, 747, 900, 1105, 1106, 1107, 1108, 1110, 1111, 1114, 1117, 1134, 1136,
|
326
|
+
12_619, 12_620,
|
327
327
|
21_203, 21_204,
|
328
328
|
21_212, 21_213,
|
329
329
|
21_221, 21_222,
|
@@ -348,8 +348,8 @@ module Aurora
|
|
348
348
|
->(v) { from_bitmask(v, AXB_INPUTS) } => [1103],
|
349
349
|
->(v) { from_bitmask(v, AXB_OUTPUTS) } => [1104],
|
350
350
|
->(v) { TO_TENTHS.call(NEGATABLE.call(v)) } => [1136],
|
351
|
-
->(v) { HEATING_MODE[v] } => [21_202, 21_211, 21_220, 21_229, 21_238, 21_247],
|
352
|
-
->(v) { FAN_MODE[v] } => [21_205, 21_214, 21_223, 21_232, 21_241, 21_250],
|
351
|
+
->(v) { HEATING_MODE[v] } => [12_602, 21_202, 21_211, 21_220, 21_229, 21_238, 21_247],
|
352
|
+
->(v) { FAN_MODE[v] } => [12_621, 21_205, 21_214, 21_223, 21_232, 21_241, 21_250],
|
353
353
|
->(v) { from_bitmask(v, HUMIDIFIER_SETTINGS) } => [31_109],
|
354
354
|
->(v) { { humidification_target: v >> 8, dehumidification_target: v & 0xff } } => [31_110],
|
355
355
|
method(:iz2_demand) => [31_005],
|
@@ -367,7 +367,8 @@ module Aurora
|
|
367
367
|
REGISTER_FORMATS = {
|
368
368
|
"%ds" => [1, 6, 9, 15, 84, 85],
|
369
369
|
"%dV" => [16, 112],
|
370
|
-
"%0.1fºF" => [19, 20, 401, 567, 740, 745, 746, 900, 1110, 1111, 1114, 1134, 1136,
|
370
|
+
"%0.1fºF" => [19, 20, 401, 567, 740, 745, 746, 747, 900, 1110, 1111, 1114, 1134, 1136,
|
371
|
+
12_619, 12_620,
|
371
372
|
21_203, 21_204,
|
372
373
|
21_212, 21_213,
|
373
374
|
21_221, 21_222,
|
@@ -398,7 +399,7 @@ module Aurora
|
|
398
399
|
base2 = 31_007 + (i - 1) * 3
|
399
400
|
base3 = 31_200 + (i - 1) * 3
|
400
401
|
{
|
401
|
-
base1 => "Zone #{i} Heating Mode",
|
402
|
+
base1 => "Zone #{i} Heating Mode (write)",
|
402
403
|
(base1 + 1) => "Zone #{i} Heating Setpoint (write)",
|
403
404
|
(base1 + 2) => "Zone #{i} Cooling Setpoint (write)",
|
404
405
|
(base1 + 3) => "Zone #{i} Fan Mode (write)",
|
@@ -493,25 +494,6 @@ module Aurora
|
|
493
494
|
61_000..61_009
|
494
495
|
].freeze
|
495
496
|
|
496
|
-
def read_all_registers(modbus_slave)
|
497
|
-
result = []
|
498
|
-
REGISTER_RANGES.each do |range|
|
499
|
-
# read at most 100 at a time
|
500
|
-
range.each_slice(100) do |keys|
|
501
|
-
result.concat(modbus_slave.holding_registers[keys.first..keys.last])
|
502
|
-
end
|
503
|
-
end
|
504
|
-
REGISTER_RANGES.map(&:to_a).flatten.zip(result).to_h
|
505
|
-
end
|
506
|
-
|
507
|
-
def diff_registers(lhs, rhs)
|
508
|
-
diff = {}
|
509
|
-
lhs.each_key do |k|
|
510
|
-
diff[k] = [lhs[k], rhs[k]] if lhs[k] != rhs[k]
|
511
|
-
end
|
512
|
-
diff
|
513
|
-
end
|
514
|
-
|
515
497
|
REGISTER_NAMES = {
|
516
498
|
1 => "Random Start Delay",
|
517
499
|
2 => "ABC Program Version",
|
@@ -579,6 +561,7 @@ module Aurora
|
|
579
561
|
741 => "Relative Humidity",
|
580
562
|
745 => "Heating Set Point",
|
581
563
|
746 => "Cooling Set Point",
|
564
|
+
747 => "Ambient Temperature",
|
582
565
|
807 => "AXB Version",
|
583
566
|
813 => "IZ2 Version?",
|
584
567
|
816 => "AOC Version 1?",
|
@@ -607,6 +590,10 @@ module Aurora
|
|
607
590
|
1153 => "Total Watts",
|
608
591
|
1157 => "Ht of Rej",
|
609
592
|
1165 => "VS Pump Watts",
|
593
|
+
12_602 => "Heating Mode (write)",
|
594
|
+
12_619 => "Heating Setpoint (write)",
|
595
|
+
12_620 => "Cooling Setpoint (write)",
|
596
|
+
12_621 => "Fan Mode (write)",
|
610
597
|
3027 => "Compressor Speed",
|
611
598
|
31_003 => "Outdoor Temp",
|
612
599
|
31_005 => "IZ2 Demand",
|
@@ -641,6 +628,25 @@ module Aurora
|
|
641
628
|
registers
|
642
629
|
end
|
643
630
|
|
631
|
+
def read_all_registers(modbus_slave)
|
632
|
+
result = []
|
633
|
+
REGISTER_RANGES.each do |range|
|
634
|
+
# read at most 100 at a time
|
635
|
+
range.each_slice(100) do |keys|
|
636
|
+
result.concat(modbus_slave.holding_registers[keys.first..keys.last])
|
637
|
+
end
|
638
|
+
end
|
639
|
+
REGISTER_RANGES.map(&:to_a).flatten.zip(result).to_h
|
640
|
+
end
|
641
|
+
|
642
|
+
def diff_registers(lhs, rhs)
|
643
|
+
diff = {}
|
644
|
+
(lhs.keys | rhs.keys).each do |k|
|
645
|
+
diff[k] = rhs[k] if lhs[k] != rhs[k]
|
646
|
+
end
|
647
|
+
diff
|
648
|
+
end
|
649
|
+
|
644
650
|
def print_registers(registers)
|
645
651
|
result = []
|
646
652
|
registers.each do |(k, value)|
|
@@ -648,15 +654,17 @@ module Aurora
|
|
648
654
|
next if REGISTER_NAMES.key?(k) && REGISTER_NAMES[k].nil?
|
649
655
|
|
650
656
|
name = REGISTER_NAMES[k]
|
657
|
+
|
651
658
|
value_proc = REGISTER_CONVERTERS.find { |(_, z)| z.include?(k) }&.first || ->(v) { v }
|
652
659
|
format = REGISTER_FORMATS.find { |(_, z)| z.include?(k) }&.first || "%s"
|
653
660
|
format = "%1$d (0x%1$04x)" unless name
|
654
|
-
name ||= "???"
|
655
661
|
|
656
662
|
value = value_proc.arity == 2 ? value_proc.call(registers, k) : value_proc.call(value)
|
657
663
|
value = value.join(", ") if value.is_a?(Array)
|
658
664
|
value = format(format, value) if value
|
659
665
|
|
666
|
+
name ||= "???"
|
667
|
+
|
660
668
|
result << "#{name} (#{k}): #{value}"
|
661
669
|
end
|
662
670
|
result.join("\n")
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Aurora
|
4
|
+
class Thermostat
|
5
|
+
attr_reader :target_mode,
|
6
|
+
:target_fan_mode,
|
7
|
+
:ambient_temperature,
|
8
|
+
:cooling_target_temperature,
|
9
|
+
:heating_target_temperature
|
10
|
+
|
11
|
+
def initialize(abc)
|
12
|
+
@abc = abc
|
13
|
+
end
|
14
|
+
|
15
|
+
def refresh(registers)
|
16
|
+
@ambient_temperature = registers[747]
|
17
|
+
@heating_target_temperature = registers[746]
|
18
|
+
@cooling_target_temperature = registers[745]
|
19
|
+
end
|
20
|
+
|
21
|
+
def target_mode=(value)
|
22
|
+
return unless (raw_value = HEATING_MODE.invert[value])
|
23
|
+
|
24
|
+
@abc.modbus_slave.holding_registers[12_602] = raw_value
|
25
|
+
@target_mode = value
|
26
|
+
end
|
27
|
+
|
28
|
+
def target_fan_mode=(value)
|
29
|
+
return unless (raw_value = FAN_MODE.invert[value])
|
30
|
+
|
31
|
+
@abc.modbus_slave.holding_registers[12_621] = raw_value
|
32
|
+
@target_fan_mode = value
|
33
|
+
end
|
34
|
+
|
35
|
+
def heating_target_temperature=(value)
|
36
|
+
return unless value >= 40 && value <= 90
|
37
|
+
|
38
|
+
raw_value = (value * 10).to_i
|
39
|
+
@abc.modbus_slave.holding_registers[12_619] = raw_value
|
40
|
+
@heating_target_temperature = value
|
41
|
+
end
|
42
|
+
|
43
|
+
def cooling_target_temperature=(value)
|
44
|
+
return unless value >= 54 && value <= 99
|
45
|
+
|
46
|
+
raw_value = (value * 10).to_i
|
47
|
+
@abc.modbus_slave.holding_registers[12_620] = raw_value
|
48
|
+
@cooling_target_temperature = value
|
49
|
+
end
|
50
|
+
|
51
|
+
def inspect
|
52
|
+
"#<Aurora::#{self.class.name} #{(instance_variables - [:@abc]).map do |iv|
|
53
|
+
"#{iv}=#{instance_variable_get(iv).inspect}"
|
54
|
+
end.join(', ')}>"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/aurora/version.rb
CHANGED
data/lib/aurora.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: waterfurnace_aurora
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.3
|
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-08-
|
11
|
+
date: 2021-08-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ccutrer-serialport
|
@@ -30,14 +30,14 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 1.4.
|
33
|
+
version: 1.4.4
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: 1.4.
|
40
|
+
version: 1.4.4
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: net-telnet-rfc2217
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -58,14 +58,14 @@ dependencies:
|
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '2.
|
61
|
+
version: '2.1'
|
62
62
|
type: :runtime
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '2.
|
68
|
+
version: '2.1'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: byebug
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -125,10 +125,12 @@ files:
|
|
125
125
|
- exe/registers.yml
|
126
126
|
- lib/aurora.rb
|
127
127
|
- lib/aurora/abc_client.rb
|
128
|
+
- lib/aurora/core_ext/string.rb
|
128
129
|
- lib/aurora/iz2_zone.rb
|
129
130
|
- lib/aurora/modbus/server.rb
|
130
131
|
- lib/aurora/modbus/slave.rb
|
131
132
|
- lib/aurora/registers.rb
|
133
|
+
- lib/aurora/thermostat.rb
|
132
134
|
- lib/aurora/version.rb
|
133
135
|
- lib/waterfurnace_aurora.rb
|
134
136
|
homepage: https://github.com/ccutrer/waterfurnace
|