let bus = {} import debounce from 'lodash.debounce' import Base from '@uci/base' import logger from '@uci-utils/logger' let log = {} // a pin makes a socket (server/listner) for each pin to which a consumer can be connected class Interrupt extends Base { constructor(pin, opts = {}) { opts.conPacket = (opts.resetCmd && !opts.conPacket) ? opts.conPacket : { cmd: opts.resetCmd, pin: pin } // will use either option // 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 uci socket. This will send this command on connect, needed to initially reset the interrupt 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 = logger({ name: 'interrupt', id: this.id }) 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 this.dbopts = { maxWait: opts.maxwait || 500, leading: opts.leading || true, trailing: opts.trailing || false } this.edge = opts.edge this.pull = opts.pull this.pin = {} //set at init this._hook = opts.hook this.packet = opts.packet || {} this.packet.pin = pin this.packet.cmd = this.packet.cmd || opts.pushcmd || 'interrupt' this.packet.count = 0 } // end constructor async init() { await super.init() // for cntrl-c exit of interrupt // create the pigio pin_num // TODO check for rpi and pigpio, if not available use onoff // wrap all needed commands so can call module for pigpio or onoff if (this.mock) bus = await import('pigpio-mock') else bus = await import('pigpio') this.pin = new bus.Gpio(this.pin_num, { mode: bus.Gpio.INPUT, pullUpDown: this.pull || bus.Gpio.PUD_DOWN // do not! set edge here as it will start the emitter -- see pigio js }) process.on('SIGINT', () => { this.exit() .then(resp => console.log('\n', resp)) // unexport on cntrl-c .catch(err => console.log('error:', err)) }) let cb = () => {} if (this.wait === 0) { cb = this._interruptProcess.bind(this, this.packet) console.log(`starting interrupt on pin ${this.pin_num} without debounce`) log.info( { packet: this.packet }, `starting interrupt on pin ${this.pin_num} without debounce` ) } else { cb = debounce( this._interruptProcess.bind(this, this.packet), this.wait, this.dbopts ) console.log( `starting interrupt on pin ${this.pin_num} with debounce wait:${ this.wait } options:${JSON.stringify(this.dbopts)}` ) log.info( { packet: this.packet, wait: this.wait, options: this.dbopts }, `starting interrupt on pin ${this.pin_num} with debounce wait:${ this.wait }` ) } this.pin.on('interrupt', cb) // rock n roll!!, start the pigpio interrupt if (!this.mock) this.pin.enableInterrupt(this.edge || bus.Gpio.RISING_EDGE) } // manual firing for testing async fire() { console.log('manually firing interrupt for pin', this.pin_num) this.pin.emit('interrupt', 1) return Promise.resolve({ status: 'fired' }) } exit() { bus.terminate() return Promise.reject('keyboard termination...terminating interrupts') } // default processor async _interruptProcess(packet) { packet.count += 1 packet.time = new Date().getTime() if (this._hook) packet = await this.hook(packet) log.info({ packet: packet }, 'packet pushing to all clients') this.push(packet) } //sample hook hook(packet) { // return a promise if anything async happens in hook // new Promise((resolve) => { console.log('=======================') 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( 'This hook allow you to modify the packet being pushed to connected clients' ) console.log( 'add .hook method for your instance or extended class to overwrite this' ) console.log( 'Must be promise returning if anything async happens in your hook' ) console.log('=======================') return packet // resolve(packet) // }) } } // end Class export default Interrupt