somfy_sdn 1.0.7 → 1.0.8

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: 66516a3bb03695b73075a496a791502683ec12052219a5cf040cfe6e9f82b04f
4
+ data.tar.gz: bad8e87688534b118e679b4126490f5eab3c6e83ee7288184368e852bf0cc450
5
5
  SHA512:
6
- metadata.gz: e515961e3b633fd7d02879b4d907d76a7bacde2e91f20ddb90893d676e88657f89f912dd645a7e32f9cc166641ae86b816f2dbe96ea1eb702922c51a3bc1b697
7
- data.tar.gz: 5825179cad7e2dd32af3a9bbca82cf706dbd0e38b10fe0a756241f8e28e954b414a37fe1a80fa0a84e0586f515b379d27a6b0ab013e9a77a71519924ae68e4a0
6
+ metadata.gz: db18252c03d784aeb3461dacffc16dae04aeb176f5b98f3b796d395f95980350576075a434794e16347eef7bfb308eccf7e4307abeceb4675254a3505d74b60d
7
+ data.tar.gz: e17af388921091abe51a23b6fcef7c3bf0d402ad55446bfc86aa698482a22cca341f7612b73d04e97ad91e060ff13f7573016e4ddb7aca24002c69f1ee9706b9
@@ -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,7 +3,7 @@ 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
8
  def parse(data)
9
9
  offset = -1
@@ -11,7 +11,7 @@ module SDN
11
11
  # we loop here scanning for a valid message
12
12
  loop do
13
13
  offset += 1
14
- return nil if data.length - offset < 11
14
+ return [nil, 0] if data.length - offset < 11
15
15
  msg = to_number(data[offset])
16
16
  length = to_number(data[offset + 1])
17
17
  ack_requested = length & 0x80 == 0x80
@@ -39,9 +39,14 @@ module SDN
39
39
  reserved = to_number(data[offset + 2])
40
40
  src = transform_param(data.slice(offset + 3, 3))
41
41
  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
42
+ begin
43
+ result = message_class.new(reserved: reserved, ack_requested: ack_requested, src: src, dest: dest)
44
+ result.parse(data.slice(offset + 9, length - 11))
45
+ result.msg = msg if message_class == UnknownMessage
46
+ rescue ArgumentError => e
47
+ puts "discarding illegal message #{e}"
48
+ result = nil
49
+ end
45
50
  [result, offset + length]
46
51
  end
47
52
  end
@@ -72,10 +77,13 @@ module SDN
72
77
  length |= 0x80 if ack_requested
73
78
  result = transform_param(self.class.const_get(:MSG)) + transform_param(length) + result
74
79
  result.concat(checksum(result))
75
- puts "wrote #{result.map { |b| '%02x' % b }.join(' ')}"
76
80
  result.pack("C*")
77
81
  end
78
82
 
83
+ def ==(other)
84
+ self.serialize == other.serialize
85
+ end
86
+
79
87
  def inspect
80
88
  "#<%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
89
  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,7 @@ require 'uri'
3
3
  require 'set'
4
4
 
5
5
  module SDN
6
- Group = Struct.new(:bridge, :addr, :positionpercent, :state) do
6
+ Group = Struct.new(:bridge, :addr, :positionpercent, :state, :motors) do
7
7
  def initialize(*)
8
8
  members.each { |k| self[k] = :nil }
9
9
  super
@@ -20,9 +20,13 @@ module SDN
20
20
  Message.print_address(Message.parse_address(addr))
21
21
  end
22
22
 
23
- def motors
23
+ def motor_objects
24
24
  bridge.motors.select { |addr, motor| motor.groups_string.include?(printed_addr) }.values
25
25
  end
26
+
27
+ def motors_string
28
+ motor_objects.map { |m| SDN::Message.print_address(SDN::Message.parse_address(m.addr)) }.sort.join(',')
29
+ end
26
30
  end
27
31
 
