somfy_sdn 1.0.7 → 1.0.12

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.
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: []