somfy_sdn 2.1.5 → 2.2.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/exe/somfy_sdn +69 -0
- data/lib/sdn/cli/mqtt/group.rb +12 -10
- data/lib/sdn/cli/mqtt/motor.rb +19 -14
- data/lib/sdn/cli/mqtt/p_queue.rb +18 -0
- data/lib/sdn/cli/mqtt/read.rb +125 -126
- data/lib/sdn/cli/mqtt/subscriptions.rb +186 -140
- data/lib/sdn/cli/mqtt/write.rb +39 -34
- data/lib/sdn/cli/mqtt.rb +84 -53
- data/lib/sdn/cli/provisioner.rb +56 -33
- data/lib/sdn/cli/simulator.rb +99 -65
- data/lib/sdn/client.rb +38 -24
- data/lib/sdn/message/control.rb +60 -30
- data/lib/sdn/message/get.rb +6 -2
- data/lib/sdn/message/helpers.rb +23 -22
- data/lib/sdn/message/ilt2/get.rb +6 -3
- data/lib/sdn/message/ilt2/master_control.rb +10 -7
- data/lib/sdn/message/ilt2/post.rb +7 -5
- data/lib/sdn/message/ilt2/set.rb +28 -19
- data/lib/sdn/message/post.rb +3 -5
- data/lib/sdn/message/set.rb +48 -22
- data/lib/sdn/message.rb +50 -34
- data/lib/sdn/version.rb +3 -1
- data/lib/sdn.rb +18 -12
- data/lib/somfy_sdn.rb +3 -1
- metadata +43 -13
- data/bin/somfy_sdn +0 -60
@@ -1,153 +1,199 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module SDN
|
2
4
|
module CLI
|
3
5
|
class MQTT
|
4
6
|
module Subscriptions
|
5
7
|
def handle_message(topic, value)
|
6
|
-
SDN.logger.info "
|
7
|
-
|
8
|
-
|
9
|
-
property
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
8
|
+
SDN.logger.info "Got #{value.inspect} at #{topic}"
|
9
|
+
unless (match = topic.match(%r{^#{Regexp.escape(@base_topic)}/
|
10
|
+
(?<addr>\h{6})/
|
11
|
+
(?<property>discover|
|
12
|
+
label|
|
13
|
+
control|
|
14
|
+
jog-(?<jog_type>pulses|ms)|
|
15
|
+
position-pulses|
|
16
|
+
position-percent|
|
17
|
+
ip|
|
18
|
+
reset|
|
19
|
+
(?<speed_type>up-speed|down-speed|slow-speed)|
|
20
|
+
up-limit|
|
21
|
+
down-limit|
|
22
|
+
direction|i
|
23
|
+
p(?<ip>\d+)-(?<ip_type>pulses|percent)|
|
24
|
+
groups)/
|
25
|
+
set$}x))
|
26
|
+
return
|
27
|
+
end
|
23
28
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
motor&.node_type == :st50ilt2 ? ns::SetMotorPosition.new(addr, :stop) : Message::Stop.new(addr)
|
42
|
-
when 'next_ip'
|
43
|
-
motor&.node_type == :st50ilt2 ? ns::SetMotorPosition.new(addr, :next_ip_down) :
|
44
|
-
Message::MoveOf.new(addr, :next_ip)
|
45
|
-
when 'previous_ip'
|
46
|
-
motor&.node_type == :st50ilt2 ? ns::SetMotorPosition.new(addr, :next_ip_up) :
|
47
|
-
Message::MoveOf.new(addr, :previous_ip)
|
48
|
-
when 'wink'
|
49
|
-
Message::Wink.new(addr)
|
50
|
-
when 'refresh'
|
51
|
-
follow_up = nil
|
52
|
-
(motor&.node_type == :st50ilt2 ? ns::GetMotorPosition : Message::GetMotorStatus).
|
53
|
-
new(addr)
|
54
|
-
end
|
55
|
-
when /jog-(?:pulses|ms)/
|
56
|
-
value = value.to_i
|
57
|
-
(motor&.node_type == :st50ilt2 ? ns::SetMotorPosition : Message::MoveOf).
|
58
|
-
new(addr, "jog_#{value < 0 ? :up : :down }_#{match[:jog_type]}".to_sym, value.abs)
|
59
|
-
when 'reset'
|
60
|
-
return unless Message::SetFactoryDefault::RESET.keys.include?(value.to_sym)
|
61
|
-
Message::SetFactoryDefault.new(addr, value.to_sym)
|
62
|
-
when 'position-pulses', 'position-percent', 'ip'
|
63
|
-
if value == 'REFRESH'
|
64
|
-
follow_up = nil
|
65
|
-
(motor&.node_type == :st50ilt2 ? ns::GetMotorPosition : Message::GetMotorStatus).
|
66
|
-
new(addr)
|
67
|
-
else
|
68
|
-
(motor&.node_type == :st50ilt2 ? ns::SetMotorPosition : Message::MoveTo).
|
69
|
-
new(addr, property.sub('position-', 'position_').to_sym, value.to_i)
|
70
|
-
end
|
71
|
-
when 'direction'
|
72
|
-
return if is_group
|
73
|
-
follow_up = Message::GetMotorDirection.new(addr)
|
74
|
-
return unless %w{standard reversed}.include?(value)
|
75
|
-
Message::SetMotorDirection.new(addr, value.to_sym)
|
76
|
-
when 'up-limit', 'down-limit'
|
77
|
-
return if is_group
|
78
|
-
if %w{delete current_position jog_ms jog_pulses}.include?(value)
|
79
|
-
type = value.to_sym
|
80
|
-
value = 10
|
81
|
-
else
|
82
|
-
type = :specified_position
|
83
|
-
end
|
84
|
-
target = property == 'up-limit' ? :up : :down
|
85
|
-
follow_up = Message::GetMotorLimits.new(addr)
|
86
|
-
Message::SetMotorLimits.new(addr, type, target, value.to_i)
|
87
|
-
when /^ip\d-(?:pulses|percent)$/
|
88
|
-
return if is_group
|
89
|
-
ip = match[:ip].to_i
|
90
|
-
return unless (1..16).include?(ip)
|
91
|
-
follow_up = ns::GetMotorIP.new(addr, ip)
|
29
|
+
addr = Message.parse_address(match[:addr])
|
30
|
+
property = match[:property]
|
31
|
+
# not homie compliant; allows linking the position-percent property
|
32
|
+
# directly to an OpenHAB rollershutter channel
|
33
|
+
if property == "position-percent" && value =~ /^(?:UP|DOWN|STOP)$/i
|
34
|
+
property = "control"
|
35
|
+
value = value.downcase
|
36
|
+
end
|
37
|
+
mqtt_addr = Message.print_address(addr).delete(".")
|
38
|
+
motor = @motors[mqtt_addr]
|
39
|
+
is_group = Message.group_address?(addr)
|
40
|
+
follow_up = if motor&.node_type == :st50ilt2
|
41
|
+
Message::ILT2::GetMotorPosition.new(addr)
|
42
|
+
else
|
43
|
+
Message::GetMotorStatus.new(addr)
|
44
|
+
end
|
45
|
+
ns = (motor&.node_type == :st50ilt2) ? Message::ILT2 : Message
|
92
46
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
47
|
+
message = case property
|
48
|
+
when "discover"
|
49
|
+
follow_up = nil
|
50
|
+
if value == "discover"
|
51
|
+
# discovery is low priority, and longer timeout
|
52
|
+
enqueue(MessageAndRetries.new(Message::GetNodeAddr.new(addr), 1, 50))
|
53
|
+
end
|
54
|
+
nil
|
55
|
+
when "label"
|
56
|
+
follow_up = Message::GetNodeLabel.new(addr)
|
57
|
+
ns::SetNodeLabel.new(addr, value) unless is_group
|
58
|
+
when "control"
|
59
|
+
case value
|
60
|
+
when "up", "down"
|
61
|
+
((motor&.node_type == :st50ilt2) ? ns::SetMotorPosition : Message::MoveTo)
|
62
|
+
.new(addr, "#{value}_limit".to_sym)
|
63
|
+
when "stop"
|
64
|
+
if motor&.node_type == :st50ilt2
|
65
|
+
ns::SetMotorPosition.new(addr,
|
66
|
+
:stop)
|
67
|
+
else
|
68
|
+
Message::Stop.new(addr)
|
69
|
+
end
|
70
|
+
when "next_ip"
|
71
|
+
if motor&.node_type == :st50ilt2
|
72
|
+
ns::SetMotorPosition.new(addr, :next_ip_down)
|
73
|
+
else
|
74
|
+
Message::MoveOf.new(addr, :next_ip)
|
75
|
+
end
|
76
|
+
when "previous_ip"
|
77
|
+
if motor&.node_type == :st50ilt2
|
78
|
+
ns::SetMotorPosition.new(addr, :next_ip_up)
|
79
|
+
else
|
80
|
+
Message::MoveOf.new(addr, :previous_ip)
|
81
|
+
end
|
82
|
+
when "wink"
|
83
|
+
Message::Wink.new(addr)
|
84
|
+
when "refresh"
|
85
|
+
follow_up = nil
|
86
|
+
((motor&.node_type == :st50ilt2) ? ns::GetMotorPosition : Message::GetMotorStatus)
|
87
|
+
.new(addr)
|
88
|
+
end
|
89
|
+
when /jog-(?:pulses|ms)/
|
90
|
+
value = value.to_i
|
91
|
+
((motor&.node_type == :st50ilt2) ? ns::SetMotorPosition : Message::MoveOf)
|
92
|
+
.new(addr, "jog_#{value.negative? ? :up : :down}_#{match[:jog_type]}".to_sym, value.abs)
|
93
|
+
when "reset"
|
94
|
+
return unless Message::SetFactoryDefault::RESET.key?(value.to_sym)
|
136
95
|
|
137
|
-
|
138
|
-
|
139
|
-
|
96
|
+
Message::SetFactoryDefault.new(addr, value.to_sym)
|
97
|
+
when "position-pulses", "position-percent", "ip"
|
98
|
+
if value == "REFRESH"
|
99
|
+
follow_up = nil
|
100
|
+
((motor&.node_type == :st50ilt2) ? ns::GetMotorPosition : Message::GetMotorStatus)
|
101
|
+
.new(addr)
|
102
|
+
else
|
103
|
+
((motor&.node_type == :st50ilt2) ? ns::SetMotorPosition : Message::MoveTo)
|
104
|
+
.new(addr, property.sub("position-", "position_").to_sym, value.to_i)
|
105
|
+
end
|
106
|
+
when "direction"
|
107
|
+
return if is_group
|
108
|
+
|
109
|
+
follow_up = Message::GetMotorDirection.new(addr)
|
110
|
+
return unless %w[standard reversed].include?(value)
|
111
|
+
|
112
|
+
Message::SetMotorDirection.new(addr, value.to_sym)
|
113
|
+
when "up-limit", "down-limit"
|
114
|
+
return if is_group
|
115
|
+
|
116
|
+
if %w[delete current_position jog_ms jog_pulses].include?(value)
|
117
|
+
type = value.to_sym
|
118
|
+
value = 10
|
119
|
+
else
|
120
|
+
type = :specified_position
|
121
|
+
end
|
122
|
+
target = (property == "up-limit") ? :up : :down
|
123
|
+
follow_up = Message::GetMotorLimits.new(addr)
|
124
|
+
Message::SetMotorLimits.new(addr, type, target, value.to_i)
|
125
|
+
when /^ip\d-(?:pulses|percent)$/
|
126
|
+
return if is_group
|
127
|
+
|
128
|
+
ip = match[:ip].to_i
|
129
|
+
return unless (1..16).cover?(ip)
|
130
|
+
|
131
|
+
follow_up = ns::GetMotorIP.new(addr, ip)
|
132
|
+
|
133
|
+
if motor&.node_type == :st50ilt2
|
134
|
+
value = if value == "delete"
|
135
|
+
nil
|
136
|
+
elsif value == "current_position"
|
137
|
+
motor.position_pulses
|
138
|
+
elsif match[:ip_type] == "pulses"
|
139
|
+
value.to_i
|
140
|
+
else
|
141
|
+
value.to_f / motor.down_limit * 100
|
142
|
+
end
|
143
|
+
ns::SetMotorIP.new(addr, ip, value)
|
144
|
+
else
|
145
|
+
type = if value == "delete"
|
146
|
+
:delete
|
147
|
+
elsif value == "current_position"
|
148
|
+
:current_position
|
149
|
+
elsif match[:ip_type] == "pulses"
|
150
|
+
:position_pulses
|
151
|
+
else
|
152
|
+
:position_percent
|
153
|
+
end
|
154
|
+
Message::SetMotorIP.new(addr, type, ip, value.to_i)
|
155
|
+
end
|
156
|
+
when "up-speed", "down-speed", "slow-speed"
|
157
|
+
return if is_group
|
158
|
+
return unless motor
|
159
|
+
|
160
|
+
follow_up = Message::GetMotorRollingSpeed.new(addr)
|
161
|
+
message = Message::SetMotorRollingSpeed.new(addr,
|
162
|
+
up_speed: motor.up_speed,
|
163
|
+
down_speed: motor.down_speed,
|
164
|
+
slow_speed: motor.slow_speed)
|
165
|
+
message.send(:"#{property.sub("-", "_")}=", value.to_i)
|
166
|
+
message
|
167
|
+
when "groups"
|
168
|
+
return if is_group
|
169
|
+
return unless motor
|
170
|
+
|
171
|
+
messages = motor.set_groups(value)
|
172
|
+
@mutex.synchronize do
|
173
|
+
messages.each { |m| @queue.push(MessageAndRetries.new(m, 5, 0)) }
|
174
|
+
@cond.signal
|
175
|
+
end
|
176
|
+
nil
|
177
|
+
end
|
178
|
+
|
179
|
+
if motor && [Message::MoveTo,
|
180
|
+
Message::Move,
|
181
|
+
Message::Wink,
|
182
|
+
Message::Stop].include?(message.class)
|
183
|
+
motor.last_action = message.class
|
184
|
+
end
|
185
|
+
|
186
|
+
return unless message
|
140
187
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
@cond.signal
|
149
|
-
end
|
188
|
+
message.ack_requested = true unless /^SDN::Message::Get/.match?(message.class.name)
|
189
|
+
@mutex.synchronize do
|
190
|
+
@queue.push(MessageAndRetries.new(message, 5, 0))
|
191
|
+
if follow_up && @queue.none? do |mr|
|
192
|
+
mr.message == follow_up
|
193
|
+
end
|
194
|
+
@queue.push(MessageAndRetries.new(follow_up, 5, 1))
|
150
195
|
end
|
196
|
+
@cond.signal
|
151
197
|
end
|
152
198
|
end
|
153
199
|
end
|
data/lib/sdn/cli/mqtt/write.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module SDN
|
2
4
|
module CLI
|
3
5
|
class MQTT
|
@@ -12,23 +14,25 @@ module SDN
|
|
12
14
|
if @response_pending
|
13
15
|
while @response_pending
|
14
16
|
remaining_wait = @response_pending - Time.now.to_f
|
15
|
-
if remaining_wait
|
16
|
-
SDN.logger.debug "
|
17
|
+
if remaining_wait.negative?
|
18
|
+
SDN.logger.debug "Timed out waiting on response"
|
17
19
|
@response_pending = nil
|
18
20
|
@broadcast_pending = nil
|
19
21
|
if @prior_message && @prior_message&.remaining_retries != 0
|
20
|
-
SDN.logger.debug "
|
21
|
-
if Message.
|
22
|
-
SDN.logger.debug "
|
22
|
+
SDN.logger.debug "Retrying #{@prior_message.remaining_retries} more times ..."
|
23
|
+
if Message.group_address?(@prior_message.message.src) && !@pending_group_motors.empty?
|
24
|
+
SDN.logger.debug "Re-targetting group message to individual motors"
|
23
25
|
@pending_group_motors.each do |addr|
|
24
26
|
new_message = @prior_message.message.dup
|
25
27
|
new_message.src = [0, 0, 1]
|
26
28
|
new_message.dest = Message.parse_address(addr)
|
27
|
-
@
|
29
|
+
@queue.push(MessageAndRetries.new(new_message,
|
30
|
+
@prior_message.remaining_retries,
|
31
|
+
@prior_message.priority))
|
28
32
|
end
|
29
33
|
@pending_group_motors = []
|
30
34
|
else
|
31
|
-
@
|
35
|
+
@queue.push(@prior_message)
|
32
36
|
end
|
33
37
|
@prior_message = nil
|
34
38
|
end
|
@@ -38,53 +42,54 @@ module SDN
|
|
38
42
|
end
|
39
43
|
end
|
40
44
|
|
41
|
-
|
42
|
-
if message_and_retries
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
45
|
+
message_and_retries = @queue.shift
|
46
|
+
if message_and_retries && (
|
47
|
+
message_and_retries.message.ack_requested ||
|
48
|
+
message_and_retries.message.class.name =~ /^SDN::Message::Get/)
|
49
|
+
@response_pending = Time.now.to_f + WAIT_TIME
|
50
|
+
@pending_group_motors = if Message.group_address?(message_and_retries.message.src)
|
51
|
+
group_addr = Message.print_address(message_and_retries.message.src).delete(
|
52
|
+
"."
|
53
|
+
)
|
54
|
+
@groups[group_addr]&.motor_objects&.map(&:addr) || []
|
55
|
+
else
|
56
|
+
[]
|
57
|
+
end
|
58
|
+
|
59
|
+
if message_and_retries.message.dest == BROADCAST_ADDRESS || (
|
60
|
+
Message.group_address?(message_and_retries.message.src) &&
|
61
|
+
message_and_retries.message.is_a?(Message::GetNodeAddr))
|
62
|
+
@broadcast_pending = Time.now.to_f + BROADCAST_WAIT
|
55
63
|
end
|
56
64
|
end
|
57
65
|
|
58
66
|
# wait until there is a message
|
59
67
|
if @response_pending
|
60
68
|
message_and_retries.remaining_retries -= 1
|
61
|
-
@prior_message = message_and_retries
|
69
|
+
@prior_message = message_and_retries
|
62
70
|
elsif message_and_retries
|
63
|
-
@prior_message = nil
|
71
|
+
@prior_message = nil
|
72
|
+
elsif @auto_discover && @motors_found
|
73
|
+
message_and_retries = MessageAndRetries.new(Message::GetNodeAddr.new, 1, 50)
|
74
|
+
@motors_found = false
|
75
|
+
# nothing pending to write, and motors found on the last iteration;
|
76
|
+
# look for more motors
|
64
77
|
else
|
65
|
-
|
66
|
-
# nothing pending to write, and motors found on the last iteration;
|
67
|
-
# look for more motors
|
68
|
-
message_and_retries = MessageAndRetries.new(Message::GetNodeAddr.new, 1, 2)
|
69
|
-
@motors_found = false
|
70
|
-
else
|
71
|
-
@cond.wait(@mutex)
|
72
|
-
end
|
78
|
+
@cond.wait(@mutex)
|
73
79
|
end
|
74
80
|
end
|
75
81
|
next unless message_and_retries
|
76
82
|
|
77
83
|
message = message_and_retries.message
|
78
|
-
SDN.logger.info "writing #{message.inspect}"
|
79
84
|
# minimum time between messages
|
80
85
|
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
81
86
|
sleep_time = 0.1 - (now - last_write_at)
|
82
|
-
sleep(sleep_time) if sleep_time
|
87
|
+
sleep(sleep_time) if sleep_time.positive?
|
83
88
|
@sdn.send(message)
|
84
89
|
last_write_at = now
|
85
90
|
end
|
86
91
|
rescue => e
|
87
|
-
SDN.logger.fatal "
|
92
|
+
SDN.logger.fatal "Failure writing: #{e}: #{e.backtrace}"
|
88
93
|
exit 1
|
89
94
|
end
|
90
95
|
end
|