28
32
  Motor = Struct.new(:bridge,
@@ -74,49 +78,50 @@ module SDN
74
78
  :ip16pulses,
75
79
  :ip16percent,
76
80
  :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
81
+ def initialize(*)
82
+ members.each { |k| self[k] = :nil }
83
+ @groups = [].fill(nil, 0, 16)
84
+ super
85
+ end
86
+
87
+ def publish(attribute, value)
88
+ if self[attribute] != value
89
+ bridge.publish("#{addr}/#{attribute}", value.to_s)
90
+ self[attribute] = value
91
+ end
92
+ end
93
+
94
+ def add_group(index, address)
95
+ group = bridge.add_group(SDN::Message.print_address(address)) if address
96
+ @groups[index] = address
97
+ group&.publish(:motors, group.motors_string)
98
+ publish(:groups, groups_string)
99
+ end
100
+
101
+ def set_groups(groups)
102
+ return unless groups =~ /^(?:\h{2}[:.]?\h{2}[:.]?\h{2}(?:,\h{2}[:.]?\h{2}[:.]?\h{2})*)?$/i
103
+ groups = groups.split(',').sort.uniq.map { |g| SDN::Message.parse_address(g) }.select { |g| SDN::Message.is_group_address?(g) }
104
+ groups.fill(nil, groups.length, 16 - groups.length)
105
+ messages = []
106
+ sdn_addr = SDN::Message.parse_address(addr)
107
+ groups.each_with_index do |g, i|
108
+ if @groups[i] != g
109
+ messages << SDN::Message::SetGroupAddr.new(sdn_addr, i, g)
110
+ messages << SDN::Message::GetGroupAddr.new(sdn_addr, i)
111
+ end
112
+ end
113
+ messages
118
114
  end
119
115
 
116
+ def groups_string
117
+ @groups.compact.map { |g| SDN::Message.print_address(g) }.sort.uniq.join(',')
118
+ end
119
+
120
+ def group_objects
121
+ groups_string.split(',').map { |addr| bridge.groups[addr.gsub('.', '')] }
122
+ end
123
+ end
124
+
120
125
  class MQTTBridge
121
126
  WAIT_TIME = 0.25
122
127
  BROADCAST_WAIT = 5.0
@@ -161,6 +166,8 @@ module SDN
161
166
  loop do
162
167
  begin
163
168
  message, bytes_read = SDN::Message.parse(buffer.bytes)
169
+ # discard how much we read
170
+ buffer = buffer[bytes_read..-1]
164
171
  unless message
165
172
  begin
166
173
  buffer.concat(@sdn.read_nonblock(64 * 1024))
@@ -175,8 +182,6 @@ module SDN
175
182
  end
176
183
  next
177
184
  end
178
- # discard how much we read
179
- buffer = buffer[bytes_read..-1]
180
185
 
181
186
  src = SDN::Message.print_address(message.src)
182
187
  # ignore the UAI Plus and ourselves
@@ -197,7 +202,7 @@ module SDN
197
202
  motor.publish(:positionpulses, message.position_pulses)
198
203
  motor.publish(:ip, message.ip)
199
204
  motor.group_objects.each do |group|
200
- positions = group.motors.map(&:positionpercent)
205
+ positions = group.motor_objects.map(&:positionpercent)
201
206
  position = nil
202
207
  # calculate an average, but only if we know a position for
203
208
  # every shade
@@ -218,7 +223,7 @@ module SDN
218
223
  motor.publish(:last_action_source, message.last_action_source)
219
224
  motor.publish(:last_action_cause, message.last_action_cause)
220
225
  motor.group_objects.each do |group|
221
- states = group.motors.map(&:state).uniq
226
+ states = group.motor_objects.map(&:state).uniq
222
227
  state = states.length == 1 ? states.first : 'mixed'
223
228
  group.publish(:state, state)
224
229
  end
@@ -232,7 +237,7 @@ module SDN
232
237
  motor.publish(:downspeed, message.down_speed)
233
238
  motor.publish(:slowspeed, message.slow_speed)
234
239
  when SDN::Message::PostMotorIP
235
- motor.publish(:"ip#{message.ip}pulses", message.position_pulses)
240
+ motor.publish(:"ip#{message.ip}pulses", message.position_pulses)
236
241
  motor.publish(:"ip#{message.ip}percent", message.position_percent)
237
242
  when SDN::Message::PostGroupAddr
238
243
  motor.add_group(message.group_index, message.group_address)
@@ -241,7 +246,9 @@ module SDN
241
246
  @mutex.synchronize do
242
247
  signal = @response_pending || !follow_ups.empty?
243
248
  @response_pending = @broadcast_pending
244
- @request_queue.concat(follow_ups)
249
+ follow_ups.each do |follow_up|
250
+ @request_queue.push(follow_up) unless @request_queue.include?(follow_up)
251
+ end
245
252
  @cond.signal if signal
246
253
  end
247
254
  rescue EOFError
@@ -281,9 +288,9 @@ module SDN
281
288
  message = @request_queue.shift
282
289
  if message
283
290
  @response_pending = Time.now.to_f + WAIT_TIME
284
- if message.dest == [0xff, 0xff, 0xff]
291
+ if message.dest == BROADCAST_ADDRESS || SDN::Message::is_group_address?(message.src) && message.is_a?(SDN::Message::GetNodeAddr)
285
292
  @broadcast_pending = Time.now.to_f + BROADCAST_WAIT
286
- end
293
+ end
287
294
  end
288
295
  end
289
296
 
@@ -293,8 +300,10 @@ module SDN
293
300
  next unless message
294
301
 
295
302
  puts "writing #{message.inspect}"
296
- @sdn.write(message.serialize)
303
+ serialized = message.serialize
304
+ @sdn.write(serialized)
297
305
  @sdn.flush
306
+ puts "wrote #{serialized.unpack("C*").map { |b| '%02x' % b }.join(' ')}"
298
307
  end
299
308
  rescue => e
300
309
  puts "failure writing: #{e}"
@@ -310,7 +319,7 @@ module SDN
310
319
  @request_queue.push(SDN::Message::GetNodeAddr.new)
311
320
  @cond.signal
312
321
  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$}))
