import {MCP230XX, map, changed, isPlainObject, to, merge} from './mcp230xx' import { byteFormat } from '@uci-utils/byte' class MCP230XXi extends MCP230XX { constructor(pins, options={}) { if (!Array.isArray(pins)) { options = pins; pins = options.interrupt.pins} // if pins sent via .interrupt.pins let opts = Object.assign({},options) // don't allow passed options to mutate if (opts.interrupt) delete opts.interrupt.pins // if .interrupt was passed then .pins must be removed opts.lport = opts.lport || (opts.interrupt || {}).port || opts.iport if (!opts.lport) opts.lpath = opts.lpath || (opts.interrupt || {}).ipath || opts.ipath || 'interrupt' // must has a socket litener for interrupt process // a socket option of name `interrupt` MUST be supplied or the default below will be used which assume only a named pipe of `interrupt` const interrupt = { name: 'interrupt', type: 'c', transport: 'n', options: { name: 'mcpi', data: { name: 'mcpi', dep: 'interrupt' }, initTimeout: 0, retryWait: 5, path: 'interrupt' } } // set the required interrupt consumer to the default options if none supplied if (!opts.sockets) opts.sockets = [ interrupt ] else if (!opts.sockets.find(socket=>socket.name.includes('interrupt'))) { opts.sockets.push(interrupt) } super(opts) this.pinsCfg = opts.pinsCfg || this.chip17 ? [{port:'A', pins:'all', cfg:'input_interrupt'},{port:'B', pins:'all', cfg:'input_interrupt'}] : [{pins:'all', cfg:'input_interrupt'}] pins.forEach((pin,index) => { this[pin] = opts['i' + pin] || {} this[pin].mport = this[pin].mport || index ? 'B' : 'A' }) this.pins = pins this.commands.interrupt = this.bindFuncs(icommands) // add interrupt pin commands to base set in "command.js" this.addNamespace('commands.interrupt', 'c') // by default allow access to interrupt commands via pushed packet (gpio interrupt) // they will be available via send as well to socket as interrupt. this._interruptProcess = process.bind(this) // default processor this.iname = (opts.interrupt || {}).name || 'interrupt' // name of interrupt consumer this.ready.addObserver(`${this.iname}:process`) // will emit on received interrupt process ready packet // ready to reset interrupt when mcp is ready, the interrupt process is connected and it up let interruptobs = this.ready.combineObservers(this.iname,['mcp:configure',this.getSocket(this.iname).obsName,`${this.iname}:process`]) this.ready.addObserver('interrupt:reset',interruptobs .pipe( map(async ready => { if (ready) { await this.resetInterrupt() const istatus = await this.send(this.iname,{cmd:'status'}) // get the status from the gpio interrupt if (istatus.pins) ready = istatus.pins.reduce((acc,pin)=>acc&&(pin.ready!=null?pin.ready :false),true) else ready = istatus.ready if (!ready) this.emit('log',{level:'fatal', msg:'gpio interrrupts were not initially reset',status:istatus}) } return ready }) //map )) // get listening socket and watch for incoming connection from interrupt process let socket = this.getSocket(`${options.name}:${options.interrupt.remote ? 't':'n'}`) || this.getSocket(options.name)// mcp t or n socket was created this.consumerConnected(socket,{add:true,consumer:this.iname}) } // end constructor async interruptState(pin) { let istate = await this.bus.read(this.getPort(pin) !== 'B' ? 0x07 : 0x17) let pullup = (this.chipCfg||'').includes('Pullup') ? true : false let state = istate.response ? true && pullup : false || !pullup return state } async resetInterrupt(pins) { if (!Array.isArray(pins)) pins = pins ? [pins] : this.pins return (await Promise.all(pins.map(async pin => { await this.bus.read(this.getPort(pin) !== 'B' ? 0x08 : 0x18) // 0x08 is intcap interrupt capture register return(await this.interruptState(pin)) }))).reduce((res,val) => res && val,true) } getPort(pin) { if (pin==='A' || pin==='B') return pin return this[pin].mport } registerInterruptProcessor(func) { this._interruptProcess = func.bind(this) } } // end of MCP230XXi Class export default MCP230XXi export { MCP230XXi, map, changed, isPlainObject, to, merge} // default processor function process(details) { details.id = this.id console.log('----default interrupt processor for mcp instance----') console.log('here is where you could either locally take some action or send on a message to another process') console.log('create your own function and register it with .interruptProcesser(function)') console.log(this.id) console.dir(details) console.log('------------------------------------------') } // commands to be added to pin packet command functions const icommands = { status: async function(packet) { let pin = packet.ipin || packet.pin let state = await this.interruptState(pin) return {cmd:'reply', request:'interrupt.status', port:this.getPort(pin), ipin:pin, ready:state} }, reset: async function(packet) { let pin = packet.ipin || packet.pin let state = await this.resetInterrupt(pin) let res = {level:state ? 'debug':'error', msg:`remote reset request from ${packet._header.sender.instanceID} for pin ${pin}: ${state?'ready': 'error'}` } this.emit('log',res) return {state:state} }, // finds which mcp pin caused the interrupt find: async function(inter) { let reply = {} // this.emit('log',{level:'debug', msg:'incoming interrupt', packet:inter}) let ipin = inter.ipin || inter.pin if (!await this.interruptState(ipin)) { // if it's already reset then this is false trip const res = await this.commands.pin.status({ pins: 'all', reg: 'intf', port:this.getPort(ipin) }) // read port interrupt status const status = await this.resetInterrupt(ipin) // now reset let istatus = await this.send(this.iname,{cmd:'status'}) // get the status from the gpio interrupt if (istatus.ready==null && istatus.pins) istatus.ready = !!istatus.pins.find(el => el.pin === ipin).ready let pin = byteFormat(res.port||[], { in: 'ARY', out: 'PLC' })[0] if (!pin) { this.emit('log',{level:'warn', cmd:inter.cmd, inter:inter, msg:'no pin associated with interrupt'}) reply = { cmd:'error', error: 'no pin associated with interrupt' } } else { // avoida bad interrupt (i.e. no pin caused interrupt) delete inter.cmd; delete inter._header inter.ipin = ipin inter.pin = pin inter.port = this[ipin].mport inter.mpc_interrupt_reset = status inter.reset = istatus.ready inter.state = (await this.commands.pin.status({pin:pin, port:this.getPort(ipin)})).state inter.closed = inter.state === 'on' ? true : false this._interruptProcess(inter) reply = {cmd:'reply', msg:'mcp interrupt pin was found', inter:inter} } if (!istatus.ready) this.emit('log',{level:'fatal', msg:'gpio interrrupt was not reset after firing',interrupt:inter}) } else reply = { cmd:'reply', msg:'interrupt was already reset nothing to find/do' } return reply } // end find } // end commands