first commit of packet based mcp code.

incoming packet interpreted and sent on to bus
listens for response and processes and sends back to requestor
Working pin.test example
master
David Kebler 2018-02-08 09:19:46 -08:00
parent 35605352bf
commit 71bd508a0c
12 changed files with 583 additions and 1 deletions

33
.eslintrc.js Normal file
View File

@ -0,0 +1,33 @@
module.exports = {
"ecmaFeatures": {
"modules": true,
"spread" : true,
"restParams" : true
},
"env": {
"es6": true,
"node": true,
"mocha": true
},
"parserOptions": {
"ecmaVersion": 2017,
"sourceType": "module"
},
"extends": "eslint:recommended",
"rules": {
"indent": [
"error",
2
],
"no-console": 0,
"semi": ["error", "never"],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"error",
"single"
]
}
}

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
/node_modules/
/coverage/
/syncd/

28
examples/ipc-test.mjs Normal file
View File

@ -0,0 +1,28 @@
/*
* i2c bus unix socket and client in one for demo
*
*/
import Base from '../../uci-base/src/base'
const PATH = '/opt/sockets/mcp.sock'
const delay = time => new Promise(res=>setTimeout(()=>res(),time))
;
(async () => {
let mcpclient = new Base({id:'mcpclient', sockets:'uc#c>n', uc:{path:PATH}})
await mcpclient.init()
console.log('=============sending============')
let packet = {cmd:'pin.test'}
console.dir(packet)
await mcpclient.send(packet)
await delay(3000)
process.kill(process.pid, 'SIGTERM')
})().catch(err => {
console.error('FATAL: UNABLE TO START SYSTEM!\n',err)
})

17
examples/mcp.mjs Normal file
View File

@ -0,0 +1,17 @@
/*
* i2c bus with both unix and tcp socket using defaults. For TCP that is host OS name and port 8080
*
*/
import MCP230XX from '../src/mcp230xx-packet'
// const PATH = ''
;
(async () => {
let mcp_chip = new MCP230XX({id:'mcp23008-27', address:'0x27', bus:{host:'sbc'} })
await mcp_chip.init()
})().catch(err => {
console.error('FATAL: UNABLE TO START SYSTEM!\n',err)
})

View File

@ -4,6 +4,8 @@
"version": "0.1.2",
"description": "Classes and Helper Functions for using the MCP chip on I2C Bus",
"scripts": {
"mcp": "SOCKETS_DIR=/opt/sockets node_modules/.bin/nodemon --require @std/esm examples/mcp",
"mcpl": "DEBUG=true SOCKETS_DIR=/opt/sockets node_modules/.bin/nodemon --require @std/esm examples/mcp",
"test": "./node_modules/.bin/mocha --reporter list --timeout 30000",
"testw": "./node_modules/.bin/mocha --reporter list -- watch --timeout 30000",
"testd": "DEBUG='1:*' ./node_modules/.bin/mocha --reporter list --watch --timeout 30000",
@ -33,11 +35,14 @@
"@uci/utils": "^0.1.0",
"aggregation": "^1.2.0"
},
"@std/esm": "cjs",
"devDependencies": {
"@std/esm": "^0.20.0",
"chai": "^3.5.0",
"codecov": "^1.0.1",
"debug": "^2.6.8",
"istanbul": "^0.4.5",
"mocha": "^3.4.2"
"mocha": "^3.4.2",
"nodemon": "^1.14.12"
}
}

304
src/mcp23008-17.mjs Normal file
View File

@ -0,0 +1,304 @@
'use strict'
const Device = require('@uci/i2c').Device,
portpin = require('./port-pin'), // classes for MCP port and pins
EventEmitter = require('events'),
_ = require('@uci/utils'),
aggregate = require('aggregation/es6')
const
Port = portpin.Port,
pinConfigs = portpin.configs, // for export so pinConfigs can be accessed at runtime
pause = _.pPause,
debug = _.debug('mcp:23008-17')
class MCP23008 extends aggregate(Device, EventEmitter) {
constructor(busObj, i2cAddress, opts = {}) {
super(busObj, i2cAddress, opts)
// opts could include options passed on to ports and pin including custom pin config, pin ids...see gpio.js
this.chip_config = opts.chip_config // TODO allow opts.chip_config to be a byte instead of a string pointer
this.ports = {}
opts.portID = 'A'
opts.pids = opts.pids ? opts.pids : opts.pidsA
this.ports.A = new Port(opts)
this.ports.A.state = new _.Byte(opts.stateA)
this.ports.A.interrupt = opts.interruptA ? opts.interruptA : opts.interrupt
} // end constructor
// helpers
pin(id) { return this.ports.A.pin(id) } // get a reference to a particular pin's object
pid(address) { return this.ports.A.pid(address) } // return pin id for a given address on a port
pid(num) { return this.ports.A.pins[num - 1].id }
portByPin(id) {
if (this.ports.A.pin(id)) { return 'A' }
return false
}
state(port = 'A') {
return this.ports[port].state.value
}
// get a handle to the ports interrupt
inter(port = 'A') {
return this.ports[port].interrupt
}
// Must call after instantiating.
async init() {
debug.L1(`\n=======\nInitializing ${this.id}`)
await this.writeChipCfg(this.chip_config) // chip settings
await this.writePinsCfg()
// write out any initial state(s)
for (let port in this.ports) {
if (this.state(port)) {
debug.L1(`writing initial port state ${port} ${this.state(port)}`)
await this.writePort(this.state(port), 'force', port)
}
}
}
// must call after init if using interrupts
async start() {
debug.L1(`starting ${ this.id }`)
for (let port in this.ports) {
// if there are interrupts being used then start them and listeners
if (this.inter(port)) {
this.startInterrupt(port)
}
}
}
async startInterrupt(port) {
await this.interruptReset(port)
debug.L1(`starting interrupt on port ${ port }`)
await this.inter(port).start()
let chip = this // scope `this` for use in the listener below
// bind handler to the chip so handler can read/write to chip/bank when interrupt is emitted
debug.L3('handler', this.inter(port).handler)
let ihandler = this.inter(port).handler.bind(this)
// inside the listener `this` is the interrupt not the chip/bank
this.inter(port).on('fired', async function () {
debug.L1(`interrupt from ${this.pin_number}`)
let data = await ihandler(port)
debug.L2(`port interrupt event 'fired' data => ${data.bank} on port ${data.port} pin# ${data.pin} pid ${data.pid}`)
chip.emit('fired', data) // emit up the class chain
})
}
async interruptReset(port = 'A') {
await this.read(portReg(0x08, port))
await pause(100) // give enough time for mcp to reset its interupt
debug.L1(`interrupt reset on ${this.id} port ${port}`)
}
async interruptPin(port, format) {
if ('AB'.indexOf(port) === -1) {
format = port ? port : null
port = 'A'
}
let result = await this.read(portReg(0x07, port))
return format ? _.byteFormat(result, { in: 'DEC', out: format }) : result
}
async writeChipCfg(cfg = 'default') {
let setting = chip_config[cfg]
let byte = _.byteFormat(setting.val, { in: setting.fmt, out: 'DEC' })
debug.L1(`writing mcp chip config ${setting.val}`)
await this.write(chip_config.cmd, byte)
}
// pin configurations should already be set before calling
async writePinsCfg() {
debug.L1('writing mcp pins config')
for (let port in this.ports) {
for (let setting in registers.pin_config) {
let reg = registers.pin_config[setting]
// TODO 0x10 should be based on chip config
let byte = 0
for (let pin of this.ports[port].pins) {
byte += pin.address * pin.cfg[setting]
debug.L3(`port: ${ port } pin: ${pin.id} setting: ${ setting } reg: ${ reg } byte: ${ byte }`)
}
await this.write(portReg(reg, port), byte)
}
}
debug.L1('done writing mcp pins config')
} // end writePinsCfg
async readPort(port, opts = {}) {
if ('AB'.indexOf(port) === -1) {
opts = port ? port : {}
port = 'A'
}
let cmd = opts.cmd ? opts.cmd : 'gpio'
let result = await this.read(portReg(registers.pin_cmd[cmd], port))
debug.L2('read prev ', this.ports[port].state.toFmtPrev('ARY'))
debug.L2('read result', _.byteFormat(result, { in: 'DEC', out: 'ARY' }))
debug.L2('read state ', this.ports[port].state.toFmt('ARY'))
// if port pins changed without a write then update state
if (_.bitChanges(_.byteFormat(result, { in: 'DEC', out: 'ARY' }), this.ports[port].state.toFmt('ARY'))) {
this.ports[port].state.value = result
}
return opts.format ? this.ports[port].state.toFmt(opts.format) : result
}
async readPin(pin, port, opts = {}) {
if ('AB'.indexOf(port) === -1) {
opts = port ? port : {}
port = 'A'
}
await this.readPort(port, opts)
// TODO return just true or false not the decimal of pin - will need new version on npm
return this.ports[port].state.bwOp(Math.pow(2, pin - 1), 'check') ? true : false
}
async writePort(byte, op, port = 'A') {
// byte MUST be a decimal
debug.L2(`port:${port}, op:${op}, current pins state\n ${this.ports[port].state.toFmt('PLC') }`)
debug.L2(`byte passed \n [${ _.byteFormat(byte, { in: 'DEC', out: 'PLC'})}]:${byte}`)
if (op !== 'force') {
byte = this.ports[port].state.bwOp(byte, op)
}
debug.L2(`byte to write\n [${ _.byteFormat(byte, { in: 'DEC', out: 'PLC' })}]:${byte}\n=======`)
await this.write(portReg(registers.pin_cmd.gpio, port), byte)
//update to the saved state
this.ports[port].state.value = byte
}
async pinsOn(pins, port, format) {
if ('AB'.indexOf(port) === -1) {
format = port
port = 'A'
}
if (format) { pins = _.byteFormat(pins, { in: format, out: 'DEC' }) }
await this.writePort(pins, 'on', port)
}
async allOn(port = 'A') {
let pins = 255
await this.writePort(pins, 'force', port)
}
async pinsOff(pins, port, format) {
if ('AB'.indexOf(port) === -1) {
format = port
port = 'A'
}
if (format) { pins = _.byteFormat(pins, { in: format, out: 'DEC' }) }
await this.writePort(pins, 'off', port)
}
async allOff(port = 'A') {
let pins = 0
await this.writePort(pins, 'force', port)
}
async toggle(pins, port, format) {
if ('AB'.indexOf(port) === -1) {
format = port
port = 'A'
}
if (format) { pins = _.byteFormat(pins, { in: format, out: 'DEC' }) }
await this.writePort(pins, 'toggle', port)
}
async allToggle(port = 'A') {
let pins = 255
await this.writePort(pins, 'toggle', port)
}
async force(pins, port, format) {
if ('AB'.indexOf(port) === -1) {
format = port
port = 'A'
}
if (format) { pins = _.byteFormat(pins, { in: format, out: 'DEC' }) }
await this.writePort(pins, 'force', port)
}
} // end 23008
class MCP23017 extends MCP23008 {
constructor(busObj, i2cAddress, opts = {}) {
super(busObj, i2cAddress, opts)
// add a second port
opts.portID = 'B'
opts.pids = opts.pidsB
this.ports.B = new Port(opts)
this.ports.B.state = new _.Byte(opts.stateB)
this.ports.B.interrupt = opts.interruptB ? opts.interruptB : opts.interrupt
}
pin(id, port) {
if (!port) {
return this.ports.A.pin(id) ? this.ports.A.pin(id) : this.ports.B.pin(id)
}
return this.ports[port].pin(id)
}
pid(num, port) {
console.log('pin for pid',this.ports[port].pins[num - 1])
return this.ports[port].pins[num - 1].id
}
} // end MCP23017 Class
// EXPORTS
module.exports = {
MCP23008,
MCP23017,
pinConfigs
}
// Local Functions and Settings=============================
function portReg(reg, port) {
// TODO check chip configuartion to know what to add for port B. CUrrenly assume IOCON=1
// index 0,1 ==== 'A','B'
if ((port === 'B') || (port === 1)) { return reg += 0x10 } else { return reg }
}
/* ww1.microchip.com/downloads/en/DeviceDoc/21952b.pdf
* or see MCP23017.pdf and MCP 23008.pdf in /docs
* see table 1.5 and details in sections 1.6.x following
* !!! for 23017 MUST initialize with bit 7 "BANK" of IOCON = 1 for the addresses below
* then for all registers add 16 (0x10) to each reg address to talk to PortB pins
* this will make reg addresses be equilvant for 23008 and PortA of 23017
* reg addresses in the config objects are all in Hexidecminal
*/
// Chip Configuration to be used with Register See Page 18 of 23017 doc
let chip_config = {
// byte: ['NULL','INTPOL','ODR','HAEN','DISSLW','SEQOP','MIRROR','BANK'] // see page 18 of 23017 datasheet for 8 setting details
cmd: 0x0A, // IOCON.BANK=0 (msb) at powerup so need to use 0x0A, if set to 1 then use
default: {
val: '10100010', // Split Banks port A + 0x10 = Port B,(ignored by 23008), Sequential operation disabled, active high=pulldown
fmt: 'STR'
},
oneint: {
val: '11100100', // same as default execpt int pins connected
fmt: 'STR'
}
}
let registers = {
pin_config: {
dir: 0,
ivrt: 1,
pullup: 6,
intr: 2,
usedef: 4,
defval: 3,
},
pin_cmd: {
intf: 7, // readonly
intcap: 8, // readonly
gpio: 9, // read/write
olat: 10 // read only
}
}

58
src/mcp230xx-packet.mjs Normal file
View File

@ -0,0 +1,58 @@
// import Base from '@uci/base'
import Base from '../../uci-base/src/base'
import pin from './pin'
import _ from '@uci/utils/src/byte'
import logger from '../../uci-logger/src/logger'
let log = {}
const LOG_OPTS = {
repo:'uci-mcp',
npm:'@uci/mcp',
file:'src/mcp230xx-packet.mjs',
class:'MCP230XX',
id:this.id,
instance_created:new Date().getTime()
}
export default class MCP230XX extends Base {
constructor(opts) {
log = logger.child(LOG_OPTS)
if (opts.bus) {
if (opts.bus.host) {
opts.bus.socket = 'bus#c>t'
opts.bus.port = opts.bus.port || 1776
}
if (opts.bus.path) opts.bus.socket = 'bus#c>n'
} else {
opts.bus = { path : (process.env.SOCKETS_DIR || __dirname) + '/i2c-bus.sock' }
opts.bus.socket = 'bus#c>n'
}
opts.nmcp = opts.nmcp || {path: (process.env.SOCKETS_DIR || __dirname) + '/mcp.sock'}
opts.sockets = 'nmcp#s>n,tmcp#s>t,'+ opts.bus.socket
console.log(opts)
super(opts)
if (!opts.address) log.fatal({opts:opts},'no i2c bus address supplied' )
this.address = opts.address
this.pin = pin
this.busSend = this.busSend.bind(this)
}
async init(){
await super.init()
}
async busSend(packet) {
return await this.socket.bus.send(packet)}
reply (packet) {
console.log('in bus reply', packet)
// this.emit('bus', packet)
this.emit(packet.id, packet)
}
// setBusListener(packet) {
// // this.on(packet.cmd+Math.random().toString().slice(1))
// }
} // end of MCP230XX Class

