[sleeplog] Add "View log" in debug + send via BT
Add "View log" function for debugging log Send data for gadgetbridge via BTmaster
parent
1a89f963ea
commit
76629a9d78
|
|
@ -5,3 +5,5 @@
|
|||
0.05: Fix LOW_MEMORY,MEMORY error on to big log size
|
||||
0.06: Reduced log size further to 750 entries
|
||||
0.10: Complete rework off this app!
|
||||
- beta01: Add interface.html to view your saved log data
|
||||
- beta02: Add "View log" function for debugging log + send data for gadgetbridge
|
||||
|
|
@ -77,9 +77,8 @@ Temporarily removed logfiles from metadata.json to prevent removal on un-/reinst
|
|||
#### To do list
|
||||
* Edit/complete README.md.
|
||||
* Update screenshots.
|
||||
* Add display debugging log functionality.
|
||||
* Add custom interface.html to view, down- and upload logged data via App Loader.
|
||||
* Send the logged information to Gadgetbridge.
|
||||
* Add more functionallities to interface.html.
|
||||
* Enable recieving data on the Gadgetbridge side + testing.
|
||||
|
||||
#### Requests, Bugs and Feedback
|
||||
Please leave requests and bug reports by raising an issue at [github.com/storm64/BangleApps](https://github.com/storm64/BangleApps) (or send me a [mail](mailto:banglejs@storm64.de)).
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ function drawGraph(log, date, pos) {
|
|||
|
||||
// set height and color values:
|
||||
// status: unknown, not worn, awake, light sleep, deep sleep, consecutive
|
||||
// color: black, red, green, cyan, violet, blue
|
||||
// color: black, red, green, cyan, blue, violet
|
||||
var heights = [0, 0.4, 0.6, 0.8, 1];
|
||||
var colors = [0, 63488, 2016, 2047, 31, 32799];
|
||||
|
||||
|
|
|
|||
|
|
@ -270,6 +270,14 @@ if (sleeplog.conf.enabled) {
|
|||
// reset saveUpToDate status
|
||||
delete this.info.saveUpToDate;
|
||||
}
|
||||
|
||||
// send status to gadgetbridge
|
||||
var gb_kinds = "unknown,not_worn,activity,light_sleep,deep_sleep";
|
||||
Bluetooth.println(JSON.stringify({
|
||||
t: "act",
|
||||
act: gb_kinds.split(",")[data.status],
|
||||
ts: data.timestamp
|
||||
}));
|
||||
|
||||
// call debugging function if set
|
||||
if (this.debug) require("sleeplog").debug(data);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"id":"sleeplog",
|
||||
"name":"Sleep Log",
|
||||
"shortName": "SleepLog",
|
||||
"version": "0.10beta01",
|
||||
"version": "0.10beta02",
|
||||
"description": "Log and view your sleeping habits. This app is using the built in movement calculation.",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
|
|
|
|||
|
|
@ -26,6 +26,266 @@
|
|||
require("Storage").writeJSON(filename, settings);
|
||||
}
|
||||
|
||||
// plot a debug file
|
||||
function plotDebug(filename) {
|
||||
// handle swipe events
|
||||
function swipeHandler(x, y) {
|
||||
if (x) {
|
||||
start -= x;
|
||||
if (start < 0 || maxStart && start > maxStart) {
|
||||
start = start < 0 ? 0 : maxStart;
|
||||
} else {
|
||||
drawGraph();
|
||||
}
|
||||
} else {
|
||||
minMove += y * 10;
|
||||
if (minMove < 0 || minMove > 300) {
|
||||
minMove = minMove < 0 ? 0 : 300;
|
||||
} else {
|
||||
drawGraph();
|
||||
}
|
||||
}
|
||||
}
|
||||
// handle touch events
|
||||
function touchHandler() {
|
||||
invert = !invert;
|
||||
drawGraph();
|
||||
}
|
||||
|
||||
// read required entries
|
||||
function readEntries(count) {
|
||||
// extract usabble data from line
|
||||
function extract(line) {
|
||||
if (!line) return;
|
||||
line = line.trim().split(",");
|
||||
return [Math.round((parseFloat(line[0]) - 25569) * 144), parseInt(line[1])];
|
||||
}
|
||||
|
||||
// open debug file
|
||||
var file = require("Storage").open(filename, "r");
|
||||
// skip title
|
||||
file.readLine();
|
||||
// skip past entries
|
||||
for (var i = 0; i < start * count; i++) { file.readLine(); }
|
||||
// define data with first entry
|
||||
var data = [extract(file.readLine())];
|
||||
// get start time in 10min steps
|
||||
var start10min = data[0][0];
|
||||
// read first required entry
|
||||
var line = extract(file.readLine());
|
||||
|
||||
// read next count entries from file
|
||||
while (data.length < count) {
|
||||
// check if line is immediately after the last entry
|
||||
if (line[0] === start10min + data.length) {
|
||||
// add line to data
|
||||
data.push(line);
|
||||
// read new line
|
||||
line = extract(file.readLine());
|
||||
// stop if no more data available
|
||||
if (!line) break;
|
||||
} else {
|
||||
// add line with unknown movement
|
||||
data.push([start10min + data.length, 0]);
|
||||
}
|
||||
}
|
||||
|
||||
// free ram
|
||||
file = undefined
|
||||
// set this start as max, if less entries than expected
|
||||
if (data.length < count) maxStart = start;
|
||||
return data;
|
||||
}
|
||||
|
||||
// draw graph at starting point
|
||||
function drawGraph() {
|
||||
// set correct or inverted drawing
|
||||
function rect(fill, x0, y0, x1, y1) {
|
||||
if (fill ^ invert) {
|
||||
g.fillRect(x0, y0, x1, y1);
|
||||
} else {
|
||||
g.clearRect(x0, y0, x1, y1);
|
||||
}
|
||||
}
|
||||
|
||||
// set witdh
|
||||
var width = g.getWidth();
|
||||
// calculate entries to display (+ set width zero based)
|
||||
var count = (width--) / 4;
|
||||
// read required entries
|
||||
var data = readEntries(count);
|
||||
|
||||
// clear app area
|
||||
g.reset().clearRect(0, width - 13, width, width);
|
||||
rect(false, 0, 24, width, width - 14);
|
||||
// draw x axis
|
||||
g.drawLine(0, width - 13, width, width - 13);
|
||||
// draw x label
|
||||
data.forEach((e, i) => {
|
||||
var startTime = new Date(e[0] * 6E5);
|
||||
if (startTime.getMinutes() === 0) {
|
||||
g.fillRect(4 * i, width - 12, 4 * i, width - 9);
|
||||
g.setFontAlign(-1, -1).setFont("6x8")
|
||||
.drawString(startTime.getHours(), 4 * i + 1, width - 8);
|
||||
} else if (startTime.getMinutes() === 30) {
|
||||
g.fillRect(4 * i, width - 12, 4 * i, width - 11);
|
||||
}
|
||||
});
|
||||
|
||||
// calculate max height
|
||||
var height = width - 38;
|
||||
// cycle through entries
|
||||
data.forEach((e, i) => {
|
||||
// check if movement available
|
||||
if (e[1]) {
|
||||
// set color depending on recognised status
|
||||
var color = e[1] < deepTh ? 31 : e[1] < lightTh ? 2047 : 2016;
|
||||
// correct according to min movement
|
||||
e[1] -= minMove;
|
||||
// keep movement in bounderies
|
||||
e[1] = e[1] < 0 ? 0 : e[1] > height ? height : e[1];
|
||||
// draw line and rectangle
|
||||
g.reset();
|
||||
rect(true, 4 * i, width - 14, 4 * i, width - 14 - e[1]);
|
||||
g.setColor(color).fillRect(4 * i + 1, width - 14, 4 * i + 3, width - 14 - e[1]);
|
||||
} else {
|
||||
// draw error in red
|
||||
g.setColor(63488).fillRect(4 * i, width - 14, 4 * i, width - 14 - height);
|
||||
}
|
||||
});
|
||||
// draw threshold lines
|
||||
[deepTh, lightTh].forEach(th => {
|
||||
th -= minMove;
|
||||
if (th > 0 && th < height) {
|
||||
// draw line
|
||||
g.reset();
|
||||
rect(true, 0, width - 14 - th, width, width - 14 - th);
|
||||
// draw value above or below line
|
||||
var yAlign = th < height / 2 ? -1 : 1;
|
||||
if (invert) g.setColor(1);
|
||||
g.setFontAlign(1, yAlign).setFont("6x8")
|
||||
.drawString(th + minMove, width - 2, width - 13 - th + 10 * yAlign);
|
||||
}
|
||||
});
|
||||
|
||||
// free ram
|
||||
data = undefined;
|
||||
}
|
||||
|
||||
// get thresholds
|
||||
var deepTh = global.sleeplog ? sleeplog.conf.deepTh : defaults.deepTh;
|
||||
var lightTh = global.sleeplog ? sleeplog.conf.lightTh : defaults.lightTh;
|
||||
// set lowest movement displayed
|
||||
var minMove = deepTh - 20;
|
||||
// set start point
|
||||
var start = 0;
|
||||
// define max start point value
|
||||
var maxStart = 0;
|
||||
// define inverted color status
|
||||
var invert = false;
|
||||
|
||||
// setup UI
|
||||
Bangle.setUI({
|
||||
mode: "custom",
|
||||
back: selectDebug,
|
||||
touch: touchHandler,
|
||||
swipe: swipeHandler
|
||||
});
|
||||
|
||||
// first draw
|
||||
drawGraph(start);
|
||||
}
|
||||
|
||||
// select a debug logfile
|
||||
function selectDebug() {
|
||||
// load debug files
|
||||
var files = require("Storage").list(/^sleeplog_\d\d\d\d\d\d\.csv$/, {sf:true});
|
||||
|
||||
// check if no files found
|
||||
if (!files.length) {
|
||||
// show prompt
|
||||
E.showPrompt( /*LANG*/"No debug files found.", {
|
||||
title: /*LANG*/"Debug log",
|
||||
buttons: {
|
||||
/*LANG*/"Back": 0
|
||||
}
|
||||
}).then(showDebug);
|
||||
} else {
|
||||
// prepare scroller
|
||||
const H = 40;
|
||||
var menuIcon = "\0\f\f\x81\0\xFF\xFF\xFF\0\0\0\0\x0F\xFF\xFF\xF0\0\0\0\0\xFF\xFF\xFF";
|
||||
// show scroller
|
||||
E.showScroller({
|
||||
h: H, c: files.length,
|
||||
back: showDebug,
|
||||
scrollMin : -24, scroll : -24, // title is 24px, rendered at -1
|
||||
draw : (idx, r) => {
|
||||
if (idx < 0) {
|
||||
return g.setFont("12x20").setFontAlign(-1,0).drawString(menuIcon + " Select file", r.x + 12, r.y + H - 12);
|
||||
} else {
|
||||
g.setColor(g.theme.bg2).fillRect({x: r.x + 4, y: r.y + 2, w: r.w - 8, h: r.h - 4, r: 5});
|
||||
var name = new Date(parseInt(files[idx].match(/\d\d\d\d\d\d/)[0]) * 36E5);
|
||||
name = name.toString().slice(0, -12).split(" ").filter((e, i) => i !== 3).join(" ");
|
||||
g.setColor(g.theme.fg2).setFont("12x20").setFontAlign(-1, 0).drawString(name, r.x + 12, r.y + H / 2);
|
||||
}
|
||||
},
|
||||
select: (idx) => plotDebug(files[idx])
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// show menu or promt to change debugging
|
||||
function showDebug() {
|
||||
// check if sleeplog is available
|
||||
if (global.sleeplog) {
|
||||
// get debug status, file and duration
|
||||
var enabled = !!sleeplog.debug;
|
||||
var file = typeof sleeplog.debug === "object";
|
||||
var duration = 0;
|
||||
// setup debugging menu
|
||||
var debugMenu = {
|
||||
"": {
|
||||
title: /*LANG*/"Debugging"
|
||||
},
|
||||
/*LANG*/"< Back": () => {
|
||||
// check if some value has changed
|
||||
if (enabled !== !!sleeplog.debug || file !== (typeof sleeplog.debug === "object") || duration)
|
||||
require("sleeplog").setDebug(enabled, file ? duration || 12 : undefined);
|
||||
// redraw main menu
|
||||
showMain(7);
|
||||
},
|
||||
/*LANG*/"View log": () => selectDebug(),
|
||||
/*LANG*/"Enable": {
|
||||
value: enabled,
|
||||
onchange: v => enabled = v
|
||||
},
|
||||
/*LANG*/"write File": {
|
||||
value: file,
|
||||
onchange: v => file = v
|
||||
},
|
||||
/*LANG*/"Duration": {
|
||||
value: file ? (sleeplog.debug.writeUntil - Date.now()) / 36E5 | 0 : 12,
|
||||
min: 1,
|
||||
max: 96,
|
||||
wrap: true,
|
||||
format: v => v + /*LANG*/ "h",
|
||||
onchange: v => duration = v
|
||||
},
|
||||
/*LANG*/"Cancel": () => showMain(7),
|
||||
};
|
||||
// show menu
|
||||
var menu = E.showMenu(debugMenu);
|
||||
} else {
|
||||
// show error prompt
|
||||
E.showPrompt("Sleeplog" + /*LANG*/"not enabled!", {
|
||||
title: /*LANG*/"Debugging",
|
||||
buttons: {
|
||||
/*LANG*/"Back": 7
|
||||
}
|
||||
}).then(showMain);
|
||||
}
|
||||
}
|
||||
|
||||
// show menu to change thresholds
|
||||
function showThresholds() {
|
||||
// setup logging menu
|
||||
|
|
@ -109,66 +369,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
// show menu or promt to change debugging
|
||||
function showDebug() {
|
||||
// check if sleeplog is available
|
||||
if (global.sleeplog) {
|
||||
// get debug status, file and duration
|
||||
var enabled = !!sleeplog.debug;
|
||||
var file = typeof sleeplog.debug === "object";
|
||||
var duration = 0;
|
||||
// setup debugging menu
|
||||
var debugMenu = {
|
||||
"": {
|
||||
title: /*LANG*/"Debugging"
|
||||
},
|
||||
/*LANG*/"< Back": () => {
|
||||
// check if some value has changed
|
||||
if (enabled !== !!sleeplog.debug || file !== (typeof sleeplog.debug === "object") || duration)
|
||||
require("sleeplog").setDebug(enabled, file ? duration || 12 : undefined);
|
||||
// redraw main menu
|
||||
showMain(7);
|
||||
},
|
||||
/*LANG*/"Display log": () => {
|
||||
// choose log...
|
||||
E.showPrompt( /*LANG*/"Function\nunder\nconstruction.", {
|
||||
title: /*LANG*/"Debug log",
|
||||
buttons: {
|
||||
/*LANG*/"Back": 0
|
||||
}
|
||||
}).then(() => menu = E.showMenu(debugMenu));
|
||||
},
|
||||
/*LANG*/"Enable": {
|
||||
value: enabled,
|
||||
onchange: v => enabled = v
|
||||
},
|
||||
/*LANG*/"write File": {
|
||||
value: file,
|
||||
onchange: v => file = v
|
||||
},
|
||||
/*LANG*/"Duration": {
|
||||
value: file ? (sleeplog.debug.writeUntil - Date.now()) / 36E5 | 0 : 12,
|
||||
min: 1,
|
||||
max: 96,
|
||||
wrap: true,
|
||||
format: v => v + /*LANG*/ "h",
|
||||
onchange: v => duration = v
|
||||
},
|
||||
/*LANG*/"Cancel": () => showMain(7),
|
||||
};
|
||||
// show menu
|
||||
var menu = E.showMenu(debugMenu);
|
||||
} else {
|
||||
// show error prompt
|
||||
E.showPrompt("Sleeplog" + /*LANG*/"not enabled!", {
|
||||
title: /*LANG*/"Debugging",
|
||||
buttons: {
|
||||
/*LANG*/"Back": 7
|
||||
}
|
||||
}).then(showMain);
|
||||
}
|
||||
}
|
||||
|
||||
// show main menu
|
||||
function showMain(selected) {
|
||||
// set debug image
|
||||
|
|
|
|||
Loading…
Reference in New Issue