Files
OTGateway/webflasher/index.html
2025-01-07 06:22:41 +03:00

318 lines
9.9 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>OTGateway Web Flasher</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css" />
<link rel="stylesheet" href="./styles.css" />
</head>
<body>
<header class="container">
<nav>
<ul>
<li>
<a href="#">
<div class="logo">OpenTherm Gateway</div>
</a>
</li>
</ul>
<ul>
<li>
<a href="https://github.com/Laxilef/OTGateway/wiki" role="button" class="secondary" target="_blank">Wiki</a>
</li>
</ul>
</nav>
</header>
<main class="container">
<article>
<div>
<hgroup>
<h2>Web Flasher</h2>
<p></p>
</hgroup>
<div>
<label>
<span>Choose version</span>
<select name="fwVersion" disabled required>
<option selected value="0">Choose version...</option>
</select>
</label>
<label class="fwBoardContainer hidden">
<span>Board</span>
<select name="fwBoard" required>
<option selected value="0">Choose board...</option>
</select>
</label>
<div class="toolContainer hidden">
<hr />
<div>
<b>Status:</b> <span class="status">Not connected</span>
<br /><br />
</div>
<div class="progress hidden">
<progress value="0" max="100"></progress>
<br /><br />
</div>
<div class="grid">
<button class="connect">💡 1. Connect</button>
<button class="flash" disabled>🚀 2. Flash</button>
</div>
</div>
</div>
<div class="powered-by">
<small>
Powered by <a href="https://github.com/espressif/esptool-js/" target="_blank" class="secondary">espressif/esptool-js</a>
</small>
</div>
</div>
</article>
</main>
<footer class="container">
<small>
<b>Made by Laxilef</b>
<a href="https://github.com/Laxilef/OTGateway/blob/master/LICENSE" target="_blank" class="secondary">License</a>
<a href="https://github.com/Laxilef/OTGateway/blob/master/" target="_blank" class="secondary">Source code</a>
<a href="https://github.com/Laxilef/OTGateway/wiki" target="_blank" class="secondary">Help</a>
<a href="https://github.com/Laxilef/OTGateway/issues" target="_blank" class="secondary">Issues & questions</a>
<a href="https://github.com/Laxilef/OTGateway/releases" target="_blank" class="secondary">Releases</a>
</small>
</footer>
<script type="module">
import { ESPLoader, Transport } from "https://unpkg.com/esptool-js/bundle.js";
let esploader = null;
const fwVersion = document.querySelector("[name='fwVersion']");
const fwBoardContainer = document.querySelector(".fwBoardContainer");
const fwBoard = document.querySelector("[name='fwBoard']");
const toolContainer = document.querySelector(".toolContainer");
const statusContainer = document.querySelector(".status");
const connectBtn = document.querySelector(".connect");
const flashBtn = document.querySelector(".flash");
const progressContainer = document.querySelector(".progress");
async function loadFwVersions() {
try {
fwVersion.disabled = true;
const response = await fetch("https://api.github.com/repos/Laxilef/OTGateway/releases");
const data = await response.json();
for (let release of data) {
const releaseDate = new Date(release.published_at).toLocaleDateString();
const option = document.createElement("option");
option.textContent = `${release.name} (${releaseDate})`;
option.value = release.id;
fwVersion.appendChild(option);
}
} catch (error) {
console.error("Error fetching releases:", error);
alert("Error fetching releases");
return false;
}
fwVersion.disabled = false;
return true;
}
async function loadFwBoards(releaseId) {
let configUrl = null;
let files = [];
try {
const response = await fetch(`https://api.github.com/repos/Laxilef/OTGateway/releases/${releaseId}/assets`);
const assets = await response.json();
for (const file of assets) {
if (file.name == "webflasher.json") {
configUrl = file.browser_download_url;
} else {
files.push({
"name": file.name,
"url": file.browser_download_url
});
}
}
} catch (error) {
console.error("Error fetching release:", error);
alert("Error fetching release");
return false;
}
if (!configUrl) {
return false;
}
try {
while (fwBoard.length > 1) {
fwBoard.remove(1);
}
const response = await fetch(`https://corsproxy.io/?url=${configUrl}`);
const boards = await response.json();
for (let board of boards) {
let value = [];
for (let file of board.files) {
for (let sFile of files) {
if (file.name == sFile.name) {
value.push({
"address": file.address,
"name": file.name,
"url": sFile.url
});
}
}
}
const option = document.createElement("option");
option.textContent = board.name;
option.value = JSON.stringify(value);
fwBoard.appendChild(option);
}
} catch (error) {
console.error("Error fetching release config:", error);
alert("Error fetching release config");
return false;
}
return true;
}
fwVersion.addEventListener("change", async (element) => {
fwBoardContainer.classList.toggle("hidden", true);
toolContainer.classList.toggle("hidden", true);
if (element.target.value == 0) {
return;
}
if (await loadFwBoards(element.target.value)) {
fwBoardContainer.classList.toggle("hidden", false);
} else {
alert("This version is not supported by web flasher");
}
});
fwBoard.addEventListener("change", async (element) => {
if (element.target.value == 0) {
toolContainer.classList.toggle("hidden", true);
return;
}
toolContainer.classList.toggle("hidden", false);
});
connectBtn.addEventListener("click", async () => {
connectBtn.disabled = true;
flashBtn.disabled = true;
try {
statusContainer.textContent = "Connecting...";
const device = await navigator.serial.requestPort({});
const transport = new Transport(device);
esploader = new ESPLoader({
transport: transport,
baudrate: parseInt(115200),
debugLogging: false,
});
const model = await esploader.main();
flashBtn.disabled = false;
statusContainer.textContent = `Connected to ${model}`;
} catch (error) {
statusContainer.textContent = "Failed to connect to device! Maybe this browser is not supported?";
} finally {
connectBtn.disabled = false;
}
});
flashBtn.addEventListener("click", async () => {
connectBtn.disabled = true;
flashBtn.disabled = true;
progressContainer.classList.toggle("hidden", false);
const progressBar = progressContainer.querySelector("progress");
progressBar.removeAttribute("value");
progressBar.removeAttribute("max");
let files = JSON.parse(fwBoard[fwBoard.selectedIndex].value);
try {
statusContainer.textContent = "Downloading files...";
for (let file of files) {
const response = await fetch(`https://corsproxy.io/?url=${file.url}`);
const firmwareArrayBuffer = await response.arrayBuffer();
const uint8Array = new Uint8Array(firmwareArrayBuffer);
file.data = "";
for (let i = 0; i < uint8Array.length; i++) {
file.data += String.fromCharCode(uint8Array[i]);
}
}
} catch (error) {
statusContainer.textContent = "Failed to download files";
connectBtn.disabled = false;
flashBtn.disabled = false;
progressContainer.classList.toggle("hidden", true);
return;
}
try {
statusContainer.textContent = "Erasing flash...";
await esploader.writeFlash({
fileArray: files,
flashSize: "keep",
eraseAll: true,
compress: true,
reportProgress: (fIndex, written, total) => {
progressBar.setAttribute("value", (written / total) * 100);
progressBar.setAttribute("max", 100);
statusContainer.textContent = `Flashing '${files[fIndex].name}'...`;
}
});
statusContainer.textContent = "Flashed successfully!";
try {
await esploader.hardReset();
} catch (error) { }
} catch (error) {
statusContainer.textContent = `Failed to write: ${error}`;
} finally {
connectBtn.disabled = false;
//flashBtn.disabled = false;
progressContainer.classList.toggle("hidden", true);
}
});
await loadFwVersions();
</script>
</body>
</html>