somfy_sdn 2.0.0 → 2.1.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8b14639678ed3c5a54d4f8d70fd217898238aed5ccd1c81c0f3b78d04592e48b
4
- data.tar.gz: 26d14eb134eee69523d2bca19f8ec32c63a3fc110ff7da704681569de2b8422b
3
+ metadata.gz: 10f8660ac4681db000372dc97f6fe9a738d706f2751965a3556d5d6dcf22215a
4
+ data.tar.gz: 8659cf3dc75cf10d677b6329dc2ab65fec9367bb2a6c70da623557f0dfc113b5
5
5
  SHA512:
6
- metadata.gz: 3da036c72689def6022a487078bc1024ff9aa10c1b9ec7d04ce6e19a0b5b1e3956e18dcdd44272e6e7e72175afe335dc7d9f4c29785004b88ad5fda344b7a84d
7
- data.tar.gz: c12a57173231c7be1b4b7557cb71c4c58fcc312deace5157893850d5301696ca77b1e743d6ed4e2b005c3cc532c267875f234bbe5bf92a564a17d5fa921b3ff9
6
+ metadata.gz: 9432f5079edfa4a6a0d70845b4bf17140269c4608fa6a006de501eb68f59806e9f85c44658f41da857c7fbd7c8ae69c4781479de36d149f60649b3c3b9670f19
7
+ data.tar.gz: 25831ea7fa94905b3cab5062216118e9b74b861b2a1c93a7ed49ce4aa64fceba30bcb3e2e79675c43c019d4d639aa944561bb0af7414192a0e981f3649511351
@@ -6,144 +6,146 @@ module SDN
6
6
  loop do
7
7
  begin
8
8
  @sdn.receive do |message|
9
- SDN.logger.info "read #{message.inspect}"
9
+ @mqtt.batch_publish do
10
+ SDN.logger.info "read #{message.inspect}"
10
11
 
11
- src = Message.print_address(message.src)
12
- # ignore the UAI Plus and ourselves
13
- if src != '7F.7F.7F' && !Message.is_group_address?(message.src) && !(motor = @motors[src.gsub('.', '')])
14
- SDN.logger.info "found new motor #{src}"
15
- @motors_found = true
16
- motor = publish_motor(src.gsub('.', ''), message.node_type)
17
- end
18
-
19
- follow_ups = []
20
- case message
21
- when Message::PostNodeLabel
22
- if (motor.publish(:label, message.label))
23
- publish("#{motor.addr}/$name", message.label)
12
+ src = Message.print_address(message.src)
13
+ # ignore the UAI Plus and ourselves
14
+ if src != '7F.7F.7F' && !Message.is_group_address?(message.src) && !(motor = @motors[src.gsub('.', '')])
15
+ SDN.logger.info "found new motor #{src}"
16
+ @motors_found = true
17
+ motor = publish_motor(src.gsub('.', ''), message.node_type)
24
18
  end
25
- when Message::PostMotorPosition,
26
- Message::ILT2::PostMotorPosition
27
- if message.is_a?(Message::ILT2::PostMotorPosition)
28
- # keep polling while it's still moving; check prior two positions
29
- if motor.position_pulses == message.position_pulses &&
30
- motor.last_position_pulses == message.position_pulses
31
- motor.publish(:state, :stopped)
32
- else
33
- motor.publish(:state, :running)
34
- if motor.position_pulses && motor.position_pulses != message.position_pulses
35
- motor.publish(:last_direction, motor.position_pulses < message.position_pulses ? :down : :up)
36
- end
37
- follow_ups << Message::ILT2::GetMotorPosition.new(message.src)
19
+
20
+ follow_ups = []
21
+ case message
22
+ when Message::PostNodeLabel
23
+ if (motor.publish(:label, message.label))
24
+ publish("#{motor.addr}/$name", message.label)
38
25
  end
39
- motor.last_position_pulses = motor.position_pulses
40
- ip = (1..16).find do |i|
41
- # divide by 5 for some leniency
42
- motor["ip#{i}_pulses"].to_i / 5 == message.position_pulses / 5
26
+ when Message::PostMotorPosition,
27
+ Message::ILT2::PostMotorPosition
28
+ if message.is_a?(Message::ILT2::PostMotorPosition)
29
+ # keep polling while it's still moving; check prior two positions
30
+ if motor.position_pulses == message.position_pulses &&
31
+ motor.last_position_pulses == message.position_pulses
32
+ motor.publish(:state, :stopped)
33
+ else
34
+ motor.publish(:state, :running)
35
+ if motor.position_pulses && motor.position_pulses != message.position_pulses
36
+ motor.publish(:last_direction, motor.position_pulses < message.position_pulses ? :down : :up)
37
+ end
38
+ follow_ups << Message::ILT2::GetMotorPosition.new(message.src)
39
+ end
40
+ motor.last_position_pulses = motor.position_pulses
41
+ ip = (1..16).find do |i|
42
+ # divide by 5 for some leniency
43
+ motor["ip#{i}_pulses"].to_i / 5 == message.position_pulses / 5
44
+ end
45
+ motor.publish(:ip, ip)
43
46
  end
44
- motor.publish(:ip, ip)
45
- end
46
- motor.publish(:position_percent, message.position_percent)
47
- motor.publish(:position_pulses, message.position_pulses)
48
- motor.publish(:ip, message.ip) if message.respond_to?(:ip)
49
- motor.group_objects.each do |group|
50
- positions_percent = group.motor_objects.map(&:position_percent)
51
- positions_pulses = group.motor_objects.map(&:position_pulses)
52
- ips = group.motor_objects.map(&:ip)
47
+ motor.publish(:position_percent, message.position_percent)
48
+ motor.publish(:position_pulses, message.position_pulses)
49
+ motor.publish(:ip, message.ip) if message.respond_to?(:ip)
50
+ motor.group_objects.each do |group|
51
+ positions_percent = group.motor_objects.map(&:position_percent)
52
+ positions_pulses = group.motor_objects.map(&:position_pulses)
53
+ ips = group.motor_objects.map(&:ip)
53
54
 
