homebridge-poolcontroller-clocklear
Version:
A (semi) private Homebridge plugin for my custom Pool Controller app
160 lines • 7.23 kB
JavaScript
/**
* Single HomeKit accessory exposing relay switches, optional scheduler switch,
* and optional load-fault contact sensors as multiple services.
*/
export class PoolControllerAccessory {
platform;
accessory;
relayIds;
relayServices = new Map();
loadServices = new Map();
schedulerService;
snapshot;
constructor(platform, accessory, initialSnapshot, relayIds) {
this.platform = platform;
this.accessory = accessory;
this.relayIds = relayIds;
this.snapshot = initialSnapshot;
this.accessory.getService(this.platform.Service.AccessoryInformation)
.setCharacteristic(this.platform.Characteristic.Manufacturer, 'LocklearTech')
.setCharacteristic(this.platform.Characteristic.Model, 'Pool Controller')
.setCharacteristic(this.platform.Characteristic.SerialNumber, 'SN-POOL-001');
for (const relayId of this.relayIds) {
this.ensureRelaySwitch(relayId);
}
if (this.platform.config.exposeScheduler !== false) {
this.ensureSchedulerSwitch();
}
else {
this.removeServiceBySubtype(this.platform.Service.Switch, 'scheduler');
}
this.syncLoadSensors();
this.applySnapshot(initialSnapshot);
}
applySnapshot(snapshot) {
this.snapshot = snapshot;
for (const relayId of this.relayIds) {
const relay = snapshot.relays.find((r) => r.relay === relayId);
const service = this.relayServices.get(relayId);
if (!relay || !service) {
continue;
}
service.setCharacteristic(this.platform.Characteristic.Name, relay.name);
service.updateCharacteristic(this.platform.Characteristic.On, relay.state === 1);
}
if (this.schedulerService) {
this.schedulerService.updateCharacteristic(this.platform.Characteristic.On, snapshot.schedulerEnabled);
}
this.syncLoadSensors();
for (const [relayId, service] of this.loadServices) {
const relay = snapshot.relays.find((r) => r.relay === relayId);
if (!relay) {
continue;
}
const fault = relay.health === 'load_absent';
service.updateCharacteristic(this.platform.Characteristic.ContactSensorState, fault
? this.platform.Characteristic.ContactSensorState.CONTACT_NOT_DETECTED
: this.platform.Characteristic.ContactSensorState.CONTACT_DETECTED);
}
}
ensureRelaySwitch(relayId) {
const subtype = `relay-${relayId}`;
const relay = this.snapshot.relays.find((r) => r.relay === relayId);
const name = relay?.name || `Relay ${relayId}`;
let service = this.accessory.getServiceById(this.platform.Service.Switch, subtype);
if (!service) {
service = this.accessory.addService(this.platform.Service.Switch, name, subtype);
}
service.setCharacteristic(this.platform.Characteristic.Name, name);
service.getCharacteristic(this.platform.Characteristic.On)
.onSet(async (value) => this.setRelayOn(relayId, value))
.onGet(() => this.getRelayOn(relayId));
this.relayServices.set(relayId, service);
}
ensureSchedulerSwitch() {
const subtype = 'scheduler';
let service = this.accessory.getServiceById(this.platform.Service.Switch, subtype);
if (!service) {
service = this.accessory.addService(this.platform.Service.Switch, 'Scheduler', subtype);
}
service.setCharacteristic(this.platform.Characteristic.Name, 'Scheduler');
service.getCharacteristic(this.platform.Characteristic.On)
.onSet(async (value) => this.setSchedulerOn(value))
.onGet(() => this.snapshot.schedulerEnabled);
this.schedulerService = service;
}
syncLoadSensors() {
if (!this.platform.config.exposeLoadSensors) {
for (const relayId of [...this.loadServices.keys()]) {
this.removeServiceBySubtype(this.platform.Service.ContactSensor, `load-${relayId}`);
this.loadServices.delete(relayId);
}
return;
}
for (const relayId of this.relayIds) {
const relay = this.snapshot.relays.find((r) => r.relay === relayId);
const hasSensing = relay !== undefined && (relay.health !== undefined || relay.loadPresent !== undefined);
const subtype = `load-${relayId}`;
if (!hasSensing) {
if (this.loadServices.has(relayId)) {
this.removeServiceBySubtype(this.platform.Service.ContactSensor, subtype);
this.loadServices.delete(relayId);
}
continue;
}
let service = this.loadServices.get(relayId)
|| this.accessory.getServiceById(this.platform.Service.ContactSensor, subtype);
if (!service) {
const name = `${relay.name} Load`;
service = this.accessory.addService(this.platform.Service.ContactSensor, name, subtype);
}
service.setCharacteristic(this.platform.Characteristic.Name, `${relay.name} Load`);
this.loadServices.set(relayId, service);
}
}
removeServiceBySubtype(serviceType, subtype) {
const existing = this.accessory.getServiceById(serviceType, subtype);
if (existing) {
this.accessory.removeService(existing);
}
}
getRelayOn(relayId) {
const relay = this.snapshot.relays.find((r) => r.relay === relayId);
return relay?.state === 1;
}
async setRelayOn(relayId, value) {
const on = Boolean(value);
try {
const relays = await this.platform.client.setRelayState(relayId, on);
this.applySnapshot({
relays,
schedulerEnabled: this.snapshot.schedulerEnabled,
});
this.platform.log.debug(`Relay ${relayId} set On ->`, on);
}
catch (error) {
this.platform.log.error(`Failed to set relay ${relayId}:`, error);
throw new this.platform.api.hap.HapStatusError(-70402 /* this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE */);
}
}
async setSchedulerOn(value) {
const desired = Boolean(value);
if (desired === this.snapshot.schedulerEnabled) {
return;
}
try {
await this.platform.client.toggleScheduler();
const schedules = await this.platform.client.getSchedules();
this.applySnapshot({
relays: this.snapshot.relays,
schedulerEnabled: schedules.schedulerEnabled,
});
this.platform.log.debug('Scheduler set On ->', schedules.schedulerEnabled);
}
catch (error) {
this.platform.log.error('Failed to toggle scheduler:', error);
throw new this.platform.api.hap.HapStatusError(-70402 /* this.platform.api.hap.HAPStatus.SERVICE_COMMUNICATION_FAILURE */);
}
}
}
//# sourceMappingURL=platformAccessory.js.map