somfy_sdn 2.4.0 → 2.5.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.
data/lib/sdn/cli/mqtt.rb CHANGED
@@ -3,7 +3,6 @@
3
3
  require "mqtt"
4
4
  require "mqtt-homeassistant"
5
5
  require "uri"
6
- require "set"
7
6
 
8
7
  require "sdn/cli/mqtt/group"
9
8
  require "sdn/cli/mqtt/motor"
@@ -15,18 +14,40 @@ require "sdn/version"
15
14
 
16
15
  module SDN
17
16
  module CLI
17
+ # @!visibility private
18
18
  class MQTT
19
+ # Queue entry wrapper containing an SDN message, retry budget, and scheduling priority.
19
20
  MessageAndRetries = Struct.new(:message, :remaining_retries, :priority)
20
21
 
21
22
  include Read
22
23
  include Write
23
24
  include Subscriptions
24
25
 
26
+ # Time to wait, in seconds, for a direct response before retrying.
25
27
  WAIT_TIME = 0.25
28
+ # Time to wait, in seconds, for broadcast or group discovery responses.
26
29
  BROADCAST_WAIT = 5.0
27
30
 
28
- attr_reader :motors, :groups
29
-
31
+ # Discovered motors keyed by normalized address.
32
+ #
33
+ # @return [{String => SDN::CLI::MQTT::Motor}]
34
+ attr_reader :motors
35
+
36
+ # Discovered groups keyed by normalized address.
37
+ #
38
+ # @return [{String => SDN::CLI::MQTT::Group}]
39
+ attr_reader :groups
40
+
41
+ # Creates the MQTT bridge, publishes discovery metadata, and starts read/write loops.
42
+ #
43
+ # @param sdn [SDN::Client]
44
+ # @param mqtt_uri [String]
45
+ # @param device_id [String] Homie bridge device identifier
46
+ # @param base_topic [String] Homie base MQTT topic
47
+ # @param auto_discover [true, false] whether to periodically discover motors
48
+ # @param known_motors [<String>] motor addresses to seed discovery
49
+ # @param homie [true, false] whether to publish Homie metadata
50
+ # @param home_assistant [true, false] whether to publish Home Assistant discovery data
30
51
  def initialize(sdn,
31
52
  mqtt_uri,
32
53
  device_id: "somfy",
@@ -43,6 +64,12 @@ module SDN
43
64
  @mqtt.set_will("#{@base_topic}/$state", "lost", retain: true)
44
65
  @mqtt.connect
45
66
 
67
+ @availability = [{
68
+ topic: "#{@base_topic}/$state",
69
+ payload_available: "ready",
70
+ payload_not_available: "lost"
71
+ }.freeze].freeze
72
+
46
73
  @motors = {}
47
74
  @groups = {}
48
75
 
@@ -75,10 +102,214 @@ module SDN
75
102
  @mqtt.publish("#{@base_topic}/#{topic}", value, retain: true, qos: 1)
76
103
  end
77
104
 
105
+ # Refreshes a group's published motor membership when one of its members changes.
106
+ #
107
+ # @param group_addr [(Integer, Integer, Integer))] group address bytes
108
+ #
109
+ # @return [void]
110
+ def touch_group(group_addr)
111
+ group = @groups[Message.print_address(group_addr).delete(".")]
112
+ group&.publish(:motors, group.motors_string)
113
+ end
114
+
115
+ # Publishes MQTT metadata for a group, creating it if necessary.
116
+ #
117
+ # @param addr [String] printable group address
118
+ #
119
+ # @return [SDN::CLI::MQTT::Group]
120
+ def add_group(addr)
121
+ addr = addr.delete(".")
122
+ group = @groups[addr]
123
+ return group if group
124
+
125
+ @mqtt.batch_publish do
126
+ hass_device = {
127
+ identifiers: addr,
128
+ model: "Shade Group",
129
+ name: addr,
130
+ via_device: @device_id
131
+ }.freeze
132
+ node_id = "#{@device_id}_#{addr}"
133
+
134
+ if @homie
135
+ publish("#{addr}/$name", addr)
136
+ publish("#{addr}/$type", "Shade Group")
137
+ publish("#{addr}/$properties",
138
+ "discover,control,jog-ms,jog-pulses,position-pulses,position-percent," \
139
+ "ip,reset,state,last-direction,motors")
140
+
141
+ publish("#{addr}/discover/$name", "Trigger Motor Discovery")
142
+ publish("#{addr}/discover/$datatype", "enum")
143
+ publish("#{addr}/discover/$format", "discover")
144
+ publish("#{addr}/discover/$settable", "true")
145
+ publish("#{addr}/discover/$retained", "false")
146
+ end
147
+
148
+ if @home_assistant
149
+ @mqtt.publish_hass_button("discover",
150
+ command_topic: "#{@base_topic}/#{addr}/discover/set",
151
+ device: hass_device,
152
+ icon: "mdi:search-add",
153
+ name: "Rediscover",
154
+ node_id:,
155
+ object_id: "discover",
156
+ payload_press: "discover",
157
+ unique_id: "#{node_id}_discover",
158
+ availability: @availability)
159
+ end
160
+
161
+ if @homie
162
+ publish("#{addr}/control/$name", "Control motors")
163
+ publish("#{addr}/control/$datatype", "enum")
164
+ publish("#{addr}/control/$format", "up,down,stop,wink,next_ip,previous_ip,refresh")
165
+ publish("#{addr}/control/$settable", "true")
166
+ publish("#{addr}/control/$retained", "false")
167
+ end
168
+
169
+ if @home_assistant
170
+ @mqtt.publish_hass_cover("group",
171
+ command_topic: "#{@base_topic}/#{addr}/control/set",
172
+ device: hass_device,
173
+ icon: "mdi:roller-shade",
174
+ name: "Group",
175
+ node_id:,
176
+ payload_close: "down",
177
+ payload_open: "up",
178
+ payload_stop: "stop",
179
+ position_open: 0,
180
+ position_closed: 100,
181
+ position_topic: "#{@base_topic}/#{addr}/position-percent",
182
+ set_position_topic: "#{@base_topic}/#{addr}/position-percent/set",
183
+ state_topic: "#{@base_topic}/#{addr}/hass-state",
184
+ unique_id: "#{node_id}_group",
185
+ availability: @availability)
186
+ {
187
+ Wink: "mdi:emoticon-wink",
188
+ Next_IP: "mdi:skip-next",
189
+ Previous_IP: "mdi:skip-previous",
190
+ Refresh: "mdi:refresh"
191
+ }.each do |command, icon|
192
+ @mqtt.publish_hass_button(command.to_s.downcase,
193
+ command_topic: "#{@base_topic}/#{addr}/control/set",
194
+ device: hass_device,
195
+ icon:,
196
+ name: command.to_s.sub("_", " "),
197
+ node_id:,
198
+ payload_press: command.to_s.downcase,
199
+ unique_id: "#{node_id}_#{command.to_s.downcase}",
200
+ availability: @availability)
201
+ end
202
+ end
203
+
204
+ if @homie
205
+ publish("#{addr}/jog-ms/$name", "Jog motors by ms")
206
+ publish("#{addr}/jog-ms/$datatype", "integer")
207
+ publish("#{addr}/jog-ms/$format", "-65535:65535")
208
+ publish("#{addr}/jog-ms/$unit", "ms")
209
+ publish("#{addr}/jog-ms/$settable", "true")
210
+ publish("#{addr}/jog-ms/$retained", "false")
211
+
212
+ publish("#{addr}/jog-pulses/$name", "Jog motors by pulses")
213
+ publish("#{addr}/jog-pulses/$datatype", "integer")
214
+ publish("#{addr}/jog-pulses/$format", "-65535:65535")
215
+ publish("#{addr}/jog-pulses/$unit", "pulses")
216
+ publish("#{addr}/jog-pulses/$settable", "true")
217
+ publish("#{addr}/jog-pulses/$retained", "false")
218
+
219
+ publish("#{addr}/position-pulses/$name", "Position from up limit (in pulses)")
220
+ publish("#{addr}/position-pulses/$datatype", "integer")
221
+ publish("#{addr}/position-pulses/$format", "0:65535")
222
+ publish("#{addr}/position-pulses/$unit", "pulses")
223
+ publish("#{addr}/position-pulses/$settable", "true")
224
+ end
225
+
226
+ if @home_assistant
227
+ @mqtt.publish_hass_number("position-pulses",
228
+ command_topic: "#{@base_topic}/#{addr}/position-pulses/set",
229
+ device: hass_device,
230
+ enabled_by_default: false,
231
+ max: 65_536,
232
+ min: 0,
233
+ name: "Position (Pulses)",
234
+ node_id:,
235
+ object_id: "position-pulses",
236
+ state_topic: "#{@base_topic}/#{addr}/position-pulses",
237
+ step: 10,
238
+ unit_of_measurement: "pulses",
239
+ unique_id: "#{node_id}_position-pulses",
240
+ availability: @availability)
241
+ end
242
+
243
+ if @homie
244
+ publish("#{addr}/position-percent/$name", "Position (in %)")
245
+ publish("#{addr}/position-percent/$datatype", "integer")
246
+ publish("#{addr}/position-percent/$format", "0:100")
247
+ publish("#{addr}/position-percent/$unit", "%")
248
+ publish("#{addr}/position-percent/$settable", "true")
249
+
250
+ publish("#{addr}/ip/$name", "Intermediate Position")
251
+ publish("#{addr}/ip/$datatype", "integer")
252
+ publish("#{addr}/ip/$format", "1:16")
253
+ publish("#{addr}/ip/$settable", "true")
254
+ end
255
+
256
+ if @home_assistant
257
+ @mqtt.publish_hass_number("ip",
258
+ command_topic: "#{@base_topic}/#{addr}/ip/set",
259
+ device: hass_device,
260
+ name: "Intermediate Position",
261
+ max: 16,
262
+ min: 0,
263
+ node_id:,
264
+ object_id: "ip",
265
+ payload_reset: "",
266
+ state_topic: "#{@base_topic}/#{addr}/ip",
267
+ unique_id: "#{node_id}_ip",
268
+ availability: @availability)
269
+ end
270
+
271
+ if @homie
272
+ publish("#{addr}/state/$name", "State of the motors")
273
+ publish("#{addr}/state/$datatype", "enum")
274
+ publish("#{addr}/state/$format", "#{Message::PostMotorStatus::STATE.keys.join(",")},mixed")
275
+
276
+ publish("#{addr}/last-direction/$name", "Direction of last motion")
277
+ publish("#{addr}/last-direction/$datatype", "enum")
278
+ publish("#{addr}/last-direction/$format", "#{Message::PostMotorStatus::DIRECTION.keys.join(",")},mixed")
279
+
280
+ publish("#{addr}/motors/$name", "Comma separated motor addresses that are members of this group")
281
+ publish("#{addr}/motors/$datatype", "string")
282
+ end
283
+
284
+ group = @groups[addr] = Group.new(self, addr)
285
+ publish("$nodes", (["FFFFFF"] + @motors.keys.sort + @groups.keys.sort).join(",")) if @homie
286
+ end
287
+ group
288
+ end
289
+
290
+ private
291
+
292
+ def motor_hass_state(state, direction, position_percent)
293
+ if state == :running
294
+ (direction == :down) ? :closing : :opening
295
+ elsif position_percent&.zero?
296
+ :open
297
+ elsif position_percent == 100
298
+ :closed
299
+ else
300
+ :stopped
301
+ end
302
+ end
303
+
78
304
  def subscribe(topic)
79
305
  @mqtt.subscribe("#{@base_topic}/#{topic}")
80
306
  end
81
307
 
308
+ # Adds a message to the prioritized write queue if it is not already present.
309
+ #
310
+ # @param message [Message]
311
+ #
312
+ # @return [void]
82
313
  def enqueue(message)
83
314
  @mutex.synchronize do
84
315
  break if @queue.include?(message)
@@ -121,7 +352,7 @@ module SDN
121
352
  name: "Somfy SDN Bridge",
122
353
  identifiers: @device_id,
123
354
  sw_version: SDN::VERSION
124
- }
355
+ }.freeze
125
356
  @mqtt.publish_hass_button("discover",
126
357
  command_topic: "#{@base_topic}/FFFFFF/discover/set",
127
358
  device: hass_device,
@@ -130,7 +361,8 @@ module SDN
130
361
  node_id: @device_id,
131
362
  object_id: "discover",
132
363
  unique_id: "#{@device_id}_discover",
133
- payload_press: "true")
364
+ payload_press: "true",
365
+ availability: @availability)
134
366
  end
