0.0.6 improvements and fixes while working on irrigation project
parent
67955d3260
commit
2f6901e7ae
|
@ -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)
|
14
package.json
14
package.json
|
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
import Schedule from './schedule.js'
|
||||||
|
import Runner from './runner.js'
|
||||||
|
|
||||||
|
export { Schedule, Runner }
|
||||||
|
|
||||||
|
|
|
@ -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
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue