Added EventSource for webUI updates, completed more features

This commit is contained in:
Djuri Baars 2023-11-08 15:27:22 +01:00
parent 91fd921e2e
commit 280764a2fa
16 changed files with 241 additions and 78 deletions

View File

@ -172,8 +172,8 @@
</div> </div>
<div class="row"> <div class="row">
<label for="tzOffset" class="col-sm-6 col-form-label">Timezone offset</label> <label for="tzOffset" class="col-sm-6 col-form-label">Timezone offset</label>
<div class="col-sm-6"> <div class="col-sm-6 mb-3">
<div class="input-group mb-3"> <div class="input-group">
<input type="number" name="tzOffset" id="tzOffset" class="form-control"> <input type="number" name="tzOffset" id="tzOffset" class="form-control">
<span class="input-group-text">min</span> <span class="input-group-text">min</span>
<button class="btn btn-outline-secondary" type="button" id="getTzOffsetBtn">Auto</button> <button class="btn btn-outline-secondary" type="button" id="getTzOffsetBtn">Auto</button>
@ -181,11 +181,21 @@
<div class="form-text">A restart is required to apply TZ offset.</div> <div class="form-text">A restart is required to apply TZ offset.</div>
</div> </div>
</div> </div>
<div class="row">
<label for="minSecPriceUpd" class="col-sm-6 col-form-label">Time between price updates</label>
<div class="col-sm-6 mb-3">
<div class="input-group">
<input type="number" name="minSecPriceUpd" id="minSecPriceUpd" class="form-control">
<span class="input-group-text">sec</span>
</div>
<div class="form-text">Short amounts might shorten lifespan.</div>
</div>
</div>
<div class="row"> <div class="row">
<div class=" col-sm-6"> <div class=" col-sm-6">
<div class="form-check form-switch"> <div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="ledFlashOnUpdate" name="ledFlashOnUpd" value="1"> <input class="form-check-input" type="checkbox" id="ledFlashOnUpdate" name="ledFlashOnUpd" value="1">
<label class="form-check-label" for="ledFlashOnUpdate">LED flash on update</label> <label class="form-check-label" for="ledFlashOnUpdate">LED flash on new block</label>
</div> </div>
</div> </div>
</div> </div>

View File