135
367
 
136
368
  subscribe_all
@@ -168,6 +400,12 @@ module SDN
168
400
  end
169
401
  end
170
402
 
403
+ # Publishes MQTT metadata for a motor and schedules follow-up SDN discovery queries.
404
+ #
405
+ # @param addr [String] printable motor address without separators
406
+ # @param node_type [Symbol, Integer] detected motor node type
407
+ #
408
+ # @return [SDN::CLI::MQTT::Motor]
171
409
  def publish_motor(addr, node_type)
172
410
  motor = nil
173
411
 
@@ -177,7 +415,7 @@ module SDN
177
415
  model_id: node_type,
178
416
  name: addr,
179
417
  via_device: @device_id
180
- }
418
+ }.freeze
181
419
  node_id = "#{@device_id}_#{addr}"
182
420
 
183
421
  if @homie
@@ -226,10 +464,11 @@ module SDN
226
464
  device: hass_device,
227
465
  icon: "mdi:search-add",
228
466
  name: "Rediscover",
229
- node_id: node_id,
467
+ node_id:,
230
468
  object_id: "discover",
231
469
  payload_press: "true",
232
- unique_id: "#{node_id}_discover")
470
+ unique_id: "#{node_id}_discover",
471
+ availability: @availability)
233
472
  end
234
473
 
235
474
  if @homie
@@ -246,8 +485,9 @@ module SDN
246
485
  icon: "mdi:rename",
247
486
  max: 16,
248
487
  name: "Label",
249
- node_id: node_id,
250
- unique_id: "#{node_id}_label")
488
+ node_id:,
489
+ unique_id: "#{node_id}_label",
490
+ availability: @availability)
251
491
  end
252
492
 
253
493
  if @homie
@@ -288,7 +528,7 @@ module SDN
288
528
  device: hass_device,
289
529
  icon: "mdi:roller-shade",
290
530
  name: "Motor",
291
- node_id: node_id,
531
+ node_id:,
292
532
  payload_close: "down",
293
533
  payload_open: "up",
294
534
  payload_stop: "stop",
@@ -297,7 +537,8 @@ module SDN
297
537
  position_topic: "#{@base_topic}/#{addr}/position-percent",
298
538
  set_position_topic: "#{@base_topic}/#{addr}/position-percent/set",
299
539
  state_topic: "#{@base_topic}/#{addr}/hass-state",
300
- unique_id: "#{node_id}_motor")
540
+ unique_id: "#{node_id}_motor",
541
+ availability: @availability)
301
542
  {
302
543
  Wink: "mdi:emoticon-wink",
303
544
  Next_IP: "mdi:skip-next",
@@ -307,11 +548,12 @@ module SDN
307
548
  @mqtt.publish_hass_button(command.to_s.downcase,
308
549
  command_topic: "#{@base_topic}/#{addr}/control/set",
309
550
  device: hass_device,
310
- icon: icon,
551
+ icon:,
311
552
  name: command.to_s.sub("_", " "),
312
- node_id: node_id,
553
+ node_id:,
313
554
  payload_press: command.to_s.downcase,
314
- unique_id: "#{node_id}_#{command.to_s.downcase}")
555
+ unique_id: "#{node_id}_#{command.to_s.downcase}",
556
+ availability: @availability)
315
557
  end
316
558
  end
317
559
 
@@ -331,12 +573,13 @@ module SDN
331
573
  max: 65_536,
332
574
  min: 0,
333
575
  name: "Position (Pulses)",
334
- node_id: node_id,
576
+ node_id:,
335
577
  object_id: "position-pulses",
336
578
  state_topic: "#{@base_topic}/#{addr}/position-pulses",
337
579
  step: 10,
338
580
  unit_of_measurement: "pulses",
339
- unique_id: "#{node_id}_position-pulses")
581
+ unique_id: "#{node_id}_position-pulses",
582
+ availability: @availability)
340
583
  end
341
584
 
342
585
  if @homie
@@ -354,11 +597,12 @@ module SDN
354
597
  name: "Intermediate Position",
355
598
  max: 16,
356
599
  min: 0,
357
- node_id: node_id,
600
+ node_id:,
358
601
  object_id: "ip",
359
602
  payload_reset: "",
360
603
  state_topic: "#{@base_topic}/#{addr}/ip",
361
- unique_id: "#{node_id}_ip")
604
+ unique_id: "#{node_id}_ip",
605
+ availability: @availability)
362
606
  end
363
607
 
364
608
  if @homie
@@ -377,12 +621,13 @@ module SDN
377
621
  icon: "mdi:roller-shade-closed",
