homebridge-unifi-access
Version:
Homebridge UniFi Access plugin providing complete HomeKit integration for the UniFi Access ecosystem with full support for most features including autoconfiguration, motion detection, multiple controllers, and realtime updates.
155 lines • 7.49 kB
JavaScript
import { validateName } from "homebridge-plugin-utils";
import { AccessReservedNames } from "./access-types.js";
import { EventEmitter } from "node:events";
export class AccessEvents extends EventEmitter {
api;
controller;
eventsHandler;
eventTimers;
hap;
log;
mqttPublishTelemetry;
platform;
udaApi;
udaDeviceState;
udaUpdatesHandler;
unsupportedDevices;
// Initialize an instance of our Access events handler.
constructor(controller) {
super();
this.api = controller.platform.api;
this.eventTimers = {};
this.hap = controller.platform.api.hap;
this.log = controller.log;
this.mqttPublishTelemetry = controller.hasFeature("Controller.Publish.Telemetry");
this.controller = controller;
this.udaApi = controller.udaApi;
this.udaDeviceState = {};
this.platform = controller.platform;
this.unsupportedDevices = {};
this.eventsHandler = null;
this.udaUpdatesHandler = null;
// If we've enabled telemetry from the controller inform the user.
if (this.mqttPublishTelemetry) {
this.log.info("Access controller telemetry enabled.");
}
this.configureEvents();
}
// Process Access API update events.
udaUpdates(packet) {
let accessDevice;
switch (packet.data.device_type) {
case "UA-Hub-Door-Mini":
case "UA-ULTRA":
case "UAH":
case "UAH-DOOR":
default:
// Lookup the device.
accessDevice = this.controller.deviceLookup(packet.event_object_id);
// No device found, we're done.
if (!accessDevice) {
break;
}
// Update our device configuration state.
accessDevice.uda = packet.data;
// If we have services on the accessory associated with the Access device that have a StatusActive characteristic set, update our availability state.
accessDevice.accessory.services.filter(x => x.testCharacteristic(this.hap.Characteristic.StatusActive))
?.map(x => x.updateCharacteristic(this.hap.Characteristic.StatusActive, accessDevice?.isOnline ?? false));
// Sync names, if configured to do so.
if (accessDevice.hints.syncName && (accessDevice.accessoryName !== validateName(accessDevice.uda.alias))) {
accessDevice.log.info("Name change detected. A restart of Homebridge may be needed in order to complete name synchronization with HomeKit.");
accessDevice.configureInfo();
}
break;
}
// Update the internal list we maintain.
this.udaDeviceState[packet.event_object_id] = packet.data;
}
// Process device additions and removals from the Access events API.
manageDevices(packet) {
// Lookup the device.
const accessDevice = this.controller.deviceLookup(packet.event_object_id);
// We're unadopting.
if (packet.event === "access.data.device.delete") {
// If it's already gone, we're done.
if (!accessDevice) {
return;
}
// Remove the device.
this.controller.removeHomeKitDevice(accessDevice);
return;
}
}
// Listen to the UniFi Access events API for updates we are interested in (e.g. unlock).
configureEvents() {
// Only configure the event listener if it exists and it's not already configured.
if (this.eventsHandler && this.udaUpdatesHandler) {
return true;
}
// Ensure we update our UDA state before we process any other events.
this.prependListener("access.data.device.update", this.udaUpdatesHandler = this.udaUpdates.bind(this));
// Process remove events.
this.prependListener("access.data.device.delete", this.manageDevices.bind(this));
// Listen for any messages coming in from our listener. We route events to the appropriate handlers based on the type of event that comes across.
this.udaApi.on("message", this.eventsHandler = (packet) => {
// Emit messages based on the event type.
this.emit(packet.event, packet);
// Emit messages based on the specific device.
this.emit(packet.event_object_id, packet);
// Finally, emit messages based on the specific event and device combination.
this.emit(packet.event + "." + packet.event_object_id, packet);
// If enabled, publish all the event traffic coming from the Access controller to MQTT.
if (this.mqttPublishTelemetry) {
this.controller.mqtt?.publish(this.controller.uda.host.mac.replace(/:/g, ""), "telemetry", JSON.stringify(packet));
}
});
return true;
}
// Motion event processing from UniFi Access.
motionEventHandler(accessDevice) {
if (!accessDevice) {
return;
}
// Only notify the user if we have a motion sensor and it's active.
const motionService = accessDevice.accessory.getService(this.hap.Service.MotionSensor);
if (motionService) {
this.motionEventDelivery(accessDevice, motionService);
}
}
// Motion event delivery to HomeKit.
motionEventDelivery(accessDevice, motionService) {
if (!accessDevice) {
return;
}
// If we have disabled motion events, we're done here.
if (("detectMotion" in accessDevice.accessory.context) && !accessDevice.accessory.context.detectMotion) {
return;
}
// If we have an active motion event inflight, we're done.
if (this.eventTimers[accessDevice.id]) {
return;
}
// Trigger the motion event in HomeKit.
motionService.updateCharacteristic(this.hap.Characteristic.MotionDetected, true);
// If we have a motion trigger switch configured, update it.
accessDevice.accessory.getServiceById(this.hap.Service.Switch, AccessReservedNames.SWITCH_MOTION_TRIGGER)?.updateCharacteristic(this.hap.Characteristic.On, true);
// Publish the motion event to MQTT, if the user has configured it.
this.controller.mqtt?.publish(accessDevice.id, "motion", "true");
// Log the event, if configured to do so.
if (accessDevice.hints.logMotion) {
accessDevice.log.info("Motion detected.");
}
// Reset our motion event after motionDuration.
this.eventTimers[accessDevice.id] = setTimeout(() => {
motionService.updateCharacteristic(this.hap.Characteristic.MotionDetected, false);
// If we have a motion trigger switch configured, update it.
accessDevice.accessory.getServiceById(this.hap.Service.Switch, AccessReservedNames.SWITCH_MOTION_TRIGGER)?.updateCharacteristic(this.hap.Characteristic.On, false);
accessDevice.log.debug("Resetting motion event.");
// Publish to MQTT, if the user has configured it.
this.controller.mqtt?.publish(accessDevice.id, "motion", "false");
// Delete the timer from our motion event tracker.
delete this.eventTimers[accessDevice.id];
}, accessDevice.hints.motionDuration * 1000);
}
}
//# sourceMappingURL=access-events.js.map