somfy_sdn 1.0.12 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/somfy_sdn +60 -0
- data/lib/sdn.rb +16 -1
- data/lib/sdn/cli/mqtt.rb +369 -0
- data/lib/sdn/cli/mqtt/group.rb +31 -0
- data/lib/sdn/cli/mqtt/motor.rb +103 -0
- data/lib/sdn/cli/mqtt/read.rb +154 -0
- data/lib/sdn/cli/mqtt/subscriptions.rb +156 -0
- data/lib/sdn/cli/mqtt/write.rb +93 -0
- data/lib/sdn/cli/provisioner.rb +234 -0
- data/lib/sdn/cli/simulator.rb +197 -0
- data/lib/sdn/client.rb +84 -0
- data/lib/sdn/message.rb +81 -30
- data/lib/sdn/{messages → message}/control.rb +79 -38
- data/lib/sdn/{messages → message}/get.rb +48 -40
- data/lib/sdn/{messages → message}/helpers.rb +33 -3
- data/lib/sdn/message/ilt2/get.rb +48 -0
- data/lib/sdn/message/ilt2/master_control.rb +35 -0
- data/lib/sdn/message/ilt2/post.rb +127 -0
- data/lib/sdn/message/ilt2/set.rb +192 -0
- data/lib/sdn/{messages → message}/post.rb +127 -61
- data/lib/sdn/{messages → message}/set.rb +99 -84
- data/lib/sdn/version.rb +1 -1
- metadata +53 -16
- data/bin/sdn_mqtt_bridge +0 -5
- data/lib/sdn/messages/ilt2/get.rb +0 -9
- data/lib/sdn/messages/ilt2/post.rb +0 -18
- data/lib/sdn/messages/ilt2/set.rb +0 -59
- data/lib/sdn/mqtt_bridge.rb +0 -711
data/lib/sdn/message.rb
CHANGED
@@ -1,15 +1,21 @@
|
|
1
|
-
require 'sdn/
|
1
|
+
require 'sdn/message/helpers'
|
2
2
|
|
3
3
|
module SDN
|
4
4
|
class MalformedMessage < RuntimeError; end
|
5
5
|
|
6
6
|
class Message
|
7
7
|
class << self
|
8
|
-
def
|
8
|
+
def inherited(klass)
|
9
|
+
return Message.inherited(klass) unless self == Message
|
10
|
+
@message_map = nil
|
11
|
+
(@subclasses ||= []) << klass
|
12
|
+
end
|
13
|
+
|
14
|
+
def expected_response?(message)
|
9
15
|
if name =~ /::Get([A-Za-z]+)/
|
10
|
-
|
16
|
+
message.class.name == name.sub("::Get", "::Post")
|
11
17
|
else
|
12
|
-
Ack
|
18
|
+
message.is_a?(Ack) || message.is_a?(Nack)
|
13
19
|
end
|
14
20
|
end
|
15
21
|
|
@@ -19,20 +25,22 @@ module SDN
|
|
19
25
|
# we loop here scanning for a valid message
|
20
26
|
loop do
|
21
27
|
offset += 1
|
28
|
+
# give these weird messages a chance
|
29
|
+
result = ILT2::MasterControl.parse(data)
|
30
|
+
return result if result
|
31
|
+
|
22
32
|
return [nil, 0] if data.length - offset < 11
|
23
33
|
msg = to_number(data[offset])
|
24
34
|
length = to_number(data[offset + 1])
|
25
35
|
ack_requested = length & 0x80 == 0x80
|
26
36
|
length &= 0x7f
|
27
37
|
# impossible message
|
28
|
-
next if length < 11 || length >
|
38
|
+
next if length < 11 || length > 43
|
29
39
|
# don't have enough data for what this message wants;
|
30
40
|
# it could be garbage on the line so keep scanning
|
31
41
|
next if length > data.length - offset
|
32
42
|
|
33
|
-
message_class =
|
34
|
-
message_class = const_get(message_class, false) if message_class
|
35
|
-
message_class ||= UnknownMessage
|
43
|
+
message_class = message_map[msg] || UnknownMessage
|
36
44
|
|
37
45
|
calculated_sum = checksum(data.slice(offset, length - 2))
|
38
46
|
read_sum = data.slice(offset + length - 2, 2)
|
@@ -41,34 +49,41 @@ module SDN
|
|
41
49
|
break
|
42
50
|
end
|
43
51
|
|
44
|
-
|
45
|
-
puts "read #{data.slice(offset, length).map { |b| '%02x' % b }.join(' ')}"
|
46
|
-
|
47
|
-
reserved = to_number(data[offset + 2])
|
52
|
+
node_type = node_type_from_number(to_number(data[offset + 2]))
|
48
53
|
src = transform_param(data.slice(offset + 3, 3))
|
49
54
|
dest = transform_param(data.slice(offset + 6, 3))
|
50
55
|
begin
|
51
|
-
result = message_class.new(
|
56
|
+
result = message_class.new(node_type: node_type, ack_requested: ack_requested, src: src, dest: dest)
|
52
57
|
result.parse(data.slice(offset + 9, length - 11))
|
53
58
|
result.msg = msg if message_class == UnknownMessage
|
54
59
|
rescue ArgumentError => e
|
55
|
-
|
60
|
+
SDN.logger.warn "discarding illegal message of type #{message_class.name}: #{e}"
|
56
61
|
result = nil
|
57
62
|
end
|
58
63
|
[result, offset + length]
|
59
64
|
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def message_map
|
69
|
+
@message_map ||=
|
70
|
+
@subclasses.inject({}) do |memo, klass|
|
71
|
+
next memo unless klass.constants(false).include?(:MSG)
|
72
|
+
memo[klass.const_get(:MSG, false)] = klass
|
73
|
+
memo
|
74
|
+
end
|
75
|
+
end
|
60
76
|
end
|
61
77
|
|
62
78
|
include Helpers
|
63
79
|
singleton_class.include Helpers
|
64
80
|
|
65
|
-
|
66
|
-
attr_writer :ack_requested
|
81
|
+
attr_accessor :node_type, :ack_requested, :src, :dest
|
67
82
|
|
68
|
-
def initialize(
|
69
|
-
@
|
83
|
+
def initialize(node_type: nil, ack_requested: false, src: nil, dest: nil)
|
84
|
+
@node_type = node_type || 0
|
70
85
|
@ack_requested = ack_requested
|
71
|
-
if src.nil? && is_group_address?(dest)
|
86
|
+
if src.nil? && !dest.nil? && is_group_address?(dest)
|
72
87
|
src = dest
|
73
88
|
dest = nil
|
74
89
|
end
|
@@ -81,7 +96,7 @@ module SDN
|
|
81
96
|
end
|
82
97
|
|
83
98
|
def serialize
|
84
|
-
result = transform_param(
|
99
|
+
result = transform_param(node_type_to_number(node_type)) + transform_param(src) + transform_param(dest) + params
|
85
100
|
length = result.length + 4
|
86
101
|
length |= 0x80 if ack_requested
|
87
102
|
result = transform_param(self.class.const_get(:MSG)) + transform_param(length) + result
|
@@ -94,11 +109,11 @@ module SDN
|
|
94
109
|
end
|
95
110
|
|
96
111
|
def inspect
|
97
|
-
"#<%s @
|
112
|
+
"#<%s @node_type=%s, @ack_requested=%s, @src=%s, @dest=%s%s>" % [self.class.name, node_type_to_string(node_type), ack_requested, print_address(src), print_address(dest), class_inspect]
|
98
113
|
end
|
99
114
|
|
100
115
|
def class_inspect
|
101
|
-
ivars = instance_variables - [:@
|
116
|
+
ivars = instance_variables - [:@node_type, :@ack_requested, :@src, :@dest, :@params]
|
102
117
|
return if ivars.empty?
|
103
118
|
ivars.map { |iv| ", #{iv}=#{instance_variable_get(iv).inspect}" }.join
|
104
119
|
end
|
@@ -121,19 +136,36 @@ module SDN
|
|
121
136
|
class Nack < Message
|
122
137
|
MSG = 0x6f
|
123
138
|
PARAMS_LENGTH = 1
|
124
|
-
VALUES = { data_error: 0x01,
|
139
|
+
VALUES = { data_error: 0x01,
|
140
|
+
unknown_message: 0x10,
|
141
|
+
node_is_locked: 0x20,
|
142
|
+
wrong_position: 0x21,
|
143
|
+
limits_not_set: 0x22,
|
144
|
+
ip_not_set: 0x23,
|
145
|
+
out_of_range: 0x24,
|
146
|
+
busy: 0xff }
|
147
|
+
# 17 limits not set?
|
148
|
+
# 37 not implemented? (get motor rolling speed)
|
149
|
+
# 39 at limit? blocked?
|
150
|
+
|
125
151
|
|
126
152
|
# presumed
|
127
153
|
attr_accessor :error_code
|
128
154
|
|
155
|
+
def initialize(dest = nil, error_code = nil, **kwargs)
|
156
|
+
kwargs[:dest] ||= dest
|
157
|
+
super(**kwargs)
|
158
|
+
self.error_code = error_code
|
159
|
+
end
|
160
|
+
|
129
161
|
def parse(params)
|
130
162
|
super
|
131
163
|
error_code = to_number(params[0])
|
132
|
-
self.error_code = VALUES[error_code] || error_code
|
164
|
+
self.error_code = VALUES.invert[error_code] || error_code
|
133
165
|
end
|
134
166
|
end
|
135
167
|
|
136
|
-
class Ack <
|
168
|
+
class Ack < SimpleRequest
|
137
169
|
MSG = 0x7f
|
138
170
|
PARAMS_LENGTH = 0
|
139
171
|
end
|
@@ -142,10 +174,25 @@ module SDN
|
|
142
174
|
class UnknownMessage < Message
|
143
175
|
attr_accessor :msg, :params
|
144
176
|
|
177
|
+
def initialize(params = [], **kwargs)
|
178
|
+
super(**kwargs)
|
179
|
+
self.params = params
|
180
|
+
end
|
181
|
+
|
145
182
|
alias parse params=
|
146
183
|
|
184
|
+
def serialize
|
185
|
+
# prevent serializing something we don't know
|
186
|
+
raise NotImplementedError unless params
|
187
|
+
super
|
188
|
+
end
|
189
|
+
|
147
190
|
def class_inspect
|
148
|
-
result =
|
191
|
+
result = if self.class == UnknownMessage
|
192
|
+
result = ", @msg=%02xh" % msg
|
193
|
+
else
|
194
|
+
super || ""
|
195
|
+
end
|
149
196
|
return result if params.empty?
|
150
197
|
|
151
198
|
result << ", @params=#{params.map { |b| "%02x" % b }.join(' ')}"
|
@@ -154,7 +201,11 @@ module SDN
|
|
154
201
|
end
|
155
202
|
end
|
156
203
|
|
157
|
-
require 'sdn/
|
158
|
-
require 'sdn/
|
159
|
-
require 'sdn/
|
160
|
-
require 'sdn/
|
204
|
+
require 'sdn/message/control'
|
205
|
+
require 'sdn/message/get'
|
206
|
+
require 'sdn/message/post'
|
207
|
+
require 'sdn/message/set'
|
208
|
+
require 'sdn/message/ilt2/get'
|
209
|
+
require 'sdn/message/ilt2/master_control'
|
210
|
+
require 'sdn/message/ilt2/post'
|
211
|
+
require 'sdn/message/ilt2/set'
|
@@ -1,5 +1,46 @@
|
|
1
1
|
module SDN
|
2
2
|
class Message
|
3
|
+
class Lock < Message
|
4
|
+
MSG = 0x06
|
5
|
+
PARAMS_LENGTH = 5
|
6
|
+
TARGET_TYPE = { current: 0, up_limit: 1, down_limit: 2, ip: 4, unlock: 5, position_percent: 7 }
|
7
|
+
|
8
|
+
attr_reader :target_type, :target, :priority
|
9
|
+
|
10
|
+
def initialize(dest = nil, target_type = :unlock, target = nil, priority = 1, **kwargs)
|
11
|
+
kwargs[:dest] ||= dest
|
12
|
+
super(**kwargs)
|
13
|
+
self.target_type = target_type
|
14
|
+
self.target = target
|
15
|
+
self.priority = priority
|
16
|
+
end
|
17
|
+
|
18
|
+
def target_type=(value)
|
19
|
+
raise ArgumentError, "target_type must be one of :current, :up_limit, :down_limit, :ip, :unlock, or :position_percent" unless TARGET_TYPE.keys.include?(value)
|
20
|
+
@target_type = value
|
21
|
+
end
|
22
|
+
|
23
|
+
def target=(value)
|
24
|
+
@target = value&. & 0xffff
|
25
|
+
end
|
26
|
+
|
27
|
+
def priority=(value)
|
28
|
+
@priority = value & 0xff
|
29
|
+
end
|
30
|
+
|
31
|
+
def parse(params)
|
32
|
+
super
|
33
|
+
self.target_type = TARGET_TYPE.invert[to_number(params[0])]
|
34
|
+
target = to_number(params[1..2], nillable: true)
|
35
|
+
self.target = target
|
36
|
+
self.priority = to_number(params[3])
|
37
|
+
end
|
38
|
+
|
39
|
+
def params
|
40
|
+
transform_param(TARGET_TYPE[target_type]) + from_number(target, 2) + transform_param(priority) + transform_param(0)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
3
44
|
# Move in momentary mode
|
4
45
|
class Move < Message
|
5
46
|
MSG = 0x01
|
@@ -32,7 +73,7 @@ module SDN
|
|
32
73
|
end
|
33
74
|
|
34
75
|
def duration=(value)
|
35
|
-
raise ArgumentError, "duration must be in range 0x0a to 0xff (#{value})"
|
76
|
+
raise ArgumentError, "duration must be in range 0x0a to 0xff (#{value})" if value && (value < 0x0a || value > 0xff)
|
36
77
|
@duration = value
|
37
78
|
end
|
38
79
|
|
@@ -48,18 +89,43 @@ module SDN
|
|
48
89
|
end
|
49
90
|
end
|
50
91
|
|
51
|
-
#
|
52
|
-
class
|
53
|
-
MSG =
|
54
|
-
PARAMS_LENGTH =
|
92
|
+
# Move relative to current position
|
93
|
+
class MoveOf < Message
|
94
|
+
MSG = 0x04
|
95
|
+
PARAMS_LENGTH = 4
|
96
|
+
TARGET_TYPE = { next_ip: 0x00, previous_ip: 0x01, jog_down_pulses: 0x02, jog_up_pulses: 0x03, jog_down_ms: 0x04, jog_up_ms: 0x05 }
|
55
97
|
|
56
|
-
|
98
|
+
attr_reader :target_type, :target
|
99
|
+
|
100
|
+
def initialize(dest = nil, target_type = nil, target = nil, **kwargs)
|
57
101
|
kwargs[:dest] ||= dest
|
58
102
|
super(**kwargs)
|
103
|
+
self.target_type = target_type
|
104
|
+
self.target = target
|
105
|
+
end
|
106
|
+
|
107
|
+
def parse(params)
|
108
|
+
super
|
109
|
+
self.target_type = TARGET_TYPE.invert[to_number(params[0])]
|
110
|
+
target = to_number(params[1..2], nillable: true)
|
111
|
+
target *= 10 if %I{jog_down_ms jog_up_ms}.include?(target_type)
|
112
|
+
self.target = target
|
113
|
+
end
|
114
|
+
|
115
|
+
def target_type=(value)
|
116
|
+
raise ArgumentError, "target_type must be one of :next_ip, :previous_ip, :jog_down_pulses, :jog_up_pulses, :jog_down_ms, :jog_up_ms" unless value.nil? || TARGET_TYPE.keys.include?(value)
|
117
|
+
@target_type = value
|
118
|
+
end
|
119
|
+
|
120
|
+
def target=(value)
|
121
|
+
value &= 0xffff if value
|
122
|
+
@target = value
|
59
123
|
end
|
60
124
|
|
61
125
|
def params
|
62
|
-
|
126
|
+
param = target || 0xffff
|
127
|
+
param /= 10 if %I{jog_down_ms jog_up_ms}.include?(target_type)
|
128
|
+
transform_param(TARGET_TYPE[target_type]) + from_number(param, 2) + transform_param(0)
|
63
129
|
end
|
64
130
|
end
|
65
131
|
|
@@ -107,43 +173,18 @@ module SDN
|
|
107
173
|
end
|
108
174
|
end
|
109
175
|
|
110
|
-
#
|
111
|
-
class
|
112
|
-
MSG =
|
113
|
-
PARAMS_LENGTH =
|
114
|
-
TARGET_TYPE = { next_ip: 0x00, previous_ip: 0x01, jog_down_pulses: 0x02, jog_up_pulses: 0x03, jog_down_ms: 0x04, jog_up_ms: 0x05 }
|
115
|
-
|
116
|
-
attr_reader :target_type, :target
|
176
|
+
# Stop movement
|
177
|
+
class Stop < Message
|
178
|
+
MSG = 0x02
|
179
|
+
PARAMS_LENGTH = 1
|
117
180
|
|
118
|
-
def initialize(dest = nil,
|
181
|
+
def initialize(dest = nil, **kwargs)
|
119
182
|
kwargs[:dest] ||= dest
|
120
183
|
super(**kwargs)
|
121
|
-
self.target_type = target_type
|
122
|
-
self.target = target
|
123
|
-
end
|
124
|
-
|
125
|
-
def parse(params)
|
126
|
-
super
|
127
|
-
self.target_type = TARGET_TYPE.invert[to_number(params[0])]
|
128
|
-
target = to_number(params[1..2], nillable: true)
|
129
|
-
target *= 10 if %I{jog_down_ms jog_up_ms}.include?(target_type)
|
130
|
-
self.target = target
|
131
|
-
end
|
132
|
-
|
133
|
-
def target_type=(value)
|
134
|
-
raise ArgumentError, "target_type must be one of :next_ip, :previous_ip, :jog_down_pulses, :jog_up_pulses, :jog_down_ms, :jog_up_ms" unless value.nil? || TARGET_TYPE.keys.include?(value)
|
135
|
-
@target_type = value
|
136
|
-
end
|
137
|
-
|
138
|
-
def target=(value)
|
139
|
-
value &= 0xffff if value
|
140
|
-
@target = value
|
141
184
|
end
|
142
185
|
|
143
186
|
def params
|
144
|
-
|
145
|
-
param /= 10 if %I{jog_down_ms jog_up_ms}.include?(target_type)
|
146
|
-
transform_param(TARGET_TYPE[target_type]) + from_number(param, 2) + transform_param(0)
|
187
|
+
transform_param(0)
|
147
188
|
end
|
148
189
|
end
|
149
190
|
|
@@ -1,32 +1,43 @@
|
|
1
1
|
module SDN
|
2
2
|
class Message
|
3
|
-
class
|
4
|
-
MSG =
|
5
|
-
|
3
|
+
class GetGroupAddr < Message
|
4
|
+
MSG = 0x41
|
5
|
+
PARAMS_LENGTH = 1
|
6
6
|
|
7
|
-
|
8
|
-
MSG = 0x0e
|
9
|
-
end
|
7
|
+
attr_reader :group_index
|
10
8
|
|
11
|
-
|
12
|
-
|
9
|
+
def initialize(dest = nil, group_index = 1, **kwargs)
|
10
|
+
kwargs[:dest] ||= dest
|
11
|
+
super(**kwargs)
|
12
|
+
self.group_index = group_index
|
13
|
+
end
|
14
|
+
|
15
|
+
def parse(params)
|
16
|
+
super
|
17
|
+
self.group_index = to_number(params[0]) + 1
|
18
|
+
end
|
19
|
+
|
20
|
+
def group_index=(value)
|
21
|
+
raise ArgumentError, "group_index is out of range" unless (1..16).include?(value)
|
22
|
+
@group_index = value
|
23
|
+
end
|
24
|
+
|
25
|
+
def params
|
26
|
+
transform_param(group_index - 1)
|
27
|
+
end
|
13
28
|
end
|
14
29
|
|
15
30
|
class GetMotorDirection < SimpleRequest
|
16
31
|
MSG = 0x22
|
17
32
|
end
|
18
33
|
|
19
|
-
class GetMotorRollingSpeed < SimpleRequest
|
20
|
-
MSG = 0x23
|
21
|
-
end
|
22
|
-
|
23
34
|
class GetMotorIP < Message
|
24
35
|
MSG = 0x25
|
25
36
|
PARAMS_LENGTH = 1
|
26
37
|
|
27
38
|
attr_reader :ip
|
28
39
|
|
29
|
-
def initialize(dest = nil, ip =
|
40
|
+
def initialize(dest = nil, ip = 1, **kwargs)
|
30
41
|
kwargs[:dest] ||= dest
|
31
42
|
super(**kwargs)
|
32
43
|
self.ip = ip
|
@@ -34,48 +45,37 @@ module SDN
|
|
34
45
|
|
35
46
|
def parse(params)
|
36
47
|
super
|
37
|
-
self.ip = to_number(params[0]
|
48
|
+
self.ip = to_number(params[0])
|
38
49
|
end
|
39
50
|
|
40
51
|
def ip=(value)
|
41
|
-
raise ArgumentError, "invalid IP #{
|
52
|
+
raise ArgumentError, "invalid IP #{value} (should be 1-16)" unless (1..16).include?(value)
|
42
53
|
@ip = value
|
43
54
|
end
|
44
55
|
|
45
56
|
def params
|
46
|
-
transform_param(
|
57
|
+
transform_param(ip)
|
47
58
|
end
|
48
59
|
end
|
49
60
|
|
50
|
-
class
|
51
|
-
MSG =
|
52
|
-
|
53
|
-
|
54
|
-
attr_reader :group_index
|
55
|
-
|
56
|
-
def initialize(dest = nil, group_index = 0, **kwargs)
|
57
|
-
kwargs[:dest] ||= dest
|
58
|
-
super(**kwargs)
|
59
|
-
self.group_index = group_index
|
60
|
-
end
|
61
|
+
class GetMotorLimits < SimpleRequest
|
62
|
+
MSG = 0x21
|
63
|
+
end
|
61
64
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
end
|
65
|
+
class GetMotorPosition < SimpleRequest
|
66
|
+
MSG = 0x0c
|
67
|
+
end
|
66
68
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
end
|
69
|
+
class GetMotorRollingSpeed < SimpleRequest
|
70
|
+
MSG = 0x23
|
71
|
+
end
|
71
72
|
|
72
|
-
|
73
|
-
|
74
|
-
end
|
73
|
+
class GetMotorStatus < SimpleRequest
|
74
|
+
MSG = 0x0e
|
75
75
|
end
|
76
76
|
|
77
|
-
class
|
78
|
-
MSG =
|
77
|
+
class GetNetworkLock < SimpleRequest
|
78
|
+
MSG = 0x26
|
79
79
|
end
|
80
80
|
|
81
81
|
class GetNodeAddr < Message
|
@@ -88,6 +88,14 @@ module SDN
|
|
88
88
|
end
|
89
89
|
end
|
90
90
|
|
91
|
+
class GetNodeAppVersion < SimpleRequest
|
92
|
+
MSG = 0x74
|
93
|
+
end
|
94
|
+
|
95
|
+
class GetNodeLabel < SimpleRequest
|
96
|
+
MSG = 0x45
|
97
|
+
end
|
98
|
+
|
91
99
|
class GetNodeSerialNumber < SimpleRequest
|
92
100
|
MSG = 0x4c
|
93
101
|
end
|