56
src/pin.mjs Normal file
View File

@ -0,0 +1,56 @@
// All the pin functions for corresponding packet cmd:'pin'
// every function should return a packet ready to send to the i2c-bus socket
// export {pin}
const pin = {
cfg: function(packet){
return new Promise( async (resolve) => {
console.log(packet)
return resolve()
})
},
test: function(packet){
return new Promise( async (resolve, reject) => {
setTimeout(() => {reject({error:'no response from bus in 2000ms'})},2000)
let id = Math.random().toString().slice(2)
let buspacket = {cmd:'scan', id:id}
console.log('packet for bus', buspacket)
this.busSend(buspacket)
this.on(buspacket.id,function(busreply){
console.log('reply emitted',busreply)
this.removeAllListeners(busreply.id)
let res = { cmd:'reply', _req:packet, _reqBus:buspacket}
if (busreply.response.indexOf(parseInt(this.address,16))!=-1) res.ready = true
res.address = this.address
console.log(res)
resolve(res)
})
})
},
status: function(packet){
return new Promise( async (resolve) => {
console.log(packet)
return resolve()
})
},
// // state is equivalent to read
// status: packet => {
// },
// // threse three only for output pins
// state : {
// on: packet => {
// },
// off: packet => {
// },
// toggle: packet => {
// }
// },
// // will create packet to determin pin caused interrupt, packet will come from interrupt module
// interrupt: {
// find: packet =>{},
// report: packet=>{} // come here after determining which pin to report to requester
// }
}
export default pin

