Wed, 03 Jul 2024 20:01:31 +0200
Version 0.4.2. Removed the components/websocket server and switched to the official http and websockets server. This server will also recover if the wifi connection disconnects and reconnects.
--- a/CMakeLists.txt Mon Jul 01 08:38:57 2024 +0200 +++ b/CMakeLists.txt Wed Jul 03 20:01:31 2024 +0200 @@ -2,9 +2,9 @@ # in this exact order for cmake to work correctly cmake_minimum_required(VERSION 3.16) -set(PROJECT_VER "0.4.1") +set(PROJECT_VER "0.4.2") set(PROJECT_NAME "brewboard") -set(EXTRA_COMPONENT_DIRS components/esp32-ds18b20 components/esp32-owb components/PID components/spidriver components/tft components/websocket) +set(EXTRA_COMPONENT_DIRS components/esp32-ds18b20 components/esp32-owb components/PID components/spidriver components/tft) include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(brewboard)
--- a/README.md Mon Jul 01 08:38:57 2024 +0200 +++ b/README.md Wed Jul 03 20:01:31 2024 +0200 @@ -31,7 +31,7 @@ * WiFi * BlueTooth, maar wordt niet gebruikt. * Timers en een Realtime Klok. -* 2 SPI bussen (voor de TFT en SD/MMC). +* 3 SPI bussen (voor het intern geheugen, de TFT en touchscreen en SD/MMC). * OTA, Over The Air updates. * Nog veel meer bussen die we niet gebruiken. @@ -71,10 +71,6 @@ De 1-wire bus: https://www.github.com/DavidAntliff/esp32-owb De DS18B20 sensoren: https://www.github.com/DavidAntliff/esp32-ds18b20 -De webserver en websockets server. Websockets worden gebruikt voor de web -client. Deze web client probeert een kopie te zijn van het touch scherm. -https://github.com/Molorius/esp32-websocket.git components/websocket - 7 Segments display font voor de web applicatie: http://www.keshikan.net Deze is gemaakt door keshikan. @@ -83,6 +79,11 @@ meeste boards hebben niet genoeg aansluitingen naar buiten, je hebt echt de 36 pens versie van Geekcreit® nodig. +Hetzelfde geld voor de TFT en Touch scherm. Deze wordt aangesloten op de +eerste vrije SPI bus, dat is SPI2 aka HSPI. Dus schermpjes met andere +bussen zoals I2C, I2S, parallel werken niet. Wil je die toch gebruiken, +dan zul je die zelf werkend moeten maken. + #ESP IDF. @@ -95,6 +96,11 @@ Om over te gaan naar deze versie is het belangrijk om onder de oude IDF eerst een update te doen naar die versie 0.3.25. Lees UPGRADE.md die in die versie zit. +De IDF versies tot en met 4.x.x waren nog flink in ontwikkeling en er waren heel +regelmatig API wijzigingen waardoor je niet makkelijk van versie kon wisselen. +Nu met versie 5.x is dit al veel stabieler en minder kritisch om een "verkeerde" +versie te gebruiken. + ---------------------------------------------------------------------------------- HENDI:
--- a/components/websocket/CMakeLists.txt Mon Jul 01 08:38:57 2024 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,2 +0,0 @@ -idf_component_register(SRCS websocket.c websocket_server.c INCLUDE_DIRS include REQUIRES mbedtls) -
--- a/components/websocket/Kconfig Mon Jul 01 08:38:57 2024 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,46 +0,0 @@ - -menu "WebSocket Server" - -config WEBSOCKET_SERVER_MAX_CLIENTS - int "Max clients" - range 1 1000 - default 10 - help - Maximum number of clients that the WebSocket - server can handle at a time. - -config WEBSOCKET_SERVER_QUEUE_SIZE - int "Queue read size" - range 1 100 - default 10 - help - Size of the queue to deal with incoming - WebSocket messages. The queue holds the - connection, not the actual message. - -config WEBSOCKET_SERVER_QUEUE_TIMEOUT - int "Queue timeout" - range 0 10000 - default 30 - help - Timeout for adding new connections to the - read queue. - -config WEBSOCKET_SERVER_TASK_STACK_DEPTH - int "Stack depth" - range 3000 20000 - default 7000 - help - Stack depth for the WebSocket server. The task - handles reads. - -config WEBSOCKET_SERVER_TASK_PRIORITY - int "Priority" - range 1 20 - default 5 - help - Priority for the WebSocket server. The task - handles reads. - - -endmenu
--- a/components/websocket/README.md Mon Jul 01 08:38:57 2024 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,247 +0,0 @@ - -By Blake Felt - blake.w.felt@gmail.com - -ESP32 WebSocket -================== - -A component for WebSockets on ESP-IDF using lwip netconn. -For an example, see https://github.com/Molorius/ESP32-Examples. - -To add to a project, type: -`git submodule add https://github.com/Molorius/esp32-websocket.git components/websocket` -into the base directory of your project. - -Some configuration options for the Server can be found in menuconfig in: -Component config ---> WebSocket Server - -This presently only has the WebSocket server code working, but client code will be added in the future (the groundwork is there). - -The code only allows one WebSocket server at a time, but this merely handles all incoming reads. New connections are added externally, so this can be used to hold various WebSocket connections. - -While this can theoretically handle very large messages, hardware constraints (RAM) limits the size of messages. I highly recommend not using more than 5000 bytes per message, but no constraint is in place for this. - -Any suggestions or fixes are gladly appreciated. - -Table of Contents -================= -* [Enumerations](#enumerations) - * [WEBSOCKET_TYPE_t](#enum-websocket_type_t) -* [Functions](#functions) - * [ws_server_start](#int-ws_server_start) - * [ws_server_stop](#int-ws_server_stop) - * [ws_server_add_client](#int-ws_server_add_clientstruct-netconn-connchar-msguint16_t-lenchar-urlvoid-callback) - * [ws_server_add_client_protocol](#int-ws_server_add_client_protocolstruct-netconn-connchar-msguint16_t-lenchar-urlchar-protocolvoid-callback) - * [ws_server_len_url](#int-ws_server_len_urlchar-url) - * [ws_server_len_all](#int-ws_server_len_all) - * [ws_server_remove_client](#int-ws_server_remove_clientint-num) - * [ws_server_remove_clients](#int-ws_server_remove_clientschar-url) - * [ws_server_remove_all](#int-ws_server_remove_all) - * [ws_server_send_text_client](#int-ws_server_send_text_clientint-numchar-msguint64_t-len) - * [ws_server_send_text_clients](#int-ws_server_send_text_clientschar-urlchar-msguint64_t-len) - * [ws_server_send_text_all](#int-ws_server_send_text_allchar-msguint64_t-len) - * [ws_server_send_text_client_from_callback](#int-ws_server_send_text_client_from_callbackint-numchar-msguint64_t-len) - * [ws_server_send_text_clients_from_callback](#int-ws_server_send_text_clients_from_callbackchar-urlchar-msguint64_t-len) - * [ws_server_send_text_all_from_callback](#int-ws_server_send_text_all_from_callbackchar-msguint64_t-len) - -Enumerations -============ - -enum WEBSOCKET_TYPE_t ---------------------- - -The different types of WebSocket events. - -*Values* - * `WEBSOCKET_CONNECT`: A new client has successfully connected. - * `WEBSOCKET_DISCONNECT_EXTERNAL`: The other side sent a disconnect message. - * `WEBSOCKET_DISCONNECT_INTERNAL`: The esp32 server sent a disconnect message. - * `WEBSOCKET_DISCONNECT_ERROR`: Disconnect due to a connection error. - * `WEBSOCKET_TEXT`: Incoming text. - * `WEBSOCKET_BIN`: Incoming binary. - * `WEBSOCKET_PING`: The other side sent a ping message. - * `WEBSOCKET_PONG`: The other side successfully replied to our ping. - -Functions -========= - -int ws_server_start() ---------------------- - -Starts the WebSocket Server. Use this function before attempting any -sort of transmission or adding a client. - -*Returns* - * 1: successful start - * 0: server already running - -int ws_server_stop() --------------------- - -Stops the WebSocket Server. New clients can still be added and -messages can be sent, but new messages will not be received. - -*Returns* - * 1: successful stop - * 0: server was not running before - -int ws_server_add_client(struct netconn* conn,char* msg,uint16_t len,char* url,void *callback) ----------------------------------------------------------------------------------------------- - -Adds a client to the WebSocket Server handler and performs the necessary handshake. - -*Parameters* - * `conn`: the lwip netconn connection. - * `msg`: the entire incoming request message to join the server. Necessary for the handshake. - * `len`: the length of `msg`. - * `url`: the NULL-terminated url. Used to keep track of clients, not required. - * `callback`: the callback that is used to run WebSocket events. This must be with parameters(uint8_t num,WEBSOCKET_TYPE_t type,char* msg,uint64_t len) where "num" is the client number, "type" is the event type, "msg" is the incoming message, and "len" is the message length. The callback itself is optional. - -*Returns* - * -2: not enough information in `msg` to perform handshake. - * -1: server full, or connection issue. - * 0 or greater: connection number - -int ws_server_add_client_protocol(struct netconn* conn,char* msg,uint16_t len,char* url,char* protocol,void *callback) ----------------------------------------------------------------------------------------------------------------------- - -Adds a client to the WebSocket Server handler and performs the necessary handshake. Will also send -the specified protocol. - -*Parameters* - * `conn`: the lwip netconn connection. - * `msg`: the entire incoming request message to join the server. Necessary for the handshake. - * `len`: the length of `msg`. - * `url`: the NULL-terminated url. Used to keep track of clients, not required. - * `protocol`: the NULL-terminated protocol. This will be sent to the client in the header. - * `callback`: the callback that is used to run WebSocket events. This must be with parameters(uint8_t num,WEBSOCKET_TYPE_t type,char* msg,uint64_t len) where "num" is the client number, "type" is the event type, "msg" is the incoming message, and "len" is the message length. The callback itself is optional. - -*Returns* - * -2: not enough information in `msg` to perform handshake. - * -1: server full, or connection issue. - * 0 or greater: connection number - -int ws_server_len_url(char* url) --------------------------------- - -Returns the number of clients connected to the specified URL. - -*Parameters* - * `url`: the NULL-terminated string of the desired URL. - -*Returns* - * The number of clients connected to the specified URL. - -int ws_server_len_all() ------------------------ - -*Returns* - * The number of connected clients. - -int ws_server_remove_client(int num) ------------------------------------- - -Removes the desired client. - -*Parameters* - * `num`: the client number - -*Returns* - * 0: not a valid client number - * 1: client disconnected - -int ws_server_remove_clients(char* url) ---------------------------------------- - -Removes all clients connect to the desired URL. - -*Parameters* - * `url`: the NULL-terminated URL. - -*Returns* - * The number of clients that were disconnected. - -int ws_server_remove_all() --------------------------- - -Removes all clients from server. - -*Returns* - * The number of clients that were disconnected. - -int ws_server_send_text_client(int num,char* msg,uint64_t len) --------------------------------------------------------------- - -Sends the desired message to the client. - -*Parameters* - * `num`: the client's number. - * `msg`: the desired message. - * `len`: the length of the message. - -*Returns* - * 0: message not sent properly - * 1: message sent - -int ws_server_send_text_clients(char* url,char* msg,uint64_t len) ------------------------------------------------------------------ - -Sends the message to clients connected to the desired URL. - -*Parameters* - * `url`: the NULL-terminated URL. - * `msg`: the desired message. - * `len`: the length of the message. - -*Returns* - * The number of clients that the message was sent to. - -int ws_server_send_text_all(char* msg,uint64_t len) ---------------------------------------------------- - -Sends the message to all connected clients. - -*Parameters* - * `msg`: the desired message - * `len`: the length of the message - -*Returns* - * The number of clients that the message was sent to. - -int ws_server_send_text_client_from_callback(int num,char* msg,uint64_t len) ----------------------------------------------------------------------------- - -Sends the desired message to the client. Only use this inside the callback function. - -*Parameters* - * `num`: the client's number. - * `msg`: the desired message. - * `len`: the length of the message. - -*Returns* - * 0: message not sent properly - * 1: message sent - -int ws_server_send_text_clients_from_callback(char* url,char* msg,uint64_t len) -------------------------------------------------------------------------------- - -Sends the message to clients connected to the desired URL. Only use this inside the callback function. - -*Parameters* - * `url`: the NULL-terminated URL. - * `msg`: the desired message. - * `len`: the length of the message. - -*Returns* - * The number of clients that the message was sent to. - -int ws_server_send_text_all_from_callback(char* msg,uint64_t len) ------------------------------------------------------------------ - -Sends the message to all connected clients. Only use this inside the callback function. - -*Parameters* - * `msg`: the desired message - * `len`: the length of the message - -*Returns* - * The number of clients that the message was sent to.
--- a/components/websocket/component.mk Mon Jul 01 08:38:57 2024 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,2 +0,0 @@ -# websocket component makefile -# all files are in default positions
--- a/components/websocket/include/websocket.h Mon Jul 01 08:38:57 2024 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,153 +0,0 @@ -/** - * @file websocket.h - * @brief Websocket functions. - * @author Blake Felt - blake.w.felt@gmail.com` - */ - - -#ifdef __cplusplus -extern "C" { -#endif - -#ifndef WEBSOCKET_H -#define WEBSOCKET_H - -#include "lwip/api.h" - - -/** - * @brief the different codes for the callbacks - */ -typedef enum { - WEBSOCKET_CONNECT, ///< Connect - WEBSOCKET_DISCONNECT_EXTERNAL, ///< the other side disconnected - WEBSOCKET_DISCONNECT_INTERNAL, ///< the esp32 disconnected - WEBSOCKET_DISCONNECT_ERROR, ///< disconnect due to error - WEBSOCKET_TEXT, ///< Text message - WEBSOCKET_BIN, ///< Binary message - WEBSOCKET_PING, ///< PING message - WEBSOCKET_PONG ///< PONG message -} WEBSOCKET_TYPE_t; - - - -/** - * @brief websocket operation codes - */ -typedef enum { - WEBSOCKET_OPCODE_CONT = 0x0, ///< Continue - WEBSOCKET_OPCODE_TEXT = 0x1, ///< Text - WEBSOCKET_OPCODE_BIN = 0x2, ///< Binary - WEBSOCKET_OPCODE_CLOSE = 0x8, ///< Close connection - WEBSOCKET_OPCODE_PING = 0x9, ///< PING message - WEBSOCKET_OPCODE_PONG = 0xA ///< PONG message -} WEBSOCKET_OPCODES_t; - - -/** - * @brief the header, useful for creating and quickly passing to functions - */ -typedef struct { - union { - struct { - uint16_t LEN:7; ///< bits 0.. 6 - uint16_t MASK:1; ///< bit 7 - uint16_t OPCODE:4; ///< bits 8.. 11 - uint16_t :3; ///< bits 12.. 14 reserved - uint16_t FIN:1; ///< bit 15 - } bit; - struct { - uint16_t ONE:8; ///< bits 0.. 7 - uint16_t ZERO:8; ///< bits 8.. 15 - } pos; - } param; ///< the initial parameters of the header - uint64_t length; ///< actual message length - union { - char part[4]; ///< the mask, array - uint32_t full; ///< the mask, all 32 bits - } key; ///< masking key - bool received; ///< was a message successfully received? -} ws_header_t; - - -/** - * @brief A client, with space for a server callback or a client callback (depending on use) - */ -typedef struct { - struct netconn* conn; ///< the connection - bool connected; ///< connection state - char* url; ///< the associated url, null terminated - char* protocol; ///< the associated protocol, null terminated - bool ping; ///< did we send a ping? - WEBSOCKET_OPCODES_t last_opcode; ///< the previous opcode - char* contin; ///< any continuation piece - bool contin_text; ///< is the continue a binary or text? - uint64_t len; ///< length of continuation - uint32_t unfinished; ///< sometimes netconn doesn't read a full frame, treated similarly to a continuation frame - void (*ccallback)(WEBSOCKET_TYPE_t type,char* msg,uint64_t len); ///< client callback - void (*scallback)(uint8_t num,WEBSOCKET_TYPE_t type,char* msg,uint64_t len); ///< server callback -} ws_client_t; - - -/** - * @brief returns the populated client struct - * does not send any header, assumes the proper handshake has already occurred - * @param conn The network connection - * @param url The connection url. - * @param ccallback callback for client (userspace) - * @param scallback callback for server (userspace) - * @return The Websocket client structure. - */ -ws_client_t ws_connect_client(struct netconn* conn, - char* url, - void (*ccallback)(WEBSOCKET_TYPE_t type,char* msg,uint64_t len), - void (*scallback)(uint8_t num,WEBSOCKET_TYPE_t type,char* msg,uint64_t len) - ); - - -/** - * @brief Disconnect a websocket client. - * @param client The ws_client_t structure with the client information. - */ -void ws_disconnect_client(ws_client_t* client); - -/** - * @brief Test if the client is connected. - * status updates after send/read/connect/disconnect. - * @param client The ws_client_t structure with the client information. - * @return True if connected, false if not. - */ -bool ws_is_connected(ws_client_t client); - -/** - * @brief Send data via websocjet to a client. This function performs the masking. - * @param client The ws_client_t structure with the client information. - * @param opcode The opcode to send the message. - * @param msg The message itself. - * @param len The length of the message. - * @param mask Encrypt?? - * @return error code or ERR_OK - */ -err_t ws_send(ws_client_t* client,WEBSOCKET_OPCODES_t opcode,char* msg,uint64_t len,bool mask); - -/** - * @brief Receive a message from a websocket connection. - * @param client The ws_client_t structure with the client information. - * @param header unmasks and returns message. populates header. - * @return Unmasks and returns message. populates header. - */ -char* ws_read(ws_client_t* client,ws_header_t* header); - -/** - * @brief Create a handshake hashed string. - * @param key The key for the hash. - * @param len The length. - * @return The string of output. - */ -char* ws_hash_handshake(char* key,uint8_t len); - -#endif // ifndef WEBSOCKET_H - -#ifdef __cplusplus -} -#endif
--- a/components/websocket/include/websocket_server.h Mon Jul 01 08:38:57 2024 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,170 +0,0 @@ -/** - * @file websocket_server.h - * @brief Websocket server functions. - * @author Blake Felt - blake.w.felt@gmail.com` - */ - -#ifdef __cplusplus -extern "C" { -#endif - -#ifndef WEBSOCKET_SERVER_H -#define WEBSOCKET_SERVER_H - -#include "websocket.h" - -#define WEBSOCKET_SERVER_MAX_CLIENTS CONFIG_WEBSOCKET_SERVER_MAX_CLIENTS ///< Maximum allowed clients -#define WEBSOCKET_SERVER_QUEUE_SIZE CONFIG_WEBSOCKET_SERVER_QUEUE_SIZE ///< Webserver requests queue size -#define WEBSOCKET_SERVER_QUEUE_TIMEOUT CONFIG_WEBSOCKET_SERVER_QUEUE_TIMEOUT ///< Queue timeout -#define WEBSOCKET_SERVER_TASK_STACK_DEPTH CONFIG_WEBSOCKET_SERVER_TASK_STACK_DEPTH ///< Task stack size -#define WEBSOCKET_SERVER_TASK_PRIORITY CONFIG_WEBSOCKET_SERVER_TASK_PRIORITY ///< Task priority -#define WEBSOCKET_SERVER_PINNED CONFIG_WEBSOCKET_SERVER_PINNED ///< Pinned to a CPU -#if WEBSOCKET_SERVER_PINNED -#define WEBSOCKET_SERVER_PINNED_CORE CONFIG_WEBSOCKET_SERVER_PINNED_CORE ///< If pinned, which CPU -#endif - -/** - * @brief Starts the server. - * @return 1 if started, 0 if the server was already started. - */ -int ws_server_start(); - -/** - * @brief Stops the server. - * @return 1 if stopped, 0 if the server was not running. - */ -int ws_server_stop(); - -/** - * @brief Adds a client, returns the client's number in the server. - * @param conn The network connection. - * @param msg The message received from the client. - * @param len The length of the message. - * @param url The url of the connection. - * @param callback The callback function. - * @return Negative if an error, else the connection number. - */ -int ws_server_add_client(struct netconn* conn, - char* msg, - uint16_t len, - char* url, - void (*callback)(uint8_t num, WEBSOCKET_TYPE_t type, char* msg, uint64_t len)); - -/** - * @brief Adds a client, returns the client's number in the server. - * @param conn The network connection. - * @param msg The message received from the client. - * @param len The length of the message. - * @param url The url of the connection. - * @param protocol The protocol requested by the client. - * @param callback The callback function. - * @return Negative if an error, else the connection number. - */ -int ws_server_add_client_protocol(struct netconn* conn, - char* msg, - uint16_t len, - char* url, - char* protocol, - void (*callback)(uint8_t num, WEBSOCKET_TYPE_t type, char* msg, uint64_t len)); - -/** - * @brief Returns the number of connected clients to url - * @param url The url to check. - * @return The number of connected clients. - */ -int ws_server_len_url(char* url); - -/** - * @brief Returns the total number of connected clients. - * @return The number of connected clients. - */ -int ws_server_len_all(); - -/** - * @brief Removes the client with the set number - * @param num The client number. - * @return 1 if success, else 0. - */ -int ws_server_remove_client(int num); - -/** - * @brief Removes all clients connected to the specified url. - * @param url The connection url. - * @return The number of clients removed. - */ -int ws_server_remove_clients(char* url); - -/** - * @brief Removes all clients from the server. - * @return The number of clients removed. - */ -int ws_server_remove_all(); - -/** - * @brief Send text to client with the set number. - * @param num The client connection number. - * @param msg The message to send. - * @param len The length of the message to send. - * @return 1 if message send. - */ -int ws_server_send_text_client(int num,char* msg,uint64_t len); -int ws_server_send_text_client_from_callback(int num,char* msg,uint64_t len); - -/** - * @brief Sends text to all clients with the set url. - * @param url The clients url. - * @param msg The message to send. - * @param len The length of the message to send. - * @return The number of clients the message was send to. - */ -int ws_server_send_text_clients(char* url,char* msg,uint64_t len); -int ws_server_send_text_clients_from_callback(char* url,char* msg,uint64_t len); - -/** - * @brief Sends text to all clients. - * @param msg The message to send. - * @param len The length of the message to send. - * @return The number of clients the message was send to. - */ -int ws_server_send_text_all(char* msg,uint64_t len); -int ws_server_send_text_all_from_callback(char* msg,uint64_t len); - -/** - * @brief Send binary message to client with the set number. - * @param num The client connection number. - * @param msg The message to send. - * @param len The length of the message to send. - * @return 1 if message send. - */ -int ws_server_send_bin_client(int num,char* msg,uint64_t len); -int ws_server_send_bin_client_from_callback(int num,char* msg,uint64_t len); - -/** - * @brief Sends binary message to all clients with the set url. - * @param url The clients url. - * @param msg The message to send. - * @param len The length of the message to send. - * @return The number of clients the message was send to. - */ -int ws_server_send_bin_clients(char* url,char* msg,uint64_t len); - -/** - * @brief Sends binary message to all clients. - * @param msg The message to send. - * @param len The length of the message to send. - * @return The number of clients the message was send to. - */ -int ws_server_send_bin_all(char* msg,uint64_t len); - -/** - * @brief Websocket ping (not working yet) - * @param num The connection number - * @return 1 if succeeded. - */ -int ws_server_ping(int num); - -#endif - -#ifdef __cplusplus -} -#endif
--- a/components/websocket/websocket.c Mon Jul 01 08:38:57 2024 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,318 +0,0 @@ -/** - * @file websocket.c - * @brief Websocket functions. - * @author Blake Felt - blake.w.felt@gmail.com` - */ - -#include "websocket.h" -#include "lwip/tcp.h" // for the netconn structure -#include "esp_system.h" // for esp_random -#include "esp_log.h" -#include "mbedtls/base64.h" -#include "mbedtls/sha1.h" -#include <string.h> - -static const char *TAG = "websocket"; - -ws_client_t ws_connect_client(struct netconn* conn, - char* url, - void (*ccallback)(WEBSOCKET_TYPE_t type,char* msg,uint64_t len), - void (*scallback)(uint8_t num,WEBSOCKET_TYPE_t type,char* msg,uint64_t len) - ) { - ws_client_t client; - client.conn = conn; - client.connected = true; - client.url = url; - client.ping = 0; - client.last_opcode = 0; - client.contin = NULL; - client.len = 0; - client.unfinished = 0; - client.ccallback = ccallback; - client.scallback = scallback; - return client; -} - -void ws_disconnect_client(ws_client_t* client) { - ws_send(client,WEBSOCKET_OPCODE_CLOSE,NULL,0,1); // tell the client to close - if(client->conn) { - client->conn->callback = NULL; // shut off the callback - netconn_close(client->conn); - netconn_delete(client->conn); - client->conn = NULL; - } - client->connected = false; - client->url = NULL; - client->last_opcode = 0; - if(client->len) { - if(client->contin) - free(client->contin); - client->len = 0; - } - client->ccallback = NULL; - client->scallback = NULL; -} - -bool ws_is_connected(ws_client_t client) { - if (client.conn && client.connected) - return 1; - return 0; -} - -static void ws_generate_mask(ws_header_t* header) { - header->param.bit.MASK = 1; - header->key.full = esp_random(); // generate a random 32 bit number -} - -static void ws_encrypt_decrypt(char* msg,ws_header_t header) { - if(header.param.bit.MASK) { - for(uint64_t i=0; i<header.length; i++) { - msg[i] ^= header.key.part[i%4]; - } - } -} - - - -err_t ws_send(ws_client_t* client,WEBSOCKET_OPCODES_t opcode,char* msg,uint64_t len,bool mask) -{ - static char *out; - static char *encrypt; - static uint64_t pos; - static uint64_t true_len; - ws_header_t header; - static err_t err; - - header.param.pos.ZERO = 0; // reset the whole header - header.param.pos.ONE = 0; - - header.param.bit.FIN = 1; // all pieces are done (you don't need a huge message anyway...) - header.param.bit.OPCODE = opcode; - // populate LEN field - pos = 2; - header.length = len; - if (len<125) { - header.param.bit.LEN = len; - } else if (len < 65536) { - header.param.bit.LEN = 126; - pos += 2; - } else { - header.param.bit.LEN = 127; - pos += 8; - } - - if (mask) { - ws_generate_mask(&header); // get a key - encrypt = malloc(len); // allocate memory for the encryption - memcpy(encrypt,msg,len); - ws_encrypt_decrypt(encrypt,header); // encrypt it! - pos += 4; // add the position - } - - true_len = pos+len; // get the length of the entire message - pos = 2; - out = malloc(true_len); // allocate dat memory - - if (out == NULL) { - ESP_LOGE(TAG, "ws_send malloc error for %llu bytes", true_len); - return ERR_MEM; - } - - out[0] = header.param.pos.ZERO; // save header - out[1] = header.param.pos.ONE; - - // put in the length, if necessary - if(header.param.bit.LEN == 126) { - out[2] = (len >> 8) & 0xFF; - out[3] = (len ) & 0xFF; - pos = 4; - } - if(header.param.bit.LEN == 127) { - //memcpy(&out[2],&len,8); - out[2] = (len >> 56) & 0xFF; - out[3] = (len >> 48) & 0xFF; - out[4] = (len >> 40) & 0xFF; - out[5] = (len >> 32) & 0xFF; - out[6] = (len >> 24) & 0xFF; - out[7] = (len >> 16) & 0xFF; - out[8] = (len >> 8) & 0xFF; - out[9] = (len) & 0xFF; - pos = 10; - } - - if(mask) { - out[pos] = header.key.part[0]; pos++; - out[pos] = header.key.part[1]; pos++; - out[pos] = header.key.part[2]; pos++; - out[pos] = header.key.part[3]; pos++; - memcpy(&out[pos],encrypt,len); // put in the encrypted message - free(encrypt); - } else { - memcpy(&out[pos],msg,len); - } - - err = netconn_write(client->conn,out,true_len,NETCONN_COPY); // finally! send it. - if (err != ERR_OK) { - client->connected = false; - ESP_LOGE(TAG, "ws_send netconn_write error %d", err); - } - free(out); // free the entire message - return err; -} - - - -char* ws_read(ws_client_t* client,ws_header_t* header) -{ - char *ret, *append; - err_t err; - struct netbuf *inbuf, *inbuf2; - char *buf, *buf2; - uint16_t len, len2; - uint64_t pos, cont_len, cont_pos; - - // if we read from this previously (not cont frames), stop reading - if(client->unfinished) { - client->unfinished--; - return NULL; - } - - err = netconn_recv(client->conn,&inbuf); - if (err != ERR_OK) { - client->connected = false; - ESP_LOGE(TAG, "ws_read netconn_recv error %d", err); - return NULL; - } - netbuf_data(inbuf,(void**)&buf, &len); - if(!buf) return NULL; - - // get the header - header->param.pos.ZERO = buf[0]; - header->param.pos.ONE = buf[1]; - - // get the message length - pos = 2; - if(header->param.bit.LEN < 125) { - header->length = header->param.bit.LEN; - } else if(header->param.bit.LEN == 126) { - header->length = buf[2] << 8 | buf[3]; - pos = 4; - } else { // LEN = 127 - header->length = (uint64_t)buf[2] << 56 | (uint64_t)buf[3] << 48 - | (uint64_t)buf[4] << 40 | (uint64_t)buf[5] << 32 - | (uint64_t)buf[6] << 24 | (uint64_t)buf[7] << 16 - | (uint64_t)buf[8] << 8 | (uint64_t)buf[9]; - pos = 10; - } - - if(header->param.bit.MASK) { - memcpy(&(header->key.full),&buf[pos],4); // extract the key - pos += 4; - } - - ret = malloc(header->length+1); // allocate memory, plus a byte - if(!ret) { - ESP_LOGE(TAG, "ws_read malloc error for %llu bytes", header->length+1); - netbuf_delete(inbuf); - header->received = 0; - return NULL; - } - - cont_len = len-pos; // get the actual length - memcpy(ret,&buf[pos],header->length); // allocate the total memory - cont_pos = cont_len; // get the initial position - - // netconn gives messages in pieces, so we need to get those (different than OPCODE_CONT) - while(cont_len < header->length) { // while the actual length is less than the header stated - err = netconn_recv(client->conn,&inbuf2); - if (err != ERR_OK) { - ESP_LOGE(TAG, "ws_read netconn_recv error %d", err); - netbuf_delete(inbuf2); - free(ret); - client->unfinished = 0; - client->connected = false; - header->received = 0; - return NULL; - } - - netbuf_data(inbuf2,(void**)&buf2, &len2); - memcpy(&ret[cont_pos],buf2,len2); - cont_pos += len2; - if (!buf2) { - client->unfinished = 0; - header->received = 0; - } - netbuf_delete(inbuf2); - client->unfinished++; - cont_len += len2; - } - - ret[header->length] = '\0'; // end string - ws_encrypt_decrypt(ret,*header); // unencrypt, if necessary - - if(header->param.bit.FIN == 0) { // if the message isn't done - if((header->param.bit.OPCODE == WEBSOCKET_OPCODE_CONT) && - ((client->last_opcode==WEBSOCKET_OPCODE_BIN) || (client->last_opcode==WEBSOCKET_OPCODE_TEXT))) { - cont_len = header->length + client->len; - append = malloc(cont_len); - memcpy(append,client->contin,client->len); - memcpy(&append[client->len],ret,header->length); - free(client->contin); - client->contin = malloc(cont_len); - client->len = cont_len; - - free(append); - free(ret); - netbuf_delete(inbuf); - //free(buf); - return NULL; - } - else if((header->param.bit.OPCODE==WEBSOCKET_OPCODE_BIN) || (header->param.bit.OPCODE==WEBSOCKET_OPCODE_TEXT)) { - if(client->len) { - free(client->contin); - } - client->contin = malloc(header->length); - memcpy(client->contin,ret,header->length); - client->len = header->length; - client->last_opcode = header->param.bit.OPCODE; - - free(ret); - netbuf_delete(inbuf); - //free(buf); - return NULL; - } - else { // there shouldn't be another FIN code.... - free(ret); - netbuf_delete(inbuf); - //free(buf); - return NULL; - } - } - client->last_opcode = header->param.bit.OPCODE; - if(inbuf) netbuf_delete(inbuf); - header->received = 1; - return ret; -} - -char* ws_hash_handshake(char* handshake,uint8_t len) { - const char hash[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - const uint8_t hash_len = sizeof(hash); - char* ret; - char key[64]; - unsigned char sha1sum[20]; - unsigned int ret_len; - - if(!len) return NULL; - ret = malloc(32); - - memcpy(key,handshake,len); - strlcpy(&key[len],hash,sizeof(key)); - mbedtls_sha1((unsigned char*)key,len+hash_len-1,sha1sum); - mbedtls_base64_encode(NULL, 0, &ret_len, sha1sum, 20); - if(!mbedtls_base64_encode((unsigned char*)ret,32,&ret_len,sha1sum,20)) { - ret[ret_len] = '\0'; - return ret; - } - free(ret); - return NULL; -}
--- a/components/websocket/websocket_server.c Mon Jul 01 08:38:57 2024 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,489 +0,0 @@ -/** - * @file websocket_server.c - * @brief Websocket server functions. - * @author Blake Felt - blake.w.felt@gmail.com` - */ - -#include "websocket_server.h" -#include "freertos/FreeRTOS.h" -#include "freertos/semphr.h" -#include "freertos/task.h" -#include "freertos/queue.h" -#include "lwip/tcp.h" -#include <string.h> - -static SemaphoreHandle_t xwebsocket_mutex; // to lock the client array -static QueueHandle_t xwebsocket_queue; // to hold the clients that send messages -static ws_client_t clients[WEBSOCKET_SERVER_MAX_CLIENTS]; // holds list of clients -static TaskHandle_t xtask; // the task itself - -/** - * @brief Add a connection to the callback queue. - * @param conn The network connection. - * @param evt The event. - * @param len Not used. - */ -static void background_callback(struct netconn* conn, enum netconn_evt evt,u16_t len) { - switch(evt) { - case NETCONN_EVT_RCVPLUS: - xQueueSendToBack(xwebsocket_queue,&conn,WEBSOCKET_SERVER_QUEUE_TIMEOUT); - break; - default: - break; - } -} - -/** - * @brief Handle client read. - * @param num The client connection number. - */ -static void handle_read(uint8_t num) -{ - ws_header_t header; - char* msg; - - header.received = 0; - msg = ws_read(&clients[num],&header); - - if (!header.received) { - if (msg) - free(msg); - return; - } - - switch(clients[num].last_opcode) { - case WEBSOCKET_OPCODE_CONT: - break; - case WEBSOCKET_OPCODE_BIN: - clients[num].scallback(num,WEBSOCKET_BIN,msg,header.length); - break; - case WEBSOCKET_OPCODE_TEXT: - clients[num].scallback(num,WEBSOCKET_TEXT,msg,header.length); - break; - case WEBSOCKET_OPCODE_PING: - ws_send(&clients[num],WEBSOCKET_OPCODE_PONG,msg,header.length,0); - clients[num].scallback(num,WEBSOCKET_PING,msg,header.length); - break; - case WEBSOCKET_OPCODE_PONG: - if (clients[num].ping) { - clients[num].scallback(num,WEBSOCKET_PONG,NULL,0); - clients[num].ping = 0; - } - break; - case WEBSOCKET_OPCODE_CLOSE: - clients[num].scallback(num,WEBSOCKET_DISCONNECT_EXTERNAL,NULL,0); - ws_disconnect_client(&clients[num]); - break; - default: - break; - } - - if(msg) - free(msg); -} - -/** - * @brief The webserver task. - */ -static void ws_server_task(void* pvParameters) -{ - struct netconn* conn; - - xwebsocket_mutex = xSemaphoreCreateMutex(); - xwebsocket_queue = xQueueCreate(WEBSOCKET_SERVER_QUEUE_SIZE, sizeof(struct netconn*)); - - // initialize all clients - for (int i = 0; i < WEBSOCKET_SERVER_MAX_CLIENTS; i++) { - clients[i].conn = NULL; - clients[i].connected = false; - clients[i].url = NULL; - clients[i].ping = 0; - clients[i].last_opcode = 0; - clients[i].contin = NULL; - clients[i].len = 0; - clients[i].ccallback = NULL; - clients[i].scallback = NULL; - } - - for(;;) { - xQueueReceive(xwebsocket_queue, &conn, portMAX_DELAY); - if(!conn) continue; // if the connection was NULL, ignore it - - if (xSemaphoreTake(xwebsocket_mutex, 25) == pdTRUE) { // take access - for(int i = 0; i < WEBSOCKET_SERVER_MAX_CLIENTS; i++) { - if(clients[i].conn == conn) { - handle_read(i); - break; - } - } - xSemaphoreGive(xwebsocket_mutex); // return access - } - } - vTaskDelete(NULL); -} - - - -int ws_server_start() -{ - if(xtask) - return 0; - - xTaskCreate(&ws_server_task, "ws_server_task", WEBSOCKET_SERVER_TASK_STACK_DEPTH, NULL, WEBSOCKET_SERVER_TASK_PRIORITY, &xtask); - return 1; -} - - - -int ws_server_stop() { - if(!xtask) - return 0; - - vTaskDelete(xtask); - return 1; -} - - - -/** - * @brief Prepare a response for a new websocket connection. - * @param buf The client message buffer. - * @param buflen The length of the buffer. - * @param handshake The jandshake hash. - * @param protocol The requested protocol or NULL. - * @return True iif success, else false. - */ -static bool prepare_response(char* buf,uint32_t buflen,char* handshake,char* protocol) { - const char WS_HEADER[] = "Upgrade: websocket\r\n"; - const char WS_KEY[] = "Sec-WebSocket-Key: "; - const char WS_RSP[] = "HTTP/1.1 101 Switching Protocols\r\n" \ - "Upgrade: websocket\r\n" \ - "Connection: Upgrade\r\n" \ - "Sec-WebSocket-Accept: %s\r\n" \ - "%s\r\n"; - - char* key_start; - char* key_end; - char* hashed_key; - - if(!strstr(buf,WS_HEADER)) return 0; - if(!buflen) return 0; - key_start = strstr(buf,WS_KEY); - if(!key_start) return 0; - key_start += 19; - key_end = strstr(key_start,"\r\n"); - if(!key_end) return 0; - - hashed_key = ws_hash_handshake(key_start,key_end-key_start); - if(!hashed_key) return 0; - if(protocol) { - char tmp[256]; - sprintf(tmp,WS_RSP,hashed_key,"Sec-WebSocket-Protocol: %s\r\n"); - sprintf(handshake,tmp,protocol); - } - else { - sprintf(handshake,WS_RSP,hashed_key,""); - } - free(hashed_key); - return 1; -} - -int ws_server_add_client_protocol(struct netconn* conn, - char* msg, - uint16_t len, - char* url, - char* protocol, - void (*callback)(uint8_t num, - WEBSOCKET_TYPE_t type, - char* msg, - uint64_t len)) { - int ret; - char handshake[256]; - - if(!prepare_response(msg,len,handshake,protocol)) { - netconn_close(conn); - netconn_delete(conn); - return -2; - } - - ret = -1; - if (xSemaphoreTake(xwebsocket_mutex, 10) == pdTRUE) { - conn->callback = background_callback; - netconn_write(conn,handshake,strlen(handshake),NETCONN_COPY); - - for(int i = 0; i < WEBSOCKET_SERVER_MAX_CLIENTS; i++) { - if(clients[i].conn) continue; - callback(i,WEBSOCKET_CONNECT,NULL,0); - clients[i] = ws_connect_client(conn,url,NULL,callback); - if(!ws_is_connected(clients[i])) { - callback(i,WEBSOCKET_DISCONNECT_ERROR,NULL,0); - ws_disconnect_client(&clients[i]); - break; - } - ret = i; - break; - } - xSemaphoreGive(xwebsocket_mutex); - } - - return ret; -} - - - -int ws_server_len_url(char* url) -{ - int ret; - ret = 0; - - if (xSemaphoreTake(xwebsocket_mutex, 10) == pdTRUE) { - for(int i=0;i<WEBSOCKET_SERVER_MAX_CLIENTS;i++) { - if(clients[i].url && !strcmp(url,clients[i].url)) - ret++; - } - xSemaphoreGive(xwebsocket_mutex); - } - - return ret; -} - -int ws_server_add_client(struct netconn* conn, - char* msg, - uint16_t len, - char* url, - void (*callback)(uint8_t num, - WEBSOCKET_TYPE_t type, - char* msg, - uint64_t len)) { - - return ws_server_add_client_protocol(conn,msg,len,url,NULL,callback); - -} - -int ws_server_len_all() -{ - int ret; - ret = 0; - - if (xSemaphoreTake(xwebsocket_mutex, 10) == pdTRUE) { - for(int i=0;i<WEBSOCKET_SERVER_MAX_CLIENTS;i++) { - if(clients[i].conn) - ret++; - } - xSemaphoreGive(xwebsocket_mutex); - } - - return ret; -} - - - -int ws_server_remove_client(int num) -{ - int ret = 0; - - if (xSemaphoreTake(xwebsocket_mutex, 10) == pdTRUE) { - if(ws_is_connected(clients[num])) { - clients[num].scallback(num,WEBSOCKET_DISCONNECT_INTERNAL,NULL,0); - ws_disconnect_client(&clients[num]); - ret = 1; - } - xSemaphoreGive(xwebsocket_mutex); - } - - return ret; -} - - - -int ws_server_remove_clients(char* url) -{ - int ret = 0; - - if (xSemaphoreTake(xwebsocket_mutex, 10) == pdTRUE) { - for(int i=0;i<WEBSOCKET_SERVER_MAX_CLIENTS;i++) { - if(ws_is_connected(clients[i]) && !strcmp(url,clients[i].url)) { - clients[i].scallback(i,WEBSOCKET_DISCONNECT_INTERNAL,NULL,0); - ws_disconnect_client(&clients[i]); - ret += 1; - } - } - xSemaphoreGive(xwebsocket_mutex); - } - - return ret; -} - -int ws_server_remove_all() -{ - int ret = 0; - - if (xSemaphoreTake(xwebsocket_mutex, 10) == pdTRUE) { - for(int i=0;i<WEBSOCKET_SERVER_MAX_CLIENTS;i++) { - if(ws_is_connected(clients[i])) { - clients[i].scallback(i,WEBSOCKET_DISCONNECT_INTERNAL,NULL,0); - ws_disconnect_client(&clients[i]); - ret += 1; - } - } - xSemaphoreGive(xwebsocket_mutex); - } - - return ret; -} - -// The following functions are already written below, but without the mutex. - -int ws_server_send_text_client(int num,char* msg,uint64_t len) -{ - int ret = 0; - - if (xSemaphoreTake(xwebsocket_mutex, 10) == pdTRUE) { - ret = ws_server_send_text_client_from_callback(num, msg, len); - xSemaphoreGive(xwebsocket_mutex); - } - - return ret; -} - - - -int ws_server_send_text_clients(char* url,char* msg,uint64_t len) -{ - int ret = 0; - - if (xSemaphoreTake(xwebsocket_mutex, 10) == pdTRUE) { - ret = ws_server_send_text_clients_from_callback(url, msg, len); - xSemaphoreGive(xwebsocket_mutex); - } - - return ret; -} - - - -int ws_server_send_text_all(char* msg,uint64_t len) -{ - int ret = 0; - - if (xSemaphoreTake(xwebsocket_mutex, 10) == pdTRUE) { - ret = ws_server_send_text_all_from_callback(msg, len); - xSemaphoreGive(xwebsocket_mutex); - } - - return ret; -} - - - -// the following functions should be used inside of the callback. The regular versions -// grab the mutex, but it is already grabbed from inside the callback so it will hang. - -int ws_server_send_text_client_from_callback(int num,char* msg,uint64_t len) -{ - int ret = 0; - - if (ws_is_connected(clients[num])) { - if ((ws_send(&clients[num],WEBSOCKET_OPCODE_TEXT,msg,len,0) == ERR_OK)) { - ret = 1; - } else { - clients[num].scallback(num,WEBSOCKET_DISCONNECT_ERROR,NULL,0); - ws_disconnect_client(&clients[num]); - ret = 0; - } - } - return ret; -} - - - -int ws_server_send_text_clients_from_callback(char* url,char* msg,uint64_t len) -{ - int ret = 0; - - for (int i=0;i<WEBSOCKET_SERVER_MAX_CLIENTS;i++) { - if (ws_is_connected(clients[i]) && !strcmp(clients[i].url,url)) { - if ((ws_send(&clients[i],WEBSOCKET_OPCODE_TEXT,msg,len,0) == ERR_OK)) { - ret++; - } else { - clients[i].scallback(i,WEBSOCKET_DISCONNECT_ERROR,NULL,0); - ws_disconnect_client(&clients[i]); - } - } - } - return ret; -} - - - -int ws_server_send_text_all_from_callback(char* msg,uint64_t len) -{ - int ret = 0; - - for (int i=0;i<WEBSOCKET_SERVER_MAX_CLIENTS;i++) { - if (ws_is_connected(clients[i])) { - if ((ws_send(&clients[i],WEBSOCKET_OPCODE_TEXT,msg,len,0) == ERR_OK)) { - ret++; - } else { - clients[i].scallback(i,WEBSOCKET_DISCONNECT_ERROR,NULL,0); - ws_disconnect_client(&clients[i]); - } - } - } - return ret; -} - - - -int ws_server_send_bin_client_from_callback(int num,char* msg,uint64_t len) -{ - int ret = 0; - - if (ws_is_connected(clients[num])) { - if ((ws_send(&clients[num],WEBSOCKET_OPCODE_BIN,msg,len,0) == ERR_OK)) { - ret = 1; - } else { - clients[num].scallback(num,WEBSOCKET_DISCONNECT_ERROR,NULL,0); - ws_disconnect_client(&clients[num]); - ret = 0; - } - } - - return ret; -} - - - -int ws_server_send_bin_client(int num,char* msg,uint64_t len) -{ - int ret = 0; - - if (xSemaphoreTake(xwebsocket_mutex, 10) == pdTRUE) { - ret = ws_server_send_bin_client_from_callback(num, msg, len); - xSemaphoreGive(xwebsocket_mutex); - } - return ret; -} - - - -int ws_server_ping(int num) -{ - int ret = 0; - - if (xSemaphoreTake(xwebsocket_mutex, 10) == pdTRUE) { - if (ws_is_connected(clients[num])) { - if ((ws_send(&clients[num], WEBSOCKET_OPCODE_PING, (char *)"BrewPing", 8, 0) == ERR_OK)) { - ret = 1; - } else { - clients[num].scallback(num, WEBSOCKET_DISCONNECT_ERROR, NULL, 0); - ws_disconnect_client(&clients[num]); - ret = 0; - } - } - xSemaphoreGive(xwebsocket_mutex); - } - - return ret; -} -
--- a/image/version.txt Mon Jul 01 08:38:57 2024 +0200 +++ b/image/version.txt Wed Jul 03 20:01:31 2024 +0200 @@ -1,1 +1,1 @@ -0.3.18 +0.3.19
--- a/image/w/webui.html Mon Jul 01 08:38:57 2024 +0200 +++ b/image/w/webui.html Wed Jul 03 20:01:31 2024 +0200 @@ -110,10 +110,10 @@ <div id="row_info"> <div style="color: yellow; text-align: center;"> - <p />Written by Michiel Broek © 2018-2021 + <p />Written by Michiel Broek © 2018-2024 </div> <div style="color: orange; text-align: center;"> - <p />Parts are written by Chris Morgan, Brett Beauregard, Blake Felt, LoBo and David Antliff. + <p />Parts are written by Chris Morgan, Brett Beauregard, LoBo and David Antliff. </div> <div> <button type="button" class="okbutton" id="ok_info">Ok</button>
--- a/main/automation.c Mon Jul 01 08:38:57 2024 +0200 +++ b/main/automation.c Wed Jul 03 20:01:31 2024 +0200 @@ -243,7 +243,7 @@ } MashState = Sub_Screen = MASH_NONE; snprintf(msg, 63, "{\"main\":\"%d\",\"sub\":\"%d\",\"timer\":\"\"}", Main_Screen, Sub_Screen); - ws_server_send_text_clients((char *)"/ws", msg, strlen(msg)); + ws_server_send_text_clients(msg); pumpTime = 0; pumpRest = false; runtime.StageResume = Main_Screen; @@ -454,7 +454,7 @@ read_recipe(config.RecipeRec); snprintf(msg, 255, "{\"main\":\"%d\",\"sub\":\"%d\",\"timer\":\"\",\"brew1\":\"%s\",\"brew2\":\"%s\"}", Main_Screen, Sub_Screen, equipment.Name, recipe.Name); - ws_server_send_text_clients((char *)"/ws", msg, strlen(msg)); + ws_server_send_text_clients(msg); y = 28; TopMessage((char *)"Automaat"); TFT_setFont(DEFAULT_FONT, NULL); @@ -674,7 +674,7 @@ } MashState = Sub_Screen = MASH_WAITTEMP; snprintf(msg, 255, "{\"main\":\"%d\",\"sub\":\"%d\",\"timer\":\"\"}", Main_Screen, Sub_Screen); - ws_server_send_text_clients((char *)"/ws", msg, strlen(msg)); + ws_server_send_text_clients(msg); log_msg(TAG, "Mash step %d type: %s time: %d sp: %6.4f-%6.4f sv: %6.4f", runtime.MashStep, mashTypes[recipe.MashStep[runtime.MashStep].Type], stageTime, recipe.MashStep[runtime.MashStep].Step_temp, recipe.MashStep[runtime.MashStep].End_temp, temp_MLT); @@ -720,7 +720,7 @@ TFT_print(temp_buf, CENTER, 135); SoundPlay(SOUND_Prompt); snprintf(msg, 255, "{\"main\":\"%d\",\"sub\":\"%d\",\"timer\":\"%s\"}", Main_Screen, Sub_Screen, temp_buf); - ws_server_send_text_clients((char *)"/ws", msg, strlen(msg)); + ws_server_send_text_clients(msg); if (recipe.MashStep[runtime.MashStep].Type == MASHTYPE_INFUSION) { if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { // No heating during the infusion. @@ -746,7 +746,7 @@ newTemp = stageTemp; MashState = Sub_Screen = MASH_REST; snprintf(msg, 63, "{\"main\":\"%d\",\"sub\":\"%d\",\"timer\":\"\"}", Main_Screen, Sub_Screen); - ws_server_send_text_clients((char *)"/ws", msg, strlen(msg)); + ws_server_send_text_clients(msg); if (! runtime.MaltAdded && runtime.MashStep == 0) { TimerSet(0); } else { @@ -839,7 +839,7 @@ updateRuntime = true; TFT_fillRect(0, 120, 320, 50, TFT_BLACK); snprintf(msg, 63, "{\"main\":\"%d\",\"sub\":\"%d\",\"timer\":\"\"}", Main_Screen, Sub_Screen); - ws_server_send_text_clients((char *)"/ws", msg, strlen(msg)); + ws_server_send_text_clients(msg); if (runtime.MashStep == 0 && ! runtime.MaltAdded && config.AskAdd) { /* @@ -863,7 +863,7 @@ SoundPlay(SOUND_Prompt); MashState = Sub_Screen = MASH_ADD; snprintf(msg, 63, "{\"main\":\"%d\",\"sub\":\"%d\",\"timer\":\"Mout storten?\"}", Main_Screen, Sub_Screen); - ws_server_send_text_clients((char *)"/ws", msg, strlen(msg)); + ws_server_send_text_clients(msg); log_msg(TAG, "Mash add prompt"); break; } @@ -886,7 +886,7 @@ TimerSet(config.IodineTime * 60); MashState = Sub_Screen = MASH_IODINE; snprintf(msg, 63, "{\"main\":\"%d\",\"sub\":\"%d\",\"timer\":\"Jodium test?\"}", Main_Screen, Sub_Screen); - ws_server_send_text_clients((char *)"/ws", msg, strlen(msg)); + ws_server_send_text_clients(msg); log_msg(TAG, "Mash iodine test prompt"); break; } @@ -907,7 +907,7 @@ SoundPlay(SOUND_Prompt); MashState = Sub_Screen = MASH_REMOVE; snprintf(msg, 127, "{\"main\":\"%d\",\"sub\":\"%d\",\"timer\":\"Mout verwijderen?\"}", Main_Screen, Sub_Screen); - ws_server_send_text_clients((char *)"/ws", msg, strlen(msg)); + ws_server_send_text_clients(msg); log_msg(TAG, "Mash remove prompt"); break; } @@ -1008,7 +1008,7 @@ } MashState = Sub_Screen = MASH_WAITTEMP; snprintf(msg, 63, "{\"main\":\"%d\",\"sub\":\"%d\",\"timer\":\"\"}", Main_Screen, Sub_Screen); - ws_server_send_text_clients((char *)"/ws", msg, strlen(msg)); + ws_server_send_text_clients(msg); break; default: break; } @@ -1179,7 +1179,7 @@ stageTemp = recipe.CoolTemp; } snprintf(msg, 63, "{\"main\":\"%d\",\"sub\":\"%d\",\"timer\":\"\"}", Main_Screen, Sub_Screen); - ws_server_send_text_clients((char *)"/ws", msg, strlen(msg)); + ws_server_send_text_clients(msg); CoolBeep = false; log_msg(TAG, "Start cooling from %6.2f to %4.1f", ds18b20_state->mlt_temperature, stageTemp); if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) { @@ -1232,7 +1232,7 @@ xSemaphoreGive(xSemaphoreDriver); if (Sub_Screen == 2) { snprintf(msg, 63, "{\"main\":\"%d\",\"sub\":\"%d\",\"timer\":\"\"}", Main_Screen, Sub_Screen); - ws_server_send_text_clients((char *)"/ws", msg, strlen(msg)); + ws_server_send_text_clients(msg); } } switch (Buttons_Scan()) { @@ -1345,7 +1345,7 @@ TFT_fillScreen(_bg); Sub_Screen = 1; snprintf(msg, 63, "{\"main\":\"%d\",\"sub\":\"%d\",\"timer\":\"\"}", Main_Screen, Sub_Screen); - ws_server_send_text_clients((char *)"/ws", msg, strlen(msg)); + ws_server_send_text_clients(msg); if (Main_Screen == MAIN_AUTO_WHIRLPOOL9) { TimeWhirlPool = recipe.Whirlpool9; if (xSemaphoreTake(xSemaphoreDriver, 10) == pdTRUE) {
--- a/main/brewboard.c Mon Jul 01 08:38:57 2024 +0200 +++ b/main/brewboard.c Wed Jul 03 20:01:31 2024 +0200 @@ -168,7 +168,7 @@ TFT_print((char *)" Ok\r\n", LASTX, LASTY); SoundPlay(SOUND_StartUp); - start_http_websocket(); + install_http_server(); vTaskDelay(1000 / portTICK_PERIOD_MS); Main_Screen = MAIN_MODE_FREE;
--- a/main/config.h Mon Jul 01 08:38:57 2024 +0200 +++ b/main/config.h Wed Jul 03 20:01:31 2024 +0200 @@ -36,6 +36,7 @@ #include "esp_netif.h" #include "esp_mac.h" #include "esp_wifi.h" +#include "esp_http_server.h" #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 2, 0) #include "esp_wpa2.h" @@ -62,7 +63,6 @@ #include "tftspi.h" #include "tft.h" #include "PID_v1.h" -#include "websocket_server.h" #include "buttons.h" #include "calibration.h"
--- a/main/task_ds18b20.c Mon Jul 01 08:38:57 2024 +0200 +++ b/main/task_ds18b20.c Wed Jul 03 20:01:31 2024 +0200 @@ -27,6 +27,7 @@ float Fake_MLT = 18.90; float Fake_HLT = 18.70; extern double Output; +extern my_equipment_t equipment; #endif static const char *TAG = "task_ds18b20";
--- a/main/task_http.c Mon Jul 01 08:38:57 2024 +0200 +++ b/main/task_http.c Wed Jul 03 20:01:31 2024 +0200 @@ -1,23 +1,21 @@ /** * @file task_http.c * @brief HTTP and Websocket server functions. - * This uses some ESP32 Websocket code written by Blake Felt - blake.w.felt@gmail.com */ #include "config.h" +#include "sys/param.h" #include "mbedtls/base64.h" #include "mbedtls/sha1.h" #include "cJSON.h" static const char *TAG = "task_http"; -static QueueHandle_t client_queue; -const static int client_queue_size = 10; -static TaskHandle_t xTaskHTTP = NULL; -static TaskHandle_t xTaskQueue = NULL; +httpd_handle_t web_server = NULL; ///< The http server handle. cJSON *root = NULL; cJSON *touch = NULL; + typedef struct _ls_list { struct _ls_list *next; char d_name[64]; @@ -26,7 +24,9 @@ } ls_list; - +/** + * @brief Clear the linked list and release memory. + */ void tidy_lslist(ls_list **lap) { ls_list *tmp, *old; @@ -39,7 +39,13 @@ } - +/** + * @brief Add a file entry to the linked list. + * @param lap Pointer to the linked list. + * @param name The entry filename. + * @param size The entry filesize. + * @param mtime The entry filedate. + */ void fill_list(ls_list **lap, char *name, off_t size, long mtime) { ls_list **tmp; @@ -55,7 +61,11 @@ } - +/** + * @brief Compare for sorting files by datetime, reversed. + * @param lsp1 Entry 1 + * @param lsp2 Entry 2 + */ int comp(ls_list **lsp1, ls_list **lsp2) { char as[20], bs[20]; @@ -67,7 +77,9 @@ } - +/** + * @brief Sort the linked list. + */ void sort_list(ls_list **lap) { ls_list *ta, **vector; @@ -101,74 +113,199 @@ } +#define CHECK_FILE_EXTENSION(filename, ext) (strcasecmp(&filename[strlen(filename) - strlen(ext)], ext) == 0) /** - * @brief Debug dump buffer - * @param buf The buffer - * @param buflen Length of the buffer + * @brief Set HTTP response content type according to file extension + * @param req The request where the content type will be set. + * @param filepath The filename to get the extension from. + * @return ESP_OK if success. */ -#if 0 -void dump_buf(char *buf, uint16_t buflen); -#endif +static esp_err_t set_content_type_from_file(httpd_req_t *req, const char *filepath) +{ + const char *type = "text/plain"; + if (CHECK_FILE_EXTENSION(filepath, ".html")) { + type = "text/html"; + } else if (CHECK_FILE_EXTENSION(filepath, ".log")) { + type = "text/plain"; + } else if (CHECK_FILE_EXTENSION(filepath, ".js")) { + type = "application/javascript"; + } else if (CHECK_FILE_EXTENSION(filepath, ".json")) { + type = "text/json"; + } else if (CHECK_FILE_EXTENSION(filepath, ".gz")) { + type = "application/x-gzip"; + } else if (CHECK_FILE_EXTENSION(filepath, ".css")) { + type = "text/css"; + } else if (CHECK_FILE_EXTENSION(filepath, ".png")) { + type = "image/png"; + } else if (CHECK_FILE_EXTENSION(filepath, ".ico")) { + type = "image/x-icon"; + } else if (CHECK_FILE_EXTENSION(filepath, ".svg")) { + type = "text/xml"; + } else if (CHECK_FILE_EXTENSION(filepath, ".ttf")) { + type = "font/ttf"; + } else if (CHECK_FILE_EXTENSION(filepath, ".woff")) { + type = "font/woff"; + } else if (CHECK_FILE_EXTENSION(filepath, ".woff2")) { + type = "font/woff2"; + } else if (CHECK_FILE_EXTENSION(filepath, ".xml")) { + type = "text/xml"; + } + return httpd_resp_set_type(req, type); +} /** - * @brief Send HTTP error and message - * @param conn The socket to send to. - * @param error The HTTP error code. - * @param message Yhe human readable explanation. - * @paeam body The human readable explanation. + * @brief Send text message to a single websocket client. + * @param num The client socket to send to. + * @param msg The message to send. + * @return ESP_OK. */ -static void http_error(struct netconn *conn, int error, char *message, char *body) +int ws_server_send_text_client(int num, char* msg) { - char *tmp = malloc(200); + httpd_ws_frame_t ws_pkt; + + memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t)); + ws_pkt.type = HTTPD_WS_TYPE_TEXT; + ws_pkt.len = strlen(msg); + ws_pkt.payload = (uint8_t*)msg; + httpd_ws_send_frame_async(web_server, num, &ws_pkt); + ESP_LOGD(TAG, "ws_server_send_text_client(%d, %s)", num, msg); + return ESP_OK; +} + - ESP_LOGI(TAG, "Http response %d - %s", error, message); - if (strlen(body)) { - snprintf(tmp, 199, "HTTP/1.1 %d %s\r\nContent-type: text/plain\r\n\r\n%s\n", error, message, body); - } else { - snprintf(tmp, 199, "HTTP/1.1 %d %s\r\n\r\n", error, message); +/** + * @brief Broadcast text message to all connected websocket clients. + * @param msg The text message. + * @return -1 if error, else the number of clients. + */ +int ws_server_send_text_clients(char* msg) +{ + httpd_ws_frame_t ws_pkt; + static size_t max_clients = CONFIG_LWIP_MAX_LISTENING_TCP; + size_t fds = max_clients; + int client_fds[CONFIG_LWIP_MAX_LISTENING_TCP] = {0}; + int count = 0; + + memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t)); + ws_pkt.type = HTTPD_WS_TYPE_TEXT; + ws_pkt.len = strlen(msg); + ws_pkt.payload = (uint8_t*)msg; + + esp_err_t ret = httpd_get_client_list(web_server, &fds, client_fds); + if (ret != ESP_OK) { + return -1; } - netconn_write(conn, tmp, strlen(tmp), NETCONN_NOCOPY); - free(tmp); + + for (int i = 0; i < fds; i++) { + httpd_ws_client_info_t client_info = httpd_ws_get_fd_info(web_server, client_fds[i]); + if (client_info == HTTPD_WS_CLIENT_WEBSOCKET) { + httpd_ws_send_frame_async(web_server, client_fds[i], &ws_pkt); + count++; + } + } + return count; } +/** + * @brief Websocket handler. + * @param req The request structure. + * @return ESP_OK or error. + */ +static esp_err_t ws_handler(httpd_req_t *req) +{ + if (req->method == HTTP_GET) { + ESP_LOGI(TAG, "New websocket connection %d", httpd_req_to_sockfd(req)); + /* Send initial screen */ + TFTstartWS(httpd_req_to_sockfd(req)); + return ESP_OK; + } + + httpd_ws_frame_t ws_pkt; + uint8_t *buf = NULL; + char jbuf[128]; + cJSON *root = NULL, *touch = NULL; + + memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t)); + ws_pkt.type = HTTPD_WS_TYPE_TEXT; + /* Set max_len = 0 to get the frame len */ + esp_err_t ret = httpd_ws_recv_frame(req, &ws_pkt, 0); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "ws_handler() httpd_ws_recv_frame failed to get frame len with %d", ret); + return ret; + } + ESP_LOGD(TAG, "frame len is %d type is %d socket %d", ws_pkt.len, ws_pkt.type, httpd_req_to_sockfd(req)); + + if (ws_pkt.len) { + /* ws_pkt.len + 1 is for NULL termination as we are expecting a string */ + buf = calloc(1, ws_pkt.len + 1); + if (buf == NULL) { + ESP_LOGE(TAG, "ws_handler() no memory for buf"); + return ESP_ERR_NO_MEM; + } + ws_pkt.payload = buf; + /* Set max_len = ws_pkt.len to get the frame payload */ + ret = httpd_ws_recv_frame(req, &ws_pkt, ws_pkt.len); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "ws_handler() httpd_ws_recv_frame failed with %d", ret); + free(buf); + return ret; + } + ESP_LOGI(TAG, "ws_handler() Got message: %s", ws_pkt.payload); + } + + if ((ws_pkt.type == HTTPD_WS_TYPE_TEXT) && (ws_pkt.len > 0) && (ws_pkt.len < 128)) { + memcpy(jbuf, ws_pkt.payload, ws_pkt.len); + jbuf[ws_pkt.len] = '\0'; + if ((root = cJSON_Parse(jbuf))) { + if ((touch = cJSON_GetObjectItem(root,"touch"))) { + int x = cJSON_GetObjectItem(touch, "x")->valueint; + int y = cJSON_GetObjectItem(touch, "y")->valueint; + WS_touched(x, y); + } else { + ESP_LOGI(TAG,"not json touch"); + } + cJSON_Delete(root); + } else { + ESP_LOGI(TAG,"not json"); + } + } + + free(buf); + return ESP_OK; +} + /** - * @brief Send HTTP file from the filesystem. - * @param conn The network connection. - * @param url The requested url. - * @param mod_since The date/time of the file, or NULL. - * @param ipstr The IP address of the remote. + * @brief Handle files download from /spiffs or /sdcard. */ -static void http_sendfile(struct netconn *conn, char *url, char *mod_since, char *ipstr) +static esp_err_t files_handler(httpd_req_t *req) { - char temp_url[128], temp_url_gz[132], header[256], c_type[32]; + char temp_url[560], temp_url_gz[564]; struct stat st; off_t filesize; - size_t sentsize; char strftime_buf[64]; - err_t err; bool send_gz; - FILE *f; + int fd = -1; - if (url[strlen(url) - 1] == '/') { - // If no filename given, use index.html - sprintf(temp_url, "/spiffs/w%sindex.html", url); - } else if (strncmp(url, "/log/", 5) == 0) { - // Logfiles are on the SD card. - sprintf(temp_url, "/sdcard/w%s", url); + if (req->uri[strlen(req->uri) - 1] == '/') { + // If no filename given, use index.html + sprintf(temp_url, "/spiffs/w%sindex.html", req->uri); + } else if (strncmp(req->uri, "/log/", 5) == 0) { + // Logfiles are on the SD card. + sprintf(temp_url, "/sdcard/w%s", req->uri); } else { - sprintf(temp_url, "/spiffs/w%s", url); - for (int i = 0; i < 127; i++) { - if (temp_url[i] == '?') - temp_url[i] = '\0'; - if (temp_url[i] == '\0') - break; - } + sprintf(temp_url, "/spiffs/w%s", req->uri); + for (int i = 0; i < 127; i++) { + if (temp_url[i] == '?') + temp_url[i] = '\0'; + if (temp_url[i] == '\0') + break; + } } - snprintf(temp_url_gz, 131, "%s.gz", temp_url); + snprintf(temp_url_gz, 563, "%s.gz", temp_url); /* * Get filesize and date for the response headers. @@ -180,6 +317,7 @@ strftime_buf[0] = '\0'; send_gz = false; if (stat(temp_url_gz, &st) == 0) { + /* The requested file is gzipped. */ filesize = st.st_size; strftime(strftime_buf, sizeof(strftime_buf), "%a, %d %b %Y %T %z", localtime(&(st.st_mtime))); send_gz = true; @@ -187,428 +325,201 @@ filesize = st.st_size; strftime(strftime_buf, sizeof(strftime_buf), "%a, %d %b %Y %T %z", localtime(&(st.st_mtime))); } - - /* - * If we have a If-Modified-Since parameter, compare that with the current - * filedate on disk. If It's the same send a 304 response. - * Cannot work on /spiffs. - * Update 18-05-2021 it seems better on idf 4.2.1 but still not correct. - */ -#if 0 - time_t Now; - struct tm timeInfo; - if (mod_since && strlen(strftime_buf)) { - time(&Now); - localtime_r(&Now, &timeInfo); - strftime(strftime_buf, sizeof(strftime_buf), "%a, %d %b %Y %T %z", &timeInfo); - sprintf(header, "HTTP/1.1 304 Not Modified\r\nDate: %s\r\n\r\n", strftime_buf); - netconn_write(conn, header, strlen(header), NETCONN_NOCOPY); - ESP_LOGI(TAG, "%s sendfile %s Not Modified, ok", ipstr, temp_url); - return; - } -#endif + ESP_LOGI(TAG, "GET `%s` file %s date %s size %d", req->uri, (send_gz) ? temp_url_gz : temp_url, strftime_buf, (int)filesize); if (send_gz) { - f = fopen(temp_url_gz, "r"); + fd = open(temp_url_gz, O_RDONLY, 0); } else { - f = fopen(temp_url, "r"); + fd = open(temp_url, O_RDONLY, 0); } - if (f == NULL) { - ESP_LOGI(TAG, "%s url \'%s\' file \'%s\' not found", ipstr, url, temp_url); - http_error(conn, 404, (char *)"Not found", (char *)"Not found"); - return; + if (fd == -1) { + ESP_LOGI(TAG, "files_handler() file not found"); + httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "File does not exist"); + return ESP_FAIL; + } + + set_content_type_from_file(req, temp_url); + if (send_gz) { + httpd_resp_set_hdr(req, "Content-Encoding", "gzip"); } - if (strcmp(".html", &temp_url[strlen(temp_url) - 5]) == 0) { - sprintf(c_type, "text/html"); - } else if (strcmp(".css", &temp_url[strlen(temp_url) - 4]) == 0) { - sprintf(c_type, "text/css"); - } else if (strcmp(".log", &temp_url[strlen(temp_url) - 4]) == 0) { - sprintf(c_type, "text/plain"); - } else if (strcmp(".js", &temp_url[strlen(temp_url) - 3]) == 0) { - sprintf(c_type, "text/javascript"); - } else if (strcmp(".json", &temp_url[strlen(temp_url) - 5]) == 0) { - sprintf(c_type, "text/json"); - } else if (strcmp(".gz", &temp_url[strlen(temp_url) - 3]) == 0) { - sprintf(c_type, "application/x-gzip"); - } else if (strcmp(".png", &temp_url[strlen(temp_url) - 4]) == 0) { - sprintf(c_type, "image/png"); - } else if (strcmp(".svg", &temp_url[strlen(temp_url) - 4]) == 0) { - sprintf(c_type, "image/svg+xml"); - } else if (strcmp(".oga", &temp_url[strlen(temp_url) - 4]) == 0) { - sprintf(c_type, "audio/ogg"); - } else if (strcmp(".ico", &temp_url[strlen(temp_url) - 4]) == 0) { - sprintf(c_type, "image/x-icon"); - } else if (strcmp(".ttf", &temp_url[strlen(temp_url) - 4]) == 0) { - sprintf(c_type, "font/ttf"); - } else if (strcmp(".woff", &temp_url[strlen(temp_url) - 5]) == 0) { - sprintf(c_type, "font/woff"); - } else if (strcmp(".woff2", &temp_url[strlen(temp_url) - 6]) == 0) { - sprintf(c_type, "font/woff2"); - } else if (strcmp(".xml", &temp_url[strlen(temp_url) - 4]) == 0) { - sprintf(c_type, "text/xml"); - } else { - sprintf(c_type, "application/octet-stream"); - printf("Unknown content type for %s\n", temp_url); - } + /* + * Now the real file send in chunks. + */ + char *chunk = malloc(1024); + ssize_t read_bytes; + do { + read_bytes = read(fd, chunk, 1024); + if (read_bytes == -1) { + ESP_LOGE(TAG, "files_handler() Failed to read file : %s", (send_gz) ? temp_url_gz : temp_url); + } else if (read_bytes > 0) { + /* Send the buffer contents as HTTP response chunk */ + if (httpd_resp_send_chunk(req, chunk, read_bytes) != ESP_OK) { + close(fd); + ESP_LOGE(TAG, "files_handler() File sending failed!"); + /* Abort sending file */ + httpd_resp_sendstr_chunk(req, NULL); + /* Respond with 500 Internal Server Error */ + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to send file"); + return ESP_FAIL; + } + } + } while (read_bytes > 0); + /* Close file after sending complete */ + close(fd); - vTaskDelay(2 / portTICK_PERIOD_MS); - // httpdHeader(connData, "Cache-Control", "max-age=3600, must-revalidate"); - if (filesize) { - sprintf(header, "HTTP/1.1 200 OK\r\nLast-Modified: %s\r\nContent-Length: %ld\r\nContent-type: %s\r\n", strftime_buf, filesize, c_type); - } else { - sprintf(header, "HTTP/1.1 200 OK\r\nContent-type: %s\r\n", c_type); - } - if (send_gz) { - strncat(header, "Content-Encoding: gzip\r\n", 255 - strlen(header)); - } - strncat(header, "\r\n", 255 - strlen(header)); // Add last empty line. - err = netconn_write(conn, header, strlen(header), NETCONN_NOCOPY); - if (err != ERR_OK) { - ESP_LOGW(TAG, "%s sendfile %s%s err=%d on header write", ipstr, temp_url, (send_gz) ? ".gz":"", err); - fclose(f); - return; - } - // if (strstr(acceptEncodingBuffer, "gzip") == NULL) - // http_error(conn, 501, "Not implemented", "Your browser does not accept gzip-compressed data."); - - sentsize = 0; - uint8_t *buff = malloc(1024); - size_t bytes; - int pause = 0; - - for (;;) { - bytes = fread(buff, 1, 1024, f); - if (bytes == 0) - break; - - err = netconn_write(conn, buff, bytes, NETCONN_NOCOPY); - if (err != ERR_OK) { - ESP_LOGW(TAG, "%s sendfile %s%s err=%d send %u bytes of %ld bytes", ipstr, temp_url, (send_gz) ? ".gz":"", err, sentsize, filesize); - break; - } - vTaskDelay(2 / portTICK_PERIOD_MS); - sentsize += bytes; - pause++; - if (pause > 50) { // 50 K - pause = 0; - vTaskDelay(50 / portTICK_PERIOD_MS); - } - } - fclose(f); - free(buff); - - if (sentsize == filesize) { - ESP_LOGI(TAG, "%s sendfile %s%s sent %u bytes, ok (%s)", ipstr, temp_url, (send_gz) ? ".gz":"", sentsize, url); - } + /* Respond with an empty chunk to signal HTTP response completion */ + httpd_resp_send_chunk(req, NULL, 0); + return ESP_OK; } - /** - * @brief Handle web ui websocket events. + * @brief Handle 'GET /logfiles.json' */ -void websock_callback(uint8_t num, WEBSOCKET_TYPE_t type, char* msg, uint64_t len) +static esp_err_t logfiles_handler(httpd_req_t *req) { - char jbuf[128]; + ls_list *lsx = NULL, *tmp; + char temp[64]; + FILE *dest = fopen("/spiffs/w/logfiles.json", "w"); + + if (dest) { + DIR *dir = opendir("/sdcard/w/log"); + if (dir) { + struct dirent* de = readdir(dir); + struct stat st; + while (de) { + snprintf(temp, 63, "/sdcard/w/log/"); + strncat(temp, de->d_name, 63 - strlen(temp)); + if (stat(temp, &st) == ESP_OK) { + fill_list(&lsx, de->d_name, st.st_size, st.st_mtime); + } + de = readdir(dir); + vTaskDelay(5 / portTICK_PERIOD_MS); + } + closedir(dir); + } else { + ESP_LOGE(TAG, "Error %d open directory /sdcard/w/log", errno); + } - switch(type) { - case WEBSOCKET_CONNECT: - case WEBSOCKET_DISCONNECT_EXTERNAL: - break; + sort_list(&lsx); + bool comma = false; + fprintf(dest, "{\"Dir\":[{\"Folder\":\"/log\",\"Files\":["); + for (tmp = lsx; tmp; tmp = tmp->next) { + fprintf(dest, "%s{\"File\":\"%s\",\"Size\":%ld,\"Date\":%ld}", (comma)?",":"", tmp->d_name, tmp->size, tmp->mtime); + comma = true; + } + fprintf(dest, "]}]}"); + fclose(dest); + tidy_lslist(&lsx); + } else { + ESP_LOGE(TAG, "Error %d write /spiffs/w/logfiles.json", errno); + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to create file"); + return ESP_FAIL; + } - case WEBSOCKET_DISCONNECT_INTERNAL: - ESP_LOGI(TAG,"Websocket client %i was disconnected",num); - break; + /* + * File is ready, send it using the files_handler(). + */ + esp_err_t ret = files_handler(req); + unlink("/spiffs/w/logfiles.json"); + return ret; +} - case WEBSOCKET_DISCONNECT_ERROR: - ESP_LOGI(TAG,"Websocket client %i was disconnected due to an error",num); - break; + +esp_err_t start_webserver(void) +{ + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); + config.lru_purge_enable = true; + config.uri_match_fn = httpd_uri_match_wildcard; - case WEBSOCKET_TEXT: - /* - * Handle json actions from the web clients, like button presses. - */ - if (len < 128) { // Safety, messages are small. - memcpy(jbuf, msg, len); - jbuf[len] = '\0'; - if ((root = cJSON_Parse(jbuf))) { - if ((touch = cJSON_GetObjectItem(root,"touch"))) { - int x = cJSON_GetObjectItem(touch, "x")->valueint; - int y = cJSON_GetObjectItem(touch, "y")->valueint; - WS_touched(x, y); - break; - } else { - ESP_LOGI(TAG,"not json touch"); - } - cJSON_Delete(root); - } else { - ESP_LOGI(TAG,"not json"); - } - } - // Log if the message in not processed. - ESP_LOGI(TAG,"Websocket client %i sent text message of size %i:\n%s",num,(unsigned)len,msg); - break; + // Start the httpd server + if (web_server == NULL) { + ESP_LOGI(TAG, "Starting httpd on port: '%d'", config.server_port); + if (httpd_start(&web_server, &config) == ESP_OK) { + // Set URI handlers + ESP_LOGD(TAG, "Registering URI handlers"); + + httpd_uri_t ws = { + .uri = "/ws", + .method = HTTP_GET, + .handler = ws_handler, + .user_ctx = NULL, + .is_websocket = true + }; + httpd_register_uri_handler(web_server, &ws); + + httpd_uri_t logfiles = { + .uri = "/logfiles.json", + .method = HTTP_GET, + .handler = logfiles_handler, + .user_ctx = NULL + }; + httpd_register_uri_handler(web_server, &logfiles); - case WEBSOCKET_BIN: - ESP_LOGI(TAG,"Websocket client %i sent bin message of size %i:\n",num,(unsigned)len); - break; + httpd_uri_t vfs_files = { + .uri = "/*", + .method = HTTP_GET, + .handler = files_handler, + .user_ctx = NULL + }; + httpd_register_uri_handler(web_server, &vfs_files); + return ESP_OK; + } + } else { + ESP_LOGI(TAG, "httpd server already started"); + return ESP_OK; + } + + ESP_LOGI(TAG, "Error starting server!"); + return ESP_FAIL; +} + - case WEBSOCKET_PING: - ESP_LOGI(TAG,"client %i pinged us with message of size %i:\n%s",num,(unsigned)len,msg); - break; +static esp_err_t stop_webserver(void) +{ + esp_err_t ret; + + ESP_LOGI(TAG, "Stopping httpd server"); + ret = httpd_stop(web_server); + web_server = NULL; + return ret; +} - case WEBSOCKET_PONG: - ESP_LOGI(TAG,"client %i responded to the ping",num); - break; + +static void disconnect_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) +{ + if (web_server) { + if (stop_webserver() == ESP_OK) { + web_server = NULL; + } else { + ESP_LOGE(TAG, "Failed to stop http server"); + } } } - -#if 0 -void dump_buf(char *buf, uint16_t buflen) +static void connect_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) { - int i = 0, l = 1; - - printf("request length %d\n00: ", buflen); - for (;;) { - if (i >= buflen) - break; - if ((buf[i] < ' ') || (buf[i] > 126)) { - if (buf[i] == '\n') { - printf("\\n\n%02d: ", l); - l++; - } else if (buf[i] == '\r') { - printf("\\r"); - } else { - printf("\\%02x", buf[i]); - } - } else { - printf("%c", buf[i]); - } - i++; - } - printf("\n"); + start_webserver(); } -#endif - /** - * @brief Serve any client. - * @param http_client_sock The socket on which the client is connected. + * @brief Install the HTTP server event handlers. + * The real server is started and stopped on events. */ -static void http_serve(struct netconn *conn) +void install_http_server(void) { - char ipstr[IPADDR_STRLEN_MAX]; - struct netbuf *inbuf; - static char *buf; - static uint16_t buflen; - static err_t err; - char url[128], *p, *mod_since = NULL; - ip_addr_t remote_addr; - uint16_t remote_port; - ls_list *lsx = NULL, *tmp; - - if (netconn_getaddr(conn, &remote_addr, &remote_port, 0) == ERR_OK) { - strcpy(ipstr, ip4addr_ntoa(ip_2_ip4(&remote_addr))); - } else { - ipstr[0] = '\0'; - } - - netconn_set_recvtimeout(conn,1000); // allow a connection timeout of 1 second - err = netconn_recv(conn, &inbuf); - - if (err != ERR_OK) { - if (err != ERR_TIMEOUT) { // Ignore timeout - ESP_LOGW(TAG,"%s error %d on read", ipstr, err); - } - netconn_close(conn); - netconn_delete(conn); - netbuf_delete(inbuf); - return; - } - - netbuf_data(inbuf, (void**)&buf, &buflen); - - if (buf) { - /* - * Build url string from the data buffer. - * It looks like: GET /app/localization.js HTTP/1.1 - */ - for (int i = 0; i < 10; i++) { - if (buf[i] == ' ') { - i++; - for (int j = i; j < 128; j++) { - url[j-i] = buf[j]; - if (url[j-i] == ' ') { - url[j-i] = '\0'; - break; - } - } - break; - } - } + ESP_LOGI(TAG, "Install HTTP server"); - if (strstr(buf, "GET /logfiles.json")) { - char temp[64]; - FILE *dest = fopen("/spiffs/w/logfiles.json", "w"); - if (dest) { - DIR *dir = opendir("/sdcard/w/log"); - if (dir) { - struct dirent* de = readdir(dir); - struct stat st; - while (de) { - snprintf(temp, 63, "/sdcard/w/log/"); - strncat(temp, de->d_name, 63 - strlen(temp)); - if (stat(temp, &st) == ESP_OK) { - fill_list(&lsx, de->d_name, st.st_size, st.st_mtime); - } - de = readdir(dir); - vTaskDelay(5 / portTICK_PERIOD_MS); - } - closedir(dir); - } else { - ESP_LOGE(TAG, "Error %d open directory /sdcard/w/log", errno); - } - - sort_list(&lsx); - bool comma = false; - fprintf(dest, "{\"Dir\":[{\"Folder\":\"/log\",\"Files\":["); - for (tmp = lsx; tmp; tmp = tmp->next) { - fprintf(dest, "%s{\"File\":\"%s\",\"Size\":%ld,\"Date\":%ld}", (comma)?",":"", tmp->d_name, tmp->size, tmp->mtime); - comma = true; - } - fprintf(dest, "]}]}"); - fclose(dest); - tidy_lslist(&lsx); - } else { - ESP_LOGE(TAG, "Error %d write /spiffs/w/logfiles.json", errno); - } - http_sendfile(conn, (char *)"/logfiles.json", NULL, ipstr); - netconn_close(conn); - netconn_delete(conn); - netbuf_delete(inbuf); - unlink("/spiffs/w/logfiles.json"); - return; - } - - // http requests - if (! strstr(buf,"Upgrade: websocket")) { // Not websocket - p = strstr(buf, "If-Modified-Since:"); // May be cached - if (p) { - size_t mod_len = strcspn(p, " "); - p += (int)(mod_len + 1); - mod_len = strcspn(p, "\r\n"); - mod_since = malloc(mod_len + 2); - memcpy(mod_since, p, mod_len); - mod_since[mod_len] = '\0'; - } - http_sendfile(conn, url, mod_since, ipstr); - if (mod_since) - free(mod_since); - mod_since = NULL; - netconn_close(conn); - netconn_delete(conn); - netbuf_delete(inbuf); - return; - } - - // websocket for web UI. - if ((strstr(buf,"GET /ws ") && strstr(buf,"Upgrade: websocket"))) { - int nr = ws_server_add_client_protocol(conn, buf, buflen, (char *)"/ws", (char *)"binary", websock_callback); - ESP_LOGI(TAG, "%s new websocket on /ws client: %d", ipstr, nr); - netbuf_delete(inbuf); - TFTstartWS(nr); - // Startup something? Init webscreen? - return; - } - -#if 0 - dump_buf(buf, buflen); -#endif - - if (strstr(buf, "GET /")) { - ESP_LOGI(TAG, "%s request: %s", ipstr, buf); - http_error(conn, 404, (char *)"Not found", (char *)"Not found"); - } else { - http_error(conn, 405, (char *)"Invalid method", (char *)"Invalid method"); - } - } - - netconn_close(conn); - netconn_delete(conn); - netbuf_delete(inbuf); + /* + * Respond to WiFi and network events. This is much better then letting the + * server run forever. + */ + ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &connect_handler, web_server)); + ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_LOST_IP, &disconnect_handler, web_server)); + ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &disconnect_handler, web_server)); } - -/** - * @brief Handles clients when they first connect. passes to a queue - */ -static void task_HTTPserver(void* pvParameters) -{ - struct netconn *conn, *newconn; - static err_t err; - - ESP_LOGI(TAG, "Start HTTP/web server"); - - conn = netconn_new(NETCONN_TCP); - netconn_bind(conn,NULL,80); - netconn_listen(conn); - - do { - err = netconn_accept(conn, &newconn); - if (err == ERR_OK) { - if (xQueueSendToBack(client_queue,&newconn,portMAX_DELAY) != pdTRUE) { - ESP_LOGW(TAG, "xQueueSendToBack() queue full"); - }; - } - vTaskDelay(5 / portTICK_PERIOD_MS); - } while (err == ERR_OK); - - ESP_LOGE(TAG, "Stopping HTTP/web server"); - netconn_close(conn); - netconn_delete(conn); - vTaskDelete(NULL); -} - - - -/** - * @brief Receives clients from queue and handle them. - */ -static void task_Queue(void* pvParameters) -{ - struct netconn* conn; - - ESP_LOGI(TAG, "Start Queue task"); - for(;;) { - xQueueReceive(client_queue, &(conn), portMAX_DELAY); - if (!conn) - continue; - http_serve(conn); - vTaskDelay(2 / portTICK_PERIOD_MS); - } - ESP_LOGE(TAG, "Stopping Queue task"); - vTaskDelete(NULL); -} - - - -void start_http_websocket(void) -{ - ESP_LOGI(TAG, "Start HTTP/Websocket server"); - - client_queue = xQueueCreate(client_queue_size,sizeof(struct netconn*)); - if (client_queue == 0) { - ESP_LOGE(TAG, "Failed to create client_queue"); - return; - } - - ws_server_start(); - xTaskCreate(&task_HTTPserver, "HTTPserver", 5000, NULL, 9, &xTaskHTTP); - xTaskCreate(&task_Queue, "Queue", 6000, NULL, 6, &xTaskQueue); -} -
--- a/main/task_http.h Mon Jul 01 08:38:57 2024 +0200 +++ b/main/task_http.h Wed Jul 03 20:01:31 2024 +0200 @@ -8,8 +8,26 @@ /** - * @brief Start the HTTP/Websocket server. + * @brief Send text to client with the set number. + * @param num The client connection number. + * @param msg The message to send. + * @param len The length of the message to send. + * @return 1 if message send. */ -void start_http_websocket(void); +int ws_server_send_text_client(int num, char* msg); + + +/** + * @brief Sends text to all clients with the set url. + * @param msg The message to send. + * @return The number of clients the message was send to. + */ +int ws_server_send_text_clients(char* msg); + + +/** + * @brief Install the HTTP/Websocket server. + */ +void install_http_server(void); #endif
--- a/main/task_sound.c Mon Jul 01 08:38:57 2024 +0200 +++ b/main/task_sound.c Wed Jul 03 20:01:31 2024 +0200 @@ -48,7 +48,7 @@ for (int i = 1; i <= sound[0]; i += 2) { if (sound != _sound_Startup) { snprintf(msg, 15, "{\"beep\":\"1\"}"); - ws_server_send_text_clients((char *)"/ws", msg, strlen(msg)); + ws_server_send_text_clients(msg); } gpio_set_level(PIEZO_BUZZER, 1); vTaskDelay(sound[i] / portTICK_PERIOD_MS);
--- a/main/task_tft.c Mon Jul 01 08:38:57 2024 +0200 +++ b/main/task_tft.c Wed Jul 03 20:01:31 2024 +0200 @@ -227,7 +227,7 @@ TFT_print(s_timer, X, Y); _oldTime = Time; snprintf(msg, 31, "{\"timer\":\"%s\"}", s_timer); - ws_server_send_text_clients((char *)"/ws", msg, strlen(msg)); + ws_server_send_text_clients(msg); } } @@ -245,7 +245,7 @@ TFT_print(s_top_msg, CENTER, 2); font_transparent = 0; snprintf(msg, 95, "{\"top_msg\":\"%s\"}", s_top_msg); - ws_server_send_text_clients((char *)"/ws", msg, strlen(msg)); + ws_server_send_text_clients(msg); } @@ -295,7 +295,7 @@ } lon = con; snprintf(msg, 95, "{\"mlt_led\":\"%s\"}", con ? "1":"0"); - ws_server_send_text_clients((char *)"/ws", msg, strlen(msg)); + ws_server_send_text_clients(msg); } cpump = (Pump_pin) ? true : false; @@ -307,7 +307,7 @@ } lpump = cpump; snprintf(msg, 95, "{\"pump_led\":\"%s\"}", cpump ? "1":"0"); - ws_server_send_text_clients((char *)"/ws", msg, strlen(msg)); + ws_server_send_text_clients(msg); } if (equipment.SSR2 == SSR2_ON_IDLE) { @@ -320,7 +320,7 @@ } lpwr = cpwr; snprintf(msg, 95, "{\"hlt_led\":\"%s\"}", cpwr ? "1":"0"); - ws_server_send_text_clients((char *)"/ws", msg, strlen(msg)); + ws_server_send_text_clients(msg); } } @@ -329,7 +329,7 @@ TFT_print(ctemp, x + 5, y + 23); strncpy(ltemp, ctemp, 16); snprintf(msg, 95, "{\"mlt_pv\":\"%s\"}", ctemp); - ws_server_send_text_clients((char *)"/ws", msg, strlen(msg)); + ws_server_send_text_clients(msg); } TFT_setFont(DEJAVU18_FONT, NULL); @@ -338,14 +338,14 @@ TFT_print(csp, x + 5, y + 70); strncpy(lsp, csp, 16); snprintf(msg, 95, "{\"mlt_sp\":\"%s\"}", csp); - ws_server_send_text_clients((char *)"/ws", msg, strlen(msg)); + ws_server_send_text_clients(msg); } if (strcmp(cpower, lpower) || (! update)) { TFT_clearStringRect(x + 120, y + 70, (char *)"100%"); TFT_print(cpower, x + 120, y + 70); strncpy(lpower, cpower, 16); snprintf(msg, 95, "{\"mlt_power\":\"%s\"}", cpower); - ws_server_send_text_clients((char *)"/ws", msg, strlen(msg)); + ws_server_send_text_clients(msg); } } @@ -396,7 +396,7 @@ } lon = con; snprintf(msg, 95, "{\"hlt_led\":\"%s\"}", con ? "1":"0"); - ws_server_send_text_clients((char *)"/ws", msg, strlen(msg)); + ws_server_send_text_clients(msg); } if (strcmp(ltemp, ctemp) || (! update)) { @@ -409,7 +409,7 @@ } strncpy(ltemp, ctemp, 16); snprintf(msg, 95, "{\"hlt_pv\":\"%s\"}", ctemp); - ws_server_send_text_clients((char *)"/ws", msg, strlen(msg)); + ws_server_send_text_clients(msg); } H = (small) ? 50 : 70; @@ -419,14 +419,14 @@ TFT_print(csp, x + 5, y + H); strncpy(lsp, csp, 16); snprintf(msg, 95, "{\"hlt_sp\":\"%s\"}", csp); - ws_server_send_text_clients((char *)"/ws", msg, strlen(msg)); + ws_server_send_text_clients(msg); } if (strcmp(cpower, lpower) || (! update)) { TFT_clearStringRect(x + 120, y + H, (char *)"100%"); TFT_print(cpower, x + 120, y + H); strncpy(lpower, cpower, 16); snprintf(msg, 95, "{\"hlt_power\":\"%s\"}", cpower); - ws_server_send_text_clients((char *)"/ws", msg, strlen(msg)); + ws_server_send_text_clients(msg); } } @@ -485,7 +485,7 @@ s_timer, s_top_msg); xSemaphoreGive(xSemaphoreDriver); - ws_server_send_text_client(client, msg, strlen(msg)); + ws_server_send_text_client(client, msg); } } @@ -531,7 +531,7 @@ */ Sub_Screen = 0; snprintf(msg, 95, "{\"main\":\"%d\",\"sub\":\"%d\",\"timer\":\"\"}", Main_Screen, Sub_Screen); - ws_server_send_text_clients((char *)"/ws", msg, strlen(msg)); + ws_server_send_text_clients(msg); ESP_LOGD(TAG, "Change screen %d to %d", Old_Screen, Main_Screen); _bg = TFT_BLACK; @@ -567,8 +567,8 @@ // ------------------------------------- _fg = TFT_ORANGE; TFT_print((char *)"Parts are written by Chris Morgan,\r\n", 0, LASTY); - TFT_print((char *)"Brett Beauregard, Blake Felt, LoBo,\r\n", 0, LASTY); - TFT_print((char *)"and David Antliff.\r\n", 0, LASTY); + TFT_print((char *)"Brett Beauregard, LoBo and David\r\n", 0, LASTY); + TFT_print((char *)"Antliff.\r\n", 0, LASTY); ShowInteger(1,140, (char *)"Free memory", (char *)" bytes", esp_get_free_heap_size()); ShowText(1,158, (char *)"IDF version", (char *)esp_get_idf_version()); Buttons_Add(130, 200, 60, 40, (char *)"Ok", 0); @@ -676,7 +676,7 @@ snprintf(msg, 95, " %s ", strftime_buf); TFT_print(msg, CENTER, 125); snprintf(msg, 95, "{\"timer\":\"%s\"}", strftime_buf); // Fix string termination and only send once/second. - ws_server_send_text_clients((char *)"/ws", msg, strlen(msg)); + ws_server_send_text_clients(msg); } break;
--- a/sdkconfig Mon Jul 01 08:38:57 2024 +0200 +++ b/sdkconfig Wed Jul 03 20:01:31 2024 +0200 @@ -741,7 +741,7 @@ # CONFIG_HTTPD_ERR_RESP_NO_DELAY is not set CONFIG_HTTPD_PURGE_BUF_LEN=32 # CONFIG_HTTPD_LOG_PURGE_DATA is not set -# CONFIG_HTTPD_WS_SUPPORT is not set +CONFIG_HTTPD_WS_SUPPORT=y # CONFIG_HTTPD_QUEUE_WORK_BLOCKING is not set CONFIG_HTTPD_SERVER_EVENT_POST_TIMEOUT=2000 # end of HTTP Server @@ -1811,16 +1811,6 @@ CONFIG_WIFI_PROV_STA_ALL_CHANNEL_SCAN=y # CONFIG_WIFI_PROV_STA_FAST_SCAN is not set # end of Wi-Fi Provisioning Manager - -# -# WebSocket Server -# -CONFIG_WEBSOCKET_SERVER_MAX_CLIENTS=10 -CONFIG_WEBSOCKET_SERVER_QUEUE_SIZE=10 -CONFIG_WEBSOCKET_SERVER_QUEUE_TIMEOUT=30 -CONFIG_WEBSOCKET_SERVER_TASK_STACK_DEPTH=7000 -CONFIG_WEBSOCKET_SERVER_TASK_PRIORITY=5 -# end of WebSocket Server # end of Component config # CONFIG_IDF_EXPERIMENTAL_FEATURES is not set