diff --git a/package.json b/package.json index 5e028e2..f6673b7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@johntalton/excamera-i2cdriver", - "version": "1.0.0", + "version": "2.0.0", "type": "module", "exports": { ".": "./src/i2c-driver.js", diff --git a/src/capture-generator/i2c-state-machine.js b/src/capture-generator/i2c-state-machine.js index f002f6d..e19ba31 100644 --- a/src/capture-generator/i2c-state-machine.js +++ b/src/capture-generator/i2c-state-machine.js @@ -25,7 +25,8 @@ export const i2cStateMachine = { [STATES.WARM_IDLE]: { 'idle': { target: STATES.WARM_IDLE }, 'data': { target: STATES.ADDRESSED, setAddress: true }, - 'stop': { target: STATES.STOPPED, closeTransaction: true } + 'stop': { target: STATES.STOPPED, closeTransaction: true }, + 'start': { target: STATES.ADDRESSING, openTransaction: true }, }, [STATES.STOPPED]: { 'idle': { target: STATES.WARM_IDLE }, @@ -39,13 +40,14 @@ export const i2cStateMachine = { 'stop': { target: STATES.STOPPED, closeTransaction: true } }, [STATES.ADDRESSED]: { - 'ack': { target: STATES.ADDRESSED_ACKED }, - 'nack': { target: STATES.WARM_IDLE } // no response + 'ack': { target: STATES.ADDRESSED_ACKED, online: true }, + 'nack': { target: STATES.WARM_IDLE, online: false } // no response }, [STATES.ADDRESSED_ACKED]: { 'data': { target: STATES.TRANSMITION, bufferBytes: true }, + 'stop': { target: STATES.STOPPED, closeTransaction: true }, - 'stop': { target: STATES.STOPPED, closeTransaction: true } + 'idle': { target: STATES.IDLE, closeTransaction: true } // ?? error }, [STATES.TRANSMITION]: { 'ack': { target: STATES.TRANSMITION_ACKED, flushBytes: true }, @@ -53,10 +55,13 @@ export const i2cStateMachine = { }, [STATES.TRANSMITION_ACKED]: { 'stop': { target: STATES.STOPPED, closeTransaction: true }, - 'data': { target: STATES.TRANSMITION, bufferBytes: true } + 'data': { target: STATES.TRANSMITION, bufferBytes: true }, + 'start': { target: STATES.ADDRESSING, openTransaction: true }, + 'idle': { target: STATES.IDLE, closeTransaction: true } // }, [STATES.TRANSMITION_NACK_END]: { 'stop': { target: STATES.STOPPED, clearAddress: true, closeTransaction: true } + // start }, diff --git a/src/capture-generator/state-machine-stream.js b/src/capture-generator/state-machine-stream.js index cd5ad38..1efbee7 100644 --- a/src/capture-generator/state-machine-stream.js +++ b/src/capture-generator/state-machine-stream.js @@ -37,7 +37,7 @@ async function takeActions(event, info, options) { if(setAddress) { options.address = event.value >> 1 - options.mode = event.value & 0b1 === 1 ? 'read' : 'write' + options.mode = (event.value & 0b1) === 1 ? 'read' : 'write' } const source = options.bytesAccumulator ?? [] diff --git a/src/i2c-driver.js b/src/i2c-driver.js index 072e099..3857a18 100644 --- a/src/i2c-driver.js +++ b/src/i2c-driver.js @@ -1,4 +1,4 @@ -import { ResponseBufferPasrser } from './parse-buffers.js' +import { ResponseBufferParser } from './parse-buffers.js' export const EXCAMERA_LABS_VENDOR_ID = 0x0403 export const EXCAMERA_LABS_PRODUCT_ID = 0x6015 @@ -54,55 +54,59 @@ const COMMAND_REPLY_LENGTH_SCAN = 112 const COMMAND_REPLY_LENGTH_TRANSMIT_STATUS_INFO = 80 const COMMAND_REPLY_LENGTH_INTERNAL_STATE = 80 +export class ExcameraLabsI2CDriverI2C { + #port -export class ExcameraLabsI2CDriver { - static from({ port }) { - return { - // - start: async (dev, readMode) => ExcameraLabsI2CDriver.start(port, dev, readMode), - stop: async () => ExcameraLabsI2CDriver.stop(port), - readACKAll: async (count) => ExcameraLabsI2CDriver.readACKAll(port, count), - readNACKFinal: async (count) => ExcameraLabsI2CDriver.readNACKFinal(port, count), - write: async (count, bufferSource) => ExcameraLabsI2CDriver.write(port, count, bufferSource), - - readRegister: async (dev, addr, count) => ExcameraLabsI2CDriver.readRegister(port, dev, addr, count), - - resetBus: async () => ExcameraLabsI2CDriver.resetBus(port), - - _transmitStatusInfo: async () => ExcameraLabsI2CDriver.transmitStatusInfo(port), - } - } - - static controlFrom({ port }) { - return { - // - reboot: async () => ExcameraLabsI2CDriver.reboot(port), + static from(options) { return new ExcameraLabsI2CDriverI2C(options) } - // - transmitStatusInfo: async () => ExcameraLabsI2CDriver.transmitStatusInfo(port), - internalState: async () => ExcameraLabsI2CDriver.internalState(port), - echoByte: async b => ExcameraLabsI2CDriver.echoByte(port, b), + /** + * @param {SerialPort} port + */ + constructor({ port }) { this.#port = port } - // - setPullupControls: async (sda, scl) => ExcameraLabsI2CDriver.setPullupControls(port, sda, scl), - setSpeed: async speed => ExcameraLabsI2CDriver.setSpeed(port, speed), - resetBus: async () => ExcameraLabsI2CDriver.resetBus(port), - scan: async () => ExcameraLabsI2CDriver.scan(port), + async start(dev, readMode) { return ExcameraLabsI2CDriver.start(this.#port, dev, readMode) } + async stop() { return ExcameraLabsI2CDriver.stop(this.#port) } + async readACKAll(count) { return ExcameraLabsI2CDriver.readACKAll(this.#port, count) } + async readNACKFinal(count) { return ExcameraLabsI2CDriver.readNACKFinal(this.#port, count) } + async write(count, bufferSource) { return ExcameraLabsI2CDriver.write(this.#port, count, bufferSource) } - // - enterMonitorMode: async () => ExcameraLabsI2CDriver.enterMonitorMode(port), - exitMonitorMode: async () => ExcameraLabsI2CDriver.exitMonitorMode(port), + async readRegister(dev, addr, count) { return ExcameraLabsI2CDriver.readRegister(this.#port, dev, addr, count) } + async resetBus() { return ExcameraLabsI2CDriver.resetBus(this.#port) } + async transmitStatusInfo() { return ExcameraLabsI2CDriver.transmitStatusInfo(this.#port) } +} - // - enterCaptureMode: async () => ExcameraLabsI2CDriver.enterCaptureMode(port), - // - enterBitbangMode: async () => ExcameraLabsI2CDriver.enterBitbangMode(port), - exitBitbangMode: async () => ExcameraLabsI2CDriver.exitBitbangModeo(port), - sendBitbangCommand: async command => ExcameraLabsI2CDriver.sendBitbangCommand(port, command), - endBitbangCommand: async () => ExcameraLabsI2CDriver.endBitbangCommand(port), - } - } +export class ExcameraLabsI2CDriver { + // static controlFrom({ port }) { + // return { + // // + // reboot: async () => ExcameraLabsI2CDriver.reboot(port), + + // // + // transmitStatusInfo: async () => ExcameraLabsI2CDriver.transmitStatusInfo(port), + // internalState: async () => ExcameraLabsI2CDriver.internalState(port), + // echoByte: async b => ExcameraLabsI2CDriver.echoByte(port, b), + + // // + // setPullupControls: async (sda, scl) => ExcameraLabsI2CDriver.setPullupControls(port, sda, scl), + // setSpeed: async speed => ExcameraLabsI2CDriver.setSpeed(port, speed), + // resetBus: async () => ExcameraLabsI2CDriver.resetBus(port), + // scan: async () => ExcameraLabsI2CDriver.scan(port), + + // // + // enterMonitorMode: async () => ExcameraLabsI2CDriver.enterMonitorMode(port), + // exitMonitorMode: async () => ExcameraLabsI2CDriver.exitMonitorMode(port), + + // // + // enterCaptureMode: async () => ExcameraLabsI2CDriver.enterCaptureMode(port), + + // // + // enterBitbangMode: async () => ExcameraLabsI2CDriver.enterBitbangMode(port), + // exitBitbangMode: async () => ExcameraLabsI2CDriver.exitBitbangMode(port), + // sendBitbangCommand: async command => ExcameraLabsI2CDriver.sendBitbangCommand(port, command), + // endBitbangCommand: async () => ExcameraLabsI2CDriver.endBitbangCommand(port), + // } + // } static async #streamChunkRead(defaultReader, recvLength) { @@ -111,24 +115,19 @@ export class ExcameraLabsI2CDriver { defaultReader.cancel() }, 2000) - const acc = [] - const accSize = () => { - return acc.map(a => a.length).reduce((sum, length) => sum += length, 0) + const scratch = { + accumulator: [], + length: 0 } while(true) { - // console.log('await read') const { value, done } = await defaultReader.read() + if(done) { break } - if(done) { - // console.log('DONE', value) - break - } - - //console.log('accumulate value', value, accSize(), recvLength) - acc.push([...value]) + scratch.accumulator.push(value) + scratch.length += value.length - if(accSize() >= recvLength) { + if(scratch.length >= recvLength) { // console.log('OVERSIZE') break } @@ -137,9 +136,10 @@ export class ExcameraLabsI2CDriver { clearTimeout(timer) //console.log({ acc }) - return Uint8Array.from(acc.reduce((flat, a) => { - return [ ...flat, ...a ] - }, [])) + // return Uint8Array.from(acc.reduce((flat, a) => { + // return [ ...flat, ...a ] + // }, [])) + return (new Blob(scratch.accumulator)).arrayBuffer() } static async #streamChunkRead_Alt(defaultReader, recvLength) { @@ -178,13 +178,17 @@ export class ExcameraLabsI2CDriver { return this.sendRecvCommand(port, command, sendBuffer, recvLength) } - /** @param {SerialPort} port */ + /** + * @param {SerialPort} port + * @param {ArrayBufferLike|ArrayBufferView} sendBuffer + */ static async sendRecvCommand(port, command, sendBuffer, recvLength) { // console.log('reader state', port.readable.locked, command, sendBuffer, recvLength) if(port.readable.locked) { console.warn('locked reader ...') - throw new Error('locked reader') + return new ArrayBuffer(0) + //throw new Error('locked reader') } const defaultWriter = port.writable.getWriter() @@ -227,21 +231,23 @@ export class ExcameraLabsI2CDriver { } finally { // console.log('finally') + await defaultWriter.ready + await defaultReader.releaseLock() await defaultWriter.releaseLock() } } static async transmitStatusInfo(port) { - return ResponseBufferPasrser.parseTransmitStatusInfo(await ExcameraLabsI2CDriver.sendRecvCommand(port, COMMAND_TRANSMIT_STATUS_INFO, undefined, COMMAND_REPLY_LENGTH_TRANSMIT_STATUS_INFO)) + return ResponseBufferParser.parseTransmitStatusInfo(await ExcameraLabsI2CDriver.sendRecvCommand(port, COMMAND_TRANSMIT_STATUS_INFO, undefined, COMMAND_REPLY_LENGTH_TRANSMIT_STATUS_INFO)) } static async internalState(port) { - return ResponseBufferPasrser.parseInternalStatus(await ExcameraLabsI2CDriver.sendRecvCommand(port, COMMAND_TRANSMIT_INTERNAL_STATE, undefined, COMMAND_REPLY_LENGTH_INTERNAL_STATE)) + return ResponseBufferParser.parseInternalStatus(await ExcameraLabsI2CDriver.sendRecvCommand(port, COMMAND_TRANSMIT_INTERNAL_STATE, undefined, COMMAND_REPLY_LENGTH_INTERNAL_STATE)) } static async echoByte(port, b) { - return ResponseBufferPasrser.parseEchoByte(await ExcameraLabsI2CDriver.sendRecvCommand(port, COMMAND_ECHO_BYTE, Uint8Array.from([ b ]), COMMAND_REPLY_LENGTH_SINGLE_BYTE)) + return ResponseBufferParser.parseEchoByte(await ExcameraLabsI2CDriver.sendRecvCommand(port, COMMAND_ECHO_BYTE, Uint8Array.from([ b ]), COMMAND_REPLY_LENGTH_SINGLE_BYTE)) } static async setSpeed(port, speed) { @@ -252,7 +258,7 @@ export class ExcameraLabsI2CDriver { static async start(port, dev, readMode) { const b = (dev << 1) | (readMode ? 1 : 0) - return ResponseBufferPasrser.parseStart(await ExcameraLabsI2CDriver.sendRecvCommand(port, COMMAND_START, Uint8Array.from([ b ]), COMMAND_REPLY_LENGTH_SINGLE_BYTE)) + return ResponseBufferParser.parseStart(await ExcameraLabsI2CDriver.sendRecvCommand(port, COMMAND_START, Uint8Array.from([ b ]), COMMAND_REPLY_LENGTH_SINGLE_BYTE)) } static async readNACKFinal(port, count) { @@ -280,7 +286,7 @@ export class ExcameraLabsI2CDriver { const command = COMMAND_MASK_WRITE | countMinusOne - return ResponseBufferPasrser.parseStart(await ExcameraLabsI2CDriver.sendRecvCommand(port, command, bufferSource, COMMAND_REPLY_LENGTH_SINGLE_BYTE)) + return ResponseBufferParser.parseStart(await ExcameraLabsI2CDriver.sendRecvCommand(port, command, bufferSource, COMMAND_REPLY_LENGTH_SINGLE_BYTE)) } static async readACKAll(port, count) { @@ -292,16 +298,16 @@ export class ExcameraLabsI2CDriver { } static async resetBus(port) { - return ResponseBufferPasrser.parseResetBus(await ExcameraLabsI2CDriver.sendRecvCommand(port, COMMAND_RESET_BUS, undefined, COMMAND_REPLY_LENGTH_SINGLE_BYTE)) + return ResponseBufferParser.parseResetBus(await ExcameraLabsI2CDriver.sendRecvCommand(port, COMMAND_RESET_BUS, undefined, COMMAND_REPLY_LENGTH_SINGLE_BYTE)) } static async readRegister(port, dev, addr, count) { const data = Uint8Array.from([ dev, addr, count ]) - return ResponseBufferPasrser.parseRegister(await ExcameraLabsI2CDriver.sendRecvCommand(port, COMMAND_READ_REGISTER, data, count)) + return ResponseBufferParser.parseRegister(await ExcameraLabsI2CDriver.sendRecvCommand(port, COMMAND_READ_REGISTER, data, count)) } static async scan(port) { - return ResponseBufferPasrser.parseScan(await ExcameraLabsI2CDriver.sendRecvCommand(port, COMMAND_SCAN, undefined, COMMAND_REPLY_LENGTH_SCAN)) + return ResponseBufferParser.parseScan(await ExcameraLabsI2CDriver.sendRecvCommand(port, COMMAND_SCAN, undefined, COMMAND_REPLY_LENGTH_SCAN)) } static async enterMonitorMode(port) { diff --git a/src/parse-buffers.js b/src/parse-buffers.js index c3c8367..88a2def 100644 --- a/src/parse-buffers.js +++ b/src/parse-buffers.js @@ -18,13 +18,31 @@ const PULLUP_LOOKUP = { [PULLUP_1_1_K]: '1.1K' } -export class ResponseBufferPasrser { - static parsePullup(b) { - const pullup_mask = 0b111 - const sda = b & pullup_mask - const scl = (b >> 3) & pullup_mask +export function* range(start, end) { + yield start + if(start === end) { return } + yield *range(start + 1, end) +} + +function assertByteLength(buffer, length) { + if(buffer.byteLength !== length) { throw new Error('invalid byte length') } +} + +function assertNonZeroByteLength(buffer) { + if(buffer.byteLength <= 0) { throw new Error('zero length buffer') } +} + + +export class ResponseBufferParser { + /** @param {number} value */ + static _parsePullup(value) { + + const PULL_UP_MASK = 0b111 + + const sda = value & PULL_UP_MASK + const scl = (value >> 3) & PULL_UP_MASK return { - b, + value, sda: PULLUP_LOOKUP[sda], sdaValue: sda, scl: PULLUP_LOOKUP[scl], @@ -32,6 +50,7 @@ export class ResponseBufferPasrser { } } + /** @param {ArrayBufferLike|ArrayBufferView} buffer */ static parseTransmitStatusInfo(buffer) { const decoder = new TextDecoder() const str = decoder.decode(buffer) @@ -62,63 +81,97 @@ export class ResponseBufferPasrser { sda: parseInt(sda, 2), scl: parseInt(scl, 2), speed: parseInt(speed, 10), - pullups: ResponseBufferPasrser.parsePullup(parseInt(pullups, 16)), + pullups: ResponseBufferParser._parsePullup(parseInt(pullups, 16)), crc: parseInt(crc, 16) // CRC-16-CCITT } } + /** @param {ArrayBufferLike|ArrayBufferView} buffer */ static parseEchoByte(buffer) { - if(buffer.byteLength <= 0) { throw new Error('length missmatch') } - return buffer[0] + assertNonZeroByteLength(buffer) + + const u8 = ArrayBuffer.isView(buffer) ? + new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength) : + new Uint8Array(buffer) + + return u8[0] } + /** @param {ArrayBufferLike|ArrayBufferView} buffer */ static parseStart(buffer) { - if(buffer.byteLength !== 1) { throw new Error('length missmatch') } - const reserved5 = 0b00110 - const arb_mask = 0b100 - const to_mask = 0b010 - const ack_mask = 0b001 + assertByteLength(buffer, 1) - const b = buffer[0] + const u8 = ArrayBuffer.isView(buffer) ? + new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength) : + new Uint8Array(buffer) - const valid = ((b >> 3) & reserved5) === reserved5 + const RESERVED_5_MASK = 0b00110 + const ARBITRATION_MASK = 0b100 + const TO_MASK = 0b010 + const ACK_MASK = 0b001 + + const [ b ] = u8 + + const valid = ((b >> 3) & RESERVED_5_MASK) === RESERVED_5_MASK return { valid, - arb: b & arb_mask, - to: b & to_mask, - ack: b & ack_mask + arb: b & ARBITRATION_MASK, + to: b & TO_MASK, + ack: b & ACK_MASK } } + /** @param {ArrayBufferLike|ArrayBufferView} buffer */ static parseRegister(buffer) { - if(buffer.byteLength !== 1) { throw new Error('length missmatch') } - return buffer[0] + assertByteLength(buffer, 1) + + const u8 = ArrayBuffer.isView(buffer) ? + new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength) : + new Uint8Array(buffer) + + return u8[0] } + /** @param {ArrayBufferLike|ArrayBufferView} buffer */ static parseResetBus(buffer) { - const sda_mask = 0b10 - const scl_mask = 0b01 - const b = buffer[0] - const sda = b & sda_mask - const scl = b & scl_mask + assertByteLength(buffer, 1) + + const u8 = ArrayBuffer.isView(buffer) ? + new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength) : + new Uint8Array(buffer) + + const [ b ] = u8 + + const SDA_MASK = 0b10 + const SCL_MASK = 0b01 + + const sda = b & SDA_MASK + const scl = b & SCL_MASK + return { sda, scl } } + /** @param {ArrayBufferLike|ArrayBufferView} buffer */ static parseScan(buffer) { - const startDev = 0x08 - // scan range 0x08 to 0x77 - - if(buffer.byteLength !== 112) { throw new Error('length missmatch') } - - return [ ...buffer ] - .map(b => ResponseBufferPasrser.parseStart(Uint8Array.from([ b ]))) - .map((result, index) => ({ - ...result, - dev: startDev + index - })) + assertByteLength(buffer, 112) + + const u8 = ArrayBuffer.isView(buffer) ? + new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength) : + new Uint8Array(buffer) + + const SCAN_START_ADDRESS = 0x08 // scan range 0x08 to 0x77 + + return range(0, u8.byteLength - 1) + .map(index => { + const result = ResponseBufferParser.parseStart(u8.subarray(index, index + 1)) + return { + ...result, + dev: SCAN_START_ADDRESS + index + } + }) } static parseInternalStatus(buffer) {