somfy_sdn 2.4.1 → 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",
@@ -81,10 +102,214 @@ module SDN
81
102
  @mqtt.publish("#{@base_topic}/#{topic}", value, retain: true, qos: 1)
82
103
  end
83
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
+
84
304
  def subscribe(topic)
85
305
  @mqtt.subscribe("#{@base_topic}/#{topic}")
86
306
  end
87
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]
88
313
  def enqueue(message)
89
314
  @mutex.synchronize do
90
315
  break if @queue.include?(message)
@@ -127,7 +352,7 @@ module SDN
127
352
  name: "Somfy SDN Bridge",
128
353
  identifiers: @device_id,
129
354
  sw_version: SDN::VERSION
130
- }
355
+ }.freeze
131
356
  @mqtt.publish_hass_button("discover",
132
357
  command_topic: "#{@base_topic}/FFFFFF/discover/set",
133
358
  device: hass_device,
@@ -175,6 +400,12 @@ module SDN
175
400
  end
176
401
  end
177
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]
178
409
  def publish_motor(addr, node_type)
179
410
  motor = nil
180
411
 
@@ -184,7 +415,7 @@ module SDN
184
415
  model_id: node_type,
185
416
  name: addr,
186
417
  via_device: @device_id
187
- }
418
+ }.freeze
188
419
  node_id = "#{@device_id}_#{addr}"
189
420
 
190
421
  if @homie
@@ -233,7 +464,7 @@ module SDN
233
464
  device: hass_device,
234
465
  icon: "mdi:search-add",
235
466
  name: "Rediscover",
236
- node_id: node_id,
467
+ node_id:,
237
468
  object_id: "discover",
238
469
  payload_press: "true",
239
470
  unique_id: "#{node_id}_discover",
@@ -254,7 +485,7 @@ module SDN
254
485
  icon: "mdi:rename",
255
486
  max: 16,
256
487
  name: "Label",
257
- node_id: node_id,
488
+ node_id:,
258
489
  unique_id: "#{node_id}_label",
259
490
  availability: @availability)
260
491
  end
@@ -297,7 +528,7 @@ module SDN
297
528
  device: hass_device,
298
529
  icon: "mdi:roller-shade",
299
530
  name: "Motor",
300
- node_id: node_id,
531
+ node_id:,
301
532
  payload_close: "down",
302
533
  payload_open: "up",
303
534
  payload_stop: "stop",
@@ -317,9 +548,9 @@ module SDN
317
548
  @mqtt.publish_hass_button(command.to_s.downcase,
318
549
  command_topic: "#{@base_topic}/#{addr}/control/set",
319
550
  device: hass_device,
320
- icon: icon,
551
+ icon:,
321
552
  name: command.to_s.sub("_", " "),
322
- node_id: node_id,
553
+ node_id:,
323
554
  payload_press: command.to_s.downcase,
324
555
  unique_id: "#{node_id}_#{command.to_s.downcase}",
325
556
  availability: @availability)
@@ -342,7 +573,7 @@ module SDN
342
573
  max: 65_536,
343
574
  min: 0,
344
575
  name: "Position (Pulses)",
345
- node_id: node_id,
576
+ node_id:,
346
577
  object_id: "position-pulses",
347
578
  state_topic: "#{@base_topic}/#{addr}/position-pulses",
348
579
  step: 10,
@@ -366,7 +597,7 @@ module SDN
366
597
  name: "Intermediate Position",
367
598
  max: 16,
368
599
  min: 0,
369
- node_id: node_id,
600
+ node_id:,
370
601
  object_id: "ip",
371
602
  payload_reset: "",
372
603
  state_topic: "#{@base_topic}/#{addr}/ip",
@@ -390,7 +621,7 @@ module SDN
390
621
  icon: "mdi:roller-shade-closed",
391
622
  max: 65_536,
392
623
  min: 0,
393
- node_id: node_id,
624
+ node_id:,
394
625
  payload_reset: "",
