somfy_sdn 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/sdn_mqtt_bridge +5 -0
- data/lib/sdn/message.rb +179 -0
- data/lib/sdn/messages/control.rb +154 -0
- data/lib/sdn/messages/get.rb +99 -0
- data/lib/sdn/messages/helpers.rb +51 -0
- data/lib/sdn/messages/post.rb +162 -0
- data/lib/sdn/messages/set.rb +230 -0
- data/lib/sdn/mqtt_bridge.rb +494 -0
- data/lib/sdn/version.rb +3 -0
- data/lib/somfy_sdn.rb +2 -0
- metadata +109 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 43e93c1bd91289025342d8fb230e4fc961a04001eb8a607a0114cf40c92459f3
|
4
|
+
data.tar.gz: 1f29a53d8e8f697b0b6933439d42d154e439992567abdcf6f216aa5d3e7dfed3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 13a0bb6280317801ea68d5a87e1fc2105f01fb6f44423cd4e7a9f553c04d2fb8a5d2009ae663b9f46889e7955c3eec6200330ba51abbd0005b4512b00b4041ca
|
7
|
+
data.tar.gz: e6bb95d108574b3ef836630721a22bf514a93d17470c6f25ecced7519c3cf646c6129121413e7f263d044b41a275f86f8ebc3050e83866b6b2e278ebc0df491e
|
data/bin/sdn_mqtt_bridge
ADDED
data/lib/sdn/message.rb
ADDED
@@ -0,0 +1,179 @@
|
|
1
|
+
require 'sdn/messages/helpers'
|
2
|
+
|
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}"
|
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
|
81
|
+
end
|
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
|
95
|
+
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)
|
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(' ')}"
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
require 'sdn/messages/control'
|
177
|
+
require 'sdn/messages/get'
|
178
|
+
require 'sdn/messages/post'
|
179
|
+
require 'sdn/messages/set'
|
@@ -0,0 +1,154 @@
|
|
1
|
+
module SDN
|
2
|
+
class Message
|
3
|
+
# Move in momentary mode
|
4
|
+
class Move < Message
|
5
|
+
MSG = 0x01
|
6
|
+
PARAMS_LENGTH = 3
|
7
|
+
DIRECTION = { down: 0x00, up: 0x01, cancel: 0x02 }.freeze
|
8
|
+
SPEED = { up: 0x00, down: 0x01, slow: 0x02 }.freeze
|
9
|
+
|
10
|
+
attr_reader :direction, :duration, :speed
|
11
|
+
|
12
|
+
def initialize(dest = nil, direction = :cancel, duration: nil, speed: :up, **kwargs)
|
13
|
+
kwargs[:dest] ||= dest
|
14
|
+
super(**kwargs)
|
15
|
+
self.direction = direction
|
16
|
+
self.duration = duration
|
17
|
+
self.speed = speed
|
18
|
+
end
|
19
|
+
|
20
|
+
def parse(params)
|
21
|
+
super
|
22
|
+
self.direction = DIRECTION.invert[to_number(params[0])]
|
23
|
+
duration = to_number(params[1])
|
24
|
+
duration = nil if duration == 0
|
25
|
+
self.duration = duration
|
26
|
+
self.speed = SPEED.invert[to_number(params[3])]
|
27
|
+
end
|
28
|
+
|
29
|
+
def direction=(value)
|
30
|
+
raise ArgumentError, "direction must be one of :down, :up, or :cancel" unless DIRECTION.keys.include?(value)
|
31
|
+
@direction = value
|
32
|
+
end
|
33
|
+
|
34
|
+
def duration=(value)
|
35
|
+
raise ArgumentError, "duration must be in range 0x0a to 0xff" unless value || value >= 0x0a && value <= 0xff
|
36
|
+
@duration = value
|
37
|
+
end
|
38
|
+
|
39
|
+
def speed=(value)
|
40
|
+
raise ArgumentError, "speed must be one of :up, :down, or :slow" unless SPEED.keys.include?(value)
|
41
|
+
@speed = speed
|
42
|
+
end
|
43
|
+
|
44
|
+
def params
|
45
|
+
transform_param(DIRECTION[direction]) +
|
46
|
+
transform_param(duration || 0) +
|
47
|
+
transform_param(SPEED[speed])
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Stop movement
|
52
|
+
class Stop < Message
|
53
|
+
MSG = 0x02
|
54
|
+
PARAMS_LENGTH = 1
|
55
|
+
|
56
|
+
def initialize(dest = nil, **kwargs)
|
57
|
+
kwargs[:dest] ||= dest
|
58
|
+
super(**kwargs)
|
59
|
+
end
|
60
|
+
|
61
|
+
def params
|
62
|
+
transform_param(0)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Move to absolute position
|
67
|
+
class MoveTo < Message
|
68
|
+
MSG = 0x03
|
69
|
+
PARAMS_LENGTH = 4
|
70
|
+
TARGET_TYPE = { down_limit: 0x00, up_limit: 0x01, ip: 0x02, position_pulses: 0x03, position_percent: 0x04 }.freeze
|
71
|
+
SPEED = { up: 0x00, down: 0x01, slow: 0x02 }.freeze
|
72
|
+
|
73
|
+
attr_reader :target_type, :target, :speed
|
74
|
+
|
75
|
+
def initialize(dest= nil, target_type = :down_limit, target = nil, speed = :up, **kwargs)
|
76
|
+
kwargs[:dest] ||= dest
|
77
|
+
super(**kwargs)
|
78
|
+
self.target_type = target_type
|
79
|
+
self.target = target
|
80
|
+
self.speed = speed
|
81
|
+
end
|
82
|
+
|
83
|
+
def parse(params)
|
84
|
+
super
|
85
|
+
self.target_type = TARGET_TYPE.invert[to_number(params[0])]
|
86
|
+
self.target = to_number(params[1..2], nillable: true)
|
87
|
+
self.speed = SPEED.invert[to_number(params[3])]
|
88
|
+
end
|
89
|
+
|
90
|
+
def target_type=(value)
|
91
|
+
raise ArgumentError, "target_type must be one of :down_limit, :up_limit, :ip, :position_pulses, or :position_percent" unless TARGET_TYPE.keys.include?(value)
|
92
|
+
@target_type = value
|
93
|
+
end
|
94
|
+
|
95
|
+
def target=(value)
|
96
|
+
value &= 0xffff if value
|
97
|
+
@target = value
|
98
|
+
end
|
99
|
+
|
100
|
+
def speed=(value)
|
101
|
+
raise ArgumentError, "speed must be one of :up, :down, or :slow" unless SPEED.keys.include?(value)
|
102
|
+
@speed = value
|
103
|
+
end
|
104
|
+
|
105
|
+
def params
|
106
|
+
transform_param(TARGET_TYPE[target_type]) + from_number(target || 0xffff, 2) + transform_param(SPEED[speed])
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Move relative to current position
|
111
|
+
class MoveOf < Message
|
112
|
+
MSG = 0x04
|
113
|
+
PARAMS_LENGTH = 4
|
114
|
+
TARGET_TYPE = { next_ip: 0x00, previous_ip: 0x01, jog_down_pulses: 0x02, jog_up_pulses: 0x03, jog_down_ms: 0x04, jog_up_ms: 0x05 }
|
115
|
+
|
116
|
+
attr_reader :target_type, :target
|
117
|
+
|
118
|
+
def initialize(dest = nil, target_type = nil, target = nil, **kwargs)
|
119
|
+
kwargs[:dest] ||= dest
|
120
|
+
super(**kwargs)
|
121
|
+
self.target_type = target_type
|
122
|
+
self.target = target
|
123
|
+
end
|
124
|
+
|
125
|
+
def parse(params)
|
126
|
+
super
|
127
|
+
self.target_type = TARGET_TYPE.invert[to_number(params[0])]
|
128
|
+
target = to_number(params[1..2], nillable: true)
|
129
|
+
target *= 10 if %I{jog_down_ms jog_up_ms}.include?(target_type)
|
130
|
+
self.target = target
|
131
|
+
end
|
132
|
+
|
133
|
+
def target_type=(value)
|
134
|
+
raise ArgumentError, "target_type must be one of :next_ip, :previous_ip, :jog_down_pulses, :jog_up_pulses, :jog_down_ms, :jog_up_ms" unless value.nil? || TARGET_TYPE.keys.include?(value)
|
135
|
+
@target_type = value
|
136
|
+
end
|
137
|
+
|
138
|
+
def target=(value)
|
139
|
+
value &= 0xffff if value
|
140
|
+
@target = value
|
141
|
+
end
|
142
|
+
|
143
|
+
def params
|
144
|
+
param = target || 0xffff
|
145
|
+
param /= 10 if %I{jog_down_ms jog_up_ms}.include?(target_type)
|
146
|
+
transform_param(TARGET_TYPE[target_type]) + from_number(param, 2) + transform_param(0)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
class Wink < SimpleRequest
|
151
|
+
MSG = 0x05
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module SDN
|
2
|
+
class Message
|
3
|
+
class GetMotorPosition < SimpleRequest
|
4
|
+
MSG = 0x0c
|
5
|
+
end
|
6
|
+
|
7
|
+
class GetMotorStatus < SimpleRequest
|
8
|
+
MSG = 0x0e
|
9
|
+
end
|
10
|
+
|
11
|
+
class GetMotorLimits < SimpleRequest
|
12
|
+
MSG = 0x21
|
13
|
+
end
|
14
|
+
|
15
|
+
class GetMotorDirection < SimpleRequest
|
16
|
+
MSG = 0x22
|
17
|
+
end
|
18
|
+
|
19
|
+
class GetMotorRollingSpeed < SimpleRequest
|
20
|
+
MSG = 0x23
|
21
|
+
end
|
22
|
+
|
23
|
+
class GetMotorIP < Message
|
24
|
+
MSG = 0x25
|
25
|
+
PARAMS_LENGTH = 1
|
26
|
+
|
27
|
+
attr_reader :ip
|
28
|
+
|
29
|
+
def initialize(dest = nil, ip = nil, **kwargs)
|
30
|
+
kwargs[:dest] ||= dest
|
31
|
+
super(**kwargs)
|
32
|
+
self.ip = ip
|
33
|
+
end
|
34
|
+
|
35
|
+
def parse(params)
|
36
|
+
super
|
37
|
+
self.ip = to_number(params[0], nillable: true)
|
38
|
+
end
|
39
|
+
|
40
|
+
def ip=(value)
|
41
|
+
raise ArgumentError, "invalid IP #{ip} (should be 1-16)" unless ip.nil? || (1..16).include?(ip)
|
42
|
+
@ip = value
|
43
|
+
end
|
44
|
+
|
45
|
+
def params
|
46
|
+
transform_param(@ip || 0xff)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class GetGroupAddr < Message
|
51
|
+
MSG = 0x41
|
52
|
+
PARAMS_LENGTH = 1
|
53
|
+
|
54
|
+
attr_reader :group_index
|
55
|
+
|
56
|
+
def initialize(dest = nil, group_index = 0, **kwargs)
|
57
|
+
kwargs[:dest] ||= dest
|
58
|
+
super(**kwargs)
|
59
|
+
self.group_index = group_index
|
60
|
+
end
|
61
|
+
|
62
|
+
def parse(params)
|
63
|
+
super
|
64
|
+
self.group_index = to_number(params[0])
|
65
|
+
end
|
66
|
+
|
67
|
+
def group_index=(value)
|
68
|
+
raise ArgumentError, "group_index is out of range" unless (0...16).include?(value)
|
69
|
+
@group_index = value
|
70
|
+
end
|
71
|
+
|
72
|
+
def params
|
73
|
+
transform_param(group_index)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class GetNodeLabel < SimpleRequest
|
78
|
+
MSG = 0x45
|
79
|
+
end
|
80
|
+
|
81
|
+
class GetNodeAddr < Message
|
82
|
+
MSG = 0x40
|
83
|
+
PARAMS_LENGTH = 0
|
84
|
+
|
85
|
+
def initialize(dest = [0xff, 0xff, 0xff], **kwargs)
|
86
|
+
kwargs[:dest] ||= dest
|
87
|
+
super(**kwargs)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class GetNodeSerialNumber < SimpleRequest
|
92
|
+
MSG = 0x4c
|
93
|
+
end
|
94
|
+
|
95
|
+
class GetNodeStackVersion < SimpleRequest
|
96
|
+
MSG = 0x70
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module SDN
|
2
|
+
class Message
|
3
|
+
module Helpers
|
4
|
+
def parse_address(addr_string)
|
5
|
+
addr_string.match(/^(\h{2})[:.]?(\h{2})[:.]?(\h{2})$/).captures.map { |byte| byte.to_i(16) }
|
6
|
+
end
|
7
|
+
|
8
|
+
def print_address(addr_bytes)
|
9
|
+
"%02X.%02X.%02X" % addr_bytes
|
10
|
+
end
|
11
|
+
|
12
|
+
def is_group_address?(addr_bytes)
|
13
|
+
addr_bytes[0..1] == [1, 1]
|
14
|
+
end
|
15
|
+
|
16
|
+
def transform_param(param)
|
17
|
+
Array(param).reverse.map { |byte| 0xff - byte }
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_number(param, nillable: false)
|
21
|
+
result = Array(param).reverse.inject(0) { |sum, byte| (sum << 8) + 0xff - byte }
|
22
|
+
result = nil if nillable && result == (1 << (8 * Array(param).length)) - 1
|
23
|
+
result
|
24
|
+
end
|
25
|
+
|
26
|
+
def from_number(number, bytes)
|
27
|
+
bytes.times.inject([]) do |res, _|
|
28
|
+
res << (0xff - number & 0xff)
|
29
|
+
number >>= 8
|
30
|
+
res
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_string(param)
|
35
|
+
chars = param.map { |b| 0xff - b }
|
36
|
+
chars[0..-1].pack("C*").sub(/\0+$/, '')
|
37
|
+
end
|
38
|
+
|
39
|
+
def from_string(string, bytes)
|
40
|
+
chars = string.bytes
|
41
|
+
chars = chars[0...16].fill(0, chars.length, bytes - chars.length)
|
42
|
+
chars.map { |b| 0xff - b }
|
43
|
+
end
|
44
|
+
|
45
|
+
def checksum(bytes)
|
46
|
+
result = bytes.sum
|
47
|
+
[result >> 8, result & 0xff]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
module SDN
|
2
|
+
class Message
|
3
|
+
class PostMotorPosition < Message
|
4
|
+
MSG = 0x0d
|
5
|
+
PARAMS_LENGTH = 5
|
6
|
+
|
7
|
+
attr_accessor :position_pulses, :position_percent, :ip
|
8
|
+
|
9
|
+
def parse(params)
|
10
|
+
super
|
11
|
+
self.position_pulses = to_number(params[0..1], nillable: true)
|
12
|
+
self.position_percent = to_number(params[2], nillable: true)
|
13
|
+
self.ip = to_number(params[4], nillable: true)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class PostMotorStatus < Message
|
18
|
+
MSG = 0x0f
|
19
|
+
PARAMS_LENGTH = 4
|
20
|
+
STATE = { stopped: 0x00, running: 0x01, blocked: 0x02, locked: 0x03 }.freeze
|
21
|
+
DIRECTION = { down: 0x00, up: 0x01 }.freeze
|
22
|
+
SOURCE = { internal: 0x00, network: 0x01, dct: 0x02 }.freeze
|
23
|
+
CAUSE = { target_reached: 0x00,
|
24
|
+
explicit_command: 0x01,
|
25
|
+
wink: 0x02,
|
26
|
+
limits_not_set: 0x10,
|
27
|
+
ip_not_set: 0x11,
|
28
|
+
polarity_not_checked: 0x12,
|
29
|
+
in_configuration_mode: 0x13,
|
30
|
+
obstacle_detection: 0x20,
|
31
|
+
over_current_protection: 0x21,
|
32
|
+
thermal_protection: 0x22 }.freeze
|
33
|
+
|
34
|
+
attr_accessor :state, :last_direction, :last_action_source, :last_action_cause
|
35
|
+
|
36
|
+
def parse(params)
|
37
|
+
super
|
38
|
+
self.state = STATE.invert[to_number(params[0])]
|
39
|
+
self.last_direction = DIRECTION.invert[to_number(params[1])]
|
40
|
+
self.last_action_source = SOURCE.invert[to_number(params[2])]
|
41
|
+
self.last_action_cause = CAUSE.invert[to_number(params[3])]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class PostMotorLimits < Message
|
46
|
+
MSG = 0x31
|
47
|
+
PARAMS_LENGTH = 4
|
48
|
+
|
49
|
+
attr_accessor :up_limit, :down_limit
|
50
|
+
|
51
|
+
def parse(params)
|
52
|
+
super
|
53
|
+
self.up_limit = to_number(params[0..1], nillable: true)
|
54
|
+
self.down_limit = to_number(params[2..3], nillable: true)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class PostMotorDirection < Message
|
59
|
+
MSG = 0x32
|
60
|
+
PARAMS_LENGTH = 1
|
61
|
+
DIRECTION = { standard: 0x00, reversed: 0x01 }.freeze
|
62
|
+
|
63
|
+
attr_accessor :direction
|
64
|
+
|
65
|
+
def parse(params)
|
66
|
+
super
|
67
|
+
self.direction = DIRECTION.invert[to_number(params[0])]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class PostMotorRollingSpeed < Message
|
72
|
+
MSG = 0x33
|
73
|
+
PARAMS_LENGTH = 6
|
74
|
+
|
75
|
+
attr_accessor :up_speed, :down_speed, :slow_speed
|
76
|
+
|
77
|
+
def parse(params)
|
78
|
+
super
|
79
|
+
self.up_speed = to_number(params[0])
|
80
|
+
self.down_speed = to_number(params[1])
|
81
|
+
self.slow_speed = to_number(params[2])
|
82
|
+
# 3 ignored params
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
class PostMotorIP < Message
|
87
|
+
MSG = 0x35
|
88
|
+
PARAMS_LENGTH = 4
|
89
|
+
|
90
|
+
attr_accessor :ip, :position_pulses, :position_percent
|
91
|
+
|
92
|
+
def parse(params)
|
93
|
+
super
|
94
|
+
self.ip = to_number(params[0])
|
95
|
+
self.position_pulses = to_number(params[1..2], nillable: true)
|
96
|
+
self.position_percent = to_number(params[3], nillable: true)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
class PostNodeAddr < Message
|
101
|
+
MSG = 0x60
|
102
|
+
PARAMS_LENGTH = 0
|
103
|
+
end
|
104
|
+
|
105
|
+
class PostGroupAddr < Message
|
106
|
+
MSG = 0x61
|
107
|
+
PARAMS_LENGTH = 4
|
108
|
+
|
109
|
+
attr_accessor :group_index, :group_address
|
110
|
+
|
111
|
+
def parse(params)
|
112
|
+
super
|
113
|
+
self.group_index = to_number(params[0])
|
114
|
+
self.group_address = transform_param(params[1..3])
|
115
|
+
self.group_address = nil if group_address == [0, 0, 0] || group_address == [0x01, 0x01, 0xff]
|
116
|
+
end
|
117
|
+
|
118
|
+
def class_inspect
|
119
|
+
", group_index=#{group_index.inspect}, group_address=#{group_address ? print_address(group_address) : 'nil'}"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
class PostNodeLabel < Message
|
124
|
+
MSG = 0x65
|
125
|
+
|
126
|
+
attr_accessor :label
|
127
|
+
|
128
|
+
def parse(params)
|
129
|
+
@label = to_string(params)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
class PostNodeSerialNumber < Message
|
134
|
+
MSG = 0x6c
|
135
|
+
|
136
|
+
# format is NNNNNNMMYYWW
|
137
|
+
# N = NodeID (address)
|
138
|
+
# M = Manufacturer ID
|
139
|
+
# Y = Year (last two digits)
|
140
|
+
# W = Week
|
141
|
+
attr_accessor :serial_number
|
142
|
+
|
143
|
+
def parse(params)
|
144
|
+
@serial_number = to_string(params)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
class PostNodeStackVersion < UnknownMessage
|
149
|
+
MSG = 0x71
|
150
|
+
|
151
|
+
attr_accessor :params
|
152
|
+
|
153
|
+
def parse(params)
|
154
|
+
# I don't know how to interpret this yet
|
155
|
+
# I get b6 bc b2 be fc fe, and UAI+ shows 5063497A3
|
156
|
+
super
|
157
|
+
end
|
158
|
+
|
159
|
+
def msg; MSG; end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|