homebridge-poolcontroller-clocklear
Version:
A (semi) private Homebridge plugin for my custom Pool Controller app
149 lines • 6.16 kB
JavaScript
import { PLATFORM_NAME, PLUGIN_NAME } from './settings.js';
import { PoolControllerAccessory } from './platformAccessory.js';
import { PoolControllerClient } from './poolControllerClient.js';
/**
* Homebridge platform: one Pool Controller accessory with multiple services.
*/
export class PoolControllerHomebridgePlatform {
log;
api;
Service;
Characteristic;
accessories = [];
client;
config;
controllerAccessory;
pollTimer;
constructor(log, config, api) {
this.log = log;
this.api = api;
this.Service = api.hap.Service;
this.Characteristic = api.hap.Characteristic;
this.config = config;
if (!config.url) {
throw new Error('Missing required config parameter: url');
}
if (!config.apiKey) {
throw new Error('Missing required config parameter: apiKey');
}
if (!config.relays || config.relays.length === 0) {
throw new Error('Missing required config parameter: relays (select at least one)');
}
this.client = new PoolControllerClient(config.url, config.apiKey);
this.log.debug('Finished initializing platform:', this.config.name || 'Pool Controller');
this.api.on('didFinishLaunching', () => {
this.discoverDevices()
.then(() => this.startPoller())
.catch((error) => this.log.error('Failed to discover pool controller devices:', error));
});
this.api.on('shutdown', () => {
if (this.pollTimer) {
clearInterval(this.pollTimer);
this.pollTimer = undefined;
}
});
}
configureAccessory(accessory) {
this.log.info('Loading accessory from cache:', accessory.displayName);
this.accessories.push(accessory);
}
controllerUUID() {
return this.api.hap.uuid.generate(`${this.config.url}::poolcontroller`);
}
legacyRelayUUID(relay) {
return this.api.hap.uuid.generate(`${this.config.url}::${relay}`);
}
async discoverDevices() {
const relays = await this.client.getRelays();
let schedulerEnabled = true;
if (this.config.exposeScheduler !== false) {
try {
const schedules = await this.client.getSchedules();
schedulerEnabled = schedules.schedulerEnabled;
}
catch (error) {
this.log.warn('Could not load scheduler state; defaulting to enabled:', error);
}
}
const relayIds = this.config.relays
.map((r) => parseInt(r, 10))
.filter((id) => {
const found = relays.some((relay) => relay.relay === id);
if (!found) {
this.log.warn(`Relay ${id} not found in pool controller, skipping`);
}
return found;
});
if (relayIds.length === 0) {
throw new Error('No configured relays were found on the pool controller');
}
this.unregisterLegacyAccessories();
const snapshot = { relays, schedulerEnabled };
const uuid = this.controllerUUID();
const displayName = this.config.name || 'Pool Controller';
const existing = this.accessories.find((accessory) => accessory.UUID === uuid);
if (existing) {
this.log.info('Restoring existing accessory from cache:', existing.displayName);
existing.displayName = displayName;
this.api.updatePlatformAccessories([existing]);
this.controllerAccessory = new PoolControllerAccessory(this, existing, snapshot, relayIds);
}
else {
this.log.info('Adding new accessory:', displayName);
const accessory = new this.api.platformAccessory(displayName, uuid);
accessory.context.isPoolController = true;
this.controllerAccessory = new PoolControllerAccessory(this, accessory, snapshot, relayIds);
this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
this.accessories.push(accessory);
}
}
unregisterLegacyAccessories() {
const controllerUUID = this.controllerUUID();
const toRemove = this.accessories.filter((accessory) => accessory.UUID !== controllerUUID
&& this.config.relays.some((relay) => accessory.UUID === this.legacyRelayUUID(relay)));
if (toRemove.length === 0) {
return;
}
for (const accessory of toRemove) {
this.log.info('Removing legacy per-relay accessory from cache:', accessory.displayName);
}
this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, toRemove);
for (const accessory of toRemove) {
const idx = this.accessories.indexOf(accessory);
if (idx >= 0) {
this.accessories.splice(idx, 1);
}
}
}
pollIntervalMs() {
const seconds = this.config.pollIntervalSeconds ?? 30;
return Math.max(5, Math.min(300, seconds)) * 1000;
}
startPoller() {
const interval = this.pollIntervalMs();
this.log.info(`Starting poolcontroller poller every ${interval / 1000}s`);
const tick = async () => {
if (!this.controllerAccessory) {
return;
}
try {
const relays = await this.client.getRelays();
let schedulerEnabled = true;
if (this.config.exposeScheduler !== false) {
const schedules = await this.client.getSchedules();
schedulerEnabled = schedules.schedulerEnabled;
}
this.controllerAccessory.applySnapshot({ relays, schedulerEnabled });
}
catch (error) {
this.log.error('Poll failed; keeping last-known state:', error);
}
};
// Immediate refresh after launch, then interval.
void tick();
this.pollTimer = setInterval(() => {
void tick();
}, interval);
}
}
//# sourceMappingURL=platform.js.map