interrupts class
extends base on interrupts class
change collection of individual interrupts to a Map
method: listenReset, for setting handler for emitted reset of each single interrupt
method: addInterSocket, to add identical consumer socket to each interrupt
interrupt class
add resetEnabled option
switch default edge to 'both'
refactor status method
refactor reset method
No hook by default, registerHook without argument sets the default hook
refactored examples

update deps
master
David Kebler 2019-08-15 14:05:30 -07:00
parent dbd8ef3347
commit 01d2ebd3a1
8 changed files with 234 additions and 121 deletions

View File

@ -1,9 +1,9 @@
import Base from '@uci/base' import Base from '@uci/base'
// const HOST = 'localhost' // const HOST = 'localhost'
const HOST = 'sbc' const HOST = process.env.HOST || 'localhost'
const PORT = 9024 const PORT = process.env.PORT // default 8080
let processor = new Base({sockets:'inter#c>t', inter:{host:HOST, port:PORT}, id:'interrupt-processor', useRootNS:true}) let processor = new Base({ id:'remote-interrupt-processor', useRootNS:true})
processor.interrupt = async function (packet) { processor.interrupt = async function (packet) {
return new Promise((resolve) => { return new Promise((resolve) => {
@ -15,21 +15,31 @@ processor.interrupt = async function (packet) {
processor.reply = async function (packet) { processor.reply = async function (packet) {
return new Promise((resolve) => { return new Promise((resolve) => {
console.log('reply from interrupt for request', packet._header.request)
console.log('reply from interrupt with packet')
console.dir(packet) console.dir(packet)
resolve({status: 'processed'}) resolve({status: 'processed'})
}) })
} }
processor.on('status', ev => {
console.log(`STATUS: **${ev.level}** ${ev.msg}`)}
)
processor.on('consumer-connection', ev => {
console.log(`client ${ev.name} was ${ev.state}`)
})
processor.on('reconnected', client => {
console.log('client reconnected:', client)
})
; ;
(async () => { (async () => {
// processor.addSocket('inter','c','t', {host:HOST, port:PORT})
processor.addSocket('inter','c','t',{host:HOST, port:PORT})
await processor.init() await processor.init()
console.log('====sending fire command to interrupt===') console.log('====sending fire command to interrupt===')
await processor.send({cmd: 'fire'}) await processor.send({cmd: 'fire'})
console.log('====sending fire command to interrupt===')
await processor.send({cmd: 'fire'})
// process.kill(process.pid, 'SIGTERM') // process.kill(process.pid, 'SIGTERM')
})().catch(err => { })().catch(err => {

View File

@ -1,26 +1,45 @@
import Interrupts from '../src/interrupts' import Interrupts from '../src/interrupts'
import Base from '@uci/base'
const PINS = [9,10,24] const PINS = [4]
let hook = (packet) => let interrupts = new Interrupts(PINS,{id:'multi-interrupt-example', resetInterval:1, resetEnabled:false, 4:{name:'mybutton'} })
let hook = function (packet)
{ {
packet.cmd = 'pin.interrupt.find' packet.cmd = 'interrupt.find'
console.dir(packet)
return packet return packet
} }
let interrupts = new Interrupts(PINS,{hook:true, 10:{wait:200} }) // interrupts.registerHook(hook)
interrupts.setHook(hook) interrupts.on('status', ev => {
console.log(`STATUS:'--${ev.level}--" ${ev.msg}`)}
)
interrupts.on('consumer-connection', ev => {
console.log(`client ${ev.name} was ${ev.state}`)
})
interrupts.listen(function (packet) {
console.log(`============== ${this.id}=========`)
console.log(`emitted packet from interrupt ${packet.id}, pin:${packet.pin}`)
console.dir(packet)
this.push(packet)
console.log('------------------------')
})
interrupts.listenReset(function (packet) {
console.log(`============== ${this.id}=========`)
console.log('an interrupt reset request emitted')
console.dir(packet)
console.log('------------------------')
})
; ;
(async () => { (async () => {
// console.log(await listener.init()) interrupts.addSocket('server','s','t')
await interrupts.init() await interrupts.init()
interrupts.fire() // interrupts.fire()
})().catch(err => { })().catch(err => {
console.error('FATAL: UNABLE TO START SYSTEM!\n',err) console.error('FATAL: UNABLE TO START SYSTEM!\n',err)

View File

@ -2,24 +2,37 @@ import Interrupt from '../src/interrupt'
const delay = time => new Promise(res=>setTimeout(()=>res(),time)) const delay = time => new Promise(res=>setTimeout(()=>res(),time))
let interrupt = new Interrupt(24,{id:'test-interrupt', wait:0, hook:true, useRootNS:true}) const IPIN = process.env.IPIN || 4
let interrupt = new Interrupt(IPIN,{id:'test-interrupt', wait:0, edge:'rising', resetInterval:1, reset:true})
interrupt.on('interrupt', packet => {
console.log('event: interrupt fired for',interrupt.pin_num)
console.log('count:', packet.count, 'state:',packet.state, 'cmd:',packet.cmd, 'data:',packet.data)
})
interrupt.on('interrupt.reset', packet => {
console.log('interrupt reset packet sent/emitted')
console.dir(packet)
})
; ;
(async () => { (async () => {
await interrupt.init() await interrupt.init()
console.log('interrupt ready and waiting') console.log('interrupt ready and waiting')
console.log('manual fire of interrupt via interrupt instance') console.log('manual fire of interrupt with default hook')
interrupt.fire() interrupt.fire()
console.log('manual fire of interrupt via interrupt instance after changing hook') console.log('manual fire of interrupt via after changing hook')
interrupt.hook = (packet) => {
interrupt.registerHook((packet) => {
packet.data='some hook added data' packet.data='some hook added data'
console.log('custom hook function modifies', packet) console.log('custom hook data prop added:', packet.data)
return packet return packet
} })
interrupt.fire() interrupt.fire()
await delay(3000)
process.kill(process.pid, 'SIGTERM')
})().catch(err => { })().catch(err => {
console.error('FATAL: UNABLE TO START SYSTEM!\n',err) console.error('FATAL: UNABLE TO START SYSTEM!\n',err)

37
examples/socket.js Normal file
View File

@ -0,0 +1,37 @@
import Base from '@uci/base'
// const HOST = 'localhost'
const HOST = process.env.HOST || 'sbc'
const PORT = process.env.PORT
let processor = new Base({sockets:'inter#c>t', inter:{host:HOST, port:PORT}, id:'interrupt-processor', useRootNS:true})
processor.interrupt = async function (packet) {
return new Promise((resolve) => {
console.log('interrupt occured')
console.dir(packet)
resolve({status: 'processed'})
})
}
processor.reply = async function (packet) {
return new Promise((resolve) => {
console.log('reply from interrupt with packet')
console.dir(packet)
resolve({status: 'processed'})
})
}
;
(async () => {
await processor.init()
console.log('====sending fire command to interrupt===')
await processor.send({cmd: 'fire'})
console.log('====sending fire command to interrupt===')
await processor.send({cmd: 'fire'})
// process.kill(process.pid, 'SIGTERM')
})().catch(err => {
console.error('FATAL: UNABLE TO START SYSTEM!\n',err)
})

View File

@ -1,14 +1,18 @@
{ {
"name": "@uci/interrupt", "name": "@uci/interrupt",
"main": "src", "main": "src",
"version": "0.2.19", "version": "0.2.22",
"description": "a class for adding interrupt processesing for gpio pins on Raspberry Pi and Similar SBCs", "description": "a class for adding interrupt processesing for gpio pins on Raspberry Pi and Similar SBCs",
"scripts": { "scripts": {
"single": "node -r esm examples/single", "single": "node -r esm examples/single",
"single:dev": "UCI_ENV=dev ./node_modules/.bin/nodemon -r esm examples/single",
"single:debug": "UCI_LOG_LEVEL=debug npm run single:dev",
"multi": "node -r esm examples/multi",
"multi:dev": "UCI_ENV=dev ./node_modules/.bin/nodemon -r esm examples/multi",
"multi:debug": "UCI_LOG_LEVEL=debug npm run multi:dev",
"client": "node -r esm examples/client", "client": "node -r esm examples/client",
"singlelog": "UCI_ENV=dev node -r esm examples/single", "client:dev": "UCI_ENV=dev ./node_modules/.bin/nodemon -r esm examples/client",
"multi": "sudo node --require esm examples/multi", "client:debug": "UCI_LOG_LEVEL=debug npm run client:dev"
"multilog": "UCI_ENV=dev node --require esm examples/multi"
}, },
"author": "David Kebler", "author": "David Kebler",
"license": "MIT", "license": "MIT",
@ -28,17 +32,15 @@
}, },
"homepage": "https://github.com/uCOMmandIt/uci-interrrupt#readme", "homepage": "https://github.com/uCOMmandIt/uci-interrrupt#readme",
"dependencies": { "dependencies": {
"@uci-utils/logger": "0.0.14", "@uci-utils/logger": "^0.0.15",
"@uci/base": "^0.1.21", "@uci/base": "^0.1.30",
"death": "^1.1.0", "death": "^1.1.0",
"onoff": "^4.1.1" "onoff": "^4.1.4"
}, },
"devDependencies": { "devDependencies": {
"chai": "^4.1.2", "chai": "^4.2.0",
"codecov": "^3.0.0", "esm": "^3.2.25",
"esm": "^3.0.58", "mocha": "^6.2.0",
"istanbul": "^0.4.5", "nodemon": "^1.19.2"
"mocha": "^5.0.1",
"nodemon": "^1.14.3"
} }
} }

View File

@ -1,16 +1,14 @@
# uCOMmandIt Interrupt Package for SBC GPio Pins # uCOMmandIt Interrupt Package for SBC GPio Pins
<!-- find and replace the package name to match --> <!-- find and replace the package name to match -->
[![Build Status](https://img.shields.io/travis/uCOMmandIt/uci-interrupt.svg?branch=master)](https://travis-ci.org/uCOMmandIt/uci-interrupt) <!-- [![Build Status](https://img.shields.io/travis/uCOMmandIt/uci-interrupt.svg?branch=master)](https://travis-ci.org/uCOMmandIt/uci-interrupt)
[![Inline docs](http://inch-ci.org/github/uCOMmandIt/uci-interrupt.svg?branch=master)](http://inch-ci.org/github/uCOMmandIt/uci-interrupt) [![Inline docs](http://inch-ci.org/github/uCOMmandIt/uci-interrupt.svg?branch=master)](http://inch-ci.org/github/uCOMmandIt/uci-interrupt)
[![devDependencies](https://img.shields.io/david/dev/uCOMmandIt/uci-interrupt.svg)](https://david-dm.org/uCOMmandIt/uci-interrupt?type=dev) [![devDependencies](https://img.shields.io/david/dev/uCOMmandIt/uci-interrupt.svg)](https://david-dm.org/uCOMmandIt/uci-interrupt?type=dev)
[![codecov](https://img.shields.io/codecov/c/github/uCOMmandIt/uci-interrupt/master.svg)](https://codecov.io/gh/uCOMmandIt/uci-interrupt) [![codecov](https://img.shields.io/codecov/c/github/uCOMmandIt/uci-interrupt/master.svg)](https://codecov.io/gh/uCOMmandIt/uci-interrupt) -->
This module creates an instance of UCI Packect Interrupt class for a SBC gpio pin supporting interrupt via epoll (kernel gpio/export) by extending the UCI base class. This module creates an UCI Packect Interrupt classes for a single or multiple gpio pin(s) supporting interrupt via epoll (kernel gpio/export). By extending the UCI base class it allows communication to/from other processes
Each pin will create it's own socket (based on options pased) By default it will create a tcp socket at 9000+pin number. I can create a socket for all transports. By default there are NO sockets created. You can create them as you need. Every interrupt thown will emit a packet as well as send and push that UCI packet if any sockets are associated with that interrupt.
When a gpio pin so set up is tripped this class pushes a UCI packet to all connected consumers.
You can pass a custom packet to push via the options and/or ammend the basic packet via a hook you provide. You can pass a custom packet to push via the options and/or ammend the basic packet via a hook you provide.
@ -30,11 +28,11 @@ Give `gpio` group permission to reading/writing from/to pins. On raspbien insta
SUBSYSTEM=="gpio*", PROGRAM="/bin/sh -c 'find -L /sys/class/gpio/ -maxdepth 2 -exec chown root:gpio {} \; -exec chmod 770 {} \; || true'" SUBSYSTEM=="gpio*", PROGRAM="/bin/sh -c 'find -L /sys/class/gpio/ -maxdepth 2 -exec chown root:gpio {} \; -exec chmod 770 {} \; || true'"
``` ```
if you get permission errors then likely this rule is not effective. Take a look at /sys/class/gpio folder and see if gpio group has been added appropriately (e.g. to /export and /unexport). if you get permission errors then likely this rule is not effective. Take a look at /sys/class/gpio folder and see if gpio group has been added appropriately (e.g. to /export and /unexport).
### Set hardware pull ### Set hardware pull
You must tell the gpio bus hardware about which pins you want to use as interrupts and what pull state you want. This only needs to be done once and is somewhat specific to the sbc you are using. Will need to do this with DTOs device tree overlays (except for Rasberry Pi)
#### Raspberry Pi #### Raspberry Pi
For raspberry pi with recent distro/kernel (e.g. Ubuntu 18.04,mainline 4.15) you can use add a line to config.txt in /boot or subdir therein. Add a line like this For raspberry pi with recent distro/kernel (e.g. Ubuntu 18.04,mainline 4.15) you can use add a line to config.txt in /boot or subdir therein. Add a line like this

View File

@ -10,12 +10,13 @@ let log = logger({package:'@uci/interrupt', file:'/src/interrupt.js'})
// conPacket is for connecting consumers. This will send this conPacket command on connect, which may needed to initilize something on related hardware // conPacket is for connecting consumers. This will send this conPacket command on connect, which may needed to initilize something on related hardware
class Interrupt extends Base { class Interrupt extends Base {
constructor(pin, opts = {}) { constructor(pin, opts = {}) {
if (typeof pin !=='number') pin = parseInt(pin) // make sure pin is a number! pin = Number(pin) // make sure pin is a number!
super(opts) super(opts)
this.id = (opts.id || 'interrupt') + ':' + pin this.id = opts.name || (opts.id || 'interrupt') + ':' + pin
log.debug({ pins: pin, opts: opts, method:'constructor', line:16, msg:'created interrupt with these opts'}) log.debug({ pins: pin, opts: opts, method:'constructor', line:16, msg:'created interrupt with these opts'})
this.pin_num = pin this.pin_num = pin
this.resetCmd = opts.resetCmd || 'interrupt.reset' 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 * 1000 // sets an interval timeout to check on status and send/emit reset command
this.mock = opts.mock || process.env.MOCK this.mock = opts.mock || process.env.MOCK
this.wait = opts.wait || 0 // debounce is off by default this.wait = opts.wait || 0 // debounce is off by default
@ -24,15 +25,14 @@ class Interrupt extends Base {
// pull down/up (down is default) can't be set here it is done by in DTOs or in RPI in config.txt // 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 // this is only used to monitor the status of the interrupt
this.pull = opts.pull || 'down' this.pull = opts.pull || 'down'
this.ready = false // true is interrupt is ready this.ready = this.edge === 'both' ? true : false // true is interrupt is ready
this.pin = {} this.pin = {}
this.hook = opts.hook this.count = 0
this.packet = opts.packet || {} this.packet = opts.packet || {}
this.packet.id = this.id this.packet.id = this.id
this.packet.pin = this.pin_num this.packet.pin = this.pin_num
this.packet.cmd = this.packet.cmd || opts.cmd || opts.interruptCmd || 'interrupt' this.packet.cmd = this.packet.cmd || opts.cmd || opts.interruptCmd || 'interrupt'
this.packet.count = 0 this.packet.count = this.count
this._hookFunc = defaultHook
this.commands = { this.commands = {
fire:this.fire.bind(this), fire:this.fire.bind(this),
status:this.status.bind(this), status:this.status.bind(this),
@ -49,32 +49,28 @@ class Interrupt extends Base {
// TODO devel mock versions for testing on other than sbc with gpios // 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 }) this.pin = new Gpio(this.pin_num, 'in', this.edge, { debounceTimeout:this.wait })
let res = await this.reset() if (this.resetEnabled) log.debug({msg:'initial connect interrupt reset packet sent', ready:await this.reset(), method:'init', line:53})
log.debug({msg:'initial connect interrupt reset packet sent', ressponse:res, method:'init', line:53}) if (this.resetInterval && this.resetEnabled) setInterval(this.reset.bind(this),this.resetInterval)
DeadJim( (signal,err) => { DeadJim( (signal,err) => {
log.warn({signal:signal, method:'init', line:56, error:err, msg:'Interrupt process was killed'}) log.warn({signal:signal, method:'init', line:56, error:err, msg:'Interrupt process was killed, remove watchers, unexport'})
this.pin.unwatchAll() this.pin.unwatchAll()
this.pin.unexport() // kill the kernel entry this.pin.unexport() // kill the kernel entry
}) })
log.debug({msg:'new interrupt pin created and watching', method:'init', line: 62, num:this.pin_num, status:await this.status() ? 'ready' : 'not ready', pin:this.pin, edge:this.edge,debounce:this.wait})
log.debug({method:'init', line: 62, msg: 'setting reconnect listener'})
this.consumersListen('reconnected', async () => {
let res = await this.reset()
log.debug({msg:'reconnected, interrupt reset packet sent', ressponse:res, method:'init', line:67})
})
if (this.resetInterval) setInterval(this.commands.reset,this.resetInterval)
this.pin.watch( function (err,value) { this.pin.watch( function (err,value) {
log.debug('sbc interrupt tripped, value:', value, 'error:', err) log.debug('interrupt tripped, value:', value, 'error:', err)
this.count +=1 this.count +=1
this._interruptProcess(value,err) this._interruptProcess(value,err)
}.bind(this)) }.bind(this))
this.on('reconnected', this.reset.bind(this))
this.on('connected', this.reset.bind(this))
log.debug({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})
} // end init } // end init
// manual firing for testing // manual firing for testing
@ -82,44 +78,55 @@ class Interrupt extends Base {
log.debug({method:'fire', line:82, msg:`mock manually firing interrupt for pin ${this.pin_num}`}) log.debug({method:'fire', line:82, msg:`mock manually firing interrupt for pin ${this.pin_num}`})
await this._interruptProcess(1) await this._interruptProcess(1)
packet.status = 'fired' packet.status = 'fired'
packet.ipin = this.pin_num packet.pin = this.pin_num
packet.cmd = 'reply' packet.cmd = 'reply'
return packet return packet
} }
// returns true if pin is ready and waiting to trigger interrupt // returns true if pin is ready and waiting to trigger interrupt
async status(packet={}) { async status(packet) {
let status = await this.pin.read() let status = await this.pin.read()
this.ready = this.pull==='down' ? !status : !!status if (this.edge !=='both') this.ready = this.pull==='down' ? !status : !!status // ready is always true for 'both'
packet.pin = this.pin_num if (packet) {
packet.cmd = 'reply' packet.pin = this.pin_num
packet.ready = this.ready packet.state = status
return packet if (this.edge !=='both') packet.ready = this.ready
} packet.cmd = 'reply'
return packet
async reset(packet={}) {
if (!(await this.status()).ready) {
packet.cmd = this.resetCmd
packet.pin =this.pin_num
this.emit(this.resetCmd) // emit locally
await this.send(packet)
await this.push(packet)
log.error({msg: `interrupt was forced reset. ready now? ${(await this.status()).ready}`})
return {cmd:'reply', reset:true, ready: (await this.status()).ready}
} }
return {cmd:'reply', reset:false, ready:true} return status
} }
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.push(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}
}
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
}
// use hook to do more processing // use hook to do more processing
async _interruptProcess(value,err) { async _interruptProcess(value,err) {
let packet = Object.assign({},this.packet) let packet = Object.assign({},this.packet)
packet.id = this.id
packet.pin = this.pin_num
packet.error = err packet.error = err
packet.state = value packet.state = value
packet.count = this.count packet.count = this.count
packet.timeStamp = Date.now() packet.timeStamp = Date.now()
packet.dateTime = new Date().toString() packet.dateTime = new Date().toString()
if (this.hook) packet = await this._hookFunc.call(this,packet) if (this._hookFunc) packet = await this._hookFunc.call(this,packet)
log.debug({packet: packet, msg:'interrupt tripped, emit/send/push packet to all connected/listening'}) log.debug({packet: packet, msg:'interrupt tripped, emit/send/push packet to all connected/listening'})
this.emit('interrupt',packet) // emit locally this.emit('interrupt',packet) // emit locally
this.send(packet) // will send a packet via client to any socket this.send(packet) // will send a packet via client to any socket
@ -127,7 +134,8 @@ class Interrupt extends Base {
} }
registerHook(func) { registerHook(func) {
this._hookFunc = func if (func) this._hookFunc = func
else this._hookFunc=defaultHook
} }
} // end Class } // end Class
@ -141,15 +149,9 @@ async function defaultHook(packet) {
// new Promise((resolve) => { // new Promise((resolve) => {
console.log('==========default hook =============') console.log('==========default hook =============')
console.log(`pin ${packet.pin} on sbc gpio bus has thrown an interrupt`) console.log(`pin ${packet.pin} on sbc gpio bus has thrown an interrupt`)
console.log(`emitting/sending/pushing to all connected socket client with cmd:${packet.cmd}`) console.log('can change anything in the packet in this hook')
console.dir(packet) console.log('to replace this use .registerHook(function)')
console.log('replace by a new function with .registerHook(function) to overwrite this') console.log('============================')
console.log('Must be async/promise returning if anything async happens in your hook')
console.log('This hook allows you to modify/add to the packet being pushed to connected clients')
console.log('the function will be bound to the instance for complete access')
console.log('if you pass a hash for .hook you can use it here as this.hook')
console.log('the hook options contains', this.hook)
console.log('by default the instance id will be attached to the packet before this')
return packet return packet
// resolve(packet) // resolve(packet)
// }) // })

View File

@ -1,16 +1,19 @@
import Interrupt from './interrupt' import Interrupt from './interrupt'
import Base from '@uci/base'
import logger from '@uci-utils/logger' import logger from '@uci-utils/logger'
let log = {} let log = {}
// will more easily create a group of sbc pin interrupts // will more easily create a group of sbc pin interrupts
class Interrupts { class Interrupts extends Base {
constructor(pins, opts = {}) { constructor(pins, opts = {}) {
super(opts)
this.id = this.id || 'interrupts' this.id = this.id || 'interrupts'
this.pins = pins this.pins = pins.map(pin => Number(pin)) // make sure actual numbers are passed
this.interrupt = {} this._interrupts = new Map()
this.s = { fire:this.fire.bind(this)} // make fire available via consumer packet send this._s = { fire:this.fire.bind(this)} // make fire available via consumer packet send
this.resetCmd = opts.resetCmd || 'interrupt.reset'
log = logger({ name: 'interrupts', id: this.id, package:'@uci/interrupt', file:'src/interrupts.js'}) log = logger({ name: 'interrupts', id: this.id, package:'@uci/interrupt', file:'src/interrupts.js'})
let pinopts = {} let pinopts = {}
pins.forEach(pin => { pins.forEach(pin => {
@ -19,48 +22,77 @@ class Interrupts {
delete opts[pin] delete opts[pin]
}) })
pins.forEach(pin => { pins.forEach(pin => {
if (typeof pin !=='number') pin = parseInt(pin)
pinopts[pin] = Object.assign({}, opts, pinopts[pin]) pinopts[pin] = Object.assign({}, opts, pinopts[pin])
pinopts[pin].id = (opts.id || 'interrupt') + ':' + pin pinopts[pin].id = pinopts[pin].id || this.id + ':' + pin
log.debug({ opts: pinopts[pin], method:'constructor', line:25, msg:`pin options for pin ${pin}`}) log.debug({ opts: pinopts[pin], method:'constructor', line:25, msg:`pin options for pin ${pin}`})
this.interrupt[pin] = new Interrupt(pin, pinopts[pin]) this._interrupts.set(pin, new Interrupt(pin, pinopts[pin]))
// bubble up events from single interrupts to common
const EVENTS = ['status','consumer-connection']
EVENTS.forEach(event => {
this.interrupt(pin).on(event, data => {
data.interrupt = { msg:'emitted event from single interrupt', pin:pin, id:pinopts[pin].id }
this.emit(event,data)
})
})
}) })
} }
interrupt(pin) { return this._interrupts.get(Number(pin)) }
async init() { async init() {
let res = await super.init()
if (res.errors) return Promise.reject(res.errors)
return Promise.all( return Promise.all(
this.pins.map(pin => { Array.from(this._interrupts).map(inter => {
return this.interrupt[pin].init() return inter[1].init()
}) })
) )
} }
// combine all interrupt emits to one handler
async listen(fn) { async listen(fn) {
this.pins.forEach(pin => { this._interrupts.forEach( inter => {
if (fn==='stop') this.interrupt[pin].removeAllListeners('interrupt') if (fn==='stop') inter.removeAllListeners(inter.packet.cmd)
else this.interrupt[pin].on('interrupt', fn.bind(this)) else inter.on(inter.packet.cmd, fn.bind(this))
}) })
} }
async addSocket() { async listenReset(fn) {
return Promise.all( this._interrupts.forEach( inter => {
this.pins.map(pin => { if (fn==='stop') inter.removeAllListeners(inter.resetCmd)
return this.interrupt[pin].addSocket(...arguments) else inter.on(inter.resetCmd, fn.bind(this))
}) })
)
} }
// manual firing for testing // only adds consumer sockets to each interrupt to same socket/server
async fire(packet) { // alternatively use listen handler and single socket
if (packet.pin) return await this.interrupt[packet.pin].fire(packet) async addInterSocket(name,type) {
for ( let pin of packet.pins) { if (type !=='s') {
packet[pin] = await this.interrupt[pin].fire(packet) return Promise.all(
} return packet Array.from(this._interrupts).map(inter => {
return inter[1].addSocket(...arguments)
})
)
}
}
// manual firing of all pins for testing
async fire(packet={}) {
if (!packet.pin || packet.pin==='all') {
for (let inter of this._interrupts.entries()) {
packet[inter[0]] = await inter[1].fire({})
}
packet.cmd='reply'
return packet
}
let pin = isNaN(Number(packet)) ? packet.pin : packet
if (this._interrupts.has(Number(pin))) return await this.interrupt(packet.pin).fire(packet)
} }
registerHook(func) { registerHook(func) {
this.pins.forEach(async pin => { this._interrupts.forEach(inter => {
this.interrupt[pin].registerHook(func) inter.registerHook(func)
}) })
} }