updates to coretemp
parent
8f9490459c
commit
f6b8aa5942
|
|
@ -3,3 +3,4 @@
|
||||||
0.03: Move code for recording to this app
|
0.03: Move code for recording to this app
|
||||||
0.04: Use default Bangle formatter for booleans
|
0.04: Use default Bangle formatter for booleans
|
||||||
0.05: Minor code improvements
|
0.05: Minor code improvements
|
||||||
|
0.06: Added advanced settings, adapted code to mirror bthrm funcitonality, and will work with latest firmware CORE Sensor Firmware (V0.87)
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,43 @@
|
||||||
# CoreTemp display
|
# CoreTemp display
|
||||||
|
|
||||||
Basic example of connecting to a bluetooth [CoreTemp](https://corebodytemp.com/) device and displaying the current skin and body core temperature readings.
|
Application to connect to the [CORE](https://corebodytemp.com/) or [calera](https://info.greenteg.com/calera-research) devices from greenteg and display the current skin and body core temperature readings.
|
||||||
|
|
||||||
|
This also includes a module (heavily influenced by the BTHRM app) so you can integrate the core sensor into your own apps/widgets. You can also pair an ANT+ heart rate strap to the CORE/calera sensor as well in the App Settings so that you can leverage the exertional algorthim for estimating core temperature.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Background task connects to any CoreTemp device (2100/2101) and emits a CoreTemp signal value for each reading.
|
Background task connects to a paired and emits a CORESensor signal value for each reading.
|
||||||
Application contains three components, one is a background task that monitors the sensor and emits a 'CoreTemp' signal on activity if activated in settings.
|
Application contains three components, one is a background task that monitors the sensor and emits a 'CORESensor' signal on activity if activated in settings.
|
||||||
The widget shows when the sensor is enabled with a mini value and blinks on use.
|
The widget shows when the sensor is enabled and connected (green) or disconnected (grey).
|
||||||
The app listens for 'CoreTemp' signals and shows the current skin and core temperatures in large numbers.
|
The app listens for 'CORESensor' signals and shows the current data.
|
||||||
|
|
||||||
|
## CORESensor Module
|
||||||
|
|
||||||
|
With the module, you can add the CORE Sensor to your own app. Simply power on the module and listen to CORESensor:
|
||||||
|
|
||||||
|
```
|
||||||
|
Bangle.setCORESensorPower(1,appName);
|
||||||
|
Bangle.on('CORESensor', (x) =>{ ... });
|
||||||
|
```
|
||||||
|
|
||||||
|
The CORESensor emits an object with following keys:
|
||||||
|
|
||||||
|
* **core**: Estimated/Predicted core temperature
|
||||||
|
* **skin**: Measured skin temperature
|
||||||
|
* **unit**: "F" or "C"
|
||||||
|
* **hr**: Heart Rate (only when ANT+ heart rate monitor is paired)
|
||||||
|
* **heatflux**: (calera device only - needs encryption level b released by greenteg)
|
||||||
|
* **hsi**: Heat Strain Index ([read more here](https://help.corebodytemp.com/en/articles/10447107-heat-strain-index), exertional algorithm only)
|
||||||
|
* **battery**: battery level
|
||||||
|
* **quality**: Used to indicate the quality or trust level of the current measurement values
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
* Integrate with other tracking/sports apps to log data.
|
* Integrate with other tracking/sports apps to log data.
|
||||||
* Add specific device selection
|
* Emit Bangle.js heart rate to device as a heart rate for internal algorthim
|
||||||
|
|
||||||
## Creator
|
## Creators/Contributors
|
||||||
|
|
||||||
Ivor Hewitt
|
Ivor Hewitt
|
||||||
|
|
||||||
|
[Nicholas Ravanelli](https://github.com/nravanelli)
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
{
|
{
|
||||||
"enabled":false
|
"enabled":false,
|
||||||
|
"debuglog": false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,114 +1 @@
|
||||||
//
|
if ((require('Storage').readJSON("coretemp.json", true) || {}).enabled != false) require("CORESensor").enable();
|
||||||
// If enabled in settings run constantly in background
|
|
||||||
//
|
|
||||||
(function() {
|
|
||||||
var log = function() {};//print
|
|
||||||
var settings = {};
|
|
||||||
var device;
|
|
||||||
var gatt;
|
|
||||||
var service;
|
|
||||||
var characteristic;
|
|
||||||
|
|
||||||
class CoreSensor {
|
|
||||||
constructor() {
|
|
||||||
this.unit = "";
|
|
||||||
this.core = -1;
|
|
||||||
this.skin = -1;
|
|
||||||
this.battery = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateSensor(event) {
|
|
||||||
if (event.target.uuid == "00002101-5b1e-4347-b07c-97b514dae121") {
|
|
||||||
var dv = event.target.value;
|
|
||||||
var flags = dv.buffer[0];
|
|
||||||
|
|
||||||
if (flags & 8) {
|
|
||||||
this.unit = "F";
|
|
||||||
} else {
|
|
||||||
this.unit = "C";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (flags & 1) {
|
|
||||||
this.skin = (dv.buffer[4] * 256 + dv.buffer[3]) / 100;
|
|
||||||
} else {
|
|
||||||
this.skin = 0;
|
|
||||||
}
|
|
||||||
if (flags & 2) {
|
|
||||||
this.core = (dv.buffer[2] * 256 + dv.buffer[1]) / 100;
|
|
||||||
} else {
|
|
||||||
this.core = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
Bangle.emit('CoreTemp',
|
|
||||||
{core : this.core, skin : this.skin, unit : this.unit});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateBatteryLevel(event) {
|
|
||||||
if (event.target.uuid == "0x2a19")
|
|
||||||
this.battery = event.target.value.getUint8(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var mySensor = new CoreSensor();
|
|
||||||
|
|
||||||
function getSensorBatteryLevel(gatt) {
|
|
||||||
gatt.getPrimaryService("180f")
|
|
||||||
.then(function(s) { return s.getCharacteristic("2a19"); })
|
|
||||||
.then(function(c) {
|
|
||||||
c.on('characteristicvaluechanged',
|
|
||||||
(event) => mySensor.updateBatteryLevel(event));
|
|
||||||
return c.startNotifications();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function connection_setup() {
|
|
||||||
log("Scanning for CoreTemp sensor...");
|
|
||||||
NRF.requestDevice({active:true,timeout : 20000, filters : [ {namePrefix : 'CORE'} ]})
|
|
||||||
.then(function(d) {
|
|
||||||
device = d;
|
|
||||||
log("Found device");
|
|
||||||
return device.gatt.connect();
|
|
||||||
})
|
|
||||||
.then(function(g) {
|
|
||||||
gatt = g;
|
|
||||||
return gatt.getPrimaryService('00002100-5b1e-4347-b07c-97b514dae121');
|
|
||||||
})
|
|
||||||
.then(function(s) {
|
|
||||||
service = s;
|
|
||||||
return service.getCharacteristic(
|
|
||||||
'00002101-5b1e-4347-b07c-97b514dae121');
|
|
||||||
})
|
|
||||||
.then(function(c) {
|
|
||||||
characteristic = c;
|
|
||||||
characteristic.on('characteristicvaluechanged',
|
|
||||||
(event) => mySensor.updateSensor(event));
|
|
||||||
return characteristic.startNotifications();
|
|
||||||
})
|
|
||||||
.then(function() {
|
|
||||||
log("Done!");
|
|
||||||
// getSensorBatteryLevel(gatt);
|
|
||||||
})
|
|
||||||
.catch(function(e) {
|
|
||||||
log(e.toString(), "ERROR");
|
|
||||||
log(e);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function connection_end() {
|
|
||||||
if (gatt != undefined)
|
|
||||||
gatt.disconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
settings = require("Storage").readJSON("coretemp.json", 1) || {};
|
|
||||||
log("Settings:");
|
|
||||||
log(settings);
|
|
||||||
|
|
||||||
if (settings.enabled) {
|
|
||||||
connection_setup();
|
|
||||||
NRF.on('disconnect', connection_setup);
|
|
||||||
}
|
|
||||||
|
|
||||||
E.on('kill', () => { connection_end(); });
|
|
||||||
|
|
||||||
})();
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
|
var settings = require("Storage").readJSON("coretemp.json", 1) || {};
|
||||||
// Simply listen for core events and show data
|
// Simply listen for core events and show data
|
||||||
|
|
||||||
//var btm = g.getHeight() - 1;
|
//var btm = g.getHeight() - 1;
|
||||||
var px = g.getWidth() / 2;
|
var px = g.getWidth() / 2;
|
||||||
|
|
||||||
|
|
@ -21,25 +21,14 @@ var corelogo = {
|
||||||
function onCore(c) {
|
function onCore(c) {
|
||||||
// Large or small font
|
// Large or small font
|
||||||
var sz = ((process.env.HWVERSION == 1) ? 3 : 2);
|
var sz = ((process.env.HWVERSION == 1) ? 3 : 2);
|
||||||
|
|
||||||
g.setFontAlign(0, 0);
|
g.setFontAlign(0, 0);
|
||||||
g.clearRect(0, 32 + 48, g.getWidth(), 32 + 48 + 24 * 4);
|
g.clearRect(0, 32 + 48, g.getWidth(), 32 + 48 + 24 * 4);
|
||||||
g.setColor(g.theme.dark ? "#CCC" : "#333"); // gray
|
g.setColor(g.theme.dark ? "#CCC" : "#333"); // gray
|
||||||
g.setFont("6x8", sz).drawString(
|
g.setFont("6x8", sz).drawString("Core: " + ((c.core < 327) ? (c.core + c.unit) : 'n/a'), px, 48 + 48);
|
||||||
"Core: " + ((c.core < 327) ? (c.core + c.unit) : 'n/a'), px, 48 + 48);
|
g.setFont("6x8", sz).drawString("Skin: " + c.skin + c.unit, px, 48 + 48 + 14);
|
||||||
g.setFont("6x8", sz).drawString("Skin: " + c.skin + c.unit, px, 48 + 48 + 24);
|
g.setFont("6x8", sz).drawString("HR: " + c.hr + " BPM", px, 48 + 48 + 28);
|
||||||
}
|
g.setFont("6x8", sz).drawString("HSI: " + c.hsi+ "/10", px, 48 + 48 + 42);
|
||||||
|
g.setFont("6x8", sz).drawString("BATT: " + c.battery+ "%", px, 48 + 48 + 56);
|
||||||
// Background task will activate once settings are enabled.
|
|
||||||
function enableSensor() {
|
|
||||||
settings = require("Storage").readJSON("coretemp.json", 1) || {};
|
|
||||||
|
|
||||||
if (!settings.enabled) {
|
|
||||||
settings.enabled = true;
|
|
||||||
require("Storage").write("coretemp.json", settings);
|
|
||||||
|
|
||||||
drawBackground("Waiting for\ndata...");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawBackground(message) {
|
function drawBackground(message) {
|
||||||
|
|
@ -50,17 +39,11 @@ function drawBackground(message) {
|
||||||
g.drawImage(corelogo, px - 146 / 2, 30);
|
g.drawImage(corelogo, px - 146 / 2, 30);
|
||||||
g.drawString(message, g.getWidth() / 2, g.getHeight() / 2 + 16);
|
g.drawString(message, g.getWidth() / 2, g.getHeight() / 2 + 16);
|
||||||
}
|
}
|
||||||
|
Bangle.setCORESensorPower(1,"COREAPP");
|
||||||
Bangle.on('CoreTemp', onCore);
|
Bangle.on('CORESensor', onCore);
|
||||||
|
|
||||||
settings = require("Storage").readJSON("coretemp.json", 1) || {};
|
|
||||||
|
|
||||||
if (!settings.enabled) {
|
if (!settings.enabled) {
|
||||||
drawBackground("Sensor off\nBTN" +
|
drawBackground("Sensor off\nEnable in Settings");
|
||||||
((process.env.HWVERSION == 1) ? '2' : '1') + " to enable");
|
|
||||||
} else {
|
} else {
|
||||||
drawBackground("Waiting for\ndata...");
|
drawBackground("Waiting for\ndata...");
|
||||||
}
|
}
|
||||||
|
|
||||||
setWatch(() => { enableSensor(); }, (process.env.HWVERSION == 1) ? BTN2 : BTN1,
|
|
||||||
{repeat : false});
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,279 @@
|
||||||
|
exports.enable = () => {
|
||||||
|
var settings = require("Storage").readJSON("coretemp.json", 1) || {};
|
||||||
|
let log = function () { };//print
|
||||||
|
Bangle.enableCORESensorLog = function () {
|
||||||
|
log = function (text, param) {
|
||||||
|
let logline = new Date().toISOString() + " - " + text;
|
||||||
|
if (param) logline += ": " + JSON.stringify(param);
|
||||||
|
print(logline);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
let gatt;
|
||||||
|
let device;
|
||||||
|
let characteristics;
|
||||||
|
let blockInit = false;
|
||||||
|
let waitingPromise = function (timeout) {
|
||||||
|
return new Promise(function (resolve) {
|
||||||
|
log("Start waiting for " + timeout);
|
||||||
|
setTimeout(() => {
|
||||||
|
log("Done waiting for " + timeout);
|
||||||
|
resolve();
|
||||||
|
}, timeout);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (settings.enabled && settings.cache) {
|
||||||
|
let addNotificationHandler = function (characteristic) {
|
||||||
|
log("Setting notification handler"/*supportedCharacteristics[characteristic.uuid].handler*/);
|
||||||
|
characteristic.on('characteristicvaluechanged', (ev) => supportedCharacteristics[characteristic.uuid].handler(ev.target.value));
|
||||||
|
};
|
||||||
|
let characteristicsFromCache = function (device) {
|
||||||
|
let service = { device: device }; // fake a BluetoothRemoteGATTService
|
||||||
|
log("Read cached characteristics");
|
||||||
|
let cache = settings.cache;
|
||||||
|
if (!cache.characteristics) return [];
|
||||||
|
let restored = [];
|
||||||
|
for (let c in cache.characteristics) {
|
||||||
|
let cached = cache.characteristics[c];
|
||||||
|
let r = new BluetoothRemoteGATTCharacteristic();
|
||||||
|
log("Restoring characteristic ", cached);
|
||||||
|
r.handle_value = cached.handle;
|
||||||
|
r.uuid = cached.uuid;
|
||||||
|
r.properties = {};
|
||||||
|
r.properties.notify = cached.notify;
|
||||||
|
r.properties.read = cached.read;
|
||||||
|
r.service = service;
|
||||||
|
addNotificationHandler(r);
|
||||||
|
log("Restored characteristic: ", r);
|
||||||
|
restored.push(r);
|
||||||
|
}
|
||||||
|
return restored;
|
||||||
|
};
|
||||||
|
let supportedCharacteristics = {
|
||||||
|
"00002101-5b1e-4347-b07c-97b514dae121": {
|
||||||
|
handler: function (dv) {
|
||||||
|
log(dv);
|
||||||
|
let index = 0;
|
||||||
|
let flags = dv.getUint8(index++);
|
||||||
|
let coreTemp = dv.getInt16(index, true) / 100.0;
|
||||||
|
index += 2;
|
||||||
|
let skinTemp = dv.getInt16(index, true) / 100.0;
|
||||||
|
index += 2;
|
||||||
|
let coreReserved = dv.getInt16(index, true); //caleraGT only with firmware decryption provided by Greenteg
|
||||||
|
index += 2;
|
||||||
|
let qualityAndState = dv.getUint8(index++);
|
||||||
|
let heartRate = dv.getUint8(index++);
|
||||||
|
let heatStrainIndex = dv.getUint8(index) / 10.0;
|
||||||
|
let dataQuality = qualityAndState & 0x07;
|
||||||
|
let hrState = (qualityAndState >> 4) & 0x03;
|
||||||
|
let data = {
|
||||||
|
core: coreTemp,
|
||||||
|
skin: skinTemp,
|
||||||
|
unit: (flags & 0b00001000) ? "F" : "C",
|
||||||
|
hr: heartRate,
|
||||||
|
heatflux: coreReserved,
|
||||||
|
hsi: heatStrainIndex,
|
||||||
|
battery: 0,
|
||||||
|
dataQuality: dataQuality,
|
||||||
|
hrState: hrState
|
||||||
|
};
|
||||||
|
if (lastReceivedData.hasOwnProperty("0x180f")) {
|
||||||
|
data.battery = lastReceivedData["0x180f"]["0x2a19"];
|
||||||
|
}
|
||||||
|
log("data", data);
|
||||||
|
Bangle.emit("CORESensor", data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"00002102-5b1e-4347-b07c-97b514dae121": {
|
||||||
|
handler: function (dv) {
|
||||||
|
log(dv);//just log the response, handle write and responses in another Promise Function (Bangle.CORESensorSendOpCode)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"0x2a19": {
|
||||||
|
//Battery
|
||||||
|
handler: function (dv) {
|
||||||
|
if (!lastReceivedData["0x180f"]) lastReceivedData["0x180f"] = {};
|
||||||
|
log("Got battery", dv);
|
||||||
|
lastReceivedData["0x180f"]["0x2a19"] = dv.getUint8(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let lastReceivedData = {
|
||||||
|
};
|
||||||
|
|
||||||
|
Bangle.isCORESensorOn = function () {
|
||||||
|
return (Bangle._PWR && Bangle._PWR.CORESensor && Bangle._PWR.CORESensor.length > 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
Bangle.isCORESensorConnected = function () {
|
||||||
|
return gatt && gatt.connected;
|
||||||
|
};
|
||||||
|
|
||||||
|
let onDisconnect = function (reason) {
|
||||||
|
blockInit = false;
|
||||||
|
log("Disconnect: " + reason);
|
||||||
|
if (Bangle.isCORESensorOn()) {
|
||||||
|
setTimeout(initCORESensor, 5000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let createCharacteristicPromise = function (newCharacteristic) {
|
||||||
|
log("Create characteristic promise", newCharacteristic);
|
||||||
|
let result = Promise.resolve();
|
||||||
|
if (newCharacteristic.readValue) {
|
||||||
|
result = result.then(() => {
|
||||||
|
log("Reading data", newCharacteristic);
|
||||||
|
return newCharacteristic.readValue().then((data) => {
|
||||||
|
if (supportedCharacteristics[newCharacteristic.uuid] && supportedCharacteristics[newCharacteristic.uuid].handler) {
|
||||||
|
supportedCharacteristics[newCharacteristic.uuid].handler(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (newCharacteristic.properties.notify) {
|
||||||
|
result = result.then(() => {
|
||||||
|
log("Starting notifications", newCharacteristic);
|
||||||
|
let startPromise = newCharacteristic.startNotifications().then(() => log("Notifications started", newCharacteristic));
|
||||||
|
startPromise = startPromise.then(() => {
|
||||||
|
return waitingPromise(3000);
|
||||||
|
});
|
||||||
|
return startPromise;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result.then(() => log("Handled characteristic", newCharacteristic));
|
||||||
|
};
|
||||||
|
|
||||||
|
let attachCharacteristicPromise = function (promise, characteristic) {
|
||||||
|
return promise.then(() => {
|
||||||
|
log("Handling characteristic:", characteristic);
|
||||||
|
return createCharacteristicPromise(characteristic);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
let initCORESensor = function () {
|
||||||
|
if (!settings.btname) {
|
||||||
|
log("CORESensor not paired, quitting");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (blockInit) {
|
||||||
|
log("CORESensor already turned on by another app, quitting");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
blockInit = true;
|
||||||
|
NRF.setScan();
|
||||||
|
let promise;
|
||||||
|
let filters;
|
||||||
|
|
||||||
|
if (!device) {
|
||||||
|
if (settings.btname) {
|
||||||
|
log("Configured device name ", settings.btname);
|
||||||
|
filters = [{ name: settings.btname }];
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log("Requesting device with filters", filters);
|
||||||
|
try {
|
||||||
|
promise = NRF.requestDevice({ filters: filters, active: true });
|
||||||
|
} catch (e) {
|
||||||
|
log("Error during initial request:", e);
|
||||||
|
onDisconnect(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
promise = promise.then((d) => {
|
||||||
|
log("Wait after request");
|
||||||
|
return waitingPromise(2000).then(() => Promise.resolve(d));
|
||||||
|
});
|
||||||
|
|
||||||
|
promise = promise.then((d) => {
|
||||||
|
log("Got device", d);
|
||||||
|
d.on('gattserverdisconnected', onDisconnect);
|
||||||
|
device = d;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
promise = Promise.resolve();
|
||||||
|
log("Reuse device", device);
|
||||||
|
}
|
||||||
|
|
||||||
|
promise = promise.then(() => {
|
||||||
|
gatt = device.gatt;
|
||||||
|
return Promise.resolve(gatt);
|
||||||
|
});
|
||||||
|
|
||||||
|
promise = promise.then((gatt) => {
|
||||||
|
if (!gatt.connected) {
|
||||||
|
log("Connecting...");
|
||||||
|
let connectPromise = gatt.connect().then(function () {
|
||||||
|
log("Connected.");
|
||||||
|
});
|
||||||
|
connectPromise = connectPromise.then(() => {
|
||||||
|
log("Wait after connect");
|
||||||
|
return waitingPromise(2000);
|
||||||
|
});
|
||||||
|
return connectPromise;
|
||||||
|
} else {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
promise = promise.then(() => {
|
||||||
|
if (!characteristics || characteristics.length == 0) {
|
||||||
|
characteristics = characteristicsFromCache(device);
|
||||||
|
}
|
||||||
|
let characteristicsPromise = Promise.resolve();
|
||||||
|
for (let characteristic of characteristics) {
|
||||||
|
characteristicsPromise = attachCharacteristicPromise(characteristicsPromise, characteristic, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return characteristicsPromise;
|
||||||
|
});
|
||||||
|
|
||||||
|
return promise.then(() => {
|
||||||
|
log("Connection established, waiting for notifications");
|
||||||
|
}).catch((e) => {
|
||||||
|
characteristics = [];
|
||||||
|
log("Error:", e);
|
||||||
|
onDisconnect(e);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
Bangle.setCORESensorPower = function (isOn, app) {
|
||||||
|
// Do app power handling
|
||||||
|
if (!app) app = "?";
|
||||||
|
log("setCORESensorPower ->", isOn, app);
|
||||||
|
if (Bangle._PWR === undefined) Bangle._PWR = {};
|
||||||
|
if (Bangle._PWR.CORESensor === undefined) Bangle._PWR.CORESensor = [];
|
||||||
|
if (isOn && !Bangle._PWR.CORESensor.includes(app)) Bangle._PWR.CORESensor.push(app);
|
||||||
|
if (!isOn && Bangle._PWR.CORESensor.includes(app)) Bangle._PWR.CORESensor = Bangle._PWR.CORESensor.filter(a => a != app);
|
||||||
|
isOn = Bangle._PWR.CORESensor.length;
|
||||||
|
// so now we know if we're really on
|
||||||
|
if (isOn) {
|
||||||
|
log("setCORESensorPower on" + app);
|
||||||
|
if (!Bangle.isCORESensorConnected()) initCORESensor();
|
||||||
|
} else { // being turned off!
|
||||||
|
log("setCORESensorPower turning off ", app);
|
||||||
|
if (gatt) {
|
||||||
|
if (gatt.connected) {
|
||||||
|
log("CORESensor: Disconnect with gatt", gatt);
|
||||||
|
try {
|
||||||
|
gatt.disconnect().then(() => {
|
||||||
|
log("CORESensor: Successful disconnect");
|
||||||
|
}).catch((e) => {
|
||||||
|
log("CORESensor: Error during disconnect promise", e);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
log("CORESensor: Error during disconnect attempt", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// disconnect when swapping apps
|
||||||
|
E.on("kill", function () {
|
||||||
|
if (gatt) {
|
||||||
|
log("CORESensor connected - disconnecting");
|
||||||
|
try { gatt.disconnect(); } catch (e) {
|
||||||
|
log("CORESensor disconnect error", e);
|
||||||
|
}
|
||||||
|
gatt = undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "coretemp",
|
"id": "coretemp",
|
||||||
"name": "CoreTemp",
|
"name": "CoreTemp",
|
||||||
"version": "0.05",
|
"version": "0.06",
|
||||||
"description": "Display CoreTemp device sensor data",
|
"description": "Display CoreTemp device sensor data",
|
||||||
"icon": "coretemp.png",
|
"icon": "coretemp.png",
|
||||||
"type": "app",
|
"type": "app",
|
||||||
|
|
@ -11,10 +11,11 @@
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"coretemp.wid.js","url":"widget.js"},
|
{"name":"coretemp.wid.js","url":"widget.js"},
|
||||||
{"name":"coretemp.app.js","url":"coretemp.js"},
|
{"name":"coretemp.app.js","url":"coretemp.js"},
|
||||||
|
{"name":"CORESensor","url":"lib.js"},
|
||||||
{"name":"coretemp.recorder.js","url":"recorder.js"},
|
{"name":"coretemp.recorder.js","url":"recorder.js"},
|
||||||
{"name":"coretemp.settings.js","url":"settings.js"},
|
{"name":"coretemp.settings.js","url":"settings.js"},
|
||||||
{"name":"coretemp.img","url":"coretemp-icon.js","evaluate":true},
|
{"name":"coretemp.img","url":"coretemp-icon.js","evaluate":true},
|
||||||
{"name":"coretemp.boot.js","url":"boot.js"}
|
{"name":"coretemp.0..boot.js","url":"boot.js"}
|
||||||
],
|
],
|
||||||
"data": [{"name":"coretemp.json","url":"app-settings.json"}],
|
"data": [{"name":"coretemp.json","url":"app-settings.json"}],
|
||||||
"screenshots": [{"url":"screenshot.png"}]
|
"screenshots": [{"url":"screenshot.png"}]
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,40 @@
|
||||||
(function(recorders) {
|
(function(recorders) {
|
||||||
recorders.coretemp = function() {
|
recorders.coretemp = function() {
|
||||||
var core = "", skin = "";
|
var core = "", skin = "", unit="", hr="", heatflux="", hsi="", battery="", quality="";
|
||||||
var hasCore = false;
|
var hasCore = false;
|
||||||
function onCore(c) {
|
function onCore(c) {
|
||||||
core=c.core;
|
core=c.core;
|
||||||
skin=c.skin;
|
skin=c.skin;
|
||||||
hasCore = true;
|
hasCore = true;
|
||||||
|
unit = c.unit;
|
||||||
|
hr = c.hr;
|
||||||
|
heatflux = c.heatflux;
|
||||||
|
hsi = c.hsi;
|
||||||
|
battery= c.battery;
|
||||||
|
quality = c.dataQuality;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
name : "Core",
|
name : "Core",
|
||||||
fields : ["Core","Skin"],
|
fields : ["Core","Skin","Unit","HeartRate","HeatFlux","HeatStrainIndex","Battery","Quality"],
|
||||||
getValues : () => {
|
getValues : () => {
|
||||||
var r = [core,skin];
|
var r = [core,skin,unit,hr,heatflux,hsi,battery,quality];
|
||||||
core = "";
|
core = "";
|
||||||
skin = "";
|
skin = "";
|
||||||
|
unit="";
|
||||||
|
hr="";
|
||||||
|
heatflux="";
|
||||||
|
hsi="";
|
||||||
|
battery="";
|
||||||
|
quality="";
|
||||||
return r;
|
return r;
|
||||||
},
|
},
|
||||||
start : () => {
|
start : () => {
|
||||||
hasCore = false;
|
hasCore = false;
|
||||||
Bangle.on('CoreTemp', onCore);
|
Bangle.on('CORESensor', onCore);
|
||||||
},
|
},
|
||||||
stop : () => {
|
stop : () => {
|
||||||
hasCore = false;
|
hasCore = false;
|
||||||
Bangle.removeListener('CoreTemp', onCore);
|
Bangle.removeListener('CORESensor', onCore);
|
||||||
},
|
},
|
||||||
draw : (x,y) => g.setColor(hasCore?"#0f0":"#8f8").drawImage(atob("DAyBAAHh0js3EuDMA8A8AWBnDj9A8A=="),x,y)
|
draw : (x,y) => g.setColor(hasCore?"#0f0":"#8f8").drawImage(atob("DAyBAAHh0js3EuDMA8A8AWBnDj9A8A=="),x,y)
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -3,44 +3,531 @@
|
||||||
* @param {function} back Use back() to return to settings menu
|
* @param {function} back Use back() to return to settings menu
|
||||||
*/
|
*/
|
||||||
(function (back) {
|
(function (back) {
|
||||||
|
var settings = {};
|
||||||
const SETTINGS_FILE = 'coretemp.json'
|
const SETTINGS_FILE = 'coretemp.json'
|
||||||
// initialize with default settings...
|
var CORECONNECTED = false;
|
||||||
let s = {
|
|
||||||
'enabled': true,
|
|
||||||
}
|
|
||||||
// ...and overwrite them with any saved values
|
|
||||||
// This way saved values are preserved if a new version adds more settings
|
|
||||||
const storage = require('Storage')
|
|
||||||
const saved = storage.readJSON(SETTINGS_FILE, 1) || {}
|
|
||||||
for (const key in saved) {
|
|
||||||
s[key] = saved[key];
|
|
||||||
}
|
|
||||||
// creates a function to safe a specific setting, e.g. save('color')(1)
|
// creates a function to safe a specific setting, e.g. save('color')(1)
|
||||||
function save(key) {
|
function writeSettings(key, value) {
|
||||||
return function (value) {
|
let s = require('Storage').readJSON(SETTINGS_FILE, true) || {};
|
||||||
s[key] = value;
|
s[key] = value;
|
||||||
storage.write(SETTINGS_FILE, s);
|
require('Storage').writeJSON(SETTINGS_FILE, s);
|
||||||
}
|
readSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSettings() {
|
function readSettings() {
|
||||||
require("Storage").write("coretemp.json", s);
|
settings = Object.assign(
|
||||||
if (WIDGETS["coretemp"])
|
require('Storage').readJSON(SETTINGS_FILE, true) || {}
|
||||||
WIDGETS["coretemp"].reload();
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
readSettings();
|
||||||
|
let log = () => { };
|
||||||
|
if (settings.debuglog)
|
||||||
|
log = print;
|
||||||
|
|
||||||
|
let supportedServices = [
|
||||||
|
"00002100-5b1e-4347-b07c-97b514dae121", // Core Body Temperature Service
|
||||||
|
"0x180f", // Battery
|
||||||
|
"0x1809", // Health Thermometer Service
|
||||||
|
];
|
||||||
|
|
||||||
|
let supportedCharacteristics = [
|
||||||
|
"00002101-5b1e-4347-b07c-97b514dae121", // Core Body Temperature Characteristic
|
||||||
|
"00002102-5b1e-4347-b07c-97b514dae121", //Core Temp Control Point (opCode for extra function)
|
||||||
|
//"0x2a1c", //Thermometer
|
||||||
|
//"0x2a1d", //Sensor Location (CORE)
|
||||||
|
"0x2a19", // Battery
|
||||||
|
];
|
||||||
|
|
||||||
|
var characteristicsToCache = function (characteristics) {
|
||||||
|
log("Cache characteristics");
|
||||||
|
let cache = {};
|
||||||
|
if (!cache.characteristics) cache.characteristics = {};
|
||||||
|
for (var c of characteristics) {
|
||||||
|
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,
|
||||||
|
"write": c.properties.write
|
||||||
|
};
|
||||||
|
}
|
||||||
|
writeSettings("cache", cache);
|
||||||
|
};
|
||||||
|
|
||||||
|
var controlPointChar;
|
||||||
|
|
||||||
|
let createCharacteristicPromise = function (newCharacteristic) {
|
||||||
|
log("Create characteristic promise", newCharacteristic.uuid);
|
||||||
|
if (newCharacteristic.uuid === "00002102-5b1e-4347-b07c-97b514dae121") {
|
||||||
|
log("Subscribing to CoreTemp Control Point Indications.");
|
||||||
|
controlPointChar = newCharacteristic;
|
||||||
|
return controlPointChar.writeValue(new Uint8Array([0x02]), {
|
||||||
|
type: "command",
|
||||||
|
handle: true
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
log("Indications enabled! Listening for responses...");
|
||||||
|
return controlPointChar.startNotifications(); //now we can send opCodes
|
||||||
|
})
|
||||||
|
.then(() => log("Finished handling CoreTemp Control Point."))
|
||||||
|
.catch(error => {
|
||||||
|
log("Error enabling indications:", error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
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 writeToControlPoint(opCode, params) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let data = new Uint8Array([opCode].concat(params));
|
||||||
|
|
||||||
|
if (!controlPointChar) {
|
||||||
|
log("Control Point characteristic not found! Reconnecting...");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Temporary handler to capture the response
|
||||||
const menu = {
|
function handleResponse(event) {
|
||||||
'' : {'title' : 'CoreTemp sensor'},
|
let response = new Uint8Array(event.target.value.buffer);
|
||||||
'< Back' : back,
|
//let responseOpCode = response[0];
|
||||||
'Enabled' : {
|
let requestOpCode = response[1]; // Matches the sent OpCode
|
||||||
value : !!s.enabled,
|
let resultCode = response[2]; // 0x01 = Success
|
||||||
onchange : v => {
|
controlPointChar.removeListener("characteristicvaluechanged", handleResponse);
|
||||||
s.enabled = v;
|
if (requestOpCode === opCode) {
|
||||||
updateSettings();
|
if (resultCode === 0x01) { //successful
|
||||||
|
resolve(response);
|
||||||
|
} else {
|
||||||
|
reject("Error Code: " + resultCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
E.showMenu(menu);
|
controlPointChar.on("characteristicvaluechanged", handleResponse);
|
||||||
|
controlPointChar.writeValue(data)
|
||||||
|
.then(() => log("Sent OpCode:", opCode.toString(16), "Params:", data))
|
||||||
|
.catch(error => {
|
||||||
|
log("Write error:", error);
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let gatt;
|
||||||
|
function cacheDevice(deviceName) {
|
||||||
|
let promise;
|
||||||
|
let filters;
|
||||||
|
characteristics = [];
|
||||||
|
filters = [{ name: deviceName }];
|
||||||
|
log("Requesting device with filters", filters);
|
||||||
|
promise = NRF.requestDevice({ filters: filters, active: settings.active });
|
||||||
|
promise = promise.then((d) => {
|
||||||
|
E.showMessage("Found!!\n" + deviceName + "\nConnecting...");
|
||||||
|
log("Got device", d);
|
||||||
|
gatt = d.gatt;
|
||||||
|
log("Connecting...");
|
||||||
|
d.on('gattserverdisconnected', function () {
|
||||||
|
CORECONNECTED = false;
|
||||||
|
log("Disconnected! ");
|
||||||
|
gatt = null;
|
||||||
|
//setTimeout(() => cacheDevice(deviceName), 5000); // Retry in 5 seconds
|
||||||
|
});
|
||||||
|
return gatt.connect().then(function () {
|
||||||
|
log("Connected.");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
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");
|
||||||
|
E.showMessage("Found " + deviceName + "\nConnected!");
|
||||||
|
CORECONNECTED = true;
|
||||||
|
characteristicsToCache(characteristics);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function ConnectToDevice(d) {
|
||||||
|
E.showMessage("Connecting...");
|
||||||
|
let count = 0;
|
||||||
|
const successHandler = () => {
|
||||||
|
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).then(successHandler).catch(errorHandler);
|
||||||
|
} else {
|
||||||
|
E.showAlert("Error during caching", e).then(() => {
|
||||||
|
E.showMenu(buildMainMenu());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return cacheDevice(d).then(successHandler).catch(errorHandler);
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
function getPairedAntHRM() {
|
||||||
|
writeToControlPoint(0x04) // Get paired HRMs
|
||||||
|
.then(response => {
|
||||||
|
let totalHRMs = response[3]; // HRM count at index 3
|
||||||
|
log("📡 PAIRED ANT+:", totalHRMs);
|
||||||
|
let promises = [];
|
||||||
|
let hrmFound = [];
|
||||||
|
for (let i = 0; i < totalHRMs; i++) {
|
||||||
|
promises.push(
|
||||||
|
writeToControlPoint(0x05, [i]) // Get HRM ID from paired list
|
||||||
|
.then(hrmResponse => {
|
||||||
|
log("🔍 Response 0x05:", hrmResponse);
|
||||||
|
|
||||||
|
let byte1 = hrmResponse[3]; // LSB
|
||||||
|
let byte2 = hrmResponse[4]; // Middle Byte
|
||||||
|
let byte3 = hrmResponse[5]; // MSB
|
||||||
|
let txType = hrmResponse[5]; // Transmission Type
|
||||||
|
let hrmState = hrmResponse[6]; // Connection State
|
||||||
|
let pairedAntId = (byte1) | (byte2 << 8) | (byte3 << 16); // ✅ Corrected parsing
|
||||||
|
let stateText = ["Closed", "Searching", "Synchronized", "Reserved"][hrmState & 0x03];
|
||||||
|
log(`🔗 HRM ${i}: ANT ID = ${pairedAntId}, Tx-Type = ${txType}, State = ${stateText}`);
|
||||||
|
hrmFound.push({ index: i, antId: pairedAntId, txType: txType, stateText: stateText });
|
||||||
|
})
|
||||||
|
.catch(e => log(`❌ Error fetching HRM ${i} ID:`, e))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Promise.all(promises).then(() => hrmFound);
|
||||||
|
})
|
||||||
|
.then(allHRMs => {
|
||||||
|
log("Retrieved all paired HRMs:", allHRMs);
|
||||||
|
return // Modified start scanning command
|
||||||
|
})
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
function clearPairedHRM_ANT() {
|
||||||
|
return writeToControlPoint(0x01) // Send OpCode 0x01 to clear list
|
||||||
|
.then(response => {
|
||||||
|
let resultCode = response[2]; // Check the success flag
|
||||||
|
if (resultCode === 0x01) {
|
||||||
|
log("ANT+ HRM list cleared successfully.");
|
||||||
|
return Promise.resolve();
|
||||||
|
} else {
|
||||||
|
log("Failed to clear ANT+ HRM list. Error code:", resultCode);
|
||||||
|
return Promise.reject(new Error(`Error code: ${resultCode}`));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
log("Error clearing ANT+ HRM list:", error);
|
||||||
|
return Promise.reject(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function scanUntilSynchronized(maxRetries, delay) {
|
||||||
|
let attempts = 0;
|
||||||
|
function checkHRMState() {
|
||||||
|
if (attempts >= maxRetries) {
|
||||||
|
log("Max scan attempts reached. HRM did not synchronize.");
|
||||||
|
E.showAlert("Max scan attempts reached. HRM did not synchronize.").then(() => E.showMenu(HRM_MENU()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log(`Attempt ${attempts + 1}/${maxRetries}: Checking HRM state...`);
|
||||||
|
writeToControlPoint(0x05, [0]) // Check paired HRM state
|
||||||
|
.then(hrmResponse => {
|
||||||
|
log("Sent OpCode: 0x05, response: ", hrmResponse);
|
||||||
|
let byte1 = hrmResponse[3]; // LSB of ANT ID
|
||||||
|
let byte2 = hrmResponse[4]; // MSB of ANT ID
|
||||||
|
let txType = hrmResponse[5]; // Transmission Type
|
||||||
|
let hrmState = hrmResponse[6]; // HRM State
|
||||||
|
let retrievedAntId = (byte1) | (byte2 << 8) | (txType << 16);
|
||||||
|
let stateText = ["Closed", "Searching", "Synchronized", "Reserved"][hrmState & 0x03];
|
||||||
|
log(`HRM Status: ANT ID = ${retrievedAntId}, Tx-Type = ${txType}, State = ${stateText}`);
|
||||||
|
E.showAlert(`HRM Status\nANT ID = ${retrievedAntId}\nState = ${stateText}`).then(() => E.showMenu(HRM_MENU()));
|
||||||
|
if (stateText === "Synchronized") {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
log(`HRM ${retrievedAntId} is not yet synchronized. Scanning again...`);
|
||||||
|
// Start scan again
|
||||||
|
writeToControlPoint(0x0D)
|
||||||
|
.then(() => writeToControlPoint(0x0A, [0xFF]))
|
||||||
|
.then(() => {
|
||||||
|
attempts++;
|
||||||
|
setTimeout(checkHRMState, delay); // Wait and retry
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
log("Error restarting scan:", error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
log("Error checking HRM state:", error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
log("Starting scan to synchronize HRM...");
|
||||||
|
writeToControlPoint(0x0A, [0xFF]) // Start initial scan
|
||||||
|
.then(() => {
|
||||||
|
setTimeout(checkHRMState, delay); // Wait and check state
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
log("Error starting initial scan:", error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function scanHRM_ANT() {
|
||||||
|
E.showMenu();
|
||||||
|
E.showMessage("Scanning for 10 seconds"); // Increased scan time
|
||||||
|
writeToControlPoint(0x0A, [0xFF])
|
||||||
|
.then(response => {
|
||||||
|
log("Received Response for 0x0A:", response);
|
||||||
|
return new Promise(resolve => setTimeout(resolve, 10000)); // Extended scan time to 10 seconds
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
return writeToControlPoint(0x0B); // Get HRM count
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
let HRMCount = response[3];
|
||||||
|
log("HRM Count Response:", HRMCount);
|
||||||
|
let hrmFound = [];
|
||||||
|
let promises = [];
|
||||||
|
for (let i = 0; i < HRMCount; i++) {
|
||||||
|
promises.push(
|
||||||
|
writeToControlPoint(0x0C, [i]) // Get Scanned HRM IDs
|
||||||
|
.then(hrmResponse => {
|
||||||
|
log("Response 0x0C:", hrmResponse);
|
||||||
|
let byte1 = hrmResponse[3]; // LSB
|
||||||
|
let byte2 = hrmResponse[4]; // MSB
|
||||||
|
let txType = hrmResponse[5]; // Transmission Type
|
||||||
|
let scannedAntId = (byte1) | (byte2 << 8) | (txType << 16); //3 byte ANT+ ID
|
||||||
|
log(`HRM ${i} ID Response: ${scannedAntId}`);
|
||||||
|
hrmFound.push({ antId: scannedAntId });
|
||||||
|
})
|
||||||
|
.catch(e => log(`Error fetching HRM ${i} ID:`, e))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Promise.all(promises).then(() => {
|
||||||
|
if (hrmFound > 0) {
|
||||||
|
let submenu_scan = {
|
||||||
|
'< Back': function () { E.showMenu(buildMainMenu()); }
|
||||||
|
};
|
||||||
|
hrmFound.forEach((hrm) => {
|
||||||
|
let id = hrm.antId;
|
||||||
|
submenu_scan[id] = function () {
|
||||||
|
E.showPrompt("Connect to\n" + id + "?", { title: "ANT+ Pairing" }).then((r) => {
|
||||||
|
if (r) {
|
||||||
|
E.showMessage("Connecting...");
|
||||||
|
let byte1 = id & 0xFF; // LSB
|
||||||
|
let byte2 = (id >> 8) & 0xFF; // Middle byte
|
||||||
|
let byte3 = (id >> 16) & 0xFF; // Transmission Type
|
||||||
|
return clearPairedHRM_ANT(). //FIRST CLEAR ALL ANT+ HRM
|
||||||
|
then(() => { writeToControlPoint(0x02, [byte1, byte2, byte3]) }) // Pair the HRM
|
||||||
|
.then(() => {
|
||||||
|
log(`HRM ${id} added to paired list.`);
|
||||||
|
writeSettings("ANT_HRM", hrm);
|
||||||
|
E.showMenu(HRM_MENU());
|
||||||
|
})
|
||||||
|
.catch(e => log(`Error adding HRM ${id} to paired list:`, e));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
});
|
||||||
|
E.showMenu(submenu_scan);
|
||||||
|
} else {
|
||||||
|
E.showAlert("No ANT+ HRM found.").then(() => E.showMenu(HRM_MENU()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(e => log("ERROR:", e));
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildMainMenu() {
|
||||||
|
let mainmenu = {
|
||||||
|
'': { 'title': 'CORE Sensor' },
|
||||||
|
'< Back': back,
|
||||||
|
'Enable': {
|
||||||
|
value: !!settings.enabled,
|
||||||
|
onchange: v => {
|
||||||
|
writeSettings("enabled", v);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'Widget': {
|
||||||
|
value: !!settings.widget,
|
||||||
|
onchange: v => {
|
||||||
|
writeSettings("widget", 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);
|
||||||
|
if(gatt) gatt.disconnect();
|
||||||
|
}
|
||||||
|
E.showMenu(buildMainMenu());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
if(!CORECONNECTED){
|
||||||
|
let connect = "Connect " + (settings.btname || settings.btid);
|
||||||
|
mainmenu[connect] = function () {ConnectToDevice(settings.btname)};
|
||||||
|
}else{
|
||||||
|
mainmenu['HRM Settings'] = function () { E.showMenu(HRM_MENU()); };
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mainmenu['Scan for CORE'] = function () { ScanForCORESensor(); };
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function HRM_MENU() {
|
||||||
|
let menu = {
|
||||||
|
'': { 'title': 'CORE: HR' },
|
||||||
|
'< Back': function () { E.showMenu(buildMainMenu()); },
|
||||||
|
'Scan for ANT+': function () { scanHRM_ANT(); }
|
||||||
|
}
|
||||||
|
if (settings.btname) {
|
||||||
|
menu['ANT+ Status'] = function () { scanUntilSynchronized(10, 3000); },
|
||||||
|
menu['Clear ANT+'] = function () {
|
||||||
|
E.showPrompt("Clear ANT+ HRs?", { title: "CLear ANT+" }).then((r) => {
|
||||||
|
if (r) {
|
||||||
|
clearPairedHRM_ANT();
|
||||||
|
}
|
||||||
|
E.showMenu(HRM_MENU());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ScanForCORESensor() {
|
||||||
|
E.showMenu();
|
||||||
|
E.showMessage("Scanning for 5 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...");
|
||||||
|
let count = 0;
|
||||||
|
const successHandler = () => {
|
||||||
|
E.showPrompt("Success!", {
|
||||||
|
buttons: { "OK": true }
|
||||||
|
}).then(() => {
|
||||||
|
writeSettings("btid", d.id);
|
||||||
|
writeSettings("btname", d.name); //Seems to only like to connect by name
|
||||||
|
E.showMenu(HRM_MENU());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const errorHandler = (e) => {
|
||||||
|
count++;
|
||||||
|
log("ERROR", e);
|
||||||
|
if (count <= 10) {
|
||||||
|
E.showMessage("Error during caching\nRetry " + count + "/10", e);
|
||||||
|
return cacheDevice(d.name).then(successHandler).catch(errorHandler);
|
||||||
|
} else {
|
||||||
|
E.showAlert("Error during caching", e).then(() => {
|
||||||
|
E.showMenu(buildMainMenu());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return cacheDevice(d.name).then(successHandler).catch(errorHandler);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
E.showMenu(submenu_scan);
|
||||||
|
}, { timeout: 5000, active: true, filters: [{ services: ["00002100-5b1e-4347-b07c-97b514dae121"] }] });
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
E.showMenu();
|
||||||
|
E.showMenu(buildMainMenu());
|
||||||
|
}
|
||||||
|
init();
|
||||||
})
|
})
|
||||||
|
|
@ -1,56 +1,48 @@
|
||||||
// TODO Change to a generic multiple sensor widget?
|
|
||||||
|
|
||||||
(() => {
|
(() => {
|
||||||
var settings = {};
|
var settings = {};
|
||||||
var count = 0;
|
var CORESensorStatus = false;
|
||||||
var core = 0;
|
|
||||||
|
|
||||||
// draw your widget
|
// draw your widget
|
||||||
function draw() {
|
function draw() {
|
||||||
if (!settings.enabled)
|
if (!settings.widget)
|
||||||
return;
|
return;
|
||||||
g.reset();
|
g.reset();
|
||||||
g.setFont("6x8", 1).setFontAlign(0, 0);
|
g.setFont("6x8", 1).setFontAlign(0, 0);
|
||||||
g.setFontAlign(0, 0);
|
g.setFontAlign(0, 0);
|
||||||
g.clearRect(this.x, this.y, this.x + 23, this.y + 23);
|
g.clearRect(this.x, this.y, this.x + 23, this.y + 23);
|
||||||
|
|
||||||
if (count & 1) {
|
if (CORESensorStatus) {
|
||||||
g.setColor("#0f0"); // green
|
g.setColor("#0f0"); // green
|
||||||
} else {
|
} else {
|
||||||
g.setColor(g.theme.dark ? "#333" : "#CCC"); // off = grey
|
g.setColor(g.theme.dark ? "#333" : "#CCC"); // off = grey
|
||||||
}
|
}
|
||||||
|
|
||||||
g.drawImage(
|
g.drawImage(
|
||||||
atob("DAyBAAHh0js3EuDMA8A8AWBnDj9A8A=="),
|
atob("FBSCAAAAADwAAAPw/8AAP/PD8AP/wwDwD//PAPAP/APA8D/AA//wP8AA/8A/AAAAPP8AAAD8/wAAAPz/AAAA/D8AAAAAP8AAA/A/8AAP8A/8AD/wD///z8AD///PAAA///AAAAP/wAA="),
|
||||||
this.x + (24 - 12) / 2, this.y + 1);
|
this.x + (24 - 12) / 2, this.y + 1);
|
||||||
|
|
||||||
g.setColor(g.theme.fg);
|
|
||||||
g.drawString(parseInt(core)+"\n."+parseInt((core*100)%100), this.x + 24 / 2, this.y + 18);
|
|
||||||
|
|
||||||
g.setColor(-1);
|
g.setColor(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set a listener to 'blink'
|
|
||||||
function onTemp(temp) {
|
|
||||||
count = count + 1;
|
|
||||||
core = temp.core;
|
|
||||||
WIDGETS["coretemp"].draw();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Called by sensor app to update status
|
// Called by sensor app to update status
|
||||||
function reload() {
|
function reload() {
|
||||||
settings = require("Storage").readJSON("coretemp.json", 1) || {};
|
settings = require("Storage").readJSON("coretemp.json", 1) || {};
|
||||||
|
if (!settings.widget) {
|
||||||
Bangle.removeListener('CoreTemp', onTemp);
|
delete WIDGETS["coretemp"];
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (settings.enabled) {
|
if (settings.enabled) {
|
||||||
WIDGETS["coretemp"].width = 24;
|
WIDGETS["coretemp"].width = 24;
|
||||||
Bangle.on('CoreTemp', onTemp);
|
|
||||||
} else {
|
} else {
|
||||||
WIDGETS["coretemp"].width = 0;
|
WIDGETS["CORESensor"].width = 0;
|
||||||
count = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Bangle.hasOwnProperty("isCORESensorConnected")) {
|
||||||
|
setInterval(function () {
|
||||||
|
if (Bangle.isCORESensorConnected() != CORESensorStatus) {
|
||||||
|
CORESensorStatus = Bangle.isCORESensorConnected();
|
||||||
|
WIDGETS["coretemp"].draw();
|
||||||
|
}
|
||||||
|
}, 10000); //runs every 10 seconds
|
||||||
|
}
|
||||||
// add the widget
|
// add the widget
|
||||||
WIDGETS["coretemp"] = {
|
WIDGETS["coretemp"] = {
|
||||||
area: "tl",
|
area: "tl",
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "gpssetup",
|
"id": "gpssetup",
|
||||||
"name": "GPS Setup",
|
"name": "GPS Setup",
|
||||||
"shortName": "GPS Setup",
|
"shortName": "GPS Setup",
|
||||||
"version": "0.03",
|
"version": "0.04",
|
||||||
"description": "Configure the GPS power options and store them in the GPS nvram",
|
"description": "Configure the GPS power options and store them in the GPS nvram",
|
||||||
"icon": "gpssetup.png",
|
"icon": "gpssetup.png",
|
||||||
"tags": "gps,tools,outdoors",
|
"tags": "gps,tools,outdoors",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue