somfy_sdn 1.0.1 → 1.0.3

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: 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