diff --git a/m5paper-stockticker.ino b/m5paper-stockticker.ino index 970835d..e9e55ca 100644 --- a/m5paper-stockticker.ino +++ b/m5paper-stockticker.ino @@ -4,6 +4,9 @@ #include #include #include +#include +#include +#include #include "images.h" #include "config.h" @@ -45,73 +48,154 @@ const int NUM_STOCKS = sizeof(myPortfolio) / sizeof(myPortfolio[0]); // ------------------------------------------------------- struct StockData { float price, change, pct; bool valid; }; +void prepareForSleepAndEnter(uint64_t sleepUs) { + // Disable all wake sources first, then allow only timer wake. + esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_ALL); + esp_sleep_enable_timer_wakeup(sleepUs); + + // Shut down radios and protocol stacks. + WiFi.disconnect(true, true); + WiFi.mode(WIFI_OFF); + esp_wifi_stop(); + esp_wifi_deinit(); + + btStop(); + esp_bt_controller_disable(); + esp_bt_controller_deinit(); + + // Power down optional domains where this core/target exposes them. +#ifdef ESP_PD_DOMAIN_RTC_PERIPH + esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF); +#endif +#ifdef ESP_PD_DOMAIN_RTC_FAST_MEM + esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_FAST_MEM, ESP_PD_OPTION_OFF); +#endif +#ifdef ESP_PD_DOMAIN_RTC_SLOW_MEM + esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_OFF); +#endif +#ifdef ESP_PD_DOMAIN_XTAL + esp_sleep_pd_config(ESP_PD_DOMAIN_XTAL, ESP_PD_OPTION_OFF); +#endif + + esp_deep_sleep_start(); +} + bool fetchStock(const char* sym, StockData& out) { out = { 0.0f, 0.0f, 0.0f, false }; - if (WiFi.status() != WL_CONNECTED) return false; + if (WiFi.status() != WL_CONNECTED) { Serial.println("[FH] no WiFi"); return false; } char url[220]; snprintf(url, sizeof(url), "https://finnhub.io/api/v1/quote?symbol=%s&token=%s", sym, apiKey); - WiFiClientSecure client; - client.setInsecure(); - HTTPClient http; - http.begin(client, url); - int code = http.GET(); - if (code != HTTP_CODE_OK) { http.end(); return false; } + for (int attempt = 1; attempt <= 3; attempt++) { + WiFiClientSecure client; + client.setInsecure(); + HTTPClient http; + http.setConnectTimeout(8000); + http.setTimeout(8000); + if (!http.begin(client, url)) { + Serial.printf("[FH] %s begin() failed (try %d)\n", sym, attempt); + http.end(); + delay(300); + continue; + } + int code = http.GET(); + if (code != HTTP_CODE_OK) { + Serial.printf("[FH] %s HTTP %d (try %d)\n", sym, code, attempt); + http.end(); + delay(400); + continue; + } - String body = http.getString(); - http.end(); + String body = http.getString(); + http.end(); - StaticJsonDocument<512> doc; - if (deserializeJson(doc, body)) return false; + StaticJsonDocument<512> doc; + DeserializationError err = deserializeJson(doc, body); + if (err) { + Serial.printf("[FH] %s JSON err: %s\n", sym, err.c_str()); + delay(300); + continue; + } - out.price = doc["c"] | 0.0f; - out.change = doc["d"] | 0.0f; - out.pct = doc["dp"] | 0.0f; - out.valid = (out.price > 0.0f); - return out.valid; + out.price = doc["c"] | 0.0f; + out.change = doc["d"] | 0.0f; + out.pct = doc["dp"] | 0.0f; + out.valid = (out.price > 0.0f); + if (out.valid) return true; + Serial.printf("[FH] %s price 0 (body: %s)\n", sym, body.c_str()); + delay(300); + } + return false; } // Alpha Vantage fetch (for mutual funds not covered by Finnhub) bool fetchStockAV(const char* sym, StockData& out) { out = { 0.0f, 0.0f, 0.0f, false }; - if (WiFi.status() != WL_CONNECTED) return false; + if (WiFi.status() != WL_CONNECTED) { Serial.println("[AV] no WiFi"); return false; } char url[256]; snprintf(url, sizeof(url), "https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=%s&apikey=%s", sym, avApiKey); - WiFiClientSecure client; - client.setInsecure(); - HTTPClient http; - http.begin(client, url); - int code = http.GET(); - if (code != HTTP_CODE_OK) { http.end(); return false; } + for (int attempt = 1; attempt <= 3; attempt++) { + WiFiClientSecure client; + client.setInsecure(); + HTTPClient http; + http.setConnectTimeout(8000); + http.setTimeout(8000); + if (!http.begin(client, url)) { + Serial.printf("[AV] %s begin() failed (try %d)\n", sym, attempt); + http.end(); + delay(300); + continue; + } + int code = http.GET(); + if (code != HTTP_CODE_OK) { + Serial.printf("[AV] %s HTTP %d (try %d)\n", sym, code, attempt); + http.end(); + delay(400); + continue; + } - String body = http.getString(); - http.end(); + String body = http.getString(); + http.end(); - StaticJsonDocument<1024> doc; - if (deserializeJson(doc, body)) return false; + StaticJsonDocument<1024> doc; + DeserializationError err = deserializeJson(doc, body); + if (err) { + Serial.printf("[AV] %s JSON err: %s\n", sym, err.c_str()); + delay(300); + continue; + } - JsonObject q = doc["Global Quote"]; - if (q.isNull()) return false; + JsonObject q = doc["Global Quote"]; + if (q.isNull()) { + // Free tier limit / rate note comes back without "Global Quote". + Serial.printf("[AV] %s no quote (body: %s)\n", sym, body.c_str()); + delay(300); + continue; + } - out.price = atof(q["05. price"] | "0"); - out.change = atof(q["09. change"] | "0"); + out.price = atof(q["05. price"] | "0"); + out.change = atof(q["09. change"] | "0"); - // "10. change percent" is like "0.0500%" - strip the trailing '%' - char pctBuf[16]; - strncpy(pctBuf, q["10. change percent"] | "0%", sizeof(pctBuf) - 1); - pctBuf[sizeof(pctBuf) - 1] = '\0'; - char* pctMark = strchr(pctBuf, '%'); - if (pctMark) *pctMark = '\0'; - out.pct = atof(pctBuf); + // "10. change percent" is like "0.0500%" - strip the trailing '%' + char pctBuf[16]; + strncpy(pctBuf, q["10. change percent"] | "0%", sizeof(pctBuf) - 1); + pctBuf[sizeof(pctBuf) - 1] = '\0'; + char* pctMark = strchr(pctBuf, '%'); + if (pctMark) *pctMark = '\0'; + out.pct = atof(pctBuf); - out.valid = (out.price > 0.0f); - return out.valid; + out.valid = (out.price > 0.0f); + if (out.valid) return true; + Serial.printf("[AV] %s price 0\n", sym); + delay(300); + } + return false; } // ------------------------------------------------------- @@ -154,12 +238,14 @@ void setup() { auto cfg = M5.config(); M5.begin(cfg); M5.Display.setRotation(1); + Serial.begin(115200); // Connect to WiFi WiFi.begin(ssid, password); for (int i = 0; i < 40 && WiFi.status() != WL_CONNECTED; i++) delay(500); if (WiFi.status() != WL_CONNECTED) { + int batPct = M5.Power.getBatteryLevel(); M5.Display.fillScreen(TFT_WHITE); M5.Display.setFont(&fonts::AsciiFont8x16); M5.Display.setTextColor(TFT_BLACK, TFT_WHITE);