somfy_sdn 1.0.7 → 1.0.12

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4edace9439651dd89a2c7618a03ae5b6ac9d4ccdf8101ac597eca8f5e66bacf9
4
- data.tar.gz: 4ecefc5e15483f4b97169f3a0e8939297c1bdd4cdbe909a2388806feaa0bbac7
3
+ metadata.gz: b2c2926c376002fa9632188a5d467c64c2987e48f892975a1a725ec71f38d021
4
+ data.tar.gz: dcafff75ded7c5e5cccc65912179dd6fbf0c33f36655cf29adb6e7e4d796b7a9
5
5
  SHA512:
6
- metadata.gz: e515961e3b633fd7d02879b4d907d76a7bacde2e91f20ddb90893d676e88657f89f912dd645a7e32f9cc166641ae86b816f2dbe96ea1eb702922c51a3bc1b697
7
- data.tar.gz: 5825179cad7e2dd32af3a9bbca82cf706dbd0e38b10fe0a756241f8e28e954b414a37fe1a80fa0a84e0586f515b379d27a6b0ab013e9a77a71519924ae68e4a0
6
+ metadata.gz: 6fc1a34073e8069bd778575a1e5f8b3531b2bf5a769f3010d2ca93d4c4a2af07af0e26c7e1bb7a7d094d729e41dfb7031a5d569294d285fc82ab5f2b93afeebe
7
+ data.tar.gz: b61b3e69a01b4b0d709fa381ce0113184f6f5d32227ce3f72eee67c74bcf5e83ff8daa813ee69ef36e9fe76ffa9bc50c9d28eb76c403f1c1db370327ad505155
@@ -0,0 +1,6 @@
1
+ require 'sdn/message'
2
+ require 'sdn/mqtt_bridge'
3
+
4
+ module SDN
5
+ BROADCAST_ADDRESS = [0xff, 0xff, 0xff]
6
+ end
@@ -3,15 +3,23 @@ require 'sdn/messages/helpers'
3
3
  module SDN
4
4
  class MalformedMessage < RuntimeError; end
5
5
 
6
- class Message
6
+ class Message
7
7
  class << self
8
+ def expected_response
9
+ if name =~ /::Get([A-Za-z]+)/
10
+ const_get("Post#{$1}", true)
11
+ else
12
+ Ack
13
+ end
14
+ end
15
+
8
16
  def parse(data)
9
17
  offset = -1
10
18
  msg = length = ack_requested = message_class = nil
11
19
  # we loop here scanning for a valid message
12
20
  loop do
13
21
  offset += 1
14
- return nil if data.length - offset < 11
22
+ return [nil, 0] if data.length - offset < 11
15
23
  msg = to_number(data[offset])
16
24
  length = to_number(data[offset + 1])
17
25
  ack_requested = length & 0x80 == 0x80
@@ -39,9 +47,14 @@ module SDN
39
47
  reserved = to_number(data[offset + 2])
40
48
  src = transform_param(data.slice(offset + 3, 3))
41
49
  dest = transform_param(data.slice(offset + 6, 3))
42
- result = message_class.new(reserved: reserved, ack_requested: ack_requested, src: src, dest: dest)
43
- result.parse(data.slice(offset + 9, length - 11))
44
- result.msg = msg if message_class == UnknownMessage
50
+ begin
51
+ result = message_class.new(reserved: reserved, ack_requested: ack_requested, src: src, dest: dest)
52
+ result.parse(data.slice(offset + 9, length - 11))
53
+ result.msg = msg if message_class == UnknownMessage
54
+ rescue ArgumentError => e
55
+ puts "discarding illegal message #{e}"
56
+ result = nil
57
+ end
45
58
  [result, offset + length]
46
59
  end
47
60
  end
@@ -50,6 +63,7 @@ module SDN
50
63
  singleton_class.include Helpers
51
64
 
52
65
  attr_reader :reserved, :ack_requested, :src, :dest
66
+ attr_writer :ack_requested
53
67
 
54
68
  def initialize(reserved: nil, ack_requested: false, src: nil, dest: nil)
55
69
  @reserved = reserved || 0x02 # message sent to Sonesse 30
@@ -72,10 +86,13 @@ module SDN
72
86
  length |= 0x80 if ack_requested
