Skip to content

Commit

Permalink
🚀 finished: Added history fetch mode
Browse files Browse the repository at this point in the history
  • Loading branch information
bastiaanv committed Dec 10, 2023
1 parent 921851c commit 073bd9e
Show file tree
Hide file tree
Showing 4 changed files with 316 additions and 35 deletions.
70 changes: 62 additions & 8 deletions src/ble.comm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ import { DanaRSEncryption } from './encryption/index';
import { ConnectionEvents } from './events/connection.events';
import DanaPump from './index';
import { DanaGeneratePacket, DanaParsePacket } from './packets/dana.packet.base';
import { CommandGeneralSetHistoryUploadMode } from './packets/dana.packet.general.set.history.upload.mode';
import { HistoryItem } from './packets/dana.packet.history.base';
import { CommandNotifyAlarm, PacketNotifyAlarm } from './packets/dana.packet.notify';
import { CommandNotifyDeliveryComplete, PacketNotifyDeliveryComplete } from './packets/dana.packet.notify.delivery.complete';
import { CommandNotifyDeliveryRateDisplay, PacketNotifyDeliveryRateDisplay } from './packets/dana.packet.notify.delivery.rate.display';
import { CommandNotifyMissedBolus, PacketNotifyMissedBolus } from './packets/dana.packet.notify.missed.bolus';
import { HistoryCode } from './packets/dana.type.history.code';
import { DANA_PACKET_TYPE } from './packets/dana.type.message.enum';
import { parseMessage } from './packets/index';
import { StorageService } from './storage.service';
Expand Down Expand Up @@ -67,8 +70,6 @@ export class BleComm {

private readonly READ_SERVICE_UUID = 'FFF0';
private readonly READ_CHAR_UUID = 'FFF1';
// private readonly BLE5_DESCRIPTOR_UUID = '00002902-0000-1000-8000-00805f9b34fb';

private readonly WRITE_SERVICE_UUID = 'FFF0';
private readonly WRITE_CHAR_UUID = 'FFF2';

Expand All @@ -80,6 +81,9 @@ export class BleComm {
// NOTE: usage of `isEasyMode` and `isUnitUD` is unknown
private isEasyMode = false;
private isUnitUD = false;
private _isInFetchHistoryMode = false;

private historyLog: HistoryItem[] = [];

private commScheduler: Record<number, { callback: (data: DanaParsePacket<unknown>) => void; timeout: any }> = {};

Expand All @@ -103,6 +107,19 @@ export class BleComm {
this._isConnected = value;
}

public get isInFetchHistoryMode() {
return this._isInFetchHistoryMode;
}

private set isInFetchHistoryMode(value: boolean) {
this._isInFetchHistoryMode = value;

// Empty history log when not in fetchHistoryMode
if (!value) {
this.historyLog = [];
}
}

// Buffers
private readBuffer: Uint8Array = new Uint8Array(0);

Expand Down Expand Up @@ -241,6 +258,17 @@ export class BleComm {
throw new Error('This message is not done processing...');
}

const isHistoryPacket = this.isHistoryPacket(packet.opCode);
if (isHistoryPacket && !this.isInFetchHistoryMode) {
throw new Error('Pump is not in history fetch mode... Send PacketGeneralSetHistoryUploadMode first');
}

if (packet.opCode === CommandGeneralSetHistoryUploadMode && packet.data) {
this.isInFetchHistoryMode = packet.data[0] === 0x01;
} else {
this.isInFetchHistoryMode = false;
}

console.log(`${formatPrefix()} Encrypting data`, packet);

let data = DanaRSEncryption.encodePacket(packet.opCode, packet.data, this.deviceName);
Expand All @@ -259,15 +287,18 @@ export class BleComm {
data = data.subarray(end);
}

// Now schedule a 5 sec timeout for the pump to send its message back
// Now schedule a 5 sec timeout (or 20 when in fetchHistoryMode) for the pump to send its message back
// This timeout will be cancelled by `processMessage` once it received the message
// If this timeout expired, disconnect from the pump and prompt an error...
return new Promise<DanaParsePacket<unknown>>((resolve, reject) => {
const timeout = setTimeout(() => {
console.error(`${formatPrefix('ERROR')} Send message timeout hit! Disconnecting from device...`, packet);
this.disconnect();
reject();
}, 5000);
const timeout = setTimeout(
() => {
console.error(`${formatPrefix('ERROR')} Send message timeout hit! Disconnecting from device...`, packet);
this.disconnect();
reject();
},
!isHistoryPacket ? 5000 : 20000
);

this.commScheduler[command] = {
callback: (data: DanaParsePacket<unknown>) => resolve(data),
Expand Down Expand Up @@ -641,6 +672,24 @@ export class BleComm {
return;
}

if (this.isHistoryPacket(message.command)) {
const data = message.data as HistoryItem;
if (data.code === HistoryCode.RECORD_TYPE_DONE_UPLOAD) {
scheduledMessage.callback({
success: true,
data: this.historyLog,
});

// Clear scheduler
clearTimeout(scheduledMessage.timeout);
delete this.commScheduler[message.command];
} else {
this.historyLog.push(data);
}

return;
}

scheduledMessage.callback(message);

// Clear scheduler
Expand Down Expand Up @@ -670,6 +719,11 @@ export class BleComm {
this.deviceName = '';
this.encryptionType = ENCRYPTION_TYPE.DEFAULT;
}

private isHistoryPacket(opCode: number): boolean {
const base = (DANA_PACKET_TYPE.TYPE_RESPONSE & 0xff) << 8;
return opCode > base + DANA_PACKET_TYPE.OPCODE_REVIEW__BASAL && opCode < base + DANA_PACKET_TYPE.OPCODE_REVIEW__ALL_HISTORY;
}
}

function formatPrefix(level: 'INFO' | 'WARNING' | 'ERROR' = 'INFO') {
Expand Down
244 changes: 228 additions & 16 deletions src/packets/dana.packet.history.base.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,234 @@
import { addDateToPacket } from './dana.packet.base';
import { ObjectValues } from '../types';
import { DATA_START, DanaParsePacket, addDateToPacket, uint8ArrayToNumber } from './dana.packet.base';
import { HistoryCode } from './dana.type.history.code';

export interface PacketHistoryBase {
from: Date | undefined;
from: Date | undefined;
}

export interface HistoryItem {
code: ObjectValues<typeof HistoryCode>;
timestamp: Date;
value?: number | undefined;
durationInMin?: number | undefined;
dailyBasal?: number | undefined;
dailyBolus?: number | undefined;
alarm?: string | undefined;
bolusType?: ReturnType<typeof getBolusType> | undefined;
}

export function generatePacketHistoryData(options: PacketHistoryBase): Uint8Array {
const data = new Uint8Array(6);

if (!options.from) {
data[0] = 0;
data[1] = 1;
data[2] = 1;
data[3] = 0;
data[4] = 0;
data[5] = 0;
} else {
addDateToPacket(data, options.from, 0, false);
}

return data;
const data = new Uint8Array(6);

if (!options.from) {
data[0] = 0;
data[1] = 1;
data[2] = 1;
data[3] = 0;
data[4] = 0;
data[5] = 0;
} else {
addDateToPacket(data, options.from, 0, false);
}

return data;
}

export function parsePacketHistory(data: Uint8Array): DanaParsePacket<HistoryItem> {
if (data.length === 3) {
return {
success: false,
data: {
code: HistoryCode.RECORD_TYPE_UNKNOWN,
timestamp: new Date(),
value: data[DATA_START],
},
};
}

// This packet marks the upload of history to be done
if (data.length === 5) {
return {
success: data[DATA_START] == 0x00,
data: {
code: HistoryCode.RECORD_TYPE_DONE_UPLOAD,
timestamp: new Date(),
value: data[DATA_START],
},
};
}

const param7 = data[DATA_START + 6];
const param8 = data[DATA_START + 7];
const value = (data[DATA_START + 8] << 8) + data[DATA_START + 9];

const recordType = data[DATA_START];
switch (recordType) {
case HistoryCode.RECORD_TYPE_BOLUS:
return {
success: true,
data: {
code: HistoryCode.RECORD_TYPE_BOLUS,
timestamp: uint8ArrayToDate(data, DATA_START + 1),
value: value * 0.01,
bolusType: getBolusType(param8),
durationInMin: (param8 & 0x0f) * 60 + param7,
},
};

case HistoryCode.RECORD_TYPE_DAILY:
const dailyBasal = (data[DATA_START + 4] << 8) + data[DATA_START + 5] * 0.01;
const dailyBolus = (data[DATA_START + 6] << 8) + data[DATA_START + 7] * 0.01;
const timestamp = uint8ArrayToDate(data, DATA_START + 1);
timestamp.setHours(0, 0, 0, 0);

return {
success: true,
data: {
code: HistoryCode.RECORD_TYPE_DAILY,
timestamp,
dailyBasal,
dailyBolus,
},
};

case HistoryCode.RECORD_TYPE_PRIME:
return {
success: true,
data: {
code: HistoryCode.RECORD_TYPE_PRIME,
timestamp: uint8ArrayToDate(data, DATA_START + 1),
value: value * 0.01,
},
};

case HistoryCode.RECORD_TYPE_REFILL:
return {
success: true,
data: {
code: HistoryCode.RECORD_TYPE_REFILL,
timestamp: uint8ArrayToDate(data, DATA_START + 1),
value: value * 0.01,
},
};

case HistoryCode.RECORD_TYPE_BASALHOUR:
return {
success: true,
data: {
code: HistoryCode.RECORD_TYPE_BASALHOUR,
timestamp: uint8ArrayToDate(data, DATA_START + 1),
value: value * 0.01,
},
};

case HistoryCode.RECORD_TYPE_TEMP_BASAL:
return {
success: true,
data: {
code: HistoryCode.RECORD_TYPE_TEMP_BASAL,
timestamp: uint8ArrayToDate(data, DATA_START + 1),
value: value * 0.01,
},
};

case HistoryCode.RECORD_TYPE_GLUCOSE:
return {
success: true,
data: {
code: HistoryCode.RECORD_TYPE_GLUCOSE,
timestamp: uint8ArrayToDate(data, DATA_START + 1),
value,
},
};

case HistoryCode.RECORD_TYPE_CARBO:
return {
success: true,
data: {
code: HistoryCode.RECORD_TYPE_CARBO,
timestamp: uint8ArrayToDate(data, DATA_START + 1),
value,
},
};

case HistoryCode.RECORD_TYPE_SUSPEND:
return {
success: true,
data: {
code: HistoryCode.RECORD_TYPE_SUSPEND,
timestamp: uint8ArrayToDate(data, DATA_START + 1),
value: param8 === 0x4f ? 1 : 0,
},
};

case HistoryCode.RECORD_TYPE_ALARM:
return {
success: true,
data: {
code: HistoryCode.RECORD_TYPE_ALARM,
timestamp: uint8ArrayToDate(data, DATA_START + 1),
value: value * 0.01,
alarm: getAlarmMessage(param8),
},
};
}

return {
success: false,
data: {
code: HistoryCode.RECORD_TYPE_UNKNOWN,
timestamp: uint8ArrayToDate(data, DATA_START + 1),
alarm: 'UNKNOWN Message type: ' + recordType,
},
};
}

function uint8ArrayToDate(buffer: Uint8Array, startIndex: number) {
const year = 2000 + buffer[startIndex];
const month = -1 + buffer[startIndex + 1];
const day = buffer[startIndex + 2];
const hour = buffer[startIndex + 3];
const min = buffer[startIndex + 4];
const sec = buffer[startIndex + 5];

return new Date(year, month, day, hour, min, sec, 0);
}

function getBolusType(param8: number) {
switch (param8 & 0xf0) {
case 0xa0:
return 'DS';
case 0xc0:
return 'E';
case 0x80:
return 'S';
case 0x90:
return 'DE';
default:
return 'None';
}
}

function getAlarmMessage(param8: number) {
switch (param8) {
case 0x50:
return 'Basal Compare';
case 0x52:
return 'Empty Reservoir';
case 0x43:
return 'Check';
case 0x4f:
return 'Occlusion';
case 0x4d:
return 'Basal max';
case 0x44:
return 'Daily max';
case 0x42:
return 'Low Battery';
case 0x53:
return 'Shutdown';
default:
return 'None';
}
}
14 changes: 14 additions & 0 deletions src/packets/dana.type.history.code.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export const HistoryCode = {
RECORD_TYPE_DONE_UPLOAD: -0x01,
RECORD_TYPE_UNKNOWN: 0x00,
RECORD_TYPE_BOLUS: 0x02,
RECORD_TYPE_DAILY: 0x03,
RECORD_TYPE_PRIME: 0x04,
RECORD_TYPE_REFILL: 0x05,
RECORD_TYPE_GLUCOSE: 0x06,
RECORD_TYPE_CARBO: 0x07,
RECORD_TYPE_SUSPEND: 0x09,
RECORD_TYPE_ALARM: 0x0a,
RECORD_TYPE_BASALHOUR: 0x0b,
RECORD_TYPE_TEMP_BASAL: 0x99,
} as const;
Loading

0 comments on commit 073bd9e

Please sign in to comment.