Module: MQTT::HomeAssistant

Defined in:
lib/mqtt/home_assistant.rb,
lib/mqtt/home_assistant/version.rb,
lib/mqtt/home_assistant/homie/node.rb,
lib/mqtt/home_assistant/homie/property.rb

Defined Under Namespace

Modules: Homie

Constant Summary collapse

ENTITY_CATEGORIES =
%i[config diagnostic system].freeze
DEVICE_CLASSES =
{
  binary_sensor: %i[
    battery
    battery_charging
    carbon_monoxide
    cold
    connectivity
    door
    garage_door
    gas
    heat
    light
    lock
    moisture
    motion
    moving
    occupancy
    opening
    plug
    power
    presence
    problem
    running
    safety
    smoke
    sound
    tamper
    update
    vibration
    window
  ].freeze,
  humidifier: %i[
    humidifier
    dehumidifier
  ].freeze,
  sensor: %i[
    apparent_power
    aqi
    atmospheric_pressure
    battery
    carbon_dioxide
    carbon_monoxide
    current
    data_rate
    data_size
    date
    distance
    duration
    energy
    energy_storage
    enum
    frequency
    gas
    humidity
    illuminance
    irradiance
    moisture
    monetary
    nitrogen_dioxide
    nitrogen_monoxide
    nitrous_oxide
    ozone
    ph
    pm1
    pm10
    pm25
    power_factor
    power
    precipitation
    precipitation_intensity
    pressure
    reactive_power
    signal_strength
    sound_pressure
    speed
    sulphur_dioxide
    temperature
    timestamp
    volatile_organic_compounds
    volatile_organic_compounds_parts
    voltage
    volume
    volume_storage
    water
    weight
    wind_speed
  ].freeze
}.freeze
STATE_CLASSES =
%i[measurement total total_increasing].freeze
ON_COMMAND_TYPES =
%i[last first brightness].freeze
VERSION =
"0.1.6"

Class Method Summary collapse

Class Method Details

.publish_binary_sensor(property, device_class: nil, expire_after: nil, force_update: false, off_delay: nil, device: nil, discovery_prefix: nil, entity_category: nil, icon: nil) ⇒ Object

Parameters:

  • property (MQTT::Homie::Property)

    A Homie property object of datatype :boolean

Raises:

  • (ArgumentError)


106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/mqtt/home_assistant.rb', line 106

def publish_binary_sensor(
  property,
  device_class: nil,
  expire_after: nil,
  force_update: false,
  off_delay: nil,

  device: nil,
  discovery_prefix: nil,
  entity_category: nil,
  icon: nil
)
  raise ArgumentError, "Homie property must be a boolean" unless property.datatype == :boolean
  if device_class && !DEVICE_CLASSES[:binary_sensor].include?(device_class)
    raise ArgumentError, "Unrecognized device_class #{device_class.inspect}"
  end

  config = base_config(property.device,
                       "#{property.node.name} #{property.name}",
                       device_class: device_class,
                       device: device,
                       entity_category: entity_category,
                       icon: icon)
           .merge({
                    payload_off: "false",
                    payload_on: "true",
                    object_id: "#{property.node.id}_#{property.id}",
                    state_topic: property.topic
                  })
  config[:expire_after] = expire_after if expire_after
  config[:force_update] = true if force_update
  config[:off_delay] = off_delay if off_delay

  publish(property.mqtt, "binary_sensor", config, discovery_prefix: discovery_prefix)
end

.publish_climate(action_property: nil, aux_property: nil, away_mode_property: nil, current_temperature_property: nil, fan_mode_property: nil, mode_property: nil, hold_property: nil, power_property: nil, swing_mode_property: nil, temperature_property: nil, temperature_high_property: nil, temperature_low_property: nil, name: nil, id: nil, precision: nil, temp_step: nil, device: nil, discovery_prefix: nil, entity_category: nil, icon: nil, templates: {}) ⇒ Object

Raises:

  • (ArgumentError)


142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/mqtt/home_assistant.rb', line 142

