somfy_sdn 1.0.12 → 2.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 +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
|