somfy_sdn 2.0.0 → 2.1.3

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 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: []