somfy_sdn 1.0.1 → 1.0.3

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: 4380614e85e00d1e41bd28809bbc2b1dc698f73cb1d5cb78f84c3e7a359674fc
4
- data.tar.gz: 87b3291290d811ccb52e20f4a0b773bd30dcab410a6421a4514f1494b9244487
3
+ metadata.gz: 61d3c1baa4f63612972ab6ba2cfc05aed5173176101a8d19e2e53756f2e4ec2b
4
+ data.tar.gz: 6c7999e7755a4d17b304da25d5b70e864a737efde7bb6b7506a309057fae0137
5
5
  SHA512:
6
- metadata.gz: 764b92020f3f77d99d23cbf4f376ea7c66104562eafa613655cbc8e0edcd4a2d276d1666925e96e2291765dd256f070ec5df6d10876f62bbffa94b52134dfbcd
7
- data.tar.gz: 46ebafb6663046bccb107083df5b88dc5046a997972aa66bb5a4360f4ef21a4dcab462522c7826d4de0d0061c9caea056e1229e45bad374f4f6e7c5f0ef5980a
6
+ metadata.gz: 75d5b1432d83f8713f81738e2ae99127be390465938f38274a026e8fe3c44cd45022cf74814e04a005c2a3b122b6735fb6424b0840cf8c1a82abff8a092a0a41
7
+ data.tar.gz: c253d1113a568f14209d2521ace015ccb8f7f0c1665f43dd5b64c29d601a5a163dc97c504c20d4a5c17e6c789cb9c51165f0108ba9e92dd8427677c48c252b51
data/lib/sdn/message.rb CHANGED
@@ -1,173 +1,172 @@
1
1
  require 'sdn/messages/helpers'
2
2
 
3
3
  module SDN
4
- class MalformedMessage < RuntimeError; end
5
-
6
- class Message
7
- class << self
8
- def readpartial(io, length, allow_empty: true)
9
- data = []
10
- while data.length < length
11
- begin
12
- data.concat(io.read_nonblock(length - data.length).bytes)
13
- rescue EOFError
14
- break
15
- rescue IO::WaitReadable
16
- break if allow_empty
17
- IO.select([io])
18
- end
19
- end
20
- data
21
- end
22
-
23
- def parse(io)
24
- io = StringIO.new(io) if io.is_a?(String)
25
- data = readpartial(io, 2, allow_empty: false)
26
- if data.length != 2
27
- # don't have enough data yet; buffer it
28
- io.ungetbyte(data.first) if data.length == 1
29
- raise MalformedMessage, "Could not get message type and length"
30
- end
31
- msg = to_number(data.first)
32
- length = to_number(data.last)
33
- ack_requested = length & 0x80 == 0x80
34
- length &= 0x7f
35
- if length < 11 || length > 32
36
- # only skip over one byte to try and re-sync
37
- io.ungetbyte(data.last)
38
- raise MalformedMessage, "Message has bogus length: #{length}"
39
- end
40
- data.concat(readpartial(io, length - 4))
41
- unless data.length == length - 2
42
- data.reverse.each { |byte| io.ungetbyte(byte) }
43
- raise MalformedMessage, "Missing data: got #{data.length} expected #{length}"
44
- end
45
-
46
- message_class = constants.find { |c| (const_get(c, false).const_get(:MSG, false) rescue nil) == msg }
47
- message_class = const_get(message_class, false) if message_class
48
- message_class ||= UnknownMessage
49
-
50
- bogus_checksums = [SetNodeLabel::MSG, PostNodeLabel::MSG].include?(msg)
51
-
52
- calculated_sum = checksum(data)
53
- read_sum = readpartial(io, 2)
54
- if read_sum.length == 0 || (!bogus_checksums && read_sum.length == 1)
55
- read_sum.each { |byte| io.ungetbyte(byte) }
56
- data.reverse.each { |byte| io.ungetbyte(byte) }
57
- raise MalformedMessage, "Missing data: got #{data.length} expected #{length}"
58
- end
59
-
60
- # check both the proper checksum, and a truncated checksum
61
- unless calculated_sum == read_sum || (bogus_checksums && calculated_sum.last == read_sum.first)
62
- raw_message = (data + read_sum).map { |b| '%02x' % b }.join(' ')
63
- # skip over single byte to try and re-sync
64
- data.shift
65
- read_sum.reverse.each { |byte| io.ungetbyte(byte) }
66
- data.reverse.each { |byte| io.ungetbyte(byte) }
67
- raise MalformedMessage, "Checksum mismatch for #{message_class.name}: #{raw_message}"
4
+ class MalformedMessage < RuntimeError; end
5
+
6
+ class Message
7
+ class << self
8
+ def readpartial(io, length, allow_empty: true)
9
+ data = []
10
+ while data.length < length
11
+ begin
12
+ data.concat(io.read_nonblock(length - data.length).bytes)
13
+ rescue EOFError
14
+ break
15
+ rescue IO::WaitReadable
16
+ break if allow_empty
17
+ IO.select([io])
68
18
  end
