homebridge-unifi-smartpower
Version:
UniFi SmartPower plugin for Homebridge.
255 lines • 14.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.UniFiSmartPowerHomebridgePlatform = void 0;
const settings_1 = require("./settings");
const platformAccessory_1 = require("./platformAccessory");
const uniFiSmartPower_1 = require("./uniFiSmartPower");
const platformAccessoryControlSwitch_1 = require("./platformAccessoryControlSwitch");
class UniFiSmartPowerHomebridgePlatform {
log;
platformConfig;
api;
static REFRESH_DEVICES_POLL_INTERVAL_MS_DEFAULT = 10 * 60 * 1000; // 10m
static REFRESH_DEVICES_POLL_INTERVAL_MS_MIN = 2 * 60 * 1000; // 2m
static REFRESH_DEVICES_POLL_INTERVAL_MS_MAX = 60 * 60 * 1000; // 60m
Service;
Characteristic;
accessories = [];
config;
uniFiSmartPower;
initialized = false;
constructor(log, platformConfig, api) {
this.log = log;
this.platformConfig = platformConfig;
this.api = api;
this.Service = this.api.hap.Service;
this.Characteristic = this.api.hap.Characteristic;
this.config = this.platformConfig;
this.uniFiSmartPower = new uniFiSmartPower_1.UniFiSmartPower(log, this.config);
const refreshDevices = async () => {
await this.discoverDevices();
this.initialized = true;
setTimeout(refreshDevices, this.refreshDevicesPollIntervalMs);
};
this.api.on("didFinishLaunching" /* APIEvent.DID_FINISH_LAUNCHING */, refreshDevices);
}
configureAccessory(accessory) {
this.log.info('Loading accessory from cache:', accessory.displayName);
this.accessories.push(accessory);
}
async discoverDevices() {
this.uniFiSmartPower.reset();
const uuids = new Set();
let isOutletEnabled = () => true;
let isSwitchPortEnabled = () => true;
let isRpsPortEnabled = () => true;
if (this.config.controlSwitch?.create &&
(this.config.controlSwitch.guardOutlets || this.config.controlSwitch.guardSwitchPorts)) {
const { name: switchName = 'UniFi Control Enabled', timeout = 0, guardOutlets = true, guardSwitchPorts = true, guardRpsPorts = true, } = this.config.controlSwitch;
const uuid = this.api.hap.uuid.generate('');
const existingAccessory = this.accessories.find((accessory) => accessory.UUID === uuid);
const accessory = existingAccessory ?? new this.api.platformAccessory(switchName, uuid);
uuids.add(uuid);
// Update the accessory context with the outlet.
accessory.context = {
switchName,
manufacturer: 'Ubiquiti',
model: 'Control Switch',
serialNumber: '1.0.0',
timeout,
guardOutlets,
guardSwitchPorts,
guardRpsPorts,
};
if (existingAccessory) {
this.log.log(this.initialized ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */, 'Restoring existing accessory from cache:', accessory.displayName);
this.api.updatePlatformAccessories([accessory]);
}
else {
this.log.info('Adding new accessory:', switchName);
this.api.registerPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [accessory]);
this.accessories.push(accessory);
}
const control = new platformAccessoryControlSwitch_1.UniFiControlSwitchPlatformAccessory(this, accessory);
if (this.config.controlSwitch.guardOutlets) {
isOutletEnabled = () => control.isEnabled();
}
if (this.config.controlSwitch.guardSwitchPorts) {
isSwitchPortEnabled = () => control.isEnabled();
}
if (this.config.controlSwitch.guardRpsPorts) {
isRpsPortEnabled = () => control.isEnabled();
}
}
let sites;
try {
sites = await this.uniFiSmartPower.getSites();
if (this.config.logApiResponses) {
this.log.info('SITES: ', JSON.stringify(sites, null, ' '));
}
}
catch (error) {
this.log.error('Failed to get sites from UniFi; verify host, port, username, and password are correct: ', error.stack || error);
return;
}
for (const site of sites) {
if (Array.isArray(this.config.excludeSites) && this.config.excludeSites.includes(site.id)) {
continue;
}
if (Array.isArray(this.config.includeSites) && !this.config.includeSites.includes(site.id)) {
continue;
}
this.log.log(this.initialized ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */, `Site [${site.id}]: ${site.name}`);
let deviceStatuses;
try {
deviceStatuses = await this.uniFiSmartPower.getDeviceStatuses(site.id);
if (this.config.logApiResponses) {
this.log.info('SITE: %S DEVICES:', site.id, JSON.stringify(deviceStatuses, null, ' '));
}
}
catch (error) {
this.log.error('Failed to get status from UniFi; verify host, port, username, and password are correct: ', error.stack || error);
return;
}
for (const deviceStatus of deviceStatuses) {
if (Array.isArray(this.config.excludeDevices) &&
this.config.excludeDevices.includes(deviceStatus.device.serialNumber)) {
continue;
}
if (Array.isArray(this.config.includeDevices) &&
!this.config.includeDevices.includes(deviceStatus.device.serialNumber)) {
continue;
}
const uuid = this.api.hap.uuid.generate(deviceStatus.device.serialNumber);
const existingAccessory = this.accessories.find((accessory) => accessory.UUID === uuid);
const accessory = existingAccessory ?? new this.api.platformAccessory(deviceStatus.device.name, uuid);
// Update the accessory context with the general info.
accessory.context = {
device: deviceStatus.device,
};
// Monkeypatch accessory methods for getting services in order to identify orphaned services
const discoveredServices = [];
const patch = (methodName) => {
const original = accessory[methodName].bind(accessory);
accessory[methodName] = (...args) => {
const service = original(...args);
if (service) {
discoveredServices.push(service);
}
return service;
};
return () => {
accessory[methodName] = original;
};
};
const patches = [patch('getService'), patch('addService'), patch('getServiceById')];
accessory
.getService(this.Service.AccessoryInformation)
.setCharacteristic(this.Characteristic.Name, deviceStatus.device.name)
.setCharacteristic(this.Characteristic.Manufacturer, 'Ubiquiti')
.setCharacteristic(this.Characteristic.Model, deviceStatus.device.model)
.setCharacteristic(this.Characteristic.SerialNumber, deviceStatus.device.serialNumber)
.setCharacteristic(this.Characteristic.FirmwareRevision, deviceStatus.device.version);
let hasAnyAccessories = false;
const logMessages = [
`Device [${deviceStatus.device.serialNumber}]: ${deviceStatus.device.name}`,
];
for (const outlet of deviceStatus.outlets) {
// Use the name of the device if there is only a single outlet.
if (deviceStatus.outlets.length === 1) {
outlet.name = deviceStatus.device.name;
}
const accessoryId = `${deviceStatus.device.serialNumber}.${outlet.index}`;
if (Array.isArray(this.config.excludeOutlets) &&
this.config.excludeOutlets.includes(accessoryId)) {
continue;
}
if (Array.isArray(this.config.includeOutlets) &&
!this.config.includeOutlets.includes(accessoryId)) {
continue;
}
logMessages.push(`Outlet [${deviceStatus.device.serialNumber}.${outlet.index}]: ${deviceStatus.device.name} > ${outlet.name}`);
hasAnyAccessories = true;
new platformAccessory_1.UniFiSmartPowerOutletPlatformAccessory(this, accessory, outlet, isOutletEnabled);
}
for (const port of deviceStatus.ports) {
if (!port.active && !this.config.includeInactivePorts) {
continue;
}
// Use the name of the device if there is only a single port.
if (deviceStatus.ports.length === 1) {
port.name = `${deviceStatus.device.name} ${port.name}`;
}
const accessoryId = `${deviceStatus.device.serialNumber}.${port.index}`;
if (Array.isArray(this.config.excludePorts) &&
this.config.excludePorts.includes(accessoryId)) {
continue;
}
if (Array.isArray(this.config.includePorts) &&
!this.config.includePorts.includes(accessoryId)) {
continue;
}
logMessages.push(`Port [${deviceStatus.device.serialNumber}.${port.index}]: ${deviceStatus.device.name} > ${port.name}`);
hasAnyAccessories = true;
new platformAccessory_1.UniFiSwitchPortPlatformAccessory(this, accessory, port, isSwitchPortEnabled);
}
for (const rpsPort of deviceStatus.rpsPorts) {
if (!rpsPort.active && !this.config.includeInactiveRpsPorts) {
continue;
}
// Use the name of the device if there is only a single port.
if (deviceStatus.ports.length === 1) {
rpsPort.name = `${deviceStatus.device.name} ${rpsPort.name}`;
}
const accessoryId = `${deviceStatus.device.serialNumber}.${rpsPort.index}`;
if (Array.isArray(this.config.excludeRpsPorts) &&
this.config.excludeRpsPorts.includes(accessoryId)) {
continue;
}
if (Array.isArray(this.config.includeRpsPorts) &&
!this.config.includeRpsPorts.includes(accessoryId)) {
continue;
}
logMessages.push(`RPS Port [${deviceStatus.device.serialNumber}.${rpsPort.index}]: ${deviceStatus.device.name} > ${rpsPort.name}`);
hasAnyAccessories = true;
new platformAccessory_1.UniFiRpsPortPlatformAccessory(this, accessory, rpsPort, isRpsPortEnabled);
}
patches.forEach((unpatch) => unpatch());
// Remove any cached services that were orphaned.
accessory.services
.filter((service) => !discoveredServices.some((s) => Object.is(s, service)))
.forEach((service) => {
this.log.info('Removing orphaned service from cache:', service.displayName);
accessory.removeService(service);
});
if (hasAnyAccessories && existingAccessory) {
this.log.log(this.initialized ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */, 'Restoring existing accessory from cache:', accessory.displayName);
logMessages.forEach((m) => this.log.log(this.initialized ? "debug" /* LogLevel.DEBUG */ : "info" /* LogLevel.INFO */, m));
this.api.updatePlatformAccessories([accessory]);
uuids.add(uuid);
}
else if (hasAnyAccessories) {
this.log.info('Adding new accessory:', accessory.displayName);
logMessages.forEach((m) => this.log.info(m));
this.api.registerPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, [accessory]);
uuids.add(uuid);
this.accessories.push(accessory);
}
}
}
const orphanedAccessories = this.accessories.filter((accessory) => !uuids.has(accessory.UUID));
if (orphanedAccessories.length > 0) {
this.log.info('Removing orphaned accessories from cache: ', orphanedAccessories.map(({ displayName }) => displayName).join(', '));
this.api.unregisterPlatformAccessories(settings_1.PLUGIN_NAME, settings_1.PLATFORM_NAME, orphanedAccessories);
for (const orphanedAccessory of orphanedAccessories) {
this.accessories.splice(this.accessories.findIndex((accessory) => accessory.UUID === orphanedAccessory.UUID), 1);
}
}
}
get refreshDevicesPollIntervalMs() {
return Math.max(UniFiSmartPowerHomebridgePlatform.REFRESH_DEVICES_POLL_INTERVAL_MS_MIN, Math.min(UniFiSmartPowerHomebridgePlatform.REFRESH_DEVICES_POLL_INTERVAL_MS_MAX, (this.config.refreshDevicesPollInterval ?? 0) * 1000 ||
UniFiSmartPowerHomebridgePlatform.REFRESH_DEVICES_POLL_INTERVAL_MS_DEFAULT));
}
}
exports.UniFiSmartPowerHomebridgePlatform = UniFiSmartPowerHomebridgePlatform;
//# sourceMappingURL=platform.js.map