Skip to content

Initial version of HomeAssistant plugin#125

Open
verwilst wants to merge 4 commits intorensongroup:masterfrom
verwilst:feature/home-assistant
Open

Initial version of HomeAssistant plugin#125
verwilst wants to merge 4 commits intorensongroup:masterfrom
verwilst:feature/home-assistant

Conversation

@verwilst
Copy link
Copy Markdown

No description provided.

@bneyr
Copy link
Copy Markdown
Contributor

bneyr commented Sep 5, 2025

Hi @verwilst
Currently we have Home Assistant integration through a HA plugin created by Wouter Coppens:
https://github.com/rensongroup/pyhaopenmotics
Is there a different approach here and what would be the reason exactly?

Kind regards

@verwilst
Copy link
Copy Markdown
Author

verwilst commented Sep 5, 2025 via email

Copy link
Copy Markdown
Contributor

@bneyr bneyr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very good! Thanks for the effort. A few small improvements I think and then we can merge.

import re
import sys

from threading import Thread
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid memory leaks, can you move either to mulitprocessing ThreadPool or start an event loop in a background task and schedule your work async?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there another plugin that uses the method that you prefer? I based my code on plugins/mqtt-client, where it's using threading as well.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the reason is that mqtt-client was written for python2, and ThreadPoolExecutor is introduced in Python 3.2
The python 2 plugins have only recently moved to python3.

On a related note: before version 3.11, we used Thread() to handle the IPC communication messages between GW & plugin.
We had one plugin that used zeromq, and connected to a broker in the thread, which caused it to not be cleaned up properly by the python garbage collector.
Now the IPC communication is moved to multiprocessing ThreadPool.


self._read_config()

paho_mqtt_wheel = '/opt/openmotics/python/plugins/HomeAssistant/paho_mqtt-1.6.1-py3-none-any.whl'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will fail during the plugin validation because the path will not exist yet.
Better to get them relative to __ file __

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've based this on plugins/mqtt-client, I've noticed it is now a relative path. Is adding the .egg file to a subdir in my src folder a good solution? It feels kind of dirty so want to make sure this is ok before proceeding.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is support in the plugin runtime for automatically loading .egg files, but it seems very legacy because everyone now loads whl files. Most of the private plugins also use the method currently used in mqtt-client with the relative path.


def set_state(self, new_state):

if new_state not in ['ON', 'OFF']:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general I'd advise to work as much as possible with case-insenstive enum definitions.

@verwilst verwilst force-pushed the feature/home-assistant branch from 19ee78d to be0209e Compare March 13, 2026 19:45
@verwilst verwilst force-pushed the feature/home-assistant branch from be0209e to 9bfa58f Compare March 13, 2026 19:50
@verwilst
Copy link
Copy Markdown
Author

Sorry for the long wait! I've rewritten this from scratch, and in the meantime implemented shutter support and changed the way the sensors are created ( Sensor is a device now, with actual measurements (temp, humidity) as sensor entities in that device.

I've also switched from threads to ThreadPool as requested. paho .egg file is now relative.

@verwilst
Copy link
Copy Markdown
Author

image image

Add _input_class(name) static method that case-insensitively matches:
  deur/door   → device_class=door,   inverted payloads (ON=closed)
  raam/window → device_class=window, inverted payloads (ON=closed)
  beweging/motion → device_class=motion, standard payloads

Inversion sets payload_on=OFF / payload_off=ON in the discovery config so
HA correctly shows Open/Closed for contacts that are active when closed.
State topic continues to publish ON/OFF unchanged.
@verwilst
Copy link
Copy Markdown
Author

Because there is no option to pass on metadata of inputs, i look at the names, and convert names that have "door" or "deur" to a door entity, "window" or "raam" to a window entity, and "motion" of "beweging" to a motion entity.

Add _room_slug() and _object_id(name, room_id) helpers. All four entity
types now include object_id in their discovery payload, built from the
entity name slug prefixed with the room slug when a room is assigned.

Examples:
  output 'mylight' in room 'Living Room' -> light.living_room_mylight
  shutter 'Another Screen' (no room)     -> cover.another_screen
  sensor 'MySensor' temperature          -> sensor.mysensor_temperature
  input 'door_bureau'                    -> binary_sensor.door_bureau

