|
| 1 | +// |
| 2 | +// FastEPD clock example |
| 3 | +// |
| 4 | +// written by Larry Bank (bitbank@pobox.com) |
| 5 | +// This program displays an attractive time, day and date display on your parallel |
| 6 | +// eink project. The time is updated once a minute with a non-flickering update. |
| 7 | +// To save power, the program uses the ESP32's deep sleep in between updates. |
| 8 | +// This creates a new problem - PSRAM contents are lost during deep sleep, so how |
| 9 | +// can we do a non-flickering update if we lost the previous image contents? |
| 10 | +// The solution is to save the last epoch time displayed in the ESP32's RTC RAM. |
| 11 | +// The 8 or 16K of RTC RAM can be retained in deep sleep for a few microamps of power. |
| 12 | +// With this info, we draw the previous time/date into PSRAM (not displayed), then draw |
| 13 | +// the new time/date and ask FastEPD to do a partial (differential) update. |
| 14 | +// |
| 15 | +// This program is free to use as you wish; no warranties are implied or granted |
| 16 | +// |
| 17 | +// N.B. For maximum battery life, remove the uSD card of your device (if present) |
| 18 | +// |
| 19 | +#include <NTPClient.h> //https://github.com/taranais/NTPClient |
| 20 | +#include <WiFi.h> |
| 21 | +#include <Wire.h> |
| 22 | +#include <esp_wifi.h> |
| 23 | +#include <HTTPClient.h> |
| 24 | +HTTPClient http; |
| 25 | +#include <FastEPD.h> |
| 26 | +// Use these fonts for a lower resolution display |
| 27 | +//#include "Roboto_Black_140.h" |
| 28 | +//#include "Roboto_Black_40.h" |
| 29 | +#include "Roboto_Black_80.h" |
| 30 | +#include "Roboto_Black_200.h" |
| 31 | +// |
| 32 | +// Change the SSID and PASSWORD to your local WiFi settings (and set the time zone offset) |
| 33 | +// |
| 34 | +const char *ssid = "your_sside"; |
| 35 | +const char *password = "your_password"; |
| 36 | +// Define your timezone offset in seconds from GMT - e.g. GMT-5 = (-5 * 3600) |
| 37 | +// This clock does not automatically adjust for daylight savings time, so you will need |
| 38 | +// to manually adjust your timezone offset (e.g. US Eastern time defined below) |
| 39 | +#define TIME_OFFSET -3600*5 |
| 40 | + |
| 41 | +FASTEPD bbep; |
| 42 | +// Keep these variables in the ESP32's RTC RAM which is preserved during deep sleep |
| 43 | +RTC_DATA_ATTR static int iCount = 0; // number of wakeups from deep sleep |
| 44 | +RTC_DATA_ATTR uint64_t oldEpoch = 0; // previous time for doing partial updates after deep sleep |
| 45 | +WiFiUDP ntpUDP; |
| 46 | +NTPClient timeClient(ntpUDP, "pool.ntp.org"); |
| 47 | +// Change these to your favorite language; accented characters are okay too! |
| 48 | +const char *szMonths[] = {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}; |
| 49 | +const char *szDays[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; |
| 50 | +// |
| 51 | +// Draw the current time |
| 52 | +// Parameters: |
| 53 | +// bPartial - true = use partial (non-flickering update) |
| 54 | +// epoch - the epoch time/date to display |
| 55 | +// bUseAsOld - flag indicating to only draw the image internally and not display it |
| 56 | +// |
| 57 | +void DisplayTime(bool bPartial, uint64_t epoch, bool bUseAsOld) |
| 58 | +{ |
| 59 | +char szTemp[32]; |
| 60 | +BB_RECT rect; |
| 61 | + |
| 62 | + bbep.fillScreen(BBEP_WHITE); |
| 63 | + bbep.setFont(Roboto_Black_200); |
| 64 | + bbep.setTextColor(BBEP_BLACK, BBEP_WHITE); |
| 65 | + // Get the current time |
| 66 | +// localtime_r((time_t *)&epoch, &myTime); |
| 67 | + struct tm *myTime = gmtime ((time_t *)&epoch); |
| 68 | + sprintf(szTemp, "%02d:%02d", myTime->tm_hour, myTime->tm_min); |
| 69 | + bbep.getStringBox(szTemp, &rect); |
| 70 | + bbep.setCursor((bbep.width() - rect.w)/2, 350); // horizontal center |
| 71 | + bbep.print(szTemp); |
| 72 | + bbep.setFont(Roboto_Black_80); |
| 73 | + sprintf(szTemp, "%s", szDays[myTime->tm_wday]); |
| 74 | + bbep.getStringBox(szTemp, &rect); |
| 75 | + bbep.setCursor((bbep.width() - rect.w)/2, 500); // horizontal center |
| 76 | + bbep.print(szTemp); // print the day of the week |
| 77 | + sprintf(szTemp, "%s %d", szMonths[myTime->tm_mon], myTime->tm_mday); |
| 78 | + bbep.getStringBox(szTemp, &rect); |
| 79 | + bbep.setCursor((bbep.width() - rect.w)/2, 660); // horizontal center |
| 80 | + bbep.print(szTemp); // print the day of the week |
| 81 | + |
| 82 | + if (bUseAsOld) { |
| 83 | + bbep.backupPlane(); // copy current image to previous |
| 84 | + } else { |
| 85 | + int rc; |
| 86 | + if (bPartial) { |
| 87 | + rc = bbep.partialUpdate(false); |
| 88 | + } else { |
| 89 | + rc = bbep.fullUpdate(CLEAR_FAST, false); |
| 90 | + } |
| 91 | + } |
| 92 | +} /* DisplayTime() */ |
| 93 | + |
| 94 | +void setup() { |
| 95 | +bool res; |
| 96 | +int rc, iTimeout; |
| 97 | + |
| 98 | +Serial.begin(115200); |
| 99 | +delay(3000); |
| 100 | +Serial.println("Starting..."); |
| 101 | +// This demo was written for Martin Fasani's "Sensoria" S3 and C5 boards |
| 102 | +// They feature a 5.2" 1280x720 8-bit parallel eink panel |
| 103 | + rc = bbep.initPanel(BB_PANEL_V7_RAW); |
| 104 | + if (rc != BBEP_SUCCESS) Serial.println("Init panel error"); |
| 105 | + rc = bbep.setPanelSize(BBEP_DISPLAY_ED052TC4); |
| 106 | + if (rc != BBEP_SUCCESS) Serial.println("Init panel size error"); |
| 107 | + bbep.setRotation(180); |
| 108 | + bbep.fillScreen(BBEP_WHITE); |
| 109 | + bbep.setPasses(6,6); // depends on the panel you're using - larger number = more contrast |
| 110 | + |
| 111 | + if (iCount == 0) { // First time starting from reset - show WiFi status |
| 112 | + bbep.setFont(FONT_12x16); |
| 113 | + bbep.setCursor(0, 16); |
| 114 | + bbep.setTextColor(BBEP_BLACK, BBEP_WHITE); |
| 115 | + bbep.printf("Connecting to WiFi AP: %s\n", ssid); |
| 116 | + bbep.fullUpdate(CLEAR_SLOW); |
| 117 | + } |
| 118 | + |
| 119 | + if ((iCount & 2047) == 0) { // sync the internal RTC to NTP time every 35 hours |
| 120 | + WiFi.begin(ssid, password); |
| 121 | + iTimeout = 0; |
| 122 | + while (WiFi.status() != WL_CONNECTED && iTimeout < 60) { |
| 123 | + delay(500); // allow up to 10 seconds to connect |
| 124 | + iTimeout++; |
| 125 | + } |
| 126 | + if (WiFi.status() == WL_CONNECTED) { |
| 127 | + if (iCount == 0) { |
| 128 | + bbep.println("\nConnected!"); |
| 129 | + bbep.partialUpdate(false); |
| 130 | + bbep.fillScreen(BBEP_WHITE); |
| 131 | + } |
| 132 | + } else { |
| 133 | + if (iCount == 0) { |
| 134 | + bbep.println("\nConnection failed!"); |
| 135 | + bbep.partialUpdate(false); |
| 136 | + } |
| 137 | + while (1) {} |
| 138 | + } |
| 139 | +// Initialize a NTPClient to get time |
| 140 | + timeClient.begin(); |
| 141 | + timeClient.setTimeOffset(TIME_OFFSET); |
| 142 | + uint64_t epochTime = 0; |
| 143 | + // loop until we get the NTP time; this can take many seconds on the ESP32-C5 |
| 144 | + while (epochTime < 1577836800) { |
| 145 | + timeClient.update(); |
| 146 | + epochTime = timeClient.getEpochTime(); |
| 147 | + vTaskDelay(50); // wait until the time is actually set correctly |
| 148 | + } |
| 149 | + //Get a time structure |
| 150 | + struct tm *ptm = gmtime ((time_t *)&epochTime); |
| 151 | + timeval tv; |
| 152 | + tv.tv_sec = epochTime; |
| 153 | + settimeofday(&tv, nullptr); // set internal RTC with the correct time |
| 154 | + timeClient.end(); // don't need it any more |
| 155 | + WiFi.disconnect(true); |
| 156 | + WiFi.mode(WIFI_OFF); |
| 157 | + } // need to sync the time |
| 158 | + |
| 159 | +} /* setup() */ |
| 160 | + |
| 161 | +void deepSleep(uint64_t time_in_ms) |
| 162 | +{ |
| 163 | + esp_sleep_enable_timer_wakeup(time_in_ms * 1000L); |
| 164 | + esp_deep_sleep_start(); |
| 165 | +} |
| 166 | + |
| 167 | +void loop() { |
| 168 | +struct timeval tv; |
| 169 | + |
| 170 | + gettimeofday(&tv, NULL); |
| 171 | + if ((iCount % 10) == 0) { |
| 172 | + DisplayTime(false, tv.tv_sec, false); // do a full update every 10 times |
| 173 | + } else { |
| 174 | + DisplayTime(true, oldEpoch, true); // redraw old time buffer for partial update |
| 175 | + DisplayTime(true, tv.tv_sec, false); // draw new time and do partial update |
| 176 | + } |
| 177 | + oldEpoch = tv.tv_sec; |
| 178 | + iCount++; |
| 179 | + bbep.deInit(); // put eInk hardware in low power mode |
| 180 | + deepSleep(60000); // sleep for 1 minute (saves energy when running from a battery) |
| 181 | + //delay(60000); // use delay if you want to keep PSRAM and USB active |
| 182 | +} /* loop() */ |
0 commit comments