BangleApps/apps/bthrm/settings.js

375 lines
11 KiB
JavaScript

(function(back) {
function writeSettings(key, value) {
let s = require('Storage').readJSON(FILE, true) || {};
s[key] = value;
require('Storage').writeJSON(FILE, s);
readSettings();
}
function readSettings(){
settings = Object.assign(
require('Storage').readJSON("bthrm.default.json", true) || {},
require('Storage').readJSON(FILE, true) || {}
);
}
let FILE="bthrm.json";
let settings;
readSettings();
let log = ()=>{};
if (settings.debuglog)
log = print;
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);
}
function buildMainMenu(){
let mainmenu = {
'': { '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:
applyCustomSettings();
break;
}
writeSettings("mode",v);
}
}
};
if (settings.btname || settings.btid){
let name = "Clear " + (settings.btname || settings.btid);
mainmenu[name] = function() {
E.showPrompt("Clear current device?").then((r)=>{
if (r) {
writeSettings("btname",undefined);
writeSettings("btid",undefined);
writeSettings("cache", undefined);
}
E.showMenu(buildMainMenu());
});
};
}
mainmenu["BLE Scan"] = ()=> createMenuFromScan();
mainmenu["Custom Mode"] = function() { E.showMenu(submenu_custom); };
mainmenu.Debug = function() { E.showMenu(submenu_debug); };
return mainmenu;
}
let submenu_debug = {
'' : { title: "Debug"},
'< Back': function() { E.showMenu(buildMainMenu()); },
'Alert on disconnect': {
value: !!settings.warnDisconnect,
onchange: v => {
writeSettings("warnDisconnect",v);
}
},
'Debug log': {
value: !!settings.debuglog,
onchange: v => {
writeSettings("debuglog",v);
}
},
'Use bonding': {
value: !!settings.bonding,
onchange: v => {
writeSettings("bonding",v);
}
},
'Use active scanning': {
value: !!settings.active,
onchange: v => {
writeSettings("active",v);
}
},
'Grace periods': function() { E.showMenu(submenu_grace); }
};
let supportedServices = [
"0x180d", // Heart Rate
"0x180f", // Battery
];
let supportedCharacteristics = [
"0x2a37", // Heart Rate
"0x2a38", // Body sensor location
"0x2a19", // Battery
];
var characteristicsToCache = function(characteristics) {
log("Cache characteristics");
let cache = {};
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");
characteristicsToCache(characteristics);
});
}
function createMenuFromScan(){
E.showMenu();
E.showMessage("Scanning for 4 seconds");
let submenu_scan = {
'< Back': function() { E.showMenu(buildMainMenu()); }
};
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) => {
log("Found device", d);
let shown = (d.name || d.id.substr(0, 17));
submenu_scan[shown] = function () {
E.showPrompt("Connect to\n" + shown + "?", {title: "Pairing"}).then((r) => {
if (r) {
E.showMessage("Connecting...", {img:require("Storage").read("bthrm.img")});
let count = 0;
const successHandler = ()=>{
E.showPrompt("Success!", {
img:require("Storage").read("bthrm.img"),
buttons: { "OK":true }
}).then(()=>{
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){
E.showMessage("Error during caching\nRetry " + count + "/10", e);
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);
}
});
};
});
}
E.showMenu(submenu_scan);
}, { timeout: 4000, active: true, filters: [{services: [ "180d" ]}]});
}
let submenu_custom = {
'' : { title: "Custom mode"},
'< Back': function() { E.showMenu(buildMainMenu()); },
'Replace HRM': {
value: !!settings.custom_replace,
onchange: v => {
writeSettings("custom_replace",v);
if (settings.mode == 3) applyCustomSettings();
}
},
'Start w. HRM': {
value: !!settings.custom_startWithHrm,
onchange: v => {
writeSettings("custom_startWithHrm",v);
if (settings.mode == 3) applyCustomSettings();
}
},
'HRM Fallback': {
value: !!settings.custom_allowFallback,
onchange: v => {
writeSettings("custom_allowFallback",v);
if (settings.mode == 3) applyCustomSettings();
}
},
'Fallback Timeout': {
value: settings.custom_fallbackTimeout,
min: 5,
max: 60,
step: 5,
format: v=>v+"s",
onchange: v => {
writeSettings("custom_fallbackTimout",v*1000);
if (settings.mode == 3) applyCustomSettings();
}
},
};
let submenu_grace = {
'' : { title: "Grace periods"},
'< Back': function() { E.showMenu(submenu_debug); },
'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);
}
}
};
E.showMenu(buildMainMenu());
});