somfy_sdn 2.1.5 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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