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 {
|
2024-03-04 20:34:50 +00:00
|
|
|
/*var cmd =*/ dv.getUint8(4); // cmd
|
2021-10-21 13:49:51 +00:00
|
|
|
//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();
|