bin2array to dynamically create C array on build

This commit is contained in:
iranl
2025-07-06 21:02:43 +02:00
parent aaa22640cc
commit 1bde4cca4b
35 changed files with 2890 additions and 932 deletions

View File

@@ -0,0 +1,39 @@
# Base style
BasedOnStyle: LLVM
# Custom configurations
IndentWidth: 4 # Number of spaces per indentation level
TabWidth: 4 # Width of a tab (useful if 'UseTab' is set)
UseTab: Never # Use spaces instead of tabs
# Function formatting
BreakBeforeBraces: Linux
AllowShortFunctionsOnASingleLine: Inline # Allow short functions to stay on a single line
AlwaysBreakBeforeMultilineStrings: false # Keep multiline strings together
# Alignment
AlignConsecutiveAssignments: true # Align consecutive assignments
AlignTrailingComments: true # Align trailing comments
# Parameter formatting
AllowAllParametersOfDeclarationOnNextLine: false # Parameters declared on the same line if possible
# Array and list formatting
BinPackArguments: true # Place multiple arguments on the same line if possible
BinPackParameters: true # Same logic for parameters
ColumnLimit: 120 # Line length limit
# Pointer and reference formatting
PointerAlignment: Right # Align pointer near the variable name or type (values: Left, Right, Middle)
ReferenceAlignment: Right # Same logic for references
# Namespace
IndentNamespace: None # No indentation for namespaces
NamespaceIndentation: None # Alternative namespace indentation option
# Comments
ReflowComments: true # Reformat comments to fit within the line length limit
# Optional advanced configurations
IncludeBlocks: Preserve # Preserve blank lines between includes
SortIncludes: true # Sort includes alphabetically EmptyLineBeforeAccessModifier: Always # Blank line before "public:", "private:", etc.

View File

@@ -0,0 +1,7 @@
---
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"

View File

@@ -0,0 +1,38 @@
name: Build
on:
push:
pull_request:
branches:
- master
jobs:
platformio:
name: PlatformIO
runs-on: ubuntu-latest
strategy:
matrix:
board: [esp32dev]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Cache PlatformIO
uses: actions/cache@v4
with:
key: ${{ runner.os }}-pio
path: |
~/.cache/pip
~/.platformio
- name: Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install PlatformIO
run: |
python -m pip install -U pip
pip install -U platformio
- run: PLATFORMIO_SRC_DIR=examples/demo PIO_BOARD=${{ matrix.board }} pio run

View File

@@ -0,0 +1,19 @@
name: PlatformIO Dependabot
on:
workflow_dispatch: # option to manually trigger the workflow
schedule:
# Runs every day at 00:00
- cron: '0 0 * * *'
jobs:
dependabot:
runs-on: ubuntu-latest
name: run PlatformIO Dependabot
steps:
- name: Checkout
uses: actions/checkout@v4
- name: run PlatformIO Dependabot
uses: peterus/platformio_dependabot@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}

