mirror of
https://github.com/Laxilef/OTGateway.git
synced 2025-12-11 18:54:28 +05:00
318 lines
9.9 KiB
HTML
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> |