Base class for all HomeKit accessories using HAP-NodeJS or Homebridge.
Provides internal device tracking, metadata validation, lifecycle management, message routing, and optional EveHome-compatible history logging.
The HomeKitDevice module provides:
- Lifecycle hooks (
onAdd,onUpdate,onRemove,onSet,onGet,onMessage,onHistory) - Static and instance
.message()routing - Public wrapper methods (
add(),update(),remove(),get(),set(),history()) - Safe characteristic binding (
addHKService,addHKCharacteristic) - EveHome-compatible history support (
history), override HomeKitDevice.EVEHOME - Internal device registry for UUID-based lookup and messaging
Supports both Homebridge plugins and standalone HAP-NodeJS environments.
import HomeKitDevice from './HomeKitDevice.js';
export default class MyDevice extends HomeKitDevice {
static TYPE = 'MyDevice';
static VERSION = '2025.06.18';
async onAdd() {
this.myService = this.addHKService(this.hap.Service.Switch);
this.addHKCharacteristic(this.myService, this.hap.Characteristic.On, {
onSet: (value) => this.setSwitch(value),
onGet: () => this.getSwitch(),
props: { minStep: 1 },
initialValue: false,
});
}
async onUpdate(deviceData) {
this.deviceData = deviceData;
this.log.debug('Updated deviceData', deviceData);
}
async onRemove() {
this.log.info('Device removed');
}
async onMessage(type, message) {
if (type === HomeKitDevice.SET) {
this.setSwitch(message);
}
}
onHistory(type, entry) {
this.log.debug(`History logged for ${type}:`, entry);
}
setSwitch(value) {
this.log.info('Switch set to:', value);
}
getSwitch() {
return true;
}
}Each device must include:
| Field | Description |
|---|---|
serialNumber |
Unique identifier for the device |
softwareVersion |
Firmware or software version |
description |
User-visible description |
manufacturer |
Manufacturer name |
model |
Model number |
Required in addition to the above:
| Field | Description |
|---|---|
hkUsername |
HomeKit MAC-style address (e.g. 11:22:33:44:55:66) |
hkPairingCode |
HomeKit setup code (e.g. 123-45-678) |
Adds the specified HAP service to the accessory if not already present.
Returns the existing or newly created service instance.
Binds a characteristic to the given service with handler and property options.
Supported options:
onSet(value)– Called when HomeKit sets the characteristic valueonGet()– Called when HomeKit reads the characteristic valueprops– Defines characteristic metadata (minStep,unit,minValue,maxValue,validValues, etc.)initialValue– Value to initialize immediately
Adds a structured entry to Eve-compatible history storage.
- Automatically sets
entry.timeto current epoch if missing - Skips redundant entries unless
options.force === true - Uses
options.timegap(in seconds) to suppress entries too close together - Calls
.onHistory(type, entry)if implemented by the subclass
this.history(this.myService, {
status: 1,
temperature: 22.5,
}, {
timegap: 60,
force: false,
});Send a message to any registered device using its UUID:
HomeKitDevice.message(uuid, HomeKitDevice.SET, value);This routes to the device’s onMessage(type, message) handler.
| Method | Called when... |
|---|---|
onAdd(message) |
A .ADD message is received when the accessory is initialized |
onUpdate(deviceData) |
A .UPDATE message updates the device configuration/state |
onRemove(message) |
A .REMOVE message is received to shut down/unregister device |
onSet(message) |
A .SET message is received with new values to apply |
onGet(message) |
A .GET message is received to query current values/state |
onMessage(type, mmessage) |
A message was received that was not handled by known types |
onHistory(type, entry) |
After a history entry is successfully logged |
When a lifecycle message such as .ADD, .UPDATE, or .REMOVE is dispatched, the HomeKitDevice class now walks the prototype chain of the target instance to invoke all defined hook methods.
This means any onAdd(), onUpdate(), onRemove(), etc. methods defined in parent classes (such as base device types or mixins) will also be called, in order from the instance itself up the prototype chain.
Each hook is only invoked once per (handler, context) pair to avoid duplicate calls when the same function appears multiple times along the chain.
This enables shared logic across subclasses without needing to manually call super.onUpdate() or similar.
For example:
- If both
BaseandExtendeddefine anonUpdate()method andExtendedextendsBase, both methods will be called. - Ordering is guaranteed: subclass first, parent classes later.
These constants are used internally for structured messaging and lifecycle dispatch:
| Constant | Description |
|---|---|
HomeKitDevice.ADD |
Sent during accessory initialization (onAdd) |
HomeKitDevice.UPDATE |
Sent to apply updated device data (onUpdate) |
HomeKitDevice.REMOVE |
Sent to unregister the accessory (onRemove) |
HomeKitDevice.SET |
Sent to apply new values (onSet) |
HomeKitDevice.GET |
Sent to query device state (onGet) |
HomeKitDevice.HISTORY |
Sent when a history entry is logged (onHistory) |
HomeKitDevice.HK_PIN_3_2_3 |
RegExp for PIN format xxx-xx-xxx |
HomeKitDevice.HK_PIN_4_4 |
RegExp for PIN format xxxx-xxxx |
HomeKitDevice.MAC_ADDR |
RegExp for HomeKit username format XX:XX:XX:XX:XX:XX |
Each subclass may define a static VERSION string for visibility in logs:
static VERSION = '2025.06.18';This project is licensed under the Apache License, Version 2.0.
You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.