somfy_sdn 1.0.11 → 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,718 +0,0 @@
1
- require 'mqtt'
2
- require 'uri'
3
- require 'set'
4
-
5
- module SDN
6
- MessageAndRetries = Struct.new(:message, :remaining_retries, :priority)
7
-
8
- Group = Struct.new(:bridge, :addr, :positionpercent, :state, :motors) do
9
- def initialize(*)
10
- members.each { |k| self[k] = :nil }
11
- super
12
- end
13
-
14
- def publish(attribute, value)
15
- if self[attribute] != value
16
- bridge.publish("#{addr}/#{attribute}", value.to_s)
17
- self[attribute] = value
18
- end
19
- end
20
-
21
- def printed_addr
22
- Message.print_address(Message.parse_address(addr))
23
- end
24
-
25
- def motor_objects
26
- bridge.motors.select { |addr, motor| motor.groups_string.include?(printed_addr) }.values
27
- end
28
-
29
- def motors_string
30
- motor_objects.map { |m| SDN::Message.print_address(SDN::Message.parse_address(m.addr)) }.sort.join(',')
31
- end
32
- end
33
-
34
- Motor = Struct.new(:bridge,
35
- :addr,
36
- :label,
37
- :positionpulses,
38
- :positionpercent,
39
- :ip,
40
- :state,
41
- :last_direction,
42
- :last_action_source,
43
- :last_action_cause,
44
- :uplimit,
45
- :downlimit,
46
- :direction,
47
- :upspeed,
48
- :downspeed,
49
- :slowspeed,
50
- :ip1pulses,
51
- :ip1percent,
52
- :ip2pulses,
53
- :ip2percent,
54
- :ip3pulses,
55
- :ip3percent,
56
- :ip4pulses,
57
- :ip4percent,
58
- :ip5pulses,
59
- :ip5percent,
60
- :ip6pulses,
61
- :ip6percent,
62
- :ip7pulses,
63
- :ip7percent,
64
- :ip8pulses,
65
- :ip8percent,
66
- :ip9pulses,
67
- :ip9percent,
68
- :ip10pulses,
69
- :ip10percent,
70
- :ip11pulses,
71
- :ip11percent,
72
- :ip12pulses,
73
- :ip12percent,
74
- :ip13pulses,
75
- :ip13percent,
76
- :ip14pulses,
77
- :ip14percent,
78
- :ip15pulses,
79
- :ip15percent,
80
- :ip16pulses,
81
- :ip16percent,
82
- :groups,
83
- :last_action) do
84
- def initialize(*)
85
- members.each { |k| self[k] = :nil }
86
- @groups = [].fill(nil, 0, 16)
87
- super
88
- end
89
-
90
- def publish(attribute, value)
91
- if self[attribute] != value
92
- bridge.publish("#{addr}/#{attribute}", value.to_s)
93
- self[attribute] = value
94
- end
95
- end
96
-
97
- def add_group(index, address)
98
- group = bridge.add_group(SDN::Message.print_address(address)) if address
99
- @groups[index] = address
100
- group&.publish(:motors, group.motors_string)
101
- publish(:groups, groups_string)
102
- end
103
-
104
- def set_groups(groups)
105
- return unless groups =~ /^(?:\h{2}[:.]?\h{2}[:.]?\h{2}(?:,\h{2}[:.]?\h{2}[:.]?\h{2})*)?$/i
106
- groups = groups.split(',').sort.uniq.map { |g| SDN::Message.parse_address(g) }.select { |g| SDN::Message.is_group_address?(g) }
107
- groups.fill(nil, groups.length, 16 - groups.length)
108
- messages = []
109
- sdn_addr = SDN::Message.parse_address(addr)
110
- groups.each_with_index do |g, i|
111
- if @groups[i] != g
112
- messages << SDN::Message::SetGroupAddr.new(sdn_addr, i, g).tap { |m| m.ack_requested = true }
113
- messages << SDN::Message::GetGroupAddr.new(sdn_addr, i)
114
- end
115
- end
116
- messages
117
- end
118
-
119
- def groups_string
120
- @groups.compact.map { |g| SDN::Message.print_address(g) }.sort.uniq.join(',')
121
- end
122
-
123
- def group_objects
124
- groups_string.split(',').map { |addr| bridge.groups[addr.gsub('.', '')] }
125
- end
126
- end
127
-
128
- class MQTTBridge
129
- WAIT_TIME = 0.25
130
- BROADCAST_WAIT = 5.0
131
-
132
- attr_reader :motors, :groups
133
-
134
- def initialize(mqtt_uri, port, device_id: "somfy", base_topic: "homie")
135
- @base_topic = "#{base_topic}/#{device_id}"
136
- @mqtt = MQTT::Client.new(mqtt_uri)
137
- @mqtt.set_will("#{@base_topic}/$state", "lost", true)
138
- @mqtt.connect
139
-
140
- @motors = {}
141
- @groups = {}
142
-
143
- @mutex = Mutex.new
144
- @cond = ConditionVariable.new
145
- @queues = [[], [], []]
146
- @response_pending = false
147
- @broadcast_pending = false
148
-
149
- publish_basic_attributes
150
-
151
- uri = URI.parse(port)
152
- if uri.scheme == "tcp"
153
- require 'socket'
154
- @sdn = TCPSocket.new(uri.host, uri.port)
155
- elsif uri.scheme == "telnet" || uri.scheme == "rfc2217"
156
- require 'net/telnet/rfc2217'
157
- @sdn = Net::Telnet::RFC2217.new('Host' => uri.host,
158
- 'Port' => uri.port || 23,
159
- 'baud' => 4800,
160
- 'parity' => Net::Telnet::RFC2217::ODD)
161
- else
162
- require 'ccutrer-serialport'
163
- @sdn = CCutrer::SerialPort.new(port, baud: 4800, parity: :odd)
164
- end
165
-
166
- read_thread = Thread.new do
167
- buffer = ""
168
-
169
- loop do
170
- begin
171
- message, bytes_read = SDN::Message.parse(buffer.bytes)
172
- # discard how much we read
173
- buffer = buffer[bytes_read..-1]
174
- unless message
175
- begin
176
- buffer.concat(@sdn.read_nonblock(64 * 1024))
177
- next
178
- rescue IO::WaitReadable, EOFError
179
- wait = buffer.empty? ? nil : WAIT_TIME
180
- if @sdn.wait_readable(wait).nil?
181
- # timed out; just discard everything
182
- puts "timed out reading; discarding buffer: #{buffer.unpack('H*').first}"
183
- buffer = ""
184
- end
185
- end
186
- next
187
- end
188
-
189
- src = SDN::Message.print_address(message.src)
190
- # ignore the UAI Plus and ourselves
191
- if src != '7F.7F.7F' && !SDN::Message::is_group_address?(message.src) && !(motor = @motors[src.gsub('.', '')])
192
- motor = publish_motor(src.gsub('.', ''))
193
- puts "found new motor #{src}"
194
- end
195
-
196
- puts "read #{message.inspect}"
197
- follow_ups = []
198
- case message
199
- when SDN::Message::PostNodeLabel
200
- if (motor.publish(:label, message.label))
201
- publish("#{motor.addr}/$name", message.label)
202
- end
203
- when SDN::Message::PostMotorPosition
204
- motor.publish(:positionpercent, message.position_percent)
205
- motor.publish(:positionpulses, message.position_pulses)
206
- motor.publish(:ip, message.ip)
207
- motor.group_objects.each do |group|
208
- positions = group.motor_objects.map(&:positionpercent)
209
- position = nil
210
- # calculate an average, but only if we know a position for
211
- # every shade
212
- if !positions.include?(:nil) && !positions.include?(nil)
213
- position = positions.inject(&:+) / positions.length
214
- end
215
-
216
- group.publish(:positionpercent, position)
217
- end
218
- when SDN::Message::PostMotorStatus
219
- if message.state == :running || motor.state == :running ||
220
- # if it's explicitly stopped, but we didn't ask it to, it's probably
221
- # changing directions so keep querying
222
- (message.state == :stopped &&
223
- message.last_action_cause == :explicit_command &&
224
- !(motor.last_action == SDN::Message::Stop || motor.last_action.nil?))
225
- follow_ups << SDN::Message::GetMotorStatus.new(message.src)
226
- end
227
- # this will do one more position request after it stopped
228
- follow_ups << SDN::Message::GetMotorPosition.new(message.src)
229
- motor.publish(:state, message.state)
230
- motor.publish(:last_direction, message.last_direction)
231
- motor.publish(:last_action_source, message.last_action_source)
232
- motor.publish(:last_action_cause, message.last_action_cause)
233
- motor.group_objects.each do |group|
234
- states = group.motor_objects.map(&:state).uniq
235
- state = states.length == 1 ? states.first : 'mixed'
236
- group.publish(:state, state)
237
- end
238
- when SDN::Message::PostMotorLimits
239
- motor.publish(:uplimit, message.up_limit)
240
- motor.publish(:downlimit, message.down_limit)
241
- when SDN::Message::PostMotorDirection
242
- motor.publish(:direction, message.direction)
243
- when SDN::Message::PostMotorRollingSpeed
244
- motor.publish(:upspeed, message.up_speed)
245
- motor.publish(:downspeed, message.down_speed)
246
- motor.publish(:slowspeed, message.slow_speed)
247
- when SDN::Message::PostMotorIP
248
- motor.publish(:"ip#{message.ip}pulses", message.position_pulses)
249
- motor.publish(:"ip#{message.ip}percent", message.position_percent)
250
- when SDN::Message::PostGroupAddr
251
- motor.add_group(message.group_index, message.group_address)
252
- end
253
-
254
- @mutex.synchronize do
255
- correct_response = @response_pending && message.src == @prior_message&.message&.dest && message.is_a?(@prior_message&.message&.class&.expected_response)
256
- signal = correct_response || !follow_ups.empty?
257
- puts "correct response #{correct_response}"
258
- puts "pending: #{@response_pending} #{@broadcast_pending}"
259
- @response_pending = @broadcast_pending if correct_response
260
- follow_ups.each do |follow_up|
261
- @queues[1].push(MessageAndRetries.new(follow_up, 5, 1)) unless @queues[1].any? { |mr| mr.message == follow_up }
262
- end
263
- @cond.signal if signal
264
- end
265
- rescue EOFError
266
- puts "EOF reading"
267
- exit 2
268
- rescue SDN::MalformedMessage => e
269
- puts "ignoring malformed message: #{e}" unless e.to_s =~ /issing data/
270
- rescue => e
271
- puts "got garbage: #{e}; #{e.backtrace}"
272
- end
273
- end
274
- end
275
-
276
- write_thread = Thread.new do
277
- begin
278
- loop do
279
- message_and_retries = nil
280
- @mutex.synchronize do
281
- # got woken up early by another command getting queued; spin
282
- if @response_pending
283
- puts "another message queued, but we're still waiting"
284
- while @response_pending
285
- remaining_wait = @response_pending - Time.now.to_f
286
- if remaining_wait < 0
287
- puts "timed out waiting on response"
288
- @response_pending = nil
289
- @broadcast_pending = nil
290
- if @prior_message&.remaining_retries != 0
291
- puts "retrying #{@prior_message.remaining_retries} more times ..."
292
- @queues[@prior_message.priority].push(@prior_message)
293
- @prior_message = nil
294
- end
295
- else
296
- puts "waiting #{remaining_wait} more..."
297
- @cond.wait(@mutex, remaining_wait)
298
- end
299
- end
300
- else
301
- # minimum time between messages
302
- puts "waiting between messages"
303
- sleep 0.1
304
- end
305
-
306
- puts "looking for next message to write"
307
- @queues.find { |q| message_and_retries = q.shift }
308
- if message_and_retries
309
- if message_and_retries.message.ack_requested || message_and_retries.message.class.name =~ /^SDN::Message::Get/
310
- @response_pending = Time.now.to_f + WAIT_TIME
311
- if message_and_retries.message.dest == BROADCAST_ADDRESS || SDN::Message::is_group_address?(message_and_retries.message.src) && message_and_retries.message.is_a?(SDN::Message::GetNodeAddr)
312
- @broadcast_pending = Time.now.to_f + BROADCAST_WAIT
313
- end
314
- end
315
- end
316
-
317
- # wait until there is a message
318
- if @response_pending
319
- message_and_retries.remaining_retries -= 1
320
- @prior_message = message_and_retries
321
- elsif message_and_retries
322
- @prior_message = nil
323
- else
324
- @cond.wait(@mutex)
325
- end
326
- end
327
- next unless message_and_retries
328
-
329
- message = message_and_retries.message
330
- puts "writing #{message.inspect}"
331
- puts "(and waiting for response)" if @response_pending
332
- serialized = message.serialize
333
- @sdn.write(serialized)
334
- @sdn.flush if @sdn.respond_to?(:flush)
335
- puts "wrote #{serialized.unpack("C*").map { |b| '%02x' % b }.join(' ')}"
336
- end
337
- rescue => e
338
- puts "failure writing: #{e}"
339
- exit 1
340
- end
341
- end
342
-
343
- @mqtt.get do |topic, value|
344
- puts "got #{value.inspect} at #{topic}"
345
- if topic == "#{@base_topic}/discovery/discover/set" && value == "true"
346
- # trigger discovery
347
- @mutex.synchronize do
348
- @queues[2].push(MessageAndRetries.new(SDN::Message::GetNodeAddr.new, 1, 2))
349
- @cond.signal
350
- end
351
- elsif (match = topic.match(%r{^#{Regexp.escape(@base_topic)}/(?<addr>\h{6})/(?<property>discover|label|down|up|stop|positionpulses|positionpercent|ip|wink|reset|(?<speed_type>upspeed|downspeed|slowspeed)|uplimit|downlimit|direction|ip(?<ip>\d+)(?<ip_type>pulses|percent)|groups)/set$}))
352
- addr = SDN::Message.parse_address(match[:addr])
353
- property = match[:property]
354
- # not homie compliant; allows linking the positionpercent property
355
- # directly to an OpenHAB rollershutter channel
356
- if property == 'positionpercent' && value =~ /^(?:UP|DOWN|STOP)$/i
357
- property = value.downcase
358
- value = "true"
359
- end
360
- mqtt_addr = SDN::Message.print_address(addr).gsub('.', '')
361
- motor = @motors[mqtt_addr]
362
- is_group = SDN::Message.is_group_address?(addr)
363
- group = @groups[mqtt_addr]
364
- follow_up = SDN::Message::GetMotorStatus.new(addr)
365
- message = case property
366
- when 'discover'
367
- follow_up = nil
368
- SDN::Message::GetNodeAddr.new(addr) if value == "true"
369
- when 'label'
370
- follow_up = SDN::Message::GetNodeLabel.new(addr)
371
- SDN::Message::SetNodeLabel.new(addr, value) unless is_group
372
- when 'stop'
373
- SDN::Message::Stop.new(addr) if value == "true"
374
- when 'up', 'down'
375
- SDN::Message::MoveTo.new(addr, "#{property}_limit".to_sym) if value == "true"
376
- when 'wink'
377
- SDN::Message::Wink.new(addr) if value == "true"
378
- when 'reset'
379
- next unless SDN::Message::SetFactoryDefault::RESET.keys.include?(value.to_sym)
380
- SDN::Message::SetFactoryDefault.new(addr, value.to_sym)
381
- when 'positionpulses', 'positionpercent', 'ip'
382
- SDN::Message::MoveTo.new(addr, property.sub('position', 'position_').to_sym, value.to_i)
383
- when 'direction'
384
- next if is_group
385
- follow_up = SDN::Message::GetMotorDirection.new(addr)
386
- next unless %w{standard reversed}.include?(value)
387
- SDN::Message::SetMotorDirection.new(addr, value.to_sym)
388
- when 'uplimit', 'downlimit'
389
- next if is_group
390
- if %w{delete current_position jog_ms jog_pulses}.include?(value)
391
- type = value.to_sym
392
- value = 10
393
- else
394
- type = :specified_position
395
- end
396
- target = property == 'uplimit' ? :up : :down
397
- follow_up = SDN::Message::GetMotorLimits.new(addr)
398
- SDN::Message::SetMotorLimits.new(addr, type, target, value.to_i)
399
- when /^ip\d(?:pulses|percent)$/
400
- next if is_group
401
- ip = match[:ip].to_i
402
- next unless (1..16).include?(ip)
403
- follow_up = SDN::Message::GetMotorIP.new(addr, ip)
404
- type = if value == 'delete'
405
- :delete
406
- elsif value == 'current_position'
407
- :current_position
408
- elsif match[:ip_type] == 'pulses'
409
- :position_pulses
410
- else
411
- :position_percent
412
- end
413
- SDN::Message::SetMotorIP.new(addr, type, ip, value.to_i)
414
- when 'upspeed', 'downspeed', 'slowspeed'
415
- next if is_group
416
- next unless motor
417
- follow_up = SDN::Message::GetMotorRollingSpeed.new(addr)
418
- message = SDN::Message::SetMotorRollingSpeed.new(addr,
419
- up_speed: motor.up_speed,
420
- down_speed: motor.down_speed,
421
- slow_speed: motor.slow_speed)
422
- message.send(:"#{property.sub('speed', '')}_speed=", value.to_i)
423
- message
424
- when 'groups'
425
- next if is_group
426
- next unless motor
427
- messages = motor.set_groups(value)
428
- @mutex.synchronize do
429
- messages.each { |m| @queues[0].push(MessageAndRetries.new(m, 5, 0)) }
430
- @cond.signal
431
- end
432
- nil
433
- end
434
-
435
- if motor
436
- motor.last_action = message.class if [Message::MoveTo, Message::Move, Message::Wink, Message::Stop].include?(message.class)
437
- end
438
-
439
- if message
440
- message.ack_requested = true if message.class.name !~ /^SDN::Message::Get/
441
- @mutex.synchronize do
442
- @queues[0].push(MessageAndRetries.new(message, 5, 0))
443
- @queues[1].push(MessageAndRetries.new(follow_up, 5, 1)) unless @queues[1].any? { |mr| mr.message == follow_up }
444
- @cond.signal
445
- end
446
- end
447
- end
448
- end
449
- end
450
-
451
- def publish(topic, value)
452
- @mqtt.publish("#{@base_topic}/#{topic}", value, true)
453
- end
454
-
455
- def subscribe(topic)
456
- @mqtt.subscribe("#{@base_topic}/#{topic}")
457
- end
458
-
459
- def enqueue(message, queue = :command)
460
- @mutex.synchronize do
461
- queue = instance_variable_get(:"#{@queue}_queue")
462
- unless queue.include?(message)
463
- queue.push(message)
464
- @cond.signal
465
- end
466
- end
467
- end
468
-
469
- def publish_basic_attributes
470
- publish("$homie", "v4.0.0")
471
- publish("$name", "Somfy SDN Network")
472
- publish("$state", "init")
473
- publish("$nodes", "discovery")
474
-
475
- publish("discovery/$name", "Discovery Node")
476
- publish("discovery/$type", "sdn")
477
- publish("discovery/$properties", "discover")
478
-
479
- publish("discovery/discover/$name", "Trigger Motor Discovery")
480
- publish("discovery/discover/$datatype", "boolean")
481
- publish("discovery/discover/$settable", "true")
482
- publish("discovery/discover/$retained", "false")
483
-
484
- subscribe("+/discover/set")
485
- subscribe("+/label/set")
486
- subscribe("+/down/set")
487
- subscribe("+/up/set")
488
- subscribe("+/stop/set")
489
- subscribe("+/positionpulses/set")
490
- subscribe("+/positionpercent/set")
491
- subscribe("+/ip/set")
492
- subscribe("+/wink/set")
493
- subscribe("+/reset/set")
494
- subscribe("+/direction/set")
495
- subscribe("+/upspeed/set")
496
- subscribe("+/downspeed/set")
497
- subscribe("+/slowspeed/set")
498
- subscribe("+/uplimit/set")
499
- subscribe("+/downlimit/set")
500
- subscribe("+/groups/set")
501
- (1..16).each do |ip|
502
- subscribe("+/ip#{ip}pulses/set")
503
- subscribe("+/ip#{ip}percent/set")
504
- end
505
-
506
- publish("$state", "ready")
507
- end
508
-
509
- def publish_motor(addr)
510
- publish("#{addr}/$name", addr)
511
- publish("#{addr}/$type", "Sonesse 30 Motor")
512
- publish("#{addr}/$properties", "discover,label,down,up,stop,positionpulses,positionpercent,ip,wink,reset,state,last_direction,last_action_source,last_action_cause,uplimit,downlimit,direction,upspeed,downspeed,slowspeed,#{(1..16).map { |ip| "ip#{ip}pulses,ip#{ip}percent" }.join(',')},groups")
513
-
514
- publish("#{addr}/discover/$name", "Trigger Motor Discovery")
515
- publish("#{addr}/discover/$datatype", "boolean")
516
- publish("#{addr}/discover/$settable", "true")
517
- publish("#{addr}/discover/$retained", "false")
518
-
519
- publish("#{addr}/label/$name", "Node label")
520
- publish("#{addr}/label/$datatype", "string")
521
- publish("#{addr}/label/$settable", "true")
522
-
523
- publish("#{addr}/down/$name", "Move in down direction")
524
- publish("#{addr}/down/$datatype", "boolean")
525
- publish("#{addr}/down/$settable", "true")
526
- publish("#{addr}/down/$retained", "false")
527
-
528
- publish("#{addr}/up/$name", "Move in up direction")
529
- publish("#{addr}/up/$datatype", "boolean")
530
- publish("#{addr}/up/$settable", "true")
531
- publish("#{addr}/up/$retained", "false")
532
-
533
- publish("#{addr}/stop/$name", "Cancel adjustments")
534
- publish("#{addr}/stop/$datatype", "boolean")
535
- publish("#{addr}/stop/$settable", "true")
536
- publish("#{addr}/stop/$retained", "false")
537
-
538
- publish("#{addr}/positionpulses/$name", "Position from up limit (in pulses)")
539
- publish("#{addr}/positionpulses/$datatype", "integer")
540
- publish("#{addr}/positionpulses/$format", "0:65535")
541
- publish("#{addr}/positionpulses/$unit", "pulses")
542
- publish("#{addr}/positionpulses/$settable", "true")
543
-
544
- publish("#{addr}/positionpercent/$name", "Position (in %)")
545
- publish("#{addr}/positionpercent/$datatype", "integer")
546
- publish("#{addr}/positionpercent/$format", "0:100")
547
- publish("#{addr}/positionpercent/$unit", "%")
548
- publish("#{addr}/positionpercent/$settable", "true")
549
-
550
- publish("#{addr}/ip/$name", "Intermediate Position")
551
- publish("#{addr}/ip/$datatype", "integer")
552
- publish("#{addr}/ip/$format", "1:16")
553
- publish("#{addr}/ip/$settable", "true")
554
-
555
- publish("#{addr}/wink/$name", "Feedback")
556
- publish("#{addr}/wink/$datatype", "boolean")
557
- publish("#{addr}/wink/$settable", "true")
558
- publish("#{addr}/wink/$retained", "false")
559
-
560
- publish("#{addr}/reset/$name", "Recall factory settings")
561
- publish("#{addr}/reset/$datatype", "enum")
562
- publish("#{addr}/reset/$format", SDN::Message::SetFactoryDefault::RESET.keys.join(','))
563
- publish("#{addr}/reset/$settable", "true")
564
- publish("#{addr}/reset/$retained", "false")
565
-
566
- publish("#{addr}/state/$name", "State of the motor")
567
- publish("#{addr}/state/$datatype", "enum")
568
- publish("#{addr}/state/$format", SDN::Message::PostMotorStatus::STATE.keys.join(','))
569
-
570
- publish("#{addr}/last_direction/$name", "Direction of last motion")
571
- publish("#{addr}/last_direction/$datatype", "enum")
572
- publish("#{addr}/last_direction/$format", SDN::Message::PostMotorStatus::DIRECTION.keys.join(','))
573
-
574
- publish("#{addr}/last_action_source/$name", "Source of last action")
575
- publish("#{addr}/last_action_source/$datatype", "enum")
576
- publish("#{addr}/last_action_source/$format", SDN::Message::PostMotorStatus::SOURCE.keys.join(','))
577
-
578
- publish("#{addr}/last_action_cause/$name", "Cause of last action")
579
- publish("#{addr}/last_action_cause/$datatype", "enum")
580
- publish("#{addr}/last_action_cause/$format", SDN::Message::PostMotorStatus::CAUSE.keys.join(','))
581
-
582
- publish("#{addr}/uplimit/$name", "Up limit (always = 0)")
583
- publish("#{addr}/uplimit/$datatype", "integer")
584
- publish("#{addr}/uplimit/$format", "0:65535")
585
- publish("#{addr}/uplimit/$unit", "pulses")
586
- publish("#{addr}/uplimit/$settable", "true")
587
-
588
- publish("#{addr}/downlimit/$name", "Down limit")
589
- publish("#{addr}/downlimit/$datatype", "integer")
590
- publish("#{addr}/downlimit/$format", "0:65535")
591
- publish("#{addr}/downlimit/$unit", "pulses")
592
- publish("#{addr}/downlimit/$settable", "true")
593
-
594
- publish("#{addr}/direction/$name", "Motor rotation direction")
595
- publish("#{addr}/direction/$datatype", "enum")
596
- publish("#{addr}/direction/$format", "standard,reversed")
597
- publish("#{addr}/direction/$settable", "true")
598
-
599
- publish("#{addr}/upspeed/$name", "Up speed")
600
- publish("#{addr}/upspeed/$datatype", "integer")
601
- publish("#{addr}/upspeed/$format", "6:28")
602
- publish("#{addr}/upspeed/$unit", "RPM")
603
- publish("#{addr}/upspeed/$settable", "true")
604
-
605
- publish("#{addr}/downspeed/$name", "Down speed, always = Up speed")
606
- publish("#{addr}/downspeed/$datatype", "integer")
607
- publish("#{addr}/downspeed/$format", "6:28")
608
- publish("#{addr}/downspeed/$unit", "RPM")
609
- publish("#{addr}/downspeed/$settable", "true")
610
-
611
- publish("#{addr}/slowspeed/$name", "Slow speed")
612
- publish("#{addr}/slowspeed/$datatype", "integer")
613
- publish("#{addr}/slowspeed/$format", "6:28")
614
- publish("#{addr}/slowspeed/$unit", "RPM")
615
- publish("#{addr}/slowspeed/$settable", "true")
616
-
617
- publish("#{addr}/groups/$name", "Group Memberships")
618
- publish("#{addr}/groups/$datatype", "string")
619
- publish("#{addr}/groups/$settable", "true")
620
-
621
- (1..16).each do |ip|
622
- publish("#{addr}/ip#{ip}pulses/$name", "Intermediate Position #{ip}")
623
- publish("#{addr}/ip#{ip}pulses/$datatype", "integer")
624
- publish("#{addr}/ip#{ip}pulses/$format", "0:65535")
625
- publish("#{addr}/ip#{ip}pulses/$unit", "pulses")
626
- publish("#{addr}/ip#{ip}pulses/$settable", "true")
627
-
628
- publish("#{addr}/ip#{ip}percent/$name", "Intermediate Position #{ip}")
629
- publish("#{addr}/ip#{ip}percent/$datatype", "integer")
630
- publish("#{addr}/ip#{ip}percent/$format", "0:100")
631
- publish("#{addr}/ip#{ip}percent/$unit", "%")
632
- publish("#{addr}/ip#{ip}percent/$settable", "true")
633
- end
634
-
635
- motor = Motor.new(self, addr)
636
- @motors[addr] = motor
637
- publish("$nodes", (["discovery"] + @motors.keys.sort + @groups.keys.sort).join(","))
638
-
639
- sdn_addr = SDN::Message.parse_address(addr)
640
- @mutex.synchronize do
641
- @queues[2].push(MessageAndRetries.new(SDN::Message::GetNodeLabel.new(sdn_addr), 5, 2))
642
- @queues[2].push(MessageAndRetries.new(SDN::Message::GetMotorStatus.new(sdn_addr), 5, 2))
643
- @queues[2].push(MessageAndRetries.new(SDN::Message::GetMotorLimits.new(sdn_addr), 5, 2))
644
- @queues[2].push(MessageAndRetries.new(SDN::Message::GetMotorDirection.new(sdn_addr), 5, 2))
645
- @queues[2].push(MessageAndRetries.new(SDN::Message::GetMotorRollingSpeed.new(sdn_addr), 5, 2))
646
- (1..16).each { |ip| @queues[2].push(MessageAndRetries.new(SDN::Message::GetMotorIP.new(sdn_addr, ip), 5, 2)) }
647
- (0...16).each { |g| @queues[2].push(MessageAndRetries.new(SDN::Message::GetGroupAddr.new(sdn_addr, g), 5, 2)) }
648
-
649
- @cond.signal
650
- end
651
-
652
- motor
653
- end
654
-
655
- def add_group(addr)
656
- addr = addr.gsub('.', '')
657
- group = @groups[addr]
658
- return group if group
659
-
660
- publish("#{addr}/$name", addr)
661
- publish("#{addr}/$type", "Shade Group")
662
- publish("#{addr}/$properties", "discover,down,up,stop,positionpulses,positionpercent,ip,wink,reset,state,motors")
663
-
664
- publish("#{addr}/discover/$name", "Trigger Motor Discovery")
665
- publish("#{addr}/discover/$datatype", "boolean")
666
- publish("#{addr}/discover/$settable", "true")
667
- publish("#{addr}/discover/$retained", "false")
668
-
669
- publish("#{addr}/down/$name", "Move in down direction")
670
- publish("#{addr}/down/$datatype", "boolean")
671
- publish("#{addr}/down/$settable", "true")
672
- publish("#{addr}/down/$retained", "false")
673
-
674
- publish("#{addr}/up/$name", "Move in up direction")
675
- publish("#{addr}/up/$datatype", "boolean")
676
- publish("#{addr}/up/$settable", "true")
677
- publish("#{addr}/up/$retained", "false")
678
-
679
- publish("#{addr}/stop/$name", "Cancel adjustments")
680
- publish("#{addr}/stop/$datatype", "boolean")
681
- publish("#{addr}/stop/$settable", "true")
682
- publish("#{addr}/stop/$retained", "false")
683
-
684
- publish("#{addr}/positionpulses/$name", "Position from up limit (in pulses)")
685
- publish("#{addr}/positionpulses/$datatype", "integer")
686
- publish("#{addr}/positionpulses/$format", "0:65535")
687
- publish("#{addr}/positionpulses/$unit", "pulses")
688
- publish("#{addr}/positionpulses/$settable", "true")
689
-
690
- publish("#{addr}/positionpercent/$name", "Position (in %)")
691
- publish("#{addr}/positionpercent/$datatype", "integer")
692
- publish("#{addr}/positionpercent/$format", "0:100")
693
- publish("#{addr}/positionpercent/$unit", "%")
694
- publish("#{addr}/positionpercent/$settable", "true")
695
-
696
- publish("#{addr}/ip/$name", "Intermediate Position")
697
- publish("#{addr}/ip/$datatype", "integer")
698
- publish("#{addr}/ip/$format", "1:16")
699
- publish("#{addr}/ip/$settable", "true")
700
-
701
- publish("#{addr}/wink/$name", "Feedback")
702
- publish("#{addr}/wink/$datatype", "boolean")
703
- publish("#{addr}/wink/$settable", "true")
704
- publish("#{addr}/wink/$retained", "false")
705
-
706
- publish("#{addr}/state/$name", "State of the motors; only set if all motors are in the same state")
707
- publish("#{addr}/state/$datatype", "enum")
708
- publish("#{addr}/state/$format", SDN::Message::PostMotorStatus::STATE.keys.join(','))
709
-
710
- publish("#{addr}/motors/$name", "Motors that are members of this group")
711
- publish("#{addr}/motors/$datatype", "string")
712
-
713
- group = @groups[addr] = Group.new(self, addr)
714
- publish("$nodes", (["discovery"] + @motors.keys.sort + @groups.keys.sort).join(","))
715
- group
716
- end
717
- end
718
- end