From 48b6680585d6fea6dd0ee71a803d8af0df106b09 Mon Sep 17 00:00:00 2001 From: David Kebler Date: Mon, 6 Jan 2020 23:26:12 -0800 Subject: [PATCH] 0.2.24 interrupts: better integration with single interrupt methods via common function better event bubble up from single interrupt allow access to methods via a socket if enabled (same with single) allow consumer reply access to reset method (same with single) Interrupt: improve handling of interval reset with new method --- examples/multi.js | 38 +++++++-------------- package.json | 2 +- src/interrupt.js | 87 +++++++++++++++++++++++++++-------------------- src/interrupts.js | 34 ++++++++++-------- 4 files changed, 83 insertions(+), 78 deletions(-) diff --git a/examples/multi.js b/examples/multi.js index 73c5ba0..7fa12d1 100644 --- a/examples/multi.js +++ b/examples/multi.js @@ -1,43 +1,29 @@ import Interrupts from '../src/interrupts' -const PINS = [4] +const PINS = [9,10] const EDGE = process.env.EDGE || 'both' const REMOTE_HOST = process.env.REMOTE_HOST || 'sbc' -const REMOTE_PORT = process.env.REMOTE_PORT || 9000 -const PORT = process.env.PORT || 9004 +const REMOTE_PORT = process.env.REMOTE_PORT || 9001 -let interrupts = new Interrupts(PINS,{id:'multi-interrupt-example', resetInterval:1, resetEnabled:false, edge:EDGE, 4:{name:'mybutton'} }) +let interrupts = new Interrupts(PINS, { id:'multi-interrupt', cmd:'interrupt.find', resetCmd:'interrupt.reset', resetInterval:0, edge:EDGE }) -// let hook = function (packet) -// { -// packet.cmd = 'interrupt.find' -// return packet -// } - -// interrupts.registerHook(hook) +interrupts.amendConsumerCommands( {reply:() => {}} ) interrupts.on('log', ev => { - console.log(`LOG:'--${ev.level}--" ${ev.msg}`)} -) - -interrupts.on('connection:socket', ev => { - // console.dir(ev) - console.log(`connected to remote socket ${ev.socketName}: ${ev.state}`) + if (ev.level !== 'debug' && ev.level !== 'trace') console.log(`LOG:'--${ev.level}-- ${ev.msg}`) + // console.log(`LOG:'--${ev.level}-- ${ev.msg}`) +}) + +interrupts.on('connection:socket', ev => { + console.log(`remote socket connection event ${ev.socketName}: ${ev.state}`) }) -// -// interrupts.listenReset(function (packet) { -// console.log(`============== ${this.id}=========`) -// console.log('an interrupt reset request emitted') -// console.dir(packet) -// console.log('------------------------') -// }) ; (async () => { - interrupts.registerSocket('inter','c','t',{host:REMOTE_HOST, port:REMOTE_PORT}) - interrupts.registerSocket('inters','s','t',{port:PORT}) + interrupts.registerSocket('interrupt','c','t',{host:REMOTE_HOST, port:REMOTE_PORT, name:'interrupt', data:{pins:PINS, type:'interrupt'}}) + // interrupts.registerSocket('listen','s','t',{port:1777}) await interrupts.init() await interrupts.socketsInit() diff --git a/package.json b/package.json index 5c7247b..3f16b0f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@uci/interrupt", "main": "src", - "version": "0.2.23", + "version": "0.2.24", "description": "a class for adding interrupt processesing for gpio pins on Raspberry Pi and Similar SBCs", "scripts": { "single": "node -r esm examples/single", diff --git a/src/interrupt.js b/src/interrupt.js index 71947a0..6ed9bbe 100644 --- a/src/interrupt.js +++ b/src/interrupt.js @@ -17,16 +17,15 @@ class Interrupt extends Base { log.debug({ pins: pin, opts: opts, method:'constructor', line:16, msg:'created interrupt with these opts'}) this.pin_num = pin this.resetCmd = opts.resetCmd || 'interrupt.reset' - this.resetEnabled = opts.reset|| opts. resetEnabled - this.resetInterval = opts.resetInterval * 1000 // sets an interval timeout to check on status and send/emit reset command + this.resetInterval = opts.resetInterval this.mock = opts.mock || process.env.MOCK this.wait = opts.wait || 0 // debounce is off by default // https://github.com/fivdi/onoff#gpiogpio-direction--edge--options this.edge = opts.edge || 'rising' // falling,both,none=no interrupt - // pull down/up (down is default) can't be set here it is done by in DTOs or in RPI in config.txt - // this is only used to monitor the status of the interrupt + // pull down/up (down is default) can't be set here + // it is done by in DTOs or in RPI in config.txt or via an external pullup/down resistor + // this setting is only needed to monitor the state of the ready interrupt and should match what is set in hardware this.pull = opts.pull || 'down' - this.__ready = this.edge === 'both' ? true : false // true is interrupt is ready this.pin = {} this.count = 0 this.packet = opts.packet || {} @@ -34,12 +33,13 @@ class Interrupt extends Base { this.packet.pin = this.pin_num this.packet.cmd = this.packet.cmd || opts.cmd || opts.interruptCmd || 'interrupt' this.packet.count = this.count - this.commands = { - fire:this.fire.bind(this), + this.amendConsumerCommands({reset:this.reset.bind(this)}) + this.amendSocketCommands({ + reset:this.reset.bind(this), status:this.status.bind(this), - reset: this.reset.bind(this) - } - this.addNamespace('commands', 's') // give access to these commands above if a socket/server is created + fire:this.fire.bind(this), + intervalReset:this.intervalReset.bind(this) + }) } // end constructor async init() { @@ -48,11 +48,9 @@ class Interrupt extends Base { // TODO devel mock versions for testing on other than sbc with gpios this.pin = new Gpio(this.pin_num, 'in', this.edge, { debounceTimeout:this.wait }) - // if (this.resetEnabled) log.debug({msg:'initial connect interrupt reset packet sent', ready:await this.reset(), method:'init', line:53}) - // if (this.resetInterval && this.resetEnabled) setInterval(this.reset.bind(this),this.resetInterval) - DeadJim( (signal,err) => { log.warn({signal:signal, method:'init', line:56, error:err, msg:'Interrupt process was killed, remove watchers, unexport'}) + clearInterval(this._intervalReset) this.pin.unwatchAll() this.pin.unexport() // kill the kernel entry }) @@ -63,8 +61,9 @@ class Interrupt extends Base { this._interruptProcess(value,err) }.bind(this)) - log.info({msg:'new interrupt pin created and watching', method:'init', line: 62, pin_num:this.pin_num, state:await this.status(), ready:this.__ready, edge:this.edge,debounce:this.wait}) + await this.intervalReset(this.resetInterval) + this.emit('log',{level:'info', msg:`new interrupt pin ${this.pin_num} created and watching`, state:await this.status(), edge:this.edge,debounce:this.wait}) } // end init @@ -74,40 +73,54 @@ class Interrupt extends Base { await this._interruptProcess(1) packet.status = 'fired' packet.pin = this.pin_num - packet.cmd = 'reply' return packet } // returns true if pin is ready and waiting to trigger interrupt async status(packet) { - let status = await this.pin.read() - if (this.edge !=='both') this.__ready = this.pull==='down' ? !status : !!status // ready is always true for 'both' + let state = await this.pin.read() + let ready = this.pull==='down' ? !state : !!state // ready is always true for 'both' if (packet) { packet.pin = this.pin_num - packet.state = status - if (this.edge !=='both') packet.ready = this.__ready - packet.cmd = 'reply' + packet.state = state + if (this.edge !=='both') packet.ready = ready return packet } - return status + return this.edge ==='both' ? state : ready } - async reset(packet) { + async intervalReset(packet) { + // console.log('intervalReset ---- passed argument',packet) + let interval = typeof packet === 'number'? packet : (packet || {}).interval + console.log('----------intervalReset--------', this.pin_num, interval) + if (!interval || interval<=0) { + clearInterval(this._intervalReset) + this._intervalReset = null + console.log('---------disabled--------') + } + else { + this._intervalReset = setInterval(this.reset.bind(this), interval*1000) + console.log('----------enabled----------') + } + return this._intervalReset ? true : false + } + + async reset(packet={}) { let res = {} - if (this.edge !=='both' && this.resetEnabled) { - if (!this.__ready) { - let reset = Object.assign({},this.packet) - reset.cmd = this.resetCmd - this.emit(this.resetCmd,reset) // emit locally - await this.send(reset) - await this.status() - log.error({msg: `interrupt was forced reset. ready now? ${this.__ready}`}) - res = {cmd:'reply', msg:`attempted interrupt reset ${this.__ready? 'succeeded' : 'failed'}`, reset:true, ready:this.__ready} + this.emit('log',{level:'info', msg:`interrupt reset request for pin ${this.pin_num}`}) + if (this.edge ==='both') res = {level:'info', reset:false, ready:true, msg:'interrupt triggered on rising and falling, no reset action needed'} + else { + if(!await this.status()) { + delete packet._header + this.emit('reset',packet) // emit locally + packet.cmd = this.resetCmd + let state = await this.status() + res = {level:state?'debug':'error', msg:`attempted interrupt reset of pin ${this.pin_num} ${state ? 'succeeded' : 'failed'}`, reset:true, ready:state} } - else res = {cmd:'reply', reset:false, ready:true, msg:'interrupt was ready, no action taken'} - } else res = {cmd:'reply', reset:false, ready:true, msg:'reset NA or disabled'} - if (packet) return Object.assign(packet,res) - return this.__ready + else res = {level:'info', reset:false, ready:true, msg:`pin ${this.pin_num} interrupt was ready, no action taken`} + } + this.emit('log',res) + return res } // use hook to do more processing @@ -121,9 +134,9 @@ class Interrupt extends Base { packet.timeStamp = Date.now() packet.dateTime = new Date().toString() if (this._hookFunc) packet = await this._hookFunc.call(this,packet) - log.debug({packet: packet, msg:'interrupt tripped, emit/send packet to all connected/listening'}) + this.emit('log',{packet: packet, msg:`interrupt tripped for pin ${this.pin_num}, emit/send packet to all listening`}) this.emit('interrupt',packet) // emit locally - this.send(packet) // will send packet via client to any connected socket + this.send(packet) // no need to await reply } // replace default processor function arguments are value of pin and any error diff --git a/src/interrupts.js b/src/interrupts.js index 0041062..8a6a181 100644 --- a/src/interrupts.js +++ b/src/interrupts.js @@ -15,12 +15,13 @@ class Interrupts extends Base { this._interrupts = new Map() this.resetCmd = opts.resetCmd || 'interrupt.reset' log = logger({ name: 'interrupts', id: this.id, package:'@uci/interrupt', file:'src/interrupts.js'}) - this.commands = { - fire:makefunc.bind(this,'fire'), - status:makefunc.bind(this,'status'), + this.amendConsumerCommands({reset:makefunc.bind(this,'reset')}) + this.amendSocketCommands({ reset:makefunc.bind(this,'reset'), - } - this.addNamespace('commands', 's') // give access to these commands above if a socket/server is created + status:makefunc.bind(this,'status'), + fire:makefunc.bind(this,'fire'), + intervalReset:makefunc.bind(this,'intervalReset') + }) let pinopts = {} pins.forEach(pin => { // remove per pin opts and store @@ -33,8 +34,10 @@ class Interrupts extends Base { log.debug({ opts: pinopts[pin], method:'constructor', line:25, msg:`pin options for pin ${pin}`}) this._interrupts.set(pin, new Interrupt(pin, pinopts[pin])) + // TODO add socket and add methods to namespace if set + // bubble up events from single interrupts - const EVENTS=['log','connection','connection:consumer', 'connection:socket'] // that should emit up from pin base + const EVENTS=['log','connection:consumer', 'connection:socket'] // that should emit up from pin base EVENTS.forEach(event => { this.interrupt(pin).on(event, obj => { if (Object.prototype.toString.call(obj) !== '[object Object]') { @@ -51,13 +54,15 @@ class Interrupts extends Base { this._interrupts.forEach( inter => { inter.on('interrupt', packet => { - this.send(packet) // send via common consumer this.emit('interrupt',packet) }) - inter.on(this.resetCmd, packet => { - this.send(packet) // send via common consumer - this.emit(this.resetCmd,packet) + inter.on('reset', packet => { + this.emit('reset',packet) }) + // overwrite single interrupt send + inter.send = async (packet) => { + return await this.send(packet) // send via common consumer + } }) } // end constructor @@ -65,6 +70,7 @@ class Interrupts extends Base { interrupt(pin) { return this._interrupts.get(Number(pin)) } // get a handle to single interrupt async init() { + return Promise.all( Array.from(this._interrupts).map(inter => { return inter[1].init() @@ -94,13 +100,13 @@ class Interrupts extends Base { export default Interrupts -async function makefunc(fn, packet) { +async function makefunc(fn, packet={}) { if (!packet.pin || packet.pin==='all') { + let res = {} for (let inter of this._interrupts.entries()) { - packet[inter[0]] = await inter[1][fn]() + res[inter[0]] = await inter[1][fn](packet) } - packet.cmd='reply' - return packet + return Object.assign(packet,res) } let pin = isNaN(Number(packet)) ? packet.pin : packet if (this._interrupts.has(Number(pin))) return await this.interrupt(packet.pin)[fn](packet)