Hemtjänst is:
- A Swedish word for home care or home service
- A specification of sorts on how devices should register with an MQTT broker
- An MQTT to HomeKit bridge
This project was started so that a collection of $platform-to-MQTT bridges could be created or adapted that announce the devices they have in a similar enough fashion that we can automatically generate HomeKit accessories from it. Though the aim for now is HomeKit the metadata published should be enough to also be able to create bridges for other platforms, like SmartThings.
dep
is used to manage the dependencies as a number of
them have a habit of making backwards incompatible changes. You'll need to follow dep
's
installation guide first.
After that you'll need to:
mkdir -p $GOPATH/src/github.com/hemtjanst
cd $GOPATH/src/github.com/hemtjanst
git clone
the repocd
into itdep ensure
to fetch the dependenciesgo build
to build the result orgo install
Follow the same instructions as install but either clone your fork into
$GOPATH/src/github.com/hemtjanst
, or if you already did a go get
before you can update the origin
remote in git to point to your fork instead.
For example:
git remote rename origin upstream
git remote add origin git@github.com:your-workspace/your-fork.git
Or, if you cloned directly into the directory from your fork:
git remote add upstream git@github.com:hemtjanst/hemtjanst.git
Now if you want to pull in changes from upstream
, i.e this repository you can
git fetch upstrseam
them and then git merge upstream/master
, or whichever branch.
Once you've go install
ed the project a binary will be in your $GOPATH/bin
.
If you do not have explicitly set a $GOPATH
environment variable it defaults
to $HOME/go
. Ensure to add $GOPATH/bin
to your $PATH
or use the full path
to the binary.
By default it will connect to an MQTT broker on localhost:1883
and expose a
HomeKit bridge on port 12345
with pairing pin-code 01020304
.
Pass a --help
for all available options.
In order to not have to continuously scan for new devices, or to subscribe to every event, a discovery mechanism has to be implemented.
A discover
topic with retain set must exist. Any device that joins the
network must subscribe to discover
. Since the retain bit is set they will
receive the discovery request and must now announce themselves to the rest
of the network. If someone then wants to initiate a full discovery all they
need to do is publish again to discover (with the retain bit set).
An announce is done by publishing to the announce/YOUR NAME
topic with persistence.
The body of the message must be the meta content for the entity that joined.
The topic layout is entirely arbitrary and so is the naming of the device.
Anyone interested in knowing about devices now simply subscribes to announce/#
and gets to know any device that joins.
Please note, the topic of the device is used to generate a unique identifier for this device, so if you change it it'll be like you removed the existing device and added a new one.
Whenever a device leaves it publishes its "root" topic to the leave
topic.
Similarly to announce this allows other clients to cancel their subscriptions
if they were explicitly wathcing that device or take any other ation.
MQTT includes a Last Will And Testament feature that will cause the broker to publish a message on a specific topic when the client is gone, gracefully or not.
If every device sets up its own MQTT client it can hence specify that as a
last will and testament the broker should publish to leave
with its original
topic. This ensures that we can always properly clean up, even if the device
falls of a cliff.
However, for bridged clients, lets say IKEA Trådfri lights that are published through a trådfri-to-mqtt broker, the bridge is the only MQTT client. Since we can't specify multiple actions in a last will and testament the bridge can still tell the broker to publish to a message to the topic, the content being a string representing a "leave ID". Every device that the bridge used to bridge must specify that same "leave ID" in its metadata so that a mapping can be maintained between "leave ID"s and devices for any cleanup purposes, such as no longer announcing this accessory to HomeKit.
When a device receives a discover it must publish it's meta underneath
announce/YOUR NAME
as detailed in the discovery protocol.
Meta is a JSON object serialised as a string that contains any additional information needed for Hemtjänst to do its thing and generate HomeKit accessories.
As it currently stands the types and features in meta follow the exact names in the HomeKit specification. To support another platform like SmartThings a similar mapping would need to be created.
The meta
document contains a number of required and optional entries. The
required ones are: name
, type,
feature`. The rest is optional.
Optional keys are: topic
, lastWillID
.
The naming of the keys follows Google's JSON style guide and as
such are in camelCase. However, ID
is always fully uppercase and any
chemical formula expressed in chemical symbols follows its relevant casing, so
CO2
, not Co2
for carbon dioxide.
A human friendly device name. Can be the same thing as the topic name or something else entirely. This will be the name of the accessory as HomeKit sees it so do pick something that makes sense and allows you to relatively easily identify the accessory.
The type of device, for example light
or CO2Sensor
. These map directly onto
HomeKit services and are considered the "primary" service. There is currently no
support for hidden, secondary or linked services.
You can find the supported devices here and how they map to HomeKit services.
Every device needs at least one feature to be defined for it, the usual required characteristic (it can be more than one). Similarly to device the characteristics mapping contains a list of which feature names map to what HomeKit characteristics.
A feature is an object itself, which can be empty, in which case the defaults
apply for the min
, max
and step
value as defined in the HomeKit spec. In
order to override them you can specify min
, max
and step
keys and set the
appropriate value. Do note that you cannot change the type of the value, so if
the HomeKit specifies something as a uint8
it will be deserialised as a
uint8
.
Similarly, we expect that in order to get and set the value of a feature a
"root topic"/<feature>/get
and set
topic exist that we can use. If those
topics are named differently you have to specify a getTopic
and a setTopic
key that have the full path to a topic (so not necessarily nested under the
"root" topic) that should be used instead.
The "root" topic of this device, for example lightbulb/kitchen
. This may also
be the name that you publish to underneath announce, so announce/lightbulb/kitchen
is entirely valid and will be used as the root topic unless the topic
key is present.
If you publish as announce/lightbulb/kitchen
but the topic is set to light/kitchen
the topic
in meta takes precedence.
The lastWillID
only has to be set for bridged devices, so in cases where each
device doesn't itself maintain a connection to the MQTT broker. When that is
the case the Last Will And Testament should be set instead to publish the
"root" topic name to the leave
topic.
The lastWillID
can be anything but needs to be unique. As such it's recommended
to use a UUIDv4 for this.
The meta
topic for a light that can just be turned on and off looks like
this:
{
"name": "kitchen stove light",
"type": "light",
"feature": {
"on": {}
}
}
Most smart lights however can also dim and usually set the colour temperature too, so it's more likely you'll want to publish something like this:
{
"name": "living room light",
"type": "light",
"feature": {
"on": {},
"brightness": {},
"colorTemperature": {
"getTopic": "light/ground_floor/living_room/warmth/get",
"setTopic": "light/ground_floor/living_room/warmth/set",
},
}
}
A contact sensor, something you might put on a window to detect if it is open or closed (as part of a security system for example) can be defined like this:
{
"name": "bathroom window",
"type": "contactSensor",
"feature": {
"contactSensorState": {}
},
"lastWillID": "f56ad37c-aa0f-45f4-8e92-f9a6dba39d84"
}