Refactor GPIO Viewer

This commit is contained in:
Theo Arends 2024-01-29 17:16:38 +01:00
parent 274f75829e
commit bfd44d06f9
2 changed files with 113 additions and 77 deletions

View File

@ -770,8 +770,6 @@ void setup(void) {
if (bitRead(Settings->rule_enabled, 0)) Run_Scripter(">BS",3,0); if (bitRead(Settings->rule_enabled, 0)) Run_Scripter(">BS",3,0);
#endif #endif
XdrvCall(FUNC_ACTIVE); // FUNC_ACTIVE
TasmotaGlobal.rules_flag.system_init = 1; TasmotaGlobal.rules_flag.system_init = 1;
} }
@ -887,6 +885,7 @@ void Scheduler(void) {
if (TimeReached(state_second)) { if (TimeReached(state_second)) {
SetNextTimeInterval(state_second, 1000); SetNextTimeInterval(state_second, 1000);
PerformEverySecond(); PerformEverySecond();
XdrvCall(FUNC_ACTIVE);
XdrvXsnsCall(FUNC_EVERY_SECOND); XdrvXsnsCall(FUNC_EVERY_SECOND);
} }

View File

@ -65,18 +65,20 @@ enum GVPinTypes {
#endif // GV_INPUT_DETECTION #endif // GV_INPUT_DETECTION
}; };
struct { typedef struct {
WiFiClient WebClient;
ESP8266WebServer *WebServer; ESP8266WebServer *WebServer;
Ticker ticker; Ticker ticker;
int lastPinStates[MAX_GPIO_PIN]; String baseUrl;
uint32_t lastSentWithNoActivity; uint32_t lastSentWithNoActivity;
uint32_t freeHeap; uint32_t freeHeap;
uint32_t freePSRAM; uint32_t freePSRAM;
uint32_t sampling; uint32_t sampling;
bool active; uint16_t port;
int8_t lastPinStates[MAX_GPIO_PIN];
bool sse_ready; bool sse_ready;
} GV; } tGV;
tGV* GV = nullptr;
WiFiClient GVWebClient;
#ifdef GV_INPUT_DETECTION #ifdef GV_INPUT_DETECTION
@ -98,35 +100,41 @@ int GetPinMode(uint8_t pin) {
#endif // GV_INPUT_DETECTION #endif // GV_INPUT_DETECTION
void GVStop(void) { void GVInit(void) {
GV.sse_ready = false; if (!GV) {
GV.ticker.detach(); GV = (tGV*)calloc(sizeof(tGV), 1);
GV.active = false; if (GV) {
GV->sampling = (GV_SAMPLING_INTERVAL < 20) ? 20 : GV_SAMPLING_INTERVAL;
GV->baseUrl = GV_BASE_URL;
GV->port = GV_PORT;
}
}
}
GV.WebServer->stop(); void GVStop(void) {
GV.WebServer = nullptr; GV->sse_ready = false;
GV->ticker.detach();
GV->WebServer->stop();
GV->WebServer = nullptr;
} }
void GVBegin(void) { void GVBegin(void) {
if (0 == GV.sampling) { if (GV->WebServer == nullptr) {
GV.sampling = (GV_SAMPLING_INTERVAL < 20) ? 20 : GV_SAMPLING_INTERVAL; GV->WebServer = new ESP8266WebServer(GV->port);
// Set CORS headers for global responses
// GV->WebServer->sendHeader(F("Access-Control-Allow-Origin"), F("*"));
// GV->WebServer->sendHeader(F("Access-Control-Allow-Methods"), F("GET, POST, OPTIONS"));
// GV->WebServer->sendHeader(F("Access-Control-Allow-Headers"), F("Content-Type"));
GV->WebServer->on("/", GVHandleRoot);
GV->WebServer->on("/release", GVHandleRelease);
GV->WebServer->on("/free_psram", GVHandleFreePSRam);
GV->WebServer->on("/sampling", GVHandleSampling);
GV->WebServer->on("/espinfo", GVHandleEspInfo);
GV->WebServer->on("/partition", GVHandlePartition);
GV->WebServer->on("/events", GVHandleEvents);
GV->WebServer->begin();
} }
GV.WebServer = new ESP8266WebServer(GV_PORT);
// Set CORS headers for global responses
// GV.WebServer->sendHeader(F("Access-Control-Allow-Origin"), F("*"));
// GV.WebServer->sendHeader(F("Access-Control-Allow-Methods"), F("GET, POST, OPTIONS"));
// GV.WebServer->sendHeader(F("Access-Control-Allow-Headers"), F("Content-Type"));
GV.WebServer->on("/", GVHandleRoot);
GV.WebServer->on("/release", GVHandleRelease);
GV.WebServer->on("/free_psram", GVHandleFreePSRam);
GV.WebServer->on("/sampling", GVHandleSampling);
GV.WebServer->on("/espinfo", GVHandleEspInfo);
GV.WebServer->on("/partition", GVHandlePartition);
GV.WebServer->on("/events", GVHandleEvents);
GV.WebServer->begin();
GV.active = true;
} }
void GVHandleRoot(void) { void GVHandleRoot(void) {
@ -134,17 +142,17 @@ void GVHandleRoot(void) {
char* content = ext_snprintf_malloc_P(HTTP_GV_PAGE, char* content = ext_snprintf_malloc_P(HTTP_GV_PAGE,
SettingsTextEscaped(SET_DEVICENAME).c_str(), SettingsTextEscaped(SET_DEVICENAME).c_str(),
GV_BASE_URL, GV->baseUrl.c_str(),
WiFi.localIP().toString().c_str(), NetworkAddress().toString().c_str(),
GV_PORT, GV->port,
ESP_getFreeSketchSpace() / 1024); ESP_getFreeSketchSpace() / 1024);
if (content == nullptr) { return; } // Avoid crash if (content == nullptr) { return; } // Avoid crash
GV.WebServer->send_P(200, "text/html", content); GV->WebServer->send_P(200, "text/html", content);
free(content); free(content);
} }
void GVWebserverSendJson(String &jsonResponse) { void GVWebserverSendJson(String &jsonResponse) {
GV.WebServer->send(200, "application/json", jsonResponse); GV->WebServer->send(200, "application/json", jsonResponse);
} }
void GVHandleRelease(void) { void GVHandleRelease(void) {
@ -164,7 +172,7 @@ void GVHandleFreePSRam(void) {
} }
void GVHandleSampling(void) { void GVHandleSampling(void) {
String jsonResponse = "{\"sampling\":\"" + String(GV.sampling) + "\"}"; String jsonResponse = "{\"sampling\":\"" + String(GV->sampling) + "\"}";
GVWebserverSendJson(jsonResponse); GVWebserverSendJson(jsonResponse);
} }
@ -242,39 +250,41 @@ void GVHandlePartition(void) {
} }
void GVHandleEvents(void) { void GVHandleEvents(void) {
GV.WebClient = GV.WebServer->client(); GVWebClient = GV->WebServer->client();
GV.WebClient.setNoDelay(true); GVWebClient.setNoDelay(true);
// GV.WebClient.setSync(true); // GVWebClient.setSync(true);
GV.WebServer->setContentLength(CONTENT_LENGTH_UNKNOWN); // The payload can go on forever GV->WebServer->setContentLength(CONTENT_LENGTH_UNKNOWN); // The payload can go on forever
GV.WebServer->sendContent_P(HTTP_GV_EVENT); GV->WebServer->sendContent_P(HTTP_GV_EVENT);
GV.sse_ready = true; // Ready for async updates GV->sse_ready = true; // Ready for async updates
if (GV.sampling != 100) { if (GV->sampling != 100) {
GV.ticker.attach_ms(GV.sampling, GVMonitorTask); // Use Tasmota Scheduler (100) or Ticker (20..99,101..1000) GV->ticker.attach_ms(GV->sampling, GVMonitorTask); // Use Tasmota Scheduler (100) or Ticker (20..99,101..1000)
} }
AddLog(LOG_LEVEL_DEBUG, PSTR("IOV: Connected")); AddLog(LOG_LEVEL_DEBUG, PSTR("IOV: Connected"));
} }
void GVEventDisconnected(void) { void GVEventDisconnected(void) {
if (GV.sse_ready) { if (GV->sse_ready) {
AddLog(LOG_LEVEL_DEBUG, PSTR("IOV: Disconnected")); AddLog(LOG_LEVEL_DEBUG, PSTR("IOV: Disconnected"));
} }
GV.sse_ready = false; // This just stops the event to be restarted by opening root page again GV->sse_ready = false; // This just stops the event to be restarted by opening root page again
GV.ticker.detach(); GV->ticker.detach();
} }
void GVCloseEvent(void) { void GVCloseEvent(void) {
GVEventSend("{}", "close", millis()); // Closes web page if (GV->WebServer) {
GVEventDisconnected(); GVEventSend("{}", "close", millis()); // Closes web page
GVEventDisconnected();
}
} }
//void GVEventSend(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0); //void GVEventSend(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0);
void GVEventSend(const char *message, const char *event, uint32_t id) { void GVEventSend(const char *message, const char *event, uint32_t id) {
if (GV.WebClient.connected()) { if (GVWebClient.connected()) {
// generateEventMessage() in AsyncEventSource.cpp // generateEventMessage() in AsyncEventSource.cpp
// GV.WebClient.printf_P(PSTR("retry:1000\nid:%u\nevent:%s\ndata:%s\n\n"), id, event, message); // GVWebClient.printf_P(PSTR("retry:1000\nid:%u\nevent:%s\ndata:%s\n\n"), id, event, message);
GV.WebClient.printf_P(PSTR("id:%u\nevent:%s\ndata:%s\n\n"), id, event, message); GVWebClient.printf_P(PSTR("id:%u\nevent:%s\ndata:%s\n\n"), id, event, message);
} else { } else {
GVEventDisconnected(); GVEventDisconnected();
} }
@ -353,10 +363,10 @@ void GVMonitorTask(void) {
#endif #endif
} }
if (originalValue != GV.lastPinStates[pin]) { if (originalValue != GV->lastPinStates[pin]) {
if (hasChanges) { jsonMessage += ","; } if (hasChanges) { jsonMessage += ","; }
jsonMessage += "\"" + String(pin) + "\":{\"s\":" + currentState + ",\"v\":" + originalValue + ",\"t\":" + pintype + "}"; jsonMessage += "\"" + String(pin) + "\":{\"s\":" + currentState + ",\"v\":" + originalValue + ",\"t\":" + pintype + "}";
GV.lastPinStates[pin] = originalValue; GV->lastPinStates[pin] = originalValue;
hasChanges = true; hasChanges = true;
} }
} }
@ -366,9 +376,9 @@ void GVMonitorTask(void) {
} }
uint32_t heap = ESP_getFreeHeap(); uint32_t heap = ESP_getFreeHeap();
if (heap != GV.freeHeap) { if (heap != GV->freeHeap) {
// Send freeHeap // Send freeHeap
GV.freeHeap = heap; GV->freeHeap = heap;
char temp[20]; char temp[20];
snprintf_P(temp, sizeof(temp), PSTR("%d KB"), heap / 1024); snprintf_P(temp, sizeof(temp), PSTR("%d KB"), heap / 1024);
GVEventSend(temp, "free_heap", millis()); GVEventSend(temp, "free_heap", millis());
@ -379,8 +389,8 @@ void GVMonitorTask(void) {
if (UsePSRAM()) { if (UsePSRAM()) {
// Send freePsram // Send freePsram
uint32_t psram = ESP.getFreePsram(); uint32_t psram = ESP.getFreePsram();
if (psram != GV.freePSRAM) { if (psram != GV->freePSRAM) {
GV.freePSRAM = psram; GV->freePSRAM = psram;
char temp[20]; char temp[20];
snprintf_P(temp, sizeof(temp), PSTR("%d KB"), psram / 1024); snprintf_P(temp, sizeof(temp), PSTR("%d KB"), psram / 1024);
GVEventSend(temp, "free_psram", millis()); GVEventSend(temp, "free_psram", millis());
@ -391,16 +401,16 @@ void GVMonitorTask(void) {
if (!hasChanges) { if (!hasChanges) {
// Send freeHeap as keepAlive // Send freeHeap as keepAlive
uint32_t last_sent = millis() - GV.lastSentWithNoActivity; uint32_t last_sent = millis() - GV->lastSentWithNoActivity;
if (last_sent > GV_KEEP_ALIVE) { if (last_sent > GV_KEEP_ALIVE) {
// No activity, resending for pulse // No activity, resending for pulse
char temp[20]; char temp[20];
snprintf_P(temp, sizeof(temp), PSTR("%d KB"), heap / 1024); snprintf_P(temp, sizeof(temp), PSTR("%d KB"), heap / 1024);
GVEventSend(temp, "free_heap", millis()); GVEventSend(temp, "free_heap", millis());
GV.lastSentWithNoActivity = millis(); GV->lastSentWithNoActivity = millis();
} }
} else { } else {
GV.lastSentWithNoActivity = millis(); GV->lastSentWithNoActivity = millis();
} }
} }
@ -409,10 +419,10 @@ void GVMonitorTask(void) {
\*********************************************************************************************/ \*********************************************************************************************/
const char kGVCommands[] PROGMEM = "GV|" // Prefix const char kGVCommands[] PROGMEM = "GV|" // Prefix
"Viewer|Sampling"; "Viewer|Sampling|Port|Url";
void (* const GVCommand[])(void) PROGMEM = { void (* const GVCommand[])(void) PROGMEM = {
&CmndGvViewer, &CmndGvSampling }; &CmndGvViewer, &CmndGvSampling, &CmndGvPort, &CmndGvUrl };
void CmndGvViewer(void) { void CmndGvViewer(void) {
/* GvViewer - Show current viewer state /* GvViewer - Show current viewer state
@ -420,10 +430,11 @@ void CmndGvViewer(void) {
GvViewer 1 - Turn viewer On GvViewer 1 - Turn viewer On
GvViewer 2 - Toggle viewer state GvViewer 2 - Toggle viewer state
*/ */
GVInit();
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 2)) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 2)) {
uint32_t state = XdrvMailbox.payload; uint32_t state = XdrvMailbox.payload;
if (2 == state) { // Toggle if (2 == state) { // Toggle
state = GV.active ^1; state = (GV->WebServer == nullptr) ^1;
} }
if (state) { // On if (state) { // On
GVBegin(); GVBegin();
@ -432,8 +443,8 @@ void CmndGvViewer(void) {
GVStop(); GVStop();
} }
} }
if (GV.active) { if (GV->WebServer) {
Response_P(PSTR("{\"%s\":\"Active on http://%s:" STR(GV_PORT) "/\"}"), XdrvMailbox.command, WiFi.localIP().toString().c_str()); Response_P(PSTR("{\"%s\":\"Active on http://%s:%d/\"}"), XdrvMailbox.command, NetworkAddress().toString().c_str(), GV->port);
} else { } else {
ResponseCmndChar_P(PSTR("Stopped")); ResponseCmndChar_P(PSTR("Stopped"));
} }
@ -443,11 +454,38 @@ void CmndGvSampling(void) {
/* GvSampling - Show current sampling interval /* GvSampling - Show current sampling interval
GvSampling 20 .. 1000 - Set sampling interval GvSampling 20 .. 1000 - Set sampling interval
*/ */
GVInit();
if ((XdrvMailbox.payload >= 20) && (XdrvMailbox.payload <= 1000)) { if ((XdrvMailbox.payload >= 20) && (XdrvMailbox.payload <= 1000)) {
GVCloseEvent(); // Stop current updates GVCloseEvent(); // Stop current updates
GV.sampling = XdrvMailbox.payload; // 20 - 1000 milliseconds GV->sampling = XdrvMailbox.payload; // 20 - 1000 milliseconds
} }
ResponseCmndNumber(GV.sampling); ResponseCmndNumber(GV->sampling);
}
void CmndGvPort(void) {
/* GvPort - Show vurrent port
GvPort 1 - Select default port
GvPort 5557 - Set port
*/
GVInit();
if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload < 65536)) {
GVCloseEvent(); // Stop current updates
GV->port = (SC_DEFAULT == XdrvMailbox.payload) ? GV_PORT : XdrvMailbox.payload;
}
ResponseCmndNumber(GV->port);
}
void CmndGvUrl(void) {
/* GvUrl - Show current url
GvUrl 1 - Select default url
GvUrl https://thelastoutpostworkshop.github.io/microcontroller_devkit/gpio_viewer_1_5/
*/
GVInit();
if (XdrvMailbox.data_len > 0) {
GVCloseEvent(); // Stop current updates
GV->baseUrl = (SC_DEFAULT == XdrvMailbox.payload) ? GV_BASE_URL : XdrvMailbox.data;
}
ResponseCmndChar(GV->baseUrl.c_str());
} }
/*********************************************************************************************\ /*********************************************************************************************\
@ -464,12 +502,11 @@ void GVSetupAndStart(void) {
AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_GPIO_VIEWER)); AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_GPIO_VIEWER));
if (!GV.active) { // WebServer not started GVInit();
GVBegin(); GVBegin(); // Start WebServer
}
char redirect[100]; char redirect[100];
snprintf_P(redirect, sizeof(redirect), PSTR("http://%s:" STR(GV_PORT) "/"), WiFi.localIP().toString().c_str()); snprintf_P(redirect, sizeof(redirect), PSTR("http://%s:%d/"), NetworkAddress().toString().c_str(), GV->port);
Webserver->sendHeader(F("Location"), redirect); Webserver->sendHeader(F("Location"), redirect);
Webserver->send(303); Webserver->send(303);
} }
@ -499,18 +536,18 @@ bool Xdrv121(uint32_t function) {
break; break;
#endif // USE_WEBSERVER #endif // USE_WEBSERVER
} }
if (GV.active) { if (GV && (GV->WebServer)) {
switch (function) { switch (function) {
case FUNC_LOOP: case FUNC_LOOP:
if (GV.WebServer) { GV.WebServer->handleClient(); } GV->WebServer->handleClient();
break; break;
case FUNC_EVERY_100_MSECOND: case FUNC_EVERY_100_MSECOND:
if (GV.sse_ready && (100 == GV.sampling)) { if (GV->sse_ready && (100 == GV->sampling)) {
GVMonitorTask(); GVMonitorTask();
} }
break; break;
case FUNC_SAVE_BEFORE_RESTART: case FUNC_SAVE_BEFORE_RESTART:
GVCloseEvent(); // Stop current updates GVCloseEvent(); // Stop current updates
break; break;
case FUNC_ACTIVE: case FUNC_ACTIVE:
result = true; result = true;