134 lines
5.5 KiB
JavaScript
134 lines
5.5 KiB
JavaScript
import Base from '@uci/base'
|
|
import DeadJim from 'death'
|
|
import { Gpio } from 'onoff'
|
|
|
|
import logger from '@uci-utils/logger'
|
|
let log = logger({package:'@uci/interrupt', file:'/src/interrupt.js'})
|
|
// a pin makes a socket (server/listner) for each pin to which a consumer can be connected
|
|
// if opts .port/.path/.topic/.wport are base number/name to which pin number is added/appended (default being 9000,9100,'interrupt')
|
|
// for specific port number pass itrt.port,itrn.path,itrm.topic,itrw.port which override it if present
|
|
// 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 {
|
|
constructor(pin, opts = {}) {
|
|
if (typeof pin !=='number') pin = parseInt(pin) // make sure pin is a number!
|
|
opts.conPacket = (opts.resetCmd && !opts.conPacket) ? { cmd: opts.resetCmd, pin: pin } : opts.conPacket // will use either option
|
|
log.debug({conPacket: opts.conPacket, msg:'connection packet for consumers'})
|
|
if (opts.path || opts.itrn) {
|
|
opts.itrn = opts.itrn || {}
|
|
if (opts.path && typeof opts.path !=='boolean') opts.path = opts.path + ':' + pin
|
|
if (typeof opts.path ==='boolean') opts.path = ''
|
|
opts.itrn.path = opts.itrn.path || opts.path || 'interrupt:' + pin
|
|
opts.itrn.conPacket = opts.conPacket
|
|
opts.sockets = (opts.sockets ? opts.sockets + ',' : '') + 'itrn#s>n'
|
|
}
|
|
if (opts.topic || opts.itrm) {
|
|
opts.itrm = opts.itrm || {}
|
|
opts.itrm.topics = opts.itrm.topic || opts.topic +'/'+ pin || 'interrupt/' + pin
|
|
opts.sockets = (opts.sockets ? opts.sockets + ',' : '') + 'itrm#s>m'
|
|
}
|
|
|
|
if (opts.itrw || opts.wport || opts.wport===0 ) {
|
|
opts.itrw = opts.itrw || {}
|
|
if (opts.wport) opts.wport = opts.wport + +pin
|
|
opts.itrw.port = opts.itrw.port || opts.wport || 9100 + +pin
|
|
opts.sockets = (opts.sockets ? opts.sockets + ',' : '') + 'itrw#s>w'
|
|
}
|
|
// default is a tcp socket server at 9000+pin
|
|
if (opts.itrt || opts.port || opts.port===0 || !opts.sockets ) {
|
|
opts.itrt = opts.itrt || {}
|
|
if (opts.port) opts.port = opts.port + +pin
|
|
opts.itrt.port = opts.itrt.port || opts.port || 9000 + +pin
|
|
opts.itrt.conPacket = opts.conPacket
|
|
opts.sockets = (opts.sockets ? opts.sockets + ',' : '') + 'itrt#s>t'
|
|
}
|
|
super(opts)
|
|
this.id = (opts.id || 'interrupt') + ':' + pin
|
|
log.info({ pins: pin, opts: opts }, 'created interrupt with these opts')
|
|
this.pin_num = pin
|
|
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
|
|
// this.pull = opts.pull || 'low' // 'high'
|
|
this.pin = {}
|
|
this.hook = opts.hook
|
|
this.packet = opts.packet || {}
|
|
this.packet.id = this.id
|
|
this.packet.pin = this.pin_num
|
|
this.packet.cmd = this.packet.cmd || opts.pushCmd || 'interrupt'
|
|
this.packet.count = 0
|
|
this._hookFunc = defaultHook
|
|
this.s = { fire:this.fire.bind(this)} // make fire available via consumer packet send
|
|
} // end constructor
|
|
|
|
async init() {
|
|
await super.init()
|
|
|
|
DeadJim( (signal,err) => {
|
|
log.warn({signal:signal, error:err, msg:'Interrupt was killed'})
|
|
this.pin.unwatchAll()
|
|
this.pin.unexport() // kill the kernel entry
|
|
|
|
})
|
|
|
|
this.pin = new Gpio(this.pin_num, 'in', this.edge, { debounceTimeout:this.wait })
|
|
|
|
log.debug({msg:'new interrupt pin created and watching', pin:this.pin, edge:this.edge,debounce:this.wait})
|
|
|
|
this.pin.watch( function (err,value) {
|
|
log.debug('sbc interrupt tripped', err, value)
|
|
this._interruptProcess(this.packet,err,value)
|
|
}.bind(this))
|
|
|
|
} // end init
|
|
|
|
// manual firing for testing
|
|
async fire(packet) {
|
|
console.log('manually firing interrupt for pin', this.pin_num)
|
|
await this._interruptProcess(this.packet,null,'manual')
|
|
packet.status = 'fired'
|
|
return packet
|
|
}
|
|
|
|
// use hook to do more processing
|
|
async _interruptProcess(packet,err,value) {
|
|
console.log('from watch listener', packet.pin, err, value)
|
|
packet.error = err
|
|
packet.state = value
|
|
packet.count += 1
|
|
packet.time = new Date().getTime()
|
|
if (this.hook) packet = await this._hookFunc.call(this,packet)
|
|
log.debug({ pin:this.pin, packet: packet }, 'packet pushing to all clients')
|
|
await this.push(packet)
|
|
}
|
|
|
|
|
|
registerHook(func) {
|
|
this._hookFunc = func
|
|
}
|
|
|
|
} // end Class
|
|
|
|
export default Interrupt
|
|
|
|
|
|
//default hook
|
|
async function defaultHook(packet) {
|
|
// return a promise or use await if anything async happens in hook
|
|
// new Promise((resolve) => {
|
|
console.log('==========default hook =============')
|
|
console.log(`pin ${packet.pin} on sbc gpio bus has thrown an interrupt`)
|
|
console.log(`pushing to all connected socket client with cmd:${packet.cmd}`)
|
|
console.dir(packet)
|
|
console.log('replace by a new function with .registerHook(function) to overwrite this')
|
|
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
|
|
// resolve(packet)
|
|
// })
|
|
}
|