80
test/mcp.test.old.js.off Normal file
View File

@ -0,0 +1,80 @@
'use strict'
const expect = require('chai').expect,
_u = require('@uci/utils'),
MCP = require('../lib/mcp23008-17')
const bus = {}
const config_sets = {
output: {
dir: 0, // 0 output,1 input
ivrt: 0,
pullup: 0,
intr: 0, // if intr = 0 usedef,deval not used
usedef: 0, // if usedef = 0 defval not used
defval: 0
},
toggle_switch: {
dir: 1, // 0 output,1 input
ivrt: 1, // for reading let 1 be zero and vice versa
pullup: 1,
intr: 1, // if intr = 0 usedef,deval not used
usedef: 0, // if usedef = 0 defval not used
defval: 0
},
momentary_switch: {
dir: 1, // 0 output,1 input
ivrt: 1, // for reading let 1 be zero and vice versa
pullup: 1,
intr: 1, // if intr = 0 usedef,deval not used
usedef: 1, // if usedef = 0 defval not used
defval: 1
}
}
// console.log(bus1)
// bus1.scan((devs)=>{console.log('devices: ', devs)})
//bus1.scan().then(results=>console.log(results))
describe('I2C Device Classes - ', function () {
let mcp17 = new MCP.MCP23017(bus, 0x20, {
pin_cfg_default: 'momentary_switch',
name: 'switches 1-16'
})
describe('MCP23017 Class - ', function () {
it('can set and get a single pin config on both ports', function () {
expect(mcp17.pin(1).cfg, 'pin configs getter failed').to.deep.equal(config_sets.momentary_switch)
expect(mcp17.pin(8).cfg.dir, 'pin config getter failed').to.equal(1)
mcp17.pin(8).cfg.dir = 0
expect(mcp17.pin(8).cfg.dir, 'pin address setter failed').to.equal(0)
expect(mcp17.pin(8, 'B').cfg.dir, 'pin address getter port B failed').to.equal(1)
mcp17.pin(8, 'B').cfg.dir = 0
expect(mcp17.pin(8, 'B').config.dir, 'pin address setter failed').to.equal(0)
})
})
let mcp8 = new MCP.MCP23008(bus, 0x21, {
pin_cfg_default: 'toggle_switch',
name: 'test 1-8'
})
describe('MCP23008 Class - ', function () {
it('can set and get a single pin config', function () {
expect(mcp8.pin(1).cfg, 'pin configs getter failed').to.deep.equal(config_sets.toggle_switch)
expect(mcp8.pin(8).cfg.dir, 'pin address getter failed').to.equal(1)
expect(mcp8.pin(8).cfg.ivrt, 'pin address getter failed').to.equal(1)
expect(mcp8.pin(8).cfg.usedef, 'pin address getter failed').to.equal(0)
mcp8.pin(8).cfg.dir = 0
expect(mcp8.pin(8).cfg.dir, 'pin address setter failed').to.equal(0)
expect(mcp8.pin(8).cfg.ivrt, 'pin address getter failed').to.equal(1)
})
})
})