├── .DS_Store ├── .clang-format ├── .github ├── dependabot.yml └── workflows │ ├── main.yml │ └── pull-request.yml ├── .gitignore ├── CMakeLists.txt ├── CODEOWNERS ├── LICENSE ├── README.md ├── boards ├── default_8mb.csv ├── gen2.json ├── max_app_4mb.csv ├── tidbyt.json ├── tronbyt-S3.json └── wroom32.json ├── components └── esp_websocket_client │ ├── .cz.yaml │ ├── CHANGELOG.md │ ├── CMakeLists.txt │ ├── Kconfig │ ├── LICENSE │ ├── README.md │ ├── esp_websocket_client.c │ ├── examples │ ├── linux │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ ├── main │ │ │ ├── CMakeLists.txt │ │ │ ├── Kconfig.projbuild │ │ │ └── websocket_linux.c │ │ ├── sdkconfig.ci.coverage │ │ ├── sdkconfig.ci.linux │ │ └── sdkconfig.defaults │ └── target │ │ ├── CMakeLists.txt │ │ ├── README.md │ │ ├── main │ │ ├── CMakeLists.txt │ │ ├── Kconfig.projbuild │ │ ├── certs │ │ │ ├── ca_cert.pem │ │ │ ├── client_cert.pem │ │ │ ├── client_key.pem │ │ │ └── server │ │ │ │ ├── server_cert.pem │ │ │ │ └── server_key.pem │ │ ├── idf_component.yml │ │ └── websocket_example.c │ │ ├── pytest_websocket.py │ │ ├── sdkconfig.ci │ │ ├── sdkconfig.ci.dynamic_buffer │ │ ├── sdkconfig.ci.mutual_auth │ │ └── sdkconfig.ci.plain_tcp │ ├── idf_component.yml │ ├── include │ └── esp_websocket_client.h │ └── test │ ├── CMakeLists.txt │ ├── main │ ├── CMakeLists.txt │ └── test_websocket_client.c │ ├── pytest_websocket.py │ ├── sdkconfig.ci │ └── sdkconfig.defaults ├── correct_firmware_hash.py ├── docs ├── CLA.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── SECURITY.md ├── SUPPORT.md └── assets │ └── social.png ├── extra_scripts ├── c.h_to_webp.py ├── output.webp ├── pre.py ├── reset.py ├── string_replace_bin.py └── webp_to_c.h.py ├── include └── README ├── lib ├── README ├── assets │ ├── assets.h │ ├── config_c │ ├── noapps_webp.c.orig │ ├── parrot_c │ ├── tronbyt.webp │ ├── tronbyt_c │ ├── tronbyt_config.webp │ └── tronbyt_config.xcf └── webp │ └── library.json ├── platformio.ini ├── sdkconfig.defaults ├── sdkconfig.pixoticker.defaults ├── sdkconfig.tronbyt-S3.defaults ├── secrets.json.example ├── secrets_place.json ├── src ├── CMakeLists.txt ├── display.cpp ├── display.h ├── flash.c ├── flash.h ├── gfx.c ├── gfx.h ├── main.c ├── remote.c ├── remote.h ├── wifi.c └── wifi.h └── test └── README /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tronbyt/firmware-esp32/ecc6fa89bab95b88a2d3b8d2fd1785dd3f4811b9/.DS_Store -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | BasedOnStyle: Google 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Build and Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - modernize 8 | tags: 9 | - "v*" 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | 18 | - uses: actions/cache@v4 19 | with: 20 | path: | 21 | ~/.cache/pip 22 | ~/.platformio/.cache 23 | key: ${{ runner.os }}-pio 24 | 25 | - uses: actions/setup-python@v5 26 | with: 27 | python-version: "3.13" 28 | 29 | - name: Install PlatformIO Core 30 | run: pip install platformio==6.1.18 31 | 32 | - name: Build PlatformIO Project 33 | run: pio run 34 | -------------------------------------------------------------------------------- /.github/workflows/pull-request.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - "**" 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | 15 | - uses: actions/cache@v4 16 | with: 17 | path: | 18 | ~/.cache/pip 19 | ~/.platformio/.cache 20 | key: ${{ runner.os }}-pio 21 | 22 | - uses: actions/setup-python@v5 23 | with: 24 | python-version: "3.13" 25 | 26 | - name: Install PlatformIO Core 27 | run: pip install platformio==6.1.18 28 | 29 | - name: Build PlatformIO Project 30 | run: pio run 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | sdkconfig 3 | sdkconfig.old 4 | .vscode 5 | .production 6 | secrets.json 7 | .DS_Store 8 | build 9 | .DS_Store 10 | dependencies.lock 11 | _secrets.json 12 | sdkconfig.matrixportal-s3 13 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16.0) 2 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 3 | project(firmware) 4 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @betterengineering @matslina 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tronbyt Firmware 2 | 3 | [![Discord Server](https://img.shields.io/discord/928484660785336380?style=flat-square)](https://discord.gg/r45MXG4kZc) 4 | 5 | This repository contains a community supported firmware for the Tidbyt hardware 🤓. 6 | 7 | ## Warning 8 | 9 | ⚠️ Warning! Flashing your Tidbyt with this firmware or derivatives could fatally 10 | damage your device. As such, flashing your Tidbyt with this firmware or 11 | derivatives voids your warranty and comes without support. 12 | 13 | ## Getting Started 14 | 15 | Follow the setup instructions for [tronbyt-server][3] unless you want to build yourself with platformio. 16 | 17 | ## Building yourself with PlatformIO 18 | 19 | Only follow these instructions if you want to build the firmware yourself. Otherwise let the [tronbyt-server][3] generate the firmware file for you. 20 | This project uses PlatformIO to build, flash, and monitor firmware on the Tidbyt. 21 | To get started, you will need to download [PlatformIO Core][2] on your computer. 22 | 23 | Additionally, this firmware is designed to work with https://github.com/tronbyt/server or 24 | you can point this firmware at any URL that hosts a WebP image that is optimized for the Tidbyt display. 25 | 26 | To flash the custom firmware on your device, run the following after replacing 27 | the variables in secrets.json.example with your desired own information and renaming it to `secrets.json` 28 | If using tronbyt_manager in docker replace the ip address to the docker host's ip address. 29 | 30 | ``` 31 | { 32 | "WIFI_SSID": "myssiD", 33 | "WIFI_PASSWORD": "", 34 | "REMOTE_URL=": "http://homeServer.local:8000/admin/tronbyt_1/next", 35 | "REFRESH_INTERVAL_SECONDS": 10, 36 | "DEFAULT_BRIGHTNESS" : 30 37 | } 38 | ``` 39 | 40 | Then run the following command: 41 | 42 | ``` 43 | pio run --environment tidbyt-gen1 --target upload 44 | ``` 45 | 46 | If you're flashing to a Tidbyt Gen2, just change to the above to use 47 | the `--environment tidbyt-gen2` flag. 48 | 49 | ## Monitoring Logs 50 | 51 | To check the output of your running firmware, run the following: 52 | 53 | ``` 54 | pio device monitor 55 | ``` 56 | 57 | ## Back to Normal 58 | 59 | To get your Tidbyt back to normal, you can run the following to flash the 60 | production firmware onto your Tidbyt: 61 | 62 | ``` 63 | pio run --target reset --environment tidbyt-gen1 64 | ``` 65 | 66 | And if you're working with a Tidbyt Gen 2: 67 | 68 | ``` 69 | pio run --target reset --environment tidbyt-gen2 70 | ``` 71 | 72 | [1]: https://github.com/tidbyt/pixlet 73 | [2]: https://docs.platformio.org/en/latest/core/installation/index.html 74 | [3]: https://github.com/tronbyt/server 75 | -------------------------------------------------------------------------------- /boards/default_8mb.csv: -------------------------------------------------------------------------------- 1 | # Name, Type, SubType, Offset, Size, Flags 2 | nvs, data, nvs, 0x9000, 0x5000, 3 | otadata, data, ota, 0xe000, 0x2000, 4 | app0, app, ota_0, 0x10000, 0x3f0000, 5 | app1, app, ota_1, 0x400000,0x3f0000 -------------------------------------------------------------------------------- /boards/gen2.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": { 3 | "core": "esp32", 4 | "f_cpu": "240000000L", 5 | "f_flash": "40000000L", 6 | "flash_mode": "dio", 7 | "mcu": "esp32", 8 | "partitions": "boards/default_8MB.csv", 9 | "variant": "esp32", 10 | "esp-idf": { 11 | "sdkconfig_path": "sdkconfig" 12 | } 13 | }, 14 | "connectivity": [ 15 | "wifi", 16 | "bluetooth", 17 | "ethernet", 18 | "can" 19 | ], 20 | "debug": { 21 | "default_tool": "ftdi", 22 | "onboard_tools": [ 23 | "ftdi" 24 | ], 25 | "openocd_board": "esp32-wrover.cfg" 26 | }, 27 | "frameworks": [ 28 | "arduino", 29 | "espidf" 30 | ], 31 | "name": "Espressif ESP32 Dev Module", 32 | "upload": { 33 | "flash_size": "8MB", 34 | "maximum_ram_size": 327680, 35 | "maximum_size": 8388608, 36 | "require_upload_port": true, 37 | "speed": 460800, 38 | "protocols": [ 39 | "esptool", 40 | "ftdi" 41 | ] 42 | }, 43 | "url": "https://tidbyt.com", 44 | "vendor": "Tidbyt" 45 | } -------------------------------------------------------------------------------- /boards/max_app_4mb.csv: -------------------------------------------------------------------------------- 1 | # Name, Type, SubType, Offset, Size, Flags 2 | nvs, data, nvs, 0x9000, 0x5000, 3 | otadata, data, ota, 0xe000, 0x2000, 4 | app0, app, factory, 0x10000, 0x3E0000, 5 | coredump, data, coredump,0x3F0000,0x10000, -------------------------------------------------------------------------------- /boards/tidbyt.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": { 3 | "core": "esp32", 4 | "f_cpu": "240000000L", 5 | "f_flash": "40000000L", 6 | "flash_mode": "dio", 7 | "mcu": "esp32", 8 | "partitions": "boards/default_8MB.csv", 9 | "variant": "esp32", 10 | "esp-idf": { 11 | "sdkconfig_path": "sdkconfig" 12 | } 13 | }, 14 | "connectivity": [ 15 | "wifi", 16 | "bluetooth", 17 | "ethernet", 18 | "can" 19 | ], 20 | "debug": { 21 | "default_tool": "ftdi", 22 | "onboard_tools": [ 23 | "ftdi" 24 | ], 25 | "openocd_board": "esp32-wrover.cfg" 26 | }, 27 | "frameworks": [ 28 | "arduino", 29 | "espidf" 30 | ], 31 | "name": "Espressif ESP32 Dev Module", 32 | "upload": { 33 | "flash_size": "8MB", 34 | "maximum_ram_size": 327680, 35 | "maximum_size": 8388608, 36 | "require_upload_port": true, 37 | "speed": 921600, 38 | "protocols": [ 39 | "esptool", 40 | "ftdi" 41 | ] 42 | }, 43 | "url": "https://tidbyt.com", 44 | "vendor": "Tidbyt" 45 | } -------------------------------------------------------------------------------- /boards/tronbyt-S3.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": { 3 | "core": "esp32", 4 | "f_cpu": "240000000L", 5 | "f_flash": "80000000L", 6 | "flash_mode": "qio", 7 | "psram_type": "opi", 8 | "mcu": "esp32s3", 9 | "partitions": "boards/default_8MB.csv", 10 | "variant": "esp32s3", 11 | "esp-idf": { 12 | "sdkconfig_path": "sdkconfig.tronbyt-S3.defaults" 13 | }, 14 | "extra_flags": [ 15 | "-DBOARD_HAS_PSRAM" 16 | ] 17 | }, 18 | "connectivity": [ 19 | "wifi", 20 | "bluetooth" 21 | ], 22 | "debug": { 23 | "default_tool": "esp-builtin", 24 | "onboard_tools": [ 25 | "esp-builtin" 26 | ], 27 | "openocd_target": "esp32s3.cfg" 28 | }, 29 | "frameworks": [ 30 | "arduino", 31 | "espidf" 32 | ], 33 | "name": "Espressif ESP32-S3-DevKitC-1-N16R8V (16 MB QD, 8MB PSRAM)", 34 | "upload": { 35 | "flash_size": "16MB", 36 | "maximum_ram_size": 327680, 37 | "maximum_size": 16777216, 38 | "require_upload_port": true, 39 | "speed": 921600 40 | }, 41 | "url": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/hw-reference/esp32s3/user-guide-devkitc-1.html", 42 | "vendor": "Espressif" 43 | } -------------------------------------------------------------------------------- /boards/wroom32.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": { 3 | "core": "esp32", 4 | "f_cpu": "240000000L", 5 | "f_flash": "40000000L", 6 | "flash_mode": "dio", 7 | "mcu": "esp32", 8 | "partitions": "boards/max_app_4mb.csv", 9 | "variant": "esp32", 10 | "esp-idf": { 11 | "sdkconfig_path": "sdkconfig" 12 | } 13 | }, 14 | "connectivity": [ 15 | "wifi", 16 | "bluetooth", 17 | "ethernet", 18 | "can" 19 | ], 20 | "debug": { 21 | "default_tool": "ftdi", 22 | "onboard_tools": [ 23 | "ftdi" 24 | ], 25 | "openocd_board": "esp32-wrover.cfg" 26 | }, 27 | "frameworks": [ 28 | "arduino", 29 | "espidf" 30 | ], 31 | "name": "Espressif ESP32 Dev Module", 32 | "upload": { 33 | "flash_size": "4MB", 34 | "maximum_ram_size": 327680, 35 | "maximum_size": 8388608, 36 | "require_upload_port": true, 37 | "speed": 921600, 38 | "protocols": [ 39 | "esptool", 40 | "ftdi" 41 | ] 42 | }, 43 | "url": "https://espressif.com", 44 | "vendor": "Espressif" 45 | } -------------------------------------------------------------------------------- /components/esp_websocket_client/.cz.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | commitizen: 3 | bump_message: 'bump(websocket): $current_version -> $new_version' 4 | pre_bump_hooks: python ../../ci/changelog.py esp_websocket_client 5 | tag_format: websocket-v$version 6 | version: 1.4.0 7 | version_files: 8 | - idf_component.yml 9 | -------------------------------------------------------------------------------- /components/esp_websocket_client/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [1.4.0](https://github.com/espressif/esp-protocols/commits/websocket-v1.4.0) 4 | 5 | ### Features 6 | 7 | - Support DS peripheral for mutual TLS ([55385ec3](https://github.com/espressif/esp-protocols/commit/55385ec3)) 8 | 9 | ### Bug Fixes 10 | 11 | - wait for task on destroy ([42674b49](https://github.com/espressif/esp-protocols/commit/42674b49)) 12 | - Fix pytest to verify client correctly ([9046af8f](https://github.com/espressif/esp-protocols/commit/9046af8f)) 13 | - propagate error type ([eeeb9006](https://github.com/espressif/esp-protocols/commit/eeeb9006)) 14 | - fix example buffer leak ([5219c39d](https://github.com/espressif/esp-protocols/commit/5219c39d)) 15 | 16 | ### Updated 17 | 18 | - chore(websocket): align structure members ([beb6e57e](https://github.com/espressif/esp-protocols/commit/beb6e57e)) 19 | - chore(websocket): remove unused client variable ([15d3a01e](https://github.com/espressif/esp-protocols/commit/15d3a01e)) 20 | 21 | ## [1.3.0](https://github.com/espressif/esp-protocols/commits/websocket-v1.3.0) 22 | 23 | ### Features 24 | 25 | - add events for begin/end thread ([d7fa24bc](https://github.com/espressif/esp-protocols/commit/d7fa24bc)) 26 | - Make example to use certificate bundle ([aecf6f80](https://github.com/espressif/esp-protocols/commit/aecf6f80)) 27 | - propagate esp_tls stack error and cert verify flags ([234f579b](https://github.com/espressif/esp-protocols/commit/234f579b)) 28 | - Add option to set and use cert_common_name in Websocket client ([3a6720de](https://github.com/espressif/esp-protocols/commit/3a6720de)) 29 | - adding support for `if_name` when using WSS transport ([333a6893](https://github.com/espressif/esp-protocols/commit/333a6893)) 30 | - allow updating reconnect timeout for retry backoffs ([bd9f0627](https://github.com/espressif/esp-protocols/commit/bd9f0627)) 31 | - allow using external tcp transport handle ([83ea2876](https://github.com/espressif/esp-protocols/commit/83ea2876)) 32 | - adding support for `keep_alive_enable` when using WSS transport ([c728eae5](https://github.com/espressif/esp-protocols/commit/c728eae5)) 33 | 34 | ### Bug Fixes 35 | 36 | - Prevent crash on network disconnect during send ([a453ca1f](https://github.com/espressif/esp-protocols/commit/a453ca1f)) 37 | - use proper interface to delete semaphore ([991ac40d](https://github.com/espressif/esp-protocols/commit/991ac40d)) 38 | - Move client to different state when disconnecting ([0d8f2a6d](https://github.com/espressif/esp-protocols/commit/0d8f2a6d)) 39 | - fix of websocket host example ([5ccc018a](https://github.com/espressif/esp-protocols/commit/5ccc018a)) 40 | - don't get transport from the list if external transport is used ([9d4d5d2d](https://github.com/espressif/esp-protocols/commit/9d4d5d2d)) 41 | - Fix locking issues of `esp_websocket_client_send_with_exact_opcode` API ([6393fcd7](https://github.com/espressif/esp-protocols/commit/6393fcd7)) 42 | 43 | ## [1.2.3](https://github.com/espressif/esp-protocols/commits/websocket-v1.2.3) 44 | 45 | ### Features 46 | 47 | - Expanded example to demonstrate the transfer over TLS ([0d0630ed76](https://github.com/espressif/esp-protocols/commit/0d0630ed76)) 48 | 49 | ### Bug Fixes 50 | 51 | - fix esp_event dependency management ([1fb02a9a60](https://github.com/espressif/esp-protocols/commit/1fb02a9a60)) 52 | - Skip warn on zero timeout and auto reconnect is disabled ([5b467cbf5c](https://github.com/espressif/esp-protocols/commit/5b467cbf5c)) 53 | - Fixed to use int return value in Tx functions ([9c54b72e1f](https://github.com/espressif/esp-protocols/commit/9c54b72e1f)) 54 | - Fixed Tx functions with DYNAMIC_BUFFER ([16174470ee](https://github.com/espressif/esp-protocols/commit/16174470ee)) 55 | - added dependency checks, sdkconfig.defaults and refined README.md ([312982e4aa](https://github.com/espressif/esp-protocols/commit/312982e4aa)) 56 | - Close websocket and dispatch event if server does not close within a reasonable amount of time. ([d85311880d](https://github.com/espressif/esp-protocols/commit/d85311880d)) 57 | - Continue waiting for TCP connection to be closed ([2b092e0db4](https://github.com/espressif/esp-protocols/commit/2b092e0db4)) 58 | 59 | ### Updated 60 | 61 | - docs(websocket): Added README for websocket host example ([2f7c58259d](https://github.com/espressif/esp-protocols/commit/2f7c58259d)) 62 | 63 | ## [1.2.2](https://github.com/espressif/esp-protocols/commits/websocket-v1.2.2) 64 | 65 | ### Bug Fixes 66 | 67 | - continuation after FIN in websocket client (#460) ([774d1c75e6](https://github.com/espressif/esp-protocols/commit/774d1c75e6)) 68 | - Re-applie refs to common comps idf_component.yml ([9fe44a4504](https://github.com/espressif/esp-protocols/commit/9fe44a4504)) 69 | 70 | ## [1.2.1](https://github.com/espressif/esp-protocols/commits/websocket-v1.2.1) 71 | 72 | ### Bug Fixes 73 | 74 | - consider failure if return value of `esp_websocket_client_send_with_exact_opcode` less than 0 ([f523b4d](https://github.com/espressif/esp-protocols/commit/f523b4d)) 75 | - fix of return value for `esp_websocket_client_send_with_opcode` API ([ba33588](https://github.com/espressif/esp-protocols/commit/ba33588)) 76 | 77 | ## [1.2.0](https://github.com/espressif/esp-protocols/commits/websocket-v1.2.0) 78 | 79 | ### Features 80 | 81 | - Added new API `esp_websocket_client_append_header` ([39e9725](https://github.com/espressif/esp-protocols/commit/39e9725)) 82 | - Added new APIs to support fragmented messages transmission ([fae80e2](https://github.com/espressif/esp-protocols/commit/fae80e2)) 83 | 84 | ### Bug Fixes 85 | 86 | - Reference common component from IDF ([74fc228](https://github.com/espressif/esp-protocols/commit/74fc228)) 87 | - Revert referencing protocol_examples_common from IDF ([b176d3a](https://github.com/espressif/esp-protocols/commit/b176d3a)) 88 | - reference protocol_examples_common from IDF ([025ede1](https://github.com/espressif/esp-protocols/commit/025ede1)) 89 | - specify override_path in example manifests ([d5e7898](https://github.com/espressif/esp-protocols/commit/d5e7898)) 90 | - Return status code correctly on esp_websocket_client_send_with_opcode ([ac8f1de](https://github.com/espressif/esp-protocols/commit/ac8f1de)) 91 | - Fix pytest exclusion, gitignore, and changelog checks ([2696221](https://github.com/espressif/esp-protocols/commit/2696221)) 92 | 93 | ## [1.1.0](https://github.com/espressif/esp-protocols/commits/websocket-v1.1.0) 94 | 95 | ### Features 96 | 97 | - Added linux port for websocket ([a22391a](https://github.com/espressif/esp-protocols/commit/a22391a)) 98 | 99 | ### Bug Fixes 100 | 101 | - added idf_component.yml for examples ([d273e10](https://github.com/espressif/esp-protocols/commit/d273e10)) 102 | 103 | ## [1.0.1](https://github.com/espressif/esp-protocols/commits/websocket-v1.0.1) 104 | 105 | ### Bug Fixes 106 | 107 | - esp_websocket_client client allow sending 0 byte packets ([b5177cb](https://github.com/espressif/esp-protocols/commit/b5177cb)) 108 | - Cleaned up printf/format warnings (-Wno-format) ([e085826](https://github.com/espressif/esp-protocols/commit/e085826)) 109 | - Added unit tests to CI + minor fix to pass it ([c974c14](https://github.com/espressif/esp-protocols/commit/c974c14)) 110 | - Reintroduce missing CHANGELOGs ([200cbb3](https://github.com/espressif/esp-protocols/commit/200cbb3), [#235](https://github.com/espressif/esp-protocols/issues/235)) 111 | 112 | ### Updated 113 | 114 | - docs(common): updated component and example links ([f48d9b2](https://github.com/espressif/esp-protocols/commit/f48d9b2)) 115 | - docs(common): improving documentation ([ca3fce0](https://github.com/espressif/esp-protocols/commit/ca3fce0)) 116 | - Fix weird error message spacings ([8bb207e](https://github.com/espressif/esp-protocols/commit/8bb207e)) 117 | 118 | ## [1.0.0](https://github.com/espressif/esp-protocols/commits/996fef7) 119 | 120 | ### Updated 121 | 122 | - esp_websocket_client: Updated version to 1.0.0 Updated tests to run agains release-v5.0 ([996fef7](https://github.com/espressif/esp-protocols/commit/996fef7)) 123 | - esp_websocket_client: * Error handling improved to show status code from server * Added new API `esp_websocket_client_set_headers` * Dispatches 'WEBSOCKET_EVENT_BEFORE_CONNECT' event before tcp connection ([d047ff5](https://github.com/espressif/esp-protocols/commit/d047ff5)) 124 | - unite all tags under common structure py test: update tags under common structure ([c6db3ea](https://github.com/espressif/esp-protocols/commit/c6db3ea)) 125 | - websocket: Support HTTP basic authorization ([1b13448](https://github.com/espressif/esp-protocols/commit/1b13448)) 126 | - Add task_name config option ([1d68884](https://github.com/espressif/esp-protocols/commit/1d68884)) 127 | - Add websocket error messages ([d68624e](https://github.com/espressif/esp-protocols/commit/d68624e)) 128 | - websocket: Added new API `esp_websocket_client_destroy_on_exit` ([f9b4790](https://github.com/espressif/esp-protocols/commit/f9b4790)) 129 | - Added badges with version of components to the respective README files ([e4c8a59](https://github.com/espressif/esp-protocols/commit/e4c8a59)) 130 | 131 | 132 | ## [0.0.4](https://github.com/espressif/esp-protocols/commits/3330b96) 133 | 134 | ### Updated 135 | 136 | - websocket: make `esp_websocket_client_send_with_opcode` a public API ([3330b96](https://github.com/espressif/esp-protocols/commit/3330b96)) 137 | - websocket: updated example to use local websocket echo server ([55dc564](https://github.com/espressif/esp-protocols/commit/55dc564)) 138 | - CI: Created a common requirements.txt ([23a537b](https://github.com/espressif/esp-protocols/commit/23a537b)) 139 | - Examples: using pytest.ini from top level directory ([aee016d](https://github.com/espressif/esp-protocols/commit/aee016d)) 140 | - CI: fixing the files to be complient with pre-commit hooks ([945bd17](https://github.com/espressif/esp-protocols/commit/945bd17)) 141 | - websocket: updated example to show json data transfer ([3456781](https://github.com/espressif/esp-protocols/commit/3456781)) 142 | 143 | 144 | ## [0.0.3](https://github.com/espressif/esp-protocols/commits/5c245db) 145 | 146 | ### Updated 147 | 148 | - esp_websocket_client: Upgraded version to 0.0.3 ([5c245db](https://github.com/espressif/esp-protocols/commit/5c245db)) 149 | - CI: Fix build issues ([6e4e4fa](https://github.com/espressif/esp-protocols/commit/6e4e4fa)) 150 | 151 | 152 | ## [0.0.2](https://github.com/espressif/esp-protocols/commits/57afa38) 153 | 154 | ### Features 155 | 156 | - Optimize memory size for websocket client init ([4cefcd3](https://github.com/espressif/esp-protocols/commit/4cefcd3)) 157 | - allow users to attach CA bundle ([d56b5d9](https://github.com/espressif/esp-protocols/commit/d56b5d9)) 158 | 159 | ### Bug Fixes 160 | 161 | - Docs to refer esp-protocols ([91a177e](https://github.com/espressif/esp-protocols/commit/91a177e)) 162 | 163 | ### Updated 164 | 165 | - Bump asio/mdns/esp_websocket_client versions ([57afa38](https://github.com/espressif/esp-protocols/commit/57afa38)) 166 | - ignore format warnings ([d66f9dc](https://github.com/espressif/esp-protocols/commit/d66f9dc)) 167 | - Minor fixes here and there ([8fe2a3a](https://github.com/espressif/esp-protocols/commit/8fe2a3a)) 168 | - CI: Added CI example run job ([76298ff](https://github.com/espressif/esp-protocols/commit/76298ff)) 169 | - Implement websocket client connect error ([9e37f53](https://github.com/espressif/esp-protocols/commit/9e37f53)) 170 | - Add methods to allow get/set of websocket client ping interval ([e55f54b](https://github.com/espressif/esp-protocols/commit/e55f54b)) 171 | - esp_websocket_client: Expose frame fin flag in websocket event ([b72a9ae](https://github.com/espressif/esp-protocols/commit/b72a9ae)) 172 | 173 | 174 | ## [0.0.1](https://github.com/espressif/esp-protocols/commits/80c3cf0) 175 | 176 | ### Updated 177 | 178 | - websocket: Initial version based on IDF 5.0 ([80c3cf0](https://github.com/espressif/esp-protocols/commit/80c3cf0)) 179 | - freertos: Remove legacy data types ([b3c777a](https://github.com/espressif/esp-protocols/commit/b3c777a), [IDF@57fd78f](https://github.com/espressif/esp-idf/commit/57fd78f5baf93a368a82cf4b2e00ca17ffc09115)) 180 | - websocket: Added configs `reconnect_timeout_ms` and `network_timeout_ms` ([8ce791e](https://github.com/espressif/esp-protocols/commit/8ce791e), [IDF#8263](https://github.com/espressif/esp-idf/issues/8263), [IDF@6c26d65](https://github.com/espressif/esp-idf/commit/6c26d6520311f83c2ebe852a487c36185a429a69)) 181 | - Add http_parser (new component) dependency ([bece6e7](https://github.com/espressif/esp-protocols/commit/bece6e7), [IDF@8e94cf2](https://github.com/espressif/esp-idf/commit/8e94cf2bb1498e94045e73e649f1046111fc6f9f)) 182 | - websocket: removed deprecated API "esp_websocket_client_send" ([46bd32d](https://github.com/espressif/esp-protocols/commit/46bd32d), [IDF@7f6ab93](https://github.com/espressif/esp-idf/commit/7f6ab93f7e52bddaf4c030d7337ea5574f33381d)) 183 | - refactor (test_utils)!: separate file for memory check functions ([525c70c](https://github.com/espressif/esp-protocols/commit/525c70c), [IDF@16514f9](https://github.com/espressif/esp-idf/commit/16514f93f06cd833306459d615458536a9f2e5cd)) 184 | - Build & config: Remove leftover files from the unsupported "make" build system ([19c0455](https://github.com/espressif/esp-protocols/commit/19c0455), [IDF@766aa57](https://github.com/espressif/esp-idf/commit/766aa5708443099f3f033b739cda0e1de101cca6)) 185 | - transport: Add CONFI_WS_TRANSPORT for optimize the code size ([9118e0f](https://github.com/espressif/esp-protocols/commit/9118e0f), [IDF@8b02c90](https://github.com/espressif/esp-idf/commit/8b02c9026af32352c8c4ed23025fb42182db6cae)) 186 | - ws_client: Fix const correctness in the API config structure ([fbdbd55](https://github.com/espressif/esp-protocols/commit/fbdbd55), [IDF@70b1247](https://github.com/espressif/esp-idf/commit/70b1247a47f4583fccd8a91bf6cc532e5741e632)) 187 | - components: Remove repeated keep alive function by ssl layer function ([de7cd72](https://github.com/espressif/esp-protocols/commit/de7cd72), [IDF@c79a907](https://github.com/espressif/esp-idf/commit/c79a907e4fef0c54175ad5659bc0df45a40745c9)) 188 | - components: Support bind socket to specified interface in esp_http_client and esp_websocket_client component ([4a608ec](https://github.com/espressif/esp-protocols/commit/4a608ec), [IDF@bead359](https://github.com/espressif/esp-idf/commit/bead3599abd875d746e64cd6749574ff2c155adb)) 189 | - esp_websocket_client: Don't log the filename when logging "Websocket already stop" ([f0351ff](https://github.com/espressif/esp-protocols/commit/f0351ff), [IDF@10bde42](https://github.com/espressif/esp-idf/commit/10bde42551b479bd4bfccc9d3c6d983f8abe0b87)) 190 | - websocket: Add websocket unit tests ([9219ff7](https://github.com/espressif/esp-protocols/commit/9219ff7), [IDF@cd01a0c](https://github.com/espressif/esp-idf/commit/cd01a0ca81ef2ba5648fd7712c9bf45bbf252339)) 191 | - websockets: Set keepalive options after adding transport to the list ([86aa0b8](https://github.com/espressif/esp-protocols/commit/86aa0b8), [IDF@99805d8](https://github.com/espressif/esp-idf/commit/99805d880f41857702b3bbb35bc0dfaf7dec3aec)) 192 | - websocket: Add configurable ping interval ([1933367](https://github.com/espressif/esp-protocols/commit/1933367), [IDF@9ff9137](https://github.com/espressif/esp-idf/commit/9ff9137e7a8b64e956c1c63e95a48f4049ad571e)) 193 | - ws_transport: Add option to propagate control packets to the app ([95cf983](https://github.com/espressif/esp-protocols/commit/95cf983), [IDF#6307](https://github.com/espressif/esp-idf/issues/6307), [IDF@acc7bd2](https://github.com/espressif/esp-idf/commit/acc7bd2ca45c21033cbd02220a27c3c1ecdd5ad0)) 194 | - Add options for esp_http_client and esp_websocket_client to support keepalive ([8a6c320](https://github.com/espressif/esp-protocols/commit/8a6c320), [IDF@b53e46a](https://github.com/espressif/esp-idf/commit/b53e46a68e8671c73e8aafe2602de5ff5a77e3db)) 195 | - websocket: support mutual tls for websocket Closes https://github.com/espressif/esp-idf/issues/6059 ([d1dd6ec](https://github.com/espressif/esp-protocols/commit/d1dd6ec), [IDF#6059](https://github.com/espressif/esp-idf/issues/6059), [IDF@5ab774f](https://github.com/espressif/esp-idf/commit/5ab774f9d8e119fff56b566fa2f9bdad853bf701)) 196 | - Whitespace: Automated whitespace fixes (large commit) ([d376480](https://github.com/espressif/esp-protocols/commit/d376480), [IDF@66fb5a2](https://github.com/espressif/esp-idf/commit/66fb5a29bbdc2482d67c52e6f66b303378c9b789)) 197 | - Websocket client: avoid deadlock if stop called from event handler ([e90272c](https://github.com/espressif/esp-protocols/commit/e90272c), [IDF@c2bb076](https://github.com/espressif/esp-idf/commit/c2bb0762bb5c24cb170bc9c96fdadb86ae2f06e7)) 198 | - tcp_transport: Added internal API for underlying socket, used for custom select on connection end for WS ([6d12d06](https://github.com/espressif/esp-protocols/commit/6d12d06), [IDF@5e9f8b5](https://github.com/espressif/esp-idf/commit/5e9f8b52e7a87371370205a387b2d94e5ac6cbf9)) 199 | - ws_client: Added support for close frame, closing connection gracefully ([1455bc0](https://github.com/espressif/esp-protocols/commit/1455bc0), [IDF@b213f2c](https://github.com/espressif/esp-idf/commit/b213f2c6d3d78ba3a95005e3206d4ce370b8a649)) 200 | - driver, http_client, web_socket, tcp_transport: remove __FILE__ from log messages ([01b4f64](https://github.com/espressif/esp-protocols/commit/01b4f64), [IDF#5637](https://github.com/espressif/esp-idf/issues/5637), [IDF@caaf62b](https://github.com/espressif/esp-idf/commit/caaf62bdad965e6b58bba74171986414057f6757)) 201 | - websocket_client : fix some issues for websocket client ([6ab0aea](https://github.com/espressif/esp-protocols/commit/6ab0aea), [IDF@341e480](https://github.com/espressif/esp-idf/commit/341e48057349d92c3b8afe5f9c0fcd0aa47500b0)) 202 | - websocket: add configurable timeout for PONG not received ([b71c49c](https://github.com/espressif/esp-protocols/commit/b71c49c), [IDF@0049385](https://github.com/espressif/esp-idf/commit/0049385850daebfe2222c8f0526b896ffaeacdd9)) 203 | - websocket client: the client now aborts the connection if send fails. ([f8e3ba7](https://github.com/espressif/esp-protocols/commit/f8e3ba7), [IDF@6bebfc8](https://github.com/espressif/esp-idf/commit/6bebfc84f3ed9c96bcb331fd0d5b0bbb26ce07a4)) 204 | - ws_client: fix fragmented send setting proper opcodes ([7a5b2d5](https://github.com/espressif/esp-protocols/commit/7a5b2d5), [IDF#4974](https://github.com/espressif/esp-idf/issues/4974), [IDF@14992e6](https://github.com/espressif/esp-idf/commit/14992e62c5573d8b6076281f16b4fe11d6bc8f87)) 205 | - esp32: add implementation of esp_timer based on TG0 LAC timer ([17281a5](https://github.com/espressif/esp-protocols/commit/17281a5), [IDF@739eb05](https://github.com/espressif/esp-idf/commit/739eb05bb97736b70507e7ebcfee58e670672d23)) 206 | - tcp_transport/ws_client: websockets now correctly handle messages longer than buffer ([aec6a75](https://github.com/espressif/esp-protocols/commit/aec6a75), [IDF@ffeda30](https://github.com/espressif/esp-idf/commit/ffeda3003c92102d2d5b145c9adb3ea3105cbbda)) 207 | - websocket: added missing event data ([a6be8e2](https://github.com/espressif/esp-protocols/commit/a6be8e2), [IDF@7c0e376](https://github.com/espressif/esp-idf/commit/7c0e3765ec009acaf2ef439e98895598b5fd9aaf)) 208 | - Add User-Agent and additional headers to esp_websocket_client ([a48b0fa](https://github.com/espressif/esp-protocols/commit/a48b0fa), [IDF@9200250](https://github.com/espressif/esp-idf/commit/9200250f512146e348f84ebfc76f9e82e2070da2)) 209 | - ws_client: fix handling timeouts by websocket client. ([1fcc001](https://github.com/espressif/esp-protocols/commit/1fcc001), [IDF#4316](https://github.com/espressif/esp-idf/issues/4316), [IDF@e1f9829](https://github.com/espressif/esp-idf/commit/e1f982921a08022ca4307900fc058ccacccd26d0)) 210 | - websocket_client: fix locking mechanism in ws-client task and when sending data ([d0121b9](https://github.com/espressif/esp-protocols/commit/d0121b9), [IDF@7c5011f](https://github.com/espressif/esp-idf/commit/7c5011f411b7662feb50fd1e53114bec390d8c2e)) 211 | - ws_client: fix for not sending ping responses, updated to pass events also for PING and PONG messages, added interfaces to send both binary and text data ([f55d839](https://github.com/espressif/esp-protocols/commit/f55d839), [IDF@abf9345](https://github.com/espressif/esp-idf/commit/abf9345b85559f4a922e8387f48336fb09994041)) 212 | - websocket_client: fix URI parsing to include also query part in websocket connection path ([f5a26c4](https://github.com/espressif/esp-protocols/commit/f5a26c4), [IDF@271e6c4](https://github.com/espressif/esp-idf/commit/271e6c4c9c57ca6715c1435a71fe3974cd2b18b3)) 213 | - ws_client: fixed posting to event loop with websocket timeout ([23f6a1d](https://github.com/espressif/esp-protocols/commit/23f6a1d), [IDF@5050506](https://github.com/espressif/esp-idf/commit/50505068c45fbe97611be9b7f2c30b8160cbb9e3)) 214 | - ws_client: added subprotocol configuration option to websocket client ([2553d65](https://github.com/espressif/esp-protocols/commit/2553d65), [IDF@de6ea39](https://github.com/espressif/esp-idf/commit/de6ea396f17be820153da6acaf977c1bf11806fb)) 215 | - ws_client: fixed path config issue when ws server configured using host and path instead of uri ([67949f9](https://github.com/espressif/esp-protocols/commit/67949f9), [IDF@c0ba9e1](https://github.com/espressif/esp-idf/commit/c0ba9e19fc6cff79f5760b991c259970bd4abeab)) 216 | - ws_client: fixed transport config option when server address configured as host, port, transport rather then uri ([bfc88ab](https://github.com/espressif/esp-protocols/commit/bfc88ab), [IDF@adee25d](https://github.com/espressif/esp-idf/commit/adee25d90e100a169e959f94db23621f6ffab0e6)) 217 | - esp_wifi: wifi support new event mechanism ([4d64495](https://github.com/espressif/esp-protocols/commit/4d64495), [IDF@003a987](https://github.com/espressif/esp-idf/commit/003a9872b7de69d799e9d37521cfbcaff9b37e85)) 218 | - tools: Mass fixing of empty prototypes (for -Wstrict-prototypes) ([da74a4a](https://github.com/espressif/esp-protocols/commit/da74a4a), [IDF@afbaf74](https://github.com/espressif/esp-idf/commit/afbaf74007e89d016dbade4072bf2e7a3874139a)) 219 | - ws_client: fix double delete issue in ws client initialization ([f718676](https://github.com/espressif/esp-protocols/commit/f718676), [IDF@9b507c4](https://github.com/espressif/esp-idf/commit/9b507c45c86cf491466d705cd7896c6f6e500d0d)) 220 | - ws_client: removed dependency on internal tcp_transport header ([13a40d2](https://github.com/espressif/esp-protocols/commit/13a40d2), [IDF@d143356](https://github.com/espressif/esp-idf/commit/d1433564ecfc885f80a7a261a88ab87d227cf1c2)) 221 | - examples: use new component registration api ([35d6f9a](https://github.com/espressif/esp-protocols/commit/35d6f9a), [IDF@6771eea](https://github.com/espressif/esp-idf/commit/6771eead80534c51efb2033c04769ef5893b4838)) 222 | - esp_websocket_client: Add websocket client component ([f3a0586](https://github.com/espressif/esp-protocols/commit/f3a0586), [IDF#2829](https://github.com/espressif/esp-idf/issues/2829), [IDF@2a2d932](https://github.com/espressif/esp-idf/commit/2a2d932cfe2404057c71bc91d9d9416200e67a03)) 223 | -------------------------------------------------------------------------------- /components/esp_websocket_client/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_build_get_property(target IDF_TARGET) 2 | 3 | if(NOT CONFIG_WS_TRANSPORT AND NOT CMAKE_BUILD_EARLY_EXPANSION) 4 | message(STATUS "Websocket transport is disabled so the esp_websocket_client component will not be built") 5 | # note: the component is still included in the build so it can become visible again in config 6 | # without needing to re-run CMake. However no source or header files are built. 7 | idf_component_register() 8 | return() 9 | endif() 10 | 11 | if(${IDF_TARGET} STREQUAL "linux") 12 | idf_component_register(SRCS "esp_websocket_client.c" 13 | INCLUDE_DIRS "include" 14 | REQUIRES esp-tls tcp_transport http_parser esp_event nvs_flash esp_stubs json 15 | PRIV_REQUIRES esp_timer) 16 | else() 17 | idf_component_register(SRCS "esp_websocket_client.c" 18 | INCLUDE_DIRS "include" 19 | REQUIRES lwip esp-tls tcp_transport http_parser esp_event 20 | PRIV_REQUIRES esp_timer) 21 | endif() 22 | -------------------------------------------------------------------------------- /components/esp_websocket_client/Kconfig: -------------------------------------------------------------------------------- 1 | menu "ESP WebSocket client" 2 | 3 | config ESP_WS_CLIENT_ENABLE_DYNAMIC_BUFFER 4 | bool "Enable websocket client dynamic buffer for send and receive data" 5 | default n 6 | help 7 | Enable this option will reallocated buffer when send or receive data and free them when end of use. 8 | This can save about 2 KB memory when no websocket data send and receive. 9 | 10 | endmenu 11 | -------------------------------------------------------------------------------- /components/esp_websocket_client/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /components/esp_websocket_client/README.md: -------------------------------------------------------------------------------- 1 | # ESP WEBSOCKET CLIENT 2 | 3 | [![Component Registry](https://components.espressif.com/components/espressif/esp_websocket_client/badge.svg)](https://components.espressif.com/components/espressif/esp_websocket_client) 4 | 5 | The `esp-websocket_client` component is a managed component for `esp-idf` that contains implementation of [WebSocket protocol client](https://datatracker.ietf.org/doc/html/rfc6455) for ESP32 6 | 7 | ## Examples 8 | 9 | Get started with example test [example](https://github.com/espressif/esp-protocols/tree/master/components/esp_websocket_client/examples): 10 | 11 | ## Documentation 12 | 13 | * View the full [html documentation](https://docs.espressif.com/projects/esp-protocols/esp_websocket_client/docs/latest/index.html) 14 | -------------------------------------------------------------------------------- /components/esp_websocket_client/examples/linux/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | 3 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 4 | 5 | set(common_component_dir ../../../../common_components) 6 | set(EXTRA_COMPONENT_DIRS 7 | ../.. 8 | "${common_component_dir}/linux_compat/esp_timer" 9 | "${common_component_dir}/linux_compat/freertos" 10 | $ENV{IDF_PATH}/examples/protocols/linux_stubs/esp_stubs 11 | $ENV{IDF_PATH}/examples/common_components/protocol_examples_common) 12 | 13 | set(COMPONENTS main) 14 | project(websocket) 15 | -------------------------------------------------------------------------------- /components/esp_websocket_client/examples/linux/README.md: -------------------------------------------------------------------------------- 1 | # ESP Websocket Client - Host Example 2 | 3 | This example demonstrates the ESP websocket client using the `linux` target. It allows for compilation and execution of the example directly within a Linux environment. 4 | 5 | ## Compilation and Execution 6 | 7 | To compile and execute this example on Linux need to set target `linux` 8 | 9 | ``` 10 | idf.py --preview set-target linux 11 | idf.py build 12 | ./websocket.elf 13 | ``` 14 | 15 | ## Example Output 16 | 17 | ``` 18 | I (164532) websocket: [APP] Startup.. 19 | I (164532) websocket: [APP] Free memory: 4294967295 bytes 20 | I (164532) websocket: [APP] IDF version: v5.3-dev-1353-gb3f7e2c8a4 21 | I (164538) websocket: Connecting to ws://echo.websocket.events... 22 | W (164538) websocket_client: `reconnect_timeout_ms` is not set, or it is less than or equal to zero, using default time out 10000 (milliseconds) 23 | W (164538) websocket_client: `network_timeout_ms` is not set, or it is less than or equal to zero, using default time out 10000 (milliseconds) 24 | I (165103) websocket: WEBSOCKET_EVENT_CONNECTED 25 | I (165539) websocket: Sending hello 0000 26 | I (165627) websocket: WEBSOCKET_EVENT_DATA 27 | I (165627) websocket: Received opcode=1 28 | W (165627) websocket: Received=hello 0000 29 | W (165627) websocket: Total payload length=10, data_len=10, current payload offset=0 30 | 31 | I (166539) websocket: Sending fragmented message 32 | ``` 33 | 34 | ## Coverage Reporting 35 | For generating a coverage report, it's necessary to enable `CONFIG_GCOV_ENABLED=y` option. Set the following configuration in your project's SDK configuration file (`sdkconfig.ci.coverage`, `sdkconfig.ci.linux` or via `menuconfig`): 36 | -------------------------------------------------------------------------------- /components/esp_websocket_client/examples/linux/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register(SRCS "websocket_linux.c" 2 | REQUIRES esp_websocket_client protocol_examples_common) 3 | 4 | if(CONFIG_GCOV_ENABLED) 5 | target_compile_options(${COMPONENT_LIB} PUBLIC --coverage -fprofile-arcs -ftest-coverage) 6 | target_link_options(${COMPONENT_LIB} PUBLIC --coverage -fprofile-arcs -ftest-coverage) 7 | 8 | idf_component_get_property(esp_websocket_client esp_websocket_client COMPONENT_LIB) 9 | 10 | target_compile_options(${esp_websocket_client} PUBLIC --coverage -fprofile-arcs -ftest-coverage) 11 | target_link_options(${esp_websocket_client} PUBLIC --coverage -fprofile-arcs -ftest-coverage) 12 | endif() 13 | -------------------------------------------------------------------------------- /components/esp_websocket_client/examples/linux/main/Kconfig.projbuild: -------------------------------------------------------------------------------- 1 | menu "Host-test config" 2 | 3 | config GCOV_ENABLED 4 | bool "Coverage analyzer" 5 | default n 6 | help 7 | Enables coverage analyzing for host tests. 8 | 9 | config WEBSOCKET_URI 10 | string "Websocket endpoint URI" 11 | default "ws://echo.websocket.events" 12 | help 13 | URL of websocket endpoint this example connects to and sends echo 14 | 15 | endmenu 16 | -------------------------------------------------------------------------------- /components/esp_websocket_client/examples/linux/main/websocket_linux.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | #include 7 | #include "nvs_flash.h" 8 | #include "protocol_examples_common.h" 9 | 10 | #include "esp_websocket_client.h" 11 | #include "esp_system.h" 12 | #include "esp_event.h" 13 | #include "esp_netif.h" 14 | 15 | static const char *TAG = "websocket"; 16 | 17 | static void log_error_if_nonzero(const char *message, int error_code) 18 | { 19 | if (error_code != 0) { 20 | ESP_LOGE(TAG, "Last error %s: 0x%x", message, error_code); 21 | } 22 | } 23 | 24 | static void websocket_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) 25 | { 26 | esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data; 27 | switch (event_id) { 28 | case WEBSOCKET_EVENT_BEGIN: 29 | ESP_LOGI(TAG, "WEBSOCKET_EVENT_BEGIN"); 30 | break; 31 | case WEBSOCKET_EVENT_CONNECTED: 32 | ESP_LOGI(TAG, "WEBSOCKET_EVENT_CONNECTED"); 33 | break; 34 | case WEBSOCKET_EVENT_DISCONNECTED: 35 | ESP_LOGI(TAG, "WEBSOCKET_EVENT_DISCONNECTED"); 36 | log_error_if_nonzero("HTTP status code", data->error_handle.esp_ws_handshake_status_code); 37 | if (data->error_handle.error_type == WEBSOCKET_ERROR_TYPE_TCP_TRANSPORT) { 38 | log_error_if_nonzero("reported from esp-tls", data->error_handle.esp_tls_last_esp_err); 39 | log_error_if_nonzero("reported from tls stack", data->error_handle.esp_tls_stack_err); 40 | log_error_if_nonzero("captured as transport's socket errno", data->error_handle.esp_transport_sock_errno); 41 | } 42 | break; 43 | case WEBSOCKET_EVENT_DATA: 44 | ESP_LOGI(TAG, "WEBSOCKET_EVENT_DATA"); 45 | ESP_LOGI(TAG, "Received opcode=%d", data->op_code); 46 | if (data->op_code == 0x08 && data->data_len == 2) { 47 | ESP_LOGW(TAG, "Received closed message with code=%d", 256 * data->data_ptr[0] + data->data_ptr[1]); 48 | } else { 49 | ESP_LOGW(TAG, "Received=%.*s", data->data_len, (char *)data->data_ptr); 50 | } 51 | 52 | // If received data contains json structure it succeed to parse 53 | ESP_LOGW(TAG, "Total payload length=%d, data_len=%d, current payload offset=%d\r\n", data->payload_len, data->data_len, data->payload_offset); 54 | 55 | break; 56 | case WEBSOCKET_EVENT_ERROR: 57 | ESP_LOGI(TAG, "WEBSOCKET_EVENT_ERROR"); 58 | log_error_if_nonzero("HTTP status code", data->error_handle.esp_ws_handshake_status_code); 59 | if (data->error_handle.error_type == WEBSOCKET_ERROR_TYPE_TCP_TRANSPORT) { 60 | log_error_if_nonzero("reported from esp-tls", data->error_handle.esp_tls_last_esp_err); 61 | log_error_if_nonzero("reported from tls stack", data->error_handle.esp_tls_stack_err); 62 | log_error_if_nonzero("captured as transport's socket errno", data->error_handle.esp_transport_sock_errno); 63 | } 64 | break; 65 | case WEBSOCKET_EVENT_FINISH: 66 | ESP_LOGI(TAG, "WEBSOCKET_EVENT_FINISH"); 67 | break; 68 | } 69 | } 70 | 71 | 72 | static void websocket_app_start(void) 73 | { 74 | esp_websocket_client_config_t websocket_cfg = {}; 75 | 76 | websocket_cfg.uri = CONFIG_WEBSOCKET_URI; 77 | 78 | ESP_LOGI(TAG, "Connecting to %s...", websocket_cfg.uri); 79 | 80 | esp_websocket_client_handle_t client = esp_websocket_client_init(&websocket_cfg); 81 | // This call demonstrates adding another header; it's called to increase code coverage 82 | esp_websocket_client_append_header(client, "HeaderNewKey", "value"); 83 | 84 | esp_websocket_register_events(client, WEBSOCKET_EVENT_ANY, websocket_event_handler, (void *)client); 85 | 86 | esp_websocket_client_start(client); 87 | char data[32]; 88 | int i = 0; 89 | while (i < 1) { 90 | if (esp_websocket_client_is_connected(client)) { 91 | int len = sprintf(data, "hello %04d", i++); 92 | ESP_LOGI(TAG, "Sending %s", data); 93 | esp_websocket_client_send_text(client, data, len, portMAX_DELAY); 94 | } 95 | vTaskDelay(1000 / portTICK_PERIOD_MS); 96 | } 97 | 98 | // Sending text data 99 | ESP_LOGI(TAG, "Sending fragmented text message"); 100 | memset(data, 'a', sizeof(data)); 101 | esp_websocket_client_send_text_partial(client, data, sizeof(data), portMAX_DELAY); 102 | memset(data, 'b', sizeof(data)); 103 | esp_websocket_client_send_cont_msg(client, data, sizeof(data), portMAX_DELAY); 104 | esp_websocket_client_send_fin(client, portMAX_DELAY); 105 | 106 | vTaskDelay(1000 / portTICK_PERIOD_MS); 107 | // Sending binary data 108 | ESP_LOGI(TAG, "Sending fragmented binary message"); 109 | char binary_data[128]; 110 | memset(binary_data, 0, sizeof(binary_data)); 111 | esp_websocket_client_send_bin_partial(client, binary_data, sizeof(binary_data), portMAX_DELAY); 112 | memset(binary_data, 1, sizeof(binary_data)); 113 | esp_websocket_client_send_cont_msg(client, binary_data, sizeof(binary_data), portMAX_DELAY); 114 | esp_websocket_client_send_fin(client, portMAX_DELAY); 115 | 116 | esp_websocket_client_destroy(client); 117 | } 118 | 119 | int main(void) 120 | { 121 | 122 | ESP_LOGI(TAG, "[APP] Startup.."); 123 | ESP_LOGI(TAG, "[APP] Free memory: %" PRIu32 " bytes", esp_get_free_heap_size()); 124 | ESP_LOGI(TAG, "[APP] IDF version: %s", esp_get_idf_version()); 125 | esp_log_level_set("*", ESP_LOG_INFO); 126 | esp_log_level_set("websocket_client", ESP_LOG_DEBUG); 127 | esp_log_level_set("transport_ws", ESP_LOG_DEBUG); 128 | esp_log_level_set("trans_tcp", ESP_LOG_DEBUG); 129 | 130 | ESP_ERROR_CHECK(nvs_flash_init()); 131 | ESP_ERROR_CHECK(esp_netif_init()); 132 | ESP_ERROR_CHECK(esp_event_loop_create_default()); 133 | 134 | /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig. 135 | * Read "Establishing Wi-Fi or Ethernet Connection" section in 136 | * examples/protocols/README.md for more information about this function. 137 | */ 138 | ESP_ERROR_CHECK(example_connect()); 139 | 140 | websocket_app_start(); 141 | return 0; 142 | } 143 | -------------------------------------------------------------------------------- /components/esp_websocket_client/examples/linux/sdkconfig.ci.coverage: -------------------------------------------------------------------------------- 1 | CONFIG_GCOV_ENABLED=y 2 | CONFIG_IDF_TARGET="linux" 3 | CONFIG_IDF_TARGET_LINUX=y 4 | CONFIG_ESP_EVENT_POST_FROM_ISR=n 5 | CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=n 6 | CONFIG_WEBSOCKET_URI="ws://echo.websocket.events" 7 | -------------------------------------------------------------------------------- /components/esp_websocket_client/examples/linux/sdkconfig.ci.linux: -------------------------------------------------------------------------------- 1 | CONFIG_IDF_TARGET="linux" 2 | CONFIG_IDF_TARGET_LINUX=y 3 | CONFIG_ESP_EVENT_POST_FROM_ISR=n 4 | CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=n 5 | CONFIG_WEBSOCKET_URI="ws://echo.websocket.events" 6 | -------------------------------------------------------------------------------- /components/esp_websocket_client/examples/linux/sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | CONFIG_IDF_TARGET="linux" 2 | CONFIG_IDF_TARGET_LINUX=y 3 | CONFIG_ESP_EVENT_POST_FROM_ISR=n 4 | CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=n 5 | CONFIG_WEBSOCKET_URI="ws://echo.websocket.events" 6 | -------------------------------------------------------------------------------- /components/esp_websocket_client/examples/target/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The following lines of boilerplate have to be in your project's CMakeLists 2 | # in this exact order for cmake to work correctly 3 | cmake_minimum_required(VERSION 3.5) 4 | 5 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 6 | project(websocket_example) 7 | -------------------------------------------------------------------------------- /components/esp_websocket_client/examples/target/README.md: -------------------------------------------------------------------------------- 1 | # Websocket Sample application 2 | 3 | This example will shows how to set up and communicate over a websocket. 4 | 5 | ## How to Use Example 6 | 7 | ### Hardware Required 8 | 9 | This example can be executed on any ESP32 board, the only required interface is WiFi and connection to internet or a local server. 10 | 11 | ### Configure the project 12 | 13 | * Open the project configuration menu (`idf.py menuconfig`) 14 | * Configure Wi-Fi or Ethernet under "Example Connection Configuration" menu. 15 | * Configure the websocket endpoint URI under "Example Configuration", if "WEBSOCKET_URI_FROM_STDIN" is selected then the example application will connect to the URI it reads from stdin (used for testing) 16 | * To test a WebSocket client example over TLS, please enable one of the following configurations: `CONFIG_WS_OVER_TLS_MUTUAL_AUTH` or `CONFIG_WS_OVER_TLS_SERVER_AUTH`. See the sections below for more details. 17 | 18 | ### Server Certificate Verification 19 | 20 | * Mutual Authentication: When `CONFIG_WS_OVER_TLS_MUTUAL_AUTH=y` is enabled, it's essential to provide valid certificates for both the server and client. 21 | This ensures a secure two-way verification process. 22 | * Server-Only Authentication: To perform verification of the server's certificate only (without requiring a client certificate), set `CONFIG_WS_OVER_TLS_SERVER_AUTH=y`. 23 | This method skips client certificate verification. 24 | * Example below demonstrates how to generate a new self signed certificates for the server and client using the OpenSSL command line tool 25 | 26 | Please note: This example represents an extremely simplified approach to generating self-signed certificates/keys with a single common CA, devoid of CN checks, lacking password protection, and featuring hardcoded key sizes and types. It is intended solely for testing purposes. 27 | In the outlined steps, we are omitting the configuration of the CN (Common Name) field due to the context of a testing environment. However, it's important to recognize that the CN field is a critical element of SSL/TLS certificates, significantly influencing the security and efficacy of HTTPS communications. This field facilitates the verification of a website's identity, enhancing trust and security in web interactions. In practical deployments beyond testing scenarios, ensuring the CN field is accurately set is paramount for maintaining the integrity and reliability of secure communications 28 | 29 | ### Generating a self signed Certificates with OpenSSL 30 | * The example below outlines the process for creating new certificates for both the server and client using OpenSSL, a widely-used command line tool for implementing TLS protocol: 31 | 32 | ``` 33 | Generate the CA's Private Key; 34 | openssl genrsa -out ca_key.pem 2048 35 | 36 | Create the CA's Certificate 37 | openssl req -new -x509 -days 3650 -key ca_key.pem -out ca_cert.pem 38 | 39 | Generate the Server's Private Key 40 | openssl genrsa -out server_key.pem 2048 41 | 42 | Generate a Certificate Signing Request (CSR) for the Server 43 | openssl req -new -key server_key.pem -out server_csr.pem 44 | 45 | Sign the Server's CSR with the CA's Certificate 46 | openssl x509 -req -days 3650 -in server_csr.pem -CA ca_cert.pem -CAkey ca_key.pem -CAcreateserial -out server_cert.pem 47 | 48 | Generate the Client's Private Key 49 | openssl genrsa -out client_key.pem 2048 50 | 51 | Generate a Certificate Signing Request (CSR) for the Client 52 | openssl req -new -key client_key.pem -out client_csr.pem 53 | 54 | Sign the Client's CSR with the CA's Certificate 55 | openssl x509 -req -days 3650 -in client_csr.pem -CA ca_cert.pem -CAkey ca_key.pem -CAcreateserial -out client_cert.pem 56 | 57 | ``` 58 | 59 | Expiry time and metadata fields can be adjusted in the invocation. 60 | 61 | Please see the openssl man pages (man openssl) for more details. 62 | 63 | It is **strongly recommended** to not reuse the example certificate in your application; 64 | it is included only for demonstration. 65 | 66 | ### Build and Flash 67 | 68 | Build the project and flash it to the board, then run monitor tool to view serial output: 69 | 70 | ``` 71 | idf.py -p PORT flash monitor 72 | ``` 73 | 74 | (To exit the serial monitor, type ``Ctrl-]``.) 75 | 76 | See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. 77 | 78 | ## Example Output 79 | 80 | ``` 81 | I (482) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE 82 | I (2492) example_connect: Ethernet Link Up 83 | I (4472) tcpip_adapter: eth ip: 192.168.2.137, mask: 255.255.255.0, gw: 192.168.2.2 84 | I (4472) example_connect: Connected to Ethernet 85 | I (4472) example_connect: IPv4 address: 192.168.2.137 86 | I (4472) example_connect: IPv6 address: fe80:0000:0000:0000:bedd:c2ff:fed4:a92b 87 | I (4482) WEBSOCKET: Connecting to ws://echo.websocket.events... 88 | I (5012) WEBSOCKET: WEBSOCKET_EVENT_CONNECTED 89 | I (5492) WEBSOCKET: Sending hello 0000 90 | I (6052) WEBSOCKET: WEBSOCKET_EVENT_DATA 91 | W (6052) WEBSOCKET: Received=hello 0000 92 | 93 | I (6492) WEBSOCKET: Sending hello 0001 94 | I (7052) WEBSOCKET: WEBSOCKET_EVENT_DATA 95 | W (7052) WEBSOCKET: Received=hello 0001 96 | 97 | I (7492) WEBSOCKET: Sending hello 0002 98 | I (8082) WEBSOCKET: WEBSOCKET_EVENT_DATA 99 | W (8082) WEBSOCKET: Received=hello 0002 100 | 101 | I (8492) WEBSOCKET: Sending hello 0003 102 | I (9152) WEBSOCKET: WEBSOCKET_EVENT_DATA 103 | W (9162) WEBSOCKET: Received=hello 0003 104 | 105 | ``` 106 | 107 | 108 | ## Python Flask echo server 109 | 110 | By default, the `ws://echo.websocket.events` endpoint is used. You can setup a Python websocket echo server locally and try the `ws://:5000` endpoint. To do this, install Flask-sock Python package 111 | 112 | ``` 113 | pip install flask-sock 114 | ``` 115 | 116 | and start a Flask websocket echo server locally by executing the following Python code: 117 | 118 | ```python 119 | from flask import Flask 120 | from flask_sock import Sock 121 | 122 | app = Flask(__name__) 123 | sock = Sock(app) 124 | 125 | 126 | @sock.route('/') 127 | def echo(ws): 128 | while True: 129 | data = ws.receive() 130 | ws.send(data) 131 | 132 | 133 | if __name__ == '__main__': 134 | # To run your Flask + WebSocket server in production you can use Gunicorn: 135 | # gunicorn -b 0.0.0.0:5000 --workers 4 --threads 100 module:app 136 | app.run(host="0.0.0.0", debug=True) 137 | ``` 138 | -------------------------------------------------------------------------------- /components/esp_websocket_client/examples/target/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(SRC_FILES "websocket_example.c") # Define source files 2 | set(INCLUDE_DIRS ".") # Define include directories 3 | set(EMBED_FILES "") # Initialize an empty list for files to embed 4 | 5 | # Conditionally append files to the list based on configuration 6 | #if(CONFIG_WS_OVER_TLS_MUTAL_AUTH) 7 | list(APPEND EMBED_FILES 8 | "certs/client_cert.pem" 9 | "certs/ca_cert.pem" 10 | "certs/client_key.pem") 11 | #endif() 12 | 13 | # Register the component with source files, include dirs, and any conditionally added embedded files 14 | idf_component_register(SRCS "${SRC_FILES}" 15 | INCLUDE_DIRS "${INCLUDE_DIRS}" 16 | EMBED_TXTFILES "${EMBED_FILES}") 17 | -------------------------------------------------------------------------------- /components/esp_websocket_client/examples/target/main/Kconfig.projbuild: -------------------------------------------------------------------------------- 1 | menu "Example Configuration" 2 | 3 | choice WEBSOCKET_URI_SOURCE 4 | prompt "Websocket URI source" 5 | default WEBSOCKET_URI_FROM_STRING 6 | help 7 | Selects the source of the URI used in the example. 8 | 9 | config WEBSOCKET_URI_FROM_STRING 10 | bool "From string" 11 | 12 | config WEBSOCKET_URI_FROM_STDIN 13 | bool "From stdin" 14 | endchoice 15 | 16 | config WEBSOCKET_URI 17 | string "Websocket endpoint URI" 18 | depends on WEBSOCKET_URI_FROM_STRING 19 | default "wss://echo.websocket.events" 20 | help 21 | URL of websocket endpoint this example connects to and sends echo 22 | 23 | config WS_OVER_TLS_SERVER_AUTH 24 | bool "Enable WebSocket over TLS with Server Certificate Verification Only" 25 | default y 26 | help 27 | Enables WebSocket connections over TLS (WSS) with server certificate verification. 28 | This setting mandates the client to verify the servers certificate, while the server 29 | does not require client certificate verification. 30 | 31 | config WS_OVER_TLS_MUTUAL_AUTH 32 | bool "Enable WebSocket over TLS with Server Client Mutual Authentification" 33 | default n 34 | help 35 | Enables WebSocket connections over TLS (WSS) with server and client mutual certificate verification. 36 | 37 | config WS_OVER_TLS_SKIP_COMMON_NAME_CHECK 38 | bool "Skip common name(CN) check during TLS authentification" 39 | default n 40 | help 41 | Skipping Common Name(CN) check during TLS(WSS) authentificatio 42 | 43 | if CONFIG_IDF_TARGET = "linux" 44 | config GCOV_ENABLED 45 | bool "Coverage analyzer" 46 | default n 47 | help 48 | Enables coverage analyzing for host tests. 49 | endif 50 | endmenu 51 | -------------------------------------------------------------------------------- /components/esp_websocket_client/examples/target/main/certs/ca_cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDazCCAlOgAwIBAgIUL04QhbSEt5oNbV4f7CeLLqTCw2gwDQYJKoZIhvcNAQEL 3 | BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM 4 | GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDAyMjMwODA2MjVaFw0zNDAy 5 | MjAwODA2MjVaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw 6 | HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB 7 | AQUAA4IBDwAwggEKAoIBAQDjc78SuXAmJeBc0el2/m+2lwtk3J/VrNxHYkhjHa8K 8 | /ybU89VvKGuv9+L3IP67WMguFTaMgivJYUePjfMchtNJLJ+4cR9BkBKH4JnyXDae 9 | s0a5181LxRo8rqcaOw9hmJTgt9R4dIRTR3GN2/VLhlR+L9OTYA54RUtMyMMpyk5M 10 | YIJbcOwiwkVLsIYnexXDfgz9vQGl/2vBQ/RBtDBvbSyBiWox9SuzOrya1HUBzJkM 11 | Iu5L0bSa0LAeXHT3i3P1Y4WPt9ub70OhUNfJtHC+XbGFSEkkQG+lfbXU75XLoMWa 12 | iATMREOcb3Mq+pn1G8o1ZHVc6lBHUkfrNfxs5P/GQcSvAgMBAAGjUzBRMB0GA1Ud 13 | DgQWBBQGkdK2gR2HrQTnZnbuWO7I1+wdxDAfBgNVHSMEGDAWgBQGkdK2gR2HrQTn 14 | ZnbuWO7I1+wdxDAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBx 15 | G0hFtMwV/agIwC3ZaYC36ZWiijFzWkJSZG+fqAy32mSoVL2uQvOT8vEfF0ZnAcPc 16 | JI4oI059dBhAVlwqv6uLHyD4Gf2bF4oSLljdTz3X23llF+/wrTC2LLqMrm09aUC0 17 | ac74Q0FVwVJJcqH1HgemCMVjna5MkwNA6B+q7uR3eQ692VqXk6vjd4fRLBg1bBO1 18 | hXjasfNxA8A9quORF5+rjYrwyUZHuzcs0FfSClckIt4tHKtt4moLufOW6/PM4fRe 19 | AgdDfiTupxYLJFz4hFPhfgCh4TjQ+f9+uP4IAjW42dJmTVZjLEku/hm5lxCFObAq 20 | RgfaNwH8Ug1r1xswjSZG 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /components/esp_websocket_client/examples/target/main/certs/client_cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDWjCCAkKgAwIBAgIUUPCOgMA2v09E29fCkogx3RUBRtEwDQYJKoZIhvcNAQEL 3 | BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM 4 | GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDAyMjMwODA3MzFaFw0zNDAy 5 | MjAwODA3MzFaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw 6 | HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB 7 | AQUAA4IBDwAwggEKAoIBAQCrNeomxI2aoP+4iUy5SiA+41oHUDZDFeJOBjv5JCsK 8 | mlvFqxE9zynmPOVpuABErOJwzerPTa4NYKvuvs5CxVJUV5CXtWANuu9majioZNzj 9 | f877MDNX/GnZHK2gnkxVrZCPaDmx9yiMsFMXgmfdrDhwoUpXbdgSyeU/al9Ds2kF 10 | 0hrHOH2LBWt/mVeLbONU5CC1HOdVVw+uRlhVlxnfhTPd/Nru3rJx7R0sN7qXcZpJ 11 | PL87WvrszLVOux24DeaOz9oiD2b7egFyUuq1BM25iCwi8s/Ths8xd0Ca1d8mEcHW 12 | FVd4w2+nUMXFE+IbP+wo6FXuiSaOBNri3rztpvCCMaWjAgMBAAGjQjBAMB0GA1Ud 13 | DgQWBBSOlA+9Vfbcfy8iS4HSd4V0KPtm4jAfBgNVHSMEGDAWgBQGkdK2gR2HrQTn 14 | ZnbuWO7I1+wdxDANBgkqhkiG9w0BAQsFAAOCAQEAOmzm/MwowKTrSpMSrmfA3MmW 15 | ULzsfa25WyAoTl90ATlg4653Y7pRaNfdvVvyi2V2LlPcmc7E0rfD53t1NxjDH1uM 16 | LgFMTNEaZ9nMRSW0kMiwaRpvmXS8Eb9PXfvIM/Mw0co/aMOtAQnfTGIqsgkQwKyk 17 | 1GG7QKQq3p4QGu5ZaTnjnaoa79hODt+0xQDD1wp6C9xwBY0M4gndAi3wkOeFkGv+ 18 | OmGPtaCBu5V9tJCZ9dfZvjkaK44NGwDw0urAcYRK2h7asnlflu7cnlGMBB0qY4kQ 19 | BX5WI8UjN6rECBHbtNRvEh06ogDdHbxYV+TibrqkkeDRw6HX1qqiEJ+iCgWEDQ== 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /components/esp_websocket_client/examples/target/main/certs/client_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCrNeomxI2aoP+4 3 | iUy5SiA+41oHUDZDFeJOBjv5JCsKmlvFqxE9zynmPOVpuABErOJwzerPTa4NYKvu 4 | vs5CxVJUV5CXtWANuu9majioZNzjf877MDNX/GnZHK2gnkxVrZCPaDmx9yiMsFMX 5 | gmfdrDhwoUpXbdgSyeU/al9Ds2kF0hrHOH2LBWt/mVeLbONU5CC1HOdVVw+uRlhV 6 | lxnfhTPd/Nru3rJx7R0sN7qXcZpJPL87WvrszLVOux24DeaOz9oiD2b7egFyUuq1 7 | BM25iCwi8s/Ths8xd0Ca1d8mEcHWFVd4w2+nUMXFE+IbP+wo6FXuiSaOBNri3rzt 8 | pvCCMaWjAgMBAAECggEAOTWjz16AXroLmRMv8v5E9h6sN6Ni7lnCrAXDRoYCZ+Ga 9 | Ztu5wCiYPJn+oqvcUxZd+Ammu6yeS1QRP468h20+DHbSFw+BUDU1x8gYtJQ3h0Fu 10 | 3VqG3ZC3odfGYNRkd4CuvGy8Uq5e+1vz9/gYUuc4WNJccAiBWg3ir6UQviOWJV46 11 | LGfdEd9hVvIGl5pmArMBVYdpj9+JHunDtG4uQxiWla5pdLjlkC2mGexD18T9d718 12 | 6I+o3YHv1Y9RPT1d4rNhYQWx6YdTTD2rmS7nTrzroj/4fXsblpXzR+/l7crlNERY 13 | 67RMPwgDR1NiAbCAJKsSbMS66lRCNlhTM4YffGAN6QKBgQDkIdcNm9j49SK5Wbl5 14 | j8U6UbcVYPzPG+2ea+fDfUIafA0VQHIuX6FgA17Kp7BDX9ldKtSBpr0Z8vetVswr 15 | agmXVMR/7QdvnZ9NpL66YA/BRs67CvsryVu4AVAzThFGySmlcXGlPq47doWDQ3B9 16 | 0BOEnVoeDXR3SabaNsEbhDYn1wKBgQDAIAUyhJcgz+LcgaAtBwdnEN57y66JlRVZ 17 | bsb6cEG/MNmnLjQYsplJjNbz4yrB5ukTChPTGRF/JQRqHoXh6DGQFHvobukwwA6x 18 | RAIIq0NLJ5HUipfOi+VpCbWUHdoUNhwjAB2qVtD4LXE2Lyn46C8ET5eRtRjUKpzV 19 | lpsq63KHFQKBgFB+cDbpCoGtXPcxZXQy+lA9jPAKLKmXHRyMzlX32F8n7iXVe3RJ 20 | YdNS3Rt8V4EuTK/G8PxeLNL/G80ZlyiqXX/79Ol+ZOVJJHBs9K8mPejgZwkwMrec 21 | cLRYIkg3/3iOehdaE9NOboOkqi9KmGKMDJb6PlXkQXflkO3l6/UdjU45AoGAen0v 22 | sxiTncjMU1eVfn+nuY8ouXaPbYoOFXmqBItDb5i+e3baohBj6F+Rv+ZKIVuNp6Ta 23 | JNErtYstOFcDdpbp2nkk0ni71WftNhkszsgZ3DV7JS3DQV0xwvj8ulUZ757b63is 24 | cShujHu0XR5OvTGSoEX6VVxHWyVb3lTp0sBPwU0CgYBe2Ieuya0X8mAbputFN64S 25 | Kv++dqktTUT8i+tp07sIrpDeYwO3D89x9kVSJj4ImlmhiBVGkxFWPkpGyBLotdse 26 | Ai/E6f5I7CDSZZC0ZucgcItNd4Yy459QY+dFwFtT3kIaD9ml8fnqQ83J9W8DWtv9 27 | 6mY9FnUUufbJcpHxN58RTw== 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /components/esp_websocket_client/examples/target/main/certs/server/server_cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDWjCCAkKgAwIBAgIUUPCOgMA2v09E29fCkogx3RUBRtAwDQYJKoZIhvcNAQEL 3 | BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM 4 | GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDAyMjMwODA2NTlaFw0zNDAy 5 | MjAwODA2NTlaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw 6 | HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB 7 | AQUAA4IBDwAwggEKAoIBAQC8WWbDxnLzTSfuQaO+kQnnzbwjhUHWn58s+BIEaO8M 8 | GG6bX+8r/SH9XjMfFS36qAN3qxgRun3YoRTaHc2QByiGjf5IL4EAPDnLN+NzUIL5 9 | 7Gi2QPQP/GksAsOGKWk/nMRPk1vcMptkFVIWSp474SQ0A92Z9z0dUIqBpjRa34kr 10 | HsAIcT59/EG7YBBadMk0fQIxQVLh3Vosky85q+0waFihe47Ef5U2UftexoUx4Vcz 11 | 6EtP60Wx+4qN+FLsr+n2B7Oz2ITqfwgqLzjNLZwm9bMjcLZ0fWm1A/W1C989MXwI 12 | w6DAPEZv7pbgp8r9phyrNieSDuuRaCvFsaXh6troAjLxAgMBAAGjQjBAMB0GA1Ud 13 | DgQWBBRJCYAQG2+1FN5P/wyAR1AsrAyb4DAfBgNVHSMEGDAWgBQGkdK2gR2HrQTn 14 | ZnbuWO7I1+wdxDANBgkqhkiG9w0BAQsFAAOCAQEAmllul/GIH7RVq85mM/SxP47J 15 | M7Z7T032KuR3n/Psyv2iq/uEV2CUje3XrKNwR2PaJL4Q6CtoWy7xgIP+9CBbjddR 16 | M7sdNQab8P2crAUtBKnkNOl/na/5KnXnjwi/PmWJJ9i2Cqt0PPkaykTWp/MLfYIw 17 | RPkY2Yo8f8gEiqXQd+0qTuMgumbgkPq3V8Lk1ocy62F5/qUhXxH+ifAXEoUQS6EG 18 | 8DlgwdZlfUY+jeM6N56WzYmxD1syjNW7faPio+qXINfpYatROhqphaMQ5SA6TRj6 19 | jcnLa31TdDdWmWYDcYgZntAv6yGi3rh0MdYqeNS0FKlMKmaH81VHs7V1UUXwUQ== 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /components/esp_websocket_client/examples/target/main/certs/server/server_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC8WWbDxnLzTSfu 3 | QaO+kQnnzbwjhUHWn58s+BIEaO8MGG6bX+8r/SH9XjMfFS36qAN3qxgRun3YoRTa 4 | Hc2QByiGjf5IL4EAPDnLN+NzUIL57Gi2QPQP/GksAsOGKWk/nMRPk1vcMptkFVIW 5 | Sp474SQ0A92Z9z0dUIqBpjRa34krHsAIcT59/EG7YBBadMk0fQIxQVLh3Vosky85 6 | q+0waFihe47Ef5U2UftexoUx4Vcz6EtP60Wx+4qN+FLsr+n2B7Oz2ITqfwgqLzjN 7 | LZwm9bMjcLZ0fWm1A/W1C989MXwIw6DAPEZv7pbgp8r9phyrNieSDuuRaCvFsaXh 8 | 6troAjLxAgMBAAECggEACNVCggTxCCMCr+RJKxs/NS1LWPkbZNbYjrHVmnpXV6Bf 9 | s460t0HoUasUx6zlGp+9heOyvcYat8maIj6KkOodBu5q0fTUXm/0n+ivlI1ejxz8 10 | ritupr9GKWe5xrVzd6XA+SBmivWenvt2/Y+jSxica4oQ3vMe3RyVWk4yn15jXu+9 11 | 7B9lNyNeZtOBr6OozHGLYw4dwWcBNv2S6wevRKfHPwn/Ch5yTH1uAskgoMxUuyK2 12 | ynNVHWUhyS4pFU7Tex5ENDel15VYdbxV/2lQ2W6fHMLtC5GWKJXXbigCX7pfOpzC 13 | BFJEfZl7ze/qptE9AR7DkLFYyMtrS7OlebYbLDOM9wKBgQD+rTdwULZibpKwlI3a 14 | 9Y22d4N/EDFvuu8LnuEiVQnXgwg9M+tlaa2liP18j1a7y/FCfoXf5sjUWCsdYR6d 15 | C0TuiOGI59hYGI94NvVLAmOutR+vJ/3jhbv5wyqEQLhJ42Yz9kWBrDCI+V3q3TdO 16 | H7wcH6suUIZpeLEJF4qHzY/1dwKBgQC9U/Pvswiww8sfysmd5shUNo4ofAZnTM1A 17 | ak6pWE3lSyiOkSm+3B2GqxYWLRoo1v+pTyhhXDtRRmxGtMNrKCsmlHef/o3c6kkG 18 | cuC2h/DiSmoITHy3BYKJoDeE54E8ubXUUKqHo41LYUs+D7M/IGxeiO13MUoIrEtF 19 | AwzVWPBU1wKBgH8barD2x6Bm+XWCHy6qIZlxGsMfDN1r2gTdvhWJhcj3D/Sj5heO 20 | X+lfbsxtKee+yOHcDesK3y8D9jjKkSHmTvgSfyX6OML3NxvTqidOwPugUHj2J8QX 21 | qhLk8mJhftj50reacWRf0TV76ADhecnXEuaic6hA7mTTpOAZzL0svm3PAoGBALWF 22 | r6VLX3KzVqZVtLb7FWmAoQ35093pCgXPpznAW3cTd4Axd/fxbTG4CUYb2i/760X2 23 | ij3Gw2yqe5fTKmYsLisgQA2bb4K28msHa6I2dmNQe5cXVp/X3Y98mJ6JpCSH3ekB 24 | qm7ABfGXCCApx28n9B8zY5JbJKNqJgS15vELA+ojAoGAAkaV2w46+3iQ6gJtQepr 25 | zGNybiYBx/Wo5fDdTS5u0xN+ZdC9fl2Zs0n7sMmUT8bWdDLcMnntHHO+oDIKyRHs 26 | TQh1n68vQ4JoegQv3Z9Z/TLEKqr9gyJC1Ao6M4bZpPhUWQwupfHColtsr2TskcnJ 27 | Nf2FpJZ7z6fQEShGlK1yTXM= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /components/esp_websocket_client/examples/target/main/idf_component.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | ## Required IDF version 3 | idf: ">=5.0" 4 | espressif/esp_websocket_client: 5 | version: "^1.0.0" 6 | override_path: "../../../" 7 | protocol_examples_common: 8 | path: ${IDF_PATH}/examples/common_components/protocol_examples_common 9 | -------------------------------------------------------------------------------- /components/esp_websocket_client/examples/target/main/websocket_example.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD 3 | * 4 | * SPDX-License-Identifier: Unlicense OR CC0-1.0 5 | */ 6 | /* ESP HTTP Client Example 7 | 8 | This example code is in the Public Domain (or CC0 licensed, at your option.) 9 | 10 | Unless required by applicable law or agreed to in writing, this 11 | software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 12 | CONDITIONS OF ANY KIND, either express or implied. 13 | */ 14 | 15 | 16 | #include 17 | #include "esp_wifi.h" 18 | #include "esp_system.h" 19 | #include "nvs_flash.h" 20 | #include "esp_event.h" 21 | #include "protocol_examples_common.h" 22 | #include "esp_crt_bundle.h" 23 | 24 | #include "freertos/FreeRTOS.h" 25 | #include "freertos/task.h" 26 | #include "freertos/semphr.h" 27 | #include "freertos/event_groups.h" 28 | 29 | #include "esp_log.h" 30 | #include "esp_websocket_client.h" 31 | #include "esp_event.h" 32 | #include 33 | 34 | #define NO_DATA_TIMEOUT_SEC 5 35 | 36 | static const char *TAG = "websocket"; 37 | 38 | static TimerHandle_t shutdown_signal_timer; 39 | static SemaphoreHandle_t shutdown_sema; 40 | 41 | static void log_error_if_nonzero(const char *message, int error_code) 42 | { 43 | if (error_code != 0) { 44 | ESP_LOGE(TAG, "Last error %s: 0x%x", message, error_code); 45 | } 46 | } 47 | 48 | static void shutdown_signaler(TimerHandle_t xTimer) 49 | { 50 | ESP_LOGI(TAG, "No data received for %d seconds, signaling shutdown", NO_DATA_TIMEOUT_SEC); 51 | xSemaphoreGive(shutdown_sema); 52 | } 53 | 54 | #if CONFIG_WEBSOCKET_URI_FROM_STDIN 55 | static void get_string(char *line, size_t size) 56 | { 57 | int count = 0; 58 | while (count < size) { 59 | int c = fgetc(stdin); 60 | if (c == '\n') { 61 | line[count] = '\0'; 62 | break; 63 | } else if (c > 0 && c < 127) { 64 | line[count] = c; 65 | ++count; 66 | } 67 | vTaskDelay(10 / portTICK_PERIOD_MS); 68 | } 69 | } 70 | 71 | #endif /* CONFIG_WEBSOCKET_URI_FROM_STDIN */ 72 | 73 | static void websocket_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) 74 | { 75 | esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data; 76 | switch (event_id) { 77 | case WEBSOCKET_EVENT_BEGIN: 78 | ESP_LOGI(TAG, "WEBSOCKET_EVENT_BEGIN"); 79 | break; 80 | case WEBSOCKET_EVENT_CONNECTED: 81 | ESP_LOGI(TAG, "WEBSOCKET_EVENT_CONNECTED"); 82 | break; 83 | case WEBSOCKET_EVENT_DISCONNECTED: 84 | ESP_LOGI(TAG, "WEBSOCKET_EVENT_DISCONNECTED"); 85 | log_error_if_nonzero("HTTP status code", data->error_handle.esp_ws_handshake_status_code); 86 | if (data->error_handle.error_type == WEBSOCKET_ERROR_TYPE_TCP_TRANSPORT) { 87 | log_error_if_nonzero("reported from esp-tls", data->error_handle.esp_tls_last_esp_err); 88 | log_error_if_nonzero("reported from tls stack", data->error_handle.esp_tls_stack_err); 89 | log_error_if_nonzero("captured as transport's socket errno", data->error_handle.esp_transport_sock_errno); 90 | } 91 | break; 92 | case WEBSOCKET_EVENT_DATA: 93 | ESP_LOGI(TAG, "WEBSOCKET_EVENT_DATA"); 94 | ESP_LOGI(TAG, "Received opcode=%d", data->op_code); 95 | if (data->op_code == 0x2) { // Opcode 0x2 indicates binary data 96 | ESP_LOG_BUFFER_HEX("Received binary data", data->data_ptr, data->data_len); 97 | } else if (data->op_code == 0x08 && data->data_len == 2) { 98 | ESP_LOGW(TAG, "Received closed message with code=%d", 256 * data->data_ptr[0] + data->data_ptr[1]); 99 | } else { 100 | ESP_LOGW(TAG, "Received=%.*s\n\n", data->data_len, (char *)data->data_ptr); 101 | } 102 | 103 | // If received data contains json structure it succeed to parse 104 | cJSON *root = cJSON_Parse(data->data_ptr); 105 | if (root) { 106 | for (int i = 0 ; i < cJSON_GetArraySize(root) ; i++) { 107 | cJSON *elem = cJSON_GetArrayItem(root, i); 108 | cJSON *id = cJSON_GetObjectItem(elem, "id"); 109 | cJSON *name = cJSON_GetObjectItem(elem, "name"); 110 | ESP_LOGW(TAG, "Json={'id': '%s', 'name': '%s'}", id->valuestring, name->valuestring); 111 | } 112 | cJSON_Delete(root); 113 | } 114 | 115 | ESP_LOGW(TAG, "Total payload length=%d, data_len=%d, current payload offset=%d\r\n", data->payload_len, data->data_len, data->payload_offset); 116 | 117 | xTimerReset(shutdown_signal_timer, portMAX_DELAY); 118 | break; 119 | case WEBSOCKET_EVENT_ERROR: 120 | ESP_LOGI(TAG, "WEBSOCKET_EVENT_ERROR"); 121 | log_error_if_nonzero("HTTP status code", data->error_handle.esp_ws_handshake_status_code); 122 | if (data->error_handle.error_type == WEBSOCKET_ERROR_TYPE_TCP_TRANSPORT) { 123 | log_error_if_nonzero("reported from esp-tls", data->error_handle.esp_tls_last_esp_err); 124 | log_error_if_nonzero("reported from tls stack", data->error_handle.esp_tls_stack_err); 125 | log_error_if_nonzero("captured as transport's socket errno", data->error_handle.esp_transport_sock_errno); 126 | } 127 | break; 128 | case WEBSOCKET_EVENT_FINISH: 129 | ESP_LOGI(TAG, "WEBSOCKET_EVENT_FINISH"); 130 | break; 131 | } 132 | } 133 | 134 | static void websocket_app_start(void) 135 | { 136 | esp_websocket_client_config_t websocket_cfg = {}; 137 | 138 | shutdown_signal_timer = xTimerCreate("Websocket shutdown timer", NO_DATA_TIMEOUT_SEC * 1000 / portTICK_PERIOD_MS, 139 | pdFALSE, NULL, shutdown_signaler); 140 | shutdown_sema = xSemaphoreCreateBinary(); 141 | 142 | #if CONFIG_WEBSOCKET_URI_FROM_STDIN 143 | char line[128]; 144 | 145 | ESP_LOGI(TAG, "Please enter uri of websocket endpoint"); 146 | get_string(line, sizeof(line)); 147 | 148 | websocket_cfg.uri = line; 149 | ESP_LOGI(TAG, "Endpoint uri: %s\n", line); 150 | 151 | #else 152 | websocket_cfg.uri = CONFIG_WEBSOCKET_URI; 153 | #endif /* CONFIG_WEBSOCKET_URI_FROM_STDIN */ 154 | 155 | #if CONFIG_WS_OVER_TLS_MUTUAL_AUTH 156 | /* Configuring client certificates for mutual authentification */ 157 | extern const char cacert_start[] asm("_binary_ca_cert_pem_start"); // CA certificate 158 | extern const char cert_start[] asm("_binary_client_cert_pem_start"); // Client certificate 159 | extern const char cert_end[] asm("_binary_client_cert_pem_end"); 160 | extern const char key_start[] asm("_binary_client_key_pem_start"); // Client private key 161 | extern const char key_end[] asm("_binary_client_key_pem_end"); 162 | 163 | websocket_cfg.cert_pem = cacert_start; 164 | websocket_cfg.client_cert = cert_start; 165 | websocket_cfg.client_cert_len = cert_end - cert_start; 166 | websocket_cfg.client_key = key_start; 167 | websocket_cfg.client_key_len = key_end - key_start; 168 | #elif CONFIG_WS_OVER_TLS_SERVER_AUTH 169 | // Using certificate bundle as default server certificate source 170 | websocket_cfg.crt_bundle_attach = esp_crt_bundle_attach; 171 | // If using a custom certificate it could be added to certificate bundle, added to the build similar to client certificates in this examples, 172 | // or read from NVS. 173 | /* extern const char cacert_start[] asm("ADDED_CERTIFICATE"); */ 174 | /* websocket_cfg.cert_pem = cacert_start; */ 175 | #endif 176 | 177 | #if CONFIG_WS_OVER_TLS_SKIP_COMMON_NAME_CHECK 178 | websocket_cfg.skip_cert_common_name_check = true; 179 | #endif 180 | 181 | ESP_LOGI(TAG, "Connecting to %s...", websocket_cfg.uri); 182 | 183 | esp_websocket_client_handle_t client = esp_websocket_client_init(&websocket_cfg); 184 | esp_websocket_register_events(client, WEBSOCKET_EVENT_ANY, websocket_event_handler, (void *)client); 185 | 186 | esp_websocket_client_start(client); 187 | xTimerStart(shutdown_signal_timer, portMAX_DELAY); 188 | char data[32]; 189 | int i = 0; 190 | while (i < 5) { 191 | if (esp_websocket_client_is_connected(client)) { 192 | int len = sprintf(data, "hello %04d", i++); 193 | ESP_LOGI(TAG, "Sending %s", data); 194 | esp_websocket_client_send_text(client, data, len, portMAX_DELAY); 195 | } 196 | vTaskDelay(1000 / portTICK_PERIOD_MS); 197 | } 198 | 199 | vTaskDelay(1000 / portTICK_PERIOD_MS); 200 | // Sending text data 201 | ESP_LOGI(TAG, "Sending fragmented text message"); 202 | memset(data, 'a', sizeof(data)); 203 | esp_websocket_client_send_text_partial(client, data, sizeof(data), portMAX_DELAY); 204 | memset(data, 'b', sizeof(data)); 205 | esp_websocket_client_send_cont_msg(client, data, sizeof(data), portMAX_DELAY); 206 | esp_websocket_client_send_fin(client, portMAX_DELAY); 207 | vTaskDelay(1000 / portTICK_PERIOD_MS); 208 | 209 | // Sending binary data 210 | ESP_LOGI(TAG, "Sending fragmented binary message"); 211 | char binary_data[5]; 212 | memset(binary_data, 0, sizeof(binary_data)); 213 | esp_websocket_client_send_bin_partial(client, binary_data, sizeof(binary_data), portMAX_DELAY); 214 | memset(binary_data, 1, sizeof(binary_data)); 215 | esp_websocket_client_send_cont_msg(client, binary_data, sizeof(binary_data), portMAX_DELAY); 216 | esp_websocket_client_send_fin(client, portMAX_DELAY); 217 | vTaskDelay(1000 / portTICK_PERIOD_MS); 218 | 219 | // Sending text data longer than ws buffer (default 1024) 220 | ESP_LOGI(TAG, "Sending text longer than ws buffer (default 1024)"); 221 | const int size = 2000; 222 | char *long_data = malloc(size); 223 | memset(long_data, 'a', size); 224 | esp_websocket_client_send_text(client, long_data, size, portMAX_DELAY); 225 | free(long_data); 226 | 227 | xSemaphoreTake(shutdown_sema, portMAX_DELAY); 228 | esp_websocket_client_close(client, portMAX_DELAY); 229 | ESP_LOGI(TAG, "Websocket Stopped"); 230 | esp_websocket_unregister_events(client, WEBSOCKET_EVENT_ANY, websocket_event_handler); 231 | esp_websocket_client_destroy(client); 232 | } 233 | 234 | void app_main(void) 235 | { 236 | ESP_LOGI(TAG, "[APP] Startup.."); 237 | ESP_LOGI(TAG, "[APP] Free memory: %" PRIu32 " bytes", esp_get_free_heap_size()); 238 | ESP_LOGI(TAG, "[APP] IDF version: %s", esp_get_idf_version()); 239 | esp_log_level_set("*", ESP_LOG_INFO); 240 | esp_log_level_set("websocket_client", ESP_LOG_DEBUG); 241 | esp_log_level_set("transport_ws", ESP_LOG_DEBUG); 242 | esp_log_level_set("trans_tcp", ESP_LOG_DEBUG); 243 | 244 | ESP_ERROR_CHECK(nvs_flash_init()); 245 | ESP_ERROR_CHECK(esp_netif_init()); 246 | ESP_ERROR_CHECK(esp_event_loop_create_default()); 247 | 248 | /* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig. 249 | * Read "Establishing Wi-Fi or Ethernet Connection" section in 250 | * examples/protocols/README.md for more information about this function. 251 | */ 252 | ESP_ERROR_CHECK(example_connect()); 253 | 254 | websocket_app_start(); 255 | } 256 | -------------------------------------------------------------------------------- /components/esp_websocket_client/examples/target/pytest_websocket.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD 2 | # SPDX-License-Identifier: Unlicense OR CC0-1.0 3 | import json 4 | import random 5 | import re 6 | import socket 7 | import ssl 8 | import string 9 | import sys 10 | from threading import Event, Thread 11 | 12 | from SimpleWebSocketServer import (SimpleSSLWebSocketServer, 13 | SimpleWebSocketServer, WebSocket) 14 | 15 | 16 | def get_my_ip(): 17 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 18 | try: 19 | # doesn't even have to be reachable 20 | s.connect(('8.8.8.8', 1)) 21 | IP = s.getsockname()[0] 22 | except Exception: 23 | IP = '127.0.0.1' 24 | finally: 25 | s.close() 26 | return IP 27 | 28 | 29 | class WebsocketTestEcho(WebSocket): 30 | def handleMessage(self): 31 | if isinstance(self.data, bytes): 32 | print(f'\n Server received binary data: {self.data.hex()}\n') 33 | self.sendMessage(self.data, binary=True) 34 | else: 35 | print(f'\n Server received: {self.data}\n') 36 | self.sendMessage(self.data) 37 | 38 | def handleConnected(self): 39 | print('Connection from: {}'.format(self.address)) 40 | 41 | def handleClose(self): 42 | print('{} closed the connection'.format(self.address)) 43 | 44 | 45 | # Simple Websocket server for testing purposes 46 | class Websocket(object): 47 | 48 | def send_data(self, data): 49 | for nr, conn in self.server.connections.items(): 50 | conn.sendMessage(data) 51 | 52 | def run(self): 53 | if self.use_tls is True: 54 | ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) 55 | ssl_context.load_cert_chain(certfile='main/certs/server/server_cert.pem', keyfile='main/certs/server/server_key.pem') 56 | if self.client_verify is True: 57 | ssl_context.load_verify_locations(cafile='main/certs/ca_cert.pem') 58 | ssl_context.verify_mode = ssl.CERT_REQUIRED 59 | ssl_context.check_hostname = False 60 | self.server = SimpleSSLWebSocketServer('', self.port, WebsocketTestEcho, ssl_context=ssl_context) 61 | else: 62 | self.server = SimpleWebSocketServer('', self.port, WebsocketTestEcho) 63 | while not self.exit_event.is_set(): 64 | self.server.serveonce() 65 | 66 | def __init__(self, port, use_tls, verify): 67 | self.port = port 68 | self.use_tls = use_tls 69 | self.client_verify = verify 70 | self.exit_event = Event() 71 | self.thread = Thread(target=self.run) 72 | self.thread.start() 73 | 74 | def __enter__(self): 75 | return self 76 | 77 | def __exit__(self, exc_type, exc_value, traceback): 78 | self.exit_event.set() 79 | self.thread.join(10) 80 | if self.thread.is_alive(): 81 | print('Thread cannot be joined', 'orange') 82 | 83 | 84 | def test_examples_protocol_websocket(dut): 85 | """ 86 | steps: 87 | 1. obtain IP address 88 | 2. connect to uri specified in the config 89 | 3. send and receive data 90 | """ 91 | 92 | # Test for echo functionality: 93 | # Sends a series of simple "hello" messages to the WebSocket server and verifies that each one is echoed back correctly. 94 | # This tests the basic responsiveness and correctness of the WebSocket connection. 95 | def test_echo(dut): 96 | dut.expect('WEBSOCKET_EVENT_CONNECTED') 97 | for i in range(0, 5): 98 | dut.expect(re.compile(b'Received=hello (\\d)')) 99 | print('All echos received') 100 | sys.stdout.flush() 101 | 102 | # Test for clean closure of the WebSocket connection: 103 | # Ensures that the WebSocket can correctly receive a close frame and terminate the connection without issues. 104 | def test_close(dut): 105 | code = dut.expect( 106 | re.compile( 107 | b'websocket: Received closed message with code=(\\d*)'))[0] 108 | print('Received close frame with code {}'.format(code)) 109 | 110 | # Test for JSON message handling: 111 | # Sends a JSON formatted string and verifies that the received message matches the expected JSON structure. 112 | def test_json(dut, websocket): 113 | json_string = """ 114 | [ 115 | { 116 | "id":"1", 117 | "name":"user1" 118 | }, 119 | { 120 | "id":"2", 121 | "name":"user2" 122 | } 123 | ] 124 | """ 125 | websocket.send_data(json_string) 126 | data = json.loads(json_string) 127 | 128 | match = dut.expect( 129 | re.compile(b'Json=({[a-zA-Z0-9]*).*}')).group(0).decode()[5:] 130 | if match == str(data[0]): 131 | print('\n Sent message and received message are equal \n') 132 | sys.stdout.flush() 133 | else: 134 | raise ValueError( 135 | 'DUT received string do not match sent string, \nexpected: {}\nwith length {}\ 136 | \nreceived: {}\nwith length {}'.format( 137 | data[0], len(data[0]), match, len(match))) 138 | 139 | # Test for receiving long messages: 140 | # This sends a message with a specified length (2000 characters) to ensure the WebSocket can handle large data payloads. Repeated 3 times for reliability. 141 | def test_recv_long_msg(dut, websocket, msg_len, repeats): 142 | 143 | send_msg = ''.join( 144 | random.choice(string.ascii_uppercase + string.ascii_lowercase + 145 | string.digits) for _ in range(msg_len)) 146 | 147 | for _ in range(repeats): 148 | websocket.send_data(send_msg) 149 | 150 | recv_msg = '' 151 | while len(recv_msg) < msg_len: 152 | match = dut.expect(re.compile( 153 | b'Received=([a-zA-Z0-9]*).*\n')).group(1).decode() 154 | recv_msg += match 155 | 156 | if recv_msg == send_msg: 157 | print('\n Sent message and received message are equal \n') 158 | sys.stdout.flush() 159 | else: 160 | raise ValueError( 161 | 'DUT received string do not match sent string, \nexpected: {}\nwith length {}\ 162 | \nreceived: {}\nwith length {}'.format( 163 | send_msg, len(send_msg), recv_msg, len(recv_msg))) 164 | 165 | # Test for receiving the first fragment of a large message: 166 | # Verifies the WebSocket's ability to correctly process the initial segment of a fragmented message. 167 | def test_recv_fragmented_msg1(dut): 168 | dut.expect('websocket: Total payload length=2000, data_len=1024, current payload offset=0') 169 | 170 | # Test for receiving the second fragment of a large message: 171 | # Confirms that the WebSocket can correctly handle and process the subsequent segment of a fragmented message. 172 | def test_recv_fragmented_msg2(dut): 173 | dut.expect('websocket: Total payload length=2000, data_len=976, current payload offset=1024') 174 | 175 | # Test for receiving fragmented text messages: 176 | # Checks if the WebSocket can accurately reconstruct a message sent in several smaller parts. 177 | def test_fragmented_txt_msg(dut): 178 | dut.expect('Received=' + 32 * 'a' + 32 * 'b') 179 | print('\nFragmented data received\n') 180 | 181 | # Extract the hexdump portion of the log line 182 | def parse_hexdump(line): 183 | match = re.search(r'\(.*\) Received binary data: ([0-9A-Fa-f ]+)', line) 184 | if match: 185 | hexdump = match.group(1).strip().replace(' ', '') 186 | # Convert the hexdump string to a bytearray 187 | return bytearray.fromhex(hexdump) 188 | return bytearray() 189 | 190 | # Capture the binary log output from the DUT 191 | def test_fragmented_binary_msg(dut): 192 | match = dut.expect(r'\(.*\) Received binary data: .*') 193 | if match: 194 | line = match.group(0).strip() 195 | if isinstance(line, bytes): 196 | line = line.decode('utf-8') 197 | 198 | # Parse the hexdump from the log line 199 | received_data = parse_hexdump(line) 200 | 201 | # Create the expected bytearray with the specified pattern 202 | expected_data = bytearray([0, 0, 0, 0, 0, 1, 1, 1, 1, 1]) 203 | 204 | # Validate the received data 205 | assert received_data == expected_data, f'Received data does not match expected data. Received: {received_data}, Expected: {expected_data}' 206 | print('\nFragmented data received\n') 207 | else: 208 | assert False, 'Log line with binary data not found' 209 | 210 | # Starting of the test 211 | try: 212 | if dut.app.sdkconfig.get('WEBSOCKET_URI_FROM_STDIN') is True: 213 | uri_from_stdin = True 214 | else: 215 | uri = dut.app.sdkconfig['WEBSOCKET_URI'] 216 | uri_from_stdin = False 217 | 218 | if dut.app.sdkconfig.get('WS_OVER_TLS_MUTUAL_AUTH') is True: 219 | use_tls = True 220 | client_verify = True 221 | else: 222 | use_tls = False 223 | client_verify = False 224 | 225 | except Exception: 226 | print('ENV_TEST_FAILURE: Cannot find uri settings in sdkconfig') 227 | raise 228 | 229 | if uri_from_stdin: 230 | server_port = 8080 231 | with Websocket(server_port, use_tls, client_verify) as ws: 232 | if use_tls is True: 233 | uri = 'wss://{}:{}'.format(get_my_ip(), server_port) 234 | else: 235 | uri = 'ws://{}:{}'.format(get_my_ip(), server_port) 236 | print('DUT connecting to {}'.format(uri)) 237 | dut.expect('Please enter uri of websocket endpoint', timeout=30) 238 | dut.write(uri) 239 | test_echo(dut) 240 | test_recv_long_msg(dut, ws, 2000, 3) 241 | test_json(dut, ws) 242 | test_fragmented_txt_msg(dut) 243 | test_fragmented_binary_msg(dut) 244 | test_recv_fragmented_msg1(dut) 245 | test_recv_fragmented_msg2(dut) 246 | test_close(dut) 247 | else: 248 | print('DUT connecting to {}'.format(uri)) 249 | test_echo(dut) 250 | -------------------------------------------------------------------------------- /components/esp_websocket_client/examples/target/sdkconfig.ci: -------------------------------------------------------------------------------- 1 | CONFIG_IDF_TARGET="esp32" 2 | CONFIG_IDF_TARGET_LINUX=n 3 | CONFIG_WEBSOCKET_URI_FROM_STDIN=n 4 | CONFIG_WEBSOCKET_URI_FROM_STRING=y 5 | CONFIG_EXAMPLE_CONNECT_ETHERNET=y 6 | CONFIG_EXAMPLE_CONNECT_WIFI=n 7 | CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y 8 | CONFIG_EXAMPLE_ETH_PHY_IP101=y 9 | CONFIG_EXAMPLE_ETH_MDC_GPIO=23 10 | CONFIG_EXAMPLE_ETH_MDIO_GPIO=18 11 | CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5 12 | CONFIG_EXAMPLE_ETH_PHY_ADDR=1 13 | CONFIG_EXAMPLE_CONNECT_IPV6=y 14 | -------------------------------------------------------------------------------- /components/esp_websocket_client/examples/target/sdkconfig.ci.dynamic_buffer: -------------------------------------------------------------------------------- 1 | CONFIG_IDF_TARGET="esp32" 2 | CONFIG_IDF_TARGET_LINUX=n 3 | CONFIG_WEBSOCKET_URI_FROM_STDIN=n 4 | CONFIG_WEBSOCKET_URI_FROM_STRING=y 5 | CONFIG_EXAMPLE_CONNECT_ETHERNET=y 6 | CONFIG_EXAMPLE_CONNECT_WIFI=n 7 | CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y 8 | CONFIG_EXAMPLE_ETH_PHY_IP101=y 9 | CONFIG_EXAMPLE_ETH_MDC_GPIO=23 10 | CONFIG_EXAMPLE_ETH_MDIO_GPIO=18 11 | CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5 12 | CONFIG_EXAMPLE_ETH_PHY_ADDR=1 13 | CONFIG_EXAMPLE_CONNECT_IPV6=y 14 | CONFIG_ESP_WS_CLIENT_ENABLE_DYNAMIC_BUFFER=y 15 | -------------------------------------------------------------------------------- /components/esp_websocket_client/examples/target/sdkconfig.ci.mutual_auth: -------------------------------------------------------------------------------- 1 | CONFIG_IDF_TARGET="esp32" 2 | CONFIG_IDF_TARGET_LINUX=n 3 | CONFIG_WEBSOCKET_URI_FROM_STDIN=y 4 | CONFIG_WEBSOCKET_URI_FROM_STRING=n 5 | CONFIG_EXAMPLE_CONNECT_ETHERNET=y 6 | CONFIG_EXAMPLE_CONNECT_WIFI=n 7 | CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y 8 | CONFIG_EXAMPLE_ETH_PHY_IP101=y 9 | CONFIG_EXAMPLE_ETH_MDC_GPIO=23 10 | CONFIG_EXAMPLE_ETH_MDIO_GPIO=18 11 | CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5 12 | CONFIG_EXAMPLE_ETH_PHY_ADDR=1 13 | CONFIG_EXAMPLE_CONNECT_IPV6=y 14 | CONFIG_WS_OVER_TLS_MUTUAL_AUTH=y 15 | CONFIG_WS_OVER_TLS_SKIP_COMMON_NAME_CHECK=y 16 | -------------------------------------------------------------------------------- /components/esp_websocket_client/examples/target/sdkconfig.ci.plain_tcp: -------------------------------------------------------------------------------- 1 | CONFIG_IDF_TARGET="esp32" 2 | CONFIG_IDF_TARGET_LINUX=n 3 | CONFIG_WEBSOCKET_URI_FROM_STDIN=y 4 | CONFIG_WEBSOCKET_URI_FROM_STRING=n 5 | CONFIG_EXAMPLE_CONNECT_ETHERNET=y 6 | CONFIG_EXAMPLE_CONNECT_WIFI=n 7 | CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET=y 8 | CONFIG_EXAMPLE_ETH_PHY_IP101=y 9 | CONFIG_EXAMPLE_ETH_MDC_GPIO=23 10 | CONFIG_EXAMPLE_ETH_MDIO_GPIO=18 11 | CONFIG_EXAMPLE_ETH_PHY_RST_GPIO=5 12 | CONFIG_EXAMPLE_ETH_PHY_ADDR=1 13 | CONFIG_EXAMPLE_CONNECT_IPV6=y 14 | CONFIG_WS_OVER_TLS_MUTUAL_AUTH=n 15 | CONFIG_WS_OVER_TLS_SERVER_AUTH=n 16 | -------------------------------------------------------------------------------- /components/esp_websocket_client/idf_component.yml: -------------------------------------------------------------------------------- 1 | version: "1.4.0" 2 | description: WebSocket protocol client for ESP-IDF 3 | url: https://github.com/espressif/esp-protocols/tree/master/components/esp_websocket_client 4 | dependencies: 5 | idf: 6 | version: ">=5.0" 7 | -------------------------------------------------------------------------------- /components/esp_websocket_client/test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This is the project CMakeLists.txt file for the test subproject 2 | cmake_minimum_required(VERSION 3.16) 3 | 4 | set(EXTRA_COMPONENT_DIRS ../../esp_websocket_client 5 | "$ENV{IDF_PATH}/tools/unit-test-app/components") 6 | 7 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 8 | project(websocket_unit_test) 9 | -------------------------------------------------------------------------------- /components/esp_websocket_client/test/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register(SRCS "test_websocket_client.c" 2 | REQUIRES test_utils 3 | INCLUDE_DIRS "." 4 | PRIV_REQUIRES unity esp_websocket_client esp_event) 5 | -------------------------------------------------------------------------------- /components/esp_websocket_client/test/main/test_websocket_client.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD 3 | * 4 | * SPDX-License-Identifier: Unlicense OR CC0-1.0 5 | * 6 | * This test code is in the Public Domain (or CC0 licensed, at your option.) 7 | * 8 | * Unless required by applicable law or agreed to in writing, this 9 | * software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 10 | * CONDITIONS OF ANY KIND, either express or implied. 11 | */ 12 | 13 | #include 14 | #include 15 | #include 16 | #include "esp_event.h" 17 | #include "unity.h" 18 | #include "test_utils.h" 19 | 20 | #include "unity_fixture.h" 21 | #include "memory_checks.h" 22 | 23 | TEST_GROUP(websocket); 24 | 25 | TEST_SETUP(websocket) 26 | { 27 | #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 1, 0) 28 | /* IDF v5.0 runs some lazy inits within printf() 29 | * This test sets the leak threshold to 0, so we need to call printf() 30 | * before recording the heap size in test_utils_record_free_mem() 31 | */ 32 | printf("TEST_SETUP: websocket\n"); 33 | #endif 34 | test_utils_record_free_mem(); 35 | TEST_ESP_OK(test_utils_set_leak_level(0, ESP_LEAK_TYPE_CRITICAL, ESP_COMP_LEAK_GENERAL)); 36 | } 37 | 38 | TEST_TEAR_DOWN(websocket) 39 | { 40 | test_utils_finish_and_evaluate_leaks(0, 0); 41 | } 42 | 43 | 44 | TEST(websocket, websocket_init_deinit) 45 | { 46 | const esp_websocket_client_config_t websocket_cfg = { 47 | // no connection takes place, but the uri has to be valid for init() to succeed 48 | .uri = "ws://echo.websocket.org", 49 | }; 50 | esp_websocket_client_handle_t client = esp_websocket_client_init(&websocket_cfg); 51 | TEST_ASSERT_NOT_EQUAL(NULL, client); 52 | esp_websocket_client_destroy(client); 53 | } 54 | 55 | TEST(websocket, websocket_init_invalid_url) 56 | { 57 | const esp_websocket_client_config_t websocket_cfg = { 58 | .uri = "INVALID", 59 | }; 60 | esp_websocket_client_handle_t client = esp_websocket_client_init(&websocket_cfg); 61 | TEST_ASSERT_NULL(client); 62 | } 63 | 64 | TEST(websocket, websocket_set_invalid_url) 65 | { 66 | const esp_websocket_client_config_t websocket_cfg = {}; 67 | esp_websocket_client_handle_t client = esp_websocket_client_init(&websocket_cfg); 68 | TEST_ASSERT_NOT_EQUAL(NULL, client); 69 | TEST_ASSERT_NOT_EQUAL(ESP_OK, esp_websocket_client_set_uri(client, "INVALID")); 70 | esp_websocket_client_destroy(client); 71 | } 72 | 73 | TEST_GROUP_RUNNER(websocket) 74 | { 75 | RUN_TEST_CASE(websocket, websocket_init_deinit) 76 | RUN_TEST_CASE(websocket, websocket_init_invalid_url) 77 | RUN_TEST_CASE(websocket, websocket_set_invalid_url) 78 | } 79 | 80 | void app_main(void) 81 | { 82 | UNITY_MAIN(websocket); 83 | } 84 | -------------------------------------------------------------------------------- /components/esp_websocket_client/test/pytest_websocket.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | from pytest_embedded import Dut 5 | 6 | 7 | def test_websocket(dut: Dut) -> None: 8 | dut.expect_unity_test_output() 9 | -------------------------------------------------------------------------------- /components/esp_websocket_client/test/sdkconfig.ci: -------------------------------------------------------------------------------- 1 | CONFIG_IDF_TARGET="esp32" 2 | CONFIG_UNITY_ENABLE_FIXTURE=y 3 | CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n 4 | -------------------------------------------------------------------------------- /components/esp_websocket_client/test/sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | CONFIG_UNITY_ENABLE_FIXTURE=y 2 | CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n 3 | -------------------------------------------------------------------------------- /correct_firmware_hash.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import sys 3 | import struct 4 | 5 | def get_esptool_output(esptool_path, file_path): 6 | result = subprocess.run( 7 | [esptool_path, '--chip', 'esp32', 'image_info', file_path], 8 | capture_output=True, 9 | text=True 10 | ) 11 | print(result.stdout) 12 | return result.stdout 13 | 14 | def parse_esptool_output(output): 15 | lines = output.splitlines() 16 | checksum = None 17 | sha256 = None 18 | for line in lines: 19 | print(line) 20 | if line.startswith("Checksum:"): 21 | parts = line.split() 22 | if "(invalid" in line: 23 | checksum = parts[-1].strip('()') 24 | else: 25 | checksum = parts[1] 26 | elif line.startswith("Validation Hash:"): 27 | sha256 = line.split()[-2] 28 | print(checksum,sha256) 29 | return checksum, sha256 30 | 31 | def update_firmware_file_with_checksum(file_path, checksum): 32 | checksum_byte = int(checksum, 16) 33 | with open(file_path, 'r+b') as f: 34 | # Write the checksum at position 33 from the end 35 | f.seek(-33, 2) 36 | f.write(struct.pack('B', checksum_byte)) 37 | 38 | def update_firmware_file_with_sha256(file_path, sha256): 39 | sha256_bytes = bytes.fromhex(sha256) 40 | with open(file_path, 'r+b') as f: 41 | # Write the SHA256 hash at the end 42 | f.seek(-32, 2) 43 | f.write(sha256_bytes) 44 | 45 | if __name__ == '__main__': 46 | if len(sys.argv) != 3: 47 | print(f"Usage: {sys.argv[0]} ") 48 | sys.exit(1) 49 | 50 | esptool_path = sys.argv[1] 51 | file_path = sys.argv[2] 52 | 53 | # Run esptool to get the initial checksum and SHA256 54 | esptool_output = get_esptool_output(esptool_path, file_path) 55 | print(f"esptool output: {esptool_output}") 56 | checksum, sha256 = parse_esptool_output(esptool_output) 57 | 58 | # Update the file with the correct checksum 59 | update_firmware_file_with_checksum(file_path, checksum) 60 | print(f"Updated file with checksum {checksum}.") 61 | 62 | # Run esptool again to get the new SHA256 after fixing the checksum 63 | esptool_output = get_esptool_output(esptool_path, file_path) 64 | _, sha256 = parse_esptool_output(esptool_output) 65 | 66 | # Update the file with the new SHA256 67 | update_firmware_file_with_sha256(file_path, sha256) 68 | print(f"Updated file with SHA256 {sha256}.") 69 | -------------------------------------------------------------------------------- /docs/CLA.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tronbyt/firmware-esp32/ecc6fa89bab95b88a2d3b8d2fd1785dd3f4811b9/docs/CLA.md -------------------------------------------------------------------------------- /docs/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tronbyt/firmware-esp32/ecc6fa89bab95b88a2d3b8d2fd1785dd3f4811b9/docs/CODE_OF_CONDUCT.md -------------------------------------------------------------------------------- /docs/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | This project and everyone participating in it is governed by the [Contributor Covenant Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to community@tidbyt.com via email. 2 | 3 | In addition, we have a [Contributor License Agreement](CLA.md) that you'll be asked to sign a part of the pull request process through an automated tool. This agreement offers protections to both you, the contributor and us, Tidbyt, Inc. It is based on the [Apache Software Foundation's CLAs](https://www.apache.org/licenses/contributor-agreements.html). 4 | 5 | 6 | OK! Now that we're past the boring part, check out our [README on how to contribute various changes](../README.md#contributing-changes). 7 | -------------------------------------------------------------------------------- /docs/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security -------------------------------------------------------------------------------- /docs/SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Support 2 | Looking for help on how to get started beyond the documentation in this repo? Create an issue here on github or join the discord. -------------------------------------------------------------------------------- /docs/assets/social.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tronbyt/firmware-esp32/ecc6fa89bab95b88a2d3b8d2fd1785dd3f4811b9/docs/assets/social.png -------------------------------------------------------------------------------- /extra_scripts/c.h_to_webp.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | # Input C file containing the array 4 | input_c_file = "/Users/tavis/code/tronbyt-firmware-http/lib/assets/noapps_webp.c" 5 | output_webp_file = "output.webp" 6 | 7 | # Read the C file 8 | with open(input_c_file, "r") as f: 9 | c_code = f.read() 10 | 11 | # Extract the hex values from the C array 12 | # The regex matches sequences like \x52, \x49, etc. 13 | hex_values = re.findall(r"\\x([0-9A-Fa-f]{2})", c_code) 14 | 15 | # Convert hex values to binary data 16 | binary_data = bytes(int(hex_value, 16) for hex_value in hex_values) 17 | 18 | # Write the binary data to a .webp file 19 | with open(output_webp_file, "wb") as f: 20 | f.write(binary_data) 21 | 22 | print(f"Binary WebP file saved to {output_webp_file}") 23 | -------------------------------------------------------------------------------- /extra_scripts/output.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tronbyt/firmware-esp32/ecc6fa89bab95b88a2d3b8d2fd1785dd3f4811b9/extra_scripts/output.webp -------------------------------------------------------------------------------- /extra_scripts/pre.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import json 5 | 6 | Import("env") 7 | 8 | 9 | def main() -> None: 10 | # copy libwebp's library.json to the lib directory 11 | env.Execute(Copy("$PROJECT_LIBDEPS_DIR/$PIOENV/libwebp/library.json", "$PROJECT_DIR/lib/webp/library.json")) 12 | 13 | sdkconfig_path = os.path.join(env["PROJECT_DIR"], "sdkconfig") 14 | if os.path.exists(sdkconfig_path): 15 | print(f"Deleting existing {sdkconfig_path} to force regeneration...") 16 | os.remove(sdkconfig_path) 17 | 18 | # if secrets.h file exists 19 | if os.path.exists("secrets.json"): 20 | # read secrets.h file 21 | with open("secrets.json", "r") as f: 22 | json_config = json.load(f) 23 | 24 | wifi_ssid = json_config.get("WIFI_SSID", "") 25 | wifi_password = json_config.get("WIFI_PASSWORD", "") 26 | remote_url = json_config.get("REMOTE_URL", "") 27 | refresh_interval_seconds = json_config.get( 28 | "REFRESH_INTERVAL_SECONDS", 10 29 | ) 30 | default_brightness = json_config.get("DEFAULT_BRIGHTNESS", 10) 31 | 32 | else: # use environment variables 33 | print( 34 | "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\nWARNING : edit secrets.json.example and save as secrets.json\n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" 35 | ) 36 | print("Using Xplaceholder values for direct firmware.bin modification.") 37 | wifi_ssid = "XplaceholderWIFISSID____________" 38 | wifi_password = "XplaceholderWIFIPASSWORD________________________________________" 39 | remote_url = "XplaceholderREMOTEURL___________________________________________________________________________________________________________" 40 | refresh_interval_seconds = ( 41 | 10 # int(os.environ.get("REFRESH_INTERVAL_SECONDS")) 42 | ) 43 | default_brightness = ( 44 | 30 # int(os.environ.get("DEFAULT_BRIGHTNESS")) 45 | ) 46 | 47 | env.Append( 48 | CCFLAGS=[ 49 | f"-DWIFI_SSID={env.StringifyMacro(wifi_ssid)}", 50 | f"-DWIFI_PASSWORD={env.StringifyMacro(wifi_password)}", 51 | f"-DREMOTE_URL={env.StringifyMacro(remote_url)}", 52 | f"-DREFRESH_INTERVAL_SECONDS={refresh_interval_seconds}", 53 | f"-DDEFAULT_BRIGHTNESS={default_brightness}", 54 | ], 55 | ) 56 | 57 | 58 | main() 59 | -------------------------------------------------------------------------------- /extra_scripts/reset.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | import requests 3 | 4 | Import("env") 5 | 6 | PRODUCTION_VERSION = { 7 | "tidbyt-gen1": "v10/35833", 8 | "tidbyt-gen1_swap": "v10/35833", 9 | "pixoticker": "v10/35833", 10 | "tronbyt-s3": "v10/35833", 11 | "tronbyt-s3-wide": "v10/35833", 12 | "tidbyt-gen2": "v11/35369", 13 | "matrixportal-s3": "v11/35369" # just to shut up the errors. 14 | }[env["PIOENV"]] 15 | 16 | def fetch_firmware(): 17 | 18 | binaries = [ 19 | "firmware.bin", 20 | "partitions.bin", 21 | "bootloader.bin" 22 | ] 23 | 24 | if not os.path.exists(f".production/{PRODUCTION_VERSION}"): 25 | os.makedirs(f".production/{PRODUCTION_VERSION}") 26 | 27 | for binary in binaries: 28 | if os.path.exists(f".production/{PRODUCTION_VERSION}/{binary}"): 29 | continue 30 | 31 | r = requests.get(f"https://storage.googleapis.com/tidbyt-public-firmware/{PRODUCTION_VERSION}/{binary}") 32 | if r.status_code != 200: 33 | raise Exception(f"Failed to fetch {binary}: {r.status_code}") 34 | 35 | with open(f".production/{PRODUCTION_VERSION}/{binary}", "wb") as f: 36 | f.write(r.content) 37 | 38 | fetch_firmware() 39 | 40 | platform = env.PioPlatform() 41 | board = env.BoardConfig() 42 | mcu = board.get("build.mcu", "esp32") 43 | 44 | env.AutodetectUploadPort() 45 | 46 | env.Replace( 47 | RESET_FIRMWARE_VERSION=PRODUCTION_VERSION, 48 | RESET_UPLOADER=os.path.join( 49 | platform.get_package_dir("tool-esptoolpy") or "", "esptool.py"), 50 | RESET_UPLOADERFLAGS=[ 51 | "--chip", mcu, 52 | "--port", '"$UPLOAD_PORT"', 53 | "--baud", "$UPLOAD_SPEED", 54 | "--before", board.get("upload.before_reset", "default_reset"), 55 | "--after", board.get("upload.after_reset", "hard_reset"), 56 | "write_flash", "-z", 57 | "--flash_mode", "${__get_board_flash_mode(__env__)}", 58 | "--flash_freq", "${__get_board_f_flash(__env__)}", 59 | "--flash_size", board.get("upload.flash_size", "detect"), 60 | "0x01000", 61 | "$PROJECT_DIR/.production/$RESET_FIRMWARE_VERSION/bootloader.bin", 62 | "0x08000", 63 | "$PROJECT_DIR/.production/$RESET_FIRMWARE_VERSION/partitions.bin", 64 | "0x10000", 65 | "$PROJECT_DIR/.production/$RESET_FIRMWARE_VERSION/firmware.bin", 66 | ], 67 | RESET_UPLOADCMD='"$PYTHONEXE" "$RESET_UPLOADER" $RESET_UPLOADERFLAGS' 68 | ) 69 | 70 | env.AddCustomTarget("reset", None, actions=['$RESET_UPLOADCMD']) 71 | -------------------------------------------------------------------------------- /extra_scripts/string_replace_bin.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import shutil 3 | 4 | # Usage 5 | file_path = ".pio/build/tidbyt/firmware.bin" 6 | new_path = file_path.replace("firmware", "firmware_mod") 7 | shutil.copy(file_path, new_path) 8 | 9 | 10 | # Replace this with the string to be replaced 11 | 12 | # new values should be the first three areuments passed to script 13 | # extract ssid, password and url from command-line arguments 14 | # substitutions = sys.argv[1:4] 15 | dict = { 16 | "XplaceholderWIFISSID____________": sys.argv[1], 17 | "XplaceholderWIFIPASSWORD________________________________________": sys.argv[2], 18 | "XplaceholderREMOTEURL___________________________________________________________________________________________________________": sys.argv[ 19 | 3 20 | ], 21 | } 22 | 23 | with open(new_path, "r+b") as f: 24 | # Read the binary file into memory 25 | content = f.read() 26 | 27 | for old_string, new_string in dict.items(): 28 | print(f"Doing {old_string}") 29 | # Ensure the new string is not longer than the original 30 | if len(new_string) > len(old_string): 31 | raise ValueError("Replacement string cannot be longer than the original string.") 32 | 33 | # Find the position of the old string 34 | position = content.find(old_string.encode("ascii") + b"\x00") 35 | if position == -1: 36 | raise ValueError(f"String '{old_string}' not found in the binary.") 37 | 38 | # Create the new string, null-terminated, and padded to match the original length 39 | padded_new_string = new_string + "\x00" 40 | padded_new_string = padded_new_string.ljust(len(old_string) + 1, '\x00') # Add padding if needed 41 | 42 | # Replace the string 43 | f.seek(position) 44 | f.write(padded_new_string.encode("ascii")) 45 | print(f"String '{old_string}' replaced with '{new_string}'.") 46 | 47 | # replace_string_in_esp_bin(file_path, placeholders, substitutions) 48 | -------------------------------------------------------------------------------- /extra_scripts/webp_to_c.h.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | 3 | # Set up argument parser 4 | parser = argparse.ArgumentParser(description='Convert a WebP file to a C header file.') 5 | parser.add_argument('input_file', type=str, help='The input WebP file') 6 | parser.add_argument('output_file', type=str, help='The output C header file') 7 | parser.add_argument('header_name', type=str, help='The header variable name') 8 | 9 | # Parse arguments 10 | args = parser.parse_args() 11 | 12 | # Read the binary WebP file 13 | input_file = args.input_file 14 | output_file = args.output_file 15 | print(f"input file is {input_file}") 16 | 17 | with open(input_file, "rb") as f: 18 | binary_data = f.read() 19 | 20 | # Create C header file content 21 | header_name = args.header_name 22 | line_length = 16 # Number of bytes per line in the array 23 | hex_lines = [] 24 | 25 | # Convert binary data to hex-encoded string lines 26 | for i in range(0, len(binary_data), line_length): 27 | line = "".join(f"\\x{byte:02X}" for byte in binary_data[i:i + line_length]) 28 | hex_lines.append(f' "{line}"') 29 | 30 | # Join lines and format the C code 31 | c_code = f"""// Generated by 'tbt dev firmware-asset', don't edit manually! 32 | #include 33 | #include 34 | const size_t {header_name}_LEN = {len(binary_data)}; 35 | const uint8_t {header_name}[] = 36 | {'\n'.join(hex_lines)}; 37 | """ 38 | 39 | # Write to an output file 40 | with open(output_file, "w") as f: 41 | f.write(c_code) 42 | 43 | print(f"C header file saved to {output_file}") -------------------------------------------------------------------------------- /include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 40 | -------------------------------------------------------------------------------- /lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /lib/assets/assets.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | extern const uint8_t ASSET_BOOT_WEBP[]; 7 | extern const size_t ASSET_BOOT_WEBP_LEN; 8 | 9 | #ifdef BOOT_WEBP_PARROT 10 | #include "lib/assets/parrot_c" 11 | #else 12 | #include "lib/assets/tronbyt_c" 13 | #endif 14 | 15 | extern const uint8_t ASSET_CONFIG_WEBP[]; 16 | extern const size_t ASSET_CONFIG_WEBP_LEN; 17 | 18 | #include "lib/assets/config_c" 19 | 20 | -------------------------------------------------------------------------------- /lib/assets/config_c: -------------------------------------------------------------------------------- 1 | // Generated by 'tbt dev firmware-asset', don't edit manually! 2 | #include 3 | #include 4 | const size_t ASSET_CONFIG_WEBP_LEN = 1658; 5 | const uint8_t ASSET_CONFIG_WEBP[] = 6 | "\x52\x49\x46\x46\x72\x06\x00\x00\x57\x45\x42\x50\x56\x50\x38\x58" 7 | "\x0A\x00\x00\x00\x20\x00\x00\x00\x3F\x00\x00\x1F\x00\x00\x49\x43" 8 | "\x43\x50\xA0\x02\x00\x00\x00\x00\x02\xA0\x6C\x63\x6D\x73\x04\x30" 9 | "\x00\x00\x6D\x6E\x74\x72\x52\x47\x42\x20\x58\x59\x5A\x20\x07\xE9" 10 | "\x00\x05\x00\x0B\x00\x09\x00\x1B\x00\x39\x61\x63\x73\x70\x41\x50" 11 | "\x50\x4C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" 12 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xF6\xD6\x00\x01" 13 | "\x00\x00\x00\x00\xD3\x2D\x6C\x63\x6D\x73\x00\x00\x00\x00\x00\x00" 14 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" 15 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" 16 | "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0D\x64\x65\x73\x63\x00\x00" 17 | "\x01\x20\x00\x00\x00\x40\x63\x70\x72\x74\x00\x00\x01\x60\x00\x00" 18 | "\x00\x36\x77\x74\x70\x74\x00\x00\x01\x98\x00\x00\x00\x14\x63\x68" 19 | "\x61\x64\x00\x00\x01\xAC\x00\x00\x00\x2C\x72\x58\x59\x5A\x00\x00" 20 | "\x01\xD8\x00\x00\x00\x14\x62\x58\x59\x5A\x00\x00\x01\xEC\x00\x00" 21 | "\x00\x14\x67\x58\x59\x5A\x00\x00\x02\x00\x00\x00\x00\x14\x72\x54" 22 | "\x52\x43\x00\x00\x02\x14\x00\x00\x00\x20\x67\x54\x52\x43\x00\x00" 23 | "\x02\x14\x00\x00\x00\x20\x62\x54\x52\x43\x00\x00\x02\x14\x00\x00" 24 | "\x00\x20\x63\x68\x72\x6D\x00\x00\x02\x34\x00\x00\x00\x24\x64\x6D" 25 | "\x6E\x64\x00\x00\x02\x58\x00\x00\x00\x24\x64\x6D\x64\x64\x00\x00" 26 | "\x02\x7C\x00\x00\x00\x24\x6D\x6C\x75\x63\x00\x00\x00\x00\x00\x00" 27 | "\x00\x01\x00\x00\x00\x0C\x65\x6E\x55\x53\x00\x00\x00\x24\x00\x00" 28 | "\x00\x1C\x00\x47\x00\x49\x00\x4D\x00\x50\x00\x20\x00\x62\x00\x75" 29 | "\x00\x69\x00\x6C\x00\x74\x00\x2D\x00\x69\x00\x6E\x00\x20\x00\x73" 30 | "\x00\x52\x00\x47\x00\x42\x6D\x6C\x75\x63\x00\x00\x00\x00\x00\x00" 31 | "\x00\x01\x00\x00\x00\x0C\x65\x6E\x55\x53\x00\x00\x00\x1A\x00\x00" 32 | "\x00\x1C\x00\x50\x00\x75\x00\x62\x00\x6C\x00\x69\x00\x63\x00\x20" 33 | "\x00\x44\x00\x6F\x00\x6D\x00\x61\x00\x69\x00\x6E\x00\x00\x58\x59" 34 | "\x5A\x20\x00\x00\x00\x00\x00\x00\xF6\xD6\x00\x01\x00\x00\x00\x00" 35 | "\xD3\x2D\x73\x66\x33\x32\x00\x00\x00\x00\x00\x01\x0C\x42\x00\x00" 36 | "\x05\xDE\xFF\xFF\xF3\x25\x00\x00\x07\x93\x00\x00\xFD\x90\xFF\xFF" 37 | "\xFB\xA1\xFF\xFF\xFD\xA2\x00\x00\x03\xDC\x00\x00\xC0\x6E\x58\x59" 38 | "\x5A\x20\x00\x00\x00\x00\x00\x00\x6F\xA0\x00\x00\x38\xF5\x00\x00" 39 | "\x03\x90\x58\x59\x5A\x20\x00\x00\x00\x00\x00\x00\x24\x9F\x00\x00" 40 | "\x0F\x84\x00\x00\xB6\xC4\x58\x59\x5A\x20\x00\x00\x00\x00\x00\x00" 41 | "\x62\x97\x00\x00\xB7\x87\x00\x00\x18\xD9\x70\x61\x72\x61\x00\x00" 42 | "\x00\x00\x00\x03\x00\x00\x00\x02\x66\x66\x00\x00\xF2\xA7\x00\x00" 43 | "\x0D\x59\x00\x00\x13\xD0\x00\x00\x0A\x5B\x63\x68\x72\x6D\x00\x00" 44 | "\x00\x00\x00\x03\x00\x00\x00\x00\xA3\xD7\x00\x00\x54\x7C\x00\x00" 45 | "\x4C\xCD\x00\x00\x99\x9A\x00\x00\x26\x67\x00\x00\x0F\x5C\x6D\x6C" 46 | "\x75\x63\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x0C\x65\x6E" 47 | "\x55\x53\x00\x00\x00\x08\x00\x00\x00\x1C\x00\x47\x00\x49\x00\x4D" 48 | "\x00\x50\x6D\x6C\x75\x63\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00" 49 | "\x00\x0C\x65\x6E\x55\x53\x00\x00\x00\x08\x00\x00\x00\x1C\x00\x73" 50 | "\x00\x52\x00\x47\x00\x42\x56\x50\x38\x4C\xAB\x03\x00\x00\x2F\x3F" 51 | "\xC0\x07\x00\x87\xE3\x38\x92\x24\x47\xA9\x1A\xB4\x56\x3F\x02\x3B" 52 | "\x70\x16\x27\x09\x7E\x27\x98\x76\xC3\x41\x6C\xDB\xAA\xB2\xF7\x37" 53 | "\xDC\xBD\x05\x4D\x08\xCE\x50\x00\x1A\xB8\xBB\x5C\xC7\x91\x6C\xAB" 54 | "\xCA\x9C\xFB\x1D\x77\x59\xB3\xA4\x08\x81\xE8\x89\x83\x08\xDC\x1D" 55 | "\xCE\xFC\x07\x10\x44\x00\x86\x08\x40\x20\x44\x10\x11\x7C\x45\x00" 56 | "\x26\x10\x40\x10\x88\xB4\x00\x09\x7F\x60\x84\x80\x04\x04\x88\x91" 57 | "\xA0\x00\x18\x42\x01\x22\x82\xA2\x50\x51\x59\xFC\xD0\x5F\x4C\x22" 58 | "\x20\x7F\xAB\x2C\x28\x40\xFC\x67\x40\x40\x54\xC8\xBE\x7C\xDA\xB2" 59 | "\x48\x49\xA2\x0C\x78\x33\x16\xE8\xF8\x0A\xD6\x3F\xEB\xF8\x23\xF0" 60 | "\xBF\xFF\x3C\xDA\x13\x31\xAC\x22\x61\x9A\xAB\x65\xAA\x8F\x63\xB5" 61 | "\x7B\x18\xD7\x77\x3D\x3F\x7A\xAE\xBE\x58\x4C\x9D\x2F\x33\xEB\x2C" 62 | "\x97\x48\xAD\xAD\xD9\x96\x0A\x43\x6B\xE3\xF5\x5F\xB0\xF6\xC3\xA1" 63 | "\x6B\x5F\xAA\x79\xB4\xBB\xB6\x69\xAE\x68\x9A\x7E\xB2\x88\xCA\xCA" 64 | "\xBF\x65\x9A\xFB\x3D\xB7\xA6\xB0\xEF\x64\x65\xF8\xBC\xFC\xF9\x78" 65 | "\x4F\x97\x56\xB5\x8B\x5B\x7D\x84\x67\xDB\x65\x90\x20\x79\xE1\xDD" 66 | "\x75\x6C\x85\xE2\xED\xFF\xEB\x38\x56\x73\xFB\xEA\x17\xFF\x86\x53" 67 | "\xB5\x66\x74\xED\x33\x9B\x83\x74\xDB\xB6\xD5\x3C\x59\xD4\x2D\xF5" 68 | "\xD4\x5D\x42\xDD\xDD\x5D\x2E\x5E\x03\x3E\x1C\xEA\x7A\xDE\xFF\x67" 69 | "\x73\xCE\xCD\x65\xF4\x05\x22\xFA\xCF\x40\x92\x14\x27\x50\xA6\x31" 70 | "\xB0\x5C\x2D\x3E\x82\x2E\x92\x17\xE6\xC4\xBC\x30\x4F\xE8\x7A\xC1" 71 | "\x27\xBA\xE9\x8A\x89\x10\x91\x67\x1E\xC3\x33\x17\xCB\x11\xA5\x07" 72 | "\x10\x21\x16\x10\xC5\x05\x25\xC1\xEC\x22\x54\x15\x41\xEB\x4C\xA4" 73 | "\xCB\xCF\x91\x54\x95\x80\x57\x8B\xD2\xE0\x39\xD5\x38\x96\x08\x49" 74 | "\xB9\xDD\x2B\x1D\x92\xEA\x51\x7D\xC6\x5E\x45\x5A\x43\x44\x9C\x72" 75 | "\x8B\xC5\x74\x66\x5F\xD7\x0B\x01\x92\xAA\x12\xF0\xAE\x06\x5D\xE2" 76 | "\x3D\xD5\x18\xD3\x8D\xA7\x92\x60\x22\xF6\x61\x73\xAA\x0A\x64\x63" 77 | "\xDB\x05\x8A\xE3\x2B\x10\xFC\x72\x26\x56\x0F\x18\xAE\x3A\xF5\xE8" 78 | "\x40\x6B\x09\xE7\xFF\xE1\x3B\x22\xFC\xD9\x11\x70\xEA\xC7\x01\x7E" 79 | "\x0E\xCA\x4C\x5D\x0D\x01\xD8\xCF\x6D\x14\xB5\xD1\x44\x3E\x05\x2A" 80 | "\xB2\xC4\x79\xB1\xF5\xB8\xE5\x11\x60\x2D\x60\xEB\x0A\xC6\x6C\x82" 81 | "\x1E\x0B\xE5\xD5\x98\x06\xD5\x8E\xF2\x91\x90\x18\xFF\xF5\x5A\x07" 82 | "\x65\x78\xC5\x08\x13\xD4\x38\x24\xE2\xC1\xCD\xDA\x73\xDE\xB9\x23" 83 | "\x2C\x8C\x30\x81\x58\x29\x51\xB8\x42\x61\x11\xE9\xB2\x0D\x49\x42" 84 | "\xA1\xF1\x7D\xC0\xAE\x0B\xF1\x2C\x8F\xC5\x79\x06\xC7\xD2\xCC\xA0" 85 | "\xF2\x7F\x05\x84\x46\xA4\x06\x31\x26\xD2\xAB\x14\x8E\x3D\x9B\x11" 86 | "\xA0\xCF\xFA\xC4\x2C\x8B\xEE\xA1\xDF\xEF\xBB\xBC\x0C\x4F\x45\x25" 87 | "\x2B\x00\x23\x54\xE3\xCD\x3E\x3D\x12\x5D\xE4\x29\x5E\x60\xFC\x17" 88 | "\xE6\x3B\xF6\x2B\x27\xC7\x27\xA6\x76\xA9\xF1\xC6\xEB\x2C\x71\x9D" 89 | "\x99\x73\xE7\xE5\xA5\xCB\xBB\xBB\x97\x97\x4F\xBB\x07\x6B\x78\x22" 90 | "\x22\x7A\x55\x5B\x59\xD7\xDA\x33\x3A\xDA\x52\x5F\x5E\x91\xD7\x40" 91 | "\x3E\x0D\x6A\x4A\xB3\x9F\x80\x85\x81\x6B\x77\x77\xEE\x96\x13\x2A" 92 | "\x54\x25\xA5\x67\x90\x29\x06\x49\x98\x67\x9A\x41\xBF\xD6\xEC\x59" 93 | "\x86\xAD\x9B\xF3\xDB\xDB\x5B\x77\xF0\x30\x35\x07\x0C\x95\xA7\x7A" 94 | "\xCA\x58\xFD\x76\x7A\x7A\x74\x74\x9A\x3B\x9D\x1A\x28\xFF\x5C\xEC" 95 | "\xD3\x99\x39\x71\xED\x5D\x6C\xFD\x3D\xBF\xBD\xBB\xBA\xFB\xF3\x70" 96 | "\xDF\xB5\xDC\x93\x76\x69\xF2\x0B\x21\xA9\xB1\x97\x47\x7B\x3B\xBB" 97 | "\x3B\x3B\xB7\xE7\x57\x0F\xB7\x37\x7B\xBB\xBB\x4B\x0C\xC3\xFA\xFA" 98 | "\xFA\x33\x07\xD6\x61\xDD\xF2\x74\xAF\xC4\x24\x19\xEB\xD8\x78\xB8" 99 | "\xEC\x9C\xFB\xE7\xDC\xE3\xF9\x6F\xE7\x56\x3B\xBB\x98\xD6\x7A\x43" 100 | "\xF6\x4A\xCF\xD6\xB7\xDC\xB7\xDC\x73\x7E\x33\xFB\xDC\xF7\xF7\x53" 101 | "\xCE\x5D\x5F\xFD\x76\xE7\xB7\x8B\x6F\xDF\x9C\x7C\xF9\xF6\xF5\x9B" 102 | "\xE9\xD7\x86\x79\x40\xC5\xE7\xCF\x21\xFF\xB2\x77\x5B\xEE\xEF\xC5" 103 | "\x85\xDB\xBB\x99\x65\xA0\x3C\x61\x30\x70\x87\x5E\xC7\xBC\x05\x63" 104 | "\xE7\xBC\x4A\xC3\xE1\xD3\xC2\xAE\x73\xEE\xF7\x1E\x4D\x55\xD4\xE6" 105 | "\xE5\x11\x9B\xF0\x55\xD1\xF7\x56\x59\x67\x7E\x9A\x24\xDB\xF7\x1F" 106 | "\xD8\xD9\x5E\x76\x5B\x6E\x7F\x8C\x05\xE5\xC5\xA0\x3C\x02\x72\xC0" 107 | "\xD6\xD6\x60\xC0\xBF\x9C\x83\xCD\xE3\xB3\xD3\x9F\xBF\x36\x66\x6A" 108 | "\xF3\x52\x1D\x95\xD4\x52\xCB\x60\xF3\x60\x73\x12\xA7\x7C\x1A\xF7" 109 | "\xE3\xDB\xB7\xDC\x9F\xA3\x8F\x31\x11\x00"; 110 | -------------------------------------------------------------------------------- /lib/assets/parrot_c: -------------------------------------------------------------------------------- 1 | // partry parrot boot screen for no psram devices. 2 | #include 3 | #include 4 | const size_t ASSET_BOOT_WEBP_LEN = 4608; 5 | const uint8_t ASSET_BOOT_WEBP[] = 6 | "\x52\x49\x46\x46\xF8\x11\x00\x00\x57\x45\x42\x50\x56\x50\x38\x58" 7 | "\x0A\x00\x00\x00\x12\x00\x00\x00\x3F\x00\x00\x1F\x00\x00\x41\x4E" 8 | "\x49\x4D\x06\x00\x00\x00\xFF\xFF\xFF\xFF\x00\x00\x41\x4E\x4D\x46" 9 | "\xBA\x01\x00\x00\x00\x00\x00\x00\x00\x00\x3F\x00\x00\x1F\x00\x00" 10 | "\x32\x00\x00\x02\x56\x50\x38\x4C\xA1\x01\x00\x00\x2F\x3F\xC0\x07" 11 | "\x00\x77\xC1\xA0\x91\x24\x45\xCB\xA0\xF8\x84\x33\x3C\xD8\x50\x1C" 12 | "\x49\x52\x82\xDB\x61\x61\xF2\x26\x75\xF7\x62\x18\x49\x52\xA2\x3D" 13 | "\xE0\x34\x40\x92\x27\x0D\x7F\x9D\xFF\x00\x84\x88\x00\x14\xE8\x49" 14 | "\x08\x0A\x61\xEC\xE7\x84\xCC\x47\x7E\x20\xF1\x02\x4D\xF4\xB3\xDF" 15 | "\x82\x80\xB0\x2D\x0C\x32\x55\x8C\x35\x73\xA5\x22\x32\x77\x2E\x62" 16 | "\x30\x4D\xC4\x88\xF4\x21\x9C\xF3\xC8\x22\xB8\x00\xC7\xC0\x4C\x81" 17 | "\x0A\x31\x73\x73\xCF\xED\xE2\xC7\x5A\xC7\xA8\xB7\xD6\x86\x75\x8F" 18 | "\x7D\x2E\x67\xFC\xD6\x3A\xEA\xE8\xF6\x7E\xB5\x32\x8C\x75\x2B\xA9" 19 | "\x82\x2C\xDB\xB6\xD3\x46\xAA\x1B\x66\xA3\x0C\x82\x77\x8B\xB9\x34" 20 | "\xFF\xF9\xD9\x51\x2D\xBD\x34\xFC\x46\xF4\x7F\x02\xEE\xFE\x87\xB1" 21 | "\x38\x15\x47\xE0\x66\x50\x0C\x5E\x44\x5C\x77\xC2\x6D\xAC\x7A\xE1" 22 | "\x7C\x30\xB8\x85\xCD\xC8\xA9\x58\xEB\xC9\x1E\xFA\x16\x96\x53\x5B" 23 | "\x2C\x97\xAB\x56\xA4\x81\x36\xF4\x0C\x85\x21\xCF\xAF\xF7\xF7\x4F" 24 | "\xDF\x63\xBD\xD3\x86\x96\xB4\x80\x78\x3C\xC6\x18\x3F\x1A\x60\xA3" 25 | "\x0B\x45\x20\xFD\xD9\xD0\xE1\x21\xC6\xF8\x5A\x57\x8D\x81\x2E\x4B" 26 | "\x92\x42\x3A\xDC\x4F\x1A\xE1\xA8\x0A\x86\x57\xE7\xDE\x0C\xE9\x55" 27 | "\x2D\xFD\xB5\x80\xA7\x18\xE3\xFB\x40\xB2\x84\x1E\x74\x4C\xDC\x3F" 28 | "\xC7\x18\xBF\x46\x92\x83\x26\x97\xD2\xBE\xC5\x78\xBF\xF2\x24\xAD" 29 | "\x1E\x6C\x42\xCA\xF8\x1D\xE3\xF3\x51\x48\x3A\x45\x5B\xA6\xCA\xE1" 30 | "\x25\x7E\xF6\x54\xB6\x09\x29\xB4\x3F\x2F\x5B\x3F\xB1\x6A\xD0\x6E" 31 | "\x5D\x12\x0D\x5A\x4E\x07\x35\x2B\xD7\x9A\xB4\x50\xB8\x99\x42\x0B" 32 | "\x0C\xDD\xC6\x26\xD1\x73\xEA\x71\xA7\x13\x65\x20\x87\xAD\x4D\x9A" 33 | "\xB7\x4A\x50\x3A\x92\xEC\x97\x83\xFC\x6A\xD0\x81\x26\x70\xD6\x96" 34 | "\x97\xD1\x07\x75\x28\x2D\xAF\xCB\x50\xAF\x0F\x4E\x17\xF6\xBD\x30" 35 | "\x3D\x38\x49\x72\xF9\x4E\x8E\x79\x65\x9F\x6B\x61\x99\x7B\x40\xA6" 36 | "\x9A\xD9\xDD\x22\x0F\x1A\x9B\xFF\x80\x3C\x3A\x7F\x03\x00\x41\x4E" 37 | "\x4D\x46\xCE\x01\x00\x00\x06\x00\x00\x00\x00\x00\x25\x00\x00\x1F" 38 | "\x00\x00\x32\x00\x00\x00\x56\x50\x38\x4C\xB5\x01\x00\x00\x2F\x25" 39 | "\xC0\x07\x10\x57\xC1\xA0\x91\x24\x45\x47\x0B\xC7\xE4\x5F\xC1\xA9" 40 | "\x7B\xE6\xD7\xC1\x26\x00\x80\x34\xB8\xBB\xF4\xA0\x30\x15\x6D\xBE" 41 | "\x4F\x41\x1B\x49\xCA\xE1\xF3\xFB\x17\xF5\x7A\x18\xE7\x3F\x04\x80" 42 | "\x12\x00\xC0\xC4\x10\xF1\x73\x60\x88\x42\xF6\x69\x22\xC4\x2B\x00" 43 | "\x01\xA0\xEA\x93\x07\x30\x42\x42\x91\x50\xEF\x60\xF7\xBF\xF5\x90" 44 | "\xA9\x7A\x90\x14\x2C\x0D\x33\x3D\xA2\x06\x13\x8B\x98\x01\x54\x19" 45 | "\xF6\x21\x33\x22\x8B\x80\x2A\x56\x44\xB5\xCA\x5B\xAB\x64\x2F\xF9" 46 | "\x28\x06\xCB\x6B\x8F\x24\x1A\xBD\xD8\xE6\x30\xCD\x66\xBF\xB6\xEB" 47 | "\x5F\x00\x41\xB6\xAD\xB6\xCD\xFF\xEE\xBD\x48\x72\x4D\x82\x04\xFC" 48 | "\x87\x64\xB3\xFF\x15\x56\x29\x2B\x88\xE8\xFF\x04\xF8\x11\x39\xEA" 49 | "\x27\xE6\xCA\x69\x63\xB4\x3B\xF1\x24\x0B\x27\x88\x4A\xBB\x18\x8F" 50 | "\x8F\x1A\x99\x7A\x45\x34\x0E\x1F\x0C\x42\x31\x5A\x0B\xA0\x67\x44" 51 | "\x34\xC6\x42\x43\x04\xB0\xCD\xE6\xBC\xAD\x0C\x6C\x43\x44\x54\xC6" 52 | "\x3D\x30\x54\x16\xEE\xFA\xAB\xFE\x6E\x83\x3B\x0E\x4C\x44\x54\x74" 53 | "\xB4\xC0\x92\x35\x9A\xA7\x52\xEA\x7D\x3C\x72\x3F\x0F\xA8\x80\x1D" 54 | "\x80\xBE\x11\xD4\xC1\x0F\xBB\x93\x69\x78\x84\x95\x45\xBC\x7A\x05" 55 | "\x5E\x04\x66\x51\xC6\x1F\x14\x2C\x00\xC0\x31\x11\x51\x5E\x97\xFA" 56 | "\xDC\x95\x52\xEF\x63\x80\x2F\x17\x99\x54\x7B\x55\x4A\x3D\xFA\x88" 57 | "\x99\x97\x2C\x91\xD6\xBB\x3F\xA5\xAE\x6D\x04\x03\x13\x51\xCE\x31" 58 | "\xC3\x1E\x7F\xD4\xDF\xCC\xC4\xBA\xF1\xE0\xCE\x7F\x8F\x1A\x71\x5D" 59 | "\x32\x97\x0C\xA9\xEF\x97\x2E\xD1\x96\xB0\xCE\x80\x59\xAC\x25\xD1" 60 | "\x17\x0D\x39\x70\x2D\x92\x35\x15\xF8\x99\xCE\x11\x9B\x5A\x95\xF0" 61 | "\xA1\x96\x8C\xDC\xAA\x80\x1B\xF3\x1D\x64\x8C\x96\x89\x7C\x9A\x2B" 62 | "\x03\x7B\x72\x32\x82\xAD\x39\x83\x6B\x03\xC0\x54\x5F\x23\x45\xD0" 63 | "\x19\xFC\xB1\x08\x6D\xBF\x3F\x0C\xB6\x04\xBD\x8F\xF3\x20\x48\xDA" 64 | "\xCE\x95\x99\xC4\x57\x30\x6D\xC3\xC1\xC1\x62\xE2\x2E\xE0\x16\x53" 65 | "\x9B\x99\xF7\x7E\x66\x27\xB3\x6B\xEF\x69\xAD\x27\xEF\xD6\xE4\xC9" 66 | "\xFF\x47\x02\x00\x41\x4E\x4D\x46\xD4\x01\x00\x00\x07\x00\x00\x00" 67 | "\x00\x00\x26\x00\x00\x1F\x00\x00\x32\x00\x00\x00\x56\x50\x38\x4C" 68 | "\xBB\x01\x00\x00\x2F\x26\xC0\x07\x10\x57\xC1\x30\x00\x40\x34\xBA" 69 | "\x9B\xB7\xBD\xDA\x96\x97\x30\x6C\x1B\x49\xD1\xD1\xCC\x41\xFF\x7D" 70 | "\x32\xDF\xFD\x8B\x41\x23\x49\x8A\x96\xC1\xBF\xC0\xB3\xC0\xCF\x30" 71 | "\xFF\x01\x04\x84\x02\x50\x4D\x88\x11\x8C\x06\xEB\x07\xFF\x0C\x70" 72 | "\x82\xFF\x48\x28\x76\x03\x31\x9A\xF2\x13\xEB\x9C\xB1\x8E\xFD\x0A" 73 | "\xF3\xA5\x91\x7C\x08\x1A\x42\xD2\x59\x1A\x6D\x92\x70\xC0\x20\x59" 74 | "\x2A\xC9\x20\x80\x04\x9C\x32\x42\x40\x09\x6D\x94\x21\xB5\x16\xEF" 75 | "\xF8\xB4\x5E\xFD\xE7\x32\xE2\x66\xCE\xE1\x9F\xBF\x77\x9F\xBE\x73" 76 | "\xFA\xBF\x1B\x20\xD8\xB6\xD5\xA6\xE2\x5A\x52\xEC\xA6\x93\x08\x5C" 77 | "\xF4\x77\xE6\x3F\xC2\xD0\xFC\x23\x88\xE8\xFF\x04\xB8\x64\xE1\x75" 78 | "\x6F\x86\x13\xE1\x37\xA0\x68\xBA\xAE\x29\xE0\x13\xB9\x70\x1A\x0D" 79 | "\x49\x9A\xE9\x02\x8F\xC8\x83\x46\x33\x68\x3A\x78\x44\x0E\x74\x86" 80 | "\x24\x8D\xEC\x87\x99\x6C\x91\x0B\xAD\x31\x33\x69\xEE\xE5\xCF\xEF" 81 | "\xAE\xA7\xAC\x3D\x22\x09\x27\xCD\x01\x92\x72\xFF\xB4\xF6\x55\x0C" 82 | "\xD8\x20\x93\x24\x1B\x0C\x1C\xFF\xAC\xB5\xCF\xDD\x0D\x27\x9F\x48" 83 | "\xC0\x8D\xE4\xA3\x51\x1C\x7E\xAC\xB5\xF6\x38\xB4\xCB\xC9\x65\x91" 84 | "\xF4\xF7\x5F\x9E\x99\x9C\xE0\x11\x71\x3B\x1D\x18\x7E\xAD\xB5\xCF" 85 | "\x72\x21\x4D\x03\x8F\x88\x41\xCF\xA0\x2C\x57\x3B\x45\x52\xD5\x48" 86 | "\x93\xA1\x19\x4F\x6B\x5F\x27\x43\x92\xD2\x27\x22\x36\x2A\x64\xEA" 87 | "\x0F\x6B\x3F\xEF\x5C\x9B\x33\x52\x8A\x08\xF6\xDF\xD6\x1E\x27\x0F" 88 | "\xA7\xA4\x4A\x47\x3C\x76\x1F\xAF\x5A\xFB\x96\xA4\xAD\x8A\xE0\x78" 89 | "\xF8\xED\xE8\x9F\x93\xB0\xC4\x98\x66\xA3\x02\xD2\xE3\x62\xFA\x18" 90 | "\xAA\x86\xC1\x26\xC9\x61\x89\xA1\x0E\x5D\x45\xDA\x69\x89\x89\x6C" 91 | "\xB1\x12\x51\xF7\xED\x94\xE3\x51\x63\x25\x22\x36\x5A\x56\xF7\x25" 92 | "\x8D\xAA\xAF\x10\x85\x8E\x54\x7D\x71\x91\x2A\x85\xD4\xC3\x19\x42" 93 | "\x04\x2A\x45\x92\x6A\xBC\x17\x32\x89\x34\x43\xE1\x82\x13\x83\x4A" 94 | "\x67\x20\x1F\x37\xDF\xD9\x84\x72\x9B\x06\xAB\x91\x6F\x37\x2D\x9C" 95 | "\xC3\xFC\x3E\x9A\x3B\x1C\xC6\xF9\x1F\x4E\x5B\xB8\xFF\x08\x00\x00" 96 | "\x41\x4E\x4D\x46\xDE\x01\x00\x00\x06\x00\x00\x00\x00\x00\x28\x00" 97 | "\x00\x1E\x00\x00\x32\x00\x00\x00\x56\x50\x38\x4C\xC5\x01\x00\x00" 98 | "\x2F\x28\x80\x07\x10\x5F\xA1\x38\x92\xA4\xE4\x04\x77\x78\x92\x10" 99 | "\xF9\x87\xE3\xA4\xC1\x30\x92\x24\x25\x2F\x2B\xB8\x43\xF6\x24\x45" 100 | "\x36\x8E\xA2\x46\x92\x94\xDD\x3D\x66\x14\x70\x72\xCE\xBF\x97\x7B" 101 | "\xE1\xFC\x07\x04\x88\x01\xC8\x4C\x04\x38\x7C\x35\x05\xC1\x50\xD2" 102 | "\x49\x51\x92\x34\x25\x07\x03\xA7\x65\xBD\xDB\xBC\xAE\x75\x6E\x7D" 103 | "\xEB\x8C\x95\xD8\x29\x06\x8A\x96\x24\x29\x08\x7D\x4A\x92\xB1\x22" 104 | "\x7F\x2E\xF6\xCA\x3F\x06\x8F\xC8\xD2\x89\xF0\x5A\x5B\x98\xAC\xD9" 105 | "\x42\x4B\x0A\x8C\x34\x57\x1A\xEE\x4C\x94\xB9\xB1\x4F\xA1\xF0\x04" 106 | "\x84\x08\x27\xD7\x51\xB0\x07\x09\xB6\xED\xB6\x8D\x03\xB8\x9B\x93" 107 | "\x3A\x1D\x2A\xB4\x44\xE2\x4B\x56\xD9\xFF\x06\x59\x44\x25\x1B\x88" 108 | "\xE8\xFF\x04\xAC\xFF\x2D\xD1\xBF\x41\xC9\xDD\x28\x7B\xA7\xCB\x38" 109 | "\x0D\x67\x8E\xD1\x0E\x3C\x58\xAF\x50\xDF\x0E\x1C\xA1\x62\x87\x5A" 110 | "\x11\xD7\xF6\xB8\x0B\x5B\x84\xFD\xA3\x07\x60\x0F\x01\x95\x61\x0B" 111 | "\x00\x7E\x66\xE6\xC9\x01\x35\x17\xE3\x1A\xCB\xE4\x51\xBF\x7C\xC9" 112 | "\xD7\x5A\x37\x43\x77\x0D\xA8\x84\xF1\x60\xEE\x60\xDE\x44\xE4\x6D" 113 | "\xBC\xB2\x7D\x96\x62\x0B\xCC\xA3\x87\x79\x17\x91\xCF\x73\x65\x5C" 114 | "\xC3\x01\x6D\x1B\x14\x80\x02\xF7\x8F\x80\xA1\x70\xF7\x08\x6D\xE1" 115 | "\x06\xF1\xFB\xBB\x88\x7C\x5C\x00\xA0\xE5\x22\x87\x3E\x31\xBE\x89" 116 | "\xC8\xBB\x09\x30\x73\x40\x1B\x06\x4D\xD4\x7F\x44\xE4\x6F\x15\x71" 117 | "\xA6\x00\x2F\x48\xDA\x9F\x22\xF2\xA3\x8D\xA0\xE5\x02\x75\xCA\x5D" 118 | "\x3F\xE5\x9B\xFB\x98\x3F\x15\x68\x53\xA8\x7E\xCB\xEB\xA8\x31\x3C" 119 | "\x0B\x34\x19\xCE\xBC\xFE\xB2\x48\x8E\x05\xAA\x0C\x58\xBE\xFA\xD4" 120 | "\x95\x88\xD6\x8D\x46\x33\x50\xB5\x48\xF6\x4C\x44\xB4\x81\xDB\x1C" 121 | "\xEF\x53\x5D\x84\x36\xDC\x5D\x46\x76\x7D\x62\x0A\xB3\x6E\xA3\x2F" 122 | "\x02\x57\x1B\xA6\x30\xC3\x4E\x93\x16\x01\xD4\x4E\xCC\x44\x44\x09" 123 | "\xE7\x9F\x93\x2B\x03\x68\x37\x31\x13\x51\xAC\x87\x5F\x4C\xAB\x65" 124 | "\x00\xED\x46\x26\xA2\xC8\x03\xD0\xC7\xDD\x54\x56\x8B\x00\xDA\x5E" 125 | "\x98\x28\x68\x01\x40\xBB\xC6\xF4\x85\x00\x57\x31\xD3\xBA\xF2\xD4" 126 | "\xED\xFF\x58\x0E\x4C\x00\x41\x4E\x4D\x46\xBC\x01\x00\x00\x05\x00" 127 | "\x00\x02\x00\x00\x2A\x00\x00\x1B\x00\x00\x32\x00\x00\x00\x56\x50" 128 | "\x38\x4C\xA3\x01\x00\x00\x2F\x2A\xC0\x06\x10\x4F\xC1\xA0\x91\x24" 129 | "\x45\x47\xCB\xFE\xB5\x9D\x0E\x86\x47\x1B\x0A\x23\x49\x32\x9E\xA7" 130 | "\xFC\x03\x7D\x1B\x8C\x03\x00\x28\x94\xCD\x77\x1B\x36\xBE\x75\x98" 131 | "\xFF\x70\x0A\xF4\x0A\x00\x8B\x7C\x97\x21\xC7\xA0\xA3\x7E\xA2\xE0" 132 | "\x61\x40\x0A\xB8\x22\xE9\x04\x51\x50\x6F\x19\x46\x08\x0C\x5A\x8D" 133 | "\x51\x61\x6B\x30\x2B\xCC\x9E\x89\x25\x60\x29\x78\x04\x92\x78\x8B" 134 | "\x41\x41\xE4\xE0\x6B\x00\x0C\x78\x2A\x87\xEC\xDB\xD8\xB5\xAF\x79" 135 | "\xA6\x3D\xB7\x59\xAE\x5E\xBB\xD2\xAB\xAB\xFF\xF6\xDC\xCE\xDA\xAE" 136 | "\x7B\x01\x82\x6C\xDB\x69\x9B\xFF\x82\x0E\x27\x02\x5B\xB2\xF4\x02" 137 | "\xE5\xFE\xFD\xEF\xD0\x50\xA9\x2B\x88\xE8\xFF\x04\xE8\x34\xD0\x9C" 138 | "\xCE\x87\x25\x44\xEB\xE3\xD2\xA5\xCC\x1C\xED\x1E\x52\x09\xDB\x36" 139 | "\x73\x3C\x19\x48\x15\x34\x81\x33\xB3\x85\xD4\x68\x02\x47\xA3\xB3" 140 | "\xBE\x67\x3E\x42\xCA\xA1\x25\x63\x4F\xB6\xEB\xDF\xCF\xDD\x21\xF6" 141 | "\xB6\x02\xAE\x99\x7D\x73\xCC\xF9\xF0\xA1\xAA\xDF\xF6\xA8\x15\x56" 142 | "\x91\x0C\x40\x4A\xDB\x37\x55\xFD\x38\x03\x07\x48\x21\xDC\x48\xD2" 143 | "\x3A\xF6\x9B\xC1\xFB\xD1\xD9\xAE\x5C\x37\x20\xD9\xAF\x5E\x23\x64" 144 | "\xBE\x41\xCA\x1C\x39\xB5\x7D\x53\xD5\x8F\x0B\xC9\x74\x14\x29\x01" 145 | "\x33\xC5\xFD\xBB\xAA\xAA\x21\xC9\x6E\x59\xC6\xCF\x38\x7D\x0C\xFC" 146 | "\x80\x37\x48\x89\x6E\x86\xFF\xBD\xEB\x73\x11\x46\xDA\x5A\x71\xFD" 147 | "\xD2\xF7\x4D\x1E\x49\xB5\x78\xFB\x7E\xFC\x38\x8E\xE6\x32\x7E\x4E" 148 | "\x7F\xF8\x42\x1C\x0B\x65\x6E\x73\xD8\x2D\x0C\xC7\x7D\x11\x45\x98" 149 | "\x43\x13\x2A\x9D\xF2\x9C\xCC\xC9\x74\x05\xA4\xC0\xD1\xE4\x19\xB3" 150 | "\xC3\x6D\x0D\xF9\xCB\x2A\x1E\x4C\x2E\x42\xF6\xEE\x00\x91\x59\x7B" 151 | "\xC6\xE3\x25\x96\x21\x93\x6D\x20\x32\xA3\x21\xD3\xAD\xB1\x31\x17" 152 | "\x21\xE3\x05\x22\x53\x2B\x92\x0C\x97\xD5\xC9\xB8\x50\x82\xD9\x2D" 153 | "\x45\x64\x0C\x81\xC3\xD8\xBA\x8B\x2D\x42\x76\x6B\x88\xC8\x88\xED" 154 | "\xAA\x3B\x40\x44\x06\xD0\x7F\x29\x22\x00\x41\x4E\x4D\x46\xB4\x01" 155 | "\x00\x00\x05\x00\x00\x02\x00\x00\x27\x00\x00\x1B\x00\x00\x32\x00" 156 | "\x00\x00\x56\x50\x38\x4C\x9B\x01\x00\x00\x2F\x27\xC0\x06\x10\x2F" 157 | "\xA1\xA8\x91\x24\x65\xF1\xD0\xBF\xCC\x7B\x31\x9F\x0D\x56\x92\x24" 158 | "\x99\x5A\xDB\xF7\xFF\xDC\xEB\xE1\x19\x8B\x08\x06\x92\x24\x19\x5A" 159 | "\xDB\x78\xE4\x7D\xDF\xB7\x8C\xF9\x0F\x00\x20\x02\x40\x31\xF2\xA9" 160 | "\x44\x8A\xB0\xC2\x53\x4A\x19\xA2\x80\x80\xAC\xF0\xAC\x56\x84\x08" 161 | "\xCA\x19\x23\x2D\xE7\x83\xE0\x32\x6F\x03\xEF\x02\xEF\x82\xE0\x13" 162 | "\x67\xBC\x98\x2A\xAB\x2C\x15\x1C\xE4\xC3\x78\xB6\x36\x3F\xE3\x6F" 163 | "\x56\xEF\x4E\x87\xF6\x18\x66\xEF\xC6\x6C\xF2\xD1\x8C\xBF\x99\xBD" 164 | "\x59\x6B\x48\xE5\x04\x88\x8D\x24\x29\x92\xAA\x97\x79\x07\xBB\xE6" 165 | "\x9E\x71\xFD\xF7\x11\xE7\x3C\x88\xE8\xFF\x04\x6C\xE7\x38\xDD\x9E" 166 | "\x8D\x43\x11\x00\x4F\xC1\x5E\xA4\x19\x86\xBE\x12\x3C\x01\x7B\x99" 167 | "\x43\x24\x35\xBA\xE6\x39\x52\x7A\xE5\xB1\x9A\x6C\x00\xA4\x09\xBC" 168 | "\x6A\x24\x0F\x00\x94\x81\x87\xC1\x5A\x17\xA9\x93\xE4\x12\x4F\xC6" 169 | "\x48\xFA\xF2\xE7\xEB\xB7\x09\xD1\x66\x01\x20\x8B\x32\x56\xBD\x72" 170 | "\xFE\xDC\x52\xFA\x5E\x87\x2D\x5B\x20\x83\x94\xAA\xFD\xEB\x96\xD2" 171 | "\xC7\x50\x4B\x27\x79\x06\x25\x69\x2C\xB5\x7B\xD9\x52\x7A\x1B\xAC" 172 | "\xF1\xC5\x3D\x00\x62\x79\xAC\xCD\xC1\x48\x72\x95\x1C\x55\x3C\xEB" 173 | "\x5E\xB7\x94\x3E\x66\x92\x3A\x4A\x86\x49\x4F\x38\xBF\xA7\x94\x3E" 174 | "\x57\x92\x0C\x45\x86\x85\xE7\xCB\x67\x4A\xE9\xCF\xEE\x68\xE4\x9E" 175 | "\xB9\xE0\xFE\x53\x7A\x88\x3F\x08\x19\xD6\x0B\xB1\x7E\x4D\x6F\x0D" 176 | "\x0F\x63\x86\xF9\x02\xCD\xDF\xE3\xC7\x64\xDA\x80\x22\x5C\xD0\xF1" 177 | "\xA7\x88\xF9\x64\xB9\xC0\x50\xAE\x3C\xD9\xEE\x35\xB5\xBF\x40\x1B" 178 | "\x4E\xB8\x08\x6E\xC8\xEA\xDA\x78\x41\x79\x1E\x7B\x01\xAE\x79\xAE" 179 | "\x7D\x38\xBB\x1C\x17\x01\x70\xA1\x56\xAA\xE9\xAC\x66\xA0\xDA\x12" 180 | "\xC0\xD9\xA8\x24\x7D\x37\xD8\x70\x8F\xB4\x02\xE0\x64\xD8\x51\xFD" 181 | "\x24\xBD\xB9\xC7\x55\x00\xE0\xA0\xF5\xA7\x76\x5D\xFC\x7D\x57\xEC" 182 | "\xB0\xDB\x9E\xBE\x03\x00\x41\x4E\x4D\x46\xA8\x01\x00\x00\x06\x00" 183 | "\x00\x02\x00\x00\x20\x00\x00\x1B\x00\x00\x32\x00\x00\x00\x56\x50" 184 | "\x38\x4C\x8F\x01\x00\x00\x2F\x20\xC0\x06\x10\x6F\xC1\x30\x92\xA4" 185 | "\x44\xD8\x0A\x9E\x34\x59\xF3\x92\x86\xDA\x00\x40\x12\x0C\x1A\xCC" 186 | "\xD1\xFF\xEF\x54\x71\xDB\x48\x6A\x6A\x19\x8F\xDF\x37\xFF\x11\x00" 187 | "\x46\x00\x68\xE4\xC7\x94\x4C\x14\x70\x51\x59\xFA\x2A\x99\xAA\xDC" 188 | "\x2E\x82\x91\x53\xEE\x42\xB1\x20\x42\x06\xC7\x4C\xF3\x7D\xE3\x3E" 189 | "\x94\x09\x19\x3C\x2F\x8D\x52\x74\x12\x3B\x64\xF2\xB8\xA0\xAE\xA0" 190 | "\x3B\x63\x02\x99\xC6\x6A\x64\x13\x31\x36\xB5\x73\xA1\x22\xA1\x71" 191 | "\x77\x22\x53\x5D\x90\x8C\xD9\xE1\x0B\xA0\xB2\x11\x40\x90\x6D\x3B" 192 | "\x6D\xF3\x5F\x98\xE4\x90\xC0\x92\x5E\x99\x41\xDE\xFF\xFE\x24\x7F" 193 | "\xA7\x5D\x41\x44\xFF\x27\x60\xF8\xDF\x22\x7F\x23\xA3\xB7\x93\x36" 194 | "\x80\xC5\x62\x0E\x4C\x93\x36\x60\x5C\x4C\x29\xF6\xD7\x05\x26\x48" 195 | "\x1B\x2B\x9F\xD9\x4E\x17\xDC\x02\x5D\xA4\xD6\x42\x23\x4D\x98\xC4" 196 | "\x66\x0E\x2E\x24\xF2\x8C\x31\x69\x6F\x7A\x32\x27\x32\x1D\xEF\x9E" 197 | "\x1E\x96\x7D\xF6\xF3\x29\xB0\x24\xCF\xF3\x48\xF7\xF8\x5B\xCA\x9B" 198 | "\xB1\x98\x4D\xE9\x32\xC9\x03\x3C\xCF\xAF\xA5\x94\xAF\xD9\x09\x4B" 199 | "\xB4\xA4\x09\x47\x92\xB6\x4B\x3C\xBD\x95\x52\x7E\x70\x31\x6E\xA9" 200 | "\x82\xC9\x15\x33\xD9\xBD\x57\x43\x9F\x79\x81\xCA\x72\xFC\x58\x7D" 201 | "\x23\x92\x69\xA7\x8A\x0A\xFB\x52\x4A\xF9\x44\x26\x69\x31\x86\x8E" 202 | "\x4A\xF7\x5C\x4A\x79\x3F\x91\x64\x5A\x29\xAC\xA6\xBF\xFF\x2D\xE5" 203 | "\xC5\x56\xB9\x53\x78\x4D\xDE\x7F\x94\x72\x1F\x2A\x9E\x14\xBD\x86" 204 | "\xEE\xE1\xF7\x63\x97\xAA\x7C\xB8\x51\x3E\xBD\x3C\x39\xD6\x11\x8A" 205 | "\xA0\x62\xDC\xCD\x53\xC3\x56\x22\x22\x98\x5F\x75\x74\x57\x36\x4F" 206 | "\x43\x0B\xC6\x2D\x82\x8E\xA9\x91\x56\x2D\x98\x48\xBB\xB1\x59\xD5" 207 | "\x0E\x68\xC0\x24\x92\x7E\x6B\xAE\x21\x4F\xF1\x90\x6A\x19\x59\x27" 208 | "\x7F\x9A\x63\x6D\x75\x3D\x44\x86\x01\x26\x8C\xBB\xEB\xC5\x06\xB5" 209 | "\x5F\x54\x7F\x2D\x02\x00\x41\x4E\x4D\x46\xB6\x01\x00\x00\x07\x00" 210 | "\x00\x03\x00\x00\x20\x00\x00\x19\x00\x00\x32\x00\x00\x00\x56\x50" 211 | "\x38\x4C\x9D\x01\x00\x00\x2F\x20\x40\x06\x10\x7F\xC1\x28\x92\x24" 212 | "\x45\x43\xDD\x3D\xC7\xF4\x3E\xFF\xE7\xED\x44\xAC\x0D\xC6\x6D\x24" 213 | "\x29\x5A\x1A\xC6\xF7\xE5\x9F\x25\x2A\x6C\x24\x29\x79\xE4\xFE\x70" 214 | "\xA7\xFF\x4A\x21\xF3\x1F\x36\xA4\x0F\x61\x2C\x70\x5B\x03\xA2\x50" 215 | "\xAF\x06\xB9\x38\xCF\xFF\x11\xD5\x7F\x54\x79\x66\xE4\xF1\x44\xE9" 216 | "\x11\x28\x09\x05\x68\x15\x02\xCA\xA1\x6B\x42\x57\x29\x51\x1E\xA7" 217 | "\xAC\x60\x55\xF0\x06\x4A\xC1\x19\x00\xC2\x48\xD1\x0D\xF0\xBE\xBC" 218 | "\x80\xEA\xBD\x36\x6E\x10\x52\x13\x6D\x49\x87\x56\x93\x79\x44\xD5" 219 | "\x44\xF8\x3D\x00\x10\x20\xE0\x01\x04\xD9\xB6\xD3\x36\x7D\x0E\x33" 220 | "\x89\xAD\x48\x3F\xCC\x91\xF7\xBF\xBC\x54\xFA\x81\x15\x44\xF4\x5F" 221 | "\x61\xDB\xB6\x8D\xD4\xA4\x7B\xBD\xD1\xFC\xD8\xBF\xC2\xCF\x5B\xAC" 222 | "\x5F\x05\x80\x76\xB7\x6A\x80\x4F\x03\x1D\xE1\x02\x51\xF4\x56\xB4" 223 | "\xF1\x59\x2C\x3D\x95\x7A\x81\x0F\x80\x88\xC4\x6A\x5A\xEF\x63\x55" 224 | "\x46\x6D\xB4\x0D\x44\x12\x6F\xC0\x22\x10\x91\x0F\x44\x7A\x73\x38" 225 | "\xEE\xA6\xC1\xFB\xE9\x9B\x18\x65\xD1\x59\x44\x1A\x5E\x52\x7A\x0E" 226 | "\xF4\x68\x24\xC0\x8E\x75\x5C\x76\x0E\x26\x31\xE0\x96\x52\x3A\x2E" 227 | "\x2A\x28\x16\xA8\xEC\xCC\x28\x2C\xAD\x33\xCE\x73\xA9\x43\x9B\x63" 228 | "\xC6\x9D\xEE\x71\xFF\x67\x49\x44\x2B\xB0\x35\x43\xDE\x39\xAD\x88" 229 | "\xC8\x94\x00\x8E\x38\x7B\xD7\x17\x07\x45\x44\x35\xC7\x9A\x65\x7E" 230 | "\x4E\xE9\xD1\x77\x44\xE4\x4B\x46\x6D\xCF\x22\x8F\x29\xDD\x7A\x81" 231 | "\x4D\x90\x63\xCB\x52\xE3\x9E\xCE\x8B\xF8\xC2\xE5\x60\x12\xF4\x22" 232 | "\xB2\x65\x79\x7C\x6C\x0D\x65\x4B\xB2\x98\x79\x0A\x13\xC9\xE2\x87" 233 | "\xFB\x49\xF6\xD0\x79\x16\x22\xFB\xE5\x50\xB2\x4D\x0B\x49\xE5\xCC" 234 | "\xD8\x15\x1F\x9E\xCD\x2D\xD3\x88\xD9\x63\x49\xBE\x98\x31\xB7\x06" 235 | "\x3D\x9C\x0A\x53\x87\x6C\x69\xFE\x44\x81\x66\x1A\xD8\x2F\x07\xA7" 236 | "\xC6\x40\x47\x50\xE9\x0A\x8D\x70\x6F\x35\x4A\x1A\x57\xBA\x42\xF3" 237 | "\xA5\x00\x1A\x00\x41\x4E\x4D\x46\xB2\x01\x00\x00\x06\x00\x00\x03" 238 | "\x00\x00\x25\x00\x00\x19\x00\x00\x32\x00\x00\x00\x56\x50\x38\x4C" 239 | "\x99\x01\x00\x00\x2F\x25\x40\x06\x10\x57\xA1\xB0\x6D\xDB\x26\xCB" 240 | "\x49\xBA\x0F\xE8\xFF\xE8\x95\xE3\x0D\xC5\x6D\x23\x29\x47\xCB\xC7" 241 | "\xCF\xEB\xBF\x4B\x54\xD4\x36\x92\xB3\xB7\xBD\x3F\x8F\xD7\xF1\xC7" 242 | "\x72\xD2\xFC\x07\x39\xC8\x0E\x10\x03\x6C\x64\x89\x3D\x02\x3F\x05" 243 | "\x83\x8F\x41\x21\xA2\x2F\xC6\x73\x40\x2E\x56\x1C\xB2\x8B\x96\xBC" 244 | "\x38\xCB\x46\x20\xB9\x1A\xEF\x29\x46\x41\x0F\x9E\x9D\xF7\x06\x66" 245 | "\x82\xC9\x8A\x73\x54\x67\x3E\xF7\xB2\xBD\xAE\x21\x6E\x95\xE5\x72" 246 | "\x04\xA8\xA5\xA3\x18\x9B\x65\xE0\x33\x80\x70\x10\x02\x1F\x20\xD8" 247 | "\xB6\xD5\xB4\xE2\xA6\xDA\x5B\x44\x08\xEF\xE6\xF7\x0A\xF3\x1F\xA1" 248 | "\xE6\x99\x32\x81\x88\xFE\x4F\x40\x7A\x6A\xE0\x50\x75\x6D\xB9\x07" 249 | "\xEE\x01\xF4\x5E\x48\x32\xB8\x1A\x77\xA8\x1D\x95\x16\x37\x32\x68" 250 | "\x03\x17\xC5\x9F\x49\x5A\xDC\xC4\xA0\x15\x2E\xDA\x23\xD0\x9C\xC9" 251 | "\x1E\x37\x30\x68\x02\x39\xD5\x81\xEE\xF2\xF5\xFB\xF3\x5E\xBB\xCA" 252 | "\xDE\xC0\xA0\x0E\x24\x5B\x58\xDA\xF7\x18\xE3\x0F\x1A\x0C\x19\xD6" 253 | "\x18\x54\x67\x92\x3C\xC1\xD1\x7E\xC4\x18\xE3\xB6\x85\x3D\xAD\x31" 254 | "\xE6\xE8\x39\x97\x40\x8E\x0B\x2F\xEE\x4C\xB7\x0A\x96\xCA\xF1\x23" 255 | "\xC5\x18\xB7\x9E\x0C\x47\x9D\xC1\xD6\x6B\xEC\x7B\x8A\xF1\x0F\x9E" 256 | "\x64\x83\x15\xB9\x68\xA6\xD7\x14\xE3\xF7\x56\x48\x5A\x95\xC1\xA1" 257 | "\xA4\xD6\xEF\xFE\x62\xFC\xEC\x48\xD2\x69\x8C\xD9\xF6\x3A\x29\xBE" 258 | "\x62\x7C\x9B\x66\x5E\x95\x9A\x29\x13\x0D\xED\xEB\xFF\xCF\xDE\xAF" 259 | "\xDB\x3B\x29\x9C\x4A\xEA\x8F\xF7\x8E\xF3\x49\x53\x4C\xE4\xD0\x89" 260 | "\x86\x3E\xDB\xF8\x85\x5E\xD1\x7A\x92\xA1\xB2\x2A\x8E\x8D\xCC\x42" 261 | "\x96\x16\x91\x59\x2E\xBA\x62\x14\x8D\x78\xCE\x27\x2C\xA0\xF6\xBC" 262 | "\xEA\xAB\xDA\xC9\xB5\xAB\xF5\xD2\x20\x54\x8A\x2D\xCB\xCE\x4E\x4E" 263 | "\x14\x1E\x69\x3E\x0A\xF5\xE2\xC7\xBA\xC8\xA6\x35\x18\x79\x53\xA1" 264 | "\x32\x20\x25\x54\xEE\xFE\x59\x4A\x48\xF7\x07\x90\x00\x00\x41\x4E" 265 | "\x4D\x46\xCA\x01\x00\x00\x06\x00\x00\x02\x00\x00\x27\x00\x00\x1B" 266 | "\x00\x00\x32\x00\x00\x00\x56\x50\x38\x4C\xB1\x01\x00\x00\x2F\x27" 267 | "\xC0\x06\x10\x47\xC1\x28\x92\x24\x45\x0B\x4D\xFE\x95\xAD\x91\xC3" 268 | "\x17\xDF\xD9\x50\x1C\x49\x52\x82\xCB\xE1\xA4\x47\xDA\x04\x62\x3F" 269 | "\x67\xDB\x48\x92\xA2\x59\x86\xFC\xE3\x3B\xFF\x6D\xE6\x9F\xFF\x28" 270 | "\x08\x2B\x82\xA2\x80\x9B\x81\xA9\x4F\x02\x98\xF9\x39\x69\xF0\x91" 271 | "\x4B\x00\x03\xE3\x00\xCC\xDA\x35\x2D\x60\x85\x43\x80\x35\x72\x41" 272 | "\x5E\x24\x37\x46\x93\x5C\x1C\x4C\x07\x07\xA1\xBA\x81\xBF\x4C\x20" 273 | "\x7E\x12\x2E\x32\xF0\x2B\x72\x13\x60\xA3\x0E\xC7\x39\x9D\xFB\xB2" 274 | "\xB7\xC3\xB9\x4E\x53\x9B\xAD\x73\x05\x08\xB2\x6D\xB5\x6D\xFE\x57" 275 | "\xA2\xA8\xDA\xA1\x7F\x78\xA9\x38\x95\xFD\x2F\x11\x19\x29\x65\x01" 276 | "\x11\xFD\x9F\x80\xF2\x1F\x72\xFD\x73\xDC\xAF\xC6\x39\xB3\xF4\xFC" 277 | "\x43\xBD\x15\x6C\x8A\xE9\xF8\x38\x2A\x4A\xD0\x18\x46\x3E\x8A\x58" 278 | "\x27\x6C\x7A\x6B\xAC\x40\x26\x3E\x86\x58\x27\x20\x08\x60\x6F\xDF" 279 | "\x2F\x9F\xB3\x88\xED\x0E\x21\x56\x09\x08\x7C\x46\x9C\x1E\x73\xCE" 280 | "\x9F\xFA\x66\x9C\xF8\x00\xE2\x73\x02\xE0\x79\x82\xF4\x2F\x39\xE7" 281 | "\xB7\xA5\xF4\xE6\x00\xE2\x53\x44\x6D\x3C\xA4\x7F\xCD\x39\x5F\x94" 282 | "\x75\x61\x1F\xF1\x2C\xB8\x2E\xB7\xAF\x25\xE7\x8B\x06\xD2\x7C\x80" 283 | "\x43\x4B\xFF\x52\x19\x00\x9A\x77\x10\x73\x6C\x89\xD3\x53\xC9\xF9" 284 | "\xDD\x02\xF0\x3B\x88\x78\x42\xF3\xFA\x90\x73\xFE\xF6\x00\xE2\x1E" 285 | "\x1E\xE6\x36\xFB\x99\xF3\x13\x47\x00\x69\x6A\x22\x62\xD3\xA5\x26" 286 | "\xB9\x7B\xCE\x97\x13\xEA\xA5\x8D\x97\x38\xFA\x26\xA8\x8F\xD7\x2F" 287 | "\x57\xA5\xB9\x85\x88\x3D\x8C\x6A\x93\xF9\x73\x8C\x55\xEC\xDA\xE6" 288 | "\x04\x99\x5C\x13\x3C\x5B\xD4\x8E\xAF\x11\xD1\x18\x00\xF8\xCE\x37" 289 | "\x41\xCB\xC6\xB9\x5C\x25\xA2\xD1\xA1\x76\xBD\x6B\xDA\x0E\xBC\x45" 290 | "\x44\x3C\x07\x6C\xFB\x49\xC9\x2E\xBD\x45\x44\xAC\x04\xD7\x45\x8D" 291 | "\xDA\xC7\xD4\x92\x96\x52\x13\xF1\xE8\xD0\x1E\xF4\x3C\xCE\xAA\x21" 292 | "\x8E\x15\x11\xAF\x82\xDD\x49\x42\x68\x99\x4A\x29\x44\x7C\x1F\xF1" 293 | "\xD3\x6B\x29\x44\x34\x38\xFF\xE3\x27\x2E\x44\xE5\x37\x32\x17\x00"; 294 | -------------------------------------------------------------------------------- /lib/assets/tronbyt.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tronbyt/firmware-esp32/ecc6fa89bab95b88a2d3b8d2fd1785dd3f4811b9/lib/assets/tronbyt.webp -------------------------------------------------------------------------------- /lib/assets/tronbyt_config.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tronbyt/firmware-esp32/ecc6fa89bab95b88a2d3b8d2fd1785dd3f4811b9/lib/assets/tronbyt_config.webp -------------------------------------------------------------------------------- /lib/assets/tronbyt_config.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tronbyt/firmware-esp32/ecc6fa89bab95b88a2d3b8d2fd1785dd3f4811b9/lib/assets/tronbyt_config.xcf -------------------------------------------------------------------------------- /lib/webp/library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "libwebp", 3 | "repository": { 4 | "type": "git", 5 | "url": "https://github.com/webmproject/libwebp.git", 6 | "branch": "main" 7 | }, 8 | "build": { 9 | "srcFilter": [ 10 | "+" 11 | ], 12 | "includeDir": "./src", 13 | "srcDir": ".", 14 | "flags": [ 15 | "-I include" 16 | ] 17 | } 18 | } -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [env] 12 | platform = espressif32 @ 6.10.0 13 | framework = espidf 14 | monitor_speed = 115200 15 | ; monitor_rts = 0 16 | ; monitor_dtr = 0 17 | extra_scripts = 18 | pre:extra_scripts/pre.py 19 | extra_scripts/reset.py 20 | monitor_filters = 21 | direct 22 | 23 | 24 | ; _____ ______ _ _ __ 25 | ; / ____| ____| \ | | /_ | 26 | ; | | __| |__ | \| |______| | 27 | ; | | |_ | __| | . ` |______| | 28 | ; | |__| | |____| |\ | | | 29 | ; \_____|______|_| \_| |_| 30 | ;################################################################################### 31 | ;################################################################################### 32 | [env:tidbyt-gen1] 33 | board = tidbyt 34 | board_build.partitions = boards/default_8mb.csv 35 | board_build.cmake_extra_args = 36 | -DSDKCONFIG_DEFAULTS="sdkconfig.defaults;" 37 | build_flags = 38 | -D NO_GFX 39 | -D NO_FAST_FUNCTIONS 40 | -D HTTP_BUFFER_SIZE_MAX=220000 41 | -D HTTP_BUFFER_SIZE_DEFAULT=100000 42 | lib_deps = 43 | https://github.com/webmproject/libwebp.git#1d86819f49edc8237fa2b844543081bcb8ef8a92 44 | https://github.com/mrcodetastic/ESP32-HUB75-MatrixPanel-DMA.git#aed04adfcda1838bf85c629a8c3b560919b3a327 ; commit as of 20250320 45 | 46 | [env:tidbyt-gen1_swap] 47 | board = tidbyt 48 | board_build.partitions = boards/default_8mb.csv 49 | board_build.cmake_extra_args = 50 | -DSDKCONFIG_DEFAULTS="sdkconfig.defaults;" 51 | build_flags = 52 | -D NO_GFX 53 | -D NO_FAST_FUNCTIONS 54 | -D SWAP_COLORS 55 | -D HTTP_BUFFER_SIZE_MAX=220000 56 | -D HTTP_BUFFER_SIZE_DEFAULT=100000 57 | lib_deps = 58 | https://github.com/webmproject/libwebp.git#1d86819f49edc8237fa2b844543081bcb8ef8a92 59 | https://github.com/mrcodetastic/ESP32-HUB75-MatrixPanel-DMA.git#aed04adfcda1838bf85c629a8c3b560919b3a327 ; commit as of 20250320 60 | 61 | ;################################################################################### 62 | ;################################################################################### 63 | 64 | 65 | 66 | ; _____ ______ _ _ ___ 67 | ; / ____| ____| \ | | |__ \ 68 | ; | | __| |__ | \| |______ ) | 69 | ; | | |_ | __| | . ` |______/ / 70 | ; | |__| | |____| |\ | / /_ 71 | ; \_____|______|_| \_| |____| 72 | ;################################################################################### 73 | ;################################################################################### 74 | [env:tidbyt-gen2] 75 | board = gen2 76 | board_build.partitions = boards/default_8mb.csv 77 | board_build.cmake_extra_args = 78 | -DSDKCONFIG_DEFAULTS="sdkconfig.defaults;" 79 | build_flags = 80 | -D NO_GFX 81 | -D NO_FAST_FUNCTIONS 82 | -D TIDBYT_GEN2 83 | -D NO_INVERT_CLOCK_PHASE 84 | -D BOOT_WEBP_TRONBYT ; not required as this is the default 85 | -D HTTP_BUFFER_SIZE_MAX=220000 86 | -D HTTP_BUFFER_SIZE_DEFAULT=100000 87 | lib_deps = 88 | https://github.com/webmproject/libwebp.git#1d86819f49edc8237fa2b844543081bcb8ef8a92 89 | https://github.com/mrcodetastic/ESP32-HUB75-MatrixPanel-DMA.git#aed04adfcda1838bf85c629a8c3b560919b3a327 ; commit as of 20250320 90 | ;################################################################################### 91 | ;################################################################################### 92 | 93 | 94 | 95 | ; _____ _______ ______ _______ _____ _____ _ ________ _____ 96 | ; | __ \_ _\ \ / / __ \__ __|_ _/ ____| |/ / ____| __ \ 97 | ; | |__) || | \ V / | | | | | | || | | ' /| |__ | |__) | 98 | ; | ___/ | | > <| | | | | | | || | | < | __| | _ / 99 | ; | | _| |_ / . \ |__| | | | _| || |____| . \| |____| | \ \ 100 | ; |_| |_____/_/ \_\____/ |_| |_____\_____|_|\_\______|_| \_\ 4MB flash, no PSRAM 101 | ;################################################################################### 102 | ;################################################################################### 103 | [env:pixoticker] 104 | board = wroom32 105 | board_build.partitions = boards/max_app_4mb.csv 106 | board_build.flash_size = 4MB 107 | board_build.cmake_extra_args = 108 | -DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.pixoticker.defaults" 109 | build_flags = 110 | -D NO_GFX 111 | -D NO_FAST_FUNCTIONS 112 | -D SWAP_COLORS 113 | -D PIXOTICKER 114 | -D HTTP_BUFFER_SIZE_MAX=30000 115 | -D HTTP_BUFFER_SIZE_DEFAULT=10000 116 | -D BOOT_WEBP_PARROT ; smaller size boot for pixoticker 117 | 118 | lib_deps = 119 | https://github.com/webmproject/libwebp.git#1d86819f49edc8237fa2b844543081bcb8ef8a92 120 | https://github.com/mrcodetastic/ESP32-HUB75-MatrixPanel-DMA.git#aed04adfcda1838bf85c629a8c3b560919b3a327 ; commit as of 20250320 121 | 122 | 123 | 124 | ; _______ _ _ _____ ____ 125 | ; |__ __| | | | | / ____|___ \ 126 | ; | |_ __ ___ _ __ | |__ _ _| |_ _____| (___ __) | 127 | ; | | '__/ _ \| '_ \| '_ \| | | | __|______\___ \ |__ < 128 | ; | | | | (_) | | | | |_) | |_| | |_ ____) |___) | 129 | ; |_|_| \___/|_| |_|_.__/ \__, |\__| |_____/|____/ 130 | ; __/ | 131 | ; |___/ 132 | ;################################################################################### 133 | ;################################################################################### 134 | [env:tronbyt-s3] 135 | board = tronbyt-S3 136 | board_build.partitions = boards/default_8mb.csv 137 | build_flags = 138 | -D NO_GFX 139 | -D NO_FAST_FUNCTIONS 140 | -D TRONBYT_S3 141 | -D NO_INVERT_CLOCK_PHASE 142 | -D BOOT_WEBP_TRONBYT ; not required as this is the default 143 | -D HTTP_BUFFER_SIZE_MAX=220000 144 | -D HTTP_BUFFER_SIZE_DEFAULT=100000 145 | -Wno-deprecated-declarations 146 | -Wno-missing-field-initializers 147 | lib_deps = 148 | https://github.com/webmproject/libwebp.git#1d86819f49edc8237fa2b844543081bcb8ef8a92 149 | https://github.com/mrcodetastic/ESP32-HUB75-MatrixPanel-DMA.git#aed04adfcda1838bf85c629a8c3b560919b3a327 ; commit as of 20250320 150 | 151 | 152 | ; __ __ _ _ _____ _ _ _____ ____ 153 | ; | \/ | | | (_) | __ \ | | | | / ____|___ \ 154 | ; | \ / | __ _| |_ _ __ ___ __ | |__) |__ _ __| |_ __ _| | | (___ __) | 155 | ; | |\/| |/ _` | __| '__| \ \/ / | ___/ _ \| '__| __/ _` | | \___ \ |__ < 156 | ; | | | | (_| | |_| | | |> < | | | (_) | | | || (_| | | ____) |___) | 157 | ; |_| |_|\__,_|\__|_| |_/_/\_\ |_| \___/|_| \__\__,_|_| |_____/|____/ 158 | 159 | ;################################################################################### 160 | ;################################################################################### 161 | [env:matrixportal-s3] 162 | board = adafruit_matrixportal_esp32s3 163 | board_build.partitions = boards/default_8mb.csv 164 | build_flags = 165 | -D NO_GFX 166 | -D NO_FAST_FUNCTIONS 167 | -D MATRIXPORTALS3 168 | -D NO_INVERT_CLOCK_PHASE 169 | -D BOOT_WEBP_TRONBYT ; not required as this is the default 170 | -D HTTP_BUFFER_SIZE_MAX=220000 171 | -D HTTP_BUFFER_SIZE_DEFAULT=100000 172 | lib_deps = 173 | https://github.com/webmproject/libwebp.git#1d86819f49edc8237fa2b844543081bcb8ef8a92 174 | https://github.com/mrcodetastic/ESP32-HUB75-MatrixPanel-DMA.git#aed04adfcda1838bf85c629a8c3b560919b3a327 ; commit as of 20250320 175 | 176 | 177 | ; _______ _ _____ ____ __ ___ _ 178 | ; |__ __| | | / ____|___ \ \ \ / (_) | | 179 | ; | |_ __ ___ _ __ | |__ _ _ _____| (___ __) |____\ \ /\ / / _ __| | ___ 180 | ; | | '__/ _ \| '_ \| '_ \| | | |______\___ \ |__ <______\ \/ \/ / | |/ _` |/ _ \ 181 | ; | | | | (_) | | | | |_) | |_| | ____) |___) | \ /\ / | | (_| | __/ 182 | ; |_|_| \___/|_| |_|_.__/ \__, | |_____/|____/ \/ \/ |_|\__,_|\___| 183 | ; __/ | 184 | ; |___/ 185 | 186 | ;################################################################################### 187 | ;################################################################################### 188 | [env:tronbyt-s3-wide] 189 | board = tronbyt-S3 190 | board_build.partitions = boards/default_8mb.csv 191 | build_flags = 192 | -D NO_GFX 193 | -D NO_FAST_FUNCTIONS 194 | -D TRONBYT_S3 195 | -D TRONBYT_S3_WIDE 196 | -D NO_INVERT_CLOCK_PHASE 197 | -D BOOT_WEBP_TRONBYT 198 | -D HTTP_BUFFER_SIZE_MAX=220000 199 | -D HTTP_BUFFER_SIZE_DEFAULT=100000 200 | -Wno-deprecated-declarations 201 | -Wno-missing-field-initializers 202 | lib_deps = 203 | https://github.com/webmproject/libwebp.git#1d86819f49edc8237fa2b844543081bcb8ef8a92 204 | https://github.com/mrcodetastic/ESP32-HUB75-MatrixPanel-DMA.git#aed04adfcda1838bf85c629a8c3b560919b3a327 205 | ;################################################################################### 206 | ;################################################################################### 207 | -------------------------------------------------------------------------------- /sdkconfig.pixoticker.defaults: -------------------------------------------------------------------------------- 1 | # modified config change for pixoticker. 4MB flash size and no PSRAM 2 | 3 | CONFIG_ESPTOOLPY_FLASHSIZE_8MB=n 4 | CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y 5 | CONFIG_ESPTOOLPY_FLASHSIZE="4MB" 6 | 7 | CONFIG_SPIRAM=n 8 | CONFIG_SPI_MASTER_ISR_IN_IRAM=n 9 | CONFIG_ESP_SLEEP_PSRAM_LEAKAGE_WORKAROUND=n 10 | 11 | CONFIG_ESP_SLEEP_FLASH_LEAKAGE_WORKAROUND=y 12 | CONFIG_ESP_SLEEP_PSRAM_LEAKAGE_WORKAROUND=n 13 | -------------------------------------------------------------------------------- /secrets.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "WIFI_SSID": "myssiD", 3 | "WIFI_PASSWORD": "PASSWORD", 4 | "REMOTE_URL": "http://192.168.1.10:8000/admin/tronbyt_1/next", 5 | "REFRESH_INTERVAL_SECONDS": 10, 6 | "DEFAULT_BRIGHTNESS" : 30 7 | } -------------------------------------------------------------------------------- /secrets_place.json: -------------------------------------------------------------------------------- 1 | { 2 | "WIFI_SSID": "XplaceholderWIFISSID____________", 3 | "WIFI_PASSWORD": "XplaceholderWIFIPASSWORD________________________________________", 4 | "REMOTE_URL": "XplaceholderREMOTEURL___________________________________________________________________________________________________________", 5 | "REFRESH_INTERVAL_SECONDS": 10, 6 | "DEFAULT_BRIGHTNESS": 30 7 | } -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file was automatically generated for projects 2 | # without default 'CMakeLists.txt' file. 3 | 4 | FILE(GLOB_RECURSE app_sources ${CMAKE_SOURCE_DIR}/src/*.*) 5 | 6 | idf_component_register(SRCS ${app_sources}) 7 | -------------------------------------------------------------------------------- /src/display.cpp: -------------------------------------------------------------------------------- 1 | #include "display.h" 2 | 3 | #include 4 | #ifdef TIDBYT_GEN2 5 | #define R1 5 6 | #define G1 23 7 | #define BL1 4 8 | #define R2 2 9 | #define G2 22 10 | #define BL2 32 11 | 12 | #define CH_A 25 13 | #define CH_B 21 14 | #define CH_C 26 15 | #define CH_D 19 16 | #define CH_E -1 // assign to pin 14 if using more than two panels 17 | 18 | #define LAT 18 19 | #define OE 27 20 | #define CLK 15 21 | #elif defined(TRONBYT_S3_WIDE) 22 | #define R1 4 23 | #define G1 5 24 | #define BL1 6 25 | #define R2 7 26 | #define G2 15 27 | #define BL2 16 28 | 29 | #define CH_A 17 30 | #define CH_B 18 31 | #define CH_C 8 32 | #define CH_D 3 33 | #define CH_E 46 34 | #define LAT 9 35 | #define OE 10 36 | #define CLK 11 37 | #elif defined(TRONBYT_S3) 38 | #define R1 4 39 | #define G1 6 40 | #define BL1 5 41 | #define R2 7 42 | #define G2 16 43 | #define BL2 15 44 | 45 | #define CH_A 17 46 | #define CH_B 18 47 | #define CH_C 8 48 | #define CH_D 3 49 | #define CH_E -1 50 | 51 | #define LAT 9 52 | #define OE 10 53 | #define CLK 11 54 | #elif defined(PIXOTICKER) 55 | #define R1 2 56 | #define G1 4 57 | #define BL1 15 58 | #define R2 16 59 | #define G2 17 60 | #define BL2 27 61 | #define CH_A 5 62 | #define CH_B 18 63 | #define CH_C 19 64 | #define CH_D 21 65 | #define CH_E 12 66 | #define CLK 22 67 | #define LAT 26 68 | #define OE 25 69 | #elif defined(MATRIXPORTALS3) 70 | // R1, G1, B1, R2, G2, B2 71 | // uint8_t rgbPins[] = {42, 41, 40, 38, 39, 37}; 72 | // uint8_t addrPins[] = {45, 36, 48, 35, 21}; 73 | // uint8_t clockPin = 2; 74 | // uint8_t latchPin = 47; 75 | // uint8_t oePin = 14; 76 | #define R1 42 77 | #define G1 41 78 | #define BL1 40 79 | #define R2 38 80 | #define G2 39 81 | #define BL2 37 82 | #define CH_A 45 83 | #define CH_B 36 84 | #define CH_C 48 85 | #define CH_D 35 86 | #define CH_E 21 87 | #define CLK 2 88 | #define LAT 47 89 | #define OE 14 90 | 91 | #else // GEN1 from here down. 92 | #ifdef SWAP_COLORS 93 | #define R1 21 94 | #define G1 2 95 | #define BL1 22 96 | #define R2 23 97 | #define G2 4 98 | #define BL2 27 99 | #else 100 | #define R1 2 101 | #define G1 22 102 | #define BL1 21 103 | #define R2 4 104 | #define G2 27 105 | #define BL2 23 106 | #endif 107 | 108 | #define CH_A 26 109 | #define CH_B 5 110 | #define CH_C 25 111 | #define CH_D 18 112 | #define CH_E -1 // assign to pin 14 if using more than two panels 113 | 114 | #define LAT 19 115 | #define OE 32 116 | #define CLK 33 117 | #endif 118 | 119 | static MatrixPanel_I2S_DMA *_matrix; 120 | static uint8_t _brightness = DISPLAY_DEFAULT_BRIGHTNESS; 121 | static const char *TAG = "display"; 122 | 123 | int display_initialize() { 124 | // Initialize the panel. 125 | HUB75_I2S_CFG::i2s_pins pins = {R1, G1, BL1, R2, G2, BL2, CH_A, 126 | CH_B, CH_C, CH_D, CH_E, LAT, OE, CLK}; 127 | 128 | #ifdef NO_INVERT_CLOCK_PHASE 129 | bool invert_clock_phase = false; 130 | #else 131 | bool invert_clock_phase = true; 132 | #endif 133 | 134 | #ifdef TRONBYT_S3_WIDE 135 | HUB75_I2S_CFG mxconfig(128, // width 136 | 64, // height 137 | 1, // chain length 138 | pins, // pin mapping 139 | HUB75_I2S_CFG::FM6126A, // driver chip 140 | true, // double-buffering 141 | HUB75_I2S_CFG::HZ_10M, // clock speed 142 | 1, // latch blanking 143 | invert_clock_phase // invert clock phase 144 | ); 145 | #else 146 | HUB75_I2S_CFG mxconfig(64, // width 147 | 32, // height 148 | 1, // chain length 149 | pins, // pin mapping 150 | HUB75_I2S_CFG::FM6126A, // driver chip 151 | true, // double-buffering 152 | HUB75_I2S_CFG::HZ_10M, // clock speed 153 | 1, // latch blanking 154 | invert_clock_phase // invert clock phase 155 | ); 156 | #endif 157 | 158 | _matrix = new MatrixPanel_I2S_DMA(mxconfig); 159 | 160 | if (!_matrix->begin()) { 161 | return 1; 162 | } 163 | display_set_brightness(DEFAULT_BRIGHTNESS); 164 | 165 | return 0; 166 | } 167 | 168 | static inline uint8_t brightness_percent_to_8bit(uint8_t pct) { 169 | if (pct > 100) pct = 100; 170 | return (uint8_t)(((uint32_t)pct * 230 + 50) / 100); // 230 as MAX 8 BIT HARDCODED 171 | } 172 | 173 | void display_set_brightness(uint8_t brightness_pct) { 174 | if (brightness_pct != _brightness) { 175 | uint8_t brightness_8bit = brightness_percent_to_8bit(brightness_pct); 176 | 177 | #ifdef MAX_BRIGHTNESS_8BIT 178 | uint8_t max_brightness_8bit = MAX_BRIGHTNESS_8BIT; 179 | if (brightness_8bit > max_brightness_8bit) { 180 | brightness_8bit = max_brightness_8bit; 181 | ESP_LOGI(TAG, "Clamping brightness to MAX_BRIGHTNESS (%d)", MAX_BRIGHTNESS_8BIT); 182 | } 183 | #endif 184 | 185 | ESP_LOGI(TAG, "Setting brightness to %d%% (%d)", brightness_pct, brightness_8bit); 186 | _matrix->setBrightness8(brightness_8bit); 187 | _matrix->clearScreen(); 188 | _brightness = brightness_pct; 189 | } 190 | } 191 | 192 | uint8_t display_get_brightness() { return _brightness; } 193 | 194 | void display_shutdown() { 195 | _matrix->clearScreen(); 196 | _matrix->stopDMAoutput(); 197 | } 198 | 199 | void display_draw(const uint8_t *pix, int width, int height, 200 | int channels, int ixR, int ixG, int ixB) { 201 | int scale = 1; 202 | #ifdef TRONBYT_S3_WIDE 203 | if (width == 64 && height == 32) { 204 | scale = 2; // Scale up to 128x64 205 | } 206 | #endif 207 | 208 | for (unsigned int i = 0; i < height; i++) { 209 | for (unsigned int j = 0; j < width; j++) { 210 | const uint8_t *p = &pix[(i * width + j) * channels]; 211 | uint8_t r = p[ixR]; 212 | uint8_t g = p[ixG]; 213 | uint8_t b = p[ixB]; 214 | 215 | // Draw each pixel scaled up (2x2 pixels for each original pixel) 216 | for (int sy = 0; sy < scale; sy++) { 217 | for (int sx = 0; sx < scale; sx++) { 218 | _matrix->drawPixelRGB888(j * scale + sx, i * scale + sy, r, g, b); 219 | } 220 | } 221 | } 222 | } 223 | _matrix->flipDMABuffer(); 224 | } 225 | 226 | void display_clear() { _matrix->clearScreen(); } 227 | -------------------------------------------------------------------------------- /src/display.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #define DISPLAY_MAX_BRIGHTNESS 100 7 | #define DISPLAY_MIN_BRIGHTNESS 1 8 | #define DISPLAY_DEFAULT_BRIGHTNESS 30 9 | extern int32_t isAnimating; // Declare the variable 10 | #ifdef __cplusplus 11 | extern "C" { 12 | #endif 13 | int display_initialize(); 14 | void display_set_brightness(uint8_t brightness_pct); 15 | uint8_t display_get_brightness(); 16 | void display_shutdown(); 17 | 18 | void display_draw(const uint8_t *pix, int width, int height, int channels, 19 | int ixR, int ixG, int ixB); 20 | 21 | void display_clear(); 22 | // int32_t isAnimating = 0; // Initialize with a valid value 23 | 24 | #ifdef __cplusplus 25 | } 26 | #endif 27 | -------------------------------------------------------------------------------- /src/flash.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int flash_initialize() { 4 | esp_err_t err = nvs_flash_init(); 5 | if (err == ESP_ERR_NVS_NO_FREE_PAGES || 6 | err == ESP_ERR_NVS_NEW_VERSION_FOUND) { 7 | ESP_ERROR_CHECK(nvs_flash_erase()); 8 | err = nvs_flash_init(); 9 | } 10 | 11 | if (err != ESP_OK) { 12 | return 1; 13 | } 14 | 15 | return 0; 16 | } 17 | 18 | void flash_shutdown() { nvs_flash_deinit(); } 19 | -------------------------------------------------------------------------------- /src/flash.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | int flash_initialize(); 4 | 5 | void flash_shutdown(); -------------------------------------------------------------------------------- /src/gfx.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "display.h" 10 | #include "esp_timer.h" 11 | 12 | static const char *TAG = "gfx"; 13 | 14 | #define GFX_TASK_CORE 1 15 | #define GFX_TASK_PRIO 2 16 | #define GFX_TASK_STACK_SIZE 4092 17 | 18 | struct gfx_state { 19 | TaskHandle_t task; 20 | SemaphoreHandle_t mutex; 21 | void *buf; 22 | size_t len; 23 | int counter; 24 | }; 25 | 26 | static struct gfx_state *_state = NULL; 27 | 28 | static void gfx_loop(void *arg); 29 | static int draw_webp(uint8_t *buf, size_t len, int32_t *isAnimating); 30 | 31 | int gfx_initialize(const void *webp, size_t len) { 32 | // Only initialize once 33 | if (_state) { 34 | ESP_LOGE(TAG, "Already initialized"); 35 | return 1; 36 | } 37 | 38 | int heapl = heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT); 39 | 40 | ESP_LOGI(TAG, "largest heap %d", heapl); 41 | // ESP_LOGI(TAG, "calling calloc"); 42 | // Initialize state 43 | ESP_LOGI(TAG, "Allocating buffer of size: %d", len); 44 | 45 | _state = calloc(1, sizeof(struct gfx_state)); 46 | _state->len = len; 47 | ESP_LOGI(TAG,"calloc buff"); 48 | _state->buf = calloc(1, len); 49 | ESP_LOGI(TAG, "done calloc, copying"); 50 | if (_state->buf == NULL) { 51 | ESP_LOGE("gfx", "Memory allocation failed!"); 52 | return 1; 53 | } 54 | memcpy(_state->buf, webp, len); 55 | ESP_LOGI(TAG, "done, copying"); 56 | 57 | _state->mutex = xSemaphoreCreateMutex(); 58 | if (_state->mutex == NULL) { 59 | ESP_LOGE(TAG, "Could not create gfx mutex"); 60 | return 1; 61 | } 62 | ESP_LOGI(TAG,"done with gfx init"); 63 | 64 | // Initialize the display 65 | if (display_initialize()) { 66 | return 1; 67 | } 68 | 69 | // Launch the graphics loop in separate task 70 | BaseType_t ret = 71 | xTaskCreatePinnedToCore(gfx_loop, // pvTaskCode 72 | "gfx_loop", // pcName 73 | GFX_TASK_STACK_SIZE, // usStackDepth 74 | (void *)&isAnimating, // pvParameters 75 | GFX_TASK_PRIO, // uxPriority 76 | &_state->task, // pxCreatedTask 77 | GFX_TASK_CORE // xCoreID 78 | ); 79 | if (ret != pdPASS) { 80 | ESP_LOGE(TAG, "Could not create gfx task"); 81 | return 1; 82 | } 83 | 84 | return 0; 85 | } 86 | 87 | int gfx_update(const void *webp, size_t len) { 88 | // Take mutex 89 | if (pdTRUE != xSemaphoreTake(_state->mutex, portMAX_DELAY)) { 90 | ESP_LOGE(TAG, "Could not take gfx mutex"); 91 | return 1; 92 | } 93 | 94 | // Update state 95 | if (len > _state->len) { 96 | // Free the old buffer only if it exists 97 | if (_state->buf) { 98 | free(_state->buf); 99 | _state->buf = NULL; // Set to NULL to avoid dangling pointers 100 | } 101 | 102 | // Allocate new memory 103 | _state->buf = malloc(len); 104 | if (!_state->buf) { 105 | ESP_LOGE("main", "Failed to allocate memory for _state->buf"); 106 | return 1; // Exit early to avoid using NULL buffer 107 | } 108 | 109 | _state->len = len; // Update length after successful allocation 110 | } 111 | 112 | // Copy data to buffer 113 | if (_state->buf && webp) { 114 | memcpy(_state->buf, webp, len); 115 | _state->counter++; 116 | } else { 117 | ESP_LOGE("main", "Buffer or input data is NULL"); 118 | } 119 | 120 | // Give mutex 121 | if (pdTRUE != xSemaphoreGive(_state->mutex)) { 122 | ESP_LOGE(TAG, "Could not give gfx mutex"); 123 | return 1; 124 | } 125 | 126 | return 0; 127 | } 128 | 129 | void gfx_shutdown() { display_shutdown(); } 130 | 131 | static void gfx_loop(void *args) { 132 | void *webp = NULL; 133 | size_t len = 0; 134 | int counter = -1; 135 | int32_t *isAnimating = (int32_t *)args; // Cast to pointer type 136 | ESP_LOGI(TAG, "Graphics loop running on core %d", xPortGetCoreID()); 137 | 138 | for (;;) { 139 | // Take mutex 140 | if (pdTRUE != xSemaphoreTake(_state->mutex, portMAX_DELAY)) { 141 | ESP_LOGE(TAG, "Could not take gfx mutex"); 142 | break; 143 | } 144 | 145 | // If there's new data, copy it to local buffer 146 | if (counter != _state->counter) { 147 | ESP_LOGI(TAG, "Loaded new webp"); 148 | if (_state->len > len) { 149 | free(webp); 150 | webp = malloc(_state->len); 151 | } 152 | len = _state->len; 153 | counter = _state->counter; 154 | memcpy(webp, _state->buf, _state->len); 155 | if (*isAnimating == -1) *isAnimating = 1; // set to 1 if -1 156 | } 157 | 158 | // Give mutex 159 | if (pdTRUE != xSemaphoreGive(_state->mutex)) { 160 | ESP_LOGE(TAG, "Could not give gfx mutex"); 161 | continue; 162 | } 163 | 164 | // Draw it 165 | ESP_LOGI(TAG, "calling draw_webp"); 166 | if (draw_webp(webp, len, isAnimating)) { 167 | ESP_LOGE(TAG, "Could not draw webp"); 168 | vTaskDelay(pdMS_TO_TICKS(1 * 1000)); 169 | *isAnimating = 0; 170 | } 171 | // vTaskDelay(pdMS_TO_TICKS(500)); // delay for anti barf 172 | } 173 | } 174 | 175 | static int draw_webp(uint8_t *buf, size_t len, int32_t *isAnimating) { 176 | // Set up WebP decoder 177 | ESP_LOGI(TAG, "starting draw_webp"); 178 | int app_dwell_secs = *isAnimating; 179 | 180 | 181 | int64_t dwell_us; 182 | 183 | if (app_dwell_secs <= 0 ) { 184 | ESP_LOGW(TAG,"isAnimating is already 0. Looping one more time while we wait."); 185 | dwell_us = 1 * 1000000; // default to 1s if it's zero so we loop again or show the image for 1 more second. 186 | } else { 187 | // ESP_LOGI(TAG, "dwell_secs : %d", app_dwell_secs); 188 | dwell_us = app_dwell_secs * 1000000; 189 | } 190 | // ESP_LOGI(TAG, "frame count: %d", animation.frame_count); 191 | 192 | WebPData webpData; 193 | WebPDataInit(&webpData); 194 | webpData.bytes = buf; 195 | webpData.size = len; 196 | 197 | WebPAnimDecoderOptions decoderOptions; 198 | WebPAnimDecoderOptionsInit(&decoderOptions); 199 | decoderOptions.color_mode = MODE_RGBA; 200 | 201 | WebPAnimDecoder *decoder = WebPAnimDecoderNew(&webpData, &decoderOptions); 202 | if (decoder == NULL) { 203 | ESP_LOGE(TAG, "Could not create WebP decoder"); 204 | return 1; 205 | } 206 | 207 | WebPAnimInfo animation; 208 | if (!WebPAnimDecoderGetInfo(decoder, &animation)) { 209 | ESP_LOGE(TAG, "Could not get WebP animation"); 210 | return 1; 211 | } 212 | int64_t start_us = esp_timer_get_time(); 213 | while (esp_timer_get_time() - start_us < dwell_us) { 214 | int lastTimestamp = 0; 215 | int delay = 0; 216 | TickType_t drawStartTick = xTaskGetTickCount(); 217 | 218 | // Draw each frame, and sleep for the delay 219 | while (WebPAnimDecoderHasMoreFrames(decoder) && *isAnimating != -1) { 220 | uint8_t *pix; 221 | int timestamp; 222 | WebPAnimDecoderGetNext(decoder, &pix, ×tamp); 223 | if (delay > 0) { 224 | xTaskDelayUntil(&drawStartTick, pdMS_TO_TICKS(delay)); 225 | } else { 226 | vTaskDelay(10); // small delay for yield. 227 | } 228 | drawStartTick = xTaskGetTickCount(); 229 | display_draw(pix, animation.canvas_width, animation.canvas_height, 4, 0, 230 | 1, 2); 231 | delay = timestamp - lastTimestamp; 232 | lastTimestamp = timestamp; 233 | } 234 | 235 | // reset decoder to start from the beginning 236 | WebPAnimDecoderReset(decoder); 237 | 238 | if (delay > 0) { 239 | xTaskDelayUntil(&drawStartTick, pdMS_TO_TICKS(delay)); 240 | } else { 241 | vTaskDelay(pdMS_TO_TICKS(100)); // Add a small fallback delay to yield CPU 242 | } 243 | 244 | // In case of a single frame, sleep for app_dwell_secs 245 | if (animation.frame_count == 1) { 246 | ESP_LOGI(TAG, "single frame delay for %d", app_dwell_secs); 247 | // xTaskDelayUntil(&start_us, dwell_us); 248 | vTaskDelay(pdMS_TO_TICKS(dwell_us / 1000)); // full dwell delay 249 | // *isAnimating = 0; 250 | break; 251 | } 252 | } 253 | WebPAnimDecoderDelete(decoder); 254 | if (app_dwell_secs != 0) { 255 | ESP_LOGI(TAG, "Setting isAnimating to 0"); 256 | *isAnimating = 0; // only set this to zero if it wasn't already set to 257 | // zero. setting it again might overwrite what the main 258 | // loop did while we were re-looping 259 | } 260 | return 0; 261 | } 262 | -------------------------------------------------------------------------------- /src/gfx.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | 6 | 7 | int gfx_initialize(const void* webp, size_t len); 8 | void gfx_update(const void* webp, size_t len); 9 | void gfx_shutdown(); 10 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "display.h" 11 | #include "flash.h" 12 | #include "gfx.h" 13 | #include "remote.h" 14 | #include "sdkconfig.h" 15 | #include "wifi.h" 16 | 17 | #define BLUE "\033[1;34m" 18 | #define RESET "\033[0m" // Reset to default color 19 | 20 | // Default URL if none is provided through WiFi manager 21 | #define DEFAULT_URL "http://URL.NOT.SET/" 22 | 23 | static const char* TAG = "main"; 24 | int32_t isAnimating = 25 | 5; // Initialize with a valid value enough time for boot animation 26 | int32_t app_dwell_secs = REFRESH_INTERVAL_SECONDS; 27 | uint8_t *webp; // main buffer downloaded webp data 28 | 29 | bool use_websocket = false; 30 | esp_websocket_client_handle_t ws_handle; 31 | 32 | static void websocket_event_handler(void *handler_args, esp_event_base_t base, 33 | int32_t event_id, void *event_data) { 34 | esp_websocket_event_data_t *data = (esp_websocket_event_data_t *)event_data; 35 | switch (event_id) { 36 | case WEBSOCKET_EVENT_CONNECTED: 37 | ESP_LOGI(TAG, "WEBSOCKET_EVENT_CONNECTED"); 38 | break; 39 | case WEBSOCKET_EVENT_DISCONNECTED: 40 | ESP_LOGI(TAG, "WEBSOCKET_EVENT_DISCONNECTED"); 41 | break; 42 | case WEBSOCKET_EVENT_DATA: 43 | ESP_LOGI(TAG, "---------------------WEBSOCKET_EVENT_DATA"); 44 | ESP_LOGI(TAG, "Received opcode=%d", data->op_code); 45 | // ESP_LOGW(TAG, "Received=%.*s", data->data_len, (char *)data->data_ptr); 46 | ESP_LOGW(TAG, "Total payload length=%d, data_len=%d, current payload offset=%d\r\n", 47 | data->payload_len, data->data_len, data->payload_offset); 48 | // Check if this is a complete message or just a fragment 49 | bool is_complete = 50 | (data->payload_offset + data->data_len >= data->payload_len); 51 | 52 | if (is_complete) { 53 | ESP_LOGI(TAG, "Message is complete"); 54 | 55 | } else { 56 | ESP_LOGI(TAG, "Message is fragmented - received %d/%d bytes", 57 | data->payload_offset + data->data_len, data->payload_len); 58 | } 59 | 60 | // Check if data contains "brightness" 61 | if (data->op_code == 1 && strstr((char *)data->data_ptr, "{\"brightness\":")) { 62 | ESP_LOGI(TAG, "Brightness data detected"); 63 | 64 | // Simple string parsing for {"brightness": xxx} 65 | char *brightness_pos = strstr((char *)data->data_ptr, "brightness"); 66 | if (brightness_pos) { 67 | // Find position after the space that follows the colon 68 | char *value_start = brightness_pos + 13; // brightness": is 13 chars 69 | 70 | // Parse the integer value directly 71 | int brightness_value = atoi(value_start); 72 | ESP_LOGI(TAG, "Parsed brightness: %d", brightness_value); 73 | 74 | // Clamp value between min and max 75 | if (brightness_value < DISPLAY_MIN_BRIGHTNESS) brightness_value = DISPLAY_MIN_BRIGHTNESS; 76 | if (brightness_value > DISPLAY_MAX_BRIGHTNESS) brightness_value = DISPLAY_MAX_BRIGHTNESS; 77 | 78 | // Set the brightness 79 | display_set_brightness((uint8_t)brightness_value); 80 | } 81 | } else if (data->op_code == 2) { 82 | // Binary data (WebP image) 83 | ESP_LOGI(TAG, "Binary data detected (WebP image)"); 84 | 85 | // Check if this is a complete message or just a fragment 86 | bool is_complete = 87 | (data->payload_offset + data->data_len >= data->payload_len); 88 | 89 | if (is_complete) { 90 | ESP_LOGI(TAG, "Message is complete"); 91 | } else { 92 | ESP_LOGI(TAG, "Message is fragmented - received %d/%d bytes", 93 | data->payload_offset + data->data_len, data->payload_len); 94 | } 95 | 96 | // Check if payload size exceeds maximum buffer size 97 | if (data->payload_len > HTTP_BUFFER_SIZE_MAX) { 98 | ESP_LOGE(TAG, "WebP payload size (%d bytes) exceeds maximum buffer size (%d bytes)", 99 | data->payload_len, HTTP_BUFFER_SIZE_MAX); 100 | break; 101 | } 102 | 103 | // First fragment or complete message - allocate memory 104 | if (data->payload_offset == 0) { 105 | // Free previous buffer if it exists 106 | if (webp != NULL) { 107 | free(webp); 108 | webp = NULL; 109 | } 110 | 111 | // Allocate memory for the full payload 112 | webp = malloc(data->payload_len); 113 | if (webp == NULL) { 114 | ESP_LOGE(TAG, "Failed to allocate memory for WebP image"); 115 | break; 116 | } 117 | } 118 | 119 | // Ensure we have a valid buffer 120 | if (webp == NULL) { 121 | ESP_LOGE(TAG, "WebP buffer is NULL, skipping fragment"); 122 | break; 123 | } 124 | 125 | // Copy this fragment to the appropriate position in the buffer 126 | memcpy(webp + data->payload_offset, data->data_ptr, data->data_len); 127 | 128 | // If complete, process the WebP image 129 | if (is_complete) { 130 | // Process the complete binary data as a WebP image 131 | gfx_update(webp, data->payload_len); 132 | 133 | // We don't control timing during websocket so use this to notify new data and to break out of current animation. 134 | isAnimating = -1; 135 | 136 | // Free the buffer after processing 137 | free(webp); 138 | webp = NULL; 139 | } 140 | } 141 | 142 | break; 143 | case WEBSOCKET_EVENT_ERROR: 144 | ESP_LOGI(TAG, "WEBSOCKET_EVENT_ERROR"); 145 | break; 146 | } 147 | } 148 | 149 | void app_main(void) { 150 | ESP_LOGI(TAG, "App Main Start"); 151 | 152 | // Setup the device flash storage. 153 | if (flash_initialize()) { 154 | ESP_LOGE(TAG, "failed to initialize flash"); 155 | return; 156 | } 157 | ESP_LOGI(TAG,"finished flash init"); 158 | esp_register_shutdown_handler(&flash_shutdown); 159 | 160 | // Setup the display. 161 | if (gfx_initialize(ASSET_BOOT_WEBP, ASSET_BOOT_WEBP_LEN)) { 162 | ESP_LOGE(TAG, "failed to initialize gfx"); 163 | return; 164 | } 165 | esp_register_shutdown_handler(&display_shutdown); 166 | 167 | // Setup WiFi. 168 | ESP_LOGI(TAG, "Initializing WiFi manager..."); 169 | // Pass empty strings to force AP mode 170 | if (wifi_initialize("", "")) { 171 | ESP_LOGE(TAG, "failed to initialize WiFi"); 172 | return; 173 | } 174 | esp_register_shutdown_handler(&wifi_shutdown); 175 | 176 | // Wait a bit for the AP to start 177 | vTaskDelay(pdMS_TO_TICKS(1000)); 178 | 179 | uint8_t mac[6]; 180 | if (!wifi_get_mac(mac)) { 181 | ESP_LOGI(TAG, "WiFi MAC: %02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], 182 | mac[2], mac[3], mac[4], mac[5]); 183 | } 184 | 185 | // Wait for WiFi connection (with a 60-second timeout) 186 | // This will block until either connected or timeout 187 | if (!wifi_wait_for_connection(60000)) { 188 | ESP_LOGW(TAG, "No WiFi connection established. Will continue to try connecting in the background."); 189 | } else { 190 | ESP_LOGI(TAG, "WiFi connected successfully!"); 191 | } 192 | 193 | 194 | // Load up the config webp so that we don't just loop the boot screen over and over again but show the ap config info webp 195 | ESP_LOGI(TAG, "Loading Config WEBP"); 196 | gfx_update(ASSET_CONFIG_WEBP, ASSET_CONFIG_WEBP_LEN); 197 | 198 | if (!wifi_is_connected()) { 199 | ESP_LOGW(TAG,"Pausing main task until wifi connected . . . "); 200 | while (!wifi_is_connected()) { 201 | vTaskDelay(pdMS_TO_TICKS(1 * 1000)); 202 | } 203 | } 204 | 205 | ESP_LOGI(TAG, "WiFi Connected, continuing main task thread . . . "); 206 | 207 | // Get the image URL from WiFi manager 208 | const char* image_url = wifi_get_image_url(); 209 | const char* url_to_use = (image_url != NULL && strlen(image_url) > 0) ? image_url : DEFAULT_URL; 210 | 211 | // Check for ws:// or wss:// in the URL 212 | if (strncmp(url_to_use, "ws://", 5) == 0 || strncmp(url_to_use, "wss://", 6) == 0) { 213 | ESP_LOGI(TAG, "Using websockets with URL: %s", url_to_use); 214 | use_websocket = true; 215 | // setup ws event handlers 216 | const esp_websocket_client_config_t ws_cfg = { 217 | .uri = url_to_use, 218 | .buffer_size = 10000, 219 | .crt_bundle_attach = esp_crt_bundle_attach, 220 | }; 221 | ws_handle = esp_websocket_client_init(&ws_cfg); 222 | esp_websocket_register_events(ws_handle, WEBSOCKET_EVENT_ANY, 223 | websocket_event_handler, 224 | (void *)ws_handle); 225 | for (;;) { 226 | if (!esp_websocket_client_is_connected(ws_handle)) { 227 | ESP_LOGW(TAG, "WebSocket not connected. Attempting to reconnect..."); 228 | esp_websocket_client_stop(ws_handle); // Safe even if already stopped 229 | esp_err_t err = esp_websocket_client_start(ws_handle); 230 | if (err != ESP_OK) { 231 | ESP_LOGE(TAG, "Reconnection failed with error %d", err); 232 | } else { 233 | ESP_LOGI(TAG, "Reconnected to WebSocket server."); 234 | } 235 | } 236 | // Do other stuff or vTaskDelay 237 | vTaskDelay(pdMS_TO_TICKS(5000)); // check every 5s 238 | } 239 | } else { 240 | // normal http 241 | ESP_LOGW(TAG, "HTTP Loop Start with URL: %s", url_to_use); 242 | for (;;) { 243 | uint8_t *webp; 244 | size_t len; 245 | static uint8_t brightness_pct = DISPLAY_DEFAULT_BRIGHTNESS; 246 | 247 | ESP_LOGI(TAG, "Fetching from URL: %s", url_to_use); 248 | if (remote_get(url_to_use, &webp, &len, &brightness_pct, 249 | &app_dwell_secs)) { 250 | ESP_LOGE(TAG, "Failed to get webp"); 251 | vTaskDelay(pdMS_TO_TICKS(1 * 5000)); 252 | } else { 253 | // Successful remote_get 254 | display_set_brightness(brightness_pct); 255 | ESP_LOGI(TAG, BLUE "Queuing new webp (%d bytes)" RESET, len); 256 | gfx_update(webp, len); 257 | free(webp); 258 | // Wait for app_dwell_secs to expire (isAnimating will be 0) 259 | ESP_LOGI(TAG, BLUE "isAnimating is %d" RESET, (int)isAnimating); 260 | if (isAnimating > 0) 261 | ESP_LOGI(TAG, BLUE "Delay for current webp" RESET); 262 | while (isAnimating > 0) { 263 | vTaskDelay(pdMS_TO_TICKS(1)); 264 | } 265 | ESP_LOGI(TAG, BLUE "Setting isAnimating to %d" RESET, 266 | (int)app_dwell_secs); 267 | isAnimating = app_dwell_secs; // use isAnimating as the container 268 | // for app_dwell_secs 269 | vTaskDelay(pdMS_TO_TICKS(1000)); 270 | } 271 | } 272 | } 273 | 274 | } 275 | -------------------------------------------------------------------------------- /src/remote.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | static const char* TAG = "remote"; 11 | 12 | struct remote_state { 13 | void* buf; 14 | size_t len; 15 | size_t size; 16 | size_t max; 17 | uint8_t brightness; 18 | int32_t dwell_secs; 19 | }; 20 | 21 | #define MAX(a, b) (((a) > (b)) ? (a) : (b)) 22 | #define MIN(a, b) (((a) < (b)) ? (a) : (b)) 23 | 24 | static esp_err_t _httpCallback(esp_http_client_event_t* event) { 25 | esp_err_t err = ESP_OK; 26 | struct remote_state* state = (struct remote_state*)event->user_data; 27 | 28 | switch (event->event_id) { 29 | case HTTP_EVENT_ERROR: 30 | ESP_LOGE(TAG, "HTTP_EVENT_ERROR"); 31 | break; 32 | 33 | case HTTP_EVENT_ON_CONNECTED: 34 | ESP_LOGD(TAG, "HTTP_EVENT_ON_CONNECTED"); 35 | break; 36 | 37 | case HTTP_EVENT_HEADER_SENT: 38 | ESP_LOGD(TAG, "HTTP_EVENT_HEADER_SENT"); 39 | break; 40 | 41 | case HTTP_EVENT_ON_HEADER: 42 | ESP_LOGD(TAG, "HTTP_EVENT_ON_HEADER, key=%s, value=%s", event->header_key, 43 | event->header_value); 44 | 45 | // Check for the Content-Length header 46 | if (strcmp(event->header_key, "Content-Length") == 0) { 47 | size_t content_length = (size_t)atoi(event->header_value); 48 | if (content_length > state->max) { 49 | ESP_LOGE(TAG, 50 | "Content-Length (%d bytes) exceeds allowed max (%d bytes)", 51 | content_length, state->max); 52 | err = ESP_ERR_NO_MEM; 53 | esp_http_client_close(event->client); // Abort the HTTP request 54 | } else { 55 | ESP_LOGI(TAG, "Content-Length Header : %d", content_length); 56 | } 57 | } 58 | 59 | // Check for the specific header key 60 | if (strcmp(event->header_key, "Tronbyt-Brightness") == 0) { 61 | state->brightness = (uint8_t)atoi(event->header_value); // API spec: 0-100 62 | ESP_LOGD(TAG, "Tronbyt-Brightness value: %d%%", state->brightness); 63 | } 64 | else if (strcmp(event->header_key, "Tronbyt-Dwell-Secs") == 0) { 65 | state->dwell_secs = (int)atoi(event->header_value); 66 | // ESP_LOGI(TAG, "Tronbyt-Dwell-Secs value: %i", dwell_secs_value); 67 | } 68 | break; 69 | 70 | case HTTP_EVENT_ON_DATA: 71 | 72 | if (event->user_data == NULL) { 73 | ESP_LOGW(TAG, "Discarding HTTP response due to missing state"); 74 | break; 75 | } 76 | 77 | // if (event->data_len > max_data_size) { 78 | // ESP_LOGW(TAG, "Discarding HTTP response due to missing state"); 79 | // break; 80 | // } 81 | 82 | // If needed, resize the buffer to fit the new data 83 | if (event->data_len + state->len > state->size) { 84 | // Determine new size 85 | state->size = 86 | MAX(MIN(state->size * 2, state->max), state->len + event->data_len); 87 | if (state->size > state->max) { 88 | ESP_LOGE(TAG, "Response size exceeds allowed max (%d bytes)", 89 | state->max); 90 | free(state->buf); 91 | err = ESP_ERR_NO_MEM; 92 | break; 93 | } 94 | 95 | // And reallocate 96 | void* new = realloc(state->buf, state->size); 97 | if (new == NULL) { 98 | ESP_LOGE(TAG, "Resizing response buffer failed"); 99 | free(state->buf); 100 | err = ESP_ERR_NO_MEM; 101 | break; 102 | } 103 | state->buf = new; 104 | } 105 | 106 | // Copy over the new data 107 | memcpy(state->buf + state->len, event->data, event->data_len); 108 | state->len += event->data_len; 109 | break; 110 | 111 | case HTTP_EVENT_ON_FINISH: 112 | ESP_LOGD(TAG, "HTTP_EVENT_ON_FINISH"); 113 | break; 114 | 115 | case HTTP_EVENT_DISCONNECTED: 116 | ESP_LOGD(TAG, "HTTP_EVENT_DISCONNECTED"); 117 | 118 | int mbedtlsErr = 0; 119 | esp_err_t err = 120 | esp_tls_get_and_clear_last_error(event->data, &mbedtlsErr, NULL); 121 | if (err != ESP_OK) { 122 | ESP_LOGE(TAG, "HTTP error - %s (mbedtls: 0x%x)", esp_err_to_name(err), 123 | mbedtlsErr); 124 | } 125 | break; 126 | 127 | case HTTP_EVENT_REDIRECT: 128 | ESP_LOGD(TAG, "HTTP_EVENT_REDIRECT"); 129 | esp_http_client_set_redirection(event->client); 130 | break; 131 | } 132 | 133 | return err; 134 | } 135 | 136 | int remote_get(const char* url, uint8_t** buf, size_t* len, uint8_t* brightness_pct, int32_t* dwell_secs) { 137 | // State for processing the response 138 | struct remote_state state = { 139 | .buf = malloc(HTTP_BUFFER_SIZE_DEFAULT), 140 | .len = 0, 141 | .size = HTTP_BUFFER_SIZE_DEFAULT, 142 | .max = HTTP_BUFFER_SIZE_MAX, 143 | .brightness = -1, 144 | .dwell_secs = -1, 145 | }; 146 | 147 | if (state.buf == NULL) { 148 | ESP_LOGE(TAG, "couldn't allocate HTTP receive buffer"); 149 | return 1; 150 | } 151 | 152 | // Set up http client 153 | esp_http_client_config_t config = { 154 | .url = url, 155 | .event_handler = _httpCallback, 156 | .user_data = &state, 157 | .timeout_ms = 10e3, 158 | .crt_bundle_attach = esp_crt_bundle_attach, 159 | }; 160 | 161 | esp_http_client_handle_t http = esp_http_client_init(&config); 162 | 163 | // Do the request 164 | esp_err_t err = esp_http_client_perform(http); 165 | if (err != ESP_OK) { 166 | ESP_LOGE(TAG, "couldn't reach %s: %s", url, esp_err_to_name(err)); 167 | if (state.buf != NULL) { 168 | free(state.buf); 169 | } 170 | esp_http_client_cleanup(http); 171 | return 1; 172 | } 173 | 174 | int status_code = esp_http_client_get_status_code(http); 175 | if (status_code != 200) { 176 | ESP_LOGE(TAG, "Server returned HTTP status %d", status_code); 177 | if (state.buf != NULL) { 178 | free(state.buf); 179 | } 180 | esp_http_client_cleanup(http); 181 | return 1; 182 | } 183 | 184 | // Write back the results. 185 | *buf = state.buf; 186 | *len = state.len; 187 | *brightness_pct = state.brightness; // Assumes API provides 0–100 as spec'd 188 | if (state.dwell_secs > -1 && state.dwell_secs < 300) *dwell_secs = state.dwell_secs; // 5 minute max ? 189 | 190 | esp_http_client_cleanup(http); 191 | ESP_LOGI(TAG,"fetched new webp"); 192 | return 0; 193 | } 194 | -------------------------------------------------------------------------------- /src/remote.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | // Retrieves url via HTTP GET. Caller is responsible for freeing buf 6 | // on success. 7 | int remote_get(const char* url, uint8_t** buf, size_t* len, uint8_t* brightness_pct, int32_t* dwell_secs); 8 | -------------------------------------------------------------------------------- /src/wifi.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | /** 9 | * @brief Initialize WiFi 10 | * 11 | * @param ssid SSID (not used, kept for compatibility) 12 | * @param password Password (not used, kept for compatibility) 13 | * @return 0 on success, non-zero on failure 14 | */ 15 | int wifi_initialize(const char *ssid, const char *password); 16 | 17 | /** 18 | * @brief Shutdown WiFi 19 | */ 20 | void wifi_shutdown(); 21 | 22 | /** 23 | * @brief Get MAC address 24 | * 25 | * @param mac Buffer to store MAC address (6 bytes) 26 | * @return 0 on success, non-zero on failure 27 | */ 28 | int wifi_get_mac(uint8_t mac[6]); 29 | 30 | /** 31 | * @brief Wait for WiFi connection with timeout 32 | * 33 | * @param timeout_ms Timeout in milliseconds 34 | * @return true if connected, false if timeout 35 | */ 36 | bool wifi_wait_for_connection(uint32_t timeout_ms); 37 | 38 | /** 39 | * @brief Get the image URL from WiFi manager 40 | * 41 | * @return Pointer to image URL string, or NULL if not set 42 | */ 43 | const char* wifi_get_image_url(); 44 | 45 | /** 46 | * @brief Check if WiFi is connected to an AP 47 | * 48 | * @return true if connected, false otherwise 49 | */ 50 | bool wifi_is_connected(void); 51 | 52 | /** 53 | * @brief Register a callback to be called when WiFi connects 54 | * 55 | * @param callback Function to call when WiFi connects 56 | */ 57 | void wifi_register_connect_callback(void (*callback)(void)); 58 | 59 | /** 60 | * @brief Register a callback to be called when WiFi disconnects 61 | * 62 | * @param callback Function to call when WiFi disconnects 63 | */ 64 | void wifi_register_disconnect_callback(void (*callback)(void)); 65 | -------------------------------------------------------------------------------- /test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PlatformIO Test Runner and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PlatformIO Unit Testing: 11 | - https://docs.platformio.org/en/latest/advanced/unit-testing/index.html 12 | --------------------------------------------------------------------------------