Previously the message handler spawned a goroutine for each message and
hoped for the best, but obviously it didn't work. Z2M often emitted the
current (old) state first, before sending out the updated state. Without
ordering, the old state might get processed later and overwrote the new,
correct state.
Message handling is now serialized, but decoupled, using a buffered
channel and a separate goroutine for processing.
This ensures that the "motion detected" event doesn't flip flop, even if
the motion sensor itself was set to a low timeout like 10-30s. It stays
in the Detected state until no further detections occur within the
timeout period (hardcoded to 1 minute).
This function allows either replacing or modifying the usual
Exposed->Characteristic value propagation.
It also adds a CurrentValue to the mapping to monitor for
changes/updates, regardless of whether the value was updated into the
Characteristic. This will be the source of truth.
If a Coordinator is present in the device list, AddDevicesFromJSON will
fail catastrophically, which shouldn't happen. Therefore, make sure only
EndDevices are considered during add. Also updated the tests to check
for this.
Added a device descriptor for failed adds. This will help with
identifying which device failed (and perhaps, why).
- Broke the code out into its own file.
- Added chaining and flipped translators to assist with re-using
existing translators. Also simplified logic when translating between
HomeKit & Z2M values by removing special cases like the "binary"
exposes. Since everything can be expressed with translators &
translator chains now, the process is streamlined.
- Wired up the defaultTranslator during the setup phase, so when mapping
is called, there's no nil checks necessary; just a direct call to the
mapping.Translator.
- Also added more documentation for the translation part since I forgot
most of it after a year.
The server used to use the hap default PIN, but using a fixed PIN is not
secure. A random PIN is now generated on first run and displayed to the
console (or journal), similar to how homebridge does it. It can also be
specified explicitly by the user in the config file.
It looks like Go has adopted 15s TCP keepalives as a default for _all_
TCP connections, which is quite dumb if you ask me.
https://github.com/golang/go/issues/48622
For the HAP server's side, it degrades iOS battery life significantly by
waking the device every 15s to respond to these packets. In the case as
a normal MQTT client, it increases traffic on top of the 60s keepalive
we've already set at the application layer. In both cases, the solution
is to just explicitly disable TCP keepalives.
Upgrade hap to the latest version that contains the fixbrutella/hap#36.
For devices that we have no idea how to handle, i.e. no services or
expose entries were processed, we return ErrUnknownDeviceType and skip
it during AddDevicesFromJSON(). This prevents a device from showing up
where HomeKit says it's not supported.
Also had to rename the existing DEBUG consts to DEVMODE, so as to not
confuse the two. DEVMODE is meant for developers and cannot be enabled
on-the-fly, whereas debug mode is for users to check that the bridge is
working, MQTT messages are received etc.
Update logging is throttled to avoid spurious messages for uncoalesced
MQTT updates and motion sensors. On my network with 10 devices, an
update is logged every 1-2 minutes on average.
So far Z2M state updates were only boolean (the device `state` on/off),
but after introducing `brightness` values, Z2M state updates may not be
recognized/acknowledged. Unmarshalled Z2M numeric values are always
float64, but updates sent out via MQTT might not be. As a result, the
values may not directly equal and may never match.
To solve this, cast the outgoing expected value into a float64 to
compare against the received Z2M value, which is already a float64.
The exposes entry looks similar to a switch, except the type is a
"light" and it has `state` and `brightness`. Tested only on single
channel dimmers.
Also added a PercentageTranslator to translate between the HomeKit
`brightness`, which is a percentage, to/from an arbitrary numeric value
in Z2M.
There doesn't seem to be any distinction in Z2M between occupancy and
motion sensors, but HomeKit has separate types. Most of the sensors are
PIR, so they are technically motion sensors instead of occupancy
sensors. There are of course _real_ occupancy sensors like mmWave, but
we'll deal with those when we get there.
encoding/json would refuse to implicitly cast the numbers into strings.
Currently we don't use these values, so let's preserve their type using
`any` for now.
An example is `keep_time` for occupancy sensors, where it's
[30, 60, 120] seconds.
Devices that have not received an update since a fixed timeout (24 hrs
for now), based on its last_seen time, will be marked as "not
responding" in the Home app. With this I can identify which devices are
unreachable or dead.
Also needed to upgrade hap with the fix for brutella/hap#30, or the
entire bridge will stop working.