322
+ 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
323
  addr = SDN::Message.parse_address(match[:addr])
315
324
  property = match[:property]
316
325
  # not homie compliant; allows linking the positionpercent property
@@ -319,10 +328,15 @@ module SDN
319
328
  property = value.downcase
320
329
  value = "true"
321
330
  end
322
- motor = @motors[SDN::Message.print_address(addr).gsub('.', '')]
331
+ mqtt_addr = SDN::Message.print_address(addr).gsub('.', '')
332
+ motor = @motors[mqtt_addr]
323
333
  is_group = SDN::Message.is_group_address?(addr)
334
+ group = @groups[mqtt_addr]
324
335
  follow_up = SDN::Message::GetMotorStatus.new(addr)
325
336
  message = case property
337
+ when 'discover'
338
+ follow_up = nil
339
+ SDN::Message::GetNodeAddr.new(addr) if value == "true"
326
340
  when 'label'
327
341
  follow_up = SDN::Message::GetNodeLabel.new(addr)
328
342
  SDN::Message::SetNodeLabel.new(addr, value) unless is_group
@@ -432,7 +446,7 @@ module SDN
432
446
  publish("discovery/discover/$settable", "true")
433
447
  publish("discovery/discover/$retained", "false")
434
448
 
435
- subscribe("discovery/discover/set")
449
+ subscribe("+/discover/set")
436
450
  subscribe("+/label/set")
437
451
  subscribe("+/down/set")
438
452
  subscribe("+/up/set")