73
87
  result = transform_param(self.class.const_get(:MSG)) + transform_param(length) + result
74
88
  result.concat(checksum(result))
75
- puts "wrote #{result.map { |b| '%02x' % b }.join(' ')}"
76
89
  result.pack("C*")
77
90
  end
78
91
 
92
+ def ==(other)
93
+ self.serialize == other.serialize
94
+ end
95
+
79
96
  def inspect
80
97
  "#<%s @reserved=%02xh, @ack_requested=%s, @src=%s, @dest=%s%s>" % [self.class.name, reserved, ack_requested, print_address(src), print_address(dest), class_inspect]
81
98
  end
@@ -27,17 +27,17 @@ module SDN
27
27
  end
28
28
 
29
29
  def direction=(value)
30
- raise ArgumentError, "direction must be one of :down, :up, or :cancel" unless DIRECTION.keys.include?(value)
30
+ raise ArgumentError, "direction must be one of :down, :up, or :cancel (#{value})" unless DIRECTION.keys.include?(value)
31
31
  @direction = value
32
32
  end
33
33
 
34
34
  def duration=(value)
35
- raise ArgumentError, "duration must be in range 0x0a to 0xff" unless value || value >= 0x0a && value <= 0xff
35
+ raise ArgumentError, "duration must be in range 0x0a to 0xff (#{value})" unless value && value >= 0x0a && value <= 0xff
36
36
  @duration = value
37
37
  end
38
38
 
39
39
  def speed=(value)
40
- raise ArgumentError, "speed must be one of :up, :down, or :slow" unless SPEED.keys.include?(value)
40
+ raise ArgumentError, "speed must be one of :up, :down, or :slow (#{value})" unless SPEED.keys.include?(value)
41
41
  @speed = speed
42
42
  end
43
43
 
@@ -0,0 +1,9 @@
1
+ module SDN
2
+ class Message
3
+ module ILT2
4
+ class GetMotorPosition < SimpleRequest
5
+ MSG = 0x44
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,18 @@
1
+ module SDN
2
+ class Message
3
+ module ILT2
4
+ class PostMotorPosition < Message
5
+ MSG = 0x64
6
+ PARAMS_LENGTH = 3
7
+
8
+ attr_accessor :position_pulses, :position_percent
9
+
10
+ def parse(params)
11
+ super
12
+ self.position_pulses = to_number(params[0..1])
13
+ self.position_percent = to_number(params[2]).to_f / 255 * 100
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,59 @@
1
+ module SDN
2
+ class Message
3
+ module ILT2
4
+ class SetMotorPosition < Message
5
+ MSG = 0x54
6
+ PARAMS_LENGTH = 3
7
+ TARGET_TYPE = {
8
+ up_limit: 1,
9
+ down_limit: 2,
10
+ stop: 3,
11
+ ip: 4,
12
+ next_ip_up: 5,
13
+ next_ip_down: 6,
14
+ jog_up: 10,
15
+ jog_down: 11,
16
+ position_percent: 16,
17
+ }.freeze
18
+
19
+ attr_reader :target_type, :target
20
+
21
+ def initialize(dest = nil, target_type = :up_limit, target = 0, **kwargs)
22
+ kwargs[:dest] ||= dest
23
+ super(**kwargs)
24
+ self.target_type = target_type
25
+ self.target = target
26
+ end
27
+
28
+ def parse(params)
29
+ super
30
+ self.target_type = TARGET_TYPE.invert[to_number(params[0])]
31
+ target = to_number(params[1..2])
32
+ if target_type == :position_percent
33
+ target = target.to_f / 255 * 100
34
+ end
35
+ self.target = target
36
+ end
37
+
38
+ def target_type=(value)
39
+ raise ArgumentError, "target_type must be one of :up_limit, :down_limit, :stop, :ip, :next_ip_up, :next_ip_down, :jog_up, :jog_down, or :position_percent" unless TARGET_TYPE.keys.include?(value)
40
+ @target_type = value
41
+ end
42
+
43
+ def target=(value)
44
+ if target_type == :position_percent && value
45
+ @target = [[0, value].max, 100].min
46
+ else
47
+ @target = value&. & 0xffff
48
+ end
49
+ end
50
+
51
+ def params
52
+ param = target
53
+ param = (param * 255 / 100).to_i if target_type == :position_percent
54
+ transform_param(TARGET_TYPE[target_type]) + from_number(param, 2)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -3,7 +3,9 @@ require 'uri'
3
3
  require 'set'
