somfy_sdn 2.1.5 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
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