homebridge-rachio-irrigation
Version:
Rachio Irrigation System platform plugin for [Homebridge](https://github.com/nfarina/homebridge).
363 lines • 21.7 kB
JavaScript
import pkg from 'homebridge-rachio-irrigation/package.json' with { type: 'json' };
import RachioAPI from '../rachioapi.js';
import listen from '../listener.js';
export default class irrigation {
platform;
log;
rachioapi;
listener;
Service;
Characteristic;
constructor(platform, log = platform.log, rachioapi = new RachioAPI(platform), listener = new listen(platform)) {
this.platform = platform;
this.log = log;
this.rachioapi = rachioapi;
this.listener = listener;
this.Service = platform.Service;
this.Characteristic = platform.Characteristic;
}
createIrrigationAccessory(device, deviceState, platformAccessory) {
if (!platformAccessory) {
// Create new Irrigation System Service
this.log.debug(`Create Irrigation device ${device.id} ${device.name}`);
platformAccessory = new this.platform.api.platformAccessory(device.name, device.id);
platformAccessory.addService(this.Service.IrrigationSystem, device.name);
}
else {
// Update Irrigation System Service
this.log.debug(`Update Irrigation device ${device.id} ${device.name}`);
}
// Check if the device is connected
const irrigationSystemService = platformAccessory.getService(this.Service.IrrigationSystem);
if (device.status == 'ONLINE') {
irrigationSystemService.setCharacteristic(this.Characteristic.StatusFault, this.Characteristic.StatusFault.NO_FAULT);
}
else {
irrigationSystemService.setCharacteristic(this.Characteristic.StatusFault, this.Characteristic.StatusFault.GENERAL_FAULT);
}
// Create AccessoryInformation Service
platformAccessory.getService(this.Service.AccessoryInformation)
.setCharacteristic(this.Characteristic.Name, device.name)
.setCharacteristic(this.Characteristic.Manufacturer, 'Rachio')
.setCharacteristic(this.Characteristic.SerialNumber, device.serialNumber)
.setCharacteristic(this.Characteristic.Model, device.model)
.setCharacteristic(this.Characteristic.Identify, true)
.setCharacteristic(this.Characteristic.FirmwareRevision, deviceState.state.firmwareVersion)
.setCharacteristic(this.Characteristic.HardwareRevision, 'Rev-2')
.setCharacteristic(this.Characteristic.SoftwareRevision, pkg.version);
this.configureIrrigationService(device, irrigationSystemService);
return platformAccessory;
}
configureIrrigationService(device, irrigationSystemService) {
this.log.info(`Configure Irrigation system for ${irrigationSystemService.getCharacteristic(this.Characteristic.Name).value}`);
// Configure IrrigationSystem Service
irrigationSystemService
.setCharacteristic(this.Characteristic.Active, this.Characteristic.Active.ACTIVE)
.setCharacteristic(this.Characteristic.InUse, this.Characteristic.InUse.NOT_IN_USE)
.setCharacteristic(this.Characteristic.StatusFault, this.Characteristic.StatusFault.NO_FAULT)
.setCharacteristic(this.Characteristic.RemainingDuration, 0);
// Check if the device is connected
switch (device.status) {
case 'ONLINE':
//irrigationSystemService.setCharacteristic(this.Characteristic.StatusFault, this.Characteristic.StatusFault.NO_FAULT)
break;
case 'OFFLINE':
//irrigationSystemService.setCharacteristic(this.Characteristic.StatusFault, this.Characteristic.StatusFault.GENERAL_FAULT)
break;
}
switch (device.scheduleModeType) {
case 'OFF':
irrigationSystemService.setCharacteristic(this.Characteristic.ProgramMode, this.Characteristic.ProgramMode.NO_PROGRAM_SCHEDULED);
break;
case 'SCHEDULED':
irrigationSystemService.setCharacteristic(this.Characteristic.ProgramMode, this.Characteristic.ProgramMode.PROGRAM_SCHEDULED);
break;
case 'MANUAL':
irrigationSystemService.setCharacteristic(this.Characteristic.ProgramMode, this.Characteristic.ProgramMode.PROGRAM_SCHEDULED_MANUAL_MODE);
break;
default:
this.log.info(`Failed to retrieve program mode setting a default value. Retrieved-${device.scheduleModeType}`);
irrigationSystemService.setCharacteristic(this.Characteristic.ProgramMode, this.Characteristic.ProgramMode.PROGRAM_SCHEDULED_MANUAL_MODE);
break;
}
irrigationSystemService.getCharacteristic(this.Characteristic.Active).onGet(this.getDeviceValue.bind(this, irrigationSystemService, 'DeviceActive'));
irrigationSystemService.getCharacteristic(this.Characteristic.InUse).onGet(this.getDeviceValue.bind(this, irrigationSystemService, 'DeviceInUse'));
irrigationSystemService.getCharacteristic(this.Characteristic.ProgramMode).onGet(this.getDeviceValue.bind(this, irrigationSystemService, 'DeviceProgramMode'));
}
createValveService(device, zone) {
// Create Valve Service
const valveService = new this.Service.Valve(zone.name, zone.id);
let defaultRuntime = this.platform.defaultRuntime;
try {
switch (this.platform.runtimeSource) {
case 0:
defaultRuntime = this.platform.defaultRuntime;
break;
case 1:
if (zone.fixedRuntime > 0) {
defaultRuntime = zone.fixedRuntime;
}
break;
case 2:
if (zone.runtime > 0) {
defaultRuntime = zone.runtime;
}
break;
}
}
catch (err) {
this.log.debug('no smart runtime found, using default runtime');
}
this.log.debug(`Created controller valve service for ${zone.name} with id ${zone.id} with ${Math.round(defaultRuntime / 60)} min runtime`);
valveService.addCharacteristic(this.Characteristic.SerialNumber); //Use Serial Number to store the zone id
valveService.addCharacteristic(this.Characteristic.Model);
valveService.addCharacteristic(this.Characteristic.ConfiguredName);
valveService.getCharacteristic(this.Characteristic.SetDuration).setProps({
minValue: 0,
maxValue: 64800,
});
valveService.getCharacteristic(this.Characteristic.RemainingDuration).setProps({
minValue: 0,
maxValue: 64800,
});
valveService
.setCharacteristic(this.Characteristic.Active, this.Characteristic.Active.INACTIVE)
.setCharacteristic(this.Characteristic.InUse, this.Characteristic.InUse.NOT_IN_USE)
.setCharacteristic(this.Characteristic.ValveType, this.Characteristic.ValveType.IRRIGATION)
.setCharacteristic(this.Characteristic.SetDuration, Math.round(defaultRuntime / 60) * 60)
.setCharacteristic(this.Characteristic.RemainingDuration, 0)
.setCharacteristic(this.Characteristic.ServiceLabelIndex, zone.zoneNumber)
.setCharacteristic(this.Characteristic.SerialNumber, zone.id)
.setCharacteristic(this.Characteristic.StatusFault, this.Characteristic.StatusFault.NO_FAULT)
.setCharacteristic(this.Characteristic.Name, zone.name)
.setCharacteristic(this.Characteristic.ConfiguredName, zone.name)
.setCharacteristic(this.Characteristic.Model, zone.customNozzle.name);
if (zone.enabled) {
valveService.setCharacteristic(this.Characteristic.IsConfigured, this.Characteristic.IsConfigured.CONFIGURED);
}
else {
valveService.setCharacteristic(this.Characteristic.IsConfigured, this.Characteristic.IsConfigured.NOT_CONFIGURED);
}
this.updateValveService(device, zone, valveService);
this.configureValveService(device, valveService);
return valveService;
}
configureValveService(device, valveService) {
const zone_Number = valveService.getCharacteristic(this.Characteristic.ServiceLabelIndex).value;
const zone_Name = valveService.getCharacteristic(this.Characteristic.Name).value;
const zone_Runtime = Number(valveService.getCharacteristic(this.Characteristic.SetDuration).value) / 60;
this.log.info(`Configured Irrigation zone-${zone_Number} for ${zone_Name} with ${zone_Runtime} min runtime`);
this.platform.valveServices.push(valveService);
// Configure Valve Service
valveService.getCharacteristic(this.Characteristic.Active)
.onGet(this.getValveValue.bind(this, valveService, 'ValveActive'))
.onSet(this.setValveValue.bind(this, device, valveService));
valveService.getCharacteristic(this.Characteristic.InUse)
.onGet(this.getValveValue.bind(this, valveService, 'ValveInUse'))
.onSet(this.setValveValue.bind(this, device, valveService));
valveService.getCharacteristic(this.Characteristic.SetDuration)
.onGet(this.getValveValue.bind(this, valveService, 'ValveSetDuration'))
.onSet(this.setValveSetDuration.bind(this, device, valveService));
valveService.getCharacteristic(this.Characteristic.RemainingDuration)
.onGet(this.getValveValue.bind(this, valveService, 'ValveRemainingDuration'));
}
updateValveService(device, zone, valveService) {
if (valveService) {
let defaultRuntime = this.platform.defaultRuntime;
try {
switch (this.platform.runtimeSource) {
case 0:
defaultRuntime = this.platform.defaultRuntime;
break;
case 1:
if (zone.fixedRuntime > 0) {
defaultRuntime = zone.fixedRuntime;
}
break;
case 2:
if (zone.runtime > 0) {
defaultRuntime = zone.runtime;
}
break;
}
}
catch (err) {
this.log.debug('no smart runtime found, using default runtime');
}
this.log.debug(`Created valve service for ${device.name} with ${defaultRuntime} sec runtime (${Math.round(defaultRuntime / 60)} min)`);
valveService
.setCharacteristic(this.Characteristic.Active, this.Characteristic.Active.INACTIVE)
.setCharacteristic(this.Characteristic.InUse, this.Characteristic.InUse.NOT_IN_USE)
.setCharacteristic(this.Characteristic.StatusFault, this.Characteristic.StatusFault.NO_FAULT)
.setCharacteristic(this.Characteristic.Name, zone.name)
.setCharacteristic(this.Characteristic.ConfiguredName, zone.name)
.setCharacteristic(this.Characteristic.Model, zone.customNozzle.name)
.setCharacteristic(this.Characteristic.SetDuration, Math.round(defaultRuntime / 60) * 60);
if (zone.enabled) {
valveService.setCharacteristic(this.Characteristic.IsConfigured, this.Characteristic.IsConfigured.CONFIGURED);
}
else {
valveService.setCharacteristic(this.Characteristic.IsConfigured, this.Characteristic.IsConfigured.NOT_CONFIGURED);
}
return valveService;
}
}
getDeviceValue(irrigationSystemService, characteristicName) {
if (irrigationSystemService.getCharacteristic(this.Characteristic.StatusFault).value == this.Characteristic.StatusFault.GENERAL_FAULT) {
throw new this.platform.HapStatusError(-70402 /* this.platform.HAPStatus.SERVICE_COMMUNICATION_FAILURE */);
}
let currentValue = null;
switch (characteristicName) {
case 'DeviceActive':
currentValue = irrigationSystemService.getCharacteristic(this.Characteristic.Active).value;
break;
case 'DeviceInUse':
currentValue = irrigationSystemService.getCharacteristic(this.Characteristic.InUse).value;
break;
case 'DeviceProgramMode':
currentValue = irrigationSystemService.getCharacteristic(this.Characteristic.ProgramMode).value;
break;
default:
this.log.debug(`Unknown Device CharacteristicName called ${characteristicName}`);
break;
}
return currentValue;
}
getValveValue(valveService, characteristicName) {
if (valveService.getCharacteristic(this.Characteristic.StatusFault).value == this.Characteristic.StatusFault.GENERAL_FAULT) {
throw new this.platform.HapStatusError(-70402 /* this.platform.HAPStatus.SERVICE_COMMUNICATION_FAILURE */);
}
let currentValue = null;
switch (characteristicName) {
case 'ValveActive':
currentValue = valveService.getCharacteristic(this.Characteristic.Active).value;
break;
case 'ValveInUse':
currentValue = valveService.getCharacteristic(this.Characteristic.InUse).value;
break;
case 'ValveSetDuration':
currentValue = valveService.getCharacteristic(this.Characteristic.SetDuration).value;
break;
case 'ValveRemainingDuration': {
// Calc remain duration
const index = this.platform.valveServices.findIndex(valve => valve.subtype === valveService.subtype);
const timeEnding = Date.parse(this.platform.endTime[index]);
const timeNow = Date.now();
let timeRemaining = Math.max(Math.round((timeEnding - timeNow) / 1000), 0);
if (isNaN(timeRemaining)) {
timeRemaining = 0;
}
currentValue = timeRemaining;
break;
}
default:
this.log.debug(`Unknown CharacteristicName called ${characteristicName}`);
break;
}
return currentValue;
}
async setValveValue(device, valveService, value) {
//this.log.debug(`${valveService.getCharacteristic(this.Characteristic.Name).value} - Set Active state to ${value}`)
if (valveService.getCharacteristic(this.Characteristic.StatusFault).value == this.Characteristic.StatusFault.GENERAL_FAULT) {
throw new this.platform.HapStatusError(-70402 /* this.platform.HAPStatus.SERVICE_COMMUNICATION_FAILURE */);
}
if (value == valveService.getCharacteristic(this.Characteristic.Active).value) {
//IOS 17 bug fix for duplicate calls
this.log.debug(`supressed duplicate call from IOS for ${valveService.getCharacteristic(this.Characteristic.Name).value}, current value ${value}, new value ${valveService.getCharacteristic(this.Characteristic.Active).value}`);
return;
}
const index = this.platform.accessories.findIndex(accessory => accessory.UUID === device.id);
const irrigationAccessory = this.platform.accessories[index];
const irrigationSystemService = irrigationAccessory.getService(this.Service.IrrigationSystem);
// Set homekit state and prepare message for Rachio API
const runTime = Number(valveService.getCharacteristic(this.Characteristic.SetDuration).value);
let response;
switch (value) {
case this.Characteristic.Active.ACTIVE:
this.log.info(`Starting zone-${valveService.getCharacteristic(this.Characteristic.ServiceLabelIndex).value} ${valveService.getCharacteristic(this.Characteristic.Name).value} for ${runTime / 60} mins`);
response = await this.rachioapi.startZone(this.platform.token, valveService.getCharacteristic(this.Characteristic.SerialNumber).value, runTime);
if (response?.status == 204) {
const myZoneStart = {
eventId: 'f2d29dab-811c-34d4-8979-b464f38380a3',
eventType: 'DEVICE_ZONE_RUN_STARTED_EVENT',
externalId: this.platform.webhook_key_local,
payload: {
durationSeconds: valveService.getCharacteristic(this.Characteristic.SetDuration).value,
endTime: new Date(Date.now() + Number(valveService.getCharacteristic(this.Characteristic.SetDuration).value) * 1000).toISOString(),
flowVolumeG: '0.0',
runType: 'MANUAL',
startTime: new Date().toISOString(),
zoneNumber: valveService.getCharacteristic(this.Characteristic.ServiceLabelIndex).value,
},
resourceId: device.id,
resourceType: 'IRRIGATION_CONTROLLER',
timestamp: new Date().toISOString().split('.')[0] + 'Z',
};
const myZoneStop = {
eventId: 'c55fe382-9aad-310d-beb4-652542deea89',
eventType: 'DEVICE_ZONE_RUN_COMPLETED_EVENT',
externalId: this.platform.webhook_key_local,
payload: {
durationSeconds: Math.round(Number(valveService.getCharacteristic(this.Characteristic.SetDuration).value)),
endTime: new Date(Date.now() + Number(valveService.getCharacteristic(this.Characteristic.SetDuration).value) * 1000).toISOString(),
flowVolumeG: '0.0',
runType: 'MANUAL',
startTime: new Date().toISOString(),
zoneNumber: valveService.getCharacteristic(this.Characteristic.ServiceLabelIndex).value,
},
resourceId: device.id,
resourceType: 'IRRIGATION_CONTROLLER',
timestamp: new Date().toISOString().split('.')[0] + 'Z',
};
this.log.debug(`Simulating webhook for zone ${myZoneStart.payload.zoneNumber} will update services`);
this.listener.localMessage(irrigationSystemService, valveService, myZoneStart);
this.platform.localWebhook = setTimeout(() => {
this.log.debug(`Simulating webhook for zone ${myZoneStop.payload.zoneNumber} will update services`);
this.listener.localMessage(irrigationSystemService, valveService, myZoneStop);
}, runTime * 1000);
}
else {
this.log.info('Failed to start valve');
}
break;
case this.Characteristic.Active.INACTIVE:
// Turn off/stopping the valve
this.log.info(`Stopping zone-${valveService.getCharacteristic(this.Characteristic.ServiceLabelIndex).value} ${valveService.getCharacteristic(this.Characteristic.Name).value}`);
response = await this.rachioapi.stopDevice(this.platform.token, device.id);
if (response?.status == 204) {
const myZoneStop = {
eventId: 'c55fe382-9aad-310d-beb4-652542deea89',
eventType: 'DEVICE_ZONE_RUN_STOPPED_EVENT',
externalId: this.platform.webhook_key_local,
payload: {
durationSeconds: Math.round(Number(valveService.getCharacteristic(this.Characteristic.SetDuration).value) - (Date.parse(this.platform.endTime[Number(valveService.subtype)]) - Date.now()) / 1000),
endTime: new Date(Date.now() + Number(valveService.getCharacteristic(this.Characteristic.SetDuration).value) * 1000).toISOString(),
flowVolumeG: '0.0',
runType: 'MANUAL',
startTime: new Date().toISOString(),
zoneNumber: valveService.getCharacteristic(this.Characteristic.ServiceLabelIndex).value,
},
resourceId: device.id,
resourceType: 'IRRIGATION_CONTROLLER',
timestamp: new Date().toISOString().split('.')[0] + 'Z',
};
this.log.debug(`Simulating webhook for zone ${myZoneStop.payload.zoneNumber} will update services`);
this.listener.localMessage(irrigationSystemService, valveService, myZoneStop);
clearTimeout(this.platform.localWebhook);
}
else {
this.log.info('Failed to stop zone');
}
break;
}
return;
}
setValveSetDuration(device, valveService, value) {
// Set default duration from Homekit value
valveService.getCharacteristic(this.Characteristic.SetDuration).updateValue(value);
this.log.debug(`Set ${device.name} duration for ${Number(value) / 60} mins`);
this.log.info(`Set ${valveService.getCharacteristic(this.Characteristic.Name).value} duration for ${Number(value) / 60} mins`);
return;
}
}
//# sourceMappingURL=irrigation.js.map