unique_id, MQTT topics and discovery topic paths are unchanged.
@tmds
Copy link
Copy Markdown
Contributor

tmds commented Mar 20, 2026

hi @verwilst , I have a small app running in my house that bridges between the HTTP API and Home assistant using MQTT.

It works well, but I'd rather have a plugin like this that runs directly on the gateway.

One issue with the MQTT is that some information is captured by Home Assistant only on the first discovery. Once it is in the database you can no longer change it via MQTT.

I want OpenMotics configuration to be the source of truth and avoid having to deal with these entities separately in home assistant. To do this requires changing the information that isn't accessible through MQTT.

Because I don't have that additional thing (yet), I've modeled the installation as a single device, so I can use the home assistant UI to delete that device or ask it to re-generate the entities. This is my way to "sync-up". The single device model does come with the drawbacks that the default friendly names are determined based on the device name and the entity name which makes it a long string that isn't userfriendly. And, the MQTT model doesn't allow components to be assigned a room, so I'm not able to model the room information through MQTT. Both of these issues don't exist with the multi-device model.

Regardless of the single/multi-device, I think MQTT is not enough to keep the configuration in sync and some direct interaction with home assistant is also needed for that.

This is a configuration issue, for the daily operation, MQTT works great.

},
"sound": {"device_class": None, "unit": "dB", "component": "sensor"},
"dust": {"device_class": "pm10", "unit": "µg/m³", "component": "sensor"},
"comfort_index": {"device_class": None, "unit": None, "component": "sensor"},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't include the energy module sensors.
In particular the power/W values are useful.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't use the energy sensors, which is why it's missing. Could you maybe add a screenshot so I can try to create some virtual equivalents to test?

@verwilst
Copy link
Copy Markdown
Author

verwilst commented Mar 20, 2026 via email

if output is None:
return
name = output["name"] or "Output {0}".format(output_id)
unique_id = "{device_id}_output_{id}".format(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've included the names rather than the ids in the unique_id. I did this because I was worried that when I re-wire something home assistant would still show the old name for something that does now a different thing. By using another unique_id when the name changes to home assistant it becomes something else.

name_slug = (
re.sub(r"[^a-z0-9]+", "_", name.lower().strip()).strip("_") or "entity"
)
room_slug = self._room_slug(room_id)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It happens often that the name for something is the name of the room. Like the name of the light in the 'Cellar' might be 'Cellar'. When the name and room are the same, the room could be left out.

Example: room="Living Room", name="Another Screen" -> "living_room_another_screen"
Without room: "another_screen"
"""
name_slug = (
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the way we name things gets determined where they show up in the OpenMotics UX and app.

Based on how I named things, I've included the room name for outputs, but I left it out for sensors and inputs. For shutters I could have gone either way.

Most of the inputs I have in my installations are from the physical switch buttons. I haven't assigned those to rooms because they don't show up in the UI grouped by room. They have names that include the room. Probably the lack of room information is what made me leave it out in the object_id.

I wouldn't mind having to set the rooms on all the sensors/inputs. I've just avoided doing it so far.

output = self._outputs.get(output_id)
if output is None:
return
name = output["name"] or "Output {0}".format(output_id)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my installation there are a bunch of lights named "Centraal" and "Raam" because I didn't know what the lights referred to when I saw them in home assistant, I've included the room name too, like Centraal (Slaapkamer).

I don't know if the UX in hass differs much depending on whether you have the room set on the device or not.

Similar to my other comment, I only added the room name to the outputs.

| Output (relay) | `switch` |
| Output (light relay) | `light` |
| Output (dimmer) | `light` with brightness |
| Input | `binary_sensor` |
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main two inputs I'm using in home assistant in home assistant are for switch buttons and motion sensors. For these use-cases it was more interesting to have these modeled as event rather than binary_sensor.

For the switch buttons event is a natural fit, someone presses/releases the button.

Also for the motion sensors I find it useful because I want to get motion events (by configuring the sensors to the lowest time) rather than assumed presence (by configuring them to a longer time).

For other types of inputs, binary_sensor can be a better match. For example when you have a window sensor.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants