UNPKG

ds2482

Version:

Provides an interface for the Dallas DS2482 onewire bridge

401 lines (309 loc) 8.55 kB
'use strict'; const I2c = require('i2c'); const cmds = require('./commands'); const utils = require('./utils'); const ROM_SIZE = 8; class DS2482 { constructor(options) { // eslint-disable-next-line no-param-reassign options = options || {}; this.i2c = options.i2c || new I2c(0x18 | (options.address & 0x03), { device: options.device || '/dev/i2c-1', }); this.channel = null; } /* * Main API */ init() { return this.reset(); } reset() { this.lastFound = null; this.lastConflict = 0; return this._resetBridge() .then(() => this._resetWire()); } configureBridge(options) { let config = 0; if (options) { if (options.activePullup) config |= cmds.CONFIG.ACTIVE; if (options.strongPullup) config |= cmds.CONFIG.STRONG; if (options.overdrive) config |= cmds.CONFIG.OVERDRIVE; } return this._wait(true) .then(() => this._i2cWrite(cmds.WRITE_CONFIG, [((~config & 0x0F) << 4) | config])) .then(() => this._readBridge()) .then(resp => { if (config !== resp) { throw new Error('Failed to configure bridge'); } return resp; }); } selectChannel(num) { const ch = cmds.SELECTION_CODES[num || 0]; if (!ch) { return Promise.reject(new Error('Invalid channel')); } if (this.channel === num) { return Promise.resolve(ch.read); } return this._wait(true) .then(() => this.writeData(cmds.CHANNEL_SELECT, [ch.write])) .then(() => this._readBridge()) .then(resp => { if (ch.read !== resp) { throw new Error('Failed to select channel'); } this.channel = num; return resp; }); } sendCommand(cmd, rom) { return (rom ? this.matchROM(rom) : this.skipROM()) .then(() => this.writeData(cmd)); } search() { this.lastFound = null; this.lastConflict = 0; const found = []; const searchNext = () => ( this.searchROM().then(resp => { found.push(resp); if (this.lastConflict) { return searchNext(); } this.lastFound = null; this.lastConflict = 0; return found; }) ); return searchNext(); } searchByFamily(family) { if (typeof family === 'string') { // eslint-disable-next-line no-param-reassign family = parseInt(family, 16); } this.lastFound = Buffer.from([family, 0, 0, 0, 0, 0, 0, 0]); this.lastConflict = 64; const found = []; const searchNext = () => ( this.searchROM().then(resp => { if (this.lastFound.readUInt8(0) === family) { found.push(resp); } if (this.lastConflict > 7 && found.length) { return searchNext(); } this.lastFound = null; this.lastConflict = 0; return found; }) ); return searchNext(); } /* * Onewire ROM API */ searchROM() { const rom = Buffer.alloc(ROM_SIZE); let offset = 0; let mask = 0x01; let bit = 0; let lastConflict = 0; const direction = () => { if (this.lastFound && bit < this.lastConflict) { return this.lastFound.readUInt8(offset) & mask; } return bit === this.lastConflict ? 1 : 0; }; const searchNextBit = () => ( this.triplet(direction()).then(resp => { const sbr = (resp & cmds.STATUS.SINGLE_BIT); const tsb = (resp & cmds.STATUS.TRIPLE_BIT); const dir = (resp & cmds.STATUS.BRANCH_DIR); if (sbr && tsb) { throw new Error('Bad search result'); } if (!sbr && !tsb && !dir) { lastConflict = bit; } const part = rom.readUInt8(offset); rom.writeUInt8(dir ? part | mask : part & ~mask, offset); mask <<= 1; bit += 1; if (mask > 128) { offset += 1; mask = 0x01; } if (offset < rom.length) { return searchNextBit(); } if (rom[0] === 0) { throw new Error('ROM invalid'); } if (!utils.checkCRC(rom)) { throw new Error('CRC mismatch'); } this.lastFound = rom; this.lastConflict = lastConflict; return rom.toString('hex'); }) ); return this._resetWire() .then(() => this.writeData(cmds.ONE_WIRE_SEARCH_ROM)) .then(() => searchNextBit()); } readROM() { return this._resetWire() .then(() => this.writeData(cmds.ONE_WIRE_READ_ROM)) .then(() => this.readData(ROM_SIZE)) .then(rom => { if (rom[0] === 0) { throw new Error('ROM invalid'); } if (!utils.checkCRC(rom)) { throw new Error('CRC mismatch'); } return rom.toString('hex'); }); } matchROM(rom) { if (typeof rom === 'string') { // eslint-disable-next-line no-param-reassign rom = Buffer.from(rom, 'hex'); } if (rom[0] === 0 || rom.length !== ROM_SIZE) { return Promise.reject(new Error('ROM invalid')); } return this._resetWire() .then(() => this.writeData(cmds.ONE_WIRE_MATCH_ROM)) .then(() => this.writeData(rom)); } skipROM() { return this._resetWire() .then(() => this.writeData(cmds.ONE_WIRE_SKIP_ROM)); } /* * Onewire read/write API */ writeData(data) { if (!(data instanceof Buffer)) { // eslint-disable-next-line no-param-reassign data = Buffer.from(Array.isArray(data) ? data : [data]); } let offset = 0; const writeNextByte = () => ( this._i2cWrite(cmds.ONE_WIRE_WRITE_BYTE, data.slice(offset, offset + 1)) .then(() => this._wait()) .then(resp => { offset += 1; if (offset < data.length) { return writeNextByte(); } return resp; }) ); return this._wait(true).then(() => writeNextByte()); } readData(size) { const data = Buffer.alloc(size); let offset = 0; const readNextByte = () => ( this._i2cWrite(cmds.ONE_WIRE_READ_BYTE) .then(() => this._wait()) .then(() => this._readBridge(cmds.REGISTERS.DATA)) .then(resp => { data.writeUInt8(resp, offset); offset += 1; if (offset < data.length) { return readNextByte(); } return data; }) ); return this._wait(true).then(() => readNextByte()); } bit(setHigh) { return this._wait(true) .then(() => this._i2cWrite(cmds.ONE_WIRE_SINGLE_BIT, [setHigh ? 0x80 : 0])) .then(() => this._wait()) .then(resp => (resp & cmds.STATUS.SINGLE_BIT ? 1 : 0)); } triplet(dir) { return this._wait(true) .then(() => this._i2cWrite(cmds.ONE_WIRE_TRIPLET, [dir ? 0x80 : 0])) .then(() => this._wait()); } /* * Private Methods */ _resetBridge() { return this._i2cWrite(cmds.DEVICE_RESET) .then(() => this._wait()) .then(resp => { this.channel = 0; return resp; }); } _resetWire() { return this._wait(true) .then(() => this._i2cWrite(cmds.ONE_WIRE_RESET)) .then(() => this._wait()) .then(resp => { if (resp & cmds.STATUS.SHORT) { throw new Error('Detected onewire short'); } if (!(resp & cmds.STATUS.PRESENCE)) { throw new Error('Failed to detected any onewire devices'); } return resp; }); } _wait(setPointer) { const checkBusy = reg => ( this._readBridge(reg).then(resp => { if (resp & cmds.STATUS.BUSY) { return utils.delay(0).then(() => checkBusy()); } return resp; }) ); return Promise.race([ checkBusy(setPointer ? cmds.REGISTERS.STATUS : null), utils.delay(20).then(() => { throw new Error('Wait timeout'); }), ]); } _readBridge(reg) { const read = () => ( this._i2cRead().then(resp => (resp >>> 0) & 0xFF) ); if (reg) { return this._i2cWrite(cmds.SET_READ_POINTER, [reg]).then(read); } return read(); } _i2cWrite(cmd, bytes) { return new Promise((resolve, reject) => { if (bytes) { this.i2c.writeBytes(cmd, bytes, err => (err ? reject(err) : resolve())); } else { this.i2c.writeByte(cmd, err => (err ? reject(err) : resolve())); } }); } _i2cRead() { return new Promise((resolve, reject) => { this.i2c.readByte((err, resp) => (err ? reject(err) : resolve(resp))); }); } } Object.assign(DS2482, { ROM_SIZE, checkCRC: utils.checkCRC, }); module.exports = DS2482;