sappho-heatmiser-proxy 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|