somfy_sdn 2.1.5 → 2.2.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 +69 -0
- data/lib/sdn/cli/mqtt/group.rb +12 -10
- data/lib/sdn/cli/mqtt/motor.rb +19 -14
- data/lib/sdn/cli/mqtt/p_queue.rb +18 -0
- data/lib/sdn/cli/mqtt/read.rb +125 -126
- data/lib/sdn/cli/mqtt/subscriptions.rb +186 -140
- data/lib/sdn/cli/mqtt/write.rb +39 -34
- data/lib/sdn/cli/mqtt.rb +84 -53
- data/lib/sdn/cli/provisioner.rb +56 -33
- data/lib/sdn/cli/simulator.rb +99 -65
- data/lib/sdn/client.rb +38 -24
- data/lib/sdn/message/control.rb +60 -30
- data/lib/sdn/message/get.rb +6 -2
- data/lib/sdn/message/helpers.rb +23 -22
- data/lib/sdn/message/ilt2/get.rb +6 -3
- data/lib/sdn/message/ilt2/master_control.rb +10 -7
- data/lib/sdn/message/ilt2/post.rb +7 -5
- data/lib/sdn/message/ilt2/set.rb +28 -19
- data/lib/sdn/message/post.rb +3 -5
- data/lib/sdn/message/set.rb +48 -22
- data/lib/sdn/message.rb +50 -34
- data/lib/sdn/version.rb +3 -1
- data/lib/sdn.rb +18 -12
- data/lib/somfy_sdn.rb +3 -1
- metadata +43 -13
- data/bin/somfy_sdn +0 -60
data/lib/sdn/cli/mqtt.rb
CHANGED
@@ -1,12 +1,15 @@
|
|
1
|
-
|
2
|
-
require 'uri'
|
3
|
-
require 'set'
|
1
|
+
# frozen_string_literal: true
|
4
2
|
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
|
9
|
-
require
|
3
|
+
require "mqtt"
|
4
|
+
require "uri"
|
5
|
+
require "set"
|
6
|
+
|
7
|
+
require "sdn/cli/mqtt/group"
|
8
|
+
require "sdn/cli/mqtt/motor"
|
9
|
+
require "sdn/cli/mqtt/p_queue"
|
10
|
+
require "sdn/cli/mqtt/read"
|
11
|
+
require "sdn/cli/mqtt/write"
|
12
|
+
require "sdn/cli/mqtt/subscriptions"
|
10
13
|
|
11
14
|
module SDN
|
12
15
|
module CLI
|
@@ -22,7 +25,12 @@ module SDN
|
|
22
25
|
|
23
26
|
attr_reader :motors, :groups
|
24
27
|
|
25
|
-
def initialize(
|
28
|
+
def initialize(sdn,
|
29
|
+
mqtt_uri,
|
30
|
+
device_id: "somfy",
|
31
|
+
base_topic: "homie",
|
32
|
+
auto_discover: true,
|
33
|
+
known_motors: [])
|
26
34
|
@base_topic = "#{base_topic}/#{device_id}"
|
27
35
|
@mqtt = ::MQTT::Client.new(mqtt_uri)
|
28
36
|
@mqtt.set_will("#{@base_topic}/$state", "lost", retain: true)
|
@@ -33,7 +41,7 @@ module SDN
|
|
33
41
|
|
34
42
|
@mutex = Mutex.new
|
35
43
|
@cond = ConditionVariable.new
|
36
|
-
@
|
44
|
+
@queue = PQueue.new
|
37
45
|
@response_pending = false
|
38
46
|
@broadcast_pending = false
|
39
47
|
|
@@ -43,11 +51,16 @@ module SDN
|
|
43
51
|
clear_tree(@base_topic)
|
44
52
|
publish_basic_attributes
|
45
53
|
|
46
|
-
@sdn =
|
54
|
+
@sdn = sdn
|
55
|
+
|
56
|
+
known_motors&.each do |addr|
|
57
|
+
addr = Message.parse_address(addr)
|
58
|
+
@queue.push(MessageAndRetries.new(Message::GetMotorPosition.new(addr), 5, 0))
|
59
|
+
end
|
47
60
|
|
48
61
|
Thread.abort_on_exception = true
|
49
|
-
|
50
|
-
|
62
|
+
Thread.new { read }
|
63
|
+
Thread.new { write }
|
51
64
|
@mqtt.get { |packet| handle_message(packet.topic, packet.payload) }
|
52
65
|
end
|
53
66
|
|
@@ -59,20 +72,19 @@ module SDN
|
|
59
72
|
@mqtt.subscribe("#{@base_topic}/#{topic}")
|
60
73
|
end
|
61
74
|
|
62
|
-
def enqueue(message
|
75
|
+
def enqueue(message)
|
63
76
|
@mutex.synchronize do
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
end
|
77
|
+
break if @queue.include?(message)
|
78
|
+
|
79
|
+
@queue.push(message)
|
80
|
+
@cond.signal
|
69
81
|
end
|
70
82
|
end
|
71
83
|
|
72
84
|
def clear_tree(topic)
|
73
85
|
@mqtt.subscribe("#{topic}/#")
|
74
86
|
@mqtt.unsubscribe("#{topic}/#", wait_for_ack: true)
|
75
|
-
|
87
|
+
until @mqtt.queue_empty?
|
76
88
|
packet = @mqtt.get
|
77
89
|
@mqtt.publish(packet.topic, nil, retain: true)
|
78
90
|
end
|
@@ -136,7 +148,7 @@ module SDN
|
|
136
148
|
@mqtt.batch_publish do
|
137
149
|
publish("#{addr}/$name", addr)
|
138
150
|
publish("#{addr}/$type", node_type.to_s)
|
139
|
-
properties = %w
|
151
|
+
properties = %w[
|
140
152
|
discover
|
141
153
|
label
|
142
154
|
state
|
@@ -149,19 +161,17 @@ module SDN
|
|
149
161
|
down-limit
|
150
162
|
groups
|
151
163
|
last-direction
|
152
|
-
|
164
|
+
] + (1..16).map { |ip| ["ip#{ip}-pulses", "ip#{ip}-percent"] }.flatten
|
153
165
|
|
154
166
|
unless node_type == :st50ilt2
|
155
|
-
properties.
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
slow-speed
|
164
|
-
}
|
167
|
+
properties.push("reset",
|
168
|
+
"last-action-source",
|
169
|
+
"last-action-cause",
|
170
|
+
"up-limit",
|
171
|
+
"direction",
|
172
|
+
"up-speed",
|
173
|
+
"down-speed",
|
174
|
+
"slow-speed")
|
165
175
|
end
|
166
176
|
|
167
177
|
publish("#{addr}/$properties", properties.join(","))
|
@@ -178,7 +188,7 @@ module SDN
|
|
178
188
|
|
179
189
|
publish("#{addr}/state/$name", "Current state of the motor")
|
180
190
|
publish("#{addr}/state/$datatype", "enum")
|
181
|
-
publish("#{addr}/state/$format", Message::PostMotorStatus::STATE.keys.join(
|
191
|
+
publish("#{addr}/state/$format", Message::PostMotorStatus::STATE.keys.join(","))
|
182
192
|
|
183
193
|
publish("#{addr}/control/$name", "Control motor")
|
184
194
|
publish("#{addr}/control/$datatype", "enum")
|
@@ -226,22 +236,22 @@ module SDN
|
|
226
236
|
|
227
237
|
publish("#{addr}/last-direction/$name", "Direction of last motion")
|
228
238
|
publish("#{addr}/last-direction/$datatype", "enum")
|
229
|
-
publish("#{addr}/last-direction/$format", Message::PostMotorStatus::DIRECTION.keys.join(
|
239
|
+
publish("#{addr}/last-direction/$format", Message::PostMotorStatus::DIRECTION.keys.join(","))
|
230
240
|
|
231
241
|
unless node_type == :st50ilt2
|
232
242
|
publish("#{addr}/reset/$name", "Recall factory settings")
|
233
243
|
publish("#{addr}/reset/$datatype", "enum")
|
234
|
-
publish("#{addr}/reset/$format", Message::SetFactoryDefault::RESET.keys.join(
|
244
|
+
publish("#{addr}/reset/$format", Message::SetFactoryDefault::RESET.keys.join(","))
|
235
245
|
publish("#{addr}/reset/$settable", "true")
|
236
246
|
publish("#{addr}/reset/$retained", "false")
|
237
247
|
|
238
248
|
publish("#{addr}/last-action-source/$name", "Source of last action")
|
239
249
|
publish("#{addr}/last-action-source/$datatype", "enum")
|
240
|
-
publish("#{addr}/last-action-source/$format", Message::PostMotorStatus::SOURCE.keys.join(
|
250
|
+
publish("#{addr}/last-action-source/$format", Message::PostMotorStatus::SOURCE.keys.join(","))
|
241
251
|
|
242
252
|
publish("#{addr}/last-action-cause/$name", "Cause of last action")
|
243
253
|
publish("#{addr}/last-action-cause/$datatype", "enum")
|
244
|
-
publish("#{addr}/last-action-cause/$format", Message::PostMotorStatus::CAUSE.keys.join(
|
254
|
+
publish("#{addr}/last-action-cause/$format", Message::PostMotorStatus::CAUSE.keys.join(","))
|
245
255
|
|
246
256
|
publish("#{addr}/up-limit/$name", "Up limit (always = 0)")
|
247
257
|
publish("#{addr}/up-limit/$datatype", "integer")
|
@@ -298,20 +308,39 @@ module SDN
|
|
298
308
|
|
299
309
|
sdn_addr = Message.parse_address(addr)
|
300
310
|
@mutex.synchronize do
|
301
|
-
|
311
|
+
# message priorities are:
|
312
|
+
# 0 - control
|
313
|
+
# 1 - follow-up (i.e. get position after control)
|
314
|
+
# 2 - get basic motor info
|
315
|
+
# 3 - get advanced motor info
|
316
|
+
# 4 - get group 1
|
317
|
+
# 5 - get ip 1
|
318
|
+
# 6 - get group 2
|
319
|
+
# 7 - get ip 2
|
320
|
+
# ...
|
321
|
+
# 50 - discover
|
322
|
+
|
323
|
+
# The Group and IP sorting makes it so you quickly get the most commonly used group
|
324
|
+
# and IP addresses, while the almost-never-used ones are pushed to the bottom of the list
|
325
|
+
|
326
|
+
@queue.push(MessageAndRetries.new(Message::GetNodeLabel.new(sdn_addr), 5, 2))
|
327
|
+
|
302
328
|
case node_type
|
303
|
-
when :st30
|
304
|
-
@
|
305
|
-
@
|
306
|
-
@
|
307
|
-
@
|
308
|
-
(1..16).each { |ip|
|
329
|
+
when :st30, 0x20 # no idea why 0x20, but that's what I get
|
330
|
+
@queue.push(MessageAndRetries.new(Message::GetMotorStatus.new(sdn_addr), 5, 2))
|
331
|
+
@queue.push(MessageAndRetries.new(Message::GetMotorLimits.new(sdn_addr), 5, 2))
|
332
|
+
@queue.push(MessageAndRetries.new(Message::GetMotorDirection.new(sdn_addr), 5, 3))
|
333
|
+
@queue.push(MessageAndRetries.new(Message::GetMotorRollingSpeed.new(sdn_addr), 5, 3))
|
334
|
+
(1..16).each { |ip|
|
335
|
+
@queue.push(MessageAndRetries.new(Message::GetMotorIP.new(sdn_addr, ip), 5, 2 * ip + 3)) }
|
309
336
|
when :st50ilt2
|
310
|
-
@
|
311
|
-
@
|
312
|
-
(1..16).each
|
337
|
+
@queue.push(MessageAndRetries.new(Message::ILT2::GetMotorSettings.new(sdn_addr), 5, 2))
|
338
|
+
@queue.push(MessageAndRetries.new(Message::ILT2::GetMotorPosition.new(sdn_addr), 5, 2))
|
339
|
+
(1..16).each do |ip|
|
340
|
+
@queue.push(MessageAndRetries.new(Message::ILT2::GetMotorIP.new(sdn_addr, ip), 5, 2 * ip + 3))
|
341
|
+
end
|
313
342
|
end
|
314
|
-
(1..16).each { |g| @
|
343
|
+
(1..16).each { |g| @queue.push(MessageAndRetries.new(Message::GetGroupAddr.new(sdn_addr, g), 5, 2 * g + 2)) }
|
315
344
|
|
316
345
|
@cond.signal
|
317
346
|
end
|
@@ -320,19 +349,21 @@ module SDN
|
|
320
349
|
end
|
321
350
|
|
322
351
|
def touch_group(group_addr)
|
323
|
-
group = @groups[Message.print_address(group_addr).
|
352
|
+
group = @groups[Message.print_address(group_addr).delete(".")]
|
324
353
|
group&.publish(:motors, group.motors_string)
|
325
354
|
end
|
326
355
|
|
327
356
|
def add_group(addr)
|
328
|
-
addr = addr.
|
357
|
+
addr = addr.delete(".")
|
329
358
|
group = @groups[addr]
|
330
359
|
return group if group
|
331
360
|
|
332
361
|
@mqtt.batch_publish do
|
333
362
|
publish("#{addr}/$name", addr)
|
334
363
|
publish("#{addr}/$type", "Shade Group")
|
335
|
-
publish("#{addr}/$properties",
|
364
|
+
publish("#{addr}/$properties",
|
365
|
+
"discover,control,jog-ms,jog-pulses,position-pulses,position-percent," \
|
366
|
+
"ip,reset,state,last-direction,motors")
|
336
367
|
|
337
368
|
publish("#{addr}/discover/$name", "Trigger Motor Discovery")
|
338
369
|
publish("#{addr}/discover/$datatype", "enum")
|
@@ -379,11 +410,11 @@ module SDN
|
|
379
410
|
|
380
411
|
publish("#{addr}/state/$name", "State of the motors")
|
381
412
|
publish("#{addr}/state/$datatype", "enum")
|
382
|
-
publish("#{addr}/state/$format", Message::PostMotorStatus::STATE.keys.join(
|
413
|
+
publish("#{addr}/state/$format", "#{Message::PostMotorStatus::STATE.keys.join(",")},mixed")
|
383
414
|
|
384
415
|
publish("#{addr}/last-direction/$name", "Direction of last motion")
|
385
416
|
publish("#{addr}/last-direction/$datatype", "enum")
|
386
|
-
publish("#{addr}/last-direction/$format", Message::PostMotorStatus::DIRECTION.keys.join(
|
417
|
+
publish("#{addr}/last-direction/$format", "#{Message::PostMotorStatus::DIRECTION.keys.join(",")},mixed")
|
387
418
|
|
388
419
|
publish("#{addr}/motors/$name", "Comma separated motor addresses that are members of this group")
|
389
420
|
publish("#{addr}/motors/$datatype", "string")
|
data/lib/sdn/cli/provisioner.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "curses"
|
2
4
|
|
3
5
|
module SDN
|
4
6
|
module CLI
|
5
7
|
class Provisioner
|
6
8
|
attr_reader :win, :sdn, :addr, :ns
|
7
9
|
|
8
|
-
def initialize(
|
9
|
-
@sdn =
|
10
|
+
def initialize(sdn, addr = nil)
|
11
|
+
@sdn = sdn
|
10
12
|
@reversed = false
|
11
13
|
@pulse_count = 10
|
12
14
|
|
@@ -24,10 +26,11 @@ module SDN
|
|
24
26
|
message = sdn.ensure(Message::GetNodeLabel.new(addr))
|
25
27
|
|
26
28
|
node_type = message.node_type
|
27
|
-
@ns = ns = node_type == :st50ilt2 ? Message::ILT2 : Message
|
29
|
+
@ns = ns = (node_type == :st50ilt2) ? Message::ILT2 : Message
|
28
30
|
|
29
|
-
print "Motor is currently labeled '#{message.label}';
|
30
|
-
|
31
|
+
print "Motor is currently labeled '#{message.label}'; " \
|
32
|
+
"what would you like to change it to (blank to leave alone)? "
|
33
|
+
new_label = $stdin.gets
|
31
34
|
|
32
35
|
unless new_label == "\n"
|
33
36
|
new_label.strip!
|
@@ -52,8 +55,10 @@ module SDN
|
|
52
55
|
Curses.nonl
|
53
56
|
Curses.curs_set(0)
|
54
57
|
@win = Curses.stdscr
|
55
|
-
|
58
|
+
|
56
59
|
process
|
60
|
+
rescue Interrupt
|
61
|
+
# exitting
|
57
62
|
rescue => e
|
58
63
|
win.setpos(0, 0)
|
59
64
|
win.addstr(e.inspect)
|
@@ -94,33 +99,44 @@ module SDN
|
|
94
99
|
wait_for_stop
|
95
100
|
when Curses::Key::LEFT
|
96
101
|
if @pos < @pulse_count
|
97
|
-
sdn.ensure(Message::ILT2::SetMotorSettings.new(addr,
|
102
|
+
sdn.ensure(Message::ILT2::SetMotorSettings.new(addr,
|
103
|
+
reversed_int,
|
104
|
+
@limit + @pulse_count - @pos,
|
105
|
+
@pulse_count))
|
98
106
|
refresh
|
99
107
|
end
|
100
|
-
|
108
|
+
if ilt2?
|
109
|
+
sdn.ensure(Message::ILT2::SetMotorPosition.new(addr, :jog_up_pulses, @pulse_count))
|
110
|
+
else
|
111
|
+
sdn.ensure(Message::MoveOf.new(addr, :jog_up_pulses, @pulse_count))
|
112
|
+
end
|
101
113
|
wait_for_stop
|
102
114
|
when Curses::Key::RIGHT
|
103
115
|
if @limit - @pos < @pulse_count
|
104
116
|
sdn.ensure(Message::ILT2::SetMotorSettings.new(addr, reversed_int, @pos + @pulse_count, @pos))
|
105
117
|
refresh
|
106
118
|
end
|
107
|
-
|
119
|
+
if ilt2?
|
120
|
+
sdn.ensure(Message::ILT2::SetMotorPosition.new(addr, :jog_down_pulses, @pulse_count))
|
121
|
+
else
|
122
|
+
sdn.ensure(Message::MoveOf.new(addr, :jog_down_pulses, @pulse_count))
|
123
|
+
end
|
108
124
|
wait_for_stop
|
109
|
-
when
|
125
|
+
when "u"
|
110
126
|
if ilt2?
|
111
127
|
sdn.ensure(Message::ILT2::SetMotorSettings.new(addr, reversed_int, @limit - @pos, 0))
|
112
128
|
else
|
113
129
|
sdn.ensure(Message::SetMotorLimits.new(addr, :current_position, :up))
|
114
130
|
end
|
115
131
|
refresh
|
116
|
-
when
|
132
|
+
when "l"
|
117
133
|
if ilt2?
|
118
134
|
sdn.ensure(Message::ILT2::SetMotorSettings.new(addr, reversed_int, @pos, @pos))
|
119
135
|
else
|
120
136
|
sdn.ensure(Message::SetMotorLimits.new(addr, :current_position, :down))
|
121
137
|
end
|
122
138
|
refresh
|
123
|
-
when
|
139
|
+
when "r"
|
124
140
|
@reversed = !@reversed
|
125
141
|
if ilt2?
|
126
142
|
sdn.ensure(Message::ILT2::SetMotorSettings.new(addr, reversed_int, @limit, @limit - @pos))
|
@@ -128,18 +144,24 @@ module SDN
|
|
128
144
|
sdn.ensure(Message::SetMotorDirection.new(addr, @reversed ? :reversed : :standard))
|
129
145
|
end
|
130
146
|
refresh
|
131
|
-
when
|
147
|
+
when "R"
|
132
148
|
next unless ilt2?
|
149
|
+
|
133
150
|
@reversed = !@reversed
|
134
151
|
sdn.ensure(Message::ILT2::SetMotorSettings.new(addr, reversed_int, @limit, @pos))
|
135
152
|
refresh
|
136
|
-
when
|
153
|
+
when "!"
|
154
|
+
next if ilt2?
|
155
|
+
|
156
|
+
sdn.send(Message::SetFactoryDefault.new(addr))
|
157
|
+
break
|
158
|
+
when "<"
|
137
159
|
@pulse_count /= 2 if @pulse_count > 5
|
138
160
|
print_help
|
139
|
-
when
|
161
|
+
when ">"
|
140
162
|
@pulse_count *= 2
|
141
163
|
print_help
|
142
|
-
when
|
164
|
+
when "q"
|
143
165
|
break
|
144
166
|
end
|
145
167
|
end
|
@@ -147,27 +169,29 @@ module SDN
|
|
147
169
|
|
148
170
|
def print_help
|
149
171
|
win.setpos(0, 0)
|
150
|
-
win.addstr(
|
151
|
-
Move the motor. Keys:
|
152
|
-
Esc stop movement
|
153
|
-
\u2191 go to upper limit
|
154
|
-
\u2193 go to lower limit
|
155
|
-
\u2190 jog up #{@pulse_count} pulses
|
156
|
-
\u2192 jog down #{@pulse_count} pulses
|
157
|
-
> increase jog size
|
158
|
-
< decrease jog size
|
159
|
-
u set upper limit at current position
|
160
|
-
l set lower limit at current position
|
161
|
-
r reverse motor
|
162
|
-
|
172
|
+
win.addstr(<<~TEXT)
|
173
|
+
Move the motor. Keys:
|
174
|
+
Esc stop movement
|
175
|
+
\u2191 go to upper limit
|
176
|
+
\u2193 go to lower limit
|
177
|
+
\u2190 jog up #{@pulse_count} pulses
|
178
|
+
\u2192 jog down #{@pulse_count} pulses
|
179
|
+
> increase jog size
|
180
|
+
< decrease jog size
|
181
|
+
u set upper limit at current position
|
182
|
+
l set lower limit at current position
|
183
|
+
r reverse motor
|
184
|
+
TEXT
|
163
185
|
|
164
186
|
if ilt2?
|
165
187
|
win.addstr("R reverse motor (but leave position alone)\n")
|
188
|
+
else
|
189
|
+
win.addstr("! factory reset\n")
|
166
190
|
end
|
167
191
|
win.addstr("q quit\n")
|
168
192
|
win.refresh
|
169
193
|
end
|
170
|
-
|
194
|
+
|
171
195
|
def wait_for_stop
|
172
196
|
win.setpos(13, 0)
|
173
197
|
win.addstr("Moving...\n")
|
@@ -183,7 +207,6 @@ r reverse motor
|
|
183
207
|
end
|
184
208
|
|
185
209
|
sdn.receive(0.1) do |message|
|
186
|
-
|
187
210
|
if message.is_a?(ns::PostMotorPosition)
|
188
211
|
last_pos = @pos
|
189
212
|
@pos = message.position_pulses
|
@@ -198,7 +221,7 @@ r reverse motor
|
|
198
221
|
win.addstr("\n")
|
199
222
|
win.nodelay = false
|
200
223
|
refresh
|
201
|
-
return
|
224
|
+
return # rubocop:disable Lint/NonLocalExitFromIterator
|
202
225
|
end
|
203
226
|
end
|
204
227
|
sleep 0.1
|