sappho-heatmiser-proxy 0.0.1
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.
- data/bin/sappho-heatmiser-proxy +12 -0
- data/lib/sappho-heatmiser-proxy.rb +28 -0
- data/lib/sappho-heatmiser-proxy/client_register.rb +62 -0
- data/lib/sappho-heatmiser-proxy/command_queue.rb +60 -0
- data/lib/sappho-heatmiser-proxy/heatmiser.rb +107 -0
- data/lib/sappho-heatmiser-proxy/heatmiser_client.rb +70 -0
- data/lib/sappho-heatmiser-proxy/heatmiser_crc.rb +48 -0
- data/lib/sappho-heatmiser-proxy/heatmiser_proxy.rb +42 -0
- data/lib/sappho-heatmiser-proxy/heatmiser_status.rb +136 -0
- data/lib/sappho-heatmiser-proxy/system_configuration.rb +27 -0
- data/lib/sappho-heatmiser-proxy/trace_log.rb +60 -0
- data/lib/sappho-heatmiser-proxy/version.rb +18 -0
- metadata +93 -0
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# See https://github.com/sappho/sappho-heatmiser-proxy/wiki for project documentation.
|
4
|
+
# This software is licensed under the GNU Affero General Public License, version 3.
|
5
|
+
# See http://www.gnu.org/licenses/agpl.html for full details of the license terms.
|
6
|
+
# Copyright 2012 Andrew Heald.
|
7
|
+
|
8
|
+
$:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
|
9
|
+
|
10
|
+
require 'sappho-heatmiser-proxy'
|
11
|
+
|
12
|
+
Sappho::Heatmiser::Proxy::CommandLine.process
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# See https://github.com/sappho/sappho-heatmiser-proxy/wiki for project documentation.
|
2
|
+
# This software is licensed under the GNU Affero General Public License, version 3.
|
3
|
+
# See http://www.gnu.org/licenses/agpl.html for full details of the license terms.
|
4
|
+
# Copyright 2012 Andrew Heald.
|
5
|
+
|
6
|
+
module Sappho
|
7
|
+
module Heatmiser
|
8
|
+
module Proxy
|
9
|
+
|
10
|
+
require 'sappho-heatmiser-proxy/heatmiser'
|
11
|
+
require 'sappho-heatmiser-proxy/heatmiser_proxy'
|
12
|
+
require 'thread'
|
13
|
+
|
14
|
+
class CommandLine
|
15
|
+
|
16
|
+
def CommandLine.process
|
17
|
+
Thread.abort_on_exception = true
|
18
|
+
hm = Heatmiser.new
|
19
|
+
hm.monitor
|
20
|
+
HeatmiserProxy.new.serve
|
21
|
+
hm.wait
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# See https://github.com/sappho/sappho-heatmiser-proxy/wiki for project documentation.
|
2
|
+
# This software is licensed under the GNU Affero General Public License, version 3.
|
3
|
+
# See http://www.gnu.org/licenses/agpl.html for full details of the license terms.
|
4
|
+
# Copyright 2012 Andrew Heald.
|
5
|
+
|
6
|
+
module Sappho
|
7
|
+
module Heatmiser
|
8
|
+
module Proxy
|
9
|
+
|
10
|
+
require 'singleton'
|
11
|
+
require 'thread'
|
12
|
+
require 'sappho-heatmiser-proxy/trace_log'
|
13
|
+
require 'sappho-heatmiser-proxy/system_configuration'
|
14
|
+
|
15
|
+
class ClientRegister
|
16
|
+
|
17
|
+
include Singleton
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
@mutex = Mutex.new
|
21
|
+
@clients = {}
|
22
|
+
@max = Integer SystemConfiguration.instance.config['heatmiser.clients.max']
|
23
|
+
@log = TraceLog.instance
|
24
|
+
end
|
25
|
+
|
26
|
+
def register client
|
27
|
+
@mutex.synchronize do
|
28
|
+
ip = client.getpeername
|
29
|
+
@clients[client] = ip = (4 ... 8).map{|pos|ip[pos]}.join('.')
|
30
|
+
@log.info "client #{ip} connected"
|
31
|
+
log
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def unregister client
|
36
|
+
@mutex.synchronize do
|
37
|
+
ip = @clients[client]
|
38
|
+
@clients.delete client
|
39
|
+
@log.info "client #{ip} disconnected"
|
40
|
+
log
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def ip client
|
45
|
+
@mutex.synchronize { @clients[client] }
|
46
|
+
end
|
47
|
+
|
48
|
+
def maxAlreadyConnected?
|
49
|
+
@mutex.synchronize { @clients.size >= @max }
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def log
|
55
|
+
@log.info "clients: #{@clients.size > 0 ? (@clients.collect{|client, ip| ip}).join(', ') : 'none'}"
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# See https://github.com/sappho/sappho-heatmiser-proxy/wiki for project documentation.
|
2
|
+
# This software is licensed under the GNU Affero General Public License, version 3.
|
3
|
+
# See http://www.gnu.org/licenses/agpl.html for full details of the license terms.
|
4
|
+
# Copyright 2012 Andrew Heald.
|
5
|
+
|
6
|
+
module Sappho
|
7
|
+
module Heatmiser
|
8
|
+
module Proxy
|
9
|
+
|
10
|
+
require 'singleton'
|
11
|
+
require 'thread'
|
12
|
+
require 'sappho-heatmiser-proxy/trace_log'
|
13
|
+
|
14
|
+
class CommandQueue
|
15
|
+
|
16
|
+
include Singleton
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
@queue = []
|
20
|
+
@mutex = Mutex.new
|
21
|
+
@log = TraceLog.instance
|
22
|
+
end
|
23
|
+
|
24
|
+
def push clientIP, command
|
25
|
+
@log.info "client #{clientIP} requests command: #{TraceLog.hex command}"
|
26
|
+
@mutex.synchronize do
|
27
|
+
@queue << {
|
28
|
+
:clientIP => clientIP,
|
29
|
+
:command => command.dup
|
30
|
+
}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def get
|
35
|
+
command = nil
|
36
|
+
@mutex.synchronize do
|
37
|
+
if @queue.size > 0
|
38
|
+
queue = @queue[0]
|
39
|
+
command = queue[:command].dup
|
40
|
+
@log.info "client #{queue[:clientIP]} command executing: #{TraceLog.hex command}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
command
|
44
|
+
end
|
45
|
+
|
46
|
+
def completed
|
47
|
+
@mutex.synchronize do
|
48
|
+
if @queue.size > 0
|
49
|
+
queue = @queue[0]
|
50
|
+
@log.info "client #{queue[:clientIP]} command completed: #{TraceLog.hex queue[:command]}"
|
51
|
+
@queue.shift
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# See https://github.com/sappho/sappho-heatmiser-proxy/wiki for project documentation.
|
2
|
+
# This software is licensed under the GNU Affero General Public License, version 3.
|
3
|
+
# See http://www.gnu.org/licenses/agpl.html for full details of the license terms.
|
4
|
+
# Copyright 2012 Andrew Heald.
|
5
|
+
|
6
|
+
module Sappho
|
7
|
+
module Heatmiser
|
8
|
+
module Proxy
|
9
|
+
|
10
|
+
require 'sappho-heatmiser-proxy/heatmiser_crc'
|
11
|
+
require 'sappho-heatmiser-proxy/heatmiser_status'
|
12
|
+
require 'sappho-heatmiser-proxy/trace_log'
|
13
|
+
require 'sappho-heatmiser-proxy/command_queue'
|
14
|
+
require 'thread'
|
15
|
+
require 'timeout'
|
16
|
+
require 'socket'
|
17
|
+
require 'sappho-heatmiser-proxy/system_configuration'
|
18
|
+
|
19
|
+
class Heatmiser
|
20
|
+
|
21
|
+
def monitor
|
22
|
+
@thread = Thread.new do
|
23
|
+
status = HeatmiserStatus.instance
|
24
|
+
queue = CommandQueue.instance
|
25
|
+
log = TraceLog.instance
|
26
|
+
config = SystemConfiguration.instance.config
|
27
|
+
hostname = config['heatmiser.address']
|
28
|
+
port = Integer config['heatmiser.port']
|
29
|
+
pin = Integer config['heatmiser.pin']
|
30
|
+
pinLo = pin & 0xFF
|
31
|
+
pinHi = (pin >> 8) & 0xFF
|
32
|
+
queryCommand = HeatmiserCRC.new([0x93, 0x0B, 0x00, pinLo, pinHi, 0x00, 0x00, 0xFF, 0xFF]).appendCRC
|
33
|
+
loop do
|
34
|
+
status.invalidate
|
35
|
+
begin
|
36
|
+
log.info "opening connection to heatmiser at #{hostname}:#{port}"
|
37
|
+
TCPSocket.open hostname, port do | socket |
|
38
|
+
log.info "connected to heatmiser at #{hostname}:#{port}"
|
39
|
+
loop do
|
40
|
+
begin
|
41
|
+
sleep 5
|
42
|
+
command = queryCommand
|
43
|
+
if queuedCommand = queue.get
|
44
|
+
command = queuedCommand
|
45
|
+
else
|
46
|
+
if status.get{status.valid ? status.deviceTimeOffset : 0.0}.abs > 5.0
|
47
|
+
timeNow = Time.now
|
48
|
+
dayOfWeek = timeNow.wday
|
49
|
+
dayOfWeek = 7 if dayOfWeek == 0
|
50
|
+
command = HeatmiserCRC.new([0xA3, 0x12, 0x00, pinLo, pinHi, 0x01, 0x2B, 0x00, 0x07,
|
51
|
+
timeNow.year - 2000,
|
52
|
+
timeNow.month,
|
53
|
+
timeNow.day,
|
54
|
+
dayOfWeek,
|
55
|
+
timeNow.hour,
|
56
|
+
timeNow.min,
|
57
|
+
timeNow.sec]).appendCRC
|
58
|
+
log.info "clock correction: #{TraceLog.hex command}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
log.debug "sending command: #{TraceLog.hex command}" if log.debug?
|
62
|
+
reply = []
|
63
|
+
startTime = Time.now
|
64
|
+
timeout 20 do
|
65
|
+
socket.write command.pack('c*')
|
66
|
+
reply = socket.read(81).unpack('c*')
|
67
|
+
end
|
68
|
+
timestamp = Time.now
|
69
|
+
log.debug "reply: #{TraceLog.hex reply}" if log.debug?
|
70
|
+
crcHi = reply.pop & 0xFF
|
71
|
+
crcLo = reply.pop & 0xFF
|
72
|
+
crc = HeatmiserCRC.new reply
|
73
|
+
if (reply[0] & 0xFF) == 0x94 and reply[1] == 0x51 and reply[2] == 0 and
|
74
|
+
crc.crcHi == crcHi and crc.crcLo == crcLo
|
75
|
+
reply << crcLo << crcHi
|
76
|
+
status.set reply, timestamp, (timestamp - startTime) do
|
77
|
+
queue.completed if queuedCommand
|
78
|
+
end
|
79
|
+
end
|
80
|
+
rescue Timeout::Error
|
81
|
+
log.info "heatmiser at #{hostname}:#{port} is not responding - assuming connection down"
|
82
|
+
break
|
83
|
+
rescue => error
|
84
|
+
log.error error
|
85
|
+
break
|
86
|
+
end
|
87
|
+
end
|
88
|
+
log.info "closing connection to heatmiser at #{hostname}:#{port}"
|
89
|
+
socket.close
|
90
|
+
end
|
91
|
+
rescue => error
|
92
|
+
log.error error
|
93
|
+
end
|
94
|
+
sleep 10
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def wait
|
100
|
+
@thread.join
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# See https://github.com/sappho/sappho-heatmiser-proxy/wiki for project documentation.
|
2
|
+
# This software is licensed under the GNU Affero General Public License, version 3.
|
3
|
+
# See http://www.gnu.org/licenses/agpl.html for full details of the license terms.
|
4
|
+
# Copyright 2012 Andrew Heald.
|
5
|
+
|
6
|
+
module Sappho
|
7
|
+
module Heatmiser
|
8
|
+
module Proxy
|
9
|
+
|
10
|
+
require 'thread'
|
11
|
+
require 'socket'
|
12
|
+
require 'sappho-heatmiser-proxy/trace_log'
|
13
|
+
require 'sappho-heatmiser-proxy/heatmiser_status'
|
14
|
+
require 'sappho-heatmiser-proxy/command_queue'
|
15
|
+
require 'sappho-heatmiser-proxy/client_register'
|
16
|
+
|
17
|
+
class HeatmiserClient
|
18
|
+
|
19
|
+
def session client
|
20
|
+
@clients = ClientRegister.instance
|
21
|
+
@clients.register client
|
22
|
+
@ip = @clients.ip client
|
23
|
+
@client = client
|
24
|
+
@status = HeatmiserStatus.instance
|
25
|
+
@log = TraceLog.instance
|
26
|
+
Thread.new do
|
27
|
+
loop do
|
28
|
+
begin
|
29
|
+
timeout 20 do
|
30
|
+
command = read 5
|
31
|
+
@log.debug "header: #{TraceLog.hex command}" if @log.debug?
|
32
|
+
packetSize = (command[1] & 0xFF) | ((command[2] << 8) & 0x0F00)
|
33
|
+
command += read(packetSize - 5)
|
34
|
+
CommandQueue.instance.push @ip, command unless (command[0] & 0xFF) == 0x93
|
35
|
+
@status.get { @client.write @status.raw.pack('c*') if @status.valid }
|
36
|
+
@log.info "command received from client #{@ip} so it is alive"
|
37
|
+
end
|
38
|
+
rescue Timeout::Error
|
39
|
+
@log.info "no command received from client #{@ip} so presuming it dormant"
|
40
|
+
break
|
41
|
+
rescue HeatmiserClient::ReadError
|
42
|
+
@log.info "unable to receive data from client #{@ip} so presuming it has disconnected"
|
43
|
+
break
|
44
|
+
rescue => error
|
45
|
+
@log.error error
|
46
|
+
break
|
47
|
+
end
|
48
|
+
end
|
49
|
+
begin
|
50
|
+
@client.close
|
51
|
+
rescue
|
52
|
+
end
|
53
|
+
@clients.unregister @client
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def read size
|
58
|
+
data = @client.read size
|
59
|
+
raise HeatmiserClient::ReadError unless data and data.size == size
|
60
|
+
data.unpack('c*')
|
61
|
+
end
|
62
|
+
|
63
|
+
class ReadError < Interrupt
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# See https://github.com/sappho/sappho-heatmiser-proxy/wiki for project documentation.
|
2
|
+
# This software is licensed under the GNU Affero General Public License, version 3.
|
3
|
+
# See http://www.gnu.org/licenses/agpl.html for full details of the license terms.
|
4
|
+
# Copyright 2012 Andrew Heald.
|
5
|
+
|
6
|
+
module Sappho
|
7
|
+
module Heatmiser
|
8
|
+
module Proxy
|
9
|
+
|
10
|
+
class HeatmiserCRC
|
11
|
+
|
12
|
+
LookupHi = [
|
13
|
+
0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70,
|
14
|
+
0x81, 0x91, 0xA1, 0xB1, 0xC1, 0xD1, 0xE1, 0xF1
|
15
|
+
]
|
16
|
+
LookupLo = [
|
17
|
+
0x00, 0x21, 0x42, 0x63, 0x84, 0xA5, 0xC6, 0xE7,
|
18
|
+
0x08, 0x29, 0x4A, 0x6B, 0x8C, 0xAD, 0xCE, 0xEF
|
19
|
+
]
|
20
|
+
attr_reader :crcHi, :crcLo
|
21
|
+
|
22
|
+
def initialize bytes
|
23
|
+
@bytes = bytes
|
24
|
+
@crcHi = 0xFF
|
25
|
+
@crcLo = 0xFF
|
26
|
+
bytes.each do |byte|
|
27
|
+
addNibble byte >> 4
|
28
|
+
addNibble byte & 0x0F
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def appendCRC
|
33
|
+
@bytes << @crcLo << @crcHi
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def addNibble nibble
|
39
|
+
t = ((@crcHi >> 4) ^ nibble) & 0x0F
|
40
|
+
@crcHi = (((@crcHi << 4) & 0xFF) | (@crcLo >> 4)) ^ LookupHi[t]
|
41
|
+
@crcLo = ((@crcLo << 4) & 0xFF) ^ LookupLo[t]
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# See https://github.com/sappho/sappho-heatmiser-proxy/wiki for project documentation.
|
2
|
+
# This software is licensed under the GNU Affero General Public License, version 3.
|
3
|
+
# See http://www.gnu.org/licenses/agpl.html for full details of the license terms.
|
4
|
+
# Copyright 2012 Andrew Heald.
|
5
|
+
|
6
|
+
module Sappho
|
7
|
+
module Heatmiser
|
8
|
+
module Proxy
|
9
|
+
|
10
|
+
require 'sappho-heatmiser-proxy/heatmiser_client'
|
11
|
+
require 'sappho-heatmiser-proxy/client_register'
|
12
|
+
require 'thread'
|
13
|
+
require 'socket'
|
14
|
+
require 'sappho-heatmiser-proxy/trace_log'
|
15
|
+
|
16
|
+
class HeatmiserProxy
|
17
|
+
|
18
|
+
def serve
|
19
|
+
Thread.new do
|
20
|
+
clients = ClientRegister.instance
|
21
|
+
port = Integer SystemConfiguration.instance.config['heatmiser.port']
|
22
|
+
log = TraceLog.instance
|
23
|
+
log.info "opening proxy server port #{port}"
|
24
|
+
TCPServer.open port do | server |
|
25
|
+
log.info "proxy server port #{port} is now open"
|
26
|
+
loop do
|
27
|
+
if clients.maxAlreadyConnected?
|
28
|
+
sleep 1
|
29
|
+
else
|
30
|
+
log.info "listening for new clients on proxy server port #{port}"
|
31
|
+
HeatmiserClient.new.session server.accept
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
# See https://github.com/sappho/sappho-heatmiser-proxy/wiki for project documentation.
|
2
|
+
# This software is licensed under the GNU Affero General Public License, version 3.
|
3
|
+
# See http://www.gnu.org/licenses/agpl.html for full details of the license terms.
|
4
|
+
# Copyright 2012 Andrew Heald.
|
5
|
+
|
6
|
+
module Sappho
|
7
|
+
module Heatmiser
|
8
|
+
module Proxy
|
9
|
+
|
10
|
+
require 'singleton'
|
11
|
+
require 'thread'
|
12
|
+
require 'sappho-heatmiser-proxy/trace_log'
|
13
|
+
|
14
|
+
class HeatmiserStatus
|
15
|
+
|
16
|
+
include Singleton
|
17
|
+
|
18
|
+
attr_reader :valid, :timestamp, :sampleTime, :timeSinceLastValid, :sensedTemperature,
|
19
|
+
:requestedTemperature, :heatOn, :keyLockOn, :frostProtectOn, :deviceTimeOffset,
|
20
|
+
:dayOfWeek, :schedule
|
21
|
+
|
22
|
+
class TimedTemperature
|
23
|
+
|
24
|
+
attr_reader :hour, :minute, :temperature
|
25
|
+
|
26
|
+
def initialize raw, bytePosition
|
27
|
+
@hour = raw[bytePosition] & 0xFF
|
28
|
+
@minute = raw[bytePosition + 1] & 0xFF
|
29
|
+
@temperature = raw[bytePosition + 2] & 0xFF
|
30
|
+
end
|
31
|
+
|
32
|
+
def valid?
|
33
|
+
@hour < 24 and @minute < 60
|
34
|
+
end
|
35
|
+
|
36
|
+
def description
|
37
|
+
"#{@hour}:#{@minute}-#{@temperature}"
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
class Schedule
|
43
|
+
|
44
|
+
attr_reader :schedule
|
45
|
+
|
46
|
+
def initialize raw, bytePosition
|
47
|
+
@schedule = []
|
48
|
+
(0 ... 4).map do |position|
|
49
|
+
timedTemperature = TimedTemperature.new(raw, bytePosition + 3 * position)
|
50
|
+
@schedule << timedTemperature if timedTemperature.valid?
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def description
|
55
|
+
(@schedule.collect {|timedTemperature| timedTemperature.description}).join(' ')
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
def initialize
|
61
|
+
@mutex = Mutex.new
|
62
|
+
@log = TraceLog.instance
|
63
|
+
@valid = false
|
64
|
+
@raw = []
|
65
|
+
@timestamp = Time.now
|
66
|
+
@sampleTime = 0.0
|
67
|
+
@timeSinceLastValid = 0.0
|
68
|
+
@sensedTemperature = 0.0
|
69
|
+
@requestedTemperature = 0
|
70
|
+
@holidayReturnTime = Time.now
|
71
|
+
@holdMinutes = 0
|
72
|
+
@heatOn = false
|
73
|
+
@keyLockOn = false
|
74
|
+
@frostProtectOn = false
|
75
|
+
@holidayOn = false
|
76
|
+
@deviceTimeOffset = 0.0
|
77
|
+
@dayOfWeek = 0
|
78
|
+
@schedule = {}
|
79
|
+
end
|
80
|
+
|
81
|
+
def raw
|
82
|
+
@raw.dup
|
83
|
+
end
|
84
|
+
|
85
|
+
def get
|
86
|
+
@mutex.synchronize { yield }
|
87
|
+
end
|
88
|
+
|
89
|
+
def set raw, timestamp, sampleTime
|
90
|
+
@mutex.synchronize do
|
91
|
+
@valid = true
|
92
|
+
begin
|
93
|
+
@raw = raw.dup
|
94
|
+
@sensedTemperature = ((raw[44] & 0xFF) | ((raw[45] << 8) & 0xFF00)) / 10.0
|
95
|
+
@holdMinutes = (raw[38] & 0xFF) | ((raw[39] << 8) & 0xFF00)
|
96
|
+
@heatOn = raw[47] == 1
|
97
|
+
@keyLockOn = raw[29] == 1
|
98
|
+
@frostProtectOn = raw[30] == 1
|
99
|
+
@holidayOn = raw[37] == 1
|
100
|
+
@holidayReturnTime = Time.local(2000 + (raw[32] & 0xFF), raw[33], raw[34], raw[35], raw[36], 0)
|
101
|
+
@requestedTemperature = @frostProtectOn ? raw[24] & 0xFF : raw[25] & 0xFF
|
102
|
+
@deviceTimeOffset = Time.local(2000 + (raw[48] & 0xFF), raw[49], raw[50],
|
103
|
+
raw[52], raw[53], raw[54]) - timestamp
|
104
|
+
dayOfWeek = raw[51]
|
105
|
+
@dayOfWeek = dayOfWeek == 7 ? 0 : dayOfWeek
|
106
|
+
@schedule = {
|
107
|
+
:weekday => Schedule.new(@raw, 55),
|
108
|
+
:weekend => Schedule.new(@raw, 67)
|
109
|
+
}
|
110
|
+
@timeSinceLastValid = timestamp - @timestamp
|
111
|
+
@timestamp = timestamp
|
112
|
+
@sampleTime = sampleTime
|
113
|
+
if @log.debug?
|
114
|
+
@log.debug "#{TraceLog.hex raw}"
|
115
|
+
@log.debug "#{@requestedTemperature} #{@holdMinutes / 60}:#{@holdMinutes % 60} #{@sensedTemperature} #{@heatOn} #{@keyLockOn} #{@frostProtectOn} #{@timeSinceLastValid} #{@dayOfWeek} #{@deviceTimeOffset} #{sampleTime} #{@holidayOn} #{@holidayReturnTime}"
|
116
|
+
@log.debug "weekday: #{@schedule[:weekday].description} weekend: #{@schedule[:weekend].description}"
|
117
|
+
else
|
118
|
+
@log.info "received status: heating is #{@heatOn ? "on" : "off"} because required temperature is #{@requestedTemperature} and actual is #{@sensedTemperature}"
|
119
|
+
end
|
120
|
+
yield
|
121
|
+
rescue => error
|
122
|
+
@log.error error
|
123
|
+
@valid = false
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def invalidate
|
129
|
+
@mutex.synchronize { @valid = false }
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# See https://github.com/sappho/sappho-heatmiser-proxy/wiki for project documentation.
|
2
|
+
# This software is licensed under the GNU Affero General Public License, version 3.
|
3
|
+
# See http://www.gnu.org/licenses/agpl.html for full details of the license terms.
|
4
|
+
# Copyright 2012 Andrew Heald.
|
5
|
+
|
6
|
+
module Sappho
|
7
|
+
module Heatmiser
|
8
|
+
module Proxy
|
9
|
+
|
10
|
+
require 'singleton'
|
11
|
+
require 'yaml'
|
12
|
+
|
13
|
+
class SystemConfiguration
|
14
|
+
|
15
|
+
include Singleton
|
16
|
+
|
17
|
+
attr_reader :config
|
18
|
+
|
19
|
+
def initialize
|
20
|
+
@config = YAML.load_file(File.expand_path(ARGV[0] || 'heatmiser-proxy.yml'))
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# See https://github.com/sappho/sappho-heatmiser-proxy/wiki for project documentation.
|
2
|
+
# This software is licensed under the GNU Affero General Public License, version 3.
|
3
|
+
# See http://www.gnu.org/licenses/agpl.html for full details of the license terms.
|
4
|
+
# Copyright 2012 Andrew Heald.
|
5
|
+
|
6
|
+
module Sappho
|
7
|
+
module Heatmiser
|
8
|
+
module Proxy
|
9
|
+
|
10
|
+
require 'singleton'
|
11
|
+
require 'thread'
|
12
|
+
require 'logger'
|
13
|
+
require 'sappho-heatmiser-proxy/system_configuration'
|
14
|
+
require 'sappho-heatmiser-proxy/version'
|
15
|
+
|
16
|
+
class TraceLog
|
17
|
+
|
18
|
+
include Singleton
|
19
|
+
|
20
|
+
def initialize
|
21
|
+
@mutex = Mutex.new
|
22
|
+
config = SystemConfiguration.instance.config
|
23
|
+
@log = Logger.new(config['log.stdout'] ? STDOUT : config['log.filename'])
|
24
|
+
@log.level = config['log.debug'] ? Logger::DEBUG : Logger::INFO
|
25
|
+
@log.formatter = proc { |severity, datetime, progname, message| "#{message}\n" }
|
26
|
+
@log.info "#{NAME} version #{VERSION} - #{HOMEPAGE}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def info message
|
30
|
+
@mutex.synchronize do
|
31
|
+
@log.info message
|
32
|
+
end if @log.info?
|
33
|
+
end
|
34
|
+
|
35
|
+
def debug message
|
36
|
+
@mutex.synchronize do
|
37
|
+
@log.debug message
|
38
|
+
end if @log.debug?
|
39
|
+
end
|
40
|
+
|
41
|
+
def error error
|
42
|
+
@mutex.synchronize do
|
43
|
+
@log.error "error! #{error.message}"
|
44
|
+
error.backtrace.each { |error| @log.error error }
|
45
|
+
end if @log.error?
|
46
|
+
end
|
47
|
+
|
48
|
+
def debug?
|
49
|
+
@log.debug?
|
50
|
+
end
|
51
|
+
|
52
|
+
def TraceLog.hex bytes
|
53
|
+
(bytes.collect {|byte| "%02x " % (byte & 0xFF)}).join
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# See https://github.com/sappho/sappho-heatmiser-proxy/wiki for project documentation.
|
2
|
+
# This software is licensed under the GNU Affero General Public License, version 3.
|
3
|
+
# See http://www.gnu.org/licenses/agpl.html for full details of the license terms.
|
4
|
+
# Copyright 2012 Andrew Heald.
|
5
|
+
|
6
|
+
module Sappho
|
7
|
+
module Heatmiser
|
8
|
+
module Proxy
|
9
|
+
NAME = "sappho-heatmiser-proxy"
|
10
|
+
VERSION = "0.0.1"
|
11
|
+
AUTHORS = ["Andrew Heald"]
|
12
|
+
EMAILS = ["andrew@heald.co.uk"]
|
13
|
+
HOMEPAGE = "https://github.com/sappho/sappho-heatmiser-proxy/wiki"
|
14
|
+
SUMMARY = "Acts as a proxy for Heatmiser hardware to allow continuous monitoring and control by many controllers"
|
15
|
+
DESCRIPTION = "See the project home page for more information"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
metadata
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sappho-heatmiser-proxy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Andrew Heald
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-02-26 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rake
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 11
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
- 9
|
32
|
+
- 2
|
33
|
+
- 2
|
34
|
+
version: 0.9.2.2
|
35
|
+
type: :development
|
36
|
+
version_requirements: *id001
|
37
|
+
description: See the project home page for more information
|
38
|
+
email:
|
39
|
+
- andrew@heald.co.uk
|
40
|
+
executables:
|
41
|
+
- sappho-heatmiser-proxy
|
42
|
+
extensions: []
|
43
|
+
|
44
|
+
extra_rdoc_files: []
|
45
|
+
|
46
|
+
files:
|
47
|
+
- bin/sappho-heatmiser-proxy
|
48
|
+
- lib/sappho-heatmiser-proxy.rb
|
49
|
+
- lib/sappho-heatmiser-proxy/client_register.rb
|
50
|
+
- lib/sappho-heatmiser-proxy/command_queue.rb
|
51
|
+
- lib/sappho-heatmiser-proxy/heatmiser.rb
|
52
|
+
- lib/sappho-heatmiser-proxy/heatmiser_client.rb
|
53
|
+
- lib/sappho-heatmiser-proxy/heatmiser_crc.rb
|
54
|
+
- lib/sappho-heatmiser-proxy/heatmiser_proxy.rb
|
55
|
+
- lib/sappho-heatmiser-proxy/heatmiser_status.rb
|
56
|
+
- lib/sappho-heatmiser-proxy/system_configuration.rb
|
57
|
+
- lib/sappho-heatmiser-proxy/trace_log.rb
|
58
|
+
- lib/sappho-heatmiser-proxy/version.rb
|
59
|
+
homepage: https://github.com/sappho/sappho-heatmiser-proxy/wiki
|
60
|
+
licenses: []
|
61
|
+
|
62
|
+
post_install_message:
|
63
|
+
rdoc_options: []
|
64
|
+
|
65
|
+
require_paths:
|
66
|
+
- lib
|
67
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
68
|
+
none: false
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
hash: 3
|
73
|
+
segments:
|
74
|
+
- 0
|
75
|
+
version: "0"
|
76
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
77
|
+
none: false
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
hash: 3
|
82
|
+
segments:
|
83
|
+
- 0
|
84
|
+
version: "0"
|
85
|
+
requirements: []
|
86
|
+
|
87
|
+
rubyforge_project: sappho-heatmiser-proxy
|
88
|
+
rubygems_version: 1.8.17
|
89
|
+
signing_key:
|
90
|
+
specification_version: 3
|
91
|
+
summary: Acts as a proxy for Heatmiser hardware to allow continuous monitoring and control by many controllers
|
92
|
+
test_files: []
|
93
|
+
|