maxcube-client 0.4.0
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.
- checksums.yaml +7 -0
- data/.rubocop.yml +32 -0
- data/Gemfile +5 -0
- data/LICENSE.md +21 -0
- data/README.md +35 -0
- data/Rakefile +6 -0
- data/bin/console +8 -0
- data/bin/maxcube-client +31 -0
- data/bin/sample_server +13 -0
- data/bin/sample_socket +13 -0
- data/bin/setup +6 -0
- data/data/load/del +6 -0
- data/data/load/meta +20 -0
- data/data/load/ntp +6 -0
- data/data/load/set_temp +13 -0
- data/data/load/set_temp_mode +12 -0
- data/data/load/set_valve +11 -0
- data/data/load/url +4 -0
- data/data/load/wake +4 -0
- data/lib/maxcube/messages.rb +148 -0
- data/lib/maxcube/messages/handler.rb +154 -0
- data/lib/maxcube/messages/parser.rb +34 -0
- data/lib/maxcube/messages/serializer.rb +59 -0
- data/lib/maxcube/messages/tcp.rb +18 -0
- data/lib/maxcube/messages/tcp/handler.rb +70 -0
- data/lib/maxcube/messages/tcp/parser.rb +46 -0
- data/lib/maxcube/messages/tcp/serializer.rb +47 -0
- data/lib/maxcube/messages/tcp/type/a.rb +32 -0
- data/lib/maxcube/messages/tcp/type/c.rb +248 -0
- data/lib/maxcube/messages/tcp/type/f.rb +33 -0
- data/lib/maxcube/messages/tcp/type/h.rb +70 -0
- data/lib/maxcube/messages/tcp/type/l.rb +131 -0
- data/lib/maxcube/messages/tcp/type/m.rb +185 -0
- data/lib/maxcube/messages/tcp/type/n.rb +44 -0
- data/lib/maxcube/messages/tcp/type/q.rb +18 -0
- data/lib/maxcube/messages/tcp/type/s.rb +246 -0
- data/lib/maxcube/messages/tcp/type/t.rb +38 -0
- data/lib/maxcube/messages/tcp/type/u.rb +19 -0
- data/lib/maxcube/messages/tcp/type/z.rb +36 -0
- data/lib/maxcube/messages/udp.rb +9 -0
- data/lib/maxcube/messages/udp/handler.rb +40 -0
- data/lib/maxcube/messages/udp/parser.rb +50 -0
- data/lib/maxcube/messages/udp/serializer.rb +30 -0
- data/lib/maxcube/messages/udp/type/h.rb +24 -0
- data/lib/maxcube/messages/udp/type/i.rb +23 -0
- data/lib/maxcube/messages/udp/type/n.rb +21 -0
- data/lib/maxcube/network.rb +14 -0
- data/lib/maxcube/network/tcp.rb +11 -0
- data/lib/maxcube/network/tcp/client.rb +174 -0
- data/lib/maxcube/network/tcp/client/commands.rb +286 -0
- data/lib/maxcube/network/tcp/sample_server.rb +96 -0
- data/lib/maxcube/network/udp.rb +11 -0
- data/lib/maxcube/network/udp/client.rb +52 -0
- data/lib/maxcube/network/udp/sample_socket.rb +65 -0
- data/lib/maxcube/version.rb +4 -0
- data/maxcube-client.gemspec +29 -0
- metadata +155 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
require_relative 'handler'
|
2
|
+
|
3
|
+
module MaxCube
|
4
|
+
module Messages
|
5
|
+
module Parser
|
6
|
+
include Handler
|
7
|
+
|
8
|
+
def read(count = 0, unpack = false)
|
9
|
+
str = if count.zero?
|
10
|
+
@io.read
|
11
|
+
else
|
12
|
+
raise IOError if @io.size - @io.pos < count
|
13
|
+
@io.read(count)
|
14
|
+
end
|
15
|
+
return str unless unpack
|
16
|
+
str = "\x00".b + str if count == 3
|
17
|
+
unpack = PACK_FORMAT[count] unless unpack.is_a?(String)
|
18
|
+
str.unpack1(unpack)
|
19
|
+
end
|
20
|
+
|
21
|
+
def parse_msg_body(body, hash, parser_type_str)
|
22
|
+
method_str = "parse_#{parser_type_str}_#{@msg_type.downcase}"
|
23
|
+
if respond_to?(method_str, true)
|
24
|
+
return hash.merge!(send(method_str, body))
|
25
|
+
end
|
26
|
+
hash[:data] = body
|
27
|
+
nil
|
28
|
+
rescue IOError
|
29
|
+
raise InvalidMessageBody
|
30
|
+
.new(@msg_type, 'unexpected EOF reached')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require_relative 'handler'
|
2
|
+
|
3
|
+
module MaxCube
|
4
|
+
module Messages
|
5
|
+
module Serializer
|
6
|
+
include Handler
|
7
|
+
|
8
|
+
def serialize(*args, esize: 0, size: 0, ocount: 0)
|
9
|
+
return args.join if size.zero? && esize.zero?
|
10
|
+
|
11
|
+
ocount, subcount, subsize = serialize_bounds(args,
|
12
|
+
esize: esize,
|
13
|
+
size: size,
|
14
|
+
ocount: ocount)
|
15
|
+
str = ''
|
16
|
+
args.reverse!
|
17
|
+
ocount.times do
|
18
|
+
str << args.pop while args.last.is_a?(String)
|
19
|
+
substr = args.pop(subcount).pack(PACK_FORMAT[subsize])
|
20
|
+
substr = substr[1..-1] if subsize == 3
|
21
|
+
str << substr
|
22
|
+
end
|
23
|
+
str << args.pop until args.empty?
|
24
|
+
|
25
|
+
str
|
26
|
+
end
|
27
|
+
|
28
|
+
def write(*args, esize: 0, size: 0, ocount: 0)
|
29
|
+
@io.write(serialize(*args, esize: esize, size: size, ocount: ocount))
|
30
|
+
end
|
31
|
+
|
32
|
+
def serialize_hash_body(hash, serializer_type_str)
|
33
|
+
method_str = "serialize_#{serializer_type_str}_#{@msg_type.downcase}"
|
34
|
+
return send(method_str, hash) if respond_to?(method_str, true)
|
35
|
+
raise InvalidMessageType
|
36
|
+
.new(@msg_type, 'serialization of message type' \
|
37
|
+
' is not implemented (yet)')
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def serialize_bounds(args, esize: 0, size: 0, ocount: 0)
|
43
|
+
icount = args.size - args.count { |a| a.is_a?(String) }
|
44
|
+
return 0 if icount.zero?
|
45
|
+
if esize.zero?
|
46
|
+
ocount = icount if ocount.zero?
|
47
|
+
subsize = size / ocount
|
48
|
+
else
|
49
|
+
size = icount * esize
|
50
|
+
ocount = size / esize
|
51
|
+
subsize = esize
|
52
|
+
end
|
53
|
+
subcount = icount / ocount
|
54
|
+
|
55
|
+
[ocount, subcount, subsize]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'maxcube/messages'
|
2
|
+
|
3
|
+
module MaxCube
|
4
|
+
module Messages
|
5
|
+
# Structure of message:
|
6
|
+
# * Starts with single letter followed by ':'
|
7
|
+
# * Ends with "\r\n"
|
8
|
+
# Example (unencoded):
|
9
|
+
# X:message\r\n
|
10
|
+
# As all messages are being split by "\r\n",
|
11
|
+
# it does not occur in single message processing,
|
12
|
+
# only in raw data processing.
|
13
|
+
module TCP
|
14
|
+
# Without "\r\n", with it it is 1900
|
15
|
+
MSG_MAX_LEN = 1898
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'maxcube/messages/tcp'
|
2
|
+
require 'maxcube/messages/handler'
|
3
|
+
|
4
|
+
module MaxCube
|
5
|
+
module Messages
|
6
|
+
module TCP
|
7
|
+
module Handler
|
8
|
+
include Messages::Handler
|
9
|
+
|
10
|
+
def valid_tcp_msg_length(msg)
|
11
|
+
msg.length.between?(2, MSG_MAX_LEN)
|
12
|
+
end
|
13
|
+
|
14
|
+
def check_tcp_msg_length(msg)
|
15
|
+
raise InvalidMessageLength unless valid_tcp_msg_length(msg)
|
16
|
+
msg.length
|
17
|
+
end
|
18
|
+
|
19
|
+
def valid_tcp_msg_format(msg)
|
20
|
+
msg =~ /\A[[:alpha:]]:[[:print:]]*\z/
|
21
|
+
end
|
22
|
+
|
23
|
+
def check_tcp_msg_format(msg)
|
24
|
+
raise InvalidMessageFormat unless valid_tcp_msg_format(msg)
|
25
|
+
msg
|
26
|
+
end
|
27
|
+
|
28
|
+
# Check single message validity, already without "\r\n" at the end
|
29
|
+
def valid_tcp_msg(msg)
|
30
|
+
valid_tcp_msg_length(msg) &&
|
31
|
+
valid_tcp_msg_format(msg) &&
|
32
|
+
valid_msg(msg)
|
33
|
+
end
|
34
|
+
|
35
|
+
def check_tcp_msg(msg)
|
36
|
+
check_tcp_msg_length(msg)
|
37
|
+
check_tcp_msg_format(msg)
|
38
|
+
check_msg(msg)
|
39
|
+
msg
|
40
|
+
end
|
41
|
+
|
42
|
+
def valid_tcp_hash(hash)
|
43
|
+
valid_hash(hash)
|
44
|
+
end
|
45
|
+
|
46
|
+
def check_tcp_hash(hash)
|
47
|
+
check_hash(hash)
|
48
|
+
hash
|
49
|
+
end
|
50
|
+
|
51
|
+
def valid_tcp_data(raw_data)
|
52
|
+
return true if raw_data.empty?
|
53
|
+
raw_data[0..1] != "\r\n" && raw_data.chars.last(2).join == "\r\n"
|
54
|
+
end
|
55
|
+
|
56
|
+
def check_tcp_data(raw_data)
|
57
|
+
# check_data_type(raw_data)
|
58
|
+
raise InvalidMessageFormat unless valid_tcp_data(raw_data)
|
59
|
+
raw_data
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def msg_msg_type(msg)
|
65
|
+
msg.chr
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require_relative 'handler'
|
2
|
+
require 'maxcube/messages/parser'
|
3
|
+
|
4
|
+
module MaxCube
|
5
|
+
module Messages
|
6
|
+
module TCP
|
7
|
+
class Parser
|
8
|
+
include Handler
|
9
|
+
include Messages::Parser
|
10
|
+
|
11
|
+
%w[a c f h l m n s].each { |f| require_relative 'type/' << f }
|
12
|
+
|
13
|
+
MSG_TYPES = %w[H F L C M N A E D b g j p o v w S].freeze
|
14
|
+
|
15
|
+
include MessageA
|
16
|
+
include MessageC
|
17
|
+
include MessageF
|
18
|
+
include MessageH
|
19
|
+
include MessageL
|
20
|
+
include MessageM
|
21
|
+
include MessageN
|
22
|
+
include MessageS
|
23
|
+
|
24
|
+
# Process set of messages - raw data separated by "\r\n"
|
25
|
+
# @param [String, #read] raw data from a Cube
|
26
|
+
# @return [Array<Hash>] particular message contents
|
27
|
+
def parse_tcp_data(raw_data)
|
28
|
+
check_tcp_data(raw_data)
|
29
|
+
raw_data.split("\r\n").map(&method(:parse_tcp_msg))
|
30
|
+
end
|
31
|
+
|
32
|
+
# Parse single message already without "\r\n"
|
33
|
+
# @param [String, #read] single message data without "\r\n"
|
34
|
+
# @return [Hash] particular message parts separated into hash,
|
35
|
+
# which should be human readable
|
36
|
+
def parse_tcp_msg(msg)
|
37
|
+
check_tcp_msg(msg)
|
38
|
+
body = msg.split(':')[1] || ''
|
39
|
+
hash = { type: @msg_type }
|
40
|
+
return hash unless parse_msg_body(body, hash, 'tcp')
|
41
|
+
check_tcp_hash(hash)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require_relative 'handler'
|
2
|
+
require 'maxcube/messages/serializer'
|
3
|
+
|
4
|
+
module MaxCube
|
5
|
+
module Messages
|
6
|
+
module TCP
|
7
|
+
class Serializer
|
8
|
+
include Handler
|
9
|
+
include Messages::Serializer
|
10
|
+
|
11
|
+
%w[a c f l m n q s t u z].each { |f| require_relative 'type/' << f }
|
12
|
+
|
13
|
+
MSG_TYPES = %w[u i s m n x g q e d B G J P O V W a r t l c v f z].freeze
|
14
|
+
|
15
|
+
include MessageA
|
16
|
+
include MessageC
|
17
|
+
include MessageF
|
18
|
+
include MessageL
|
19
|
+
include MessageM
|
20
|
+
include MessageN
|
21
|
+
include MessageQ
|
22
|
+
include MessageS
|
23
|
+
include MessageT
|
24
|
+
include MessageU
|
25
|
+
include MessageZ
|
26
|
+
|
27
|
+
# Send set of messages separated by "\r\n"
|
28
|
+
# @param [Array<Hash>] particular message contents
|
29
|
+
# @return [String] raw data for a Cube
|
30
|
+
def serialize_tcp_hashes(hashes)
|
31
|
+
raw_data = hashes.map(&method(:serialize_tcp_hash)).join
|
32
|
+
check_tcp_data(raw_data)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Serialize data from hash into message with "\r\n" at the end
|
36
|
+
# @param [Hash, #read] particular human readable message parts
|
37
|
+
# (it is assumed to contain valid data)
|
38
|
+
# @return [String] single message data with "\r\n" at the end
|
39
|
+
def serialize_tcp_hash(hash)
|
40
|
+
check_tcp_hash(hash)
|
41
|
+
msg = "#{@msg_type}:" << serialize_hash_body(hash, 'tcp')
|
42
|
+
check_tcp_msg(msg) << "\r\n"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
|
2
|
+
module MaxCube
|
3
|
+
module Messages
|
4
|
+
module TCP
|
5
|
+
class Parser
|
6
|
+
module MessageA
|
7
|
+
private
|
8
|
+
|
9
|
+
# Acknowledgement message to previous command
|
10
|
+
# e.g. factory reset (a), delete a device (t), wake up (z)
|
11
|
+
# Ignore all contents of the message
|
12
|
+
def parse_tcp_a(_body)
|
13
|
+
{}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class Serializer
|
19
|
+
module MessageA
|
20
|
+
private
|
21
|
+
|
22
|
+
# Factory reset command
|
23
|
+
# Does not contain any data
|
24
|
+
# Acknowledgement (A) follows
|
25
|
+
def serialize_tcp_a(_hash)
|
26
|
+
''
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,248 @@
|
|
1
|
+
|
2
|
+
module MaxCube
|
3
|
+
module Messages
|
4
|
+
module TCP
|
5
|
+
class Parser
|
6
|
+
module MessageC
|
7
|
+
private
|
8
|
+
|
9
|
+
KEYS = %i[length address rf_address device_type
|
10
|
+
test_result serial_number].freeze
|
11
|
+
OPT_KEYS = %i[
|
12
|
+
firmware_version _firmware_version room_id
|
13
|
+
|
14
|
+
portal_enabled button_up_mode button_down_mode portal_url
|
15
|
+
|
16
|
+
comfort_temperature eco_temperature
|
17
|
+
max_setpoint_temperature min_setpoint_temperature
|
18
|
+
temperature_offset window_open_temperature window_open_duration
|
19
|
+
boost_duration valve_opening
|
20
|
+
decalcification_day decalcification_hour
|
21
|
+
max_valve_setting valve_offset
|
22
|
+
|
23
|
+
unknown unknown1 unknown2 unknown3 unknown4 weekly_program
|
24
|
+
].freeze
|
25
|
+
|
26
|
+
LENGTHS = [6].freeze
|
27
|
+
|
28
|
+
# Configuration message
|
29
|
+
def parse_tcp_c(body)
|
30
|
+
addr, enc_data = parse_tcp_c_split(body)
|
31
|
+
|
32
|
+
@io = StringIO.new(decode(enc_data), 'rb')
|
33
|
+
|
34
|
+
hash = parse_tcp_c_head(addr)
|
35
|
+
parse_tcp_c_device_type(hash)
|
36
|
+
|
37
|
+
hash
|
38
|
+
end
|
39
|
+
|
40
|
+
########################
|
41
|
+
|
42
|
+
def parse_tcp_c_split(body)
|
43
|
+
addr, enc_data = body.split(',')
|
44
|
+
check_msg_part_lengths(LENGTHS, addr)
|
45
|
+
to_ints(16, 'device address', addr)
|
46
|
+
[addr, enc_data]
|
47
|
+
end
|
48
|
+
|
49
|
+
def parse_tcp_c_head(addr)
|
50
|
+
@length = read(1, true)
|
51
|
+
# 'rf_address' should correspond with 'addr',
|
52
|
+
# but it is not checked (yet)
|
53
|
+
rf_address = read(3, true)
|
54
|
+
device_type = device_type(read(1, true))
|
55
|
+
hash = {
|
56
|
+
address: addr,
|
57
|
+
length: @length,
|
58
|
+
rf_address: rf_address,
|
59
|
+
device_type: device_type,
|
60
|
+
}
|
61
|
+
|
62
|
+
if device_type == :cube
|
63
|
+
# For 'cube' type, both fiels seem to be combined
|
64
|
+
# into 'firmware_version' string
|
65
|
+
room_id__fw_v = read(2, 'H*')
|
66
|
+
hash[:firmware_version] = room_id__fw_v[2..3] +
|
67
|
+
room_id__fw_v[0..1]
|
68
|
+
else
|
69
|
+
# For other types, both 'room_id' and 'firmware_version'
|
70
|
+
# are unpacked as numbers
|
71
|
+
# How should be 'firmware_version' interpreted ?
|
72
|
+
hash[:room_id] = read(1, true)
|
73
|
+
hash[:_firmware_version] = read(1, true)
|
74
|
+
end
|
75
|
+
|
76
|
+
hash.merge!(
|
77
|
+
test_result: read(1, true),
|
78
|
+
serial_number: read(10),
|
79
|
+
)
|
80
|
+
rescue IOError
|
81
|
+
raise InvalidMessageBody
|
82
|
+
.new(@msg_type,
|
83
|
+
'unexpected EOF reached at head of decoded message data')
|
84
|
+
end
|
85
|
+
|
86
|
+
def parse_tcp_c_cube_button_mode_temp(value, base)
|
87
|
+
(value - base).to_f / 2 + 4.5
|
88
|
+
end
|
89
|
+
|
90
|
+
def parse_tcp_c_cube_button_mode(hash, up, down)
|
91
|
+
{ 'up' => up, 'down' => down }.each do |k, v|
|
92
|
+
mode_key = "button_#{k}_mode".to_sym
|
93
|
+
case v
|
94
|
+
when 0x00
|
95
|
+
hash[mode_key] = :auto
|
96
|
+
when 0x41
|
97
|
+
hash[mode_key] = :eco
|
98
|
+
when 0x42
|
99
|
+
hash[mode_key] = :comfort
|
100
|
+
else
|
101
|
+
temp_key = "button_#{k}_temperature".to_sym
|
102
|
+
if v.between?(0x09, 0x3d)
|
103
|
+
hash[mode_key] = :auto_temp
|
104
|
+
hash[temp_key] = parse_tcp_c_cube_button_mode_temp(v, 0x09)
|
105
|
+
elsif v.between?(0x49, 0x7d)
|
106
|
+
hash[mode_key] = :manual
|
107
|
+
hash[temp_key] = parse_tcp_c_cube_button_mode_temp(v, 0x49)
|
108
|
+
else
|
109
|
+
hash[mode_key] = :unknown
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def parse_tcp_c_cube
|
116
|
+
hash = {
|
117
|
+
portal_enabled: !read(1, true).zero?,
|
118
|
+
unknown1: read(11),
|
119
|
+
}
|
120
|
+
|
121
|
+
pushbutton_up_config = read(1, true)
|
122
|
+
hash[:unknown2] = read(32)
|
123
|
+
pushbutton_down_config = read(1, true)
|
124
|
+
parse_tcp_c_cube_button_mode(hash,
|
125
|
+
pushbutton_up_config,
|
126
|
+
pushbutton_down_config)
|
127
|
+
|
128
|
+
# ! Exact decoding of time zones is not clear yet
|
129
|
+
hash.merge!(
|
130
|
+
unknown3: read(21),
|
131
|
+
portal_url: read(128),
|
132
|
+
# _timezone_winter: read(5),
|
133
|
+
# timezone_winter_month: read(1, true),
|
134
|
+
# timezone_winter_day: DAYS_OF_WEEK[read(1, true)],
|
135
|
+
# timezone_winter_hour: read(1, true),
|
136
|
+
# _timezone_winter_offset: read(4),
|
137
|
+
# _timezone_daylight: read(5),
|
138
|
+
# timezone_daylight_month: read(1, true),
|
139
|
+
# timezone_daylight_day: DAYS_OF_WEEK[read(1, true)],
|
140
|
+
# timezone_daylight_hour: read(1, true),
|
141
|
+
# _timezone_daylight_offset: read(4),
|
142
|
+
# unknown4: read(1),
|
143
|
+
unknown4: read,
|
144
|
+
)
|
145
|
+
end
|
146
|
+
|
147
|
+
def parse_tcp_c_thermostat_1
|
148
|
+
{
|
149
|
+
comfort_temperature: read(1, true).to_f / 2,
|
150
|
+
eco_temperature: read(1, true).to_f / 2,
|
151
|
+
max_setpoint_temperature: read(1, true).to_f / 2,
|
152
|
+
min_setpoint_temperature: read(1, true).to_f / 2,
|
153
|
+
}
|
154
|
+
end
|
155
|
+
|
156
|
+
def parse_tcp_c_program(subhash)
|
157
|
+
program = DAYS_OF_WEEK.zip([]).to_h
|
158
|
+
program.each_key do |day|
|
159
|
+
setpoints = []
|
160
|
+
13.times do
|
161
|
+
setpoint = read(2, true)
|
162
|
+
temperature = ((setpoint & 0xfe00) >> 9).to_f / 2
|
163
|
+
time_until = (setpoint & 0x01ff) * 5
|
164
|
+
setpoints << {
|
165
|
+
temperature: temperature,
|
166
|
+
hours_until: time_until / 60,
|
167
|
+
minutes_until: time_until % 60,
|
168
|
+
}
|
169
|
+
end
|
170
|
+
program[day] = setpoints
|
171
|
+
end
|
172
|
+
subhash[:weekly_program] = program
|
173
|
+
end
|
174
|
+
|
175
|
+
def parse_tcp_c_radiator
|
176
|
+
subhash = parse_tcp_c_thermostat_1.merge!(
|
177
|
+
temperature_offset: read(1, true).to_f / 2 - 3.5,
|
178
|
+
window_open_temperature: read(1, true).to_f / 2,
|
179
|
+
window_open_duration: read(1, true) * 5,
|
180
|
+
)
|
181
|
+
|
182
|
+
boost = read(1, true)
|
183
|
+
boost_duration = ((boost & 0xe0) >> 5) * 5
|
184
|
+
boost_duration = 60 if boost_duration > 30
|
185
|
+
|
186
|
+
decalcification = read(1, true)
|
187
|
+
|
188
|
+
subhash.merge!(
|
189
|
+
boost_duration: boost_duration,
|
190
|
+
valve_opening: (boost & 0x1f) * 5,
|
191
|
+
decalcification_day: day_of_week((decalcification & 0xe0) >> 5),
|
192
|
+
decalcification_hour: decalcification & 0x1f,
|
193
|
+
max_valve_setting: read(1, true) * (100.0 / 255),
|
194
|
+
valve_offset: read(1, true) * (100.0 / 255),
|
195
|
+
)
|
196
|
+
|
197
|
+
parse_tcp_c_program(subhash)
|
198
|
+
|
199
|
+
subhash
|
200
|
+
end
|
201
|
+
|
202
|
+
def parse_tcp_c_wall
|
203
|
+
subhash = parse_tcp_c_thermostat_1
|
204
|
+
parse_tcp_c_program(subhash)
|
205
|
+
subhash[:unknown] = read(3)
|
206
|
+
|
207
|
+
subhash
|
208
|
+
end
|
209
|
+
|
210
|
+
def parse_tcp_c_device_type(hash)
|
211
|
+
device_type = hash[:device_type]
|
212
|
+
hash.merge!(
|
213
|
+
case device_type
|
214
|
+
when :cube
|
215
|
+
parse_tcp_c_cube
|
216
|
+
when :radiator_thermostat, :radiator_thermostat_plus
|
217
|
+
parse_tcp_c_radiator
|
218
|
+
when :wall_thermostat
|
219
|
+
parse_tcp_c_wall
|
220
|
+
else
|
221
|
+
{}
|
222
|
+
end
|
223
|
+
)
|
224
|
+
rescue IOError
|
225
|
+
device_type_str = device_type.to_s.split('_')
|
226
|
+
.map(&:capitalize).join(' ')
|
227
|
+
raise InvalidMessageBody
|
228
|
+
.new(@msg_type,
|
229
|
+
'unexpected EOF reached in decoded message data of ' \
|
230
|
+
"'#{device_type_str}' device type")
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
class Serializer
|
236
|
+
module MessageC
|
237
|
+
private
|
238
|
+
|
239
|
+
# Request for configuration message (C)
|
240
|
+
# Does not contain any data
|
241
|
+
def serialize_tcp_c(_hash)
|
242
|
+
''
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|