somfy_sdn 2.2.1 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1e5c2c8156980740a28a35f3709e51175fa7df14206b88e636f1e5a4eecf8630
4
- data.tar.gz: d1fa621ba70dc475755996b6d0c66621953675a4bc9725a840d530facf51a6ef
3
+ metadata.gz: 58db5a5328195a9cbd9458c4702f2e31490b7a5967d7e94e4ac9fc84a569ba88
4
+ data.tar.gz: cb58ee6964aeb41ce39737b3bdacd44974b2f40dd53395c0376c09b8e2add269
5
5
  SHA512:
6
- metadata.gz: 3b09021cb98f1b1c7bc88c42d0b93d6e2da5115bd6d36ebdd69d6af36101857ad3844f77915b5ca706245d8feb468f920529642f46186c2b3583d56cea85482e
7
- data.tar.gz: d0cf5c1d5cf0fc2edffdec548ee41122f49e2bab5454296084c2f61e068137f0cccdfe394809d6efafec8e282903046a1174cc47e89867643a86ba9573684727
6
+ metadata.gz: 0d2f32416ae7e943f101868cdda35162f1b14dd7e047d1df2725c36095fc4437e713fc32546b03e10c89e013b00210062ff3e8dfd88f6503266cc3a65e5e262b
7
+ data.tar.gz: 3b1e3732ad5d70fbac17f5e498b418a096c890e37b570e5da70cfbeee77c2b0ae6cec25d0ae2f8c3f39bac80e836ab8a70018b43ba75ca3be3421478281f8757
data/exe/somfy_sdn CHANGED
@@ -3,6 +3,7 @@
3
3
 
4
4
  require "somfy_sdn"
5
5
  require "thor"
6
+ require "uri"
6
7
 
7
8
  class SomfySDNCLI < Thor
8
9
  class_option :verbose, type: :boolean, default: false, desc: "Log protocol messages"
@@ -11,6 +11,7 @@ module SDN
11
11
  :position_percent,
12
12
  :ip,
13
13
  :state,
14
+ :hass_state,
14
15
  :last_direction,
15
16
  :last_action_source,
16
17
  :last_action_cause,
@@ -83,6 +83,16 @@ module SDN
83
83
  follow_ups << Message::GetMotorPosition.new(message.src)
84
84
  motor.publish(:state, message.state)
85
85
  motor.publish(:last_direction, message.last_direction)
86
+ hass_state = if message.state == :running
87
+ (message.last_direction == :down) ? :closing : :opening
88
+ elsif motor.position_percent&.zero?
89
+ :open
90
+ elsif motor.position_percent == 100
91
+ :closed
92
+ else
93
+ :stopped
94
+ end
95
+ motor.publish(:hass_state, hass_state)
86
96
  motor.publish(:last_action_source, message.last_action_source)
87
97
  motor.publish(:last_action_cause, message.last_action_cause)
88
98
  motor.group_objects.each do |group|
@@ -59,7 +59,7 @@ module SDN
59
59
  case value
60
60
  when "up", "down"
61
61
  ((motor&.node_type == :st50ilt2) ? ns::SetMotorPosition : Message::MoveTo)
62
- .new(addr, "#{value}_limit".to_sym)
62
+ .new(addr, :"#{value}_limit")
63
63
  when "stop"
64
64
  if motor&.node_type == :st50ilt2
65
65
  ns::SetMotorPosition.new(addr,
@@ -89,7 +89,7 @@ module SDN
89
89
  when /jog-(?:pulses|ms)/
90
90
  value = value.to_i
91
91
  ((motor&.node_type == :st50ilt2) ? ns::SetMotorPosition : Message::MoveOf)
92
- .new(addr, "jog_#{value.negative? ? :up : :down}_#{match[:jog_type]}".to_sym, value.abs)
92
+ .new(addr, :"jog_#{value.negative? ? :up : :down}_#{match[:jog_type]}", value.abs)
93
93
  when "reset"
94
94
  return unless Message::SetFactoryDefault::RESET.key?(value.to_sym)
95
95
 
data/lib/sdn/cli/mqtt.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "mqtt"
4
+ require "mqtt-homeassistant"
4
5
  require "uri"
5
6
  require "set"
6
7
 
@@ -32,6 +33,7 @@ module SDN
32
33
  auto_discover: true,
33
34
  known_motors: [])