54
- position_percent = nil
55
- # calculate an average, but only if we know a position for
56
- # every shade
57
- if !positions_percent.include?(:nil) && !positions_percent.include?(nil)
58
- position_percent = positions_percent.inject(&:+) / positions_percent.length
59
- end
55
+ position_percent = nil
56
+ # calculate an average, but only if we know a position for
57
+ # every shade
58
+ if !positions_percent.include?(:nil) && !positions_percent.include?(nil)
59
+ position_percent = positions_percent.inject(&:+) / positions_percent.length
60
+ end
60
61
 
61
- position_pulses = nil
62
- if !positions_pulses.include?(:nil) && !positions_pulses.include?(nil)
63
- position_pulses = positions_pulses.inject(&:+) / positions_pulses.length
64
- end
62
+ position_pulses = nil
63
+ if !positions_pulses.include?(:nil) && !positions_pulses.include?(nil)
64
+ position_pulses = positions_pulses.inject(&:+) / positions_pulses.length
65
+ end
65
66
 
66
- ip = nil
67
- ip = ips.first if ips.uniq.length == 1
68
- ip = nil if ip == :nil
67
+ ip = nil
68
+ ip = ips.first if ips.uniq.length == 1
69
+ ip = nil if ip == :nil
69
70
 
70
- group.publish(:position_percent, position_percent)
71
- group.publish(:position_pulses, position_pulses)
72
- group.publish(:ip, ip)
73
- end
74
- when Message::PostMotorStatus
75
- if message.state == :running || motor.state == :running ||
76
- # if it's explicitly stopped, but we didn't ask it to, it's probably
77
- # changing directions so keep querying
78
- (message.state == :stopped &&
79
- message.last_action_cause == :explicit_command &&
80
- !(motor.last_action == Message::Stop || motor.last_action.nil?))
81
- follow_ups << Message::GetMotorStatus.new(message.src)
82
- end
83
- # this will do one more position request after it stopped
84
- follow_ups << Message::GetMotorPosition.new(message.src)
85
- motor.publish(:state, message.state)
86
- motor.publish(:last_direction, message.last_direction)
87
- motor.publish(:last_action_source, message.last_action_source)
88
- motor.publish(:last_action_cause, message.last_action_cause)
89
- motor.group_objects.each do |group|
90
- states = group.motor_objects.map(&:state).uniq
91
- state = states.length == 1 ? states.first : 'mixed'
92
- group.publish(:state, state)
71
+ group.publish(:position_percent, position_percent)
72
+ group.publish(:position_pulses, position_pulses)
73
+ group.publish(:ip, ip)
74
+ end
75
+ when Message::PostMotorStatus
76
+ if message.state == :running || motor.state == :running ||
77
+ # if it's explicitly stopped, but we didn't ask it to, it's probably
78
+ # changing directions so keep querying
79
+ (message.state == :stopped &&
80
+ message.last_action_cause == :explicit_command &&
81
+ !(motor.last_action == Message::Stop || motor.last_action.nil?))
82
+ follow_ups << Message::GetMotorStatus.new(message.src)
83
+ end
84
+ # this will do one more position request after it stopped
85
+ follow_ups << Message::GetMotorPosition.new(message.src)
86
+ motor.publish(:state, message.state)
87
+ motor.publish(:last_direction, message.last_direction)
88
+ motor.publish(:last_action_source, message.last_action_source)
89
+ motor.publish(:last_action_cause, message.last_action_cause)
90
+ motor.group_objects.each do |group|
91
+ states = group.motor_objects.map(&:state).uniq
92
+ state = states.length == 1 ? states.first : 'mixed'
93
+ group.publish(:state, state)
93
94
 
94
- directions = group.motor_objects.map(&:last_direction).uniq
95
- direction = directions.length == 1 ? directions.first : 'mixed'
96
- group.publish(:last_direction, direction)
97
- end
98
- when Message::PostMotorLimits
99
- motor.publish(:up_limit, message.up_limit)
100
- motor.publish(:down_limit, message.down_limit)
101
- when Message::ILT2::PostMotorSettings
102
- motor.publish(:down_limit, message.limit)
103
- when Message::PostMotorDirection
104
- motor.publish(:direction, message.direction)
105
- when Message::PostMotorRollingSpeed
106
- motor.publish(:up_speed, message.up_speed)
107
- motor.publish(:down_speed, message.down_speed)
108
- motor.publish(:slow_speed, message.slow_speed)
109
- when Message::PostMotorIP,
110
- Message::ILT2::PostMotorIP
111
- motor.publish(:"ip#{message.ip}_pulses", message.position_pulses)
112
- if message.respond_to?(:position_percent)
113
- motor.publish(:"ip#{message.ip}_percent", message.position_percent)
114
- elsif motor.down_limit
115
- motor.publish(:"ip#{message.ip}_percent", message.position_pulses.to_f / motor.down_limit * 100)
95
+ directions = group.motor_objects.map(&:last_direction).uniq
96
+ direction = directions.length == 1 ? directions.first : 'mixed'
97
+ group.publish(:last_direction, direction)
98
+ end
99
+ when Message::PostMotorLimits
100
+ motor.publish(:up_limit, message.up_limit)
101
+ motor.publish(:down_limit, message.down_limit)
102
+ when Message::ILT2::PostMotorSettings
103
+ motor.publish(:down_limit, message.limit)
104
+ when Message::PostMotorDirection
105
+ motor.publish(:direction, message.direction)
106
+ when Message::PostMotorRollingSpeed
107
+ motor.publish(:up_speed, message.up_speed)
108
+ motor.publish(:down_speed, message.down_speed)
109
+ motor.publish(:slow_speed, message.slow_speed)
110
+ when Message::PostMotorIP,
111
+ Message::ILT2::PostMotorIP
112
+ motor.publish(:"ip#{message.ip}_pulses", message.position_pulses)
113
+ if message.respond_to?(:position_percent)
114
+ motor.publish(:"ip#{message.ip}_percent", message.position_percent)
115
+ elsif motor.down_limit
116
+ motor.publish(:"ip#{message.ip}_percent", message.position_pulses.to_f / motor.down_limit * 100)
117
+ end
118
+ when Message::PostGroupAddr
119
+ motor.add_group(message.group_index, message.group_address)
116
120
  end
