waterfurnace_aurora 0.2.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Aurora
2
- VERSION = '0.2.0'
4
+ VERSION = "0.3.1"
3
5
  end
data/lib/aurora.rb CHANGED
@@ -1,10 +1,13 @@
1
- require 'rmodbus'
1
+ # frozen_string_literal: true
2
2
 
3
- require 'aurora/abc_client'
4
- require 'aurora/iz2_zone'
5
- require 'aurora/modbus/server'
6
- require 'aurora/modbus/slave'
7
- require 'aurora/registers'
3
+ require "rmodbus"
4
+
5
+ require "aurora/abc_client"
6
+ require "aurora/iz2_zone"
7
+ require "aurora/thermostat"
8
+ require "aurora/modbus/server"
9
+ require "aurora/modbus/slave"
10
+ require "aurora/registers"
8
11
 
9
12
  # extend ModBus for WaterFurnace's custom functions
10
13
  ModBus::RTUServer.include(Aurora::ModBus::Server)
@@ -1 +1,3 @@
1
- require 'aurora'
1
+ # frozen_string_literal: true
2
+
3
+ require "aurora"
metadata CHANGED
@@ -1,57 +1,57 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: waterfurnace_aurora
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cody Cutrer
8
8
  autorequire:
9
- bindir: bin
9
+ bindir: exe
10
10
  cert_chain: []
11
- date: 2021-08-23 00:00:00.000000000 Z
11
+ date: 2021-08-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: homie-mqtt
14
+ name: ccutrer-serialport
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 1.4.1
19
+ version: 1.0.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 1.4.1
26
+ version: 1.0.0
27
27
  - !ruby/object:Gem::Dependency
28
- name: net-telnet-rfc2217
28
+ name: homie-mqtt
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 0.0.4
33
+ version: 1.4.2
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: 0.0.4
40
+ version: 1.4.2
41
41
  - !ruby/object:Gem::Dependency
42
- name: ccutrer-serialport
42
+ name: net-telnet-rfc2217
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 1.0.0
47
+ version: 0.0.4
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: 1.0.0
54
+ version: 0.0.4
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rmodbus-ccutrer
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -81,51 +81,56 @@ dependencies:
81
81
  - !ruby/object:Gem::Version
82
82
  version: '9.0'
83
83
  - !ruby/object:Gem::Dependency
84
- name: rake
84
+ name: gserver
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: '13.0'
89
+ version: 0.0.1
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: '13.0'
96
+ version: 0.0.1
97
97
  - !ruby/object:Gem::Dependency
98
- name: gserver
98
+ name: rubocop
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
101
  - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: 0.0.1
103
+ version: '1.19'
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
- version: 0.0.1
110
+ version: '1.19'
111
111
  description:
112
112
  email: cody@cutrer.com'
113
113
  executables:
114
+ - aurora_monitor
114
115
  - aurora_mqtt_bridge
116
+ - aurora_mock
117
+ - aurora_fetch
115
118
  extensions: []
116
119
  extra_rdoc_files: []
117
120
  files:
118
- - bin/aurora_fetch
119
- - bin/aurora_mock
120
- - bin/aurora_monitor
121
- - bin/aurora_mqtt_bridge
122
- - bin/registers.yml
121
+ - exe/aurora_fetch
122
+ - exe/aurora_mock
123
+ - exe/aurora_monitor
124
+ - exe/aurora_mqtt_bridge
125
+ - exe/registers.yml
123
126
  - lib/aurora.rb
124
127
  - lib/aurora/abc_client.rb
128
+ - lib/aurora/core_ext/string.rb
125
129
  - lib/aurora/iz2_zone.rb
126
130
  - lib/aurora/modbus/server.rb
127
131
  - lib/aurora/modbus/slave.rb
128
132
  - lib/aurora/registers.rb
133
+ - lib/aurora/thermostat.rb
129
134
  - lib/aurora/version.rb
130
135
  - lib/waterfurnace_aurora.rb
131
136
  homepage: https://github.com/ccutrer/waterfurnace
@@ -140,7 +145,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
140
145
  requirements:
141
146
  - - ">="
142
147
  - !ruby/object:Gem::Version
143
- version: '0'
148
+ version: '2.6'
144
149
  required_rubygems_version: !ruby/object:Gem::Requirement
145
150
  requirements:
146
151
  - - ">="