395
626
  state_topic: "#{@base_topic}/#{addr}/down-limit",
396
627
  step: 10,
@@ -422,7 +653,7 @@ module SDN
422
653
  enabled_by_default: false,
423
654
  entity_category: :config,
424
655
  name: "Reset #{key.to_s.sub("_", " ")}",
425
- node_id: node_id,
656
+ node_id:,
426
657
  payload_press: key,
427
658
  unique_id: "#{node_id}_#{key}",
428
659
  availability: @availability)
@@ -432,7 +663,8 @@ module SDN
432
663
  if @homie
433
664
  publish("#{addr}/last-action-source/$name", "Source of last action")
434
665
  publish("#{addr}/last-action-source/$datatype", "enum")
435
- 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(","))
436
668
  end
437
669
 
438
670
  if @home_assistant
@@ -441,7 +673,7 @@ module SDN
441
673
  device_class: :enum,
442
674
  entity_category: :diagnostic,
443
675
  name: "Source of last action",
444
- node_id: node_id,
676
+ node_id:,
445
677
  object_id: "last-action-source",
446
678
  options: Message::PostMotorStatus::SOURCE.keys,
447
679
  state_topic: "#{@base_topic}/#{addr}/last-action-source",
@@ -452,7 +684,8 @@ module SDN
452
684
  if @homie
453
685
  publish("#{addr}/last-action-cause/$name", "Cause of last action")
454
686
  publish("#{addr}/last-action-cause/$datatype", "enum")
455
- 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(","))
456
689
  end
457
690
 
458
691
  if @home_assistant
@@ -461,7 +694,7 @@ module SDN
461
694
  device_class: :enum,
462
695
  entity_category: :diagnostic,
463
696
  name: "Cause of last action",
464
- node_id: node_id,
697
+ node_id:,
465
698
  object_id: "last-action-cause",
466
699
  options: Message::PostMotorStatus::CAUSE.keys,
467
700
  state_topic: "#{@base_topic}/#{addr}/last-action-cause",
@@ -486,7 +719,7 @@ module SDN
486
719
  max: 65_536,
487
720
  min: 0,
488
721
  name: "Up Limit",
489
- node_id: node_id,
722
+ node_id:,
490
723
  payload_reset: "",
491
724
  state_topic: "#{@base_topic}/#{addr}/up-limit",
492
725
  step: 10,
@@ -509,7 +742,7 @@ module SDN
509
742
  entity_category: :config,
510
743
  icon: "mdi:circle-arrows",
511
744
  name: "Motor rotation direction",
512
- node_id: node_id,
745
+ node_id:,
513
746
  object_id: "direction",
514
747
  options: %w[standard reversed],
515
748
  state_topic: "#{@base_topic}/#{addr}/direction",
@@ -547,7 +780,7 @@ module SDN
547
780
  max: 28,
548
781
  min: 6,
549
782
  name: "#{speed_type} speed",
550
- node_id: node_id,
783
+ node_id:,
551
784
  state_topic: "#{@base_topic}/#{addr}/#{speed_type.downcase}-speed",
552
785
  unit_of_measurement: "RPM",
553
786
  unique_id: "#{node_id}_#{speed_type.downcase}-speed",
@@ -580,7 +813,7 @@ module SDN
580
813
  max: 65_536,
581
814
  min: 0,
582
815
  name: "Intermediation Position #{ip} (Pulses)",
583
- node_id: node_id,
816
+ node_id:,
584
817
  object_id: "ip#{ip}-pulses",
585
818
  payload_reset: "",
586
819
  state_topic: "#{@base_topic}/#{addr}/ip#{ip}-pulses",
@@ -607,7 +840,7 @@ module SDN
607
840
  max: 100,
608
841
  min: 0,
609
842
  name: "Intermediation Position #{ip} (Percent)",
610
- node_id: node_id,
843
+ node_id:,
611
844
  object_id: "ip#{ip}-percent",
612
845
  payload_reset: "",
613
846
  state_topic: "#{@base_topic}/#{addr}/ip#{ip}-percent",
@@ -665,181 +898,6 @@ module SDN
665
898
 
666
899
  motor
667
900
  end
668
-
669
- def touch_group(group_addr)
670
- group = @groups[Message.print_address(group_addr).delete(".")]
671
- group&.publish(:motors, group.motors_string)
672
- end
673
-
674
- def add_group(addr)
675
- addr = addr.delete(".")
676
- group = @groups[addr]
677
- return group if group
678
-
679
- @mqtt.batch_publish do
680
- hass_device = {
681
- identifiers: addr,
682
- model: "Shade Group",
683
- name: addr,
684
- via_device: @device_id
685
- }
686
- node_id = "#{@device_id}_#{addr}"
687
-
688
- if @homie
689
- publish("#{addr}/$name", addr)
690
- publish("#{addr}/$type", "Shade Group")
691
- publish("#{addr}/$properties",
692
- "discover,control,jog-ms,jog-pulses,position-pulses,position-percent," \
693
- "ip,reset,state,last-direction,motors")
694
-
695
- publish("#{addr}/discover/$name", "Trigger Motor Discovery")
696
- publish("#{addr}/discover/$datatype", "enum")
697
- publish("#{addr}/discover/$format", "discover")
698
- publish("#{addr}/discover/$settable", "true")
699
- publish("#{addr}/discover/$retained", "false")
700
- end
701
-
702
- if @home_assistant
703
- @mqtt.publish_hass_button("discover",
704
- command_topic: "#{@base_topic}/#{addr}/discover/set",
705
- device: hass_device,
706
- icon: "mdi:search-add",
707
- name: "Rediscover",
708
- node_id: node_id,
709
- object_id: "discover",
710
- payload_press: "discover",
711
- unique_id: "#{node_id}_discover",
712
- availability: @availability)
713
- end
714
-
715
- if @homie
716
- publish("#{addr}/control/$name", "Control motors")
717
- publish("#{addr}/control/$datatype", "enum")
718
- publish("#{addr}/control/$format", "up,down,stop,wink,next_ip,previous_ip,refresh")
719
- publish("#{addr}/control/$settable", "true")
720
- publish("#{addr}/control/$retained", "false")
721
- end
722
-
723
- if @home_assistant
724
- @mqtt.publish_hass_cover("group",
725
- command_topic: "#{@base_topic}/#{addr}/control/set",
726
- device: hass_device,
727
- icon: "mdi:roller-shade",
728
- name: "Group",
729
- node_id: node_id,
730
- payload_close: "down",
731
- payload_open: "up",
732
- payload_stop: "stop",
733
- position_open: 0,
734
- position_closed: 100,
735
- position_topic: "#{@base_topic}/#{addr}/position-percent",
736
- set_position_topic: "#{@base_topic}/#{addr}/position-percent/set",
737
- state_topic: "#{@base_topic}/#{addr}/hass-state",
738
- unique_id: "#{node_id}_group",
739
- availability: @availability)
740
- {
741
- Wink: "mdi:emoticon-wink",
742
- Next_IP: "mdi:skip-next",
743
- Previous_IP: "mdi:skip-previous",
744
- Refresh: "mdi:refresh"
745
- }.each do |command, icon|
746
- @mqtt.publish_hass_button(command.to_s.downcase,
747
- command_topic: "#{@base_topic}/#{addr}/control/set",
748
- device: hass_device,
749
- icon: icon,
750
- name: command.to_s.sub("_", " "),
751
- node_id: node_id,
752
- payload_press: command.to_s.downcase,
753
- unique_id: "#{node_id}_#{command.to_s.downcase}",
754
- availability: @availability)
755
- end
756
- end
757
-
758
- if @homie
759
- publish("#{addr}/jog-ms/$name", "Jog motors by ms")
760
- publish("#{addr}/jog-ms/$datatype", "integer")
761
- publish("#{addr}/jog-ms/$format", "-65535:65535")
762
- publish("#{addr}/jog-ms/$unit", "ms")
763
- publish("#{addr}/jog-ms/$settable", "true")
764
- publish("#{addr}/jog-ms/$retained", "false")
765
-
766
- publish("#{addr}/jog-pulses/$name", "Jog motors by pulses")
767
- publish("#{addr}/jog-pulses/$datatype", "integer")
768
- publish("#{addr}/jog-pulses/$format", "-65535:65535")
769
- publish("#{addr}/jog-pulses/$unit", "pulses")
770
- publish("#{addr}/jog-pulses/$settable", "true")
771
- publish("#{addr}/jog-pulses/$retained", "false")
772
-
773
- publish("#{addr}/position-pulses/$name", "Position from up limit (in pulses)")
774
- publish("#{addr}/position-pulses/$datatype", "integer")
775
- publish("#{addr}/position-pulses/$format", "0:65535")
776
- publish("#{addr}/position-pulses/$unit", "pulses")
777
- publish("#{addr}/position-pulses/$settable", "true")
778
- end
779
-
780
- if @home_assistant
781
- @mqtt.publish_hass_number("position-pulses",
782
- command_topic: "#{@base_topic}/#{addr}/position-pulses/set",
783
- device: hass_device,
784
- enabled_by_default: false,
785
- max: 65_536,
786
- min: 0,
787
- name: "Position (Pulses)",
788
- node_id: node_id,
789
- object_id: "position-pulses",
790
- state_topic: "#{@base_topic}/#{addr}/position-pulses",
791
- step: 10,
792
- unit_of_measurement: "pulses",
793
- unique_id: "#{node_id}_position-pulses",
794
- availability: @availability)
795
- end
796
-
797
- if @homie
798
- publish("#{addr}/position-percent/$name", "Position (in %)")
799
- publish("#{addr}/position-percent/$datatype", "integer")
800
- publish("#{addr}/position-percent/$format", "0:100")
801
- publish("#{addr}/position-percent/$unit", "%")
802
- publish("#{addr}/position-percent/$settable", "true")
803
-
804
- publish("#{addr}/ip/$name", "Intermediate Position")
805
- publish("#{addr}/ip/$datatype", "integer")
806
- publish("#{addr}/ip/$format", "1:16")
807
- publish("#{addr}/ip/$settable", "true")
808
- end
809
-
810
- if @home_assistant
811
- @mqtt.publish_hass_number("ip",
812
- command_topic: "#{@base_topic}/#{addr}/ip/set",
813
- device: hass_device,
814
- name: "Intermediate Position",
815
- max: 16,
816
- min: 0,
817
- node_id: node_id,
818
- object_id: "ip",
819
- payload_reset: "",
820
- state_topic: "#{@base_topic}/#{addr}/ip",
821
- unique_id: "#{node_id}_ip",
822
- availability: @availability)
823
- end
824
-
825
- if @homie
826
- publish("#{addr}/state/$name", "State of the motors")
827
- publish("#{addr}/state/$datatype", "enum")
828
- publish("#{addr}/state/$format", "#{Message::PostMotorStatus::STATE.keys.join(",")},mixed")
829
-
830
- publish("#{addr}/last-direction/$name", "Direction of last motion")
831
- publish("#{addr}/last-direction/$datatype", "enum")
832
- publish("#{addr}/last-direction/$format", "#{Message::PostMotorStatus::DIRECTION.keys.join(",")},mixed")
833
-
834
- publish("#{addr}/motors/$name", "Comma separated motor addresses that are members of this group")
835
- publish("#{addr}/motors/$datatype", "string")
836
- end
837
-
838
- group = @groups[addr] = Group.new(self, addr)
839
- publish("$nodes", (["FFFFFF"] + @motors.keys.sort + @groups.keys.sort).join(",")) if @homie
840
- end
841
- group
842
- end
843
901
  end
844
902
  end
845
903
  end