This guide documents the conventions and lessons learned while building and debugging the custom HomeKit adapters in this repository and their interaction with node-red-contrib-homekit-bridged.
Applies to the custom Node-RED adapters in this repository that:
homekit-service nodes from node-red-contrib-homekit-bridgedCurrent adapters in scope:
adapters/water-leak-sensor/homekit-adapteradapters/presence-sensor/homekit-adapterThe adapter is responsible for:
node-red-contrib-homekit-bridged is responsible for:
Important practical detail:
node-red-contrib-homekit-bridged delays bridge publication by 5s7-10s after startupThis means early messages can be accepted by Node-RED but still be ineffective for HomeKit initialization.
Adapters must use a bounded bootstrap window.
Current convention:
bootstrapDeadlineMs = 10000Reason:
10s is long enough for flow initialization and bridge publication, while still reasonable for startupCurrent established behavior:
.../last and live topics at startup10s bootstrap deadline.../lastReason:
At bootstrap end, always emit a full snapshot of the known service state.
Do not rely on:
At bootstrap timeout:
null output for that cycleDo not fabricate values just to fill a payload.
Adapters may deduplicate repeated payloads during normal operation, but the final bootstrap snapshot must bypass local deduplication.
Reason:
water-leak-sensorCurrent supported model:
SNZB-05PSubscribe on startup to:
.../last.../value.../availabilityBootstrap is based on:
water_leakbattery or battery_lowpresence-sensorCurrent supported model:
ZG-204ZVSubscribe on startup to:
.../last.../valueBootstrap is based on:
motiontemperaturehumidityilluminancebattery or battery_lowKeep .../last subscribed during bootstrap.
Unsubscribe from .../last only after the final bootstrap snapshot was emitted.
Adapters should maintain a local normalized state and derive HomeKit payloads from that state, not directly forward upstream messages.
Use explicit conversions:
Current convention:
{"StatusLowBattery":0,"BatteryLevel":100,"ChargingState":2}
Notes:
BatteryLevel is uint8, integer 0..100ChargingState = 2 means NOT_CHARGEABLEFor services that include status flags, publish explicit values for:
StatusActiveStatusFaultStatusLowBatteryStatusTamperedThese should be derived from local state, not omitted.
homekit-service configurationThis is the main practical integration rule with node-red-contrib-homekit-bridged.
Some HomeKit characteristics are optional in HAP, but the adapter still publishes them.
If they are not materialized on the homekit-service node, HomeKit may:
characteristicPropertieswater-leak-sensorBattery service:
{"BatteryLevel":{},"ChargingState":{}}
presence-sensorBattery service:
{"BatteryLevel":{},"ChargingState":{}}
Motion service:
{"StatusActive":{},"StatusFault":{},"StatusLowBattery":{},"StatusTampered":{}}
Occupancy service:
{"StatusActive":{},"StatusFault":{},"StatusLowBattery":{},"StatusTampered":{}}
Observed behavior during development:
BatteryLevel = 0 and ChargingState = Not ChargingThe characteristicProperties JSON above forces the relevant optional characteristics to be materialized on the corresponding homekit-service.
Do not use boolean values like:
{"BatteryLevel":true,"ChargingState":true}
even if they happen to work as a side effect.
Use empty objects:
{"BatteryLevel":{},"ChargingState":{}}
This is clearer and aligns with the intended structure of characteristicProperties.
Current adapters use:
startSubscriptions after 250msThis is acceptable for subscription startup, but it is not enough to assume HomeKit readiness.
Observed in node-red-contrib-homekit-bridged:
5000msImplication:
5s after startup should not be trusted as bootstrap-finalizing messages for HomeKit behaviorObserved in practice:
7-10sCurrent convention therefore remains:
10sWhen writing a new HomeKit adapter:
homekit-service nodes.buildBatteryMsg, buildMotionMsg, etc.Recommended order:
testing first.node-red.service on 192.168.2.104.10s.production.Known hosts:
testing: node-red@192.168.2.104production: node-red@192.168.2.101legacy: pi@192.168.2.133Important service name difference observed:
testing, restart node-red.servicenodered.serviceEach HomeKit adapter help page should document:
homekit-service nodescharacteristicProperties JSON as copy-pasteable code blocksThis is not optional. The downstream Node-RED flow configuration is part of the adapter contract.
waitForSetupMsg is for deferred node configuration, not for runtime bootstrap value materialization.Unless a strong reason appears, keep these defaults for new HomeKit adapters:
250ms10000ms{"StatusLowBattery":0,"BatteryLevel":100,"ChargingState":2}
characteristicProperties