data/bin/aurora_fetch DELETED
@@ -1,25 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require 'aurora'
4
- require 'ccutrer-serialport'
5
- require 'uri'
6
-
7
- uri = URI.parse(ARGV[0])
8
-
9
- args = if uri.scheme == "telnet" || uri.scheme == "rfc2217"
10
- require 'net/telnet/rfc2217'
11
- [Net::Telnet::RFC2217.new('Host' => uri.host,
12
- 'Port' => uri.port || 23,
13
- 'baud' => 19200,
14
- 'parity' => Net::Telnet::RFC2217::EVEN)]
15
- else
16
- [CCutrer::SerialPort.new(uri.path, baud: 19200, parity: :even)]
17
- end
18
-
19
- client = ModBus::RTUClient.new(*args)
20
- client.debug = true
21
- slave = client.with_slave(1)
22
-
23
- registers = slave.holding_registers[ARGV[1].to_i]
24
-
25
- puts registers.inspect
data/bin/aurora_monitor DELETED
@@ -1,76 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require 'aurora'
4
- require 'ccutrer-serialport'
5
- require 'socket'
6
- require 'uri'
7
-
8
- uri = URI.parse(ARGV[0])
9
-
10
- args = if uri.scheme == "telnet" || uri.scheme == "rfc2217"
11
- require 'net/telnet/rfc2217'
12
- [Net::Telnet::RFC2217.new('Host' => uri.host,
13
- 'Port' => uri.port || 23,
14
- 'baud' => 19200,
15
- 'parity' => Net::Telnet::RFC2217::EVEN)]
16
- else
17
- [CCutrer::SerialPort.new(uri.path, baud: 19200, parity: :even)]
18
- end
19
-
20
- server = ModBus::RTUServer.new(*args)
21
- server.promiscuous = true
22
- server.debug = true
23
-
24
- server.request_callback = ->(uid, func, req) do
25
- if func == 68
26
- puts "===== no idea to #{uid}: #{req.inspect}"
27
- elsif func == 67
28
- puts "===== write discontiguous registers to #{uid}:"
29
- registers = req.map { |p| [p[:addr], p[:val]] }.to_h
30
- puts Aurora.print_registers(registers)
31
- elsif func == 16
32
- puts "===== write multiple registers to #{uid}:"
33
- registers = Range.new(req[:addr], req[:addr] + req[:quant] - 1).zip(req[:val]).to_h
34
- puts Aurora.print_registers(registers)
35
- elsif [3, 65, 66].include?(func)
36
- else
37
- puts "**** new func #{func}"
38
- end
39
- end
40
-
41
- server.response_callback = ->(uid, func, res, req) do
42
- if func == 3 && res.is_a?(Array) && req
43
- unless req[:quant] == res.length
44
- puts "wrong number of results"
45
- next
46
- end
47
- puts "===== read registers from #{uid}"
48
- registers = Range.new(req[:addr], req[:addr] + req[:quant], true).to_a.zip(res).to_h
49
- puts Aurora.print_registers(registers)
50
- elsif func == 65 && res.is_a?(Array) && req
51
- register_list = []
52
- req.each { |params| register_list.concat(Range.new(params[:addr], params[:addr] + params[:quant], true).to_a) }
53
- unless register_list.length == res.length
54
- puts "wrong number of results"
55
- next
56
- end
57
- puts "===== read multiple register ranges from #{uid}"
58
- result = register_list.zip(res).to_h
59
- puts Aurora.print_registers(result)
60
- elsif func == 66 && res.is_a?(Array) && req
61
- unless req.length == res.length
62
- puts "wrong number of results"
63
- next
64
- end
65
- puts "===== read discontiguous registers from #{uid}"
66
- registers = req.zip(res).to_h
67
- puts Aurora.print_registers(registers)
68
- elsif [16, 67, 68].include?(func)
69
- else
70
- puts "**** new func #{func}"
71
- end
72
- end
73
-
74
- require 'byebug'
75
- server.send(:serve, server.instance_variable_get(:@sp))
76
- loop {}
@@ -1,179 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require 'aurora'
4
- require 'homie-mqtt'
5
- require 'ccutrer-serialport'
6
- require 'uri'
7
-
8
- uri = URI.parse(ARGV[0])
9
- mqtt_uri = ARGV[1]
10
-
11
- args = if uri.scheme == "telnet" || uri.scheme == "rfc2217"
12
- require 'net/telnet/rfc2217'
13
- [Net::Telnet::RFC2217.new('Host' => uri.host,
14
- 'Port' => uri.port || 23,
15
- 'baud' => 19200,
16
- 'parity' => Net::Telnet::RFC2217::EVEN)]
17
- else
18
- [CCutrer::SerialPort.new(uri.path, baud: 19200, parity: :even)]
19
- end
20
-
21
- slave = ModBus::RTUClient.new(*args).with_slave(1)
22
- abc = Aurora::ABCClient.new(slave)
23
-
24
- class MQTTBridge
25
- def initialize(abc, homie)
26
- @abc = abc
27
- @homie = homie
28
- @mutex = Mutex.new
29
-
30
- @homie.instance_variable_set(:@block, ->(topic, value) do
31
- @mutex.synchronize do
32
- case topic
33
- when /\$modbus$/
34
- query = value.split(',').map do |addr|
35
- if addr == 'known'
36
- Aurora::REGISTER_NAMES.keys
37
- elsif addr =~ /^(\d+)\.\.(\d+)$/
38
- Regexp.last_match(1).to_i..Regexp.last_match(2).to_i
39
- else
40
- addr.to_i
41
- end
42
- end
43
- queries = Aurora.normalize_ranges(query)
44
- registers = {}
45
- queries.each do |query|
46
- registers.merge!(@abc.modbus_slave.read_multiple_holding_registers(*query))
47
- end
48
- result = Aurora.print_registers(registers)
49
- @homie.mqtt.publish("#{@home.topic}/$modbus/response", result, retain: false, qos: 1)
50
- when %r{\$modbus/(\d+)$}
51
- register = Regexp.last_match(1).to_i
52
- value = case value
53
- when /\d+/
54
- value.to_i
55
- when /0x(\d+)/
56
- Regexp.last_match(1).to_i(16)
57
- end
58
- @abc.modbus_slave.holding_registers[register] = value if value
59
- end
60
- end
61
- rescue StandardError => e
62
- puts "failed processing message: #{e}\n#{e.backtrace}"
63
- end)
64
-
65
- publish_basic_attributes
66
-
67
- loop do
68
- begin
69
- @mutex.synchronize do
70
- @abc.refresh
71
- @homie_abc["compressor-speed"].value = @abc.compressor_speed
72
- @homie_abc["current-mode"].value = @abc.current_mode
73
- @homie_abc["dhw-water-temperature"].value = @abc.dhw_water_temperature
74
- @homie_abc["entering-air-temperature"].value = @abc.entering_air_temperature
75
- @homie_abc["entering-water-temperature"].value = @abc.entering_water_temperature
76
- @homie_abc["fan-speed"].value = @abc.fan_speed
77
- @homie_abc["leaving-air-temperature"].value = @abc.leaving_air_temperature
78
- @homie_abc["leaving-water-temperature"].value = @abc.leaving_water_temperature
79
- @homie_abc["outdoor-temperature"].value = @abc.outdoor_temperature
80
- @homie_abc["relative-humidity"].value = @abc.relative_humidity
81
- @homie_abc["waterflow"].value = @abc.waterflow
82
- @homie_abc["fp1"].value = @abc.fp1
83
- @homie_abc["fp2"].value = @abc.fp2
84
-
85
- @abc.iz2_zones.each do |z|
86
- homie_zone = @homie["zone#{z.zone_number}"]
87
- homie_zone["target-mode"].value = z.target_mode
88
- homie_zone["current-mode"].value = z.current_mode
89
- homie_zone["target-fan-mode"].value = z.target_fan_mode
90
- homie_zone["current-fan-mode"].value = z.current_fan_mode
91
- homie_zone["fan-intermittent-on"].value = z.fan_intermittent_on
92
- homie_zone["fan-intermittent-off"].value = z.fan_intermittent_off
93
- homie_zone["priority"].value = z.priority
94
- homie_zone["size"].value = z.size
95
- homie_zone["normalized-size"].value = z.normalized_size
96
- homie_zone["ambient-temperature"].value = z.ambient_temperature
97
- homie_zone["heating-target-temperature"].value = z.heating_target_temperature
98
- homie_zone["cooling-target-temperature"].value = z.cooling_target_temperature
99
- end
100
- end
101
- rescue => e
102
- puts "got garbage: #{e}; #{e.backtrace}"
103
- exit 1
104
- end
105
- sleep(5)
106
- end
107
- end
108
-
109
- def publish(topic, value)
110
- @mqtt.publish("#{@base_topic}/#{topic}", value, true, 1)
111
- end
112
-
113
- def publish_attribute(attr, value)
114
- if !@attributes.key?(attr) || @attributes[attr] != value
115
- publish(attr, value.to_s)
116
- @attributes[attr] = value
117
- end
118
- end
119
-
120
- def subscribe(topic)
121
- @mqtt.subscribe("#{@base_topic}/#{topic}")
122
- end
123
-
124
- def publish_basic_attributes
125
- @homie_abc = @homie.node("abc", "Aurora Basic Control", "ABC") do |node|
126
- node.property("compressor-speed", "Compressor Speed", :integer, @abc.compressor_speed, format: 0..1)
127
- node.property("current-mode", "Current Heating/Cooling Mode", :enum, @abc.current_mode, format: %w[lockout standby blower h1 h2 c1 c2 eh1 eh2])
128
- node.property("dhw-water-temperature", "DHW Water Temperature", :float, @abc.dhw_water_temperature, unit: "ºF")
129
- node.property("entering-air-temperature", "Entering Air Temperature", :float, @abc.entering_air_temperature, unit: "ºF")
130
- node.property("entering-water-temperature", "Entering Water Temperature", :float, @abc.entering_water_temperature, unit: "ºF")
131
- node.property("fan-speed", "Fan Speed", :integer, @abc.fan_speed, format: 0..11)
132
- node.property("leaving-air-temperature", "Leaving Air Temperature", :float, @abc.leaving_air_temperature, unit: "ºF")
133
- node.property("leaving-water-temperature", "Leaving Water Temperature", :float, @abc.leaving_water_temperature, unit: "ºF")
134
- node.property("outdoor-temperature", "Outdoor Temperature", :float, @abc.outdoor_temperature, unit: "ºF")
135
- node.property("relative-humidity", "Relative Humidity", :integer, @abc.relative_humidity, unit: "%", format: 0..100)
136
- node.property("waterflow", "Waterflow", :float, unit: "gpm")
137
- node.property("fp1", "FP1 Sensor", :float, @abc.fp1, unit: "ºF")
138
- node.property("fp2", "FP2 Sensor", :float, @abc.fp2, unit: "ºF")
139
- end
140
-
141
- @abc.iz2_zones.each_with_index do |zone|
142
- @homie.node("zone#{zone.zone_number}", "Zone #{zone.zone_number}", "IntelliZone 2 Zone") do |node|
143
- allowed_modes = %w[off auto cool heat]
144
- allowed_modes << "eheat" if zone.zone_number == 1
145
- node.property("target-mode", "Target Heating/Cooling Mode", :enum, zone.target_mode, format: allowed_modes) do |value, property|
146
- @mutex.synchronize { property.value = zone.target_mode = value.to_sym }
147
- end
148
- node.property("current-mode", "Current Heating/Cooling Mode Requested", :enum, zone.current_mode, format: %w[standby h1 h2 h3 c1 c2])
149
- node.property("target-fan-mode", "Target Fan Mode", :enum, zone.target_fan_mode, format: %w[auto continuous intermittent]) do |value, property|
150
- @mutex.synchronize { property.value = zone.target_fan_mode = value.to_sym }
151
- end
152
- node.property("current-fan-mode", "Current Fan Status", :boolean, zone.current_fan_mode)
153
- node.property("fan-intermittent-on", "Fan Intermittent Mode On Duration", :enum, zone.fan_intermittent_on, unit: "M", format: %w[0 5 10 15 20]) do |value, property|
154
- @mutex.synchronize { property.value = zone.fan_intermittent_on = value.to_i }
155
- end
156
- node.property("fan-intermittent-off", "Fan Intermittent Mode Off Duration", :enum, zone.fan_intermittent_on, unit: "M", format: %w[0 5 10 15 20 25 30 35 40]) do |value, property|
157
- @mutex.synchronize { property.value = zone.fan_intermittent_on = value.to_i }
158
- end
159
- node.property("priority", "Zone Priority", :enum, zone.priority, format: %w[economy comfort])
160
- node.property("size", "Size", :enum, zone.size, format: %w[0 25 45 70])
161
- node.property("normalized-size", "Normalized Size", :integer, zone.normalized_size, unit: "%", format: 0..100)
162
- node.property("ambient-temperature", "Ambient Temperature", :float, zone.ambient_temperature, unit: "ºF")
163
- node.property("heating-target-temperature", "Heating Target Temperature", :integer, zone.heating_target_temperature, unit: "ºF", format: 40..90) do |value, property|
164
- @mutex.synchronize { property.value = zone.heating_target_temperature = value }
165
- end
166
- node.property("cooling-target-temperature", "Cooling Target Temperature", :integer, zone.cooling_target_temperature, unit: "ºF", format: 54..99) do |value, property|
167
- @mutex.synchronize { property.value = zone.cooling_target_temperature = value }
168
- end
169
- end
170
- end
171
-
172
- # direct access to modbus registers for debugging purposes
173
- @homie.mqtt.subscribe("#{@homie.topic}/$modbus")
174
- @homie.mqtt.subscribe("#{@homie.topic}/$modbus/+")
175
- @homie.publish
176
- end
177
- end
178
-
179
- MQTTBridge.new(abc, MQTT::Homie::Device.new("aurora", "Aurora MQTT Bridge", mqtt: mqtt_uri))