34
35
  @base_topic = "#{base_topic}/#{device_id}"
36
+ @device_id = device_id
35
37
  @mqtt = ::MQTT::Client.new(mqtt_uri)
36
38
  @mqtt.set_will("#{@base_topic}/$state", "lost", retain: true)
37
39
  @mqtt.connect
@@ -107,6 +109,21 @@ module SDN
107
109
  publish("FFFFFF/discover/$settable", "true")
108
110
  publish("FFFFFF/discover/$retained", "false")
109
111
 
112
+ hass_device = {
113
+ name: "Somfy SDN Bridge",
114
+ identifiers: @device_id,
115
+ sw_version: SDN::VERSION
116
+ }
117
+ @mqtt.publish_hass_button("discover",
118
+ command_topic: "#{@base_topic}/FFFFFF/discover/set",
119
+ device: hass_device,
120
+ icon: "mdi:search-add",
121
+ name: "Discover Motors",
122
+ node_id: @device_id,
123
+ object_id: "discover",
124
+ unique_id: "#{@device_id}_discover",
125
+ payload_press: "true")
126
+
110
127
  subscribe_all
111
128
 
112
129
  publish("$state", "ready")
@@ -146,6 +163,14 @@ module SDN
146
163
  motor = nil
147
164
 
148
165
  @mqtt.batch_publish do
166
+ hass_device = {
167
+ identifiers: addr,
168
+ model_id: node_type,
169
+ name: addr,
170
+ via_device: @device_id
171
+ }
172
+ node_id = "#{@device_id}_#{addr}"
173
+
149
174
  publish("#{addr}/$name", addr)
150
175
  publish("#{addr}/$type", node_type.to_s)
151
176
  properties = %w[
@@ -181,10 +206,28 @@ module SDN
181
206
  publish("#{addr}/discover/$format", "discover")
182
207
  publish("#{addr}/discover/$settable", "true")
183
208
  publish("#{addr}/discover/$retained", "false")
209
+ @mqtt.publish_hass_button("discover",
210
+ command_topic: "#{@base_topic}/#{addr}/discover/set",
211
+ device: hass_device,
212
+ icon: "mdi:search-add",
213
+ name: "Rediscover",
214
+ node_id: node_id,
215
+ object_id: "discover",
216
+ payload_press: "true",
217
+ unique_id: "#{node_id}_discover")
184
218
 
185
219
  publish("#{addr}/label/$name", "Node label")
186
220
  publish("#{addr}/label/$datatype", "string")
187
221
  publish("#{addr}/label/$settable", "true")
222
+ @mqtt.publish_hass_text("label",
223
+ command_topic: "#{@base_topic}/#{addr}/label/set",
224
+ device: hass_device,
225
+ entity_category: :config,
226
+ icon: "mdi:rename",
227
+ max: 16,
228
+ name: "Label",
229
+ node_id: node_id,
230
+ unique_id: "#{node_id}_label")
188
231
 
189
232
  publish("#{addr}/state/$name", "Current state of the motor")
190
233
  publish("#{addr}/state/$datatype", "enum")
@@ -216,24 +259,95 @@ module SDN
216
259
  publish("#{addr}/position-percent/$unit", "%")
217
260
  publish("#{addr}/position-percent/$settable", "true")
218
261
 
262
+ @mqtt.publish_hass_cover("motor",
263
+ command_topic: "#{@base_topic}/#{addr}/control/set",
264
+ device: hass_device,
265
+ icon: "mdi:roller-shade",
266
+ name: "Motor",
267
+ node_id: node_id,
268
+ payload_close: "down",
269
+ payload_open: "up",
270
+ payload_stop: "stop",
271
+ position_open: 0,
272
+ position_closed: 100,
273
+ position_topic: "#{@base_topic}/#{addr}/position-percent",
274
+ set_position_topic: "#{@base_topic}/#{addr}/position-percent/set",
275
+ state_topic: "#{@base_topic}/#{addr}/hass-state",
276
+ unique_id: "#{node_id}_motor")
277
+ {
278
+ Wink: "mdi:emoticon-wink",
279
+ Next_IP: "mdi:skip-next",
280
+ Previous_IP: "mdi:skip-previous",
281
+ Refresh: "mdi:refresh"
282
+ }.each do |command, icon|
283
+ @mqtt.publish_hass_button(command.to_s.downcase,
284
+ command_topic: "#{@base_topic}/#{addr}/control/set",
285
+ device: hass_device,
286
+ icon: icon,
287
+ name: command.to_s.sub("_", " "),
288
+ node_id: node_id,
289
+ payload_press: command.to_s.downcase,
290
+ unique_id: "#{node_id}_#{command.to_s.downcase}")
291
+ end
292
+
219
293
  publish("#{addr}/position-pulses/$name", "Position from up limit (in pulses)")
220
294
  publish("#{addr}/position-pulses/$datatype", "integer")
221
295
  publish("#{addr}/position-pulses/$format", "0:65535")
222
296
  publish("#{addr}/position-pulses/$unit", "pulses")
223
297
  publish("#{addr}/position-pulses/$settable", "true")
224
298
 
299
+ @mqtt.publish_hass_number("position-pulses",
300
+ command_topic: "#{@base_topic}/#{addr}/position-pulses/set",
301
+ device: hass_device,
302
+ enabled_by_default: false,
303
+ max: 65_536,
304
+ min: 0,
305
+ name: "Position (Pulses)",
306
+ node_id: node_id,
307
+ object_id: "position-pulses",
308
+ state_topic: "#{@base_topic}/#{addr}/position-pulses",
309
+ step: 10,
310
+ unit_of_measurement: "pulses",
311
+ unique_id: "#{node_id}_position-pulses")
312
+
225
313
  publish("#{addr}/ip/$name", "Intermediate Position")
226
314
  publish("#{addr}/ip/$datatype", "integer")
227
315
  publish("#{addr}/ip/$format", "1:16")
228
316
  publish("#{addr}/ip/$settable", "true")
229
317
  publish("#{addr}/ip/$retained", "false") if node_type == :st50ilt2
230
318
 
319
+ @mqtt.publish_hass_number("ip",
320
+ command_topic: "#{@base_topic}/#{addr}/ip/set",
321
+ device: hass_device,
322
+ name: "Intermediate Position",
323
+ max: 16,
324
+ min: 0,
325
+ node_id: node_id,
326
+ object_id: "ip",
327
+ payload_reset: "",
328
+ state_topic: "#{@base_topic}/#{addr}/ip",
329
+ unique_id: "#{node_id}_ip")
330
+
231
331
  publish("#{addr}/down-limit/$name", "Down limit")
232
332
  publish("#{addr}/down-limit/$datatype", "integer")
233
333
  publish("#{addr}/down-limit/$format", "0:65535")
234
334
  publish("#{addr}/down-limit/$unit", "pulses")
235
335
  publish("#{addr}/down-limit/$settable", "true")
236
336
 
337
+ @mqtt.publish_hass_number("down-limit",
338
+ command_topic: "#{@base_topic}/#{addr}/down-limit/set",
339
+ device: hass_device,
340
+ entity_category: :config,
341
+ icon: "mdi:roller-shade-closed",
342
+ max: 65_536,
343
+ min: 0,
344
+ node_id: node_id,
345
+ payload_reset: "",
346
+ state_topic: "#{@base_topic}/#{addr}/down-limit",
347
+ step: 10,
348
+ unit_of_measurement: "pulses",
349
+ unique_id: "#{node_id}_down-limit")
350
+
237
351
  publish("#{addr}/last-direction/$name", "Direction of last motion")
238
352
  publish("#{addr}/last-direction/$datatype", "enum")
239
353
  publish("#{addr}/last-direction/$format", Message::PostMotorStatus::DIRECTION.keys.join(","))
@@ -245,25 +359,86 @@ module SDN
245
359
  publish("#{addr}/reset/$settable", "true")
246
360
  publish("#{addr}/reset/$retained", "false")
247
361
 
362
+ Message::SetFactoryDefault::RESET.each_key do |key|
363
+ @mqtt.publish_hass_button("reset_#{key}",
364
+ command_topic: "#{@base_topic}/#{addr}/reset/set",
365
+ device: hass_device,
366
+ enabled_by_default: false,
367
+ entity_category: :config,
368
+ name: "Reset #{key.to_s.sub("_", " ")}",
369
+ node_id: node_id,
370
+ payload_press: key,
371
+ unique_id: "#{node_id}_#{key}")
372
+ end
373
+
248
374
  publish("#{addr}/last-action-source/$name", "Source of last action")
249
375
  publish("#{addr}/last-action-source/$datatype", "enum")
250
376
  publish("#{addr}/last-action-source/$format", Message::PostMotorStatus::SOURCE.keys.join(","))
251
377
 
378
+ @mqtt.publish_hass_sensor("last-action-source",
379
+ device: hass_device,
380
+ device_class: :enum,
381
+ entity_category: :diagnostic,
382
+ name: "Source of last action",
383
+ node_id: node_id,
384
+ object_id: "last-action-source",
385
+ options: Message::PostMotorStatus::SOURCE.keys,
386
+ state_topic: "#{@base_topic}/#{addr}/last-action-source",
387
+ unique_id: "#{node_id}_last-action-source")
388
+
252
389
  publish("#{addr}/last-action-cause/$name", "Cause of last action")
253
390
  publish("#{addr}/last-action-cause/$datatype", "enum")
254
391
  publish("#{addr}/last-action-cause/$format", Message::PostMotorStatus::CAUSE.keys.join(","))
255
392
 
393
+ @mqtt.publish_hass_sensor("last-action-cause",
394
+ device: hass_device,
395
+ device_class: :enum,
396
+ entity_category: :diagnostic,
397
+ name: "Cause of last action",
398
+ node_id: node_id,
399
+ object_id: "last-action-cause",
400
+ options: Message::PostMotorStatus::CAUSE.keys,
401
+ state_topic: "#{@base_topic}/#{addr}/last-action-cause",
402
+ unique_id: "#{node_id}_last-action-cause")
403
+
256
404
  publish("#{addr}/up-limit/$name", "Up limit (always = 0)")
257
405
  publish("#{addr}/up-limit/$datatype", "integer")
258
406
  publish("#{addr}/up-limit/$format", "0:65535")
259
407
  publish("#{addr}/up-limit/$unit", "pulses")
260
408
  publish("#{addr}/up-limit/$settable", "true")
261
409
 
410
+ @mqtt.publish_hass_number("up-limit",
411
+ command_topic: "#{@base_topic}/#{addr}/up-limit/set",
412
+ device: hass_device,
413
+ entity_category: :config,
414
+ icon: "mdi:roller-shade-open",
415
+ max: 65_536,
416
+ min: 0,
417
+ name: "Up Limit",
418
+ node_id: node_id,
419
+ payload_reset: "",
420
+ state_topic: "#{@base_topic}/#{addr}/up-limit",
421
+ step: 10,
422
+ unit_of_measurement: "pulses",
423
+ unique_id: "#{node_id}_up-limit")
424
+
262
425
  publish("#{addr}/direction/$name", "Motor rotation direction")
263
426
  publish("#{addr}/direction/$datatype", "enum")
264
427
  publish("#{addr}/direction/$format", "standard,reversed")
265
428
  publish("#{addr}/direction/$settable", "true")
266
429
 
430
+ @mqtt.publish_hass_select("direction",
431
+ command_topic: "#{@base_topic}/#{addr}/direction/set",
432
+ device: hass_device,
433
+ entity_category: :config,
434
+ icon: "mdi:circle-arrows",
435
+ name: "Motor rotation direction",
436
+ node_id: node_id,
437
+ object_id: "direction",
438
+ options: %w[standard reversed],
439
+ state_topic: "#{@base_topic}/#{addr}/direction",
440
+ unique_id: "#{node_id}_direction")
441
+
267
442
  publish("#{addr}/up-speed/$name", "Up speed")
268
443
  publish("#{addr}/up-speed/$datatype", "integer")
269
444
  publish("#{addr}/up-speed/$format", "6:28")
@@ -281,6 +456,21 @@ module SDN
281
456
  publish("#{addr}/slow-speed/$format", "6:28")
282
457
  publish("#{addr}/slow-speed/$unit", "RPM")
283
458
  publish("#{addr}/slow-speed/$settable", "true")
459
+
460
+ %w[Up Slow].each do |speed_type|
461
+ @mqtt.publish_hass_number("#{speed_type.downcase}-speed",
462
+ command_topic: "#{@base_topic}/#{addr}/#{speed_type.downcase}-speed/set",
463
+ device: hass_device,
464
+ entity_category: :config,
465
+ icon: "mdi:car-speed-limiter",
466
+ max: 28,
467
+ min: 6,
468
+ name: "#{speed_type} speed",
469
+ node_id: node_id,
470
+ state_topic: "#{@base_topic}/#{addr}/#{speed_type.downcase}-speed",
471
+ unit_of_measurement: "RPM",
472
+ unique_id: "#{node_id}_#{speed_type.downcase}-speed")
473
+ end
284
474
  end
285
475
 
286
476
  publish("#{addr}/groups/$name", "Group Memberships (comma separated, address must start 0101xx)")
@@ -294,11 +484,41 @@ module SDN
294
484
  publish("#{addr}/ip#{ip}-pulses/$unit", "pulses")
295
485
  publish("#{addr}/ip#{ip}-pulses/$settable", "true")
296
486
 
487
+ @mqtt.publish_hass_number("ip#{ip}-pulses",
488
+ command_topic: "#{@base_topic}/#{addr}/ip#{ip}-pulses/set",
489
+ device: hass_device,
490
+ enabled_by_default: false,
491
+ entity_category: :config,
492
+ max: 65_536,
493
+ min: 0,
494
+ name: "Intermediation Position #{ip} (Pulses)",
495
+ node_id: node_id,
496
+ object_id: "ip#{ip}-pulses",
497
+ payload_reset: "",
498
+ state_topic: "#{@base_topic}/#{addr}/ip#{ip}-pulses",
499
+ step: 10,
500
+ unit_of_measurement: "pulses",
501
+ unique_id: "#{node_id}_ip#{ip}-pulses")
502
+
297
503
  publish("#{addr}/ip#{ip}-percent/$name", "Intermediate Position #{ip}")
298
504
  publish("#{addr}/ip#{ip}-percent/$datatype", "integer")
299
505
  publish("#{addr}/ip#{ip}-percent/$format", "0:100")
300
506
  publish("#{addr}/ip#{ip}-percent/$unit", "%")
301
507
  publish("#{addr}/ip#{ip}-percent/$settable", "true")
508
+
509
+ @mqtt.publish_hass_number("ip#{ip}-percent",
510
+ command_topic: "#{@base_topic}/#{addr}/ip#{ip}-percent/set",
511
+ device: hass_device,
512
+ entity_category: :config,
513
+ max: 100,
514
+ min: 0,
515
+ name: "Intermediation Position #{ip} (Percent)",
516
+ node_id: node_id,
517
+ object_id: "ip#{ip}-percent",
518
+ payload_reset: "",
519
+ state_topic: "#{@base_topic}/#{addr}/ip#{ip}-percent",
520
+ unit_of_measurement: "%",
521
+ unique_id: "#{node_id}_ip#{ip}-percent")
302
522
  end
303
523
 
304
524
  motor = Motor.new(self, addr, node_type)
@@ -130,11 +130,7 @@ module SDN
130
130
  end
131
131
  refresh
132
132
  when "l"
133
- if ilt2?
134
- sdn.ensure(Message::ILT2::SetMotorSettings.new(addr, reversed_int, @pos, @pos))
135
- else
136
- sdn.ensure(Message::SetMotorLimits.new(addr, :current_position, :down))
137
- end
133
+ sdn.ensure(Message::ILT2::SetMotorSettings.new(addr, reversed_int, @pos, @pos))
138
134
  refresh
139
135
  when "r"
140
136
  @reversed = !@reversed
data/lib/sdn/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SDN
4
- VERSION = "2.2.1"
4
+ VERSION = "2.3.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: somfy_sdn
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.1
4
+ version: 2.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cody Cutrer
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-11-23 00:00:00.000000000 Z
11
+ date: 2025-01-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ccutrer-serialport
@@ -39,7 +39,7 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: '1.4'
41
41
  - !ruby/object:Gem::Dependency
42
- name: mqtt-ccutrer
42
+ name: mqtt-homeassistant
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
@@ -80,20 +80,6 @@ dependencies:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
82
  version: '1.1'
83
- - !ruby/object:Gem::Dependency
84
- name: byebug
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - "~>"
88
- - !ruby/object:Gem::Version
89
- version: '9.0'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - "~>"
95
- - !ruby/object:Gem::Version
96
- version: '9.0'
97
83
  - !ruby/object:Gem::Dependency
98
84
  name: rake
99
85
  requirement: !ruby/object:Gem::Requirement
@@ -108,35 +94,7 @@ dependencies:
108
94
  - - "~>"
109
95
  - !ruby/object:Gem::Version
110
96
  version: '13.0'
111
- - !ruby/object:Gem::Dependency
112
- name: rubocop-inst
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - "~>"
116
- - !ruby/object:Gem::Version
117
- version: '1.0'
118
- type: :development
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - "~>"
123
- - !ruby/object:Gem::Version
124
- version: '1.0'
125
- - !ruby/object:Gem::Dependency
126
- name: rubocop-rake
127
- requirement: !ruby/object:Gem::Requirement
128
- requirements:
129
- - - "~>"
130
- - !ruby/object:Gem::Version
131
- version: '0.6'
132
- type: :development
133
- prerelease: false
134
- version_requirements: !ruby/object:Gem::Requirement
135
- requirements:
136
- - - "~>"
137
- - !ruby/object:Gem::Version
138
- version: '0.6'
139
- description:
97
+ description:
140
98
  email: cody@cutrer.com'
141
99
  executables:
142
100
  - somfy_sdn
@@ -172,7 +130,7 @@ licenses:
172
130
  - MIT
173
131
  metadata:
174
132
  rubygems_mfa_required: 'true'
175
- post_install_message:
133
+ post_install_message:
176
134
  rdoc_options: []
177
135
  require_paths:
178
136
  - lib
@@ -187,8 +145,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
187
145
  - !ruby/object:Gem::Version
188
146
  version: '0'
189
147
  requirements: []
190
- rubygems_version: 3.3.7
191
- signing_key:
148
+ rubygems_version: 3.5.11
149
+ signing_key:
192
150
  specification_version: 4
193
151
  summary: Library for communication with Somfy SDN RS-485 motorized shades
194
152
  test_files: []