One-window install: profile, wifi, USB flash and pairing hints (0.2.27)
"Nieuw scherm" used to span two dialogs plus a copy/download of the installation YAML. It is now one dialog: board, name (node name derived, editable), wifi only when secrets.yaml lacks it, the detected USB port, and Installeren. The add-on writes the profile with unique API/OTA keys, merges just the missing wifi lines into secrets.yaml (comments and other secrets untouched, result re-parsed before writing), then builds and flashes with the live ESPHome log and stage in the same dialog; a failed build offers a retry without a second profile. The YAML download endpoint is gone: the profile already lives in the ESPHome folder. Pairing happens in Home Assistant itself, so the done view and a sidebar card per not-yet-paired profile say so, with a button to Apparaten & diensten, the API key to copy and the reminder to allow HA actions. One profile per screen; the dialog says that too. Tests: wifi merge cases, install pre-checks, pending list, profile metadata; the date-anchored hourly forecast test now follows the clock. Firmware 0.2.22 stays current. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -260,7 +260,62 @@ function renderUpdate(screen) {
|
||||
} else return null;
|
||||
return row;
|
||||
}
|
||||
function openIntegrations() {
|
||||
// Pairing happens in Home Assistant itself. This page lives in HA's ingress iframe,
|
||||
// so send the top window to Apparaten & diensten (same origin); elsewhere open a tab.
|
||||
const path = "/config/integrations/dashboard";
|
||||
try {
|
||||
window.top.location.assign(path);
|
||||
} catch {
|
||||
window.open(path, "_blank");
|
||||
}
|
||||
}
|
||||
async function copyText(text, element) {
|
||||
try {
|
||||
if (!navigator.clipboard || !window.isSecureContext) throw new Error();
|
||||
await navigator.clipboard.writeText(text);
|
||||
toast("API-sleutel gekopieerd.");
|
||||
} catch {
|
||||
if (element) {
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(element);
|
||||
const selection = window.getSelection();
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
}
|
||||
toast(document.execCommand("copy") ? "API-sleutel gekopieerd." : "De sleutel is geselecteerd. Kopieer met Ctrl+C of Command+C.");
|
||||
}
|
||||
}
|
||||
// Profiles that Home Assistant does not list yet: the freshly flashed screen is not lost, it
|
||||
// still has to be added under Apparaten & diensten, outside this page.
|
||||
function renderPending() {
|
||||
const box = $("#pending");
|
||||
box.replaceChildren();
|
||||
for (const p of inventory.pending || []) {
|
||||
const card = node("div", undefined, "pending");
|
||||
card.append(
|
||||
node("strong", p.friendly),
|
||||
node("small", p.installed
|
||||
? "Geïnstalleerd, maar nog niet in Home Assistant. Dat doe je buiten ESP Screens: voeg het ontdekte ESPHome-apparaat toe onder Instellingen → Apparaten & diensten, plak daar de API-sleutel en zet bij Configureren “Allow the device to perform Home Assistant actions” aan."
|
||||
: `Nog niet in Home Assistant. Al geflasht? Voeg het ESPHome-apparaat toe onder Instellingen → Apparaten & diensten en sta daarna bij Configureren de Home Assistant-acties toe. Nog niet geflasht? Firmware & USB → ${p.file}.`),
|
||||
);
|
||||
const actions = node("div", undefined, "pending-actions");
|
||||
const go = node("button", "Open Apparaten & diensten", "mini");
|
||||
go.type = "button";
|
||||
go.onclick = openIntegrations;
|
||||
actions.append(go);
|
||||
if (p.api_key) {
|
||||
const copy = node("button", "Kopieer API-sleutel", "mini quiet");
|
||||
copy.type = "button";
|
||||
copy.onclick = () => copyText(p.api_key);
|
||||
actions.append(copy);
|
||||
}
|
||||
card.append(actions);
|
||||
box.append(card);
|
||||
}
|
||||
}
|
||||
function renderScreens() {
|
||||
renderPending();
|
||||
// Keep an open address form alive across the periodic refresh.
|
||||
if ($("#screens .screen-host")) {
|
||||
renderUpdates();
|
||||
@@ -976,85 +1031,250 @@ for (const [value, label] of [
|
||||
};
|
||||
$("#filters").append(b);
|
||||
}
|
||||
async function checkWifiSecrets() {
|
||||
const fields = $("#wifi-fields"), status = $("#wifi-status");
|
||||
fields.hidden = true;
|
||||
fields.disabled = true;
|
||||
$("#create-profile").disabled = true;
|
||||
status.textContent = "ESPHome wifi-instellingen controleren…";
|
||||
// ----- Nieuw scherm: profiel, wifi en de eerste flash in één venster -----
|
||||
const installer = {
|
||||
poll: null, view: "setup", file: null, friendly: "", board: "cyd", target: "",
|
||||
apiKey: null, nodeEdited: false, ports: null, jobState: null,
|
||||
};
|
||||
// ESPHome's node-name rule: lowercase ASCII, digits and dashes, starting with a letter.
|
||||
function slug(text) {
|
||||
const clean = text.toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "")
|
||||
.replace(/[^a-z0-9]+/g, "-").replace(/^[^a-z]+/, "").slice(0, 30).replace(/-+$/, "");
|
||||
return clean || "scherm";
|
||||
}
|
||||
function portLabel(port) {
|
||||
const id = port.replace(/^\/dev\/serial\/by-id\/usb-/, "").replace(/-if\d+(-port\d+)?$/, "").replace(/_/g, " ");
|
||||
return `USB · ${id === port ? port.replace(/^\/dev\//, "") : id}`;
|
||||
}
|
||||
function syncNode() {
|
||||
const form = $("#install-form");
|
||||
if (!installer.nodeEdited) form.elements.name.value = slug(form.elements.friendly_name.value);
|
||||
$("#node-preview").textContent = form.elements.name.value || "…";
|
||||
}
|
||||
function renderTargets(ports) {
|
||||
const select = $("#install-target");
|
||||
const key = ports.join("\n");
|
||||
if (installer.ports === key) return;
|
||||
installer.ports = key;
|
||||
const current = select.value;
|
||||
const later = node("option", "Later · alleen het profiel bewaren");
|
||||
later.value = "";
|
||||
select.replaceChildren(...ports.map((p) => { const o = node("option", portLabel(p)); o.value = p; return o; }), later);
|
||||
select.value = ports.includes(current) ? current : ports[0] || "";
|
||||
renderTargetHint();
|
||||
}
|
||||
function renderTargetHint() {
|
||||
const select = $("#install-target");
|
||||
const ports = select.options.length - 1;
|
||||
$("#target-hint").textContent = !ports
|
||||
? "Geen USB-poort gevonden. Sluit het scherm met een datakabel aan op de Home Assistant-machine; de lijst ververst vanzelf."
|
||||
: !select.value
|
||||
? "Het profiel komt in de ESPHome-map. Installeren kan later via Firmware & USB, of vanuit ESPHome Device Builder."
|
||||
: ports > 1
|
||||
? "Meer dan één bord aangesloten: kies de poort van dit scherm."
|
||||
: "Eén keer via USB; daarna gaat alles draadloos.";
|
||||
$("#install-go").textContent = select.value ? "Installeren" : "Profiel bewaren";
|
||||
}
|
||||
function renderWifi(wifi) {
|
||||
const fields = $("#wifi-fields");
|
||||
const ask = wifi?.state === "new" || wifi?.state === "missing";
|
||||
fields.hidden = !ask;
|
||||
fields.disabled = !ask;
|
||||
if (ask) {
|
||||
const missing = wifi.missing || [];
|
||||
$("#wifi-ssid-label").hidden = !missing.includes("wifi_ssid");
|
||||
$("#wifi-password-label").hidden = !missing.includes("wifi_password");
|
||||
$("#wifi-status").textContent = wifi.state === "new"
|
||||
? "Eén keer invullen: ESP Screens bewaart dit in ESPHome secrets.yaml, volgende schermen gebruiken het automatisch."
|
||||
: "Je ESPHome secrets.yaml mist nog wifi-gegevens. ESP Screens vult alleen de ontbrekende regels aan.";
|
||||
}
|
||||
return wifi?.state === "ready"
|
||||
? "Wifi komt uit je ESPHome secrets.yaml."
|
||||
: wifi?.state === "invalid"
|
||||
? "secrets.yaml in de ESPHome-map is geen geldige YAML. Herstel het bestand eerst; het wordt niet overschreven."
|
||||
: "";
|
||||
}
|
||||
async function installerRefresh() {
|
||||
let data;
|
||||
try {
|
||||
const {wifi} = await (await api("firmware")).json();
|
||||
if (wifi?.state === "ready") {
|
||||
status.textContent = "✓ Bestaande ESPHome-wifi gevonden. Dit scherm gebruikt automatisch wifi_ssid en wifi_password uit je secrets.yaml.";
|
||||
$("#create-profile").disabled = false;
|
||||
} else if (wifi?.state === "new") {
|
||||
status.textContent = "Nog geen ESPHome-secrets gevonden. Vul wifi één keer in; volgende schermen gebruiken deze gegevens automatisch.";
|
||||
fields.hidden = false;
|
||||
fields.disabled = false;
|
||||
$("#create-profile").disabled = false;
|
||||
} else {
|
||||
status.textContent = "Controleer wifi_ssid en wifi_password in ESPHome secrets.yaml. Je bestaande bestand blijft behouden. Sluit en open deze wizard opnieuw na aanpassen.";
|
||||
}
|
||||
} catch {
|
||||
status.textContent = "ESPHome-secrets konden niet worden gecontroleerd. Sluit en open de wizard opnieuw om het nogmaals te proberen.";
|
||||
data = await (await api("firmware")).json();
|
||||
} catch (e) {
|
||||
$("#install-note").textContent = e.message;
|
||||
return;
|
||||
}
|
||||
const job = data.job;
|
||||
const ours = job && installer.file && job.file === installer.file;
|
||||
if (ours) installer.jobState = job.state;
|
||||
if (installer.view === "setup") {
|
||||
if (ours && job.state === "running") return showProgress(job, data.logs);
|
||||
renderTargets(data.ports || []);
|
||||
const wifiNote = renderWifi(data.wifi);
|
||||
const busy = job?.state === "running";
|
||||
const flashing = !!$("#install-target").value;
|
||||
$("#install-note").textContent = busy
|
||||
? `Er loopt al een build of installatie (${job.file}). Wacht tot die klaar is.`
|
||||
: !data.available && flashing
|
||||
? "ESPHome CLI ontbreekt in deze installatie; alleen het profiel bewaren kan."
|
||||
: wifiNote;
|
||||
$("#install-go").disabled = data.wifi?.state === "invalid" || (flashing && (busy || !data.available));
|
||||
} else if (installer.view === "progress" && ours) {
|
||||
renderProgress(job, data.logs);
|
||||
}
|
||||
}
|
||||
$("#new-screen").onclick = $("#start").onclick = () => {
|
||||
function showProgress(job, logs) {
|
||||
installer.view = "progress";
|
||||
installer.jobState = job.state;
|
||||
$("#install-setup").hidden = true;
|
||||
$("#install-progress").hidden = false;
|
||||
$("#install-log-wrap").hidden = false;
|
||||
renderProgress(job, logs);
|
||||
}
|
||||
function outcome(ok) {
|
||||
$("#progress-spin").hidden = true;
|
||||
$("#progress-mark").hidden = false;
|
||||
$("#progress-mark").textContent = ok ? "✓" : "✕";
|
||||
$("#progress-mark").className = `outcome ${ok ? "ok" : "bad"}`;
|
||||
}
|
||||
function renderProgress(job, logs) {
|
||||
const running = job.state === "running";
|
||||
const ok = job.state === "success";
|
||||
if (running) {
|
||||
$("#progress-spin").hidden = false;
|
||||
$("#progress-mark").hidden = true;
|
||||
} else outcome(ok);
|
||||
$("#install-title").textContent = running ? "Even geduld…" : ok ? "Klaar." : "Dat lukte niet.";
|
||||
$("#progress-title").textContent = running
|
||||
? job.stage === "upload" ? `Firmware naar ${installer.friendly} schrijven…` : "Firmware bouwen…"
|
||||
: ok ? `Firmware staat op ${installer.friendly}` : "Installeren mislukt";
|
||||
$("#progress-detail").textContent = running
|
||||
? job.stage === "upload"
|
||||
? "Haal de USB-kabel nog niet los."
|
||||
: "Een eerste build duurt op een Raspberry enkele minuten. Je mag dit venster sluiten: de installatie loopt door en je vindt hem terug onder Nieuw scherm."
|
||||
: ok
|
||||
? `Het scherm start op en verbindt met je wifi.${installer.board === "cyd" ? " De CYD vraagt eerst om een touch-kalibratie: tik de kruisjes aan." : ""} Koppel het nu aan Home Assistant:`
|
||||
: logs.filter((l) => /error/i.test(l)).pop() || logs.filter((l) => /failed|mislukt|fout/i.test(l)).pop() || "Bekijk het log hieronder.";
|
||||
const pre = $("#install-log");
|
||||
const stick = pre.scrollTop + pre.clientHeight >= pre.scrollHeight - 8;
|
||||
pre.textContent = logs.join("\n");
|
||||
if (stick) pre.scrollTop = pre.scrollHeight;
|
||||
if (!running && !ok) $("#install-log-wrap").open = true;
|
||||
$("#install-result").hidden = !ok;
|
||||
if (ok) renderSteps();
|
||||
$("#install-retry").hidden = running || ok;
|
||||
$("#install-close").textContent = ok ? "Klaar" : "Sluiten";
|
||||
$("#install-close").classList.toggle("quiet", !ok);
|
||||
}
|
||||
function showSaved() {
|
||||
installer.view = "done";
|
||||
$("#install-setup").hidden = true;
|
||||
$("#install-progress").hidden = false;
|
||||
outcome(true);
|
||||
$("#install-title").textContent = "Profiel bewaard.";
|
||||
$("#progress-title").textContent = `${installer.file} staat in de ESPHome-map`;
|
||||
$("#progress-detail").textContent =
|
||||
"Installeren kan zodra het scherm aan de Home Assistant-machine hangt: Firmware & USB → dit profiel → USB-poort → Bouwen & installeren. Of open het profiel in ESPHome Device Builder (dezelfde map) en flash vanuit je browser. Bewaar de API-sleutel voor de koppeling:";
|
||||
$("#install-result").hidden = false;
|
||||
renderSteps();
|
||||
$("#install-log-wrap").hidden = true;
|
||||
$("#install-retry").hidden = true;
|
||||
$("#install-close").textContent = "Klaar";
|
||||
$("#install-close").classList.remove("quiet");
|
||||
}
|
||||
function renderSteps() {
|
||||
$("#api-key").textContent = installer.apiKey || "";
|
||||
const steps = [
|
||||
["Ga naar Home Assistant → Instellingen → Apparaten & diensten.", ` Dit gebeurt buiten ESP Screens. Home Assistant ontdekt ${installer.friendly} als ESPHome-apparaat; klik op Toevoegen. Niet ontdekt? Voeg ESPHome handmatig toe met het IP-adres van het scherm. `],
|
||||
["Plak de API-sleutel", " hierboven zodra Home Assistant om een encryptiesleutel vraagt."],
|
||||
["Sta HA-acties toe:", " ESPHome-integratie → Configureren → “Allow the device to perform Home Assistant actions”. Zonder dit ziet het scherm alles, maar bedient het niets."],
|
||||
["Kies je tegels.", " Terug in ESP Screens verschijnt het scherm binnen een halve minuut in de lijst links; tot die tijd staat het daar als “nog niet in Home Assistant”."],
|
||||
];
|
||||
$("#install-steps").replaceChildren(...steps.map(([b, t], i) => {
|
||||
const li = node("li");
|
||||
li.append(node("b", b), t);
|
||||
if (i === 0) {
|
||||
const go = node("button", "Open Apparaten & diensten", "mini");
|
||||
go.type = "button";
|
||||
go.onclick = openIntegrations;
|
||||
li.append(go);
|
||||
}
|
||||
return li;
|
||||
}));
|
||||
}
|
||||
function resetInstaller() {
|
||||
Object.assign(installer, { view: "setup", file: null, apiKey: null, nodeEdited: false, ports: null, jobState: null, target: "" });
|
||||
const form = $("#install-form");
|
||||
form.reset();
|
||||
$("#node-label").hidden = true;
|
||||
$("#install-setup").hidden = false;
|
||||
$("#install-progress").hidden = true;
|
||||
$("#install-result").hidden = true;
|
||||
$("#install-log-wrap").open = false;
|
||||
$("#install-log").textContent = "";
|
||||
$("#install-title").textContent = "Aansluiten en installeren.";
|
||||
$("#install-status").textContent = "";
|
||||
$("#install-note").textContent = "";
|
||||
$("#install-target").replaceChildren();
|
||||
$("#target-hint").textContent = "";
|
||||
$("#install-close").classList.add("quiet");
|
||||
syncNode();
|
||||
}
|
||||
function openInstaller() {
|
||||
if (installer.view !== "progress") resetInstaller();
|
||||
$("#installer").showModal();
|
||||
checkWifiSecrets();
|
||||
installerRefresh();
|
||||
clearInterval(installer.poll);
|
||||
installer.poll = setInterval(installerRefresh, 3000);
|
||||
}
|
||||
$("#new-screen").onclick = $("#start").onclick = openInstaller;
|
||||
$("#close-install").onclick = $("#install-close").onclick = () => $("#installer").close();
|
||||
$("#installer").addEventListener("close", () => {
|
||||
clearInterval(installer.poll);
|
||||
// A finished job is shown once; the next open starts a fresh form.
|
||||
if (installer.view === "progress" && installer.jobState !== "running") installer.view = "done";
|
||||
});
|
||||
$("#install-target").onchange = () => { renderTargetHint(); installerRefresh(); };
|
||||
$("#install-form").elements.friendly_name.oninput = syncNode;
|
||||
$("#install-form").elements.name.oninput = () => { installer.nodeEdited = true; syncNode(); };
|
||||
$("#edit-node").onclick = () => {
|
||||
installer.nodeEdited = true;
|
||||
$("#node-label").hidden = false;
|
||||
$("#install-form").elements.name.focus();
|
||||
};
|
||||
$("#close-install").onclick = () => $("#installer").close();
|
||||
let generatedName = "";
|
||||
$("#install-form").onsubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
$("#download").disabled = true;
|
||||
const form = e.target;
|
||||
syncNode();
|
||||
if (!form.reportValidity()) return;
|
||||
const data = Object.fromEntries(new FormData(form));
|
||||
$("#install-go").disabled = true;
|
||||
$("#install-status").textContent = "";
|
||||
try {
|
||||
const data = Object.fromEntries(new FormData(e.target));
|
||||
const response = await api("install", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
$("#yaml-output").value = await response.text();
|
||||
generatedName = data.name;
|
||||
$("#yaml-result").hidden = false;
|
||||
$("#install-status").textContent =
|
||||
"Je YAML is klaar. Kopieer hem naar ESPHome en bewaar het bestand met sleutels.";
|
||||
} catch (e) {
|
||||
$("#install-status").textContent = e.message;
|
||||
const result = await (await api("firmware/profiles", { method: "POST", body: JSON.stringify(data) })).json();
|
||||
Object.assign(installer, { file: result.file, apiKey: result.api_key, friendly: data.friendly_name.trim(), board: data.board, target: data.target });
|
||||
form.elements.wifi_password.value = "";
|
||||
if (result.job) showProgress(result.job, []);
|
||||
else showSaved();
|
||||
} catch (err) {
|
||||
$("#install-status").textContent = err.message;
|
||||
} finally {
|
||||
$("#download").disabled = false;
|
||||
$("#install-go").disabled = false;
|
||||
}
|
||||
};
|
||||
$("#copy-yaml").onclick = async () => {
|
||||
$("#install-retry").onclick = async () => {
|
||||
try {
|
||||
if (navigator.clipboard && window.isSecureContext)
|
||||
await navigator.clipboard.writeText($("#yaml-output").value);
|
||||
else {
|
||||
$("#yaml-output").select();
|
||||
if (!document.execCommand("copy")) throw new Error();
|
||||
}
|
||||
$("#install-status").textContent =
|
||||
"Gekopieerd. Plak dit bij Edit in ESPHome Device Builder.";
|
||||
} catch {
|
||||
$("#yaml-output").select();
|
||||
$("#install-status").textContent =
|
||||
"De tekst is geselecteerd. Kopieer met Ctrl+C of Command+C.";
|
||||
const { ports } = await (await api("firmware")).json();
|
||||
// The board may have been replugged; a single visible port is unambiguous.
|
||||
if (!ports.includes(installer.target) && ports.length === 1) installer.target = ports[0];
|
||||
const job = await (await api("firmware/jobs", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ file: installer.file, action: "install", target: installer.target }),
|
||||
})).json();
|
||||
showProgress(job, []);
|
||||
} catch (err) {
|
||||
toast(err.message);
|
||||
}
|
||||
};
|
||||
$("#download-yaml").onclick = () => {
|
||||
const url = URL.createObjectURL(
|
||||
new Blob([$("#yaml-output").value], { type: "text/plain" }),
|
||||
);
|
||||
const a = node("a");
|
||||
a.href = url;
|
||||
a.download = `${generatedName}.yaml`;
|
||||
document.body.append(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
setTimeout(() => URL.revokeObjectURL(url), 30000);
|
||||
$("#install-status").textContent =
|
||||
"Download gestart. Blokkeert je browser deze? Gebruik Kopieer YAML.";
|
||||
};
|
||||
$("#copy-key").onclick = () => copyText(installer.apiKey || "", $("#api-key"));
|
||||
window.addEventListener("beforeunload", (e) => {
|
||||
if (dirty) {
|
||||
e.preventDefault();
|
||||
@@ -1184,26 +1404,6 @@ for (const action of ["validate", "build", "install"])
|
||||
toast(e.message);
|
||||
}
|
||||
};
|
||||
$("#create-profile").onclick = async () => {
|
||||
const form = $("#install-form");
|
||||
if (!form.reportValidity()) return;
|
||||
try {
|
||||
const data = await (
|
||||
await api("firmware/profiles", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(Object.fromEntries(new FormData(form))),
|
||||
})
|
||||
).json();
|
||||
generatedName = data.file.replace(/\.yaml$/, " ").trim();
|
||||
$("#yaml-output").value = data.yaml;
|
||||
$("#yaml-result").hidden = false;
|
||||
$("#install-status").textContent =
|
||||
`${data.file} bewaard. Open Firmware & USB om te installeren.`;
|
||||
form.elements.wifi_password.value = "";
|
||||
} catch (e) {
|
||||
toast(e.message);
|
||||
}
|
||||
};
|
||||
function openSection(id) {
|
||||
const section = $(id);
|
||||
if (section.tagName === "DETAILS") section.open = true;
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<button id="new-screen" class="tool tool-primary">
|
||||
<span class="tool-icon" aria-hidden="true">+</span>
|
||||
<span class="tool-text"
|
||||
><b>Nieuw scherm</b><small>Koppel een CYD of Guition</small></span
|
||||
><b>Nieuw scherm</b><small>Aansluiten en installeren</small></span
|
||||
>
|
||||
</button>
|
||||
<button id="open-firmware" class="tool">
|
||||
@@ -47,9 +47,10 @@
|
||||
</div>
|
||||
<ol class="howto" aria-label="Zo werkt het">
|
||||
<li>
|
||||
<b>1 · Koppelen</b><span
|
||||
>Kies <b>Nieuw scherm</b> voor de installatie-YAML met eigen
|
||||
sleutels. Eén keer via USB flashen, daarna gaat alles draadloos.</span
|
||||
<b>1 · Installeren</b><span
|
||||
>Sluit het scherm via USB aan op de Home Assistant-machine en kies
|
||||
<b>Nieuw scherm</b>: naam, wifi en klaar. Eén keer via USB, daarna
|
||||
gaat alles draadloos.</span
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
@@ -61,7 +62,8 @@
|
||||
<li>
|
||||
<b>3 · Opslaan</b><span
|
||||
>Met <b>Opslaan & naar scherm</b> staat het direct op je scherm.
|
||||
Nieuwe schermfuncties installeer je via <b>Firmware & USB</b>.</span
|
||||
Nieuwe schermfuncties komen met <b>Bijwerken</b> draadloos op je
|
||||
scherm.</span
|
||||
>
|
||||
</li>
|
||||
</ol>
|
||||
@@ -69,9 +71,10 @@
|
||||
<aside>
|
||||
<h2>Mijn schermen</h2>
|
||||
<div id="screens"></div>
|
||||
<div id="pending"></div>
|
||||
<p class="hint">
|
||||
Nieuw scherm gekoppeld? Het verschijnt hier automatisch binnen 30
|
||||
seconden.
|
||||
Nieuw scherm gekoppeld in Home Assistant? Het verschijnt hier
|
||||
automatisch binnen 30 seconden.
|
||||
</p>
|
||||
<button class="quiet" id="refresh">↻ Vernieuwen</button>
|
||||
<div class="updates" id="updates" hidden>
|
||||
@@ -93,8 +96,8 @@
|
||||
<span class="empty-icon">▦</span>
|
||||
<h2>Begin met een scherm</h2>
|
||||
<p>
|
||||
Sluit een CYD of Guition aan. We maken de installatie-YAML voor je
|
||||
en helpen je met de eerste flash.
|
||||
Sluit een CYD of Guition via USB aan op je Home Assistant-machine.
|
||||
Profiel, firmware en de eerste flash gebeuren in één venster.
|
||||
</p>
|
||||
<button id="start">Nieuw scherm installeren</button>
|
||||
</section>
|
||||
@@ -184,8 +187,8 @@
|
||||
</section>
|
||||
</div>
|
||||
<p class="firmware-link">
|
||||
Firmware bouwen en via USB of wifi installeren kan via
|
||||
<b>Firmware & USB</b> bovenaan. Je bestaande YAML en secrets blijven
|
||||
Een bestaand profiel opnieuw bouwen of via USB of wifi installeren kan
|
||||
via <b>Firmware & USB</b> bovenaan. Je eigen YAML en secrets blijven
|
||||
bewaard.
|
||||
</p>
|
||||
<details class="help" id="help">
|
||||
@@ -206,11 +209,13 @@
|
||||
tegel op <b>Dubbelbreed</b> voor meer ruimte.
|
||||
</p>
|
||||
<p>
|
||||
Nieuwe firmware bouw en installeer je via <b>Firmware & USB</b> met
|
||||
de ingebouwde ESPHome CLI: kies je eigen profiel, dan een USB-poort of
|
||||
wifi, en klik op <b>Bouwen & installeren</b>. Je tegels blijven in
|
||||
deze app bewaard; een offline scherm krijgt de wijzigingen zodra het
|
||||
terug is.
|
||||
Een nieuw scherm installeer je via <b>Nieuw scherm</b>: het profiel
|
||||
met eigen sleutels, ontbrekende wifi in ESPHome secrets.yaml, de build
|
||||
en de USB-flash gebeuren in één venster, met de API-sleutel voor de
|
||||
koppeling aan het eind. Nieuwe firmware voor bestaande schermen komt
|
||||
via <b>Bijwerken</b> in de lijst, of handmatig via
|
||||
<b>Firmware & USB</b>. Je tegels blijven in deze app bewaard; een
|
||||
offline scherm krijgt de wijzigingen zodra het terug is.
|
||||
</p>
|
||||
</details>
|
||||
</main>
|
||||
@@ -219,7 +224,7 @@
|
||||
<div class="dialog-head">
|
||||
<div>
|
||||
<p class="eyebrow">NIEUW SCHERM</p>
|
||||
<h2>Van USB naar aan de muur.</h2>
|
||||
<h2 id="install-title">Aansluiten en installeren.</h2>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
@@ -230,104 +235,102 @@
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
<p>
|
||||
Kies je scherm. Elk bestand krijgt eigen sleutels voor een veilige
|
||||
koppeling en draadloze updates.
|
||||
</p>
|
||||
<fieldset>
|
||||
<legend>Welk scherm heb je?</legend>
|
||||
<label class="board"
|
||||
><input type="radio" name="board" value="cyd" checked /><span
|
||||
><b>CYD · 2,8 inch</b
|
||||
><small>ESP32-2432S028 · 320 × 240</small></span
|
||||
></label
|
||||
><label class="board"
|
||||
><input type="radio" name="board" value="guition" /><span
|
||||
><b>Guition · 4 inch</b
|
||||
><small>ESP32-S3-4848S040 · 480 × 480 · GT911</small></span
|
||||
<div id="install-setup">
|
||||
<p>
|
||||
Sluit het scherm met een USB-datakabel aan op de machine waarop Home
|
||||
Assistant draait. ESP Screens maakt een eigen profiel met unieke
|
||||
sleutels, bouwt de firmware en zet die direct op het scherm.
|
||||
</p>
|
||||
<fieldset>
|
||||
<legend>Welk scherm heb je?</legend>
|
||||
<label class="board"
|
||||
><input type="radio" name="board" value="cyd" checked /><span
|
||||
><b>CYD · 2,8 inch</b
|
||||
><small>ESP32-2432S028 · 320 × 240</small></span
|
||||
></label
|
||||
><label class="board"
|
||||
><input type="radio" name="board" value="guition" /><span
|
||||
><b>Guition · 4 inch</b
|
||||
><small>ESP32-S3-4848S040 · 480 × 480 · GT911</small></span
|
||||
></label
|
||||
>
|
||||
</fieldset>
|
||||
<label
|
||||
>Naam<input
|
||||
name="friendly_name"
|
||||
required
|
||||
maxlength="60"
|
||||
placeholder="Scherm woonkamer"
|
||||
autocomplete="off" /><small class="node-line"
|
||||
>Apparaatnaam <code id="node-preview">scherm</code
|
||||
><button type="button" class="link" id="edit-node">
|
||||
aanpassen
|
||||
</button></small
|
||||
><small
|
||||
>Elk scherm krijgt een eigen profiel met eigen sleutels. Vier
|
||||
schermen? Doorloop dit vier keer met een andere naam.</small
|
||||
></label
|
||||
>
|
||||
</fieldset>
|
||||
<label
|
||||
>Herkenbare naam<input
|
||||
name="friendly_name"
|
||||
required
|
||||
maxlength="60"
|
||||
placeholder="Scherm woonkamer" /></label
|
||||
><label
|
||||
>Unieke apparaatnaam<input
|
||||
name="name"
|
||||
required
|
||||
pattern="[a-z][a-z0-9-]{0,29}"
|
||||
maxlength="30"
|
||||
placeholder="scherm-woonkamer"
|
||||
/><small
|
||||
>Kleine letters, cijfers en streepjes. Elk scherm een andere
|
||||
naam.</small
|
||||
></label>
|
||||
<p id="wifi-status" role="status"></p>
|
||||
<fieldset id="wifi-fields" hidden disabled>
|
||||
<legend>Wifi voor je eerste ESPHome-apparaat</legend>
|
||||
<label
|
||||
>Wifi-naam<input
|
||||
name="wifi_ssid"
|
||||
autocomplete="off"
|
||||
/></label>
|
||||
<label
|
||||
>Wifiwachtwoord<input
|
||||
name="wifi_password"
|
||||
type="password"
|
||||
autocomplete="new-password"
|
||||
/></label>
|
||||
</fieldset>
|
||||
<button type="button" id="create-profile">
|
||||
Bewaar profiel in ESP Screens
|
||||
</button>
|
||||
<button type="submit" id="download">Maak installatie-YAML</button>
|
||||
<p id="install-status" role="status"></p>
|
||||
<div id="yaml-result" hidden>
|
||||
<label id="node-label" hidden
|
||||
>Apparaatnaam<input
|
||||
name="name"
|
||||
pattern="[a-z][a-z0-9-]{0,29}"
|
||||
maxlength="30"
|
||||
autocomplete="off" /><small
|
||||
>Kleine letters, cijfers en streepjes. Elk scherm een andere
|
||||
naam.</small
|
||||
></label
|
||||
>
|
||||
<fieldset id="wifi-fields" class="wifi" hidden disabled>
|
||||
<legend>Wifi</legend>
|
||||
<p class="hint" id="wifi-status"></p>
|
||||
<label id="wifi-ssid-label"
|
||||
>Wifi-naam (2,4 GHz)<input name="wifi_ssid" autocomplete="off"
|
||||
/></label>
|
||||
<label id="wifi-password-label"
|
||||
>Wifiwachtwoord<input
|
||||
name="wifi_password"
|
||||
type="password"
|
||||
autocomplete="new-password"
|
||||
/></label>
|
||||
</fieldset>
|
||||
<label
|
||||
>Je eigen ESPHome-YAML<textarea
|
||||
id="yaml-output"
|
||||
readonly
|
||||
rows="9"
|
||||
spellcheck="false"
|
||||
></textarea>
|
||||
</label>
|
||||
<div class="yaml-actions">
|
||||
<button type="button" id="copy-yaml">Kopieer YAML</button
|
||||
><button type="button" class="quiet" id="download-yaml">
|
||||
Download bestand
|
||||
>Installeren via<select name="target" id="install-target"></select
|
||||
><small id="target-hint"></small
|
||||
></label>
|
||||
<p class="hint" id="install-note"></p>
|
||||
<div class="install-actions">
|
||||
<button type="submit" id="install-go">Installeren</button>
|
||||
<span id="install-status" role="status"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="install-progress" hidden>
|
||||
<div class="progress-head">
|
||||
<span class="spin" id="progress-spin"></span>
|
||||
<span class="outcome" id="progress-mark" hidden></span>
|
||||
<strong id="progress-title"></strong>
|
||||
</div>
|
||||
<p id="progress-detail"></p>
|
||||
<div id="install-result" hidden>
|
||||
<div class="key-box">
|
||||
<span>API-sleutel</span><code id="api-key"></code>
|
||||
<button type="button" class="mini" id="copy-key">Kopieer</button>
|
||||
</div>
|
||||
<ol class="steps" id="install-steps"></ol>
|
||||
</div>
|
||||
<details id="install-log-wrap">
|
||||
<summary>Log van ESPHome</summary>
|
||||
<pre id="install-log"></pre>
|
||||
</details>
|
||||
<div class="install-actions">
|
||||
<button type="button" id="install-retry" hidden>
|
||||
Opnieuw proberen
|
||||
</button>
|
||||
<button type="button" class="quiet" id="install-close">
|
||||
Sluiten
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<ol class="steps">
|
||||
<li>
|
||||
<b>Bewaar je profiel.</b> Bestaande wifi-secrets worden behouden;
|
||||
bij een eerste installatie vul je wifi hierboven in.
|
||||
</li>
|
||||
<li>
|
||||
<b>Open Firmware & USB.</b> Kies dit profiel, sluit het scherm op je
|
||||
Raspberry aan en selecteer de juiste USB-poort. Kies Bouwen &
|
||||
installeren.
|
||||
</li>
|
||||
<li>
|
||||
<b>Koppel in HA → Apparaten & diensten → ESPHome.</b> Gebruik de
|
||||
API-sleutel uit je eigen YAML en sta HA-acties toe.
|
||||
</li>
|
||||
<li>
|
||||
<b>Kies hier je tegels.</b> CYD vraagt bij de eerste start om
|
||||
kalibratie; Guition niet.
|
||||
</li>
|
||||
<li>
|
||||
<b>Later updaten?</b> Kies hetzelfde profiel en het IP-adres van je
|
||||
scherm in Firmware & USB.
|
||||
</li>
|
||||
</ol>
|
||||
<p class="hint">
|
||||
Bewaar je YAML met sleutels. Gebruik later Firmware & USB met het
|
||||
IP-adres voor draadloze updates. Tegels wijzigen doe je hier.
|
||||
</p>
|
||||
</form>
|
||||
</dialog>
|
||||
<dialog id="firmware-dialog">
|
||||
|
||||
@@ -989,3 +989,145 @@ pre {
|
||||
.icon-choice.icon-auto .mdi { font-size: 22px; }
|
||||
.icon-picker .hint { margin: 0; }
|
||||
.icon-hint { font-weight: 400; }
|
||||
|
||||
/* Nieuw scherm: profiel, wifi en de eerste flash in één venster. */
|
||||
.link {
|
||||
background: none;
|
||||
color: var(--accent);
|
||||
padding: 0 0 0 8px;
|
||||
font-weight: 600;
|
||||
font-size: 12px;
|
||||
}
|
||||
.node-line code {
|
||||
background: var(--soft);
|
||||
padding: 1px 6px;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
}
|
||||
#installer .wifi {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 0;
|
||||
margin: 0 0 16px;
|
||||
padding: 12px 16px 2px;
|
||||
border: 1px solid #f4e3c2;
|
||||
background: #fff9ee;
|
||||
border-radius: 12px;
|
||||
}
|
||||
#installer .wifi legend {
|
||||
padding: 0 4px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
#installer .wifi .hint {
|
||||
margin: 0 0 12px;
|
||||
}
|
||||
#installer select {
|
||||
margin-top: 7px;
|
||||
padding: 11px 12px;
|
||||
border-radius: 10px;
|
||||
border-color: #cdd5df;
|
||||
}
|
||||
.install-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-top: 18px;
|
||||
}
|
||||
#install-status {
|
||||
color: #b23b3b;
|
||||
font-size: 13px;
|
||||
}
|
||||
#install-note:empty {
|
||||
display: none;
|
||||
}
|
||||
.progress-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-size: 17px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
.progress-head .spin {
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
border-width: 3px;
|
||||
}
|
||||
.outcome {
|
||||
font-size: 22px;
|
||||
line-height: 1;
|
||||
font-weight: 700;
|
||||
}
|
||||
.outcome.ok {
|
||||
color: #21704b;
|
||||
}
|
||||
.outcome.bad {
|
||||
color: #b23b3b;
|
||||
}
|
||||
.key-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 12px;
|
||||
background: #f7f9fc;
|
||||
margin: 4px 0 16px;
|
||||
}
|
||||
.key-box span {
|
||||
flex: none;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--muted);
|
||||
}
|
||||
.key-box code {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
font-size: 12px;
|
||||
}
|
||||
#install-log-wrap {
|
||||
margin-top: 12px;
|
||||
}
|
||||
#install-log-wrap summary {
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--muted);
|
||||
}
|
||||
#install-log {
|
||||
max-height: 240px;
|
||||
margin: 10px 0 0;
|
||||
}
|
||||
|
||||
/* Profiles that Home Assistant does not list yet: pairing happens outside this page. */
|
||||
#pending:empty {
|
||||
display: none;
|
||||
}
|
||||
.pending {
|
||||
border: 1px dashed #b9cbec;
|
||||
border-radius: 14px;
|
||||
padding: 12px 14px;
|
||||
margin-bottom: 10px;
|
||||
background: #f8fbff;
|
||||
}
|
||||
.pending strong {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
}
|
||||
.pending small {
|
||||
margin: 3px 0 10px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.pending-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
.pending-actions .quiet {
|
||||
padding: 5px 9px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.steps .mini {
|
||||
display: inline-block;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user