sappho-heatmiser-proxy 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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