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,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==