3
resources/AsyncWebSerial/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
/.pio
/.vscode
node_modules

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Andrea Sessa
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,95 @@
# AsyncWebSerial
AsyncWebSerial: Simplify ESP32 debugging and logging with seamless browser-based serial communication.
## Features
- **Browser-Based Serial Access**: Log and debug ESP32 microcontrollers directly from a web browser using the Web Serial API.
- **Real-Time Communication**: Stream logs and data in real time, eliminating the need for traditional serial monitors.
- **Asynchronous Operation**: Leverages asynchronous processing for smooth and efficient communication.
- **Cross-Platform Compatibility**: Works on any browser that supports the Web Serial API, no additional software required.
- **Customizable Integration**: Easily integrates into ESP32 projects, enabling tailored debugging and logging workflows.
- **User-Friendly Interface**: Provides an intuitive way to monitor and interact with the ESP32 during development.
## Dependencies
- [AsyncTCP (mathieucarbou fork)](https://github.com/mathieucarbou/AsyncTCP)
- [ESPAsyncWebServer (mathieucarbou fork)](https://github.com/mathieucarbou/ESPAsyncWebServer)
## Build web interface
Install frontend dependencies:
```bash
yarn && yarn build
```
## Implementation
### Add AsyncWebSerial to your platformIO project platformio.ini
```ini
[env:esp32]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200
lib_deps =
circuitcode/AsyncWebSerial
```
### Include the library in your code
```cpp
#include <AsyncWebSerial.h>
```
### Initialize the library
```cpp
AsyncWebServer server(80);
AsyncWebSerial webSerial;
```
### Use the library
```cpp
void setup() {
webSerial.begin(&server);
server.begin();
}
void loop() {
webSerial.loop();
}
```
### Send data to the AsyncWebSerial
```cpp
webSerial.println("Hello, World!");
webSerial.printf("Hello, %s!", "World");
```
### Receive data from the AsyncWebSerial
You can use the `onMessage` method to receive data from the AsyncWebSerial. The method accepts a callback function that will be called when data is received. The callback function can accept both `const char *` and `String` data types.
```cpp
webSerial.onMessage([](const char *data, size_t len) {
Serial.write(data, len);
});
webSerial.onMessage([](const String &msg) {
Serial.println(msg);
});
```
### Connect to the device serial page
Navigate to `http://<device-ip>/webserial` to access the serial page.
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

View File

@@ -0,0 +1,18 @@
#include <Arduino.h>
#include <AsyncWebSerial.h>
#include <ESPAsyncWebServer.h>
AsyncWebSerial webSerial;
AsyncWebServer server(80);
void setup()
{
webSerial.begin(&server);
server.begin();
}
void loop()
{
webSerial.loop();
delay(10);
}

View File

@@ -0,0 +1,74 @@
const fs = require("fs");
const path = require("path");
const zlib = require("zlib");
const htmlMinifier = require("html-minifier-terser").minify;
// Define paths
const inputPath = path.join(__dirname, "index.html");
const outputPath = path.join(__dirname, "../src/AsyncWebSerialHTML.h");
// Function to split buffer into 64-byte chunks
function splitIntoChunks(buffer, chunkSize) {
let chunks = [];
for (let i = 0; i < buffer.length; i += chunkSize) {
chunks.push(buffer.slice(i, i + chunkSize));
}
return chunks;
}
(async function () {
// read the index.html file
const indexHtml = fs.readFileSync(inputPath, "utf8").toString();
// Minify the HTML content
const minifiedHtml = await htmlMinifier(indexHtml, {
collapseWhitespace: true,
removeComments: true,
removeAttributeQuotes: true,
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
useShortDoctype: true,
minifyCSS: true,
minifyJS: true,
shortAttributes: true,
shortClassName: true,
});
let oldSize = (indexHtml.length / 1024).toFixed(2);
let newSize = (minifiedHtml.length / 1024).toFixed(2);
console.log(`[Minifier] Original: ${oldSize}KB | Minified: ${newSize}KB`);
// Gzip the minified HTML content
let gzippedHtml = zlib.gzipSync(minifiedHtml);
// Recreate the AsyncWebSerialHTML.h file with the new gzipped content
// the content is stored as a byte array split into 64 byte chunks to avoid issues with the IDE
let content = `#ifndef AsyncWebSerial_HTML_H
#define AsyncWebSerial_HTML_H
#include <Arduino.h>
const uint8_t ASYNCWEBSERIAL_HTML[] PROGMEM = {\n`;
// Split gzipped HTML into 64-byte chunks
let chunks = splitIntoChunks(gzippedHtml, 64);
chunks.forEach((chunk, index) => {
content += ` ${Array.from(chunk)
.map((byte) => `0x${byte.toString(16).padStart(2, "0")}`)
.join(", ")}`;
if (index < chunks.length - 1) {
content += ",\n";
}
});
content += `\n};
#endif // AsyncWebSerial_HTML_H`;
// Write the content to the output file
fs.writeFileSync(outputPath, content);
console.log("AsyncWebSerialHTML.h file created successfully!");
})();

View File

@@ -0,0 +1,365 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>NukiHub WebSerial</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style type="text/css">
div {
display: block;
}
a {
margin: 0.4rem;
text-decoration: none;
}
*,
::after,
::before {
box-sizing: border-box;
border-width: 0;
}
html {
height: 100%;
}
body {
overscroll-behavior: none;
height: 100%;
width: 100%;
margin: 0;
padding: 0;
font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI",
Roboto, "Helvetica Neue", sans-serif;
background: #000;
height: 100%;
}
.app {
height: 100%;
display: flex;
flex-direction: column;
}
.grid {
display: grid;
}
.gap-2 {
gap: 0.5rem;
}
.content {
flex: 1 1 0%;
align-content: flex-end;
height: 100%;
color: #d4d4d8;
font-family: monospace;
overflow-x: hidden;
overflow-y: auto;
font-size: 14px;
}
.content p {
margin: 0;
overflow-wrap: break-word;
text-wrap: wrap;
white-space: pre-line;
}
.panel {
position: relative;
border: #fff 0.5rem solid;
border-radius: 1rem;
max-width: 45rem;
width: calc(100% - 1rem);
font-size: medium;
}
.buttons {
display: flex;
align-items: center;
justify-content: flex-end;
margin-left: auto;
margin-right: auto;
border-bottom-width: 1px;
flex-direction: row;
column-gap: 1.5rem;
padding-top: 0.5rem;
padding-bottom: 0.5rem;
padding-right: 0.5rem;
color: #a1a1aa;
}
.buttons button {
cursor: pointer;
padding: 8px 10px 8px;
font-size: medium;
outline-style: none;
border: 0px;
color: #a1a1aa;
background-color: transparent;
}
.buttons button svg {
width: 1.4rem;
height: 1.4rem;
}
.buttons button:hover {
background-color: #18181b;
}
.w-full {
width: 100%;
}
.rounded {
border-radius: 0.5rem;
}
.flex {
display: flex;
}
.grow {
flex-grow: 1;
}
.shadow {
filter: drop-shadow(0 4px 3px rgb(0 0 0 / 0.07)) drop-shadow(0 2px 2px rgb(0 0 0 / 0.06));
}
.items-center {
align-items: center;
}
.command_container {
border-top-width: 1px;
border-bottom: 1px;
border-color: #18181b;
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem;
}
input {
margin: 0;
padding: .375rem .75rem;
border: 0 solid #6b7280;
border-radius: .25rem;
font-family: monospace;
font-size: .875rem;
line-height: 1.5rem;
background-color: #ffffff0d;
color: #fff;
}
.command {
width: 100%;
}
#submit-button {
border: 0 solid #e5e7eb;
margin: 0;
text-transform: none;
background-image: none;
cursor: pointer;
display: flex;
width: 100%;
align-items: center;
justify-content: center;
border-radius: .25rem;
padding: .5rem 1.5rem;
font-size: .875rem;
line-height: 1.25rem;
color: #fff;
background-color: rgb(29 78 216);
}
#submit-button svg {
width: 1rem;
height: 1rem;
}
.ml-4 {
margin-left: 1rem;
}
.connection-status {
display: flex;
justify-content: flex-end;
border-bottom-width: 1px;
padding: 0.5rem 1rem;
font-size: .75rem;
line-height: 1rem;
color: rgb(113 113 122);
align-items: center;
border-color: rgb(24 24 27);
gap: 0.5rem;
}
.badge {
width: 0.375rem;
height: 0.375rem;
border-radius: 9999px;
}
.badge.green {
background-color: #22c55e;
}
.badge.orange {
background-color: #f59e0b;
}
.badge.red {
background-color: #ef4444;
}
</style>
</head>
<body>
<div class="app">
<header>
<div class="buttons">
<div>
Buffer size:
<input type="text" id="buffer" class="rounded shadow" placeholder="Buffer size" value="1000">
</div>
<button class="rounded shadow" onclick="terminalClean()">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M4 7l16 0" />
<path d="M10 11l0 6" />
<path d="M14 11l0 6" />
<path d="M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2 -2l1 -12" />
<path d="M9 7v-3a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v3" />
</svg>
</button>
<button class="rounded shadow" onclick="enableScroll=!enableScroll">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M5 13a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v6a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-6z" />
<path d="M11 16a1 1 0 1 0 2 0a1 1 0 0 0 -2 0" />
<path d="M8 11v-4a4 4 0 1 1 8 0v4" />
</svg>
</button>
<button class="rounded shadow" onclick="enableTimestamp=!enableTimestamp">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M3 12a9 9 0 1 0 18 0a9 9 0 0 0 -18 0" />
<path d="M12 7v5l3 3" />
</svg>
</button>
</div>
</header>
<div class="content"></div>
<footer>
<div class="command_container">
<form class="flex w-full items-center">
<input id="command" autocomplete="off" type="text" required="" class="command"
placeholder="Enter command here">
<div class="ml-4">
<button id="submit-button" type="submit">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M10 14l11 -11" />
<path d="M21 3l-6.5 18a.55 .55 0 0 1 -1 0l-3.5 -7l-7 -3.5a.55 .55 0 0 1 0 -1l18 -6.5" />
</svg>
</button>
</div>
</form>
</div>
<div class="connection-status"></div>
</footer>
</div>
</body>
<script type="text/javascript">
let enableScroll = true;
let enableTimestamp = true;
let url = `ws://${window.location.hostname}/ws`;
let websocket;
let contentArea = document.querySelector('.content');
let connectionStatus = document.querySelector('.connection-status');
initApp();
function initApp() {
connectionStatus.innerHTML = '';
initWebSocket();
document.querySelector('form').addEventListener('submit', function (e) {
e.preventDefault();
let command = document.getElementById('command').value;
websocket.send(command);
document.getElementById('command').value = '';
});
}
function initWebSocket() {
connectionStatus.innerHTML = '<div class="badge orange"></div> Connecting...';
websocket = new WebSocket(url);
websocket.onopen = onOpen;
websocket.onclose = onClose;
websocket.onmessage = onMessage;
}
function onOpen(event) {
connectionStatus.innerHTML = '<div class="badge green"></div> Connected';
terminalWrite('Connected to ' + url);
}
function onClose(event) {
connectionStatus.innerHTML = '<div class="badge red"></div> Disconnected';
setTimeout(initWebSocket, 2000);
}
function onMessage(event) {
terminalWrite(event.data);
}
function terminalWrite(data) {
if (enableTimestamp) {
let now = new Date();
data = "[" + now.toLocaleTimeString() + "] " + data;
}
contentArea.innerHTML += '<p>' + data + '</p>';
if (enableScroll) {
contentArea.scrollTop = contentArea.scrollHeight;
}
// Limit buffer size to avoid memory issues in the browser
let bufferSize = parseInt(document.getElementById('buffer').value);
if (isNaN(bufferSize)) {
bufferSize = 1000;
}
let lines = contentArea.querySelectorAll('p');
if (lines.length > bufferSize) {
for (let i = 0; i < lines.length - bufferSize; i++) {
contentArea.removeChild(lines[i]);
}
}
}
function terminalClean() {
contentArea.innerHTML = '';
}
</script>
</html>

