Skip to content

Commit

Permalink
Merge pull request #382 from krkeegan/Discovery_Additions
Browse files Browse the repository at this point in the history
Discovery Documentation; Additional Variables and Tweaks
  • Loading branch information
krkeegan authored Apr 19, 2021
2 parents 4b5822d + c162ca2 commit 600b377
Show file tree
Hide file tree
Showing 6 changed files with 300 additions and 49 deletions.
24 changes: 19 additions & 5 deletions config-example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,14 @@ mqtt:
# https://www.home-assistant.io/docs/mqtt/discovery
# if discover_topic_base is defined, devices (as defined in config.yaml)
# announce themselves to Home Assistant. Announcing occurs once
# upon startup of insteon-mqtt and based on request (see announce command on
# modem)
# upon startup of insteon-mqtt and whenever HomeAssistant restart.
#
# The details of discovery_entities and how to define your own discovery
# templates can be found here:
# https://github.com/TD22057/insteon-mqtt/blob/master/docs/discovery.md
#
# Any additional variables that a specific device may offer are documented
# in the comments below under that device class.
#
# TO DISABLE THE DISCOVERY SERVICE: Comment out the line below by placing a
# hash character # at the start of the line.
Expand All @@ -220,10 +226,18 @@ mqtt:
{
"ids": "{{address}}",
"mf": "Insteon",
"mdl": "{{model}}",
"sw": "{{firmware}}",
"mdl": "{%- if model_number != 'Unknown' -%}
{{model_number}} - {{model_description}}
{%- elif dev_cat_name != 'Unknown' -%}
{{dev_cat_name}} - 0x{{'%0x' % sub_cat|int }}
{%- elif dev_cat == 0 and sub_cat == 0 -%}
No Info
{%- else -%}
0x{{'%0x' % dev_cat|int }} - 0x{{'%0x' % sub_cat|int }}
{%- endif -%}",
"sw": "0x{{'%0x' % firmware|int }} - {{engine}}",
"name": "{{name_user_case}}",
"via_device": "{{modem_addr}}"
"via_device": "{{modem_addr}}",
}
# Trigger modem virtual scenes. Modem scenes are where the modem is a
Expand Down
9 changes: 9 additions & 0 deletions docs/config_extra.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ Some device types have additional settings.

> No such settings yet.

## Settings

