homebridge-unifi-occupancy-lite
Version:
Lightweight UniFi occupancy sensor plugin for Homebridge using API tokens only. Works with UDM Pro/SE, UDM, UDR and other UniFi OS devices.
199 lines (198 loc) • 9.01 kB
JavaScript
"use strict";
/*
* Based on homebridge-unifi-occupancy by DouweM
* Original: https://github.com/DouweM/homebridge-unifi-occupancy
* Licensed under Apache-2.0
*
* Modified for homebridge-unifi-occupancy-lite - simplified, resident-based presence detection
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.UnifiOccupancyPlatform = void 0;
const settings_1 = require("./settings");
const unifi_client_1 = require("./unifi-client");
const resident_1 = require("./resident");
const global_presence_accessory_handler_1 = require("./global_presence_accessory_handler");
const wifi_point_accessory_handler_1 = require("./wifi_point_accessory_handler");
class UnifiOccupancyPlatform {
constructor(log, config, api) {
this.log = log;
this.config = config;
this.api = api;
this.Service = this.api.hap.Service;
this.Characteristic = this.api.hap.Characteristic;
this.residents = [];
this.wifiPoints = [];
this.accessories = new Map();
this.wifiPointHandlers = new Map();
if (!this.parseConfig()) {
return;
}
this.api.on('didFinishLaunching', () => {
this.connect();
this.setupAccessories();
this.refresh()
.then(() => this.refreshPeriodically())
.catch(error => {
this.log.error('Failed to initialize:', error);
});
});
}
parseConfig() {
var _a, _b, _c, _d;
if (!this.config.unifi) {
this.log.error('ERROR: UniFi Controller is not configured.');
return false;
}
if (!this.config.unifi.apiKey) {
this.log.error('ERROR: UniFi API Key is required.');
return false;
}
if (this.config.unifi.useSiteManagerApi && !this.config.unifi.hostId) {
this.log.error('ERROR: Host ID is required when using Site Manager API.');
return false;
}
// Set defaults
(_a = this.config).interval || (_a.interval = 180);
(_b = this.config).globalPresenceSensor ?? (_b.globalPresenceSensor = true);
(_c = this.config).residents || (_c.residents = []);
(_d = this.config).wifiPoints || (_d.wifiPoints = []);
// Initialize residents
this.residents = this.config.residents.map((residentConfig) => new resident_1.Resident(residentConfig));
// Initialize wifi points
this.wifiPoints = this.config.wifiPoints.map((wifiPointConfig) => new resident_1.WifiPoint(wifiPointConfig));
this.log.info(`Configured ${this.residents.length} residents with ${this.residents.reduce((sum, r) => sum + r.devices.length, 0)} total devices`);
this.log.info(`Configured ${this.wifiPoints.length} WiFi points`);
return true;
}
connect() {
this.log.debug('Connecting to UniFi Controller...');
this.unifi = new unifi_client_1.UniFiLiteClient({
controller: this.config.unifi.controller,
apiKey: this.config.unifi.apiKey,
site: this.config.unifi.site || 'default',
secure: this.config.unifi.secure || false,
useSiteManagerApi: this.config.unifi.useSiteManagerApi || false,
hostId: this.config.unifi.hostId
});
this.log.info('UniFi API Client initialized');
}
setupAccessories() {
// Setup global presence sensor
if (this.config.globalPresenceSensor) {
this.setupGlobalPresenceAccessory();
}
// Setup WiFi point accessories
this.wifiPoints.forEach(wifiPoint => {
this.setupWifiPointAccessory(wifiPoint);
});
}
setupGlobalPresenceAccessory() {
const uuid = this.api.hap.uuid.generate('global-presence');
let accessory = this.accessories.get(uuid);
if (!accessory) {
accessory = new this.api.platformAccessory('Global Presence', uuid);
this.api.registerPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [accessory]);
this.accessories.set(uuid, accessory);
this.log.info('Created new global presence accessory');
}
this.globalPresenceHandler = new global_presence_accessory_handler_1.GlobalPresenceAccessoryHandler(this, accessory, this.residents);
}
setupWifiPointAccessory(wifiPoint) {
const uuid = this.api.hap.uuid.generate(`wifi-point-${wifiPoint.name}`);
let accessory = this.accessories.get(uuid);
if (!accessory) {
accessory = new this.api.platformAccessory(`${wifiPoint.name} Presence`, uuid);
this.api.registerPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [accessory]);
this.accessories.set(uuid, accessory);
this.log.info(`Created new wifi point accessory: ${wifiPoint.name}`);
}
const handler = new wifi_point_accessory_handler_1.WifiPointAccessoryHandler(this, accessory, wifiPoint, this.residents);
this.wifiPointHandlers.set(wifiPoint.name, handler);
}
refreshPeriodically() {
setInterval(() => this.refresh(), this.config.interval * 1000);
}
configureAccessory(accessory) {
this.accessories.set(accessory.UUID, accessory);
}
async refresh() {
try {
this.log.debug('Refreshing device presence...');
// Get clients from UniFi
const clients = await this.unifi.getClients();
const accessPoints = await this.unifi.getNetworkDevices();
// Update device status for each resident
for (const resident of this.residents) {
for (const device of resident.devices) {
// Reset device status
device.isOnline = false;
device.currentAccessPoint = undefined;
// Find matching client
const matchingClient = clients.find(client => device.matchesClient(client));
if (matchingClient) {
// Get traffic data if needed
let trafficData = null;
if (device.minTrafficAmount && device.minTrafficAmount > 0) {
trafficData = await this.unifi.getClientTrafficLast15Min(matchingClient.mac);
}
device.updateFromClient(matchingClient, trafficData);
}
}
// Update resident presence based on device status
resident.updatePresence();
}
// Map access points for wifi point matching
for (const wifiPoint of this.wifiPoints) {
const matchingAP = accessPoints.find(ap => wifiPoint.matchesAccessPoint(ap));
if (matchingAP) {
wifiPoint.mac = matchingAP.mac;
}
}
// Update accessory handlers
this.globalPresenceHandler?.updateResidents(this.residents);
this.globalPresenceHandler?.refresh();
this.wifiPointHandlers.forEach(handler => {
handler.updateResidents(this.residents);
handler.refresh();
});
// Log presence summary
const homeResidents = this.residents.filter(r => r.isHome).map(r => r.name);
if (homeResidents.length > 0) {
this.log.info(`Residents at home: ${homeResidents.join(', ')}`);
}
else {
this.log.info('No residents detected at home');
}
}
catch (error) {
this.log.error('Error refreshing device presence:', error);
}
}
// Remove unused accessories
removeUnusedAccessories() {
const expectedAccessories = new Set();
// Add global presence if enabled
if (this.config.globalPresenceSensor) {
expectedAccessories.add(this.api.hap.uuid.generate('global-presence'));
}
// Add wifi point accessories
this.wifiPoints.forEach(wifiPoint => {
expectedAccessories.add(this.api.hap.uuid.generate(`wifi-point-${wifiPoint.name}`));
});
// Remove accessories that are no longer needed
const accessoriesToRemove = [];
this.accessories.forEach((accessory, uuid) => {
if (!expectedAccessories.has(uuid)) {
accessoriesToRemove.push(accessory);
}
});
if (accessoriesToRemove.length > 0) {
this.log.info(`Removing ${accessoriesToRemove.length} unused accessories`);
this.api.unregisterPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, accessoriesToRemove);
accessoriesToRemove.forEach(accessory => {
this.accessories.delete(accessory.UUID);
});
}
}
}
exports.UnifiOccupancyPlatform = UnifiOccupancyPlatform;