UNPKG

openi2c

Version:

This library is a set of cross platform drivers for common I2C devices.

831 lines (705 loc) 30.3 kB
import * as i2c from 'i2c-bus'; import { sleep } from '../../utils'; import { debug } from '../../debug'; import { Module } from '../Module'; import { Packet, PacketError, PacketHeader } from './Packet'; export * from './constants'; import { BNO_CHANNEL_EXE, FEATURE_ENABLE_TIMEOUT, ENABLED_ACTIVITIES, DEFAULT_REPORT_INTERVAL, SHTP_REPORT_PRODUCT_ID_REQUEST, SET_FEATURE_COMMAND, BNO_CHANNEL_CONTROL, SHTP_REPORT_PRODUCT_ID_RESPONSE, BNO_CHANNEL_SHTP_COMMAND, AVAIL_SENSOR_REPORTS, REPORT_LENGTHS, ME_CALIBRATE, SAVE_DCD, GET_FEATURE_RESPONSE, INITIAL_REPORTS, COMMAND_RESPONSE, BNO_REPORT_STEP_COUNTER, BNO_REPORT_SHAKE_DETECTOR, BNO_REPORT_STABILITY_CLASSIFIER, BNO_REPORT_ACTIVITY_CLASSIFIER, BNO_REPORT_MAGNETOMETER, RAW_REPORTS, BNO_HEADER_LEN, BNO_REPORT_ACCELEROMETER, BNO_REPORT_GYROSCOPE, BNO_REPORT_ROTATION_VECTOR, ME_CAL_CONFIG, COMMAND_REQUEST, DEFAULT_TIMEOUT, ME_GET_CAL } from './constants'; export const defaultConfig = { ADDRESS: 0x4B, DATA_BUFFER_SIZE: 512, // Not sure if this is nessesary in the config. PACKET_READ_TIMEOUT: 2.0, } type Config = typeof defaultConfig; export class BNO08X extends Module<Config> { private dataBuffer: Buffer; private commandBuffer: Buffer = Buffer.alloc(12) private readings: Record<number, any> = {}; // for saving the most recent reading when decoding several packets private meCalibrationStartedAt: number = -1; private calibrationComplete: boolean = false; private magnetometerAccuracy: number = 0; private waitForInitialize: boolean = true; private initComplete: boolean = false; private idRead: boolean = false; private packetSlices: [number, Buffer][] = []; // TODO: this is wrong there should be one per channel per direction private sequenceNumber: number[] = [0, 0, 0, 0, 0, 0]; private twoEndedSequenceNumbers: Map<number, number> = new Map(); private dcdSavedAt: number = -1; constructor(busNumber: number = 0, address: number = defaultConfig.ADDRESS, config: Partial<Config> = defaultConfig) { super(busNumber, address, config); this.dataBuffer = Buffer.alloc(this.config.DATA_BUFFER_SIZE); } /** * Initialize the sensor */ async init() { for (let i = 0; i < 3; i++) { // this.hardReset(); await this.softReset(); try { if (await this.checkId()) { return; } } catch (error) { await sleep(500); } } throw new Error("Could not read ID"); } getFeatureEnableReport( featureId: keyof typeof AVAIL_SENSOR_REPORTS, sensorSpecificConfig: number = 0, reportInterval: number = DEFAULT_REPORT_INTERVAL, ): Buffer { let setFeatureReport = Buffer.alloc(17); setFeatureReport[0] = SET_FEATURE_COMMAND; setFeatureReport[1] = featureId; setFeatureReport.writeUInt32LE(reportInterval, 5); setFeatureReport.writeUInt32LE(sensorSpecificConfig, 13); return setFeatureReport; } async beginCalibration() { await this.sendMeCommand( [ 1, // calibrate accel 1, // calibrate gyro 1, // calibrate mag ME_CAL_CONFIG, 0, // calibrate planar acceleration 0, // 'on_table' calibration 0, // reserved 0, // reserved 0, // reserved ] ) this.calibrationComplete = false; } async calibrationStatus() { await this.sendMeCommand([ 0, // calibrate accel 0, // calibrate gyro 0, // calibrate mag ME_GET_CAL, // constant value for getting calibration status 0, // calibrate planar acceleration 0, // 'on_table' calibration 0, // reserved 0, // reserved 0, // reserved ]); return this.magnetometerAccuracy; } private async sendMeCommand(subcommandParams?: number[]): Promise<void> { const startTime = Date.now(); const localBuffer = this.commandBuffer; this.insertCommandRequestReport( ME_CALIBRATE, this.commandBuffer, // should use this._dataBuffer :\ but sendPacket doesn't this.getReportSeqId(COMMAND_REQUEST), subcommandParams, ); await this.sendPacket(BNO_CHANNEL_CONTROL, localBuffer); this.incrementReportSeq(COMMAND_REQUEST); while (Date.now() - startTime < DEFAULT_TIMEOUT) { await this.processAvailablePackets(); if (this.meCalibrationStartedAt > startTime) { break; } } } private incrementReportSeq(reportId: number): void { const current = this.twoEndedSequenceNumbers.get(reportId) ?? 0; this.twoEndedSequenceNumbers.set(reportId, (current + 1) % 256); } private getReportSeqId(reportId: number): number { return this.twoEndedSequenceNumbers.get(reportId) ?? 0; } private insertCommandRequestReport( command: number, buffer: Uint8Array, nextSequenceNumber: number, commandParams?: number[] ): void { if (commandParams && commandParams.length > 9) { throw new Error(`Command request reports can only have up to 9 arguments but ${commandParams.length} were given`); } buffer.fill(0, 0, 12); buffer[0] = COMMAND_REQUEST; buffer[1] = nextSequenceNumber; buffer[2] = command; if (commandParams === undefined) { return; } commandParams.forEach((param, idx) => { buffer[3 + idx] = param; }); } /** * Used to enable a given feature of the BNO08x */ async enableFeature(featureId: keyof typeof AVAIL_SENSOR_REPORTS) { this.debug("\n********** Enabling feature id:", featureId, "**********") let setFeatureReport: Buffer; if (featureId === BNO_REPORT_ACTIVITY_CLASSIFIER) { setFeatureReport = this.getFeatureEnableReport(featureId, ENABLED_ACTIVITIES); } else { setFeatureReport = this.getFeatureEnableReport(featureId); } let featureDependency = RAW_REPORTS[featureId]; //rawReports.get(featureId, null); if (featureDependency && !(featureDependency in this.readings)) { this.debug("Enabling feature dependency:", featureDependency); await this.enableFeature(featureDependency); } // if the feature was enabled it will have a key in the readings dict this.debug("Enabling", featureId); await this.sendPacket(BNO_CHANNEL_CONTROL, setFeatureReport); let startTime = Date.now(); // 1 while ((Date.now() - startTime) < FEATURE_ENABLE_TIMEOUT) { await this.processAvailablePackets(10); if (featureId in this.readings) { return; } } throw new Error("Was not able to enable feature " + featureId); } /** * A tuple representing the acceleration measurements on the X, Y, and Z * axes in meters per second squared */ async acceleration(): Promise<[number, number, number]> { await this.processAvailablePackets(); if (this.readings[BNO_REPORT_ACCELEROMETER]) { return this.readings[BNO_REPORT_ACCELEROMETER]; } else { throw new Error("No accel report found, is it enabled?"); } } /** * Get the roll pitch yaw on xyz axis using acceleration */ async euler(): Promise<[number, number, number]> { // Placeholder values for acceleration on x, y, and z axes // Replace these with actual accelerometer readings const [ax, ay, az] = await this.acceleration() // Calculate roll and pitch based on the acceleration data // Roll (rotation around x-axis) const roll = Math.atan2(ay, az); // Pitch (rotation around y-axis) const pitch = Math.atan2(-ax, Math.sqrt(ay * ay + az * az)); // Yaw (rotation around z-axis) cannot be determined from acceleration alone // Placeholder value for yaw const yaw = 0; // This would require a magnetometer to calculate accurately // Convert radians to degrees const rollDeg = roll * (180 / Math.PI); const pitchDeg = pitch * (180 / Math.PI); const yawDeg = yaw * (180 / Math.PI); return [rollDeg, pitchDeg, yawDeg]; } /** * A tuple representing Gyro's rotation measurements on the X, Y, and Z * axes in radians per second */ async gyro(): Promise<[number, number, number]> { await this.processAvailablePackets(); if (this.readings[BNO_REPORT_GYROSCOPE]) { return this.readings[BNO_REPORT_GYROSCOPE]; } else { throw new Error("No gyro report found, is it enabled?"); } } /** * A tuple of the current magnetic field measurements on the X, Y, and Z axes */ async magnetic(): Promise<[number, number, number]> { await this.processAvailablePackets(); if (this.readings[BNO_REPORT_MAGNETOMETER]) { return this.readings[BNO_REPORT_MAGNETOMETER]; } else { throw new Error("No magfield report found, is it enabled?"); } } /** * The current heading in degrees */ async heading() { const [magX, magY] = await this.magnetic(); // Assuming bno.magnetic() returns [magX, magY, magZ] let heading = Math.atan2(magY, magX) * (180 / Math.PI); // Convert radians to degrees if (heading < 0) { heading += 360; // Adjust for negative values to get a full 360° range } return heading; } /** * A quaternion representing the current rotation vector */ async quaternion(): Promise<[number, number, number, number]> { await this.processAvailablePackets(); if (this.readings[BNO_REPORT_ROTATION_VECTOR]) { return this.readings[BNO_REPORT_ROTATION_VECTOR]; } else { throw new Error("No quaternion report found, is it enabled?"); } } /** * A quaternion representing the current rotation vector expressed as a quaternion with no * specific reference for heading, while roll and pitch are referenced against gravity. To * prevent sudden jumps in heading due to corrections, the `gameQuaternion` property is not * corrected using the magnetometer. Some drift is expected */ async gameQuaternion(): Promise<[number, number, number, number]> { await this.processAvailablePackets(); if (this.readings[BNO_REPORT_ROTATION_VECTOR]) { return this.readings[BNO_REPORT_ROTATION_VECTOR]; } else { throw new Error("No quaternion report found, is it enabled?"); } } /** * The number of steps detected since the sensor was initialized */ async steps(): Promise<number>{ await this.processAvailablePackets(); if (this.readings[BNO_REPORT_STEP_COUNTER]) { return this.readings[BNO_REPORT_STEP_COUNTER]; } else { throw new Error("No quaternion report found, is it enabled?"); } } async processAvailablePackets(maxPackets: number | null = null) { let processedCount = 0; while (await this.dataReady()) { if (maxPackets && processedCount > maxPackets) { return; } let packet: Packet; try { packet = await this.readPacket(); } catch (error) { if (error instanceof PacketError) { continue; } else { throw error; } } this.handlePacket(packet); processedCount += 1; } this.debug(" ** DONE! **"); } private async hardReset() { } /** * Reset the sensor to an initial unconfigured state */ private async softReset() { this.debug("Soft resetting...", ""); const data = Buffer.from([1]); await this.sendPacket(BNO_CHANNEL_EXE, data); await sleep(500); for (let i = 0; i < 3; i++) { try { const _packet = await this.readPacket(); } catch (error) { if (error instanceof PacketError) { await sleep(500); } else { throw error; } } } this.debug("OK!"); } private async checkId(): Promise<boolean> { this.debug("\n********** READ ID **********"); if (this.idRead) { return true; } let data = Buffer.from([ SHTP_REPORT_PRODUCT_ID_REQUEST, 0, // padding ]); this.debug("\n** Sending ID Request Report **"); await this.sendPacket(BNO_CHANNEL_CONTROL, data); this.debug("\n** Waiting for packet **"); // _a_ packet arrived, but which one? // TODO this could cause an infinite loop? False never reached? while (true) { const packet = await this.waitForPacketType(BNO_CHANNEL_CONTROL, SHTP_REPORT_PRODUCT_ID_RESPONSE); let sensorId = this.parseSensorId(packet); if (sensorId) { this.idRead = true; return true; } this.debug("Packet didn't have sensor ID report, trying again"); } return false; } private parseSensorId(packet: Packet): number | null { if (packet.data[0] !== SHTP_REPORT_PRODUCT_ID_RESPONSE) { return null; } const swMajor = packet.data.readUInt8(2); const swMinor = packet.data.readUInt8(3); const swPatch = packet.data.readUInt16LE(12); const swPartNumber = packet.data.readUInt32LE(4); const swBuildNumber = packet.data.readUInt32LE(8); this.debug("FROM PACKET SLICE:"); this.debug(`*** Part Number: ${swPartNumber}`); this.debug(`*** Software Version: ${swMajor}.${swMinor}.${swPatch}`); this.debug(`*** Build: ${swBuildNumber}`); return swPartNumber; } private async waitForPacketType(channelNumber: number, reportId: number | null = null, timeout: number = 5000.0): Promise<Packet> { this.debug(`** Waiting for packet on channel ${channelNumber}${reportId ? ` with report id ${reportId.toString(16)}` : ""}`); let start_time = Date.now(); // TODO make these async somehow. while ((Date.now() - start_time) / 1000 < timeout) { let newPacket = await this.waitForPacket(); if (newPacket.channelNumber === channelNumber) { if (reportId !== null) { if (newPacket.reportId === reportId) { return newPacket; } } else { return newPacket; } } if (![BNO_CHANNEL_EXE, BNO_CHANNEL_SHTP_COMMAND].includes(newPacket.channelNumber)) { this.debug("passing packet to handler for de-slicing"); this.handlePacket(newPacket); } } throw new Error(`Timed out waiting for a packet on channel ${channelNumber}`); } private reportLength(report_id: number): number { if (report_id < 0xF0) { // it's a sensor report return AVAIL_SENSOR_REPORTS[report_id][2]; } return REPORT_LENGTHS[report_id]; } private separateBatch(packet: Packet) { const reportSlices: [number, Buffer][] = []; // get first report id, loop up its report length // read that many bytes, parse them let nextByteIndex = 0; while (nextByteIndex < packet.header.dataLength) { const reportId = packet.data[nextByteIndex]; const requiredBytes = this.reportLength(reportId); const unprocessedByteCount = packet.header.dataLength - nextByteIndex; // handle incomplete remainder if (unprocessedByteCount < requiredBytes) { throw new Error("Unprocessable Batch bytes " + unprocessedByteCount); } // we have enough bytes to read // add a slice to the list that was passed in const reportSlice = packet.data.subarray(nextByteIndex, nextByteIndex + requiredBytes); reportSlices.push([reportSlice[0], reportSlice]); nextByteIndex = nextByteIndex + requiredBytes; } return reportSlices; } private handlePacket(packet: Packet): void { try { const packetSlices = this.separateBatch(packet); while (packetSlices.length > 0) { this.processReport(...packetSlices.pop()!); } } catch (error) { console.error(packet); // throw error; } } private handleCommandResponse(reportBytes: Buffer): void { // in origional code: _parse_command_response // CMD response report: // 0 Report ID = 0xF1 // 1 Sequence number // 2 Command // 3 Command sequence number // 4 Response sequence number // 5 R0-10 A set of response values. The interpretation of these values is specific // to the response for each command. const reportBody = Array.from(reportBytes.subarray(0, 5)); const responseValues = Array.from(reportBytes.subarray(5, 16)); const [_report_id, _seq_number, command, _command_seq_number, _response_seq_number] = reportBody; const [commandStatus, ..._rest] = responseValues; if (command === ME_CALIBRATE && commandStatus === 0) { this.meCalibrationStartedAt = Date.now(); } if (command === SAVE_DCD) { if (commandStatus === 0) { this.dcdSavedAt = Date.now(); } else { throw new Error("Unable to save calibration data"); } } } private handleControlReport(reportId: number, reportBytes: Buffer): void { // if (reportId === SHTP_REPORT_PRODUCT_ID_RESPONSE) { // // in origional code: _parse_sensor_id in main file not in class // const sensorId = this.parseSensorId(reportBytes); // if (!sensorId) { // throw new Error(`Wrong report id for sensor id: ${reportBytes[0].toString(16)}`); // } // } if (reportId === GET_FEATURE_RESPONSE) { // in origional code: _parse_get_feature_response_report // unpack_from("<BBBHIII", report_bytes) const getFeatureReport = [ reportBytes.readUint8(0), // report_id reportBytes.readUint8(1), reportBytes.readUint8(2), reportBytes.readUInt16LE(3), reportBytes.readUInt32LE(5), reportBytes.readUInt32LE(9), reportBytes.readUInt32LE(13) ]; const [_report_id, featureReportId, ..._remainder] = getFeatureReport; this.readings[featureReportId] = INITIAL_REPORTS[featureReportId] || [0.0, 0.0, 0.0]; } if (reportId === COMMAND_RESPONSE) { this.handleCommandResponse(reportBytes); } } private processReport(reportId: number, reportBytes: Buffer): void { if (reportId >= 0xF0) { this.handleControlReport(reportId, reportBytes); return; } // this.debug("\tProcessing report:", reports[report_id]); // if (this._debug) { // let outstr = ""; // for (let idx = 0; idx < report_bytes.length; idx++) { // const packet_byte = report_bytes[idx]; // const packet_index = idx; // if ((packet_index % 4) === 0) { // outstr += `\nDBG::\t\t[0x${packet_index.toString(16).padStart(2, '0')}] `; // } // outstr += `0x${packet_byte.toString(16).padStart(2, '0')} `; // } // this._dbg(outstr); // this._dbg(""); // } if (reportId === BNO_REPORT_STEP_COUNTER) { this.readings[reportId] = this.parseStepCounterReport(reportBytes); return; } if (reportId === BNO_REPORT_SHAKE_DETECTOR) { const shakeDetected = this.parseShakeReport(reportBytes); if (!this.readings[BNO_REPORT_SHAKE_DETECTOR]) { this.readings[BNO_REPORT_SHAKE_DETECTOR] = shakeDetected; } return; } if (reportId === BNO_REPORT_STABILITY_CLASSIFIER) { const stabilityClassification = this.parseStabilityClassifierReport(reportBytes); this.readings[BNO_REPORT_STABILITY_CLASSIFIER] = stabilityClassification; return; } if (reportId === BNO_REPORT_ACTIVITY_CLASSIFIER) { const activityClassification = this.parseActivityClassifierReport(reportBytes); this.readings[BNO_REPORT_ACTIVITY_CLASSIFIER] = activityClassification; return; } const [sensorData, accuracy] = this.parseSensorReportData(reportBytes); if (reportId === BNO_REPORT_MAGNETOMETER) { this.magnetometerAccuracy = accuracy; } this.readings[reportId] = sensorData; } private parseSensorReportData(reportBytes: Buffer): [number[], number] { let dataOffset = 4; // this may not always be true const reportId = reportBytes[0]; const [scalar, count] = AVAIL_SENSOR_REPORTS[reportId]; let isUnsigned = false; if (RAW_REPORTS[reportId]) { // raw reports are unsigned isUnsigned = true; } const results: number[] = []; let accuracy = reportBytes.readUInt8(2) & 0b11; for (let offsetIdx = 0; offsetIdx < count; offsetIdx++) { const totalOffset = dataOffset + (offsetIdx * 2); let rawData; if (isUnsigned) { rawData = reportBytes.readUint16LE(totalOffset); } else { rawData = reportBytes.readInt16LE(totalOffset); } const scaledData = rawData * scalar; results.push(scaledData); } return [results, accuracy]; } private parseStepCounterReport(reportBytes: Buffer) { const stepCount = reportBytes.readUInt16LE(8); return stepCount; } private parseShakeReport(reportBytes: Buffer) { const shakeDetected = (reportBytes.readUInt16LE(4) & 0x111) > 0; return shakeDetected; } private parseStabilityClassifierReport(reportBytes: Buffer) { const classificationBitfield = reportBytes.readUInt8(4); const stabilityClassification = ["Unknown", "On Table", "Stationary", "Stable", "In motion"][classificationBitfield]; return stabilityClassification; } private parseActivityClassifierReport(reportBytes: Buffer) { // 0 Report ID = 0x1E // 1 Sequence number // 2 Status // 3 Delay // 4 Page Number + EOS // 5 Most likely state // 6-15 Classification (10 x Page Number) + confidence const activities = [ "Unknown", "In-Vehicle", "On-Bicycle", "On-Foot", "Still", "Tilting", "Walking", "Running", "OnStairs", ]; const endAndPageNumber = reportBytes.readUInt8(4); const pageNumber = endAndPageNumber & 0x7F; const mostLikely = reportBytes.readUInt8(5); const confidences = Array.from(reportBytes.subarray(6, 15)); const classification: { [key: string]: string | number } = {}; classification["most_likely"] = activities[mostLikely]; for (let idx = 0; idx < confidences.length; idx++) { const raw_confidence = confidences[idx]; const confidence = (10 * pageNumber) + raw_confidence; const activity_string = activities[idx]; classification[activity_string] = confidence; } return classification; } private async waitForPacket(timeout?: number): Promise<Packet> { timeout = timeout || this.config.PACKET_READ_TIMEOUT; let start_time = Date.now(); // TODO make these async somehow. while ((Date.now() - start_time) / 1000 < timeout) { if (!await this.dataReady()) { continue; } let packet: Packet; try { packet = await this.readPacket(); } catch (error) { if (error instanceof PacketError) { continue; } else { throw error; } } return packet; } throw new Error("Timed out waiting for a packet"); } private async readHeader(): Promise<PacketHeader> { const buffer = Buffer.alloc(BNO_HEADER_LEN); await this.readInto(buffer, buffer.length); // this.readInto(this.dataBuffer, 4); // this is expecting a header const packetHeader = Packet.headerFromBuffer(buffer); this.debug(packetHeader); return packetHeader; } private async readPacket(): Promise<Packet> { try{ // TODO Might be able to remove this and get header in a better way // await this.readInto(this.dataBuffer, 4); // this is expecting a header // const header = Packet.headerFromBuffer(this.dataBuffer); // let packetByteCount = header.packetByteCount; // const channelNumber = header.channelNumber; // const sequenceNumber = header.sequenceNumber; // this.sequenceNumber[channelNumber] = sequenceNumber; // if (packetByteCount === 0) { // this.debug("SKIPPING NO PACKETS AVAILABLE IN i2c._read_packet"); // throw new PacketError("No packet available"); // } // packetByteCount -= 4; // this.debug( // "channel", // channelNumber, // "has", // packetByteCount, // "bytes available to read", // ); // const header = await this.readHeader(); // HERE Have commented out is ready to see if it's receiving the data from the sensor and messing up all the other reads. // I think we need to read the data as it comes in and then parse it into packets. // const headerData = Buffer.alloc(4); // await this.readInto(headerData, headerData.length); // let packetByteCount = header.packetByteCount - 4; const header = await this.readHeader(); // this.updateSequenceNumber(header); const buffer = Buffer.alloc(header.packetByteCount); await this.readInto(buffer, buffer.length); const packet = new Packet(buffer); // await this.read(packetByteCount); // const newPacket = new Packet(this.dataBuffer); this.debug(packet); // this.updateSequenceNumber(packet.header); return packet; } catch (error) { throw new PacketError("Failed to load packet"); } } // This might no be nessesary since directions have different sequence numbers. Do we need to read with the same sequence number? private updateSequenceNumber(packetHeader: PacketHeader): void { const channel = packetHeader.channelNumber; const seq = packetHeader.sequenceNumber; this.sequenceNumber[channel] = seq; } private async read(requestedReadLength: number) { this.debug("trying to read", requestedReadLength, "bytes"); // +4 for the header const totalReadLength = requestedReadLength + 4; if (totalReadLength > this.config.DATA_BUFFER_SIZE) { this.dataBuffer = Buffer.alloc(totalReadLength); this.debug( `!!!!!!!!!!!! ALLOCATION: increased _data_buffer to Uint8Array(${totalReadLength}) !!!!!!!!!!!!!` ); } await this.readInto(this.dataBuffer, totalReadLength); } private async dataReady() { let header = await this.readHeader(); if (header.channelNumber > 5) { this.debug("channel number out of range:", header.channelNumber); } let ready: boolean; if (header.packetByteCount === 0x7FFF) { console.log("Byte count is 0x7FFF/0xFFFF; Error?"); if (header.sequenceNumber === 0xFF) { console.log("Sequence number is 0xFF; Error?"); } ready = false; } else { ready = header.dataLength > 0; } // this._dbg("\tdata ready", ready); return ready; } private async sendPacket(channel: number, data: Buffer): Promise<number> { const dataLength = data.length; const writeLength = dataLength + 4; const buffer = Buffer.alloc(writeLength); buffer.writeUInt16LE(writeLength, 0); // packet length MSB and LSB buffer[2] = channel; buffer[3] = this.sequenceNumber[channel]; data.forEach((byte, idx) => { // Write the data into the buffer after the header buffer[4 + idx] = byte; }); const packet = new Packet(buffer); this.debug("Sending packet:"); this.debug(packet); await this.write(buffer); this.sequenceNumber[channel] = (this.sequenceNumber[channel] + 1) % 256; // Sequence number per channel that resets after 255 return this.sequenceNumber[channel]; } } export const config = defaultConfig;