4
4
 
5
5
  module SDN
6
- Group = Struct.new(:bridge, :addr, :positionpercent, :state) do
6
+ MessageAndRetries = Struct.new(:message, :remaining_retries, :priority)
7
+
8
+ Group = Struct.new(:bridge, :addr, :positionpercent, :state, :motors) do
7
9
  def initialize(*)
8
10
  members.each { |k| self[k] = :nil }
9
11
  super
@@ -20,9 +22,13 @@ module SDN
20
22
  Message.print_address(Message.parse_address(addr))
21
23
  end
22
24
 
23
- def motors
25
+ def motor_objects
24
26
  bridge.motors.select { |addr, motor| motor.groups_string.include?(printed_addr) }.values
25
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
26
32
  end
27
33
 
28
34
  Motor = Struct.new(:bridge,
@@ -73,49 +79,51 @@ module SDN
73
79
  :ip15percent,
74
80
  :ip16pulses,
75
81
  :ip16percent,
76
- :groups) do
77
- def initialize(*)
78
- members.each { |k| self[k] = :nil }
79
- @groups = [].fill(nil, 0, 16)
80
- super
81
- end
82
-
83
- def publish(attribute, value)
84
- if self[attribute] != value
85
- bridge.publish("#{addr}/#{attribute}", value.to_s)
86
- self[attribute] = value
87
- end
88
- end
89
-
90
- def add_group(index, address)
91
- bridge.add_group(SDN::Message.print_address(address)) if address
92
- @groups[index] = address
93
- publish(:groups, groups_string)
94
- end
95
-
96
- def set_groups(groups)
97
- return unless groups =~ /^(?:\h{2}[:.]?\h{2}[:.]?\h{2}(?:,\h{2}[:.]?\h{2}[:.]?\h{2})*)?$/i
98
- groups = groups.split(',').sort.uniq.map { |g| SDN::Message.parse_address(g) }
99
- groups.fill(nil, groups.length, 16 - groups.length)
100
- messages = []
101
- sdn_addr = SDN::Message.parse_address(addr)
102
- groups.each_with_index do |g, i|
103
- if @groups[i] != g
104
- messages << SDN::Message::SetGroupAddr.new(sdn_addr, i, g)
105
- messages << SDN::Message::GetGroupAddr.new(sdn_addr, i)
106
- end
107
- end
108
- messages
109
- end
110
-
111
- def groups_string
112
- @groups.compact.map { |g| SDN::Message.print_address(g) }.sort.uniq.join(',')
113
- end
114
-
115
- def group_objects
116
- groups_string.split(',').map { |addr| bridge.groups[addr.gsub('.', '')] }
117
- end
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('.', '')] }
118
125
  end
126
+ end
119
127
 
120
128
  class MQTTBridge
121
129
  WAIT_TIME = 0.25
@@ -134,8 +142,7 @@ module SDN
134
142
 
135
143
  @mutex = Mutex.new
136
144
  @cond = ConditionVariable.new
137
- @command_queue = []
138
- @request_queue = []
145
+ @queues = [[], [], []]
139
146
  @response_pending = false
140
147
  @broadcast_pending = false
141
148
 
@@ -152,20 +159,23 @@ module SDN
152
159
  'baud' => 4800,
153
160
  'parity' => Net::Telnet::RFC2217::ODD)
154
161
  else
155
- require 'serialport'
156
- @sdn = SerialPort.open(port, "baud" => 4800, "parity" => SerialPort::ODD)
162
+ require 'ccutrer-serialport'
163
+ @sdn = CCutrer::SerialPort.new(port, baud: 4800, parity: :odd)
157
164
  end
158
165
 
159
166
  read_thread = Thread.new do
160
167
  buffer = ""
168
+
161
169
  loop do
162
170
  begin
163
171
  message, bytes_read = SDN::Message.parse(buffer.bytes)
172
+ # discard how much we read
173
+ buffer = buffer[bytes_read..-1]
164
174
  unless message
165
175
  begin
166
176
  buffer.concat(@sdn.read_nonblock(64 * 1024))
167
177
  next
168
- rescue IO::WaitReadable
178
+ rescue IO::WaitReadable, EOFError
169
179
  wait = buffer.empty? ? nil : WAIT_TIME
170
180
  if @sdn.wait_readable(wait).nil?
171
181
  # timed out; just discard everything
@@ -175,8 +185,6 @@ module SDN
175
185
  end
176
186
  next
177
187
  end
178
- # discard how much we read
179
- buffer = buffer[bytes_read..-1]
180
188
 
181
189
  src = SDN::Message.print_address(message.src)
182
190
  # ignore the UAI Plus and ourselves
@@ -197,7 +205,7 @@ module SDN
197
205
  motor.publish(:positionpulses, message.position_pulses)
198
206
  motor.publish(:ip, message.ip)
199
207
  motor.group_objects.each do |group|
200
- positions = group.motors.map(&:positionpercent)
208
+ positions = group.motor_objects.map(&:positionpercent)
201
209
  position = nil
202
210
  # calculate an average, but only if we know a position for
203
211
  # every shade
@@ -208,7 +216,12 @@ module SDN
208
216
  group.publish(:positionpercent, position)
209
217
  end
210
218
  when SDN::Message::PostMotorStatus
211
- if message.state == :running || motor.state == :running
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?))
212
225
  follow_ups << SDN::Message::GetMotorStatus.new(message.src)
213
226
  end
214
227
  # this will do one more position request after it stopped
@@ -218,7 +231,7 @@ module SDN
218
231
  motor.publish(:last_action_source, message.last_action_source)
219
232
  motor.publish(:last_action_cause, message.last_action_cause)
220
233
  motor.group_objects.each do |group|
221
- states = group.motors.map(&:state).uniq
234
+ states = group.motor_objects.map(&:state).uniq
222
235
  state = states.length == 1 ? states.first : 'mixed'
223
236
  group.publish(:state, state)
224
237
  end
@@ -232,16 +245,19 @@ module SDN
232
245
  motor.publish(:downspeed, message.down_speed)
233
246
  motor.publish(:slowspeed, message.slow_speed)
234
247
  when SDN::Message::PostMotorIP
235
- motor.publish(:"ip#{message.ip}pulses", message.position_pulses)
248
+ motor.publish(:"ip#{message.ip}pulses", message.position_pulses)
236
249
  motor.publish(:"ip#{message.ip}percent", message.position_percent)
237
250
  when SDN::Message::PostGroupAddr
238
251
  motor.add_group(message.group_index, message.group_address)
239
252
  end
240
253
 
241
254
  @mutex.synchronize do
242
- signal = @response_pending || !follow_ups.empty?
243
- @response_pending = @broadcast_pending
244
- @request_queue.concat(follow_ups)
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
+ @response_pending = @broadcast_pending if correct_response
258
+ follow_ups.each do |follow_up|
259
+ @queues[1].push(MessageAndRetries.new(follow_up, 5, 1)) unless @queues[1].any? { |mr| mr.message == follow_up }
260
+ end
245
261
  @cond.signal if signal
246
262
  end
247
263
  rescue EOFError
@@ -258,7 +274,7 @@ module SDN
258
274
  write_thread = Thread.new do
259
275
  begin
260
276
  loop do
261
- message = nil
277
+ message_and_retries = nil
262
278
  @mutex.synchronize do
263
279
  # got woken up early by another command getting queued; spin
264
280
  if @response_pending
@@ -268,33 +284,48 @@ module SDN
268
284
  puts "timed out waiting on response"
269
285
  @response_pending = nil
270
286
  @broadcast_pending = nil
287
+ if @prior_message&.remaining_retries != 0
288
+ puts "retrying #{@prior_message.remaining_retries} more times ..."
289
+ @queues[@prior_message.priority].push(@prior_message)
290
+ @prior_message = nil
291
+ end
271
292
  else
272
293
  @cond.wait(@mutex, remaining_wait)
273
294
  end
274
295
  end
275
296
  else
297
+ # minimum time between messages
276
298
  sleep 0.1
277
299
  end
278
300
 
279
- message = @command_queue.shift
280
- unless message
281
- message = @request_queue.shift
282
- if message
301
+ @queues.find { |q| message_and_retries = q.shift }
302
+ if message_and_retries
303
+ if message_and_retries.message.ack_requested || message_and_retries.message.class.name =~ /^SDN::Message::Get/
283
304
  @response_pending = Time.now.to_f + WAIT_TIME
