291 lines
9.5 KiB
JavaScript
291 lines
9.5 KiB
JavaScript
// native
|
|
import { sync as getConfig } from 'load-yaml-file'
|
|
import delay from 'delay'
|
|
import deepFreeze from 'deep-freeze'
|
|
import clone from 'clone'
|
|
import { all as merge } from 'deepmerge'
|
|
// import to from 'await-to-js'
|
|
// UCI
|
|
import RxClass from '@uci-utils/rx-class'
|
|
import { Gpio } from '@uci/gpio'
|
|
import { Runner, Schedule } from '@uci-utils/scheduler'
|
|
import { init as initHass } from './ha.js'
|
|
import { PUMP, ZONE, RUNNER } from './defaults.js'
|
|
|
|
// TODO change to dynamic imports
|
|
// import Base from '@uci/base' // TODO add Hass websocket client to Base
|
|
// TODO extend the class conditionally with uci base
|
|
|
|
|
|
class Irrigation extends RxClass {
|
|
constructor(opts) {
|
|
super()
|
|
this.config = deepFreeze((typeof opts ==='string') ? getConfig(opts) : opts)
|
|
// console.log(this.config)
|
|
this.deployed = this.config.deployed==null ? 'true' : this.config.deployed // assume deployed by default
|
|
|
|
this.settings = {
|
|
zones:{},
|
|
pump: clone(this.config.pump.settings)
|
|
}
|
|
this.zones = clone(this.config.zones)
|
|
Object.keys(this.zones).forEach(id => {
|
|
const zone = this.zones[id]
|
|
zone.id = id
|
|
zone.name = zone.name || zone.id
|
|
this.zoneResetSettings(zone)
|
|
delete zone.settings
|
|
this.set(['zones',zone.id,'state'],'off')
|
|
})
|
|
console.log('done setting up zones')
|
|
this.pump = clone(this.config.pump)
|
|
this.settings.pump = merge([{},PUMP,this.pump.settings])
|
|
this.set('pump.state','off')
|
|
this.deltas = this.config.deltas
|
|
console.log('done setting up pump')
|
|
const rOpts = {
|
|
name: this.config.name ||'irrigation',
|
|
schedules: Object.values(this.zones).map(zone => {
|
|
const zsets = this.get(['settings.zones',zone.id,'schedule'])
|
|
const sch = new Schedule ({ name: zone.name, id:zone.id, settings:zsets})
|
|
this.$del(['settings.zones',zone.id,'schedule'],true)
|
|
return sch
|
|
})
|
|
}
|
|
|
|
this.runner = new Runner (Object.assign(rOpts,RUNNER))
|
|
|
|
this.rxAdd('settings',{traverse:true}) // TODO add store subscription here
|
|
// console.log('runner', this.runner)
|
|
console.log('schedules and runner created')
|
|
console.log('enabled schedules', this.runner.schedules.map(sch=>sch.id))
|
|
|
|
if (this.deployed) {
|
|
console.log('deployed, setting up gpios')
|
|
this.pump.gpio = new Gpio(this.pump.gpio_pin_num,{activeLow:true})
|
|
Object.values(this.zones).forEach(zone =>{
|
|
zone.gpio = new Gpio(zone.gpio_pin_num,{activeLow:true})
|
|
})
|
|
}
|
|
}
|
|
|
|
async init() {
|
|
|
|
let init = false
|
|
|
|
await this.pumpGPIO('off')
|
|
|
|
if (this.config.hass) await initHass.call(this)
|
|
|
|
// set up zone schedules and init
|
|
Object.values(this.zones).forEach(zone => {
|
|
const sch = this.runner.getSchedule(zone.id)
|
|
// console.log(this.runner.getSchedule(zone.id).get('settings'))
|
|
// this.rxSubscribe(['settings.zones',zone.id,'schedule.enabled'],'abort',value => {
|
|
// console.log(zone.id,'zone schedule enabled change',value)
|
|
// if (init) {
|
|
// if (value==='off') {
|
|
// console.log('removing any active schedules', sch.id)
|
|
// this.runner.removeActive(sch.id)
|
|
// }
|
|
// }
|
|
// })
|
|
// this.zoneSchCountdown(zone.id)
|
|
sch.registerStartAction(start,
|
|
this.get(['settings.zones',zone.id,'duration']),
|
|
this.zoneOn.bind(this,zone,'on','auto'),
|
|
this.zoneOn.bind(this,zone,'off','auto')
|
|
) // passing zone here make it available in action
|
|
sch.registerStopAction(stop,this.zoneOn.bind(this,zone,'off','auto'))
|
|
this.zoneOff(zone)
|
|
})
|
|
|
|
// this.runner.rxSubscribe('countdown','runner',value => { console.log('trigger #', this.runner._toID,'in', value)})
|
|
// this.runner.rxSubscribe('running','runner',list => { console.log('running schedules:',list)})
|
|
// this.runner.rxSubscribe('queue','runner',list => { console.log('queued schedules:',list)})
|
|
// this.runner.rxSubscribe('queueCount','runner',value => { console.log('queue schedule count', value)})
|
|
// this.runner.rxSubscribe('runningCount','runner',value => { console.log('running schedule count', value)})
|
|
|
|
console.log('Irrigation Initialization Done')
|
|
|
|
init = true
|
|
|
|
} // end init
|
|
|
|
getZone (id) {
|
|
return this.zones[id]
|
|
}
|
|
|
|
zoneResetSettings(zone) {
|
|
if (typeof zone ==='string') zone = this.getZone(zone)
|
|
this.settings.zones[zone.id] = merge([{},ZONE,
|
|
this.config.zone_settings_default || {},
|
|
zone.settings,
|
|
this.config.zone_settings_common || {}
|
|
])
|
|
// console.log(zone.id,this.getZoneProp(zone,'settings'))
|
|
}
|
|
|
|
setZoneProp(zone,path,value) {
|
|
if (typeof zone !=='string') zone=zone.id
|
|
return this.set(['zones',zone,path],value)
|
|
}
|
|
|
|
getZoneProp(zone,path) {
|
|
if (typeof zone !=='string') zone=zone.id
|
|
return this.get(['zones',zone,path])
|
|
}
|
|
|
|
async pumpPrime (prime=3) {
|
|
// console.log('priming pump')
|
|
// await this.pump.gpio.on()
|
|
await this.pumpGPIO('on')
|
|
await delay(prime*1000) // let pump get to pressure
|
|
// console.log('waited for prime',prime,'secs')
|
|
// await this.pump.gpio.off()
|
|
await this.pumpGPIO('off')
|
|
}
|
|
|
|
async pumpDripMode (drip) {
|
|
console.log('-----using drip pump cycling----')
|
|
this.pump.dripMode = true
|
|
await this.pumpPrime(this.pump.prime)
|
|
const pause = drip === true ? (this.pump.settings.drip || 60) : drip
|
|
// console.log('pump is primed pausing for',pause,'secs')
|
|
this.pump.dripInt = setInterval(async ()=>{
|
|
await this.pumpPrime(this.pump.prime)
|
|
// console.log('pump is primed again pausing for',pause,'secs')
|
|
},pause*1000)
|
|
}
|
|
|
|
async pumpToggle() {
|
|
return await this.pumpGPIO('toggle')
|
|
}
|
|
|
|
async pumpOff () {
|
|
return await this.pumpGPIO('off')
|
|
}
|
|
|
|
async pumpOn () {
|
|
return await this.pumpGPIO('on')
|
|
}
|
|
|
|
async pumpGPIO(state) {
|
|
state == null ? 'on' : state
|
|
if (state === true) state='on'
|
|
if (state === false) state='off'
|
|
if (this.deployed) {
|
|
// await this.pump.gpio.read()
|
|
// if (this.pump.gpio.state !== state ) {
|
|
let res = await this.pump.gpio[state]()
|
|
// console.log('pump state change to',res)
|
|
this.pump.state = this.pump.gpio.state
|
|
this.pump.active = this.pump.gpio.value
|
|
// console.log('change of pump state',this.pump.state)
|
|
return res
|
|
// }
|
|
}
|
|
else console.log('mock pump state change', state)
|
|
}
|
|
|
|
// Todo think of better way to handle automatic zone change vs manual
|
|
async zoneOn (zone,state,auto) {
|
|
const zset = this.settings.zones[zone.id]
|
|
const pset = this.settings.pump
|
|
// console.log(zone.id,'auto,zone.auto,state',auto,zone.auto,state)
|
|
if (typeof zone === 'string') zone = this.zones[zone]
|
|
state = state == null ? 'on' : state
|
|
if (state === true) state='on'
|
|
if (state === false ) state='off'
|
|
if (!zone.auto) zone.auto = state == 'on' && auto==='auto'
|
|
// console.log('zone.auto',zone.auto)
|
|
if (this.deployed) {
|
|
await zone.gpio.read()
|
|
if (zone.gpio.state !== state ) {
|
|
const cstate = await zone.gpio[state]()?'on':'off'
|
|
if(cstate !== state) {
|
|
console.log('ERROR: zone state request was not realized, aborting!!')
|
|
return {error:'ERROR: zone state request was not realized, aborting!!'}
|
|
}
|
|
// console.log(zone.id, 'new zone state',state)
|
|
zone.state = state
|
|
zone.active = zone.gpio.value
|
|
}
|
|
}
|
|
if (pset.use && !(zset.pump === 'off')) { // unless specifically turned off use pump
|
|
// console.log('handling pump with zone state')
|
|
if (zset.drip) {
|
|
if (!this.pump.dripMode && state==='on') this.pumpDripMode(zset.drip)
|
|
if (state==='off') {
|
|
// console.log(zone.id,'pump off: clearing drip mode')
|
|
clearInterval(this.pump.dripInt)
|
|
this.pump.dripMode = false
|
|
// console.log('--------clearing drip cycling mode------------')
|
|
await this.pumpGPIO('off')
|
|
}
|
|
} else await this.pumpGPIO(state)
|
|
}
|
|
// console.log(zone.name,zone.id,'is', state)
|
|
|
|
if (!zone.auto && state==='on') {
|
|
const duration = zset.duration || 10
|
|
console.log('MANUAL - setting timeout to turn off in',duration,'minutes')
|
|
zone._offTO = setTimeout( () => {
|
|
console.log(zone.name, zone.id, 'run of ', duration,' is over')
|
|
this.zoneOn(zone,'off')
|
|
},
|
|
duration*60000)
|
|
} else clearTimeout(zone._offTO)
|
|
|
|
if (state ==='off' && auto==='auto') zone.auto = false
|
|
|
|
}
|
|
|
|
zoneOff(zone) {
|
|
this.zoneOn(zone,'off')
|
|
}
|
|
|
|
} // end class
|
|
|
|
export default Irrigation
|
|
export { Irrigation }
|
|
|
|
// zone schedule start action
|
|
function start (duration,on,off,activeSch) {
|
|
console.log('start',duration,on,off,activeSch.name)
|
|
return new Promise(resolve => {
|
|
console.log('starting',activeSch.runID)
|
|
console.log('---watering for', duration,'minutes')
|
|
on()
|
|
const tick = setInterval(()=>{
|
|
console.log(activeSch.runID,'duration tick')
|
|
}, 1000)
|
|
this.once('abort:'+activeSch.runID,()=> {
|
|
clearTimeout(run)
|
|
clearInterval(tick)
|
|
off()
|
|
console.log('aborting run>>>', activeSch.runID)
|
|
})
|
|
const run = setTimeout(()=>{
|
|
console.log('stopping>>>',activeSch.runID)
|
|
clearInterval(tick)
|
|
off()
|
|
this.removeAllListeners('abort:'+activeSch.runID)
|
|
resolve()
|
|
}, duration*1000 * 60)
|
|
})
|
|
}
|
|
|
|
// zone stop action
|
|
function stop (stop,activeSch) {
|
|
console.log('aborting run for action id',activeSch.runID, stop)
|
|
this.emit('abort:'+activeSch.runID)
|
|
}
|
|
|
|
// const objectMap = (obj, fn) =>
|
|
// Object.fromEntries(
|
|
// Object.entries(obj).map(
|
|
// ([k, v], i) => [k, fn(v, k, i)]
|
|
// )
|
|
// )
|