159 lines
7.2 KiB
JavaScript
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
|