378
622
  max: 65_536,
379
623
  min: 0,
380
- node_id: node_id,
624
+ node_id:,
381
625
  payload_reset: "",
382
626
  state_topic: "#{@base_topic}/#{addr}/down-limit",
383
627
  step: 10,
384
628
  unit_of_measurement: "pulses",
385
- unique_id: "#{node_id}_down-limit")
629
+ unique_id: "#{node_id}_down-limit",
630
+ availability: @availability)
386
631
  end
387
632
 
388
633
  if @homie
@@ -408,16 +653,18 @@ module SDN
408
653
  enabled_by_default: false,
409
654
  entity_category: :config,
410
655
  name: "Reset #{key.to_s.sub("_", " ")}",
411
- node_id: node_id,
656
+ node_id:,
412
657
  payload_press: key,
413
- unique_id: "#{node_id}_#{key}")
658
+ unique_id: "#{node_id}_#{key}",
659
+ availability: @availability)
414
660
  end
415
661
  end
416
662
 
417
663
  if @homie
418
664
  publish("#{addr}/last-action-source/$name", "Source of last action")
419
665
  publish("#{addr}/last-action-source/$datatype", "enum")
420
- publish("#{addr}/last-action-source/$format", Message::PostMotorStatus::SOURCE.keys.join(","))
666
+ publish("#{addr}/last-action-source/$format",
667
+ (Message::PostMotorStatus::SOURCE.keys + [:bridge]).join(","))
421
668
  end
422
669
 
423
670
  if @home_assistant
@@ -426,17 +673,19 @@ module SDN
426
673
  device_class: :enum,
427
674
  entity_category: :diagnostic,
428
675
  name: "Source of last action",
429
- node_id: node_id,
676
+ node_id:,
430
677
  object_id: "last-action-source",
431
678
  options: Message::PostMotorStatus::SOURCE.keys,
432
679
  state_topic: "#{@base_topic}/#{addr}/last-action-source",
433
- unique_id: "#{node_id}_last-action-source")
680
+ unique_id: "#{node_id}_last-action-source",
681
+ availability: @availability)
434
682
  end
435
683
 
436
684
  if @homie
437
685
  publish("#{addr}/last-action-cause/$name", "Cause of last action")
438
686
  publish("#{addr}/last-action-cause/$datatype", "enum")
439
- publish("#{addr}/last-action-cause/$format", Message::PostMotorStatus::CAUSE.keys.join(","))
687
+ publish("#{addr}/last-action-cause/$format",
688
+ (Message::PostMotorStatus::CAUSE.keys + [:timeout]).join(","))
440
689
  end
441
690
 
442
691
  if @home_assistant
@@ -445,11 +694,12 @@ module SDN
445
694
  device_class: :enum,
446
695
  entity_category: :diagnostic,
