Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion apps/heatsuite/ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,7 @@
`heatsuite.app.js` made scanning faster for devices (1s scan time)
`heatsuite.surveylib.js` changed to be more standalone, with more UI options (see docs)
0.13: `heatsuite.surveylib.js` added a right hand scroll bar for questions where the responses exceed the screen space
`heatsuite.wid.js` fixed charging notification issue
`heatsuite.wid.js` fixed charging notification issue
0.14: Package starter (empty) tasks/survey data
Harden blood pressure pairing, measurement parsing, and result handling
Improve recorder and BLE lifecycle coordination
7 changes: 7 additions & 0 deletions apps/heatsuite/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,13 @@ The `Searching...` text at the bottom of the screen shows that the watch is sear

_Note: This will only show when a bluetooth device is associated with a task._

## CORE Sensor recording

HeatSuite installs CoreTemp as the CORE BLE runtime provider, but CORE recording
is off by default. Enable the `CORE Sensor` recorder in HeatSuite settings when
CORE samples should be included in HeatSuite minute data. HeatSuite does not
turn on CoreTemp's standalone background `Enable` setting.

## Why does swiping right open the HeatSuite App?

The objective of HeatSuite was to make data collection in the field easier for participants. By default, swiping right when the HeatSuite widget is visible will open the app. This can be toggled off in the app settings.
Expand Down
18 changes: 13 additions & 5 deletions apps/heatsuite/custom.html
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,17 @@
const STORAGEFILE_SUFFIX = ' (SF)';
let HeatSuiteFileList = [];
//default Schema
let heatsuite__settings_defaultSchema = {
"DEBUG": false,
"SAVE_DEBUG": false,
"notifications": true,
"record": ["bat", "steps", "hrm", "baro", "acc"],
"filePrefix": "htst",
"GPS": true,
"GPSAdaptiveTime": 2,
"GPSInterval": 30,
"GPSScanTime": 5
};
let heatsuite__taskFile_defaultSchema = [
{
"id": "survey",
Expand Down Expand Up @@ -609,14 +620,11 @@
}
}
function onInit(device) {
let settings = {};
let settings = Object.assign({}, heatsuite__settings_defaultSchema);
let promise = Promise.resolve();
promise = promise.then(() => {
return readStorageJSONAsync("heatsuite.default.json"); //retaining to keep it compatible with older versions
});
promise = promise.then(defaults => {
return readStorageJSONAsync("heatsuite.settings.json").then(user => {
settings = Object.assign({}, defaults, user);
settings = Object.assign({}, heatsuite__settings_defaultSchema, user);
});
});
promise = promise.then(() => {
Expand Down
100 changes: 87 additions & 13 deletions apps/heatsuite/heatsuite.app.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
let studyTasksJSON = "heatsuite.tasks.json";
let studyTasks = require('Storage').readJSON(studyTasksJSON, true) || {};
let studyTasks = require('Storage').readJSON(studyTasksJSON, true) || [];
if (!Array.isArray(studyTasks)) studyTasks = [];

let Layout = require("Layout");
let modHS = require("HSModule");
Expand All @@ -11,6 +12,71 @@ let settings = modHS.getSettings();

let appCache = modHS.getCache();

function safeStringify(value) {
try {
return JSON.stringify(value);
} catch (e) {
return String(value);
}
}

function byteToHex(value) {
let out = (value & 0xFF).toString(16);
return out.length < 2 ? "0" + out : out;
}

function bufferToHex(buffer) {
if (!buffer) return "";
let arr = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer);
let bytes = [];
for (let i = 0; i < arr.length; i++) bytes.push(byteToHex(arr[i]));
return bytes.join(" ");
}

function serviceDataToHex(serviceData) {
if (!serviceData) return "{}";
let out = {};
Object.keys(serviceData).forEach(k => {
out[k] = bufferToHex(serviceData[k]);
});
return safeStringify(out);
}

function log() {
let parts = [];
for (let i = 0; i < arguments.length; i++) parts.push(String(arguments[i]));
modHS.log(parts.join(" "));
}

function logScanDevice(d) {
log("[Scan] device",
"id=" + d.id,
"name=" + (d.name || ""),
"rssi=" + d.rssi,
"services=" + safeStringify(d.services || []),
d.data ? "payload=" + bufferToHex(d.data) : "",
d.serviceData ? "serviceData=" + serviceDataToHex(d.serviceData) : "");
}

function stopBLEDevices() {
if (global.WIDGETS && WIDGETS["heatsuite"] && WIDGETS["heatsuite"].stopBLEDevices) {
return Promise.resolve(WIDGETS["heatsuite"].stopBLEDevices());
}
return Promise.resolve();
}

function loadTaskApp(app) {
NRF.setScan();

stopBLEDevices().then(function () {
NRF.setScan();
Bangle.load(app);
}).catch(function (e) {
modHS.log("Failed to stop BLE before task: " + e);
Bangle.load(app);
});
}

function queueNRFFindDeviceTimeout() {
if (NRFFindDeviceTimeout) clearTimeout(NRFFindDeviceTimeout);
NRFFindDeviceTimeout = setTimeout(function () {
Expand All @@ -25,17 +91,15 @@ function findBtDevices() {
let found = false;
if (devices.length !== 0) {
devices.some((d) => {
modHS.log("Found device", d);
logScanDevice(d);
let services = d.services;
modHS.log("Services: ", services);
if (services !== undefined && services.includes('1810') && d.id === settings.bt_bloodPressure_id) {
//Blood Pressure
found = true;
layout.msg.label = "BP Found";
layout.render();
if (NRFFindDeviceTimeout) clearTimeout(NRFFindDeviceTimeout);
WIDGETS['heatsuite'].stopBLEDevices();
Bangle.load('heatsuite.bp.js');
loadTaskApp('heatsuite.bp.js');
return true;
} else if (services !== undefined && (services.includes('181b') || services.includes('181d')) && studyTasks.some(task => task.id === "bodyMass")) {
if (services.includes('181b')) {
Expand Down Expand Up @@ -76,17 +140,15 @@ function findBtDevices() {
layout.msg.label = "Scale Found";
layout.render();
if (NRFFindDeviceTimeout) clearTimeout(NRFFindDeviceTimeout);
WIDGETS['heatsuite'].stopBLEDevices();
Bangle.load('heatsuite.mass.js');
loadTaskApp('heatsuite.mass.js');
return true;
} else if (services !== undefined && services.includes('1809') && d.id === settings.bt_coreTemperature_id) {
//Core Temperature
found = true;
layout.msg.label = "Temp Found";
layout.render();
if (NRFFindDeviceTimeout) clearTimeout(NRFFindDeviceTimeout);
WIDGETS['heatsuite'].stopBLEDevices();
Bangle.load('heatsuite.bletemp.js');
loadTaskApp('heatsuite.bletemp.js');
return true;
}
});
Expand Down Expand Up @@ -193,16 +255,28 @@ function draw() {
if (row.c.length > 0) {
layoutOut.c.push(row);
}
//Final
//Final
if(btRequired) layoutOut.c.push({ type: "txt", font: "6x8:2", label: "Searching...", id: "msg", fillx: 1 });
let options = {
let options = {
lazy: true,
btns:[{label:"Exit", cb: l=>Bangle.showClock() }]
};

layout = new Layout(layoutOut, options);
layout.render();
if(btRequired) queueNRFFindDeviceTimeout();

if (btRequired) {
log("[BLE preflight] stopping BLE before task scan");

stopBLEDevices().then(function () {
log("[BLE preflight] complete, starting scan loop");
queueNRFFindDeviceTimeout();
}).catch(function (e) {
modHS.log("[BLE preflight] failed: " + e);
queueNRFFindDeviceTimeout();
});
}

queueTaskScreenTimeout();
}

Expand Down
Loading