diff --git a/code/components/jomjol_flowcontroll/CMakeLists.txt b/code/components/jomjol_flowcontroll/CMakeLists.txt index 2b58ada9..bc8efa26 100644 --- a/code/components/jomjol_flowcontroll/CMakeLists.txt +++ b/code/components/jomjol_flowcontroll/CMakeLists.txt @@ -2,6 +2,6 @@ FILE(GLOB_RECURSE app_sources ${CMAKE_CURRENT_SOURCE_DIR}/*.*) idf_component_register(SRCS ${app_sources} INCLUDE_DIRS "." - REQUIRES esp_timer esp_wifi jomjol_tfliteclass jomjol_helper jomjol_controlcamera jomjol_mqtt jomjol_influxdb jomjol_fileserver_ota jomjol_image_proc jomjol_wlan) + REQUIRES esp_timer esp_wifi jomjol_tfliteclass jomjol_helper jomjol_controlcamera jomjol_mqtt jomjol_influxdb jomjol_fileserver_ota jomjol_image_proc jomjol_wlan jomjol_helper) diff --git a/code/components/jomjol_flowcontroll/ClassFlowControll.cpp b/code/components/jomjol_flowcontroll/ClassFlowControll.cpp index f54df869..b1193f76 100644 --- a/code/components/jomjol_flowcontroll/ClassFlowControll.cpp +++ b/code/components/jomjol_flowcontroll/ClassFlowControll.cpp @@ -24,6 +24,7 @@ extern "C" { #include "server_mqtt.h" #endif //ENABLE_MQTT +#include "websocket.h" #include "server_help.h" #include "MainFlowControl.h" #include "../../include/defines.h" @@ -187,6 +188,8 @@ void ClassFlowControll::SetInitialParameter(void) aktRunNr = 0; aktstatus = "Flow task not yet created"; aktstatusWithTime = aktstatus; + + schedule_websocket_message("{\"state\": \"" + aktstatus + "\"}"); } @@ -269,6 +272,8 @@ void ClassFlowControll::InitFlow(std::string config) aktstatus = "Initialization"; aktstatusWithTime = aktstatus; + schedule_websocket_message("{\"state\": \"" + aktstatus + "\"}"); + //#ifdef ENABLE_MQTT //MQTTPublish(mqttServer_getMainTopic() + "/" + "status", "Initialization", 1, false); // Right now, not possible -> MQTT Service is going to be started later //#endif //ENABLE_MQTT @@ -331,6 +336,8 @@ void ClassFlowControll::setActStatus(std::string _aktstatus) { aktstatus = _aktstatus; aktstatusWithTime = aktstatus; + + schedule_websocket_message("{\"state\": \"" + aktstatus + "\"}"); } @@ -347,6 +354,8 @@ void ClassFlowControll::doFlowTakeImageOnly(string time) #ifdef ENABLE_MQTT MQTTPublish(mqttServer_getMainTopic() + "/" + "status", aktstatus, 1, false); #endif //ENABLE_MQTT + + schedule_websocket_message("{\"state\": \"" + aktstatus + "\"}"); FlowControll[i]->doFlow(time); } @@ -379,6 +388,7 @@ bool ClassFlowControll::doFlow(string time) aktstatus = TranslateAktstatus(FlowControll[i]->name()); aktstatusWithTime = aktstatus + " (" + zw_time + ")"; LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Status: " + aktstatusWithTime); + schedule_websocket_message("{\"state\": \"" + aktstatus + "\"}"); #ifdef ENABLE_MQTT MQTTPublish(mqttServer_getMainTopic() + "/" + "status", aktstatus, qos, false); #endif //ENABLE_MQTT @@ -413,6 +423,7 @@ bool ClassFlowControll::doFlow(string time) aktstatus = "Flow finished"; aktstatusWithTime = aktstatus + " (" + zw_time + ")"; //LogFile.WriteToFile(ESP_LOG_INFO, TAG, aktstatusWithTime); + schedule_websocket_message("{\"state\": \"" + aktstatus + "\"}"); #ifdef ENABLE_MQTT MQTTPublish(mqttServer_getMainTopic() + "/" + "status", aktstatus, qos, false); #endif //ENABLE_MQTT diff --git a/code/components/jomjol_flowcontroll/ClassFlowMQTT.cpp b/code/components/jomjol_flowcontroll/ClassFlowMQTT.cpp index cb5147a2..e2207b3f 100644 --- a/code/components/jomjol_flowcontroll/ClassFlowMQTT.cpp +++ b/code/components/jomjol_flowcontroll/ClassFlowMQTT.cpp @@ -9,6 +9,7 @@ #include "ClassLogFile.h" #include "time_sntp.h" +#include "websocket.h" #include "interface_mqtt.h" #include "ClassFlowPostProcessing.h" #include "ClassFlowControll.h" @@ -249,11 +250,15 @@ bool ClassFlowMQTT::doFlow(string zwtime) namenumber = maintopic + "/" + namenumber + "/"; - if (result.length() > 0) + if (result.length() > 0) { success |= MQTTPublish(namenumber + "value", result, qos, SetRetainFlag); + schedule_websocket_message("{\"value\": \"" + result + "\", \"number\": \"" + (*NUMBERS)[i]->name + "\"}"); + } - if (resulterror.length() > 0) + if (resulterror.length() > 0) { success |= MQTTPublish(namenumber + "error", resulterror, qos, SetRetainFlag); + schedule_websocket_message("{\"error\": \"" + resulterror + "\", \"number\": \"" + (*NUMBERS)[i]->name + "\"}"); + } if (resultrate.length() > 0) { success |= MQTTPublish(namenumber + "rate", resultrate, qos, SetRetainFlag); @@ -273,8 +278,10 @@ bool ClassFlowMQTT::doFlow(string zwtime) success |= MQTTPublish(namenumber + "rate_per_digitalization_round", resultchangabs, qos, SetRetainFlag); } - if (resultraw.length() > 0) + if (resultraw.length() > 0) { success |= MQTTPublish(namenumber + "raw", resultraw, qos, SetRetainFlag); + schedule_websocket_message("{\"raw\": \"" + resultraw + "\", \"number\": \"" + (*NUMBERS)[i]->name + "\"}"); + } if (resulttimestamp.length() > 0) success |= MQTTPublish(namenumber + "timestamp", resulttimestamp, qos, SetRetainFlag); diff --git a/code/components/jomjol_flowcontroll/ClassFlowPostProcessing.cpp b/code/components/jomjol_flowcontroll/ClassFlowPostProcessing.cpp index ceed7d3a..81349e74 100644 --- a/code/components/jomjol_flowcontroll/ClassFlowPostProcessing.cpp +++ b/code/components/jomjol_flowcontroll/ClassFlowPostProcessing.cpp @@ -8,6 +8,7 @@ #include +#include "websocket.h" #include "time_sntp.h" #include "esp_log.h" @@ -882,6 +883,8 @@ bool ClassFlowPostProcessing::doFlow(string zwtime) NUMBERS[j]->ReturnValue = ""; NUMBERS[j]->lastvalue = imagetime; + schedule_websocket_message("{\"status\": \"" + NUMBERS[j]->ErrorMessageText + "\", \"number\": \"" + NUMBERS[j]->name + "\"}"); + string _zw = NUMBERS[j]->name + ": Raw: " + NUMBERS[j]->ReturnRawValue + ", Value: " + NUMBERS[j]->ReturnValue + ", Status: " + NUMBERS[j]->ErrorMessageText; LogFile.WriteToFile(ESP_LOG_ERROR, TAG, _zw); WriteDataLog(j); @@ -915,6 +918,8 @@ bool ClassFlowPostProcessing::doFlow(string zwtime) NUMBERS[j]->ReturnRateValue = ""; NUMBERS[j]->lastvalue = imagetime; + schedule_websocket_message("{\"status\": \"" + NUMBERS[j]->ErrorMessageText + "\", \"number\": \"" + NUMBERS[j]->name + "\"}"); + string _zw = NUMBERS[j]->name + ": Raw: " + NUMBERS[j]->ReturnRawValue + ", Value: " + NUMBERS[j]->ReturnValue + ", Status: " + NUMBERS[j]->ErrorMessageText; LogFile.WriteToFile(ESP_LOG_ERROR, TAG, _zw); WriteDataLog(j); diff --git a/code/components/jomjol_flowcontroll/MainFlowControl.cpp b/code/components/jomjol_flowcontroll/MainFlowControl.cpp index 26084fb2..8ef074a3 100644 --- a/code/components/jomjol_flowcontroll/MainFlowControl.cpp +++ b/code/components/jomjol_flowcontroll/MainFlowControl.cpp @@ -13,6 +13,7 @@ #include "Helper.h" #include "statusled.h" +#include "websocket.h" #include "esp_camera.h" #include "time_sntp.h" #include "ClassControllCamera.h" @@ -985,6 +986,10 @@ void task_autodoFlow(void *pvParameter) { LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "----------------------------------------------------------------"); // Clear separation between runs std::string _zw = "Round #" + std::to_string(++countRounds) + " started"; + + schedule_websocket_message("{\"round\": \"" + std::to_string(countRounds) + "\"}"); + schedule_websocket_message("{\"uptime\": \"" + std::to_string(getUpTime()) + "\"}"); + time_t roundStartTime = getUpTime(); LogFile.WriteToFile(ESP_LOG_INFO, TAG, _zw); fr_start = esp_timer_get_time(); @@ -1012,12 +1017,15 @@ void task_autodoFlow(void *pvParameter) // Round finished -> Logfile LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Round #" + std::to_string(countRounds) + " completed (" + std::to_string(getUpTime() - roundStartTime) + " seconds)"); + schedule_websocket_message("{\"round duration\": \"" + std::to_string(getUpTime() - roundStartTime) + "\"}"); // CPU Temp -> Logfile LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "CPU Temperature: " + std::to_string((int)temperatureRead()) + "°C"); + schedule_websocket_message("{\"cpu temperature\": \"" + std::to_string(temperatureRead()) + "\"}"); // WIFI Signal Strength (RSSI) -> Logfile LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "WIFI Signal (RSSI): " + std::to_string(get_WIFI_RSSI()) + "dBm"); + schedule_websocket_message("{\"wifi rssi\": \"" + std::to_string(get_WIFI_RSSI()) + "\"}"); // Check if time is synchronized (if NTP is configured) if (getUseNtp() && !getTimeIsSet()) { diff --git a/code/components/jomjol_helper/CMakeLists.txt b/code/components/jomjol_helper/CMakeLists.txt index 2bca3ade..92042011 100644 --- a/code/components/jomjol_helper/CMakeLists.txt +++ b/code/components/jomjol_helper/CMakeLists.txt @@ -2,6 +2,6 @@ FILE(GLOB_RECURSE app_sources ${CMAKE_CURRENT_SOURCE_DIR}/*.*) idf_component_register(SRCS ${app_sources} INCLUDE_DIRS "." - REQUIRES esp_timer tflite-lib jomjol_logfile fatfs sdmmc) + REQUIRES esp_timer tflite-lib jomjol_logfile fatfs sdmmc esp_http_server) diff --git a/code/components/jomjol_helper/websocket.cpp b/code/components/jomjol_helper/websocket.cpp new file mode 100644 index 00000000..f2d9d9fa --- /dev/null +++ b/code/components/jomjol_helper/websocket.cpp @@ -0,0 +1,129 @@ +#include "esp_log.h" +#include + +#include "../../include/defines.h" + +#include "ClassLogFile.h" +#include "freertos/ringbuf.h" +#include "psram.h" + +#include "websocket.h" + + +#define MAX_MESSAGE_LENGTH 100 + +static const char *TAG = "WEBSOCKET"; + +static httpd_handle_t server = NULL; +static httpd_handle_t websocket_handle = NULL; + +/* + * Structure holding server handle and message + * in order to use out of request send */ +struct async_resp_arg { + httpd_handle_t hd; + char message[MAX_MESSAGE_LENGTH]; +}; + + +/* + * async send function, which we put into the httpd work queue + */ +static void websocket_send_pending_message(void *arg) { + esp_err_t ret; + struct async_resp_arg *resp_arg = (struct async_resp_arg *)arg; + + LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Sending Websocket message: '" + std::string(resp_arg->message) + "'"); + + httpd_ws_frame_t ws_pkt; + memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t)); + ws_pkt.payload = (uint8_t *)resp_arg->message; + ws_pkt.len = strlen(resp_arg->message); + ws_pkt.type = HTTPD_WS_TYPE_TEXT; + + static size_t max_clients = CONFIG_LWIP_MAX_LISTENING_TCP; + size_t fds = max_clients; + int client_fds[max_clients]; + + ret = httpd_get_client_list(server, &fds, client_fds); + if (ret != ESP_OK) { + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to get Websocket client ist: " + std::to_string(ret) + "!"); + free_psram_heap("websocket msg", resp_arg); + return; + } + + /* Send it to all websocket clients */ + for (int i = 0; i < fds; i++) { + int client_info = httpd_ws_get_fd_info(server, client_fds[i]); + if (client_info == HTTPD_WS_CLIENT_WEBSOCKET) { + httpd_ws_send_frame_async(websocket_handle, client_fds[i], &ws_pkt); + } + } + + free_psram_heap("websocket msg", resp_arg); +} + + +esp_err_t schedule_websocket_message(std::string message) { + // return 0; + esp_err_t ret; + + if (websocket_handle == NULL) { // No websocket connecten open + return ESP_OK; + } + + LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Scheduled websocket message: '" + message + "'"); + + struct async_resp_arg *resp_arg = (struct async_resp_arg *)malloc_psram_heap("websocket msg", + sizeof(struct async_resp_arg), MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); + if (resp_arg == NULL) { + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Failed to malloc memory for scheduled websocket message!"); + return ESP_ERR_NO_MEM; + } + + strncpy(resp_arg->message, message.c_str(), MAX_MESSAGE_LENGTH); + + ret = httpd_queue_work(websocket_handle, websocket_send_pending_message, resp_arg); + if (ret != ESP_OK) { + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Websocket Scheduling failed: " + std::to_string(ret) + "!"); + free_psram_heap("websocket msg", resp_arg); + } + + return ret; +} + + +static esp_err_t ws_handler(httpd_req_t *req) { + if (req->method == HTTP_GET) { + LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Handshake done, the new websocket connection was opened"); + websocket_handle = req->handle; + } + + return ESP_OK; +} + + +static const httpd_uri_t ws_uri = { + .uri = "/ws", + .method = HTTP_GET, + .handler = ws_handler, + .user_ctx = NULL, + .is_websocket = true +}; + + +esp_err_t start_websocket_server(httpd_handle_t _server) { + esp_err_t ret; + + LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Init Websocket Server"); + + server = _server; + + // Registering the ws handler + LogFile.WriteToFile(ESP_LOG_DEBUG, TAG, "Registering URI handler"); + ret = httpd_register_uri_handler(server, &ws_uri); + if (ret != ESP_OK) { + LogFile.WriteToFile(ESP_LOG_ERROR, TAG, "Registering Websocket URI handler failed: " + std::to_string(ret)); + } + return ret; +} diff --git a/code/components/jomjol_helper/websocket.h b/code/components/jomjol_helper/websocket.h new file mode 100644 index 00000000..ffc32d5b --- /dev/null +++ b/code/components/jomjol_helper/websocket.h @@ -0,0 +1,10 @@ +#ifndef WEBSOCKET_H +#define WEBSOCKET_H + +#include + +esp_err_t start_websocket_server(httpd_handle_t server); + +esp_err_t schedule_websocket_message(std::string message); + +#endif // WEBSOCKET_H \ No newline at end of file diff --git a/code/components/jomjol_time_sntp/CMakeLists.txt b/code/components/jomjol_time_sntp/CMakeLists.txt index 6eade8c3..6689f902 100644 --- a/code/components/jomjol_time_sntp/CMakeLists.txt +++ b/code/components/jomjol_time_sntp/CMakeLists.txt @@ -2,6 +2,6 @@ FILE(GLOB_RECURSE app_sources ${CMAKE_CURRENT_SOURCE_DIR}/*.*) idf_component_register(SRCS ${app_sources} INCLUDE_DIRS "." - REQUIRES tflite-lib jomjol_logfile jomjol_configfile) + REQUIRES tflite-lib jomjol_logfile jomjol_configfile jomjol_helper) diff --git a/code/components/jomjol_time_sntp/time_sntp.cpp b/code/components/jomjol_time_sntp/time_sntp.cpp index 3c620f16..0cdb786e 100644 --- a/code/components/jomjol_time_sntp/time_sntp.cpp +++ b/code/components/jomjol_time_sntp/time_sntp.cpp @@ -13,6 +13,7 @@ #include "esp_sntp.h" #include "../../include/defines.h" +#include "websocket.h" #include "ClassLogFile.h" #include "configFile.h" @@ -68,6 +69,8 @@ void time_sync_notification_cb(struct timeval *tv) } LogFile.WriteToFile(ESP_LOG_INFO, TAG, "Time is synced with NTP Server " + getServerName() + ": " + getCurrentTimeString("%Y-%m-%d %H:%M:%S")); + + schedule_websocket_message("{\"ntp\": \"synchronized\"}"); } diff --git a/code/main/main.cpp b/code/main/main.cpp index 1e12c0ac..c68c0370 100644 --- a/code/main/main.cpp +++ b/code/main/main.cpp @@ -36,6 +36,7 @@ #include "MainFlowControl.h" #include "server_file.h" #include "server_ota.h" +#include "websocket.h" #include "time_sntp.h" #include "configFile.h" //#include "ClassControllCamera.h" @@ -491,7 +492,8 @@ extern "C" void app_main(void) // ******************************************** ESP_LOGD(TAG, "starting servers"); - server = start_webserver(); + server = start_webserver(); + start_websocket_server(server); register_server_camera_uri(server); register_server_main_flow_task_uri(server); register_server_file_uri(server, "/sdcard"); diff --git a/code/main/server_main.cpp b/code/main/server_main.cpp index 1a8ce905..70167b80 100644 --- a/code/main/server_main.cpp +++ b/code/main/server_main.cpp @@ -427,7 +427,7 @@ httpd_handle_t start_webserver(void) config.server_port = 80; config.ctrl_port = 32768; config.max_open_sockets = 5; //20210921 --> previously 7 - config.max_uri_handlers = 39; // previously 24, 20220511: 35, 20221220: 37, 2023-01-02:38 + config.max_uri_handlers = 45; // previously 24, 20220511: 35, 20221220: 37, 2023-01-02:38, 20230430 (adding websocket): 45 config.max_resp_headers = 8; config.backlog_conn = 5; config.lru_purge_enable = true; // this cuts old connections if new ones are needed. diff --git a/code/sdkconfig.defaults b/code/sdkconfig.defaults index e41514a1..534e7118 100644 --- a/code/sdkconfig.defaults +++ b/code/sdkconfig.defaults @@ -109,6 +109,7 @@ CONFIG_ESP_INT_WDT_TIMEOUT_MS=300 CONFIG_HTTPD_MAX_REQ_HDR_LEN=1024 CONFIG_HTTPD_PURGE_BUF_LEN=16 +CONFIG_HTTPD_WS_SUPPORT=y CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=16 CONFIG_ESP32_WIFI_CACHE_TX_BUFFER_NUM=16 diff --git a/sd-card/html/websocket.html b/sd-card/html/websocket.html new file mode 100644 index 00000000..71b1e257 --- /dev/null +++ b/sd-card/html/websocket.html @@ -0,0 +1,65 @@ + + + + + + + +
+ + + + +