117
- when Message::PostGroupAddr
118
- motor.add_group(message.group_index, message.group_address)
119
- end
120
121
 
121
- @mutex.synchronize do
122
- prior_message_to_group = Message.is_group_address?(@prior_message&.message&.src) if @prior_message
122
+ @mutex.synchronize do
123
+ prior_message_to_group = Message.is_group_address?(@prior_message&.message&.src) if @prior_message
123
124
 
124
- correct_response = @response_pending && @prior_message&.message&.class&.expected_response?(message)
125
- correct_response = false if !prior_message_to_group && message.src != @prior_message&.message&.dest
126
- correct_response = false if prior_message_to_group && message.dest != @prior_message&.message&.src
125
+ correct_response = @response_pending && @prior_message&.message&.class&.expected_response?(message)
126
+ correct_response = false if !prior_message_to_group && message.src != @prior_message&.message&.dest
127
+ correct_response = false if prior_message_to_group && message.dest != @prior_message&.message&.src
127
128
 
128
- if prior_message_to_group && correct_response
129
- @pending_group_motors.delete(Message.print_address(message.src).gsub('.', ''))
130
- correct_response = false unless @pending_group_motors.empty?
131
- end
129
+ if prior_message_to_group && correct_response
130
+ @pending_group_motors.delete(Message.print_address(message.src).gsub('.', ''))
131
+ correct_response = false unless @pending_group_motors.empty?
132
+ end
132
133
 
133
- signal = correct_response || !follow_ups.empty?
134
- @response_pending = @broadcast_pending if correct_response
135
- follow_ups.each do |follow_up|
136
- @queues[1].push(MessageAndRetries.new(follow_up, 5, 1)) unless @queues[1].any? { |mr| mr.message == follow_up }
134
+ signal = correct_response || !follow_ups.empty?
135
+ @response_pending = @broadcast_pending if correct_response
136
+ follow_ups.each do |follow_up|
137
+ @queues[1].push(MessageAndRetries.new(follow_up, 5, 1)) unless @queues[1].any? { |mr| mr.message == follow_up }
138
+ end
139
+ @cond.signal if signal
137
140
  end
138
- @cond.signal if signal
141
+ rescue EOFError
142
+ SDN.logger.fatal "EOF reading"
143
+ exit 2
144
+ rescue MalformedMessage => e
145
+ SDN.logger.warn "ignoring malformed message: #{e}" unless e.to_s =~ /issing data/
146
+ rescue => e
147
+ SDN.logger.error "got garbage: #{e}; #{e.backtrace}"
139
148
  end
140
- rescue EOFError
141
- SDN.logger.fatal "EOF reading"
142
- exit 2
143
- rescue MalformedMessage => e
144
- SDN.logger.warn "ignoring malformed message: #{e}" unless e.to_s =~ /issing data/
145
- rescue => e
146
- SDN.logger.error "got garbage: #{e}; #{e.backtrace}"
147
149
  end
148
150
  end
149
151
  end
data/lib/sdn/cli/mqtt.rb CHANGED
@@ -25,7 +25,7 @@ module SDN
25
25
  def initialize(port, mqtt_uri, device_id: "somfy", base_topic: "homie", auto_discover: true)
26
26
  @base_topic = "#{base_topic}/#{device_id}"
27
27
  @mqtt = ::MQTT::Client.new(mqtt_uri)
28
- @mqtt.set_will("#{@base_topic}/$state", "lost", true)
28
+ @mqtt.set_will("#{@base_topic}/$state", "lost", retain: true)
29
29
  @mqtt.connect
30
30
 
31
31
  @motors = {}
@@ -40,17 +40,19 @@ module SDN
40
40
  @auto_discover = auto_discover
41
41
  @motors_found = true
42
42
 
43
+ clear_tree(@base_topic)
43
44
  publish_basic_attributes
44
45
 
45
46
  @sdn = Client.new(port)
46
47
 
48
+ Thread.abort_on_exception = true
47
49
  read_thread = Thread.new { read }
48
50
  write_thread = Thread.new { write }
49
- @mqtt.get { |topic, value| handle_message(topic, value) }
51
+ @mqtt.get { |packet| handle_message(packet.topic, packet.payload) }
50
52
  end
51
53
 
52
54
  def publish(topic, value)
53
- @mqtt.publish("#{@base_topic}/#{topic}", value, true, 0)
55
+ @mqtt.publish("#{@base_topic}/#{topic}", value, retain: true, qos: 1)
54
56
  end
55
57
 
56
58
  def subscribe(topic)
@@ -67,22 +69,45 @@ module SDN
67
69
  end
68
70
  end
69
71
 
72
+ def clear_tree(topic)
73
+ @mqtt.subscribe("#{topic}/#")
74
+ @mqtt.unsubscribe("#{topic}/#", wait_for_ack: true)
75
+ while !@mqtt.queue_empty?
76
+ packet = @mqtt.get
77
+ @mqtt.publish(packet.topic, nil, retain: true)
78
+ end
79
+ end
80
+
70
81
  def publish_basic_attributes
71
- publish("$homie", "v4.0.0")
72
- publish("$name", "Somfy SDN Network")
73
- publish("$state", "init")
74
- publish("$nodes", "FFFFFF")
82
+ @mqtt.batch_publish do
83
+ publish("$homie", "4.0.0")
84
+ publish("$name", "Somfy SDN Network")
85
+ publish("$state", "init")
86
+ publish("$nodes", "FFFFFF")
87
+
88
+ publish("FFFFFF/$name", "Broadcast")
89
+ publish("FFFFFF/$type", "sdn")
90
+ publish("FFFFFF/$properties", "discover")
91
+
92
+ publish("FFFFFF/discover/$name", "Trigger Motor Discovery")
93
+ publish("FFFFFF/discover/$datatype", "enum")
94
+ publish("FFFFFF/discover/$format", "discover")
95
+ publish("FFFFFF/discover/$settable", "true")
96
+ publish("FFFFFF/discover/$retained", "false")
75
97
 
