sappho-heatmiser-proxy 0.1.1 → 0.1.2

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.
@@ -1,12 +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
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
+ $: << File.expand_path('../lib', __FILE__)
9
+
10
+ require 'sappho-heatmiser-proxy'
11
+
12
+ Sappho::Heatmiser::Proxy::CommandLine.process
@@ -1,35 +1,44 @@
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_client'
12
- require 'sappho-basics/auto_flush_log'
13
- require 'sappho-socket/safe_server'
14
- require 'sappho-heatmiser-proxy/version'
15
- require 'thread'
16
-
17
- class CommandLine
18
-
19
- def CommandLine.process
20
- Sappho::ApplicationAutoFlushLog.instance.info "#{NAME} version #{VERSION} - #{HOMEPAGE}"
21
- port = SystemConfiguration.instance.heatmiserPort
22
- maxClients = SystemConfiguration.instance.maxClients
23
- Sappho::Socket::SafeServer.new('heatmiser proxy', port, maxClients).serve do
24
- | socket, ip | HeatmiserClient.new(socket, ip).communicate
25
- end
26
- Thread.new do
27
- Heatmiser.new.monitor
28
- end.join
29
- end
30
-
31
- end
32
-
33
- end
34
- end
35
- end
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_client'
12
+ require 'sappho-basics/auto_flush_log'
13
+ require 'sappho-socket/safe_server'
14
+ require 'sappho-heatmiser-proxy/version'
15
+ require 'mongo_mapper'
16
+ require 'mongo/connection'
17
+ require 'thread'
18
+
19
+ class CommandLine
20
+
21
+ def CommandLine.process
22
+ log = Sappho::ApplicationAutoFlushLog.instance
23
+ log.info "#{NAME} version #{VERSION} - #{HOMEPAGE}"
24
+ config = SystemConfiguration.instance
25
+ if config.mongoLogging
26
+ log.info "connecting to mongodb database #{config.mongodbDatabase} on #{config.mongodbHostname}:#{config.mongodbPort}"
27
+ MongoMapper.connection = Mongo::Connection.new config.mongodbHostname, config.mongodbPort
28
+ MongoMapper.database = config.mongodbDatabase
29
+ else
30
+ log.info 'logging to mongodb is disabled'
31
+ end
32
+ Sappho::Socket::SafeServer.new('heatmiser proxy', config.heatmiserPort, config.maxClients, config.detailedLogging).serve do
33
+ | socket, ip | HeatmiserClient.new(socket, ip).communicate
34
+ end
35
+ Thread.new do
36
+ Heatmiser.new.monitor
37
+ end.join
38
+ end
39
+
40
+ end
41
+
42
+ end
43
+ end
44
+ end
@@ -1,60 +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-basics/auto_flush_log'
13
-
14
- class CommandQueue
15
-
16
- include Singleton, Sappho::LogUtilities
17
-
18
- def initialize
19
- @queue = []
20
- @mutex = Mutex.new
21
- @log = Sappho::ApplicationAutoFlushLog.instance
22
- end
23
-
24
- def push clientIP, command
25
- @log.info "client #{clientIP} requests command: #{hexString 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: #{hexString 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: #{hexString queue[:command]}"
51
- @queue.shift
52
- end
53
- end
54
- end
55
-
56
- end
57
-
58
- end
59
- end
60
- end
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-basics/auto_flush_log'
13
+
14
+ class CommandQueue
15
+
16
+ include Singleton, Sappho::LogUtilities
17
+
18
+ def initialize
19
+ @queue = []
20
+ @mutex = Mutex.new
21
+ @log = Sappho::ApplicationAutoFlushLog.instance
22
+ end
23
+
24
+ def push clientIP, command
25
+ @log.info "client #{clientIP} requests command: #{hexString 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: #{hexString 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: #{hexString queue[:command]}"
51
+ @queue.shift
52
+ end
53
+ end
54
+ end
55
+
56
+ end
57
+
58
+ end
59
+ end
60
+ end
@@ -1,86 +1,96 @@
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-basics/auto_flush_log'
13
- require 'sappho-heatmiser-proxy/command_queue'
14
- require 'sappho-heatmiser-proxy/system_configuration'
15
- require 'sappho-socket/safe_socket'
16
-
17
- class Heatmiser
18
-
19
- include Sappho::LogUtilities
20
-
21
- def monitor
22
- status = HeatmiserStatus.instance
23
- queue = CommandQueue.instance
24
- log = Sappho::ApplicationAutoFlushLog.instance
25
- config = SystemConfiguration.instance
26
- desc = "heatmiser at #{config.heatmiserHostname}:#{config.heatmiserPort}"
27
- queryCommand = HeatmiserCRC.new([0x93, 0x0B, 0x00, config.pinLo, config.pinHi, 0x00, 0x00, 0xFF, 0xFF]).appendCRC
28
- socket = Sappho::Socket::SafeSocket.new 5
29
- loop do
30
- begin
31
- command = queryCommand
32
- if queuedCommand = queue.get
33
- command = queuedCommand
34
- else
35
- if status.get{status.valid ? status.deviceTimeOffset : 0.0}.abs > 150
36
- timeNow = Time.now
37
- dayOfWeek = timeNow.wday
38
- dayOfWeek = 7 if dayOfWeek == 0
39
- command = HeatmiserCRC.new([0xA3, 0x12, 0x00, config.pinLo, config.pinHi, 0x01, 0x2B, 0x00, 0x07,
40
- timeNow.year - 2000,
41
- timeNow.month,
42
- timeNow.day,
43
- dayOfWeek,
44
- timeNow.hour,
45
- timeNow.min,
46
- timeNow.sec]).appendCRC
47
- log.info "clock correction: #{hexString command}"
48
- end
49
- end
50
- log.debug "sending command: #{hexString command}" if log.debug?
51
- socket.close # just in case it wasn't last time around
52
- socket.open config.heatmiserHostname, config.heatmiserPort
53
- socket.settle 0.1
54
- startTime = Time.now
55
- socket.write command.pack('c*')
56
- reply = socket.read(81).unpack('c*')
57
- timestamp = Time.now
58
- socket.settle 0.1
59
- socket.close
60
- log.debug "reply: #{hexString reply}" if log.debug?
61
- crcHi = reply.pop & 0xFF
62
- crcLo = reply.pop & 0xFF
63
- crc = HeatmiserCRC.new reply
64
- if (reply[0] & 0xFF) == 0x94 and reply[1] == 0x51 and reply[2] == 0 and
65
- crc.crcHi == crcHi and crc.crcLo == crcLo
66
- reply << crcLo << crcHi
67
- status.set reply, timestamp, (timestamp - startTime) do
68
- queue.completed if queuedCommand
69
- end
70
- end
71
- rescue Timeout::Error
72
- status.invalidate
73
- log.info "#{desc} is not responding - assuming connection down"
74
- rescue => error
75
- status.invalidate
76
- log.error error
77
- end
78
- socket.settle 2
79
- end
80
- end
81
-
82
- end
83
-
84
- end
85
- end
86
- end
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-basics/auto_flush_log'
13
+ require 'sappho-heatmiser-proxy/command_queue'
14
+ require 'sappho-heatmiser-proxy/system_configuration'
15
+ require 'sappho-heatmiser-proxy/model/heatmiser_log'
16
+ require 'sappho-socket/safe_socket'
17
+
18
+ class Heatmiser
19
+
20
+ include Sappho::LogUtilities
21
+
22
+ def monitor
23
+ status = HeatmiserStatus.instance
24
+ queue = CommandQueue.instance
25
+ log = Sappho::ApplicationAutoFlushLog.instance
26
+ config = SystemConfiguration.instance
27
+ desc = "heatmiser #{config.heatmiserId} at #{config.heatmiserHostname}:#{config.heatmiserPort}"
28
+ log.info "connecting to #{desc}"
29
+ queryCommand = HeatmiserCRC.new([0x93, 0x0B, 0x00, config.pinLo, config.pinHi, 0x00, 0x00, 0xFF, 0xFF]).appendCRC
30
+ socket = Sappho::Socket::SafeSocket.new 5
31
+ loop do
32
+ begin
33
+ command = queryCommand
34
+ if queuedCommand = queue.get
35
+ command = queuedCommand
36
+ else
37
+ if status.get{status.valid ? status.deviceTimeOffset : 0.0}.abs > 5 and config.heatmiserHardware
38
+ timeNow = Time.now
39
+ dayOfWeek = timeNow.wday
40
+ dayOfWeek = 7 if dayOfWeek == 0
41
+ command = HeatmiserCRC.new([0xA3, 0x12, 0x00, config.pinLo, config.pinHi, 0x01, 0x2B, 0x00, 0x07,
42
+ timeNow.year - 2000,
43
+ timeNow.month,
44
+ timeNow.day,
45
+ dayOfWeek,
46
+ timeNow.hour,
47
+ timeNow.min,
48
+ timeNow.sec]).appendCRC
49
+ log.info "clock correction: #{hexString command}"
50
+ end
51
+ end
52
+ log.debug "sending command: #{hexString command}" if log.debug?
53
+ socket.open config.heatmiserHostname, config.heatmiserPort
54
+ socket.settle 0.1
55
+ startTime = Time.now
56
+ socket.write command.pack('c*')
57
+ reply = socket.read(81).unpack('c*')
58
+ timestamp = Time.now
59
+ log.debug "reply: #{hexString reply}" if log.debug?
60
+ crcHi = reply.pop & 0xFF
61
+ crcLo = reply.pop & 0xFF
62
+ crc = HeatmiserCRC.new reply
63
+ if (reply[0] & 0xFF) == 0x94 and reply[1] == 0x51 and reply[2] == 0 and
64
+ crc.crcHi == crcHi and crc.crcLo == crcLo
65
+ reply << crcLo << crcHi
66
+ status.set reply, timestamp, (timestamp - startTime)
67
+ queue.completed if queuedCommand
68
+ Sappho::Heatmiser::Model::HeatmiserLog.new(status.get{{
69
+ :deviceId => config.heatmiserId,
70
+ :timestamp => timestamp,
71
+ :sensedTemperature => status.sensedTemperature,
72
+ :requestedTemperature => status.requestedTemperature,
73
+ :heatOn => status.heatOn,
74
+ :frostProtectOn => status.frostProtectOn,
75
+ :deviceTimeOffset => status.deviceTimeOffset}}).save if config.mongoLogging
76
+ else
77
+ log.info "#{desc} responded with invalid bytes - ignoring it this time"
78
+ end
79
+ rescue Timeout::Error
80
+ status.invalidate
81
+ log.info "#{desc} is not responding - the connection might be down"
82
+ rescue => error
83
+ status.invalidate
84
+ log.error error
85
+ end
86
+ socket.settle 0.1
87
+ socket.close
88
+ socket.settle 2
89
+ end
90
+ end
91
+
92
+ end
93
+
94
+ end
95
+ end
96
+ end
@@ -1,78 +1,78 @@
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-basics/auto_flush_log'
11
- require 'sappho-heatmiser-proxy/heatmiser_status'
12
- require 'sappho-heatmiser-proxy/command_queue'
13
- require 'sappho-heatmiser-proxy/system_configuration'
14
-
15
- class HeatmiserClient
16
-
17
- include Sappho::LogUtilities
18
-
19
- def initialize client, ip
20
- @ip = ip
21
- @client = client
22
- @status = HeatmiserStatus.instance
23
- @log = Sappho::ApplicationAutoFlushLog.instance
24
- end
25
-
26
- def communicate
27
- config = SystemConfiguration.instance
28
- active = true
29
- while active do
30
- begin
31
- command = read 5
32
- if command == 'check'
33
- reply = @status.get {
34
- @status.timeSinceLastValid > 60 ?
35
- 'error: no response from heatmiser unit in last minute' :
36
- @status.valid ? 'ok' : 'error: last response from heatmiser unit was invalid'
37
- }
38
- @log.info "client #{@ip} checking status - reply: #{reply}"
39
- @client.write "#{reply}\r\n"
40
- active = false
41
- else
42
- command = command.unpack('c*')
43
- @log.debug "header: #{hexString command}" if @log.debug?
44
- raise ClientDataError, "invalid pin" unless (command[3] & 0xFF) == config.pinLo and (command[4] & 0xFF) == config.pinHi
45
- packetSize = (command[1] & 0xFF) | ((command[2] << 8) & 0xFF00)
46
- raise ClientDataError, "invalid packet size" if packetSize < 7 or packetSize > 128
47
- command += read(packetSize - 5).unpack('c*')
48
- CommandQueue.instance.push @ip, command unless (command[0] & 0xFF) == 0x93
49
- @status.get { @client.write @status.raw.pack('c*') if @status.valid }
50
- @log.info "command received from client #{@ip} so it is alive"
51
- end
52
- rescue Timeout::Error
53
- @log.info "timeout on client #{@ip} so presuming it dormant"
54
- active = false
55
- rescue ClientDataError => error
56
- @log.info "data error from client #{@ip}: #{error.message}"
57
- active = false
58
- rescue => error
59
- @log.error error
60
- active = false
61
- end
62
- end
63
- end
64
-
65
- def read size
66
- data = @client.read size
67
- raise ClientDataError, "nothing received so presuming it has disconnected" unless data and data.size == size
68
- data
69
- end
70
-
71
- class ClientDataError < Interrupt
72
- end
73
-
74
- end
75
-
76
- end
77
- end
78
- end
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-basics/auto_flush_log'
11
+ require 'sappho-heatmiser-proxy/heatmiser_status'
12
+ require 'sappho-heatmiser-proxy/command_queue'
13
+ require 'sappho-heatmiser-proxy/system_configuration'
14
+
15
+ class HeatmiserClient
16
+
17
+ include Sappho::LogUtilities
18
+
19
+ def initialize client, ip
20
+ @ip = ip
21
+ @client = client
22
+ @status = HeatmiserStatus.instance
23
+ @log = Sappho::ApplicationAutoFlushLog.instance
24
+ end
25
+
26
+ def communicate
27
+ config = SystemConfiguration.instance
28
+ active = true
29
+ while active do
30
+ begin
31
+ command = read 5
32
+ if command == 'check'
33
+ reply = @status.get {
34
+ @status.timeSinceLastValid > 60 ?
35
+ 'error: no response from heatmiser unit in last minute' :
36
+ @status.valid ? 'ok' : 'error: last response from heatmiser unit was invalid'
37
+ }
38
+ @log.info "client #{@ip} checking status - reply: #{reply}"
39
+ @client.write "#{reply}\r\n"
40
+ active = false
41
+ else
42
+ command = command.unpack('c*')
43
+ @log.debug "header: #{hexString command}" if @log.debug?
44
+ raise ClientDataError, "invalid pin" unless (command[3] & 0xFF) == config.pinLo and (command[4] & 0xFF) == config.pinHi
45
+ packetSize = (command[1] & 0xFF) | ((command[2] << 8) & 0xFF00)
46
+ raise ClientDataError, "invalid packet size" if packetSize < 7 or packetSize > 128
47
+ command += read(packetSize - 5).unpack('c*')
48
+ CommandQueue.instance.push @ip, command unless (command[0] & 0xFF) == 0x93
49
+ @status.get { @client.write @status.raw.pack('c*') if @status.valid }
50
+ @log.info "command received from client #{@ip} so it is alive"
51
+ end
52
+ rescue Timeout::Error
53
+ @log.info "timeout on client #{@ip} so presuming it dormant"
54
+ active = false
55
+ rescue ClientDataError => error
56
+ @log.info "data error from client #{@ip}: #{error.message}"
57
+ active = false
58
+ rescue => error
59
+ @log.error error
60
+ active = false
61
+ end
62
+ end
63
+ end
64
+
65
+ def read size
66
+ data = @client.read size
67
+ raise ClientDataError, "nothing received so presuming it has disconnected" unless data and data.size == size
68
+ data
69
+ end
70
+
71
+ class ClientDataError < Interrupt
72
+ end
73
+
74
+ end
75
+
76
+ end
77
+ end
78
+ end
@@ -1,48 +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
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
@@ -1,136 +1,131 @@
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-basics/auto_flush_log'
13
-
14
- class HeatmiserStatus
15
-
16
- include Singleton, Sappho::LogUtilities
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 = Sappho::ApplicationAutoFlushLog.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 "#{hexString 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
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-basics/auto_flush_log'
13
+
14
+ class HeatmiserStatus
15
+
16
+ include Singleton, Sappho::LogUtilities
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 = Sappho::ApplicationAutoFlushLog.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 = false
92
+ @raw = raw.dup
93
+ @sensedTemperature = ((raw[44] & 0xFF) | ((raw[45] << 8) & 0xFF00)) / 10.0
94
+ @holdMinutes = (raw[38] & 0xFF) | ((raw[39] << 8) & 0xFF00)
95
+ @heatOn = raw[47] == 1
96
+ @keyLockOn = raw[29] == 1
97
+ @frostProtectOn = raw[30] == 1
98
+ @holidayOn = raw[37] == 1
99
+ @holidayReturnTime = Time.local(2000 + (raw[32] & 0xFF), raw[33], raw[34], raw[35], raw[36], 0)
100
+ @requestedTemperature = @frostProtectOn ? raw[24] & 0xFF : raw[25] & 0xFF
101
+ @deviceTimeOffset = Time.local(2000 + (raw[48] & 0xFF), raw[49], raw[50],
102
+ raw[52], raw[53], raw[54]) - timestamp
103
+ dayOfWeek = raw[51]
104
+ @dayOfWeek = dayOfWeek == 7 ? 0 : dayOfWeek
105
+ @schedule = {
106
+ :weekday => Schedule.new(@raw, 55),
107
+ :weekend => Schedule.new(@raw, 67)
108
+ }
109
+ @timeSinceLastValid = timestamp - @timestamp
110
+ @timestamp = timestamp
111
+ @sampleTime = sampleTime
112
+ @valid = true
113
+ if @log.debug?
114
+ @log.debug "#{hexString 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
+ end
121
+ end
122
+
123
+ def invalidate
124
+ @mutex.synchronize { @valid = false }
125
+ end
126
+
127
+ end
128
+
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,29 @@
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 Model
9
+
10
+ require 'mongo_mapper'
11
+ require 'mongo_mapper/document'
12
+
13
+ class HeatmiserLog
14
+
15
+ include MongoMapper::Document
16
+
17
+ key :deviceId, String
18
+ key :timestamp, Time
19
+ key :sensedTemperature, Float
20
+ key :requestedTemperature, Integer
21
+ key :heatOn, Boolean
22
+ key :frostProtectOn, Boolean
23
+ key :deviceTimeOffset, Float
24
+
25
+ end
26
+
27
+ end
28
+ end
29
+ end
@@ -1,33 +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
- require 'singleton'
11
- require 'yaml'
12
-
13
- class SystemConfiguration
14
-
15
- include Singleton
16
-
17
- attr_reader :heatmiserHostname, :heatmiserPort, :pinLo, :pinHi, :maxClients
18
-
19
- def initialize
20
- data = YAML.load_file(File.expand_path(ARGV[0] || 'heatmiser-proxy.yml'))
21
- @heatmiserHostname = data['heatmiser.address']
22
- @heatmiserPort = Integer data['heatmiser.port']
23
- pin = Integer data['heatmiser.pin']
24
- @pinLo = pin & 0xFF
25
- @pinHi = (pin >> 8) & 0xFF
26
- @maxClients = Integer data['clients.max']
27
- end
28
-
29
- end
30
-
31
- end
32
- end
33
- end
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
+ require 'sappho-basics/auto_flush_log'
13
+
14
+ class SystemConfiguration
15
+
16
+ include Singleton
17
+
18
+ attr_reader :heatmiserId, :heatmiserHostname, :heatmiserPort, :heatmiserHardware,
19
+ :pinLo, :pinHi, :maxClients,
20
+ :mongoLogging, :mongodbHostname, :mongodbPort, :mongodbDatabase,
21
+ :detailedLogging
22
+
23
+ def initialize
24
+ log = Sappho::ApplicationAutoFlushLog.instance
25
+ filename = File.expand_path(ARGV[0] || 'heatmiser-proxy.yml')
26
+ log.info "loading application configuration from #{filename}"
27
+ data = YAML.load_file(filename)
28
+ @heatmiserId = data['heatmiser.id']
29
+ @heatmiserHostname = data['heatmiser.address']
30
+ @heatmiserPort = data.has_key?('heatmiser.port') ? Integer(data['heatmiser.port']) : 8068
31
+ @heatmiserHardware = data.has_key? 'heatmiser.hardware'
32
+ pin = Integer data['heatmiser.pin']
33
+ @pinLo = pin & 0xFF
34
+ @pinHi = (pin >> 8) & 0xFF
35
+ @maxClients = data.has_key?('clients.max') ? Integer(data['clients.max']) : 10
36
+ @mongoLogging = data.has_key?('mongodb.address') and data.has_key?('mongodb.database')
37
+ @mongodbHostname = data['mongodb.address']
38
+ @mongodbPort = data.has_key?('mongodb.port') ? Integer(data['mongodb.port']) : 27017
39
+ @mongodbDatabase = data['mongodb.database']
40
+ @detailedLogging = data.has_key? 'logging.detailed'
41
+ raise "missing settings in #{filename}" unless @heatmiserId and @heatmiserHostname
42
+ end
43
+
44
+ end
45
+
46
+ end
47
+ end
48
+ end
@@ -1,18 +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.1.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
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.1.2'
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 CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sappho-heatmiser-proxy
3
3
  version: !ruby/object:Gem::Version
