somfy_sdn 1.0.12 → 2.1.2
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/cli/mqtt/group.rb +31 -0
- data/lib/sdn/cli/mqtt/motor.rb +103 -0
- data/lib/sdn/cli/mqtt/read.rb +156 -0
- data/lib/sdn/cli/mqtt/subscriptions.rb +156 -0
- data/lib/sdn/cli/mqtt/write.rb +93 -0
- data/lib/sdn/cli/mqtt.rb +397 -0
- data/lib/sdn/cli/provisioner.rb +234 -0
- data/lib/sdn/cli/simulator.rb +197 -0
- data/lib/sdn/client.rb +86 -0
- 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/message.rb +81 -30
- data/lib/sdn/version.rb +1 -1
- data/lib/sdn.rb +16 -1
- metadata +63 -26
- 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/cli/mqtt.rb
ADDED
@@ -0,0 +1,397 @@
|
|
1
|
+
require 'mqtt'
|
2
|
+
require 'uri'
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
require 'sdn/cli/mqtt/group'
|
6
|
+
require 'sdn/cli/mqtt/motor'
|
7
|
+
require 'sdn/cli/mqtt/read'
|
8
|
+
require 'sdn/cli/mqtt/write'
|
9
|
+
require 'sdn/cli/mqtt/subscriptions'
|
10
|
+
|
11
|
+
module SDN
|
12
|
+
module CLI
|
13
|
+
class MQTT
|
14
|
+
MessageAndRetries = Struct.new(:message, :remaining_retries, :priority)
|
15
|
+
|
16
|
+
include Read
|
17
|
+
include Write
|
18
|
+
include Subscriptions
|
19
|
+
|
20
|
+
WAIT_TIME = 0.25
|
21
|
+
BROADCAST_WAIT = 5.0
|
22
|
+
|
23
|
+
attr_reader :motors, :groups
|
24
|
+
|
25
|
+
def initialize(port, mqtt_uri, device_id: "somfy", base_topic: "homie", auto_discover: true)
|
26
|
+
@base_topic = "#{base_topic}/#{device_id}"
|
27
|
+
@mqtt = ::MQTT::Client.new(mqtt_uri)
|
28
|
+
@mqtt.set_will("#{@base_topic}/$state", "lost", retain: true)
|
29
|
+
@mqtt.connect
|
30
|
+
|
31
|
+
@motors = {}
|
32
|
+
@groups = {}
|
33
|
+
|
34
|
+
@mutex = Mutex.new
|
35
|
+
@cond = ConditionVariable.new
|
36
|
+
@queues = [[], [], []]
|
37
|
+
@response_pending = false
|
38
|
+
@broadcast_pending = false
|
39
|
+
|
40
|
+
@auto_discover = auto_discover
|
41
|
+
@motors_found = true
|
42
|
+
|
43
|
+
clear_tree(@base_topic)
|
44
|
+
publish_basic_attributes
|
45
|
+
|
46
|
+
@sdn = Client.new(port)
|
47
|
+
|
48
|
+
read_thread = Thread.new { read }
|
49
|
+
write_thread = Thread.new { write }
|
50
|
+
@mqtt.get { |packet| handle_message(packet.topic, packet.payload) }
|
51
|
+
end
|
52
|
+
|
53
|
+
def publish(topic, value)
|
54
|
+
@mqtt.publish("#{@base_topic}/#{topic}", value, retain: true, qos: 1)
|
55
|
+
end
|
56
|
+
|
57
|
+
def subscribe(topic)
|
58
|
+
@mqtt.subscribe("#{@base_topic}/#{topic}")
|
59
|
+
end
|
60
|
+
|
61
|
+
def enqueue(message, queue = 0)
|
62
|
+
@mutex.synchronize do
|
63
|
+
queue = @queues[queue]
|
64
|
+
unless queue.include?(message)
|
65
|
+
queue.push(message)
|
66
|
+
@cond.signal
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def clear_tree(topic)
|
72
|
+
@mqtt.subscribe("#{topic}/#")
|
73
|
+
@mqtt.unsubscribe("#{topic}/#", wait_for_ack: true)
|
74
|
+
while !@mqtt.queue_empty?
|
75
|
+
packet = @mqtt.get
|
76
|
+
@mqtt.publish(packet.topic, nil, retain: true)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def publish_basic_attributes
|
81
|
+
@mqtt.batch_publish do
|
82
|
+
publish("$homie", "4.0.0")
|
83
|
+
publish("$name", "Somfy SDN Network")
|
84
|
+
publish("$state", "init")
|
85
|
+
publish("$nodes", "FFFFFF")
|
86
|
+
|
87
|
+
publish("FFFFFF/$name", "Broadcast")
|
88
|
+
publish("FFFFFF/$type", "sdn")
|
89
|
+
publish("FFFFFF/$properties", "discover")
|
90
|
+
|
91
|
+
publish("FFFFFF/discover/$name", "Trigger Motor Discovery")
|
92
|
+
publish("FFFFFF/discover/$datatype", "enum")
|
93
|
+
publish("FFFFFF/discover/$format", "discover")
|
94
|
+
publish("FFFFFF/discover/$settable", "true")
|
95
|
+
publish("FFFFFF/discover/$retained", "false")
|
96
|
+
|
97
|
+
subscribe_all
|
98
|
+
|
99
|
+
publish("$state", "ready")
|
100
|
+
end
|
101
|
+
|
102
|
+
@mqtt.on_reconnect do
|
103
|
+
subscribe_all
|
104
|
+
publish("$state", :init)
|
105
|
+
publish("$state", :ready)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def subscribe_all
|
110
|
+
subscribe("+/discover/set")
|
111
|
+
subscribe("+/label/set")
|
112
|
+
subscribe("+/control/set")
|
113
|
+
subscribe("+/jog-ms/set")
|
114
|
+
subscribe("+/jog-pulses/set")
|
115
|
+
subscribe("+/position-pulses/set")
|
116
|
+
subscribe("+/position-percent/set")
|
117
|
+
subscribe("+/ip/set")
|
118
|
+
subscribe("+/reset/set")
|
119
|
+
subscribe("+/direction/set")
|
120
|
+
subscribe("+/up-speed/set")
|
121
|
+
subscribe("+/down-speed/set")
|
122
|
+
subscribe("+/slow-speed/set")
|
123
|
+
subscribe("+/up-limit/set")
|
124
|
+
subscribe("+/down-limit/set")
|
125
|
+
subscribe("+/groups/set")
|
126
|
+
(1..16).each do |ip|
|
127
|
+
subscribe("+/ip#{ip}-pulses/set")
|
128
|
+
subscribe("+/ip#{ip}-percent/set")
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def publish_motor(addr, node_type)
|
133
|
+
motor = nil
|
134
|
+
|
135
|
+
@mqtt.batch_publish do
|
136
|
+
publish("#{addr}/$name", addr)
|
137
|
+
publish("#{addr}/$type", node_type.to_s)
|
138
|
+
properties = %w{
|
139
|
+
discover
|
140
|
+
label
|
141
|
+
state
|
142
|
+
control
|
143
|
+
jog-ms
|
144
|
+
jog-pulses
|
145
|
+
position-pulses
|
146
|
+
position-percent
|
147
|
+
ip
|
148
|
+
down-limit
|
149
|
+
groups
|
150
|
+
last-direction
|
151
|
+
} + (1..16).map { |ip| ["ip#{ip}-pulses", "ip#{ip}-percent"] }.flatten
|
152
|
+
|
153
|
+
unless node_type == :st50ilt2
|
154
|
+
properties.concat %w{
|
155
|
+
reset
|
156
|
+
last-action-source
|
157
|
+
last-action-cause
|
158
|
+
up-limit
|
159
|
+
direction
|
160
|
+
up-speed
|
161
|
+
down-speed
|
162
|
+
slow-speed
|
163
|
+
}
|
164
|
+
end
|
165
|
+
|
166
|
+
publish("#{addr}/$properties", properties.join(","))
|
167
|
+
|
168
|
+
publish("#{addr}/discover/$name", "Trigger Motor Discovery")
|
169
|
+
publish("#{addr}/discover/$datatype", "enum")
|
170
|
+
publish("#{addr}/discover/$format", "discover")
|
171
|
+
publish("#{addr}/discover/$settable", "true")
|
172
|
+
publish("#{addr}/discover/$retained", "false")
|
173
|
+
|
174
|
+
publish("#{addr}/label/$name", "Node label")
|
175
|
+
publish("#{addr}/label/$datatype", "string")
|
176
|
+
publish("#{addr}/label/$settable", "true")
|
177
|
+
|
178
|
+
publish("#{addr}/state/$name", "Current state of the motor")
|
179
|
+
publish("#{addr}/state/$datatype", "enum")
|
180
|
+
publish("#{addr}/state/$format", Message::PostMotorStatus::STATE.keys.join(','))
|
181
|
+
|
182
|
+
publish("#{addr}/control/$name", "Control motor")
|
183
|
+
publish("#{addr}/control/$datatype", "enum")
|
184
|
+
publish("#{addr}/control/$format", "up,down,stop,wink,next_ip,previous_ip,refresh")
|
185
|
+
publish("#{addr}/control/$settable", "true")
|
186
|
+
publish("#{addr}/control/$retained", "false")
|
187
|
+
|
188
|
+
publish("#{addr}/jog-ms/$name", "Jog motor by ms")
|
189
|
+
publish("#{addr}/jog-ms/$datatype", "integer")
|
190
|
+
publish("#{addr}/jog-ms/$format", "-65535:65535")
|
191
|
+
publish("#{addr}/jog-ms/$unit", "ms")
|
192
|
+
publish("#{addr}/jog-ms/$settable", "true")
|
193
|
+
publish("#{addr}/jog-ms/$retained", "false")
|
194
|
+
|
195
|
+
publish("#{addr}/jog-pulses/$name", "Jog motor by pulses")
|
196
|
+
publish("#{addr}/jog-pulses/$datatype", "integer")
|
197
|
+
publish("#{addr}/jog-pulses/$format", "-65535:65535")
|
198
|
+
publish("#{addr}/jog-pulses/$unit", "pulses")
|
199
|
+
publish("#{addr}/jog-pulses/$settable", "true")
|
200
|
+
publish("#{addr}/jog-pulses/$retained", "false")
|
201
|
+
|
202
|
+
publish("#{addr}/position-percent/$name", "Position (in %)")
|
203
|
+
publish("#{addr}/position-percent/$datatype", "integer")
|
204
|
+
publish("#{addr}/position-percent/$format", "0:100")
|
205
|
+
publish("#{addr}/position-percent/$unit", "%")
|
206
|
+
publish("#{addr}/position-percent/$settable", "true")
|
207
|
+
|
208
|
+
publish("#{addr}/position-pulses/$name", "Position from up limit (in pulses)")
|
209
|
+
publish("#{addr}/position-pulses/$datatype", "integer")
|
210
|
+
publish("#{addr}/position-pulses/$format", "0:65535")
|
211
|
+
publish("#{addr}/position-pulses/$unit", "pulses")
|
212
|
+
publish("#{addr}/position-pulses/$settable", "true")
|
213
|
+
|
214
|
+
publish("#{addr}/ip/$name", "Intermediate Position")
|
215
|
+
publish("#{addr}/ip/$datatype", "integer")
|
216
|
+
publish("#{addr}/ip/$format", "1:16")
|
217
|
+
publish("#{addr}/ip/$settable", "true")
|
218
|
+
publish("#{addr}/ip/$retained", "false") if node_type == :st50ilt2
|
219
|
+
|
220
|
+
publish("#{addr}/down-limit/$name", "Down limit")
|
221
|
+
publish("#{addr}/down-limit/$datatype", "integer")
|
222
|
+
publish("#{addr}/down-limit/$format", "0:65535")
|
223
|
+
publish("#{addr}/down-limit/$unit", "pulses")
|
224
|
+
publish("#{addr}/down-limit/$settable", "true")
|
225
|
+
|
226
|
+
publish("#{addr}/last-direction/$name", "Direction of last motion")
|
227
|
+
publish("#{addr}/last-direction/$datatype", "enum")
|
228
|
+
publish("#{addr}/last-direction/$format", Message::PostMotorStatus::DIRECTION.keys.join(','))
|
229
|
+
|
230
|
+
unless node_type == :st50ilt2
|
231
|
+
publish("#{addr}/reset/$name", "Recall factory settings")
|
232
|
+
publish("#{addr}/reset/$datatype", "enum")
|
233
|
+
publish("#{addr}/reset/$format", Message::SetFactoryDefault::RESET.keys.join(','))
|
234
|
+
publish("#{addr}/reset/$settable", "true")
|
235
|
+
publish("#{addr}/reset/$retained", "false")
|
236
|
+
|
237
|
+
publish("#{addr}/last-action-source/$name", "Source of last action")
|
238
|
+
publish("#{addr}/last-action-source/$datatype", "enum")
|
239
|
+
publish("#{addr}/last-action-source/$format", Message::PostMotorStatus::SOURCE.keys.join(','))
|
240
|
+
|
241
|
+
publish("#{addr}/last-action-cause/$name", "Cause of last action")
|
242
|
+
publish("#{addr}/last-action-cause/$datatype", "enum")
|
243
|
+
publish("#{addr}/last-action-cause/$format", Message::PostMotorStatus::CAUSE.keys.join(','))
|
244
|
+
|
245
|
+
publish("#{addr}/up-limit/$name", "Up limit (always = 0)")
|
246
|
+
publish("#{addr}/up-limit/$datatype", "integer")
|
247
|
+
publish("#{addr}/up-limit/$format", "0:65535")
|
248
|
+
publish("#{addr}/up-limit/$unit", "pulses")
|
249
|
+
publish("#{addr}/up-limit/$settable", "true")
|
250
|
+
|
251
|
+
publish("#{addr}/direction/$name", "Motor rotation direction")
|
252
|
+
publish("#{addr}/direction/$datatype", "enum")
|
253
|
+
publish("#{addr}/direction/$format", "standard,reversed")
|
254
|
+
publish("#{addr}/direction/$settable", "true")
|
255
|
+
|
256
|
+
publish("#{addr}/up-speed/$name", "Up speed")
|
257
|
+
publish("#{addr}/up-speed/$datatype", "integer")
|
258
|
+
publish("#{addr}/up-speed/$format", "6:28")
|
259
|
+
publish("#{addr}/up-speed/$unit", "RPM")
|
260
|
+
publish("#{addr}/up-speed/$settable", "true")
|
261
|
+
|
262
|
+
publish("#{addr}/down-speed/$name", "Down speed, always = Up speed")
|
263
|
+
publish("#{addr}/down-speed/$datatype", "integer")
|
264
|
+
publish("#{addr}/down-speed/$format", "6:28")
|
265
|
+
publish("#{addr}/down-speed/$unit", "RPM")
|
266
|
+
publish("#{addr}/down-speed/$settable", "true")
|
267
|
+
|
268
|
+
publish("#{addr}/slow-speed/$name", "Slow speed")
|
269
|
+
publish("#{addr}/slow-speed/$datatype", "integer")
|
270
|
+
publish("#{addr}/slow-speed/$format", "6:28")
|
271
|
+
publish("#{addr}/slow-speed/$unit", "RPM")
|
272
|
+
publish("#{addr}/slow-speed/$settable", "true")
|
273
|
+
end
|
274
|
+
|
275
|
+
publish("#{addr}/groups/$name", "Group Memberships (comma separated, address must start 0101xx)")
|
276
|
+
publish("#{addr}/groups/$datatype", "string")
|
277
|
+
publish("#{addr}/groups/$settable", "true")
|
278
|
+
|
279
|
+
(1..16).each do |ip|
|
280
|
+
publish("#{addr}/ip#{ip}-pulses/$name", "Intermediate Position #{ip}")
|
281
|
+
publish("#{addr}/ip#{ip}-pulses/$datatype", "integer")
|
282
|
+
publish("#{addr}/ip#{ip}-pulses/$format", "0:65535")
|
283
|
+
publish("#{addr}/ip#{ip}-pulses/$unit", "pulses")
|
284
|
+
publish("#{addr}/ip#{ip}-pulses/$settable", "true")
|
285
|
+
|
286
|
+
publish("#{addr}/ip#{ip}-percent/$name", "Intermediate Position #{ip}")
|
287
|
+
publish("#{addr}/ip#{ip}-percent/$datatype", "integer")
|
288
|
+
publish("#{addr}/ip#{ip}-percent/$format", "0:100")
|
289
|
+
publish("#{addr}/ip#{ip}-percent/$unit", "%")
|
290
|
+
publish("#{addr}/ip#{ip}-percent/$settable", "true")
|
291
|
+
end
|
292
|
+
|
293
|
+
motor = Motor.new(self, addr, node_type)
|
294
|
+
@motors[addr] = motor
|
295
|
+
publish("$nodes", (["FFFFFF"] + @motors.keys.sort + @groups.keys.sort).join(","))
|
296
|
+
end
|
297
|
+
|
298
|
+
sdn_addr = Message.parse_address(addr)
|
299
|
+
@mutex.synchronize do
|
300
|
+
@queues[2].push(MessageAndRetries.new(Message::GetNodeLabel.new(sdn_addr), 5, 2))
|
301
|
+
case node_type
|
302
|
+
when :st30
|
303
|
+
@queues[2].push(MessageAndRetries.new(Message::GetMotorStatus.new(sdn_addr), 5, 2))
|
304
|
+
@queues[2].push(MessageAndRetries.new(Message::GetMotorLimits.new(sdn_addr), 5, 2))
|
305
|
+
@queues[2].push(MessageAndRetries.new(Message::GetMotorDirection.new(sdn_addr), 5, 2))
|
306
|
+
@queues[2].push(MessageAndRetries.new(Message::GetMotorRollingSpeed.new(sdn_addr), 5, 2))
|
307
|
+
(1..16).each { |ip| @queues[2].push(MessageAndRetries.new(Message::GetMotorIP.new(sdn_addr, ip), 5, 2)) }
|
308
|
+
when :st50ilt2
|
309
|
+
@queues[2].push(MessageAndRetries.new(Message::ILT2::GetMotorSettings.new(sdn_addr), 5, 2))
|
310
|
+
@queues[2].push(MessageAndRetries.new(Message::ILT2::GetMotorPosition.new(sdn_addr), 5, 2))
|
311
|
+
(1..16).each { |ip| @queues[2].push(MessageAndRetries.new(Message::ILT2::GetMotorIP.new(sdn_addr, ip), 5, 2)) }
|
312
|
+
end
|
313
|
+
(1..16).each { |g| @queues[2].push(MessageAndRetries.new(Message::GetGroupAddr.new(sdn_addr, g), 5, 2)) }
|
314
|
+
|
315
|
+
@cond.signal
|
316
|
+
end
|
317
|
+
|
318
|
+
motor
|
319
|
+
end
|
320
|
+
|
321
|
+
def touch_group(group_addr)
|
322
|
+
group = @groups[Message.print_address(group_addr).gsub('.', '')]
|
323
|
+
group&.publish(:motors, group.motors_string)
|
324
|
+
end
|
325
|
+
|
326
|
+
def add_group(addr)
|
327
|
+
addr = addr.gsub('.', '')
|
328
|
+
group = @groups[addr]
|
329
|
+
return group if group
|
330
|
+
|
331
|
+
@mqtt.batch_publish do
|
332
|
+
publish("#{addr}/$name", addr)
|
333
|
+
publish("#{addr}/$type", "Shade Group")
|
334
|
+
publish("#{addr}/$properties", "discover,control,jog-ms,jog-pulses,position-pulses,position-percent,ip,reset,state,last-direction,motors")
|
335
|
+
|
336
|
+
publish("#{addr}/discover/$name", "Trigger Motor Discovery")
|
337
|
+
publish("#{addr}/discover/$datatype", "enum")
|
338
|
+
publish("#{addr}/discover/$format", "discover")
|
339
|
+
publish("#{addr}/discover/$settable", "true")
|
340
|
+
publish("#{addr}/discover/$retained", "false")
|
341
|
+
|
342
|
+
publish("#{addr}/control/$name", "Control motors")
|
343
|
+
publish("#{addr}/control/$datatype", "enum")
|
344
|
+
publish("#{addr}/control/$format", "up,down,stop,wink,next_ip,previous_ip,refresh")
|
345
|
+
publish("#{addr}/control/$settable", "true")
|
346
|
+
publish("#{addr}/control/$retained", "false")
|
347
|
+
|
348
|
+
publish("#{addr}/jog-ms/$name", "Jog motors by ms")
|
349
|
+
publish("#{addr}/jog-ms/$datatype", "integer")
|
350
|
+
publish("#{addr}/jog-ms/$format", "-65535:65535")
|
351
|
+
publish("#{addr}/jog-ms/$unit", "ms")
|
352
|
+
publish("#{addr}/jog-ms/$settable", "true")
|
353
|
+
publish("#{addr}/jog-ms/$retained", "false")
|
354
|
+
|
355
|
+
publish("#{addr}/jog-pulses/$name", "Jog motors by pulses")
|
356
|
+
publish("#{addr}/jog-pulses/$datatype", "integer")
|
357
|
+
publish("#{addr}/jog-pulses/$format", "-65535:65535")
|
358
|
+
publish("#{addr}/jog-pulses/$unit", "pulses")
|
359
|
+
publish("#{addr}/jog-pulses/$settable", "true")
|
360
|
+
publish("#{addr}/jog-pulses/$retained", "false")
|
361
|
+
|
362
|
+
publish("#{addr}/position-pulses/$name", "Position from up limit (in pulses)")
|
363
|
+
publish("#{addr}/position-pulses/$datatype", "integer")
|
364
|
+
publish("#{addr}/position-pulses/$format", "0:65535")
|
365
|
+
publish("#{addr}/position-pulses/$unit", "pulses")
|
366
|
+
publish("#{addr}/position-pulses/$settable", "true")
|
367
|
+
|
368
|
+
publish("#{addr}/position-percent/$name", "Position (in %)")
|
369
|
+
publish("#{addr}/position-percent/$datatype", "integer")
|
370
|
+
publish("#{addr}/position-percent/$format", "0:100")
|
371
|
+
publish("#{addr}/position-percent/$unit", "%")
|
372
|
+
publish("#{addr}/position-percent/$settable", "true")
|
373
|
+
|
374
|
+
publish("#{addr}/ip/$name", "Intermediate Position")
|
375
|
+
publish("#{addr}/ip/$datatype", "integer")
|
376
|
+
publish("#{addr}/ip/$format", "1:16")
|
377
|
+
publish("#{addr}/ip/$settable", "true")
|
378
|
+
|
379
|
+
publish("#{addr}/state/$name", "State of the motors")
|
380
|
+
publish("#{addr}/state/$datatype", "enum")
|
381
|
+
publish("#{addr}/state/$format", Message::PostMotorStatus::STATE.keys.join(',') + ",mixed")
|
382
|
+
|
383
|
+
publish("#{addr}/last-direction/$name", "Direction of last motion")
|
384
|
+
publish("#{addr}/last-direction/$datatype", "enum")
|
385
|
+
publish("#{addr}/last-direction/$format", Message::PostMotorStatus::DIRECTION.keys.join(',') + ",mixed")
|
386
|
+
|
387
|
+
publish("#{addr}/motors/$name", "Comma separated motor addresses that are members of this group")
|
388
|
+
publish("#{addr}/motors/$datatype", "string")
|
389
|
+
|
390
|
+
group = @groups[addr] = Group.new(self, addr)
|
391
|
+
publish("$nodes", (["FFFFFF"] + @motors.keys.sort + @groups.keys.sort).join(","))
|
392
|
+
end
|
393
|
+
group
|
394
|
+
end
|
395
|
+
end
|
396
|
+
end
|
397
|
+
end
|
@@ -0,0 +1,234 @@
|
|
1
|
+
require 'curses'
|
2
|
+
|
3
|
+
module SDN
|
4
|
+
module CLI
|
5
|
+
class Provisioner
|
6
|
+
attr_reader :win, :sdn, :addr, :ns
|
7
|
+
|
8
|
+
def initialize(port, addr = nil)
|
9
|
+
@sdn = Client.new(port)
|
10
|
+
@reversed = false
|
11
|
+
@pulse_count = 10
|
12
|
+
|
13
|
+
if addr
|
14
|
+
@addr = addr = Message.parse_address(addr)
|
15
|
+
else
|
16
|
+
puts "Discovering motor..."
|
17
|
+
message = sdn.ensure(Message::GetNodeAddr.new)
|
18
|
+
puts "Found #{message.node_type}"
|
19
|
+
@addr = addr = message.src
|
20
|
+
end
|
21
|
+
|
22
|
+
puts "Preparing to provision motor #{Message.print_address(addr)}"
|
23
|
+
|
24
|
+
message = sdn.ensure(Message::GetNodeLabel.new(addr))
|
25
|
+
|
26
|
+
node_type = message.node_type
|
27
|
+
@ns = ns = node_type == :st50ilt2 ? Message::ILT2 : Message
|
28
|
+
|
29
|
+
print "Motor is currently labeled '#{message.label}'; what would you like to change it to (blank to leave alone)? "
|
30
|
+
new_label = STDIN.gets
|
31
|
+
|
32
|
+
unless new_label == "\n"
|
33
|
+
new_label.strip!
|
34
|
+
sdn.ensure(ns::SetNodeLabel.new(addr, new_label))
|
35
|
+
end
|
36
|
+
|
37
|
+
# make sure some limits exist
|
38
|
+
unless ns == Message::ILT2
|
39
|
+
limits = sdn.ensure(Message::GetMotorLimits.new(addr))
|
40
|
+
if limits.up_limit.nil? || limits.down_limit.nil?
|
41
|
+
sdn.ensure(Message::SetMotorLimits.new(addr, :delete, :up))
|
42
|
+
sdn.ensure(Message::SetMotorLimits.new(addr, :delete, :down))
|
43
|
+
sdn.ensure(Message::SetMotorLimits.new(addr, :current_position, :up))
|
44
|
+
sdn.ensure(Message::SetMotorLimits.new(addr, :specified_position, :down, 500))
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
Curses.init_screen
|
49
|
+
begin
|
50
|
+
Curses.noecho
|
51
|
+
Curses.crmode
|
52
|
+
Curses.nonl
|
53
|
+
Curses.curs_set(0)
|
54
|
+
@win = Curses.stdscr
|
55
|
+
|
56
|
+
process
|
57
|
+
rescue => e
|
58
|
+
win.setpos(0, 0)
|
59
|
+
win.addstr(e.inspect)
|
60
|
+
win.addstr("\n")
|
61
|
+
win.addstr(e.backtrace.join("\n"))
|
62
|
+
win.refresh
|
63
|
+
sleep 10
|
64
|
+
ensure
|
65
|
+
Curses.close_screen
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def process
|
70
|
+
win.keypad = true
|
71
|
+
print_help
|
72
|
+
refresh
|
73
|
+
|
74
|
+
loop do
|
75
|
+
char = win.getch
|
76
|
+
case char
|
77
|
+
when 27 # Esc
|
78
|
+
stop
|
79
|
+
refresh
|
80
|
+
when Curses::Key::UP
|
81
|
+
if ilt2?
|
82
|
+
sdn.ensure(Message::ILT2::SetMotorPosition.new(addr, :up_limit))
|
83
|
+
else
|
84
|
+
sdn.ensure(Message::MoveTo.new(addr, :up_limit))
|
85
|
+
end
|
86
|
+
wait_for_stop
|
87
|
+
when Curses::Key::DOWN
|
88
|
+
if ilt2?
|
89
|
+
sdn.ensure(Message::ILT2::SetMotorPosition.new(addr, :down_limit))
|
90
|
+
else
|
91
|
+
sdn.ensure(Message::MoveTo.new(addr, :down_limit))
|
92
|
+
end
|
93
|
+
wait_for_stop
|
94
|
+
when Curses::Key::LEFT
|
95
|
+
if @pos < @pulse_count
|
96
|
+
sdn.ensure(Message::ILT2::SetMotorSettings.new(addr, reversed_int, @limit + @pulse_count - @pos, @pulse_count))
|
97
|
+
refresh
|
98
|
+
end
|
99
|
+
sdn.ensure(Message::ILT2::SetMotorPosition.new(addr, :jog_up_pulses, @pulse_count))
|
100
|
+
wait_for_stop
|
101
|
+
when Curses::Key::RIGHT
|
102
|
+
if @limit - @pos < @pulse_count
|
103
|
+
sdn.ensure(Message::ILT2::SetMotorSettings.new(addr, reversed_int, @pos + @pulse_count, @pos))
|
104
|
+
refresh
|
105
|
+
end
|
106
|
+
sdn.ensure(Message::ILT2::SetMotorPosition.new(addr, :jog_down_pulses, @pulse_count))
|
107
|
+
wait_for_stop
|
108
|
+
when 'u'
|
109
|
+
if ilt2?
|
110
|
+
sdn.ensure(Message::ILT2::SetMotorSettings.new(addr, reversed_int, @limit - @pos, 0))
|
111
|
+
else
|
112
|
+
sdn.ensure(Message::SetMotorLimits.new(addr, :current_position, :up))
|
113
|
+
end
|
114
|
+
refresh
|
115
|
+
when 'l'
|
116
|
+
if ilt2?
|
117
|
+
sdn.ensure(Message::ILT2::SetMotorSettings.new(addr, reversed_int, @pos, @pos))
|
118
|
+
else
|
119
|
+
sdn.ensure(Message::SetMotorLimits.new(addr, :current_position, :down))
|
120
|
+
end
|
121
|
+
refresh
|
122
|
+
when 'r'
|
123
|
+
@reversed = !@reversed
|
124
|
+
if ilt2?
|
125
|
+
sdn.ensure(Message::ILT2::SetMotorSettings.new(addr, reversed_int, @limit, @limit - @pos))
|
126
|
+
else
|
127
|
+
sdn.ensure(Message::SetMotorDirection.new(addr, @reversed ? :reversed : :standard))
|
128
|
+
end
|
129
|
+
refresh
|
130
|
+
when 'R'
|
131
|
+
next unless ilt2?
|
132
|
+
@reversed = !@reversed
|
133
|
+
sdn.ensure(Message::ILT2::SetMotorSettings.new(addr, reversed_int, @limit, @pos))
|
134
|
+
refresh
|
135
|
+
when '<'
|
136
|
+
@pulse_count /= 2 if @pulse_count > 5
|
137
|
+
print_help
|
138
|
+
when '>'
|
139
|
+
@pulse_count *= 2
|
140
|
+
print_help
|
141
|
+
when 'q'
|
142
|
+
break
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def print_help
|
148
|
+
win.setpos(0, 0)
|
149
|
+
win.addstr(<<-INSTRUCTIONS)
|
150
|
+
Move the motor. Keys:
|
151
|
+
Esc stop movement
|
152
|
+
\u2191 go to upper limit
|
153
|
+
\u2193 go to lower limit
|
154
|
+
\u2190 jog up #{@pulse_count} pulses
|
155
|
+
\u2192 jog down #{@pulse_count} pulses
|
156
|
+
> increase jog size
|
157
|
+
< decrease jog size
|
158
|
+
u set upper limit at current position
|
159
|
+
l set lower limit at current position
|
160
|
+
r reverse motor
|
161
|
+
INSTRUCTIONS
|
162
|
+
|
163
|
+
if ilt2?
|
164
|
+
win.addstr("R reverse motor (but leave position alone)\n")
|
165
|
+
end
|
166
|
+
win.addstr("q quit\n")
|
167
|
+
win.refresh
|
168
|
+
end
|
169
|
+
|
170
|
+
def wait_for_stop
|
171
|
+
win.setpos(13, 0)
|
172
|
+
win.addstr("Moving...\n")
|
173
|
+
loop do
|
174
|
+
win.nodelay = true
|
175
|
+
stop if win.getch == 27 # Esc
|
176
|
+
sdn.send(ns::GetMotorPosition.new(addr))
|
177
|
+
sdn.receive do |message|
|
178
|
+
next unless message.is_a?(ns::PostMotorPosition)
|
179
|
+
last_pos = @pos
|
180
|
+
@pos = message.position_pulses
|
181
|
+
win.setpos(14, 0)
|
182
|
+
win.addstr("Position: #{@pos}\n")
|
183
|
+
|
184
|
+
if last_pos == @pos
|
185
|
+
win.setpos(13, 0)
|
186
|
+
win.addstr("\n")
|
187
|
+
win.nodelay = false
|
188
|
+
refresh
|
189
|
+
return
|
190
|
+
end
|
191
|
+
end
|
192
|
+
sleep 0.1
|
193
|
+
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def refresh
|
198
|
+
pos = sdn.ensure(ns::GetMotorPosition.new(addr))
|
199
|
+
@pos = pos.position_pulses
|
200
|
+
if ilt2?
|
201
|
+
settings = sdn.ensure(Message::ILT2::GetMotorSettings.new(addr))
|
202
|
+
@limit = settings.limit
|
203
|
+
else
|
204
|
+
limits = sdn.ensure(Message::GetMotorLimits.new(addr))
|
205
|
+
@limit = limits.down_limit
|
206
|
+
direction = sdn.ensure(Message::GetMotorDirection.new(addr))
|
207
|
+
@reversed = direction.direction == :reversed
|
208
|
+
end
|
209
|
+
|
210
|
+
win.setpos(14, 0)
|
211
|
+
win.addstr("Position: #{@pos}\n")
|
212
|
+
win.addstr("Limit: #{@limit}\n")
|
213
|
+
win.addstr("Reversed: #{@reversed}\n")
|
214
|
+
win.refresh
|
215
|
+
end
|
216
|
+
|
217
|
+
def stop
|
218
|
+
if ilt2?
|
219
|
+
sdn.ensure(Message::ILT2::SetMotorPosition.new(addr, :stop))
|
220
|
+
else
|
221
|
+
sdn.ensure(Message::Stop.new(addr))
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
def ilt2?
|
226
|
+
ns == Message::ILT2
|
227
|
+
end
|
228
|
+
|
229
|
+
def reversed_int
|
230
|
+
@reversed ? 1 : 0
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|