76
- publish("FFFFFF/$name", "Broadcast")
77
- publish("FFFFFF/$type", "sdn")
78
- publish("FFFFFF/$properties", "discover")
98
+ subscribe_all
79
99
 
80
- publish("FFFFFF/discover/$name", "Trigger Motor Discovery")
81
- publish("FFFFFF/discover/$datatype", "enum")
82
- publish("FFFFFF/discover/$format", "discover")
83
- publish("FFFFFF/discover/$settable", "true")
84
- publish("FFFFFF/discover/$retained", "false")
100
+ publish("$state", "ready")
101
+ end
102
+
103
+ @mqtt.on_reconnect do
104
+ subscribe_all
105
+ publish("$state", :init)
106
+ publish("$state", :ready)
107
+ end
108
+ end
85
109
 
110
+ def subscribe_all
86
111
  subscribe("+/discover/set")
87
112
  subscribe("+/label/set")
88
113
  subscribe("+/control/set")
@@ -103,172 +128,174 @@ module SDN
103
128
  subscribe("+/ip#{ip}-pulses/set")
104
129
  subscribe("+/ip#{ip}-percent/set")
105
130
  end
106
-
107
- publish("$state", "ready")
108
131
  end
109
132
 
110
133
  def publish_motor(addr, node_type)
111
- publish("#{addr}/$name", addr)
112
- publish("#{addr}/$type", node_type.to_s)
113
- properties = %w{
114
- discover
115
- label
116
- state
117
- control
118
- jog-ms
119
- jog-pulses
120
- position-pulses
121
- position-percent
122
- ip
123
- down-limit
124
- groups
125
- last-direction
126
- } + (1..16).map { |ip| ["ip#{ip}-pulses", "ip#{ip}-percent"] }.flatten
127
-
128
- unless node_type == :st50ilt2
129
- properties.concat %w{
130
- reset
131
- last-action-source
132
- last-action-cause
133
- up-limit
134
- direction
135
- up-speed
136
- down-speed
137
- slow-speed
138
- }
139
- end
134
+ motor = nil
135
+
136
+ @mqtt.batch_publish do
137
+ publish("#{addr}/$name", addr)
138
+ publish("#{addr}/$type", node_type.to_s)
139
+ properties = %w{
140
+ discover
141
+ label
142
+ state
143
+ control
144
+ jog-ms
145
+ jog-pulses
146
+ position-pulses
147
+ position-percent
148
+ ip
149
+ down-limit
150
+ groups
151
+ last-direction
152
+ } + (1..16).map { |ip| ["ip#{ip}-pulses", "ip#{ip}-percent"] }.flatten
153
+
154
+ unless node_type == :st50ilt2
155
+ properties.concat %w{
156
+ reset
157
+ last-action-source
158
+ last-action-cause
159
+ up-limit
160
+ direction
161
+ up-speed
162
+ down-speed
163
+ slow-speed
164
+ }
165
+ end
140
166
 
