irrigation/src/ha.js

172 lines
6.2 KiB
JavaScript

import Hass from '@uci/ha'
import { PUMP, ZONE, RUNNER } from './ha-constants.js'
let initialized = false
async function init(opts) {
console.log('Initializing Home Assistant Integration')
opts = opts || this.config.hass
if (!opts.access_token) throw('no access token to HA supplied, not way to connect')
this.hass = new Hass(opts)
const hass = this.hass
hass.on('error',ev => console.log('!!!! HASS socket error !!!!!\n', ev))
hass.on('connection',async state => {
console.log('HA connection event >', state)
// if (hass.eventBusId) console.log('listener established for HA event bus')
if (state==='ready') {
console.log('HA connection ready, listener established for all of HA event bus')
for (let prop in RUNNER) {
let ent = `${opts.namespace ? opts.namespace+'_':''}${RUNNER[prop]}`
await hass.setVariable(ent, this.runner[prop])
}
await hass.updateEntity(
`input_boolean.${opts.namespace ? opts.namespace+'_':''}enabled`,
this.runner.enabled ? 'on':'off')
const pumpEnt = makeEntityName('pump',PUMP.tmpl,opts.namespace)
hass[PUMP.hass_function].call(hass,pumpEnt,this.pump.state) // reset state
if (!initialized) {
for (let prop in RUNNER) {
let ent = `${opts.namespace ? opts.namespace+'_':''}${RUNNER[prop]}`
this.runner.rxSubscribe(prop,'ha',value => {
// console.log('runner', ent, ev)
hass.setVariable(ent, value)
})
}
hass.on(
`input_boolean.${opts.namespace ? opts.namespace+'_':''}enabled`,
entity => {this.runner.enabled = entity.state==='on'?true:false
})
this.rxSubscribe('pump.state','ha',value => {
// console.log('pump state change sent to HA:',ev.value) //,pumpEnt,PUMP.hass_function)
hass[PUMP.hass_function].call(hass,pumpEnt,value)
})
hass.on(pumpEnt,
entity => {
// console.log('HASS: pump change request',entity.state)
this.pumpGPIO(entity.state)
})
console.log('done runner and pump HA setup')
// prepare the zones communication with HA
}
for (let id in this.zones) {
let _zone = this.zones[id]
// console.log('HA setup for', _zone.id)
await zone.call(this,_zone,'reset',opts.namespace)
// console.log('done resetting zone',_zone.id)
if (!initialized) {
await zone.call(this,_zone,'subscribe',opts.namespace)
await zone.call(this,_zone,'watch',opts.namespace)
}
}
console.log('done zones HA setup')
hass.ready = true // TODO but this in ha code
setTimeout(()=> { // wait for any incoming bogus stuff from ha
console.log('HA Initialization Complete')
initialized = true},1000)
} // end hass ready
else {
hass.eventBusID == null
hass.ready=false
// cleanup
}
}) // end connection listner
console.log('attempting connection to home assistant')
hass.connect()
} // end initHass
async function zone(zone,req,namespace) {
const hass = this.hass
if (typeof zone === 'string') zone = this.getZone(zone)
const sch = this.runner.getSchedule(zone.id)
// console.log('schedule subscriptions',sch.$get(['_rx_.props']))
if (req ==='reset') await this.hass.setSelect(makeEntityName(zone.id,ZONE['delta'].tmpl,namespace), Object.values(this.deltas))
Object.keys(ZONE).forEach(ent =>{
let path;let inst
if (ZONE[ent].schedule) {
inst = sch
path = [ZONE[ent].path,ent]
} else {
inst = this
path = [ZONE[ent].path,'zones',zone.id,ent]
}
// console.log(zone.id, ZONE[ent].schedule,ent,path)
let alterValue = (val) => {
return ZONE[ent].out_value_function ? ZONE[ent].out_value_function(val,this.deltas) : val
}
const entName = makeEntityName(zone.id,ZONE[ent].tmpl,namespace)
const func = this.hass[ZONE[ent].hass_function] || this.hass['setNumber']
// console.log(req,ent)
switch (req) {
case 'reset':
// console.log('to value:',alterValue(get(path)))
func.call(this.hass,entName,alterValue(inst.get(path)))
break
case 'subscribe':
// console.log(ent,entName,path,inst.id)
inst.rxSubscribe(path,'ha',async function (value) {
// console.log('subscribe event',ev)
value = alterValue(value)
// console.log(inst.id, entName, await hass.getEntities(entName))
let hassState = (await hass.getEntities(entName)).state
// console.log(hassState,value,ev.value)
if (typeof(value) === 'number') hassState = Number(hassState)
// console.log('hass value',hassState,'new value',value)
if (hassState !== value ) {
// console.log(ent,'prop',this.zones[zone.id].settings[ent])
// if (ent!=='countdown') console.log(zone.id,': new state sent to HA (old,new):',hassState,':',value ) // entName,value,func)
func.call(hass,entName,value)
}
}) // end subscribe
break
case 'unsubscribe':
break
case 'watch':
hass.on(entName,
function (zone,path,oent,entity) {
let value = oent.in_value_function ? oent.in_value_function(entity.state,this.deltas) : entity.state
// console.log('converted value', value,'access to this',!!this.set)
if (oent.in_function) oent.in_function.call(this,zone,value,path)
if (!oent.in_no_state_change) {
const curValue = inst.get(path)
if (typeof(curValue) === 'number') value = Number(value)
// console.log(curValue,value, initialized)
if (curValue !== value && initialized) {
// console.log(zone.id,ent,entName,'Updating with new value from HA (old,new)',curValue,value)
// console.log(`setting value from HA=> ${zone.id} : ${ent} : ${value}`)
inst.set(path,value)
}
}
}.bind(this,zone,path,ZONE[ent])
)
break
default:
}
}) // next zone entity
} // end zone
export { init, zone }
// create zone HA entity names
function makeEntityName(name,tmpl,namespace) {
const replaceWith = `${namespace ? namespace+'_':''}${name}`
return tmpl.replace('%r%',replaceWith)
}