uci-mcp/src/mcp230xxi.js

159 lines
7.2 KiB
JavaScript

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