### `discovery_class` - Discovery Template Class
This key can be used to define a custom discovery template for this device.
The value of this setting should be a subkey under the `mqtt` key in the yaml
config file. This subkey must contain a `discovery_entities` list of each of
the discovery entities. For more details and examples see
[Discovery](https://github.com/TD22057/insteon-mqtt/blob/master/docs/discovery.md).

## Advanced Settings
These settings can be used with all device types, but should be considered advanced. These settings may cause undesirable effects.

Expand Down
191 changes: 191 additions & 0 deletions docs/discovery.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
# MQTT Discovery Platform

HomeAssistant allows for entities to be defined through an MQTT discovery topic.
This means, that for general installations, a user need only setup InsteonMQTT
following the [Configuration Instructions](https://github.com/TD22057/insteon-mqtt/blob/master/docs/configuration.md)
and HomeAssistant should just work! No need to add individual entities to the
HomeAssistant configuration.

Read more about the [HomeAssistant Discovery Platform](https://www.home-assistant.io/docs/mqtt/discovery/).

## Customization

Of course, all of us will likely want to tweak or edit the default
configuration and this can be done using [Jinja templates](https://github.com/TD22057/insteon-mqtt/blob/master/docs/Templating.md).

### Default Device Templates

By default,
a device will look to its corresponding subkey in the `mqtt` key. So for
example, a dimmer device will by default look the the `dimmer` subkey under
the `mqtt` key:

```YAML
insteon:
device:
dimmer:
- aa.bb.cc: my_dimmer

mqtt:
dimmer:
discovery_entities:
# This is where device aa.bb.cc will look to find its template by
# default
```

### Using a Custom Device Template

Each device can also define a distinct template for its discovery entities.
This is done using [Device Specific Configuration](https://github.com/TD22057/insteon-mqtt/blob/master/docs/config_extra.md).
Specifically, using the `discovery_class` key. So you can do the following:
```yaml
insteon:
device:
dimmer:
- dd.ee.ff: my_dimmer
discovery_class: my_discovery_class

mqtt:
my_discovery_class: # < Note the class name
discovery_entities:
# This is where device dd.ee.ff will look to find its template by
# default
```

This class can be reused by any number of devices. Any device that uses the
entry `discovery_class: my_discovery_class` will look to this class.

### Writing a `discovery_entities` Template
The `discovery_entities` key should contain a list. Each list entry will
generate an entity in HomeAssistant. Some devices may only have one entity,
other devices may have multiple entities.

Each entry `discovery_entities` is an associative array with the __required__
keys `component` and `config`.
- `component` - (String) One of the supported HomeAssistant MQTT components,
eg. `binary_sensor`, `light`, `switch`
- `config` - (jinja template) The template must produce a json string that is
acceptable to HomeAssistant. The contents of what is required in this json
string are defined by the
[HomeAssistant Discovery Platform](https://www.home-assistant.io/docs/mqtt/discovery/).

> The `config` json template __must include__ an entry for `unique_id` or
`uniq_id` containing a unique id for this entity. It is __strongly
recommended__ that you use the the device address as part of this unique id.
The recommended format is `{{address}}_suffix` where the suffix is something
that plainly describes the nature of this enity. Devices with only a single
entity do not need a suffix, but it is still good practice to use one.

The `config` template has a number of variables available to it. For all
devices this includes at minimum the following, devices may also add
additional variables unique to these devices:

- `name` = (str) device name in lower case
- `address` = (str) hexadecimal address of device as a string
- `name_user_case` = (str) device name in the case entered by
the user
- `engine` = (str) device engine version (e.g. i1, i2, i2cs). Will return
`Unknown` if unknown.
- `model_number` = (str) device model number (e.g. 2476D). Will return
`Unknown` if unknown.
- `model_description` = (str) description (e.g. SwitchLinc Dimmer) Will return
`Unknown` if unknown.
- `firmware` = (int) device firmware version. Will return 0 by default
- `dev_cat` = (int) device category. Will return 0 by default
- `dev_cat_name` = (str) device category name Will return `Unknown` if unknown.
- `sub_cat` = (int) device sub-category. Will return 0 by default
- `modem_addr` = (str) hexadecimal address of modem as a string
- `device_info_template` = (jinja template) a template defined in
config.yaml. _See below_
- `<<topics>>` = (str) topic keys as defined in the config.yaml
file under the _default class_ for this device are available as variables.

> The `<<topics>>` available are __always__ those listed under the default
class for this device. So for a `dimmer` the topics will be gathered from
the `mqtt->dimmer` key. Topics listed under a user defined `discovery_class`
will be ignored.

Additional variables may be offered by specific devices classes. Those
variables are defined in the `config-example.yaml` file under the relevant
`mqtt` device keys.

#### Example `discovery_entities` templates

```yaml
mqtt:
# Other keys ommitted
dimmer:
# Other keys omitted
discovery_entities:
- component: 'light'
config: |-
{
"uniq_id": "{{address}}_light",
"name": "{{name_user_case}}",
"cmd_t": "{{level_topic}}",
"stat_t": "{{state_topic}}",
"brightness": true,
"schema": "json",
"device": {{device_info_template}}
}
```
### The Special `device_info_template` Variable
Inside HomeAssistant each entity config can contain a description about the
device that the entity is contained in. This is mostly a cosmetic feature
that provides some level of topology to HomeAssistant and can allow you to
see all of the entities on a single device.

This device description configuration is likely going to use an identical
template from one device to the next. To make things easier, the subkey
`device_info_template` can be defined under the `mqtt` key. The contents
of this key should be a template that when rendered produces the device_info
relevant to the majority of your devices. This template can then be inserted
into any of the `discovery_entities` by using the `device_info_template`
variable.

For example, the following a complex template that produces a nice device
info:
```YAML
mqtt:
# Other keys omitted
device_info_template: |-
{
"ids": "{{address}}",
"mf": "Insteon",
"mdl": "{%- if model_number != 'Unknown' -%}
{{model_number}} - {{model_description}}
{%- elif dev_cat_name != 'Unknown' -%}
{{dev_cat_name}} - 0x{{'%0x' % sub_cat|int }}
{%- elif dev_cat == 0 and sub_cat == 0 -%}
No Info
{%- else -%}
0x{{'%0x' % dev_cat|int }} - 0x{{'%0x' % sub_cat|int }}
{%- endif -%}",
"sw": "0x{{'%0x' % firmware|int }} - {{engine}}",
"name": "{{name_user_case}}",
"via_device": "{{modem_addr}}",
}
```

This when used in a `discovery_entities` template described above will render
as:

```JSON
{
"uniq_id": "4f.23.38_light",
"name": "my dimmer",
"cmd_t": "insteon/4f.23.38/level",
"stat_t": "insteon/4f.23.38/state",
"brightness": true,
"schema": "json",
"device": {
"ids": "4f.23.38",
"mf": "Insteon",
"mdl": "2477D - SwitchLinc Dimmer (Dual-Band)",
"sw": "0x45 - i2cs",
"name": "my dimmer",
"via_device": "41.ee.e6",
}
}
```
5 changes: 1 addition & 4 deletions insteon_mqtt/mqtt/Dimmer.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,7 @@ def unsubscribe(self, link):
def discovery_template_data(self, **kwargs):
"""This extends the template data variables defined in the base class
Returns the dict from the base class plus these additional keys that
contain the topic as defined in the config.yaml if any:
state_topic, on_off_topic, level_topic, scene_topic,
manual_state_topic
Adds in level_topic for dimmers.
"""
# Set up the variables that can be used in the templates.
data = super().discovery_template_data(**kwargs) # pylint:disable=E1101
Expand Down
33 changes: 21 additions & 12 deletions insteon_mqtt/mqtt/Mqtt.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ def __init__(self, mqtt_link, modem):
# The device_info_template
self.device_info_template = ""

# The discovery base topic, None if not enabled
self.discovery_topic_base = None

# MQTT message parameters. These get loaded via the config.
self.qos = 1
self.retain = True
Expand Down Expand Up @@ -120,6 +123,11 @@ def load_config(self, data):
if 'device_info_template' in data:
self.device_info_template = data['device_info_template']

# Check to see that discovery_topic_base is set in config
self.discovery_topic_base = data.get('discovery_topic_base', None)
if self.discovery_topic_base is None:
LOG.debug("Discovery disabled, discovery_topic_base not defined.")

# MQTT message parameters.
self.qos = data.get('qos', self.qos)
self.retain = data.get('retain', self.retain)
Expand Down Expand Up @@ -208,9 +216,7 @@ def handle_new_device(self, modem, device):
# and publish its discovery entities
if self.link.connected:
obj.subscribe(self.link, self.qos)
if (hasattr(obj, 'publish_discovery') and
callable(obj.publish_discovery)):
obj.publish_discovery(obj.device)
self._publish_device_discovery(obj)

#-----------------------------------------------------------------------
def handle_cmd(self, client, userdata, message):
Expand Down Expand Up @@ -369,22 +375,26 @@ def handle_ha_status(self, client, userdata, message):

payload = message.payload.decode("utf-8").strip().lower()
if payload == 'online':
self._publish_discovery()
for device in self.devices.values():
self._publish_device_discovery(device)
elif payload != 'offline':
LOG.warning("Unexpected HomeAssistant status message %s %s",
message.topic, message.payload)

#-----------------------------------------------------------------------
def _publish_discovery(self):
"""Trigger each device to publish its discovery entities
def _publish_device_discovery(self, device):
"""Trigger a device to publish its discovery entities
Checks that discovery is enabled and that the device supports
discovery.
Loops all devices and if they have the functionality, causes them to
publish their Discovery Entities
Args:
device: the device to send the publish discovery command
"""
for device in self.devices.values():
if self.discovery_topic_base is not None:
if (hasattr(device, 'publish_discovery') and
callable(device.publish_discovery)):
device.publish_discovery(device.device)
device.publish_discovery()

#-----------------------------------------------------------------------
def _startup(self):
Expand All @@ -406,8 +416,7 @@ def _startup(self):

for device in self.devices.values():
device.subscribe(self.link, self.qos)

self._publish_discovery()
self._publish_device_discovery(device)

#-----------------------------------------------------------------------
def _shutdown(self):
Expand Down
Loading

0 comments on commit 600b377

Please sign in to comment.