UNPKG

homebridge-poolcontroller-clocklear

Version:

A (semi) private Homebridge plugin for my custom Pool Controller app

160 lines 7.23 kB
/** * 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