somfy_sdn 2.4.0 → 2.5.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 +4 -4
- data/exe/somfy_sdn +1 -0
- data/lib/sdn/cli/mqtt/group.rb +11 -0
- data/lib/sdn/cli/mqtt/motor.rb +25 -1
- data/lib/sdn/cli/mqtt/p_queue.rb +6 -0
- data/lib/sdn/cli/mqtt/read.rb +46 -47
- data/lib/sdn/cli/mqtt/subscriptions.rb +8 -0
- data/lib/sdn/cli/mqtt/write.rb +67 -27
- data/lib/sdn/cli/mqtt.rb +296 -211
- data/lib/sdn/cli/provisioner.rb +34 -1
- data/lib/sdn/cli/simulator.rb +78 -11
- data/lib/sdn/cli.rb +6 -0
- data/lib/sdn/client.rb +42 -2
- data/lib/sdn/message/control.rb +136 -12
- data/lib/sdn/message/get.rb +57 -0
- data/lib/sdn/message/helpers.rb +64 -0
- data/lib/sdn/message/ilt2/get.rb +24 -0
- data/lib/sdn/message/ilt2/master_control.rb +10 -0
- data/lib/sdn/message/ilt2/post.rb +79 -13
- data/lib/sdn/message/ilt2/set.rb +112 -7
- data/lib/sdn/message/post.rb +181 -20
- data/lib/sdn/message/set.rb +166 -8
- data/lib/sdn/message.rb +121 -14
- data/lib/sdn/version.rb +2 -1
- data/lib/sdn.rb +13 -5
- metadata +5 -18
data/lib/sdn/message/set.rb
CHANGED
|
@@ -2,9 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
module SDN
|
|
4
4
|
class Message
|
|
5
|
+
# Command that resets a motor's persisted settings.
|
|
5
6
|
class SetFactoryDefault < Message
|
|
7
|
+
# (see Ack::MSG)
|
|
6
8
|
MSG = 0x1f
|
|
9
|
+
# (see Ack::PARAMS_LENGTH)
|
|
7
10
|
PARAMS_LENGTH = 1
|
|
11
|
+
# Mapping from symbolic reset scopes to SDN parameter values.
|
|
8
12
|
RESET = { all_settings: 0x00,
|
|
9
13
|
group_addresses: 0x01,
|
|
10
14
|
limits: 0x11,
|
|
@@ -13,14 +17,25 @@ module SDN
|
|
|
13
17
|
ips: 0x15,
|
|
14
18
|
locks: 0x17 }.freeze
|
|
15
19
|
|
|
20
|
+
# @!attribute [rw] reset
|
|
21
|
+
#
|
|
22
|
+
# Requested reset scope.
|
|
23
|
+
#
|
|
24
|
+
# @return [:all_settings, :group_addresses, :limits, :rotation, :rolling_speed, :ips, :locks]
|
|
16
25
|
attr_reader :reset
|
|
17
26
|
|
|
27
|
+
# Creates a factory reset command.
|
|
28
|
+
#
|
|
29
|
+
# @param dest [(Integer, Integer, Integer), nil] destination address
|
|
30
|
+
# @param reset [:all_settings, :group_addresses, :limits, :rotation, :rolling_speed, :ips, :locks] reset scope
|
|
31
|
+
# @param kwargs [Hash] additional message options
|
|
18
32
|
def initialize(dest = nil, reset = :all_settings, **kwargs)
|
|
19
33
|
kwargs[:dest] ||= dest
|
|
20
34
|
super(**kwargs)
|
|
21
35
|
self.reset = reset
|
|
22
36
|
end
|
|
23
37
|
|
|
38
|
+
# (see Message#parse)
|
|
24
39
|
def parse(params)
|
|
25
40
|
super
|
|
26
41
|
self.reset = RESET.invert[to_number(params)]
|
|
@@ -35,18 +50,37 @@ module SDN
|
|
|
35
50
|
@reset = value
|
|
36
51
|
end
|
|
37
52
|
|
|
53
|
+
# (see Message#params)
|
|
38
54
|
def params
|
|
39
55
|
transform_param(RESET[reset])
|
|
40
56
|
end
|
|
41
57
|
end
|
|
42
58
|
|
|
59
|
+
# Command that assigns a motor to a group slot.
|
|
43
60
|
class SetGroupAddr < Message
|
|
61
|
+
# (see Ack::MSG)
|
|
44
62
|
MSG = 0x51
|
|
63
|
+
# (see Ack::PARAMS_LENGTH)
|
|
45
64
|
PARAMS_LENGTH = 4
|
|
46
65
|
|
|
66
|
+
# @!attribute [rw] group_address
|
|
67
|
+
#
|
|
68
|
+
# @return [(Integer, Integer, Integer), nil]
|
|
47
69
|
attr_accessor :group_address
|
|
70
|
+
|
|
71
|
+
# @!attribute [rw] group_index
|
|
72
|
+
#
|
|
73
|
+
# Group slot to assign the address to in the range 1..16.
|
|
74
|
+
#
|
|
75
|
+
# @return [Integer]
|
|
48
76
|
attr_reader :group_index
|
|
49
77
|
|
|
78
|
+
# Creates a group assignment command.
|
|
79
|
+
#
|
|
80
|
+
# @param dest [(Integer, Integer, Integer), nil] destination address
|
|
81
|
+
# @param group_index [Integer] group slot
|
|
82
|
+
# @param group_address [(Integer, Integer, Integer), nil] group address
|
|
83
|
+
# @param kwargs [Hash] additional message options
|
|
50
84
|
def initialize(dest = nil, group_index = 1, group_address = nil, **kwargs)
|
|
51
85
|
kwargs[:dest] ||= dest
|
|
52
86
|
super(**kwargs)
|
|
@@ -54,6 +88,7 @@ module SDN
|
|
|
54
88
|
self.group_address = group_address
|
|
55
89
|
end
|
|
56
90
|
|
|
91
|
+
# (see Message#parse)
|
|
57
92
|
def parse(params)
|
|
58
93
|
super
|
|
59
94
|
self.group_index = to_number(params[0]) + 1
|
|
@@ -66,28 +101,43 @@ module SDN
|
|
|
66
101
|
@group_index = value
|
|
67
102
|
end
|
|
68
103
|
|
|
104
|
+
# (see Message#params)
|
|
69
105
|
def params
|
|
70
106
|
transform_param(group_index - 1) + transform_param(group_address || [0, 0, 0])
|
|
71
107
|
end
|
|
72
108
|
|
|
109
|
+
# (see Message#class_inspect)
|
|
73
110
|
def class_inspect
|
|
74
111
|
", group_index=#{group_index.inspect}, group_address=#{group_address ? print_address(group_address) : "nil"}"
|
|
75
112
|
end
|
|
76
113
|
end
|
|
77
114
|
|
|
115
|
+
# Command that sets a motor's rotation direction.
|
|
78
116
|
class SetMotorDirection < Message
|
|
117
|
+
# (see Ack::MSG)
|
|
79
118
|
MSG = 0x12
|
|
119
|
+
# (see Ack::PARAMS_LENGTH)
|
|
80
120
|
PARAMS_LENGTH = 1
|
|
121
|
+
# Mapping from symbolic directions to SDN parameter values.
|
|
81
122
|
DIRECTION = { standard: 0x00, reversed: 0x01 }.freeze
|
|
82
123
|
|
|
124
|
+
# @!attribute [rw] direction
|
|
125
|
+
#
|
|
126
|
+
# @return [:standard, :reversed]
|
|
83
127
|
attr_reader :direction
|
|
84
128
|
|
|
129
|
+
# Creates a motor direction command.
|
|
130
|
+
#
|
|
131
|
+
# @param dest [(Integer, Integer, Integer), nil] destination address
|
|
132
|
+
# @param direction [:standard, :reversed] rotation direction
|
|
133
|
+
# @param kwargs [Hash] additional message options
|
|
85
134
|
def initialize(dest = nil, direction = :standard, **kwargs)
|
|
86
135
|
kwargs[:dest] ||= dest
|
|
87
136
|
super(**kwargs)
|
|
88
137
|
self.direction = direction
|
|
89
138
|
end
|
|
90
139
|
|
|
140
|
+
# (see Message#parse)
|
|
91
141
|
def parse(params)
|
|
92
142
|
super
|
|
93
143
|
self.direction = DIRECTION.invert[to_number(params[0])]
|
|
@@ -99,23 +149,52 @@ module SDN
|
|
|
99
149
|
@direction = value
|
|
100
150
|
end
|
|
101
151
|
|
|
152
|
+
# (see Message#params)
|
|
102
153
|
def params
|
|
103
154
|
transform_param(DIRECTION[direction])
|
|
104
155
|
end
|
|
105
156
|
end
|
|
106
157
|
|
|
158
|
+
# Command that stores, deletes, or distributes intermediate positions.
|
|
107
159
|
class SetMotorIP < Message
|
|
160
|
+
# (see Ack::MSG)
|
|
108
161
|
MSG = 0x15
|
|
162
|
+
# (see Ack::PARAMS_LENGTH)
|
|
109
163
|
PARAMS_LENGTH = 4
|
|
110
164
|
# for distribute, value is how many IPs to distribute over
|
|
165
|
+
# Mapping from symbolic write modes to SDN parameter values.
|
|
111
166
|
TYPE = { delete: 0x00,
|
|
112
167
|
current_position: 0x01,
|
|
113
168
|
position_pulses: 0x02,
|
|
114
169
|
position_percent: 0x03,
|
|
115
170
|
distribute: 0x04 }.freeze
|
|
116
171
|
|
|
117
|
-
|
|
118
|
-
|
|
172
|
+
# @!attribute [rw] type
|
|
173
|
+
#
|
|
174
|
+
# Requested intermediate-position write mode.
|
|
175
|
+
#
|
|
176
|
+
# @return [:delete, :current_position, :position_pulses, :position_percent, :distribute]
|
|
177
|
+
attr_reader :type
|
|
178
|
+
|
|
179
|
+
# @!attribute [rw] ip
|
|
180
|
+
#
|
|
181
|
+
# Requested intermediate-position slot in the range 1..16.
|
|
182
|
+
#
|
|
183
|
+
# @return [Integer, nil]
|
|
184
|
+
attr_reader :ip
|
|
185
|
+
|
|
186
|
+
# @!attribute [rw] value
|
|
187
|
+
#
|
|
188
|
+
# @return [Integer, nil]
|
|
189
|
+
attr_reader :value
|
|
190
|
+
|
|
191
|
+
# Creates an intermediate position write command.
|
|
192
|
+
#
|
|
193
|
+
# @param dest [(Integer, Integer, Integer), nil] destination address
|
|
194
|
+
# @param type [Symbol] IP write mode
|
|
195
|
+
# @param ip [Integer, nil] IP slot
|
|
196
|
+
# @param value [Integer, nil] IP value
|
|
197
|
+
# @param kwargs [Hash] additional message options
|
|
119
198
|
def initialize(dest = nil, type = :delete, ip = nil, value = nil, **kwargs)
|
|
120
199
|
kwargs[:dest] ||= dest
|
|
121
200
|
super(**kwargs)
|
|
@@ -124,6 +203,7 @@ module SDN
|
|
|
124
203
|
self.value = value
|
|
125
204
|
end
|
|
126
205
|
|
|
206
|
+
# (see Message#parse)
|
|
127
207
|
def parse(params)
|
|
128
208
|
super
|
|
129
209
|
self.type = TYPE.invert[to_number(params[0])]
|
|
@@ -143,7 +223,7 @@ module SDN
|
|
|
143
223
|
end
|
|
144
224
|
|
|
145
225
|
def ip=(value)
|
|
146
|
-
raise ArgumentError, "ip must be in range 1..16 or nil" unless
|
|
226
|
+
raise ArgumentError, "ip must be in range 1..16 or nil" unless value.nil? || (1..16).cover?(value)
|
|
147
227
|
|
|
148
228
|
@ip = value
|
|
149
229
|
end
|
|
@@ -152,19 +232,45 @@ module SDN
|
|
|
152
232
|
@value = value&.& 0xffff
|
|
153
233
|
end
|
|
154
234
|
|
|
235
|
+
# (see Message#params)
|
|
155
236
|
def params
|
|
156
237
|
transform_param(TYPE[type]) + transform_param(ip || 0) + from_number(value || 0, 2)
|
|
157
238
|
end
|
|
158
239
|
end
|
|
159
240
|
|
|
241
|
+
# Command that modifies a motor's travel limits.
|
|
160
242
|
class SetMotorLimits < Message
|
|
243
|
+
# (see Ack::MSG)
|
|
161
244
|
MSG = 0x11
|
|
245
|
+
# (see Ack::PARAMS_LENGTH)
|
|
162
246
|
PARAMS_LENGTH = 4
|
|
247
|
+
# Mapping from symbolic limit write modes to SDN parameter values.
|
|
163
248
|
TYPE = { delete: 0x00, current_position: 0x01, specified_position: 0x02, jog_ms: 0x04, jog_pulses: 0x05 }.freeze
|
|
249
|
+
# Mapping from symbolic limit targets to SDN parameter values.
|
|
164
250
|
TARGET = { down: 0x00, up: 0x01 }.freeze
|
|
165
251
|
|
|
166
|
-
|
|
167
|
-
|
|
252
|
+
# @!attribute [rw] type
|
|
253
|
+
#
|
|
254
|
+
# @return [:delete, :current_position, :specified_position, :jog_ms, :jog_pulses]
|
|
255
|
+
attr_reader :type
|
|
256
|
+
|
|
257
|
+
# @!attribute [rw] target
|
|
258
|
+
#
|
|
259
|
+
# @return [:up, :down]
|
|
260
|
+
attr_reader :target
|
|
261
|
+
|
|
262
|
+
# @!attribute [rw] value
|
|
263
|
+
#
|
|
264
|
+
# @return [Integer, nil]
|
|
265
|
+
attr_reader :value
|
|
266
|
+
|
|
267
|
+
# Creates a limit write command.
|
|
268
|
+
#
|
|
269
|
+
# @param dest [(Integer, Integer, Integer), nil] destination address
|
|
270
|
+
# @param type [:delete, :current_position, :specified_position, :jog_ms, :jog_pulses] limit write mode
|
|
271
|
+
# @param target [:up, :down] target limit
|
|
272
|
+
# @param value [Integer, nil]
|
|
273
|
+
# @param kwargs [Hash] additional message options
|
|
168
274
|
def initialize(dest = nil, type = :delete, target = :up, value = nil, **kwargs)
|
|
169
275
|
kwargs[:dest] ||= dest
|
|
170
276
|
super(**kwargs)
|
|
@@ -173,6 +279,7 @@ module SDN
|
|
|
173
279
|
self.value = value
|
|
174
280
|
end
|
|
175
281
|
|
|
282
|
+
# (see Message#parse)
|
|
176
283
|
def parse(params)
|
|
177
284
|
super
|
|
178
285
|
self.type = TYPE.invert[to_number(params[0])]
|
|
@@ -199,6 +306,7 @@ module SDN
|
|
|
199
306
|
@value = value&.& 0xffff
|
|
200
307
|
end
|
|
201
308
|
|
|
309
|
+
# (see Message#params)
|
|
202
310
|
def params
|
|
203
311
|
param = value || 0
|
|
204
312
|
param /= 10 if target == :jog_ms
|
|
@@ -206,12 +314,35 @@ module SDN
|
|
|
206
314
|
end
|
|
207
315
|
end
|
|
208
316
|
|
|
317
|
+
# Command that updates a motor's rolling speeds.
|
|
209
318
|
class SetMotorRollingSpeed < Message
|
|
319
|
+
# (see Ack::MSG)
|
|
210
320
|
MSG = 0x13
|
|
321
|
+
# (see Ack::PARAMS_LENGTH)
|
|
211
322
|
PARAMS_LENGTH = 3
|
|
212
323
|
|
|
213
|
-
|
|
214
|
-
|
|
324
|
+
# @!attribute [rw] up_speed
|
|
325
|
+
#
|
|
326
|
+
# @return [Integer, nil]
|
|
327
|
+
attr_accessor :up_speed
|
|
328
|
+
|
|
329
|
+
# @!attribute [rw] down_speed
|
|
330
|
+
#
|
|
331
|
+
# @return [Integer, nil]
|
|
332
|
+
attr_accessor :down_speed
|
|
333
|
+
|
|
334
|
+
# @!attribute [rw] slow_speed
|
|
335
|
+
#
|
|
336
|
+
# @return [Integer, nil]
|
|
337
|
+
attr_accessor :slow_speed
|
|
338
|
+
|
|
339
|
+
# Creates a rolling-speed write command.
|
|
340
|
+
#
|
|
341
|
+
# @param dest [(Integer, Integer, Integer), nil] destination address
|
|
342
|
+
# @param up_speed [Integer, nil] upward speed
|
|
343
|
+
# @param down_speed [Integer, nil] downward speed
|
|
344
|
+
# @param slow_speed [Integer, nil] slow speed
|
|
345
|
+
# @param kwargs [Hash] additional message options
|
|
215
346
|
def initialize(dest = nil, up_speed: nil, down_speed: nil, slow_speed: nil, **kwargs)
|
|
216
347
|
kwargs[:dest] ||= dest
|
|
217
348
|
super(**kwargs)
|
|
@@ -220,6 +351,7 @@ module SDN
|
|
|
220
351
|
self.slow_speed = slow_speed
|
|
221
352
|
end
|
|
222
353
|
|
|
354
|
+
# (see Message#parse)
|
|
223
355
|
def parse(params)
|
|
224
356
|
super
|
|
225
357
|
self.up_speed = to_number(params[0])
|
|
@@ -227,42 +359,68 @@ module SDN
|
|
|
227
359
|
self.slow_speed = to_number(params[2])
|
|
228
360
|
end
|
|
229
361
|
|
|
362
|
+
# (see Message#params)
|
|
230
363
|
def params
|
|
231
364
|
transform_param(up_speed || 0xff) + transform_param(down_speed || 0xff) + transform_param(slow_speed || 0xff)
|
|
232
365
|
end
|
|
233
366
|
end
|
|
234
367
|
|
|
368
|
+
# Command that applies or clears the network lock state.
|
|
235
369
|
class SetNetworkLock < Message
|
|
370
|
+
# (see Ack::MSG)
|
|
236
371
|
MSG = 0x16
|
|
237
372
|
|
|
238
|
-
|
|
373
|
+
# @!attribute [rw] locked
|
|
374
|
+
#
|
|
375
|
+
# @return [true, false, nil]
|
|
376
|
+
attr_accessor :locked
|
|
377
|
+
|
|
378
|
+
# @!attribute [rw] priority
|
|
379
|
+
#
|
|
380
|
+
# @return [Integer, nil]
|
|
381
|
+
attr_accessor :priority
|
|
239
382
|
|
|
383
|
+
# (see Message#parse)
|
|
240
384
|
def parse(params)
|
|
241
385
|
self.locked = to_number(params[0]) == 1
|
|
242
386
|
self.priority = to_number(params[1])
|
|
243
387
|
end
|
|
244
388
|
|
|
389
|
+
# (see Message#params)
|
|
245
390
|
def params
|
|
246
391
|
transform_param(locked ? 1 : 0) + transform_param(priority)
|
|
247
392
|
end
|
|
248
393
|
end
|
|
249
394
|
|
|
395
|
+
# Command that writes a node's user-visible label.
|
|
250
396
|
class SetNodeLabel < Message
|
|
397
|
+
# (see Ack::MSG)
|
|
251
398
|
MSG = 0x55
|
|
399
|
+
# Maximum label length, in bytes
|
|
252
400
|
MAX_LENGTH = 16
|
|
253
401
|
|
|
402
|
+
# @!attribute [rw] label
|
|
403
|
+
#
|
|
404
|
+
# @return [String]
|
|
254
405
|
attr_accessor :label
|
|
255
406
|
|
|
407
|
+
# Creates a node label write command.
|
|
408
|
+
#
|
|
409
|
+
# @param dest [(Integer, Integer, Integer), nil] destination address
|
|
410
|
+
# @param label [String]
|
|
411
|
+
# @param kwargs [Hash] additional message options
|
|
256
412
|
def initialize(dest = nil, label = "", **kwargs)
|
|
257
413
|
kwargs[:dest] ||= dest
|
|
258
414
|
super(**kwargs)
|
|
259
415
|
self.label = label
|
|
260
416
|
end
|
|
261
417
|
|
|
418
|
+
# (see Message#parse)
|
|
262
419
|
def parse(params)
|
|
263
420
|
self.label = to_string(params)
|
|
264
421
|
end
|
|
265
422
|
|
|
423
|
+
# (see Message#params)
|
|
266
424
|
def params
|
|
267
425
|
from_string(label, self.class::MAX_LENGTH)
|
|
268
426
|
end
|
data/lib/sdn/message.rb
CHANGED
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
require "sdn/message/helpers"
|
|
4
4
|
|
|
5
5
|
module SDN
|
|
6
|
+
# Raised when a parsed frame is structurally valid but its payload does not match the target message type.
|
|
6
7
|
class MalformedMessage < RuntimeError; end
|
|
7
8
|
|
|
9
|
+
# Base type for all SDN protocol frames and message-specific serializers/parsers.
|
|
8
10
|
class Message
|
|
9
11
|
class << self
|
|
12
|
+
# @!visibility private
|
|
10
13
|
def inherited(klass)
|
|
11
14
|
super
|
|
12
15
|
return Message.inherited(klass) unless self == Message
|
|
@@ -15,6 +18,11 @@ module SDN
|
|
|
15
18
|
(@subclasses ||= []) << klass
|
|
16
19
|
end
|
|
17
20
|
|
|
21
|
+
# Determines whether a received message satisfies the expected response for this request type.
|
|
22
|
+
#
|
|
23
|
+
# @param message [SDN::Message] received message candidate
|
|
24
|
+
#
|
|
25
|
+
# @return [true, false]
|
|
18
26
|
def expected_response?(message)
|
|
19
27
|
if /::Get([A-Za-z]+)/.match?(name)
|
|
20
28
|
message.class.name == name.sub("::Get", "::Post") # rubocop:disable Style/ClassEqualityComparison
|
|
@@ -23,6 +31,11 @@ module SDN
|
|
|
23
31
|
end
|
|
24
32
|
end
|
|
25
33
|
|
|
34
|
+
# Attempts to parse the first valid SDN message from a byte stream.
|
|
35
|
+
#
|
|
36
|
+
# @param data [<Integer>] raw bytes buffered from the transport
|
|
37
|
+
#
|
|
38
|
+
# @return [(SDN::Message, Integer)] parsed message and consumed byte count
|
|
26
39
|
def parse(data)
|
|
27
40
|
offset = -1
|
|
28
41
|
msg = length = ack_requested = message_class = nil
|
|
@@ -58,7 +71,7 @@ module SDN
|
|
|
58
71
|
src = transform_param(data.slice(offset + 3, 3))
|
|
59
72
|
dest = transform_param(data.slice(offset + 6, 3))
|
|
60
73
|
begin
|
|
61
|
-
result = message_class.new(node_type
|
|
74
|
+
result = message_class.new(node_type:, ack_requested:, src:, dest:)
|
|
62
75
|
result.parse(data.slice(offset + 9, length - 11))
|
|
63
76
|
result.msg = msg if message_class == UnknownMessage
|
|
64
77
|
rescue ArgumentError => e
|
|
@@ -70,10 +83,13 @@ module SDN
|
|
|
70
83
|
|
|
71
84
|
private
|
|
72
85
|
|
|
86
|
+
# Builds a lookup table from protocol opcode to message class.
|
|
87
|
+
#
|
|
88
|
+
# @return [{Integer => Class}]
|
|
73
89
|
def message_map
|
|
74
90
|
@message_map ||=
|
|
75
91
|
@subclasses.each_with_object({}) do |klass, memo|
|
|
76
|
-
next memo unless klass.
|
|
92
|
+
next memo unless klass.const_defined?(:MSG, false)
|
|
77
93
|
|
|
78
94
|
memo[klass.const_get(:MSG, false)] = klass
|
|
79
95
|
end
|
|
@@ -83,8 +99,32 @@ module SDN
|
|
|
83
99
|
include Helpers
|
|
84
100
|
singleton_class.include Helpers
|
|
85
101
|
|
|
86
|
-
|
|
87
|
-
|
|
102
|
+
# Message sender node type.
|
|
103
|
+
#
|
|
104
|
+
# @return [Symbol, Integer]
|
|
105
|
+
attr_accessor :node_type
|
|
106
|
+
|
|
107
|
+
# Whether this message requests an acknowledgement.
|
|
108
|
+
#
|
|
109
|
+
# @return [true, false]
|
|
110
|
+
attr_accessor :ack_requested
|
|
111
|
+
|
|
112
|
+
# Source address for the message.
|
|
113
|
+
#
|
|
114
|
+
# @return [(Integer, Integer, Integer)]
|
|
115
|
+
attr_accessor :src
|
|
116
|
+
|
|
117
|
+
# Destination address for the message.
|
|
118
|
+
#
|
|
119
|
+
# @return [(Integer, Integer, Integer)]
|
|
120
|
+
attr_accessor :dest
|
|
121
|
+
|
|
122
|
+
# Builds a message envelope with optional addressing and acknowledgement behavior.
|
|
123
|
+
#
|
|
124
|
+
# @param node_type [Symbol, Integer, nil] originating node type
|
|
125
|
+
# @param ack_requested [true, false] whether the receiver should acknowledge the message
|
|
126
|
+
# @param src [(Integer, Integer, Integer), nil] source address bytes
|
|
127
|
+
# @param dest [(Integer, Integer, Integer), nil] destination address bytes
|
|
88
128
|
def initialize(node_type: nil, ack_requested: false, src: nil, dest: nil)
|
|
89
129
|
@node_type = node_type || 0
|
|
90
130
|
@ack_requested = ack_requested
|
|
@@ -96,6 +136,13 @@ module SDN
|
|
|
96
136
|
@dest = dest || [0, 0, 0]
|
|
97
137
|
end
|
|
98
138
|
|
|
139
|
+
# Parses protocol-specific parameter bytes into this message instance.
|
|
140
|
+
#
|
|
141
|
+
# @param params [<Integer>] raw parameter bytes
|
|
142
|
+
#
|
|
143
|
+
# @return [void]
|
|
144
|
+
#
|
|
145
|
+
# @raise [SDN::MalformedMessage] when the parameter payload length is invalid
|
|
99
146
|
def parse(params)
|
|
100
147
|
return unless self.class.const_defined?(:PARAMS_LENGTH) && params.length != self.class.const_get(:PARAMS_LENGTH)
|
|
101
148
|
|
|
@@ -104,6 +151,9 @@ module SDN
|
|
|
104
151
|
end}"
|
|
105
152
|
end
|
|
106
153
|
|
|
154
|
+
# Serializes the message to a packed SDN frame.
|
|
155
|
+
#
|
|
156
|
+
# @return [String] binary protocol frame
|
|
107
157
|
def serialize
|
|
108
158
|
result = transform_param(node_type_to_number(node_type)) + transform_param(src) + transform_param(dest) + params
|
|
109
159
|
length = result.length + 4
|
|
@@ -113,10 +163,12 @@ module SDN
|
|
|
113
163
|
result.pack("C*")
|
|
114
164
|
end
|
|
115
165
|
|
|
166
|
+
# @return [true, false]
|
|
116
167
|
def ==(other)
|
|
117
168
|
serialize == other.serialize
|
|
118
169
|
end
|
|
119
170
|
|
|
171
|
+
# @return [String]
|
|
120
172
|
def inspect
|
|
121
173
|
format("#<%s @node_type=%s, @ack_requested=%s, @src=%s, @dest=%s%s>",
|
|
122
174
|
self.class.name,
|
|
@@ -128,6 +180,11 @@ module SDN
|
|
|
128
180
|
end
|
|
129
181
|
alias_method :to_s, :inspect
|
|
130
182
|
|
|
183
|
+
private
|
|
184
|
+
|
|
185
|
+
# Returns the protocol-specific portion of the inspection string.
|
|
186
|
+
#
|
|
187
|
+
# @return [String, nil]
|
|
131
188
|
def class_inspect
|
|
132
189
|
ivars = instance_variables - %i[@node_type @ack_requested @src @dest @params]
|
|
133
190
|
return if ivars.empty?
|
|
@@ -137,22 +194,43 @@ module SDN
|
|
|
137
194
|
|
|
138
195
|
protected
|
|
139
196
|
|
|
197
|
+
# Returns the protocol-specific payload bytes for this message.
|
|
198
|
+
#
|
|
199
|
+
# @return [<Integer>]
|
|
140
200
|
def params
|
|
141
201
|
[]
|
|
142
202
|
end
|
|
143
203
|
|
|
204
|
+
# Base class for request messages with no protocol-specific parameters.
|
|
144
205
|
class SimpleRequest < Message
|
|
206
|
+
# Number of protocol parameter bytes expected
|
|
145
207
|
PARAMS_LENGTH = 0
|
|
146
208
|
|
|
209
|
+
# Creates a simple request whose only payload is the destination address.
|
|
210
|
+
#
|
|
211
|
+
# @param dest [(Integer, Integer, Integer), nil] destination address
|
|
212
|
+
# @param kwargs [Hash] additional message options
|
|
147
213
|
def initialize(dest = nil, **kwargs)
|
|
148
214
|
kwargs[:dest] ||= dest
|
|
149
215
|
super(**kwargs)
|
|
150
216
|
end
|
|
151
217
|
end
|
|
152
218
|
|
|
219
|
+
# Positive acknowledgement response for commands that requested one.
|
|
220
|
+
class Ack < SimpleRequest
|
|
221
|
+
# Protocol message number
|
|
222
|
+
MSG = 0x7f
|
|
223
|
+
# (see SimpleRequest::PARAMS_LENGTH)
|
|
224
|
+
PARAMS_LENGTH = 0
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Negative acknowledgement response emitted when a request cannot be fulfilled.
|
|
153
228
|
class Nack < Message
|
|
229
|
+
# (see Ack::MSG)
|
|
154
230
|
MSG = 0x6f
|
|
231
|
+
# (see Ack::PARAMS_LENGTH)
|
|
155
232
|
PARAMS_LENGTH = 1
|
|
233
|
+
# Mapping from symbolic error names to SDN error codes.
|
|
156
234
|
VALUES = { data_error: 0x01,
|
|
157
235
|
unknown_message: 0x10,
|
|
158
236
|
node_is_locked: 0x20,
|
|
@@ -165,38 +243,66 @@ module SDN
|
|
|
165
243
|
# 37 not implemented? (get motor rolling speed)
|
|
166
244
|
# 39 at limit? blocked?
|
|
167
245
|
|
|
168
|
-
#
|
|
246
|
+
# Parsed NACK error code as a symbolic name or raw integer.
|
|
247
|
+
#
|
|
248
|
+
# @return [Symbol, Integer, nil]
|
|
169
249
|
attr_accessor :error_code
|
|
170
250
|
|
|
251
|
+
# Creates a negative acknowledgement message.
|
|
252
|
+
#
|
|
253
|
+
# @param dest [(Integer, Integer, Integer), nil] destination address
|
|
254
|
+
# @param error_code [Symbol, Integer, nil] protocol error code
|
|
255
|
+
# @param kwargs [Hash] additional message options
|
|
171
256
|
def initialize(dest = nil, error_code = nil, **kwargs)
|
|
172
257
|
kwargs[:dest] ||= dest
|
|
173
258
|
super(**kwargs)
|
|
174
259
|
self.error_code = error_code
|
|
175
260
|
end
|
|
176
261
|
|
|
262
|
+
# Parses the negative acknowledgement error code.
|
|
263
|
+
#
|
|
264
|
+
# @param params [<Integer>] raw parameter bytes
|
|
177
265
|
def parse(params)
|
|
178
266
|
super
|
|
179
267
|
error_code = to_number(params[0])
|
|
180
268
|
self.error_code = VALUES.invert[error_code] || error_code
|
|
181
269
|
end
|
|
182
|
-
end
|
|
183
270
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
271
|
+
# (see Message#params)
|
|
272
|
+
def params
|
|
273
|
+
transform_param(VALUES[error_code] || error_code)
|
|
274
|
+
end
|
|
187
275
|
end
|
|
188
276
|
|
|
189
|
-
#
|
|
277
|
+
# Fallback wrapper for frames whose opcode is unknown to the library.
|
|
278
|
+
#
|
|
279
|
+
# Messages after this point were decoded from UAI+ communication and may be named wrong.
|
|
190
280
|
class UnknownMessage < Message
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
281
|
+
# Unknown protocol opcode captured for this message.
|
|
282
|
+
#
|
|
283
|
+
# @return [Integer, nil]
|
|
284
|
+
attr_accessor :msg
|
|
285
|
+
|
|
286
|
+
# Raw undecoded parameter bytes for this message.
|
|
287
|
+
#
|
|
288
|
+
# @return [<Integer>, nil]
|
|
289
|
+
attr_accessor :params
|
|
290
|
+
|
|
291
|
+
# Creates a message wrapper for an unrecognized protocol opcode.
|
|
292
|
+
#
|
|
293
|
+
# @param params [<Integer>] raw unparsed parameter bytes
|
|
294
|
+
def initialize(params = [], **)
|
|
295
|
+
super(**)
|
|
195
296
|
self.params = params
|
|
196
297
|
end
|
|
197
298
|
|
|
198
299
|
alias_method :parse, :params=
|
|
199
300
|
|
|
301
|
+
# Serializes an unknown message only when raw parameters are present.
|
|
302
|
+
#
|
|
303
|
+
# @return [String] binary protocol frame
|
|
304
|
+
#
|
|
305
|
+
# @raise [NotImplementedError] when parameters are unavailable
|
|
200
306
|
def serialize
|
|
201
307
|
# prevent serializing something we don't know
|
|
202
308
|
raise NotImplementedError unless params
|
|
@@ -204,6 +310,7 @@ module SDN
|
|
|
204
310
|
super
|
|
205
311
|
end
|
|
206
312
|
|
|
313
|
+
# (see Message#class_inspect)
|
|
207
314
|
def class_inspect
|
|
208
315
|
result = if instance_of?(UnknownMessage)
|
|
209
316
|
format(", @msg=%02xh", msg)
|
data/lib/sdn/version.rb
CHANGED
data/lib/sdn.rb
CHANGED
|
@@ -5,18 +5,26 @@ require "logger"
|
|
|
5
5
|
require "sdn/client"
|
|
6
6
|
require "sdn/message"
|
|
7
7
|
|
|
8
|
+
# Top-level Somfy SDN module
|
|
8
9
|
module SDN
|
|
10
|
+
# Broadcast destination address used for discovery and network-wide requests.
|
|
9
11
|
BROADCAST_ADDRESS = [0xff, 0xff, 0xff].freeze
|
|
10
12
|
|
|
11
13
|
class << self
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
# @!attribute [rw] logger
|
|
15
|
+
#
|
|
16
|
+
# Sets a logger for use by this gem. If a logger is not set, a default logger that
|
|
17
|
+
# outputs to stdout with INFO level will be used.
|
|
18
|
+
#
|
|
19
|
+
# @return [Logger]
|
|
20
|
+
def logger=(value)
|
|
21
|
+
value.datetime_format = "%Y-%m-%d %H:%M:%S.%L"
|
|
22
|
+
value.formatter = proc do |severity, datetime, _progname, msg|
|
|
23
|
+
"#{datetime.strftime(value.datetime_format)} " \
|
|
16
24
|
"[#{Process.pid}/#{Thread.current.object_id}] " \
|
|
17
25
|
"#{severity}: #{msg}\n"
|
|
18
26
|
end
|
|
19
|
-
@logger =
|
|
27
|
+
@logger = value
|
|
20
28
|
end
|
|
21
29
|
|
|
22
30
|
def logger
|