uci-mcp/src/mcp230xxi.js

146 lines
5.6 KiB
JavaScript

import {MCP230XX, map, changed, isPlainObject, to, merge} from './mcp230xx'
import { byteFormat } from '@uci-utils/byte'
// import Ready from '@uci-utils/ready'
import logger from '@uci-utils/logger'
let log = {}
log = logger({
file: 'src/mcp230xxi.js',
class: 'MCP230XXi',
name: 'mcp-interrupt',
})
// if opts.iport not set then will be generated based on pin number
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
log.debug({method:'constructor', line:21, msg:'passed options before setting',options:opts})
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
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'}]
log = logger({
file: 'src/mcp230xxi.js',
class: 'MCP230XXi',
name: 'mcp-interrupt',
id: this.id
})
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._interruptProcess = process.bind(this) // default processor
const conditionHandler = async ev => {
if ((ev||{}).state ==='connected'){
let data = (ev.data ||{})
if (data.type === 'interrupt' || [ev.name, data.name, data.id].some(name => (name||'').includes('interrupt')) ) {
return true
}
}
return false
}
this.ready.addObserver('interrupt:connected',this.getSocket(`mcp-${opts.lport? 't':'n'}`),{event:'connection:consumer',condition:conditionHandler})
this.ready.addObserver('interrupt:reset',this.ready.combineObservers(['mcp','interrupt:connected']).pipe(
map(async ready=>{
if (ready) return await this.resetInterrupt()
return false
})
))
} // 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
this._ireset = (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)
return this._ireset
}
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 ipin = inter.ipin || inter.pin
if (!await this.interruptState(ipin)) { // if it's already reset then this is false trip
let res = await this.commands.pin.status({ pins: 'all', reg: 'intf', port:this.getPort(ipin) }) // read port interrupt status
let status = await this.resetInterrupt(ipin) // now reset
let pin = byteFormat(res.port||[], { in: 'ARY', out: 'PLC' })[0]
if (!pin) {
log.warn({cmd:'interrupt.find', line:132, inter:inter, msg:'no pin associated with interrupt'})
return { error: 'no pin associated with interrupt' }
}
else { // avoid 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.interrupt_ready = status
inter.state = (await this.commands.pin.status({pin:pin, port:this.getPort(ipin)})).state
this._interruptProcess(inter)
// replying with reset command which is also a check
return {cmd:'reset', pin:ipin}
}
}
}
} // end commands