@@ -460,7 +474,12 @@ module SDN
460
474
  def publish_motor(addr)
461
475
  publish("#{addr}/$name", addr)
462
476
  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")
477
+ 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")
478
+
479
+ publish("#{addr}/discover/$name", "Trigger Motor Discovery")
480
+ publish("#{addr}/discover/$datatype", "boolean")
481
+ publish("#{addr}/discover/$settable", "true")
482
+ publish("#{addr}/discover/$retained", "false")
464
483
 
465
484
  publish("#{addr}/label/$name", "Node label")
466
485
  publish("#{addr}/label/$datatype", "string")
@@ -580,7 +599,7 @@ module SDN
580
599
 
581
600
  motor = Motor.new(self, addr)
582
601
  @motors[addr] = motor
583
- publish("$nodes", (["discovery"] + @motors.keys.sort + @groups.keys.sort.to_a).join(","))
602
+ publish("$nodes", (["discovery"] + @motors.keys.sort + @groups.keys.sort).join(","))
584
603
 
585
604
  sdn_addr = SDN::Message.parse_address(addr)
586
605
  @mutex.synchronize do
@@ -600,11 +619,17 @@ module SDN
600
619
 
601
620
  def add_group(addr)
602
621
  addr = addr.gsub('.', '')
603
- return if @groups.key?(addr)
622
+ group = @groups[addr]
623
+ return group if group
604
624
 
605
625
  publish("#{addr}/$name", addr)
606
626
  publish("#{addr}/$type", "Shade Group")
607
- publish("#{addr}/$properties", "down,up,stop,positionpulses,positionpercent,ip,wink,reset")
627
+ publish("#{addr}/$properties", "discover,down,up,stop,positionpulses,positionpercent,ip,wink,reset,state,motors")
628
+
629
+ publish("#{addr}/discover/$name", "Trigger Motor Discovery")
630
+ publish("#{addr}/discover/$datatype", "boolean")
631
+ publish("#{addr}/discover/$settable", "true")
632
+ publish("#{addr}/discover/$retained", "false")
608
633
 
609
634
  publish("#{addr}/down/$name", "Move in down direction")
610
635
  publish("#{addr}/down/$datatype", "boolean")
@@ -619,7 +644,7 @@ module SDN
619
644
  publish("#{addr}/stop/$name", "Cancel adjustments")
620
645
  publish("#{addr}/stop/$datatype", "boolean")
621
646
  publish("#{addr}/stop/$settable", "true")
622
- publish("#{addr}/stop/$retained", "false")
647
+ publish("#{addr}/stop/$retained", "false")
623
648
 
624
649
  publish("#{addr}/positionpulses/$name", "Position from up limit (in pulses)")
625
650
  publish("#{addr}/positionpulses/$datatype", "integer")
@@ -647,8 +672,12 @@ module SDN
647
672
  publish("#{addr}/state/$datatype", "enum")
648
673
  publish("#{addr}/state/$format", SDN::Message::PostMotorStatus::STATE.keys.join(','))
649
674
 
650
- @groups[addr] = Group.new(self, addr)
651
- publish("$nodes", (["discovery"] + @motors.keys.sort + @groups.to_a).join(","))
675
+ publish("#{addr}/motors/$name", "Motors that are members of this group")
676
+ publish("#{addr}/motors/$datatype", "string")
677
+
678
+ group = @groups[addr] = Group.new(self, addr)
679
+ publish("$nodes", (["discovery"] + @motors.keys.sort + @groups.keys.sort).join(","))
680
+ group
652
681
  end
653
682
  end
654
683
  end
@@ -1,3 +1,3 @@
1
1
  module SDN
2
- VERSION = '1.0.7'
2
+ VERSION = '1.0.8'
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.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cody Cutrer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-05-15 00:00:00.000000000 Z
11
+ date: 2020-07-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mqtt
@@ -30,14 +30,14 @@ 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
42
  name: serialport
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -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