284
- if message.dest == [0xff, 0xff, 0xff]
305
+ 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)
285
306
  @broadcast_pending = Time.now.to_f + BROADCAST_WAIT
286
- end
307
+ end
287
308
  end
288
309
  end
289
310
 
290
- # spin until there is a message
291
- @cond.wait(@mutex) unless message
311
+ # wait until there is a message
312
+ if @response_pending
313
+ message_and_retries.remaining_retries -= 1
314
+ @prior_message = message_and_retries
315
+ elsif message_and_retries
316
+ @prior_message = nil
317
+ else
318
+ @cond.wait(@mutex)
319
+ end
292
320
  end
293
- next unless message
321
+ next unless message_and_retries
294
322
 
323
+ message = message_and_retries.message
295
324
  puts "writing #{message.inspect}"
296
- @sdn.write(message.serialize)
297
- @sdn.flush
325
+ serialized = message.serialize
326
+ @sdn.write(serialized)
327
+ @sdn.flush if @sdn.respond_to?(:flush)
328
+ puts "wrote #{serialized.unpack("C*").map { |b| '%02x' % b }.join(' ')}"
298
329
  end
299
330
  rescue => e
300
331
  puts "failure writing: #{e}"
@@ -307,10 +338,10 @@ module SDN
307
338
  if topic == "#{@base_topic}/discovery/discover/set" && value == "true"
308
339
  # trigger discovery
309
340
  @mutex.synchronize do
310
- @request_queue.push(SDN::Message::GetNodeAddr.new)
341
+ @queues[2].push(MessageAndRetries.new(SDN::Message::GetNodeAddr.new, 1, 2))
311
342
  @cond.signal
312
343
  end
313
- elsif (match = topic.match(%r{^#{Regexp.escape(@base_topic)}/(?<addr>\h{6})/(?<property>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$}))
344
+ 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$}))
314
345
  addr = SDN::Message.parse_address(match[:addr])
315
346
  property = match[:property]
316
347
  # not homie compliant; allows linking the positionpercent property
@@ -319,10 +350,15 @@ module SDN
319
350
  property = value.downcase
320
351
  value = "true"
321
352
  end
322
- motor = @motors[SDN::Message.print_address(addr).gsub('.', '')]
353
+ mqtt_addr = SDN::Message.print_address(addr).gsub('.', '')
354
+ motor = @motors[mqtt_addr]
323
355
  is_group = SDN::Message.is_group_address?(addr)
356
+ group = @groups[mqtt_addr]
324
357
  follow_up = SDN::Message::GetMotorStatus.new(addr)
325
358
  message = case property
359
+ when 'discover'
360
+ follow_up = nil
361
+ SDN::Message::GetNodeAddr.new(addr) if value == "true"
326
362
  when 'label'
327
363
  follow_up = SDN::Message::GetNodeLabel.new(addr)
328
364
  SDN::Message::SetNodeLabel.new(addr, value) unless is_group
@@ -383,15 +419,21 @@ module SDN
383
419
  next unless motor
384
420
  messages = motor.set_groups(value)
385
421
  @mutex.synchronize do
386
- messages.each { |m| @command_queue.push(m) }
422
+ messages.each { |m| @queues[0].push(MessageAndRetries.new(m, 5, 0)) }
387
423
  @cond.signal
388
424
  end
389
425
  nil
390
426
  end
427
+
428
+ if motor
429
+ motor.last_action = message.class if [Message::MoveTo, Message::Move, Message::Wink, Message::Stop].include?(message.class)
430
+ end
431
+
391
432
  if message
433
+ message.ack_requested = true if message.class.name !~ /^SDN::Message::Get/
392
434
  @mutex.synchronize do
393
- @command_queue.push(message)
394
- @request_queue.push(follow_up) unless @request_queue.include?(follow_up)
435
+ @queues[0].push(MessageAndRetries.new(message, 5, 0))
436
+ @queues[1].push(MessageAndRetries.new(follow_up, 5, 1)) unless @queues[1].any? { |mr| mr.message == follow_up }
395
437
  @cond.signal
396
438
  end