View File

@@ -0,0 +1,12 @@
{
"scripts": {
"build": "node build.js"
},
"dependencies": {
"html-minifier-terser": "^7.1.0"
},
"license": "MIT",
"files": [
"LICENSE"
]
}

View File

@@ -0,0 +1,168 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@jridgewell/gen-mapping@^0.3.5":
version "0.3.5"
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36"
integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==
dependencies:
"@jridgewell/set-array" "^1.2.1"
"@jridgewell/sourcemap-codec" "^1.4.10"
"@jridgewell/trace-mapping" "^0.3.24"
"@jridgewell/resolve-uri@^3.1.0":
version "3.1.2"
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6"
integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==
"@jridgewell/set-array@^1.2.1":
version "1.2.1"
resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280"
integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==
"@jridgewell/source-map@^0.3.3":
version "0.3.6"
resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.6.tgz#9d71ca886e32502eb9362c9a74a46787c36df81a"
integrity sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==
dependencies:
"@jridgewell/gen-mapping" "^0.3.5"
"@jridgewell/trace-mapping" "^0.3.25"
"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a"
integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==
"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25":
version "0.3.25"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0"
integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==
dependencies:
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
acorn@^8.8.2:
version "8.14.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0"
integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==
buffer-from@^1.0.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
camel-case@^4.1.2:
version "4.1.2"
resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.2.tgz#9728072a954f805228225a6deea6b38461e1bd5a"
integrity sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==
dependencies:
pascal-case "^3.1.2"
tslib "^2.0.3"
clean-css@~5.3.2:
version "5.3.3"
resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.3.3.tgz#b330653cd3bd6b75009cc25c714cae7b93351ccd"
integrity sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==
dependencies:
source-map "~0.6.0"
commander@^10.0.0:
version "10.0.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06"
integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==
commander@^2.20.0:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
dot-case@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751"
integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==
dependencies:
no-case "^3.0.4"
tslib "^2.0.3"
entities@^4.4.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48"
integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==
html-minifier-terser@^7.1.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz#18752e23a2f0ed4b0f550f217bb41693e975b942"
integrity sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==
dependencies:
camel-case "^4.1.2"
clean-css "~5.3.2"
commander "^10.0.0"
entities "^4.4.0"
param-case "^3.0.4"
relateurl "^0.2.7"
terser "^5.15.1"
lower-case@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28"
integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==
dependencies:
tslib "^2.0.3"
no-case@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d"
integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==
dependencies:
lower-case "^2.0.2"
tslib "^2.0.3"
param-case@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5"
integrity sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==
dependencies:
dot-case "^3.0.4"
tslib "^2.0.3"
pascal-case@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.2.tgz#b48e0ef2b98e205e7c1dae747d0b1508237660eb"
integrity sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==
dependencies:
no-case "^3.0.4"
tslib "^2.0.3"
relateurl@^0.2.7:
version "0.2.7"
resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9"
integrity sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==
source-map-support@~0.5.20:
version "0.5.21"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f"
integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==
dependencies:
buffer-from "^1.0.0"
source-map "^0.6.0"
source-map@^0.6.0, source-map@~0.6.0:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
terser@^5.15.1:
version "5.36.0"
resolved "https://registry.yarnpkg.com/terser/-/terser-5.36.0.tgz#8b0dbed459ac40ff7b4c9fd5a3a2029de105180e"
integrity sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w==
dependencies:
"@jridgewell/source-map" "^0.3.3"
acorn "^8.8.2"
commander "^2.20.0"
source-map-support "~0.5.20"
tslib@^2.0.3:
version "2.8.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==

View File

@@ -0,0 +1,33 @@
{
"name": "AsyncWebSerial",
"version": "1.0.1",
"keywords": "web serial, ESP32",
"description": "AsyncWebSerial: Simplify ESP32 debugging and logging with seamless browser-based serial communication.",
"homepage": "https://github.com/circuitcode/AsyncWebSerial",
"repository": {
"type": "git",
"url": "https://github.com/circuitcode/AsyncWebSerial.git"
},
"authors": [
{
"name": "Andrea Sessa",
"maintanier": true
}
],
"license": "MIT",
"framworks": "arduino",
"platforms": "espressif32",
"headers": [
"AsyncWebSerial.h"
],
"export": {
"include": [
"examples",
"src",
"library.json",
"library.properties",
"LICENSE",
"README.md"
]
}
}

View File

@@ -0,0 +1,10 @@
name=AsyncWebSerial
version=1.0.1
author=Andrea Sessa <andrea.sessa@gmail.com>
maintainer=Andrea Sessa <andrea.sessa@gmail.com>
category=Device Control
sentence=Browser-based logging and debugging for ESP32 using the Web Serial API.
paragraph=AsyncWebSerial is a lightweight library designed for ESP32 microcontrollers, enabling developers to log and debug their projects directly from a web browser. Its asynchronous design ensures smooth communication, making it an ideal tool for modern ESP32 development workflows.
url=https://github.com/circuitcode/AsyncWebSerial
architectures=esp32
license=MIT

View File

@@ -0,0 +1,13 @@
[platformio]
lib_dir = .
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
lib_deps =
mathieucarbou/AsyncTCP@^3.2.14
mathieucarbou/ESPAsyncWebServer@^3.3.23
build_flags =
-D CONFIG_ASYNC_TCP_QUEUE_SIZE=512
-D CONFIG_ASYNC_TCP_RUNNING_CORE=1

View File

