0.0.6 improvements and fixes while working on irrigation project

master
David Kebler 2020-05-30 18:07:23 -07:00
parent 67955d3260
commit 2f6901e7ae
5 changed files with 82 additions and 58 deletions

5
README.md Normal file
View File

@ -0,0 +1,5 @@
# Schedule and Queue Classes
For reoccuring schedule on an interval with start stop actions. Queue allows multiple schedules with facility to avoid simultanteous action if desired
The primary use case is to scehduel multiple irrigation zones that share a common pump (not simultaneous)

View File

@ -1,26 +1,22 @@
{ {
"name": "@uci-utils/scheduler", "name": "@uci-utils/scheduler",
"version": "0.0.4", "version": "0.0.6",
"description": "an irrigation style scheduler interfacing with Home Assistant", "description": "an interval reoccuring style scheduler and runner",
"type": "module",
"main": "src/index.js", "main": "src/index.js",
"scripts": { "scripts": {
"start": "node ./example/scheduler.js", "example:sch": "nodemon -r esm examples/schedule.js",
"dev": "UCI_ENV=dev nodemon -r esm ./", "example:que": "nodemon -r esm examples/queue.js"
"example:sch": "nodemon examples/schedule.js",
"example:que": "nodemon examples/queue.js",
"dev:debug": "UCI_ENV=dev UCI_LOG_LEVEL=debug nodemon -r esm ./"
}, },
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"await-to-js": "^2.1.1", "await-to-js": "^2.1.1",
"clone": "^2.1.2", "clone": "^2.1.2",
"esm": "^3.2.25",
"moment": "^2.26.0", "moment": "^2.26.0",
"moment-duration-format": "^2.3.2" "moment-duration-format": "^2.3.2"
}, },
"devDependencies": { "devDependencies": {
"esm": "^3.2.25",
"nodemon": "^1.19.1" "nodemon": "^1.19.1"
} }
} }

6
src/index.js Normal file
View File

@ -0,0 +1,6 @@
import Schedule from './schedule.js'
import Runner from './runner.js'
export { Schedule, Runner }

View File

