mirror of
https://github.com/athom-tech/esp32-configs.git
synced 2026-07-27 19:56:13 +00:00
update
This commit is contained in:
@@ -285,25 +285,74 @@ jobs:
|
||||
'{name: $name, version: $version, home_assistant_domain: $homeAssistantDomain, new_install_prompt_erase: false, builds: []}' \
|
||||
> release-assets/Athom-ESP32-Device.manifest.json
|
||||
|
||||
for device_dir in firmware/*; do
|
||||
[ -d "${device_dir}" ] || continue
|
||||
device="$(basename "${device_dir}")"
|
||||
shopt -s nullglob
|
||||
mapfile -t devices < <(jq -r '.[]' <<< "${FILES_JSON}")
|
||||
for device in "${devices[@]}"; do
|
||||
device_dir="firmware/${device}"
|
||||
manifest_path="${device_dir}/manifest.json"
|
||||
|
||||
if [ -f "${device_dir}/manifest.json" ]; then
|
||||
cp "${device_dir}/manifest.json" "release-assets/${device}.manifest.json"
|
||||
|
||||
tmp_manifest="$(mktemp)"
|
||||
jq --slurpfile manifest "${device_dir}/manifest.json" \
|
||||
'.builds += ($manifest[0].builds // [$manifest[0]])' \
|
||||
release-assets/Athom-ESP32-Device.manifest.json > "${tmp_manifest}"
|
||||
mv "${tmp_manifest}" release-assets/Athom-ESP32-Device.manifest.json
|
||||
if [ ! -d "${device_dir}" ]; then
|
||||
echo "Expected firmware directory '${device_dir}'." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cp "${device_dir}/"*.bin release-assets/
|
||||
if [ ! -f "${manifest_path}" ]; then
|
||||
echo "Expected manifest '${manifest_path}'." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
bins=("${device_dir}/"*.bin)
|
||||
factory_bins=("${device_dir}/"*.factory.bin)
|
||||
ota_bins=("${device_dir}/"*.ota.bin)
|
||||
|
||||
if [ "${#bins[@]}" -eq 0 ]; then
|
||||
echo "Expected at least one binary in '${device_dir}'." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "${#factory_bins[@]}" -ne 1 ]; then
|
||||
echo "Expected one factory binary in '${device_dir}', found ${#factory_bins[@]}." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "${#ota_bins[@]}" -ne 1 ]; then
|
||||
echo "Expected one OTA binary in '${device_dir}', found ${#ota_bins[@]}." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
device_manifest="$(mktemp)"
|
||||
cp "${manifest_path}" "${device_manifest}"
|
||||
|
||||
for binary in "${bins[@]}"; do
|
||||
binary_name="$(basename "${binary}")"
|
||||
asset_name="${device}-${binary_name}"
|
||||
|
||||
if [ -e "release-assets/${asset_name}" ]; then
|
||||
echo "Release asset '${asset_name}' already exists." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cp "${binary}" "release-assets/${asset_name}"
|
||||
|
||||
tmp_manifest="$(mktemp)"
|
||||
jq --arg from "${binary_name}" \
|
||||
--arg to "${asset_name}" \
|
||||
'walk(if type == "object" and has("path") and (.path | split("/")[-1]) == $from then .path = $to else . end)' \
|
||||
"${device_manifest}" > "${tmp_manifest}"
|
||||
mv "${tmp_manifest}" "${device_manifest}"
|
||||
done
|
||||
|
||||
cp "${device_manifest}" "release-assets/${device}.manifest.json"
|
||||
rm -f "${device_manifest}"
|
||||
|
||||
tmp_manifest="$(mktemp)"
|
||||
jq --slurpfile manifest "release-assets/${device}.manifest.json" \
|
||||
'.builds += ($manifest[0].builds // [$manifest[0]])' \
|
||||
release-assets/Athom-ESP32-Device.manifest.json > "${tmp_manifest}"
|
||||
mv "${tmp_manifest}" release-assets/Athom-ESP32-Device.manifest.json
|
||||
done
|
||||
|
||||
cd release-assets
|
||||
shopt -s nullglob
|
||||
for binary in *.bin; do
|
||||
md5sum "${binary}" > "${binary}.md5"
|
||||
sha256sum "${binary}" > "${binary}.sha256"
|
||||
@@ -329,6 +378,23 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for manifest in *.manifest.json; do
|
||||
while IFS= read -r asset_path; do
|
||||
if [ -z "${asset_path}" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
case "${asset_path}" in
|
||||
http://*|https://*) continue ;;
|
||||
esac
|
||||
|
||||
if [ ! -f "${asset_path}" ]; then
|
||||
echo "Manifest '${manifest}' references missing asset '${asset_path}'." >&2
|
||||
exit 1
|
||||
fi
|
||||
done < <(jq -r '[.builds[]? | (.ota.path? // empty), (.parts[]?.path? // empty)] | .[]' "${manifest}")
|
||||
done
|
||||
|
||||
- name: Create GitHub release
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
|
||||
@@ -27,6 +27,7 @@ env:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success'
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -57,8 +58,8 @@ jobs:
|
||||
)"
|
||||
|
||||
if [ "$(jq 'length' <<< "${releases}")" -eq 0 ]; then
|
||||
echo '{"latest": null, "versions": []}' > output/firmware/versions.json
|
||||
exit 0
|
||||
echo "No esphome-* firmware releases are available; refusing to publish an installer without firmware assets." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
latest_tag="$(jq -r 'map(select(.isLatest == true))[0].tagName // .[0].tagName' <<< "${releases}")"
|
||||
@@ -74,12 +75,33 @@ jobs:
|
||||
|
||||
if [ ! -f "${target}/devices.json" ]; then
|
||||
echo "Release ${tag} does not include devices.json." >&2
|
||||
continue
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "${tag}" = "${latest_tag}" ]; then
|
||||
cp "${target}/"* output/firmware/
|
||||
fi
|
||||
jq -e '.devices | type == "array" and length > 0' "${target}/devices.json" >/dev/null
|
||||
|
||||
while IFS= read -r manifest; do
|
||||
manifest_file="${target}/${manifest#firmware/}"
|
||||
if [ ! -f "${manifest_file}" ]; then
|
||||
echo "Release ${tag} is missing manifest '${manifest_file}'." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
while IFS= read -r asset_path; do
|
||||
if [ -z "${asset_path}" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
case "${asset_path}" in
|
||||
http://*|https://*) continue ;;
|
||||
esac
|
||||
|
||||
if [ ! -f "${target}/${asset_path}" ]; then
|
||||
echo "Manifest '${manifest_file}' references missing asset '${target}/${asset_path}'." >&2
|
||||
exit 1
|
||||
fi
|
||||
done < <(jq -r '[.builds[]? | (.ota.path? // empty), (.parts[]?.path? // empty)] | .[]' "${manifest_file}")
|
||||
done < <(jq -r '.devices[].manifest' "${target}/devices.json")
|
||||
|
||||
jq --arg version "${version}" \
|
||||
--arg tag "${tag}" \
|
||||
@@ -92,6 +114,16 @@ jobs:
|
||||
mv output/firmware/versions.tmp output/firmware/versions.json
|
||||
done
|
||||
|
||||
latest_version="${latest_tag#esphome-}"
|
||||
latest_target="output/firmware/${latest_version}"
|
||||
test -d "${latest_target}"
|
||||
find output/firmware -mindepth 1 -maxdepth 1 -type f ! -name versions.json -delete
|
||||
cp "${latest_target}/"* output/firmware/
|
||||
|
||||
test -f output/firmware/devices.json
|
||||
test -f output/firmware/Athom-ESP32-Device.manifest.json
|
||||
jq -e '.latest and (.versions | length > 0)' output/firmware/versions.json >/dev/null
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v5
|
||||
with:
|
||||
|
||||
+40
-49
@@ -6,7 +6,7 @@ ESPhome project by Shenzhen Athom Technology Co., Ltd., China.
|
||||
|
||||
Choose a firmware version and device, then connect it over USB to install the matching pre-built firmware.
|
||||
|
||||
<section class="installer" aria-label="Firmware installer">
|
||||
<section id="installer" class="installer" aria-label="Firmware installer" aria-disabled="true">
|
||||
<div class="controls">
|
||||
<label class="field" for="version-select">
|
||||
<span>Firmware version</span>
|
||||
@@ -29,7 +29,7 @@ Choose a firmware version and device, then connect it over USB to install the ma
|
||||
<h3 id="selected-title">Select firmware</h3>
|
||||
<p id="selected-detail">The installer will use the selected version and device manifest.</p>
|
||||
</div>
|
||||
<esp-web-install-button id="install-button" manifest="firmware/Athom-ESP32-Device.manifest.json"></esp-web-install-button>
|
||||
<esp-web-install-button id="install-button" manifest="firmware/Athom-ESP32-Device.manifest.json" disabled></esp-web-install-button>
|
||||
</div>
|
||||
|
||||
<p id="status" class="status" role="status"></p>
|
||||
@@ -67,6 +67,13 @@ Choose a firmware version and device, then connect it over USB to install the ma
|
||||
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));
|
||||
@@ -143,6 +150,11 @@ Choose a firmware version and device, then connect it over USB to install the ma
|
||||
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;
|
||||
@@ -157,38 +169,15 @@ Choose a firmware version and device, then connect it over USB to install the ma
|
||||
|
||||
<script type="module" src="https://unpkg.com/esp-web-tools@10/dist/web/install-button.js?module"></script>
|
||||
<script>
|
||||
const fallbackDeviceIds = [
|
||||
"athom-1gang-switch",
|
||||
"athom-2ch-relay-board",
|
||||
"athom-2gang-switch",
|
||||
"athom-3gang-switch",
|
||||
"athom-4ch-relay-board",
|
||||
"athom-4gang-switch",
|
||||
"athom-8ch-relay-board",
|
||||
"athom-energy-monitor-x2",
|
||||
"athom-energy-monitor-x6",
|
||||
"athom-garage-door",
|
||||
"athom-ld2450-sensor",
|
||||
"athom-mini-relay-v2",
|
||||
"athom-presence-sensor-v3",
|
||||
"athom-rf-ir-remote",
|
||||
"athom-rgbcw-bulb",
|
||||
"athom-rgbcw-light",
|
||||
"athom-scd40-sensor",
|
||||
"athom-sht40-sensor",
|
||||
"athom-smart-plug",
|
||||
"athom-smart-plug-v5",
|
||||
"athom-wall-outlet-v3",
|
||||
"athom-zigbee-gateway"
|
||||
];
|
||||
|
||||
const state = {
|
||||
versions: [],
|
||||
selectedVersion: "",
|
||||
selectedDevice: "",
|
||||
filter: ""
|
||||
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");
|
||||
@@ -222,19 +211,6 @@ Choose a firmware version and device, then connect it over USB to install the ma
|
||||
};
|
||||
}
|
||||
|
||||
function fallbackVersions() {
|
||||
return [{
|
||||
version: "latest",
|
||||
tag: "latest",
|
||||
devices: fallbackDeviceIds.map((id) => ({
|
||||
id,
|
||||
name: formatDeviceName(id),
|
||||
yaml: `${id}.yaml`,
|
||||
manifest: `firmware/${id}.manifest.json`
|
||||
}))
|
||||
}];
|
||||
}
|
||||
|
||||
async function loadInstallerIndex() {
|
||||
try {
|
||||
const response = await fetch("firmware/versions.json", { cache: "no-store" });
|
||||
@@ -243,13 +219,21 @@ Choose a firmware version and device, then connect it over USB to install the ma
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
state.versions = data.versions.map(normalizeVersion);
|
||||
state.selectedVersion = data.latest || state.versions[0]?.version || "";
|
||||
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 = fallbackVersions();
|
||||
state.selectedVersion = state.versions[0].version;
|
||||
status.textContent = "Version index is not available yet; using latest firmware paths.";
|
||||
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 || "";
|
||||
@@ -301,6 +285,7 @@ Choose a firmware version and device, then connect it over USB to install the ma
|
||||
versionSelect.append(option);
|
||||
}
|
||||
versionSelect.value = state.selectedVersion;
|
||||
versionSelect.disabled = !state.ready;
|
||||
}
|
||||
|
||||
function renderDeviceSelect() {
|
||||
@@ -313,6 +298,7 @@ Choose a firmware version and device, then connect it over USB to install the ma
|
||||
deviceSelect.append(option);
|
||||
}
|
||||
deviceSelect.value = state.selectedDevice;
|
||||
deviceSelect.disabled = !state.ready;
|
||||
}
|
||||
|
||||
function renderGrid() {
|
||||
@@ -322,7 +308,7 @@ Choose a firmware version and device, then connect it over USB to install the ma
|
||||
if (visibleDevices.length === 0) {
|
||||
const empty = document.createElement("p");
|
||||
empty.className = "empty";
|
||||
empty.textContent = "No matching devices.";
|
||||
empty.textContent = state.ready ? "No matching devices." : "Firmware index is not available.";
|
||||
grid.append(empty);
|
||||
return;
|
||||
}
|
||||
@@ -344,16 +330,21 @@ Choose a firmware version and device, then connect it over USB to install the ma
|
||||
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 = "";
|
||||
installButton.setAttribute("manifest", "firmware/Athom-ESP32-Device.manifest.json");
|
||||
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() {
|
||||
|
||||
Reference in New Issue
Block a user