447
696
  name: "Cause of last action",
448
- node_id: node_id,
697
+ node_id:,
449
698
  object_id: "last-action-cause",
450
699
  options: Message::PostMotorStatus::CAUSE.keys,
451
700
  state_topic: "#{@base_topic}/#{addr}/last-action-cause",
452
- unique_id: "#{node_id}_last-action-cause")
701
+ unique_id: "#{node_id}_last-action-cause",
702
+ availability: @availability)
453
703
  end
454
704
 
455
705
  if @homie
@@ -460,7 +710,7 @@ module SDN
460
710
  publish("#{addr}/up-limit/$settable", "true")
461
711
  end
462
712
 
463
- if @homie
713
+ if @home_assistant
464
714
  @mqtt.publish_hass_number("up-limit",
465
715
  command_topic: "#{@base_topic}/#{addr}/up-limit/set",
466
716
  device: hass_device,
@@ -469,12 +719,13 @@ module SDN
469
719
  max: 65_536,
470
720
  min: 0,
471
721
  name: "Up Limit",
472
- node_id: node_id,
722
+ node_id:,
473
723
  payload_reset: "",
474
724
  state_topic: "#{@base_topic}/#{addr}/up-limit",
475
725
  step: 10,
476
726
  unit_of_measurement: "pulses",
477
- unique_id: "#{node_id}_up-limit")
727
+ unique_id: "#{node_id}_up-limit",
728
+ availability: @availability)
478
729
  end
479
730
 
480
731
  if @homie
@@ -484,18 +735,19 @@ module SDN
484
735
  publish("#{addr}/direction/$settable", "true")
485
736
  end
486
737
 
487
- if @homie
738
+ if @home_assistant
488
739
  @mqtt.publish_hass_select("direction",
489
740
  command_topic: "#{@base_topic}/#{addr}/direction/set",
490
741
  device: hass_device,
491
742
  entity_category: :config,
492
743
  icon: "mdi:circle-arrows",
493
744
  name: "Motor rotation direction",
494
- node_id: node_id,
745
+ node_id:,
495
746
  object_id: "direction",
496
747
  options: %w[standard reversed],
497
748
  state_topic: "#{@base_topic}/#{addr}/direction",
498
- unique_id: "#{node_id}_direction")
749
+ unique_id: "#{node_id}_direction",
750
+ availability: @availability)
499
751
  end
500
752
 
501
753
  if @homie
@@ -528,10 +780,11 @@ module SDN
528
780
  max: 28,
529
781
  min: 6,
530
782
  name: "#{speed_type} speed",
531
- node_id: node_id,
783
+ node_id:,
532
784
  state_topic: "#{@base_topic}/#{addr}/#{speed_type.downcase}-speed",
533
785
  unit_of_measurement: "RPM",
534
- unique_id: "#{node_id}_#{speed_type.downcase}-speed")
786
+ unique_id: "#{node_id}_#{speed_type.downcase}-speed",
787
+ availability: @availability)
535
788
  end
536
789
  end
537
790
  end
@@ -560,13 +813,14 @@ module SDN
560
813
  max: 65_536,
561
814
  min: 0,
562
815
  name: "Intermediation Position #{ip} (Pulses)",
563
- node_id: node_id,
816
+ node_id:,
564
817
  object_id: "ip#{ip}-pulses",
565
818
  payload_reset: "",
566
819
  state_topic: "#{@base_topic}/#{addr}/ip#{ip}-pulses",
567
820
  step: 10,
568
821
  unit_of_measurement: "pulses",
569
- unique_id: "#{node_id}_ip#{ip}-pulses")
822
+ unique_id: "#{node_id}_ip#{ip}-pulses",
823
+ availability: @availability)
570
824
  end
571
825
 
572
826
  if @homie
@@ -586,12 +840,13 @@ module SDN
586
840
  max: 100,
587
841
  min: 0,
588
842
  name: "Intermediation Position #{ip} (Percent)",
589
- node_id: node_id,
843
+ node_id:,
590
844
  object_id: "ip#{ip}-percent",
