From c0c18178fd1bc450d3e3ad9cfa34bb5bdeed3a64 Mon Sep 17 00:00:00 2001 From: Bryan Nielsen Date: Sun, 4 Mar 2018 14:38:35 -0700 Subject: [PATCH 01/35] Converted to use node-pi-buttons module and made pi-buttons a separate service. --- config/buttons.json | 10 +- lib/hid-menu/hid-menu.js | 29 +-- lib/pi-buttons/a.out | Bin 14132 -> 0 bytes lib/pi-buttons/build.sh | 5 - lib/pi-buttons/buttons.c | 393 --------------------------------------- lib/pi-buttons/buttons.h | 108 ----------- lib/pi-buttons/index.js | 28 --- lib/pi-buttons/setup.sh | 15 -- openaps-menu.sh | 3 +- package.json | 4 +- 10 files changed, 23 insertions(+), 572 deletions(-) delete mode 100755 lib/pi-buttons/a.out delete mode 100755 lib/pi-buttons/build.sh delete mode 100644 lib/pi-buttons/buttons.c delete mode 100644 lib/pi-buttons/buttons.h delete mode 100644 lib/pi-buttons/index.js delete mode 100755 lib/pi-buttons/setup.sh diff --git a/config/buttons.json b/config/buttons.json index c7b9e38..6f1a4f1 100644 --- a/config/buttons.json +++ b/config/buttons.json @@ -1,10 +1,10 @@ { - "pins": { - "buttonUp": 11, - "buttonDown": 13 + "gpios": { + "buttonUp": 17, + "buttonDown": 27 }, "options": { - "pressed": 200, - "clicked": 400 + "socketPath": "/var/run/pi-buttons.sock", + "reconnectTimeout": 3000 } } diff --git a/lib/hid-menu/hid-menu.js b/lib/hid-menu/hid-menu.js index cfb427a..fec9b85 100644 --- a/lib/hid-menu/hid-menu.js +++ b/lib/hid-menu/hid-menu.js @@ -8,38 +8,36 @@ const Menube = require('menube'); function createHIDMenu(configButtons, configMenus) { - if (!configButtons.pins || !configButtons.pins.buttonUp || !configButtons.pins.buttonDown) { + if (!configButtons.gpios || !configButtons.gpios.buttonUp || !configButtons.gpios.buttonDown) { throw new Error('Incomplete pins definition in configuration.'); } - var pins = configButtons.pins; + var gpios = configButtons.gpios; var buttonOptions = configButtons.options || {}; var onChange = configMenus.onChange; var menu = Menube(configMenus.menuFile, configMenus.menuSettings); var displayDirty = false; - // var buttons = require('rpi-gpio-buttons')([pins.buttonUp, pins.buttonDown], buttonOptions); - var piButtons = require('../pi-buttons'); + var piButtons = require('node-pi-buttons')(configButtons.options); menu.on('menu_changed', function () { displayDirty = false; // the parent will redraw the display }); -// buttons piButtons - .on('clicked', function (pin) { + .on('clicked', function (gpio, data) { if (displayDirty) { // fake menu changed to force redraw menu.emit('menu_changed'); displayDirty = false; } else { - switch(pin) { - case pins.buttonUp: + switch(parseInt(gpio, 10)) { + case gpios.buttonUp: if (!displayDirty) { menu.menuUp(); } break; - case pins.buttonDown: + case gpios.buttonDown: if (!displayDirty) { menu.menuDown(); } @@ -47,31 +45,34 @@ function createHIDMenu(configButtons, configMenus) { } } }) - .on('double_clicked', function (pin) { + .on('double_clicked', function (gpio, data) { if (displayDirty) { // fake menu changed to force redraw menu.emit('menu_changed'); displayDirty = false; } else { - switch (pin) { - case pins.buttonUp: + switch (parseInt(gpio, 10)) { + case gpios.buttonUp: menu.menuBack(); break; - case pins.buttonDown: + case gpios.buttonDown: displayDirty = true; // activate may write something to the display menu.activateSelect(); break; } } }) - .on('released', function (pin) { + .on('released', function (gpio, data) { if (displayDirty) { // fake menu changed to force redraw menu.emit('menu_changed'); displayDirty = false; } + }) + .on('error', function (data) { + console.log('ERROR: ', data.error); }); return menu; diff --git a/lib/pi-buttons/a.out b/lib/pi-buttons/a.out deleted file mode 100755 index b8e6cf48dddcdc26d83ca40613d264ccce778789..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14132 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "buttons.h" - - -int main(int argc, char *argv[]) { - int l, gpioCount; - buttonDefinition * buttons[MAX_BUTTONS]; - pthread_t buttonThread; - pthread_t socketThread; - int clients[MAX_CLIENTS]; - - - // TODO these need to come from a config file or command line args - const char * gpios[] = { - "17", - "27" - }; - gpioCount = 2; - - // init client file descriptors - for(l = 0; l < MAX_CLIENTS; l++) { - clients[l] = -1; - } - pthread_create(&socketThread, NULL, &socketServer, &clients); - - // init buttons - for(l = 0; l < gpioCount; l++) { - buttons[l] = (buttonDefinition *)malloc(sizeof(buttonDefinition)); - buttons[l]->gpio = gpios[l]; - pthread_mutex_init(&buttons[l]->lockControl, NULL); - pthread_barrier_init(&buttons[l]->barrierControl, NULL, 2); - buttons[l]->clients = clients; - pthread_create(&buttons[l]->parent, NULL, &buttonParent, buttons[l]); - } - - // TODO can all threads be monitored simultaneously and restart if one fails? - for(l = 0; l < gpioCount; l++) { - pthread_join(buttons[l]->parent, NULL); - } - -} - - -void * buttonParent(void * args) { - buttonDefinition * button; - button = (buttonDefinition *)args; - char buff[30]; - struct pollfd pollfdStruct; - int pollStatus; - uint8_t c; - - sprintf(buff, "/sys/class/gpio/gpio%s/value", button->gpio); - if ((button->fd = open(buff, O_RDWR)) < 0) { - printf("Failed to open gpio%d.\n", button->gpio); - exit(1); - } - - // configure polling structure - pollfdStruct.fd = button->fd; - pollfdStruct.events = POLLPRI | POLLERR; - - // configure button structure - button->state = STATE_INIT; - button->debounceState = INACTIVE; - button->value = RELEASED; - - // clear out any waiting gpio values - pollStatus = poll(&pollfdStruct, 1, 10); // 10 millisecond wait for input - if (pollStatus > 0) { - if (pollfdStruct.revents & POLLPRI) { - lseek (pollfdStruct.fd, 0, SEEK_SET) ; // Rewind - (void)read (pollfdStruct.fd, &c, 1) ; // Read & clear - } - } - - // reset button state - button->state = STATE_IDLE; - pthread_mutex_lock(&button->lockControl); - pthread_create(&button->child, NULL, &buttonChild, button); - - for(;;) { - pollStatus = poll(&pollfdStruct, 1, -1) ; - if (pollStatus > 0) { - if (pollfdStruct.revents & POLLPRI) { - lseek (pollfdStruct.fd, 0, SEEK_SET) ; // Rewind - (void)read (pollfdStruct.fd, &c, 1) ; // Read & clear - - button->lastValue = c; - - if (button->debounceState == INACTIVE) { - // when not in a debounce state then signal child about button change - pthread_mutex_unlock(&button->lockControl); // signal child button event has started - pthread_barrier_wait(&button->barrierControl); // wait on begin sychronization - pthread_mutex_lock(&button->lockControl); // regain lock for next event - pthread_barrier_wait(&button->barrierControl); // signal child synchronized - } - else { - // TODO do we need to do anything if in debounce? probably not. - } - } - - } - else { - // something wrong with poll status - - } - } -} - - -void * buttonChild(void * args) { - buttonDefinition * button; - button = (buttonDefinition *)args; - int lockStatus, startDebounce = 0; - long lockTimeout = TIMEOUT_FALSE; - char eventMsg[EVENT_MSG_MAX_LENGTH]; - - // main loop - for(;;) { - // wait for next event, unlock or timeout - if (lockTimeout) { - lockStatus = pthread_mutex_timedlock(&button->lockControl, &button->conditionTime); - } - else { - lockStatus = pthread_mutex_lock(&button->lockControl); // wait for parent to signal button event started - } - - lockTimeout = TIMEOUT_FALSE; - if (lockStatus != ETIMEDOUT && button->debounceState == INACTIVE) { - // not a timeout and not in debounce state, start debounce of button value - button->debounceState = ACTIVE; - button->value = button->lastValue; - clock_gettime(CLOCK_REALTIME, &button->lastTime); - setConditionNS(&button->lastTime, &button->conditionTime, DEBOUNCE_NS); - lockTimeout = TIMEOUT_TRUE; - emitFormattedMessage(eventMsg, EVENT_STRING[button_changed], button); - } - else if (lockStatus == ETIMEDOUT && button->debounceState == ACTIVE) { - button->debounceState = INACTIVE; - // timed out while in the debounce state, perform state transition - switch(button->state) { - case STATE_IDLE: - if (button->value == PRESSED && button->lastValue == PRESSED) { - // button pressed and held - button->state = STATE_PRESSED; - emitFormattedMessage(eventMsg, EVENT_STRING[button_press], button); - setConditionNS(&button->lastTime, &button->conditionTime, PRESSED_NS); - lockTimeout = TIMEOUT_TRUE; - } - else if (button->value == PRESSED) { - // button pressed and released within debounce - emitFormattedMessage(eventMsg, EVENT_STRING[button_press], button); - emitFormattedMessage(eventMsg, EVENT_STRING[button_release], button); - button->state = STATE_CLICKED; - setConditionNS(&button->lastTime, &button->conditionTime, CLICKED_NS); - lockTimeout = TIMEOUT_TRUE; - } - break; - - case STATE_PRESSED: - if (button->lastValue == RELEASED) { - // button released - emitFormattedMessage(eventMsg, EVENT_STRING[button_release], button); - button->state = STATE_CLICKED; - setConditionNS(&button->lastTime, &button->conditionTime, CLICKED_NS); - lockTimeout = TIMEOUT_TRUE; - } - else if (button->value == RELEASED && button->lastValue == PRESSED) { - // button released and pressed within debounce - emitFormattedMessage(eventMsg, EVENT_STRING[button_release], button); - emitFormattedMessage(eventMsg, EVENT_STRING[button_press], button); - button->state = STATE_CLICKED_PRESSED; - setConditionNS(&button->lastTime, &button->conditionTime, PRESSED_NS); - lockTimeout = TIMEOUT_TRUE; - } - else { - // unknown, reset state - button->state = STATE_IDLE; - } - break; - - case STATE_CLICKED: - if (button->lastValue == PRESSED) { - // after clicked button pressed and held - button->state = STATE_CLICKED_PRESSED; - emitFormattedMessage(eventMsg, EVENT_STRING[button_press], button); - setConditionNS(&button->lastTime, &button->conditionTime, PRESSED_NS); - lockTimeout = TIMEOUT_TRUE; - } - else if (button->value == PRESSED && button->lastValue == RELEASED) { - // after clicked button pressed and released within debounce - emitFormattedMessage(eventMsg, EVENT_STRING[button_press], button); - emitFormattedMessage(eventMsg, EVENT_STRING[button_release], button); - button->state = STATE_DOUBLE_CLICKED; - setConditionNS(&button->lastTime, &button->conditionTime, CLICKED_NS); - lockTimeout = TIMEOUT_TRUE; - } - else { - // unknown, reset state - button->state = STATE_IDLE; - } - break; - - case STATE_CLICKED_PRESSED: - if (button->lastValue == RELEASED) { - emitFormattedMessage(eventMsg, EVENT_STRING[button_release], button); - button->state = STATE_DOUBLE_CLICKED; - emitState(eventMsg, button); - button->state = STATE_IDLE; - } - else { - emitState(eventMsg, button); - button->state = STATE_RELEASE_WAIT; - } - break; - - case STATE_DOUBLE_CLICKED: - case STATE_RELEASE_WAIT: - emitState(eventMsg, button); - button->state = STATE_IDLE; - break; - } - } - else { - // emit state and transition state - emitState(eventMsg, button); - switch(button->state) { - case STATE_PRESSED: - case STATE_CLICKED_PRESSED: - button->state = STATE_RELEASE_WAIT; - break; - - default: - button->state = STATE_IDLE; - break; - } - } - - // if child has lock then perform handshake with parent - if (!lockStatus) { - pthread_mutex_unlock(&button->lockControl); // release for synchronization - pthread_barrier_wait(&button->barrierControl); // begin synchronization - pthread_barrier_wait(&button->barrierControl); // wait for synchronized - } - } -} - - - -void * socketServer(void * args) { - int * clients; - clients = (int *)args; - int l, fd, socket = openSocket(); - - while (1) { - // wait for connection - if ( (fd = accept(socket, NULL, NULL)) == -1) { - fprintf(stderr, "Error accepting incoming connection.\n"); - continue; - } - - for(l = 0; l < MAX_CLIENTS; l++) { - if (clients[l] == -1) { - clients[l] = fd; - break; - } - } - - if (l == MAX_CLIENTS) { - send(fd, ERROR_MAX_CLIENTS, strlen(ERROR_MAX_CLIENTS), MSG_NOSIGNAL); - } - else { - // add connection to empty slot -printf("Connect %d\n", fd); - } - } -} - - -// emit event for the current button state -void emitState(char * buffer, buttonDefinition * button) { - switch (button->state) { - case STATE_PRESSED: - // emit event and transition to release wait - emitFormattedMessage(buffer, EVENT_STRING[pressed], button); - break; - - case STATE_CLICKED: - // emit event and transition to idle - emitFormattedMessage(buffer, EVENT_STRING[clicked], button); - break; - - case STATE_CLICKED_PRESSED: - // emit event and transition to release wait - emitFormattedMessage(buffer, EVENT_STRING[clicked_pressed], button); - break; - - case STATE_DOUBLE_CLICKED: - // emit event and transition to idle - emitFormattedMessage(buffer, EVENT_STRING[double_clicked], button); - break; - - case STATE_RELEASE_WAIT: - // emit event and transition to idle - emitFormattedMessage(buffer, EVENT_STRING[released], button); - break; - } -} - - -void emitFormattedMessage(char * buffer, const char * eventString, buttonDefinition * button) { - if (strlen(EVENT_MSG_FORMAT) + strlen(eventString) + strlen(button->gpio) >= EVENT_MSG_MAX_LENGTH) { - fprintf(stderr, "Emit message too large for buffer."); - } - else { - sprintf(buffer, EVENT_MSG_FORMAT, eventString, button->gpio, button->lastTime.tv_sec, button->lastTime.tv_nsec); - emitMessage(buffer, button->clients); - } -} - - -void emitMessage(const char * msg, int * clients) { - int l, wl; - for(l = 0; l < MAX_CLIENTS; l++) { - if (clients[l] != -1) { - wl = send(clients[l], msg, strlen(msg), MSG_NOSIGNAL); - if (wl == -1) { - // failure, remove client - close(clients[l]); - clients[l] = -1; - } - } - } -} - - -int openSocket() { - struct sockaddr_un addr; - int fd; - - if ( (fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { - fprintf(stderr, "Error opening event socket."); - exit(-1); - } - - memset(&addr, 0, sizeof(addr)); - addr.sun_family = AF_UNIX; - strncpy(addr.sun_path, EVENT_SOCKET_PATH, sizeof(addr.sun_path)-1); - unlink(EVENT_SOCKET_PATH); - - if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) { - fprintf(stderr, "Event socket bind error."); - exit(-1); - } - - if (listen(fd, 5) == -1) { - fprintf(stderr, "Event socket listen error."); - exit(-1); - } - - return fd; -} - - -void setConditionNS(struct timespec * currentTime, struct timespec * targetTime, uint32_t ns) { - targetTime->tv_sec = currentTime->tv_sec; - targetTime->tv_nsec = currentTime->tv_nsec; - if (1000000000 - targetTime->tv_nsec < ns) { - targetTime->tv_sec += 1; - targetTime->tv_nsec = ns - (targetTime->tv_nsec - 1000000000); - } - else { - targetTime->tv_nsec = targetTime->tv_nsec + ns; - } -} diff --git a/lib/pi-buttons/buttons.h b/lib/pi-buttons/buttons.h deleted file mode 100644 index 4949758..0000000 --- a/lib/pi-buttons/buttons.h +++ /dev/null @@ -1,108 +0,0 @@ -#define EVENT_SOCKET_PATH "./buttonevents" -#define MAX_BUTTONS 10 -#define MAX_CLIENTS 2 -#define ERROR_MAX_CLIENTS "error {\"error\": \"Maximum client connections exceeded.\"}" - -enum ButtonState { - STATE_INIT, - STATE_IDLE, - STATE_PRESSED, - STATE_CLICKED, - STATE_CLICKED_PRESSED, - STATE_DOUBLE_CLICKED, - STATE_RELEASE_WAIT -}; - -enum DebounceState { - INACTIVE, - ACTIVE -}; - -enum LockTimeoutState { - TIMEOUT_FALSE, - TIMEOUT_TRUE -}; - -enum ButtonValue { - PRESSED = 48, - RELEASED -}; - -// I.E. 'button_changed {"gpio": "17", "time": 012345678900123456789}' -#define EVENT_MSG_MAX_LENGTH 128 -static const char * EVENT_MSG_FORMAT = "%s {\"gpio\": \"%s\", \"time\": {\"tv_sec\": %ld, \"tv_nsec\": %ld}}\n"; - -// define events -#define FOREACH_EVENT(EVENT) \ - EVENT(button_changed) \ - EVENT(button_press) \ - EVENT(button_release) \ - EVENT(pressed) \ - EVENT(clicked) \ - EVENT(clicked_pressed) \ - EVENT(double_clicked) \ - EVENT(released) \ - -#define GENERATE_ENUM(ENUM) ENUM, -#define GENERATE_STRING(STRING) #STRING, - -enum EVENT_ENUM { - FOREACH_EVENT(GENERATE_ENUM) -}; - -static const char *EVENT_STRING[] = { - FOREACH_EVENT(GENERATE_STRING) -}; - - -#define SEC_NSEC 1000000000 -#define DEBOUNCE_MS 30 -#define DEBOUNCE_NS 20000000 -#define PRESSED_MS 200 -#define PRESSED_NS 200000000 -#define CLICKED_MS 200 -#define CLICKED_NS 200000000 - -typedef struct { - pthread_mutex_t lockControl; - pthread_barrier_t barrierControl; - struct timespec lastTime; - struct timespec conditionTime; - const char * gpio; - int fd; // file descriptor for button input - enum ButtonState state; - int debounceState; - uint8_t value; - uint8_t lastValue; - int * clients; - pthread_t parent; - pthread_t child; - long debounce_ns; -} buttonDefinition; - -typedef struct { - char ** gpios; - int gpioCount; - int * clients; -} pollerThreadArgs; - -typedef struct { - int index; - int fd; // file descriptor for button input - enum ButtonState state; - int debouncing; - uint8_t value; - uint8_t lastValue; - int * clients; -} gpioButton; - -void * buttonPoller(void * args); -void * buttonDebounce(void * args); -void * socketServer(void * args); -int openSocket(); -void emitMessage(const char * msg, int * clients); -void * buttonParent(void * args); -void * buttonChild(void * args); -void emitState(char * buffer, buttonDefinition * button); -void emitFormattedMessage(char * buffer, const char * eventString, buttonDefinition * button); -void setConditionNS(struct timespec * currentTime, struct timespec * targetTime, uint32_t ns); diff --git a/lib/pi-buttons/index.js b/lib/pi-buttons/index.js deleted file mode 100644 index 7f92464..0000000 --- a/lib/pi-buttons/index.js +++ /dev/null @@ -1,28 +0,0 @@ -'use strict'; - -const net = require('net'); -const events = require('events'); -const emitter = new events.EventEmitter(); -const pins = { [17]: 11, [27]: 13 }; - -var client = net.createConnection(__dirname + "/buttonevents"); - -client.on("connect", function() { - -}); - -client.on("data", function(data) { - let packets = data.toString().split(/\r?\n/); - packets.forEach(packet => { - var parts = /^([^{]+)\s({.*})/.exec(packet); - if (parts) { - try { - var d = JSON.parse(parts[2]); - emitter.emit(parts[1], pins[d.gpio], d); - } - catch (e) {} - } - }); -}); - -module.exports = emitter; diff --git a/lib/pi-buttons/setup.sh b/lib/pi-buttons/setup.sh deleted file mode 100755 index 14cf0a6..0000000 --- a/lib/pi-buttons/setup.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/sh - -echo 17 > /sys/class/gpio/export -echo 22 > /sys/class/gpio/export -echo 27 > /sys/class/gpio/export - -sleep 3 - -echo "in" > /sys/class/gpio/gpio17/direction -echo "both" > /sys/class/gpio/gpio17/edge - -echo "in" > /sys/class/gpio/gpio27/direction -echo "both" > /sys/class/gpio/gpio27/edge - -echo "out" > /sys/class/gpio/gpio22/direction diff --git a/openaps-menu.sh b/openaps-menu.sh index ea3f3f0..3cfeb21 100755 --- a/openaps-menu.sh +++ b/openaps-menu.sh @@ -1,3 +1,2 @@ #!/bin/bash - -(cd lib/pi-buttons/ && ./setup.sh && ./a.out) & node index.js +node index.js diff --git a/package.json b/package.json index 8994aa2..3fb697e 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "oled-font-5x7": "^1.0.0", "oled-i2c-bus": "git+https://github.com/bnielsen1965/oled-i2c-bus.git", "pngparse": "^2.0.1", - "rpi-gpio": "^0.9.1", - "rpi-gpio-buttons": "^1.0.2" + "node-pi-buttons": "git+https://github.com/bnielsen1965/node-pi-buttons.git", + "rpi-gpio": "^0.9.1" } } From e7bb7c6c399ec7db46efb0a858d91e20f5763909 Mon Sep 17 00:00:00 2001 From: Jon Cluck Date: Fri, 15 Jun 2018 21:25:00 -0400 Subject: [PATCH 02/35] Fix for low number of BGs and offset Fixes crash when actual number of BGs in monitor/glucose.json is fewer than the number we want to display (72 or 120). Thanks to @mhaeberli for catching & patching! Also fixes graph offset so we have a continuous line of BGs. --- scripts/status.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/status.js b/scripts/status.js index 72689ad..9f1cacc 100644 --- a/scripts/status.js +++ b/scripts/status.js @@ -99,8 +99,9 @@ var numBGs = (suggested.predBGs != undefined) ? (72) : (120); //fill the whole g var date = new Date(); var zerotime = date.getTime() - ((numBGs * 5) * 600); var zero_x = numBGs + 5; -for (var i = 0; i <= numBGs; i++) { - var x = 2 + zero_x + Math.round(((((bg[i].date - zerotime)/1000)/60)/5)); +var iterMax = Math.min(numBGs, bg.length) +for (var i = 0; i < iterMax; ++i) { + var x = zero_x + Math.round(((((bg[i].date - zerotime)/1000)/60)/5)); var y = Math.round( 21 - ( ( bg[i].glucose - 250 ) / 8 ) ); //left and right boundaries if ( x < 5 ) x = 5; @@ -118,7 +119,7 @@ for (var i = 0; i <= numBGs; i++) { //render predictions, only if we have them if (suggested.predBGs != undefined) { //render line between actual BG and predicted - x = zero_x + 3; + x = zero_x + 1; display.oled.drawLine(x, 51, x, 21, 1); //render predictions var predictions = [suggested.predBGs.IOB, suggested.predBGs.ZT, suggested.predBGs.UAM, suggested.predBGs.COB]; From c86941e49915a6969fd6e4b8c2cff834bdb1e66c Mon Sep 17 00:00:00 2001 From: Jon Cluck Date: Tue, 19 Jun 2018 10:00:13 -0400 Subject: [PATCH 03/35] customizations for jon's production rig --- config/menus/menu.json | 43 ++++++++----- scripts/big_bg_status.js | 136 +++++++++++++++++++++++++++++++++++++++ scripts/getip.sh | 7 +- 3 files changed, 166 insertions(+), 20 deletions(-) create mode 100644 scripts/big_bg_status.js diff --git a/config/menus/menu.json b/config/menus/menu.json index a0a1d67..d11b730 100644 --- a/config/menus/menu.json +++ b/config/menus/menu.json @@ -7,6 +7,11 @@ "command": "node scripts/status.js", "emit": "nothing" }, + { + "label": "Big BG Status", + "command": "node scripts/big_bg_status.js", + "emit": "nothing" + }, { "label": "Set Temp Target", "menu": [ @@ -54,7 +59,7 @@ }, { "label": "Unicorn Logo", - "command": "node scripts/unicorn.js", + "command": "node scripts/display_image.js ./static/unicorn.png", "emit": "nothing" } ] @@ -63,30 +68,29 @@ "label": "Wifi", "menu": [ { - "label": "Current Wifi Network", - "command": "./scripts/getwifi.sh", - "emit": "showoutput" + "label": "Connect Pixel", + "command": "nmcli c up 'Pixel Network'", + "emit": "showoutput" }, { - "label": "Current Hostname", - "command": "./scripts/gethostname.sh", - "emit": "showoutput" + "label": "Disconnect Pixel", + "command": "nmcli c down 'Pixel Network'", + "emit": "showoutput" }, { - "label": "Current IP Address", - "command": "./scripts/getip.sh", - "emit": "showoutput" + "label": "Show WiFi Networks", + "command": "nmcli --terse --fields IN-USE,SSID device wifi list", + "emit": "showoutput" }, { - "label": "Show network.log", - "command": "cat /var/log/openaps/network.log | fold -w 23 -s | tail -8", - "emit": "showoutput" + "label": "Wireless Status", + "command": "nmcli --terse --fields TYPE,CONNECTION,STATE device status", + "emit": "showoutput" }, { - "label": "Select Open Wifi Net", - "options": "./scripts/list_open_wifi.sh", - "selectScript": "./scripts/add_open_wifi.sh", - "selectEmit": "showoutput" + "label": "Current IP Address", + "command": "./scripts/getip.sh", + "emit": "showoutput" } ] }, @@ -137,6 +141,11 @@ "command": "echo Rebooting in; echo 1 minute; shutdown -r", "emit": "showoutput" }, + { + "label": "Shutdown Now", + "command": "shutdown -h now", + "emit": "showoutput" + }, { "label": "Cancel Reboot", "command": "shutdown -c; echo Reboot canceled", diff --git a/scripts/big_bg_status.js b/scripts/big_bg_status.js new file mode 100644 index 0000000..8a99097 --- /dev/null +++ b/scripts/big_bg_status.js @@ -0,0 +1,136 @@ +'use strict'; + +const i2c = require('i2c-bus'); +const path = require('path'); +const extend = require('extend'); +var os = require('os'); +var fs = require('fs'); +var font = require('oled-font-5x7'); +var i2cBus = i2c.openSync(1); + +// Rounds value to 'digits' decimal places +function round(value, digits) +{ + if (! digits) { digits = 0; } + var scale = Math.pow(10, digits); + return Math.round(value * scale) / scale; +} + +function convert_bg(value, profile) +{ + if (profile != null && profile.out_units == "mmol/L") + { + return round(value / 18, 1).toFixed(1); + } + else + { + return Math.round(value); + } +} + +function stripLeadingZero(value) +{ + var re = /^(-)?0+(?=[\.\d])/; + return value.toString().replace( re, '$1'); +} + +// setup the display +var displayConfig = require('/root/src/openaps-menu/config/display.json'); +displayConfig.i2cBus = i2cBus; +var display = require('/root/src/openaps-menu/lib/display/ssd1306')(displayConfig); + +//Parse all the .json files we need +try { + var profile = JSON.parse(fs.readFileSync("/root/myopenaps/settings/profile.json")); +} catch (e) { + // Note: profile.json is optional as it's only needed for mmol conversion for now. Print an error, but not return + console.error("Could not parse profile.json: ", e); +} +try { + var batterylevel = JSON.parse(fs.readFileSync("/root/myopenaps/monitor/edison-battery.json")); +} catch (e) { + console.error("Could not parse edison-battery.json: ", e); +} + +if(batterylevel) { + //Process and display battery gauge + display.oled.drawLine(115, 57, 127, 57, 1); //top + display.oled.drawLine(115, 63, 127, 63, 1); //bottom + display.oled.drawLine(115, 57, 115, 63, 1); //left + display.oled.drawLine(127, 57, 127, 63, 1); //right + display.oled.drawLine(114, 59, 114, 61, 1); //iconify + var batt = Math.round(127 - (batterylevel.battery / 10)); + display.oled.fillRect(batt, 58, 126, 62, 1); //fill battery gauge +} + +try { + var suggested = JSON.parse(fs.readFileSync("/root/myopenaps/enact/suggested.json")); +} catch (e) { + return console.error("Could not parse suggested.json: ", e); +} +try { + var bg = JSON.parse(fs.readFileSync("/root/myopenaps/monitor/glucose.json")); +} catch (e) { + return console.error("Could not parse glucose.json: ", e); +} + +//calculate timeago for BG +var startDate = new Date(bg[0].date); +var endDate = new Date(); +var minutes = Math.round(( (endDate.getTime() - startDate.getTime()) / 1000) / 60); +if (bg[0].delta) { + var delta = Math.round(bg[0].delta); +} else if (bg[1] && bg[0].date - bg[1].date > 200000 ) { + var delta = Math.round(bg[0].glucose - bg[1].glucose); +} else if (bg[2] && bg[0].date - bg[2].date > 200000 ) { + var delta = Math.round(bg[0].glucose - bg[2].glucose); +} else if (bg[3] && bg[0].date - bg[3].date > 200000 ) { + var delta = Math.round(bg[0].glucose - bg[3].glucose); +} else { + var delta = 0; +} + +//display BG number, add plus sign if delta is positive +display.oled.setCursor(0,0); +if (delta >= 0) { + display.oled.writeString(font, 3, ""+convert_bg(bg[0].glucose, profile), 1, true); + display.oled.writeString(font, 1, "+"+stripLeadingZero(convert_bg(delta, profile)), 1, true); + display.oled.writeString(font, 2, " "+minutes+"m", 1, true); +} else { + display.oled.writeString(font, 3, ""+convert_bg(bg[0].glucose, profile), 1, true); + display.oled.writeString(font, 1, ""+stripLeadingZero(convert_bg(delta, profile)), 1, true); + display.oled.writeString(font, 2, " "+minutes+"m", 1, true); +} + +//calculate timeago for last successful loop +var stats = fs.statSync("/tmp/pump_loop_success"); +var date = new Date(stats.mtime); +var hour = date.getHours(); +hour = (hour < 10 ? "0" : "") + hour; +var min = date.getMinutes(); +min = (min < 10 ? "0" : "") + min; + +//display last loop time +display.oled.setCursor(0,57); +display.oled.writeString(font, 1, "Last loop at: "+hour+":"+min, 1, true); + +//files for IOB and COB: +try { + var iob = JSON.parse(fs.readFileSync("/root/myopenaps/monitor/iob.json")); +} catch (e) { + return console.error("Could not parse iob.json: ", e); +} + +try { + var cob = JSON.parse(fs.readFileSync("/root/myopenaps/monitor/meal.json")); +} catch (e) { + return console.error("Could not parse meal.json: ", e); +} + +//parse and render COB/IOB +display.oled.setCursor(0,23); +display.oled.writeString(font, 1, "IOB:", 1, true); +display.oled.writeString(font, 2, " "+iob[0].iob+'U', 1, true); +display.oled.setCursor(0,39); +display.oled.writeString(font, 1, "COB:", 1, true); +display.oled.writeString(font, 2, " "+cob.mealCOB+'g', 1, true); diff --git a/scripts/getip.sh b/scripts/getip.sh index 132510e..32eaf3b 100755 --- a/scripts/getip.sh +++ b/scripts/getip.sh @@ -1,5 +1,6 @@ #!/bin/bash -IP=$(ip -f inet -o addr show wlan0|cut -d\ -f 7 | cut -d/ -f 1) -echo -e "Current IP Address:\n$IP\n" -echo -e "To connect: ssh\npi@$IP\n" +wlan0IP=$(ip -f inet -o addr show wlan0|cut -d\ -f 7 | cut -d/ -f 1) +bnep0IP=$(ip -f inet -o addr show bnep0|cut -d\ -f 7 | cut -d/ -f 1) +echo -e "Current WiFi IP:\n$wlan0IP\n" +echo -e "Current BT IP:\n$bnep0IP\n" From 61950565dd64a8c68752ec9244e7f8c3cab6982c Mon Sep 17 00:00:00 2001 From: Jon Cluck Date: Sat, 14 Jul 2018 11:52:06 -0400 Subject: [PATCH 04/35] various personalizations and possible improvements --- config/menus/menu.json | 14 +++++++++++++ index.js | 3 +++ scripts/status.js | 46 +++++++++++++++++++++++++----------------- 3 files changed, 45 insertions(+), 18 deletions(-) diff --git a/config/menus/menu.json b/config/menus/menu.json index d11b730..3af95a6 100644 --- a/config/menus/menu.json +++ b/config/menus/menu.json @@ -1,4 +1,8 @@ [ + { + "label": "Screen Off", + "emit": "screenoff" + }, { "label": "OpenAPS", "menu": [ @@ -77,6 +81,16 @@ "command": "nmcli c down 'Pixel Network'", "emit": "showoutput" }, + { + "label": "Wifi Off", + "command": "nmcli radio wifi off", + "emit": "showoutput" + }, + { + "label": "Wifi On", + "command": "nmcli radio wifi on", + "emit": "showoutput" + }, { "label": "Show WiFi Networks", "command": "nmcli --terse --fields IN-USE,SSID device wifi list", diff --git a/index.js b/index.js index 51b62ed..88cc9bc 100644 --- a/index.js +++ b/index.js @@ -64,6 +64,9 @@ var hidMenu = require('./lib/hid-menu/hid-menu')(buttonsConfig, menuConfig); hidMenu .on('nothing', function () { }) +.on('screenoff', function () { + display.turnOffDisplay(); +}) .on('showvoltage', function () { voltage() .then(function (v) { diff --git a/scripts/status.js b/scripts/status.js index 9f1cacc..7cfbc7b 100644 --- a/scripts/status.js +++ b/scripts/status.js @@ -40,6 +40,9 @@ var displayConfig = require('/root/src/openaps-menu/config/display.json'); displayConfig.i2cBus = i2cBus; var display = require('/root/src/openaps-menu/lib/display/ssd1306')(displayConfig); +//dim the display +display.oled.dimDisplay(true); + //Parse all the .json files we need try { var profile = JSON.parse(fs.readFileSync("/root/myopenaps/settings/profile.json")); @@ -55,13 +58,13 @@ try { if(batterylevel) { //Process and display battery gauge - display.oled.drawLine(115, 57, 127, 57, 1); //top - display.oled.drawLine(115, 63, 127, 63, 1); //bottom - display.oled.drawLine(115, 57, 115, 63, 1); //left - display.oled.drawLine(127, 57, 127, 63, 1); //right - display.oled.drawLine(114, 59, 114, 61, 1); //iconify + display.oled.drawLine(115, 57, 127, 57, 1, false); //top + display.oled.drawLine(115, 63, 127, 63, 1, false); //bottom + display.oled.drawLine(115, 57, 115, 63, 1, false); //left + display.oled.drawLine(127, 57, 127, 63, 1, false); //right + display.oled.drawLine(114, 59, 114, 61, 1, false); //iconify var batt = Math.round(127 - (batterylevel.battery / 10)); - display.oled.fillRect(batt, 58, 126, 62, 1); //fill battery gauge + display.oled.fillRect(batt, 58, 126, 62, 1, false); //fill battery gauge } //Create and render clock @@ -72,17 +75,17 @@ function displayClock() { var min = date.getMinutes(); min = (min < 10 ? "0" : "") + min; display.oled.setCursor(83, 57); - display.oled.writeString(font, 1, hour+":"+min, 1, true); + display.oled.writeString(font, 1, hour+":"+min, 1, true, false); } displayClock(); //bg graph -display.oled.drawLine(5, 51, 5, 21, 1); -display.oled.drawLine(5, 51, 127, 51, 1); +display.oled.drawLine(5, 51, 5, 21, 1, false); +display.oled.drawLine(5, 51, 127, 51, 1, false); //targets high and low -display.oled.drawLine(2, 30, 5, 30, 1); -display.oled.drawLine(2, 40, 5, 40, 1); +display.oled.drawLine(2, 30, 5, 30, 1, false); +display.oled.drawLine(2, 40, 5, 40, 1, false); try { var suggested = JSON.parse(fs.readFileSync("/root/myopenaps/enact/suggested.json")); @@ -109,7 +112,7 @@ for (var i = 0; i < iterMax; ++i) { //upper and lower boundaries if ( y < 21 ) y = 21; if ( y > 51 ) y = 51; - display.oled.drawPixel([x, y, 1]); + display.oled.drawPixel([x, y, 1, false]); // if we have multiple data points within 3m, look further back to fill in the graph if ( bg[i-1] && bg[i-1].date - bg[i].date < 200000 ) { numBGs++; @@ -120,7 +123,7 @@ for (var i = 0; i < iterMax; ++i) { if (suggested.predBGs != undefined) { //render line between actual BG and predicted x = zero_x + 1; - display.oled.drawLine(x, 51, x, 21, 1); + display.oled.drawLine(x, 51, x, 21, 1, false); //render predictions var predictions = [suggested.predBGs.IOB, suggested.predBGs.ZT, suggested.predBGs.UAM, suggested.predBGs.COB]; for (i = 0; i <= 48; i++) { @@ -132,7 +135,7 @@ if (suggested.predBGs != undefined) { //upper and lower boundaries if ( y < 21 ) y = 21; if ( y > 51 ) y = 51; - display.oled.drawPixel([x, y, 1]); + display.oled.drawPixel([x, y, 1, false]); } } } @@ -155,9 +158,9 @@ if (bg[0].delta) { //display BG number, add plus sign if delta is positive display.oled.setCursor(0,57); if (delta >= 0) { - display.oled.writeString(font, 1, "BG:"+convert_bg(bg[0].glucose, profile)+"+"+stripLeadingZero(convert_bg(delta, profile))+" "+minutes+"m", 1, true); + display.oled.writeString(font, 1, "BG:"+convert_bg(bg[0].glucose, profile)+"+"+stripLeadingZero(convert_bg(delta, profile))+" "+minutes+"m", 1, true, false); } else { - display.oled.writeString(font, 1, "BG:"+convert_bg(bg[0].glucose, profile)+""+stripLeadingZero(convert_bg(delta, profile))+" "+minutes+"m", 1, true); + display.oled.writeString(font, 1, "BG:"+convert_bg(bg[0].glucose, profile)+""+stripLeadingZero(convert_bg(delta, profile))+" "+minutes+"m", 1, true, false); } try { @@ -175,7 +178,7 @@ minutes = Math.round(( (endDate.getTime() - startDate.getTime()) / 1000) / 60); //render current temp basal display.oled.setCursor(0,0); var tempRate = Math.round(temp.rate*10)/10; -display.oled.writeString(font, 1, "TB: "+temp.duration+'m '+tempRate+'U/h '+'('+minutes+'m ago)', 1); +display.oled.writeString(font, 1, "TB: "+temp.duration+'m '+tempRate+'U/h '+'('+minutes+'m ago)', 1, false); try { var iob = JSON.parse(fs.readFileSync("/root/myopenaps/monitor/iob.json")); @@ -190,4 +193,11 @@ try { } //parse and render COB/IOB display.oled.setCursor(0,8); -display.oled.writeString(font, 1, "COB: "+cob.mealCOB+"g IOB: "+iob[0].iob+'U', 1, true); +display.oled.writeString(font, 1, "COB: "+cob.mealCOB+"g IOB: "+iob[0].iob+'U', 1, true, false); + +//display everything in the buffer +display.oled.update(); + +if ( endDate % 2 == 1 ) { display.oled.invertDisplay(true); } else { display.oled.invertDisplay(false); } + + From d35155905312eb0c8be2e0f22f583c3a282e821f Mon Sep 17 00:00:00 2001 From: Jon Cluck Date: Sun, 15 Jul 2018 10:05:02 -0400 Subject: [PATCH 05/35] revert screenoff code and add pump status to screen --- config/menus/menu.json | 4 ---- index.js | 3 --- scripts/status.js | 16 +++++++++++++++- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/config/menus/menu.json b/config/menus/menu.json index 3af95a6..489ce55 100644 --- a/config/menus/menu.json +++ b/config/menus/menu.json @@ -1,8 +1,4 @@ [ - { - "label": "Screen Off", - "emit": "screenoff" - }, { "label": "OpenAPS", "menu": [ diff --git a/index.js b/index.js index 88cc9bc..51b62ed 100644 --- a/index.js +++ b/index.js @@ -64,9 +64,6 @@ var hidMenu = require('./lib/hid-menu/hid-menu')(buttonsConfig, menuConfig); hidMenu .on('nothing', function () { }) -.on('screenoff', function () { - display.turnOffDisplay(); -}) .on('showvoltage', function () { voltage() .then(function (v) { diff --git a/scripts/status.js b/scripts/status.js index 7cfbc7b..ba98c95 100644 --- a/scripts/status.js +++ b/scripts/status.js @@ -195,9 +195,23 @@ try { display.oled.setCursor(0,8); display.oled.writeString(font, 1, "COB: "+cob.mealCOB+"g IOB: "+iob[0].iob+'U', 1, true, false); +//if the pump is suspended, display a message to that effect +try { + var pumpstatus = JSON.parse(fs.readFileSync("/root/myopenaps/monitor/status.json")); +} catch (e) { + console.error("Could not parse status.json: ", e); +} + +if ( pumpstatus.suspended == true ) { + display.oled.setCursor(28,24); + display.oled.writeString(font, 1, "PUMP SUSPENDED", 1, true, false); +} + //display everything in the buffer display.oled.update(); -if ( endDate % 2 == 1 ) { display.oled.invertDisplay(true); } else { display.oled.invertDisplay(false); } +//randomly invert display to evenly wear the OLED diodes +display.oled.invertDisplay((endDate % 2 == 1)); + From 966dbe70417494edef50a3245aea953cc1fe3c71 Mon Sep 17 00:00:00 2001 From: Jon Cluck Date: Sun, 15 Jul 2018 11:04:54 -0400 Subject: [PATCH 06/35] status screen improvements and bugfixes --- scripts/big_bg_status.js | 46 ++++++++++++++++++++++++---------------- scripts/status.js | 16 ++++++-------- 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/scripts/big_bg_status.js b/scripts/big_bg_status.js index 8a99097..227eb21 100644 --- a/scripts/big_bg_status.js +++ b/scripts/big_bg_status.js @@ -39,6 +39,9 @@ var displayConfig = require('/root/src/openaps-menu/config/display.json'); displayConfig.i2cBus = i2cBus; var display = require('/root/src/openaps-menu/lib/display/ssd1306')(displayConfig); +//dim the display +display.oled.dimDisplay(true); + //Parse all the .json files we need try { var profile = JSON.parse(fs.readFileSync("/root/myopenaps/settings/profile.json")); @@ -54,13 +57,14 @@ try { if(batterylevel) { //Process and display battery gauge - display.oled.drawLine(115, 57, 127, 57, 1); //top - display.oled.drawLine(115, 63, 127, 63, 1); //bottom - display.oled.drawLine(115, 57, 115, 63, 1); //left - display.oled.drawLine(127, 57, 127, 63, 1); //right - display.oled.drawLine(114, 59, 114, 61, 1); //iconify - var batt = Math.round(127 - (batterylevel.battery / 10)); - display.oled.fillRect(batt, 58, 126, 62, 1); //fill battery gauge + display.oled.drawLine(116, 57, 127, 57, 1, false); //top + display.oled.drawLine(116, 63, 127, 63, 1, false); //bottom + display.oled.drawLine(116, 57, 116, 63, 1, false); //left + display.oled.drawLine(127, 57, 127, 63, 1, false); //right + display.oled.drawLine(115, 59, 115, 61, 1, false); //iconify + var battRect = Math.round(batterylevel.battery / 10); + var battX = (127 - battRect); + display.oled.fillRect(battX, 58, battRect, 5, 1, false); //fill battery gauge } try { @@ -93,13 +97,13 @@ if (bg[0].delta) { //display BG number, add plus sign if delta is positive display.oled.setCursor(0,0); if (delta >= 0) { - display.oled.writeString(font, 3, ""+convert_bg(bg[0].glucose, profile), 1, true); - display.oled.writeString(font, 1, "+"+stripLeadingZero(convert_bg(delta, profile)), 1, true); - display.oled.writeString(font, 2, " "+minutes+"m", 1, true); + display.oled.writeString(font, 3, ""+convert_bg(bg[0].glucose, profile), 1, true, false); + display.oled.writeString(font, 1, "+"+stripLeadingZero(convert_bg(delta, profile)), 1, true, false); + display.oled.writeString(font, 2, " "+minutes+"m", 1, true, false); } else { - display.oled.writeString(font, 3, ""+convert_bg(bg[0].glucose, profile), 1, true); - display.oled.writeString(font, 1, ""+stripLeadingZero(convert_bg(delta, profile)), 1, true); - display.oled.writeString(font, 2, " "+minutes+"m", 1, true); + display.oled.writeString(font, 3, ""+convert_bg(bg[0].glucose, profile), 1, true, false); + display.oled.writeString(font, 1, ""+stripLeadingZero(convert_bg(delta, profile)), 1, true, false); + display.oled.writeString(font, 2, " "+minutes+"m", 1, true, false); } //calculate timeago for last successful loop @@ -112,7 +116,7 @@ min = (min < 10 ? "0" : "") + min; //display last loop time display.oled.setCursor(0,57); -display.oled.writeString(font, 1, "Last loop at: "+hour+":"+min, 1, true); +display.oled.writeString(font, 1, "Last loop at: "+hour+":"+min, 1, true, false); //files for IOB and COB: try { @@ -129,8 +133,14 @@ try { //parse and render COB/IOB display.oled.setCursor(0,23); -display.oled.writeString(font, 1, "IOB:", 1, true); -display.oled.writeString(font, 2, " "+iob[0].iob+'U', 1, true); +display.oled.writeString(font, 1, "IOB:", 1, true, false); +display.oled.writeString(font, 2, " "+iob[0].iob+'U', 1, true, false); display.oled.setCursor(0,39); -display.oled.writeString(font, 1, "COB:", 1, true); -display.oled.writeString(font, 2, " "+cob.mealCOB+'g', 1, true); +display.oled.writeString(font, 1, "COB:", 1, true, false); +display.oled.writeString(font, 2, " "+cob.mealCOB+'g', 1, true, false); + +//display everything in the buffer +display.oled.update(); + +//fandomly invert display to evenly wear the OLED diodes +display.oled.invertDisplay((endDate % 2 == 1)); diff --git a/scripts/status.js b/scripts/status.js index ba98c95..637771c 100644 --- a/scripts/status.js +++ b/scripts/status.js @@ -58,13 +58,14 @@ try { if(batterylevel) { //Process and display battery gauge - display.oled.drawLine(115, 57, 127, 57, 1, false); //top - display.oled.drawLine(115, 63, 127, 63, 1, false); //bottom - display.oled.drawLine(115, 57, 115, 63, 1, false); //left + display.oled.drawLine(116, 57, 127, 57, 1, false); //top + display.oled.drawLine(116, 63, 127, 63, 1, false); //bottom + display.oled.drawLine(116, 57, 116, 63, 1, false); //left display.oled.drawLine(127, 57, 127, 63, 1, false); //right - display.oled.drawLine(114, 59, 114, 61, 1, false); //iconify - var batt = Math.round(127 - (batterylevel.battery / 10)); - display.oled.fillRect(batt, 58, 126, 62, 1, false); //fill battery gauge + display.oled.drawLine(115, 59, 115, 61, 1, false); //iconify + var battRect = Math.round(batterylevel.battery / 10); + var battX = (127 - battRect); + display.oled.fillRect(battX, 58, battRect, 5, 1, false); //fill battery gauge } //Create and render clock @@ -212,6 +213,3 @@ display.oled.update(); //randomly invert display to evenly wear the OLED diodes display.oled.invertDisplay((endDate % 2 == 1)); - - - From 4de7a5ecf170def3f4142a60af9832108610815a Mon Sep 17 00:00:00 2001 From: Jon Cluck Date: Sun, 15 Jul 2018 11:20:39 -0400 Subject: [PATCH 07/35] Update README.md --- README.md | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/README.md b/README.md index f2278e5..e66ca9e 100644 --- a/README.md +++ b/README.md @@ -1,20 +1 @@ -# openaps-menu - -This is the repository holding the menu-based software code, which you may choose to add to an Explorer HAT or other screen-based rig in order to visualize and enter information into a Pi-based #OpenAPS rig. - -See [here](https://github.com/EnhancedRadioDevices/Explorer-HAT) for more details on the Explorer HAT hardware. - -## Example screen outputs (Note: these are examples. The latest code may yield different menu items and screen displays) - -### Status screen: - -![Status screen](https://github.com/openaps/openaps-menu/blob/master/images/status.JPG) - -### Graph: - -![Graph visual on screen](https://github.com/openaps/openaps-menu/blob/master/images/graph.JPG) - - - - - +This repo contains customizations for Jon's setup, not everything will work for you. Please use https://github.com/openaps/openaps-menu! From a789f53d3446a5023db94ac52c76cfdb843b5fc3 Mon Sep 17 00:00:00 2001 From: Jon Cluck Date: Sun, 15 Jul 2018 13:42:28 -0400 Subject: [PATCH 08/35] improvements, bugfixes, and exit on display malfunction --- scripts/big_bg_status.js | 9 +- scripts/status.js | 179 ++++++++++++++++++++++----------------- 2 files changed, 107 insertions(+), 81 deletions(-) diff --git a/scripts/big_bg_status.js b/scripts/big_bg_status.js index 227eb21..877c77a 100644 --- a/scripts/big_bg_status.js +++ b/scripts/big_bg_status.js @@ -37,7 +37,14 @@ function stripLeadingZero(value) // setup the display var displayConfig = require('/root/src/openaps-menu/config/display.json'); displayConfig.i2cBus = i2cBus; -var display = require('/root/src/openaps-menu/lib/display/ssd1306')(displayConfig); + +//check to see if the display works, exit with error code if it doesn't +try { + var display = require('/root/src/openaps-menu/lib/display/ssd1306')(displayConfig); +} catch (e) { + console.error("Could not set up display:\n", e); + process.exit(1); +} //dim the display display.oled.dimDisplay(true); diff --git a/scripts/status.js b/scripts/status.js index 637771c..2ed2e37 100644 --- a/scripts/status.js +++ b/scripts/status.js @@ -35,10 +35,17 @@ function stripLeadingZero(value) return value.toString().replace( re, '$1'); } -// setup the display +//setup the display var displayConfig = require('/root/src/openaps-menu/config/display.json'); displayConfig.i2cBus = i2cBus; -var display = require('/root/src/openaps-menu/lib/display/ssd1306')(displayConfig); + +//check to see if the display works, exit with error code if it doesn't +try { + var display = require('/root/src/openaps-menu/lib/display/ssd1306')(displayConfig); +} catch (e) { + console.error("Could not set up display:\n", e); + process.exit(1); +} //dim the display display.oled.dimDisplay(true); @@ -47,7 +54,7 @@ display.oled.dimDisplay(true); try { var profile = JSON.parse(fs.readFileSync("/root/myopenaps/settings/profile.json")); } catch (e) { - // Note: profile.json is optional as it's only needed for mmol conversion for now. Print an error, but not return + // Note: profile.json is optional as it's only needed for mmol conversion and target lines. Print an error, but not return console.error("Could not parse profile.json: ", e); } try { @@ -55,6 +62,36 @@ try { } catch (e) { console.error("Could not parse edison-battery.json: ", e); } +try { + var status = JSON.parse(fs.readFileSync("/root/myopenaps/monitor/status.json")); +} catch (e) { + console.error("Could not parse status.json: ", e); +} +try { + var suggested = JSON.parse(fs.readFileSync("/root/myopenaps/enact/suggested.json")); +} catch (e) { + return console.error("Could not parse suggested.json: ", e); +} +try { + var bg = JSON.parse(fs.readFileSync("/root/myopenaps/monitor/glucose.json")); +} catch (e) { + return console.error("Could not parse glucose.json: ", e); +} +try { + var temp = JSON.parse(fs.readFileSync("/root/myopenaps/monitor/last_temp_basal.json")); +} catch (e) { + return console.error("Could not parse last_temp_basal.json: ", e); +} +try { + var iob = JSON.parse(fs.readFileSync("/root/myopenaps/monitor/iob.json")); +} catch (e) { + return console.error("Could not parse iob.json: ", e); +} +try { + var cob = JSON.parse(fs.readFileSync("/root/myopenaps/monitor/meal.json")); +} catch (e) { + return console.error("Could not parse meal.json: ", e); +} if(batterylevel) { //Process and display battery gauge @@ -63,60 +100,71 @@ if(batterylevel) { display.oled.drawLine(116, 57, 116, 63, 1, false); //left display.oled.drawLine(127, 57, 127, 63, 1, false); //right display.oled.drawLine(115, 59, 115, 61, 1, false); //iconify - var battRect = Math.round(batterylevel.battery / 10); - var battX = (127 - battRect); - display.oled.fillRect(battX, 58, battRect, 5, 1, false); //fill battery gauge + var batt = Math.round(batterylevel.battery / 10); + display.oled.fillRect(127-batt, 58, batt, 5, 1, false); //fill battery gauge } -//Create and render clock -function displayClock() { - var date = new Date(); - var hour = date.getHours(); - hour = (hour < 10 ? "0" : "") + hour; - var min = date.getMinutes(); - min = (min < 10 ? "0" : "") + min; - display.oled.setCursor(83, 57); - display.oled.writeString(font, 1, hour+":"+min, 1, true, false); +//render clock +var clockDate = new Date(); +var clockHour = clockDate.getHours(); +clockHour = (clockHour < 10 ? "0" : "") + clockHour; +var clockMin = clockDate.getMinutes(); +clockMin = (clockMin < 10 ? "0" : "") + clockMin; +display.oled.setCursor(83, 57); +display.oled.writeString(font, 1, clockHour+":"+clockMin, 1, true, false); + +//display reason for not looping and move the graph to make room for the message! +var notLoopingReason = suggested.reason; +display.oled.setCursor(0,16); +var yOffset = 0; +if (status.suspended == true) { + display.oled.writeString(font, 1, "PUMP SUSPENDED", 1, true, false); + yOffset = 3; +} +else if (status.bolusing == true) { + display.oled.writeString(font, 1, "PUMP BOLULSING", 1, true, false); + yOffset = 3; +} +else if (notLoopingReason.includes("CGM is calibrating")) { + display.oled.writeString(font, 1, "CGM calib./???/noisy", 1, true, false); + yOffset = 3; +} +else if (notLoopingReason.includes("CGM data is unchanged")) { + display.oled.writeString(font, 1, "CGM is unchanged", 1, true, false); + yOffset = 3; } -displayClock(); +//bg graph axes +display.oled.drawLine(5, 51+yOffset, 5, 21+yOffset, 1, false); +display.oled.drawLine(5, 51+yOffset, 127, 51+yOffset, 1, false); -//bg graph -display.oled.drawLine(5, 51, 5, 21, 1, false); -display.oled.drawLine(5, 51, 127, 51, 1, false); -//targets high and low -display.oled.drawLine(2, 30, 5, 30, 1, false); -display.oled.drawLine(2, 40, 5, 40, 1, false); +//display targets high and low +var targetLow = Math.round( (21+yOffset) - ( ( profile.bg_targets.targets[0].low - 250 ) / 8 ) ); +var targetHigh = Math.round( (21+yOffset) - ( ( profile.bg_targets.targets[0].high - 250 ) / 8 ) ); + +display.oled.drawLine(2, targetHigh, 5, targetHigh, 1, false); +display.oled.drawLine(2, targetLow, 5, targetLow, 1, false); -try { - var suggested = JSON.parse(fs.readFileSync("/root/myopenaps/enact/suggested.json")); -} catch (e) { - return console.error("Could not parse suggested.json: ", e); -} -try { - var bg = JSON.parse(fs.readFileSync("/root/myopenaps/monitor/glucose.json")); -} catch (e) { - return console.error("Could not parse glucose.json: ", e); -} //render BG graph var numBGs = (suggested.predBGs != undefined) ? (72) : (120); //fill the whole graph with BGs if there are no predictions var date = new Date(); var zerotime = date.getTime() - ((numBGs * 5) * 600); var zero_x = numBGs + 5; -var iterMax = Math.min(numBGs, bg.length) -for (var i = 0; i < iterMax; ++i) { - var x = zero_x + Math.round(((((bg[i].date - zerotime)/1000)/60)/5)); - var y = Math.round( 21 - ( ( bg[i].glucose - 250 ) / 8 ) ); - //left and right boundaries - if ( x < 5 ) x = 5; - if ( x > 127 ) x = 127; - //upper and lower boundaries - if ( y < 21 ) y = 21; - if ( y > 51 ) y = 51; - display.oled.drawPixel([x, y, 1, false]); - // if we have multiple data points within 3m, look further back to fill in the graph - if ( bg[i-1] && bg[i-1].date - bg[i].date < 200000 ) { - numBGs++; +for (var i = 0; i < numBGs; i++) { + if (bg[i] != null) { + var x = zero_x + Math.round(((((bg[i].date - zerotime)/1000)/60)/5)); + var y = Math.round( (21+yOffset) - ( ( bg[i].glucose - 250 ) / 8 ) ); + //left and right boundaries + if ( x < 5 ) x = 5; + if ( x > 127 ) x = 127; + //upper and lower boundaries + if ( y < (21+yOffset) ) y = (21+yOffset); + if ( y > (51+yOffset) ) y = (51+yOffset); + display.oled.drawPixel([x, y, 1, false]); + // if we have multiple data points within 3m, look further back to fill in the graph + if ( bg[i-1] && bg[i-1].date - bg[i].date < 200000 ) { + numBGs++; + } } } @@ -124,18 +172,18 @@ for (var i = 0; i < iterMax; ++i) { if (suggested.predBGs != undefined) { //render line between actual BG and predicted x = zero_x + 1; - display.oled.drawLine(x, 51, x, 21, 1, false); + display.oled.drawLine(x, 51+yOffset, x, 21+yOffset, 1, false); //render predictions var predictions = [suggested.predBGs.IOB, suggested.predBGs.ZT, suggested.predBGs.UAM, suggested.predBGs.COB]; for (i = 0; i <= 48; i++) { x++; for(var n = 0; n <=3 && (predictions[n] != undefined); n++) { - y = Math.round( 21 - ( (predictions[n][i] - 250 ) / 8) ); + y = Math.round( (21+yOffset) - ( (predictions[n][i] - 250 ) / 8) ); //right boundary if ( x > 127 ) x = 127; //upper and lower boundaries - if ( y < 21 ) y = 21; - if ( y > 51 ) y = 51; + if ( y < (21+yOffset) ) y = (21+yOffset); + if ( y > (51+yOffset) ) y = (51+yOffset); display.oled.drawPixel([x, y, 1, false]); } } @@ -164,12 +212,6 @@ if (delta >= 0) { display.oled.writeString(font, 1, "BG:"+convert_bg(bg[0].glucose, profile)+""+stripLeadingZero(convert_bg(delta, profile))+" "+minutes+"m", 1, true, false); } -try { - var temp = JSON.parse(fs.readFileSync("/root/myopenaps/monitor/last_temp_basal.json")); -} catch (e) { - return console.error("Could not parse last_temp_basal.json: ", e); -} - //calculate timeago for status var stats = fs.statSync("/root/myopenaps/monitor/last_temp_basal.json"); startDate = new Date(stats.mtime); @@ -181,32 +223,9 @@ display.oled.setCursor(0,0); var tempRate = Math.round(temp.rate*10)/10; display.oled.writeString(font, 1, "TB: "+temp.duration+'m '+tempRate+'U/h '+'('+minutes+'m ago)', 1, false); -try { - var iob = JSON.parse(fs.readFileSync("/root/myopenaps/monitor/iob.json")); -} catch (e) { - return console.error("Could not parse iob.json: ", e); -} - -try { - var cob = JSON.parse(fs.readFileSync("/root/myopenaps/monitor/meal.json")); -} catch (e) { - return console.error("Could not parse meal.json: ", e); -} //parse and render COB/IOB display.oled.setCursor(0,8); -display.oled.writeString(font, 1, "COB: "+cob.mealCOB+"g IOB: "+iob[0].iob+'U', 1, true, false); - -//if the pump is suspended, display a message to that effect -try { - var pumpstatus = JSON.parse(fs.readFileSync("/root/myopenaps/monitor/status.json")); -} catch (e) { - console.error("Could not parse status.json: ", e); -} - -if ( pumpstatus.suspended == true ) { - display.oled.setCursor(28,24); - display.oled.writeString(font, 1, "PUMP SUSPENDED", 1, true, false); -} +display.oled.writeString(font, 1, "COB: "+cob.mealCOB+"g IOB: "+iob[0].iob+'U', 1, false, false); //display everything in the buffer display.oled.update(); From 6ee63b1053a31d13079a6d1436490b2c8442f98f Mon Sep 17 00:00:00 2001 From: Jon Cluck Date: Sun, 15 Jul 2018 14:00:08 -0400 Subject: [PATCH 09/35] Clearer error messages --- scripts/big_bg_status.js | 55 ++++++++++++++++++---------------------- scripts/status.js | 18 ++++++------- 2 files changed, 34 insertions(+), 39 deletions(-) diff --git a/scripts/big_bg_status.js b/scripts/big_bg_status.js index 877c77a..70ce4cd 100644 --- a/scripts/big_bg_status.js +++ b/scripts/big_bg_status.js @@ -42,7 +42,7 @@ displayConfig.i2cBus = i2cBus; try { var display = require('/root/src/openaps-menu/lib/display/ssd1306')(displayConfig); } catch (e) { - console.error("Could not set up display:\n", e); + console.error("Could not set up HAT display:\n", e); process.exit(1); } @@ -54,12 +54,32 @@ try { var profile = JSON.parse(fs.readFileSync("/root/myopenaps/settings/profile.json")); } catch (e) { // Note: profile.json is optional as it's only needed for mmol conversion for now. Print an error, but not return - console.error("Could not parse profile.json: ", e); + console.error("Status screen display error: could not parse profile.json: ", e); } try { var batterylevel = JSON.parse(fs.readFileSync("/root/myopenaps/monitor/edison-battery.json")); } catch (e) { - console.error("Could not parse edison-battery.json: ", e); + console.error("Status screen display error: could not parse edison-battery.json: ", e); +} +try { + var suggested = JSON.parse(fs.readFileSync("/root/myopenaps/enact/suggested.json")); +} catch (e) { + return console.error("Status screen display error: could not parse suggested.json: ", e); +} +try { + var bg = JSON.parse(fs.readFileSync("/root/myopenaps/monitor/glucose.json")); +} catch (e) { + return console.error("Status screen display error: could not parse glucose.json: ", e); +} +try { + var iob = JSON.parse(fs.readFileSync("/root/myopenaps/monitor/iob.json")); +} catch (e) { + return console.error("Status screen display error: could not parse iob.json: ", e); +} +try { + var cob = JSON.parse(fs.readFileSync("/root/myopenaps/monitor/meal.json")); +} catch (e) { + return console.error("Status screen display error: could not parse meal.json: ", e); } if(batterylevel) { @@ -69,20 +89,8 @@ if(batterylevel) { display.oled.drawLine(116, 57, 116, 63, 1, false); //left display.oled.drawLine(127, 57, 127, 63, 1, false); //right display.oled.drawLine(115, 59, 115, 61, 1, false); //iconify - var battRect = Math.round(batterylevel.battery / 10); - var battX = (127 - battRect); - display.oled.fillRect(battX, 58, battRect, 5, 1, false); //fill battery gauge -} - -try { - var suggested = JSON.parse(fs.readFileSync("/root/myopenaps/enact/suggested.json")); -} catch (e) { - return console.error("Could not parse suggested.json: ", e); -} -try { - var bg = JSON.parse(fs.readFileSync("/root/myopenaps/monitor/glucose.json")); -} catch (e) { - return console.error("Could not parse glucose.json: ", e); + var batt = Math.round(batterylevel.battery / 10); + display.oled.fillRect(127-batt, 58, batt, 5, 1, false); //fill battery gauge } //calculate timeago for BG @@ -125,19 +133,6 @@ min = (min < 10 ? "0" : "") + min; display.oled.setCursor(0,57); display.oled.writeString(font, 1, "Last loop at: "+hour+":"+min, 1, true, false); -//files for IOB and COB: -try { - var iob = JSON.parse(fs.readFileSync("/root/myopenaps/monitor/iob.json")); -} catch (e) { - return console.error("Could not parse iob.json: ", e); -} - -try { - var cob = JSON.parse(fs.readFileSync("/root/myopenaps/monitor/meal.json")); -} catch (e) { - return console.error("Could not parse meal.json: ", e); -} - //parse and render COB/IOB display.oled.setCursor(0,23); display.oled.writeString(font, 1, "IOB:", 1, true, false); diff --git a/scripts/status.js b/scripts/status.js index 2ed2e37..a5637a7 100644 --- a/scripts/status.js +++ b/scripts/status.js @@ -43,7 +43,7 @@ displayConfig.i2cBus = i2cBus; try { var display = require('/root/src/openaps-menu/lib/display/ssd1306')(displayConfig); } catch (e) { - console.error("Could not set up display:\n", e); + console.error("Could not set up HAT display:\n", e); process.exit(1); } @@ -55,42 +55,42 @@ try { var profile = JSON.parse(fs.readFileSync("/root/myopenaps/settings/profile.json")); } catch (e) { // Note: profile.json is optional as it's only needed for mmol conversion and target lines. Print an error, but not return - console.error("Could not parse profile.json: ", e); + console.error("Status screen display error: could not parse profile.json: ", e); } try { var batterylevel = JSON.parse(fs.readFileSync("/root/myopenaps/monitor/edison-battery.json")); } catch (e) { - console.error("Could not parse edison-battery.json: ", e); + console.error("Status screen display error: could not parse edison-battery.json: ", e); } try { var status = JSON.parse(fs.readFileSync("/root/myopenaps/monitor/status.json")); } catch (e) { - console.error("Could not parse status.json: ", e); + console.error("Status screen display error: could not parse status.json: ", e); } try { var suggested = JSON.parse(fs.readFileSync("/root/myopenaps/enact/suggested.json")); } catch (e) { - return console.error("Could not parse suggested.json: ", e); + return console.error("Status screen display error: could not parse suggested.json: ", e); } try { var bg = JSON.parse(fs.readFileSync("/root/myopenaps/monitor/glucose.json")); } catch (e) { - return console.error("Could not parse glucose.json: ", e); + return console.error("Status screen display error: could not parse glucose.json: ", e); } try { var temp = JSON.parse(fs.readFileSync("/root/myopenaps/monitor/last_temp_basal.json")); } catch (e) { - return console.error("Could not parse last_temp_basal.json: ", e); + return console.error("Status screen display error: could not parse last_temp_basal.json: ", e); } try { var iob = JSON.parse(fs.readFileSync("/root/myopenaps/monitor/iob.json")); } catch (e) { - return console.error("Could not parse iob.json: ", e); + return console.error("Status screen display error: could not parse iob.json: ", e); } try { var cob = JSON.parse(fs.readFileSync("/root/myopenaps/monitor/meal.json")); } catch (e) { - return console.error("Could not parse meal.json: ", e); + return console.error("Status screen display error: could not parse meal.json: ", e); } if(batterylevel) { From 75bc4ac024881f68d236bb93a63480c1e20eae7e Mon Sep 17 00:00:00 2001 From: Jon Cluck Date: Sun, 15 Jul 2018 15:29:53 -0400 Subject: [PATCH 10/35] comments --- scripts/status.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/scripts/status.js b/scripts/status.js index a5637a7..d591c6a 100644 --- a/scripts/status.js +++ b/scripts/status.js @@ -93,8 +93,8 @@ try { return console.error("Status screen display error: could not parse meal.json: ", e); } +//Process and display battery gauge, if we can if(batterylevel) { - //Process and display battery gauge display.oled.drawLine(116, 57, 127, 57, 1, false); //top display.oled.drawLine(116, 63, 127, 63, 1, false); //bottom display.oled.drawLine(116, 57, 116, 63, 1, false); //left @@ -113,7 +113,7 @@ clockMin = (clockMin < 10 ? "0" : "") + clockMin; display.oled.setCursor(83, 57); display.oled.writeString(font, 1, clockHour+":"+clockMin, 1, true, false); -//display reason for not looping and move the graph to make room for the message! +//display reason for not looping and move the graph to make room for the message and to draw attention to it! var notLoopingReason = suggested.reason; display.oled.setCursor(0,16); var yOffset = 0; @@ -130,9 +130,11 @@ else if (notLoopingReason.includes("CGM is calibrating")) { yOffset = 3; } else if (notLoopingReason.includes("CGM data is unchanged")) { - display.oled.writeString(font, 1, "CGM is unchanged", 1, true, false); + display.oled.writeString(font, 1, "CGM data unchanged", 1, true, false); yOffset = 3; } +//add more on-screen warnings/messages here, maybe some special ones for xdrip-js users + //bg graph axes display.oled.drawLine(5, 51+yOffset, 5, 21+yOffset, 1, false); @@ -218,16 +220,16 @@ startDate = new Date(stats.mtime); endDate = new Date(); minutes = Math.round(( (endDate.getTime() - startDate.getTime()) / 1000) / 60); -//render current temp basal +//display current temp basal display.oled.setCursor(0,0); var tempRate = Math.round(temp.rate*10)/10; display.oled.writeString(font, 1, "TB: "+temp.duration+'m '+tempRate+'U/h '+'('+minutes+'m ago)', 1, false); -//parse and render COB/IOB +//display COB/IOB line display.oled.setCursor(0,8); display.oled.writeString(font, 1, "COB: "+cob.mealCOB+"g IOB: "+iob[0].iob+'U', 1, false, false); -//display everything in the buffer +//write to the screen display.oled.update(); //randomly invert display to evenly wear the OLED diodes From 67fcc1f5c4f07c44dcdfdc9f26a5a88181c9aded Mon Sep 17 00:00:00 2001 From: Jon Cluck Date: Sun, 15 Jul 2018 16:32:08 -0400 Subject: [PATCH 11/35] fixing error handling when only writing to the screen once --- scripts/status.js | 239 +++++++++++++++++++++++----------------------- 1 file changed, 122 insertions(+), 117 deletions(-) diff --git a/scripts/status.js b/scripts/status.js index d591c6a..f407887 100644 --- a/scripts/status.js +++ b/scripts/status.js @@ -43,8 +43,7 @@ displayConfig.i2cBus = i2cBus; try { var display = require('/root/src/openaps-menu/lib/display/ssd1306')(displayConfig); } catch (e) { - console.error("Could not set up HAT display:\n", e); - process.exit(1); + return console.error("Could not set up HAT display:\n", e); } //dim the display @@ -54,7 +53,6 @@ display.oled.dimDisplay(true); try { var profile = JSON.parse(fs.readFileSync("/root/myopenaps/settings/profile.json")); } catch (e) { - // Note: profile.json is optional as it's only needed for mmol conversion and target lines. Print an error, but not return console.error("Status screen display error: could not parse profile.json: ", e); } try { @@ -70,30 +68,31 @@ try { try { var suggested = JSON.parse(fs.readFileSync("/root/myopenaps/enact/suggested.json")); } catch (e) { - return console.error("Status screen display error: could not parse suggested.json: ", e); + console.error("Status screen display error: could not parse suggested.json: ", e); } try { var bg = JSON.parse(fs.readFileSync("/root/myopenaps/monitor/glucose.json")); } catch (e) { - return console.error("Status screen display error: could not parse glucose.json: ", e); + console.error("Status screen display error: could not parse glucose.json: ", e); } try { var temp = JSON.parse(fs.readFileSync("/root/myopenaps/monitor/last_temp_basal.json")); + var stats = fs.statSync("/root/myopenaps/monitor/last_temp_basal.json"); } catch (e) { - return console.error("Status screen display error: could not parse last_temp_basal.json: ", e); + console.error("Status screen display error: could not parse last_temp_basal.json: ", e); } try { var iob = JSON.parse(fs.readFileSync("/root/myopenaps/monitor/iob.json")); } catch (e) { - return console.error("Status screen display error: could not parse iob.json: ", e); + console.error("Status screen display error: could not parse iob.json: ", e); } try { var cob = JSON.parse(fs.readFileSync("/root/myopenaps/monitor/meal.json")); } catch (e) { - return console.error("Status screen display error: could not parse meal.json: ", e); + console.error("Status screen display error: could not parse meal.json: ", e); } -//Process and display battery gauge, if we can +//Process and display battery gauge if(batterylevel) { display.oled.drawLine(116, 57, 127, 57, 1, false); //top display.oled.drawLine(116, 63, 127, 63, 1, false); //bottom @@ -104,130 +103,136 @@ if(batterylevel) { display.oled.fillRect(127-batt, 58, batt, 5, 1, false); //fill battery gauge } -//render clock -var clockDate = new Date(); -var clockHour = clockDate.getHours(); -clockHour = (clockHour < 10 ? "0" : "") + clockHour; -var clockMin = clockDate.getMinutes(); -clockMin = (clockMin < 10 ? "0" : "") + clockMin; -display.oled.setCursor(83, 57); -display.oled.writeString(font, 1, clockHour+":"+clockMin, 1, true, false); - //display reason for not looping and move the graph to make room for the message and to draw attention to it! -var notLoopingReason = suggested.reason; -display.oled.setCursor(0,16); var yOffset = 0; -if (status.suspended == true) { - display.oled.writeString(font, 1, "PUMP SUSPENDED", 1, true, false); - yOffset = 3; -} -else if (status.bolusing == true) { - display.oled.writeString(font, 1, "PUMP BOLULSING", 1, true, false); - yOffset = 3; -} -else if (notLoopingReason.includes("CGM is calibrating")) { - display.oled.writeString(font, 1, "CGM calib./???/noisy", 1, true, false); - yOffset = 3; -} -else if (notLoopingReason.includes("CGM data is unchanged")) { - display.oled.writeString(font, 1, "CGM data unchanged", 1, true, false); - yOffset = 3; -} +if (status && suggested) { + var notLoopingReason = suggested.reason; + display.oled.setCursor(0,16); + if (status.suspended == true) { + display.oled.writeString(font, 1, "PUMP SUSPENDED", 1, true, false); + yOffset = 3; + } + else if (status.bolusing == true) { + display.oled.writeString(font, 1, "PUMP BOLULSING", 1, true, false); + yOffset = 3; + } + else if (notLoopingReason.includes("CGM is calibrating")) { + display.oled.writeString(font, 1, "CGM calib./???/noisy", 1, true, false); + yOffset = 3; + } + else if (notLoopingReason.includes("CGM data is unchanged")) { + display.oled.writeString(font, 1, "CGM data unchanged", 1, true, false); + yOffset = 3; + } //add more on-screen warnings/messages here, maybe some special ones for xdrip-js users +} +//display targets high and low +if (profile) { + var targetLow = Math.round( (21+yOffset) - ( ( profile.bg_targets.targets[0].low - 250 ) / 8 ) ); + var targetHigh = Math.round( (21+yOffset) - ( ( profile.bg_targets.targets[0].high - 250 ) / 8 ) ); + display.oled.drawLine(2, targetHigh, 5, targetHigh, 1, false); + display.oled.drawLine(2, targetLow, 5, targetLow, 1, false); +} + +if (bg) { + //rander BG graph + var numBGs = (suggested.predBGs != undefined) ? (72) : (120); //fill the whole graph with BGs if there are no predictions + var date = new Date(); + var zerotime = date.getTime() - ((numBGs * 5) * 600); + var zero_x = numBGs + 5; + for (var i = 0; i < numBGs; i++) { + if (bg[i] != null) { + var x = zero_x + Math.round(((((bg[i].date - zerotime)/1000)/60)/5)); + var y = Math.round( (21+yOffset) - ( ( bg[i].glucose - 250 ) / 8 ) ); + //left and right boundaries + if ( x < 5 ) x = 5; + if ( x > 127 ) x = 127; + //upper and lower boundaries + if ( y < (21+yOffset) ) y = (21+yOffset); + if ( y > (51+yOffset) ) y = (51+yOffset); + display.oled.drawPixel([x, y, 1, false]); + // if we have multiple data points within 3m, look further back to fill in the graph + if ( bg[i-1] && bg[i-1].date - bg[i].date < 200000 ) { + numBGs++; + } + } + } -//bg graph axes -display.oled.drawLine(5, 51+yOffset, 5, 21+yOffset, 1, false); -display.oled.drawLine(5, 51+yOffset, 127, 51+yOffset, 1, false); + //calculate timeago for BG + var startDate = new Date(bg[0].date); + var endDate = new Date(); + var minutes = Math.round(( (endDate.getTime() - startDate.getTime()) / 1000) / 60); + if (bg[0].delta) { + var delta = Math.round(bg[0].delta); + } else if (bg[1] && bg[0].date - bg[1].date > 200000 ) { + var delta = Math.round(bg[0].glucose - bg[1].glucose); + } else if (bg[2] && bg[0].date - bg[2].date > 200000 ) { + var delta = Math.round(bg[0].glucose - bg[2].glucose); + } else if (bg[3] && bg[0].date - bg[3].date > 200000 ) { + var delta = Math.round(bg[0].glucose - bg[3].glucose); + } else { + var delta = 0; + } + //display BG number and timeago, add plus sign if delta is positive + display.oled.setCursor(0,57); + if (delta >= 0) { + display.oled.writeString(font, 1, "BG:"+convert_bg(bg[0].glucose, profile)+"+"+stripLeadingZero(convert_bg(delta, profile))+" "+minutes+"m", 1, true, false); + } else { + display.oled.writeString(font, 1, "BG:"+convert_bg(bg[0].glucose, profile)+""+stripLeadingZero(convert_bg(delta, profile))+" "+minutes+"m", 1, true, false); + } +} -//display targets high and low -var targetLow = Math.round( (21+yOffset) - ( ( profile.bg_targets.targets[0].low - 250 ) / 8 ) ); -var targetHigh = Math.round( (21+yOffset) - ( ( profile.bg_targets.targets[0].high - 250 ) / 8 ) ); - -display.oled.drawLine(2, targetHigh, 5, targetHigh, 1, false); -display.oled.drawLine(2, targetLow, 5, targetLow, 1, false); - -//render BG graph -var numBGs = (suggested.predBGs != undefined) ? (72) : (120); //fill the whole graph with BGs if there are no predictions -var date = new Date(); -var zerotime = date.getTime() - ((numBGs * 5) * 600); -var zero_x = numBGs + 5; -for (var i = 0; i < numBGs; i++) { - if (bg[i] != null) { - var x = zero_x + Math.round(((((bg[i].date - zerotime)/1000)/60)/5)); - var y = Math.round( (21+yOffset) - ( ( bg[i].glucose - 250 ) / 8 ) ); - //left and right boundaries - if ( x < 5 ) x = 5; +//render predictions, but only if we have them +if (suggested && suggested.predBGs != undefined) { + //render line between actual BG and predicted + x = zero_x + 1; + display.oled.drawLine(x, 51+yOffset, x, 21+yOffset, 1, false); + //render predictions + var predictions = [suggested.predBGs.IOB, suggested.predBGs.ZT, suggested.predBGs.UAM, suggested.predBGs.COB]; + for (i = 0; i <= 48; i++) { + x++; + for(var n = 0; n <=3 && (predictions[n] != undefined); n++) { + y = Math.round( (21+yOffset) - ( (predictions[n][i] - 250 ) / 8) ); + //right boundary if ( x > 127 ) x = 127; //upper and lower boundaries if ( y < (21+yOffset) ) y = (21+yOffset); if ( y > (51+yOffset) ) y = (51+yOffset); display.oled.drawPixel([x, y, 1, false]); - // if we have multiple data points within 3m, look further back to fill in the graph - if ( bg[i-1] && bg[i-1].date - bg[i].date < 200000 ) { - numBGs++; } } } -//render predictions, only if we have them -if (suggested.predBGs != undefined) { - //render line between actual BG and predicted - x = zero_x + 1; - display.oled.drawLine(x, 51+yOffset, x, 21+yOffset, 1, false); - //render predictions - var predictions = [suggested.predBGs.IOB, suggested.predBGs.ZT, suggested.predBGs.UAM, suggested.predBGs.COB]; - for (i = 0; i <= 48; i++) { - x++; - for(var n = 0; n <=3 && (predictions[n] != undefined); n++) { - y = Math.round( (21+yOffset) - ( (predictions[n][i] - 250 ) / 8) ); - //right boundary - if ( x > 127 ) x = 127; - //upper and lower boundaries - if ( y < (21+yOffset) ) y = (21+yOffset); - if ( y > (51+yOffset) ) y = (51+yOffset); - display.oled.drawPixel([x, y, 1, false]); - } - } +//display current temp basal and how long ago it was set +if (stats && temp) { + startDate = new Date(stats.mtime); + endDate = new Date(); + minutes = Math.round(( (endDate.getTime() - startDate.getTime()) / 1000) / 60); + //display current temp basal + display.oled.setCursor(0,0); + var tempRate = Math.round(temp.rate*10)/10; + display.oled.writeString(font, 1, "TB: "+temp.duration+'m '+tempRate+'U/h '+'('+minutes+'m ago)', 1, false); +} + +//display COB and IOB on second line of the screen +if (iob && cob) { + display.oled.setCursor(0,8); + display.oled.writeString(font, 1, "COB: "+cob.mealCOB+"g IOB: "+iob[0].iob+'U', 1, false, false); } -//calculate timeago for BG -var startDate = new Date(bg[0].date); -var endDate = new Date(); -var minutes = Math.round(( (endDate.getTime() - startDate.getTime()) / 1000) / 60); -if (bg[0].delta) { - var delta = Math.round(bg[0].delta); -} else if (bg[1] && bg[0].date - bg[1].date > 200000 ) { - var delta = Math.round(bg[0].glucose - bg[1].glucose); -} else if (bg[2] && bg[0].date - bg[2].date > 200000 ) { - var delta = Math.round(bg[0].glucose - bg[2].glucose); -} else if (bg[3] && bg[0].date - bg[3].date > 200000 ) { - var delta = Math.round(bg[0].glucose - bg[3].glucose); -} else { - var delta = 0; -} - -//display BG number, add plus sign if delta is positive -display.oled.setCursor(0,57); -if (delta >= 0) { - display.oled.writeString(font, 1, "BG:"+convert_bg(bg[0].glucose, profile)+"+"+stripLeadingZero(convert_bg(delta, profile))+" "+minutes+"m", 1, true, false); -} else { - display.oled.writeString(font, 1, "BG:"+convert_bg(bg[0].glucose, profile)+""+stripLeadingZero(convert_bg(delta, profile))+" "+minutes+"m", 1, true, false); -} - -//calculate timeago for status -var stats = fs.statSync("/root/myopenaps/monitor/last_temp_basal.json"); -startDate = new Date(stats.mtime); -endDate = new Date(); -minutes = Math.round(( (endDate.getTime() - startDate.getTime()) / 1000) / 60); - -//display current temp basal -display.oled.setCursor(0,0); -var tempRate = Math.round(temp.rate*10)/10; -display.oled.writeString(font, 1, "TB: "+temp.duration+'m '+tempRate+'U/h '+'('+minutes+'m ago)', 1, false); - -//display COB/IOB line -display.oled.setCursor(0,8); -display.oled.writeString(font, 1, "COB: "+cob.mealCOB+"g IOB: "+iob[0].iob+'U', 1, false, false); + +//bg graph axes +display.oled.drawLine(5, 51+yOffset, 5, 21+yOffset, 1, false); +display.oled.drawLine(5, 51+yOffset, 127, 51+yOffset, 1, false); + +//render clock +var clockDate = new Date(); +var clockHour = clockDate.getHours(); +clockHour = (clockHour < 10 ? "0" : "") + clockHour; +var clockMin = clockDate.getMinutes(); +clockMin = (clockMin < 10 ? "0" : "") + clockMin; +display.oled.setCursor(83, 57); +display.oled.writeString(font, 1, clockHour+":"+clockMin, 1, true, false); //write to the screen display.oled.update(); From 3dd7e3960a16227a4d1238bf5ff8b3e3fa969766 Mon Sep 17 00:00:00 2001 From: Jon Cluck Date: Sun, 15 Jul 2018 17:41:46 -0400 Subject: [PATCH 12/35] bugfixes --- scripts/status.js | 87 ++++++++++++++++++++++++----------------------- 1 file changed, 44 insertions(+), 43 deletions(-) diff --git a/scripts/status.js b/scripts/status.js index f407887..a6b902e 100644 --- a/scripts/status.js +++ b/scripts/status.js @@ -2,12 +2,13 @@ 'use strict'; const i2c = require('i2c-bus'); -const path = require('path'); -const extend = require('extend'); -var os = require('os'); var fs = require('fs'); var font = require('oled-font-5x7'); var i2cBus = i2c.openSync(1); +const homeDir = require('os').homedir(); + +var openapsDir = "/root/myopenaps"; //if you're using a nonstandard OpenAPS directory, set that here +var evenOLEDwear = false; //if you want to prevent OLED burn-in symptoms by inverting the screen, set this to true // Rounds value to 'digits' decimal places function round(value, digits) @@ -36,58 +37,57 @@ function stripLeadingZero(value) } //setup the display -var displayConfig = require('/root/src/openaps-menu/config/display.json'); +var displayConfig = require(homeDir+'/src/openaps-menu/config/display.json'); displayConfig.i2cBus = i2cBus; //check to see if the display works, exit with error code if it doesn't try { - var display = require('/root/src/openaps-menu/lib/display/ssd1306')(displayConfig); + var display = require(homeDir+'/src/openaps-menu/lib/display/ssd1306')(displayConfig); } catch (e) { return console.error("Could not set up HAT display:\n", e); } -//dim the display -display.oled.dimDisplay(true); +display.oled.clearDisplay(false); //clear display buffer //Parse all the .json files we need try { - var profile = JSON.parse(fs.readFileSync("/root/myopenaps/settings/profile.json")); + var profile = JSON.parse(fs.readFileSync(openapsDir+"/settings/profile.json")); } catch (e) { console.error("Status screen display error: could not parse profile.json: ", e); } try { - var batterylevel = JSON.parse(fs.readFileSync("/root/myopenaps/monitor/edison-battery.json")); + var batterylevel = JSON.parse(fs.readFileSync(openapsDir+"/monitor/edison-battery.json")); } catch (e) { console.error("Status screen display error: could not parse edison-battery.json: ", e); } try { - var status = JSON.parse(fs.readFileSync("/root/myopenaps/monitor/status.json")); + var status = JSON.parse(fs.readFileSync(openapsDir+"/monitor/status.json")); } catch (e) { console.error("Status screen display error: could not parse status.json: ", e); } try { - var suggested = JSON.parse(fs.readFileSync("/root/myopenaps/enact/suggested.json")); + var suggested = JSON.parse(fs.readFileSync(openapsDir+"/enact/suggested.json")); } catch (e) { console.error("Status screen display error: could not parse suggested.json: ", e); } try { - var bg = JSON.parse(fs.readFileSync("/root/myopenaps/monitor/glucose.json")); + var bg = JSON.parse(fs.readFileSync(openapsDir+"/monitor/glucose.json")); } catch (e) { console.error("Status screen display error: could not parse glucose.json: ", e); } try { - var temp = JSON.parse(fs.readFileSync("/root/myopenaps/monitor/last_temp_basal.json")); - var stats = fs.statSync("/root/myopenaps/monitor/last_temp_basal.json"); + var temp = JSON.parse(fs.readFileSync(openapsDir+"/monitor/last_temp_basal.json")); + var statusStats = fs.statSync(openapsDir+"/monitor/last_temp_basal.json"); } catch (e) { console.error("Status screen display error: could not parse last_temp_basal.json: ", e); } try { - var iob = JSON.parse(fs.readFileSync("/root/myopenaps/monitor/iob.json")); + var iob = JSON.parse(fs.readFileSync(openapsDir+"/monitor/iob.json")); } catch (e) { console.error("Status screen display error: could not parse iob.json: ", e); } try { - var cob = JSON.parse(fs.readFileSync("/root/myopenaps/monitor/meal.json")); + var cob = JSON.parse(fs.readFileSync(openapsDir+"/monitor/meal.json")); } catch (e) { console.error("Status screen display error: could not parse meal.json: ", e); } @@ -98,36 +98,36 @@ if(batterylevel) { display.oled.drawLine(116, 63, 127, 63, 1, false); //bottom display.oled.drawLine(116, 57, 116, 63, 1, false); //left display.oled.drawLine(127, 57, 127, 63, 1, false); //right - display.oled.drawLine(115, 59, 115, 61, 1, false); //iconify + display.oled.drawLine(115, 59, 115, 61, 1, false); //make it look like a battery var batt = Math.round(batterylevel.battery / 10); display.oled.fillRect(127-batt, 58, batt, 5, 1, false); //fill battery gauge } -//display reason for not looping and move the graph to make room for the message and to draw attention to it! -var yOffset = 0; +//display reason for not looping, and move the graph to make room for the message +var yOffset = 0; //offset for graph, if we need to move it if (status && suggested) { var notLoopingReason = suggested.reason; display.oled.setCursor(0,16); if (status.suspended == true) { - display.oled.writeString(font, 1, "PUMP SUSPENDED", 1, true, false); + display.oled.writeString(font, 1, "PUMP SUSPENDED", 1, false, 0, false); yOffset = 3; } else if (status.bolusing == true) { - display.oled.writeString(font, 1, "PUMP BOLULSING", 1, true, false); + display.oled.writeString(font, 1, "PUMP BOLULSING", 1, false, 0, false); yOffset = 3; } else if (notLoopingReason.includes("CGM is calibrating")) { - display.oled.writeString(font, 1, "CGM calib./???/noisy", 1, true, false); + display.oled.writeString(font, 1, "CGM calib./???/noisy", 1, false, 0, false); yOffset = 3; } else if (notLoopingReason.includes("CGM data is unchanged")) { - display.oled.writeString(font, 1, "CGM data unchanged", 1, true, false); + display.oled.writeString(font, 1, "CGM data unchanged", 1, false, 0, false); yOffset = 3; } -//add more on-screen warnings/messages here, maybe some special ones for xdrip-js users +//add more on-screen warnings/messages, maybe some special ones for xdrip-js users? } -//display targets high and low +//display current target(s) if (profile) { var targetLow = Math.round( (21+yOffset) - ( ( profile.bg_targets.targets[0].low - 250 ) / 8 ) ); var targetHigh = Math.round( (21+yOffset) - ( ( profile.bg_targets.targets[0].high - 250 ) / 8 ) ); @@ -136,7 +136,7 @@ if (profile) { } if (bg) { - //rander BG graph + //render BG graph var numBGs = (suggested.predBGs != undefined) ? (72) : (120); //fill the whole graph with BGs if there are no predictions var date = new Date(); var zerotime = date.getTime() - ((numBGs * 5) * 600); @@ -177,14 +177,14 @@ if (bg) { //display BG number and timeago, add plus sign if delta is positive display.oled.setCursor(0,57); if (delta >= 0) { - display.oled.writeString(font, 1, "BG:"+convert_bg(bg[0].glucose, profile)+"+"+stripLeadingZero(convert_bg(delta, profile))+" "+minutes+"m", 1, true, false); + display.oled.writeString(font, 1, "BG:"+convert_bg(bg[0].glucose, profile)+"+"+stripLeadingZero(convert_bg(delta, profile))+" "+minutes+"m", 1, false, 0, false); } else { - display.oled.writeString(font, 1, "BG:"+convert_bg(bg[0].glucose, profile)+""+stripLeadingZero(convert_bg(delta, profile))+" "+minutes+"m", 1, true, false); + display.oled.writeString(font, 1, "BG:"+convert_bg(bg[0].glucose, profile)+""+stripLeadingZero(convert_bg(delta, profile))+" "+minutes+"m", 1, false, 0, false); } } -//render predictions, but only if we have them -if (suggested && suggested.predBGs != undefined) { +//render predictions on the graph, but only if we have them +if (bg && suggested && suggested.predBGs != undefined) { //render line between actual BG and predicted x = zero_x + 1; display.oled.drawLine(x, 51+yOffset, x, 21+yOffset, 1, false); @@ -204,24 +204,24 @@ if (suggested && suggested.predBGs != undefined) { } } -//display current temp basal and how long ago it was set -if (stats && temp) { - startDate = new Date(stats.mtime); +//display current temp basal and how long ago it was set, on the first line of the screen +if (statusStats && temp) { + startDate = new Date(statusStats.mtime); endDate = new Date(); - minutes = Math.round(( (endDate.getTime() - startDate.getTime()) / 1000) / 60); + var minutesAgo = Math.round(( (endDate.getTime() - startDate.getTime()) / 1000) / 60); //display current temp basal display.oled.setCursor(0,0); var tempRate = Math.round(temp.rate*10)/10; - display.oled.writeString(font, 1, "TB: "+temp.duration+'m '+tempRate+'U/h '+'('+minutes+'m ago)', 1, false); + display.oled.writeString(font, 1, "TB: "+temp.duration+'m '+tempRate+'U/h '+'('+minutesAgo+'m ago)', 1, false, 0, false); } -//display COB and IOB on second line of the screen +//display current COB and IOB, on the second line of the screen if (iob && cob) { display.oled.setCursor(0,8); - display.oled.writeString(font, 1, "COB: "+cob.mealCOB+"g IOB: "+iob[0].iob+'U', 1, false, false); + display.oled.writeString(font, 1, "COB: "+cob.mealCOB+"g IOB: "+iob[0].iob+'U', 1, false, 0, false); } -//bg graph axes +//display bg graph axes display.oled.drawLine(5, 51+yOffset, 5, 21+yOffset, 1, false); display.oled.drawLine(5, 51+yOffset, 127, 51+yOffset, 1, false); @@ -232,10 +232,11 @@ clockHour = (clockHour < 10 ? "0" : "") + clockHour; var clockMin = clockDate.getMinutes(); clockMin = (clockMin < 10 ? "0" : "") + clockMin; display.oled.setCursor(83, 57); -display.oled.writeString(font, 1, clockHour+":"+clockMin, 1, true, false); +display.oled.writeString(font, 1, clockHour+":"+clockMin, 1, false, 0, false); -//write to the screen -display.oled.update(); +display.oled.dimDisplay(true); //dim the display +display.oled.update(); //write buffer to the screen -//randomly invert display to evenly wear the OLED diodes -display.oled.invertDisplay((endDate % 2 == 1)); +if (evenOLEDwear == true) { + display.oled.invertDisplay((endDate % 2 == 1)); +} From 7c935cb6877c1814f3bf41e9fcd1d5c37cfc5bbb Mon Sep 17 00:00:00 2001 From: Jon Cluck Date: Sun, 15 Jul 2018 21:30:03 -0400 Subject: [PATCH 13/35] stop calling status scripts from the commandline --- config/menus/menu.json | 9 +-- index.js | 20 ++++++- scripts/big_bg_status.js | 124 +++++++++++++++++++-------------------- scripts/status.js | 18 ++---- 4 files changed, 87 insertions(+), 84 deletions(-) diff --git a/config/menus/menu.json b/config/menus/menu.json index 489ce55..4607159 100644 --- a/config/menus/menu.json +++ b/config/menus/menu.json @@ -4,13 +4,11 @@ "menu": [ { "label": "Status Graph", - "command": "node scripts/status.js", - "emit": "nothing" + "emit": "showGFXstatus" }, { "label": "Big BG Status", - "command": "node scripts/big_bg_status.js", - "emit": "nothing" + "emit": "showbigBGstatus" }, { "label": "Set Temp Target", @@ -59,8 +57,7 @@ }, { "label": "Unicorn Logo", - "command": "node scripts/display_image.js ./static/unicorn.png", - "emit": "nothing" + "emit": "showlogo" } ] }, diff --git a/index.js b/index.js index 51b62ed..bcb86f6 100644 --- a/index.js +++ b/index.js @@ -35,7 +35,7 @@ var voltage = require('./lib/voltage/voltage')(voltageConfig) var batteryConfig = require('./config/battery.json') var socketServer = require('./lib/socket-server/socket-server')({ voltage: voltage, - battery: batteryConfig + battery: batteryConfig, }) socketServer .on('error', (err) => { @@ -45,7 +45,9 @@ socketServer console.log('socket-server warning: ', warn.reason) }) - +// graphical status scripts +const graphicalStatus = require('./scripts/status.js'); +const bigBGStatus = require('./scripts/big_bg_status.js'); // setup the menus var buttonsConfig = require('./config/buttons.json'); @@ -64,6 +66,20 @@ var hidMenu = require('./lib/hid-menu/hid-menu')(buttonsConfig, menuConfig); hidMenu .on('nothing', function () { }) +.on('showGFXstatus', function () { + graphicalStatus(display); +}) +.on('showbigBGstatus', function () { + bigBGStatus(display); +}) +.on('showlogo', function () { + pngparse.parseFile('./static/unicorn.png', function(err, image) { + if(err) + throw err + display.clear(); + display.oled.drawBitmap(image.data); +}); +}) .on('showvoltage', function () { voltage() .then(function (v) { diff --git a/scripts/big_bg_status.js b/scripts/big_bg_status.js index 70ce4cd..db1145f 100644 --- a/scripts/big_bg_status.js +++ b/scripts/big_bg_status.js @@ -1,12 +1,12 @@ -'use strict'; +module.exports = bigbgstatus; + +function bigbgstatus(display) { -const i2c = require('i2c-bus'); const path = require('path'); const extend = require('extend'); var os = require('os'); var fs = require('fs'); var font = require('oled-font-5x7'); -var i2cBus = i2c.openSync(1); // Rounds value to 'digits' decimal places function round(value, digits) @@ -34,20 +34,8 @@ function stripLeadingZero(value) return value.toString().replace( re, '$1'); } -// setup the display -var displayConfig = require('/root/src/openaps-menu/config/display.json'); -displayConfig.i2cBus = i2cBus; - -//check to see if the display works, exit with error code if it doesn't -try { - var display = require('/root/src/openaps-menu/lib/display/ssd1306')(displayConfig); -} catch (e) { - console.error("Could not set up HAT display:\n", e); - process.exit(1); -} - -//dim the display -display.oled.dimDisplay(true); +display.oled.dimDisplay(true); //dim the display +display.oled.clearDisplay(false); //clear the buffer //Parse all the .json files we need try { @@ -64,22 +52,27 @@ try { try { var suggested = JSON.parse(fs.readFileSync("/root/myopenaps/enact/suggested.json")); } catch (e) { - return console.error("Status screen display error: could not parse suggested.json: ", e); + console.error("Status screen display error: could not parse suggested.json: ", e); } try { var bg = JSON.parse(fs.readFileSync("/root/myopenaps/monitor/glucose.json")); } catch (e) { - return console.error("Status screen display error: could not parse glucose.json: ", e); + console.error("Status screen display error: could not parse glucose.json: ", e); } try { var iob = JSON.parse(fs.readFileSync("/root/myopenaps/monitor/iob.json")); } catch (e) { - return console.error("Status screen display error: could not parse iob.json: ", e); + console.error("Status screen display error: could not parse iob.json: ", e); } try { var cob = JSON.parse(fs.readFileSync("/root/myopenaps/monitor/meal.json")); } catch (e) { - return console.error("Status screen display error: could not parse meal.json: ", e); + console.error("Status screen display error: could not parse meal.json: ", e); +} +try { + var stats = fs.statSync("/tmp/pump_loop_success"); +} catch (e) { + console.error("Status screen display error: could not find pump_loop_success"); } if(batterylevel) { @@ -94,55 +87,62 @@ if(batterylevel) { } //calculate timeago for BG -var startDate = new Date(bg[0].date); -var endDate = new Date(); -var minutes = Math.round(( (endDate.getTime() - startDate.getTime()) / 1000) / 60); -if (bg[0].delta) { - var delta = Math.round(bg[0].delta); -} else if (bg[1] && bg[0].date - bg[1].date > 200000 ) { - var delta = Math.round(bg[0].glucose - bg[1].glucose); -} else if (bg[2] && bg[0].date - bg[2].date > 200000 ) { - var delta = Math.round(bg[0].glucose - bg[2].glucose); -} else if (bg[3] && bg[0].date - bg[3].date > 200000 ) { - var delta = Math.round(bg[0].glucose - bg[3].glucose); -} else { - var delta = 0; -} - -//display BG number, add plus sign if delta is positive -display.oled.setCursor(0,0); -if (delta >= 0) { - display.oled.writeString(font, 3, ""+convert_bg(bg[0].glucose, profile), 1, true, false); - display.oled.writeString(font, 1, "+"+stripLeadingZero(convert_bg(delta, profile)), 1, true, false); - display.oled.writeString(font, 2, " "+minutes+"m", 1, true, false); -} else { - display.oled.writeString(font, 3, ""+convert_bg(bg[0].glucose, profile), 1, true, false); - display.oled.writeString(font, 1, ""+stripLeadingZero(convert_bg(delta, profile)), 1, true, false); - display.oled.writeString(font, 2, " "+minutes+"m", 1, true, false); +if(bg && profile) { + var startDate = new Date(bg[0].date); + var endDate = new Date(); + var minutes = Math.round(( (endDate.getTime() - startDate.getTime()) / 1000) / 60); + if (bg[0].delta) { + var delta = Math.round(bg[0].delta); + } else if (bg[1] && bg[0].date - bg[1].date > 200000 ) { + var delta = Math.round(bg[0].glucose - bg[1].glucose); + } else if (bg[2] && bg[0].date - bg[2].date > 200000 ) { + var delta = Math.round(bg[0].glucose - bg[2].glucose); + } else if (bg[3] && bg[0].date - bg[3].date > 200000 ) { + var delta = Math.round(bg[0].glucose - bg[3].glucose); + } else { + var delta = 0; + } + + //display BG number, add plus sign if delta is positive + display.oled.setCursor(0,0); + if (delta >= 0) { + display.oled.writeString(font, 3, ""+convert_bg(bg[0].glucose, profile), 1, false, 0, false); + display.oled.writeString(font, 1, "+"+stripLeadingZero(convert_bg(delta, profile)), 1, false, 0, false); + display.oled.writeString(font, 2, " "+minutes+"m", 1, false, 0, false); + } else { + display.oled.writeString(font, 3, ""+convert_bg(bg[0].glucose, profile), 1, false, 0, false); + display.oled.writeString(font, 1, ""+stripLeadingZero(convert_bg(delta, profile)), 1, false, 0, false); + display.oled.writeString(font, 2, " "+minutes+"m", 1, false, 0, false); + } } //calculate timeago for last successful loop -var stats = fs.statSync("/tmp/pump_loop_success"); -var date = new Date(stats.mtime); -var hour = date.getHours(); -hour = (hour < 10 ? "0" : "") + hour; -var min = date.getMinutes(); -min = (min < 10 ? "0" : "") + min; - -//display last loop time -display.oled.setCursor(0,57); -display.oled.writeString(font, 1, "Last loop at: "+hour+":"+min, 1, true, false); +if(stats) { + var date = new Date(stats.mtime); + var hour = date.getHours(); + hour = (hour < 10 ? "0" : "") + hour; + var min = date.getMinutes(); + min = (min < 10 ? "0" : "") + min; + + //display last loop time + display.oled.setCursor(0,57); + display.oled.writeString(font, 1, "Last loop at: "+hour+":"+min, 1, false, 0, false); +} //parse and render COB/IOB -display.oled.setCursor(0,23); -display.oled.writeString(font, 1, "IOB:", 1, true, false); -display.oled.writeString(font, 2, " "+iob[0].iob+'U', 1, true, false); -display.oled.setCursor(0,39); -display.oled.writeString(font, 1, "COB:", 1, true, false); -display.oled.writeString(font, 2, " "+cob.mealCOB+'g', 1, true, false); +if(iob && cob) { + display.oled.setCursor(0,23); + display.oled.writeString(font, 1, "IOB:", 1, false, 0, false); + display.oled.writeString(font, 2, " "+iob[0].iob+'U', 1, false, 0, false); + display.oled.setCursor(0,39); + display.oled.writeString(font, 1, "COB:", 1, false, 0, false); + display.oled.writeString(font, 2, " "+cob.mealCOB+'g', 1, false, 0, false); +} //display everything in the buffer display.oled.update(); //fandomly invert display to evenly wear the OLED diodes display.oled.invertDisplay((endDate % 2 == 1)); + +} //from start of function diff --git a/scripts/status.js b/scripts/status.js index a6b902e..ada4e3f 100644 --- a/scripts/status.js +++ b/scripts/status.js @@ -1,10 +1,9 @@ +module.exports = graphicalStatus; -'use strict'; +function graphicalStatus(display) { -const i2c = require('i2c-bus'); var fs = require('fs'); var font = require('oled-font-5x7'); -var i2cBus = i2c.openSync(1); const homeDir = require('os').homedir(); var openapsDir = "/root/myopenaps"; //if you're using a nonstandard OpenAPS directory, set that here @@ -36,17 +35,6 @@ function stripLeadingZero(value) return value.toString().replace( re, '$1'); } -//setup the display -var displayConfig = require(homeDir+'/src/openaps-menu/config/display.json'); -displayConfig.i2cBus = i2cBus; - -//check to see if the display works, exit with error code if it doesn't -try { - var display = require(homeDir+'/src/openaps-menu/lib/display/ssd1306')(displayConfig); -} catch (e) { - return console.error("Could not set up HAT display:\n", e); -} - display.oled.clearDisplay(false); //clear display buffer //Parse all the .json files we need @@ -240,3 +228,5 @@ display.oled.update(); //write buffer to the screen if (evenOLEDwear == true) { display.oled.invertDisplay((endDate % 2 == 1)); } + +}//from graphicalStatus From 315dd9f7e4b617854d91337d584d42402c1b6d03 Mon Sep 17 00:00:00 2001 From: Jon Cluck Date: Sun, 15 Jul 2018 22:34:07 -0400 Subject: [PATCH 14/35] use socket server to control display --- index.js | 11 +++++++---- lib/socket-server/socket-server.js | 8 ++++++++ scripts/status.sh | 7 +++++++ scripts/unicorn.js | 25 ------------------------- 4 files changed, 22 insertions(+), 29 deletions(-) create mode 100755 scripts/status.sh delete mode 100644 scripts/unicorn.js diff --git a/index.js b/index.js index bcb86f6..b14856b 100644 --- a/index.js +++ b/index.js @@ -26,6 +26,10 @@ pngparse.parseFile('./static/unicorn.png', function(err, image) { display.oled.drawBitmap(image.data); }); +// load up graphical status scripts +const graphicalStatus = require('./scripts/status.js'); +const bigBGStatus = require('./scripts/big_bg_status.js'); + // setup battery voltage monitor var voltageConfig = require('./config/voltage.json') voltageConfig.i2cBus = i2cBus @@ -44,10 +48,9 @@ socketServer .on('warning', (warn) => { console.log('socket-server warning: ', warn.reason) }) - -// graphical status scripts -const graphicalStatus = require('./scripts/status.js'); -const bigBGStatus = require('./scripts/big_bg_status.js'); +.on('displaystatus', function () { + graphicalStatus(display); +}) // setup the menus var buttonsConfig = require('./config/buttons.json'); diff --git a/lib/socket-server/socket-server.js b/lib/socket-server/socket-server.js index dfffc5d..e18221c 100644 --- a/lib/socket-server/socket-server.js +++ b/lib/socket-server/socket-server.js @@ -91,6 +91,10 @@ module.exports = function (config) { try { switch (cmd.command) { + case 'status': + emitDisplay() + socketServer.clientWrite(client, 200, 'Success') + break; case 'read_voltage': require('./commands/read_voltage')(config) .then((response) => { @@ -134,6 +138,10 @@ module.exports = function (config) { emitter.emit('warning', { reason: warnString }) } + function emitDisplay() { + emitter.emit('displaystatus') + } + // handle any exit events in the server process process .on('exit', exitHandler) diff --git a/scripts/status.sh b/scripts/status.sh new file mode 100755 index 0000000..452e594 --- /dev/null +++ b/scripts/status.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +command -v socat >/dev/null 2>&1 || { echo >&2 "I require socat but it's not installed. Aborting."; exit 1; } + +RESPONSE=`echo '{"command":"status"}' | socat -,ignoreeof ~/src/openaps-menu/socket-server.sock | sed -n 's/.*"response":\([^}]*\)}/\1/p'` +[[ $RESPONSE = *[![:space:]]* ]] && echo $RESPONSE +#./getvoltage.sh | sed -n 's/.*"response":\([^}]*\)}/\1/p' diff --git a/scripts/unicorn.js b/scripts/unicorn.js deleted file mode 100644 index 68aed35..0000000 --- a/scripts/unicorn.js +++ /dev/null @@ -1,25 +0,0 @@ - -'use strict'; - - -const i2c = require('i2c-bus'); -const path = require('path'); -const pngparse = require('pngparse'); -const extend = require('extend'); - -var i2cBus = i2c.openSync(1); - -// setup the display -var displayConfig = require('../config/display.json'); -displayConfig.i2cBus = i2cBus; -var display = require('../lib/display/ssd1306')(displayConfig); - -// display the logo -pngparse.parseFile('./static/unicorn.png', function(err, image) { - if(err) - throw err - display.oled.drawBitmap(image.data); -}); - -//dim the display -display.oled.dimDisplay(true); From 30e5d3b58980f8fea7eebab967eb6ed010b41890 Mon Sep 17 00:00:00 2001 From: Jon Cluck Date: Mon, 16 Jul 2018 15:27:28 -0400 Subject: [PATCH 15/35] improvements and bugfixes --- lib/socket-server/socket-server.js | 2 +- scripts/status.js | 18 ++++++++++++------ scripts/status.sh | 6 ++++-- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/lib/socket-server/socket-server.js b/lib/socket-server/socket-server.js index e18221c..fad3088 100644 --- a/lib/socket-server/socket-server.js +++ b/lib/socket-server/socket-server.js @@ -93,7 +93,7 @@ module.exports = function (config) { switch (cmd.command) { case 'status': emitDisplay() - socketServer.clientWrite(client, 200, 'Success') + socketServer.clientWrite(client, 201, 'HAT Display Updating') break; case 'read_voltage': require('./commands/read_voltage')(config) diff --git a/scripts/status.js b/scripts/status.js index ada4e3f..cc3765f 100644 --- a/scripts/status.js +++ b/scripts/status.js @@ -1,13 +1,9 @@ -module.exports = graphicalStatus; - -function graphicalStatus(display) { - var fs = require('fs'); var font = require('oled-font-5x7'); const homeDir = require('os').homedir(); var openapsDir = "/root/myopenaps"; //if you're using a nonstandard OpenAPS directory, set that here -var evenOLEDwear = false; //if you want to prevent OLED burn-in symptoms by inverting the screen, set this to true +var evenOLEDwear = true; //if you want to prevent OLED burn-in symptoms by inverting the screen, set this to true // Rounds value to 'digits' decimal places function round(value, digits) @@ -35,6 +31,14 @@ function stripLeadingZero(value) return value.toString().replace( re, '$1'); } +module.exports = graphicalStatus; + +// +//Start of status display function +// + +function graphicalStatus(display) { + display.oled.clearDisplay(false); //clear display buffer //Parse all the .json files we need @@ -229,4 +233,6 @@ if (evenOLEDwear == true) { display.oled.invertDisplay((endDate % 2 == 1)); } -}//from graphicalStatus + // +}//End of status display function + // diff --git a/scripts/status.sh b/scripts/status.sh index 452e594..9474969 100755 --- a/scripts/status.sh +++ b/scripts/status.sh @@ -1,7 +1,9 @@ #!/bin/bash command -v socat >/dev/null 2>&1 || { echo >&2 "I require socat but it's not installed. Aborting."; exit 1; } - RESPONSE=`echo '{"command":"status"}' | socat -,ignoreeof ~/src/openaps-menu/socket-server.sock | sed -n 's/.*"response":\([^}]*\)}/\1/p'` -[[ $RESPONSE = *[![:space:]]* ]] && echo $RESPONSE +[[ $RESPONSE = *[![:space:]]* ]] +if [ $RESPONSE == "{}" ]; then + echo "Updating HAT Display..." +fi #./getvoltage.sh | sed -n 's/.*"response":\([^}]*\)}/\1/p' From 68e13c8952e2940eec4650e52b3460c48d470bbf Mon Sep 17 00:00:00 2001 From: Jon Cluck Date: Mon, 16 Jul 2018 18:47:44 -0400 Subject: [PATCH 16/35] status screen for cas --- scripts/casstatus.js | 176 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 scripts/casstatus.js diff --git a/scripts/casstatus.js b/scripts/casstatus.js new file mode 100644 index 0000000..8da8ba3 --- /dev/null +++ b/scripts/casstatus.js @@ -0,0 +1,176 @@ +var fs = require('fs'); +var font = require('oled-font-5x7'); +const homeDir = require('os').homedir(); + +var openapsDir = "/root/myopenaps"; //if you're using a nonstandard OpenAPS directory, set that here +var evenOLEDwear = true; //if you want to prevent OLED burn-in symptoms by inverting the screen, set this to true + +// Rounds value to 'digits' decimal places +function round(value, digits) +{ + if (! digits) { digits = 0; } + var scale = Math.pow(10, digits); + return Math.round(value * scale) / scale; +} + +function convert_bg(value, profile) +{ + if (profile != null && profile.out_units == "mmol/L") + { + return round(value / 18, 1).toFixed(1); + } + else + { + return Math.round(value); + } +} + +function stripLeadingZero(value) +{ + var re = /^(-)?0+(?=[\.\d])/; + return value.toString().replace( re, '$1'); +} + +module.exports = graphicalStatus; + +// +//Start of status display function +// + +function graphicalStatus(display) { + +display.oled.clearDisplay(false); //clear display buffer + +//Parse all the .json files we need +try { + var profile = JSON.parse(fs.readFileSync(openapsDir+"/settings/profile.json")); +} catch (e) { + console.error("Status screen display error: could not parse profile.json: ", e); +} +try { + var suggested = JSON.parse(fs.readFileSync(openapsDir+"/enact/suggested.json")); +} catch (e) { + console.error("Status screen display error: could not parse suggested.json: ", e); +} +try { + var bg = JSON.parse(fs.readFileSync(openapsDir+"/monitor/glucose.json")); +} catch (e) { + console.error("Status screen display error: could not parse glucose.json: ", e); +} +try { + var iob = JSON.parse(fs.readFileSync(openapsDir+"/monitor/iob.json")); +} catch (e) { + console.error("Status screen display error: could not parse iob.json: ", e); +} +try { + var cob = JSON.parse(fs.readFileSync(openapsDir+"/monitor/meal.json")); +} catch (e) { + console.error("Status screen display error: could not parse meal.json: ", e); +} + +var yOffset = 12; //offset for graph, if we need to move it + +//display current target(s) +if (profile) { + var targetLow = Math.round( (21+yOffset) - ( ( profile.bg_targets.targets[0].low - 250 ) / 8 ) ); + var targetHigh = Math.round( (21+yOffset) - ( ( profile.bg_targets.targets[0].high - 250 ) / 8 ) ); + display.oled.drawLine(2, targetHigh, 5, targetHigh, 1, false); + display.oled.drawLine(2, targetLow, 5, targetLow, 1, false); +} + +if (bg) { + //render BG graph + var numBGs = (suggested.predBGs != undefined) ? (72) : (120); //fill the whole graph with BGs if there are no predictions + var date = new Date(); + var zerotime = date.getTime() - ((numBGs * 5) * 600); + var zero_x = numBGs + 5; + for (var i = 0; i < numBGs; i++) { + if (bg[i] != null) { + var x = zero_x + Math.round(((((bg[i].date - zerotime)/1000)/60)/5)); + var y = Math.round( (21+yOffset) - ( ( bg[i].glucose - 250 ) / 8 ) ); + //left and right boundaries + if ( x < 5 ) x = 5; + if ( x > 127 ) x = 127; + //upper and lower boundaries + if ( y < (21+yOffset) ) y = (21+yOffset); + if ( y > (51+yOffset) ) y = (51+yOffset); + display.oled.drawPixel([x, y, 1, false]); + // if we have multiple data points within 3m, look further back to fill in the graph + if ( bg[i-1] && bg[i-1].date - bg[i].date < 200000 ) { + numBGs++; + } + } + } + + //calculate timeago for BG + var startDate = new Date(bg[0].date); + var endDate = new Date(); + var minutes = Math.round(( (endDate.getTime() - startDate.getTime()) / 1000) / 60); + if (bg[0].delta) { + var delta = Math.round(bg[0].delta); + } else if (bg[1] && bg[0].date - bg[1].date > 200000 ) { + var delta = Math.round(bg[0].glucose - bg[1].glucose); + } else if (bg[2] && bg[0].date - bg[2].date > 200000 ) { + var delta = Math.round(bg[0].glucose - bg[2].glucose); + } else if (bg[3] && bg[0].date - bg[3].date > 200000 ) { + var delta = Math.round(bg[0].glucose - bg[3].glucose); + } else { + var delta = 0; + } + //display BG number and timeago, add plus sign if delta is positive + display.oled.setCursor(0,0); + if (delta >= 0) { + //display.oled.writeString(font, 3, "42.0", 1, false, 0, false); + //display.oled.writeString(font, 2, "+6.9", 1, false, 0, false); + display.oled.writeString(font, 3, ""+convert_bg(bg[0].glucose, profile), 1, false, 0, false); + display.oled.writeString(font, 2, "+"+stripLeadingZero(convert_bg(delta, profile)), 1, false, 0, false); + } else { + display.oled.writeString(font, 3, ""+convert_bg(bg[0].glucose, profile), 1, false, 0, false); + display.oled.writeString(font, 2, ""+stripLeadingZero(convert_bg(delta, profile)), 1, false, 0, false); + } +} + +//render predictions on the graph, but only if we have them +if (bg && suggested && suggested.predBGs != undefined) { + //render line between actual BG and predicted + x = zero_x + 1; + display.oled.drawLine(x, 51+yOffset, x, 21+yOffset, 1, false); + //render predictions + var predictions = [suggested.predBGs.IOB, suggested.predBGs.ZT, suggested.predBGs.UAM, suggested.predBGs.COB]; + for (i = 0; i <= 48; i++) { + x++; + for(var n = 0; n <=3 && (predictions[n] != undefined); n++) { + y = Math.round( (21+yOffset) - ( (predictions[n][i] - 250 ) / 8) ); + //right boundary + if ( x > 127 ) x = 127; + //upper and lower boundaries + if ( y < (21+yOffset) ) y = (21+yOffset); + if ( y > (51+yOffset) ) y = (51+yOffset); + display.oled.drawPixel([x, y, 1, false]); + } + } +} + +//display current COB and IOB, on the second line of the screen +if (iob && cob) { + display.oled.setCursor(0,22); + display.oled.writeString(font, 1, "COB: "+cob.mealCOB+"g IOB: "+iob[0].iob+"U", 1, false, 0, false); +} + +//display bg graph axes +display.oled.drawLine(5, 51+yOffset, 5, 21+yOffset, 1, false); +display.oled.drawLine(5, 51+yOffset, 127, 51+yOffset, 1, false); + +//render clock + +display.oled.dimDisplay(true); //dim the display +display.oled.update(); //write buffer to the screen + +if (evenOLEDwear == true) { + var endDate = new Date(); + display.oled.invertDisplay((endDate % 2 == 1)); +} + + // +}//End of status display function + // From 2a35a2bb78582c36394e91d34d0a6a5a2a07204e Mon Sep 17 00:00:00 2001 From: Jon Cluck Date: Wed, 18 Jul 2018 22:37:04 -0400 Subject: [PATCH 17/35] nice typo --- scripts/status.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/status.js b/scripts/status.js index cc3765f..7b1399a 100644 --- a/scripts/status.js +++ b/scripts/status.js @@ -105,7 +105,7 @@ if (status && suggested) { yOffset = 3; } else if (status.bolusing == true) { - display.oled.writeString(font, 1, "PUMP BOLULSING", 1, false, 0, false); + display.oled.writeString(font, 1, "PUMP BOLUSING", 1, false, 0, false); yOffset = 3; } else if (notLoopingReason.includes("CGM is calibrating")) { From 07ea8a9168ebc17ec95fdc5f4f3530377ba885bd Mon Sep 17 00:00:00 2001 From: Jon Cluck Date: Wed, 18 Jul 2018 22:41:06 -0400 Subject: [PATCH 18/35] Remove custom rig code --- config/menus/menu.json | 46 +++++++++++++++--------------------------- 1 file changed, 16 insertions(+), 30 deletions(-) diff --git a/config/menus/menu.json b/config/menus/menu.json index 4607159..887780b 100644 --- a/config/menus/menu.json +++ b/config/menus/menu.json @@ -65,39 +65,30 @@ "label": "Wifi", "menu": [ { - "label": "Connect Pixel", - "command": "nmcli c up 'Pixel Network'", - "emit": "showoutput" - }, - { - "label": "Disconnect Pixel", - "command": "nmcli c down 'Pixel Network'", - "emit": "showoutput" - }, - { - "label": "Wifi Off", - "command": "nmcli radio wifi off", - "emit": "showoutput" + "label": "Current Wifi Network", + "command": "./scripts/getwifi.sh", + "emit": "showoutput" }, { - "label": "Wifi On", - "command": "nmcli radio wifi on", - "emit": "showoutput" + "label": "Current Hostname", + "command": "./scripts/gethostname.sh", + "emit": "showoutput" }, { - "label": "Show WiFi Networks", - "command": "nmcli --terse --fields IN-USE,SSID device wifi list", - "emit": "showoutput" + "label": "Current IP Address", + "command": "./scripts/getip.sh", + "emit": "showoutput" }, { - "label": "Wireless Status", - "command": "nmcli --terse --fields TYPE,CONNECTION,STATE device status", - "emit": "showoutput" + "label": "Show network.log", + "command": "cat /var/log/openaps/network.log | fold -w 23 -s | tail -8", + "emit": "showoutput" }, { - "label": "Current IP Address", - "command": "./scripts/getip.sh", - "emit": "showoutput" + "label": "Select Open Wifi Net", + "options": "./scripts/list_open_wifi.sh", + "selectScript": "./scripts/add_open_wifi.sh", + "selectEmit": "showoutput" } ] }, @@ -148,11 +139,6 @@ "command": "echo Rebooting in; echo 1 minute; shutdown -r", "emit": "showoutput" }, - { - "label": "Shutdown Now", - "command": "shutdown -h now", - "emit": "showoutput" - }, { "label": "Cancel Reboot", "command": "shutdown -c; echo Reboot canceled", From 2ca742a530f845b6c07b2afab5ed0934f4f20e0e Mon Sep 17 00:00:00 2001 From: Jon Cluck Date: Wed, 18 Jul 2018 22:43:01 -0400 Subject: [PATCH 19/35] revert readme --- README.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e66ca9e..f2278e5 100644 --- a/README.md +++ b/README.md @@ -1 +1,20 @@ -This repo contains customizations for Jon's setup, not everything will work for you. Please use https://github.com/openaps/openaps-menu! +# openaps-menu + +This is the repository holding the menu-based software code, which you may choose to add to an Explorer HAT or other screen-based rig in order to visualize and enter information into a Pi-based #OpenAPS rig. + +See [here](https://github.com/EnhancedRadioDevices/Explorer-HAT) for more details on the Explorer HAT hardware. + +## Example screen outputs (Note: these are examples. The latest code may yield different menu items and screen displays) + +### Status screen: + +![Status screen](https://github.com/openaps/openaps-menu/blob/master/images/status.JPG) + +### Graph: + +![Graph visual on screen](https://github.com/openaps/openaps-menu/blob/master/images/graph.JPG) + + + + + From a1fac9a6d3154a972e55c4b4c2fcb5c08269465d Mon Sep 17 00:00:00 2001 From: Jon Cluck Date: Mon, 23 Jul 2018 10:52:51 -0400 Subject: [PATCH 20/35] fixes and improvements --- index.js | 42 ++++++++++++++++-------------- lib/socket-server/socket-server.js | 9 +++---- scripts/status.js | 2 +- scripts/status.sh | 7 +++-- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/index.js b/index.js index b14856b..d1561df 100644 --- a/index.js +++ b/index.js @@ -16,19 +16,13 @@ var i2cBus = i2c.openSync(1); // setup the display var displayConfig = require('./config/display.json'); displayConfig.i2cBus = i2cBus; -var display = require('./lib/display/ssd1306')(displayConfig); -// display the logo -pngparse.parseFile('./static/unicorn.png', function(err, image) { - if(err) - throw err - display.clear(); - display.oled.drawBitmap(image.data); -}); - -// load up graphical status scripts -const graphicalStatus = require('./scripts/status.js'); -const bigBGStatus = require('./scripts/big_bg_status.js'); +try { + var display = require('./lib/display/ssd1306')(displayConfig); + displayImage('./static/unicorn.png'); //display logo +} catch (e) { + console.warn("Could not setup display:", e); +} // setup battery voltage monitor var voltageConfig = require('./config/voltage.json') @@ -49,9 +43,24 @@ socketServer console.log('socket-server warning: ', warn.reason) }) .on('displaystatus', function () { - graphicalStatus(display); + if (display) { + graphicalStatus(display); + } }) +function displayImage(pathToImage) { + pngparse.parseFile(pathToImage, function(err, image) { + if(err) + throw err + display.clear(); + display.oled.drawBitmap(image.data); + }); +} + +// load up graphical status scripts +const graphicalStatus = require('./scripts/status.js'); +const bigBGStatus = require('./scripts/big_bg_status.js'); + // setup the menus var buttonsConfig = require('./config/buttons.json'); var menuConfig = { @@ -76,12 +85,7 @@ hidMenu bigBGStatus(display); }) .on('showlogo', function () { - pngparse.parseFile('./static/unicorn.png', function(err, image) { - if(err) - throw err - display.clear(); - display.oled.drawBitmap(image.data); -}); + displayImage('./static/unicorn.png'); }) .on('showvoltage', function () { voltage() diff --git a/lib/socket-server/socket-server.js b/lib/socket-server/socket-server.js index fad3088..1a9307b 100644 --- a/lib/socket-server/socket-server.js +++ b/lib/socket-server/socket-server.js @@ -92,9 +92,10 @@ module.exports = function (config) { try { switch (cmd.command) { case 'status': - emitDisplay() - socketServer.clientWrite(client, 201, 'HAT Display Updating') + emitter.emit('displaystatus'); + socketServer.clientWrite(client, 200, 'Success', 'HAT Display Updated'); break; + case 'read_voltage': require('./commands/read_voltage')(config) .then((response) => { @@ -138,10 +139,6 @@ module.exports = function (config) { emitter.emit('warning', { reason: warnString }) } - function emitDisplay() { - emitter.emit('displaystatus') - } - // handle any exit events in the server process process .on('exit', exitHandler) diff --git a/scripts/status.js b/scripts/status.js index cc3765f..7b1399a 100644 --- a/scripts/status.js +++ b/scripts/status.js @@ -105,7 +105,7 @@ if (status && suggested) { yOffset = 3; } else if (status.bolusing == true) { - display.oled.writeString(font, 1, "PUMP BOLULSING", 1, false, 0, false); + display.oled.writeString(font, 1, "PUMP BOLUSING", 1, false, 0, false); yOffset = 3; } else if (notLoopingReason.includes("CGM is calibrating")) { diff --git a/scripts/status.sh b/scripts/status.sh index 9474969..3115173 100755 --- a/scripts/status.sh +++ b/scripts/status.sh @@ -1,9 +1,8 @@ #!/bin/bash command -v socat >/dev/null 2>&1 || { echo >&2 "I require socat but it's not installed. Aborting."; exit 1; } + RESPONSE=`echo '{"command":"status"}' | socat -,ignoreeof ~/src/openaps-menu/socket-server.sock | sed -n 's/.*"response":\([^}]*\)}/\1/p'` -[[ $RESPONSE = *[![:space:]]* ]] -if [ $RESPONSE == "{}" ]; then - echo "Updating HAT Display..." -fi +echo $RESPONSE + #./getvoltage.sh | sed -n 's/.*"response":\([^}]*\)}/\1/p' From 12b38ac0a9d3c8352492a28602339a4f4b5ed489 Mon Sep 17 00:00:00 2001 From: Jon Cluck Date: Mon, 23 Jul 2018 12:55:31 -0400 Subject: [PATCH 21/35] Revert "upgrade jon-dev with node pi buttons" --- config/buttons.json | 10 +- lib/hid-menu/hid-menu.js | 29 ++- lib/pi-buttons/a.out | Bin 0 -> 14132 bytes lib/pi-buttons/build.sh | 5 + lib/pi-buttons/buttons.c | 393 +++++++++++++++++++++++++++++++++++++++ lib/pi-buttons/buttons.h | 108 +++++++++++ lib/pi-buttons/index.js | 28 +++ lib/pi-buttons/setup.sh | 15 ++ openaps-menu.sh | 3 +- package.json | 4 +- 10 files changed, 572 insertions(+), 23 deletions(-) create mode 100755 lib/pi-buttons/a.out create mode 100755 lib/pi-buttons/build.sh create mode 100644 lib/pi-buttons/buttons.c create mode 100644 lib/pi-buttons/buttons.h create mode 100644 lib/pi-buttons/index.js create mode 100755 lib/pi-buttons/setup.sh diff --git a/config/buttons.json b/config/buttons.json index 6f1a4f1..c7b9e38 100644 --- a/config/buttons.json +++ b/config/buttons.json @@ -1,10 +1,10 @@ { - "gpios": { - "buttonUp": 17, - "buttonDown": 27 + "pins": { + "buttonUp": 11, + "buttonDown": 13 }, "options": { - "socketPath": "/var/run/pi-buttons.sock", - "reconnectTimeout": 3000 + "pressed": 200, + "clicked": 400 } } diff --git a/lib/hid-menu/hid-menu.js b/lib/hid-menu/hid-menu.js index fec9b85..cfb427a 100644 --- a/lib/hid-menu/hid-menu.js +++ b/lib/hid-menu/hid-menu.js @@ -8,36 +8,38 @@ const Menube = require('menube'); function createHIDMenu(configButtons, configMenus) { - if (!configButtons.gpios || !configButtons.gpios.buttonUp || !configButtons.gpios.buttonDown) { + if (!configButtons.pins || !configButtons.pins.buttonUp || !configButtons.pins.buttonDown) { throw new Error('Incomplete pins definition in configuration.'); } - var gpios = configButtons.gpios; + var pins = configButtons.pins; var buttonOptions = configButtons.options || {}; var onChange = configMenus.onChange; var menu = Menube(configMenus.menuFile, configMenus.menuSettings); var displayDirty = false; - var piButtons = require('node-pi-buttons')(configButtons.options); + // var buttons = require('rpi-gpio-buttons')([pins.buttonUp, pins.buttonDown], buttonOptions); + var piButtons = require('../pi-buttons'); menu.on('menu_changed', function () { displayDirty = false; // the parent will redraw the display }); +// buttons piButtons - .on('clicked', function (gpio, data) { + .on('clicked', function (pin) { if (displayDirty) { // fake menu changed to force redraw menu.emit('menu_changed'); displayDirty = false; } else { - switch(parseInt(gpio, 10)) { - case gpios.buttonUp: + switch(pin) { + case pins.buttonUp: if (!displayDirty) { menu.menuUp(); } break; - case gpios.buttonDown: + case pins.buttonDown: if (!displayDirty) { menu.menuDown(); } @@ -45,34 +47,31 @@ function createHIDMenu(configButtons, configMenus) { } } }) - .on('double_clicked', function (gpio, data) { + .on('double_clicked', function (pin) { if (displayDirty) { // fake menu changed to force redraw menu.emit('menu_changed'); displayDirty = false; } else { - switch (parseInt(gpio, 10)) { - case gpios.buttonUp: + switch (pin) { + case pins.buttonUp: menu.menuBack(); break; - case gpios.buttonDown: + case pins.buttonDown: displayDirty = true; // activate may write something to the display menu.activateSelect(); break; } } }) - .on('released', function (gpio, data) { + .on('released', function (pin) { if (displayDirty) { // fake menu changed to force redraw menu.emit('menu_changed'); displayDirty = false; } - }) - .on('error', function (data) { - console.log('ERROR: ', data.error); }); return menu; diff --git a/lib/pi-buttons/a.out b/lib/pi-buttons/a.out new file mode 100755 index 0000000000000000000000000000000000000000..b8e6cf48dddcdc26d83ca40613d264ccce778789 GIT binary patch literal 14132 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "buttons.h" + + +int main(int argc, char *argv[]) { + int l, gpioCount; + buttonDefinition * buttons[MAX_BUTTONS]; + pthread_t buttonThread; + pthread_t socketThread; + int clients[MAX_CLIENTS]; + + + // TODO these need to come from a config file or command line args + const char * gpios[] = { + "17", + "27" + }; + gpioCount = 2; + + // init client file descriptors + for(l = 0; l < MAX_CLIENTS; l++) { + clients[l] = -1; + } + pthread_create(&socketThread, NULL, &socketServer, &clients); + + // init buttons + for(l = 0; l < gpioCount; l++) { + buttons[l] = (buttonDefinition *)malloc(sizeof(buttonDefinition)); + buttons[l]->gpio = gpios[l]; + pthread_mutex_init(&buttons[l]->lockControl, NULL); + pthread_barrier_init(&buttons[l]->barrierControl, NULL, 2); + buttons[l]->clients = clients; + pthread_create(&buttons[l]->parent, NULL, &buttonParent, buttons[l]); + } + + // TODO can all threads be monitored simultaneously and restart if one fails? + for(l = 0; l < gpioCount; l++) { + pthread_join(buttons[l]->parent, NULL); + } + +} + + +void * buttonParent(void * args) { + buttonDefinition * button; + button = (buttonDefinition *)args; + char buff[30]; + struct pollfd pollfdStruct; + int pollStatus; + uint8_t c; + + sprintf(buff, "/sys/class/gpio/gpio%s/value", button->gpio); + if ((button->fd = open(buff, O_RDWR)) < 0) { + printf("Failed to open gpio%d.\n", button->gpio); + exit(1); + } + + // configure polling structure + pollfdStruct.fd = button->fd; + pollfdStruct.events = POLLPRI | POLLERR; + + // configure button structure + button->state = STATE_INIT; + button->debounceState = INACTIVE; + button->value = RELEASED; + + // clear out any waiting gpio values + pollStatus = poll(&pollfdStruct, 1, 10); // 10 millisecond wait for input + if (pollStatus > 0) { + if (pollfdStruct.revents & POLLPRI) { + lseek (pollfdStruct.fd, 0, SEEK_SET) ; // Rewind + (void)read (pollfdStruct.fd, &c, 1) ; // Read & clear + } + } + + // reset button state + button->state = STATE_IDLE; + pthread_mutex_lock(&button->lockControl); + pthread_create(&button->child, NULL, &buttonChild, button); + + for(;;) { + pollStatus = poll(&pollfdStruct, 1, -1) ; + if (pollStatus > 0) { + if (pollfdStruct.revents & POLLPRI) { + lseek (pollfdStruct.fd, 0, SEEK_SET) ; // Rewind + (void)read (pollfdStruct.fd, &c, 1) ; // Read & clear + + button->lastValue = c; + + if (button->debounceState == INACTIVE) { + // when not in a debounce state then signal child about button change + pthread_mutex_unlock(&button->lockControl); // signal child button event has started + pthread_barrier_wait(&button->barrierControl); // wait on begin sychronization + pthread_mutex_lock(&button->lockControl); // regain lock for next event + pthread_barrier_wait(&button->barrierControl); // signal child synchronized + } + else { + // TODO do we need to do anything if in debounce? probably not. + } + } + + } + else { + // something wrong with poll status + + } + } +} + + +void * buttonChild(void * args) { + buttonDefinition * button; + button = (buttonDefinition *)args; + int lockStatus, startDebounce = 0; + long lockTimeout = TIMEOUT_FALSE; + char eventMsg[EVENT_MSG_MAX_LENGTH]; + + // main loop + for(;;) { + // wait for next event, unlock or timeout + if (lockTimeout) { + lockStatus = pthread_mutex_timedlock(&button->lockControl, &button->conditionTime); + } + else { + lockStatus = pthread_mutex_lock(&button->lockControl); // wait for parent to signal button event started + } + + lockTimeout = TIMEOUT_FALSE; + if (lockStatus != ETIMEDOUT && button->debounceState == INACTIVE) { + // not a timeout and not in debounce state, start debounce of button value + button->debounceState = ACTIVE; + button->value = button->lastValue; + clock_gettime(CLOCK_REALTIME, &button->lastTime); + setConditionNS(&button->lastTime, &button->conditionTime, DEBOUNCE_NS); + lockTimeout = TIMEOUT_TRUE; + emitFormattedMessage(eventMsg, EVENT_STRING[button_changed], button); + } + else if (lockStatus == ETIMEDOUT && button->debounceState == ACTIVE) { + button->debounceState = INACTIVE; + // timed out while in the debounce state, perform state transition + switch(button->state) { + case STATE_IDLE: + if (button->value == PRESSED && button->lastValue == PRESSED) { + // button pressed and held + button->state = STATE_PRESSED; + emitFormattedMessage(eventMsg, EVENT_STRING[button_press], button); + setConditionNS(&button->lastTime, &button->conditionTime, PRESSED_NS); + lockTimeout = TIMEOUT_TRUE; + } + else if (button->value == PRESSED) { + // button pressed and released within debounce + emitFormattedMessage(eventMsg, EVENT_STRING[button_press], button); + emitFormattedMessage(eventMsg, EVENT_STRING[button_release], button); + button->state = STATE_CLICKED; + setConditionNS(&button->lastTime, &button->conditionTime, CLICKED_NS); + lockTimeout = TIMEOUT_TRUE; + } + break; + + case STATE_PRESSED: + if (button->lastValue == RELEASED) { + // button released + emitFormattedMessage(eventMsg, EVENT_STRING[button_release], button); + button->state = STATE_CLICKED; + setConditionNS(&button->lastTime, &button->conditionTime, CLICKED_NS); + lockTimeout = TIMEOUT_TRUE; + } + else if (button->value == RELEASED && button->lastValue == PRESSED) { + // button released and pressed within debounce + emitFormattedMessage(eventMsg, EVENT_STRING[button_release], button); + emitFormattedMessage(eventMsg, EVENT_STRING[button_press], button); + button->state = STATE_CLICKED_PRESSED; + setConditionNS(&button->lastTime, &button->conditionTime, PRESSED_NS); + lockTimeout = TIMEOUT_TRUE; + } + else { + // unknown, reset state + button->state = STATE_IDLE; + } + break; + + case STATE_CLICKED: + if (button->lastValue == PRESSED) { + // after clicked button pressed and held + button->state = STATE_CLICKED_PRESSED; + emitFormattedMessage(eventMsg, EVENT_STRING[button_press], button); + setConditionNS(&button->lastTime, &button->conditionTime, PRESSED_NS); + lockTimeout = TIMEOUT_TRUE; + } + else if (button->value == PRESSED && button->lastValue == RELEASED) { + // after clicked button pressed and released within debounce + emitFormattedMessage(eventMsg, EVENT_STRING[button_press], button); + emitFormattedMessage(eventMsg, EVENT_STRING[button_release], button); + button->state = STATE_DOUBLE_CLICKED; + setConditionNS(&button->lastTime, &button->conditionTime, CLICKED_NS); + lockTimeout = TIMEOUT_TRUE; + } + else { + // unknown, reset state + button->state = STATE_IDLE; + } + break; + + case STATE_CLICKED_PRESSED: + if (button->lastValue == RELEASED) { + emitFormattedMessage(eventMsg, EVENT_STRING[button_release], button); + button->state = STATE_DOUBLE_CLICKED; + emitState(eventMsg, button); + button->state = STATE_IDLE; + } + else { + emitState(eventMsg, button); + button->state = STATE_RELEASE_WAIT; + } + break; + + case STATE_DOUBLE_CLICKED: + case STATE_RELEASE_WAIT: + emitState(eventMsg, button); + button->state = STATE_IDLE; + break; + } + } + else { + // emit state and transition state + emitState(eventMsg, button); + switch(button->state) { + case STATE_PRESSED: + case STATE_CLICKED_PRESSED: + button->state = STATE_RELEASE_WAIT; + break; + + default: + button->state = STATE_IDLE; + break; + } + } + + // if child has lock then perform handshake with parent + if (!lockStatus) { + pthread_mutex_unlock(&button->lockControl); // release for synchronization + pthread_barrier_wait(&button->barrierControl); // begin synchronization + pthread_barrier_wait(&button->barrierControl); // wait for synchronized + } + } +} + + + +void * socketServer(void * args) { + int * clients; + clients = (int *)args; + int l, fd, socket = openSocket(); + + while (1) { + // wait for connection + if ( (fd = accept(socket, NULL, NULL)) == -1) { + fprintf(stderr, "Error accepting incoming connection.\n"); + continue; + } + + for(l = 0; l < MAX_CLIENTS; l++) { + if (clients[l] == -1) { + clients[l] = fd; + break; + } + } + + if (l == MAX_CLIENTS) { + send(fd, ERROR_MAX_CLIENTS, strlen(ERROR_MAX_CLIENTS), MSG_NOSIGNAL); + } + else { + // add connection to empty slot +printf("Connect %d\n", fd); + } + } +} + + +// emit event for the current button state +void emitState(char * buffer, buttonDefinition * button) { + switch (button->state) { + case STATE_PRESSED: + // emit event and transition to release wait + emitFormattedMessage(buffer, EVENT_STRING[pressed], button); + break; + + case STATE_CLICKED: + // emit event and transition to idle + emitFormattedMessage(buffer, EVENT_STRING[clicked], button); + break; + + case STATE_CLICKED_PRESSED: + // emit event and transition to release wait + emitFormattedMessage(buffer, EVENT_STRING[clicked_pressed], button); + break; + + case STATE_DOUBLE_CLICKED: + // emit event and transition to idle + emitFormattedMessage(buffer, EVENT_STRING[double_clicked], button); + break; + + case STATE_RELEASE_WAIT: + // emit event and transition to idle + emitFormattedMessage(buffer, EVENT_STRING[released], button); + break; + } +} + + +void emitFormattedMessage(char * buffer, const char * eventString, buttonDefinition * button) { + if (strlen(EVENT_MSG_FORMAT) + strlen(eventString) + strlen(button->gpio) >= EVENT_MSG_MAX_LENGTH) { + fprintf(stderr, "Emit message too large for buffer."); + } + else { + sprintf(buffer, EVENT_MSG_FORMAT, eventString, button->gpio, button->lastTime.tv_sec, button->lastTime.tv_nsec); + emitMessage(buffer, button->clients); + } +} + + +void emitMessage(const char * msg, int * clients) { + int l, wl; + for(l = 0; l < MAX_CLIENTS; l++) { + if (clients[l] != -1) { + wl = send(clients[l], msg, strlen(msg), MSG_NOSIGNAL); + if (wl == -1) { + // failure, remove client + close(clients[l]); + clients[l] = -1; + } + } + } +} + + +int openSocket() { + struct sockaddr_un addr; + int fd; + + if ( (fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { + fprintf(stderr, "Error opening event socket."); + exit(-1); + } + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + strncpy(addr.sun_path, EVENT_SOCKET_PATH, sizeof(addr.sun_path)-1); + unlink(EVENT_SOCKET_PATH); + + if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) { + fprintf(stderr, "Event socket bind error."); + exit(-1); + } + + if (listen(fd, 5) == -1) { + fprintf(stderr, "Event socket listen error."); + exit(-1); + } + + return fd; +} + + +void setConditionNS(struct timespec * currentTime, struct timespec * targetTime, uint32_t ns) { + targetTime->tv_sec = currentTime->tv_sec; + targetTime->tv_nsec = currentTime->tv_nsec; + if (1000000000 - targetTime->tv_nsec < ns) { + targetTime->tv_sec += 1; + targetTime->tv_nsec = ns - (targetTime->tv_nsec - 1000000000); + } + else { + targetTime->tv_nsec = targetTime->tv_nsec + ns; + } +} diff --git a/lib/pi-buttons/buttons.h b/lib/pi-buttons/buttons.h new file mode 100644 index 0000000..4949758 --- /dev/null +++ b/lib/pi-buttons/buttons.h @@ -0,0 +1,108 @@ +#define EVENT_SOCKET_PATH "./buttonevents" +#define MAX_BUTTONS 10 +#define MAX_CLIENTS 2 +#define ERROR_MAX_CLIENTS "error {\"error\": \"Maximum client connections exceeded.\"}" + +enum ButtonState { + STATE_INIT, + STATE_IDLE, + STATE_PRESSED, + STATE_CLICKED, + STATE_CLICKED_PRESSED, + STATE_DOUBLE_CLICKED, + STATE_RELEASE_WAIT +}; + +enum DebounceState { + INACTIVE, + ACTIVE +}; + +enum LockTimeoutState { + TIMEOUT_FALSE, + TIMEOUT_TRUE +}; + +enum ButtonValue { + PRESSED = 48, + RELEASED +}; + +// I.E. 'button_changed {"gpio": "17", "time": 012345678900123456789}' +#define EVENT_MSG_MAX_LENGTH 128 +static const char * EVENT_MSG_FORMAT = "%s {\"gpio\": \"%s\", \"time\": {\"tv_sec\": %ld, \"tv_nsec\": %ld}}\n"; + +// define events +#define FOREACH_EVENT(EVENT) \ + EVENT(button_changed) \ + EVENT(button_press) \ + EVENT(button_release) \ + EVENT(pressed) \ + EVENT(clicked) \ + EVENT(clicked_pressed) \ + EVENT(double_clicked) \ + EVENT(released) \ + +#define GENERATE_ENUM(ENUM) ENUM, +#define GENERATE_STRING(STRING) #STRING, + +enum EVENT_ENUM { + FOREACH_EVENT(GENERATE_ENUM) +}; + +static const char *EVENT_STRING[] = { + FOREACH_EVENT(GENERATE_STRING) +}; + + +#define SEC_NSEC 1000000000 +#define DEBOUNCE_MS 30 +#define DEBOUNCE_NS 20000000 +#define PRESSED_MS 200 +#define PRESSED_NS 200000000 +#define CLICKED_MS 200 +#define CLICKED_NS 200000000 + +typedef struct { + pthread_mutex_t lockControl; + pthread_barrier_t barrierControl; + struct timespec lastTime; + struct timespec conditionTime; + const char * gpio; + int fd; // file descriptor for button input + enum ButtonState state; + int debounceState; + uint8_t value; + uint8_t lastValue; + int * clients; + pthread_t parent; + pthread_t child; + long debounce_ns; +} buttonDefinition; + +typedef struct { + char ** gpios; + int gpioCount; + int * clients; +} pollerThreadArgs; + +typedef struct { + int index; + int fd; // file descriptor for button input + enum ButtonState state; + int debouncing; + uint8_t value; + uint8_t lastValue; + int * clients; +} gpioButton; + +void * buttonPoller(void * args); +void * buttonDebounce(void * args); +void * socketServer(void * args); +int openSocket(); +void emitMessage(const char * msg, int * clients); +void * buttonParent(void * args); +void * buttonChild(void * args); +void emitState(char * buffer, buttonDefinition * button); +void emitFormattedMessage(char * buffer, const char * eventString, buttonDefinition * button); +void setConditionNS(struct timespec * currentTime, struct timespec * targetTime, uint32_t ns); diff --git a/lib/pi-buttons/index.js b/lib/pi-buttons/index.js new file mode 100644 index 0000000..7f92464 --- /dev/null +++ b/lib/pi-buttons/index.js @@ -0,0 +1,28 @@ +'use strict'; + +const net = require('net'); +const events = require('events'); +const emitter = new events.EventEmitter(); +const pins = { [17]: 11, [27]: 13 }; + +var client = net.createConnection(__dirname + "/buttonevents"); + +client.on("connect", function() { + +}); + +client.on("data", function(data) { + let packets = data.toString().split(/\r?\n/); + packets.forEach(packet => { + var parts = /^([^{]+)\s({.*})/.exec(packet); + if (parts) { + try { + var d = JSON.parse(parts[2]); + emitter.emit(parts[1], pins[d.gpio], d); + } + catch (e) {} + } + }); +}); + +module.exports = emitter; diff --git a/lib/pi-buttons/setup.sh b/lib/pi-buttons/setup.sh new file mode 100755 index 0000000..14cf0a6 --- /dev/null +++ b/lib/pi-buttons/setup.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +echo 17 > /sys/class/gpio/export +echo 22 > /sys/class/gpio/export +echo 27 > /sys/class/gpio/export + +sleep 3 + +echo "in" > /sys/class/gpio/gpio17/direction +echo "both" > /sys/class/gpio/gpio17/edge + +echo "in" > /sys/class/gpio/gpio27/direction +echo "both" > /sys/class/gpio/gpio27/edge + +echo "out" > /sys/class/gpio/gpio22/direction diff --git a/openaps-menu.sh b/openaps-menu.sh index 3cfeb21..ea3f3f0 100755 --- a/openaps-menu.sh +++ b/openaps-menu.sh @@ -1,2 +1,3 @@ #!/bin/bash -node index.js + +(cd lib/pi-buttons/ && ./setup.sh && ./a.out) & node index.js diff --git a/package.json b/package.json index 3fb697e..8994aa2 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "oled-font-5x7": "^1.0.0", "oled-i2c-bus": "git+https://github.com/bnielsen1965/oled-i2c-bus.git", "pngparse": "^2.0.1", - "node-pi-buttons": "git+https://github.com/bnielsen1965/node-pi-buttons.git", - "rpi-gpio": "^0.9.1" + "rpi-gpio": "^0.9.1", + "rpi-gpio-buttons": "^1.0.2" } } From 837282db244425fca338fab57ce60a9fab375501 Mon Sep 17 00:00:00 2001 From: Jon Cluck Date: Tue, 24 Jul 2018 11:05:04 -0400 Subject: [PATCH 22/35] Don't crash if buttons are pressed & screen broken This should help keep openaps-menu from crashing when the screen is broken. --- index.js | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/index.js b/index.js index d1561df..3019fe8 100644 --- a/index.js +++ b/index.js @@ -108,16 +108,18 @@ hidMenu // display the current menu on the display function showMenu(menu) { - display.clear(); - var text = ''; + if (display) { + display.clear(); + var text = ''; - var p = menu.getParentSelect(); - text += p ? '[' + p.label + ']\n' : ''; - var c = menu.getCurrentSelect(); - menu.getActiveMenu().forEach(function (m) { - text += (m.selected ? '>' : ' ') + m.label + '\n'; - }); + var p = menu.getParentSelect(); + text += p ? '[' + p.label + ']\n' : ''; + var c = menu.getCurrentSelect(); + menu.getActiveMenu().forEach(function (m) { + text += (m.selected ? '>' : ' ') + m.label + '\n'; + }); -// console.log(text); - display.write(text); + // console.log(text); + display.write(text); + } } From 1595979bf0f70d119c83c904da040426b02d032c Mon Sep 17 00:00:00 2001 From: Jon Cluck Date: Mon, 6 Aug 2018 11:39:51 -0400 Subject: [PATCH 23/35] bugfixes and add preferences toggle for status --- index.js | 45 ++++++++++++++++++++++++++++++--------------- package.json | 2 +- scripts/status.js | 6 +++++- 3 files changed, 36 insertions(+), 17 deletions(-) diff --git a/index.js b/index.js index d1561df..206dca4 100644 --- a/index.js +++ b/index.js @@ -10,6 +10,7 @@ const i2c = require('i2c-bus'); const path = require('path'); const pngparse = require('pngparse'); const extend = require('extend'); +var fs = require('fs'); var i2cBus = i2c.openSync(1); @@ -21,7 +22,7 @@ try { var display = require('./lib/display/ssd1306')(displayConfig); displayImage('./static/unicorn.png'); //display logo } catch (e) { - console.warn("Could not setup display:", e); + console.warn("Could not setup display:", e); } // setup battery voltage monitor @@ -44,7 +45,18 @@ socketServer }) .on('displaystatus', function () { if (display) { - graphicalStatus(display); + var preferences; + fs.readFile('/root/myopenaps/preferences.json', function (err, data) { + if (err) throw err; + preferences = JSON.parse(data); + if (preferences.status_screen == "bigbgstatus") { + bigBGStatus(display); + } else if (preferences.status_screen == "off") { + //don't auto-update the screen if it's turned off + } else { + graphStatus(display); //default to graph status + } + }); } }) @@ -58,8 +70,9 @@ function displayImage(pathToImage) { } // load up graphical status scripts -const graphicalStatus = require('./scripts/status.js'); +const graphStatus = require('./scripts/status.js'); const bigBGStatus = require('./scripts/big_bg_status.js'); +// if you want to add your own status display script, it will be easiest to replace one of the above! // setup the menus var buttonsConfig = require('./config/buttons.json'); @@ -78,8 +91,8 @@ var hidMenu = require('./lib/hid-menu/hid-menu')(buttonsConfig, menuConfig); hidMenu .on('nothing', function () { }) -.on('showGFXstatus', function () { - graphicalStatus(display); +.on('showgraphstatus', function () { + graphStatus(display); }) .on('showbigBGstatus', function () { bigBGStatus(display); @@ -108,16 +121,18 @@ hidMenu // display the current menu on the display function showMenu(menu) { - display.clear(); - var text = ''; + if (display) { + display.clear(); + var text = ''; - var p = menu.getParentSelect(); - text += p ? '[' + p.label + ']\n' : ''; - var c = menu.getCurrentSelect(); - menu.getActiveMenu().forEach(function (m) { - text += (m.selected ? '>' : ' ') + m.label + '\n'; - }); + var p = menu.getParentSelect(); + text += p ? '[' + p.label + ']\n' : ''; + var c = menu.getCurrentSelect(); + menu.getActiveMenu().forEach(function (m) { + text += (m.selected ? '>' : ' ') + m.label + '\n'; + }); -// console.log(text); - display.write(text); + // console.log(text); + display.write(text); + } } diff --git a/package.json b/package.json index 3fb697e..0ab2c55 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "i2c-bus": "^1.2.2", "menube": "^1.0.3", "oled-font-5x7": "^1.0.0", - "oled-i2c-bus": "git+https://github.com/bnielsen1965/oled-i2c-bus.git", + "oled-i2c-bus": "git+https://github.com/baltazorr/oled-i2c-bus.git", "pngparse": "^2.0.1", "node-pi-buttons": "git+https://github.com/bnielsen1965/node-pi-buttons.git", "rpi-gpio": "^0.9.1" diff --git a/scripts/status.js b/scripts/status.js index 7b1399a..ee97a61 100644 --- a/scripts/status.js +++ b/scripts/status.js @@ -39,7 +39,7 @@ module.exports = graphicalStatus; function graphicalStatus(display) { -display.oled.clearDisplay(false); //clear display buffer +display.oled.clearDisplay(true); //clear display buffer //Parse all the .json files we need try { @@ -116,6 +116,10 @@ if (status && suggested) { display.oled.writeString(font, 1, "CGM data unchanged", 1, false, 0, false); yOffset = 3; } + else if (notLoopingReason.includes("BG data is too old")) { + display.oled.writeString(font, 1, "BG data too old", 1, false, 0, false); + yOffset = 3; + } //add more on-screen warnings/messages, maybe some special ones for xdrip-js users? } From 0fee87992d3b9822c15d4876034a8a8cb5f5a8ef Mon Sep 17 00:00:00 2001 From: Jon Cluck Date: Mon, 6 Aug 2018 13:54:43 -0400 Subject: [PATCH 24/35] more preferences, update big_bg_status.js --- index.js | 12 ++++++---- scripts/big_bg_status.js | 51 ++++++++++++++++++++++++---------------- scripts/status.js | 16 +++++++------ 3 files changed, 47 insertions(+), 32 deletions(-) diff --git a/index.js b/index.js index 206dca4..a65e74c 100644 --- a/index.js +++ b/index.js @@ -14,6 +14,8 @@ var fs = require('fs'); var i2cBus = i2c.openSync(1); +var openapsDir = "/root/myopenaps"; //if you're using a nonstandard OpenAPS directory, set that here. NOT RECOMMENDED. + // setup the display var displayConfig = require('./config/display.json'); displayConfig.i2cBus = i2cBus; @@ -46,15 +48,15 @@ socketServer .on('displaystatus', function () { if (display) { var preferences; - fs.readFile('/root/myopenaps/preferences.json', function (err, data) { + fs.readFile(openapsDir+'/preferences.json', function (err, data) { if (err) throw err; preferences = JSON.parse(data); if (preferences.status_screen == "bigbgstatus") { - bigBGStatus(display); + bigBGStatus(display, openapsDir); } else if (preferences.status_screen == "off") { //don't auto-update the screen if it's turned off } else { - graphStatus(display); //default to graph status + graphStatus(display, openapsDir); //default to graph status } }); } @@ -92,10 +94,10 @@ hidMenu .on('nothing', function () { }) .on('showgraphstatus', function () { - graphStatus(display); + graphStatus(display, openapsDir); }) .on('showbigBGstatus', function () { - bigBGStatus(display); + bigBGStatus(display, openapsDir); }) .on('showlogo', function () { displayImage('./static/unicorn.png'); diff --git a/scripts/big_bg_status.js b/scripts/big_bg_status.js index db1145f..3e9483b 100644 --- a/scripts/big_bg_status.js +++ b/scripts/big_bg_status.js @@ -1,10 +1,3 @@ -module.exports = bigbgstatus; - -function bigbgstatus(display) { - -const path = require('path'); -const extend = require('extend'); -var os = require('os'); var fs = require('fs'); var font = require('oled-font-5x7'); @@ -34,38 +27,45 @@ function stripLeadingZero(value) return value.toString().replace( re, '$1'); } -display.oled.dimDisplay(true); //dim the display -display.oled.clearDisplay(false); //clear the buffer +module.exports = bigbgstatus; + +// +//Start of status display function +// + +function bigbgstatus(display, openapsDir) { + +display.oled.clearDisplay(true); //clear the buffer //Parse all the .json files we need try { - var profile = JSON.parse(fs.readFileSync("/root/myopenaps/settings/profile.json")); + var profile = JSON.parse(fs.readFileSync(openapsDir+"/settings/profile.json")); } catch (e) { // Note: profile.json is optional as it's only needed for mmol conversion for now. Print an error, but not return console.error("Status screen display error: could not parse profile.json: ", e); } try { - var batterylevel = JSON.parse(fs.readFileSync("/root/myopenaps/monitor/edison-battery.json")); + var batterylevel = JSON.parse(fs.readFileSync(openapsDir+"/monitor/edison-battery.json")); } catch (e) { console.error("Status screen display error: could not parse edison-battery.json: ", e); } try { - var suggested = JSON.parse(fs.readFileSync("/root/myopenaps/enact/suggested.json")); + var suggested = JSON.parse(fs.readFileSync(openapsDir+"/enact/suggested.json")); } catch (e) { console.error("Status screen display error: could not parse suggested.json: ", e); } try { - var bg = JSON.parse(fs.readFileSync("/root/myopenaps/monitor/glucose.json")); + var bg = JSON.parse(fs.readFileSync(openapsDir+"/monitor/glucose.json")); } catch (e) { console.error("Status screen display error: could not parse glucose.json: ", e); } try { - var iob = JSON.parse(fs.readFileSync("/root/myopenaps/monitor/iob.json")); + var iob = JSON.parse(fs.readFileSync(openapsDir+"/monitor/iob.json")); } catch (e) { console.error("Status screen display error: could not parse iob.json: ", e); } try { - var cob = JSON.parse(fs.readFileSync("/root/myopenaps/monitor/meal.json")); + var cob = JSON.parse(fs.readFileSync(openapsDir+"/monitor/meal.json")); } catch (e) { console.error("Status screen display error: could not parse meal.json: ", e); } @@ -139,10 +139,21 @@ if(iob && cob) { display.oled.writeString(font, 2, " "+cob.mealCOB+'g', 1, false, 0, false); } -//display everything in the buffer -display.oled.update(); +display.oled.dimDisplay(true); //dim the display +display.oled.update(); // write buffer to the screen + +fs.readFile(openapsDir+"/preferences.json", function (err, data) { + if (err) throw err; + preferences = JSON.parse(data); + if (preferences.wearOLEDevenly == false) { + display.oled.invertDisplay(false); + } else { + display.oled.invertDisplay((endDate % 2 == 1)); + } +}); + + // +}//End of status display function + // -//fandomly invert display to evenly wear the OLED diodes -display.oled.invertDisplay((endDate % 2 == 1)); -} //from start of function diff --git a/scripts/status.js b/scripts/status.js index ee97a61..5f727f6 100644 --- a/scripts/status.js +++ b/scripts/status.js @@ -1,9 +1,5 @@ var fs = require('fs'); var font = require('oled-font-5x7'); -const homeDir = require('os').homedir(); - -var openapsDir = "/root/myopenaps"; //if you're using a nonstandard OpenAPS directory, set that here -var evenOLEDwear = true; //if you want to prevent OLED burn-in symptoms by inverting the screen, set this to true // Rounds value to 'digits' decimal places function round(value, digits) @@ -37,7 +33,7 @@ module.exports = graphicalStatus; //Start of status display function // -function graphicalStatus(display) { +function graphicalStatus(display, openapsDir) { display.oled.clearDisplay(true); //clear display buffer @@ -233,9 +229,15 @@ display.oled.writeString(font, 1, clockHour+":"+clockMin, 1, false, 0, false); display.oled.dimDisplay(true); //dim the display display.oled.update(); //write buffer to the screen -if (evenOLEDwear == true) { +fs.readFile(openapsDir+"/preferences.json", function (err, data) { + if (err) throw err; + preferences = JSON.parse(data); + if (preferences.wearOLEDevenly == false) { + display.oled.invertDisplay(false); + } else { display.oled.invertDisplay((endDate % 2 == 1)); -} + } +}); // }//End of status display function From 281334e4ebd5c55d59f345f98f3ec5261758e80b Mon Sep 17 00:00:00 2001 From: Jon Cluck Date: Mon, 6 Aug 2018 14:06:29 -0400 Subject: [PATCH 25/35] Update README.md --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index f2278e5..ca21808 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,18 @@ This is the repository holding the menu-based software code, which you may choos See [here](https://github.com/EnhancedRadioDevices/Explorer-HAT) for more details on the Explorer HAT hardware. +You can set your preferred auto-updating status screen using the following setting in your `~/myopenaps/preferences.json`: + +`"status_screen": "bigbgstatus"` will display the big BG status screen (no graph). + +`"status_screen": "off"` will turn the auto-updating screen off. + + +The auto-updating status script will invert the display about 50% of the time, to prevent burn-in on the OLED screen. You can turn this off with the following setting in your `~/myopenaps/preferences.json`: + +`"wearOLEDevenly": false` + + ## Example screen outputs (Note: these are examples. The latest code may yield different menu items and screen displays) ### Status screen: From 521eeec53601a3c005cfcc41d6d245740e2b381d Mon Sep 17 00:00:00 2001 From: Jon Cluck Date: Mon, 6 Aug 2018 14:10:09 -0400 Subject: [PATCH 26/35] Update menu.json --- config/menus/menu.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/menus/menu.json b/config/menus/menu.json index 887780b..11dfac7 100644 --- a/config/menus/menu.json +++ b/config/menus/menu.json @@ -4,7 +4,7 @@ "menu": [ { "label": "Status Graph", - "emit": "showGFXstatus" + "emit": "showgraphstatus" }, { "label": "Big BG Status", From 06c0d51f623c3fd82e0b5d49dcc364fd1d91e6c0 Mon Sep 17 00:00:00 2001 From: Jon Cluck Date: Tue, 25 Sep 2018 13:35:41 -0400 Subject: [PATCH 27/35] Delete casstatus.js --- scripts/casstatus.js | 176 ------------------------------------------- 1 file changed, 176 deletions(-) delete mode 100644 scripts/casstatus.js diff --git a/scripts/casstatus.js b/scripts/casstatus.js deleted file mode 100644 index 8da8ba3..0000000 --- a/scripts/casstatus.js +++ /dev/null @@ -1,176 +0,0 @@ -var fs = require('fs'); -var font = require('oled-font-5x7'); -const homeDir = require('os').homedir(); - -var openapsDir = "/root/myopenaps"; //if you're using a nonstandard OpenAPS directory, set that here -var evenOLEDwear = true; //if you want to prevent OLED burn-in symptoms by inverting the screen, set this to true - -// Rounds value to 'digits' decimal places -function round(value, digits) -{ - if (! digits) { digits = 0; } - var scale = Math.pow(10, digits); - return Math.round(value * scale) / scale; -} - -function convert_bg(value, profile) -{ - if (profile != null && profile.out_units == "mmol/L") - { - return round(value / 18, 1).toFixed(1); - } - else - { - return Math.round(value); - } -} - -function stripLeadingZero(value) -{ - var re = /^(-)?0+(?=[\.\d])/; - return value.toString().replace( re, '$1'); -} - -module.exports = graphicalStatus; - -// -//Start of status display function -// - -function graphicalStatus(display) { - -display.oled.clearDisplay(false); //clear display buffer - -//Parse all the .json files we need -try { - var profile = JSON.parse(fs.readFileSync(openapsDir+"/settings/profile.json")); -} catch (e) { - console.error("Status screen display error: could not parse profile.json: ", e); -} -try { - var suggested = JSON.parse(fs.readFileSync(openapsDir+"/enact/suggested.json")); -} catch (e) { - console.error("Status screen display error: could not parse suggested.json: ", e); -} -try { - var bg = JSON.parse(fs.readFileSync(openapsDir+"/monitor/glucose.json")); -} catch (e) { - console.error("Status screen display error: could not parse glucose.json: ", e); -} -try { - var iob = JSON.parse(fs.readFileSync(openapsDir+"/monitor/iob.json")); -} catch (e) { - console.error("Status screen display error: could not parse iob.json: ", e); -} -try { - var cob = JSON.parse(fs.readFileSync(openapsDir+"/monitor/meal.json")); -} catch (e) { - console.error("Status screen display error: could not parse meal.json: ", e); -} - -var yOffset = 12; //offset for graph, if we need to move it - -//display current target(s) -if (profile) { - var targetLow = Math.round( (21+yOffset) - ( ( profile.bg_targets.targets[0].low - 250 ) / 8 ) ); - var targetHigh = Math.round( (21+yOffset) - ( ( profile.bg_targets.targets[0].high - 250 ) / 8 ) ); - display.oled.drawLine(2, targetHigh, 5, targetHigh, 1, false); - display.oled.drawLine(2, targetLow, 5, targetLow, 1, false); -} - -if (bg) { - //render BG graph - var numBGs = (suggested.predBGs != undefined) ? (72) : (120); //fill the whole graph with BGs if there are no predictions - var date = new Date(); - var zerotime = date.getTime() - ((numBGs * 5) * 600); - var zero_x = numBGs + 5; - for (var i = 0; i < numBGs; i++) { - if (bg[i] != null) { - var x = zero_x + Math.round(((((bg[i].date - zerotime)/1000)/60)/5)); - var y = Math.round( (21+yOffset) - ( ( bg[i].glucose - 250 ) / 8 ) ); - //left and right boundaries - if ( x < 5 ) x = 5; - if ( x > 127 ) x = 127; - //upper and lower boundaries - if ( y < (21+yOffset) ) y = (21+yOffset); - if ( y > (51+yOffset) ) y = (51+yOffset); - display.oled.drawPixel([x, y, 1, false]); - // if we have multiple data points within 3m, look further back to fill in the graph - if ( bg[i-1] && bg[i-1].date - bg[i].date < 200000 ) { - numBGs++; - } - } - } - - //calculate timeago for BG - var startDate = new Date(bg[0].date); - var endDate = new Date(); - var minutes = Math.round(( (endDate.getTime() - startDate.getTime()) / 1000) / 60); - if (bg[0].delta) { - var delta = Math.round(bg[0].delta); - } else if (bg[1] && bg[0].date - bg[1].date > 200000 ) { - var delta = Math.round(bg[0].glucose - bg[1].glucose); - } else if (bg[2] && bg[0].date - bg[2].date > 200000 ) { - var delta = Math.round(bg[0].glucose - bg[2].glucose); - } else if (bg[3] && bg[0].date - bg[3].date > 200000 ) { - var delta = Math.round(bg[0].glucose - bg[3].glucose); - } else { - var delta = 0; - } - //display BG number and timeago, add plus sign if delta is positive - display.oled.setCursor(0,0); - if (delta >= 0) { - //display.oled.writeString(font, 3, "42.0", 1, false, 0, false); - //display.oled.writeString(font, 2, "+6.9", 1, false, 0, false); - display.oled.writeString(font, 3, ""+convert_bg(bg[0].glucose, profile), 1, false, 0, false); - display.oled.writeString(font, 2, "+"+stripLeadingZero(convert_bg(delta, profile)), 1, false, 0, false); - } else { - display.oled.writeString(font, 3, ""+convert_bg(bg[0].glucose, profile), 1, false, 0, false); - display.oled.writeString(font, 2, ""+stripLeadingZero(convert_bg(delta, profile)), 1, false, 0, false); - } -} - -//render predictions on the graph, but only if we have them -if (bg && suggested && suggested.predBGs != undefined) { - //render line between actual BG and predicted - x = zero_x + 1; - display.oled.drawLine(x, 51+yOffset, x, 21+yOffset, 1, false); - //render predictions - var predictions = [suggested.predBGs.IOB, suggested.predBGs.ZT, suggested.predBGs.UAM, suggested.predBGs.COB]; - for (i = 0; i <= 48; i++) { - x++; - for(var n = 0; n <=3 && (predictions[n] != undefined); n++) { - y = Math.round( (21+yOffset) - ( (predictions[n][i] - 250 ) / 8) ); - //right boundary - if ( x > 127 ) x = 127; - //upper and lower boundaries - if ( y < (21+yOffset) ) y = (21+yOffset); - if ( y > (51+yOffset) ) y = (51+yOffset); - display.oled.drawPixel([x, y, 1, false]); - } - } -} - -//display current COB and IOB, on the second line of the screen -if (iob && cob) { - display.oled.setCursor(0,22); - display.oled.writeString(font, 1, "COB: "+cob.mealCOB+"g IOB: "+iob[0].iob+"U", 1, false, 0, false); -} - -//display bg graph axes -display.oled.drawLine(5, 51+yOffset, 5, 21+yOffset, 1, false); -display.oled.drawLine(5, 51+yOffset, 127, 51+yOffset, 1, false); - -//render clock - -display.oled.dimDisplay(true); //dim the display -display.oled.update(); //write buffer to the screen - -if (evenOLEDwear == true) { - var endDate = new Date(); - display.oled.invertDisplay((endDate % 2 == 1)); -} - - // -}//End of status display function - // From 7cfbc0afd2ecc8e16d6f228061024749404fadd6 Mon Sep 17 00:00:00 2001 From: Jon Cluck Date: Tue, 25 Sep 2018 14:13:23 -0400 Subject: [PATCH 28/35] Revert package.json Bryan merged upstream bugfixes... --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 99b384d..8994aa2 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "i2c-bus": "^1.2.2", "menube": "^1.0.3", "oled-font-5x7": "^1.0.0", - "oled-i2c-bus": "git+https://github.com/baltazorr/oled-i2c-bus.git", + "oled-i2c-bus": "git+https://github.com/bnielsen1965/oled-i2c-bus.git", "pngparse": "^2.0.1", "rpi-gpio": "^0.9.1", "rpi-gpio-buttons": "^1.0.2" From b3fecda8a6cac37e60dfea22a347071d4089af25 Mon Sep 17 00:00:00 2001 From: Jon Cluck Date: Tue, 25 Sep 2018 16:03:39 -0400 Subject: [PATCH 29/35] Remove comma Random comma? --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index a65e74c..482e28d 100644 --- a/index.js +++ b/index.js @@ -36,7 +36,7 @@ var voltage = require('./lib/voltage/voltage')(voltageConfig) var batteryConfig = require('./config/battery.json') var socketServer = require('./lib/socket-server/socket-server')({ voltage: voltage, - battery: batteryConfig, + battery: batteryConfig }) socketServer .on('error', (err) => { From 89372a2f28accd973be61214cd0b52217d053386 Mon Sep 17 00:00:00 2001 From: Jon Cluck Date: Tue, 25 Sep 2018 17:34:36 -0400 Subject: [PATCH 30/35] Add preferences switch info --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ca21808..675d22c 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,13 @@ You can set your preferred auto-updating status screen using the following setti `"status_screen": "off"` will turn the auto-updating screen off. -The auto-updating status script will invert the display about 50% of the time, to prevent burn-in on the OLED screen. You can turn this off with the following setting in your `~/myopenaps/preferences.json`: +By default, the auto-updating status script will invert the display about 50% of the time, to prevent burn-in on the OLED screen. You can turn this off with the following setting in your `~/myopenaps/preferences.json`: -`"wearOLEDevenly": false` +`"wearOLEDevenly": "off"` + +Or you can have it invert the display from 8pm to 8am with: + +`"wearOLEDevenly": "nightandday"` ## Example screen outputs (Note: these are examples. The latest code may yield different menu items and screen displays) From f0addabd9e49a101645094ded31cc1d1bae1b432 Mon Sep 17 00:00:00 2001 From: Jon Cluck Date: Tue, 25 Sep 2018 17:35:30 -0400 Subject: [PATCH 31/35] Add 8am-8pm invert display option. --- scripts/big_bg_status.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/scripts/big_bg_status.js b/scripts/big_bg_status.js index 3e9483b..f9da241 100644 --- a/scripts/big_bg_status.js +++ b/scripts/big_bg_status.js @@ -141,13 +141,20 @@ if(iob && cob) { display.oled.dimDisplay(true); //dim the display display.oled.update(); // write buffer to the screen - + fs.readFile(openapsDir+"/preferences.json", function (err, data) { if (err) throw err; preferences = JSON.parse(data); - if (preferences.wearOLEDevenly == false) { + if (preferences.wearOLEDevenly.includes("off")) { + display.oled.invertDisplay(false); + } + else if (preferences.wearOLEDevenly.includes("nightandday") && (clockHour >= 20 || clockHour <= 8)) { display.oled.invertDisplay(false); - } else { + } + else if (preferences.wearOLEDevenly.includes("nightandday") && (clockHour <= 20 || clockHour >= 8)) { + display.oled.invertDisplay(true); + } + else { display.oled.invertDisplay((endDate % 2 == 1)); } }); From 7efd19810fce9f06d47e0be4489f9647853fdc35 Mon Sep 17 00:00:00 2001 From: Jon Cluck Date: Tue, 25 Sep 2018 17:36:31 -0400 Subject: [PATCH 32/35] Add 8am-8pm invert display option. --- scripts/status.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/scripts/status.js b/scripts/status.js index 5f727f6..685b086 100644 --- a/scripts/status.js +++ b/scripts/status.js @@ -232,9 +232,16 @@ display.oled.update(); //write buffer to the screen fs.readFile(openapsDir+"/preferences.json", function (err, data) { if (err) throw err; preferences = JSON.parse(data); - if (preferences.wearOLEDevenly == false) { + if (preferences.wearOLEDevenly.includes("off")) { display.oled.invertDisplay(false); - } else { + } + else if (preferences.wearOLEDevenly.includes("nightandday") && (clockHour >= 20 || clockHour <= 8)) { + display.oled.invertDisplay(false); + } + else if (preferences.wearOLEDevenly.includes("nightandday") && (clockHour <= 20 || clockHour >= 8)) { + display.oled.invertDisplay(true); + } + else { display.oled.invertDisplay((endDate % 2 == 1)); } }); From 2995f52f234d0c6b02677471e0dfefc53cf6f429 Mon Sep 17 00:00:00 2001 From: Jon Cluck Date: Thu, 8 Nov 2018 09:37:59 -0500 Subject: [PATCH 33/35] Variable name bugfix --- scripts/big_bg_status.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/big_bg_status.js b/scripts/big_bg_status.js index f9da241..2382142 100644 --- a/scripts/big_bg_status.js +++ b/scripts/big_bg_status.js @@ -148,10 +148,10 @@ fs.readFile(openapsDir+"/preferences.json", function (err, data) { if (preferences.wearOLEDevenly.includes("off")) { display.oled.invertDisplay(false); } - else if (preferences.wearOLEDevenly.includes("nightandday") && (clockHour >= 20 || clockHour <= 8)) { + else if (preferences.wearOLEDevenly.includes("nightandday") && (hour >= 20 || hour <= 8)) { display.oled.invertDisplay(false); } - else if (preferences.wearOLEDevenly.includes("nightandday") && (clockHour <= 20 || clockHour >= 8)) { + else if (preferences.wearOLEDevenly.includes("nightandday") && (hour <= 20 || hour >= 8)) { display.oled.invertDisplay(true); } else { From b3bf41a1e373ced83fac87bfc2bad861ebb25622 Mon Sep 17 00:00:00 2001 From: Jon Cluck Date: Thu, 8 Nov 2018 09:43:37 -0500 Subject: [PATCH 34/35] Logic fix for day/night inversion --- scripts/status.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/status.js b/scripts/status.js index 685b086..c40693c 100644 --- a/scripts/status.js +++ b/scripts/status.js @@ -238,7 +238,7 @@ fs.readFile(openapsDir+"/preferences.json", function (err, data) { else if (preferences.wearOLEDevenly.includes("nightandday") && (clockHour >= 20 || clockHour <= 8)) { display.oled.invertDisplay(false); } - else if (preferences.wearOLEDevenly.includes("nightandday") && (clockHour <= 20 || clockHour >= 8)) { + else if (preferences.wearOLEDevenly.includes("nightandday") && (clockHour <= 20 && clockHour >= 8)) { display.oled.invertDisplay(true); } else { From 8059d33424afae22caecfb652da4cbaba6868e1b Mon Sep 17 00:00:00 2001 From: Jon Cluck Date: Thu, 8 Nov 2018 09:44:01 -0500 Subject: [PATCH 35/35] Logic fix for day/night inversion --- scripts/big_bg_status.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/big_bg_status.js b/scripts/big_bg_status.js index 2382142..a426bf8 100644 --- a/scripts/big_bg_status.js +++ b/scripts/big_bg_status.js @@ -151,7 +151,7 @@ fs.readFile(openapsDir+"/preferences.json", function (err, data) { else if (preferences.wearOLEDevenly.includes("nightandday") && (hour >= 20 || hour <= 8)) { display.oled.invertDisplay(false); } - else if (preferences.wearOLEDevenly.includes("nightandday") && (hour <= 20 || hour >= 8)) { + else if (preferences.wearOLEDevenly.includes("nightandday") && (hour <= 20 && hour >= 8)) { display.oled.invertDisplay(true); } else {