def publish_climate(
  action_property: nil,
  aux_property: nil,
  away_mode_property: nil,
  current_temperature_property: nil,
  fan_mode_property: nil,
  mode_property: nil,
  hold_property: nil,
  power_property: nil,
  swing_mode_property: nil,
  temperature_property: nil,
  temperature_high_property: nil,
  temperature_low_property: nil,
  name: nil,
  id: nil,
  precision: nil,
  temp_step: nil,

  device: nil,
  discovery_prefix: nil,
  entity_category: nil,
  icon: nil,
  templates: {}
)
  properties = {
    action: action_property,
    aux: aux_property,
    away_mode: away_mode_property,
    current_temperature: current_temperature_property,
    fan_mode: fan_mode_property,
    mode: mode_property,
    hold: hold_property,
    power: power_property,
    swing_mode: swing_mode_property,
    temperature: temperature_property,
    temperature_high: temperature_high_property,
    temperature_low: temperature_low_property
  }.compact
  raise ArgumentError, "At least one property must be specified" if properties.empty?
  raise ArgumentError, "Power property must be a boolean" if power_property && power_property.datatype != :boolean

  node = properties.first.last.node

  config = base_config(node.device,
                       name || node.name,
                       device: device,
                       entity_category: entity_category,
                       icon: icon)

  config[:object_id] = id || node.id
  read_only_props = %i[action current_temperature]
  properties.each do |prefix, property|
    add_property(config, property, prefix, templates: templates, read_only: read_only_props.include?(prefix))
  end
  temp_properties = [
    temperature_property,
    temperature_high_property,
    temperature_low_property
  ].compact
  unless (temp_ranges = temp_properties.map(&:range).compact).empty?
    config[:min_temp] = temp_ranges.map(&:begin).min
    config[:max_temp] = temp_ranges.map(&:end).max
  end
  temperature_unit = temp_properties.map(&:unit).compact.first
  config[:temperature_unit] = temperature_unit[-1] if temperature_unit
  {
    nil => mode_property,
    :fan => fan_mode_property,
    :hold => hold_property,
    :swing => swing_mode_property
  }.compact.each do |prefix, property|
    valid_set = %w[auto off cool heat dry fan_only] if prefix.nil?
    add_enum(config, property, prefix, valid_set)
  end
  config[:precision] = precision if precision
  config[:temp_step] = temp_step if temp_step
  if power_property
    config[:payload_on] = "true"
    config[:payload_off] = "false"
  end

  publish(node.mqtt, "climate", config, discovery_prefix: discovery_prefix)
end

.publish_fan(property, oscillation_property: nil, percentage_property: nil, preset_mode_property: nil, device: nil, discovery_prefix: nil, entity_category: nil, icon: nil) ⇒ Object



226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
# File 'lib/mqtt/home_assistant.rb', line 226

def publish_fan(
  property,
  oscillation_property: nil,
  percentage_property: nil,
  preset_mode_property: nil,

  device: nil,
  discovery_prefix: nil,
  entity_category: nil,
  icon: nil
)
  config = base_config(property.device,
                       name || property.node.name,
                       device: device,
                       device_class: device_class,
                       entity_category: entity_category,
                       icon: icon,
                       templates: {})
  add_property(config, oscillation_property, :oscillation_property, templates: templates)
  add_property(config, percentage_property, :percentage, templates: templates)
  if percentage_property&.range
    config[:speed_range_min] = percentage_property.range.begin
    config[:speed_range_max] = percentage_property.range.end
  end
  add_property(config, preset_mode_property, :preset, templates: templates)
  add_enum(config, preset_mode_property, :preset)

  publish(node.mqtt, "fan", config, discovery_prefix: discovery_prefix)
end

.publish_humidifier(property, device_class:, target_property:, mode_property: nil, name: nil, id: nil, device: nil, discovery_prefix: nil, entity_category: nil, icon: nil) ⇒ Object

Raises:

  • (ArgumentError)


256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
# File 'lib/mqtt/home_assistant.rb', line 256