@@ -0,0 +1,119 @@
/*
* AsyncWebSerial.cpp
*
* This file implements the AsyncWebSerial class, which provides a web-based serial interface
* using an asynchronous web server and WebSocket communication. It allows for sending
* and receiving serial data over a web interface, with optional authentication.
*
* Usage:
* - Call begin() to initialize the AsyncWebSerial with a server and URL.
* - Use onMessage() to set a callback for received messages.
* - Use setAuthentication() to enable authentication for the web interface.
* - Use the Print interface to send data to the web interface.
* - Call loop() periodically to clean up inactive clients.
*/
#include "AsyncWebSerial.h"
#include "AsyncWebSerialHTML.h"
void AsyncWebSerial::begin(AsyncWebServer *server, const char *url)
{
_server = server;
_socket = new AsyncWebSocket("/ws_serial");
if (_isAuthenticationRequired)
_socket->setAuthentication(_username.c_str(), _password.c_str());
// handle web page request
_server->on(url, HTTP_GET, [&](AsyncWebServerRequest *request) {
if (_isAuthenticationRequired && !request->authenticate(_username.c_str(), _password.c_str()))
return request->requestAuthentication();
AsyncWebServerResponse *response =
request->beginResponse(200, "text/html", ASYNCWEBSERIAL_HTML, sizeof(ASYNCWEBSERIAL_HTML));
response->addHeader("Content-Encoding", "gzip");
request->send(response);
});
// handle websocket connection
_socket->onEvent([&](AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg,
uint8_t *data, size_t len) {
switch (type) {
case WS_EVT_CONNECT:
// set the client to not close when the queue is full
client->setCloseClientOnQueueFull(false);
break;
case WS_EVT_DATA:
if (len > 0) {
// invoke the message received callback
if (_onMessageReceived != nullptr)
_onMessageReceived(data, len);
}
break;
}
});
_server->addHandler(_socket);
}
// onMessage callback setter
void AsyncWebSerial::onMessage(WSMessageCallback callback)
{
_onMessageReceived = callback;
}
// onMessage callback setter
void AsyncWebSerial::onMessage(WSStringMessageCallback callback)
{
// keep a reference to the callback function
_stringMessageCallback = callback;
// set the internal callback to convert the uint8_t data to a string and
// call the string callback
_onMessageReceived = [&](uint8_t *data, size_t len) {
if (data && len) {
String msg;
msg.reserve(len);
msg = String((char *)data, len);
_stringMessageCallback(msg);
}
};
}
void AsyncWebSerial::setAuthentication(const String &username, const String &password)
{
_username = username;
_password = password;
_isAuthenticationRequired = !_username.isEmpty() && !_password.isEmpty();
// if the socket is already created, set the authentication immediately
if (_socket != nullptr)
_socket->setAuthentication(_username.c_str(), _password.c_str());
}
void AsyncWebSerial::loop()
{
if (_socket)
_socket->cleanupClients();
}
// Print interface implementation
size_t AsyncWebSerial::write(uint8_t m)
{
if (!_socket)
return 0;
return write(&m, 1);
return 1;
}
// Print interface implementation
size_t AsyncWebSerial::write(const uint8_t *buffer, size_t size)
{
if (!_socket || size == 0)
return 0;
String message((const char *)buffer, size);
_socket->textAll(message);
return size;
}

View File

@@ -0,0 +1,48 @@
/**
* @file AsyncWebSerial.h
* @brief A web serial interface for the ESP32 microcontroller.
* @license MIT License (https://opensource.org/licenses/MIT)
*/
#ifndef AsyncWebSerial_h
#define AsyncWebSerial_h
#include <Arduino.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <WiFi.h>
#include <stdlib_noniso.h>
#include <functional>
// define the callable object type for the message received callback
typedef std::function<void(uint8_t *data, size_t len)> WSMessageCallback;
typedef std::function<void(const String &msg)> WSStringMessageCallback;
class AsyncWebSerial : public Print
{
public:
void begin(AsyncWebServer *server, const char *url = "/webserial");
void setAuthentication(const String &username, const String &password);
void onMessage(WSMessageCallback callback);
void onMessage(WSStringMessageCallback callback);
void loop();
// Print interface implementation
virtual size_t write(uint8_t) override;
virtual size_t write(const uint8_t *buffer, size_t size) override;
private:
AsyncWebServer *_server;
AsyncWebSocket *_socket;
bool _isAuthenticationRequired = false;
String _username;
String _password;
WSMessageCallback _onMessageReceived = nullptr;
WSStringMessageCallback _stringMessageCallback = nullptr;
};
#endif // AsyncWebSerial_h

View File

