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",
"version": "0.0.4",
"description": "an irrigation style scheduler interfacing with Home Assistant",
"type": "module",
"version": "0.0.6",
"description": "an interval reoccuring style scheduler and runner",
"main": "src/index.js",
"scripts": {
"start": "node ./example/scheduler.js",
"dev": "UCI_ENV=dev nodemon -r esm ./",
"example:sch": "nodemon examples/schedule.js",
"example:que": "nodemon examples/queue.js",
"dev:debug": "UCI_ENV=dev UCI_LOG_LEVEL=debug nodemon -r esm ./"
"example:sch": "nodemon -r esm examples/schedule.js",
"example:que": "nodemon -r esm examples/queue.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"await-to-js": "^2.1.1",
"clone": "^2.1.2",
"esm": "^3.2.25",
"moment": "^2.26.0",
"moment-duration-format": "^2.3.2"
},
"devDependencies": {
"esm": "^3.2.25",
"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'
// Queue Multiple Schedulers
class Queue extends EventEmitter {
class UCIScheduleRunner extends EventEmitter {
constructor(opts) {
super(opts)
this.name = opts.name || 'queue'
this.queue = []
if (opts.schedules) opts.schedules.forEach(sch => this.add(sch))
this.schedules = []
if (opts.schedules) opts.schedules.forEach(sch => this.addSchedule(sch))
this.one = opts.one
this._active = {} // list of active schedules organized by timeout
this._toID = 0 // id of timeout
this._delayed = []
this._activeCount = 0
this._active = {} // object or lists of active schedules by timeout id
this._pausedSchedules = []
this.init()
}
get countdown() {
return (this.queue[0]||{}).countdown
return (this.schedules[0]||{}).countdown
}
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}
})
}
get nextName(){
return (this.queue[0]||{}).name
return (this.schedules[0]||{}).name
}
init() {
@ -44,14 +44,14 @@ class Queue extends EventEmitter {
setTimeout () {
// clearTimeout(this._timeout)
const next = this.queue[0] || {}
const next = this.schedules[0] || {}
this._toID++
console.log('+++++++++++++++++++++ next timeout',this._toID, 'in', Math.round(next.countdownMS/1000),'++++++++++++++++++++++++++')
this._timeout = setTimeout(() => {
console.log('**********************timeout triggered', this._toID, '********************************')
let dur = 0
// don't mutate original queue, make copy for active list
const queue = clone(this.queue)
const queue = clone(this.schedules)
this._active[this._toID]=[]
queue.forEach(sch =>{ // add first and any others set for same time
dur += sch.durationMS
@ -66,21 +66,17 @@ class Queue extends EventEmitter {
},next.countdownMS)
}
stop () {
console.log('---------------------stopping queue, active schedules will complete--------------------------')
console.log(this._timeout)
stop (now) {
console.log('---------------------stopping runner------------------------')
clearTimeout(this._timeout)
}
kill () {
console.log('---------------------stopping queue, active schedules will be terminated as well--------------------')
clearTimeout(this._timeout)
// console.log(this._active)
for (let list in this._active) {
this._active[list].forEach(sch=>{
clearTimeout(sch._stopTimeout)
sch.stopAction()}
)
if (now) {
console.log('also stopping all in progress schedules')
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() {
return (this.queue[0]||{}).nextTS
return (this.schedules[0]||{}).nextTS
}
get nextDT(){
return (this.queue[0]||{}).nextDT
return (this.schedules[0]||{}).nextDT
}
add (sch) {
// schedule instance
addSchedule (sch) {
if (getBaseClass(sch) === 'UCISchedule') {
this.queue.push(sch)
this.schedules.push(sch)
this.sort()
}
else {
@ -130,20 +127,41 @@ class Queue extends EventEmitter {
}
}
// schedule id
removeSchedule (id) {
this.queue = this.queue.filter(a => a.id !== id)
this.schedules = this.schedules.filter(a => a.id !== id)
}
getSchedule (id) {
return this.queue.find(a => a.id === id)
return this.schedules.find(a => a.id === id)
}
updateSchedule (id) {
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 () {
for(let sch of this.queue) {
for(let sch of this.schedules) {
// console.log('updating', sch.name)
sch.update()
}
@ -151,12 +169,12 @@ class Queue extends EventEmitter {
}
sort () {
this.queue.sort((a,b) => a.nextTS - b.nextTS)
this.schedules.sort((a,b) => a.nextTS - b.nextTS)
}
}
export default Queue
export {Queue}
export default UCIScheduleRunner
export { UCIScheduleRunner as Runner }
function start (sch, toid) {
toid = sch.toid || toid

View File

@ -13,30 +13,23 @@ class UCISchedule extends EventEmitter {
this.id = opts.id || this.name.replace(/ /g, '_')
this.desc = opts.desc
this.hour = opts.hour || 0
this.dev = opts.dev || process.env.UCI_ENV==='dev'
this.dev = opts.dev
this.minute = opts.minute || 0
this.delta = opts.delta || 6 // time to next trigger in hours
this.duration = opts.duration || 10 // in minutes
// computed values
this.nextTS = 0 // the next trigger time in seconds
this.enabled = opts.enabled || true
this.active = false
// this.active = false // if schedule is currently active
// this.on('active',active=>this.active=active)
this.simultaneous = opts.simulanteous // if true delay this scheduled event until current scheduled event completes
// this.lastActiveTS
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.update()
}
// async readyWait () {
// return new Promise( resolve => {
// if (!this.active) {
// resolve()
// return}
// this.once('active',active=>{if (!active) resolve()})
// })
// }
get countdown() {
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
// console.log('updating',this.hour,this.minute,this.delta, this.dev)
let baseTS = this.hour*3600+this.minute*60
let dt = new Date()
let intoDayTS = (dt.getSeconds() + (60 * dt.getMinutes()) + (60 * 60 * dt.getHours()))
@ -72,7 +66,7 @@ class UCISchedule extends EventEmitter {
this.nextTS = nowTS - intoDayTS + baseTS
if (!this.dev) {
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
}
} else this.nextTS = nowTS + this.delta
@ -81,20 +75,25 @@ class UCISchedule extends EventEmitter {
}
registerStartAction(func) {
if (!Array.isArray(func)) func = [func]
this.startAction = func
//
// if (!Array.isArray(func)) func = [func]
this._startAction = func
}
registerStopAction(func) {
if (!Array.isArray(func)) func = [func]
this.stopAction = func
// if (!Array.isArray(func)) func = [func]
this._stopAction = func
}
// todo support a array of functions
async startAction (data) {
this.active = true
this.on('active',true)
this._startAction(data)
}
async stopAction (data) {
this.active = false
this.on('active',false)
this._stopAction(data)
}