@ -19,12 +19,7 @@ toTime = (secs) => {
return obj; return obj;
} }
getBcStatus = () => { let processStatusData = (jsonData) => {
fetch('/api/status', {
method: 'get'
})
.then(response => response.json())
.then(jsonData => {
var source = document.getElementById("entry-template").innerHTML; var source = document.getElementById("entry-template").innerHTML;
var template = Handlebars.compile(source); var template = Handlebars.compile(source);
@ -35,7 +30,7 @@ getBcStatus = () => {
memTotal: Math.round(jsonData.espHeapSize / 1024), memTotal: Math.round(jsonData.espHeapSize / 1024),
uptime: toTime(jsonData.espUptime), uptime: toTime(jsonData.espUptime),
currentScreen: jsonData.currentScreen, currentScreen: jsonData.currentScreen,
rendered: jsonData.rendered, rendered: jsonData.data,
data: jsonData.data, data: jsonData.data,
screens: screens, screens: screens,
ledStatus: jsonData.ledStatus ? jsonData.ledStatus.map((t) => (t).toString(16)) : [], ledStatus: jsonData.ledStatus ? jsonData.ledStatus.map((t) => (t).toString(16)) : [],
@ -44,14 +39,45 @@ getBcStatus = () => {
document.getElementById('output').innerHTML = template(context); document.getElementById('output').innerHTML = template(context);
})
.catch(err => {
//error block
});
} }
interval = setInterval(getBcStatus, 2500);
getBcStatus(); if (!!window.EventSource) {
var source = new EventSource('/events');
source.addEventListener('open', function (e) {
console.log("Status EventSource Connected");
if (e.data) {
processStatusData(JSON.parse(e.data));
}
}, false);
source.addEventListener('error', function (e) {
if (e.target.readyState != EventSource.OPEN) {
console.log("Status EventSource Disconnected");
}
source.close();
}, false);
source.addEventListener('status', function (e) {
processStatusData(JSON.parse(e.data));
}, false);
}
// getBcStatus = () => {
// fetch('/api/status', {
// method: 'get'
// })
// .then(response => response.json())
// .then()
// .catch(err => {
// //error block
// });
// }
// interval = setInterval(getBcStatus, 2500);
// getBcStatus();
fetch('/api/settings', { fetch('/api/settings', {
method: 'get' method: 'get'
@ -77,17 +103,18 @@ fetch('/api/settings', {
if (jsonData.useBitcoinNode) if (jsonData.useBitcoinNode)
document.getElementById('useBitcoinNode').checked = true; document.getElementById('useBitcoinNode').checked = true;
let nodeFields = ["rpcHost", "rpcPort", "rpcUser", "tzOffset"]; // let nodeFields = ["rpcHost", "rpcPort", "rpcUser", "tzOffset"];
for (let n of nodeFields) { // for (let n of nodeFields) {
document.getElementById(n).value = jsonData[n]; // document.getElementById(n).value = jsonData[n];
} // }
document.getElementById('timePerScreen').value = jsonData.timerSeconds / 60; document.getElementById('timePerScreen').value = jsonData.timerSeconds / 60;
document.getElementById('ledBrightness').value = jsonData.ledBrightness; document.getElementById('ledBrightness').value = jsonData.ledBrightness;
document.getElementById('fullRefreshMin').value = jsonData.fullRefreshMin; document.getElementById('fullRefreshMin').value = jsonData.fullRefreshMin;
document.getElementById('wpTimeout').value = jsonData.wpTimeout; document.getElementById('tzOffset').value = jsonData.tzOffset;
document.getElementById('mempoolInstance').value = jsonData.mempoolInstance; document.getElementById('mempoolInstance').value = jsonData.mempoolInstance;
document.getElementById('minSecPriceUpd').value = jsonData.minSecPriceUpd;
if (jsonData.gitRev) if (jsonData.gitRev)
document.getElementById('gitRev').innerHTML = "Version: " + jsonData.gitRev; document.getElementById('gitRev').innerHTML = "Version: " + jsonData.gitRev;

View File

@ -24,12 +24,11 @@ board_build.partitions = partition.csv
build_flags = build_flags =
!python scripts/git_rev.py !python scripts/git_rev.py
-DLAST_BUILD_TIME=$UNIX_TIME -DLAST_BUILD_TIME=$UNIX_TIME
-DASYNCWEBSERVER_REGEX -DARDUINO_USB_CDC_ON_BOOT
-D ARDUINO_USB_CDC_ON_BOOT
-fexceptions -fexceptions
build_unflags = build_unflags =
-fno-exceptions
-Werror=all -Werror=all
-fno-exceptions
lib_deps = lib_deps =
bblanchon/ArduinoJson@^6.21.3 bblanchon/ArduinoJson@^6.21.3
esphome/Improv@^1.2.3 esphome/Improv@^1.2.3
@ -49,3 +48,5 @@ build_flags =
-D NEOPIXEL_PIN=34 -D NEOPIXEL_PIN=34
-D NEOPIXEL_COUNT=4 -D NEOPIXEL_COUNT=4
-D NUM_SCREENS=7 -D NUM_SCREENS=7
build_unflags =
${btclock_base.build_unflags}

View File

@ -32,7 +32,7 @@ void setupBlockNotify()
{ {
String blockHeightStr = http->getString(); String blockHeightStr = http->getString();
currentBlockHeight = blockHeightStr.toInt(); currentBlockHeight = blockHeightStr.toInt();
xTaskNotifyGive(blockUpdateTaskHandle); // xTaskNotifyGive(blockUpdateTaskHandle);
} }
// std::strcpy(wsServer, String("wss://" + mempoolInstance + "/api/v1/ws").c_str()); // std::strcpy(wsServer, String("wss://" + mempoolInstance + "/api/v1/ws").c_str());
@ -95,9 +95,12 @@ void onWebsocketMessage(esp_websocket_event_data_t *event_data)
if (blockUpdateTaskHandle != nullptr) { if (blockUpdateTaskHandle != nullptr) {
xTaskNotifyGive(blockUpdateTaskHandle); xTaskNotifyGive(blockUpdateTaskHandle);
if (preferences.getBool("ledFlashOnUpd", false)) {
vTaskDelay(pdMS_TO_TICKS(250)); // Wait until screens are updated
queueLedEffect(LED_FLASH_BLOCK_NOTIFY); queueLedEffect(LED_FLASH_BLOCK_NOTIFY);
} }
} }
}
doc.clear(); doc.clear();
} }

View File

@ -10,6 +10,7 @@ std::map<int, std::string> screenNameMap;
void setup() void setup()
{ {
setupPreferences();
setupHardware(); setupHardware();
if (mcp.digitalRead(3) == LOW) if (mcp.digitalRead(3) == LOW)
{ {
@ -20,7 +21,6 @@ void setup()
setupDisplays(); setupDisplays();
tryImprovSetup(); tryImprovSetup();
setupPreferences();
setupWebserver(); setupWebserver();
// setupWifi(); // setupWifi();
@ -112,7 +112,13 @@ void setupTimers()
void finishSetup() void finishSetup()
{ {
if (preferences.getBool("ledStatus", false)) {
setLights(preferences.getUInt("ledColor", 0xFFCC00));
} else {
clearLeds(); clearLeds();
}
} }
std::map<int, std::string> getScreenNameMap() { std::map<int, std::string> getScreenNameMap() {
@ -122,7 +128,7 @@ std::map<int, std::string> getScreenNameMap() {
void setupHardware() void setupHardware()
{ {
setupLeds(); setupLeds();
WiFi.setHostname(getMyHostname().c_str());;
if (psramInit()) if (psramInit())
{ {
Serial.println(F("PSRAM is correctly initialized")); Serial.println(F("PSRAM is correctly initialized"));

View File

@ -9,6 +9,7 @@
#include <esp_sntp.h> #include <esp_sntp.h>
#include "epd.hpp" #include "epd.hpp"
#include "improv.hpp" #include "improv.hpp"
#include <map>
#include "lib/screen_handler.hpp" #include "lib/screen_handler.hpp"
#include "lib/webserver.hpp" #include "lib/webserver.hpp"
@ -22,6 +23,7 @@
#define TIME_OFFSET_SECONDS 3600 #define TIME_OFFSET_SECONDS 3600
#define USER_AGENT "BTClock/2.0" #define USER_AGENT "BTClock/2.0"
#define MCP_DEV_ADDR 0x20 #define MCP_DEV_ADDR 0x20
#define DEFAULT_SECONDS_BETWEEN_PRICE_UPDATE 30
#define DEFAULT_FG_COLOR GxEPD_WHITE #define DEFAULT_FG_COLOR GxEPD_WHITE
#define DEFAULT_BG_COLOR GxEPD_BLACK #define DEFAULT_BG_COLOR GxEPD_BLACK

View File

@ -148,7 +148,16 @@ extern "C" void updateDisplay(void *pvParameters) noexcept
if (epdContent[epdIndex].compareTo(currentEpdContent[epdIndex]) != 0) if (epdContent[epdIndex].compareTo(currentEpdContent[epdIndex]) != 0)
{ {
displays[epdIndex].init(0, false); // Little longer reset duration because of MCP displays[epdIndex].init(0, false); // Little longer reset duration because of MCP
uint count = 0;
while (EPD_BUSY[epdIndex].digitalRead() == HIGH || count < 10) {
vTaskDelay(pdMS_TO_TICKS(100));
if (count >= 9) {
displays[epdIndex].init(0, false);
}
count++;
}
bool updatePartial = true; bool updatePartial = true;

View File

@ -185,20 +185,30 @@ void blinkDelayTwoColor(int d, int times, uint32_t c1, uint32_t c2)
void clearLeds() void clearLeds()
{ {
preferences.putBool("ledStatus", false);
pixels.clear(); pixels.clear();
pixels.show(); pixels.show();
} }
void setLights(int r, int g, int b) void setLights(int r, int g, int b)
{ {
setLights(pixels.Color(r, g, b));
}
void setLights(uint32_t color)
{
preferences.putUInt("ledColor", color);
preferences.putBool("ledStatus", true);
for (int i = 0; i < NEOPIXEL_COUNT; i++) for (int i = 0; i < NEOPIXEL_COUNT; i++)
{ {
pixels.setPixelColor(i, pixels.Color(r, g, b)); pixels.setPixelColor(i, color);
} }
pixels.show(); pixels.show();
} }
QueueHandle_t getLedTaskQueue() QueueHandle_t getLedTaskQueue()
{ {
return ledTaskQueue; return ledTaskQueue;

View File

@ -36,3 +36,4 @@ void clearLeds();
QueueHandle_t getLedTaskQueue(); QueueHandle_t getLedTaskQueue();
bool queueLedEffect(uint effect); bool queueLedEffect(uint effect);
void setLights(int r, int g, int b); void setLights(int r, int g, int b);
void setLights(uint32_t color);

View File

@ -4,6 +4,7 @@ const char *wsServerPrice = "wss://ws.coincap.io/prices?assets=bitcoin";
// WebsocketsClient client; // WebsocketsClient client;
esp_websocket_client_handle_t clientPrice = NULL; esp_websocket_client_handle_t clientPrice = NULL;
unsigned long int currentPrice; unsigned long int currentPrice;
unsigned long int lastPriceUpdate = 0;
void setupPriceNotify() void setupPriceNotify()
{ {
@ -47,15 +48,21 @@ void onWebsocketPriceMessage(esp_websocket_event_data_t* event_data)
if (doc.containsKey("bitcoin")) { if (doc.containsKey("bitcoin")) {
if (currentPrice != doc["bitcoin"].as<long>()) { if (currentPrice != doc["bitcoin"].as<long>()) {
const unsigned long oldPrice = currentPrice; uint minSecPriceUpd = preferences.getUInt("minSecPriceUpd", DEFAULT_SECONDS_BETWEEN_PRICE_UPDATE);
uint currentTime = esp_timer_get_time() / 1000000;
if (lastPriceUpdate == 0 || (currentTime - lastPriceUpdate) > minSecPriceUpd) {
// const unsigned long oldPrice = currentPrice;
currentPrice = doc["bitcoin"].as<long>(); currentPrice = doc["bitcoin"].as<long>();
lastPriceUpdate = currentTime;
// if (abs((int)(oldPrice-currentPrice)) > round(0.0015*oldPrice)) { // if (abs((int)(oldPrice-currentPrice)) > round(0.0015*oldPrice)) {
if (priceUpdateTaskHandle != nullptr && (getCurrentScreen() == SCREEN_BTC_TICKER || getCurrentScreen() == SCREEN_MSCW_TIME)) if (priceUpdateTaskHandle != nullptr && (getCurrentScreen() == SCREEN_BTC_TICKER || getCurrentScreen() == SCREEN_MSCW_TIME))
xTaskNotifyGive(priceUpdateTaskHandle); xTaskNotifyGive(priceUpdateTaskHandle);
//} //}
} }
} }
}
} }
unsigned long getPrice() { unsigned long getPrice() {

View File

@ -180,6 +180,8 @@ void setupTasks()
xTaskCreate(taskBlockUpdate, "updateBlock", 2048, NULL, tskIDLE_PRIORITY, &blockUpdateTaskHandle); xTaskCreate(taskBlockUpdate, "updateBlock", 2048, NULL, tskIDLE_PRIORITY, &blockUpdateTaskHandle);
xTaskCreate(taskTimeUpdate, "updateTime", 4096, NULL, tskIDLE_PRIORITY, &timeUpdateTaskHandle); xTaskCreate(taskTimeUpdate, "updateTime", 4096, NULL, tskIDLE_PRIORITY, &timeUpdateTaskHandle);
xTaskCreate(taskScreenRotate, "rotateScreen", 2048, NULL, tskIDLE_PRIORITY, &taskScreenRotateTaskHandle); xTaskCreate(taskScreenRotate, "rotateScreen", 2048, NULL, tskIDLE_PRIORITY, &taskScreenRotateTaskHandle);
setCurrentScreen(preferences.getUInt("currentScreen", 0));
} }
void setupTimeUpdateTimer(void *pvParameters) void setupTimeUpdateTimer(void *pvParameters)
@ -212,7 +214,10 @@ void setupScreenRotateTimer(void *pvParameters)
.name = "screen_rotate_timer"}; .name = "screen_rotate_timer"};
esp_timer_create(&screenRotateTimerConfig, &screenRotateTimer); esp_timer_create(&screenRotateTimerConfig, &screenRotateTimer);
if (preferences.getBool("timerActive", true)) {
esp_timer_start_periodic(screenRotateTimer, getTimerSeconds() * usPerSecond); esp_timer_start_periodic(screenRotateTimer, getTimerSeconds() * usPerSecond);
}
vTaskDelete(NULL); vTaskDelete(NULL);
} }
@ -233,11 +238,13 @@ void setTimerActive(bool status)
{ {
esp_timer_start_periodic(screenRotateTimer, getTimerSeconds() * usPerSecond); esp_timer_start_periodic(screenRotateTimer, getTimerSeconds() * usPerSecond);
queueLedEffect(LED_EFFECT_START_TIMER); queueLedEffect(LED_EFFECT_START_TIMER);
preferences.putBool("timerActive", true);
} }
else else
{ {
esp_timer_stop(screenRotateTimer); esp_timer_stop(screenRotateTimer);
queueLedEffect(LED_EFFECT_PAUSE_TIMER); queueLedEffect(LED_EFFECT_PAUSE_TIMER);
preferences.putBool("timerActive", false);
} }
} }

View File

@ -4,3 +4,9 @@ int modulo(int x, int N)
{ {
return (x % N + N) % N; return (x % N + N) % N;
} }
String getMyHostname() {
byte mac[6];
WiFi.macAddress(mac);
return "btclock" + String(mac[4], 16) = String(mac[5], 16);
}

View File

@ -1 +1,7 @@
#pragma once
#include <WiFi.h>
#include "shared.hpp"
int modulo(int x,int N); int modulo(int x,int N);
String getMyHostname();

View File

@ -1,6 +1,7 @@
#include "webserver.hpp" #include "webserver.hpp"
AsyncWebServer server(80); AsyncWebServer server(80);
AsyncEventSource events("/events");
void setupWebserver() void setupWebserver()
{ {
@ -10,6 +11,18 @@ void setupWebserver()
return; return;
} }
events.onConnect([](AsyncEventSourceClient *client)
{
if (client->lastId())
{
Serial.printf("Client reconnected! Last message ID that it gat is: %u\n", client->lastId());
}
// send event with message "hello!", id current millis
// and set reconnect delay to 1 second
eventSourceLoop();
});
server.addHandler(&events);
server.serveStatic("/css", LittleFS, "/css/"); server.serveStatic("/css", LittleFS, "/css/");
server.serveStatic("/js", LittleFS, "/js/"); server.serveStatic("/js", LittleFS, "/js/");
server.serveStatic("/font", LittleFS, "/font/"); server.serveStatic("/font", LittleFS, "/font/");
@ -30,31 +43,38 @@ void setupWebserver()
server.on("/api/lights/off", HTTP_GET, onApiLightsOff); server.on("/api/lights/off", HTTP_GET, onApiLightsOff);
server.on("/api/lights/color", HTTP_GET, onApiLightsSetColor); server.on("/api/lights/color", HTTP_GET, onApiLightsSetColor);
server.on("^\\/api\\/lights\\/([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$", HTTP_GET, onApiLightsSetColor);
// server.on("^\\/api\\/lights\\/([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$", HTTP_GET, onApiLightsSetColor);
server.on("/api/restart", HTTP_GET, onApiRestart); server.on("/api/restart", HTTP_GET, onApiRestart);
server.addRewrite(new OneParamRewrite("/api/lights/{color}", "/api/lights/color?c={color}"));
server.addRewrite(new OneParamRewrite("/api/show/screen/{s}", "/api/show/screen?s={s}")); server.addRewrite(new OneParamRewrite("/api/show/screen/{s}", "/api/show/screen?s={s}"));
server.addRewrite(new OneParamRewrite("/api/show/text/{text}", "/api/show/text?t={text}")); server.addRewrite(new OneParamRewrite("/api/show/text/{text}", "/api/show/text?t={text}"));
server.addRewrite(new OneParamRewrite("/api/show/number/{number}", "/api/show/text?t={text}")); server.addRewrite(new OneParamRewrite("/api/show/number/{number}", "/api/show/text?t={text}"));
server.onNotFound(onNotFound); server.onNotFound(onNotFound);
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*");
server.begin(); server.begin();
if (!MDNS.begin(getMyHostname()))
{
Serial.println(F("Error setting up MDNS responder!"));
while (1)
{
delay(1000);
}
}
MDNS.addService("http", "tcp", 80);
} }
/** StaticJsonDocument<768> getStatusObject()
* @Api
* @Path("/api/status")
*/
void onApiStatus(AsyncWebServerRequest *request)
{ {
AsyncResponseStream *response = request->beginResponseStream("application/json"); StaticJsonDocument<768> root;
StaticJsonDocument<512> root;
root["currentScreen"] = getCurrentScreen(); root["currentScreen"] = getCurrentScreen();
root["numScreens"] = NUM_SCREENS; root["numScreens"] = NUM_SCREENS;
root["timerRunning"] = isTimerActive();; root["timerRunning"] = isTimerActive();
root["espUptime"] = esp_timer_get_time() / 1000000; root["espUptime"] = esp_timer_get_time() / 1000000;
root["currentPrice"] = getPrice(); root["currentPrice"] = getPrice();
root["currentBlockHeight"] = getBlockHeight(); root["currentBlockHeight"] = getBlockHeight();
@ -67,6 +87,37 @@ void onApiStatus(AsyncWebServerRequest *request)
conStatus["price"] = isPriceNotifyConnected(); conStatus["price"] = isPriceNotifyConnected();
conStatus["blocks"] = isBlockNotifyConnected(); conStatus["blocks"] = isBlockNotifyConnected();
return root;
}
void eventSourceLoop()
{
if (!events.count()) return;
StaticJsonDocument<768> root = getStatusObject();
JsonArray data = root.createNestedArray("data");
String epdContent[NUM_SCREENS];
std::array<String, NUM_SCREENS> retEpdContent = getCurrentEpdContent();
std::copy(std::begin(retEpdContent), std::end(retEpdContent), epdContent);
copyArray(epdContent, data);
size_t bufSize = measureJson(root);
char buffer[bufSize];
String bufString;
serializeJson(root, bufString);
events.send(bufString.c_str(), "status");
}
/**
* @Api
* @Path("/api/status")
*/
void onApiStatus(AsyncWebServerRequest *request)
{
AsyncResponseStream *response = request->beginResponseStream("application/json");
StaticJsonDocument<768> root = getStatusObject();
JsonArray data = root.createNestedArray("data"); JsonArray data = root.createNestedArray("data");
JsonArray rendered = root.createNestedArray("rendered"); JsonArray rendered = root.createNestedArray("rendered");
String epdContent[NUM_SCREENS]; String epdContent[NUM_SCREENS];
@ -107,7 +158,6 @@ void onApiActionTimerRestart(AsyncWebServerRequest *request)
request->send(200); request->send(200);
} }
void onApiShowScreen(AsyncWebServerRequest *request) void onApiShowScreen(AsyncWebServerRequest *request)
{ {
if (request->hasParam("s")) if (request->hasParam("s"))
@ -128,13 +178,14 @@ void onApiShowText(AsyncWebServerRequest *request)
t.toUpperCase(); // This is needed as long as lowercase letters are glitchy t.toUpperCase(); // This is needed as long as lowercase letters are glitchy
std::array<String, NUM_SCREENS> textEpdContent; std::array<String, NUM_SCREENS> textEpdContent;
for (uint i = 0; i < NUM_SCREENS; i++) { for (uint i = 0; i < NUM_SCREENS; i++)
{
textEpdContent[i] = t[i]; textEpdContent[i] = t[i];
} }
setEpdContent(textEpdContent); setEpdContent(textEpdContent);
} }
//setCurrentScreen(SCREEN_CUSTOM); // setCurrentScreen(SCREEN_CUSTOM);
request->send(200); request->send(200);
} }
@ -156,7 +207,8 @@ void onApiSettingsGet(AsyncWebServerRequest *request)
root["fgColor"] = getFgColor(); root["fgColor"] = getFgColor();
root["bgColor"] = getBgColor(); root["bgColor"] = getBgColor();
root["timerSeconds"] = getTimerSeconds(); root["timerSeconds"] = getTimerSeconds();
root["timerRunning"] = isTimerActive();; root["timerRunning"] = isTimerActive();
root["minSecPriceUpd"] = preferences.getUInt("minSecPriceUpd", DEFAULT_SECONDS_BETWEEN_PRICE_UPDATE);
root["fullRefreshMin"] = preferences.getUInt("fullRefreshMin", 30); root["fullRefreshMin"] = preferences.getUInt("fullRefreshMin", 30);
root["wpTimeout"] = preferences.getUInt("wpTimeout", 600); root["wpTimeout"] = preferences.getUInt("wpTimeout", 600);
root["tzOffset"] = preferences.getInt("gmtOffset", TIME_OFFSET_SECONDS) / 60; root["tzOffset"] = preferences.getInt("gmtOffset", TIME_OFFSET_SECONDS) / 60;
@ -166,7 +218,6 @@ void onApiSettingsGet(AsyncWebServerRequest *request)
root["rpcHost"] = preferences.getString("rpcHost", BITCOIND_HOST); root["rpcHost"] = preferences.getString("rpcHost", BITCOIND_HOST);
root["mempoolInstance"] = preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE); root["mempoolInstance"] = preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE);
root["epdColors"] = 2;
root["ledFlashOnUpdate"] = preferences.getBool("ledFlashOnUpd", false); root["ledFlashOnUpdate"] = preferences.getBool("ledFlashOnUpd", false);
root["ledBrightness"] = preferences.getUInt("ledBrightness", 128); root["ledBrightness"] = preferences.getUInt("ledBrightness", 128);
@ -312,6 +363,16 @@ void onApiSettingsPost(AsyncWebServerRequest *request)
settingsChanged = true; settingsChanged = true;
} }
if (request->hasParam("minSecPriceUpd", true))
{
AsyncWebParameter *p = request->getParam("minSecPriceUpd", true);
int minSecPriceUpd = p->value().toInt() * 60;
preferences.putInt("minSecPriceUpd", minSecPriceUpd);
Serial.print("Setting minSecPriceUpd ");
Serial.println(minSecPriceUpd);
settingsChanged = true;
}
if (request->hasParam("timePerScreen", true)) if (request->hasParam("timePerScreen", true))
{ {
AsyncWebParameter *p = request->getParam("timePerScreen", true); AsyncWebParameter *p = request->getParam("timePerScreen", true);
@ -381,14 +442,16 @@ void onApiLightsOff(AsyncWebServerRequest *request)
void onApiLightsSetColor(AsyncWebServerRequest *request) void onApiLightsSetColor(AsyncWebServerRequest *request)
{ {
String rgbColor = request->pathArg(0); if (request->hasParam("c"))
{
String rgbColor = request->getParam("c")->value();
uint r, g, b; uint r, g, b;
sscanf(rgbColor.c_str(), "%02x%02x%02x", &r, &g, &b); sscanf(rgbColor.c_str(), "%02x%02x%02x", &r, &g, &b);
setLights(r, g, b); setLights(r, g, b);
request->send(200, "text/plain", rgbColor); request->send(200, "text/plain", rgbColor);
}
} }
void onIndex(AsyncWebServerRequest *request) { request->send(LittleFS, "/index.html", String(), false); } void onIndex(AsyncWebServerRequest *request) { request->send(LittleFS, "/index.html", String(), false); }
void onNotFound(AsyncWebServerRequest *request) void onNotFound(AsyncWebServerRequest *request)

View File

@ -3,6 +3,7 @@
#include "ESPAsyncWebServer.h" #include "ESPAsyncWebServer.h"
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <LittleFS.h> #include <LittleFS.h>
#include <ESPmDNS.h>
#include "lib/block_notify.hpp" #include "lib/block_notify.hpp"
#include "lib/price_notify.hpp" #include "lib/price_notify.hpp"
@ -33,3 +34,6 @@ void onApiRestart(AsyncWebServerRequest *request);
void onIndex(AsyncWebServerRequest *request); void onIndex(AsyncWebServerRequest *request);
void onNotFound(AsyncWebServerRequest *request); void onNotFound(AsyncWebServerRequest *request);
StaticJsonDocument<768> getStatusObject();
void eventSourceLoop();

View File

@ -10,6 +10,7 @@ extern "C" void app_main()
while (true) while (true)
{ {
vTaskDelay(pdMS_TO_TICKS(5000)); eventSourceLoop();
vTaskDelay(pdMS_TO_TICKS(2500));
} }
} }