def publish_humidifier(
  property,
  device_class:,
  target_property:,
  mode_property: nil,
  name: nil,
  id: nil,

  device: nil,
  discovery_prefix: nil,
  entity_category: nil,
  icon: nil
)
  raise ArgumentError, "Homie property must be a boolean" unless property.datatype == :boolean

  unless DEVICE_CLASSES[:humidifier].include?(device_class)
    raise ArgumentError, "Unrecognized device_class #{device_class.inspect}"
  end

  config = base_config(property.device,
                       name || property.node.name,
                       device: device,
                       device_class: device_class,
                       entity_category: entity_category,
                       icon: icon)
           .merge({
                    command_topic: "#{property.topic}/set",
                    target_humidity_command_topic: "#{target_property.topic}/set",
                    payload_off: "false",
                    payload_on: "true",
                    object_id: id || property.node.id
                  })
  add_property(config, property)
  add_property(config, target_property, :target_humidity)
  if (range = target_property.range)
    config[:min_humidity] = range.begin
    config[:max_humidity] = range.end
  end
  add_property(config, mode_property, :mode)
  add_enum(config, mode_property)

  publish(property.mqtt, "humidifier", config, discovery_prefix: discovery_prefix)
end

.publish_light(property = nil, brightness_property: nil, color_mode_property: nil, color_temp_property: nil, effect_property: nil, hs_property: nil, rgb_property: nil, white_property: nil, xy_property: nil, on_command_type: nil, device: nil, discovery_prefix: nil, entity_category: nil, icon: nil, templates: {}) ⇒ Object