@@ -0,0 +1,46 @@
#ifndef AsyncWebSerial_HTML_H
#define AsyncWebSerial_HTML_H
#include <Arduino.h>
const uint8_t ASYNCWEBSERIAL_HTML[] PROGMEM = {
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xdd, 0x59, 0x61, 0x8f, 0xdb, 0x36, 0x12, 0xfd, 0x2b, 0xac, 0xf6, 0x9a, 0x48, 0xb7, 0xa6, 0x2c, 0xc9, 0xf6, 0xae, 0x57, 0xb2, 0x0c, 0x24, 0x9b, 0x1c, 0x12, 0x20, 0xc9, 0x01, 0xb7, 0x29, 0x7a, 0x40, 0x51, 0xb4, 0x94, 0x34, 0xb2, 0xd9, 0xa5, 0x48, 0x95, 0xa2, 0x6c, 0x6f, 0x04, 0xff, 0xf7, 0x03, 0x29, 0xd9, 0x96, 0xec, 0x4d,
0xdb, 0xe0, 0xbe, 0x35, 0x89, 0x2d, 0x71, 0x38, 0xe4, 0x90, 0x6f, 0xde, 0x0c, 0xc7, 0xcc, 0xe2, 0xbb, 0x4c, 0xa4, 0xea, 0xa9, 0x04, 0xb4, 0x56, 0x05, 0x5b, 0x2e, 0xba, 0x6f, 0x20, 0xd9, 0x72, 0x51, 0x80, 0x22, 0x28, 0x5d, 0x13, 0x59, 0x81, 0x8a, 0x6b, 0x95, 0xe3, 0x79, 0x27, 0x5b, 0x2b, 0x55, 0x62, 0xf8, 0xbd, 0xa6, 0x9b, 0xf8, 0xbf, 0xf8, 0x87, 0x57, 0xf8, 0x5e, 0x14, 0x25, 0x51,
0x34, 0x61, 0x80, 0x52, 0xc1, 0x15, 0x70, 0x15, 0x5b, 0xef, 0xdf, 0xc6, 0x90, 0xad, 0xc0, 0x5a, 0x2e, 0x14, 0x55, 0x0c, 0x96, 0xaf, 0xaa, 0x27, 0x9e, 0xfe, 0x08, 0xc9, 0x03, 0x48, 0x4a, 0xd8, 0x62, 0xdc, 0x4a, 0xdb, 0xf9, 0x38, 0x29, 0x20, 0xde, 0x50, 0xd8, 0x96, 0x42, 0xaa, 0xd3, 0x14, 0x5b, 0x9a, 0xa9, 0x75, 0x9c, 0xc1, 0x86, 0xa6, 0x80, 0x4d, 0x63, 0x44, 0x39, 0x55, 0x94, 0x30,
0x5c, 0xa5, 0x84, 0x41, 0xec, 0x5b, 0xcb, 0x45, 0xa5, 0x9e, 0x18, 0x2c, 0x33, 0xba, 0x69, 0x32, 0x5a, 0x95, 0x8c, 0x3c, 0x85, 0x09, 0x13, 0xe9, 0xe3, 0x9e, 0x34, 0x05, 0x91, 0x2b, 0xca, 0x43, 0x77, 0x2a, 0xa1, 0x88, 0x14, 0xec, 0x14, 0xce, 0x20, 0x15, 0x92, 0x28, 0x2a, 0x78, 0xc8, 0x05, 0x87, 0xfd, 0x3f, 0x47, 0x61, 0x48, 0x72, 0x05, 0x72, 0x14, 0x86, 0x09, 0xe4, 0x42, 0x42, 0x93,
0x88, 0x1d, 0xae, 0xe8, 0x17, 0xca, 0x57, 0x61, 0x22, 0x64, 0x06, 0x12, 0x27, 0x62, 0x17, 0x75, 0xaf, 0x66, 0x0d, 0xa1, 0xb7, 0xd7, 0x20, 0x35, 0x6b, 0xa0, 0xab, 0xb5, 0x0a, 0x7d, 0xcf, 0xfb, 0x7e, 0x9f, 0x88, 0xec, 0xa9, 0x11, 0x1b, 0x90, 0x55, 0x2a, 0x05, 0x63, 0x38, 0x81, 0x35, 0xd9, 0x50, 0x21, 0x8d, 0x99, 0xa8, 0xa7, 0x19, 0xb5, 0x53, 0x98, 0xd7, 0x6e, 0x7d, 0x5e, 0x54, 0x92,
0x2c, 0xd3, 0x06, 0xbd, 0x28, 0x17, 0x5c, 0xe1, 0x9c, 0x14, 0x94, 0x3d, 0x85, 0x98, 0x94, 0x25, 0x03, 0x5c, 0x3d, 0x55, 0x0a, 0x8a, 0x51, 0xfb, 0xc0, 0x35, 0x1d, 0xbd, 0x66, 0x94, 0x3f, 0x7e, 0x24, 0xe9, 0x83, 0x91, 0xfc, 0x4b, 0x70, 0x35, 0xb2, 0x1e, 0x60, 0x25, 0x00, 0xfd, 0xf0, 0xde, 0x1a, 0xfd, 0x47, 0x24, 0x42, 0x89, 0x91, 0xf5, 0x0e, 0xd8, 0x06, 0x14, 0x4d, 0x09, 0xfa, 0x04,
0x35, 0x58, 0xa3, 0x8a, 0xf0, 0x0a, 0x57, 0x20, 0x69, 0x1e, 0x25, 0x24, 0x7d, 0x5c, 0x49, 0x51, 0xf3, 0x2c, 0xbc, 0xf2, 0x3c, 0xaf, 0xbf, 0xbc, 0xbd, 0x4b, 0xca, 0xb2, 0xbf, 0xb3, 0xe8, 0x00, 0x6a, 0xce, 0x60, 0x17, 0xe9, 0x2f, 0x9c, 0x51, 0x09, 0xa9, 0xc1, 0x30, 0x15, 0xac, 0x2e, 0xf8, 0xde, 0x5d, 0x49, 0x9a, 0x1d, 0xd1, 0xd7, 0x8d, 0xbd, 0xbb, 0x22, 0x25, 0x0e, 0x9a, 0x15, 0x29,
0x43, 0x77, 0x26, 0xa1, 0xd8, 0xbb, 0x9d, 0x53, 0x1b, 0x3d, 0x45, 0xe8, 0x23, 0x1f, 0x79, 0xdf, 0x47, 0x84, 0xd1, 0x15, 0xc7, 0x5d, 0x8f, 0xb1, 0x80, 0x81, 0x67, 0x03, 0xb8, 0x52, 0xc1, 0x84, 0x0c, 0xaf, 0xb2, 0x69, 0x36, 0xcd, 0xe6, 0x03, 0x78, 0x0a, 0xc1, 0x45, 0x55, 0x92, 0x14, 0x22, 0x8d, 0x7b, 0xce, 0xc4, 0x16, 0xef, 0xc2, 0x35, 0xcd, 0x32, 0xe0, 0x27, 0xc9, 0x53, 0x48, 0x6a,
0x25, 0xda, 0x71, 0x15, 0xfd, 0x02, 0xa1, 0x3f, 0x2d, 0x77, 0xc7, 0xc5, 0xa0, 0xb2, 0x39, 0xfa, 0xe0, 0x38, 0x64, 0x2b, 0x49, 0x19, 0x26, 0x12, 0xc8, 0x23, 0xde, 0x0a, 0x99, 0xb5, 0xc4, 0x31, 0x42, 0xfd, 0x15, 0x6d, 0xd7, 0x54, 0x01, 0x36, 0x86, 0xc3, 0x52, 0x02, 0x66, 0x94, 0xc3, 0xde, 0x2d, 0x09, 0x07, 0xd6, 0x94, 0xa2, 0xa2, 0x06, 0x18, 0x09, 0x8c, 0x28, 0xba, 0x81, 0x8e, 0x37,
0xe1, 0x55, 0x9e, 0xe7, 0xc8, 0x00, 0x81, 0x2a, 0xc1, 0x68, 0x76, 0xe0, 0x93, 0x24, 0x19, 0xad, 0xab, 0xd0, 0xd7, 0x04, 0x2d, 0xc8, 0xae, 0xe3, 0xd7, 0x54, 0x2b, 0x76, 0x44, 0x49, 0x09, 0x4b, 0x6d, 0x8d, 0x04, 0xc2, 0x48, 0xab, 0x39, 0xbd, 0xbd, 0x14, 0x90, 0xd1, 0xba, 0xd8, 0xbb, 0x49, 0xad, 0x94, 0xe0, 0x55, 0x33, 0x70, 0x55, 0x8b, 0x2d, 0x55, 0x50, 0x54, 0x61, 0x0a, 0x5c, 0x81,
0x8c, 0x7e, 0xab, 0x2b, 0x45, 0xf3, 0xa7, 0x4b, 0xc0, 0x5b, 0x10, 0x30, 0x83, 0x5c, 0xb5, 0x78, 0x75, 0x02, 0x69, 0xfc, 0x60, 0x24, 0xc7, 0x58, 0x50, 0x4a, 0x14, 0xdd, 0x3a, 0xfd, 0xf2, 0x82, 0x11, 0x52, 0x6c, 0xa3, 0x96, 0x15, 0x58, 0xfb, 0xde, 0x37, 0x7b, 0x3e, 0xf0, 0x1b, 0x2b, 0xd1, 0xd1, 0xe1, 0x28, 0x69, 0xe7, 0x3b, 0x13, 0xb6, 0x66, 0x5b, 0x59, 0xe7, 0x7f, 0xe2, 0x13, 0x9f,
0x90, 0xe3, 0x56, 0x51, 0xfb, 0x6c, 0xd2, 0x5a, 0x56, 0x42, 0x86, 0xa5, 0xa0, 0x66, 0x83, 0x87, 0x38, 0x9a, 0x97, 0x3b, 0xe4, 0x7b, 0xe5, 0x0e, 0xcd, 0xf5, 0x0a, 0xcf, 0xf0, 0x8a, 0x44, 0xad, 0xb4, 0xd3, 0xb0, 0x49, 0x1c, 0x6d, 0x80, 0x76, 0x6e, 0xf2, 0x86, 0xe6, 0x7a, 0x81, 0x82, 0xdb, 0x0e, 0x25, 0x09, 0xaf, 0x4a, 0x22, 0x81, 0xab, 0xf3, 0xb5, 0xa0, 0x6a, 0xb3, 0x6a, 0x3a, 0x5c,
0xda, 0x8c, 0x73, 0xa0, 0xb1, 0x69, 0x9d, 0xab, 0x87, 0x6b, 0x4d, 0xb8, 0xe6, 0xc2, 0xc2, 0x95, 0x3f, 0xf7, 0xe7, 0x7e, 0xb2, 0x77, 0xb7, 0x38, 0xaf, 0x19, 0x6b, 0x4e, 0xe9, 0x62, 0xef, 0x1a, 0x3d, 0xc8, 0x9a, 0x21, 0x79, 0xba, 0xf8, 0xd2, 0x8e, 0x18, 0x30, 0x40, 0x87, 0xa5, 0xd8, 0x9a, 0x78, 0xc3, 0xfa, 0x2d, 0xf4, 0xf7, 0x6e, 0xb5, 0x26, 0x99, 0x96, 0x51, 0xa6, 0x40, 0x86, 0x99,
0x14, 0x25, 0x6e, 0x45, 0xb6, 0x87, 0xa6, 0xe5, 0x0e, 0x4d, 0xca, 0x1d, 0x92, 0xab, 0xc4, 0xf6, 0x90, 0xfe, 0x3b, 0x46, 0xae, 0x77, 0xeb, 0x38, 0x68, 0xa8, 0x17, 0x94, 0x3b, 0xf3, 0x19, 0xea, 0xdd, 0x38, 0xce, 0xde, 0x35, 0x6c, 0xc3, 0x2d, 0xdb, 0x9a, 0x4b, 0x02, 0xea, 0xb8, 0x2b, 0x0a, 0xc2, 0xb3, 0x5f, 0x34, 0x03, 0x09, 0xe5, 0x1a, 0x80, 0x76, 0x2f, 0x4a, 0x94, 0x3d, 0x52, 0x0d,
0xc8, 0xd6, 0x97, 0x0c, 0x30, 0x8a, 0xbe, 0x95, 0xef, 0x26, 0x68, 0x71, 0x02, 0x6a, 0x0b, 0xc0, 0x8f, 0x64, 0xd1, 0x51, 0xb5, 0xa7, 0xbc, 0xac, 0x55, 0x73, 0x91, 0x90, 0xdd, 0xc9, 0xad, 0x09, 0x5a, 0xd7, 0x3c, 0x8e, 0x34, 0x69, 0x83, 0x18, 0x5d, 0xdd, 0x24, 0xb7, 0xc1, 0xdc, 0x3b, 0x0b, 0x66, 0x37, 0x30, 0xba, 0xcf, 0xa7, 0xaa, 0x13, 0x19, 0xdd, 0x79, 0x3b, 0xa7, 0xa1, 0xe2, 0x91,
0x2a, 0xad, 0x99, 0x0b, 0x52, 0xe4, 0xe6, 0x8f, 0x97, 0x45, 0xa7, 0xf6, 0x11, 0xcc, 0x3e, 0x45, 0xae, 0xaa, 0x3a, 0x29, 0xa8, 0xc2, 0x5d, 0x78, 0x9c, 0xaf, 0x17, 0x66, 0x70, 0x0b, 0xc9, 0xe9, 0xdc, 0x31, 0xa9, 0xcd, 0x70, 0x3a, 0x17, 0xb2, 0xe8, 0x42, 0xe1, 0x64, 0x9c, 0x16, 0x64, 0xd5, 0x05, 0xc8, 0x59, 0xa0, 0x0d, 0x90, 0xef, 0x9d, 0x68, 0x7f, 0xc1, 0x09, 0x9d, 0xf8, 0x59, 0xcc,
0x8e, 0xb0, 0x1b, 0xd0, 0x3b, 0x34, 0xfe, 0x0c, 0xb2, 0xa0, 0x9f, 0x27, 0xf2, 0x3c, 0xbf, 0x84, 0x4f, 0x33, 0x35, 0xb8, 0x43, 0xb7, 0x73, 0x14, 0xf8, 0x37, 0xce, 0x19, 0x48, 0xfd, 0xb8, 0xed, 0x47, 0xad, 0x09, 0xaa, 0x82, 0xe1, 0x69, 0xd3, 0xcf, 0x8e, 0xfe, 0xe1, 0x2c, 0xe3, 0x6d, 0xc2, 0xc3, 0x95, 0x22, 0xaa, 0x3e, 0x4b, 0xbd, 0x5f, 0xcd, 0xb3, 0x5f, 0x4b, 0xa2, 0x67, 0xfb, 0x3e,
0xdb, 0xf5, 0x33, 0x9b, 0x3e, 0xed, 0x58, 0xef, 0xcd, 0xf7, 0x27, 0xc8, 0x7c, 0x82, 0xc0, 0x79, 0xce, 0x05, 0x83, 0xe0, 0x31, 0x60, 0x4c, 0x91, 0xfe, 0x77, 0xeb, 0x44, 0xbd, 0x03, 0x3a, 0x21, 0xd9, 0x0a, 0x3a, 0x24, 0x3a, 0xde, 0x1f, 0xc0, 0x38, 0x34, 0x87, 0x4e, 0xbb, 0xbb, 0xbb, 0xbb, 0xd3, 0x87, 0xa9, 0x19, 0xe8, 0xae, 0x24, 0x00, 0x7f, 0x26, 0x9f, 0x05, 0x41, 0x3a, 0x9b, 0xc1,
0x41, 0x4b, 0x48, 0xc2, 0x57, 0xf0, 0x8c, 0x5a, 0x3e, 0xbb, 0x03, 0x2f, 0x39, 0xa8, 0x49, 0x9d, 0xe5, 0x2e, 0x74, 0x20, 0x9f, 0x4e, 0xa7, 0xd3, 0xfd, 0x62, 0xdc, 0x96, 0x7c, 0x8b, 0x71, 0x5b, 0xa5, 0xea, 0xda, 0x6b, 0xb9, 0xc8, 0xe8, 0x06, 0xa5, 0x8c, 0x54, 0x55, 0x4c, 0xca, 0xb2, 0x2d, 0x60, 0x41, 0xf6, 0xc5, 0x5d, 0xfe, 0x35, 0xa2, 0xe5, 0xeb, 0x3a, 0xcf, 0x41, 0x22, 0x03, 0x30,
0x5a, 0x98, 0xe8, 0x47, 0x34, 0x8b, 0x93, 0x56, 0xdc, 0x0e, 0xb0, 0xba, 0x7c, 0x8b, 0xda, 0xd4, 0x67, 0xa1, 0x92, 0x91, 0x14, 0xd6, 0x82, 0x65, 0x20, 0x63, 0xab, 0x37, 0x83, 0x85, 0x36, 0x84, 0xd5, 0x10, 0xfb, 0x9e, 0xe7, 0x2d, 0x17, 0x63, 0x3d, 0xff, 0xa2, 0x23, 0xd7, 0x57, 0x66, 0x12, 0x3c, 0x65, 0x34, 0x7d, 0x8c, 0x15, 0xc8, 0x82, 0x72, 0xc2, 0xee, 0x19, 0x10, 0x6e, 0x3b, 0xcb,
0x45, 0xb5, 0x59, 0xa1, 0x5d, 0xc1, 0x78, 0x15, 0xeb, 0x12, 0x3b, 0x1c, 0x8f, 0xb7, 0xdb, 0xad, 0xbb, 0x9d, 0xb8, 0x42, 0xae, 0xc6, 0x81, 0xe7, 0x79, 0x63, 0xad, 0xd0, 0x96, 0xc5, 0xc1, 0x14, 0xb5, 0xee, 0xd1, 0x6f, 0xba, 0x76, 0x7e, 0x2d, 0x76, 0xb1, 0xa5, 0xf3, 0xb1, 0x71, 0xaf, 0x85, 0x72, 0xca, 0x58, 0xac, 0xa3, 0x17, 0x55, 0x4a, 0x8a, 0x47, 0x88, 0xd3, 0x5a, 0xea, 0x93, 0xeb,
0x5e, 0xa3, 0xd9, 0xc9, 0x70, 0x37, 0xd7, 0xa1, 0xa9, 0x59, 0x96, 0x92, 0x32, 0x36, 0x0b, 0xee, 0x0b, 0x7f, 0x13, 0x94, 0xb7, 0xd2, 0xe5, 0xa2, 0x24, 0x6a, 0x7d, 0x98, 0xd3, 0xcc, 0x9f, 0xc5, 0xd6, 0x47, 0x0f, 0x79, 0xeb, 0x60, 0xba, 0x09, 0xa6, 0xef, 0xbc, 0x2f, 0x7d, 0xdb, 0xe3, 0x4e, 0x5f, 0xeb, 0x4c, 0xd1, 0x2d, 0xf3, 0x6f, 0x90, 0x67, 0xf5, 0x85, 0xbe, 0x87, 0x7c, 0x9f, 0x79,
0xe8, 0x66, 0x28, 0x9d, 0x3e, 0x27, 0x9d, 0xe9, 0x09, 0x90, 0x1f, 0x90, 0x00, 0x05, 0xa8, 0x3d, 0x7c, 0x02, 0x14, 0xac, 0xe7, 0x83, 0x36, 0x0e, 0x98, 0x8f, 0xb0, 0x1f, 0x0c, 0x46, 0xde, 0xa1, 0xdb, 0x0d, 0x9e, 0x10, 0x53, 0x78, 0x22, 0x0f, 0xe9, 0x27, 0xf6, 0xd7, 0xd3, 0x81, 0xc0, 0xdf, 0x4c, 0xf4, 0x18, 0x8d, 0xf1, 0x72, 0x31, 0x6e, 0x3d, 0xb8, 0x44, 0x7f, 0xd1, 0x95, 0x16, 0x70,
0x92, 0x30, 0x78, 0x30, 0xf5, 0x7f, 0xfc, 0x5d, 0xbf, 0x65, 0xfd, 0xdd, 0xfd, 0x3a, 0x43, 0xfe, 0xe4, 0xe8, 0x02, 0xdf, 0xb8, 0x60, 0xed, 0x7b, 0x03, 0x49, 0xb0, 0xb9, 0xe9, 0xb5, 0xb1, 0xf6, 0x1a, 0x1e, 0xa8, 0x60, 0x3d, 0x6a, 0x83, 0x6f, 0xbe, 0x0c, 0x69, 0xe0, 0x23, 0xff, 0xa6, 0x73, 0x92, 0x6f, 0xbc, 0xeb, 0x1d, 0x5d, 0xe6, 0xe9, 0x31, 0x43, 0x2e, 0xcd, 0x91, 0xef, 0x6f, 0xf0,
0x94, 0x4c, 0xd1, 0xb4, 0xf3, 0xe9, 0x1c, 0x79, 0x9b, 0xe9, 0xff, 0xed, 0xd5, 0xcf, 0xb4, 0x80, 0x4a, 0x91, 0xa2, 0x3c, 0x38, 0xf6, 0x28, 0xf8, 0xdb, 0xfb, 0x56, 0x1f, 0x2b, 0xe4, 0x0e, 0xdd, 0x75, 0x0e, 0xf0, 0xe7, 0xc8, 0xeb, 0x9a, 0xc6, 0x03, 0xba, 0x3d, 0xf4, 0x58, 0x80, 0x6e, 0x37, 0x33, 0x36, 0x41, 0xcf, 0xc4, 0x52, 0x97, 0x1b, 0xc7, 0x97, 0xc9, 0xb9, 0x3b, 0x2a, 0x0f, 0x1a,
0xb9, 0x10, 0xea, 0x5c, 0xe1, 0xac, 0x72, 0xd4, 0x4a, 0xb2, 0x38, 0xf8, 0x4f, 0x1f, 0xb1, 0xa8, 0x2d, 0x99, 0x51, 0xbf, 0x0a, 0xb5, 0x96, 0xa7, 0xe4, 0xde, 0x4d, 0x81, 0xf4, 0xef, 0x99, 0x54, 0x14, 0x25, 0x03, 0x05, 0xb1, 0xc8, 0x73, 0x24, 0xf5, 0x4d, 0x86, 0x84, 0x2c, 0xb6, 0xac, 0xa1, 0xb5, 0x61, 0xc6, 0x7f, 0xab, 0x27, 0x44, 0x87, 0xae, 0x35, 0x48, 0x7d, 0xb7, 0x71, 0x5a, 0xa1,
0xae, 0x15, 0x8e, 0x79, 0x9f, 0x66, 0xf1, 0xb0, 0xca, 0xd0, 0x37, 0x2c, 0x9d, 0xe8, 0xef, 0xce, 0x18, 0x9d, 0xd0, 0xa7, 0xcc, 0xd7, 0x09, 0xd6, 0x1f, 0x50, 0x23, 0xf0, 0xd1, 0x84, 0xe1, 0x1b, 0x77, 0x86, 0xfc, 0x39, 0x71, 0x67, 0x33, 0xa4, 0x3f, 0x5d, 0xf4, 0xfb, 0xc8, 0x63, 0x78, 0xe2, 0xce, 0x10, 0xbe, 0x65, 0xf8, 0x16, 0xe9, 0xd7, 0x33, 0x15, 0x4d, 0x36, 0xe6, 0xcf, 0x91, 0x9e,
0xe0, 0xeb, 0xd4, 0xd2, 0xa4, 0x38, 0x34, 0x06, 0xf4, 0x1a, 0x16, 0x6c, 0x3d, 0xfd, 0x96, 0x69, 0x5d, 0xb3, 0xad, 0x25, 0xaa, 0x54, 0xd2, 0x52, 0x2d, 0x19, 0x28, 0xb4, 0x85, 0xa4, 0x12, 0xe9, 0x23, 0xa8, 0xd1, 0x30, 0xbf, 0x7b, 0xa3, 0x8b, 0xcc, 0xe0, 0x8d, 0x6a, 0xc9, 0xe2, 0x5f, 0xb7, 0x55, 0x38, 0x1e, 0xff, 0xa3, 0xd9, 0x52, 0x9e, 0x89, 0xad, 0xcb, 0x44, 0x6a, 0xee, 0x9b, 0xdc,
0xb5, 0xa8, 0x94, 0xbe, 0xe6, 0xda, 0x87, 0x97, 0x7d, 0xfa, 0xd6, 0x6b, 0x3f, 0xde, 0x56, 0xbf, 0x54, 0xe6, 0x6e, 0xec, 0xd7, 0x51, 0x17, 0x0e, 0xaf, 0x24, 0x90, 0x38, 0x13, 0x69, 0x5d, 0x00, 0x57, 0xee, 0xef, 0x35, 0xc8, 0xa7, 0x07, 0x60, 0x90, 0x2a, 0x21, 0x6d, 0xeb, 0x70, 0x89, 0x61, 0x39, 0xa3, 0xd3, 0xf6, 0x1e, 0xcc, 0xee, 0xfe, 0x70, 0xcc, 0x10, 0x08, 0xcb, 0x89, 0xf2, 0x9a,
0x1b, 0x09, 0xd2, 0x77, 0x6b, 0xaf, 0xca, 0xd2, 0x76, 0x9a, 0xf3, 0x09, 0x5d, 0xca, 0x39, 0xc8, 0x77, 0x9f, 0x3f, 0x7e, 0x88, 0x2d, 0xcb, 0xdc, 0xc1, 0xe9, 0x8b, 0x3c, 0x83, 0x8b, 0xed, 0x8c, 0xbe, 0x66, 0x4d, 0xfb, 0xc2, 0x72, 0x5c, 0x92, 0x65, 0x6f, 0x37, 0xc0, 0xd5, 0x07, 0x5a, 0x29, 0xe0, 0x20, 0x6d, 0xab, 0x8d, 0x03, 0x6b, 0x64, 0x1f, 0x4c, 0xdb, 0xe0, 0x34, 0xe0, 0x96, 0x12,
0xb4, 0xde, 0x1b, 0xc8, 0x49, 0xcd, 0x94, 0xed, 0x44, 0xda, 0x01, 0xfc, 0xb4, 0x99, 0x15, 0xa8, 0xb7, 0x0c, 0xf4, 0xeb, 0xeb, 0xa7, 0xf7, 0x99, 0x6d, 0x75, 0xa1, 0x68, 0x39, 0xae, 0xa9, 0xbf, 0xa2, 0xa3, 0xaf, 0xdc, 0x0a, 0x78, 0x66, 0xf3, 0xde, 0xca, 0xfe, 0x64, 0x68, 0x6c, 0x59, 0x7b, 0xc7, 0xd9, 0x0f, 0x90, 0xe8, 0xed, 0xf0, 0x8f, 0xf0, 0x78, 0xd9, 0xa3, 0x99, 0x65, 0xaa, 0x57,
0xd4, 0x16, 0xb9, 0x56, 0x47, 0x2a, 0x74, 0xdf, 0x0d, 0xe6, 0x2b, 0xd7, 0x75, 0x5f, 0x8e, 0x8e, 0xab, 0x8c, 0x39, 0x6c, 0xd1, 0xc9, 0x4a, 0x2d, 0x99, 0x73, 0xea, 0x74, 0x05, 0x17, 0x25, 0xf0, 0x58, 0xf0, 0x7f, 0x97, 0xc0, 0x07, 0xf2, 0x94, 0x89, 0x0a, 0x62, 0xc1, 0xef, 0xf5, 0x73, 0xd0, 0x53, 0x40, 0x55, 0x91, 0x95, 0xee, 0xfb, 0xd8, 0xbe, 0x9d, 0x76, 0xd4, 0xce, 0xa3, 0x71, 0xfe,
0xb6, 0xbd, 0x98, 0xb2, 0xfe, 0x7c, 0x2b, 0x90, 0xbd, 0x1c, 0x1d, 0x4a, 0xd7, 0x1f, 0x25, 0x55, 0x60, 0x5b, 0xc7, 0x1e, 0xa4, 0x04, 0xb2, 0xae, 0xf5, 0x66, 0xfa, 0xc6, 0xcd, 0x5a, 0xbf, 0xdd, 0xba, 0x84, 0xec, 0x68, 0xfb, 0x0d, 0xad, 0xd2, 0x93, 0xf9, 0x0a, 0x94, 0x0e, 0x3d, 0x51, 0x2b, 0x7b, 0xe0, 0xac, 0x51, 0x00, 0x93, 0x81, 0xe5, 0x0e, 0x09, 0x6d, 0x7b, 0xb8, 0x64, 0x70, 0x33,
0xa2, 0x48, 0x4f, 0xf7, 0xac, 0xdb, 0x69, 0x68, 0x6e, 0x9f, 0x05, 0xb9, 0xd3, 0x40, 0x6c, 0xfd, 0x64, 0x5d, 0xdb, 0xda, 0x75, 0x6f, 0x88, 0x02, 0xc7, 0x55, 0xe2, 0x83, 0xd0, 0x77, 0xd1, 0x5a, 0xe7, 0x41, 0x49, 0xca, 0x57, 0xb6, 0x73, 0x6d, 0xfd, 0x8c, 0xac, 0x6b, 0xd8, 0xf7, 0xc2, 0xf8, 0xb4, 0xcf, 0xeb, 0xd8, 0x5a, 0x94, 0x4b, 0xeb, 0x1a, 0xae, 0xad, 0xc5, 0xb8, 0x5c, 0x5a, 0x83,
0xc4, 0xf2, 0xe2, 0x85, 0xdd, 0x1f, 0xd4, 0xde, 0x26, 0x7f, 0x16, 0x65, 0x7c, 0x29, 0x7d, 0x67, 0x8e, 0x85, 0x43, 0x90, 0x94, 0xfa, 0x92, 0xfe, 0x3d, 0x57, 0xf6, 0x57, 0x29, 0xdf, 0xfe, 0xb6, 0x39, 0x30, 0xde, 0x89, 0x68, 0xf5, 0x89, 0x7c, 0xb2, 0xb9, 0xf3, 0xe2, 0x85, 0xcd, 0x63, 0x1f, 0x26, 0xed, 0x4c, 0x6a, 0x60, 0x69, 0x10, 0xd0, 0xaf, 0x18, 0xb3, 0xad, 0xd2, 0x72, 0x22, 0x9a,
0xdb, 0xca, 0x65, 0xc0, 0x57, 0x6a, 0xbd, 0xe4, 0x4e, 0x2e, 0xa4, 0xad, 0x47, 0x42, 0xec, 0x45, 0xb0, 0x38, 0x74, 0x60, 0x1e, 0xc1, 0xf5, 0xb5, 0xd3, 0x9f, 0x4c, 0x42, 0x21, 0x36, 0x70, 0xbf, 0xa6, 0x2c, 0xb3, 0xd5, 0x4f, 0xf0, 0xf3, 0x33, 0xd0, 0x77, 0xbf, 0x83, 0x9a, 0x67, 0x81, 0xd3, 0x41, 0x7a, 0x4c, 0x51, 0x8b, 0x71, 0x97, 0xa4, 0x17, 0x63, 0xf3, 0x3f, 0x16, 0xff, 0x03, 0xd8,
0x33, 0xf7, 0x07, 0xc7, 0x18, 0x00, 0x00
};
#endif // AsyncWebSerial_HTML_H