mirror of https://github.com/espruino/BangleApps
312 lines
9.1 KiB
JavaScript
312 lines
9.1 KiB
JavaScript
E.setFlags({pretokenise:1});
|
|
|
|
function log(msg) {
|
|
console.log("heart: " + msg + "; mem used: " + process.memory().usage / process.memory().blocksize);
|
|
return;
|
|
}
|
|
|
|
log("start");
|
|
|
|
Bangle.loadWidgets();
|
|
Bangle.drawWidgets();
|
|
|
|
var settings = require("Storage").readJSON("heart.json",1)||{};
|
|
|
|
function getFileNbr(n) {
|
|
return ".heart"+n.toString(36);
|
|
}
|
|
|
|
function updateSettings() {
|
|
require("Storage").write("heart.json", settings);
|
|
if (WIDGETS["heart"])
|
|
WIDGETS["heart"].reload();
|
|
return;
|
|
}
|
|
|
|
function showMainMenu() {
|
|
const mainMenu = {
|
|
'': { 'title': 'Heart Recorder' },
|
|
'RECORD': {
|
|
value: !!settings.isRecording,
|
|
onchange: v => {
|
|
settings.isRecording = v;
|
|
updateSettings();
|
|
}
|
|
},
|
|
'File Number': {
|
|
value: settings.fileNbr|0,
|
|
min: 0,
|
|
max: 35,
|
|
step: 1,
|
|
onchange: v => {
|
|
settings.isRecording = false;
|
|
settings.fileNbr = v;
|
|
updateSettings();
|
|
}
|
|
},
|
|
'View Records': ()=>{createRecordMenu(viewRecord.bind());},
|
|
'Graph Records': ()=>{createRecordMenu(graphRecord.bind());},
|
|
'< Back': ()=>{load();}
|
|
};
|
|
return E.showMenu(mainMenu);
|
|
}
|
|
|
|
// Date().as().str cannot be used as it always returns UTC time
|
|
function getDateString(timestamp) {
|
|
var date = new Date(timestamp);
|
|
var day = date.getDate() < 10 ? "0" + date.getDate().toString() : date.getDate().toString();
|
|
var month = date.getMonth() < 10 ? "0" + date.getMonth().toString() : date.getMonth().toString();
|
|
return day + "." + month + "." + date.getFullYear();
|
|
}
|
|
|
|
// Date().as().str cannot be used as it always returns UTC time
|
|
function getTimeString(timestamp) {
|
|
var date = new Date(timestamp);
|
|
var hour = date.getHours() < 10 ? '0' + date.getHours().toString() : date.getHours().toString();
|
|
var minute = date.getMinutes() < 10 ? '0' + date.getMinutes().toString() : date.getMinutes().toString();
|
|
return hour + ':' + minute;
|
|
}
|
|
|
|
function createRecordMenu(func) {
|
|
const menu = {
|
|
'': { 'title': 'Heart Records' }
|
|
};
|
|
var found = false;
|
|
for (var n=0;n<36;n++) {
|
|
var line = require("Storage").open(getFileNbr(n),"r").readLine();
|
|
if (line!==undefined) {
|
|
menu["#" + n + " " + getDateString(line.split(",")[0]*1000) + " " + getTimeString(line.split(",")[0]*1000)] = func.bind(null, n);
|
|
found = true;
|
|
}
|
|
}
|
|
if (!found)
|
|
menu["No Records Found"] = function(){};
|
|
menu['< Back'] = ()=>{showMainMenu();};
|
|
return E.showMenu(menu);
|
|
}
|
|
|
|
function viewRecord(n) {
|
|
E.showMenu({'': 'Heart Record '+n});
|
|
E.showMessage(
|
|
"Loading Data ...\n\nMay take a while,\nwill vibrate\nwhen done.",
|
|
'Heart Record '+n
|
|
);
|
|
const menu = {
|
|
'': { 'title': 'Heart Record '+n }
|
|
};
|
|
var heartTime;
|
|
var f = require("Storage").open(getFileNbr(n),"r");
|
|
var l = f.readLine();
|
|
// using arrays for memory optimization
|
|
var limits = Uint8Array(2);
|
|
// using arrays for memory optimization
|
|
var avg = Uint32Array(2);
|
|
// minimum
|
|
limits[0] = 2000;
|
|
// maximum
|
|
limits[1] = 0;
|
|
// count
|
|
avg[0] = 0;
|
|
// average sum
|
|
avg[1] = 0;
|
|
var count = 0;
|
|
var value = 0;
|
|
if (l!==undefined)
|
|
heartTime = new Date(l.split(",")[0]*1000);
|
|
log("parsing records");
|
|
while (l!==undefined) {
|
|
count++;
|
|
if (parseInt(l.split(',')[2]) >= 70) {
|
|
avg[0]++;
|
|
value = parseInt(l.split(',')[1]);
|
|
if (value < limits[0]) {
|
|
limits[0] = value;
|
|
} else if (value > limits[1]) {
|
|
limits[1] = value;
|
|
}
|
|
avg[1] += value;
|
|
}
|
|
l = f.readLine();
|
|
}
|
|
l = undefined;
|
|
value = undefined;
|
|
log("finished parsing");
|
|
if (heartTime)
|
|
menu[" "+heartTime.toString().substr(4,17)] = function(){};
|
|
menu[count + " records"] = function(){};
|
|
menu["Min: " + limits[0]] = function(){};
|
|
menu["Max: " + limits[1]] = function(){};
|
|
menu["Avg: " + Math.round(avg[1] / avg[0])] = function(){};
|
|
menu["Erase"] = function() {
|
|
E.showPrompt("Delete Record?").then(function(v) {
|
|
if (v) {
|
|
if (n == settings.fileNbr) {
|
|
settings.isRecording = false;
|
|
updateSettings();
|
|
}
|
|
require("Storage").open(getFileNbr(n),"r").erase();
|
|
E.showMenu();
|
|
createRecordMenu(viewRecord.bind());
|
|
} else
|
|
return viewRecord(n);
|
|
});
|
|
};
|
|
menu['< Back'] = ()=>{createRecordMenu(viewRecord.bind());};
|
|
Bangle.buzz(200, 0.3);
|
|
return E.showMenu(menu);
|
|
}
|
|
|
|
function stop() {
|
|
E.showMenu();
|
|
load();
|
|
}
|
|
|
|
function graphRecord(n) {
|
|
var headline = "Heart Record " + n;
|
|
E.showMenu({'': headline});
|
|
E.showMessage(
|
|
"Loading Data ...\n\nMay take a while,\nwill vibrate\nwhen done.",
|
|
headline
|
|
);
|
|
|
|
const MinMeasurement = 30;
|
|
const MaxMeasurement = 150;
|
|
const GraphXLabel = 35;
|
|
const GraphXZero = 40;
|
|
const GraphY100 = 60;
|
|
const GraphMarkerOffset = 5;
|
|
// calculate number of pixels based on display width
|
|
const MaxValueCount = g.getWidth() - GraphXZero - ( g.getWidth() - 220 ) - GraphMarkerOffset;
|
|
// calculate Y axis "0" pixel
|
|
const GraphYZero = g.getHeight() - g.setFont("Vector", 10).getFontHeight() - GraphMarkerOffset * 2;
|
|
// calculate X axis max drawable pixel
|
|
const GraphXMax = GraphXZero + MaxValueCount;
|
|
// calculate space between labels of scale
|
|
const LabelOffset = (GraphYZero - GraphY100) / (MaxMeasurement - MinMeasurement);
|
|
|
|
var lineCount = 0;
|
|
var startLine = 1;
|
|
var f = require("Storage").open(getFileNbr(n),"r");
|
|
var line = f.readLine();
|
|
|
|
log("Counting lines");
|
|
|
|
while (line !== undefined) {
|
|
lineCount++;
|
|
line = f.readLine();
|
|
}
|
|
|
|
log(`lineCount: ${lineCount}`);
|
|
if (lineCount > MaxValueCount)
|
|
startLine = lineCount - MaxValueCount;
|
|
f = undefined;
|
|
line = undefined;
|
|
lineCount = undefined;
|
|
log(`startLine: ${startLine}`);
|
|
|
|
f = require("Storage").open(getFileNbr(n),"r");
|
|
line = f.readLine();
|
|
|
|
var times = Uint32Array(2);
|
|
var tempCount = 0;
|
|
var positionX = GraphXZero;
|
|
var positionY = GraphYZero;
|
|
var measure;
|
|
|
|
while (line !== undefined) {
|
|
currentLine = line;
|
|
line = f.readLine();
|
|
tempCount++;
|
|
if (tempCount == startLine) {
|
|
// generating rgaph in loop when reaching startLine to keep loading
|
|
// message on screen until graph can be drawn
|
|
g.reset().clearRect(0,24,g.getWidth(),g.getHeight()).
|
|
// Home for Btn2
|
|
setColor(g.theme.fg).
|
|
drawLine(220, 118, 227, 110).
|
|
drawLine(227, 110, 234, 118).
|
|
drawPoly([222,117,222,125,232,125,232,117], false).
|
|
drawRect(226,120,229,125).
|
|
|
|
// headline
|
|
setFontAlign(0, -1, 0).
|
|
setFont("6x8", 2).
|
|
drawString(headline, g.getWidth()/2 - headline.length/2, GraphY100 - g.getFontHeight() - GraphMarkerOffset).
|
|
|
|
// Chart
|
|
setColor(1, 1, 0).
|
|
// horizontal bottom line
|
|
drawLine(GraphXZero, GraphYZero + GraphMarkerOffset, GraphXZero, GraphY100).
|
|
// vertical left line
|
|
drawLine(GraphXZero - GraphMarkerOffset, GraphYZero, GraphXMax + GraphMarkerOffset, GraphYZero).
|
|
// scale indicator line for 100%
|
|
drawLine(GraphXZero - GraphMarkerOffset, GraphY100, GraphXZero, GraphY100).
|
|
// scale indicator line for 50%
|
|
drawLine(GraphXZero - GraphMarkerOffset, GraphY100 + (GraphYZero - GraphY100)/2, GraphXZero, GraphY100 + (GraphYZero - GraphY100)/2).
|
|
// background line for 50%
|
|
setColor(g.theme.fg).
|
|
drawLine(GraphXZero + 1, GraphY100 + (GraphYZero - GraphY100)/2, GraphXMax, GraphY100 + (GraphYZero - GraphY100)/2).
|
|
setFontAlign(1, -1, 0).
|
|
setFont("Vector", 10);
|
|
|
|
// scale text
|
|
for (var i = MaxMeasurement; i >= MinMeasurement; i-=10) {
|
|
g.drawString(i, GraphXLabel, GraphY100 + LabelOffset * ( MaxMeasurement - i ) - GraphMarkerOffset);
|
|
}
|
|
|
|
log("Finished drawing chart");
|
|
} else if (tempCount > startLine) {
|
|
positionX++;
|
|
if (parseInt(currentLine.split(",")[2]) >= 70) {
|
|
g.setColor(1, 0.3, 0.3);
|
|
oldPositionY = positionY;
|
|
measure = parseInt(currentLine.split(",")[1]);
|
|
positionY = GraphYZero - measure + MinMeasurement;
|
|
if (positionY > GraphYZero) {
|
|
positionY = GraphYZero;
|
|
}
|
|
if (positionY < GraphY100) {
|
|
positionY = GraphY100;
|
|
}
|
|
|
|
if (times[0] === 0) {
|
|
times[0] = parseInt(currentLine.split(",")[0]);
|
|
}
|
|
if (tempCount == startLine + 1) {
|
|
g.setPixel(positionX, positionY);
|
|
} else {
|
|
g.drawLine(positionX - 1, oldPositionY, positionX, positionY);
|
|
times[1] = parseInt(currentLine.split(",")[0]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
g.setColor(1, 1, 0).setFont("Vector", 10);
|
|
log('startTime: ' + times[0]);
|
|
log('endTime: ' + times[1]);
|
|
|
|
if (times[0] !== 0) {
|
|
g.setFontAlign(-1, -1, 0).
|
|
drawString(getTimeString(times[0]*1000), 15, GraphYZero + 12);
|
|
}
|
|
|
|
if (times[1] !== 0) {
|
|
var dateStr = getDateString(times[1]*1000);
|
|
g.setFontAlign(-1, -1, 0).
|
|
drawString(dateStr, GraphXMax/2 - dateStr.length/2 - GraphMarkerOffset, GraphYZero + 12).
|
|
setFontAlign(1, -1, 0).
|
|
drawString(getTimeString(times[1]*1000), GraphXMax, GraphYZero + 12);
|
|
}
|
|
|
|
log("Finished rendering data");
|
|
Bangle.buzz(200, 0.3);
|
|
g.flip();
|
|
setWatch(stop, (global.BTN2!==undefined)?BTN2:BTN1, {edge:"falling", debounce:50, repeat:false});
|
|
return;
|
|
}
|
|
|
|
showMainMenu();
|
|
|
|
// vim: et ts=2 sw=2
|