69
- # the checksum was truncated; put back the unused byte
70
- io.ungetbyte(read_sum.last) if calculated_sum != read_sum && read_sum.length == 2
71
-
72
- puts "read #{(data + read_sum).map { |b| '%02x' % b }.join(' ')}"
73
-
74
- reserved = to_number(data[2])
75
- src = transform_param(data[3..5])
76
- dest = transform_param(data[6..8])
77
- result = message_class.new(reserved: reserved, ack_requested: ack_requested, src: src, dest: dest)
78
- result.parse(data[9..-1])
79
- result.msg = msg if message_class == UnknownMessage
80
- result
81
19
  end
20
+ data
82
21
  end
83
-
84
- include Helpers
85
- singleton_class.include Helpers
86
-
87
- attr_reader :reserved, :ack_requested, :src, :dest
88
-
89
- def initialize(reserved: nil, ack_requested: false, src: nil, dest: nil)
90
- @reserved = reserved || 0x02 # message sent to Sonesse 30
91
- @ack_requested = ack_requested
92
- if src.nil? && is_group_address?(dest)
93
- src = dest
94
- dest = nil
22
+
23
+ def parse(io)
24
+ io = StringIO.new(io) if io.is_a?(String)
25
+ data = readpartial(io, 2, allow_empty: false)
26
+ if data.length != 2
27
+ # don't have enough data yet; buffer it
28
+ io.ungetbyte(data.first) if data.length == 1
29
+ raise MalformedMessage, "Could not get message type and length"
95
30
  end
96
- @src = src || [0, 0, 1]
97
- @dest = dest || [0, 0, 0]
98
- end
99
-
100
- def parse(params)
101
- raise MalformedMessage, "unrecognized params for #{self.class.name}: #{params.map { |b| '%02x' % b }}" if self.class.const_defined?(:PARAMS_LENGTH) && params.length != self.class.const_get(:PARAMS_LENGTH)
102
- end
103
-
104
- def serialize
105
- result = transform_param(reserved) + transform_param(src) + transform_param(dest) + params
106
- length = result.length + 4
107
- length |= 0x80 if ack_requested
108
- result = transform_param(self.class.const_get(:MSG)) + transform_param(length) + result
109
- result.concat(checksum(result))
110
- puts "wrote #{result.map { |b| '%02x' % b }.join(' ')}"
111
- result.pack("C*")
112
- end
113
-
114
- def inspect
115
- "#<%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]
116
- end
117
-
118
- def class_inspect
119
- ivars = instance_variables - [:@reserved, :@ack_requested, :@src, :@dest]
120
- return if ivars.empty?
121
- ivars.map { |iv| ", #{iv}=#{instance_variable_get(iv).inspect}" }.join
122
- end
123
-
124
- protected
125
-
126
- def params; []; end
127
-
128
- public
129
-
130
- class SimpleRequest < Message
131
- PARAMS_LENGTH = 0
132
-
133
- def initialize(dest = nil, **kwargs)
134
- kwargs[:dest] ||= dest
135
- super(**kwargs)
31
+ msg = to_number(data.first)
32
+ length = to_number(data.last)
33
+ ack_requested = length & 0x80 == 0x80
34
+ length &= 0x7f
35
+ if length < 11 || length > 32
36
+ # only skip over one byte to try and re-sync
37
+ io.ungetbyte(data.last)
38
+ raise MalformedMessage, "Message has bogus length: #{length}"
39
+ end
40
+ data.concat(readpartial(io, length - 4))
41
+ unless data.length == length - 2
42
+ data.reverse.each { |byte| io.ungetbyte(byte) }
43
+ raise MalformedMessage, "Missing data: got #{data.length} expected #{length}"
136
44
  end
137
- end
138
45
 
139
- class Nack < Message
140
- MSG = 0x6f
141
- PARAMS_LENGTH = 1
142
- VALUES = { data_error: 0x01, unknown_message: 0x10, node_is_locked: 0x20, wrong_position: 0x21, limits_not_set: 0x22, ip_not_set: 0x23, out_of_range: 0x24, busy: 0xff }
143
-
144
- # presumed
145
- attr_accessor :error_code
146
-
147
- def parse(params)
148
- super
149
- error_code = to_number(params[0])
150
- self.error_code = VALUES[error_code] || error_code
46
+ message_class = constants.find { |c| (const_get(c, false).const_get(:MSG, false) rescue nil) == msg }
47
+ message_class = const_get(message_class, false) if message_class
48
+ message_class ||= UnknownMessage
49
+
50
+ bogus_checksums = [SetNodeLabel::MSG, PostNodeLabel::MSG].include?(msg)
51
+
52
+ calculated_sum = checksum(data)
53
+ read_sum = readpartial(io, 2)
54
+ if read_sum.length == 0 || (!bogus_checksums && read_sum.length == 1)
55
+ read_sum.each { |byte| io.ungetbyte(byte) }
56
+ data.reverse.each { |byte| io.ungetbyte(byte) }
57
+ raise MalformedMessage, "Missing data: got #{data.length} expected #{length}"
151
58
  end
59
+
60
+ # check both the proper checksum, and a truncated checksum
61
+ unless calculated_sum == read_sum || (bogus_checksums && calculated_sum.last == read_sum.first)
62
+ raw_message = (data + read_sum).map { |b| '%02x' % b }.join(' ')
63
+ # skip over single byte to try and re-sync
64
+ data.shift
65
+ read_sum.reverse.each { |byte| io.ungetbyte(byte) }
66
+ data.reverse.each { |byte| io.ungetbyte(byte) }
67
+ raise MalformedMessage, "Checksum mismatch for #{message_class.name}: #{raw_message}"
68
+ end
69
+ # the checksum was truncated; put back the unused byte
70
+ io.ungetbyte(read_sum.last) if calculated_sum != read_sum && read_sum.length == 2
71
+
72
+ puts "read #{(data + read_sum).map { |b| '%02x' % b }.join(' ')}"
73
+
74
+ reserved = to_number(data[2])
75
+ src = transform_param(data[3..5])
76
+ dest = transform_param(data[6..8])
77
+ result = message_class.new(reserved: reserved, ack_requested: ack_requested, src: src, dest: dest)
78
+ result.parse(data[9..-1])
79
+ result.msg = msg if message_class == UnknownMessage
80
+ result
152
81
  end
153
-
154
- class Ack < Message
155
- MSG = 0x7f
156
- PARAMS_LENGTH = 0
82
+ end
83
+
84
+ include Helpers
85
+ singleton_class.include Helpers
86
+
87
+ attr_reader :reserved, :ack_requested, :src, :dest
88
+
89
+ def initialize(reserved: nil, ack_requested: false, src: nil, dest: nil)
90
+ @reserved = reserved || 0x02 # message sent to Sonesse 30
91
+ @ack_requested = ack_requested
92
+ if src.nil? && is_group_address?(dest)
93
+ src = dest
94
+ dest = nil
157
95
  end
96
+ @src = src || [0, 0, 1]
97
+ @dest = dest || [0, 0, 0]
98
+ end
158
99
 
