somfy_sdn 1.0.12 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/somfy_sdn +60 -0
- data/lib/sdn.rb +16 -1
- data/lib/sdn/cli/mqtt.rb +369 -0
- data/lib/sdn/cli/mqtt/group.rb +31 -0
- data/lib/sdn/cli/mqtt/motor.rb +103 -0
- data/lib/sdn/cli/mqtt/read.rb +154 -0
- data/lib/sdn/cli/mqtt/subscriptions.rb +156 -0
- data/lib/sdn/cli/mqtt/write.rb +93 -0
- data/lib/sdn/cli/provisioner.rb +234 -0
- data/lib/sdn/cli/simulator.rb +197 -0
- data/lib/sdn/client.rb +84 -0
- data/lib/sdn/message.rb +81 -30
- data/lib/sdn/{messages → message}/control.rb +79 -38
- data/lib/sdn/{messages → message}/get.rb +48 -40
- data/lib/sdn/{messages → message}/helpers.rb +33 -3
- data/lib/sdn/message/ilt2/get.rb +48 -0
- data/lib/sdn/message/ilt2/master_control.rb +35 -0
- data/lib/sdn/message/ilt2/post.rb +127 -0
- data/lib/sdn/message/ilt2/set.rb +192 -0
- data/lib/sdn/{messages → message}/post.rb +127 -61
- data/lib/sdn/{messages → message}/set.rb +99 -84
- data/lib/sdn/version.rb +1 -1
- metadata +53 -16
- data/bin/sdn_mqtt_bridge +0 -5
- data/lib/sdn/messages/ilt2/get.rb +0 -9
- data/lib/sdn/messages/ilt2/post.rb +0 -18
- data/lib/sdn/messages/ilt2/set.rb +0 -59
- data/lib/sdn/mqtt_bridge.rb +0 -711
@@ -13,6 +13,34 @@ module SDN
|
|
13
13
|
addr_bytes[0..1] == [1, 1]
|
14
14
|
end
|
15
15
|
|
16
|
+
def node_type_from_number(number)
|
17
|
+
case number
|
18
|
+
when 1; :st50ilt2
|
19
|
+
when 2; :st30
|
20
|
+
when 6; :glydea
|
21
|
+
when 7; :st50ac
|
22
|
+
when 8; :st50dc
|
23
|
+
when 0x70; :lt50
|
24
|
+
else; number
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def node_type_to_number(type)
|
29
|
+
case type
|
30
|
+
when :st50ilt2; 1
|
31
|
+
when :st30; 2
|
32
|
+
when :glydea; 6
|
33
|
+
when :st50ac; 7
|
34
|
+
when :st50dc; 8
|
35
|
+
when :lt50; 0x70
|
36
|
+
else; type
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def node_type_to_string(type)
|
41
|
+
type.is_a?(Integer) ? "%02xh" % type : type.inspect
|
42
|
+
end
|
43
|
+
|
16
44
|
def transform_param(param)
|
17
45
|
Array(param).reverse.map { |byte| 0xff - byte }
|
18
46
|
end
|
@@ -23,7 +51,9 @@ module SDN
|
|
23
51
|
result
|
24
52
|
end
|
25
53
|
|
26
|
-
def from_number(number, bytes)
|
54
|
+
def from_number(number, bytes = 1)
|
55
|
+
number ||= 1 ** (bytes * 8) - 1
|
56
|
+
number = number.to_i
|
27
57
|
bytes.times.inject([]) do |res, _|
|
28
58
|
res << (0xff - number & 0xff)
|
29
59
|
number >>= 8
|
@@ -33,12 +63,12 @@ module SDN
|
|
33
63
|
|
34
64
|
def to_string(param)
|
35
65
|
chars = param.map { |b| 0xff - b }
|
36
|
-
chars
|
66
|
+
chars.pack("C*").sub(/\0+$/, '').strip
|
37
67
|
end
|
38
68
|
|
39
69
|
def from_string(string, bytes)
|
40
70
|
chars = string.bytes
|
41
|
-
chars = chars[0...
|
71
|
+
chars = chars[0...bytes].fill(' '.ord, chars.length, bytes - chars.length)
|
42
72
|
chars.map { |b| 0xff - b }
|
43
73
|
end
|
44
74
|
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module SDN
|
2
|
+
class Message
|
3
|
+
module ILT2
|
4
|
+
class GetIRConfig < SimpleRequest
|
5
|
+
MSG = 0x49
|
6
|
+
end
|
7
|
+
|
8
|
+
class GetLockStatus < SimpleRequest
|
9
|
+
MSG = 0x4b
|
10
|
+
end
|
11
|
+
|
12
|
+
class GetMotorIP < Message
|
13
|
+
MSG = 0x43
|
14
|
+
PARAMS_LENGTH = 1
|
15
|
+
|
16
|
+
attr_reader :ip
|
17
|
+
|
18
|
+
def initialize(dest = nil, ip = 1, **kwargs)
|
19
|
+
kwargs[:dest] ||= dest
|
20
|
+
super(**kwargs)
|
21
|
+
self.ip = ip
|
22
|
+
end
|
23
|
+
|
24
|
+
def ip=(value)
|
25
|
+
raise ArgumentError, "invalid IP #{value} (should be 1-16)" unless (1..16).include?(value)
|
26
|
+
@ip = value
|
27
|
+
end
|
28
|
+
|
29
|
+
def parse(params)
|
30
|
+
super
|
31
|
+
self.ip = to_number(params[0]) + 1
|
32
|
+
end
|
33
|
+
|
34
|
+
def params
|
35
|
+
transform_param(@ip - 1)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class GetMotorPosition < SimpleRequest
|
40
|
+
MSG = 0x44
|
41
|
+
end
|
42
|
+
|
43
|
+
class GetMotorSettings < SimpleRequest
|
44
|
+
MSG = 0x42
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module SDN
|
2
|
+
class Message
|
3
|
+
module ILT2
|
4
|
+
class MasterControl
|
5
|
+
class << self
|
6
|
+
include Helpers
|
7
|
+
|
8
|
+
def parse(data)
|
9
|
+
return unless data.length >= 5
|
10
|
+
return unless checksum(data[0..2]) == data[3..4]
|
11
|
+
# no clue what's special about these
|
12
|
+
return unless data[0..1] == [0xfa, 0x7a]
|
13
|
+
klass = case data[2]
|
14
|
+
when 0x00; Down
|
15
|
+
when 0xfa; Up
|
16
|
+
when 0xff; Stop
|
17
|
+
end
|
18
|
+
return unless klass
|
19
|
+
[klass.new, 5]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Down < MasterControl
|
24
|
+
end
|
25
|
+
|
26
|
+
class Stop < MasterControl
|
27
|
+
end
|
28
|
+
|
29
|
+
class Up < MasterControl
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
module SDN
|
2
|
+
class Message
|
3
|
+
module ILT2
|
4
|
+
class PostIRConfig < Message
|
5
|
+
MSG = 0x69
|
6
|
+
PARAMS_LENGTH = 1
|
7
|
+
|
8
|
+
attr_reader :channels
|
9
|
+
|
10
|
+
def initialize(channels = nil, **kwargs)
|
11
|
+
super(**kwargs)
|
12
|
+
self.channels = channels
|
13
|
+
end
|
14
|
+
|
15
|
+
def channels=(value)
|
16
|
+
@channels = value &. & 0xff
|
17
|
+
end
|
18
|
+
|
19
|
+
def parse(params)
|
20
|
+
super
|
21
|
+
self.channels = to_number(params[0])
|
22
|
+
end
|
23
|
+
|
24
|
+
def params
|
25
|
+
transform_param(channels)
|
26
|
+
end
|
27
|
+
|
28
|
+
def class_inspect
|
29
|
+
", @channels=#{channels.chr.unpack('b8').first}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class PostLockStatus < Message
|
34
|
+
MSG = 0x6B
|
35
|
+
PARAMS_LENGTH = 1
|
36
|
+
|
37
|
+
attr_accessor :priority # 0 for not locked
|
38
|
+
|
39
|
+
def initialize(priority = nil, **kwargs)
|
40
|
+
super(**kwargs)
|
41
|
+
self.priority = priority
|
42
|
+
end
|
43
|
+
|
44
|
+
def parse(params)
|
45
|
+
super
|
46
|
+
self.current_lock_priority = to_number(params[0])
|
47
|
+
end
|
48
|
+
|
49
|
+
def params
|
50
|
+
transform_param(priority)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class PostMotorIP < Message
|
55
|
+
MSG = 0x63
|
56
|
+
PARAMS_LENGTH = 3
|
57
|
+
|
58
|
+
attr_accessor :ip, :position_pulses
|
59
|
+
|
60
|
+
def initialize(ip = nil, position_pulses = nil, **kwargs)
|
61
|
+
super(**kwargs)
|
62
|
+
self.ip = ip
|
63
|
+
self.position_pulses = position_pulses
|
64
|
+
end
|
65
|
+
|
66
|
+
def parse(params)
|
67
|
+
super
|
68
|
+
self.ip = to_number(params[0]) + 1
|
69
|
+
self.position_pulses = to_number(params[1..2], nillable: true)
|
70
|
+
end
|
71
|
+
|
72
|
+
def params
|
73
|
+
transform_param(ip - 1) + from_number(position_pulses, 2)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class PostMotorPosition < Message
|
78
|
+
MSG = 0x64
|
79
|
+
PARAMS_LENGTH = 3
|
80
|
+
|
81
|
+
attr_accessor :position_pulses, :position_percent
|
82
|
+
|
83
|
+
def initialize(position_pulses = nil, position_percent = nil, **kwargs)
|
84
|
+
super(**kwargs)
|
85
|
+
self.position_pulses = position_pulses
|
86
|
+
self.position_percent = position_percent
|
87
|
+
end
|
88
|
+
|
89
|
+
def parse(params)
|
90
|
+
super
|
91
|
+
self.position_pulses = to_number(params[0..1])
|
92
|
+
self.position_percent = to_number(params[2]).to_f / 255 * 100
|
93
|
+
end
|
94
|
+
|
95
|
+
def params
|
96
|
+
from_number(position_pulses, 2) +
|
97
|
+
from_number(position_percent && position_percent * 255 / 100)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
class PostMotorSettings < UnknownMessage
|
102
|
+
MSG = 0x62
|
103
|
+
PARAMS_LENGTH = 3
|
104
|
+
|
105
|
+
attr_accessor :limit
|
106
|
+
|
107
|
+
def initialize(limit = nil, **kwargs)
|
108
|
+
super(**kwargs)
|
109
|
+
self.limit = limit
|
110
|
+
end
|
111
|
+
|
112
|
+
def parse(params)
|
113
|
+
super
|
114
|
+
self.limit = to_number(params[1..2])
|
115
|
+
end
|
116
|
+
|
117
|
+
def params
|
118
|
+
transform_param(0) + from_number(limit, 2)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
class PostNodeLabel < ::SDN::Message::PostNodeLabel
|
123
|
+
MAX_LENGTH = 32
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,192 @@
|
|
1
|
+
module SDN
|
2
|
+
class Message
|
3
|
+
module ILT2
|
4
|
+
class SetIRConfig < PostIRConfig
|
5
|
+
MSG = 0x59
|
6
|
+
|
7
|
+
def initialize(dest = nil, channels = nil, **kwargs)
|
8
|
+
kwargs[:dest] ||= dest
|
9
|
+
super(channels, **kwargs)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class SetLockStatus < Message
|
14
|
+
MSG = 0x5B
|
15
|
+
PARAMS_LENGTH = 3
|
16
|
+
TARGET_TYPE = {
|
17
|
+
current: 0,
|
18
|
+
up_limit: 1,
|
19
|
+
down_limit: 2,
|
20
|
+
ip: 4,
|
21
|
+
unlock: 5
|
22
|
+
}
|
23
|
+
|
24
|
+
# when target_type is down_limit, target is number of 10ms intervals it's still allowed to roll up
|
25
|
+
attr_reader :target_type, :target, :priority
|
26
|
+
|
27
|
+
def initialize(dest = nil, target_type = :unlock, target = nil, priority = 1, **kwargs)
|
28
|
+
kwargs[:dest] ||= dest
|
29
|
+
super(**kwargs)
|
30
|
+
self.target_type = target_type
|
31
|
+
self.target = target
|
32
|
+
self.priority = priority
|
33
|
+
end
|
34
|
+
|
35
|
+
def parse(params)
|
36
|
+
super
|
37
|
+
self.target_type = TARGET_TYPE.invert[to_number(params[0])]
|
38
|
+
self.target = to_number(params[1])
|
39
|
+
self.priority = to_number(params[2])
|
40
|
+
end
|
41
|
+
|
42
|
+
def target_type=(value)
|
43
|
+
raise ArgumentError, "target_type must be one of :current, :up_limit, :down_limit, :ip, or :unlock" unless TARGET_TYPE.keys.include?(value)
|
44
|
+
@target_type = value
|
45
|
+
end
|
46
|
+
|
47
|
+
def target=(value)
|
48
|
+
@target = value&. & 0xff
|
49
|
+
end
|
50
|
+
|
51
|
+
def priority=(value)
|
52
|
+
raise ArgumentError, "priority must be between 1 and 100" unless (1..100).include?(value)
|
53
|
+
@priority = value
|
54
|
+
end
|
55
|
+
|
56
|
+
def params
|
57
|
+
transform_param(TARGET_TYPE[target_type]) + transform_param(target) + transform_param(priority)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class SetMotorIP < Message
|
62
|
+
MSG = 0x53
|
63
|
+
PARAMS_LENGTH = 3
|
64
|
+
|
65
|
+
attr_reader :ip, :value
|
66
|
+
|
67
|
+
def initialize(dest = nil, ip = 1, value = nil, **kwargs)
|
68
|
+
kwargs[:dest] ||= dest
|
69
|
+
super(**kwargs)
|
70
|
+
self.ip = ip
|
71
|
+
self.value = value
|
72
|
+
end
|
73
|
+
|
74
|
+
def parse(params)
|
75
|
+
super
|
76
|
+
self.ip = to_number(params[0]) + 1
|
77
|
+
self.value = to_number(params[1..2], nillable: true)
|
78
|
+
end
|
79
|
+
|
80
|
+
def ip=(value)
|
81
|
+
raise ArgumentError, "ip must be in range 1..16 or nil" unless ip.nil? || (1..16).include?(ip)
|
82
|
+
@ip = value
|
83
|
+
end
|
84
|
+
|
85
|
+
def value=(value)
|
86
|
+
@value = value &. & 0xffff
|
87
|
+
end
|
88
|
+
|
89
|
+
def params
|
90
|
+
transform_param(ip - 1) + from_number(value, 2)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
class SetMotorLimits < UnknownMessage
|
95
|
+
MSG = 0x5C
|
96
|
+
end
|
97
|
+
|
98
|
+
class SetMotorPosition < Message
|
99
|
+
MSG = 0x54
|
100
|
+
PARAMS_LENGTH = 3
|
101
|
+
TARGET_TYPE = {
|
102
|
+
up_limit: 1,
|
103
|
+
down_limit: 2,
|
104
|
+
stop: 3,
|
105
|
+
ip: 4,
|
106
|
+
next_ip_up: 5,
|
107
|
+
next_ip_down: 6,
|
108
|
+
position_pulses: 8,
|
109
|
+
jog_up_ms: 10,
|
110
|
+
jog_down_ms: 11,
|
111
|
+
jog_up_pulses: 12,
|
112
|
+
jog_down_pulses: 13,
|
113
|
+
position_percent: 16,
|
114
|
+
}.freeze
|
115
|
+
|
116
|
+
attr_reader :target_type, :target
|
117
|
+
|
118
|
+
def initialize(dest = nil, target_type = :up_limit, target = 0, **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])
|
129
|
+
if target_type == :position_percent
|
130
|
+
target = target.to_f / 255 * 100
|
131
|
+
end
|
132
|
+
if target_type == :ip
|
133
|
+
target += 1
|
134
|
+
end
|
135
|
+
self.target = target
|
136
|
+
end
|
137
|
+
|
138
|
+
def target_type=(value)
|
139
|
+
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)
|
140
|
+
@target_type = value
|
141
|
+
end
|
142
|
+
|
143
|
+
def target=(value)
|
144
|
+
if target_type == :position_percent && value
|
145
|
+
@target = [[0, value].max, 100].min
|
146
|
+
else
|
147
|
+
@target = value&. & 0xffff
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def params
|
152
|
+
param = target
|
153
|
+
param = (param * 255 / 100).to_i if target_type == :position_percent
|
154
|
+
param -= 1 if target_type == :ip
|
155
|
+
transform_param(TARGET_TYPE[target_type]) + from_number(param, 2)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# the motor does not move, and just stores the new values
|
160
|
+
# flags of 1 is reverse direction, but you have to set it every time
|
161
|
+
class SetMotorSettings < UnknownMessage
|
162
|
+
MSG = 0x52
|
163
|
+
PARAMS_LENGTH = 5
|
164
|
+
|
165
|
+
attr_accessor :flags, :down_limit, :position_pulses
|
166
|
+
|
167
|
+
def initialize(dest = nil, flags = 0, down_limit = 0, position_pulses = 0, **kwargs)
|
168
|
+
kwargs[:dest] ||= dest
|
169
|
+
super(**kwargs)
|
170
|
+
self.flags = flags
|
171
|
+
self.down_limit = down_limit
|
172
|
+
self.position_pulses = position_pulses
|
173
|
+
end
|
174
|
+
|
175
|
+
def parse(params)
|
176
|
+
super
|
177
|
+
self.flags = to_number(params[0])
|
178
|
+
self.down_limit = to_number(params[1..2])
|
179
|
+
self.position_pulses = to_number(params[3..4])
|
180
|
+
end
|
181
|
+
|
182
|
+
def params
|
183
|
+
transform_param(flags) + from_number(down_limit, 2) + from_number(position_pulses, 2)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
class SetNodeLabel < ::SDN::Message::SetNodeLabel
|
188
|
+
MAX_LENGTH = 32
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|