feat: Setup core utilities for power management and robust data fetching
This commit is contained in:
1 parent
6d64aabc51
commit
cef7814700
1 file changed
+126
-40
+126
-40
@@ -4,6 +4,9 @@
|
||||
#include <HTTPClient.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <time.h>
|
||||
#include <esp_sleep.h>
|
||||
#include <esp_bt.h>
|
||||
#include <esp_wifi.h>
|
||||
#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);
|
||||
|
||||
Reference in new issue
Block a user