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.
@@ -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 "got #{value.inspect} at #{topic}"
7
- if (match = topic.match(%r{^#{Regexp.escape(@base_topic)}/(?<addr>\h{6})/(?<property>discover|label|control|jog-(?<jog_type>pulses|ms)|position-pulses|position-percent|ip|reset|(?<speed_type>up-speed|down-speed|slow-speed)|up-limit|down-limit|direction|ip(?<ip>\d+)-(?<ip_type>pulses|percent)|groups)/set$}))
8
- addr = Message.parse_address(match[:addr])
9
- property = match[:property]
10
- # not homie compliant; allows linking the position-percent property
11
- # directly to an OpenHAB rollershutter channel
12
- if property == 'position-percent' && value =~ /^(?:UP|DOWN|STOP)$/i
13
- property = "control"
14
- value = value.downcase
15
- end
16
- mqtt_addr = Message.print_address(addr).gsub('.', '')
17
- motor = @motors[mqtt_addr]
18
- is_group = Message.is_group_address?(addr)
19
- group = @groups[mqtt_addr]
20
- follow_up = motor&.node_type == :st50ilt2 ? Message::ILT2::GetMotorPosition.new(addr) :
21
- Message::GetMotorStatus.new(addr)
22
- ns = motor&.node_type == :st50ilt2 ? Message::ILT2 : Message
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
- message = case property
25
- when 'discover'
26
- follow_up = nil
27
- if value == "discover"
28
- # discovery is low priority, and longer timeout
29
- enqueue(MessageAndRetries.new(Message::GetNodeAddr.new(addr), 1, 2), 2)
30
- end
31
- nil
32
- when 'label'
33
- follow_up = Message::GetNodeLabel.new(addr)
34
- ns::SetNodeLabel.new(addr, value) unless is_group
35
- when 'control'
36
- case value
37
- when 'up', 'down'
38
- (motor&.node_type == :st50ilt2 ? ns::SetMotorPosition : Message::MoveTo).
39
- new(addr, "#{value}_limit".to_sym)
40
- when 'stop'
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
- if motor&.node_type == :st50ilt2
94
- value = if value == 'delete'
95
- nil
96
- elsif value == 'current_position'
97
- motor.position_pulses
98
- elsif match[:ip_type] == 'pulses'
99
- value.to_i
100
- else
101
- value.to_f / motor.down_limit * 100
102
- end
103
- ns::SetMotorIP.new(addr, ip, value)
104
- else
105
- type = if value == 'delete'
106
- :delete
107
- elsif value == 'current_position'
108
- :current_position
109
- elsif match[:ip_type] == 'pulses'
110
- :position_pulses
111
- else
112
- :position_percent
113
- end
114
- Message::SetMotorIP.new(addr, type, ip, value.to_i)
115
- end
116
- when 'up-speed', 'down-speed', 'slow-speed'
117
- return if is_group
118
- return unless motor
119
- follow_up = Message::GetMotorRollingSpeed.new(addr)
120
- message = Message::SetMotorRollingSpeed.new(addr,
121
- up_speed: motor.up_speed,
122
- down_speed: motor.down_speed,
123
- slow_speed: motor.slow_speed)
124
- message.send(:"#{property.sub('-', '_')}=", value.to_i)
125
- message
126
- when 'groups'
127
- return if is_group
128
- return unless motor
129
- messages = motor.set_groups(value)
130
- @mutex.synchronize do
131
- messages.each { |m| @queues[0].push(MessageAndRetries.new(m, 5, 0)) }
132
- @cond.signal
133
- end
134
- nil
135
- end
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
- if motor
138
- motor.last_action = message.class if [Message::MoveTo, Message::Move, Message::Wink, Message::Stop].include?(message.class)
139
- end
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
- if message
142
- message.ack_requested = true if message.class.name !~ /^SDN::Message::Get/
143
- @mutex.synchronize do
144
- @queues[0].push(MessageAndRetries.new(message, 5, 0))
145
- if follow_up
146
- @queues[1].push(MessageAndRetries.new(follow_up, 5, 1)) unless @queues[1].any? { |mr| mr.message == follow_up }
147
- end
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
@@ -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 < 0
16
- SDN.logger.debug "timed out waiting on response"
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 "retrying #{@prior_message.remaining_retries} more times ..."
21
- if Message.is_group_address?(@prior_message.message.src) && !@pending_group_motors.empty?
22
- SDN.logger.debug "re-targetting group message to individual motors"
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
- @queues[@prior_message.priority].push(MessageAndRetries.new(new_message, @prior_message.remaining_retries, @prior_message.priority))
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
- @queues[@prior_message.priority].push(@prior_message)
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
- @queues.find { |q| message_and_retries = q.shift }
42
- if message_and_retries
43
- if message_and_retries.message.ack_requested || message_and_retries.message.class.name =~ /^SDN::Message::Get/
44
- @response_pending = Time.now.to_f + WAIT_TIME
45
- @pending_group_motors = if Message.is_group_address?(message_and_retries.message.src)
46
- group_addr = Message.print_address(message_and_retries.message.src).gsub('.', '')
47
- @groups[group_addr]&.motor_objects&.map(&:addr) || []
48
- else
49
- []
50
- end
51
-
52
- if message_and_retries.message.dest == BROADCAST_ADDRESS || Message.is_group_address?(message_and_retries.message.src) && message_and_retries.message.is_a?(Message::GetNodeAddr)
53
- @broadcast_pending = Time.now.to_f + BROADCAST_WAIT
54
- end
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
- if @auto_discover && @motors_found
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 > 0
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 "failure writing: #{e}: #{e.backtrace}"
92
+ SDN.logger.fatal "Failure writing: #{e}: #{e.backtrace}"
88
93
  exit 1
89
94
  end
90
95
  end