141
- publish("#{addr}/$properties", properties.join(","))
142
-
143
- publish("#{addr}/discover/$name", "Trigger Motor Discovery")
144
- publish("#{addr}/discover/$datatype", "enum")
145
- publish("#{addr}/discover/$format", "discover")
146
- publish("#{addr}/discover/$settable", "true")
147
- publish("#{addr}/discover/$retained", "false")
148
-
149
- publish("#{addr}/label/$name", "Node label")
150
- publish("#{addr}/label/$datatype", "string")
151
- publish("#{addr}/label/$settable", "true")
152
-
153
- publish("#{addr}/state/$name", "Current state of the motor")
154
- publish("#{addr}/state/$datatype", "enum")
155
- publish("#{addr}/state/$format", Message::PostMotorStatus::STATE.keys.join(','))
156
-
157
- publish("#{addr}/control/$name", "Control motor")
158
- publish("#{addr}/control/$datatype", "enum")
159
- publish("#{addr}/control/$format", "up,down,stop,wink,next_ip,previous_ip,refresh")
160
- publish("#{addr}/control/$settable", "true")
161
- publish("#{addr}/control/$retained", "false")
162
-
163
- publish("#{addr}/jog-ms/$name", "Jog motor by ms")
164
- publish("#{addr}/jog-ms/$datatype", "integer")
165
- publish("#{addr}/jog-ms/$format", "-65535:65535")
166
- publish("#{addr}/jog-ms/$unit", "ms")
167
- publish("#{addr}/jog-ms/$settable", "true")
168
- publish("#{addr}/jog-ms/$retained", "false")
169
-
170
- publish("#{addr}/jog-pulses/$name", "Jog motor by pulses")
171
- publish("#{addr}/jog-pulses/$datatype", "integer")
172
- publish("#{addr}/jog-pulses/$format", "-65535:65535")
173
- publish("#{addr}/jog-pulses/$unit", "pulses")
174
- publish("#{addr}/jog-pulses/$settable", "true")
175
- publish("#{addr}/jog-pulses/$retained", "false")
176
-
177
- publish("#{addr}/position-percent/$name", "Position (in %)")
178
- publish("#{addr}/position-percent/$datatype", "integer")
179
- publish("#{addr}/position-percent/$format", "0:100")
180
- publish("#{addr}/position-percent/$unit", "%")
181
- publish("#{addr}/position-percent/$settable", "true")
182
-
183
- publish("#{addr}/position-pulses/$name", "Position from up limit (in pulses)")
184
- publish("#{addr}/position-pulses/$datatype", "integer")
185
- publish("#{addr}/position-pulses/$format", "0:65535")
186
- publish("#{addr}/position-pulses/$unit", "pulses")
187
- publish("#{addr}/position-pulses/$settable", "true")
188
-
189
- publish("#{addr}/ip/$name", "Intermediate Position")
190
- publish("#{addr}/ip/$datatype", "integer")
191
- publish("#{addr}/ip/$format", "1:16")
192
- publish("#{addr}/ip/$settable", "true")
193
- publish("#{addr}/ip/$retained", "false") if node_type == :st50ilt2
194
-
195
- publish("#{addr}/down-limit/$name", "Down limit")
196
- publish("#{addr}/down-limit/$datatype", "integer")
197
- publish("#{addr}/down-limit/$format", "0:65535")
198
- publish("#{addr}/down-limit/$unit", "pulses")
199
- publish("#{addr}/down-limit/$settable", "true")
200
-
201
- publish("#{addr}/last-direction/$name", "Direction of last motion")
202
- publish("#{addr}/last-direction/$datatype", "enum")
203
- publish("#{addr}/last-direction/$format", Message::PostMotorStatus::DIRECTION.keys.join(','))
204
-
205
- unless node_type == :st50ilt2
206
- publish("#{addr}/reset/$name", "Recall factory settings")
207
- publish("#{addr}/reset/$datatype", "enum")
208
- publish("#{addr}/reset/$format", Message::SetFactoryDefault::RESET.keys.join(','))
209
- publish("#{addr}/reset/$settable", "true")
210
- publish("#{addr}/reset/$retained", "false")
211
-
212
- publish("#{addr}/last-action-source/$name", "Source of last action")
213
- publish("#{addr}/last-action-source/$datatype", "enum")
214
- publish("#{addr}/last-action-source/$format", Message::PostMotorStatus::SOURCE.keys.join(','))
215
-
216
- publish("#{addr}/last-action-cause/$name", "Cause of last action")
217
- publish("#{addr}/last-action-cause/$datatype", "enum")
218
- publish("#{addr}/last-action-cause/$format", Message::PostMotorStatus::CAUSE.keys.join(','))
219
-
220
- publish("#{addr}/up-limit/$name", "Up limit (always = 0)")
221
- publish("#{addr}/up-limit/$datatype", "integer")
222
- publish("#{addr}/up-limit/$format", "0:65535")
223
- publish("#{addr}/up-limit/$unit", "pulses")
224
- publish("#{addr}/up-limit/$settable", "true")
225
-
226
- publish("#{addr}/direction/$name", "Motor rotation direction")
227
- publish("#{addr}/direction/$datatype", "enum")
228
- publish("#{addr}/direction/$format", "standard,reversed")
229
- publish("#{addr}/direction/$settable", "true")
230
-
231
- publish("#{addr}/up-speed/$name", "Up speed")
232
- publish("#{addr}/up-speed/$datatype", "integer")
233
- publish("#{addr}/up-speed/$format", "6:28")
234
- publish("#{addr}/up-speed/$unit", "RPM")
235
- publish("#{addr}/up-speed/$settable", "true")
236
-
237
- publish("#{addr}/down-speed/$name", "Down speed, always = Up speed")
238
- publish("#{addr}/down-speed/$datatype", "integer")
239
- publish("#{addr}/down-speed/$format", "6:28")
240
- publish("#{addr}/down-speed/$unit", "RPM")
241
- publish("#{addr}/down-speed/$settable", "true")
242
-
243
- publish("#{addr}/slow-speed/$name", "Slow speed")
244
- publish("#{addr}/slow-speed/$datatype", "integer")
245
- publish("#{addr}/slow-speed/$format", "6:28")
246
- publish("#{addr}/slow-speed/$unit", "RPM")
247
- publish("#{addr}/slow-speed/$settable", "true")
248
- end
167
+ publish("#{addr}/$properties", properties.join(","))
168
+
169
+ publish("#{addr}/discover/$name", "Trigger Motor Discovery")
170
+ publish("#{addr}/discover/$datatype", "enum")
171
+ publish("#{addr}/discover/$format", "discover")
172
+ publish("#{addr}/discover/$settable", "true")
173
+ publish("#{addr}/discover/$retained", "false")
174
+
175
+ publish("#{addr}/label/$name", "Node label")
176
+ publish("#{addr}/label/$datatype", "string")
177
+ publish("#{addr}/label/$settable", "true")
178
+
179
+ publish("#{addr}/state/$name", "Current state of the motor")
180
+ publish("#{addr}/state/$datatype", "enum")
181
+ publish("#{addr}/state/$format", Message::PostMotorStatus::STATE.keys.join(','))
182
+
183
+ publish("#{addr}/control/$name", "Control motor")
184
+ publish("#{addr}/control/$datatype", "enum")
185
+ publish("#{addr}/control/$format", "up,down,stop,wink,next_ip,previous_ip,refresh")
186
+ publish("#{addr}/control/$settable", "true")
187
+ publish("#{addr}/control/$retained", "false")
188
+
189
+ publish("#{addr}/jog-ms/$name", "Jog motor by ms")
190
+ publish("#{addr}/jog-ms/$datatype", "integer")
191
+ publish("#{addr}/jog-ms/$format", "-65535:65535")
192
+ publish("#{addr}/jog-ms/$unit", "ms")
193
+ publish("#{addr}/jog-ms/$settable", "true")
194
+ publish("#{addr}/jog-ms/$retained", "false")
195
+
196
+ publish("#{addr}/jog-pulses/$name", "Jog motor by pulses")
197
+ publish("#{addr}/jog-pulses/$datatype", "integer")
198
+ publish("#{addr}/jog-pulses/$format", "-65535:65535")
199
+ publish("#{addr}/jog-pulses/$unit", "pulses")
200
+ publish("#{addr}/jog-pulses/$settable", "true")
201
+ publish("#{addr}/jog-pulses/$retained", "false")
202
+
203
+ publish("#{addr}/position-percent/$name", "Position (in %)")
204
+ publish("#{addr}/position-percent/$datatype", "integer")
205
+ publish("#{addr}/position-percent/$format", "0:100")
206
+ publish("#{addr}/position-percent/$unit", "%")
207
+ publish("#{addr}/position-percent/$settable", "true")
208
+
209
+ publish("#{addr}/position-pulses/$name", "Position from up limit (in pulses)")
210
+ publish("#{addr}/position-pulses/$datatype", "integer")
211
+ publish("#{addr}/position-pulses/$format", "0:65535")
212
+ publish("#{addr}/position-pulses/$unit", "pulses")
213
+ publish("#{addr}/position-pulses/$settable", "true")
214
+
215
+ publish("#{addr}/ip/$name", "Intermediate Position")
216
+ publish("#{addr}/ip/$datatype", "integer")
217
+ publish("#{addr}/ip/$format", "1:16")
218
+ publish("#{addr}/ip/$settable", "true")
219
+ publish("#{addr}/ip/$retained", "false") if node_type == :st50ilt2
220
+
221
+ publish("#{addr}/down-limit/$name", "Down limit")
222
+ publish("#{addr}/down-limit/$datatype", "integer")
223
+ publish("#{addr}/down-limit/$format", "0:65535")
224
+ publish("#{addr}/down-limit/$unit", "pulses")
225
+ publish("#{addr}/down-limit/$settable", "true")
226
+
227
+ publish("#{addr}/last-direction/$name", "Direction of last motion")
228
+ publish("#{addr}/last-direction/$datatype", "enum")
229
+ publish("#{addr}/last-direction/$format", Message::PostMotorStatus::DIRECTION.keys.join(','))
230
+
231
+ unless node_type == :st50ilt2
232
+ publish("#{addr}/reset/$name", "Recall factory settings")
233
+ publish("#{addr}/reset/$datatype", "enum")
234
+ publish("#{addr}/reset/$format", Message::SetFactoryDefault::RESET.keys.join(','))
235
+ publish("#{addr}/reset/$settable", "true")
236
+ publish("#{addr}/reset/$retained", "false")
237
+
238
+ publish("#{addr}/last-action-source/$name", "Source of last action")
239
+ publish("#{addr}/last-action-source/$datatype", "enum")
240
+ publish("#{addr}/last-action-source/$format", Message::PostMotorStatus::SOURCE.keys.join(','))
241
+
242
+ publish("#{addr}/last-action-cause/$name", "Cause of last action")
243
+ publish("#{addr}/last-action-cause/$datatype", "enum")
244
+ publish("#{addr}/last-action-cause/$format", Message::PostMotorStatus::CAUSE.keys.join(','))
245
+
246
+ publish("#{addr}/up-limit/$name", "Up limit (always = 0)")
247
+ publish("#{addr}/up-limit/$datatype", "integer")
248
+ publish("#{addr}/up-limit/$format", "0:65535")
249
+ publish("#{addr}/up-limit/$unit", "pulses")
250
+ publish("#{addr}/up-limit/$settable", "true")
251
+
252
+ publish("#{addr}/direction/$name", "Motor rotation direction")
253
+ publish("#{addr}/direction/$datatype", "enum")
254
+ publish("#{addr}/direction/$format", "standard,reversed")
255
+ publish("#{addr}/direction/$settable", "true")
256
+
257
+ publish("#{addr}/up-speed/$name", "Up speed")
258
+ publish("#{addr}/up-speed/$datatype", "integer")
259
+ publish("#{addr}/up-speed/$format", "6:28")
260
+ publish("#{addr}/up-speed/$unit", "RPM")
261
+ publish("#{addr}/up-speed/$settable", "true")
262
+
263
+ publish("#{addr}/down-speed/$name", "Down speed, always = Up speed")
264
+ publish("#{addr}/down-speed/$datatype", "integer")
265
+ publish("#{addr}/down-speed/$format", "6:28")
266
+ publish("#{addr}/down-speed/$unit", "RPM")
267
+ publish("#{addr}/down-speed/$settable", "true")
268
+
269
+ publish("#{addr}/slow-speed/$name", "Slow speed")
270
+ publish("#{addr}/slow-speed/$datatype", "integer")
271
+ publish("#{addr}/slow-speed/$format", "6:28")
272
+ publish("#{addr}/slow-speed/$unit", "RPM")
273
+ publish("#{addr}/slow-speed/$settable", "true")
274
+ end
249
275
 
