BangleApps/apps/vernierrespirate/app.js

257 lines
7.2 KiB
JavaScript
Raw Normal View History

2021-10-21 13:49:51 +00:00
// get settings
var settings = require("Storage").readJSON("vernierrespirate.json",1)||{};
settings.vibrateBPM = settings.vibrateBPM||27;
// settings.vibrate; // undefined / "calculated" / "vernier"
function saveSettings() {
require("Storage").writeJSON("vernierrespirate.json", settings);
}
g.clear();
var graphHeight = g.getHeight()-100;
var last = {
time : Date.now(),
x : 0,
y : 24,
};
var avrValue;
var aboveAvr = false;
var lastBreath;
var lastBreaths = [];
var vibrateInterval;
function onMsg(txt) {
print(txt);
E.showMessage(txt);
}
function setVibrate(isOn) {
var wasOn = vibrateInterval!==undefined;
if (isOn == wasOn) return;
if (isOn) {
vibrateInterval = setInterval(function() {
Bangle.buzz();
}, 1000);
} else {
clearInterval(vibrateInterval);
vibrateInterval = undefined;
}
}
function onBreath() {
var t = Date.now();
if (lastBreath!==undefined) {
// time between breaths
var value = 60000 / (t-lastBreath);
// average of last 3
while (lastBreaths.length>=3) lastBreaths.shift(); // keep length small
lastBreaths.push(value);
value = E.sum(lastBreaths) / lastBreaths.length;
// draw value
g.reset();
g.clearRect(0,g.getHeight()-100,g.getWidth(),g.getHeight()-50);
g.setFont("6x8").setFontAlign(0,0);
g.drawString("Calculated measurement", g.getWidth()/2, g.getHeight()-95);
g.setFont("Vector",40).setFontAlign(0,0);
g.drawString(value.toFixed(2), g.getWidth()/2, g.getHeight()-70);
// set vibration IF we're doing it from our calculations
if (settings.vibrate == "calculated")
setVibrate(value > settings.vibrateBPM);
}
lastBreath = t;
}
function onData(n, value) {
g.reset();
if (n==2) {
function scale(v) {
return Math.max(graphHeight - (1+v*4),24);
}
if (avrValue==undefined) avrValue=value;
avrValue = avrValue*0.95 + value*0.05;
if (avrValue < 1) avrValue = 1;
if (value > avrValue) {
if (!aboveAvr) onBreath();
aboveAvr = true;
} else aboveAvr = false;
var t = Date.now();
var x = Math.round((t - last.time) / 100) // 10 per second
if (last.x>=g.getWidth()) {
x = 0;
last.x = 0;
last.time = t;
g.clearRect(0,24,g.getWidth(),graphHeight);
}
var y = scale(value);
g.setPixel(x, scale(avrValue), "#f00");
g.drawLine(last.x, last.y, x, y);
last.x = x;
last.y = y;
}
if (n==4) {
g.clearRect(0,g.getHeight()-50,g.getWidth(),g.getHeight());
g.setFont("6x8").setFontAlign(0,0);
g.drawString("GoDirect measurement", g.getWidth()/2, g.getHeight()-45);
g.setFont("Vector",40).setFontAlign(0,0);
g.drawString(value.toFixed(2), g.getWidth()/2, g.getHeight()-20);
// set vibration IF we're doing it from our calculations
if (settings.vibrate == "vernier")
setVibrate(value > settings.vibrateBPM);
}
Bangle.setLCDPower(1); // ensure LCD is on
}
function connect() {
var gatt, service, rx, tx;
var rollingCounter = 0xFF;
// any button to exit
Bangle.setUI("updown", function() {
setVibrate(false);
Bangle.buzz();
try {
if (gatt) gatt.disconnect();
} catch (e) {
}
setTimeout(mainMenu, 1000);
});
function sendCommand(subCommand) {
const command = new Uint8Array(4 + subCommand.length);
command.set(new Uint8Array(subCommand), 4);
// Populate the packet header bytes
command[0] = 0x58; // header
command[1] = command.length;
command[2] = --rollingCounter;
command[3] = E.sum(command) & 0xFF; // checksum
return tx.writeValue(command);
}
function firstSetBit(v) {
return v & -v;
}
function handleResponse(dv) {
//print(dv.buffer);
var resType = dv.getUint8(0);
if (resType==0x20) {
// [32, 25, 207, 216, 6, 6, 0, 2, 252, 128, 138, 7, 191, 0, 0, 192, 127, 128, 49, 8, 191, 0, 0, 192, 127])
// 6 = data type = real
// 6,0 = bit mask for sensors
// 2 = value count
if (dv.getUint8(4)!=6) return; //throw "Not float32 data";
var sensorIds = dv.getUint16(5, true);
// var count = dv.getUint8(7); doesn't seem right
var offs = 9;
while (sensorIds) {
var value = dv.getFloat32(offs, true);
var s = firstSetBit(sensorIds);
if (isFinite(value)) onData(s,value);
//else print(s,value);
sensorIds &= ~s;
offs += 4;
}
} else {
var cmd = dv.getUint8(4); // cmd
//print("CMD",dv.buffer);
}
}
onMsg("Searching...");
NRF.requestDevice({ filters: [{ namePrefix: 'GDX-RB' }] }).then(function(device) {
device.on("gattserverdisconnected", function() {
onMsg("Device disconnected");
});
onMsg("Found. Connecting...");
return device.gatt.connect({minInterval:20, maxInterval:20});
}).then(function(g) {
gatt = g;
return gatt.getPrimaryService("d91714ef-28b9-4f91-ba16-f0d9a604f112");
}).then(function(s) {
service = s;
return service.getCharacteristic("f4bf14a6-c7d5-4b6d-8aa8-df1a7c83adcb");
}).then(function(c) {
tx = c;
return service.getCharacteristic("b41e6675-a329-40e0-aa01-44d2f444babe");
}).then(function(c) {
rx = c;
rx.on('characteristicvaluechanged', function(event) {
//print("EVT",event.target.value.buffer);
handleResponse(event.target.value);
});
return rx.startNotifications();
}).then(function() {
onMsg("Init");
sendCommand([ // init
0x1a, 0xa5, 0x4a, 0x06,
0x49, 0x07, 0x48, 0x08,
0x47, 0x09, 0x46, 0x0a,
0x45, 0x0b, 0x44, 0x0c,
0x43, 0x0d, 0x42, 0x0e,
0x41,
]);
/*setTimeout(function() {
print("Set measurement period");
var us = 100000; // period in us
sendCommand([0x1b, 0xff, 0x00,
us & 255,
(us >> 8) & 255,
(us >> 16) & 255,
(us >> 24) & 255,
0x00,
0x00,
0x00,
0x00]);
}, 100);*/
/* setTimeout(function() {
print("Get sensor info");
sendCommand([0x51, 0]); // get sensor IDs
// returns [152, 10, 1, 39, 81, 253, 54, 0, 0, 0]
// 54 is the bit mask of available channels
//sendCommand([106, 16]); // get sensor info
}, 2000);*/
setTimeout(function() {
onMsg("Start measurements");
//https://github.com/VernierST/godirect-js/blob/main/src/Device.js#L588
var channels = 6; // data channels 4 and 2
sendCommand([ // start measurements
0x18, 0xff, 0x01, channels,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00
]);
}, 500);
}).catch(function() {
onMsg("Connect Fail");
});
}
Bangle.loadWidgets();
Bangle.drawWidgets();
function mainMenu() {
var vibText = ["No","Calculated","Vernier"];
var vibValue = ["","calculated","vernier"];
E.showMenu({"":{title:"Respiration Belt"},
"< Back" : () => { saveSettings(); load(); },
"Connect" : () => { saveSettings(); E.showMenu(); connect(); },
"Vib" : {
value : Math.max(vibValue.indexOf(settings.vibrate),0),
format : v => vibText[v],
min:0,max:2,
onchange : v => { settings.vibrate=vibValue[v]; }
},
"BPM" : {
value : settings.vibrateBPM,
min:10,max:50,
onchange : v => { settings.vibrateBPM=v; }
}
});
}
mainMenu();