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) }