somfy_sdn 1.0.0
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 +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
|