4
- hash: 25
4
+ hash: 31
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 1
9
- - 1
10
- version: 0.1.1
9
+ - 2
10
+ version: 0.1.2
11
11
  platform: ruby
12
12
  authors:
13
13
  - Andrew Heald
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2012-03-18 00:00:00 Z
18
+ date: 2012-03-19 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: rake
@@ -50,6 +50,54 @@ dependencies:
50
50
  version: 0.1.1
51
51
  type: :runtime
52
52
  version_requirements: *id002
53
+ - !ruby/object:Gem::Dependency
54
+ name: mongo_mapper
55
+ prerelease: false
56
+ requirement: &id003 !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ hash: 51
62
+ segments:
63
+ - 0
64
+ - 11
65
+ - 0
66
+ version: 0.11.0
67
+ type: :runtime
68
+ version_requirements: *id003
69
+ - !ruby/object:Gem::Dependency
70
+ name: mongo
71
+ prerelease: false
72
+ requirement: &id004 !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ hash: 13
78
+ segments:
79
+ - 1
80
+ - 6
81
+ - 1
82
+ version: 1.6.1
83
+ type: :runtime
84
+ version_requirements: *id004
85
+ - !ruby/object:Gem::Dependency
86
+ name: bson_ext
87
+ prerelease: false
88
+ requirement: &id005 !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ hash: 13
94
+ segments:
95
+ - 1
96
+ - 6
97
+ - 1
98
+ version: 1.6.1
99
+ type: :runtime
100
+ version_requirements: *id005
53
101
  description: See the project home page for more information
