About
ESPhome project by Shenzhen Athom Technology Co., Ltd., China.
Installation
Choose a firmware version and device, then connect it over USB to install the matching pre-built firmware.
Select firmware
The installer will use the selected version and device manifest.
<style>
.installer {
margin-top: 1.5rem;
}
.controls {
display: grid;
grid-template-columns: minmax(150px, 0.7fr) minmax(210px, 1fr) minmax(170px, 0.8fr);
gap: 14px;
margin-bottom: 18px;
}
.field {
display: grid;
gap: 6px;
color: #333;
font-weight: 700;
}
.field select,
.field input {
box-sizing: border-box;
width: 100%;
min-height: 42px;
border: 1px solid #c6cbd1;
border-radius: 6px;
padding: 8px 10px;
background: #fff;
color: #222;
font: inherit;
}
.field select:disabled,
.field input:disabled {
background: #f4f6f8;
color: #68727d;
cursor: not-allowed;
}
.device-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 10px;
margin-bottom: 20px;
}
.device-card {
min-height: 60px;
border: 1px solid #d3d7dc;
border-radius: 6px;
padding: 10px 12px;
background: #fff;
color: #24292f;
text-align: left;
cursor: pointer;
font: inherit;
}
.device-card:hover,
.device-card:focus {
border-color: #1e9fea;
outline: none;
}
.device-card[aria-pressed="true"] {
border-color: #1e9fea;
box-shadow: inset 4px 0 0 #1e9fea;
}
.device-name {
display: block;
font-weight: 700;
line-height: 1.25;
}
.device-meta {
display: block;
margin-top: 4px;
color: #65717c;
font-size: 0.82rem;
line-height: 1.2;
overflow-wrap: anywhere;
}
.empty {
grid-column: 1 / -1;
margin: 0;
color: #65717c;
}
.install-box {
display: flex;
align-items: center;
justify-content: space-between;
gap: 20px;
border-top: 1px solid #d8dde3;
padding-top: 18px;
}
.install-box h3 {
margin: 0 0 4px;
color: #24292f;
}
.install-box p,
.status {
margin: 0;
color: #4d5660;
}
.status {
margin-top: 12px;
min-height: 1.4em;
}
.installer[aria-disabled="true"] esp-web-install-button {
opacity: 0.55;
pointer-events: none;
}
@media (max-width: 760px) {
.controls {
grid-template-columns: 1fr;
}
.install-box {
align-items: flex-start;
flex-direction: column;
}
}
</style>
<script type="module" src="
https://unpkg.com/esp-web-tools@10/dist/web/install-button.js?module"></script>
<script>
const state = {
versions: [],
selectedVersion: "",
selectedDevice: "",
filter: "",
ready: false
};
const installer = document.querySelector("#installer");
const versionSelect = document.querySelector("#version-select");
const deviceSelect = document.querySelector("#device-select");
const search = document.querySelector("#device-search");
const grid = document.querySelector("#device-grid");
const installButton = document.querySelector("#install-button");
const title = document.querySelector("#selected-title");
const detail = document.querySelector("#selected-detail");
const status = document.querySelector("#status");
function formatDeviceName(id) {
return id
.split("-")
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
.join(" ");
}
function normalizeDevice(device, version) {
return {
id: device.id,
name: device.name || formatDeviceName(device.id),
yaml: device.yaml || `${device.id}.yaml`,
manifest: device.manifest || `firmware/${version}/${device.id}.manifest.json`
};
}
function normalizeVersion(version) {
return {
version: version.version,
tag: version.tag || `esphome-${version.version}`,
devices: (version.devices || []).map((device) => normalizeDevice(device, version.version))
};
}
async function loadInstallerIndex() {
try {
const response = await fetch("firmware/versions.json", { cache: "no-store" });
if (!response.ok) {
throw new Error(`versions.json returned ${response.status}`);
}
const data = await response.json();
state.versions = (data.versions || []).map(normalizeVersion);
if (state.versions.length === 0) {
throw new Error("versions.json does not include firmware versions");
}
const latestVersion = state.versions.find((item) => item.version === data.latest);
state.selectedVersion = latestVersion?.version || state.versions[0]?.version || "";
state.ready = true;
status.textContent = `Loaded ${state.versions.length} firmware version${state.versions.length === 1 ? "" : "s"}; latest is ${state.selectedVersion}.`;
} catch (error) {
state.versions = [];
state.selectedVersion = "";
state.selectedDevice = "";
state.ready = false;
status.textContent = "Firmware version index is not available yet; installation is disabled.";
}
state.selectedDevice = getCurrentDevices()[0]?.id || "";
render();
}
function getCurrentVersion() {
return state.versions.find((item) => item.version === state.selectedVersion) || state.versions[0];
}
function getCurrentDevices() {
return getCurrentVersion()?.devices || [];
}
function getSelectedDevice() {
return getCurrentDevices().find((device) => device.id === state.selectedDevice) || getCurrentDevices()[0];
}
function getVisibleDevices() {
const query = state.filter.trim().toLowerCase();
const devices = getCurrentDevices();
if (!query) {
return devices;
}
return devices.filter((device) =>
`${device.name} ${device.id} ${device.yaml}`.toLowerCase().includes(query)
);
}
function chooseVersion(version) {
state.selectedVersion = version;
state.selectedDevice = getCurrentDevices()[0]?.id || "";
render();
}
function chooseDevice(deviceId) {
state.selectedDevice = deviceId;
render();
}
function renderVersionSelect() {
versionSelect.replaceChildren();
for (const item of state.versions) {
const option = document.createElement("option");
option.value = item.version;
option.textContent = item.version;
option.selected = item.version === state.selectedVersion;
versionSelect.append(option);
}
versionSelect.value = state.selectedVersion;
versionSelect.disabled = !state.ready;
}
function renderDeviceSelect() {
deviceSelect.replaceChildren();
for (const device of getCurrentDevices()) {
const option = document.createElement("option");
option.value = device.id;
option.textContent = device.name;
option.selected = device.id === state.selectedDevice;
deviceSelect.append(option);
}
deviceSelect.value = state.selectedDevice;
deviceSelect.disabled = !state.ready;
}
function renderGrid() {
grid.replaceChildren();
const visibleDevices = getVisibleDevices();
if (visibleDevices.length === 0) {
const empty = document.createElement("p");
empty.className = "empty";
empty.textContent = state.ready ? "No matching devices." : "Firmware index is not available.";
grid.append(empty);
return;
}
for (const device of visibleDevices) {
const button = document.createElement("button");
button.type = "button";
button.className = "device-card";
button.setAttribute("aria-pressed", String(device.id === state.selectedDevice));
button.innerHTML = '
';
button.querySelector(".device-name").textContent = device.name;
button.querySelector(".device-meta").textContent = device.yaml;
button.addEventListener("click", () => chooseDevice(device.id));
grid.append(button);
}
}
function renderInstaller() {
const version = getCurrentVersion();
const device = getSelectedDevice();
installer.setAttribute("aria-disabled", String(!state.ready));
search.disabled = !state.ready;
if (!version || !device) {
title.textContent = "No firmware available";
detail.textContent = "Firmware index is unavailable; installation is disabled.";
installButton.removeAttribute("manifest");
installButton.setAttribute("disabled", "");
return;
}
title.textContent = `${device.name} - ${version.version}`;
detail.textContent = `${device.yaml} -> ${device.manifest}`;
installButton.setAttribute("manifest", device.manifest);
installButton.removeAttribute("disabled");
}
function render() {
renderVersionSelect();
renderDeviceSelect();
renderGrid();
renderInstaller();
}
versionSelect.addEventListener("change", (event) => chooseVersion(event.target.value));
deviceSelect.addEventListener("change", (event) => chooseDevice(event.target.value));
search.addEventListener("input", (event) => {
state.filter = event.target.value;
renderGrid();
});
loadInstallerIndex();
</script>