250
- publish("#{addr}/groups/$name", "Group Memberships (comma separated, address must start 0101xx)")
251
- publish("#{addr}/groups/$datatype", "string")
252
- publish("#{addr}/groups/$settable", "true")
276
+ publish("#{addr}/groups/$name", "Group Memberships (comma separated, address must start 0101xx)")
277
+ publish("#{addr}/groups/$datatype", "string")
278
+ publish("#{addr}/groups/$settable", "true")
279
+
280
+ (1..16).each do |ip|
281
+ publish("#{addr}/ip#{ip}-pulses/$name", "Intermediate Position #{ip}")
282
+ publish("#{addr}/ip#{ip}-pulses/$datatype", "integer")
283
+ publish("#{addr}/ip#{ip}-pulses/$format", "0:65535")
284
+ publish("#{addr}/ip#{ip}-pulses/$unit", "pulses")
285
+ publish("#{addr}/ip#{ip}-pulses/$settable", "true")
286
+
287
+ publish("#{addr}/ip#{ip}-percent/$name", "Intermediate Position #{ip}")
288
+ publish("#{addr}/ip#{ip}-percent/$datatype", "integer")
289
+ publish("#{addr}/ip#{ip}-percent/$format", "0:100")
290
+ publish("#{addr}/ip#{ip}-percent/$unit", "%")
291
+ publish("#{addr}/ip#{ip}-percent/$settable", "true")
292
+ end
253
293
 
254
- (1..16).each do |ip|
255
- publish("#{addr}/ip#{ip}-pulses/$name", "Intermediate Position #{ip}")
256
- publish("#{addr}/ip#{ip}-pulses/$datatype", "integer")
257
- publish("#{addr}/ip#{ip}-pulses/$format", "0:65535")
258
- publish("#{addr}/ip#{ip}-pulses/$unit", "pulses")
259
- publish("#{addr}/ip#{ip}-pulses/$settable", "true")
260
-
261
- publish("#{addr}/ip#{ip}-percent/$name", "Intermediate Position #{ip}")
262
- publish("#{addr}/ip#{ip}-percent/$datatype", "integer")
263
- publish("#{addr}/ip#{ip}-percent/$format", "0:100")
264
- publish("#{addr}/ip#{ip}-percent/$unit", "%")
265
- publish("#{addr}/ip#{ip}-percent/$settable", "true")
294
+ motor = Motor.new(self, addr, node_type)
295
+ @motors[addr] = motor
296
+ publish("$nodes", (["FFFFFF"] + @motors.keys.sort + @groups.keys.sort).join(","))
266
297
  end
267
298
 
