somfy_sdn 1.0.1 → 1.0.3
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 +4 -4
- data/lib/sdn/message.rb +156 -157
- data/lib/sdn/mqtt_bridge.rb +5 -1
- data/lib/sdn/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 61d3c1baa4f63612972ab6ba2cfc05aed5173176101a8d19e2e53756f2e4ec2b
|
4
|
+
data.tar.gz: 6c7999e7755a4d17b304da25d5b70e864a737efde7bb6b7506a309057fae0137
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 75d5b1432d83f8713f81738e2ae99127be390465938f38274a026e8fe3c44cd45022cf74814e04a005c2a3b122b6735fb6424b0840cf8c1a82abff8a092a0a41
|
7
|
+
data.tar.gz: c253d1113a568f14209d2521ace015ccb8f7f0c1665f43dd5b64c29d601a5a163dc97c504c20d4a5c17e6c789cb9c51165f0108ba9e92dd8427677c48c252b51
|
data/lib/sdn/message.rb
CHANGED
@@ -1,173 +1,172 @@
|
|
1
1
|
require 'sdn/messages/helpers'
|
2
2
|
|
3
3
|
module SDN
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
end
|
19
|
-
end
|
20
|
-
data
|
21
|
-
end
|
22
|
-
|
23
|
-
def parse(io)
|
24
|
-
io = StringIO.new(io) if io.is_a?(String)
|
25
|
-
data = readpartial(io, 2, allow_empty: false)
|
26
|
-
if data.length != 2
|
27
|
-
# don't have enough data yet; buffer it
|
28
|
-
io.ungetbyte(data.first) if data.length == 1
|
29
|
-
raise MalformedMessage, "Could not get message type and length"
|
30
|
-
end
|
31
|
-
msg = to_number(data.first)
|
32
|
-
length = to_number(data.last)
|
33
|
-
ack_requested = length & 0x80 == 0x80
|
34
|
-
length &= 0x7f
|
35
|
-
if length < 11 || length > 32
|
36
|
-
# only skip over one byte to try and re-sync
|
37
|
-
io.ungetbyte(data.last)
|
38
|
-
raise MalformedMessage, "Message has bogus length: #{length}"
|
39
|
-
end
|
40
|
-
data.concat(readpartial(io, length - 4))
|
41
|
-
unless data.length == length - 2
|
42
|
-
data.reverse.each { |byte| io.ungetbyte(byte) }
|
43
|
-
raise MalformedMessage, "Missing data: got #{data.length} expected #{length}"
|
44
|
-
end
|
45
|
-
|
46
|
-
message_class = constants.find { |c| (const_get(c, false).const_get(:MSG, false) rescue nil) == msg }
|
47
|
-
message_class = const_get(message_class, false) if message_class
|
48
|
-
message_class ||= UnknownMessage
|
49
|
-
|
50
|
-
bogus_checksums = [SetNodeLabel::MSG, PostNodeLabel::MSG].include?(msg)
|
51
|
-
|
52
|
-
calculated_sum = checksum(data)
|
53
|
-
read_sum = readpartial(io, 2)
|
54
|
-
if read_sum.length == 0 || (!bogus_checksums && read_sum.length == 1)
|
55
|
-
read_sum.each { |byte| io.ungetbyte(byte) }
|
56
|
-
data.reverse.each { |byte| io.ungetbyte(byte) }
|
57
|
-
raise MalformedMessage, "Missing data: got #{data.length} expected #{length}"
|
58
|
-
end
|
59
|
-
|
60
|
-
# check both the proper checksum, and a truncated checksum
|
61
|
-
unless calculated_sum == read_sum || (bogus_checksums && calculated_sum.last == read_sum.first)
|
62
|
-
raw_message = (data + read_sum).map { |b| '%02x' % b }.join(' ')
|
63
|
-
# skip over single byte to try and re-sync
|
64
|
-
data.shift
|
65
|
-
read_sum.reverse.each { |byte| io.ungetbyte(byte) }
|
66
|
-
data.reverse.each { |byte| io.ungetbyte(byte) }
|
67
|
-
raise MalformedMessage, "Checksum mismatch for #{message_class.name}: #{raw_message}"
|
4
|
+
class MalformedMessage < RuntimeError; end
|
5
|
+
|
6
|
+
class Message
|
7
|
+
class << self
|
8
|
+
def readpartial(io, length, allow_empty: true)
|
9
|
+
data = []
|
10
|
+
while data.length < length
|
11
|
+
begin
|
12
|
+
data.concat(io.read_nonblock(length - data.length).bytes)
|
13
|
+
rescue EOFError
|
14
|
+
break
|
15
|
+
rescue IO::WaitReadable
|
16
|
+
break if allow_empty
|
17
|
+
IO.select([io])
|
68
18
|
end
|
69
|
-
# the checksum was truncated; put back the unused byte
|
70
|
-
io.ungetbyte(read_sum.last) if calculated_sum != read_sum && read_sum.length == 2
|
71
|
-
|
72
|
-
puts "read #{(data + read_sum).map { |b| '%02x' % b }.join(' ')}"
|
73
|
-
|
74
|
-
reserved = to_number(data[2])
|
75
|
-
src = transform_param(data[3..5])
|
76
|
-
dest = transform_param(data[6..8])
|
77
|
-
result = message_class.new(reserved: reserved, ack_requested: ack_requested, src: src, dest: dest)
|
78
|
-
result.parse(data[9..-1])
|
79
|
-
result.msg = msg if message_class == UnknownMessage
|
80
|
-
result
|
81
19
|
end
|
20
|
+
data
|
82
21
|
end
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
@ack_requested = ack_requested
|
92
|
-
if src.nil? && is_group_address?(dest)
|
93
|
-
src = dest
|
94
|
-
dest = nil
|
22
|
+
|
23
|
+
def parse(io)
|
24
|
+
io = StringIO.new(io) if io.is_a?(String)
|
25
|
+
data = readpartial(io, 2, allow_empty: false)
|
26
|
+
if data.length != 2
|
27
|
+
# don't have enough data yet; buffer it
|
28
|
+
io.ungetbyte(data.first) if data.length == 1
|
29
|
+
raise MalformedMessage, "Could not get message type and length"
|
95
30
|
end
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
length
|
107
|
-
|
108
|
-
|
109
|
-
result.concat(checksum(result))
|
110
|
-
puts "wrote #{result.map { |b| '%02x' % b }.join(' ')}"
|
111
|
-
result.pack("C*")
|
112
|
-
end
|
113
|
-
|
114
|
-
def inspect
|
115
|
-
"#<%s @reserved=%02xh, @ack_requested=%s, @src=%s, @dest=%s%s>" % [self.class.name, reserved, ack_requested, print_address(src), print_address(dest), class_inspect]
|
116
|
-
end
|
117
|
-
|
118
|
-
def class_inspect
|
119
|
-
ivars = instance_variables - [:@reserved, :@ack_requested, :@src, :@dest]
|
120
|
-
return if ivars.empty?
|
121
|
-
ivars.map { |iv| ", #{iv}=#{instance_variable_get(iv).inspect}" }.join
|
122
|
-
end
|
123
|
-
|
124
|
-
protected
|
125
|
-
|
126
|
-
def params; []; end
|
127
|
-
|
128
|
-
public
|
129
|
-
|
130
|
-
class SimpleRequest < Message
|
131
|
-
PARAMS_LENGTH = 0
|
132
|
-
|
133
|
-
def initialize(dest = nil, **kwargs)
|
134
|
-
kwargs[:dest] ||= dest
|
135
|
-
super(**kwargs)
|
31
|
+
msg = to_number(data.first)
|
32
|
+
length = to_number(data.last)
|
33
|
+
ack_requested = length & 0x80 == 0x80
|
34
|
+
length &= 0x7f
|
35
|
+
if length < 11 || length > 32
|
36
|
+
# only skip over one byte to try and re-sync
|
37
|
+
io.ungetbyte(data.last)
|
38
|
+
raise MalformedMessage, "Message has bogus length: #{length}"
|
39
|
+
end
|
40
|
+
data.concat(readpartial(io, length - 4))
|
41
|
+
unless data.length == length - 2
|
42
|
+
data.reverse.each { |byte| io.ungetbyte(byte) }
|
43
|
+
raise MalformedMessage, "Missing data: got #{data.length} expected #{length}"
|
136
44
|
end
|
137
|
-
end
|
138
45
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
46
|
+
message_class = constants.find { |c| (const_get(c, false).const_get(:MSG, false) rescue nil) == msg }
|
47
|
+
message_class = const_get(message_class, false) if message_class
|
48
|
+
message_class ||= UnknownMessage
|
49
|
+
|
50
|
+
bogus_checksums = [SetNodeLabel::MSG, PostNodeLabel::MSG].include?(msg)
|
51
|
+
|
52
|
+
calculated_sum = checksum(data)
|
53
|
+
read_sum = readpartial(io, 2)
|
54
|
+
if read_sum.length == 0 || (!bogus_checksums && read_sum.length == 1)
|
55
|
+
read_sum.each { |byte| io.ungetbyte(byte) }
|
56
|
+
data.reverse.each { |byte| io.ungetbyte(byte) }
|
57
|
+
raise MalformedMessage, "Missing data: got #{data.length} expected #{length}"
|
151
58
|
end
|
59
|
+
|
60
|
+
# check both the proper checksum, and a truncated checksum
|
61
|
+
unless calculated_sum == read_sum || (bogus_checksums && calculated_sum.last == read_sum.first)
|
62
|
+
raw_message = (data + read_sum).map { |b| '%02x' % b }.join(' ')
|
63
|
+
# skip over single byte to try and re-sync
|
64
|
+
data.shift
|
65
|
+
read_sum.reverse.each { |byte| io.ungetbyte(byte) }
|
66
|
+
data.reverse.each { |byte| io.ungetbyte(byte) }
|
67
|
+
raise MalformedMessage, "Checksum mismatch for #{message_class.name}: #{raw_message}"
|
68
|
+
end
|
69
|
+
# the checksum was truncated; put back the unused byte
|
70
|
+
io.ungetbyte(read_sum.last) if calculated_sum != read_sum && read_sum.length == 2
|
71
|
+
|
72
|
+
puts "read #{(data + read_sum).map { |b| '%02x' % b }.join(' ')}"
|
73
|
+
|
74
|
+
reserved = to_number(data[2])
|
75
|
+
src = transform_param(data[3..5])
|
76
|
+
dest = transform_param(data[6..8])
|
77
|
+
result = message_class.new(reserved: reserved, ack_requested: ack_requested, src: src, dest: dest)
|
78
|
+
result.parse(data[9..-1])
|
79
|
+
result.msg = msg if message_class == UnknownMessage
|
80
|
+
result
|
152
81
|
end
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
82
|
+
end
|
83
|
+
|
84
|
+
include Helpers
|
85
|
+
singleton_class.include Helpers
|
86
|
+
|
87
|
+
attr_reader :reserved, :ack_requested, :src, :dest
|
88
|
+
|
89
|
+
def initialize(reserved: nil, ack_requested: false, src: nil, dest: nil)
|
90
|
+
@reserved = reserved || 0x02 # message sent to Sonesse 30
|
91
|
+
@ack_requested = ack_requested
|
92
|
+
if src.nil? && is_group_address?(dest)
|
93
|
+
src = dest
|
94
|
+
dest = nil
|
157
95
|
end
|
96
|
+
@src = src || [0, 0, 1]
|
97
|
+
@dest = dest || [0, 0, 0]
|
98
|
+
end
|
158
99
|
|
159
|
-
|
160
|
-
class
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
100
|
+
def parse(params)
|
101
|
+
raise MalformedMessage, "unrecognized params for #{self.class.name}: #{params.map { |b| '%02x' % b }}" if self.class.const_defined?(:PARAMS_LENGTH) && params.length != self.class.const_get(:PARAMS_LENGTH)
|
102
|
+
end
|
103
|
+
|
104
|
+
def serialize
|
105
|
+
result = transform_param(reserved) + transform_param(src) + transform_param(dest) + params
|
106
|
+
length = result.length + 4
|
107
|
+
length |= 0x80 if ack_requested
|
108
|
+
result = transform_param(self.class.const_get(:MSG)) + transform_param(length) + result
|
109
|
+
result.concat(checksum(result))
|
110
|
+
puts "wrote #{result.map { |b| '%02x' % b }.join(' ')}"
|
111
|
+
result.pack("C*")
|
112
|
+
end
|
113
|
+
|
114
|
+
def inspect
|
115
|
+
"#<%s @reserved=%02xh, @ack_requested=%s, @src=%s, @dest=%s%s>" % [self.class.name, reserved, ack_requested, print_address(src), print_address(dest), class_inspect]
|
116
|
+
end
|
117
|
+
|
118
|
+
def class_inspect
|
119
|
+
ivars = instance_variables - [:@reserved, :@ack_requested, :@src, :@dest]
|
120
|
+
return if ivars.empty?
|
121
|
+
ivars.map { |iv| ", #{iv}=#{instance_variable_get(iv).inspect}" }.join
|
122
|
+
end
|
123
|
+
|
124
|
+
protected
|
125
|
+
|
126
|
+
def params; []; end
|
127
|
+
|
128
|
+
public
|
129
|
+
|
130
|
+
class SimpleRequest < Message
|
131
|
+
PARAMS_LENGTH = 0
|
132
|
+
|
133
|
+
def initialize(dest = nil, **kwargs)
|
134
|
+
kwargs[:dest] ||= dest
|
135
|
+
super(**kwargs)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
class Nack < Message
|
140
|
+
MSG = 0x6f
|
141
|
+
PARAMS_LENGTH = 1
|
142
|
+
VALUES = { data_error: 0x01, unknown_message: 0x10, node_is_locked: 0x20, wrong_position: 0x21, limits_not_set: 0x22, ip_not_set: 0x23, out_of_range: 0x24, busy: 0xff }
|
143
|
+
|
144
|
+
# presumed
|
145
|
+
attr_accessor :error_code
|
146
|
+
|
147
|
+
def parse(params)
|
148
|
+
super
|
149
|
+
error_code = to_number(params[0])
|
150
|
+
self.error_code = VALUES[error_code] || error_code
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
class Ack < Message
|
155
|
+
MSG = 0x7f
|
156
|
+
PARAMS_LENGTH = 0
|
157
|
+
end
|
158
|
+
|
159
|
+
# messages after this point were decoded from UAI+ communication and may be named wrong
|
160
|
+
class UnknownMessage < Message
|
161
|
+
attr_accessor :msg, :params
|
162
|
+
|
163
|
+
alias parse params=
|
164
|
+
|
165
|
+
def class_inspect
|
166
|
+
result = ", @msg=%02xh" % msg
|
167
|
+
return result if params.empty?
|
168
|
+
|
169
|
+
result << ", @params=#{params.map { |b| "%02x" % b }.join(' ')}"
|
171
170
|
end
|
172
171
|
end
|
173
172
|
end
|
data/lib/sdn/mqtt_bridge.rb
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
require 'mqtt'
|
2
|
+
require 'serialport'
|
3
|
+
require 'set'
|
4
|
+
|
1
5
|
module SDN
|
2
6
|
Motor = Struct.new(:bridge,
|
3
7
|
:addr,
|
@@ -199,7 +203,7 @@ module SDN
|
|
199
203
|
next unless SDN::Message::SetFactoryDefault::RESET.keys.include?(value.to_sym)
|
200
204
|
SDN::Message::SetFactoryDefault.new(addr, value.to_sym)
|
201
205
|
when 'positionpulses', 'positionpercent', 'ip'
|
202
|
-
SDN::Message::MoveTo.new(addr, property.to_sym, value.to_i)
|
206
|
+
SDN::Message::MoveTo.new(addr, property.sub('position', 'position_').to_sym, value.to_i)
|
203
207
|
when 'direction'
|
204
208
|
next if is_group
|
205
209
|
follow_up = SDN::Message::GetMotorDirection.new(addr)
|
data/lib/sdn/version.rb
CHANGED