diff --git a/README.md b/README.md new file mode 100644 index 0000000..a21e147 --- /dev/null +++ b/README.md @@ -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) diff --git a/package.json b/package.json index cdefeac..1172664 100644 --- a/package.json +++ b/package.json @@ -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" } } diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..e40fe03 --- /dev/null +++ b/src/index.js @@ -0,0 +1,6 @@ +import Schedule from './schedule.js' +import Runner from './runner.js' + +export { Schedule, Runner } + + diff --git a/src/queue.js b/src/runner.js similarity index 72% rename from src/queue.js rename to src/runner.js index f21f36c..28aa2c3 100644 --- a/src/queue.js +++ b/src/runner.js @@ -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 diff --git a/src/schedule.js b/src/schedule.js index 4ec3bf4..0e5c2bf 100644 --- a/src/schedule.js +++ b/src/schedule.js @@ -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) }