397
439
  end
@@ -432,7 +474,7 @@ module SDN
432
474
  publish("discovery/discover/$settable", "true")
433
475
  publish("discovery/discover/$retained", "false")
434
476
 
435
- subscribe("discovery/discover/set")
477
+ subscribe("+/discover/set")
436
478
  subscribe("+/label/set")
437
479
  subscribe("+/down/set")
438
480
  subscribe("+/up/set")
@@ -460,7 +502,12 @@ module SDN
460
502
  def publish_motor(addr)
461
503
  publish("#{addr}/$name", addr)
462
504
  publish("#{addr}/$type", "Sonesse 30 Motor")
463
- publish("#{addr}/$properties", "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")
505
+ 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")
506
+
507
+ publish("#{addr}/discover/$name", "Trigger Motor Discovery")
508
+ publish("#{addr}/discover/$datatype", "boolean")
509
+ publish("#{addr}/discover/$settable", "true")
510
+ publish("#{addr}/discover/$retained", "false")
464
511
 
465
512
  publish("#{addr}/label/$name", "Node label")
466
513
  publish("#{addr}/label/$datatype", "string")
@@ -580,17 +627,17 @@ module SDN
580
627
 
581
628
  motor = Motor.new(self, addr)
582
629
  @motors[addr] = motor
583
- publish("$nodes", (["discovery"] + @motors.keys.sort + @groups.keys.sort.to_a).join(","))
630
+ publish("$nodes", (["discovery"] + @motors.keys.sort + @groups.keys.sort).join(","))
584
631
 
585
632
  sdn_addr = SDN::Message.parse_address(addr)
586
633
  @mutex.synchronize do
587
- @request_queue.push(SDN::Message::GetNodeLabel.new(sdn_addr))
588
- @request_queue.push(SDN::Message::GetMotorStatus.new(sdn_addr))
589
- @request_queue.push(SDN::Message::GetMotorLimits.new(sdn_addr))
590
- @request_queue.push(SDN::Message::GetMotorDirection.new(sdn_addr))
591
- @request_queue.push(SDN::Message::GetMotorRollingSpeed.new(sdn_addr))
592
- (1..16).each { |ip| @request_queue.push(SDN::Message::GetMotorIP.new(sdn_addr, ip)) }
593
- (0...16).each { |g| @request_queue.push(SDN::Message::GetGroupAddr.new(sdn_addr, g)) }
634
+ @queues[2].push(MessageAndRetries.new(SDN::Message::GetNodeLabel.new(sdn_addr), 5, 2))
635
+ @queues[2].push(MessageAndRetries.new(SDN::Message::GetMotorStatus.new(sdn_addr), 5, 2))
636
+ @queues[2].push(MessageAndRetries.new(SDN::Message::GetMotorLimits.new(sdn_addr), 5, 2))
637
+ @queues[2].push(MessageAndRetries.new(SDN::Message::GetMotorDirection.new(sdn_addr), 5, 2))
638
+ @queues[2].push(MessageAndRetries.new(SDN::Message::GetMotorRollingSpeed.new(sdn_addr), 5, 2))
639
+ (1..16).each { |ip| @queues[2].push(MessageAndRetries.new(SDN::Message::GetMotorIP.new(sdn_addr, ip), 5, 2)) }
640
+ (0...16).each { |g| @queues[2].push(MessageAndRetries.new(SDN::Message::GetGroupAddr.new(sdn_addr, g), 5, 2)) }
594
641
 
595
642
  @cond.signal
596
643
  end
@@ -600,11 +647,17 @@ module SDN
600
647
 
601
648
  def add_group(addr)
602
649
  addr = addr.gsub('.', '')
603
- return if @groups.key?(addr)
650
+ group = @groups[addr]
651
+ return group if group
604
652
 
605
653
  publish("#{addr}/$name", addr)
606
654
  publish("#{addr}/$type", "Shade Group")
607
- publish("#{addr}/$properties", "down,up,stop,positionpulses,positionpercent,ip,wink,reset")
655
+ publish("#{addr}/$properties", "discover,down,up,stop,positionpulses,positionpercent,ip,wink,reset,state,motors")
656
+
657
+ publish("#{addr}/discover/$name", "Trigger Motor Discovery")
658
+ publish("#{addr}/discover/$datatype", "boolean")
659
+ publish("#{addr}/discover/$settable", "true")
660
+ publish("#{addr}/discover/$retained", "false")
608
661
 