‘default` schema only for now



301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
# File 'lib/mqtt/home_assistant.rb', line 301

def publish_light(
  property = nil,
  brightness_property: nil,
  color_mode_property: nil,
  color_temp_property: nil,
  effect_property: nil,
  hs_property: nil,
  rgb_property: nil,
  white_property: nil,
  xy_property: nil,
  on_command_type: nil,

  device: nil,
  discovery_prefix: nil,
  entity_category: nil,
  icon: nil,
  templates: {}
)
  if on_command_type && !ON_COMMAND_TYPES.include?(on_command_type)
    raise ArgumentError, "Invalid on_command_type #{on_command_type.inspect}"
  end

  # automatically infer a brightness-only light and adjust config
  if brightness_property && property.nil?
    property = brightness_property
    on_command_type = :brightness
  end

  config = base_config(property.device,
                       "#{property.node.name} #{property.name}",
                       device: device,
                       entity_category: entity_category,
                       icon: icon)
  config[:object_id] = "#{property.node.id}_#{property.id}"
  add_property(config, property)
  case property.datatype
  when :boolean
    config[:payload_off] = "false"
    config[:payload_on] = "true"
  when :integer
    config[:payload_off] = "0"
  when :float
    config[:payload_off] = "0.0"
  end
  add_property(config, brightness_property, :brightness, templates: templates)
  config[:brightness_scale] = brightness_property.range.end if brightness_property&.range
  add_property(config, color_mode_property, :color_mode, templates: templates)
  add_property(config, color_temp_property, :color_temp, templates: templates)
  if color_temp_property&.range && color_temp_property.unit == "mired"
    config[:min_mireds] = color_temp_property.range.begin
    config[:max_mireds] = color_temp_property.range.end
  end
  add_property(config, effect_property, :effect, templates: templates)
  config[:effect_list] = effect_property.range if effect_property&.datatype == :enum
  add_property(config, hs_property, :hs, templates: templates)
  add_property(config, rgb_property, :rgb, templates: templates)
  add_property(config, white_property, :white, templates: templates)
  config[:white_scale] = white_property.range.end if white_property&.range
  add_property(config, xy_property, :xy, templates: templates)
  config[:on_command_type] = on_command_type if on_command_type

  publish(property.mqtt, "light", config, discovery_prefix: discovery_prefix)
end

.publish_number(property, step: nil, device: nil, discovery_prefix: nil, entity_category: nil, icon: nil) ⇒ Object

Raises:

  • (ArgumentError)


365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
# File 'lib/mqtt/home_assistant.rb', line 365

def publish_number(
  property,
  step: nil,

  device: nil,
  discovery_prefix: nil,
  entity_category: nil,
  icon: nil
)
  raise ArgumentError, "Homie property must be an integer or a float" unless %i[integer
                                                                                float].include?(property.datatype)

  config = base_config(property.device,
                       "#{property.node.name} #{property.name}",
                       device: device,
                       entity_category: entity_category,
                       icon: icon)
  config[:object_id] = "#{property.node.id}_#{property.id}"
  add_property(config, property)
  config[:unit_of_measurement] = property.unit if property.unit
  if property.range
    config[:min] = property.range.begin
    config[:max] = property.range.end
  end
  config[:step] = step if step

  publish(property.mqtt, "number", config, discovery_prefix: discovery_prefix)
end

.publish_scene(property, device: nil, discovery_prefix: nil, entity_category: nil, icon: nil) ⇒ Object



394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
# File 'lib/mqtt/home_assistant.rb', line 394

def publish_scene(
  property,

  device: nil,
  discovery_prefix: nil,
  entity_category: nil,
  icon: nil
)
  unless property.datatype == :enum && property.range.length == 1
    raise ArgumentError, "Homie property must be an enum with a single value"
  end

  config = base_config(property.device,
                       "#{property.node.name} #{property.name}",
                       device: device,
                       entity_category: entity_category,
                       icon: icon)
  config[:object_id] = "#{property.node.id}_#{property.id}"
  add_property(config, property)
  config[:payload_on] = property.range.first

  publish(property.mqtt, "scene", config, discovery_prefix: discovery_prefix)
end

.publish_select(property, device: nil, discovery_prefix: nil, entity_category: nil, icon: nil) ⇒ Object

Raises:

  • (ArgumentError)


418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
# File 'lib/mqtt/home_assistant.rb', line 418

def publish_select(
  property,

  device: nil,
  discovery_prefix: nil,
  entity_category: nil,
  icon: nil
)
  raise ArgumentError, "Homie property must be an enum" unless property.datatype == :enum
  raise ArgumentError, "Homie property must be settable" unless property.settable?

  config = base_config(property.device,
                       "#{property.node.name} #{property.name}",
                       device: device,
                       entity_category: entity_category,
                       icon: icon)
  config[:object_id] = "#{property.node.id}_#{property.id}"
  add_property(config, property)
  config[:options] = property.range

  publish(property.mqtt, "select", config, discovery_prefix: discovery_prefix)
end

.publish_sensor(property, device_class: nil, expire_after: nil, force_update: false, state_class: nil, device: nil, discovery_prefix: nil, entity_category: nil, icon: nil) ⇒ Object

Parameters:

  • property (MQTT::Homie::Property)

    A Homie property object



442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
# File 'lib/mqtt/home_assistant.rb', line 442

def publish_sensor(
  property,
  device_class: nil,
  expire_after: nil,
  force_update: false,
  state_class: nil,

  device: nil,
  discovery_prefix: nil,
  entity_category: nil,
  icon: nil
)
  device_class ||= :enum if property.datatype == :enum
  if device_class && !DEVICE_CLASSES[:sensor].include?(device_class)
    raise ArgumentError, "Unrecognized device_class #{device_class.inspect}"
  end
  if state_class && !STATE_CLASSES.include?(state_class)
    raise ArgumentError, "Unrecognized state_class #{state_class.inspect}"
  end

  config = base_config(property.device,
                       "#{property.node.name} #{property.name}",
                       device: device,
                       device_class: device_class,
                       entity_category: entity_category,
                       icon: icon)
           .merge({
                    object_id: "#{property.node.id}_#{property.id}",
                    state_topic: property.topic
                  })
  config[:state_class] = state_class if state_class
  config[:expire_after] = expire_after if expire_after
  config[:force_update] = true if force_update
  config[:unit_of_measurement] = property.unit if property.unit

  publish(property.mqtt, "sensor", config, discovery_prefix: discovery_prefix)
end

.publish_switch(property, device_class: nil, device: nil, discovery_prefix: nil, entity_category: nil, icon: nil) ⇒ Object

Parameters:

  • property (MQTT::Homie::Property)

    A Homie property object of datatype :boolean

Raises:

  • (ArgumentError)


481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
# File 'lib/mqtt/home_assistant.rb', line 481

def publish_switch(property,
                   device_class: nil,

                   device: nil,
                   discovery_prefix: nil,
                   entity_category: nil,
                   icon: nil)
  raise ArgumentError, "Homie property must be a boolean" unless property.datatype == :boolean

  config = base_config(property.device,
                       "#{property.node.name} #{property.name}",
                       device: device,
                       device_class: device_class,
                       entity_category: entity_category,
                       icon: icon)
           .merge({
                    object_id: "#{property.node.id}_#{property.id}",
                    payload_off: "false",
                    payload_on: "true"
                  })
  add_property(config, property)

  publish(property.mqtt, "switch", config, discovery_prefix: discovery_prefix)
end