Files
NightscoutEInkClient/NightscoutEInkClient.ino

480 lines
13 KiB
C++

#include "Adafruit_ThinkInk.h"
#include <ArduinoJson.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <time.h>
#include "esp_sleep.h"
//PIN LAYOUTS
#define SRAM_CS 32
#define EPD_CS 15
#define EPD_DC 33
#define EPD_RESET -1 // can set to -1 and share with microcontroller Reset!
#define EPD_BUSY 27 // can set to -1 to not use a pin (will wait a fixed delay)
#define EPD_ENABLE 12
#define BATTERY_PIN A13
ThinkInk_290_Tricolor_Z10 display(EPD_DC, EPD_RESET, EPD_CS, SRAM_CS, EPD_BUSY);
#define COLOR0 EPD_WHITE
#define COLOR1 EPD_BLACK
#define COLOR2 EPD_RED
const char* ssid = "Breqtest";
const char* password = "aidsaids";
const char* nightscoutUrl = "http://nightscout.chrispr.org:8082/api/v1/entries?count=20";
// TIME CONSTANTS
const long gmtOffset_sec = -18000;
const int daylightOffset_sec = 3600;
const char* ntpServer = "pool.ntp.org";
#define uS_TO_S_FACTOR 1000000 /* Conversion factor for micro seconds to seconds */
#define TIME_TO_SLEEP 5 /* Time ESP32 will go to sleep (in seconds) */
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
while (!Serial) { delay(10); }
Serial.println("Nightscout CGM BS Monitor starting...");
ConnectToWifi();
struct tm timeinfo;
initTime();
if(!getLocalTime(&timeinfo)){
Serial.println("Failed to obtain time");
}
else {
Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S");
}
//turn EPD on
pinMode(EPD_ENABLE, OUTPUT);
digitalWrite(EPD_ENABLE, HIGH);
//ConnectToWifi();
String nightscoutResponse = httpGETRequest(nightscoutUrl);
//Serial.println(nightscoutResponse);
DynamicJsonDocument doc(8192);
DeserializationError error = deserializeJson(doc, nightscoutResponse);
if (error) {
Serial.print(F("deserializeJson() failed: "));
Serial.println(error.f_str());
return;
}
JsonArray array = doc.as<JsonArray>();
display.begin();
display.clearBuffer();
RenderGraph(array);
RenderRightPane(array[0]);
RenderBatteryPercentage();
display.display();
String dateString = array[0]["dateString"].as<String>();
unsigned long long dateInt = array[0]["date"].as<unsigned long long>();
int correctedDateInt = dateInt / 1000;
time_t lastSyncTime = (time_t)correctedDateInt;
/*
Serial.println(dateInt);
Serial.println(correctedDateInt);
struct tm *lastSync;
//Serial.println(lastSync);
lastSync = localtime(&lastSyncTime);
Serial.print("Last Sync Time ");
Serial.println(lastSync, "%A, %B %d %Y %H:%M:%S");
int lastReadInSecondsWithinTheHour = GetLastSyncTimeInSecondsWithinHour(dateString);
Serial.print("lastReadInSecondsWithinTheHour: ");
Serial.println(lastReadInSecondsWithinTheHour);
SleepWithTimeSync(lastReadInSecondsWithinTheHour);
*/
SleepUntilNextReading(lastSyncTime);
}
void initTime()
{
struct tm timeinfo;
if(!getLocalTime(&timeinfo))
configTime(0, 0, ntpServer);
while(!getLocalTime(&timeinfo)){
Serial.println("Sleeping until time is set..");
delay(100);
}
//Setting EST timezone
setenv("TZ", "EST+5EDT,M3.2.0/2,M11.1.0/2",1);
tzset();
}
int GetLastSyncTimeInSecondsWithinHour(String dateString) {
//"dateString":"2022-02-11T19:59:42.481-0500"
int idx = dateString.indexOf("T");
int minutesInTheHour = dateString.substring(idx+4, idx+6).toInt();
int secondsInTheHour = dateString.substring(idx+7, idx+9).toInt();
return (minutesInTheHour * 60) + secondsInTheHour;
}
void SleepUntilNextReading(time_t lastReading) {
struct tm timeinfo;
time_t localTime;
if(!getLocalTime(&timeinfo)){
Serial.println("Error getting local time");
}
localTime = mktime(&timeinfo);
double timeDiff = difftime(localTime, lastReading);
Serial.print(timeDiff);
Serial.println(" seconds diff between lastReading and current time");
int secondsToSleep = 300 - (int)timeDiff;
//Add one more to not jump the gun
secondsToSleep += 3;
if(secondsToSleep > 0 && secondsToSleep < 300) {
//secondsToSleep = secondsToSleep + 7;
Serial.print("Sleeping ");
Serial.println(secondsToSleep);
esp_sleep_enable_timer_wakeup(secondsToSleep * uS_TO_S_FACTOR);
Serial.flush();
TurnOffEPD();
esp_deep_sleep_start();
}
else {
SleepFiveMins();
}
}
void SleepWithTimeSync(int lastReadingInSeconds) {
//configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
struct tm timeinfo;
if(!getLocalTime(&timeinfo)){
Serial.println("Failed to obtain time");
SleepFiveMins();
}
//Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S");
int secondsInTheHour = (timeinfo.tm_min * 60) + timeinfo.tm_sec;
Serial.print("currentSecondsWithinTheHour");
Serial.println(secondsInTheHour);
int secondsToSleep = 300 - (secondsInTheHour - lastReadingInSeconds);
Serial.print("Would like to sleep ");
Serial.println(secondsToSleep);
if(secondsToSleep > 0 && secondsToSleep < 300) {
//buffer for new processing
secondsToSleep = secondsToSleep + 7;
esp_sleep_enable_timer_wakeup(secondsToSleep * uS_TO_S_FACTOR);
Serial.flush();
TurnOffEPD();
esp_deep_sleep_start();
}
else {
SleepFiveMins();
}
}
void RenderBatteryPercentage() {
int batteryPinInput = analogRead(BATTERY_PIN);
Serial.printf("raw adc value %d", batteryPinInput);
//Vin = 3.3(read/4098)
float batteryVoltage = 3.3 * (float(batteryPinInput) / 4098.0);
//ESP32 A13 returns half the voltage, so double this reading
batteryVoltage = batteryVoltage * 2.0;
Serial.print("calc. voltage ");
Serial.println(batteryVoltage);
//Render on screen
display.setCursor(245, 110);
display.setTextSize(2);
if(batteryVoltage < 3.6)
display.setTextColor(COLOR2);
else
display.setTextColor(COLOR1);
display.print(batteryVoltage);
display.print("v");
}
void TurnOffEPD() {
digitalWrite(EPD_ENABLE, LOW);
delay(1000);
gpio_hold_en(GPIO_NUM_12);
gpio_deep_sleep_hold_en();
}
void SleepFiveMins() {
esp_sleep_enable_timer_wakeup(285 * uS_TO_S_FACTOR);
Serial.flush();
TurnOffEPD();
esp_deep_sleep_start();
}
void RenderGraph(JsonArray sgvRecords) {
boolean redrawScreen = true;
double x = 0;
double y = 100;
double cnt = 0;
for(JsonVariant v : sgvRecords) {
JsonObject obj = v.as<JsonObject>();
Serial.println(obj["sgv"].as<String>());
if(obj["device"].as<String>().indexOf("xDrip") == -1)
continue;
if(cnt > 45)
continue;
Graph(display, cnt, obj["sgv"].as<double>(), 20, 110, 210, 105, 0,45, 5,60,180,10,"", "", "", COLOR1, COLOR1, COLOR2, COLOR1, COLOR0, redrawScreen);
cnt += 5;
}
}
void RenderTrendArrow(String trend) {
int charSize = 4;
if(trend.equalsIgnoreCase("DoubleUp")) {
display.drawChar(235, 60, 0x18, COLOR2, COLOR0, charSize);
display.drawChar(255, 60, 0x18, COLOR2, COLOR0, charSize);
}
else if(trend.equalsIgnoreCase("SingleUp")) {
display.drawChar(235, 60, 0x18, COLOR2, COLOR0, charSize);
}
else if(trend.equalsIgnoreCase("FortyFiveUp")) {
display.drawChar(235, 60, 0xBA, COLOR1, COLOR0, charSize);
}
else if(trend.equalsIgnoreCase("Flat")) {
display.drawChar(235, 60, 0x1A, COLOR1, COLOR0, charSize);
}
else if (trend.equalsIgnoreCase("FortyFiveDown")) {
display.drawChar(235, 60, 0xC9, COLOR1, COLOR0, charSize);
}
else if( trend.equalsIgnoreCase("SingleDown")) {
display.drawChar(235, 60, 0x19, COLOR2, COLOR0, charSize);
}
else if (trend.equalsIgnoreCase("DoubleDown")) {
display.drawChar(235, 60, 0x19, COLOR2, COLOR0, charSize);
display.drawChar(255, 60, 0x19, COLOR2, COLOR0, charSize);
}
else
{
Serial.print("An unrecognized trend was received. The trend is ");
Serial.println(trend);
}
}
void RenderRightPane(JsonVariant sgvRecord) {
display.setCursor(235, 10);
display.setTextSize(3);
display.setTextColor(COLOR2);
display.print(sgvRecord["sgv"].as<String>());
display.setCursor(235, 35);
display.setTextSize(3);
display.setTextColor(COLOR1);
int delta = int(sgvRecord["delta"].as<double>());
if(delta > 0)
display.print("+");
display.print(delta);
//display.setCursor(235, 70);
//display.setTextSize(3);
//display.setTextColor(COLOR1);
//display.drawChar(235, 60, 0x1A, COLOR1, COLOR0, 3);
RenderTrendArrow(sgvRecord["direction"].as<String>());
//time
String sysTime = sgvRecord["sysTime"].as<String>();
int idx = sysTime.indexOf("T");
String timePart = sysTime.substring(idx + 1, idx + 6);
display.setCursor(235, 90);
display.setTextSize(2);
display.setTextColor(COLOR1);
display.print(timePart);
}
void loop() {
// Because we deep sleep, this will _NEVER_ be run
}
String httpGETRequest(const char* url) {
HTTPClient http;
// Your IP address with path or Domain name with URL path
http.begin(url);
http.addHeader("Content-Type", "application/json");
http.addHeader("Accept", "application/json");
// Send HTTP POST request
int httpResponseCode = http.GET();
String payload = "{}";
if (httpResponseCode>0) {
Serial.print("HTTP Response code: ");
Serial.println(httpResponseCode);
payload = http.getString();
}
else {
Serial.print("Error code: ");
Serial.println(httpResponseCode);
}
// Free resources
http.end();
return payload;
}
void ConnectToWifi() {
//WiFi.mode(WIFI_STA);
//WiFi.disconnect();
WiFi.begin(ssid, password);
int cnt=0;
Serial.println("Connecting to WiFi...");
while(WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print('.');
//Serial.print("status:" );
//Serial.println(WiFi.status());
//Serial.println(WiFi.localIP());
cnt++;
if(cnt > 50){
Serial.println("Cycling wifi");
WiFi.disconnect();
WiFi.begin(ssid, password);
cnt = 0;
}
}
Serial.println("");
Serial.print("Connected to WiFi network with IP Address: ");
Serial.println(WiFi.localIP());
}
/*
function to draw a cartesian coordinate system and plot whatever data you want
just pass x and y and the graph will be drawn
huge arguement list
&d name of your display object
x = x data point
y = y datapont
gx = x graph location (lower left)
gy = y graph location (lower left)
w = width of graph
h = height of graph
xlo = lower bound of x axis
xhi = upper bound of x asis
xinc = division of x axis (distance not count)
ylo = lower bound of y axis
yhi = upper bound of y asis
yinc = division of y axis (distance not count)
title = title of graph
xlabel = x asis label
ylabel = y asis label
gcolor = graph line colors
acolor = axi ine colors
pcolor = color of your plotted data
tcolor = text color
bcolor = background color
&redraw = flag to redraw graph on fist call only
*/
double ox , oy ;
void Graph(ThinkInk_290_Tricolor_Z10 &d, double x, double y, double gx, double gy, double w, double h, double xlo, double xhi, double xinc, double ylo, double yhi, double yinc, String title, String xlabel, String ylabel, unsigned int gcolor, unsigned int acolor, unsigned int pcolor, unsigned int tcolor, unsigned int bcolor, boolean &redraw) {
double ydiv, xdiv;
// initialize old x and old y in order to draw the first point of the graph
// but save the transformed value
// note my transform funcition is the same as the map function, except the map uses long and we need doubles
//static double ox = (x - xlo) * ( w) / (xhi - xlo) + gx;
//static double oy = (y - ylo) * (gy - h - gy) / (yhi - ylo) + gy;
double i;
double temp;
int rot, newrot;
if (redraw == true) {
redraw = false;
ox = (x - xlo) * ( w) / (xhi - xlo) + gx;
oy = (y - ylo) * (gy - h - gy) / (yhi - ylo) + gy;
// draw y scale
for ( i = ylo; i <= yhi; i += yinc) {
// compute the transform
temp = (i - ylo) * (gy - h - gy) / (yhi - ylo) + gy;
if (i == 0) {
d.drawLine(gx, temp, gx + w, temp, acolor);
}
else {
d.drawLine(gx, temp, gx + w, temp, gcolor);
}
d.setTextSize(1);
d.setTextColor(tcolor, bcolor);
d.setCursor(gx - 20, temp);
// precision is default Arduino--this could really use some format control
d.println(int(i));
}
// draw x scale
for (i = xlo; i <= xhi; i += xinc) {
// compute the transform
temp = (i - xlo) * ( w) / (xhi - xlo) + gx;
if (i == 0) {
d.drawLine(temp, gy, temp, gy - h, acolor);
}
else {
d.drawLine(temp, gy, temp, gy - h, gcolor);
}
d.setTextSize(1);
d.setTextColor(tcolor, bcolor);
d.setCursor(temp, gy + 5);
// precision is default Arduino--this could really use some format control
d.println(int(i));
}
//now draw the labels
d.setTextSize(2);
d.setTextColor(tcolor, bcolor);
d.setCursor(gx , gy - h - 30);
d.println(title);
d.setTextSize(1);
d.setTextColor(acolor, bcolor);
d.setCursor(gx , gy + 20);
d.println(xlabel);
d.setTextSize(1);
d.setTextColor(acolor, bcolor);
d.setCursor(gx - 30, gy - h - 10);
d.println(ylabel);
}
//graph drawn now plot the data
// the entire plotting code are these few lines...
// recall that ox and oy are initialized as static above
x = (x - xlo) * ( w) / (xhi - xlo) + gx;
y = (y - ylo) * (gy - h - gy) / (yhi - ylo) + gy;
d.drawLine(ox, oy, x, y, pcolor);
d.drawLine(ox, oy + 1, x, y + 1, pcolor);
d.drawLine(ox, oy - 1, x, y - 1, pcolor);
ox = x;
oy = y;
}