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.
data/lib/sdn/cli/mqtt.rb CHANGED
@@ -1,12 +1,15 @@
1
- require 'mqtt'
2
- require 'uri'
3
- require 'set'
1
+ # frozen_string_literal: true
4
2
 
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'
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(port, mqtt_uri, device_id: "somfy", base_topic: "homie", auto_discover: true)
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
- @queues = [[], [], []]
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 = Client.new(port)
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
- read_thread = Thread.new { read }
50
- write_thread = Thread.new { write }
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, queue = 0)
75
+ def enqueue(message)
63
76
  @mutex.synchronize do
64
- queue = @queues[queue]
65
- unless queue.include?(message)
66
- queue.push(message)
67
- @cond.signal
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
- while !@mqtt.queue_empty?
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
- } + (1..16).map { |ip| ["ip#{ip}-pulses", "ip#{ip}-percent"] }.flatten
164
+ ] + (1..16).map { |ip| ["ip#{ip}-pulses", "ip#{ip}-percent"] }.flatten
153
165
 
154
166
  unless node_type == :st50ilt2
155
- properties.concat %w{
156
- reset
157
- last-action-source
158
- last-action-cause
159
- up-limit
160
- direction
161
- up-speed
162
- down-speed
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
- @queues[2].push(MessageAndRetries.new(Message::GetNodeLabel.new(sdn_addr), 5, 2))
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
- @queues[2].push(MessageAndRetries.new(Message::GetMotorStatus.new(sdn_addr), 5, 2))
305
- @queues[2].push(MessageAndRetries.new(Message::GetMotorLimits.new(sdn_addr), 5, 2))
306
- @queues[2].push(MessageAndRetries.new(Message::GetMotorDirection.new(sdn_addr), 5, 2))
307
- @queues[2].push(MessageAndRetries.new(Message::GetMotorRollingSpeed.new(sdn_addr), 5, 2))
308
- (1..16).each { |ip| @queues[2].push(MessageAndRetries.new(Message::GetMotorIP.new(sdn_addr, ip), 5, 2)) }
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
- @queues[2].push(MessageAndRetries.new(Message::ILT2::GetMotorSettings.new(sdn_addr), 5, 2))
311
- @queues[2].push(MessageAndRetries.new(Message::ILT2::GetMotorPosition.new(sdn_addr), 5, 2))
312
- (1..16).each { |ip| @queues[2].push(MessageAndRetries.new(Message::ILT2::GetMotorIP.new(sdn_addr, ip), 5, 2)) }
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| @queues[2].push(MessageAndRetries.new(Message::GetGroupAddr.new(sdn_addr, g), 5, 2)) }
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).gsub('.', '')]
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.gsub('.', '')
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", "discover,control,jog-ms,jog-pulses,position-pulses,position-percent,ip,reset,state,last-direction,motors")
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(',') + ",mixed")
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(',') + ",mixed")
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")
@@ -1,12 +1,14 @@
1
- require 'curses'
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(port, addr = nil)
9
- @sdn = Client.new(port)
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}'; what would you like to change it to (blank to leave alone)? "
30
- new_label = STDIN.gets
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, reversed_int, @limit + @pulse_count - @pos, @pulse_count))
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
- sdn.ensure(Message::ILT2::SetMotorPosition.new(addr, :jog_up_pulses, @pulse_count))
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
- sdn.ensure(Message::ILT2::SetMotorPosition.new(addr, :jog_down_pulses, @pulse_count))
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 'u'
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 'l'
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 'r'
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 'R'
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 'q'
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(<<-INSTRUCTIONS)
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
- INSTRUCTIONS
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