From 6fdcfce3a8a4eb5f3e5db9148ea91210f150d100 Mon Sep 17 00:00:00 2001
From: Aiden
Date: Sat, 30 May 2026 12:36:03 +0800
Subject: [PATCH] Update ci.yml
---
.github/workflows/ci.yml | 48 ++--
.github/workflows/publish-firmware.yml | 360 ++++++++++++++++++++++---
athom-smart-plug-v5.yaml | 2 +-
3 files changed, 357 insertions(+), 53 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index e9685b5..857b1f4 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -9,34 +9,43 @@ on:
- cron: '0 0 * * *'
concurrency:
- group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
+ discover:
+ name: Discover ESPHome YAML files
+ runs-on: ubuntu-latest
+ outputs:
+ matrix: ${{ steps.files.outputs.matrix }}
+ steps:
+ - name: Checkout source code
+ uses: actions/checkout@v4
+
+ - name: Discover root-level YAML files
+ id: files
+ run: |
+ set -euo pipefail
+
+ mapfile -t files < <(find . -maxdepth 1 -type f -name '*.yaml' -exec basename {} .yaml \; | sort)
+ if [ "${#files[@]}" -eq 0 ]; then
+ echo "No root-level ESPHome YAML files found." >&2
+ exit 1
+ fi
+
+ printf '%s\n' "${files[@]}"
+ printf 'matrix=%s\n' "$(printf '%s\n' "${files[@]}" | jq -R -s -c 'split("\n")[:-1]')" >> "${GITHUB_OUTPUT}"
+
ci:
name: Building ${{ matrix.file }} / ${{ matrix.esphome-version }}
runs-on: ubuntu-latest
+ needs:
+ - discover
strategy:
fail-fast: false
max-parallel: 3
matrix:
- #### Modify below here to match your project ####
- file:
- - athom-2ch-relay-board
- - athom-4ch-relay-board
- - athom-8ch-relay-board
- - athom-energy-monitor-x2
- - athom-energy-monitor-x6
- - athom-garage-door
- - athom-mini-relay-v2
- - athom-smart-plug
- - athom-zigbee-gateway
- - athom-presence-sensor-v3
- - athom-scd40-sensor
- - athom-sht40-sensor
- - athom-ld2450-sensor
- #### Modify above here to match your project ####
-
+ file: ${{ fromJson(needs.discover.outputs.matrix) }}
esphome-version:
- stable
- beta
@@ -44,8 +53,9 @@ jobs:
steps:
- name: Checkout source code
uses: actions/checkout@v4
+
- name: ESPHome ${{ matrix.esphome-version }}
uses: esphome/build-action@v7
with:
yaml-file: ${{ matrix.file }}.yaml
- version: ${{ matrix.esphome-version }}
\ No newline at end of file
+ version: ${{ matrix.esphome-version }}
diff --git a/.github/workflows/publish-firmware.yml b/.github/workflows/publish-firmware.yml
index fc69677..60f93e5 100644
--- a/.github/workflows/publish-firmware.yml
+++ b/.github/workflows/publish-firmware.yml
@@ -1,44 +1,338 @@
name: Publish Firmware
on:
- release:
- types: [published]
+ schedule:
+ - cron: '27 */6 * * *'
+ workflow_dispatch:
+ inputs:
+ esphome_version:
+ description: 'ESPHome version to build. Leave empty to use the latest stable release.'
+ required: false
+ type: string
+ force:
+ description: 'Recreate the GitHub release if it already exists.'
+ required: false
+ default: false
+ type: boolean
permissions:
contents: write
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event.inputs.esphome_version || 'latest-stable' }}
+ cancel-in-progress: false
+
jobs:
+ prepare:
+ name: Prepare Release
+ runs-on: ubuntu-latest
+ outputs:
+ esphome-version: ${{ steps.release.outputs.esphome-version }}
+ release-tag: ${{ steps.release.outputs.release-tag }}
+ release-url: ${{ steps.release.outputs.release-url }}
+ release-notes: ${{ steps.release.outputs.release-notes }}
+ should-build: ${{ steps.release.outputs.should-build }}
+ matrix: ${{ steps.files.outputs.matrix }}
+ files: ${{ steps.files.outputs.files }}
+ steps:
+ - name: Checkout source code
+ uses: actions/checkout@v4
+
+ - name: Resolve ESPHome release
+ id: release
+ env:
+ GH_TOKEN: ${{ github.token }}
+ GH_REPO: ${{ github.repository }}
+ INPUT_VERSION: ${{ inputs.esphome_version }}
+ FORCE: ${{ inputs.force }}
+ run: |
+ set -euo pipefail
+
+ if [ -n "${INPUT_VERSION}" ]; then
+ esphome_version="${INPUT_VERSION#v}"
+ else
+ esphome_version="$(
+ gh api repos/esphome/esphome/releases/latest --jq .tag_name
+ )"
+ esphome_version="${esphome_version#v}"
+ fi
+
+ if [ -z "${esphome_version}" ] || [ "${esphome_version}" = "null" ]; then
+ echo "Unable to resolve an ESPHome release version." >&2
+ exit 1
+ fi
+
+ release_tag="esphome-${esphome_version}"
+ release_url="https://github.com/${GH_REPO}/releases/tag/${release_tag}"
+ release_notes="Firmware built automatically from ESPHome ${esphome_version} stable release."
+ should_build="true"
+
+ if gh release view "${release_tag}" >/dev/null 2>&1; then
+ if [ "${FORCE}" = "true" ]; then
+ echo "Release ${release_tag} already exists and will be replaced after a successful build."
+ else
+ should_build="false"
+ fi
+ fi
+
+ {
+ echo "esphome-version=${esphome_version}"
+ echo "release-tag=${release_tag}"
+ echo "release-url=${release_url}"
+ echo "release-notes=${release_notes}"
+ echo "should-build=${should_build}"
+ } >> "${GITHUB_OUTPUT}"
+
+ if [ "${should_build}" = "true" ]; then
+ echo "Building firmware with ESPHome ${esphome_version}."
+ else
+ echo "Release ${release_tag} already exists. Nothing to build."
+ fi
+
+ - name: Discover ESPHome YAML files
+ id: files
+ run: |
+ set -euo pipefail
+
+ mapfile -t files < <(find . -maxdepth 1 -type f -name '*.yaml' -exec basename {} \; | sort)
+ if [ "${#files[@]}" -eq 0 ]; then
+ echo "No root-level ESPHome YAML files found." >&2
+ exit 1
+ fi
+
+ json="$(printf '%s\n' "${files[@]}" | jq -R -s -c 'split("\n")[:-1]')"
+ names="$(printf '%s\n' "${files[@]}" | sed 's/\.yaml$//' | jq -R -s -c 'split("\n")[:-1]')"
+
+ {
+ echo "matrix=${json}"
+ echo "files=${names}"
+ } >> "${GITHUB_OUTPUT}"
+
build-firmware:
- name: Build Firmware
- uses: esphome/workflows/.github/workflows/build.yml@2025.7.0
- with:
- #### Modify below here to match your project ####
- files: |
- athom-2ch-relay-board.yaml
- athom-4ch-relay-board.yaml
- athom-8ch-relay-board.yaml
- athom-energy-monitor-x2.yaml
- athom-energy-monitor-x6.yaml
- athom-garage-door.yaml
- athom-mini-relay-v2.yaml
- athom-smart-plug.yaml
- athom-zigbee-gateway.yaml
- athom-presence-sensor-v3.yaml
- athom-scd40-sensor.yaml
- athom-sht40-sensor.yaml
- athom-ld2450-sensor.yaml
- esphome-version: 2025.7.0
- combined-name: Athom-ESP32-Device
- #### Modify above here to match your project ####
-
- release-summary: ${{ github.event.release.body }}
- release-url: ${{ github.event.release.html_url }}
- release-version: ${{ github.event.release.tag_name }}
-
- upload-to-release:
- name: Upload to Release
- uses: esphome/workflows/.github/workflows/upload-to-gh-release.yml@2025.7.0
+ name: Build ${{ matrix.file }}
+ runs-on: ubuntu-latest
needs:
+ - prepare
+ if: needs.prepare.outputs.should-build == 'true'
+ strategy:
+ fail-fast: false
+ max-parallel: 3
+ matrix:
+ file: ${{ fromJson(needs.prepare.outputs.matrix) }}
+ steps:
+ - name: Checkout source code
+ uses: actions/checkout@v4
+
+ - name: ESPHome ${{ needs.prepare.outputs.esphome-version }}
+ id: build
+ continue-on-error: true
+ uses: esphome/build-action@v7
+ with:
+ yaml-file: ${{ matrix.file }}
+ version: ${{ needs.prepare.outputs.esphome-version }}
+ complete-manifest: true
+ release-summary: ${{ needs.prepare.outputs.release-notes }}
+ release-url: ${{ needs.prepare.outputs.release-url }}
+
+ - name: Collect firmware files
+ run: |
+ set -euo pipefail
+
+ name="${{ matrix.file }}"
+ name="${name%.yaml}"
+ mkdir -p "firmware/${name}"
+
+ build_name="${{ steps.build.outputs.name }}"
+ build_dir=""
+
+ if [ -n "${build_name}" ] && [ -d "${build_name}" ]; then
+ build_dir="${build_name}"
+ else
+ mapfile -t candidates < <(find . -mindepth 2 -maxdepth 2 -name '*.factory.bin' -exec dirname {} \; | sed 's#^\./##' | sort -u)
+ if [ "${#candidates[@]}" -eq 1 ]; then
+ build_dir="${candidates[0]}"
+ build_name="$(basename "${build_dir}")"
+ fi
+ fi
+
+ if [ -z "${build_dir}" ] || [ ! -d "${build_dir}" ]; then
+ echo "Expected ESPHome build output directory was not found." >&2
+ find . -maxdepth 3 -type f \( -name '*.bin' -o -name 'manifest.json' \) -print >&2
+ exit 1
+ fi
+
+ cp "${build_dir}/"*.bin "firmware/${name}/"
+
+ factory_bin="$(find "firmware/${name}" -maxdepth 1 -name '*.factory.bin' -print -quit)"
+ ota_bin="$(find "firmware/${name}" -maxdepth 1 -name '*.ota.bin' -print -quit)"
+
+ if [ -z "${factory_bin}" ] || [ -z "${ota_bin}" ]; then
+ echo "Expected factory and OTA binaries in '${build_dir}'." >&2
+ exit 1
+ fi
+
+ if [ ! -f "${build_dir}/manifest.json" ]; then
+ case "${build_name}" in
+ *-esp32) chip_family="ESP32" ;;
+ *-esp32s2) chip_family="ESP32-S2" ;;
+ *-esp32s3) chip_family="ESP32-S3" ;;
+ *-esp32c3) chip_family="ESP32-C3" ;;
+ *-esp32c5) chip_family="ESP32-C5" ;;
+ *-esp32c6) chip_family="ESP32-C6" ;;
+ *-esp8266) chip_family="ESP8266" ;;
+ *) echo "Cannot infer chip family from build name '${build_name}'." >&2; exit 1 ;;
+ esac
+
+ jq -n --arg chipFamily "${chip_family}" \
+ --arg name "${name}" \
+ --arg version "${{ needs.prepare.outputs.esphome-version }}" \
+ --arg otaPath "$(basename "${ota_bin}")" \
+ --arg otaMd5 "$(md5sum "${ota_bin}" | awk '{print $1}')" \
+ --arg otaSha256 "$(sha256sum "${ota_bin}" | awk '{print $1}')" \
+ --arg factoryPath "$(basename "${factory_bin}")" \
+ --arg factoryMd5 "$(md5sum "${factory_bin}" | awk '{print $1}')" \
+ --arg factorySha256 "$(sha256sum "${factory_bin}" | awk '{print $1}')" \
+ '{
+ name: $name,
+ version: $version,
+ home_assistant_domain: "esphome",
+ new_install_prompt_erase: false,
+ builds: [
+ {
+ chipFamily: $chipFamily,
+ ota: {
+ path: $otaPath,
+ md5: $otaMd5,
+ sha256: $otaSha256
+ },
+ parts: [
+ {
+ path: $factoryPath,
+ offset: 0,
+ md5: $factoryMd5,
+ sha256: $factorySha256
+ }
+ ]
+ }
+ ]
+ }' > "firmware/${name}/manifest.json"
+ else
+ cp "${build_dir}/manifest.json" "firmware/${name}/manifest.json"
+ fi
+
+ find "firmware/${name}" -maxdepth 1 -type f -print | sort
+ test -f "firmware/${name}/manifest.json"
+
+ - name: Upload firmware artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: firmware-${{ matrix.file }}
+ path: firmware/
+ if-no-files-found: error
+ retention-days: 1
+
+ publish-release:
+ name: Publish Release
+ runs-on: ubuntu-latest
+ needs:
+ - prepare
- build-firmware
- with:
- version: ${{ github.event.release.tag_name }}
\ No newline at end of file
+ if: needs.prepare.outputs.should-build == 'true'
+ steps:
+ - name: Download firmware artifacts
+ uses: actions/download-artifact@v4
+ with:
+ path: downloaded-artifacts
+ pattern: firmware-*
+ merge-multiple: true
+
+ - name: Prepare release assets
+ env:
+ RELEASE_TAG: ${{ needs.prepare.outputs.release-tag }}
+ ESPHOME_VERSION: ${{ needs.prepare.outputs.esphome-version }}
+ RELEASE_NOTES: ${{ needs.prepare.outputs.release-notes }}
+ FILES_JSON: ${{ needs.prepare.outputs.files }}
+ REPOSITORY: ${{ github.repository }}
+ run: |
+ set -euo pipefail
+
+ mkdir -p release-assets firmware
+ if [ -d downloaded-artifacts/firmware ]; then
+ cp -R downloaded-artifacts/firmware/. firmware/
+ else
+ cp -R downloaded-artifacts/. firmware/
+ fi
+
+ jq -n --arg name "Athom ESP32 Device" \
+ --arg version "${ESPHOME_VERSION}" \
+ --arg homeAssistantDomain "esphome" \
+ '{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}")"
+
+ 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
+ fi
+
+ cp "${device_dir}/"*.bin release-assets/
+ done
+
+ cd release-assets
+ shopt -s nullglob
+ for binary in *.bin; do
+ md5sum "${binary}" > "${binary}.md5"
+ sha256sum "${binary}" > "${binary}.sha256"
+ done
+
+ expected_count="$(jq 'length' <<< "${FILES_JSON}")"
+ manifest_count="$(find . -maxdepth 1 -name '*.manifest.json' ! -name 'Athom-ESP32-Device.manifest.json' | wc -l)"
+ factory_count="$(find . -maxdepth 1 -name '*.factory.bin' | wc -l)"
+ ota_count="$(find . -maxdepth 1 -name '*.ota.bin' | wc -l)"
+
+ if [ "${manifest_count}" -ne "${expected_count}" ]; then
+ echo "Expected ${expected_count} device manifests, found ${manifest_count}." >&2
+ exit 1
+ fi
+
+ if [ "${factory_count}" -ne "${expected_count}" ]; then
+ echo "Expected ${expected_count} factory binaries, found ${factory_count}." >&2
+ exit 1
+ fi
+
+ if [ "${ota_count}" -ne "${expected_count}" ]; then
+ echo "Expected ${expected_count} OTA binaries, found ${ota_count}." >&2
+ exit 1
+ fi
+
+ - name: Create GitHub release
+ env:
+ GH_TOKEN: ${{ github.token }}
+ GH_REPO: ${{ github.repository }}
+ FORCE: ${{ inputs.force }}
+ RELEASE_TAG: ${{ needs.prepare.outputs.release-tag }}
+ ESPHOME_VERSION: ${{ needs.prepare.outputs.esphome-version }}
+ RELEASE_NOTES: ${{ needs.prepare.outputs.release-notes }}
+ run: |
+ set -euo pipefail
+
+ if gh release view "${RELEASE_TAG}" >/dev/null 2>&1; then
+ if [ "${FORCE}" = "true" ]; then
+ gh release delete "${RELEASE_TAG}" --yes --cleanup-tag
+ else
+ echo "Release ${RELEASE_TAG} already exists." >&2
+ exit 1
+ fi
+ fi
+
+ gh release create "${RELEASE_TAG}" release-assets/* \
+ --title "ESPHome ${ESPHOME_VERSION} firmware" \
+ --notes "${RELEASE_NOTES}"
diff --git a/athom-smart-plug-v5.yaml b/athom-smart-plug-v5.yaml
index e4fc53b..981a3bc 100644
--- a/athom-smart-plug-v5.yaml
+++ b/athom-smart-plug-v5.yaml
@@ -122,7 +122,7 @@ mdns:
web_server:
port: 80
-# version: 3
+ version: 3
network:
enable_ipv6: ${ipv6_enable}