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 = {}) { if (typeof pin !=='number') pin = parseInt(pin) // make sure pin is a number! 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 (process.env.UCI_MOCK==='true') 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) 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 ) 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) this.registerHook(defaultHook) } // end init // 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') } // use hook to do more processing async _interruptProcess(packet) { packet.count += 1 packet.time = new Date().getTime() packet.id = this.id if (this.hook) packet = await this._hookFunc.call(this,packet) log.info({ packet: packet }, 'packet pushing to all clients') 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 allow 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) // }) }