I initially started by using Apple HomeKit via HomeBridge, which has a large collection of plugins for many of my devices. However, it was always limited to being controlled from Apple devices, and the automation abilities were limited.
I briefly looked at Home Assistant, which is open-source and can be controlled from Linux, but seemed brittle and inflexible.
When I was gifted some hardware that lacked plugins for either HomeBridge or Home Assistant, requiring me to write software to use them, I decided I would invest that effort into building my own platform.
To lead a “better” life.
To lead a less stressful life:
To lead a comfier life:
This is a hobby project, so it must be low-effort to maintain:
The system will not “own” the devices it controls: the TV’s own remote must continue to work.
When I used HomeKit, a frequent problem was that I had to use an Apple product to use it. If I had left my phone charging instead of in my pocket, I had to go and get it, which grew to be an annoyance. It should thus be controllable without needing a specific item:
Now that I have my requirements, there are a few high-level models of operation for managing the distributed state.
First, a quick glossary:
Remote objects (RPC, REST, SOAP, …) is a common idiom in distributed computing. A sender sends a message to recipient, which actions the message, possibly returning a response. The sender must know where to send the message, or talk to a central router that itself must know where to send the message.
Under this system, to set up a new actuator for an existing control, I would need to add the actuator to the control’s configuration. This is more wiring and configuration than I can be bothered with, and contravenes the low maintenance requirement.
In an intent-based control system there is a central intended system state, and one or more actuators that try to make it reality. For example, if the intent says that the lights should be on, an actuator daemon continually checks the actual state of the lights, turning them on if they are off.
This allows for low coupling, as the controller that sets the intent doesn’t care which actuator turns on the light and how, which meets the low maintenance requirement. However a naïve implementation risks violating the real world requirement, e.g. if a light is turned off at the device itself, the actuator will repeatedly try to turn it back on.
The current implementation uses MQTT as a sort of real-time database. MQTT has “topics”, and clients can publish events to topics and subscribe to events from topics.
It meets the low maintenance requirement:
To meet the real world requirement, actuators only act when a topic’s value changes. The two rules for actuators are:
For example, with one actuator, one observer, and an external control:
home/living-room/tv/poweris set to
An example with two actuators:
home/living-room/speakers/poweris set to
MQTT is similar to a simple key-value store, with strings as keys and unstructured bytes as values. The keys can have hierarchy, like a filesystem path, but this isn’t required.
As it is stringly typed, I have opted for a key naming scheme:
Similar to the Prometheus best practices, topics have
_suffixes that state a topic’s units and valid values. For example, a topic
home/kitchen/speakers/volume_percent can only have number values in the range
Since not all types of control are so convenient, topics can have optional metadata topics, e.g. a topic
…/foo_enum can have a metadata topic
…/foo_enum/values to list valid values for controllers to choose from.
The hierarchy also allows for controllers presenting a unified device to a user, even if it is actually multiple devices:
speakers/poweris controlled by an Energenie RF relay socket.
speakers/input_enumis controlled by Snapcast.
speakers/volume_percentis controlled by ALSA.