homebridge-rachio-irrigation
Version:
Rachio Irrigation System platform plugin for [Homebridge](https://github.com/nfarina/homebridge).
325 lines (310 loc) • 15.5 kB
JavaScript
let packageJson = require('../package.json')
let RachioAPI = require('../rachioapi')
let listener = require('../listener')
let polling = require('../polling')
class valve {
constructor(platform, log, config) {
this.log = log
this.config = config
this.platform = platform
this.rachioapi = new RachioAPI(platform, log)
this.listener = new listener(platform, log, config)
this.polling = new polling(platform, log, config)
this.pollValves = config.pollValves ? config.pollValves : false
}
createValveAccessory(base, property, valve, platformAccessory) {
let valveService
if (!platformAccessory) {
// Create new Valve System Service
this.log.debug('Create valve accessory %s %s', valve.id, property.address.locality)
platformAccessory = new PlatformAccessory(property.address.locality, valve.id)
//valveService = platformAccessory.addService(Service.Valve, valve.id, valve.id) // changed warning message for "-" in name
valveService = platformAccessory.addService(Service.Valve, valve.id.replace(/-/g, ''), valve.id)
valveService.addCharacteristic(Characteristic.SerialNumber) //Use Serial Number to store the zone id
valveService.addCharacteristic(Characteristic.Model)
valveService.addCharacteristic(Characteristic.ConfiguredName)
valveService.addCharacteristic(Characteristic.ProgramMode)
} else {
// Update Valve System Service
this.log.debug('Update valve accessory %s %s', valve.id, valve.name)
}
// Create AccessoryInformation Service
platformAccessory
.getService(Service.AccessoryInformation)
.setCharacteristic(Characteristic.Name, property.address.locality)
.setCharacteristic(Characteristic.Manufacturer, 'Rachio')
.setCharacteristic(Characteristic.SerialNumber, base.serialNumber)
.setCharacteristic(Characteristic.Model, 'SHVK001')
.setCharacteristic(Characteristic.Identify, true)
.setCharacteristic(Characteristic.FirmwareRevision, valve.state.reportedState.firmwareVersion)
.setCharacteristic(Characteristic.HardwareRevision, base.macAddress)
.setCharacteristic(Characteristic.SoftwareRevision, packageJson.version)
.setCharacteristic(Characteristic.ProductData, 'Valve')
// Create Valve Service
// Check if the valve is connected
valveService = platformAccessory.getService(Service.Valve)
if (valve.state.reportedState.connected == true) {
valveService.setCharacteristic(Characteristic.StatusFault, Characteristic.StatusFault.NO_FAULT)
} else {
this.log.warn('%s disconnected at %s! This will show as non-responding in Homekit until the connection is restored.', valve.name, valve.state.reportedState.lastSeen)
valveService.setCharacteristic(Characteristic.StatusFault, Characteristic.StatusFault.GENERAL_FAULT)
}
return platformAccessory
}
configureValveService(base, valve, valveService) {
this.log.info('Configure Valve service for %s', valveService.getCharacteristic(Characteristic.Name).value)
valveService
.setCharacteristic(Characteristic.Active, Characteristic.Active.ACTIVE)
.setCharacteristic(Characteristic.InUse, Characteristic.InUse.NOT_IN_USE)
.setCharacteristic(Characteristic.StatusFault, !valve.state.reportedState.connected)
.setCharacteristic(Characteristic.Duration, valve.state.desiredState.defaultRuntimeSeconds)
.setCharacteristic(Characteristic.RemainingDuration, 0)
valveService.getCharacteristic(Characteristic.Active).on('get', this.getDeviceValue.bind(this, valveService, 'DeviceActive'))
valveService.getCharacteristic(Characteristic.InUse).on('get', this.getDeviceValue.bind(this, valveService, 'DeviceInUse'))
valveService.getCharacteristic(Characteristic.ProgramMode).on('get', this.getDeviceValue.bind(this, valveService, 'DeviceProgramMode'))
}
updateValveService(base, valve, valveService) {
if (this.pollValves) {
this.log.warn('Polling for Hose Timers is enabled')
}
let defaultRuntime = this.platform.defaultRuntime
valve.enabled = true // need rachio valve version of enabled
this.log.debug(valve)
try {
switch (this.platform.runtimeSource) {
case 0:
defaultRuntime = this.platform.defaultRuntime
break
case 1:
if (device.state.defaultRunTimeSeconds > 0) {
defaultRuntime = device.state.desiredState.defaultRuntimeSeconds
}
break
case 2:
if (zone.flow_data.cycle_run_time_sec > 0) {
defaultRuntime = device.state.desiredState.defaultRuntimeSeconds
}
break
default:
defaultRuntime = this.platform.defaultRuntime
break
}
} catch (err) {
this.log.debug('error setting runtime, using default runtime')
}
this.log.debug('Created valve service for %s with %s sec runtime (%s min)', valve.name, defaultRuntime, Math.round(defaultRuntime / 60))
valveService
.setCharacteristic(Characteristic.ValveType, this.platform.valveType)
.setCharacteristic(Characteristic.IsConfigured, Characteristic.IsConfigured.CONFIGURED)
.setCharacteristic(Characteristic.ServiceLabelIndex, valve.zone)
.setCharacteristic(Characteristic.StatusFault, !valve.state.reportedState.connected)
.setCharacteristic(Characteristic.SerialNumber, valve.id)
.setCharacteristic(Characteristic.Name, valve.name)
.setCharacteristic(Characteristic.ConfiguredName, valve.name)
.setCharacteristic(Characteristic.Model, 'SHV101')
if (valve.state.reportedState.lastWateringAction) {
let start = valve.state.reportedState.lastWateringAction.start
let duration = valve.state.reportedState.lastWateringAction.durationSeconds
let endTime = new Date(start).getTime() + duration * 1000
let remaining = Math.max(Math.round((endTime - Date.now()) / 1000), 0)
this.platform.endTime[valveService.getCharacteristic(Characteristic.SerialNumber).value] = endTime
valveService
.setCharacteristic(Characteristic.Active, Characteristic.Active.ACTIVE)
.setCharacteristic(Characteristic.InUse, Characteristic.InUse.IN_USE)
.setCharacteristic(Characteristic.SetDuration, duration)
.setCharacteristic(Characteristic.RemainingDuration, remaining)
} else {
let start = Date.now()
let duration = Math.ceil(defaultRuntime / 60) * 60
let endTime = new Date(start).getTime() + duration * 1000
let remaining = Math.max(Math.round((endTime - Date.now()) / 1000), 0)
this.platform.endTime[valveService.getCharacteristic(Characteristic.SerialNumber).value] = endTime
valveService
.setCharacteristic(Characteristic.Active, Characteristic.Active.INACTIVE)
.setCharacteristic(Characteristic.InUse, Characteristic.InUse.NOT_IN_USE)
.setCharacteristic(Characteristic.SetDuration, duration)
.setCharacteristic(Characteristic.RemainingDuration, remaining)
}
if (valve.enabled) {
valveService.setCharacteristic(Characteristic.IsConfigured, Characteristic.IsConfigured.CONFIGURED)
} else {
valveService.setCharacteristic(Characteristic.IsConfigured, Characteristic.IsConfigured.NOT_CONFIGURED)
}
return valveService
}
configureValveService(device, valveService) {
this.log.info(
'Configured zone-%s for %s with %s min runtime',
valveService.getCharacteristic(Characteristic.ServiceLabelIndex).value,
valveService.getCharacteristic(Characteristic.Name).value,
valveService.getCharacteristic(Characteristic.SetDuration).value / 60
)
valveService.getCharacteristic(Characteristic.Active).on('get', this.getValveValue.bind(this, valveService, 'ValveActive')).on('set', this.setValveValue.bind(this, device, valveService))
valveService.getCharacteristic(Characteristic.InUse).on('get', this.getValveValue.bind(this, valveService, 'ValveInUse')).on('set', this.setValveValue.bind(this, device, valveService))
valveService.getCharacteristic(Characteristic.SetDuration).on('get', this.getValveValue.bind(this, valveService, 'ValveSetDuration')).on('set', this.setValveSetDuration.bind(this, device, valveService))
valveService.getCharacteristic(Characteristic.RemainingDuration).on('get', this.getValveValue.bind(this, valveService, 'ValveRemainingDuration'))
}
getDeviceValue(valveService, characteristicName, callback) {
switch (characteristicName) {
case 'DeviceActive':
//this.log.debug('%s=%s %s', valveService.getCharacteristic(Characteristic.Name).value, characteristicName,valveService.getCharacteristic(Characteristic.Active).value)
if (valveService.getCharacteristic(Characteristic.StatusFault).value == Characteristic.StatusFault.GENERAL_FAULT) {
callback('error')
} else {
callback(null, valveService.getCharacteristic(Characteristic.Active).value)
}
break
case 'DeviceInUse':
//this.log.debug('%s=%s %s', valveService.getCharacteristic(Characteristic.Name).value, characteristicName,valveService.getCharacteristic(Characteristic.InUse).value)
callback(null, valveService.getCharacteristic(Characteristic.InUse).value)
break
case 'DeviceProgramMode':
//this.log.debug('%s=%s %s', valveService.getCharacteristic(Characteristic.Name).value, characteristicName,valveService.getCharacteristic(Characteristic.ProgramMode).value)
callback(null, valveService.getCharacteristic(Characteristic.ProgramMode).value)
break
default:
this.log.debug('Unknown Device Characteristic Name called', characteristicName)
callback()
break
}
}
getValveValue(valveService, characteristicName, callback) {
//this.log.debug('value', valveService.getCharacteristic(Characteristic.Name).value, characteristicName)
switch (characteristicName) {
case 'ValveActive':
if (valveService.getCharacteristic(Characteristic.StatusFault).value == Characteristic.StatusFault.GENERAL_FAULT) {
callback('error')
} else {
//this.polling.startLiveUpdate(valveService) ///disabled for webhooks
if (this.pollValves) {
this.polling.startLiveUpdate(valveService)
}
callback(null, valveService.getCharacteristic(Characteristic.Active).value)
}
break
case 'ValveInUse':
callback(null, valveService.getCharacteristic(Characteristic.InUse).value)
break
case 'ValveSetDuration':
callback(null, valveService.getCharacteristic(Characteristic.SetDuration).value)
break
case 'ValveRemainingDuration':
// Calc remain duration
let timeEnding = Date.parse(this.platform.endTime[valveService.subtype])
let timeNow = Date.now()
let timeRemaining = Math.max(Math.round((timeEnding - timeNow) / 1000), 0)
if (isNaN(timeRemaining)) {
timeRemaining = 0
}
//valveService.getCharacteristic(Characteristic.RemainingDuration).updateValue(timeRemaining)
callback(null, timeRemaining)
break
default:
this.log.debug('Unknown Valve Characteristic Name called', characteristicName)
callback()
break
}
}
async setValveValue(device, valveService, value, callback) {
//this.log.debug('%s - Set Active state to %s', valveService.getCharacteristic(Characteristic.Name).value, value)
if (value == valveService.getCharacteristic(Characteristic.Active).value) {
//IOS 17 bug fix for duplicate calls
this.log.debug('supressed duplicate call from IOS for %s, current value %s, new value %s', valveService.getCharacteristic(Characteristic.Name).value, value, valveService.getCharacteristic(Characteristic.Active).value)
callback()
return
}
// Set homekit state and prepare message for rachio API
let runTime = valveService.getCharacteristic(Characteristic.SetDuration).value
let endTime = new Date(Date.now() + runTime * 1000).toISOString()
let response
switch (value) {
case Characteristic.Active.ACTIVE:
// Turn on/idle the valve
this.log.info('Starting %s valve for %s mins', valveService.getCharacteristic(Characteristic.Name).value, runTime / 60)
response = await this.rachioapi.startWatering(this.platform.token, device.id, runTime)
if (response.status == 200) {
//json start stuff
let myJsonStart = {
eventId: '53936ce8-299a-3a02-9f8a-754a55291333',
eventType: 'VALVE_RUN_START_EVENT',
externalId: this.platform.webhook_key_local,
payload: {
durationSeconds: valveService.getCharacteristic(Characteristic.SetDuration).value,
flowDetected: false,
runType: 'QUICK_RUN',
startTime: new Date().toISOString()
},
resourceId: device.id,
resourceType: 'VALVE',
timestamp: new Date().toISOString()
}
let myJsonStop = {
eventId: '6defa6ff-a169-3477-aa57-3c028455387d',
eventType: 'VALVE_RUN_END_EVENT',
externalId: this.platform.webhook_key_local,
payload: {
durationSeconds: Math.round(valveService.getCharacteristic(Characteristic.SetDuration).value),
endReason: 'COMPLETED',
flowDetected: false,
runType: 'QUICK_RUN',
startTime: new Date().toISOString()
},
resourceId: device.id,
resourceType: 'VALVE',
timestamp: new Date().toISOString()
}
this.log.debug('Simulating websocket event for %s', myJsonStart.resourceId)
if (this.platform.showWebhookMessages) {
this.log.debug('webhook sent from <%s> %s', this.platform.webhook_key_local, JSON.stringify(myJsonStart, null, 2))
}
this.platform.listener.eventMsg(null, valveService, myJsonStart)
this.localWebhook = setTimeout(() => {
this.log.debug('Simulating websocket event for %s', myJsonStop.resourceId)
this.platform.endTime[valveService.getCharacteristic(Characteristic.SerialNumber).value] = new Date(Date.now()).toISOString()
if (this.platform.showWebhookMessages) {
this.log.debug('webhook sent from <%s> %s', this.platform.webhook_key_local, JSON.stringify(myJsonStop, null, 2))
}
this.platform.listener.eventMsg(null, valveService, myJsonStop)
}, runTime * 1000)
}
break
case Characteristic.Active.INACTIVE:
// Turn off/stopping the valve
this.log.info('Stopping Zone', valveService.getCharacteristic(Characteristic.Name).value)
response = await this.rachioapi.stopWatering(this.platform.token, device.id)
if (response.status == 200) {
//json stop stuff
let myJsonStop = {
eventId: '6defa6ff-a169-3477-aa57-3c028455387d',
eventType: 'VALVE_RUN_END_EVENT',
externalId: this.platform.webhook_key_local,
payload: {
durationSeconds: Math.round(valveService.getCharacteristic(Characteristic.SetDuration).value - (Date.parse(this.platform.endTime[valveService.subtype]) - Date.now()) / 1000),
endReason: 'COMPLETED',
flowDetected: false,
runType: 'QUICK_RUN',
startTime: new Date().toISOString()
},
resourceId: device.id,
resourceType: 'VALVE',
timestamp: new Date().toISOString()
}
this.log.debug('Simulating websocket event for %s', myJsonStop.resourceId)
if (this.platform.showWebhookMessages) {
this.log.debug('webhook sent from <%s> %s', this.platform.webhook_key_local, JSON.stringify(myJsonStop, null, 2))
}
this.platform.listener.eventMsg(null, valveService, myJsonStop)
clearTimeout(this.localWebhook)
} else this.log.info('Failed to stop valve')
break
}
callback()
}
setValveSetDuration(device, valveService, value, callback) {
// Set default duration from Homekit value
valveService.getCharacteristic(Characteristic.SetDuration).updateValue(value)
this.log.info('Set %s duration for %s mins', valveService.getCharacteristic(Characteristic.Name).value, value / 60)
callback()
}
}
module.exports = valve