├── .DS_Store ├── .github └── workflows │ └── release.yml ├── .gitignore ├── License ├── README.md ├── docs ├── flipper-board-serial-monitor.gif ├── flipper-uart-terminal.gif └── schematics.webp ├── flipper-postman-esp32s2.ino ├── http_utils.cpp ├── http_utils.h ├── led.cpp ├── led.h ├── splash.cpp ├── splash.h ├── uart_utils.cpp ├── uart_utils.h ├── version.h ├── wifi_utils.cpp └── wifi_utils.h /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MassivDash/flipper-postman-esp32s2/35a2769412777646ebef8c168ba0449abb7bdfc4/.DS_Store -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Build and Release Arduino Project 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*.*.*" # Triggers the workflow on version tags 7 | workflow_dispatch: # Allows the workflow to be run on demand 8 | 9 | permissions: 10 | contents: write # Grants write permissions 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout repository 18 | uses: actions/checkout@v2 19 | 20 | - name: Set up Arduino CLI 21 | run: | 22 | mkdir -p ~/arduino-cli 23 | cd ~/arduino-cli 24 | curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | sh 25 | 26 | - name: Install ESP32 core 27 | run: | 28 | ~/arduino-cli/bin/arduino-cli core update-index 29 | ~/arduino-cli/bin/arduino-cli core install esp32:esp32 30 | 31 | - name: Install dependencies 32 | run: | 33 | ~/arduino-cli/bin/arduino-cli lib install ArduinoJson 34 | ~/arduino-cli/bin/arduino-cli lib install HTTPClient 35 | 36 | - name: Compile sketch 37 | run: | 38 | mkdir -p ./binaries 39 | ~/arduino-cli/bin/arduino-cli compile --fqbn esp32:esp32:esp32s2 --libraries ./libraries --output-dir ./binaries ./flipper-postman-esp32s2.ino 40 | 41 | - name: Create Release 42 | uses: softprops/action-gh-release@v2 43 | 44 | with: 45 | files: binaries/* 46 | draft: false 47 | prerelease: false 48 | generate_release_notes: true 49 | body: | 50 | This is the release for version ${{ github.ref }}. 51 | It contains the compiled binaries for the ESP32-S2 Flipper Dev Board. 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .DS_Store -------------------------------------------------------------------------------- /License: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | 21 | Lukasz Celitan -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flipper Postman Board Software for ESP32S2 (Flipper Dev Board) 2 | 3 | ## Project Information 4 | 5 | Custom flash software for Flipper Dev Board exposing the WIFI and HTTP methods via serial (UART) that can be picked up by the flipper zero device for building web enabled applications. 6 | 7 | ![Flipper Dev Board](./docs/schematics.webp) 8 | \*image taken from https://docs.flipper.net/development/hardware/wifi-developer-board/schematics 9 | 10 | ## Features 11 | 12 | - Connect / Disonnect to WiFi networks 13 | - List available WiFi networks 14 | - Make HTTP GET, POST, PATCH and STREAM requests 15 | - Custom HTTP request builder with all the methods GET, POST, HEAD, DELETE, PATCH, PUT, attach custom headers (Authorization), show and hide response headers 16 | - Stream serial responses for large payloads. 17 | - UDP communication, send commands and messages over the network via UDP packets 18 | - LED indicators for different states 19 | 20 | ## UDP / Network communication 21 | 22 | After you establish wifi the board listens to UDP packets you can use mobile apps (with TCP/UDP) or use cmd line tools like `echo -n "GET https://api.spacexdata.com/v3/rockets" | nc -u 192.168.0.115 1234`. 23 | 24 | The board will transmit the local port and into the serial 25 | 26 | You can also send custom messages to the flipper, type MESSAGE: for transmitting whatever you want to your flipper device. 27 | 28 | ## Flipper Zero Uart Terminal 29 | 30 | You can use the https://github.com/cool4uma/UART_Terminal to communicate directly with the board. You don't have to write `https://` it will be auto added by the board. 31 | 32 | ![Flipper UART Terminal](./docs/flipper-uart-terminal.gif) 33 | 34 | ## Arduino Ide Serial Monitor 35 | 36 | Communication via usb to flipperzero (GPIO->USB-UART Bridge) with Arduino Ide Serial Monitor 37 | 38 | ![Arduino Ide SerialMonitor](./docs/flipper-board-serial-monitor.gif) 39 | 40 | ## Installation (Build from source) 41 | 42 | 0. Git clone the project 43 | 1. Install [Arduino IDE](https://www.arduino.cc/en/software) 44 | 2. Arduino IDE -> Settings (Preferences) 45 | 46 | Copy and paste the `https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json` 47 | 48 | 3. Wait for installation 49 | 4. Arduino IDE -> Tools -> Board -> Board Manager -> look for `esp32 by Expressif systems` and install 50 | 5. Arduino IDE -> Tools -> Manage Libraries -> find ArduinoJSON package and install 51 | 6. Arduino IDE -> Select Board -> choose esp32s2 dev board 52 | 53 | [UART Terminal Application](https://github.com/cool4uma/UART_Terminal). You can also connect flipper to your computer via usb cable then enter GPIO and enable the UART-USB Bridge to communicate directly to board via Arduino IDE Serial Monitor or [minicom](https://wiki.emacinc.com/wiki/Getting_Started_With_Minicom) cmd line program 54 | 55 | 7. Set the SSID and password for the WiFi connection using the `SET_SSID` and `SET_PASSWORD` commands. 56 | 8. Activate the WiFi connection using the `ACTIVATE_WIFI` command. 57 | 9. Use the `GET`, `POST`, or `GET_STREAM` commands to make HTTP requests. 58 | 10. Build custom HTTP requests using the `BUILD_HTTP_*` commands. 59 | 11. Execute custom HTTP requests using the `EXECUTE_HTTP_CALL` command. 60 | 12. Use the `?` or `HELP` commands to print help information. 61 | 62 | ## SERIAL API Documentation 63 | 64 | This section provides detailed information about the available commands, their descriptions, arguments, responses, and response types. It also includes the possible serial responses that the receiver can expect. 65 | 66 | ### Commands 67 | 68 | | Command | Description | Arguments | Response Type | Response Description | 69 | | ----------------------------------------------- | ------------------------------------------------- | ----------------------------------- | ------------- | ------------------------------------------------------------------------------------------------------- | 70 | | `VERSION` | Get board version | None | Text | `VERSION: ` | 71 | | `WIFI_CONNECT ` | Connect to a WiFi network | ` ` | Text | `WIFI_SSID:
WIFI_PASSWORD:
WIFI_CONNECT: Connecting to WiFi...` | 72 | | `WIFI_SET_SSID ` | Set the SSID for WiFi connection | `` | Text | `WIFI_SSID: ` | 73 | | `WIFI_SET_PASSWORD ` | Set the password for WiFi connection | `` | Text | `WIFI_PASSWORD: ` | 74 | | `WIFI_ACTIVATE` | Activate the WiFi connection | None | Text | `WIFI_CONNECT: Connecting to WiFi...` | 75 | | `WIFI_DEACTIVATE` | Disconnect from the WiFi network | None | Text | `WIFI_DISCONNECT: Wifi disconnected` | 76 | | `WIFI_LIST` | List available WiFi networks | None | Text | `WIFI_LIST: ` | 77 | | `WIFI_STATUS` | Show WiFi status | None | Text | `WIFI_STATUS: CONNECTED` or `WIFI_STATUS: DISCONNECTED` | 78 | | `WIFI_GET_ACTIVE_SSID` | Get the name of the connected SSID | None | Text | `WIFI_GET_ACTIVE_SSID: ` or `WIFI_GET_ACTIVE_SSID: Not connected` | 79 | | `WIFI_GET_LOCAL_IP` | Get the local IP address | None | Text | `` | 80 | | `GET ` | Make an HTTP GET request | `` | Text | `GET:
STATUS:
RESPONSE:

RESPONSE_END` | 81 | | `GET_STREAM ` | Make an HTTP GET request and stream the response | `` | Stream | `GET_STREAM:
STATUS:
STREAM:

STREAM_END` | 82 | | `FILE_STREAM ` | Direct stream, no messages | `` | Text | `` | 83 | | `POST ` | Make an HTTP POST request with JSON payload | ` ` | Text | `POST:
Payload:
STATUS:
RESPONSE:

RESPONSE_END` | 84 | | `POST_STREAM ` | Make an HTTP POST request and stream the response | ` ` | Stream | `POST_STREAM:
STATUS:
STREAM:

