UNPKG

@homebridge-plugins/homebridge-rainbird

Version:

The Rainbird plugin allows you to access your Rainbird device(s) from HomeKit.

327 lines 17.1 kB
import { fromEvent, interval, Subject } from 'rxjs'; import { debounceTime, skipWhile, tap } from 'rxjs/operators'; import { DeviceBase } from './DeviceBase.js'; /** * Platform Accessory * An instance of this class is created for each accessory your platform registers * Each accessory may expose multiple services of different service types. */ export class IrrigationSystem extends DeviceBase { platform; // Service irrigation; valves = new Map(); // Irrigation System Updates irrigationSystemUpdateInProgress; doIrrigationSystemUpdate; constructor(platform, accessory, device, rainbird) { super(platform, accessory, device, rainbird); this.platform = platform; // this is subject we use to track when we need to send changes to Rainbird Client this.doIrrigationSystemUpdate = new Subject(); this.irrigationSystemUpdateInProgress = false; if (accessory.context.configured === undefined) { accessory.context.configured = {}; } if (accessory.context.duration === undefined) { accessory.context.duration = {}; } // Irrigation Service this.debugLog('Configure Irrigation Service'); this.irrigation = { service: this.accessory.getService(this.hap.Service.IrrigationSystem) ?? this.accessory.addService(this.hap.Service.IrrigationSystem), Active: this.hap.Characteristic.Active.ACTIVE, InUse: this.hap.Characteristic.InUse.NOT_IN_USE, }; // Add Irrigation Service's Characteristics this.irrigation.service .setCharacteristic(this.hap.Characteristic.Name, accessory.displayName) .setCharacteristic(this.hap.Characteristic.Active, this.irrigation.Active) .setCharacteristic(this.hap.Characteristic.InUse, this.irrigation.InUse) .setCharacteristic(this.hap.Characteristic.ProgramMode, this.hap.Characteristic.ProgramMode.NO_PROGRAM_SCHEDULED) .setCharacteristic(this.hap.Characteristic.RemainingDuration, 0) .setCharacteristic(this.hap.Characteristic.StatusFault, this.hap.Characteristic.StatusFault.NO_FAULT); // Create handlers for required characteristics this.irrigation.service .getCharacteristic(this.hap.Characteristic.Active) .onGet(() => { this.rainbird.refreshStatus(); return this.irrigation.Active; }) .onSet(this.setActive.bind(this)); this.irrigation.service .getCharacteristic(this.hap.Characteristic.ProgramMode) .onGet(() => { return this.irrigation.service.getCharacteristic(this.hap.Characteristic.ProgramMode).value; }); this.irrigation.service .getCharacteristic(this.hap.Characteristic.InUse) .onGet(() => { this.rainbird.refreshStatus(); return this.irrigation.InUse; }); this.irrigation.service .getCharacteristic(this.hap.Characteristic.StatusFault) .onGet(() => { return this.irrigation.service.getCharacteristic(this.hap.Characteristic.StatusFault).value; }); const minValueRemainingDuration = device.minValueRemainingDuration ?? 0; const maxValueRemainingDuration = (device.maxValueRemainingDuration ?? 3600) * rainbird.zones.length; if (maxValueRemainingDuration > 4294967295) { this.warnLog('maxValueRemainingDuration cannot be above \x1B[1m\x1B[3m4294967295\x1B[0m, Setting to \x1B[1m\x1B[3m4294967295\x1B[0m.'); } this.irrigation.service .getCharacteristic(this.hap.Characteristic.RemainingDuration) .setProps({ minValue: device.minValueRemainingDuration, maxValue: Math.min(device.maxValueRemainingDuration * rainbird.zones.length, 4294967295), validValueRanges: [minValueRemainingDuration, Math.min(maxValueRemainingDuration)], }) .onGet(() => { this.rainbird.refreshStatus(); return this.rainbird.remainingDuration(); }); // Valves for zones for (const zone of rainbird.zones) { const name = `Zone ${zone}`; this.debugLog(`Load Valve Service for ${name}`); if (this.accessory.context.configured[zone] === undefined) { this.accessory.context.configured[zone] = this.hap.Characteristic.IsConfigured.CONFIGURED; } if (this.accessory.context.duration[zone] === undefined) { this.accessory.context.duration[zone] = 300; } this.valves.set(zone, { service: this.accessory.getService(name) ?? this.accessory.addService(this.hap.Service.Valve, name, `${zone}`), Active: this.hap.Characteristic.Active.INACTIVE, InUse: this.hap.Characteristic.InUse.NOT_IN_USE, }); // Add Valve Service's Characteristics this.valves .get(zone) .service .setCharacteristic(this.hap.Characteristic.Name, name) .setCharacteristic(this.hap.Characteristic.Active, this.valves.get(zone).Active) .setCharacteristic(this.hap.Characteristic.InUse, this.valves.get(zone).InUse) .setCharacteristic(this.hap.Characteristic.ValveType, this.hap.Characteristic.ValveType.IRRIGATION) .setCharacteristic(this.hap.Characteristic.SetDuration, this.accessory.context.duration[zone]) .setCharacteristic(this.hap.Characteristic.RemainingDuration, 0) .setCharacteristic(this.hap.Characteristic.IsConfigured, this.accessory.context.configured[zone]) .setCharacteristic(this.hap.Characteristic.ServiceLabelIndex, zone) .setCharacteristic(this.hap.Characteristic.StatusFault, this.hap.Characteristic.StatusFault.NO_FAULT); this.irrigation.service.addLinkedService(this.valves.get(zone).service); // Create handlers for required Valve characteristics this.debugLog(`Configure Characteristics for ${name}`); this.valves .get(zone) .service .getCharacteristic(this.hap.Characteristic.Active) .onGet(() => { this.rainbird.refreshStatus(); return this.valves.get(zone).Active; }) .onSet(this.setValveActive.bind(this, zone)); this.valves .get(zone) .service .getCharacteristic(this.hap.Characteristic.InUse) .onGet(() => { this.rainbird.refreshStatus(); return this.valves.get(zone).InUse; }); this.valves .get(zone) .service .getCharacteristic(this.hap.Characteristic.IsConfigured) .onGet(() => { return this.valves.get(zone).service.getCharacteristic(this.hap.Characteristic.IsConfigured).value; }) .onSet(this.setValveIsConfigured.bind(this, zone)); this.valves .get(zone) .service .getCharacteristic(this.hap.Characteristic.StatusFault) .onGet(() => { return this.valves.get(zone).service.getCharacteristic(this.hap.Characteristic.StatusFault).value; }); this.valves .get(zone) .service .getCharacteristic(this.hap.Characteristic.ValveType) .onGet(() => { return this.valves.get(zone).service.getCharacteristic(this.hap.Characteristic.ValveType).value; }); this.valves .get(zone) .service .getCharacteristic(this.hap.Characteristic.SetDuration) .onGet(() => { return Number(this.accessory.context.duration[zone]); }) .onSet(this.setValveSetDuration.bind(this, zone)); this.valves .get(zone) .service .getCharacteristic(this.hap.Characteristic.RemainingDuration) .setProps({ minValue: device.minValueRemainingDuration, maxValue: device.maxValueRemainingDuration, }) .onGet(() => { this.rainbird.refreshStatus(); return this.rainbird.remainingDuration(zone); }); } // Initial Device Parse this.parseStatus(); this.updateHomeKitCharacteristics(); // Device Parse when status event emitted fromEvent(rainbird, 'status').subscribe({ next: () => { this.parseStatus(); this.updateHomeKitCharacteristics(); }, }); // Start an update interval interval(this.platform.config.options.refreshRate * 1000) .pipe(skipWhile(() => this.irrigationSystemUpdateInProgress)) .subscribe(() => { this.rainbird.refreshStatus(); }); this.doIrrigationSystemUpdate .pipe(tap(() => { this.irrigationSystemUpdateInProgress = true; }), debounceTime(this.platform.config.options.pushRate * 1000)) .subscribe(async (zone) => { try { await this.pushChanges(zone); } catch (e) { this.debugLog(`${this.constructor.name} ${this.accessory.displayName} - ${JSON.stringify(e.messsage)}`); if (this.deviceLogging.includes('debug')) { this.debugLog(`${this.constructor.name} ${this.accessory.displayName} - ${JSON.stringify(e)}`); } } this.irrigationSystemUpdateInProgress = false; }); } /** * Parse the device status from the RainbirdClient */ parseStatus() { // Irrigation System this.irrigation.Active = this.rainbird.isActive() ? this.hap.Characteristic.Active.ACTIVE : this.hap.Characteristic.Active.INACTIVE; this.irrigation.InUse = this.rainbird.isInUse() ? this.hap.Characteristic.InUse.IN_USE : this.hap.Characteristic.InUse.NOT_IN_USE; this.debugLog(`${this.constructor.name}: ${this.accessory.displayName}, Active: ${this.irrigation.Active}, InUse: ${this.irrigation.InUse}`); // Valves for (const [zone, valve] of this.valves.entries()) { valve.Active = this.rainbird.isActive(zone) ? this.hap.Characteristic.Active.ACTIVE : this.hap.Characteristic.Active.INACTIVE; valve.InUse = this.rainbird.isInUse(zone) ? this.hap.Characteristic.InUse.IN_USE : this.hap.Characteristic.InUse.NOT_IN_USE; this.debugLog(`${this.constructor.name} Valve: ${zone}, Active: ${valve.Active}, InUse: ${valve.InUse}`); } } /** * Updates the status for each of the HomeKit Characteristics */ updateHomeKitCharacteristics() { // Irrigation System if (this.irrigation.Active === undefined) { this.debugLog(`${this.constructor.name} ${this.accessory.displayName} Active: ${this.irrigation.Active}`); } else { this.irrigation.service.updateCharacteristic(this.hap.Characteristic.Active, this.irrigation.Active); this.debugLog(`${this.constructor.name} ${this.accessory.displayName} updateCharacteristic Active: ${this.irrigation.Active}`); } if (this.irrigation.InUse === undefined) { this.debugLog(`${this.constructor.name} ${this.accessory.displayName} InUse: ${this.irrigation.InUse}`); } else { this.irrigation.service.updateCharacteristic(this.hap.Characteristic.InUse, this.irrigation.InUse); this.debugLog(`${this.constructor.name} ${this.accessory.displayName} updateCharacteristic InUse: ${this.irrigation.InUse}`); } if (this.rainbird.remainingDuration() === undefined) { this.debugLog(`${this.constructor.name} ${this.accessory.displayName} RemainingDuration: ${this.rainbird.remainingDuration()}`); } else { this.irrigation.service.updateCharacteristic(this.hap.Characteristic.RemainingDuration, this.rainbird.remainingDuration()); this.debugLog(`${this.constructor.name} ${this.accessory.displayName} updateCharacteristic RemainingDuration: ${this.rainbird.remainingDuration()}`); } // Valves for (const [zone, valve] of this.valves.entries()) { if (valve.Active === undefined) { this.debugLog(`${this.constructor.name} Valve ${zone} Active: ${valve.Active}`); } else { valve.service.updateCharacteristic(this.hap.Characteristic.Active, valve.Active); this.debugLog(`${this.constructor.name} Valve ${zone} updateCharacteristic Active: ${valve.Active}`); } if (valve.InUse === undefined) { this.debugLog(`${this.constructor.name} Valve ${zone} InUse: ${valve.InUse}`); } else { valve.service.updateCharacteristic(this.hap.Characteristic.InUse, valve.InUse); this.debugLog(`${this.constructor.name} Valve ${zone} updateCharacteristic InUse: ${valve.InUse}`); } if (this.accessory.context.duration[zone] === undefined) { this.debugLog(`${this.constructor.name} Valve ${zone} SetDuration: ${this.accessory.context.duration[zone]}`); } else { valve.service.updateCharacteristic(this.hap.Characteristic.SetDuration, this.accessory.context.duration[zone]); this.debugLog(`${this.constructor.name} Valve ${zone} updateCharacteristic SetDuration: ${this.accessory.context.duration[zone]}`); } if (this.accessory.context.configured[zone] === undefined) { this.debugLog(`${this.constructor.name} Valve ${zone} IsConfigured: ${this.accessory.context.configured[zone]}`); } else { valve.service.updateCharacteristic(this.hap.Characteristic.IsConfigured, this.accessory.context.configured[zone]); this.debugLog(`${this.constructor.name} Valve ${zone} updateCharacteristic IsConfigured: ${this.accessory.context.configured[zone]}`); } if (this.rainbird.remainingDuration(zone) === undefined) { this.debugLog(`${this.constructor.name} Valve ${zone} RemainingDuration: ${this.rainbird.remainingDuration(zone)}`); } else { valve.service.updateCharacteristic(this.hap.Characteristic.RemainingDuration, this.rainbird.remainingDuration(zone)); this.debugLog(`${this.constructor.name} Valve ${zone} updateCharacteristic RemainingDuration: ${this.rainbird.remainingDuration(zone)}`); } } } /** * Pushes the requested changes to the RainbirdClient */ async pushChanges(zone) { if (this.valves.get(zone).Active === this.hap.Characteristic.Active.ACTIVE) { const durationSeconds = Number(this.accessory.context.duration[zone] ?? 300); if (this.platform.supportsStackRunZone(this.accessory.context.deviceID)) { const durationMinutes = Math.max(1, Math.min(Math.ceil(durationSeconds / 60), 255)); await this.rainbird.stackRunZone(0, zone, durationMinutes); } else { this.rainbird.activateZone(zone, durationSeconds); } } else { await this.rainbird.deactivateZone(zone); } this.debugLog(`${this.constructor.name} ${this.accessory.displayName}, pushChanges: [Valve: ${zone},` + ` Active: ${this.valves.get(zone).Active}, SetDuration: ${this.accessory.context.duration[zone]}]`); } setActive(value) { this.debugLog(`${this.constructor.name} ${this.accessory.displayName}, Set Active: ${value}`); this.irrigation.Active = value; } setValveActive(zone, value) { this.debugLog(`${this.constructor.name} ${this.accessory.displayName}, Valve: ${zone}, Set Active: ${value}`); this.valves.get(zone).Active = value; this.doIrrigationSystemUpdate.next(zone); } setValveIsConfigured(zone, value) { this.debugLog(`${this.constructor.name} ${this.accessory.displayName}, Valve: ${zone}, Set IsConfigured: ${value}`); this.accessory.context.configured[zone] = value; this.rainbird.enableZone(zone, value === this.hap.Characteristic.IsConfigured.CONFIGURED); } setValveSetDuration(zone, value) { this.debugLog(`${this.constructor.name} ${this.accessory.displayName}, Valve: ${zone}, Set SetDuration: ${value}`); this.accessory.context.duration[zone] = value; } } //# sourceMappingURL=IrrigationSystem.js.map