diff --git a/apps/sleeplog/ChangeLog b/apps/sleeplog/ChangeLog index 890d54dce..1adc4a4dc 100644 --- a/apps/sleeplog/ChangeLog +++ b/apps/sleeplog/ChangeLog @@ -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 \ No newline at end of file diff --git a/apps/sleeplog/README.md b/apps/sleeplog/README.md index 9f0e6a44f..13f95070c 100644 --- a/apps/sleeplog/README.md +++ b/apps/sleeplog/README.md @@ -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)). diff --git a/apps/sleeplog/app.js b/apps/sleeplog/app.js index 097897d35..c75bf1e4c 100644 --- a/apps/sleeplog/app.js +++ b/apps/sleeplog/app.js @@ -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]; diff --git a/apps/sleeplog/boot.js b/apps/sleeplog/boot.js index a86c3f554..1b303f282 100644 --- a/apps/sleeplog/boot.js +++ b/apps/sleeplog/boot.js @@ -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); diff --git a/apps/sleeplog/metadata.json b/apps/sleeplog/metadata.json index 909aee24e..8eb9c5e5c 100644 --- a/apps/sleeplog/metadata.json +++ b/apps/sleeplog/metadata.json @@ -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", diff --git a/apps/sleeplog/settings.js b/apps/sleeplog/settings.js index 597c1f2d6..5544c302e 100644 --- a/apps/sleeplog/settings.js +++ b/apps/sleeplog/settings.js @@ -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