Skip to content

Commit

Permalink
Merge pull request #1648 from tidepool-org/platinum-ble
Browse files Browse the repository at this point in the history
Add Bluetooth support for ReliOn Platinum meter (UPLOAD-1121)
  • Loading branch information
gniezen authored Oct 22, 2024
2 parents 7537217 + 62b374b commit 582d559
Show file tree
Hide file tree
Showing 14 changed files with 120 additions and 48 deletions.
6 changes: 5 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,16 @@ jobs:
name: Install nvm and node
command: |
set +e
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.5/install.sh | bash
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
source ~/.nvm/nvm.sh
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
nvm install v18.17.1
nvm alias default v18.17.1
- run: node -v
- run: npm install --global npm node-gyp
- run: echo $SHELL
- run: npm -v
- run: node-gyp -v
- run: curl -o- -L https://yarnpkg.com/install.sh | bash
Expand Down
1 change: 1 addition & 0 deletions app/actions/sync.js
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,7 @@ export function uploadFailure(err, errProps, device) {
const properties = {
type: _.get(device, 'source.type', undefined),
source: `${actionUtils.getUploadTrackingId(device)}`,
device: device.key,
os: `${actionUtils.getOSDetails()}`,
error: err
};
Expand Down
2 changes: 1 addition & 1 deletion app/components/Upload.js
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ export default class Upload extends Component {
return this.handleMedtronic600Upload();
}

if (device === 'caresensble' || device === 'onetouchverioble' || device === 'foracareble') {
if (device === 'caresensble' || device === 'onetouchverioble' || device === 'foracareble' || device === 'relionplatinumble') {
return this.handleBluetoothUpload(_.get(upload, 'key', null));
}

Expand Down
2 changes: 1 addition & 1 deletion app/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "tidepool-uploader",
"productName": "tidepool-uploader",
"version": "2.59.1-handle-empty.4",
"version": "2.59.1-platinum-ble.4",
"description": "Tidepool Project Universal Uploader",
"main": "./main.prod.js",
"author": {
Expand Down
27 changes: 19 additions & 8 deletions app/reducers/devices.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ const devices = {
source: {type: 'device', driverId: 'AbbottPrecisionXtra'},
enabled: {mac: false, win: true, linux: true}
},
accuchekusb: {
instructions: i18n.t('Plug in meter with micro-USB cable'),
name: 'Accu-Chek Aviva Connect, Instant, Guide & Guide Me',
key: 'accuchekusb',
source: {type: 'device', driverId: 'AccuChekUSB'},
enabled: {mac: true, win: true, linux: true},
powerOnlyWarning: true, // shows warning for power-only USB cables
},
bayercontournext: {
instructions: i18n.t('Plug meter into USB port'),
key: 'bayercontournext',
Expand Down Expand Up @@ -254,6 +262,17 @@ const devices = {
enabled: {mac: true, win: true, linux: true},
powerOnlyWarning: true, // shows warning for power-only USB cables
},
relionplatinumble: {
instructions: {
text: i18n.t('To pair: With meter off, hold the down arrow button until Bluetooth symbol appears \u2022 For full pairing instructions,'),
linkText: i18n.t('visit our support site'),
link: 'https://support.tidepool.org/hc/en-us/articles/14204858905492-Uploading-your-ReliOn-Platinum-meter',
},
name: 'ReliOn Platinum (using Bluetooth)',
key: 'relionplatinumble',
source: {type: 'device', driverId: 'BluetoothLE'},
enabled: {mac: true, win: true, linux: true}
},
relionpremier: {
instructions: i18n.t('Plug in meter with cable and make sure the meter is switched on'),
name: 'ReliOn Premier (BLU, Voice & Classic)',
Expand All @@ -268,14 +287,6 @@ const devices = {
source: {type: 'device', driverId: 'ReliOnPrime'},
enabled: {mac: true, win: true, linux: true},
},
accuchekusb: {
instructions: i18n.t('Plug in meter with micro-USB cable'),
name: 'Accu-Chek Aviva Connect, Instant, Guide & Guide Me',
key: 'accuchekusb',
source: {type: 'device', driverId: 'AccuChekUSB'},
enabled: {mac: true, win: true, linux: true},
powerOnlyWarning: true, // shows warning for power-only USB cables
},
tandem: {
instructions: i18n.t('Plug in pump with micro-USB'),
key: 'tandem',
Expand Down
39 changes: 32 additions & 7 deletions lib/drivers/bluetoothLE/bluetoothLEDriver.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import structJs from '../../struct';
import common from '../../commonFunctions';
import uploadDataPeriod from '../../../app/utils/uploadDataPeriod';
import api from '../../core/api';
import getModelName from '../roche/models';

const struct = structJs();

Expand Down Expand Up @@ -89,7 +90,8 @@ module.exports = (config) => {
if (!cfg.deviceInfo.name.startsWith('CareSens') &&
!cfg.deviceInfo.name.startsWith('ReliOn 2395') &&
!cfg.deviceInfo.name.startsWith('ReliOn 0015') &&
!cfg.deviceInfo.name.startsWith('TNG VOICE')) {
!cfg.deviceInfo.name.startsWith('TNG VOICE') &&
!cfg.deviceInfo.name.startsWith('meter+')) {
return cb (new Error('We don\'t currently support this meter.'));
}

Expand All @@ -108,7 +110,12 @@ module.exports = (config) => {

cfg.deviceTags = ['bgm'];
cfg.deviceInfo.deviceId = `${[cfg.deviceInfo.manufacturers]}-${cfg.deviceInfo.model}`;
if(env.electron){

if(cfg.deviceInfo.name.startsWith('meter+')) {
cfg.deviceInfo.serial = cfg.deviceInfo.model + cfg.deviceInfo.name.slice(6);
cfg.deviceInfo.deviceId += `-${cfg.deviceInfo.serial}`;
data.deviceModel = getModelName(cfg.deviceInfo.model);
} else if(env.electron){
cfg.deviceInfo.deviceId += `-${remote.getGlobal('bluetoothDeviceId')}`;
} else {
cfg.deviceInfo.deviceId += `-${cfg.deviceComms.ble.device.id}`;
Expand Down Expand Up @@ -308,20 +315,19 @@ module.exports = (config) => {
record.value = 601;
annotation = {
code: 'bg/out-of-range',
threshold: 600,
value: 'high',
};
} else if (record.value < 20) {
record.value = 19;
} else if (record.value < 10) {
record.value = 9; // set value below lowest known threshold
annotation = {
code: 'bg/out-of-range',
threshold: 20,
value: 'low',
};
}
}

if (record.type !== 10) { //check that it's not control solution
if (record.type !== 10 && // check that it's not control solution
record.type !== 4) { // Accu-Chek meters record unlisted values with type 4
let postRecord = null;

if (isKetone) {
Expand All @@ -334,12 +340,25 @@ module.exports = (config) => {
.with_units(record.units);
}

if (cfg.deviceInfo.manufacturers.includes('Roche')) {
// The roche/accuChekUSB.js driver does not record seconds, so to prevent
// duplicates we also remove seconds from the Bluetooth uploads for Roche
// meters (even the one branded as ReliOn Platinum)
record.timestamp.setSeconds(0);
}

postRecord
.with_deviceTime(sundial.formatDeviceTime(record.timestamp))
.set('index', record.seqNum);

if (annotation) {
annotate.annotateEvent(postRecord, annotation);
// BG treshold values are not provided, and we know there are meters
// with different thresholds, e.g. Accu-chek Instant range is 10-600mg/dL
// vs 20-600mg/dL for other meters, so we annotate that threshold is unknown
annotate.annotateEvent(postRecord, {
code: 'bg/unknown-value',
});
}

cfg.tzoUtil.fillInUTCInfo(postRecord, record.timestamp);
Expand Down Expand Up @@ -384,6 +403,12 @@ module.exports = (config) => {
version: cfg.version,
};

if(cfg.deviceInfo.name.startsWith('meter+')) {
// For Roche meters, we want to store the model number in the upload
// record instead of the model name, to match the USB version
sessionInfo.deviceModel = cfg.deviceInfo.model;
}

cfg.api.upload.toPlatform(
data.post_records, sessionInfo, progress, cfg.groupId,
(err, result) => {
Expand Down
23 changes: 2 additions & 21 deletions lib/drivers/roche/accuChekUSB.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ import {
getProductionSpecEntry,
} from './utils';

import getModelName from './models';

const isBrowser = typeof window !== 'undefined';
// eslint-disable-next-line no-console
const debug = isBrowser ? require('bows')('AccuChekUSBDriver') : console.log;
Expand Down Expand Up @@ -507,27 +509,6 @@ module.exports = (config) => {
// timezone is applied across-the-board
cfg.tzoUtil = new TZOUtil(cfg.timezone, new Date().toISOString(), []);

const models = [
{ name: 'Aviva Connect', numbers: [483, 484, 497, 498, 499, 500, 502, 685] },
{ name: 'Performa Connect', numbers: [479, 501, 503, 765] },
{ name: 'Guide', numbers: [921, 922, 923, 925, 926, 929, 930, 932] },
{ name: 'Instant (single-button)', numbers: [958, 959, 960, 961, 963, 964, 965] },
{ name: 'Guide Me', numbers: [897, 898, 901, 902, 903, 904, 905] },
{ name: 'Instant (two-button)', numbers: [972, 973, 975, 976, 977, 978, 979, 980] },
{ name: 'Instant S (single-button)', numbers: [966, 967, 968, 969, 970, 971] },
{ name: 'ReliOn Platinum', numbers: [982] },
];

const getModelName = (number) => {
// eslint-disable-next-line no-restricted-syntax
for (const i in models) {
if (models[i].numbers.indexOf(_.toInteger(number)) >= 0) {
return models[i].name;
}
}
return `Unknown model ${number}`;
};

return {
/* eslint no-param-reassign:
[ "error", { "props": true, "ignorePropertyModificationsFor": ["data"] } ] */
Expand Down
41 changes: 41 additions & 0 deletions lib/drivers/roche/models.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* == BSD2 LICENSE ==
* Copyright (c) 2024, Tidepool Project
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the associated License, which is identical to the BSD 2-Clause
* License as published by the Open Source Initiative at opensource.org.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the License for more details.
*
* You should have received a copy of the License along with this program; if
* not, you can obtain one from Tidepool Project at tidepool.org.
* == BSD2 LICENSE ==
*/

import _ from 'lodash';

const models = [
{ name: 'Aviva Connect', numbers: [483, 484, 497, 498, 499, 500, 502, 685] },
{ name: 'Performa Connect', numbers: [479, 501, 503, 765] },
{ name: 'Guide', numbers: [921, 922, 923, 925, 926, 929, 930, 932] },
{ name: 'Instant (single-button)', numbers: [958, 959, 960, 961, 963, 964, 965] },
{ name: 'Guide Me', numbers: [897, 898, 901, 902, 903, 904, 905] },
{ name: 'Instant (two-button)', numbers: [972, 973, 975, 976, 977, 978, 979, 980] },
{ name: 'Instant S (single-button)', numbers: [966, 967, 968, 969, 970, 971] },
{ name: 'ReliOn Platinum', numbers: [982] },
];

const getModelName = (number) => {
// eslint-disable-next-line no-restricted-syntax
for (const i in models) {
if (models[i].numbers.indexOf(_.toInteger(number)) >= 0) {
return models[i].name;
}
}
return `Unknown model ${number}`;
};

export default getModelName;
2 changes: 1 addition & 1 deletion lib/drivers/tandem
4 changes: 3 additions & 1 deletion locales/en/translation.missing.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,10 @@
"You can continue to use your current version,": "You can continue to use your current version,",
"but we recommend updating as soon as possible.": "but we recommend updating as soon as possible.",
"Plug in PDA with micro-USB": "Plug in PDA with micro-USB",
"There is no new data since last time. If you think this is an error, please reach out below.": "There is no new data since last time. If you think this is an error, please reach out below.",
"Share this issue with the Tidepool Support Team": "Share this issue with the Tidepool Support Team",
"To pair: With meter off, hold the down arrow button until Bluetooth symbol appears • For full pairing instructions,": "To pair: With meter off, hold the down arrow button until Bluetooth symbol appears • For full pairing instructions,",
"We couldn't log you in. Try again in a few minutes.": "We couldn't log you in. Try again in a few minutes.",
"There is no new data since last time. If you think this is an error, please reach out below.": "There is no new data since last time. If you think this is an error, please reach out below.",
"Data is up to date": "Data is up to date",
"There is no data on the device. If you think this is an error, please reach out below.": "There is no data on the device. If you think this is an error, please reach out below.",
"Unlock PDM. Plug into USB. Tap Export on PDM. Click Upload.": "Unlock PDM. Plug into USB. Tap Export on PDM. Click Upload."
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "tidepool-uploader",
"version": "2.59.1-handle-empty.4",
"version": "2.59.1-platinum-ble.4",
"description": "Tidepool Project Universal Uploader",
"private": true,
"main": "main.prod.js",
Expand Down Expand Up @@ -35,7 +35,7 @@
"start-main-dev": "cross-env HOT=1 NODE_ENV=development electron --no-sandbox -r @babel/register ./app/main.dev.js",
"server": "node server",
"prepare-qa-build": "node -r @babel/register scripts/prepare-qa-build.js",
"package": "npm run build-quiet && electron-builder -p always -c electron-builder-publish.js"
"package": "yarn build-quiet && electron-builder -p always -c electron-builder-publish.js"
},
"dependencies": {
"@electron/remote": "2.1.2",
Expand Down
9 changes: 6 additions & 3 deletions test/app/actions/async.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1735,7 +1735,8 @@ describe('Asynchronous Actions', () => {
type: targetDevice.source.type,
source: targetDevice.source.driverId,
os: 'BeOS R5.1 (RISC-V)',
error: err
error: err,
device: 'a_pump',
}
}
}
Expand Down Expand Up @@ -1844,7 +1845,8 @@ describe('Asynchronous Actions', () => {
type: targetDevice.source.type,
source: targetDevice.source.driverId,
os: 'BeOS R5.1 (RISC-V)',
error: err
error: err,
device: 'a_pump',
}
}
}
Expand Down Expand Up @@ -1957,7 +1959,8 @@ describe('Asynchronous Actions', () => {
type: targetDevice.source.type,
source: targetDevice.source.driverId,
os: 'BeOS R5.1 (RISC-V)',
error: err
error: err,
device: 'a_pump',
}
}
}
Expand Down
6 changes: 4 additions & 2 deletions test/app/actions/sync.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -828,7 +828,8 @@ describe('Synchronous Actions', () => {
type: device.source.type,
source: device.source.driverId,
os: 'BeOS R5.1 (RISC-V)',
error: resError
error: resError,
device: undefined,
}
}
}
Expand Down Expand Up @@ -859,7 +860,8 @@ describe('Synchronous Actions', () => {
source: device.source.driverId,
os: 'BeOS R5.1 (RISC-V)',
error: resError,
limit: '4 weeks'
limit: '4 weeks',
device: undefined,
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions test/app/actions/utils.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ describe('utils', () => {
source: 'bar',
os: 'BeOS R5.1 (RISC-V)',
error: displayErr,
device: undefined,
}
}
}
Expand Down Expand Up @@ -130,6 +131,7 @@ describe('utils', () => {
source: 'bar',
os: 'BeOS R5.1 (RISC-V)',
error: displayErr,
device: undefined,
}
}
}
Expand Down

0 comments on commit 582d559

Please sign in to comment.