diff --git a/.gitignore b/.gitignore index 24c853d..2882970 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /node_modules/ /coverage/ /syncd/ +*.lock diff --git a/.npmignore b/.npmignore index f16fc41..80b7ab3 100644 --- a/.npmignore +++ b/.npmignore @@ -2,3 +2,4 @@ tests/ test/ *.test.js testing/ +examples diff --git a/demo/interrupt.js b/demo/interrupt.js deleted file mode 100644 index 74dceb9..0000000 --- a/demo/interrupt.js +++ /dev/null @@ -1,53 +0,0 @@ -// to try this demo -// npm install @uci/interrupt -// Connect 5V MCP interrupt pin via 3.3 V logic level!!!!! converter to a gpio pin -// Set your SETTINGS below -// npm run inter - -const Bus = require('@uci/i2c').Bus, - MCP = require('../src/mcp23008-17'), - // use this require when using your own code - // MCP = require('@uci/mcp') - Interrupt = require('@uci/interrupt') - -let bus = new Bus() - -// SETTINGS -let ADDR = 0x20 -let CHIP = 'MCP23017' -let MS = 300 // so humans can watch the light otherwise set to zero -let pinA = 12 -let pinB = 16 - -let id = `${CHIP} at I2C bus address of 0x${ADDR.toString(16)}` - -let mcp = new MCP[CHIP](bus, ADDR, { - pin_cfg_default: 'toggle_switch', - id: id, - interruptA: new Interrupt(pinA, ihandler), - interruptB: new Interrupt(pinB, ihandler), - stateA: 128, - pidsA: ['sw1', 'sw2', 'sw3', 'sw4', 'sw5', 'sw6', 'sw7', 'sw8'], - pidsB: ['sw9', 'sw10', 'sw11', 'sw12', 'sw13', 'sw14', 'sw15', 'sw16'] -}); // must have ; for async immediate function following - -(async function () { - - await mcp.init() - await mcp.start() - - mcp.on('fired', (data) => { - console.log(`bank event 'fired' data => ${data.bank} on port ${data.port} pin# ${data.pin} pid ${data.pid}`) - }) - -})() - -//=================================// - -async function ihandler(port) { - let pin = await this.interruptPin(port, 'PLC') - await this.interruptReset(port) - let data = { bank: this.id, port: port, pin: pin, pid: this.pid(pin, port) } - // console.log('data in handler', data) - return data -} diff --git a/demo/sample.js b/demo/sample.js deleted file mode 100644 index ce85a6d..0000000 --- a/demo/sample.js +++ /dev/null @@ -1,52 +0,0 @@ -const Bus = require('@uci/i2c').Bus, - MCP = require('../src/mcp23008-17'), - // use this require when using your own code - // MCP = require('@uci/mcp') - pause = require('@uci/utils').pPause - -let bus = new Bus() - -let ADDR = 0x27 -let CHIP = 'MCP23017' -let MS = 300 // so humans can watch the light otherwise set to zero -let PORT = 'B' - -let mcp = new MCP[CHIP](bus, ADDR); - -(async function () { - - await mcp.init() - await mcp.allOff(PORT) // start clean - console.log('all pins off') - await pause(MS) - - let pins = [1, 7, 8] - await mcp.pinsOn(pins, PORT, 'PLC') - let result = await mcp.readPort(PORT, { format: 'PLC' }) - console.log(`${pins} on = current state: ${result.sort()}`) - await pause(MS) - - pins = [2, 1, 6] - await mcp.toggle(pins, PORT, 'PLC') - result = await mcp.readPort(PORT, { format: 'PLC' }) - console.log(`${pins} toggle = current state: ${result.sort()}`) - await pause(MS) - - pins = [2, 1, 7] - await mcp.pinsOff(pins, PORT, 'PLC') - result = await mcp.readPort(PORT, { format: 'PLC' }) - console.log(`${pins} off = current state: ${result.sort()}`) - await pause(MS) - - let pin = 7 - result = await mcp.readPin(pin, PORT) ? 'on' : 'off' - console.log(`Pin ${pin} is ${result}`) - - pin = 6 - result = await mcp.readPin(pin, PORT) ? 'on' : 'off' - console.log(`Pin ${pin} is ${result}`) - - console.log('all pins off') - await mcp.allOff(PORT) // clear port after each test - -})() diff --git a/package.json b/package.json index 7e1a62d..e4aedb5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@uci/mcp", "main": "src", - "version": "0.1.3", + "version": "0.1.4", "description": "Classes and Helper Functions for using the MCP chip on I2C Bus", "scripts": { "relays": "node -r esm examples/relays", @@ -28,16 +28,13 @@ }, "homepage": "https://github.com/uCOMmandIt/uci-mcp#readme", "dependencies": { - "@uci/i2c-device": "^0.1.3", - "@uci/interrupt": "^0.1.4", - "@uci/logger": "^0.0.2", - "@uci/mqtt": "^0.0.3", + "@uci/i2c-device": "^0.1.4", + "@uci/logger": "0.0.3", "@uci/utils": "^0.1.1" }, "devDependencies": { "chai": "^4.1.2", "codecov": "^3.0.0", - "debug": "^3.1.0", "esm": "^3.0.22", "istanbul": "^0.4.5", "mocha": "^5.0.1", diff --git a/src/cjs/mcp23008-17.js b/src/cjs/mcp23008-17.js deleted file mode 100644 index a4db9fc..0000000 --- a/src/cjs/mcp23008-17.js +++ /dev/null @@ -1,304 +0,0 @@ -'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 - } -} diff --git a/src/cjs/port-pin.js b/src/cjs/port-pin.js deleted file mode 100644 index 08e0a84..0000000 --- a/src/cjs/port-pin.js +++ /dev/null @@ -1,140 +0,0 @@ -'use strict' - -const _ = require('@uci/utils') - -class Pin { - - constructor(id, address, opts = {}) { - // at a minimum a byte address is required - this.address = address - // a pin id MUST be supplied in opts 1 to 8 - this.id = id - // a custom pin configuration can be passed or choosen from one of the sets, if neither than the default output config is chosen - // TODO allow custom pin configurations based on passed opts so individual pins can be configured differently - if (opts.pin_cfg_default) { - if (_.keyExists(config_sets, opts.pin_cfg_default)) { - this.config = _.clone(config_sets[opts.pin_cfg_default]) - } else { - console.log(`WARNING config set ${opts.pin_cfg_default} not found using default output set`) - this.config = _.clone(config_sets.output) - } - } else { - this.config = _.clone(config_sets.output) - } - // after setting a default overwrite part or all of pin config with custom - if (opts.pinConfig) { - this.config = Object.assign(this.config, opts.pinConfig) - } - // this.cfgSet = false // set to true when for sure the configuration has been written, changed to false when config is changed. - } - - //getters and setters - get adr() { - return this.address - } - - get pid() { return this.id } - - set pid(pid) { this.id = pid } - - // config getters and setters - get cfg() { - return this.config - } - set cfg(config) { - this.config = _.merge(this.config, config) // this merges partial changes - // write the config here. - } - - show() { - console.log(`\n=====Pin ${this.id} =====\n Decimal Address ${this.address}`) - console.log('Conifguration: ', this.config) - } - -} // End GPIO Pin - -//====================== - -// A port of 8 GPIO pins -class Port { - - constructor(opts = {}) { - - this.id = opts.portID ? opts.portID : '' - this.desc = opts.portDesc ? opts.portDesc : '' - // Create group of 8 pins for port - this.pins = new Array(8) - let pid = '' - for (let i = 0; i < 8; i++) { - pid = opts.pids ? opts.pids[i] : i + 1 - this.pins[i] = new Pin(pid, Math.pow(2, i), opts) - } - } - - // return handle to a port's pin from id - pin(id) { - for (let pins of this.pins) { - if (pin.id === id) { - return pin - } - } - return false - } - // return pin's id on a port from address - pid(address) { - return Math.log(address) / Math.log(2) - } - -} // end GPIO Port CLass - -//==================== - -// return list of all pin configs, handle to particular config or adding of custom pin configuration at runtime -function configs(cfg) { - if (_.isString(cfg)) { - if (cfg === 'list') { - return Object.keys(config_sets) - } - if (_.keyExists(config_sets, cfg)) { - return config_sets[cfg] - } - return 'configuration no found' - } - _.merge(config_sets, cfg) - // console.log(config_sets) - return `pin configuration ${Object.keys(cfg)} added to configuration set` -} - -module.exports = { - Port, - Pin, - configs -} - -let 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 - } -} diff --git a/test/gpio.test.js.off b/test/gpio.test.js.off deleted file mode 100644 index 10d1d5f..0000000 --- a/test/gpio.test.js.off +++ /dev/null @@ -1,63 +0,0 @@ -'use strict' - -const expect = require('chai').expect, - gpio = require('../lib/gpio') - -let pin = new gpio.Pin(128) - -describe('GPIO Pin Class - ', function () { - - it('Verify getter and setter ', function () { - expect(pin.cfg, 'config getter failed').to.deep.equal(gpio.configs('output')) - expect(pin.cfg.intr, 'config get subkeys failed').to.equal(0) - - pin.cfg = gpio.configs('toggle_switch') - expect(pin.cfg, 'config setter failed').to.deep.equal(gpio.configs('toggle_switch')) - expect(pin.adr.value, 'pin address getter failed').to.equal(128) - expect(pin.adr.fmt, 'pin address format getter failed').to.equal('DEC') - expect(pin.id, 'pin id default from address failed').to.equal(8) - pin.adr = { - 'val': 20, - 'fmt': 'HEX' - } - expect(pin.adr.value, 'pin address setter failed').to.equal(20) - expect(pin.adr.fmt, 'pin address format setter failed').to.equal('HEX') - pin.id = 2 - expect(pin.id, 'pin id setter failed').to.equal(2) - pin.adr = 32 - expect(pin.adr.value, 'pin address setter failed').to.equal(32) - expect(pin.adr.fmt, 'pin address format setter failed').to.equal('DEC') - }) - - it('should merge partial config change', function () { - pin.cfg = gpio.configs('output') - pin.cfg = { dir: 15 } - let merged = { dir: 15, ivrt: 0, pullup: 0, intr: 0, usedef: 0, defval: 0 } - expect(pin.cfg, 'single config value change failed').to.deep.equal(merged) - }) - - // TODO waiting on new clone function - // it('should clone a new Pin with change of address', function () { - // expect(newPin.adr.val, "clone pin failed").to.equal(4) - // expect(newPin.cfg.dir, "clone pin failed").to.equal(15) - // }) - -}) - -let port = new gpio.Port({ portID: 'A1' }) - -describe('GPIO Port Class - ', function () { - - it('should set the pin ids and addresses by default', function () { - expect(port.pin(7).id, 'a pin\'s id failed').to.equal(7) - expect(port.pin(7).adr.value, 'id and address don\'t match').to.equal(64) - }) - - let port2 = new gpio.Port({ reverse: true }) - - it('reversing pin number order should work', function () { - expect(port2.pin(2).adr.value, 'reversing pin numbers failed').to.equal(64) - }) - - //TODO add a pin map for the port so pins could be arranged in any order -})