#include "Adafruit_ThinkInk.h" #include #include #include #include #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) */ RTC_DATA_ATTR int sleepCount = 0; void setup() { // put your setup code here, to run once: Serial.begin(115200); while (!Serial) { delay(10); } Serial.println("Nightscout CGM BS Monitor starting..."); //print_wakeup_reason(); 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); 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(); display.begin(); display.clearBuffer(); JsonVariant latestReading = array[0]; SleepIfCGMWarmup(latestReading); RenderGraph(array); RenderRightPane(latestReading); RenderBatteryPercentage(); display.display(); unsigned long long dateInt = latestReading["date"].as(); int correctedDateInt = dateInt / 1000; time_t lastSyncTime = (time_t)correctedDateInt; SleepUntilNextReading(lastSyncTime); } void print_wakeup_reason(){ esp_sleep_wakeup_cause_t wakeup_reason; wakeup_reason = esp_sleep_get_wakeup_cause(); switch(wakeup_reason) { case ESP_SLEEP_WAKEUP_EXT0 : Serial.println("Wakeup caused by external signal using RTC_IO"); break; case ESP_SLEEP_WAKEUP_EXT1 : Serial.println("Wakeup caused by external signal using RTC_CNTL"); break; case ESP_SLEEP_WAKEUP_TIMER : Serial.println("Wakeup caused by timer"); break; case ESP_SLEEP_WAKEUP_TOUCHPAD : Serial.println("Wakeup caused by touchpad"); break; case ESP_SLEEP_WAKEUP_ULP : Serial.println("Wakeup caused by ULP program"); break; default : Serial.printf("Wakeup was not caused by deep sleep: %d\n",wakeup_reason); break; } } void initTime() { struct tm timeinfo; //if(!getLocalTime(&timeinfo)) if(sleepCount % 5 == 0) { Serial.println("Syncing with NTP server.."); 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 SleepIfCGMWarmup(JsonVariant latestReading) { if(latestReading["direction"].as().equalsIgnoreCase("NOT COMPUTABLE")) { Serial.println("CGM warmup period. Sleeping 5"); SleepFiveMins(); } } 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 += 10; sleepCount++; 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(); } } unsigned long GetCurrentEpochTime() { struct tm timeinfo; time_t localTime; if(!getLocalTime(&timeinfo)){ Serial.println("Error getting local time"); } localTime = mktime(&timeinfo); return (unsigned long)localTime; } 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() { int secondsToSleep = 300; Serial.print("Sleeping "); Serial.println(secondsToSleep); esp_sleep_enable_timer_wakeup(secondsToSleep * uS_TO_S_FACTOR); Serial.flush(); TurnOffEPD(); esp_deep_sleep_start(); } void RenderGraph(JsonArray sgvRecords) { boolean redrawScreen = true; double x = 0; double y = 100; int cnt = -45; //Get 46 minutes into the past, to filter out older records unsigned long timeCutoff = GetCurrentEpochTime() - (46 * 60); for(int i=sgvRecords.size(); i >= 0; i--) { JsonObject obj = sgvRecords[i].as(); //v.as(); Serial.println(obj["sgv"].as()); if(obj["device"].as().indexOf("xDrip") == -1) { continue; } if(cnt > 0) continue; unsigned long readingTime = (unsigned long)(obj["date"].as() / 1000); Serial.printf("readingTime: %d\n", readingTime); if(readingTime < timeCutoff) continue; Graph(display, cnt, obj["sgv"].as(), 20, 110, 210, 105, -45,0, 5,60,180,10,"", "", "", COLOR1, COLOR1, COLOR2, COLOR1, COLOR0, redrawScreen); Serial.printf("Graphing x: %+d y: %f\n", cnt, obj["sgv"].as()); 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()); display.setCursor(235, 35); display.setTextSize(3); display.setTextColor(COLOR1); int delta = int(sgvRecord["delta"].as()); if(delta > 0) display.print("+"); display.print(delta); RenderTrendArrow(sgvRecord["direction"].as()); //time String sysTime = sgvRecord["sysTime"].as(); 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(abs(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; }