591
845
  payload_reset: "",
592
846
  state_topic: "#{@base_topic}/#{addr}/ip#{ip}-percent",
593
847
  unit_of_measurement: "%",
594
- unique_id: "#{node_id}_ip#{ip}-percent")
848
+ unique_id: "#{node_id}_ip#{ip}-percent",
849
+ availability: @availability)
595
850
  end
596
851
 
597
852
  motor = Motor.new(self, addr, node_type)
@@ -643,176 +898,6 @@ module SDN
643
898
 
644
899
  motor
645
900
  end
646
-
647
- def touch_group(group_addr)
648
- group = @groups[Message.print_address(group_addr).delete(".")]
649
- group&.publish(:motors, group.motors_string)
650
- end
651
-
652
- def add_group(addr)
653
- addr = addr.delete(".")
654
- group = @groups[addr]
655
- return group if group
656
-
657
- @mqtt.batch_publish do
658
- hass_device = {
659
- identifiers: addr,
660
- model: "Shade Group",
661
- name: addr,
662
- via_device: @device_id
663
- }
664
- node_id = "#{@device_id}_#{addr}"
665
-
666
- if @homie
667
- publish("#{addr}/$name", addr)
668
- publish("#{addr}/$type", "Shade Group")
669
- publish("#{addr}/$properties",
670
- "discover,control,jog-ms,jog-pulses,position-pulses,position-percent," \
671
- "ip,reset,state,last-direction,motors")
672
-
673
- publish("#{addr}/discover/$name", "Trigger Motor Discovery")
674
- publish("#{addr}/discover/$datatype", "enum")
675
- publish("#{addr}/discover/$format", "discover")
676
- publish("#{addr}/discover/$settable", "true")
677
- publish("#{addr}/discover/$retained", "false")
678
- end
679
-
680
- if @home_assistant
681
- @mqtt.publish_hass_button("discover",
682
- command_topic: "#{@base_topic}/#{addr}/discover/set",
683
- device: hass_device,
684
- icon: "mdi:search-add",
685
- name: "Rediscover",
686
- node_id: node_id,
687
- object_id: "discover",
688
- payload_press: "discover",
689
- unique_id: "#{node_id}_discover")
690
- end
691
-
692
- if @homie
693
- publish("#{addr}/control/$name", "Control motors")
694
- publish("#{addr}/control/$datatype", "enum")
695
- publish("#{addr}/control/$format", "up,down,stop,wink,next_ip,previous_ip,refresh")
696
- publish("#{addr}/control/$settable", "true")
697
- publish("#{addr}/control/$retained", "false")
698
- end
699
-
700
- if @home_assistant
701
- @mqtt.publish_hass_cover("group",
702
- command_topic: "#{@base_topic}/#{addr}/control/set",
703
- device: hass_device,
704
- icon: "mdi:roller-shade",
705
- name: "Group",
706
- node_id: node_id,
707
- payload_close: "down",
708
- payload_open: "up",
709
- payload_stop: "stop",
710
- position_open: 0,
711
- position_closed: 100,
712
- position_topic: "#{@base_topic}/#{addr}/position-percent",
713
- set_position_topic: "#{@base_topic}/#{addr}/position-percent/set",
714
- state_topic: "#{@base_topic}/#{addr}/hass-state",
715
- unique_id: "#{node_id}_group")
716
- {
717
- Wink: "mdi:emoticon-wink",
718
- Next_IP: "mdi:skip-next",
719
- Previous_IP: "mdi:skip-previous",
720
- Refresh: "mdi:refresh"
721
- }.each do |command, icon|
722
- @mqtt.publish_hass_button(command.to_s.downcase,
723
- command_topic: "#{@base_topic}/#{addr}/control/set",
724
- device: hass_device,
725
- icon: icon,
726
- name: command.to_s.sub("_", " "),
727
- node_id: node_id,
728
- payload_press: command.to_s.downcase,
729
- unique_id: "#{node_id}_#{command.to_s.downcase}")
730
- end
731
- end
732
-
733
- if @homie
734
- publish("#{addr}/jog-ms/$name", "Jog motors by ms")
735
- publish("#{addr}/jog-ms/$datatype", "integer")
736
- publish("#{addr}/jog-ms/$format", "-65535:65535")
737
- publish("#{addr}/jog-ms/$unit", "ms")
738
- publish("#{addr}/jog-ms/$settable", "true")
739
- publish("#{addr}/jog-ms/$retained", "false")
740
-
741
- publish("#{addr}/jog-pulses/$name", "Jog motors by pulses")
742
- publish("#{addr}/jog-pulses/$datatype", "integer")
743
- publish("#{addr}/jog-pulses/$format", "-65535:65535")
744
- publish("#{addr}/jog-pulses/$unit", "pulses")
745
- publish("#{addr}/jog-pulses/$settable", "true")
746
- publish("#{addr}/jog-pulses/$retained", "false")
747
-
748
- publish("#{addr}/position-pulses/$name", "Position from up limit (in pulses)")
749
- publish("#{addr}/position-pulses/$datatype", "integer")
750
- publish("#{addr}/position-pulses/$format", "0:65535")
751
- publish("#{addr}/position-pulses/$unit", "pulses")
752
- publish("#{addr}/position-pulses/$settable", "true")
753
- end
754
-
755
- if @home_assistant
756
- @mqtt.publish_hass_number("position-pulses",
757
- command_topic: "#{@base_topic}/#{addr}/position-pulses/set",
758
- device: hass_device,
759
- enabled_by_default: false,
760
- max: 65_536,
761
- min: 0,
762
- name: "Position (Pulses)",
763
- node_id: node_id,
764
- object_id: "position-pulses",
765
- state_topic: "#{@base_topic}/#{addr}/position-pulses",
766
- step: 10,
767
- unit_of_measurement: "pulses",
768
- unique_id: "#{node_id}_position-pulses")
769
- end
770
-
771
- if @homie
772
- publish("#{addr}/position-percent/$name", "Position (in %)")
773
- publish("#{addr}/position-percent/$datatype", "integer")
774
- publish("#{addr}/position-percent/$format", "0:100")
775
- publish("#{addr}/position-percent/$unit", "%")
776
- publish("#{addr}/position-percent/$settable", "true")
777
-
778
- publish("#{addr}/ip/$name", "Intermediate Position")
779
- publish("#{addr}/ip/$datatype", "integer")
780
- publish("#{addr}/ip/$format", "1:16")
781
- publish("#{addr}/ip/$settable", "true")
782
- end
783
-
784
- if @home_assistant
785
- @mqtt.publish_hass_number("ip",
786
- command_topic: "#{@base_topic}/#{addr}/ip/set",
787
- device: hass_device,
788
- name: "Intermediate Position",
789
- max: 16,
790
- min: 0,
791
- node_id: node_id,
792
- object_id: "ip",
793
- payload_reset: "",
794
- state_topic: "#{@base_topic}/#{addr}/ip",
795
- unique_id: "#{node_id}_ip")
796
- end
797
-
798
- if @homie
799
- publish("#{addr}/state/$name", "State of the motors")
800
- publish("#{addr}/state/$datatype", "enum")
801
- publish("#{addr}/state/$format", "#{Message::PostMotorStatus::STATE.keys.join(",")},mixed")
802
-
803
- publish("#{addr}/last-direction/$name", "Direction of last motion")
804
- publish("#{addr}/last-direction/$datatype", "enum")
805
- publish("#{addr}/last-direction/$format", "#{Message::PostMotorStatus::DIRECTION.keys.join(",")},mixed")
806
-
807
- publish("#{addr}/motors/$name", "Comma separated motor addresses that are members of this group")
808
- publish("#{addr}/motors/$datatype", "string")
809
- end
810
-
811
- group = @groups[addr] = Group.new(self, addr)
812
- publish("$nodes", (["FFFFFF"] + @motors.keys.sort + @groups.keys.sort).join(",")) if @homie
813
- end
814
- group
815
- end
816
901
  end
817
902
  end
818
903
  end