54
102
  email:
55
103
  - andrew@heald.co.uk
@@ -61,14 +109,15 @@ extra_rdoc_files: []
61
109
 
62
110
  files:
63
111
  - bin/sappho-heatmiser-proxy
64
- - lib/sappho-heatmiser-proxy.rb
65
- - lib/sappho-heatmiser-proxy/version.rb
112
+ - lib/sappho-heatmiser-proxy/command_queue.rb
66
113
  - lib/sappho-heatmiser-proxy/heatmiser.rb
114
+ - lib/sappho-heatmiser-proxy/heatmiser_client.rb
67
115
  - lib/sappho-heatmiser-proxy/heatmiser_crc.rb
68
- - lib/sappho-heatmiser-proxy/system_configuration.rb
69
116
  - lib/sappho-heatmiser-proxy/heatmiser_status.rb
70
- - lib/sappho-heatmiser-proxy/command_queue.rb
71
- - lib/sappho-heatmiser-proxy/heatmiser_client.rb
117
+ - lib/sappho-heatmiser-proxy/model/heatmiser_log.rb
118
+ - lib/sappho-heatmiser-proxy/system_configuration.rb
119
+ - lib/sappho-heatmiser-proxy/version.rb
120
+ - lib/sappho-heatmiser-proxy.rb
72
121
  homepage: https://github.com/sappho/sappho-heatmiser-proxy/wiki
73
122
  licenses: []
74
123
 
@@ -98,7 +147,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
98
147
  requirements: []
99
148
 
100
149
  rubyforge_project: sappho-heatmiser-proxy
101
- rubygems_version: 1.8.17
150
+ rubygems_version: 1.8.11
102
151
  signing_key:
103
152
  specification_version: 3
104
153
  summary: Acts as a proxy for Heatmiser hardware to allow continuous monitoring and control by many controllers