UNPKG

homebridge-platform-orbit

Version:

Orbit Irrigation System platform plugin for [HomeBridge](https://github.com/nfarina/homebridge).

412 lines (334 loc) 19.4 kB
import { API, HAP, PlatformAccessory, PlatformConfig, Logger, Service } from "homebridge"; const PluginName = 'homebridge-platform-orbit'; const PlatformName = 'orbit'; let hap: HAP; export = (api: API) => { hap = api.hap; api.registerPlatform(PluginName, PlatformName, PlatformOrbit); }; import { OrbitAPI, OrbitDevice } from './orbitapi'; class PlatformOrbit { private readonly email: string = ""; private readonly password: string = ""; private accessories: { [uuid: string]: PlatformAccessory } = {}; constructor(public readonly log: Logger, public readonly config: PlatformConfig, public readonly api: API) { if (!config || !config["email"] || !config["password"]) { this.log.error("Platform config incorrect or missing. Check the config.json file."); } else { this.email = config["email"]; this.password = config["password"]; this.log.info('Starting PlatformOrbit using homebridge API', api.version); if (api) { // save the api for use later this.api = api; // if finished loading cache accessories this.api.on("didFinishLaunching", () => { // Load the orbit devices this.loadDevices(); }); } } } loadDevices() { this.log.debug("Loading the devices"); // login to the API and get the token let orbitAPI: OrbitAPI = new OrbitAPI(this.log, this.email, this.password); orbitAPI.login() .then(() => { // get an array of the devices orbitAPI.getDevices() .then((devices: OrbitDevice[]) => { // loop through each device devices.forEach((device: OrbitDevice) => { // Generate irrigation service uuid const uuid: string = hap.uuid.generate(device.id); // Check if device is already loaded from cache if (this.accessories[uuid]) { this.log.info('Configuring cached device', device.name); // Setup Irrigation Accessory and Irrigation Service let irrigationAccessory = this.accessories[uuid]; irrigationAccessory.context.timeEnding = []; this.api.updatePlatformAccessories([irrigationAccessory]); this.configureIrrigationService(irrigationAccessory.getService(hap.Service.IrrigationSystem)!); // Find the valve Services associated with the accessory irrigationAccessory.services.forEach((service: any) => { if (hap.Service.Valve.UUID === service.UUID) { // Configure Valve Service this.configureValveService(irrigationAccessory, service, device); } }); } else { this.log.info('Creating and configuring new device', device.name); // Create Irrigation Accessory and Irrigation Service let irrigationAccessory = new this.api.platformAccessory(device.name, uuid); irrigationAccessory.context.timeEnding = []; let irrigationSystemService = this.createIrrigationService(irrigationAccessory, device); this.configureIrrigationService(irrigationSystemService); // Create and configure Values services and link to Irrigation Service device.zones.forEach((zone: any) => { let valveService = this.createValveService(irrigationAccessory, zone['station'], zone['name']); irrigationSystemService.addLinkedService(valveService); this.configureValveService(irrigationAccessory, valveService, device); }); // Register platform accessory this.log.debug('Registering platform accessory'); this.api.registerPlatformAccessories(PluginName, PlatformName, [irrigationAccessory]); this.accessories[uuid] = irrigationAccessory; } device.openConnection(); device.onMessage(this.processMessage.bind(this)); device.sync(); }); }).catch((error) => { this.log.error('Unable to get devices', error); }); }) .catch((error) => { this.log.error('Unable to get token', error); }); } configureAccessory(accessory: PlatformAccessory) { // Add cached devices to the accessories arrary this.log.info('Loading accessory from cache', accessory.displayName); this.accessories[accessory.UUID] = accessory; } createIrrigationService(irrigationAccessory: PlatformAccessory, device: OrbitDevice): Service { this.log.debug('Create Irrigation service', device.id); // Update AccessoryInformation Service irrigationAccessory.getService(hap.Service.AccessoryInformation)! .setCharacteristic(hap.Characteristic.Name, device.name) .setCharacteristic(hap.Characteristic.Manufacturer, "Orbit") .setCharacteristic(hap.Characteristic.SerialNumber, device.id) .setCharacteristic(hap.Characteristic.Model, device.hardware_version) .setCharacteristic(hap.Characteristic.FirmwareRevision, device.firmware_version); // Add Irrigation System Service let irrigationSystemService = irrigationAccessory.addService(hap.Service.IrrigationSystem, device.name) .setCharacteristic(hap.Characteristic.Name, device.name) .setCharacteristic(hap.Characteristic.Active, hap.Characteristic.Active.ACTIVE) .setCharacteristic(hap.Characteristic.InUse, hap.Characteristic.InUse.NOT_IN_USE) .setCharacteristic(hap.Characteristic.ProgramMode, hap.Characteristic.ProgramMode.NO_PROGRAM_SCHEDULED) .setCharacteristic(hap.Characteristic.RemainingDuration, 0) .setCharacteristic(hap.Characteristic.StatusFault, device.is_connected ? hap.Characteristic.StatusFault.NO_FAULT : hap.Characteristic.StatusFault.GENERAL_FAULT); return irrigationSystemService; } configureIrrigationService(irrigationSystemService: Service) { this.log.debug('Configure Irrigation service', irrigationSystemService.getCharacteristic(hap.Characteristic.Name).value) // Configure Irrigation System Service irrigationSystemService .getCharacteristic(hap.Characteristic.Active) .onGet(() => { this.log.debug("IrrigationSystem", irrigationSystemService.getCharacteristic(hap.Characteristic.Name).value, "Active = ", irrigationSystemService.getCharacteristic(hap.Characteristic.Active).value ? "ACTIVE" : "INACTIVE"); return irrigationSystemService.getCharacteristic(hap.Characteristic.Active).value; }) .onSet((value) => { this.log.info("Set irrigation system ", irrigationSystemService.getCharacteristic(hap.Characteristic.Name).value, " to ", value ? "ACTIVE" : "INACTIVE"); irrigationSystemService.getCharacteristic(hap.Characteristic.Active).updateValue(value); }); irrigationSystemService .getCharacteristic(hap.Characteristic.ProgramMode) .onGet(() => { this.log.debug("IrrigationSystem", irrigationSystemService.getCharacteristic(hap.Characteristic.Name).value, "ProgramMode = ", irrigationSystemService.getCharacteristic(hap.Characteristic.ProgramMode).value); return irrigationSystemService.getCharacteristic(hap.Characteristic.ProgramMode).value; }); irrigationSystemService .getCharacteristic(hap.Characteristic.InUse) .onGet(() => { this.log.debug("IrrigationSystem", irrigationSystemService.getCharacteristic(hap.Characteristic.Name).value, "InUse = ", irrigationSystemService.getCharacteristic(hap.Characteristic.InUse).value ? "IN_USE" : "NOT_IN_USE"); return irrigationSystemService.getCharacteristic(hap.Characteristic.InUse).value; }); irrigationSystemService .getCharacteristic(hap.Characteristic.StatusFault) .onGet(() => { this.log.debug("IrrigationSystem", irrigationSystemService.getCharacteristic(hap.Characteristic.Name).value, "StatusFault = ", irrigationSystemService.getCharacteristic(hap.Characteristic.StatusFault).value ? "GENERAL_FAULT" : "NO_FAULT"); return irrigationSystemService.getCharacteristic(hap.Characteristic.StatusFault).value; }); irrigationSystemService .getCharacteristic(hap.Characteristic.RemainingDuration) .onGet(() => { this.log.debug("IrrigationSystem", irrigationSystemService.getCharacteristic(hap.Characteristic.Name).value, "RemainingDuration = ", irrigationSystemService.getCharacteristic(hap.Characteristic.RemainingDuration).value); return irrigationSystemService.getCharacteristic(hap.Characteristic.RemainingDuration).value; }); } createValveService(irrigationAccessory: PlatformAccessory, station: string, name: string): Service { this.log.debug("Create Valve service " + name + " with station " + station); // Add Valve Service let valve = irrigationAccessory.addService(hap.Service.Valve, name, station); valve .setCharacteristic(hap.Characteristic.Active, hap.Characteristic.Active.INACTIVE) .setCharacteristic(hap.Characteristic.InUse, hap.Characteristic.InUse.NOT_IN_USE) .setCharacteristic(hap.Characteristic.ValveType, hap.Characteristic.ValveType.IRRIGATION) .setCharacteristic(hap.Characteristic.SetDuration, 300) .setCharacteristic(hap.Characteristic.RemainingDuration, 0) .setCharacteristic(hap.Characteristic.IsConfigured, hap.Characteristic.IsConfigured.CONFIGURED) .setCharacteristic(hap.Characteristic.ServiceLabelIndex, station) .setCharacteristic(hap.Characteristic.StatusFault, hap.Characteristic.StatusFault.NO_FAULT) .setCharacteristic(hap.Characteristic.Name, name); return valve } configureValveService(irrigationAccessory: PlatformAccessory, valveService: Service, device: OrbitDevice) { this.log.debug("Configure Valve service", valveService.getCharacteristic(hap.Characteristic.Name).value); // Configure Valve Service valveService .getCharacteristic(hap.Characteristic.Active) .onGet(() => { this.log.debug("Valve", valveService.getCharacteristic(hap.Characteristic.Name).value, "Active = ", valveService.getCharacteristic(hap.Characteristic.Active).value ? "ACTIVE" : "INACTIVE"); return valveService.getCharacteristic(hap.Characteristic.Active).value; }) .onSet((value) => { // Prepare message for API let station = valveService.getCharacteristic(hap.Characteristic.ServiceLabelIndex).value as number; let run_time = valveService.getCharacteristic(hap.Characteristic.SetDuration).value as number / 60; if (value == hap.Characteristic.Active.ACTIVE) { // Turn on the valve this.log.info("Start zone", valveService.getCharacteristic(hap.Characteristic.Name).value, "for", run_time, "mins"); device.startZone(station, run_time); } else { // Turn off the valve this.log.info("Stop zone", valveService.getCharacteristic(hap.Characteristic.Name).value); device.stopZone(); } }); valveService .getCharacteristic(hap.Characteristic.InUse) .onGet(() => { this.log.debug("Valve", valveService.getCharacteristic(hap.Characteristic.Name).value, "InUse = ", valveService.getCharacteristic(hap.Characteristic.InUse).value ? "IN_USE" : "NOT_IN_USE"); return valveService.getCharacteristic(hap.Characteristic.InUse).value; }); valveService .getCharacteristic(hap.Characteristic.IsConfigured) .onGet(() => { this.log.debug("Valve", valveService.getCharacteristic(hap.Characteristic.Name).value, "IsConfigured = ", valveService.getCharacteristic(hap.Characteristic.IsConfigured).value ? "CONFIGURED" : "NOT_CONFIGURED"); return valveService.getCharacteristic(hap.Characteristic.IsConfigured).value; }) .onSet((value) => { this.log.info("Set valve ", valveService.getCharacteristic(hap.Characteristic.Name).value, " isConfigured to ", value); valveService.getCharacteristic(hap.Characteristic.IsConfigured).updateValue(value); }); valveService .getCharacteristic(hap.Characteristic.StatusFault) .onGet(() => { this.log.debug("Valve", valveService.getCharacteristic(hap.Characteristic.Name).value, "StatusFault = ", valveService.getCharacteristic(hap.Characteristic.StatusFault).value ? "GENERAL_FAULT" : "NO_FAULT"); return valveService.getCharacteristic(hap.Characteristic.StatusFault).value; }); valveService .getCharacteristic(hap.Characteristic.ValveType) .onGet(() => { this.log.debug("Valve", valveService.getCharacteristic(hap.Characteristic.Name).value, "ValveType = ", valveService.getCharacteristic(hap.Characteristic.ValveType).value); return valveService.getCharacteristic(hap.Characteristic.ValveType).value; }); valveService .getCharacteristic(hap.Characteristic.SetDuration) .onGet(() => { this.log.debug("Valve", valveService.getCharacteristic(hap.Characteristic.Name).value, "SetDuration = ", valveService.getCharacteristic(hap.Characteristic.SetDuration).value); return valveService.getCharacteristic(hap.Characteristic.SetDuration).value; }) .onSet((value) => { // Update the value run duration this.log.info("Set valve ", valveService.getCharacteristic(hap.Characteristic.Name).value, " SetDuration to ", value); valveService.getCharacteristic(hap.Characteristic.SetDuration).updateValue(value); }); valveService .getCharacteristic(hap.Characteristic.RemainingDuration) .onGet(() => { // Calc remain duration let station = valveService.getCharacteristic(hap.Characteristic.ServiceLabelIndex).value as number; let timeRemaining = Math.max(Math.round((irrigationAccessory.context.timeEnding[station] - Date.now()) / 1000), 0); if (isNaN(timeRemaining)) { timeRemaining = 0; } this.log.debug("Valve", valveService.getCharacteristic(hap.Characteristic.Name).value, "RemainingDuration =", timeRemaining); return timeRemaining; }) irrigationAccessory.context.timeEnding[valveService.getCharacteristic(hap.Characteristic.ServiceLabelIndex).value as number] = 0; } processMessage(message: any) { // Incoming data let jsonData = JSON.parse(message); // Find the irrigation system and process message let irrigationAccessory: PlatformAccessory = this.accessories[hap.uuid.generate(jsonData['device_id'])]; let irrigationSystemService: Service = irrigationAccessory.getService(hap.Service.IrrigationSystem)!; switch (jsonData['event']) { case "watering_in_progress_notification": this.log.debug("Watering_in_progress_notification Station", "device =", irrigationSystemService.getCharacteristic(hap.Characteristic.Name).value, "station =", jsonData['current_station'], "Runtime =", jsonData['run_time']); // Update Irrigation System Service irrigationSystemService.getCharacteristic(hap.Characteristic.InUse).updateValue(hap.Characteristic.InUse.IN_USE); // Find the valve Services irrigationAccessory.services.forEach((service: Service) => { if (hap.Service.Valve.UUID === service.UUID) { // Update Valve Services let station: number = service.getCharacteristic(hap.Characteristic.ServiceLabelIndex).value as number; if (station == jsonData['current_station']) { service.getCharacteristic(hap.Characteristic.Active).updateValue(hap.Characteristic.Active.ACTIVE); service.getCharacteristic(hap.Characteristic.InUse).updateValue(hap.Characteristic.InUse.IN_USE); service.getCharacteristic(hap.Characteristic.RemainingDuration).updateValue(jsonData['run_time'] * 60); irrigationAccessory.context.timeEnding[station] = Date.now() + parseInt(jsonData['run_time']) * 60 * 1000; } else { service.getCharacteristic(hap.Characteristic.Active).updateValue(hap.Characteristic.Active.INACTIVE); service.getCharacteristic(hap.Characteristic.InUse).updateValue(hap.Characteristic.InUse.NOT_IN_USE); service.getCharacteristic(hap.Characteristic.RemainingDuration).updateValue(0); } }; }); break; case "watering_complete": case "device_idle": this.log.debug("Watering_complete or device_idle"); // Update Irrigation System Service irrigationSystemService.getCharacteristic(hap.Characteristic.InUse).updateValue(hap.Characteristic.InUse.NOT_IN_USE); // Find the valve Services irrigationAccessory.services.forEach(function (service: Service) { // Update Valve hap.Service if (hap.Service.Valve.UUID === service.UUID) { service.getCharacteristic(hap.Characteristic.Active).updateValue(hap.Characteristic.Active.INACTIVE); service.getCharacteristic(hap.Characteristic.InUse).updateValue(hap.Characteristic.InUse.NOT_IN_USE); service.getCharacteristic(hap.Characteristic.RemainingDuration).updateValue(0); } }); break; case "change_mode": this.log.debug("change_mode", jsonData['mode']); // Update the ProgramMode switch (jsonData['mode']) { case "off": irrigationSystemService.getCharacteristic(hap.Characteristic.ProgramMode).updateValue(hap.Characteristic.ProgramMode.NO_PROGRAM_SCHEDULED); break; case "auto": irrigationSystemService.getCharacteristic(hap.Characteristic.ProgramMode).updateValue(hap.Characteristic.ProgramMode.PROGRAM_SCHEDULED); break; case "manual": irrigationSystemService.getCharacteristic(hap.Characteristic.ProgramMode).updateValue(hap.Characteristic.ProgramMode.PROGRAM_SCHEDULED_MANUAL_MODE_); break; } break; case "program_changed": this.log.debug("program_change - do nothing"); break; case "rain_delay": this.log.debug("rain_delay - do nothing"); break; case "device_connected": this.log.debug("device_connected"); irrigationSystemService.getCharacteristic(hap.Characteristic.StatusFault).updateValue(hap.Characteristic.StatusFault.NO_FAULT); break; case "device_disconnected": this.log.debug("device_disconnected"); irrigationSystemService.getCharacteristic(hap.Characteristic.StatusFault).updateValue(hap.Characteristic.StatusFault.GENERAL_FAULT); break; case "clear_low_battery": this.log.debug("clear_low_battery - do nothing"); break; default: this.log.debug("Unhandled message received: " + jsonData['event']); break; } } }