609
662
  publish("#{addr}/down/$name", "Move in down direction")
610
663
  publish("#{addr}/down/$datatype", "boolean")
@@ -619,7 +672,7 @@ module SDN
619
672
  publish("#{addr}/stop/$name", "Cancel adjustments")
620
673
  publish("#{addr}/stop/$datatype", "boolean")
621
674
  publish("#{addr}/stop/$settable", "true")
622
- publish("#{addr}/stop/$retained", "false")
675
+ publish("#{addr}/stop/$retained", "false")
623
676
 
624
677
  publish("#{addr}/positionpulses/$name", "Position from up limit (in pulses)")
625
678
  publish("#{addr}/positionpulses/$datatype", "integer")
@@ -647,8 +700,12 @@ module SDN
647
700
  publish("#{addr}/state/$datatype", "enum")
648
701
  publish("#{addr}/state/$format", SDN::Message::PostMotorStatus::STATE.keys.join(','))
649
702
 
650
- @groups[addr] = Group.new(self, addr)
651
- publish("$nodes", (["discovery"] + @motors.keys.sort + @groups.to_a).join(","))
703
+ publish("#{addr}/motors/$name", "Motors that are members of this group")
704
+ publish("#{addr}/motors/$datatype", "string")
705
+
706
+ group = @groups[addr] = Group.new(self, addr)
707
+ publish("$nodes", (["discovery"] + @motors.keys.sort + @groups.keys.sort).join(","))
708
+ group
652
709
  end
653
710
  end
654
711
  end
@@ -1,3 +1,3 @@
1
1
  module SDN
2
- VERSION = '1.0.7'
2
+ VERSION = '1.0.12'
3
3
  end
@@ -1,2 +1 @@
1
- require 'sdn/message'
2
- require 'sdn/mqtt_bridge'
1
+ require 'sdn'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: somfy_sdn
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.7
4
+ version: 1.0.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cody Cutrer
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-05-15 00:00:00.000000000 Z
11
+ date: 2020-11-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mqtt
@@ -30,28 +30,28 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 0.0.2
33
+ version: 0.0.3
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 0.0.2
40
+ version: 0.0.3
41
41
  - !ruby/object:Gem::Dependency
42
- name: serialport
42
+ name: ccutrer-serialport
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 1.3.1
47
+ version: 1.0.0
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: 1.3.1
54
+ version: 1.0.0
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: byebug
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -80,7 +80,7 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: '13.0'
83
- description:
83
+ description:
84
84
  email: cody@cutrer.com'
85
85
  executables:
86
86
  - sdn_mqtt_bridge
@@ -88,10 +88,14 @@ extensions: []
88
88
  extra_rdoc_files: []
89
89
  files:
90
90
  - bin/sdn_mqtt_bridge
91
+ - lib/sdn.rb
91
92
  - lib/sdn/message.rb
92
93
  - lib/sdn/messages/control.rb
93
94
  - lib/sdn/messages/get.rb
94
95
  - lib/sdn/messages/helpers.rb
96
+ - lib/sdn/messages/ilt2/get.rb
97
+ - lib/sdn/messages/ilt2/post.rb
98
+ - lib/sdn/messages/ilt2/set.rb
95
99
  - lib/sdn/messages/post.rb
96
100
  - lib/sdn/messages/set.rb
97
101
  - lib/sdn/mqtt_bridge.rb
@@ -101,7 +105,7 @@ homepage: https://github.com/ccutrer/somfy_sdn
101
105
  licenses:
102
106
  - MIT
103
107
  metadata: {}
104
- post_install_message:
108
+ post_install_message:
105
109
  rdoc_options: []
106
110
  require_paths:
107
111
  - lib
@@ -116,8 +120,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
116
120
  - !ruby/object:Gem::Version
117
121
  version: '0'
118
122
  requirements: []
119
- rubygems_version: 3.0.3
120
- signing_key:
123
+ rubygems_version: 3.1.4
124
+ signing_key:
121
125
  specification_version: 4
122
126
  summary: Library for communication with Somfy SDN RS-485 motorized shades
123
127
  test_files: []