159
- # messages after this point were decoded from UAI+ communication and may be named wrong
160
- class UnknownMessage < Message
161
- attr_accessor :msg, :params
162
-
163
- alias parse params=
164
-
165
- def class_inspect
166
- result = ", @msg=%02xh" % msg
167
- return result if params.empty?
168
-
169
- result << ", @params=#{params.map { |b| "%02x" % b }.join(' ')}"
170
- end
100
+ def parse(params)
101
+ raise MalformedMessage, "unrecognized params for #{self.class.name}: #{params.map { |b| '%02x' % b }}" if self.class.const_defined?(:PARAMS_LENGTH) && params.length != self.class.const_get(:PARAMS_LENGTH)
102
+ end
103
+
104
+ def serialize
105
+ result = transform_param(reserved) + transform_param(src) + transform_param(dest) + params
106
+ length = result.length + 4
107
+ length |= 0x80 if ack_requested
108
+ result = transform_param(self.class.const_get(:MSG)) + transform_param(length) + result
109
+ result.concat(checksum(result))
110
+ puts "wrote #{result.map { |b| '%02x' % b }.join(' ')}"
111
+ result.pack("C*")
112
+ end
113
+
114
+ def inspect
115
+ "#<%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]
116
+ end
117
+
118
+ def class_inspect
119
+ ivars = instance_variables - [:@reserved, :@ack_requested, :@src, :@dest]
120
+ return if ivars.empty?
121
+ ivars.map { |iv| ", #{iv}=#{instance_variable_get(iv).inspect}" }.join
122
+ end
123
+
124
+ protected
125
+
126
+ def params; []; end
127
+
128
+ public
129
+
130
+ class SimpleRequest < Message
131
+ PARAMS_LENGTH = 0
132
+
133
+ def initialize(dest = nil, **kwargs)
134
+ kwargs[:dest] ||= dest
135
+ super(**kwargs)
136
+ end
137
+ end
138
+
139
+ class Nack < Message
140
+ MSG = 0x6f
141
+ PARAMS_LENGTH = 1
142
+ VALUES = { data_error: 0x01, unknown_message: 0x10, node_is_locked: 0x20, wrong_position: 0x21, limits_not_set: 0x22, ip_not_set: 0x23, out_of_range: 0x24, busy: 0xff }
143
+
144
+ # presumed
145
+ attr_accessor :error_code
146
+
147
+ def parse(params)
148
+ super
149
+ error_code = to_number(params[0])
150
+ self.error_code = VALUES[error_code] || error_code
151
+ end
152
+ end
153
+
154
+ class Ack < Message
155
+ MSG = 0x7f
156
+ PARAMS_LENGTH = 0
157
+ end
158
+
159
+ # messages after this point were decoded from UAI+ communication and may be named wrong
160
+ class UnknownMessage < Message
161
+ attr_accessor :msg, :params
162
+
163
+ alias parse params=
164
+
165
+ def class_inspect
166
+ result = ", @msg=%02xh" % msg
167
+ return result if params.empty?
168
+
169
+ result << ", @params=#{params.map { |b| "%02x" % b }.join(' ')}"
171
170
  end
172
171
  end
173
172
  end
@@ -1,3 +1,7 @@
1
+ require 'mqtt'
2
+ require 'serialport'
3
+ require 'set'
4
+
1
5
  module SDN
2
6
  Motor = Struct.new(:bridge,
3
7
  :addr,
@@ -199,7 +203,7 @@ module SDN
199
203
  next unless SDN::Message::SetFactoryDefault::RESET.keys.include?(value.to_sym)
200
204
  SDN::Message::SetFactoryDefault.new(addr, value.to_sym)
201
205
  when 'positionpulses', 'positionpercent', 'ip'
202
- SDN::Message::MoveTo.new(addr, property.to_sym, value.to_i)
206
+ SDN::Message::MoveTo.new(addr, property.sub('position', 'position_').to_sym, value.to_i)
203
207
  when 'direction'
204
208
  next if is_group
205
209
  follow_up = SDN::Message::GetMotorDirection.new(addr)
data/lib/sdn/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module SDN
2
- VERSION = '1.0.1'
2
+ VERSION = '1.0.3'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: somfy_sdn
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cody Cutrer