diff --git a/lib/drivers/glucorx/glucoRxDriver.js b/lib/drivers/glucorx/glucoRxDriver.js index 2b3d10bde..43d449863 100644 --- a/lib/drivers/glucorx/glucoRxDriver.js +++ b/lib/drivers/glucorx/glucoRxDriver.js @@ -108,6 +108,7 @@ const MODELS = { }; const READ_TIMEOUT = 2000; // in milliseconds +const MAX_IDLE_READS = 2; // consecutive read timeouts before giving up on a packet const KETONE_VALUE_FACTOR = 10; const KETONE_HI = 8.0; @@ -189,58 +190,48 @@ class GlucoRx { return response; } - async commandResponse(cmd, payload) { - let message = ''; - - await this.hidDevice.sendPromisified(this.buildPacket(cmd, payload)); - debug('Sent command.'); - + /** + * The CP2110 hands us UART bytes in HID reports of varying size, so a single + * packet can arrive in one report or spread over several (macOS 26 delivers + * them one byte at a time). Reassemble the stream until we have a full packet, + * discarding anything that precedes the start byte. + */ + async readPacket() { let raw = []; - let result; - let foundStart = false; - let foundEnd = false; - let packetSize = 64; - do { - result = []; + let idleReads = 0; + + while (true) { // requests to devices are sequential // eslint-disable-next-line no-await-in-loop - result = await this.hidDevice.receiveTimeout(READ_TIMEOUT); - debug('Incoming bytes:', common.bytes2hex(result)); - - if (result.length > 0) { - let bytes = null; - if (result.length > 1) { - if (result.slice(1).every(item => item === 0)) { - // only first byte in array is valid - bytes = [result[0]]; - } else { - const [ length ] = result; - bytes = result.slice(1, length + 1); - } - } else { - bytes = result; - } + const chunk = await this.hidDevice.receiveTimeout(READ_TIMEOUT); - if (!foundStart) { - if (bytes.includes(CONTROL.START)) { - foundStart = true; - } + if (chunk.length === 0) { + idleReads += 1; + if (idleReads >= MAX_IDLE_READS) { + throw new Error('Timed out waiting for a complete packet.'); } + } else { + idleReads = 0; + debug('Incoming bytes:', common.bytes2hex(chunk)); + raw = raw.concat(chunk); - if (foundStart) { - raw = raw.concat(bytes); + const start = raw.indexOf(CONTROL.START); + if (start > 0) { + raw = raw.slice(start); + } - if (raw[raw.length-2] === CONTROL.MD_STOP && raw.length >= PACKET_SIZE) { - foundEnd = true; - } + if (start >= 0 && raw.length >= PACKET_SIZE) { + return raw.slice(0, PACKET_SIZE); } } - } while (!foundEnd); - - // Only process if we get data - if (raw.length > 0) { - message = this.extractPacketIntoMessages(raw); } + } + + async commandResponse(cmd, payload) { + await this.hidDevice.sendPromisified(this.buildPacket(cmd, payload)); + debug('Sent command.'); + + const message = this.extractPacketIntoMessages(await this.readPacket()); if (message === COMMAND.COMM_MODE) { // try again @@ -322,10 +313,17 @@ class GlucoRx { async ping() { await this.hidDevice.sendPromisified(this.buildPacket(COMMAND.READ_MODEL)); debug('Sent ping.'); - const result = await this.hidDevice.receiveTimeout(READ_TIMEOUT); - debug('Received:', common.bytes2hex(result)); - if (result.length === 0) { + let result = null; + try { + // consume the whole reply, so that we don't leave part of it behind for + // the next command to trip over + result = await this.readPacket(); + } catch (err) { + debug('No response to ping:', err.message); + } + + if (result == null) { if (this.retries <= 3) { debug('Retrying..'); this.retries += 1; @@ -334,6 +332,7 @@ class GlucoRx { throw new Error('Device not responding.'); } } else { + debug('Received:', common.bytes2hex(result)); this.retries = 0; } } diff --git a/lib/hidDevice.js b/lib/hidDevice.js index d0288c15d..ecead4d04 100644 --- a/lib/hidDevice.js +++ b/lib/hidDevice.js @@ -28,7 +28,9 @@ module.exports = (config) => { const packets = []; function readListener(event) { - packets.push(new Uint8Array(event.data.buffer)); + // event.data is a view of the report payload, without the report ID, and is + // not necessarily positioned at the start of its underlying buffer. + packets.push(new Uint8Array(event.data.buffer, event.data.byteOffset, event.data.byteLength)); webHid.dispatchEvent(new Event('data')); } diff --git a/locales/en/translation.missing.json b/locales/en/translation.missing.json index 2b0d1af2e..a69e77b99 100644 --- a/locales/en/translation.missing.json +++ b/locales/en/translation.missing.json @@ -101,5 +101,8 @@ "Return to Login": "Return to Login", "Turn meter on and check the Bluetooth icon is flashing": "Turn meter on and check the Bluetooth icon is flashing", "Please correct the {{device}}'s time or change your Tidepool time zone to match your {{device}} if appropriate and try again.": "Please correct the {{device}}'s time or change your Tidepool time zone to match your {{device}} if appropriate and try again.", - "Plug in meter with mini-USB cable": "Plug in meter with mini-USB cable" + "Plug in meter with mini-USB cable": "Plug in meter with mini-USB cable", + "Plug in meter with": "Plug in meter with", + "EZSync002B cable": "EZSync002B cable", + "Plug in meter with mini-USB cable. V6 meter not compatible with MacOS.": "Plug in meter with mini-USB cable. V6 meter not compatible with MacOS." } \ No newline at end of file