268
- motor = Motor.new(self, addr, node_type)
269
- @motors[addr] = motor
270
- publish("$nodes", (["FFFFFF"] + @motors.keys.sort + @groups.keys.sort).join(","))
271
-
272
299
  sdn_addr = Message.parse_address(addr)
273
300
  @mutex.synchronize do
274
301
  @queues[2].push(MessageAndRetries.new(Message::GetNodeLabel.new(sdn_addr), 5, 2))
@@ -302,66 +329,68 @@ module SDN
302
329
  group = @groups[addr]
303
330
  return group if group
304
331
 
305
- publish("#{addr}/$name", addr)
306
- publish("#{addr}/$type", "Shade Group")
307
- publish("#{addr}/$properties", "discover,control,jog-ms,jog-pulses,position-pulses,position-percent,ip,reset,state,last-direction,motors")
308
-
309
- publish("#{addr}/discover/$name", "Trigger Motor Discovery")
310
- publish("#{addr}/discover/$datatype", "enum")
311
- publish("#{addr}/discover/$format", "discover")
312
- publish("#{addr}/discover/$settable", "true")
313
- publish("#{addr}/discover/$retained", "false")
314
-
315
- publish("#{addr}/control/$name", "Control motors")
316
- publish("#{addr}/control/$datatype", "enum")
317
- publish("#{addr}/control/$format", "up,down,stop,wink,next_ip,previous_ip,refresh")
318
- publish("#{addr}/control/$settable", "true")
319
- publish("#{addr}/control/$retained", "false")
320
-
321
- publish("#{addr}/jog-ms/$name", "Jog motors by ms")
322
- publish("#{addr}/jog-ms/$datatype", "integer")
323
- publish("#{addr}/jog-ms/$format", "-65535:65535")
324
- publish("#{addr}/jog-ms/$unit", "ms")
325
- publish("#{addr}/jog-ms/$settable", "true")
326
- publish("#{addr}/jog-ms/$retained", "false")
327
-
328
- publish("#{addr}/jog-pulses/$name", "Jog motors by pulses")
329
- publish("#{addr}/jog-pulses/$datatype", "integer")
330
- publish("#{addr}/jog-pulses/$format", "-65535:65535")
331
- publish("#{addr}/jog-pulses/$unit", "pulses")
332
- publish("#{addr}/jog-pulses/$settable", "true")
333
- publish("#{addr}/jog-pulses/$retained", "false")
334
-
335
- publish("#{addr}/position-pulses/$name", "Position from up limit (in pulses)")
336
- publish("#{addr}/position-pulses/$datatype", "integer")
337
- publish("#{addr}/position-pulses/$format", "0:65535")
338
- publish("#{addr}/position-pulses/$unit", "pulses")
339
- publish("#{addr}/position-pulses/$settable", "true")
340
-
341
- publish("#{addr}/position-percent/$name", "Position (in %)")
342
- publish("#{addr}/position-percent/$datatype", "integer")
343
- publish("#{addr}/position-percent/$format", "0:100")
344
- publish("#{addr}/position-percent/$unit", "%")
345
- publish("#{addr}/position-percent/$settable", "true")
346
-
347
- publish("#{addr}/ip/$name", "Intermediate Position")
348
- publish("#{addr}/ip/$datatype", "integer")
349
- publish("#{addr}/ip/$format", "1:16")
350
- publish("#{addr}/ip/$settable", "true")
351
-
352
- publish("#{addr}/state/$name", "State of the motors")
353
- publish("#{addr}/state/$datatype", "enum")
354
- publish("#{addr}/state/$format", Message::PostMotorStatus::STATE.keys.join(',') + ",mixed")
355
-
356
- publish("#{addr}/last-direction/$name", "Direction of last motion")
357
- publish("#{addr}/last-direction/$datatype", "enum")
358
- publish("#{addr}/last-direction/$format", Message::PostMotorStatus::DIRECTION.keys.join(',') + ",mixed")
359
-
360
- publish("#{addr}/motors/$name", "Comma separated motor addresses that are members of this group")
361
- publish("#{addr}/motors/$datatype", "string")
362
-
363
- group = @groups[addr] = Group.new(self, addr)
364
- publish("$nodes", (["FFFFFF"] + @motors.keys.sort + @groups.keys.sort).join(","))
332
+ @mqtt.batch_publish do
333
+ publish("#{addr}/$name", addr)
334
+ publish("#{addr}/$type", "Shade Group")
335
+ publish("#{addr}/$properties", "discover,control,jog-ms,jog-pulses,position-pulses,position-percent,ip,reset,state,last-direction,motors")
336
+
337
+ publish("#{addr}/discover/$name", "Trigger Motor Discovery")
338
+ publish("#{addr}/discover/$datatype", "enum")
339
+ publish("#{addr}/discover/$format", "discover")
340
+ publish("#{addr}/discover/$settable", "true")
341
+ publish("#{addr}/discover/$retained", "false")
342
+
343
+ publish("#{addr}/control/$name", "Control motors")
344
+ publish("#{addr}/control/$datatype", "enum")
345
+ publish("#{addr}/control/$format", "up,down,stop,wink,next_ip,previous_ip,refresh")
346
+ publish("#{addr}/control/$settable", "true")
347
+ publish("#{addr}/control/$retained", "false")
348
+
349
+ publish("#{addr}/jog-ms/$name", "Jog motors by ms")
350
+ publish("#{addr}/jog-ms/$datatype", "integer")
351
+ publish("#{addr}/jog-ms/$format", "-65535:65535")
352
+ publish("#{addr}/jog-ms/$unit", "ms")
353
+ publish("#{addr}/jog-ms/$settable", "true")
354
+ publish("#{addr}/jog-ms/$retained", "false")
355
+
356
+ publish("#{addr}/jog-pulses/$name", "Jog motors by pulses")
357
+ publish("#{addr}/jog-pulses/$datatype", "integer")
358
+ publish("#{addr}/jog-pulses/$format", "-65535:65535")
359
+ publish("#{addr}/jog-pulses/$unit", "pulses")
360
+ publish("#{addr}/jog-pulses/$settable", "true")
361
+ publish("#{addr}/jog-pulses/$retained", "false")
362
+
363
+ publish("#{addr}/position-pulses/$name", "Position from up limit (in pulses)")
364
+ publish("#{addr}/position-pulses/$datatype", "integer")
365
+ publish("#{addr}/position-pulses/$format", "0:65535")
366
+ publish("#{addr}/position-pulses/$unit", "pulses")
367
+ publish("#{addr}/position-pulses/$settable", "true")
368
+
369
+ publish("#{addr}/position-percent/$name", "Position (in %)")
370
+ publish("#{addr}/position-percent/$datatype", "integer")
371
+ publish("#{addr}/position-percent/$format", "0:100")
372
+ publish("#{addr}/position-percent/$unit", "%")
373
+ publish("#{addr}/position-percent/$settable", "true")
374
+
375
+ publish("#{addr}/ip/$name", "Intermediate Position")
376
+ publish("#{addr}/ip/$datatype", "integer")
377
+ publish("#{addr}/ip/$format", "1:16")
378
+ publish("#{addr}/ip/$settable", "true")
379
+
380
+ publish("#{addr}/state/$name", "State of the motors")
381
+ publish("#{addr}/state/$datatype", "enum")
382
+ publish("#{addr}/state/$format", Message::PostMotorStatus::STATE.keys.join(',') + ",mixed")
383
+
384
+ publish("#{addr}/last-direction/$name", "Direction of last motion")
385
+ publish("#{addr}/last-direction/$datatype", "enum")
386
+ publish("#{addr}/last-direction/$format", Message::PostMotorStatus::DIRECTION.keys.join(',') + ",mixed")
387
+
388
+ publish("#{addr}/motors/$name", "Comma separated motor addresses that are members of this group")
389
+ publish("#{addr}/motors/$datatype", "string")
390
+
391
+ group = @groups[addr] = Group.new(self, addr)
392
+ publish("$nodes", (["FFFFFF"] + @motors.keys.sort + @groups.keys.sort).join(","))
393
+ end
365
394
  group
