2022-01-06 16:39:39 +00:00
|
|
|
(function(back) {
|
2022-01-28 22:47:56 +00:00
|
|
|
function writeSettings(key, value) {
|
2024-04-10 17:02:06 +00:00
|
|
|
let s = require('Storage').readJSON(FILE, true) || {};
|
2022-01-28 22:47:56 +00:00
|
|
|
s[key] = value;
|
|
|
|
require('Storage').writeJSON(FILE, s);
|
|
|
|
readSettings();
|
|
|
|
}
|
2022-04-05 14:53:52 +00:00
|
|
|
|
2022-01-28 22:47:56 +00:00
|
|
|
function readSettings(){
|
|
|
|
settings = Object.assign(
|
|
|
|
require('Storage').readJSON("bthrm.default.json", true) || {},
|
|
|
|
require('Storage').readJSON(FILE, true) || {}
|
|
|
|
);
|
|
|
|
}
|
2022-04-05 14:53:52 +00:00
|
|
|
|
2024-04-10 17:02:06 +00:00
|
|
|
let FILE="bthrm.json";
|
|
|
|
let settings;
|
2022-01-28 22:47:56 +00:00
|
|
|
readSettings();
|
2022-01-06 16:39:39 +00:00
|
|
|
|
2024-04-10 17:02:06 +00:00
|
|
|
let log = ()=>{};
|
|
|
|
if (settings.debuglog)
|
|
|
|
log = print;
|
|
|
|
|
2022-07-01 22:48:23 +00:00
|
|
|
function applyCustomSettings(){
|
|
|
|
writeSettings("enabled",true);
|
|
|
|
writeSettings("replace",settings.custom_replace);
|
|
|
|
writeSettings("startWithHrm",settings.custom_startWithHrm);
|
|
|
|
writeSettings("allowFallback",settings.custom_allowFallback);
|
|
|
|
writeSettings("fallbackTimeout",settings.custom_fallbackTimeout);
|
|
|
|
}
|
|
|
|
|
2022-03-09 19:30:39 +00:00
|
|
|
function buildMainMenu(){
|
2024-04-10 17:02:06 +00:00
|
|
|
let mainmenu = {
|
2022-03-09 19:30:39 +00:00
|
|
|
'': { 'title': 'Bluetooth HRM' },
|
|
|
|
'< Back': back,
|
|
|
|
'Mode': {
|
|
|
|
value: 0 | settings.mode,
|
|
|
|
min: 0,
|
|
|
|
max: 3,
|
|
|
|
format: v => ["Off", "Default", "Both", "Custom"][v],
|
|
|
|
onchange: v => {
|
|
|
|
settings.mode = v;
|
|
|
|
switch (v){
|
|
|
|
case 0:
|
|
|
|
writeSettings("enabled",false);
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
writeSettings("enabled",true);
|
|
|
|
writeSettings("replace",true);
|
|
|
|
writeSettings("startWithHrm",true);
|
|
|
|
writeSettings("allowFallback",true);
|
|
|
|
writeSettings("fallbackTimeout",10);
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
writeSettings("enabled",true);
|
|
|
|
writeSettings("replace",false);
|
|
|
|
writeSettings("startWithHrm",false);
|
|
|
|
writeSettings("allowFallback",false);
|
|
|
|
break;
|
|
|
|
case 3:
|
2022-07-01 22:48:23 +00:00
|
|
|
applyCustomSettings();
|
2022-03-09 19:30:39 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
writeSettings("mode",v);
|
2022-02-13 17:05:21 +00:00
|
|
|
}
|
|
|
|
}
|
2022-03-09 19:30:39 +00:00
|
|
|
};
|
|
|
|
|
2022-04-05 14:53:52 +00:00
|
|
|
if (settings.btname || settings.btid){
|
2024-04-10 17:02:06 +00:00
|
|
|
let name = "Clear " + (settings.btname || settings.btid);
|
2022-03-09 19:30:39 +00:00
|
|
|
mainmenu[name] = function() {
|
2022-04-05 14:53:52 +00:00
|
|
|
E.showPrompt("Clear current device?").then((r)=>{
|
2022-03-09 19:30:39 +00:00
|
|
|
if (r) {
|
|
|
|
writeSettings("btname",undefined);
|
2022-04-05 14:53:52 +00:00
|
|
|
writeSettings("btid",undefined);
|
2024-04-10 17:02:06 +00:00
|
|
|
writeSettings("cache", undefined);
|
2022-03-09 19:30:39 +00:00
|
|
|
}
|
|
|
|
E.showMenu(buildMainMenu());
|
|
|
|
});
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
mainmenu["BLE Scan"] = ()=> createMenuFromScan();
|
|
|
|
mainmenu["Custom Mode"] = function() { E.showMenu(submenu_custom); };
|
|
|
|
mainmenu.Debug = function() { E.showMenu(submenu_debug); };
|
|
|
|
return mainmenu;
|
|
|
|
}
|
|
|
|
|
2024-04-10 17:02:06 +00:00
|
|
|
let submenu_debug = {
|
2022-02-13 17:05:21 +00:00
|
|
|
'' : { title: "Debug"},
|
2022-03-09 19:30:39 +00:00
|
|
|
'< Back': function() { E.showMenu(buildMainMenu()); },
|
2022-02-13 17:05:21 +00:00
|
|
|
'Alert on disconnect': {
|
|
|
|
value: !!settings.warnDisconnect,
|
2022-01-28 22:47:56 +00:00
|
|
|
onchange: v => {
|
2022-02-13 17:05:21 +00:00
|
|
|
writeSettings("warnDisconnect",v);
|
2022-01-28 22:47:56 +00:00
|
|
|
}
|
|
|
|
},
|
2022-02-13 17:05:21 +00:00
|
|
|
'Debug log': {
|
|
|
|
value: !!settings.debuglog,
|
2022-01-28 22:47:56 +00:00
|
|
|
onchange: v => {
|
2022-02-13 17:05:21 +00:00
|
|
|
writeSettings("debuglog",v);
|
2022-01-28 22:47:56 +00:00
|
|
|
}
|
|
|
|
},
|
2022-11-06 14:52:14 +00:00
|
|
|
'Use bonding': {
|
|
|
|
value: !!settings.bonding,
|
|
|
|
onchange: v => {
|
|
|
|
writeSettings("bonding",v);
|
|
|
|
}
|
|
|
|
},
|
2022-11-16 16:30:52 +00:00
|
|
|
'Use active scanning': {
|
|
|
|
value: !!settings.active,
|
|
|
|
onchange: v => {
|
|
|
|
writeSettings("active",v);
|
|
|
|
}
|
|
|
|
},
|
2022-02-13 17:05:21 +00:00
|
|
|
'Grace periods': function() { E.showMenu(submenu_grace); }
|
|
|
|
};
|
2022-03-09 19:30:39 +00:00
|
|
|
|
2024-04-10 17:02:06 +00:00
|
|
|
let supportedServices = [
|
|
|
|
"0x180d", // Heart Rate
|
|
|
|
"0x180f", // Battery
|
|
|
|
];
|
|
|
|
|
|
|
|
let supportedCharacteristics = [
|
|
|
|
"0x2a37", // Heart Rate
|
|
|
|
"0x2a38", // Body sensor location
|
|
|
|
"0x2a19", // Battery
|
|
|
|
];
|
|
|
|
|
2024-04-10 20:21:05 +00:00
|
|
|
var characteristicsToCache = function(characteristics) {
|
2024-04-10 17:02:06 +00:00
|
|
|
log("Cache characteristics");
|
2024-04-10 20:21:05 +00:00
|
|
|
let cache = {};
|
2024-04-10 17:02:06 +00:00
|
|
|
if (!cache.characteristics) cache.characteristics = {};
|
|
|
|
for (var c of characteristics){
|
|
|
|
//"handle_value":16,"handle_decl":15
|
|
|
|
log("Saving handle " + c.handle_value + " for characteristic: ", c.uuid);
|
|
|
|
cache.characteristics[c.uuid] = {
|
|
|
|
"handle": c.handle_value,
|
|
|
|
"uuid": c.uuid,
|
|
|
|
"notify": c.properties.notify,
|
|
|
|
"read": c.properties.read
|
|
|
|
};
|
|
|
|
}
|
|
|
|
writeSettings("cache", cache);
|
|
|
|
};
|
|
|
|
|
|
|
|
let createCharacteristicPromise = function(newCharacteristic) {
|
|
|
|
log("Create characteristic promise", newCharacteristic.uuid);
|
|
|
|
return Promise.resolve().then(()=>log("Handled characteristic", newCharacteristic.uuid));
|
|
|
|
};
|
|
|
|
|
|
|
|
let attachCharacteristicPromise = function(promise, characteristic) {
|
|
|
|
return promise.then(()=>{
|
|
|
|
log("Handling characteristic:", characteristic.uuid);
|
|
|
|
return createCharacteristicPromise(characteristic);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
let characteristics;
|
|
|
|
|
|
|
|
let createCharacteristicsPromise = function(newCharacteristics) {
|
|
|
|
log("Create characteristics promise ", newCharacteristics.length);
|
|
|
|
let result = Promise.resolve();
|
|
|
|
for (let c of newCharacteristics){
|
|
|
|
if (!supportedCharacteristics.includes(c.uuid)) continue;
|
|
|
|
log("Supporting characteristic", c.uuid);
|
|
|
|
characteristics.push(c);
|
|
|
|
|
|
|
|
result = attachCharacteristicPromise(result, c);
|
|
|
|
}
|
|
|
|
return result.then(()=>log("Handled characteristics"));
|
|
|
|
};
|
|
|
|
|
|
|
|
let createServicePromise = function(service) {
|
|
|
|
log("Create service promise", service.uuid);
|
|
|
|
let result = Promise.resolve();
|
|
|
|
result = result.then(()=>{
|
|
|
|
log("Handling service", service.uuid);
|
|
|
|
return service.getCharacteristics().then((c)=>createCharacteristicsPromise(c));
|
|
|
|
});
|
|
|
|
return result.then(()=>log("Handled service", service.uuid));
|
|
|
|
};
|
|
|
|
|
|
|
|
let attachServicePromise = function(promise, service) {
|
|
|
|
return promise.then(()=>createServicePromise(service));
|
|
|
|
};
|
|
|
|
|
|
|
|
function cacheDevice(deviceId){
|
|
|
|
let promise;
|
|
|
|
let filters;
|
|
|
|
let gatt;
|
|
|
|
characteristics = [];
|
|
|
|
filters = [{ id: deviceId }];
|
|
|
|
|
|
|
|
log("Requesting device with filters", filters);
|
|
|
|
promise = NRF.requestDevice({ filters: filters, active: settings.active });
|
|
|
|
|
|
|
|
promise = promise.then((d)=>{
|
|
|
|
log("Got device", d);
|
|
|
|
gatt = d.gatt;
|
|
|
|
log("Connecting...");
|
|
|
|
return gatt.connect().then(function() {
|
|
|
|
log("Connected.");
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
if (settings.bonding){
|
|
|
|
promise = promise.then(() => {
|
|
|
|
log(JSON.stringify(gatt.getSecurityStatus()));
|
|
|
|
if (gatt.getSecurityStatus().bonded) {
|
|
|
|
log("Already bonded");
|
|
|
|
return Promise.resolve();
|
|
|
|
} else {
|
|
|
|
log("Start bonding");
|
|
|
|
return gatt.startBonding()
|
|
|
|
.then(() => log("Security status after bonding" + gatt.getSecurityStatus()));
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
promise = promise.then(()=>{
|
|
|
|
log("Getting services");
|
|
|
|
return gatt.getPrimaryServices();
|
|
|
|
});
|
|
|
|
|
|
|
|
promise = promise.then((services)=>{
|
|
|
|
log("Got services", services.length);
|
|
|
|
let result = Promise.resolve();
|
|
|
|
for (let service of services){
|
|
|
|
if (!(supportedServices.includes(service.uuid))) continue;
|
|
|
|
log("Supporting service", service.uuid);
|
|
|
|
result = attachServicePromise(result, service);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
});
|
|
|
|
|
|
|
|
return promise.then(()=>{
|
|
|
|
log("Connection established, saving cache");
|
2024-04-10 20:21:05 +00:00
|
|
|
characteristicsToCache(characteristics);
|
2024-04-10 17:02:06 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-03-09 19:30:39 +00:00
|
|
|
function createMenuFromScan(){
|
|
|
|
E.showMenu();
|
2022-04-05 14:53:52 +00:00
|
|
|
E.showMessage("Scanning for 4 seconds");
|
2022-03-09 19:30:39 +00:00
|
|
|
|
2024-04-10 17:02:06 +00:00
|
|
|
let submenu_scan = {
|
2022-03-09 19:30:39 +00:00
|
|
|
'< Back': function() { E.showMenu(buildMainMenu()); }
|
|
|
|
};
|
2022-04-05 14:53:52 +00:00
|
|
|
NRF.findDevices(function(devices) {
|
|
|
|
submenu_scan[''] = { title: `Scan (${devices.length} found)`};
|
|
|
|
if (devices.length === 0) {
|
|
|
|
E.showAlert("No devices found")
|
|
|
|
.then(() => E.showMenu(buildMainMenu()));
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
devices.forEach((d) => {
|
2024-04-10 17:02:06 +00:00
|
|
|
log("Found device", d);
|
|
|
|
let shown = (d.name || d.id.substr(0, 17));
|
2022-04-05 14:53:52 +00:00
|
|
|
submenu_scan[shown] = function () {
|
2024-04-10 19:39:06 +00:00
|
|
|
E.showPrompt("Connect to\n" + shown + "?", {title: "Pairing"}).then((r) => {
|
2022-04-05 14:53:52 +00:00
|
|
|
if (r) {
|
2024-04-10 19:39:06 +00:00
|
|
|
E.showMessage("Connecting...", {img:require("Storage").read("bthrm.img")});
|
2024-04-10 17:02:06 +00:00
|
|
|
let count = 0;
|
|
|
|
const successHandler = ()=>{
|
2024-04-10 19:39:06 +00:00
|
|
|
E.showPrompt("Success!", {
|
|
|
|
img:require("Storage").read("bthrm.img"),
|
|
|
|
buttons: { "OK":true }
|
|
|
|
}).then(()=>{
|
2024-04-10 17:02:06 +00:00
|
|
|
writeSettings("btid", d.id);
|
|
|
|
// Store the name for displaying later. Will connect by ID
|
|
|
|
if (d.name) {
|
|
|
|
writeSettings("btname", d.name);
|
|
|
|
}
|
|
|
|
E.showMenu(buildMainMenu());
|
|
|
|
});
|
|
|
|
};
|
|
|
|
const errorHandler = (e)=>{
|
|
|
|
count++;
|
|
|
|
log("ERROR", e);
|
|
|
|
if (count <= 10){
|
2024-04-10 19:39:06 +00:00
|
|
|
E.showMessage("Error during caching\nRetry " + count + "/10", e);
|
2024-04-10 17:02:06 +00:00
|
|
|
return cacheDevice(d.id).then(successHandler).catch(errorHandler);
|
|
|
|
} else {
|
|
|
|
E.showAlert("Error during caching", e).then(()=>{
|
|
|
|
E.showMenu(buildMainMenu());
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
return cacheDevice(d.id).then(successHandler).catch(errorHandler);
|
2022-04-05 14:53:52 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
2022-03-09 19:30:39 +00:00
|
|
|
});
|
|
|
|
}
|
2022-04-05 14:53:52 +00:00
|
|
|
E.showMenu(submenu_scan);
|
|
|
|
}, { timeout: 4000, active: true, filters: [{services: [ "180d" ]}]});
|
2022-03-09 19:30:39 +00:00
|
|
|
}
|
|
|
|
|
2024-04-10 17:02:06 +00:00
|
|
|
let submenu_custom = {
|
2022-02-13 17:05:21 +00:00
|
|
|
'' : { title: "Custom mode"},
|
2022-03-09 19:30:39 +00:00
|
|
|
'< Back': function() { E.showMenu(buildMainMenu()); },
|
2022-01-28 22:47:56 +00:00
|
|
|
'Replace HRM': {
|
2022-02-13 17:05:21 +00:00
|
|
|
value: !!settings.custom_replace,
|
2022-01-06 16:39:39 +00:00
|
|
|
onchange: v => {
|
2022-02-13 17:05:21 +00:00
|
|
|
writeSettings("custom_replace",v);
|
2022-07-01 22:48:23 +00:00
|
|
|
if (settings.mode == 3) applyCustomSettings();
|
2022-01-28 22:47:56 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
'Start w. HRM': {
|
2022-02-13 17:05:21 +00:00
|
|
|
value: !!settings.custom_startWithHrm,
|
2022-01-28 22:47:56 +00:00
|
|
|
onchange: v => {
|
2022-02-13 17:05:21 +00:00
|
|
|
writeSettings("custom_startWithHrm",v);
|
2022-07-01 22:48:23 +00:00
|
|
|
if (settings.mode == 3) applyCustomSettings();
|
2022-01-28 22:47:56 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
'HRM Fallback': {
|
2022-02-13 17:05:21 +00:00
|
|
|
value: !!settings.custom_allowFallback,
|
2022-01-28 22:47:56 +00:00
|
|
|
onchange: v => {
|
2022-02-13 17:05:21 +00:00
|
|
|
writeSettings("custom_allowFallback",v);
|
2022-07-01 22:48:23 +00:00
|
|
|
if (settings.mode == 3) applyCustomSettings();
|
2022-01-28 22:47:56 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
'Fallback Timeout': {
|
2022-02-13 17:05:21 +00:00
|
|
|
value: settings.custom_fallbackTimeout,
|
2022-01-28 22:47:56 +00:00
|
|
|
min: 5,
|
|
|
|
max: 60,
|
|
|
|
step: 5,
|
|
|
|
format: v=>v+"s",
|
|
|
|
onchange: v => {
|
2022-02-13 17:05:21 +00:00
|
|
|
writeSettings("custom_fallbackTimout",v*1000);
|
2022-07-01 22:48:23 +00:00
|
|
|
if (settings.mode == 3) applyCustomSettings();
|
2022-01-28 22:47:56 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
};
|
2022-04-05 14:53:52 +00:00
|
|
|
|
2024-04-10 17:02:06 +00:00
|
|
|
let submenu_grace = {
|
2022-01-28 22:47:56 +00:00
|
|
|
'' : { title: "Grace periods"},
|
2022-02-13 17:05:21 +00:00
|
|
|
'< Back': function() { E.showMenu(submenu_debug); },
|
2022-01-28 22:47:56 +00:00
|
|
|
'Request': {
|
|
|
|
value: settings.gracePeriodRequest,
|
|
|
|
min: 0,
|
|
|
|
max: 3000,
|
|
|
|
step: 100,
|
|
|
|
format: v=>v+"ms",
|
|
|
|
onchange: v => {
|
|
|
|
writeSettings("gracePeriodRequest",v);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
'Connect': {
|
|
|
|
value: settings.gracePeriodConnect,
|
|
|
|
min: 0,
|
|
|
|
max: 3000,
|
|
|
|
step: 100,
|
|
|
|
format: v=>v+"ms",
|
|
|
|
onchange: v => {
|
|
|
|
writeSettings("gracePeriodConnect",v);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
'Notification': {
|
|
|
|
value: settings.gracePeriodNotification,
|
|
|
|
min: 0,
|
|
|
|
max: 3000,
|
|
|
|
step: 100,
|
|
|
|
format: v=>v+"ms",
|
|
|
|
onchange: v => {
|
|
|
|
writeSettings("gracePeriodNotification",v);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2022-04-05 14:53:52 +00:00
|
|
|
|
2022-03-09 19:30:39 +00:00
|
|
|
E.showMenu(buildMainMenu());
|
2024-10-28 19:16:14 +00:00
|
|
|
})
|