STREAM_END` | 85 | | `BUILD_HTTP_METHOD ` | Set the HTTP method for custom request | `` | Text | `HTTP_SET_METHOD: ` | 86 | | `BUILD_HTTP_URL ` | Set the URL for custom HTTP request | `` | Text | `HTTP_URL: ` | 87 | | `BUILD_HTTP_HEADER ` | Add a header to custom HTTP request | `` | Text | `HTTP_ADD_HEADER: ` | 88 | | `BUILD_HTTP_PAYLOAD ` | Set the payload for custom HTTP request | `` | Text | `HTTP_SET_PAYLOAD: ` | 89 | | `REMOVE_HTTP_HEADER ` | Remove a header from custom HTTP request | `` | Text | `HTTP_REMOVE_HEADER: ` | 90 | | `RESET_HTTP_CONFIG` | Reset custom HTTP request configuration | None | Text | `HTTP_CONFIG_RESET: All configurations reset` | 91 | | `BUILD_HTTP_SHOW_RESPONSE_HEADERS ` | Show or hide HTTP response headers | `` | Text | `HTTP_BUILDER_SHOW_RESPONSE_HEADERS: ` | 92 | | `BUILD_HTTP_IMPLEMENTATION ` | Set HTTP implementation type | `` | Text | `HTTP_SET_IMPLEMENTATION: ` | 93 | | `EXECUTE_HTTP_CALL` | Execute the custom HTTP request | None | Text/Stream | Depends on implementation type | 94 | | `BUILD_HTTP_SHOW_CONFIG` | Show current HTTP configuration | None | Text | `HTTP_BUILDER_CONFIG: ` | 95 | | `MESSAGE_UDP ` | Send UDP message | ` ` | Text | `UDP message sent:
To IP: , Port: ` | 96 | | `?` | Print help information | None | Text | `Available Commands: ` | 97 | | `HELP` | Print help information | None | Text | `Available Commands: ` | 98 | 99 | #### Sending Commands 100 | 101 | To set the SSID for the WiFi connection: 102 | 103 | ```plaintext 104 | SET_SSID MyWiFiNetwork 105 | ``` 106 | 107 | To make an HTTP GET request: 108 | 109 | ```plaintext 110 | GET https://api.example.com/data 111 | ``` 112 | 113 | To make an HTTP GET request and stream the response: 114 | 115 | ```plaintext 116 | GET_STREAM https://api.example.com/data 117 | ``` 118 | 119 | To make an HTTP POST request with a JSON payload: 120 | 121 | ```plaintext 122 | POST https://api.example.com/data {"key":"value"} 123 | ``` 124 | 125 | ## HTTP Builder 126 | 127 | The firmware holds a http config you can manipulate and then execute the call. 128 | Check the flags in the table above. 129 | 130 | #### Show response headers 131 | 132 | HTTP builder if set for showing headers will only transmit the headers from this list `"Content-Type", "Content-Length", "Connection", 133 | "Date", "Server"` 134 | 135 | #### Simple Build call 136 | 137 | BUILD_HTTP_METHOD HEAD 138 | BUILD_HTTP_URL https://api.com/ 139 | EXECUTE_HTTP_CALL 140 | 141 | #### Receiving Responses 142 | 143 | When you send a `GET_STREAM` command, you will receive the following responses: 144 | 145 | ```plaintext 146 | GET_STREAM request to: https://api.example.com/data 147 | STREAM: 148 | 149 | STREAM_END 150 | ``` 151 | 152 | When you list available WiFi networks: 153 | 154 | ```plaintext 155 | WIFI_LIST: Available WiFi networks: 156 | ``` 157 | 158 | ## Notes 159 | 160 | - The firmware currently follow strict redirects (`HTTPC_STRICT_FOLLOW_REDIRECTS` - strict RFC2616, only requests using GET or HEAD methods will be redirected (using the same method), since the RFC requires end-user confirmation in other cases.) 161 | - Website crawls will print html only on smaller websites. 162 | - You should be able to stream files and images to flipper via stream (untested) 163 | - Simple get call will make a head call first and determine the possible size of the content, that will not always be possible, if the content length is unknown, firmware will choose safer stream method 164 | 165 | ## ESP32 links 166 | 167 | - [ESP32 Arduino Setup ](https://randomnerdtutorials.com/installing-the-esp32-board-in-arduino-ide-windows-instructions/) 168 | - [ESP-IDF](https://docs.espressif.com/projects/esp-idf/en/v4.3/esp32s2/api-guides/tools/idf-tools.html) 169 | - [ESP32S2 Specs](https://www.espressif.com/sites/default/files/documentation/esp32-s2_datasheet_en.pdf) 170 | 171 | Author: SpaceGhost @ spaceout.pl 172 | -------------------------------------------------------------------------------- /docs/flipper-board-serial-monitor.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MassivDash/flipper-postman-esp32s2/35a2769412777646ebef8c168ba0449abb7bdfc4/docs/flipper-board-serial-monitor.gif -------------------------------------------------------------------------------- /docs/flipper-uart-terminal.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MassivDash/flipper-postman-esp32s2/35a2769412777646ebef8c168ba0449abb7bdfc4/docs/flipper-uart-terminal.gif -------------------------------------------------------------------------------- /docs/schematics.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MassivDash/flipper-postman-esp32s2/35a2769412777646ebef8c168ba0449abb7bdfc4/docs/schematics.webp -------------------------------------------------------------------------------- /flipper-postman-esp32s2.ino: -------------------------------------------------------------------------------- 1 | #include "http_utils.h" 2 | #include "led.h" 3 | #include "splash.h" 4 | #include "uart_utils.h" 5 | #include "wifi_utils.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | const char *ssid; 12 | const char *password; 13 | 14 | AsyncUDP udp; 15 | 16 | void setup() { 17 | UART0.begin(115200); 18 | while (!Serial) { 19 | ; // Wait for Serial to be ready 20 | } 21 | 22 | led_init(); 23 | led_set_blue(255); 24 | 25 | uart_buffer_Mutex = xSemaphoreCreateMutex(); 26 | if (uart_buffer_Mutex == NULL) { 27 | log_e("Error creating Mutex. Sketch will fail."); 28 | while (true) { 29 | UART0.println("Mutex error (NULL). Program halted."); 30 | delay(2000); 31 | } 32 | } 33 | led_set_blue(0); 34 | UART0.onReceive(UART0_RX_CB); 35 | init_cmds(); 36 | 37 | printSplashScreen(); 38 | printTitle(); 39 | } 40 | 41 | void loop() { 42 | handleSerialInput(); 43 | // Other loop operations can go here 44 | } -------------------------------------------------------------------------------- /http_utils.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file http_utils.cpp 3 | * @brief HTTP utility functions and configurations 4 | * 5 | * This file contains utility functions for handling HTTP requests, 6 | * managing HTTP configurations, and processing HTTP responses. 7 | */ 8 | 9 | #include "http_utils.h" 10 | #include "led.h" 11 | #include "uart_utils.h" 12 | #include 13 | #include 14 | 15 | /// Maximum content length for HTTP responses 16 | const int MAX_CONTENT_LENGTH = 512 * 1024; 17 | 18 | /** 19 | * @struct HttpCallConfig 20 | * @brief Configuration for HTTP calls 21 | */ 22 | struct HttpCallConfig { 23 | String method; ///< HTTP method 24 | String implementation; ///< Implementation type (e.g., "CALL" or "STREAM") 25 | String url; ///< URL for the HTTP request 26 | std::vector> headers; ///< HTTP headers 27 | String payload; ///< Request payload 28 | bool showResponseHeaders; ///< Flag to show response headers 29 | 30 | void reset() { ///< Reset the configuration 31 | method = ""; 32 | url = ""; 33 | implementation = "CALL"; 34 | headers.clear(); 35 | payload = ""; 36 | showResponseHeaders = false; 37 | } 38 | 39 | void removeHeader(String name) { ///< Remove a specific header 40 | headers.erase(std::remove_if(headers.begin(), headers.end(), 41 | [&](const std::pair &header) { 42 | return header.first == name; 43 | }), 44 | headers.end()); 45 | } 46 | }; 47 | 48 | /// Global HTTP call configuration 49 | HttpCallConfig httpCallConfig; 50 | 51 | /** 52 | * @brief Print response to UART or UDP packet 53 | * @param response The response string to print 54 | * @param packet Pointer to AsyncUDPPacket, can be null for UART output 55 | */ 56 | void printResponse(String response, AsyncUDPPacket *packet) { 57 | if (response.isEmpty()) { 58 | response = "empty"; 59 | } 60 | if (packet) { 61 | packet->printf("%s", response.c_str()); 62 | } else { 63 | UART0.println(response); 64 | } 65 | } 66 | 67 | /** 68 | * @brief Set flag to show response headers 69 | * @param show Boolean flag to show or hide headers 70 | * @param packet Pointer to AsyncUDPPacket for response 71 | */ 72 | void setShowResponseHeaders(bool show, AsyncUDPPacket *packet) { 73 | httpCallConfig.showResponseHeaders = show; 74 | printResponse("HTTP_BUILDER_SHOW_RESPONSE_HEADERS: " + 75 | String(show ? "true" : "false"), 76 | packet); 77 | } 78 | 79 | /** 80 | * @brief Get and print current HTTP builder configuration 81 | * @param packet Pointer to AsyncUDPPacket for response 82 | */ 83 | void getHttpBuilderConfig(AsyncUDPPacket *packet) { 84 | printResponse("HTTP_BUILDER_CONFIG: ", packet); 85 | printResponse("HTTP_METHOD: " + httpCallConfig.method, packet); 86 | printResponse("HTTP_URL: " + httpCallConfig.url, packet); 87 | printResponse("HTTP_PAYLOAD: " + httpCallConfig.payload, packet); 88 | printResponse("HTTP_IMPLEMENTATION: " + httpCallConfig.implementation, 89 | packet); 90 | printResponse("HTTP_HEADERS: ", packet); 91 | for (const auto &header : httpCallConfig.headers) { 92 | printResponse(header.first + ": " + header.second, packet); 93 | } 94 | } 95 | 96 | /** 97 | * @brief Set HTTP method for the request 98 | * @param method HTTP method string 99 | * @param packet Pointer to AsyncUDPPacket for response 100 | */ 101 | void setHttpMethod(String method, AsyncUDPPacket *packet) { 102 | if (method != "GET" && method != "POST" && method != "PATCH" && 103 | method != "PUT" && method != "DELETE" && method != "HEAD") { 104 | printResponse("HTTP_ERROR: Invalid HTTP method. Supported methods: GET, " 105 | "POST, PATCH, PUT, DELETE, HEAD", 106 | packet); 107 | return; 108 | } 109 | 110 | httpCallConfig.method = method; 111 | printResponse("HTTP_SET_METHOD: " + method, packet); 112 | } 113 | 114 | /** 115 | * @brief Set URL for the HTTP request 116 | * @param url URL string 117 | * @param packet Pointer to AsyncUDPPacket for response 118 | */ 119 | 120 | void setHttpUrl(String url, AsyncUDPPacket *packet) { 121 | httpCallConfig.url = ensureHttpsPrefix(url); 122 | printResponse("HTTP_URL: " + httpCallConfig.url, packet); 123 | } 124 | 125 | /** 126 | * @brief Add an HTTP header to the configuration 127 | * @param header Header string in "name:value" format 128 | * @param packet Pointer to AsyncUDPPacket for response 129 | */ 130 | void addHttpHeader(String header, AsyncUDPPacket *packet) { 131 | int separatorIndex = header.indexOf(':'); 132 | if (separatorIndex != -1) { 133 | String name = header.substring(0, separatorIndex); 134 | String value = header.substring(separatorIndex + 1); 135 | httpCallConfig.headers.push_back(std::make_pair(name, value)); 136 | printResponse("HTTP_ADD_HEADER: " + name + ": " + value, packet); 137 | } else { 138 | printResponse("HTTP_ERROR: Invalid header format, use HEADER name:value", 139 | packet); 140 | } 141 | } 142 | 143 | /** 144 | * @brief Remove an HTTP header from the configuration 145 | * @param name Name of the header to remove 146 | * @param packet Pointer to AsyncUDPPacket for response 147 | */ 148 | void removeHttpHeader(String name, AsyncUDPPacket *packet) { 149 | httpCallConfig.removeHeader(name); 150 | printResponse("HTTP_REMOVE_HEADER: " + name, packet); 151 | } 152 | 153 | /** 154 | * @brief Reset the HTTP configuration to default values 155 | * @param packet Pointer to AsyncUDPPacket for response 156 | */ 157 | void resetHttpConfig(AsyncUDPPacket *packet) { 158 | httpCallConfig.reset(); 159 | printResponse("HTTP_CONFIG_REST: All configurations reset", packet); 160 | } 161 | 162 | /** 163 | * @brief Set the payload for the HTTP request 164 | * @param payload Payload string 165 | * @param packet Pointer to AsyncUDPPacket for response 166 | */ 167 | void setHttpPayload(String payload, AsyncUDPPacket *packet) { 168 | httpCallConfig.payload = payload; 169 | printResponse("HTTP_SET_PAYLOAD: " + payload, packet); 170 | } 171 | 172 | /** 173 | * @brief Set the implementation type for HTTP requests 174 | * @param implementation Implementation type string 175 | * @param packet Pointer to AsyncUDPPacket for response 176 | */ 177 | void setHttpImplementation(String implementation, AsyncUDPPacket *packet) { 178 | httpCallConfig.implementation = implementation; 179 | printResponse("HTTP_SET_IMPLEMENTATION: " + implementation, packet); 180 | } 181 | 182 | /** 183 | * @brief Get the content length of a resource at the given URL 184 | * @param url URL to check 185 | * @return int Content length or -1 if error 186 | */ 187 | int getContentLength(String url) { 188 | HTTPClient http; 189 | http.begin(url); 190 | int httpResponseCode = http.sendRequest("HEAD"); 191 | 192 | if (httpResponseCode > 0) { 193 | int contentLength = http.getSize(); 194 | http.end(); 195 | return contentLength; 196 | } else { 197 | http.end(); 198 | return -1; // Indicate an error 199 | } 200 | } 201 | 202 | /** 203 | * @brief Handle streaming HTTP response 204 | * @param http HTTPClient object 205 | * @param packet Pointer to AsyncUDPPacket for response 206 | */ 207 | void handleStreamResponse(HTTPClient &http, AsyncUDPPacket *packet) { 208 | int len = http.getSize(); 209 | const size_t bufferSize = 512; // Buffer size for reading data 210 | uint8_t buff[bufferSize + 1] = {0}; // Buffer with space for null-terminator 211 | 212 | NetworkClient *stream = http.getStreamPtr(); 213 | 214 | printResponse("STREAM: ", packet); 215 | while (http.connected() && (len > 0 || len == -1) ) { 216 | size_t size = stream->available(); 217 | if (size) { 218 | int c = stream->readBytes(buff, min(size, bufferSize - 1)); // Adjust for null terminator 219 | buff[c] = '\0'; // Null-terminate the buffer 220 | if (packet) { 221 | packet->write(buff, c); 222 | } else { 223 | UART0.write(buff, c); 224 | } 225 | if (len > 0) { 226 | len -= c; 227 | } 228 | } 229 | delay(1); // Yield control to the system 230 | } 231 | printResponse("\nSTREAM_END", packet); 232 | } 233 | 234 | /** 235 | * @brief Handle file streaming HTTP response 236 | * @param http HTTPClient object 237 | * @param packet Pointer to AsyncUDPPacket for response 238 | */ 239 | void handleFileStreamResponse(HTTPClient &http, AsyncUDPPacket *packet) { 240 | int len = http.getSize(); 241 | uint8_t buff[512] = {0}; 242 | 243 | NetworkClient *stream = http.getStreamPtr(); 244 | 245 | while (http.connected() && (len > 0 || len == -1)) { 246 | size_t size = stream->available(); 247 | if (size) { 248 | int c = stream->readBytes(buff, ((size > sizeof(buff)) ? sizeof(buff) : size)); 249 | UART0.write(buff, c); 250 | if (len > 0) { 251 | len -= c; 252 | } 253 | } 254 | delay(1); // Yield control to the system 255 | } 256 | } 257 | 258 | /** 259 | * @brief Handle HTTP response as a string 260 | * @param http HTTPClient object 261 | * @param packet Pointer to AsyncUDPPacket for response 262 | */ 263 | void handleGetStringResponse(HTTPClient &http, AsyncUDPPacket *packet) { 264 | // Check available heap memory 265 | size_t freeHeap = ESP.getFreeHeap(); 266 | const size_t minHeapThreshold = 1024; // Minimum heap space to avoid overflow 267 | 268 | if (freeHeap < minHeapThreshold) { 269 | printResponse("WIFI_ERROR: Not enough memory to process the response.", 270 | packet); 271 | return; 272 | } 273 | 274 | String payload = http.getString(); 275 | if (payload.isEmpty()) { 276 | payload = "empty"; 277 | } 278 | 279 | // Check again after getting the payload 280 | freeHeap = ESP.getFreeHeap(); 281 | if (freeHeap < minHeapThreshold) { 282 | printResponse("WIFI_ERROR: Not enough memory to process the full response.", 283 | packet); 284 | return; 285 | } 286 | 287 | printResponse("RESPONSE:", packet); 288 | printResponse(payload, packet); 289 | printResponse("RESPONSE_END", packet); 290 | } 291 | 292 | /** 293 | * @brief Make an HTTP GET request 294 | * @param url URL for the request 295 | * @param packet Pointer to AsyncUDPPacket for response 296 | */ 297 | void makeHttpRequest(String url, AsyncUDPPacket *packet) { 298 | if (WiFi.status() == WL_CONNECTED) { 299 | int contentLength = getContentLength(url); 300 | 301 | if (contentLength > MAX_CONTENT_LENGTH) { 302 | String warnMsg = "WARNING: Content of " + String(contentLength) + 303 | " exceeds maximum length of " + 304 | String(MAX_CONTENT_LENGTH) + 305 | " bytes for simple calls. Using stream, if" 306 | "the blue light stays on, reset the board."; 307 | printResponse(warnMsg, packet); 308 | } 309 | 310 | if (contentLength == -1) { 311 | String warnMsg = 312 | "WARNING: Content-Length is unknown (-1). These calls tend to crash " 313 | "to board. If the blue light stays on, reset the board."; 314 | printResponse(warnMsg, packet); 315 | } 316 | 317 | HTTPClient http; 318 | led_set_blue(255); 319 | http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); 320 | http.begin(url); 321 | 322 | int httpResponseCode = http.GET(); 323 | 324 | if (httpResponseCode > 0) { 325 | String response = "STATUS: " + String(httpResponseCode) + "\n"; 326 | printResponse(response, packet); 327 | 328 | if (contentLength == -1 || contentLength > MAX_CONTENT_LENGTH) { 329 | handleStreamResponse(http, packet); 330 | } else { 331 | handleGetStringResponse(http, packet); 332 | } 333 | } else { 334 | String errorMsg = "HTTP_ERROR: " + getHttpErrorMessage(httpResponseCode); 335 | printResponse(errorMsg, packet); 336 | } 337 | 338 | http.end(); 339 | led_set_blue(0); 340 | } else { 341 | led_set_blue(0); 342 | led_error(); 343 | String errorMsg = "HTTP_ERROR: WiFi Disconnected"; 344 | printResponse(errorMsg, packet); 345 | } 346 | } 347 | 348 | /** 349 | * @brief Make an HTTP GET request for file streaming 350 | * @param url URL for the request 351 | * @param packet Pointer to AsyncUDPPacket for response 352 | */ 353 | void makeHttpFileRequest(String url, AsyncUDPPacket *packet) { 354 | if (WiFi.status() == WL_CONNECTED) { 355 | HTTPClient http; 356 | led_set_blue(255); 357 | http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); 358 | http.begin(url); 359 | 360 | 361 | int httpResponseCode = http.GET(); 362 | 363 | if (httpResponseCode > 0) { 364 | handleFileStreamResponse(http, packet); 365 | } 366 | 367 | http.end(); 368 | led_set_blue(0); 369 | } 370 | } 371 | 372 | /** 373 | * @brief Make an HTTP GET request with streaming response 374 | * @param url URL for the request 375 | * @param packet Pointer to AsyncUDPPacket for response 376 | */ 377 | void makeHttpRequestStream(String url, AsyncUDPPacket *packet) { 378 | if (WiFi.status() == WL_CONNECTED) { 379 | HTTPClient http; 380 | led_set_blue(255); 381 | http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); 382 | http.begin(url); 383 | 384 | int contentLength = getContentLength(url); 385 | 386 | if (contentLength > MAX_CONTENT_LENGTH) { 387 | String warnMsg = "WARNING: Content of " + String(contentLength) + 388 | " exceeds maximum length for stream of." + 389 | String(MAX_CONTENT_LENGTH) + 390 | " bytes. If" 391 | "the blue light stays on," 392 | "reset the board."; 393 | printResponse(warnMsg, packet); 394 | } 395 | 396 | if (contentLength == -1) { 397 | String warnMsg = 398 | "WARNING: Content-Length is unknown (-1). These calls tend to crash " 399 | "to board. If the blue light stays on, reset the board."; 400 | printResponse(warnMsg, packet); 401 | } 402 | 403 | int httpResponseCode = http.GET(); 404 | 405 | if (httpResponseCode > 0) { 406 | String response = "STATUS: " + String(httpResponseCode) + "\n"; 407 | printResponse(response, packet); 408 | handleStreamResponse(http, packet); 409 | } else { 410 | String errorMsg = 411 | "HTTP_ERROR: " + HTTPClient::errorToString(httpResponseCode) + 412 | " (Code: " + String(httpResponseCode) + ")"; 413 | printResponse(errorMsg, packet); 414 | } 415 | 416 | http.end(); 417 | led_set_blue(0); 418 | } else { 419 | String errorMsg = "HTTP_ERROR: WiFi Disconnected"; 420 | printResponse(errorMsg, packet); 421 | led_set_blue(0); 422 | led_error(); 423 | } 424 | } 425 | 426 | /** 427 | * @brief Make an HTTP POST request 428 | * @param url URL for the request 429 | * @param jsonPayload JSON payload for the request 430 | * @param packet Pointer to AsyncUDPPacket for response 431 | */ 432 | void makeHttpPostRequest(String url, String jsonPayload, 433 | AsyncUDPPacket *packet) { 434 | if (WiFi.status() == WL_CONNECTED) { 435 | led_set_blue(255); 436 | HTTPClient http; 437 | http.begin(url); 438 | http.addHeader("Content-Type", "application/json"); 439 | 440 | int httpResponseCode = http.POST(jsonPayload); 441 | 442 | if (httpResponseCode > 0) { 443 | String response = "STATUS: " + String(httpResponseCode) + "\n"; 444 | printResponse(response, packet); 445 | handleGetStringResponse(http, packet); 446 | } else { 447 | String errorMsg = "HTTP_ERROR: " + getHttpErrorMessage(httpResponseCode); 448 | printResponse(errorMsg, packet); 449 | led_set_blue(0); 450 | led_error(); 451 | } 452 | http.end(); 453 | led_set_blue(0); 454 | } else { 455 | String errorMsg = "HTTP_ERROR: WiFi Disconnected"; 456 | printResponse(errorMsg, packet); 457 | led_set_blue(0); 458 | led_error(); 459 | } 460 | } 461 | 462 | /** 463 | * @brief Make an HTTP POST request for file streaming 464 | * @param url URL for the request 465 | * @param jsonPayload JSON payload for the request 466 | * @param packet Pointer to AsyncUDPPacket for response 467 | */ 468 | void makeHttpPostFileRequest(String url, String jsonPayload, AsyncUDPPacket *packet) { 469 | if (WiFi.status() == WL_CONNECTED) { 470 | HTTPClient http; 471 | led_set_blue(255); 472 | http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); 473 | http.begin(url); 474 | http.addHeader("Content-Type", "application/json"); 475 | 476 | int httpResponseCode = http.POST(jsonPayload); 477 | 478 | if (httpResponseCode > 0) { 479 | handleFileStreamResponse(http, packet); 480 | } 481 | 482 | http.end(); 483 | led_set_blue(0); 484 | } 485 | } 486 | 487 | /** 488 | * @brief Execute an HTTP call based on the current configuration 489 | * @param packet Pointer to AsyncUDPPacket for response 490 | */ 491 | 492 | void executeHttpCall(AsyncUDPPacket *packet) { 493 | if (httpCallConfig.url.isEmpty() || httpCallConfig.method.isEmpty()) { 494 | String errorMsg = "HTTP URL or Method not set"; 495 | printResponse(errorMsg, packet); 496 | return; 497 | } 498 | 499 | if (WiFi.status() == WL_CONNECTED) { 500 | led_set_blue(255); 501 | HTTPClient http; 502 | http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); 503 | http.begin(httpCallConfig.url); 504 | 505 | for (const auto &header : httpCallConfig.headers) { 506 | http.addHeader(header.first, header.second); 507 | } 508 | 509 | // Collect common headers 510 | const char *headerKeys[] = {"Content-Type", "Content-Length", "Connection", 511 | "Date", "Server"}; 512 | size_t headerKeysCount = sizeof(headerKeys) / sizeof(headerKeys[0]); 513 | http.collectHeaders(headerKeys, headerKeysCount); 514 | 515 | String response; 516 | int httpResponseCode; 517 | 518 | if (httpCallConfig.method == "GET") { 519 | httpResponseCode = http.GET(); 520 | } else if (httpCallConfig.method == "POST") { 521 | httpResponseCode = http.POST(httpCallConfig.payload); 522 | } else if (httpCallConfig.method == "PATCH") { 523 | httpResponseCode = http.PATCH(httpCallConfig.payload); 524 | } else if (httpCallConfig.method == "PUT") { 525 | httpResponseCode = http.PUT(httpCallConfig.payload); 526 | } else if (httpCallConfig.method == "DELETE") { 527 | httpResponseCode = http.sendRequest("DELETE", httpCallConfig.payload); 528 | } else if (httpCallConfig.method == "HEAD") { 529 | httpResponseCode = http.sendRequest("HEAD"); 530 | } else { 531 | String errorMsg = "Unsupported HTTP method: " + httpCallConfig.method; 532 | printResponse(errorMsg, packet); 533 | http.end(); 534 | led_set_blue(0); 535 | return; 536 | } 537 | 538 | if (httpResponseCode > 0) { 539 | response = "STATUS: " + String(httpResponseCode) + "\n"; 540 | printResponse(response, packet); 541 | 542 | if (httpCallConfig.showResponseHeaders) { 543 | printResponse("HEADERS:", packet); 544 | // Get the header count 545 | int headerCount = http.headers(); 546 | 547 | // Iterate over the collected headers and print them 548 | for (int i = 0; i < headerCount; i++) { 549 | String headerName = http.headerName(i); 550 | String headerValue = http.header(i); 551 | printResponse(headerName + ": " + headerValue, packet); 552 | } 553 | } 554 | 555 | if (httpCallConfig.implementation == "STREAM") { 556 | handleStreamResponse(http, packet); 557 | } else { 558 | handleGetStringResponse(http, packet); 559 | } 560 | } else { 561 | String errorMsg = "HTTP_ERROR: " + getHttpErrorMessage(httpResponseCode); 562 | printResponse(errorMsg, packet); 563 | led_set_blue(0); 564 | led_error(); 565 | } 566 | 567 | http.end(); 568 | response = ""; 569 | led_set_blue(0); 570 | } else { 571 | String errorMsg = "HTTP_ERROR: WiFi Disconnected"; 572 | led_set_blue(0); 573 | led_error(); 574 | printResponse(errorMsg, packet); 575 | } 576 | } 577 | 578 | /** 579 | * @brief Get a human-readable error message for HTTP error codes 580 | * @param httpCode HTTP error code 581 | * @return String Error message 582 | */ 583 | String getHttpErrorMessage(int httpCode) { 584 | switch (httpCode) { 585 | case HTTPC_ERROR_CONNECTION_REFUSED: 586 | return "Connection refused"; 587 | case HTTPC_ERROR_SEND_HEADER_FAILED: 588 | return "Send header failed"; 589 | case HTTPC_ERROR_SEND_PAYLOAD_FAILED: 590 | return "Send payload failed"; 591 | case HTTPC_ERROR_NOT_CONNECTED: 592 | return "Not connected"; 593 | case HTTPC_ERROR_CONNECTION_LOST: 594 | return "Connection lost"; 595 | case HTTPC_ERROR_NO_STREAM: 596 | return "No stream"; 597 | case HTTPC_ERROR_NO_HTTP_SERVER: 598 | return "No HTTP server"; 599 | case HTTPC_ERROR_TOO_LESS_RAM: 600 | return "Not enough RAM"; 601 | case HTTPC_ERROR_ENCODING: 602 | return "Transfer encoding error"; 603 | case HTTPC_ERROR_STREAM_WRITE: 604 | return "Stream write error"; 605 | case HTTPC_ERROR_READ_TIMEOUT: 606 | return "Connection timeout"; 607 | default: 608 | return "Unknown error: " + String(httpCode); 609 | } 610 | } -------------------------------------------------------------------------------- /http_utils.h: -------------------------------------------------------------------------------- 1 | #ifndef HTTP_UTILS_H 2 | #define HTTP_UTILS_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // HTTP utility functions 11 | void makeHttpRequest(String url, AsyncUDPPacket *packet); 12 | void makeHttpRequestStream(String url, AsyncUDPPacket *packet); 13 | void makeHttpPostRequest(String url, String jsonPayload, 14 | AsyncUDPPacket *packet); 15 | void makeHttpPostFileRequest(String url, String jsonPayload, AsyncUDPPacket *packet); 16 | 17 | // HTTP configuration functions 18 | void setHttpMethod(String method, AsyncUDPPacket *packet); 19 | void setHttpUrl(String url, AsyncUDPPacket *packet); 20 | void addHttpHeader(String header, AsyncUDPPacket *packet); 21 | void setHttpPayload(String payload, AsyncUDPPacket *packet); 22 | void setHttpImplementation(String implementation, AsyncUDPPacket *packet); 23 | void removeHttpHeader(String name, AsyncUDPPacket *packet); 24 | void resetHttpConfig(AsyncUDPPacket *packet); 25 | void executeHttpCall(AsyncUDPPacket *packet); 26 | void getHttpBuilderConfig(AsyncUDPPacket *packet); 27 | void makeHttpFileRequest(String url, AsyncUDPPacket *packet); 28 | // HTTP Helper functions 29 | void setShowResponseHeaders(bool show, AsyncUDPPacket *packet); 30 | String getHttpErrorMessage(int httpCode); 31 | void printResponse(String response, AsyncUDPPacket *packet); 32 | 33 | #endif // HTTP_UTILS_H -------------------------------------------------------------------------------- /led.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file led.cpp 3 | * @brief LED control functions for RGB LED 4 | * 5 | * This file contains functions to initialize and control an RGB LED 6 | * using PWM (Pulse Width Modulation) on an ESP32 microcontroller. 7 | */ 8 | 9 | #include "led.h" 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | 16 | /// GPIO pin for red LED 17 | #define LED_PIN_RED (6) 18 | /// GPIO pin for green LED 19 | #define LED_PIN_GREEN (5) 20 | /// GPIO pin for blue LED 21 | #define LED_PIN_BLUE (4) 22 | 23 | /// LEDC speed mode 24 | #define LEDC_MODE LEDC_LOW_SPEED_MODE 25 | 26 | /// Tag for ESP logging 27 | #define TAG "led" 28 | 29 | /// Maximum PWM value 30 | #define LED_PWM_MAX_VAL 256U 31 | 32 | /// Maximum brightness value for red LED 33 | #define LED_RED_MAX_VAL 20U 34 | /// Maximum brightness value for green LED 35 | #define LED_GREEN_MAX_VAL 20U 36 | /// Maximum brightness value for blue LED 37 | #define LED_BLUE_MAX_VAL 20U 38 | 39 | /** 40 | * @brief Initialize LED PWM channels 41 | * 42 | * This function sets up the LEDC timer and channels for controlling the RGB LED. 43 | */ 44 | 45 | void led_init() { 46 | ESP_LOGI(TAG, "init"); 47 | ledc_timer_config_t ledc_timer = {.speed_mode = LEDC_MODE, 48 | .duty_resolution = LEDC_TIMER_8_BIT, 49 | .timer_num = LEDC_TIMER_0, 50 | .freq_hz = 51 | 5000, // Set output frequency at 5 kHz 52 | .clk_cfg = LEDC_AUTO_CLK}; 53 | ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer)); 54 | 55 | ledc_channel_config_t ledc_channel_red = { 56 | .gpio_num = LED_PIN_RED, 57 | .speed_mode = LEDC_MODE, 58 | .channel = LEDC_CHANNEL_0, 59 | .intr_type = LEDC_INTR_DISABLE, 60 | .timer_sel = LEDC_TIMER_0, 61 | .duty = LED_PWM_MAX_VAL, // Set duty to 100% 62 | .hpoint = 0}; 63 | ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel_red)); 64 | 65 | ledc_channel_config_t ledc_channel_green = { 66 | .gpio_num = LED_PIN_GREEN, 67 | .speed_mode = LEDC_MODE, 68 | .channel = LEDC_CHANNEL_1, 69 | .intr_type = LEDC_INTR_DISABLE, 70 | .timer_sel = LEDC_TIMER_0, 71 | .duty = LED_PWM_MAX_VAL, // Set duty to 100% 72 | .hpoint = 0}; 73 | ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel_green)); 74 | 75 | ledc_channel_config_t ledc_channel_blue = { 76 | .gpio_num = LED_PIN_BLUE, 77 | .speed_mode = LEDC_MODE, 78 | .channel = LEDC_CHANNEL_2, 79 | .intr_type = LEDC_INTR_DISABLE, 80 | .timer_sel = LEDC_TIMER_0, 81 | .duty = LED_PWM_MAX_VAL, // Set duty to 100% 82 | .hpoint = 0}; 83 | ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel_blue)); 84 | ESP_LOGI(TAG, "init done"); 85 | } 86 | 87 | /** 88 | * @brief Set RGB LED color 89 | * 90 | * @param red Red component (0-255) 91 | * @param green Green component (0-255) 92 | * @param blue Blue component (0-255) 93 | */ 94 | void led_set(uint8_t red, uint8_t green, uint8_t blue) { 95 | led_set_red(red); 96 | led_set_green(green); 97 | led_set_blue(blue); 98 | } 99 | 100 | /** 101 | * @brief Set red LED brightness 102 | * 103 | * @param value Brightness value (0-255) 104 | */ 105 | void led_set_red(uint8_t value) { 106 | uint32_t pwm_value = ((uint32_t)value * LED_RED_MAX_VAL) / 255; 107 | ESP_ERROR_CHECK( 108 | ledc_set_duty(LEDC_MODE, LEDC_CHANNEL_0, LED_PWM_MAX_VAL - pwm_value)); 109 | ESP_ERROR_CHECK(ledc_update_duty(LEDC_MODE, LEDC_CHANNEL_0)); 110 | } 111 | 112 | /** 113 | * @brief Set green LED brightness 114 | * 115 | * @param value Brightness value (0-255) 116 | */ 117 | void led_set_green(uint8_t value) { 118 | uint32_t pwm_value = ((uint32_t)value * LED_GREEN_MAX_VAL) / 255; 119 | ESP_ERROR_CHECK( 120 | ledc_set_duty(LEDC_MODE, LEDC_CHANNEL_1, LED_PWM_MAX_VAL - pwm_value)); 121 | ESP_ERROR_CHECK(ledc_update_duty(LEDC_MODE, LEDC_CHANNEL_1)); 122 | } 123 | 124 | /** 125 | * @brief Set blue LED brightness 126 | * 127 | * @param value Brightness value (0-255) 128 | */ 129 | void led_set_blue(uint8_t value) { 130 | uint32_t pwm_value = ((uint32_t)value * LED_BLUE_MAX_VAL) / 255; 131 | ESP_ERROR_CHECK( 132 | ledc_set_duty(LEDC_MODE, LEDC_CHANNEL_2, LED_PWM_MAX_VAL - pwm_value)); 133 | ESP_ERROR_CHECK(ledc_update_duty(LEDC_MODE, LEDC_CHANNEL_2)); 134 | } 135 | 136 | /** 137 | * @brief Display error indication using red LED 138 | * 139 | * This function turns on the red LED for 3 second and then turns it off. 140 | */ 141 | void led_error() { 142 | led_set_red(255); 143 | delay(3000); 144 | led_set_red(0); 145 | } 146 | -------------------------------------------------------------------------------- /led.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | 5 | void led_init(); 6 | 7 | void led_set(uint8_t red, uint8_t green, uint8_t blue); 8 | 9 | void led_set_red(uint8_t value); 10 | void led_set_green(uint8_t value); 11 | void led_set_blue(uint8_t value); 12 | 13 | void led_error(); 14 | -------------------------------------------------------------------------------- /splash.cpp: -------------------------------------------------------------------------------- 1 | #include "uart_utils.h" 2 | #include "version.h" 3 | #include 4 | // Function to print the splash screen 5 | void printSplashScreen() { 6 | UART0.println(" "); 7 | UART0.println(" "); 8 | UART0.println(" "); 9 | UART0.println(" @@@@# "); 10 | UART0.println(" @@@@@@@@@ "); 11 | UART0.println(" @@@@@@@@@@@@@ "); 12 | UART0.println(" @@@@@@@@@@@@@@@@@ "); 13 | UART0.println(" (@@@@ @@@@@* "); 14 | UART0.println(" @@@@ @@@@@ "); 15 | UART0.println(" *@@@@ @@@@@, "); 16 | UART0.println(" @@@@@@ @@@@@@ "); 17 | UART0.println(" @@@@@@* *@@ @@@ @@@@@@@ "); 18 | UART0.println(" @@@@@@@ . @@@@@@@@ "); 19 | UART0.println(" @@@@@@@@ /@@@@@@@@ "); 20 | UART0.println(" @@@@@@@@@ @@@@@@@@@ "); 21 | UART0.println(" @@@@@@@@@& @@@@@@@@@@ "); 22 | UART0.println(" *@@@@@@@@@# @@@@@@@@@@% "); 23 | UART0.println(" @@@@@@@@@&* %&@@@@@@@@@ "); 24 | UART0.println(" "); 25 | UART0.println(" "); 26 | UART0.println(" "); 27 | UART0.println(" "); 28 | UART0.println(" "); 29 | UART0.println(); 30 | } 31 | 32 | // Function to print the title 33 | void printTitle() { 34 | UART0.println("==========================================================="); 35 | UART0.println("|| ||"); 36 | UART0.println("|| Flipper Postman Board v" + String(version) + 37 | " ||"); 38 | UART0.println("|| by SpaceGhost at spaceout.pl ||"); 39 | UART0.println("|| ||"); 40 | UART0.println("==========================================================="); 41 | UART0.println(); 42 | UART0.println("Type '?' to see available commands"); 43 | } 44 | -------------------------------------------------------------------------------- /splash.h: -------------------------------------------------------------------------------- 1 | #ifndef SPLASH_H 2 | #define SPLASH_H 3 | 4 | #include "uart_utils.h" 5 | #include 6 | 7 | // Function to print the splash screen 8 | void printSplashScreen(); 9 | void printTitle(); 10 | 11 | #endif // SPLASH_H -------------------------------------------------------------------------------- /uart_utils.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file uart_commands.cpp 3 | * @brief UART command handling and WiFi utilities 4 | * 5 | * This file contains functions for handling UART commands, including WiFi operations, 6 | * HTTP requests, and UDP messaging. It also defines the command structure and initializes 7 | * the available commands for the system. 8 | */ 9 | 10 | #include "uart_utils.h" 11 | #include "http_utils.h" 12 | #include "led.h" 13 | #include "version.h" 14 | #include "wifi_utils.h" 15 | #include 16 | 17 | 18 | /** 19 | * @brief Buffer to store incoming UART data 20 | */ 21 | String uart_buffer = ""; 22 | 23 | /** 24 | * @brief Mutex to protect access to the UART buffer 25 | */ 26 | SemaphoreHandle_t uart_buffer_Mutex = NULL; 27 | 28 | /** 29 | * @brief Timeout for UART communication in milliseconds 30 | */ 31 | const uint32_t communicationTimeout_ms = 500; 32 | 33 | 34 | 35 | /** 36 | * @brief Ensure the URL has an HTTPS prefix 37 | * 38 | * @param url The URL to check and modify if necessary 39 | * @return String The URL with HTTPS prefix 40 | */ 41 | String ensureHttpsPrefix(String url) { 42 | if (!url.startsWith("http://") && !url.startsWith("https://")) { 43 | return "https://" + url; 44 | } 45 | return url; 46 | } 47 | 48 | /** 49 | * @brief Placeholder function for command execution 50 | * @param argument The argument passed to the command 51 | * @param packet Pointer to the UDP packet (can be null) 52 | */ 53 | void placeholderCommand(String argument, AsyncUDPPacket *packet) {} 54 | 55 | 56 | /** 57 | * @brief Array of available commands 58 | */ 59 | Command commands[] = { 60 | {"VERSION", "VERSION: Get board version", placeholderCommand}, 61 | {"WIFI_CONNECT", "WIFI_CONNECT ", placeholderCommand}, 62 | {"WIFI_SET_SSID", "WIFI_SET_SSID ssid ", placeholderCommand}, 63 | {"WIFI_SET_PASSWORD", "WIFI_SET_PASSWORD password ", placeholderCommand}, 64 | {"WIFI_ACTIVATE", "WIFI_ACTIVATE", placeholderCommand}, 65 | {"WIFI_DEACTIVATE", "WIFI_DEACTIVATE", placeholderCommand}, 66 | {"WIFI_LIST", "WIFI_LIST", placeholderCommand}, 67 | {"WIFI_STATUS", "WIFI_STATUS: Show wifi status CONNECTED / DISCONNECTED", 68 | placeholderCommand}, 69 | {"WIFI_GET_ACTIVE_SSID", "WIFI_GET_ACTIVE_SSID: ", 70 | placeholderCommand}, 71 | {"WIFI_GET_LOCAL_IP", "WIFI_GET_LOCAL_IP", placeholderCommand}, 72 | {"GET", "GET ", placeholderCommand}, 73 | {"GET_STREAM", "GET_STREAM ", placeholderCommand}, 74 | {"FILE_STREAM", "FILE_STREAM ", placeholderCommand}, 75 | {"POST", "POST ", placeholderCommand}, 76 | {"POST_STREAM", "POST_STREAM ", placeholderCommand}, 77 | {"BUILD_HTTP_METHOD", "BUILD_HTTP_METHOD ", placeholderCommand}, 78 | {"BUILD_HTTP_URL", "BUILD_HTTP_URL ", placeholderCommand}, 79 | {"BUILD_HTTP_HEADER", "BUILD_HTTP_HEADER ", placeholderCommand}, 80 | {"BUILD_HTTP_PAYLOAD", "BUILD_HTTP_PAYLOAD ", placeholderCommand}, 81 | {"REMOVE_HTTP_HEADER", "REMOVE_HTTP_HEADER ", placeholderCommand}, 82 | {"RESET_HTTP_CONFIG", "RESET_HTTP_CONFIG", placeholderCommand}, 83 | {"BUILD_HTTP_SHOW_RESPONSE_HEADERS", 84 | "BUILD_HTTP_SHOW_RESPONSE_HEADERS ", placeholderCommand}, 85 | {"BUILD_HTTP_IMPLEMENTATION", "BUILD_HTTP_IMPLEMENTATION ", 86 | placeholderCommand}, 87 | {"EXECUTE_HTTP_CALL", "EXECUTE_HTTP_CALL", placeholderCommand}, 88 | {"BUILD_HTTP_SHOW_CONFIG", 89 | "BUILD_HTTP_SHOW_CONFIG: Show current HTTP configuration", 90 | placeholderCommand}, 91 | {"MESSAGE_UDP", "MESSAGE_UDP ", placeholderCommand}, 92 | {"?", "type ? to print help", placeholderCommand}, 93 | {"HELP", "HELP", placeholderCommand}}; 94 | 95 | /** 96 | * @brief UART0 receive callback function 97 | * 98 | * This function is called when data is available on UART0. 99 | * It reads the data into the uart_buffer with a timeout. 100 | */ 101 | void UART0_RX_CB() { 102 | if (xSemaphoreTake(uart_buffer_Mutex, portMAX_DELAY)) { 103 | uint32_t now = millis(); 104 | while ((millis() - now) < communicationTimeout_ms) { 105 | if (UART0.available()) { 106 | uart_buffer += (char)UART0.read(); 107 | now = millis(); 108 | } 109 | } 110 | xSemaphoreGive(uart_buffer_Mutex); 111 | } 112 | } 113 | 114 | /** 115 | * @brief Set the WiFi SSID 116 | * 117 | * @param argument The SSID to set 118 | * @param packet Pointer to the UDP packet (can be null) 119 | */ 120 | void setSSIDCommand(String argument, AsyncUDPPacket *packet) { 121 | printResponse("WIFI_SSID: " + argument, packet); 122 | setSSID(argument); 123 | led_set_green(255); 124 | delay(1000); 125 | led_set_green(0); 126 | } 127 | 128 | /** 129 | * @brief Handle the incoming serial input 130 | * 131 | * This function processes the data in uart_buffer, 132 | * extracts the command and argument, and calls the appropriate handler. 133 | */ 134 | void setPasswordCommand(String argument, AsyncUDPPacket *packet) { 135 | setPassword(argument); 136 | printResponse("WIFI_PASSWORD: " + argument, packet); 137 | led_set_green(255); 138 | delay(1000); 139 | led_set_green(0); 140 | } 141 | 142 | /** 143 | * @brief Activate WiFi connection 144 | * @param argument Unused parameter 145 | * @param packet Pointer to AsyncUDPPacket, unused in this function 146 | */ 147 | void activateWiFiCommand(String argument, AsyncUDPPacket *packet) { 148 | connectToWiFi(); 149 | } 150 | 151 | /** 152 | * @brief Check and print WiFi connection status 153 | * @param argument Unused parameter 154 | * @param packet Pointer to AsyncUDPPacket for response 155 | */ 156 | void checkWiFiStatusCommand(String argument, AsyncUDPPacket *packet) { 157 | if (WiFi.status() == WL_CONNECTED) { 158 | printResponse("WIFI_STATUS: CONNECTED", packet); 159 | } else { 160 | printResponse("WIFI_STATUS: DISCONNECTED", packet); 161 | } 162 | } 163 | 164 | /** 165 | * @brief Disconnect from WiFi 166 | * @param argument Unused parameter 167 | * @param packet Pointer to AsyncUDPPacket, unused in this function 168 | */ 169 | void disconnectWiFiCommand(String argument, AsyncUDPPacket *packet) { 170 | disconnectFromWiFi(); 171 | } 172 | 173 | /** 174 | * @brief List available WiFi networks 175 | * @param argument Unused parameter 176 | * @param packet Pointer to AsyncUDPPacket for response 177 | */ 178 | void listWiFiCommand(String argument, AsyncUDPPacket *packet) { 179 | String list = listWiFiNetworks(); 180 | printResponse(list, packet); 181 | } 182 | 183 | /** 184 | * @brief Get and print local WiFi IP address 185 | * @param argument Unused parameter 186 | * @param packet Pointer to AsyncUDPPacket for response 187 | */ 188 | void getWifiLocalIp(String argument, AsyncUDPPacket *packet) { 189 | String ipString = getLocalIpString(); 190 | printResponse(ipString, packet); 191 | } 192 | 193 | /** 194 | * @brief Perform HTTP GET request 195 | * @param argument URL for the GET request 196 | * @param packet Pointer to AsyncUDPPacket for response 197 | */ 198 | void getCommand(String argument, AsyncUDPPacket *packet) { 199 | argument = ensureHttpsPrefix(argument); 200 | printResponse("GET request to: " + argument, packet); 201 | makeHttpRequest(argument, packet); 202 | } 203 | 204 | /** 205 | * @brief Perform HTTP GET request for file streaming 206 | * @param argument URL for the GET request 207 | * @param packet Pointer to AsyncUDPPacket for response 208 | */ 209 | void getFileStreamCommand(String argument, AsyncUDPPacket *packet) { 210 | argument = ensureHttpsPrefix(argument); 211 | (argument, packet); 212 | makeHttpFileRequest(argument, packet); 213 | } 214 | 215 | /** 216 | * @brief Perform streaming HTTP GET request 217 | * @param argument URL for the GET request 218 | * @param packet Pointer to AsyncUDPPacket for response 219 | */ 220 | void getStreamCommand(String argument, AsyncUDPPacket *packet) { 221 | argument = ensureHttpsPrefix(argument); 222 | printResponse("GET_STREAM: " + argument, packet); 223 | makeHttpRequestStream(argument, packet); 224 | } 225 | 226 | /** 227 | * @brief Perform HTTP POST request 228 | * @param argument String containing URL and JSON payload 229 | * @param packet Pointer to AsyncUDPPacket for response 230 | */ 231 | void postCommand(String argument, AsyncUDPPacket *packet) { 232 | int jsonStartIndex = argument.indexOf(' ') + 1; 233 | String url = argument.substring(0, jsonStartIndex - 1); 234 | String jsonPayload = argument.substring(jsonStartIndex); 235 | printResponse("POST: " + url, packet); 236 | printResponse("Payload: " + jsonPayload, packet); 237 | makeHttpPostRequest(url, jsonPayload, packet); 238 | } 239 | 240 | 241 | /** 242 | * @brief Perform streaming HTTP POST request 243 | * @param argument String containing URL and JSON payload 244 | * @param packet Pointer to AsyncUDPPacket for response 245 | */ 246 | void postStreamCommand(String argument, AsyncUDPPacket *packet) { 247 | int jsonStartIndex = argument.indexOf(' ') + 1; 248 | String url = argument.substring(0, jsonStartIndex - 1); 249 | String jsonPayload = argument.substring(jsonStartIndex); 250 | makeHttpPostFileRequest(url, jsonPayload, packet); 251 | } 252 | 253 | 254 | /** 255 | * @brief Set HTTP method for request builder 256 | * @param argument HTTP method to set 257 | * @param packet Pointer to AsyncUDPPacket for response 258 | */ 259 | void buildHttpMethodCommand(String argument, AsyncUDPPacket *packet) { 260 | setHttpMethod(argument, packet); 261 | } 262 | 263 | /** 264 | * @brief Set URL for request builder 265 | * @param argument URL to set 266 | * @param packet Pointer to AsyncUDPPacket for response 267 | */ 268 | void buildHttpUrlCommand(String argument, AsyncUDPPacket *packet) { 269 | setHttpUrl(argument, packet); 270 | } 271 | 272 | /** 273 | * @brief Add HTTP header for request builder 274 | * @param argument Header to add (format: "key:value") 275 | * @param packet Pointer to AsyncUDPPacket for response 276 | */ 277 | void buildHttpHeaderCommand(String argument, AsyncUDPPacket *packet) { 278 | addHttpHeader(argument, packet); 279 | } 280 | 281 | /** 282 | * @brief Set HTTP payload for request builder 283 | * @param argument Payload to set 284 | * @param packet Pointer to AsyncUDPPacket for response 285 | */ 286 | void buildHttpPayloadCommand(String argument, AsyncUDPPacket *packet) { 287 | setHttpPayload(argument, packet); 288 | } 289 | 290 | /** 291 | * @brief Remove HTTP header from request builder 292 | * @param argument Header key to remove 293 | * @param packet Pointer to AsyncUDPPacket for response 294 | */ 295 | void removeHttpHeaderCommand(String argument, AsyncUDPPacket *packet) { 296 | removeHttpHeader(argument, packet); 297 | } 298 | 299 | /** 300 | * @brief Reset HTTP configuration in request builder 301 | * @param argument Unused parameter 302 | * @param packet Pointer to AsyncUDPPacket for response 303 | */ 304 | void resetHttpConfigCommand(String argument, AsyncUDPPacket *packet) { 305 | resetHttpConfig(packet); 306 | } 307 | 308 | /** 309 | * @brief Get current HTTP builder configuration 310 | * @param argument Unused parameter 311 | * @param packet Pointer to AsyncUDPPacket for response 312 | */ 313 | void getHttpBuilderConfigCommand(String argument, AsyncUDPPacket *packet) { 314 | getHttpBuilderConfig(packet); 315 | } 316 | 317 | /** 318 | * @brief Set HTTP implementation (STREAM or CALL) 319 | * @param argument Implementation type ("STREAM" or "CALL") 320 | * @param packet Pointer to AsyncUDPPacket for response 321 | */ 322 | void buildHttpImplementationCommand(String argument, AsyncUDPPacket *packet) { 323 | // Check if argument is a valid string of either STREAM or CALL; 324 | // if not, print an error message 325 | if (argument != "STREAM" && argument != "CALL") { 326 | printResponse( 327 | "HTTP_ERROR: Invalid HTTP implementation. Supported implementations: " 328 | "STREAM, CALL", 329 | packet); 330 | return; 331 | } 332 | setHttpImplementation(argument, packet); 333 | } 334 | 335 | 336 | /** 337 | * @brief Set whether to show response headers 338 | * @param argument "true" or "false" 339 | * @param packet Pointer to AsyncUDPPacket for response 340 | */ 341 | void buildHttpShowResponseHeadersCommand(String argument, 342 | AsyncUDPPacket *packet) { 343 | setShowResponseHeaders(argument.equalsIgnoreCase("true"), packet); 344 | } 345 | 346 | /** 347 | * @brief Execute the built HTTP call 348 | * @param argument Unused parameter 349 | * @param packet Pointer to AsyncUDPPacket for response 350 | */ 351 | void executeHttpCallCommand(String argument, AsyncUDPPacket *packet) { 352 | executeHttpCall(packet); 353 | } 354 | 355 | /** 356 | * @brief Get active WiFi network SSID 357 | * @param argument Unused parameter 358 | * @param packet Pointer to AsyncUDPPacket for response 359 | */ 360 | void wifiNetworkCommand(String argument, AsyncUDPPacket *packet) { 361 | if (WiFi.status() == WL_CONNECTED) { 362 | printResponse("WIFI_GET_ACTIVE_SSID: " + WiFi.SSID(), packet); 363 | } else { 364 | printResponse("WIFI_GET_ACTIVE_SSID: Not connected", packet); 365 | } 366 | } 367 | 368 | 369 | /** 370 | * @brief Connect to WiFi network 371 | * @param argument String containing SSID and password 372 | * @param packet Pointer to AsyncUDPPacket for response 373 | */ 374 | void connectCommand(String argument, AsyncUDPPacket *packet) { 375 | int spaceIndex = argument.indexOf(' '); 376 | if (spaceIndex != -1) { 377 | String ssid = argument.substring(0, spaceIndex); 378 | String password = argument.substring(spaceIndex + 1); 379 | setSSID(ssid); 380 | setPassword(password); 381 | connectToWiFi(); 382 | } else { 383 | printResponse("WIFI_ERROR: Invalid CONNECT command format. Use: CONNECT " 384 | " ", 385 | packet); 386 | } 387 | } 388 | 389 | 390 | /** 391 | * @brief Get board version 392 | * @param argument Unused parameter 393 | * @param packet Pointer to AsyncUDPPacket for response 394 | */ 395 | void getBoardVersionCommand(String argument, AsyncUDPPacket *packet) { 396 | printResponse("VERSION: " + String(version), packet); 397 | } 398 | 399 | 400 | /** 401 | * @brief Display help information for available commands 402 | * @param argument Unused parameter 403 | * @param packet Pointer to AsyncUDPPacket for response 404 | */ 405 | void helpCommand(String argument, AsyncUDPPacket *packet) { 406 | printResponse("Available Commands:", packet); 407 | for (int i = 0; i < sizeof(commands) / sizeof(commands[0]); i++) { 408 | printResponse(commands[i].description, packet); 409 | } 410 | } 411 | 412 | /** 413 | * @brief Handle incoming command 414 | * @param command Command string 415 | * @param argument Command argument string 416 | * @param packet Pointer to AsyncUDPPacket for response 417 | */ 418 | void handleCommand(String command, String argument, AsyncUDPPacket *packet) { 419 | for (int i = 0; i < sizeof(commands) / sizeof(commands[0]); i++) { 420 | if (commands[i].name == command) { 421 | commands[i].execute(argument, packet); 422 | return; 423 | } 424 | } 425 | printResponse("Unknown command", packet); 426 | } 427 | 428 | /** 429 | * @brief Handle the incoming serial input 430 | * 431 | * This function processes the data in uart_buffer, 432 | * extracts the command and argument, and calls the appropriate handler. 433 | */ 434 | void handleSerialInput() { 435 | if (uart_buffer.length() > 0) { 436 | if (xSemaphoreTake(uart_buffer_Mutex, portMAX_DELAY)) { 437 | uart_buffer.trim(); 438 | 439 | String command; 440 | String argument; 441 | 442 | int spaceIndex = uart_buffer.indexOf(' '); 443 | if (spaceIndex != -1) { 444 | command = uart_buffer.substring(0, spaceIndex); 445 | argument = uart_buffer.substring(spaceIndex + 1); 446 | } else { 447 | command = uart_buffer; 448 | } 449 | 450 | handleCommand(command, argument, nullptr); 451 | 452 | uart_buffer = ""; 453 | xSemaphoreGive(uart_buffer_Mutex); 454 | } 455 | } 456 | } 457 | 458 | 459 | /** 460 | * @brief Handle UDP message command 461 | * 462 | * This function parses the argument string to extract the message, remote IP address, 463 | * and remote port. It then sends a UDP message to the specified address and port. 464 | * 465 | * @param argument String containing the message, remote IP, and remote port 466 | * Format: " " 467 | * @param packet Pointer to AsyncUDPPacket, unused in this function 468 | * 469 | * @note The function expects the argument to be in the format: 470 | * " " 471 | * where can contain spaces, is a valid IP address, 472 | * and is a valid port number. 473 | * 474 | * @warning If the IP address format is invalid, an error message is printed to UART0, 475 | * and the function returns without sending the message. 476 | * 477 | * @see sendUDPMessage() 478 | */ 479 | void handleMessageUDPCommand(String argument, AsyncUDPPacket *packet) { 480 | // Find the position of the last two spaces 481 | int lastSpaceIndex = argument.lastIndexOf(' '); 482 | int secondLastSpaceIndex = argument.lastIndexOf(' ', lastSpaceIndex - 1); 483 | 484 | // Extract remotePort 485 | uint16_t remotePort = argument.substring(lastSpaceIndex + 1).toInt(); 486 | 487 | // Extract remoteIP 488 | String remoteIPString = argument.substring(secondLastSpaceIndex + 1, lastSpaceIndex); 489 | IPAddress remoteIP; 490 | if (!remoteIP.fromString(remoteIPString)) { 491 | UART0.println("ERROR: Invalid IP address format"); 492 | return; 493 | } 494 | 495 | // Extract message 496 | String message = argument.substring(0, secondLastSpaceIndex); 497 | 498 | // Send the UDP message 499 | sendUDPMessage(message.c_str(), remoteIP, remotePort); 500 | 501 | UART0.println("UDP message sent: " + message); 502 | UART0.println("To IP: " + remoteIPString + ", Port: " + String(remotePort)); 503 | } 504 | 505 | /** 506 | * @brief Initialize the commands array with actual functions 507 | */ 508 | void initializeCommands() { 509 | commands[0].execute = getBoardVersionCommand; 510 | commands[1].execute = connectCommand; 511 | commands[2].execute = setSSIDCommand; 512 | commands[3].execute = setPasswordCommand; 513 | commands[4].execute = activateWiFiCommand; 514 | commands[5].execute = disconnectWiFiCommand; 515 | commands[6].execute = listWiFiCommand; 516 | commands[7].execute = checkWiFiStatusCommand; 517 | commands[8].execute = wifiNetworkCommand; 518 | commands[9].execute = getWifiLocalIp; 519 | commands[10].execute = getCommand; 520 | commands[11].execute = getStreamCommand; 521 | commands[12].execute = getFileStreamCommand; 522 | commands[13].execute = postCommand; 523 | commands[14].execute = postStreamCommand; 524 | commands[15].execute = buildHttpMethodCommand; 525 | commands[16].execute = buildHttpUrlCommand; 526 | commands[17].execute = buildHttpHeaderCommand; 527 | commands[18].execute = buildHttpPayloadCommand; 528 | commands[19].execute = removeHttpHeaderCommand; 529 | commands[20].execute = resetHttpConfigCommand; 530 | commands[21].execute = buildHttpShowResponseHeadersCommand; 531 | commands[22].execute = buildHttpImplementationCommand; 532 | commands[23].execute = executeHttpCallCommand; 533 | commands[24].execute = getHttpBuilderConfigCommand; 534 | commands[25].execute = handleMessageUDPCommand; 535 | commands[26].execute = helpCommand; 536 | commands[27].execute = helpCommand; 537 | } 538 | 539 | /** 540 | * @brief Initialize UART commands and related resources 541 | */ 542 | void init_cmds() { 543 | uart_buffer_Mutex = xSemaphoreCreateMutex(); 544 | initializeCommands(); 545 | // Other setup code... 546 | } -------------------------------------------------------------------------------- /uart_utils.h: -------------------------------------------------------------------------------- 1 | #ifndef UART_UTILS_H 2 | #define UART_UTILS_H 3 | 4 | #if ARDUINO_USB_CDC_ON_BOOT 5 | #define UART0 Serial0 6 | #else 7 | #define UART0 Serial 8 | #endif 9 | 10 | #include 11 | #include 12 | #include 13 | // Command structure 14 | struct Command { 15 | String name; 16 | String description; 17 | void (*execute)(String argument, AsyncUDPPacket *packet); 18 | }; 19 | 20 | extern String uart_buffer; 21 | extern SemaphoreHandle_t uart_buffer_Mutex; 22 | extern const uint32_t communicationTimeout_ms; 23 | 24 | void UART0_RX_CB(); 25 | void setSSIDCommand(String argument, AsyncUDPPacket *packet); 26 | void setPasswordCommand(String argument, AsyncUDPPacket *packet); 27 | void activateWiFiCommand(String argument, AsyncUDPPacket *packet); 28 | void disconnectWiFiCommand(String argument, AsyncUDPPacket *packet); 29 | void listWiFiCommand(String argument, AsyncUDPPacket *packet); 30 | void getCommand(String argument, AsyncUDPPacket *packet); 31 | void getStreamCommand(String argument, AsyncUDPPacket *packet); 32 | void postCommand(String argument, AsyncUDPPacket *packet); 33 | void buildHttpMethodCommand(String argument, AsyncUDPPacket *packet); 34 | void buildHttpUrlCommand(String argument, AsyncUDPPacket *packet); 35 | void buildHttpHeaderCommand(String argument, AsyncUDPPacket *packet); 36 | void buildHttpPayloadCommand(String argument, AsyncUDPPacket *packet); 37 | void removeHttpHeaderCommand(String argument, AsyncUDPPacket *packet); 38 | void resetHttpConfigCommand(String argument, AsyncUDPPacket *packet); 39 | void buildHttpImplementationCommand(String argument, AsyncUDPPacket *packet); 40 | void buildHttpShowResponseHeadersCommand(String argument, 41 | AsyncUDPPacket *packet); 42 | void executeHttpCallCommand(String argument, AsyncUDPPacket *packet); 43 | void connectCommand(String argument, AsyncUDPPacket *packet); 44 | void helpCommand(String argument, AsyncUDPPacket *packet); 45 | void handleCommand(String command, String argument, AsyncUDPPacket *packet); 46 | void handleSerialInput(); 47 | String ensureHttpsPrefix(String url); 48 | void handleCommand(String command, String argument); 49 | void init_cmds(); 50 | 51 | #endif // UART_UTILS_H -------------------------------------------------------------------------------- /version.h: -------------------------------------------------------------------------------- 1 | #ifndef VERSION_H 2 | #define VERSION_H 3 | 4 | 5 | /** 6 | * @brief Version number imported by the help and console splash functions. 7 | */ 8 | static const char *version = "0.1.11"; 9 | 10 | #endif // VERSION_H -------------------------------------------------------------------------------- /wifi_utils.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file wifi_utils.cpp 3 | * @brief Utilities for WiFi connection and UDP communication 4 | * 5 | * This file contains functions for managing WiFi connections, 6 | * including connecting to networks, scanning for available networks, 7 | * and sending UDP messages. It also provides utilities for setting 8 | * and retrieving WiFi credentials. 9 | */ 10 | 11 | 12 | #include "wifi_utils.h" 13 | #include "led.h" 14 | #include "uart_utils.h" 15 | #include 16 | #include 17 | 18 | // Hold values for pass and ssid 19 | static String ssid; 20 | static String password; 21 | 22 | // Create a UDP instance for network communication 23 | extern AsyncUDP udp; 24 | 25 | // WIFI Connection settings 26 | const int MAX_RETRY_COUNT = 10; // Maximum number of retries 27 | const int RETRY_DELAY_MS = 1000; 28 | 29 | /** 30 | * @brief Connect to the WiFi network 31 | * 32 | * This function attempts to connect to the WiFi network using the provided 33 | * SSID and password. It will retry a few times before giving up. 34 | */ 35 | void connectToWiFi() { 36 | 37 | // Check against empty SSID and password 38 | if (ssid.isEmpty()) { 39 | UART0.println("WIFI_ERROR: SSID is missing"); 40 | return; 41 | } 42 | 43 | if (password.isEmpty()) { 44 | UART0.println("WIFI_ERROR: Password is missing"); 45 | return; 46 | } 47 | 48 | // Start up wifi 49 | led_set_blue(255); 50 | WiFi.mode(WIFI_STA); 51 | WiFi.begin(ssid.c_str(), password.c_str()); 52 | 53 | // Retry counter 54 | int retryCount = 0; 55 | 56 | // Try and connect 57 | led_set_blue(0); 58 | while (WiFi.status() != WL_CONNECTED && retryCount < MAX_RETRY_COUNT) { 59 | led_set_blue(255); 60 | delay(RETRY_DELAY_MS); 61 | retryCount++; 62 | UART0.println("WIFI_CONNECT: Connecting to WiFi... try " + 63 | String(retryCount) + "/" + String(MAX_RETRY_COUNT)); 64 | 65 | led_set_blue(0); 66 | 67 | // Check for specific WiFi statuses 68 | if (WiFi.status() == WL_CONNECT_FAILED) { 69 | UART0.println("WIFI_ERROR: Failed to connect to WiFi: Incorrect password " 70 | "or other issue."); 71 | break; 72 | } 73 | } 74 | 75 | // After retries still not connected ? Return error 76 | if (WiFi.status() != WL_CONNECTED) { 77 | led_error(); 78 | UART0.println("WIFI_ERROR: Failed to connect to WiFi"); 79 | return; 80 | } 81 | 82 | // Connected to WiFi 83 | led_set_blue(0); 84 | led_set_green(255); 85 | UART0.println("WIFI_CONNECTED: Connected to " + ssid); 86 | UART0.print("WIFI_INFO: IP Address: "); 87 | UART0.println(WiFi.localIP()); 88 | led_set_green(0); 89 | 90 | // Start listening for UDP packets 91 | if (udp.listen(1234)) { 92 | UART0.println("WIFI_INFO: UDP listening on port 1234"); 93 | 94 | udp.onPacket([](AsyncUDPPacket packet) { 95 | String receivedData = String((char *)packet.data(), packet.length()); 96 | UART0.println("WIFI_UDP_INCOMING_DATA: " + receivedData); 97 | 98 | String command; 99 | String argument; 100 | 101 | // Parse commands and arguments 102 | int spaceIndex = receivedData.indexOf(' '); 103 | if (spaceIndex != -1) { 104 | command = receivedData.substring(0, spaceIndex); 105 | argument = receivedData.substring(spaceIndex + 1); 106 | } else { 107 | command = receivedData; 108 | } 109 | 110 | // Special UDP Connection method 111 | // Direct Message stream to uart from UDP 112 | if (command == "MESSAGE") { 113 | // Stream the message directly to UART and return 114 | UART0.println("MESSAGE: " + argument); 115 | return; 116 | } 117 | 118 | // Uart file command system 119 | handleCommand(command, argument, &packet); 120 | }); 121 | } 122 | 123 | UART0.println("WIFI_SUCCESS: WiFi connected"); 124 | } 125 | 126 | 127 | /** 128 | * @brief Disconnect from the current WiFi network 129 | */ 130 | void disconnectFromWiFi() { 131 | WiFi.disconnect(); 132 | UART0.println("WIFI_DISCONNECT: Wifi disconnected"); 133 | } 134 | 135 | /** 136 | * @brief List available WiFi networks 137 | * 138 | * @return String A comma-separated list of available WiFi SSIDs 139 | */ 140 | String listWiFiNetworks() { 141 | UART0.println("WIFI_LIST: Scanning WiFi networks..."); 142 | led_set_blue(255); 143 | int n = WiFi.scanNetworks(); 144 | String result = ""; 145 | for (int i = 0; i < n; ++i) { 146 | result += WiFi.SSID(i); 147 | if (i < n - 1) { 148 | result += ", "; 149 | } 150 | } 151 | led_set_blue(0); 152 | return result; 153 | } 154 | 155 | /** 156 | * @brief Set the SSID for WiFi connection 157 | * 158 | * @param newSSID The new SSID to set 159 | */ 160 | void setSSID(String newSSID) { ssid = newSSID; } 161 | 162 | 163 | /** 164 | * @brief Set the password for WiFi connection 165 | * 166 | * @param newPassword The new password to set 167 | */ 168 | void setPassword(String newPassword) { password = newPassword; } 169 | 170 | /** 171 | * @brief Get the current SSID 172 | * 173 | * @return const char* Pointer to the current SSID 174 | */ 175 | const char *getSSID() { return ssid.c_str(); } 176 | 177 | /** 178 | * @brief Get the current password 179 | * 180 | * @return const char* Pointer to the current password 181 | */ 182 | const char *getPassword() { return password.c_str(); } 183 | 184 | /** 185 | * @brief Send a UDP message 186 | * 187 | * @param message The message to send 188 | * @param remoteIP The IP address of the remote device 189 | * @param remotePort The port number of the remote device 190 | */ 191 | void sendUDPMessage(const char* message, IPAddress remoteIP, uint16_t remotePort) { 192 | led_set_blue(255); 193 | udp.writeTo((const uint8_t*)message, strlen(message), remoteIP, remotePort); 194 | led_set_blue(0); 195 | } 196 | 197 | /** 198 | * @brief Get the local IP address as a string 199 | * 200 | * @return String The local IP address 201 | */ 202 | String getLocalIpString() { 203 | led_set_blue(255); 204 | led_set_blue(0); 205 | return WiFi.localIP().toString(); 206 | } 207 | -------------------------------------------------------------------------------- /wifi_utils.h: -------------------------------------------------------------------------------- 1 | #ifndef WIFI_UTILS_H 2 | #define WIFI_UTILS_H 3 | 4 | #include 5 | 6 | void connectToWiFi(); 7 | void setSSID(String ssid); 8 | void setPassword(String password); 9 | void disconnectFromWiFi(); 10 | const char *getSSID(); 11 | const char *getPassword(); 12 | String listWiFiNetworks(); 13 | String getLocalIpString(); 14 | void sendUDPMessage(const char* message, IPAddress remoteIP, uint16_t remotePort); 15 | 16 | #endif // WIFI_UTILS_H --------------------------------------------------------------------------------