366
395
  end
367
396
  end
data/lib/sdn/client.rb CHANGED
@@ -9,10 +9,12 @@ module SDN
9
9
  TCPSocket.new(uri.host, uri.port)
10
10
  elsif uri.scheme == "telnet" || uri.scheme == "rfc2217"
11
11
  require 'net/telnet/rfc2217'
12
- Net::Telnet::RFC2217.new('Host' => uri.host,
13
- 'Port' => uri.port || 23,
14
- 'baud' => 4800,
15
- 'parity' => Net::Telnet::RFC2217::ODD)
12
+ Net::Telnet::RFC2217.new(host: uri.host,
13
+ port: uri.port || 23,
14
+ baud: 4800,
15
+ data_bits: 8,
16
+ parity: :odd,
17
+ stop_bits: 1)
16
18
  elsif port == "/dev/ptmx"
17
19
  require 'pty'
18
20
  io, slave = PTY.open
@@ -56,18 +58,30 @@ module SDN
56
58
  unless message
57
59
  break unless messages.empty?
58
60
 
61
+ # one EOF is just serial ports saying they have no data;
62
+ # two EOFs in a row is the file is dead and gone
63
+ eofs = 0
59
64
  begin
60
65
  block = @io.read_nonblock(64 * 1024)
61
66
  SDN.logger.debug "read #{block.unpack("H*").first.gsub(/\h{2}/, "\\0 ")}"
62
67
  @buffer.concat(block)
63
68
  next
64
- rescue IO::WaitReadable, EOFError
69
+ rescue IO::WaitReadable, EOFError => e
70
+ if e.is_a?(EOFError)
71
+ eofs += 1
72
+ else
73
+ eofs = 0
74
+ end
75
+ raise if eofs == 2
76
+
65
77
  wait = @buffer.empty? ? timeout : WAIT_TIME
66
78
  if @io.wait_readable(wait).nil?
67
79
  # timed out; just discard everything
68
80
  SDN.logger.debug "discarding #{@buffer.unpack("H*").first.gsub(/\h{2}/, "\\0 ")} due to timeout"
69
81
  @buffer = ""
70
82
  end
83
+
84
+ retry
71
85
  end
72
86
  next
73
87
  end
data/lib/sdn/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module SDN
2
- VERSION = '2.0.0'
2
+ VERSION = '2.1.3'
3
3
  end
metadata CHANGED
@@ -1,17 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: somfy_sdn
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cody Cutrer
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-02-13 00:00:00.000000000 Z
11
+ date: 2022-01-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: ccutrer-serialport
14
+ name: mqtt-ccutrer
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
@@ -25,47 +25,47 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: curses
28
+ name: ccutrer-serialport
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '1.4'
33
+ version: '1.0'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '1.4'
40
+ version: '1.0'
41
41
  - !ruby/object:Gem::Dependency
42
- name: mqtt
42
+ name: curses
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 0.5.0
47
+ version: '1.4'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: 0.5.0
54
+ version: '1.4'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: net-telnet-rfc2217
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: 0.0.3
61
+ version: '1.0'
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: 0.0.3
68
+ version: '1.0'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: thor
71
71
  requirement: !ruby/object:Gem::Requirement
@@ -108,7 +108,7 @@ dependencies:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
110
  version: '13.0'
111
- description:
111
+ description:
112
112
  email: cody@cutrer.com'
113
113
  executables:
114
114
  - somfy_sdn
@@ -142,7 +142,7 @@ homepage: https://github.com/ccutrer/somfy_sdn
142
142
  licenses:
143
143
  - MIT
144
144
  metadata: {}
145
- post_install_message:
145
+ post_install_message:
146
146
  rdoc_options: []
147
147
  require_paths:
148
148
  - lib
@@ -157,8 +157,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
157
157
  - !ruby/object:Gem::Version
158
158
  version: '0'
159
159
  requirements: []
160
- rubygems_version: 3.1.4
161
- signing_key:
160
+ rubygems_version: 3.1.2
161
+ signing_key:
162
162
  specification_version: 4
163
163
  summary: Library for communication with Somfy SDN RS-485 motorized shades
164
164
  test_files: []