@ -2,33 +2,33 @@ import clone from 'clone'
import { EventEmitter } from 'events' import { EventEmitter } from 'events'
// Queue Multiple Schedulers // Queue Multiple Schedulers
class Queue extends EventEmitter { class UCIScheduleRunner extends EventEmitter {
constructor(opts) { constructor(opts) {
super(opts) super(opts)
this.name = opts.name || 'queue' this.name = opts.name || 'queue'
this.queue = [] this.schedules = []
if (opts.schedules) opts.schedules.forEach(sch => this.add(sch)) if (opts.schedules) opts.schedules.forEach(sch => this.addSchedule(sch))
this.one = opts.one this.one = opts.one
this._active = {} // list of active schedules organized by timeout
this._toID = 0 // id of timeout this._toID = 0 // id of timeout
this._delayed = [] this._delayed = []
this._activeCount = 0 this._activeCount = 0
this._active = {} // object or lists of active schedules by timeout id this._active = {} // object or lists of active schedules by timeout id
this._pausedSchedules = []
this.init() this.init()
} }
get countdown() { get countdown() {
return (this.queue[0]||{}).countdown return (this.schedules[0]||{}).countdown
} }
get names(){ get names(){
return this.queue.map(sch => { return this.schedules.map(sch => {
return {name: sch.name, countdown: sch.countdown, next: sch.nextDT, simultaneous:sch.simultaneous} return {name: sch.name, countdown: sch.countdown, next: sch.nextDT, simultaneous:sch.simultaneous}
}) })
} }
get nextName(){ get nextName(){
return (this.queue[0]||{}).name return (this.schedules[0]||{}).name
} }
init() { init() {
@ -44,14 +44,14 @@ class Queue extends EventEmitter {
setTimeout () { setTimeout () {
// clearTimeout(this._timeout) // clearTimeout(this._timeout)
const next = this.queue[0] || {} const next = this.schedules[0] || {}
this._toID++ this._toID++
console.log('+++++++++++++++++++++ next timeout',this._toID, 'in', Math.round(next.countdownMS/1000),'++++++++++++++++++++++++++') console.log('+++++++++++++++++++++ next timeout',this._toID, 'in', Math.round(next.countdownMS/1000),'++++++++++++++++++++++++++')
this._timeout = setTimeout(() => { this._timeout = setTimeout(() => {
console.log('**********************timeout triggered', this._toID, '********************************') console.log('**********************timeout triggered', this._toID, '********************************')
let dur = 0 let dur = 0
// don't mutate original queue, make copy for active list // don't mutate original queue, make copy for active list
const queue = clone(this.queue) const queue = clone(this.schedules)
this._active[this._toID]=[] this._active[this._toID]=[]
queue.forEach(sch =>{ // add first and any others set for same time queue.forEach(sch =>{ // add first and any others set for same time
dur += sch.durationMS dur += sch.durationMS
@ -66,21 +66,17 @@ class Queue extends EventEmitter {
},next.countdownMS) },next.countdownMS)
} }
stop () { stop (now) {
console.log('---------------------stopping queue, active schedules will complete--------------------------') console.log('---------------------stopping runner------------------------')
console.log(this._timeout)
clearTimeout(this._timeout) clearTimeout(this._timeout)
} if (now) {
console.log('also stopping all in progress schedules')
kill () { for (let list in this._active) {
console.log('---------------------stopping queue, active schedules will be terminated as well--------------------') this._active[list].forEach(sch=>{
clearTimeout(this._timeout) clearTimeout(sch._stopTimeout)
// console.log(this._active) sch.stopAction()}
for (let list in this._active) { )
this._active[list].forEach(sch=>{ }
clearTimeout(sch._stopTimeout)
sch.stopAction()}
)
} }
} }
@ -112,16 +108,17 @@ class Queue extends EventEmitter {
} }
get nextTS() { get nextTS() {
return (this.queue[0]||{}).nextTS return (this.schedules[0]||{}).nextTS
} }
get nextDT(){ get nextDT(){
return (this.queue[0]||{}).nextDT return (this.schedules[0]||{}).nextDT
} }
add (sch) { // schedule instance
addSchedule (sch) {
if (getBaseClass(sch) === 'UCISchedule') { if (getBaseClass(sch) === 'UCISchedule') {
this.queue.push(sch) this.schedules.push(sch)
this.sort() this.sort()
} }
else { else {
@ -130,20 +127,41 @@ class Queue extends EventEmitter {
} }
} }
// schedule id
removeSchedule (id) { removeSchedule (id) {
this.queue = this.queue.filter(a => a.id !== id) this.schedules = this.schedules.filter(a => a.id !== id)
} }
getSchedule (id) { getSchedule (id) {
return this.queue.find(a => a.id === id) return this.schedules.find(a => a.id === id)
} }
updateSchedule (id) { updateSchedule (id) {
this.getSchedule(id).update() this.getSchedule(id).update()
} }
enableSchedule(id,state) {
if (state == null) state = true
if (state) {
const sch = this._pausedSchedules.find(a => a.id === id)
if (sch) {
this.addSchedule(sch)
sch.enabled = true
sch.emit('enabled',true)
}
} else {
const sch = this.schedules.find(a => a.id === id)
if (sch) {
this._pausedSchedules.push(sch)
this.removeSchedule(id)
sch.enabled = false
sch.emit('enabled',false)
}
}
}
update () { update () {
for(let sch of this.queue) { for(let sch of this.schedules) {
// console.log('updating', sch.name) // console.log('updating', sch.name)
sch.update() sch.update()
} }
@ -151,12 +169,12 @@ class Queue extends EventEmitter {
} }
sort () { sort () {
this.queue.sort((a,b) => a.nextTS - b.nextTS) this.schedules.sort((a,b) => a.nextTS - b.nextTS)
} }
} }
export default Queue export default UCIScheduleRunner
export {Queue} export { UCIScheduleRunner as Runner }
function start (sch, toid) { function start (sch, toid) {
toid = sch.toid || toid toid = sch.toid || toid

View File

@ -13,30 +13,23 @@ class UCISchedule extends EventEmitter {
this.id = opts.id || this.name.replace(/ /g, '_') this.id = opts.id || this.name.replace(/ /g, '_')
this.desc = opts.desc this.desc = opts.desc
this.hour = opts.hour || 0 this.hour = opts.hour || 0
this.dev = opts.dev || process.env.UCI_ENV==='dev' this.dev = opts.dev
this.minute = opts.minute || 0 this.minute = opts.minute || 0
this.delta = opts.delta || 6 // time to next trigger in hours this.delta = opts.delta || 6 // time to next trigger in hours
this.duration = opts.duration || 10 // in minutes this.duration = opts.duration || 10 // in minutes
// computed values // computed values
this.nextTS = 0 // the next trigger time in seconds this.nextTS = 0 // the next trigger time in seconds
this.enabled = opts.enabled || true this.enabled = opts.enabled || true
this.active = false
// this.active = false // if schedule is currently active // this.active = false // if schedule is currently active
// this.on('active',active=>this.active=active) // this.on('active',active=>this.active=active)
this.simultaneous = opts.simulanteous // if true delay this scheduled event until current scheduled event completes this.simultaneous = opts.simulanteous // if true delay this scheduled event until current scheduled event completes
// this.lastActiveTS // this.lastActiveTS
this._startAction = opts.startAction || defaultStartAction.bind(this) // single (or array) of function(s) this._startAction = opts.startAction || defaultStartAction.bind(this) // single (or array) of function(s)
this._stopAction = opts.stopAction || defaultStopAction.bind(this) // single (or array) of function(s) this._stopAction = opts.stopAction || defaultStopAction.bind(this) // single (or array) of function(s)
this.update()
} }
// async readyWait () {
// return new Promise( resolve => {
// if (!this.active) {
// resolve()
// return}
// this.once('active',active=>{if (!active) resolve()})
// })
// }
get countdown() { get countdown() {
return moment.duration(parseInt((this.nextTS - Math.floor(Date.now()/1000))),'seconds').format('hh:mm:ss') return moment.duration(parseInt((this.nextTS - Math.floor(Date.now()/1000))),'seconds').format('hh:mm:ss')
} }
@ -65,6 +58,7 @@ class UCISchedule extends EventEmitter {
// } // }
update() { // all TS in seconds update() { // all TS in seconds
// console.log('updating',this.hour,this.minute,this.delta, this.dev)
let baseTS = this.hour*3600+this.minute*60 let baseTS = this.hour*3600+this.minute*60
let dt = new Date() let dt = new Date()
let intoDayTS = (dt.getSeconds() + (60 * dt.getMinutes()) + (60 * 60 * dt.getHours())) let intoDayTS = (dt.getSeconds() + (60 * dt.getMinutes()) + (60 * 60 * dt.getHours()))
@ -72,7 +66,7 @@ class UCISchedule extends EventEmitter {
this.nextTS = nowTS - intoDayTS + baseTS this.nextTS = nowTS - intoDayTS + baseTS
if (!this.dev) { if (!this.dev) {
while (nowTS > this.nextTS) { while (nowTS > this.nextTS) {
// console.log(`now ${nowTS} is beyond next ${this.nextTS} adding delta ${this.delta} hours`) // console.log(`now ${nowTS} is beyond next ${this.nextTS} adding delta ${this.delta} hours`)
this.nextTS += this.delta * 3600 this.nextTS += this.delta * 3600
} }
} else this.nextTS = nowTS + this.delta } else this.nextTS = nowTS + this.delta
@ -81,20 +75,25 @@ class UCISchedule extends EventEmitter {
} }
registerStartAction(func) { registerStartAction(func) {
if (!Array.isArray(func)) func = [func] //
this.startAction = func // if (!Array.isArray(func)) func = [func]
this._startAction = func
} }
registerStopAction(func) { registerStopAction(func) {
if (!Array.isArray(func)) func = [func] // if (!Array.isArray(func)) func = [func]
this.stopAction = func this._stopAction = func
} }
// todo support a array of functions
async startAction (data) { async startAction (data) {
this.active = true
this.on('active',true)
this._startAction(data) this._startAction(data)
} }
async stopAction (data) { async stopAction (data) {
this.active = false
this.on('active',false)
this._stopAction(data) this._stopAction(data)
} }