From 4bcd9f1d94934710d624f68c2e7b829a1d752a8e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 16 Oct 2026 06:05:54 +0000 Subject: [PATCH 01/14] Bump the actions-deps group with 4 updates (#243) Bumps the actions-deps group with 4 updates: [ptr727/ProjectTemplate/.github/workflows/merge-bot-task.yml](https://github.com/ptr727/projecttemplate), [ptr727/ProjectTemplate/.github/workflows/publish-plan-task.yml](https://github.com/ptr727/projecttemplate), [ptr727/ProjectTemplate/.github/workflows/validate-task.yml](https://github.com/ptr727/projecttemplate) and [ptr727/ProjectTemplate/.github/workflows/build-release-task.yml](https://github.com/ptr727/projecttemplate). Updates `ptr727/ProjectTemplate/.github/workflows/merge-bot-task.yml` from 2.0.1004 to 2.0.1020 - [Release notes](https://github.com/ptr727/projecttemplate/releases) - [Changelog](https://github.com/ptr727/ProjectTemplate/blob/main/HISTORY.md) - [Commits](https://github.com/ptr727/projecttemplate/compare/098016c83723e384c9da0088c13a00ca14e02fd5...ae63f7bf971dfac46c727d1005a822f0a957dd1e) Updates `ptr727/ProjectTemplate/.github/workflows/publish-plan-task.yml` from 2.0.1004 to 2.0.1020 - [Release notes](https://github.com/ptr727/projecttemplate/releases) - [Changelog](https://github.com/ptr727/ProjectTemplate/blob/main/HISTORY.md) - [Commits](https://github.com/ptr727/projecttemplate/compare/098016c83723e384c9da0088c13a00ca14e02fd5...ae63f7bf971dfac46c727d1005a822f0a957dd1e) Updates `ptr727/ProjectTemplate/.github/workflows/validate-task.yml` from 2.0.1004 to 2.0.1020 - [Release notes](https://github.com/ptr727/projecttemplate/releases) - [Changelog](https://github.com/ptr727/ProjectTemplate/blob/main/HISTORY.md) - [Commits](https://github.com/ptr727/projecttemplate/compare/098016c83723e384c9da0088c13a00ca14e02fd5...ae63f7bf971dfac46c727d1005a822f0a957dd1e) Updates `ptr727/ProjectTemplate/.github/workflows/build-release-task.yml` from 2.0.1004 to 2.0.1020 - [Release notes](https://github.com/ptr727/projecttemplate/releases) - [Changelog](https://github.com/ptr727/ProjectTemplate/blob/main/HISTORY.md) - [Commits](https://github.com/ptr727/projecttemplate/compare/098016c83723e384c9da0088c13a00ca14e02fd5...ae63f7bf971dfac46c727d1005a822f0a957dd1e) --- updated-dependencies: - dependency-name: ptr727/ProjectTemplate/.github/workflows/merge-bot-task.yml dependency-version: 2.0.1020 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions-deps - dependency-name: ptr727/ProjectTemplate/.github/workflows/publish-plan-task.yml dependency-version: 2.0.1020 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions-deps - dependency-name: ptr727/ProjectTemplate/.github/workflows/validate-task.yml dependency-version: 2.0.1020 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions-deps - dependency-name: ptr727/ProjectTemplate/.github/workflows/build-release-task.yml dependency-version: 2.0.1020 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions-deps ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/merge-bot-pull-request.yml | 2 +- .github/workflows/publish-release.yml | 6 +++--- .github/workflows/test-pull-request.yml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/merge-bot-pull-request.yml b/.github/workflows/merge-bot-pull-request.yml index 5fd67bf..e7ad5f1 100644 --- a/.github/workflows/merge-bot-pull-request.yml +++ b/.github/workflows/merge-bot-pull-request.yml @@ -19,7 +19,7 @@ jobs: merge-bot: name: Merge bot pull request job - uses: ptr727/ProjectTemplate/.github/workflows/merge-bot-task.yml@098016c83723e384c9da0088c13a00ca14e02fd5 # 2.0.1004 + uses: ptr727/ProjectTemplate/.github/workflows/merge-bot-task.yml@ae63f7bf971dfac46c727d1005a822f0a957dd1e # 2.0.1020 secrets: CODEGEN_APP_CLIENT_ID: ${{ secrets.CODEGEN_APP_CLIENT_ID }} CODEGEN_APP_PRIVATE_KEY: ${{ secrets.CODEGEN_APP_PRIVATE_KEY }} diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index cce5cca..7210e08 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -28,7 +28,7 @@ jobs: plan: name: Plan release job needs: [assert-dispatch-ref] - uses: ptr727/ProjectTemplate/.github/workflows/publish-plan-task.yml@098016c83723e384c9da0088c13a00ca14e02fd5 # 2.0.1004 + uses: ptr727/ProjectTemplate/.github/workflows/publish-plan-task.yml@ae63f7bf971dfac46c727d1005a822f0a957dd1e # 2.0.1020 with: event_name: ${{ github.event_name }} actor: ${{ github.actor }} @@ -38,7 +38,7 @@ jobs: name: Validate sources job needs: [plan] if: ${{ needs.plan.outputs.publish == 'true' }} - uses: ptr727/ProjectTemplate/.github/workflows/validate-task.yml@098016c83723e384c9da0088c13a00ca14e02fd5 # 2.0.1004 + uses: ptr727/ProjectTemplate/.github/workflows/validate-task.yml@ae63f7bf971dfac46c727d1005a822f0a957dd1e # 2.0.1020 permissions: contents: read secrets: @@ -48,7 +48,7 @@ jobs: name: Publish project release job needs: [plan, validate] if: ${{ needs.plan.outputs.publish == 'true' && needs.validate.result == 'success' }} - uses: ptr727/ProjectTemplate/.github/workflows/build-release-task.yml@098016c83723e384c9da0088c13a00ca14e02fd5 # 2.0.1004 + uses: ptr727/ProjectTemplate/.github/workflows/build-release-task.yml@ae63f7bf971dfac46c727d1005a822f0a957dd1e # 2.0.1020 permissions: contents: write actions: write diff --git a/.github/workflows/test-pull-request.yml b/.github/workflows/test-pull-request.yml index 1eca770..19cc8d3 100644 --- a/.github/workflows/test-pull-request.yml +++ b/.github/workflows/test-pull-request.yml @@ -32,7 +32,7 @@ jobs: validate: name: Validate sources job - uses: ptr727/ProjectTemplate/.github/workflows/validate-task.yml@098016c83723e384c9da0088c13a00ca14e02fd5 # 2.0.1004 + uses: ptr727/ProjectTemplate/.github/workflows/validate-task.yml@ae63f7bf971dfac46c727d1005a822f0a957dd1e # 2.0.1020 permissions: contents: read secrets: From 28c1eea6d68afb749990d1aba4d6b02fa4d3c241 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Oct 2026 06:12:43 +0000 Subject: [PATCH 02/14] Bump the actions-deps group with 4 updates (#245) Bumps the actions-deps group with 4 updates: [ptr727/ProjectTemplate/.github/workflows/merge-bot-task.yml](https://github.com/ptr727/projecttemplate), [ptr727/ProjectTemplate/.github/workflows/publish-plan-task.yml](https://github.com/ptr727/projecttemplate), [ptr727/ProjectTemplate/.github/workflows/validate-task.yml](https://github.com/ptr727/projecttemplate) and [ptr727/ProjectTemplate/.github/workflows/build-release-task.yml](https://github.com/ptr727/projecttemplate). Updates `ptr727/ProjectTemplate/.github/workflows/merge-bot-task.yml` from 2.0.1020 to 2.0.1074 - [Release notes](https://github.com/ptr727/projecttemplate/releases) - [Changelog](https://github.com/ptr727/ProjectTemplate/blob/main/HISTORY.md) - [Commits](https://github.com/ptr727/projecttemplate/compare/ae63f7bf971dfac46c727d1005a822f0a957dd1e...0f1ac327fc545940dd5e38cf24a8a36a00439ec9) Updates `ptr727/ProjectTemplate/.github/workflows/publish-plan-task.yml` from 2.0.1020 to 2.0.1074 - [Release notes](https://github.com/ptr727/projecttemplate/releases) - [Changelog](https://github.com/ptr727/ProjectTemplate/blob/main/HISTORY.md) - [Commits](https://github.com/ptr727/projecttemplate/compare/ae63f7bf971dfac46c727d1005a822f0a957dd1e...0f1ac327fc545940dd5e38cf24a8a36a00439ec9) Updates `ptr727/ProjectTemplate/.github/workflows/validate-task.yml` from 2.0.1020 to 2.0.1074 - [Release notes](https://github.com/ptr727/projecttemplate/releases) - [Changelog](https://github.com/ptr727/ProjectTemplate/blob/main/HISTORY.md) - [Commits](https://github.com/ptr727/projecttemplate/compare/ae63f7bf971dfac46c727d1005a822f0a957dd1e...0f1ac327fc545940dd5e38cf24a8a36a00439ec9) Updates `ptr727/ProjectTemplate/.github/workflows/build-release-task.yml` from 2.0.1020 to 2.0.1074 - [Release notes](https://github.com/ptr727/projecttemplate/releases) - [Changelog](https://github.com/ptr727/ProjectTemplate/blob/main/HISTORY.md) - [Commits](https://github.com/ptr727/projecttemplate/compare/ae63f7bf971dfac46c727d1005a822f0a957dd1e...0f1ac327fc545940dd5e38cf24a8a36a00439ec9) --- updated-dependencies: - dependency-name: ptr727/ProjectTemplate/.github/workflows/merge-bot-task.yml dependency-version: 2.0.1074 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions-deps - dependency-name: ptr727/ProjectTemplate/.github/workflows/publish-plan-task.yml dependency-version: 2.0.1074 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions-deps - dependency-name: ptr727/ProjectTemplate/.github/workflows/validate-task.yml dependency-version: 2.0.1074 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions-deps - dependency-name: ptr727/ProjectTemplate/.github/workflows/build-release-task.yml dependency-version: 2.0.1074 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions-deps ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/merge-bot-pull-request.yml | 2 +- .github/workflows/publish-release.yml | 6 +++--- .github/workflows/test-pull-request.yml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/merge-bot-pull-request.yml b/.github/workflows/merge-bot-pull-request.yml index e7ad5f1..69450fb 100644 --- a/.github/workflows/merge-bot-pull-request.yml +++ b/.github/workflows/merge-bot-pull-request.yml @@ -19,7 +19,7 @@ jobs: merge-bot: name: Merge bot pull request job - uses: ptr727/ProjectTemplate/.github/workflows/merge-bot-task.yml@ae63f7bf971dfac46c727d1005a822f0a957dd1e # 2.0.1020 + uses: ptr727/ProjectTemplate/.github/workflows/merge-bot-task.yml@0f1ac327fc545940dd5e38cf24a8a36a00439ec9 # 2.0.1074 secrets: CODEGEN_APP_CLIENT_ID: ${{ secrets.CODEGEN_APP_CLIENT_ID }} CODEGEN_APP_PRIVATE_KEY: ${{ secrets.CODEGEN_APP_PRIVATE_KEY }} diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 7210e08..d10d870 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -28,7 +28,7 @@ jobs: plan: name: Plan release job needs: [assert-dispatch-ref] - uses: ptr727/ProjectTemplate/.github/workflows/publish-plan-task.yml@ae63f7bf971dfac46c727d1005a822f0a957dd1e # 2.0.1020 + uses: ptr727/ProjectTemplate/.github/workflows/publish-plan-task.yml@0f1ac327fc545940dd5e38cf24a8a36a00439ec9 # 2.0.1074 with: event_name: ${{ github.event_name }} actor: ${{ github.actor }} @@ -38,7 +38,7 @@ jobs: name: Validate sources job needs: [plan] if: ${{ needs.plan.outputs.publish == 'true' }} - uses: ptr727/ProjectTemplate/.github/workflows/validate-task.yml@ae63f7bf971dfac46c727d1005a822f0a957dd1e # 2.0.1020 + uses: ptr727/ProjectTemplate/.github/workflows/validate-task.yml@0f1ac327fc545940dd5e38cf24a8a36a00439ec9 # 2.0.1074 permissions: contents: read secrets: @@ -48,7 +48,7 @@ jobs: name: Publish project release job needs: [plan, validate] if: ${{ needs.plan.outputs.publish == 'true' && needs.validate.result == 'success' }} - uses: ptr727/ProjectTemplate/.github/workflows/build-release-task.yml@ae63f7bf971dfac46c727d1005a822f0a957dd1e # 2.0.1020 + uses: ptr727/ProjectTemplate/.github/workflows/build-release-task.yml@0f1ac327fc545940dd5e38cf24a8a36a00439ec9 # 2.0.1074 permissions: contents: write actions: write diff --git a/.github/workflows/test-pull-request.yml b/.github/workflows/test-pull-request.yml index 19cc8d3..9478abb 100644 --- a/.github/workflows/test-pull-request.yml +++ b/.github/workflows/test-pull-request.yml @@ -32,7 +32,7 @@ jobs: validate: name: Validate sources job - uses: ptr727/ProjectTemplate/.github/workflows/validate-task.yml@ae63f7bf971dfac46c727d1005a822f0a957dd1e # 2.0.1020 + uses: ptr727/ProjectTemplate/.github/workflows/validate-task.yml@0f1ac327fc545940dd5e38cf24a8a36a00439ec9 # 2.0.1074 permissions: contents: read secrets: From 49a2e71af314f2a9c33d4137b06075463cebfe5e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Oct 2026 06:22:41 +0000 Subject: [PATCH 03/14] Bump ruff from 0.17.0 to 0.17.1 in the pypi-deps group (#247) Bumps the pypi-deps group with 1 update: [ruff](https://github.com/astral-sh/ruff). Updates `ruff` from 0.17.0 to 0.17.1 - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/0.17.0...0.17.1) --- updated-dependencies: - dependency-name: ruff dependency-version: 0.17.1 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: pypi-deps ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- uv.lock | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/uv.lock b/uv.lock index 21c7bd8..33ae348 100644 --- a/uv.lock +++ b/uv.lock @@ -328,27 +328,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.17.0" +version = "0.17.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e9/a7/70debb024dfacda67b8e560cc7511f52b34b9348a8cc8c1ec23036dcd51d/ruff-0.17.0.tar.gz", hash = "sha256:5cd03240d8208a557c2a9655a5cb07ebe36aa6bb35065f97d48c1f6adef5a322", size = 5320554, upload-time = "2026-10-09T19:47:29.248Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e7/e6d5749983b020e6cb1055abec0a6f361a8e94f0184cdcf56d4760995f02/ruff-0.17.1.tar.gz", hash = "sha256:5bb796c5112e9fb9527f2ef2faa0a130d6312d19de92bcfaddd70dbb2d41020f", size = 5323349, upload-time = "2026-10-15T15:23:18.64Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/f8/ee5ab9da6089eae2a33e6008b01deb1eda19992c1c8e10661e98cee1640f/ruff-0.17.0-py3-none-linux_armv6l.whl", hash = "sha256:0e271826af9a20d18c6cfae8c51e82959167c24859686ddd3eb9a7f0842ce81e", size = 10148907, upload-time = "2026-10-09T19:46:38.695Z" }, - { url = "https://files.pythonhosted.org/packages/9f/d9/2f81fb5a9d580afbb11b1c8ff915233a11f2a1b27405d7991f183c5e1976/ruff-0.17.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5f0ca4a40f81403689c04f12966e22f44e329ae362072d8f1587b7bda87f603b", size = 10224208, upload-time = "2026-10-09T19:46:41.711Z" }, - { url = "https://files.pythonhosted.org/packages/a7/20/643f3c8f75594f937b2bf74801241c56a2e2b8e139d24dff8b66b28cdd7f/ruff-0.17.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:cbf7149e0927dc3295d5d64679a4765576eef71b00782b2ae969ef82274d6bb9", size = 9273324, upload-time = "2026-10-09T19:46:44.323Z" }, - { url = "https://files.pythonhosted.org/packages/ec/91/627700b233d367736cb274f1bd0b47d1f2b12f68878192812bd875adadc3/ruff-0.17.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13ee90156522998c3037059d8f66885c8adeeaf7643bdce2caceee196ecd23e0", size = 9953190, upload-time = "2026-10-09T19:46:47.155Z" }, - { url = "https://files.pythonhosted.org/packages/cd/92/91f7b5ed39490f89d6cbf56e1f543c383667a725efa8e2c2dee0f01f5591/ruff-0.17.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3d8e4a002a94cd9d0dc48b51dc69d807a172b5b9bf2b668e656424dc5b55ead1", size = 9961746, upload-time = "2026-10-09T19:46:50.098Z" }, - { url = "https://files.pythonhosted.org/packages/87/c5/7310f9fc63ce11ff6394edbd5e85433dfb0e14c9fbf6ccc97f1538491bc7/ruff-0.17.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c0b8a60c06a218c337e1161638d34757f83449243e2db161483ddf948e53ad14", size = 10625483, upload-time = "2026-10-09T19:46:53.379Z" }, - { url = "https://files.pythonhosted.org/packages/a5/8d/97443f0dca4a03a0bc7629fd396fd494a1cb6666121e38c5075acb217d8f/ruff-0.17.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a330178bdffc4205dbf3bda11d93e059e388fd6546f8cdd304501a9160363c0d", size = 11350129, upload-time = "2026-10-09T19:46:56.486Z" }, - { url = "https://files.pythonhosted.org/packages/9c/0a/c525efd9777be4b6b012e6969a3012648468e7e6c4b3e5b46af69f46e8eb/ruff-0.17.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7bb08489e234876fa2da67ae3ea938e9a2156da80293e0e4365abd6973d98329", size = 10906223, upload-time = "2026-10-09T19:47:00.263Z" }, - { url = "https://files.pythonhosted.org/packages/2c/3c/4a01195d93420cad1175bedad13a515dc8a56f95a6e39789b92e582682f5/ruff-0.17.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc73e7c133e82d55b5f15897b2a442d72c0cb4a0c886c46801ce3c247150b60c", size = 10386314, upload-time = "2026-10-09T19:47:03.057Z" }, - { url = "https://files.pythonhosted.org/packages/c7/72/1a3951665485a921f6375f91e754a1854d5a645d41acc3642668064ff64d/ruff-0.17.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:db4f74c533403ab70fe4007873f6ae0c9f94a8b03158cf48d78788e47cdbe399", size = 10565464, upload-time = "2026-10-09T19:47:05.831Z" }, - { url = "https://files.pythonhosted.org/packages/90/8c/539b4d8c082f57e18db8ae2be85a460d77861c79dcd5798e32b536a6a06f/ruff-0.17.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3d8cc360e666d1914e47b0777c6906d70cf18891a55532bd0a16844195d70859", size = 10079468, upload-time = "2026-10-09T19:47:08.617Z" }, - { url = "https://files.pythonhosted.org/packages/e0/b8/84286966db79434e8c26b585b0a0f6897cb3ab1c51a4aa4df10c28488b62/ruff-0.17.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d66de796b726c4801e05fa99a2a8d7a780e107be222486c304ab61765561e866", size = 9958556, upload-time = "2026-10-09T19:47:11.324Z" }, - { url = "https://files.pythonhosted.org/packages/69/50/27b6eed27b83fcdd5bfa0d52b83231e29094754374698da404d094487ae3/ruff-0.17.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c3f268baf004aea944f040623327119527ea231af15f7fb7890e82cea0679589", size = 10352173, upload-time = "2026-10-09T19:47:14.188Z" }, - { url = "https://files.pythonhosted.org/packages/2a/fa/955399fd13044cd827862044117d784a59e3196f6cce7424908ac9a7f914/ruff-0.17.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:864b6c1acb6b0bccf94b5a3938a1531fd09aaca5e5659a2e7bf0f3cf2a685540", size = 10791761, upload-time = "2026-10-09T19:47:16.931Z" }, - { url = "https://files.pythonhosted.org/packages/ae/bf/024e01e1f6aec87768696725e648ed5b438941341eea8f8100beb681961f/ruff-0.17.0-py3-none-win32.whl", hash = "sha256:5e50aa5b84decd9fe5b0bb0e6f71c3b592f1767ed09faa4b7207d933961e35cd", size = 9904112, upload-time = "2026-10-09T19:47:19.75Z" }, - { url = "https://files.pythonhosted.org/packages/cc/77/1ee73df41dcc8d1cdb686ee4bc46ea29704ea175feb6b95c78420f631ab8/ruff-0.17.0-py3-none-win_amd64.whl", hash = "sha256:8ab76bcda86dfd28e13776cb5de3c7bcdcf1ae3d37ed761113d1a5a415dc134c", size = 10115585, upload-time = "2026-10-09T19:47:22.698Z" }, - { url = "https://files.pythonhosted.org/packages/fd/71/eb4f0ccc844aece56963e8578df9c95d4c00f580547d52329d4035d3af18/ruff-0.17.0-py3-none-win_arm64.whl", hash = "sha256:c154c73ff43f9854395e24cac507af13078962e53d2b511605058d22af1fdb88", size = 9872035, upload-time = "2026-10-09T19:47:26.306Z" }, + { url = "https://files.pythonhosted.org/packages/2e/78/90c46ca6250709f7c37e397b60d06061adee8d0f7d13959ed6a8e49b14fd/ruff-0.17.1-py3-none-linux_armv6l.whl", hash = "sha256:1796c41eafec66f4635125dd1e074a7aa27b8d20c187d484360acf11d247fc4a", size = 10147528, upload-time = "2026-10-15T15:22:33.667Z" }, + { url = "https://files.pythonhosted.org/packages/aa/d2/a2e28bc35b083ae51fa07dfba5280a667c8b01ecaee7802fc8849dcd5c46/ruff-0.17.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:8eadb09a788010be29c895418bdfa513e4e1c65e64b94e5b19e4cd381f6cc7fb", size = 10225974, upload-time = "2026-10-15T15:22:36.328Z" }, + { url = "https://files.pythonhosted.org/packages/8e/71/7a9029a81c8206ee01a98f6cfeeb56e6c095af86857581a37045c46d7e94/ruff-0.17.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:29b454f18ca3eeaf37fbf140841dcf27d36109e29dee9aae8acecb68c26c6f74", size = 9270361, upload-time = "2026-10-15T15:22:38.651Z" }, + { url = "https://files.pythonhosted.org/packages/c0/8c/e075e9c917cb0575297880c96768304e881240171c870cbd8d9b7b54bec2/ruff-0.17.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:872e03dd231744d6c5ec5c8af6be52b71323fc6ee096e4c8b34d9f844ee3b978", size = 9938300, upload-time = "2026-10-15T15:22:41.284Z" }, + { url = "https://files.pythonhosted.org/packages/87/27/0b4bb926a49de2383f161272b4d863571a4c7ecdbf595a6bc58813a13367/ruff-0.17.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4c0d6cb5f8fbac77eec9af49f4fc0eaf856b05545e372b0694d24b4e76b1c640", size = 9948464, upload-time = "2026-10-15T15:22:43.681Z" }, + { url = "https://files.pythonhosted.org/packages/14/27/518d57e0e02aecb8ea4acac6b57b51a53173d25fa734952bfdd7d531e88e/ruff-0.17.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78beced4a3c94dfedbee4b999b95b6a27b35820fdc0afbacc6d37a8da26c1cd1", size = 10638639, upload-time = "2026-10-15T15:22:46.081Z" }, + { url = "https://files.pythonhosted.org/packages/01/82/d89a4e498ade59e6cee52d67f8da569019372212b87468c1fdeae884601c/ruff-0.17.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7feefc63921202894f0e158b0e32f45a5950d70a7fb693ccb9264816c25b1c05", size = 11337238, upload-time = "2026-10-15T15:22:48.894Z" }, + { url = "https://files.pythonhosted.org/packages/a2/7c/3b0fdef9352656317c977086fe1d802902406e4f2a0d8578da90c0e6d1c5/ruff-0.17.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:684154861a819bf1f433669d2a0ce9c3e54677c65b8ebb866cc2a7ec86345785", size = 10896327, upload-time = "2026-10-15T15:22:51.45Z" }, + { url = "https://files.pythonhosted.org/packages/62/86/5566290219708ce2928f3a29c4f843a1185b341ec6111c4631a33c5ceced/ruff-0.17.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6ef98e51de8ef38082f90653ceeb84944bd7673783eea13e1647c5b9b064189", size = 10350811, upload-time = "2026-10-15T15:22:54.401Z" }, + { url = "https://files.pythonhosted.org/packages/ad/d2/ca8462dabb3c413990d49703ba306c30c862195a75448d080fa8fcce9775/ruff-0.17.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:8f9aa5b5bd5fa1937803ea609c8f153b8de39bf3e0e149897ae34b0c5b52f467", size = 10569994, upload-time = "2026-10-15T15:22:57.142Z" }, + { url = "https://files.pythonhosted.org/packages/e3/ad/dd1864c1f1db17c8cfb297ce70b352388e6977620f66f7182c52855407df/ruff-0.17.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:80e53b0e5faa3c5667f035daaa032893ae42b7cfe708c886d39ed131e54688ab", size = 10059672, upload-time = "2026-10-15T15:23:00.084Z" }, + { url = "https://files.pythonhosted.org/packages/65/45/3f56cf1d2168c2165cdd5ac6f9668aee66a4cfdd4caf2b2cc5ccb2b1d568/ruff-0.17.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:056c5d5fa0ac790051ae165d7035c7bf956d02de2db557f2e9b31d877a505fca", size = 9947803, upload-time = "2026-10-15T15:23:03.132Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4b/5457cb2be4c1d1ea5e919558131a0f197a4a454a0530d9e30820e8206e4c/ruff-0.17.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:bd8f5a55b10f371642eacd0e8665a42a8b97da15c9a7187c79e697b77b5e0ce4", size = 10351807, upload-time = "2026-10-15T15:23:05.628Z" }, + { url = "https://files.pythonhosted.org/packages/49/fb/db15b4f8fa24a1e36b99e1401f42ef6df16ee299a48670d5d262b771e7d7/ruff-0.17.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ce05e6f3973611d9550a600e04bb531061e368baba31610eb57c1509a8f21a0a", size = 10765716, upload-time = "2026-10-15T15:23:08.092Z" }, + { url = "https://files.pythonhosted.org/packages/0a/e8/1e186578076aff7a80735f625005fde678412ab609cd323ed182eb7d6b87/ruff-0.17.1-py3-none-win32.whl", hash = "sha256:1de0030a8aa78c65c5fecb84b2e5c390ac684c0452875a02afc7c083d49a496a", size = 9895093, upload-time = "2026-10-15T15:23:10.582Z" }, + { url = "https://files.pythonhosted.org/packages/6a/2e/da3d9589bae8e8b58f3a48bd5bc0b980232fb14a83bf02c3be7660f87c1b/ruff-0.17.1-py3-none-win_amd64.whl", hash = "sha256:5df76f16580fab58d0f6c98a3ac254a4abceb7b2489b8b678335869cff77b3d7", size = 10097955, upload-time = "2026-10-15T15:23:13.309Z" }, + { url = "https://files.pythonhosted.org/packages/df/c9/0f2a4dea06a5e12c1191ee3d96b1e1233797d0d5e18f97a2b909a95a9836/ruff-0.17.1-py3-none-win_arm64.whl", hash = "sha256:42400b9a6ff8515ff27796f07f92e0195be011c86e81e381606f58ea5502fef4", size = 9853542, upload-time = "2026-10-15T15:23:16.05Z" }, ] [[package]] From 4394aef16c56a7916ff21c9ad318127c1f0a46be Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Oct 2026 06:05:41 +0000 Subject: [PATCH 04/14] Bump the actions-deps group with 4 updates (#249) Bumps the actions-deps group with 4 updates: [ptr727/ProjectTemplate/.github/workflows/merge-bot-task.yml](https://github.com/ptr727/projecttemplate), [ptr727/ProjectTemplate/.github/workflows/publish-plan-task.yml](https://github.com/ptr727/projecttemplate), [ptr727/ProjectTemplate/.github/workflows/validate-task.yml](https://github.com/ptr727/projecttemplate) and [ptr727/ProjectTemplate/.github/workflows/build-release-task.yml](https://github.com/ptr727/projecttemplate). Updates `ptr727/ProjectTemplate/.github/workflows/merge-bot-task.yml` from 2.0.1074 to 2.0.1085 - [Release notes](https://github.com/ptr727/projecttemplate/releases) - [Changelog](https://github.com/ptr727/ProjectTemplate/blob/main/HISTORY.md) - [Commits](https://github.com/ptr727/projecttemplate/compare/0f1ac327fc545940dd5e38cf24a8a36a00439ec9...569af223195f1d3a0fe9b673f2e0c266f0f0f92e) Updates `ptr727/ProjectTemplate/.github/workflows/publish-plan-task.yml` from 2.0.1074 to 2.0.1085 - [Release notes](https://github.com/ptr727/projecttemplate/releases) - [Changelog](https://github.com/ptr727/ProjectTemplate/blob/main/HISTORY.md) - [Commits](https://github.com/ptr727/projecttemplate/compare/0f1ac327fc545940dd5e38cf24a8a36a00439ec9...569af223195f1d3a0fe9b673f2e0c266f0f0f92e) Updates `ptr727/ProjectTemplate/.github/workflows/validate-task.yml` from 2.0.1074 to 2.0.1085 - [Release notes](https://github.com/ptr727/projecttemplate/releases) - [Changelog](https://github.com/ptr727/ProjectTemplate/blob/main/HISTORY.md) - [Commits](https://github.com/ptr727/projecttemplate/compare/0f1ac327fc545940dd5e38cf24a8a36a00439ec9...569af223195f1d3a0fe9b673f2e0c266f0f0f92e) Updates `ptr727/ProjectTemplate/.github/workflows/build-release-task.yml` from 2.0.1074 to 2.0.1085 - [Release notes](https://github.com/ptr727/projecttemplate/releases) - [Changelog](https://github.com/ptr727/ProjectTemplate/blob/main/HISTORY.md) - [Commits](https://github.com/ptr727/projecttemplate/compare/0f1ac327fc545940dd5e38cf24a8a36a00439ec9...569af223195f1d3a0fe9b673f2e0c266f0f0f92e) --- updated-dependencies: - dependency-name: ptr727/ProjectTemplate/.github/workflows/merge-bot-task.yml dependency-version: 2.0.1085 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions-deps - dependency-name: ptr727/ProjectTemplate/.github/workflows/publish-plan-task.yml dependency-version: 2.0.1085 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions-deps - dependency-name: ptr727/ProjectTemplate/.github/workflows/validate-task.yml dependency-version: 2.0.1085 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions-deps - dependency-name: ptr727/ProjectTemplate/.github/workflows/build-release-task.yml dependency-version: 2.0.1085 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions-deps ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/merge-bot-pull-request.yml | 2 +- .github/workflows/publish-release.yml | 6 +++--- .github/workflows/test-pull-request.yml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/merge-bot-pull-request.yml b/.github/workflows/merge-bot-pull-request.yml index 69450fb..57f208a 100644 --- a/.github/workflows/merge-bot-pull-request.yml +++ b/.github/workflows/merge-bot-pull-request.yml @@ -19,7 +19,7 @@ jobs: merge-bot: name: Merge bot pull request job - uses: ptr727/ProjectTemplate/.github/workflows/merge-bot-task.yml@0f1ac327fc545940dd5e38cf24a8a36a00439ec9 # 2.0.1074 + uses: ptr727/ProjectTemplate/.github/workflows/merge-bot-task.yml@569af223195f1d3a0fe9b673f2e0c266f0f0f92e # 2.0.1085 secrets: CODEGEN_APP_CLIENT_ID: ${{ secrets.CODEGEN_APP_CLIENT_ID }} CODEGEN_APP_PRIVATE_KEY: ${{ secrets.CODEGEN_APP_PRIVATE_KEY }} diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index d10d870..f77b49d 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -28,7 +28,7 @@ jobs: plan: name: Plan release job needs: [assert-dispatch-ref] - uses: ptr727/ProjectTemplate/.github/workflows/publish-plan-task.yml@0f1ac327fc545940dd5e38cf24a8a36a00439ec9 # 2.0.1074 + uses: ptr727/ProjectTemplate/.github/workflows/publish-plan-task.yml@569af223195f1d3a0fe9b673f2e0c266f0f0f92e # 2.0.1085 with: event_name: ${{ github.event_name }} actor: ${{ github.actor }} @@ -38,7 +38,7 @@ jobs: name: Validate sources job needs: [plan] if: ${{ needs.plan.outputs.publish == 'true' }} - uses: ptr727/ProjectTemplate/.github/workflows/validate-task.yml@0f1ac327fc545940dd5e38cf24a8a36a00439ec9 # 2.0.1074 + uses: ptr727/ProjectTemplate/.github/workflows/validate-task.yml@569af223195f1d3a0fe9b673f2e0c266f0f0f92e # 2.0.1085 permissions: contents: read secrets: @@ -48,7 +48,7 @@ jobs: name: Publish project release job needs: [plan, validate] if: ${{ needs.plan.outputs.publish == 'true' && needs.validate.result == 'success' }} - uses: ptr727/ProjectTemplate/.github/workflows/build-release-task.yml@0f1ac327fc545940dd5e38cf24a8a36a00439ec9 # 2.0.1074 + uses: ptr727/ProjectTemplate/.github/workflows/build-release-task.yml@569af223195f1d3a0fe9b673f2e0c266f0f0f92e # 2.0.1085 permissions: contents: write actions: write diff --git a/.github/workflows/test-pull-request.yml b/.github/workflows/test-pull-request.yml index 9478abb..fa1b26e 100644 --- a/.github/workflows/test-pull-request.yml +++ b/.github/workflows/test-pull-request.yml @@ -32,7 +32,7 @@ jobs: validate: name: Validate sources job - uses: ptr727/ProjectTemplate/.github/workflows/validate-task.yml@0f1ac327fc545940dd5e38cf24a8a36a00439ec9 # 2.0.1074 + uses: ptr727/ProjectTemplate/.github/workflows/validate-task.yml@569af223195f1d3a0fe9b673f2e0c266f0f0f92e # 2.0.1085 permissions: contents: read secrets: From 5baed682c5fd836e56ee2aff5bae3dc9324a41a1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Oct 2026 06:05:46 +0000 Subject: [PATCH 05/14] Bump the actions-deps group with 4 updates (#251) Bumps the actions-deps group with 4 updates: [ptr727/ProjectTemplate/.github/workflows/merge-bot-task.yml](https://github.com/ptr727/projecttemplate), [ptr727/ProjectTemplate/.github/workflows/publish-plan-task.yml](https://github.com/ptr727/projecttemplate), [ptr727/ProjectTemplate/.github/workflows/validate-task.yml](https://github.com/ptr727/projecttemplate) and [ptr727/ProjectTemplate/.github/workflows/build-release-task.yml](https://github.com/ptr727/projecttemplate). Updates `ptr727/ProjectTemplate/.github/workflows/merge-bot-task.yml` from 2.0.1085 to 2.0.1090 - [Release notes](https://github.com/ptr727/projecttemplate/releases) - [Changelog](https://github.com/ptr727/ProjectTemplate/blob/main/HISTORY.md) - [Commits](https://github.com/ptr727/projecttemplate/compare/569af223195f1d3a0fe9b673f2e0c266f0f0f92e...07b0a520b47e850ac1b26ab47f83843a56a99461) Updates `ptr727/ProjectTemplate/.github/workflows/publish-plan-task.yml` from 2.0.1085 to 2.0.1090 - [Release notes](https://github.com/ptr727/projecttemplate/releases) - [Changelog](https://github.com/ptr727/ProjectTemplate/blob/main/HISTORY.md) - [Commits](https://github.com/ptr727/projecttemplate/compare/569af223195f1d3a0fe9b673f2e0c266f0f0f92e...07b0a520b47e850ac1b26ab47f83843a56a99461) Updates `ptr727/ProjectTemplate/.github/workflows/validate-task.yml` from 2.0.1085 to 2.0.1090 - [Release notes](https://github.com/ptr727/projecttemplate/releases) - [Changelog](https://github.com/ptr727/ProjectTemplate/blob/main/HISTORY.md) - [Commits](https://github.com/ptr727/projecttemplate/compare/569af223195f1d3a0fe9b673f2e0c266f0f0f92e...07b0a520b47e850ac1b26ab47f83843a56a99461) Updates `ptr727/ProjectTemplate/.github/workflows/build-release-task.yml` from 2.0.1085 to 2.0.1090 - [Release notes](https://github.com/ptr727/projecttemplate/releases) - [Changelog](https://github.com/ptr727/ProjectTemplate/blob/main/HISTORY.md) - [Commits](https://github.com/ptr727/projecttemplate/compare/569af223195f1d3a0fe9b673f2e0c266f0f0f92e...07b0a520b47e850ac1b26ab47f83843a56a99461) --- updated-dependencies: - dependency-name: ptr727/ProjectTemplate/.github/workflows/merge-bot-task.yml dependency-version: 2.0.1090 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions-deps - dependency-name: ptr727/ProjectTemplate/.github/workflows/publish-plan-task.yml dependency-version: 2.0.1090 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions-deps - dependency-name: ptr727/ProjectTemplate/.github/workflows/validate-task.yml dependency-version: 2.0.1090 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions-deps - dependency-name: ptr727/ProjectTemplate/.github/workflows/build-release-task.yml dependency-version: 2.0.1090 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions-deps ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/merge-bot-pull-request.yml | 2 +- .github/workflows/publish-release.yml | 6 +++--- .github/workflows/test-pull-request.yml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/merge-bot-pull-request.yml b/.github/workflows/merge-bot-pull-request.yml index 57f208a..53e9150 100644 --- a/.github/workflows/merge-bot-pull-request.yml +++ b/.github/workflows/merge-bot-pull-request.yml @@ -19,7 +19,7 @@ jobs: merge-bot: name: Merge bot pull request job - uses: ptr727/ProjectTemplate/.github/workflows/merge-bot-task.yml@569af223195f1d3a0fe9b673f2e0c266f0f0f92e # 2.0.1085 + uses: ptr727/ProjectTemplate/.github/workflows/merge-bot-task.yml@07b0a520b47e850ac1b26ab47f83843a56a99461 # 2.0.1090 secrets: CODEGEN_APP_CLIENT_ID: ${{ secrets.CODEGEN_APP_CLIENT_ID }} CODEGEN_APP_PRIVATE_KEY: ${{ secrets.CODEGEN_APP_PRIVATE_KEY }} diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index f77b49d..4dfd54c 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -28,7 +28,7 @@ jobs: plan: name: Plan release job needs: [assert-dispatch-ref] - uses: ptr727/ProjectTemplate/.github/workflows/publish-plan-task.yml@569af223195f1d3a0fe9b673f2e0c266f0f0f92e # 2.0.1085 + uses: ptr727/ProjectTemplate/.github/workflows/publish-plan-task.yml@07b0a520b47e850ac1b26ab47f83843a56a99461 # 2.0.1090 with: event_name: ${{ github.event_name }} actor: ${{ github.actor }} @@ -38,7 +38,7 @@ jobs: name: Validate sources job needs: [plan] if: ${{ needs.plan.outputs.publish == 'true' }} - uses: ptr727/ProjectTemplate/.github/workflows/validate-task.yml@569af223195f1d3a0fe9b673f2e0c266f0f0f92e # 2.0.1085 + uses: ptr727/ProjectTemplate/.github/workflows/validate-task.yml@07b0a520b47e850ac1b26ab47f83843a56a99461 # 2.0.1090 permissions: contents: read secrets: @@ -48,7 +48,7 @@ jobs: name: Publish project release job needs: [plan, validate] if: ${{ needs.plan.outputs.publish == 'true' && needs.validate.result == 'success' }} - uses: ptr727/ProjectTemplate/.github/workflows/build-release-task.yml@569af223195f1d3a0fe9b673f2e0c266f0f0f92e # 2.0.1085 + uses: ptr727/ProjectTemplate/.github/workflows/build-release-task.yml@07b0a520b47e850ac1b26ab47f83843a56a99461 # 2.0.1090 permissions: contents: write actions: write diff --git a/.github/workflows/test-pull-request.yml b/.github/workflows/test-pull-request.yml index fa1b26e..72ed8b8 100644 --- a/.github/workflows/test-pull-request.yml +++ b/.github/workflows/test-pull-request.yml @@ -32,7 +32,7 @@ jobs: validate: name: Validate sources job - uses: ptr727/ProjectTemplate/.github/workflows/validate-task.yml@569af223195f1d3a0fe9b673f2e0c266f0f0f92e # 2.0.1085 + uses: ptr727/ProjectTemplate/.github/workflows/validate-task.yml@07b0a520b47e850ac1b26ab47f83843a56a99461 # 2.0.1090 permissions: contents: read secrets: From b2dfb2cd33c8e35e9ef8e6ee36ba89781bcdc2f7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 Oct 2026 06:05:40 +0000 Subject: [PATCH 06/14] Bump the actions-deps group with 4 updates (#252) Bumps the actions-deps group with 4 updates: [ptr727/ProjectTemplate/.github/workflows/merge-bot-task.yml](https://github.com/ptr727/projecttemplate), [ptr727/ProjectTemplate/.github/workflows/publish-plan-task.yml](https://github.com/ptr727/projecttemplate), [ptr727/ProjectTemplate/.github/workflows/validate-task.yml](https://github.com/ptr727/projecttemplate) and [ptr727/ProjectTemplate/.github/workflows/build-release-task.yml](https://github.com/ptr727/projecttemplate). Updates `ptr727/ProjectTemplate/.github/workflows/merge-bot-task.yml` from 2.0.1090 to 2.0.1100 - [Release notes](https://github.com/ptr727/projecttemplate/releases) - [Changelog](https://github.com/ptr727/ProjectTemplate/blob/main/HISTORY.md) - [Commits](https://github.com/ptr727/projecttemplate/compare/07b0a520b47e850ac1b26ab47f83843a56a99461...46bf16f181e5d20f53aef238e6da7549d5e24250) Updates `ptr727/ProjectTemplate/.github/workflows/publish-plan-task.yml` from 2.0.1090 to 2.0.1100 - [Release notes](https://github.com/ptr727/projecttemplate/releases) - [Changelog](https://github.com/ptr727/ProjectTemplate/blob/main/HISTORY.md) - [Commits](https://github.com/ptr727/projecttemplate/compare/07b0a520b47e850ac1b26ab47f83843a56a99461...46bf16f181e5d20f53aef238e6da7549d5e24250) Updates `ptr727/ProjectTemplate/.github/workflows/validate-task.yml` from 2.0.1090 to 2.0.1100 - [Release notes](https://github.com/ptr727/projecttemplate/releases) - [Changelog](https://github.com/ptr727/ProjectTemplate/blob/main/HISTORY.md) - [Commits](https://github.com/ptr727/projecttemplate/compare/07b0a520b47e850ac1b26ab47f83843a56a99461...46bf16f181e5d20f53aef238e6da7549d5e24250) Updates `ptr727/ProjectTemplate/.github/workflows/build-release-task.yml` from 2.0.1090 to 2.0.1100 - [Release notes](https://github.com/ptr727/projecttemplate/releases) - [Changelog](https://github.com/ptr727/ProjectTemplate/blob/main/HISTORY.md) - [Commits](https://github.com/ptr727/projecttemplate/compare/07b0a520b47e850ac1b26ab47f83843a56a99461...46bf16f181e5d20f53aef238e6da7549d5e24250) --- updated-dependencies: - dependency-name: ptr727/ProjectTemplate/.github/workflows/merge-bot-task.yml dependency-version: 2.0.1100 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions-deps - dependency-name: ptr727/ProjectTemplate/.github/workflows/publish-plan-task.yml dependency-version: 2.0.1100 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions-deps - dependency-name: ptr727/ProjectTemplate/.github/workflows/validate-task.yml dependency-version: 2.0.1100 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions-deps - dependency-name: ptr727/ProjectTemplate/.github/workflows/build-release-task.yml dependency-version: 2.0.1100 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions-deps ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/merge-bot-pull-request.yml | 2 +- .github/workflows/publish-release.yml | 6 +++--- .github/workflows/test-pull-request.yml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/merge-bot-pull-request.yml b/.github/workflows/merge-bot-pull-request.yml index 53e9150..6c89dac 100644 --- a/.github/workflows/merge-bot-pull-request.yml +++ b/.github/workflows/merge-bot-pull-request.yml @@ -19,7 +19,7 @@ jobs: merge-bot: name: Merge bot pull request job - uses: ptr727/ProjectTemplate/.github/workflows/merge-bot-task.yml@07b0a520b47e850ac1b26ab47f83843a56a99461 # 2.0.1090 + uses: ptr727/ProjectTemplate/.github/workflows/merge-bot-task.yml@46bf16f181e5d20f53aef238e6da7549d5e24250 # 2.0.1100 secrets: CODEGEN_APP_CLIENT_ID: ${{ secrets.CODEGEN_APP_CLIENT_ID }} CODEGEN_APP_PRIVATE_KEY: ${{ secrets.CODEGEN_APP_PRIVATE_KEY }} diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 4dfd54c..f35be7c 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -28,7 +28,7 @@ jobs: plan: name: Plan release job needs: [assert-dispatch-ref] - uses: ptr727/ProjectTemplate/.github/workflows/publish-plan-task.yml@07b0a520b47e850ac1b26ab47f83843a56a99461 # 2.0.1090 + uses: ptr727/ProjectTemplate/.github/workflows/publish-plan-task.yml@46bf16f181e5d20f53aef238e6da7549d5e24250 # 2.0.1100 with: event_name: ${{ github.event_name }} actor: ${{ github.actor }} @@ -38,7 +38,7 @@ jobs: name: Validate sources job needs: [plan] if: ${{ needs.plan.outputs.publish == 'true' }} - uses: ptr727/ProjectTemplate/.github/workflows/validate-task.yml@07b0a520b47e850ac1b26ab47f83843a56a99461 # 2.0.1090 + uses: ptr727/ProjectTemplate/.github/workflows/validate-task.yml@46bf16f181e5d20f53aef238e6da7549d5e24250 # 2.0.1100 permissions: contents: read secrets: @@ -48,7 +48,7 @@ jobs: name: Publish project release job needs: [plan, validate] if: ${{ needs.plan.outputs.publish == 'true' && needs.validate.result == 'success' }} - uses: ptr727/ProjectTemplate/.github/workflows/build-release-task.yml@07b0a520b47e850ac1b26ab47f83843a56a99461 # 2.0.1090 + uses: ptr727/ProjectTemplate/.github/workflows/build-release-task.yml@46bf16f181e5d20f53aef238e6da7549d5e24250 # 2.0.1100 permissions: contents: write actions: write diff --git a/.github/workflows/test-pull-request.yml b/.github/workflows/test-pull-request.yml index 72ed8b8..eeb9b6d 100644 --- a/.github/workflows/test-pull-request.yml +++ b/.github/workflows/test-pull-request.yml @@ -32,7 +32,7 @@ jobs: validate: name: Validate sources job - uses: ptr727/ProjectTemplate/.github/workflows/validate-task.yml@07b0a520b47e850ac1b26ab47f83843a56a99461 # 2.0.1090 + uses: ptr727/ProjectTemplate/.github/workflows/validate-task.yml@46bf16f181e5d20f53aef238e6da7549d5e24250 # 2.0.1100 permissions: contents: read secrets: From 1bcd00d24154da80a02b57d7a8382142de4d743b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 Oct 2026 06:06:00 +0000 Subject: [PATCH 07/14] Bump the actions-deps group with 4 updates (#254) Bumps the actions-deps group with 4 updates: [ptr727/ProjectTemplate/.github/workflows/merge-bot-task.yml](https://github.com/ptr727/projecttemplate), [ptr727/ProjectTemplate/.github/workflows/publish-plan-task.yml](https://github.com/ptr727/projecttemplate), [ptr727/ProjectTemplate/.github/workflows/validate-task.yml](https://github.com/ptr727/projecttemplate) and [ptr727/ProjectTemplate/.github/workflows/build-release-task.yml](https://github.com/ptr727/projecttemplate). Updates `ptr727/ProjectTemplate/.github/workflows/merge-bot-task.yml` from 2.0.1100 to 2.0.1120 - [Release notes](https://github.com/ptr727/projecttemplate/releases) - [Changelog](https://github.com/ptr727/ProjectTemplate/blob/main/HISTORY.md) - [Commits](https://github.com/ptr727/projecttemplate/compare/46bf16f181e5d20f53aef238e6da7549d5e24250...4b10111c29a2b6200cf270e44e39846614f4a1c0) Updates `ptr727/ProjectTemplate/.github/workflows/publish-plan-task.yml` from 2.0.1100 to 2.0.1120 - [Release notes](https://github.com/ptr727/projecttemplate/releases) - [Changelog](https://github.com/ptr727/ProjectTemplate/blob/main/HISTORY.md) - [Commits](https://github.com/ptr727/projecttemplate/compare/46bf16f181e5d20f53aef238e6da7549d5e24250...4b10111c29a2b6200cf270e44e39846614f4a1c0) Updates `ptr727/ProjectTemplate/.github/workflows/validate-task.yml` from 2.0.1100 to 2.0.1120 - [Release notes](https://github.com/ptr727/projecttemplate/releases) - [Changelog](https://github.com/ptr727/ProjectTemplate/blob/main/HISTORY.md) - [Commits](https://github.com/ptr727/projecttemplate/compare/46bf16f181e5d20f53aef238e6da7549d5e24250...4b10111c29a2b6200cf270e44e39846614f4a1c0) Updates `ptr727/ProjectTemplate/.github/workflows/build-release-task.yml` from 2.0.1100 to 2.0.1120 - [Release notes](https://github.com/ptr727/projecttemplate/releases) - [Changelog](https://github.com/ptr727/ProjectTemplate/blob/main/HISTORY.md) - [Commits](https://github.com/ptr727/projecttemplate/compare/46bf16f181e5d20f53aef238e6da7549d5e24250...4b10111c29a2b6200cf270e44e39846614f4a1c0) --- updated-dependencies: - dependency-name: ptr727/ProjectTemplate/.github/workflows/merge-bot-task.yml dependency-version: 2.0.1120 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions-deps - dependency-name: ptr727/ProjectTemplate/.github/workflows/publish-plan-task.yml dependency-version: 2.0.1120 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions-deps - dependency-name: ptr727/ProjectTemplate/.github/workflows/validate-task.yml dependency-version: 2.0.1120 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions-deps - dependency-name: ptr727/ProjectTemplate/.github/workflows/build-release-task.yml dependency-version: 2.0.1120 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions-deps ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/merge-bot-pull-request.yml | 2 +- .github/workflows/publish-release.yml | 6 +++--- .github/workflows/test-pull-request.yml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/merge-bot-pull-request.yml b/.github/workflows/merge-bot-pull-request.yml index 6c89dac..3583acb 100644 --- a/.github/workflows/merge-bot-pull-request.yml +++ b/.github/workflows/merge-bot-pull-request.yml @@ -19,7 +19,7 @@ jobs: merge-bot: name: Merge bot pull request job - uses: ptr727/ProjectTemplate/.github/workflows/merge-bot-task.yml@46bf16f181e5d20f53aef238e6da7549d5e24250 # 2.0.1100 + uses: ptr727/ProjectTemplate/.github/workflows/merge-bot-task.yml@4b10111c29a2b6200cf270e44e39846614f4a1c0 # 2.0.1120 secrets: CODEGEN_APP_CLIENT_ID: ${{ secrets.CODEGEN_APP_CLIENT_ID }} CODEGEN_APP_PRIVATE_KEY: ${{ secrets.CODEGEN_APP_PRIVATE_KEY }} diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index f35be7c..dee607e 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -28,7 +28,7 @@ jobs: plan: name: Plan release job needs: [assert-dispatch-ref] - uses: ptr727/ProjectTemplate/.github/workflows/publish-plan-task.yml@46bf16f181e5d20f53aef238e6da7549d5e24250 # 2.0.1100 + uses: ptr727/ProjectTemplate/.github/workflows/publish-plan-task.yml@4b10111c29a2b6200cf270e44e39846614f4a1c0 # 2.0.1120 with: event_name: ${{ github.event_name }} actor: ${{ github.actor }} @@ -38,7 +38,7 @@ jobs: name: Validate sources job needs: [plan] if: ${{ needs.plan.outputs.publish == 'true' }} - uses: ptr727/ProjectTemplate/.github/workflows/validate-task.yml@46bf16f181e5d20f53aef238e6da7549d5e24250 # 2.0.1100 + uses: ptr727/ProjectTemplate/.github/workflows/validate-task.yml@4b10111c29a2b6200cf270e44e39846614f4a1c0 # 2.0.1120 permissions: contents: read secrets: @@ -48,7 +48,7 @@ jobs: name: Publish project release job needs: [plan, validate] if: ${{ needs.plan.outputs.publish == 'true' && needs.validate.result == 'success' }} - uses: ptr727/ProjectTemplate/.github/workflows/build-release-task.yml@46bf16f181e5d20f53aef238e6da7549d5e24250 # 2.0.1100 + uses: ptr727/ProjectTemplate/.github/workflows/build-release-task.yml@4b10111c29a2b6200cf270e44e39846614f4a1c0 # 2.0.1120 permissions: contents: write actions: write diff --git a/.github/workflows/test-pull-request.yml b/.github/workflows/test-pull-request.yml index eeb9b6d..41021bb 100644 --- a/.github/workflows/test-pull-request.yml +++ b/.github/workflows/test-pull-request.yml @@ -32,7 +32,7 @@ jobs: validate: name: Validate sources job - uses: ptr727/ProjectTemplate/.github/workflows/validate-task.yml@46bf16f181e5d20f53aef238e6da7549d5e24250 # 2.0.1100 + uses: ptr727/ProjectTemplate/.github/workflows/validate-task.yml@4b10111c29a2b6200cf270e44e39846614f4a1c0 # 2.0.1120 permissions: contents: read secrets: From 6b9aba1a13c3a9e065f9af6abb94707c94a68c25 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sat, 24 Oct 2026 19:39:31 -0700 Subject: [PATCH 08/14] Add M5Stack CoreS3 and Camera Overlay Templates (#257) * Add M5Stack CoreS3 and Camera Overlay Templates The CoreS3 is the first touchscreen board in the catalog. The board template carries the display, touch and backlight drivers, since each one depends on the AW9523B expander or an AXP2101 rail, and the example device draws a tabbed LVGL interface that exercises each part of the board. The camera is an opt-in overlay. Its pixel clock is GPIO45, the flash voltage strap, so a reset taken while it streams can leave the board looping in its bootloader. The board template holds the camera in reset, and the overlay releases it and holds it in reset again on shutdown. Verified on a bench unit over USB and OTA: display, touch, backlight, speaker, microphone, camera, RTC, light, proximity, IMU and PMU temperature. Battery sensing is untested. Co-Authored-By: Claude Opus 5.5 * Keep New CoreS3 Prose Under the Sentence Length Cap Co-Authored-By: Claude Opus 5.5 * Address Strict Review Findings on the CoreS3 Templates The RTC now sets the clock once at boot rather than polling, so it no longer overwrites a network time source every 15 minutes. The camera switch is named Camera Enable, since it drives the reset line rather than the rail, and the docs say re-enabling it needs a restart. The level bubble guards a NaN accelerometer state, the slider follows the backlight, and the idle wake restores the last brightness. Co-Authored-By: Claude Opus 5.5 * Wake the CoreS3 Screen From Any Backlight Turn-On The slider reads the light's target brightness, since the current values lag until the transition ends. Turning the backlight on from Home Assistant resumes LVGL and restarts the idle timeout, so the screen is live rather than a frozen frame. Co-Authored-By: Claude Opus 5.5 * Tie the CoreS3 Example Screen Sleep to the Backlight LVGL pauses whenever the backlight turns off and resumes whenever it turns on, so a tap wakes the screen however it went dark. The edge swipe ignores touches while the screen is dark, and the redundant redraw and activity call are gone, since resuming LVGL does both. The board header names the RTC write as tested and the boot-time read as checked in code only. Co-Authored-By: Claude Opus 5.5 * Keep the CoreS3 Touch Controller Scanning and Fix the Fade Tap The FT6336 drops to a slow monitor scan a few seconds after the last touch, so a quick tap on a dark screen often went unseen and waking it took several taps. The template now holds the controller in its active scan mode. The touch wake reads the light's target state, the same as the edge swipe, so a tap during the fade keeps the screen on. Co-Authored-By: Claude Opus 5.5 * Write the FT6336 Power Mode Through the Touchscreen Device The write uses the touchscreen's own I2C bus and address, so a device that overrides either keeps the fix. OPERATIONS.md records the monitor scan behavior and why the write runs at priority -100. Co-Authored-By: Claude Opus 5.5 * Name the FT6336 Register Before Its Value in OPERATIONS.md Co-Authored-By: Claude Opus 5.5 * List the CoreS3 Optional Substitutions in the README Entry Co-Authored-By: Claude Opus 5.5 --------- Co-authored-by: Claude Opus 5.5 --- HISTORY.md | 3 + OPERATIONS.md | 30 +- README.md | 34 +- cspell.json | 1 + templates/m5stack-cores3-camera.yaml | 81 +++++ templates/m5stack-cores3.yaml | 467 +++++++++++++++++++++++++++ test/m5stack-cores3.yaml | 456 ++++++++++++++++++++++++++ version.json | 2 +- 8 files changed, 1068 insertions(+), 6 deletions(-) create mode 100644 templates/m5stack-cores3-camera.yaml create mode 100644 templates/m5stack-cores3.yaml create mode 100644 test/m5stack-cores3.yaml diff --git a/HISTORY.md b/HISTORY.md index 11ee2a2..2479d5b 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -4,6 +4,9 @@ ESPHome configuration templates and projects. ## Release History +- Version 2.4: + - Added M5Stack CoreS3 support, the first touchscreen template, with an LVGL example device. + - Added an M5Stack CoreS3 camera overlay for the GC0308 camera. - Version 2.3: - Changed OTA to encryption only, using the API key, so only the upgrade bridge build still needs the `ota_password` secret. - Fixed the compile warnings the templates and the EasyStart component can address. diff --git a/OPERATIONS.md b/OPERATIONS.md index 155c891..676dd63 100644 --- a/OPERATIONS.md +++ b/OPERATIONS.md @@ -28,7 +28,7 @@ The mechanics below were confirmed against ESPHome 2026.7.3 by validating a scra Some templates deviate from the plain shorthand, and each says so in its own block: - [`easystart.yaml`][easystart-template] is parameterized, so it takes the `url` / `ref` / `files` form with `vars` rather than the one-line shorthand. -- The overlays are composed on their board template as a second package entry. The `esp32-s3-wroom-*` board definitions compose on [`esp32-s3-devkitc.yaml`][devkitc-template], and [`heltec-l76k-gnss.yaml`][heltec-gnss-template] and [`waveshare-esp32-s3-eth-camera.yaml`][waveshare-camera-template] on their own boards. +- The overlays are composed on their board template as a second package entry. The `esp32-s3-wroom-*` board definitions compose on [`esp32-s3-devkitc.yaml`][devkitc-template], and [`heltec-l76k-gnss.yaml`][heltec-gnss-template], [`waveshare-esp32-s3-eth-camera.yaml`][waveshare-camera-template] and [`m5stack-cores3-camera.yaml`][m5stack-cores3-camera-template] on their own boards. - [`templates/secrets.yaml`][secrets-template] is repository-internal plumbing and is not externally usable, since it re-exports a path that exists only in this tree. Every substitution a template defines or needs is declared in that template's own header. A template composing others also exposes their knobs, so a consumer reads the composed set rather than one file, and [`common.yaml`][common-template] is the usual composer. [`README.md`][readme] catalogs knobs for an adopter choosing a template, and where it names one, the header is what it has to agree with. @@ -288,7 +288,7 @@ Do not `!remove` blocks from Apollo's package without checking what depends on t [`templates/heltec-wifi-lora32-v4-r8.yaml`][heltec-template] is a board template. [`templates/heltec-l76k-gnss.yaml`][heltec-gnss-template] is an overlay for the GNSS module that plugs into its header. It composes as a second `packages:` entry, the way the camera overlay above composes onto its board template. - **What is confirmed on hardware is USB and OTA flashing, WiFi, the OLED, both rails, the GNSS UART and the battery ADC.** The user button is untested. The radio and front-end pins are documented rather than claimed, so nothing exercises them. -- **The board bus is an `i2c:` list entry, and a mapping there loses the bus in silence.** `merge_config` keeps only the consuming config's value when one side is a mapping and the other a list, so a consumer's list entry silently drops a template's mapping bus and nothing reports it. Two list entries concatenate instead, which is why [Waveshare ESP32-S3-ETH Camera][waveshare-camera] takes that form. A device on the merged bus then needs an explicit `i2c_id`, and says so with `Too many candidates found`. Seven templates declare a bus, all in the list form, and the six board buses use the id `board_i2c`. +- **The board bus is an `i2c:` list entry, and a mapping there loses the bus in silence.** `merge_config` keeps only the consuming config's value when one side is a mapping and the other a list. A consumer's list entry therefore drops a template's mapping bus, and nothing reports it. Two list entries concatenate instead, which is why [Waveshare ESP32-S3-ETH Camera][waveshare-camera] takes that form. A device on the merged bus then needs an explicit `i2c_id`, and says so with `Too many candidates found`. Every template that declares a bus uses the list form, and each board bus uses the id `board_i2c`. - **`Vext` gates the I2C bus and its pull-ups, so the rail has to come up before the bus.** The bus sets up at `BUS` priority and the `switch` raising the rail at `HARDWARE`, which is the wrong order. An `on_boot` handler at priority 1100 raises the rail first, through the IDF GPIO API. Without it the bus recovery reads SCL low and errors on every boot. - **The GNSS UART direction is the trap on this board, and the vendor macro names cause it.** MeshCore calls `Serial1.setPins(PIN_GPS_TX, PIN_GPS_RX)` into a signature that takes rx first. Its `PIN_GPS_RX=38` and `PIN_GPS_TX=39` therefore name the module's pins rather than the MCU's. The MCU side is tx on GPIO38 and rx on GPIO39. - **ESPHome writes the module nothing, which is what makes the overlay a test.** The `gps` component only reads NMEA, so a silent link is the module's own state. The `$PCAS` buttons are the only writes. They separate a dead link from a module whose saved configuration disabled NMEA output, since a version query answers in both cases. @@ -305,6 +305,30 @@ Do not `!remove` blocks from Apollo's package without checking what depends on t - **The SX1262 cannot be driven through ESPHome on this board.** The `sx126x` component reaches an RF switch only through the radio's own DIO2. The KCT8103L front-end is wired to three ESP32 pins instead, one selecting the transmit or receive path per packet. Those pins are documented and left unclaimed. - **A bench unit needs both reboot timeouts disabled.** Override `api_reboot_timeout` and `wifi_reboot_timeout` to `0s`. Every template that sets a reboot timeout reads the matching substitution, so this reaches far more than this board. An ethernet board carries no `wifi:` block, so the WiFi half does nothing there. A unit on a desk with no Home Assistant restarts part way through a cold GNSS acquisition. +### M5Stack CoreS3 + +[`templates/m5stack-cores3.yaml`][m5stack-cores3-template] is a board template. [`templates/m5stack-cores3-camera.yaml`][m5stack-cores3-camera-template] is an overlay for its camera, composed as a second `packages:` entry. + +- **The display, touch and backlight drivers sit in the board template, unlike the OLED boards.** The LCD reset is AW9523B pin 9, the touch reset is pin 0, and the backlight is the AXP2101's DLDO1 rail. A device writing its own display block would rebuild that plumbing. The display carries no lambda and `update_interval: never`, which is what LVGL needs. A lambda device extends `board_display` with its lambda, an update interval and `auto_clear_enabled: true`. +- **The AXP2101, AW9523B and AW88298 drivers are M5Stack's external components, pinned to a commit.** ESPHome core closed the pull requests for the first two and pointed at M5Stack's repository instead. That repository changes without releases, so moving the ref needs a bench re-test. +- **An AXP2101 rail output does nothing until something turns it on.** The output platform enables a rail only in `write_state`, so a declared rail keeps whatever the previous firmware or the PMU default left. An `on_boot` at priority 800 turns on every rail but the backlight's, which the backlight light turns on at priority 600. M5Stack's own YAML declares the rails without turning them on, which works only because its factory firmware already did. +- **The vendor AXP2101 component enables the PMU watchdog and never feeds it.** Its default mode raises an interrupt that does not reach the ESP32, and a bench unit ran for hours without a PMU reset. Do not switch the watchdog to a reset mode without adding a feed. +- **GPIO35 is both the display DC pin and the microSD MISO.** M5GFX switches the pin's function on every chip select and `mipi_spi` does not, which is one more reason the slot stays unclaimed. +- **The GC0308 has no JPEG encoder.** With the default `pixel_format: JPEG` the driver returns `ESP_ERR_NOT_SUPPORTED` and `esp32_camera` marks itself failed. `RGB565` with a `jpeg_quality` converts in software, which needs PSRAM. +- **VGA with auto exposure returned one byte-identical flat frame on the bench, every time.** QVGA with the manual exposure and gain from M5Stack's Stack-chan firmware returns a real image. Both changed together, so which one mattered is not established. +- **The camera's pixel clock is GPIO45, and GPIO45 picks the flash voltage at every reset.** The PMU keeps the camera powered across an ESP32 reset, so a streaming camera can hold the strap high. The flash then runs from the 1.8V LDO and the bootloader loops on misreads. Every reset taken with the camera streaming outside the shutdown hook looped on the bench, and every cold power on booted. The evidence, in order: + - The loop printed `rst:0x3 (RTC_SW_SYS_RST),boot:0x3c` and `partition 0 invalid magic number 0x10aa`, where the magic is `0x50aa`. + - The partition table read back from flash was byte-identical to the built one, so the bootloader misread intact flash. + - esptool's ESP32-S3 target defines `GPIO_STRAP_VDDSPI_MASK = 1 << 4`, and `0x3c` has bit 4 set, so GPIO45 read high. + - `espefuse summary` showed `VDD_SPI_FORCE` unburned, so the strap alone decides the flash voltage. +- **A power cycle recovers, and the overlay prevents the orderly cases.** Hold the power button for 6 seconds, since a reset leaves the camera streaming. The overlay's `on_shutdown` holds the camera in reset, and an OTA reboot with the camera streaming came back clean. The reset after an esptool flash bypasses that hook. Turn `Camera Enable` off over the API first, and a USB flash then boots without a power cycle. The switch drives the sensor's reset line rather than its rail, so turning it back on resets the sensor's registers and needs a restart. A crash or a brownout bypasses the shutdown hook as well. +- **Burning `VDD_SPI` to 3.3V removes the hazard, and nothing here does it.** `espefuse set-flash-voltage 3.3V` is permanent, and the flash voltage is inferred from behavior rather than read from a schematic. The template leaves it to the owner. +- **The camera web server asserts on reboot after the camera failed setup.** `esp32_camera_web_server` marks itself failed before it creates its semaphore, and its `on_shutdown` deletes the null semaphore in `vQueueDelete`. It shows as a crash on the reboot after an OTA, and a working camera never reaches it. +- **The microphone and the speaker share one I2S bus, so only one runs at a time.** The example device stops its `sound_level` sensor, waits until the microphone stops capturing, plays the tune, and restarts the sensor when playback finishes. A version without the wait froze the screen on a press, and the version with it has not. +- **The touch mapping matches M5GFX, 0 to 319 and 0 to 239 with no offset.** A raw capture over the API ran from y 0 at the top edge to 239 at the bottom. Fingertip taps read up to about 20 pixels low near the bottom and within 4 pixels near the top. That is finger contact rather than a panel offset, so the template does not correct for it. +- **The FT6336 drops quick taps once it falls into its monitor scan.** It slows its scan a few seconds after the last touch. Waking a dark screen then took several taps, and the touchscreen component logged one of three. ESPHome's `ft63x6` driver sets the active scan rate in register 0x88 and leaves the power mode in register 0x86 at its default. The board template writes 0 to register 0x86 through the touchscreen's own I2C device. It does so in an `on_boot` at priority -100, since the driver's setup resets the controller. A single tap then wakes the screen. +- **The example device swipes between tabs only from the screen edge.** LVGL's tabview swipe is a plain scroll that also starts on the slider and the drawing pad, so the example turns it off. A drag starting within 24 pixels of the left or right edge and moving 60 pixels inward changes tab instead. + ### RGB LED Status [`templates/rgb-led-status.yaml`][rgb-led-status-template] derives from [Flo-R1der's package][flo-r1der-link] but does not import it, because that version binds its state to `wifi:` `on_connect` triggers and so cannot run on an Ethernet board. @@ -667,6 +691,8 @@ Sharp edges in the tooling around this repository, each one learned by tripping [governance-write-safety]: ./GOVERNANCE.md#repository-boundaries-and-write-safety [heltec-gnss-template]: ./templates/heltec-l76k-gnss.yaml [heltec-template]: ./templates/heltec-wifi-lora32-v4-r8.yaml +[m5stack-cores3-camera-template]: ./templates/m5stack-cores3-camera.yaml +[m5stack-cores3-template]: ./templates/m5stack-cores3.yaml [max17048-template]: ./templates/max17048.yaml [min-version-template]: ./templates/min-version.yaml [office-bluetooth-proxy]: ./office-bluetooth-proxy.yaml diff --git a/README.md b/README.md index fa895ea..58e30b4 100755 --- a/README.md +++ b/README.md @@ -19,10 +19,10 @@ ESPHome configuration templates and projects. ### Release Notes -**Version 2.3**: +**Version 2.4**: -- Changed OTA to encryption only, using the API key, so only the upgrade bridge build still needs the `ota_password` secret. -- Fixed the compile warnings the templates and the EasyStart component can address. +- Added M5Stack CoreS3 support, the first touchscreen template, with an LVGL example device. +- Added an M5Stack CoreS3 camera overlay for the GC0308 camera. See [HISTORY.md][history] for the release notes. @@ -231,6 +231,27 @@ Shared building-block includes, composed via `packages:` by the device templates - The module is a Quectel L76K on a CASIC chipset, so it speaks PCAS sentences rather than u-blox UBX or MediaTek PMTK. Its substitutions, and the pin direction trap that swaps the UART pair if MeshCore's macros are read literally, are in the [overlay][heltec-l76k-gnss] header. - Note: the overlay is untested against an actual fix, because the bench unit had no sky view. The link, the direction and the protocol are confirmed. +#### M5Stack CoreS3 Devkit + +- [Template][m5stack-cores3] for the [M5Stack CoreS3][m5stack-shop-cores3-link], an ESP32-S3 with 16MB Quad Flash, 8MB Quad PSRAM, a 320x240 capacitive touch display, and an AXP2101 power management IC. The [CoreS3 docs][m5stack-docs-cores3-link] carry the pin map the template header repeats. +- Includes the display, the touchscreen, and the backlight as a dimmable light. +- Includes the RTC written through from Home Assistant time, the ambient light and proximity sensor, and pitch and roll from the IMU. +- Includes the microphone and speaker on a shared I2S bus, and the battery sensors. +- Includes switches for the 5V boost, the bus 5V output, and USB OTG power. +- Optional substitutions: + - `backlight_brightness`: `80%` by default, the level the backlight turns on at during boot. + - `bus_power_restore_mode`: `ALWAYS_OFF` by default, and `ALWAYS_ON` feeds 5V out to the M5-Bus and the Grove ports. + - `mic_gain`: `36dB` by default, the ES7210 microphone gain M5Stack's own configuration uses. +- Flash over USB-C. The port is the chip's native USB with no UART bridge, so no adapter is needed. +- The display and touch drivers live in the template, because their resets sit on the AW9523B expander and the backlight on an AXP2101 rail. The display carries no lambda, so a device draws on it with [LVGL][esphome-components-lvgl-link], as the [example device][test-m5stack-cores3] does with a tabbed touch interface. +- ESPHome core has no AXP2101, AW9523B, or AW88298 driver, so the template pulls those from [M5Stack's own components][github-m5stack-esphome-yaml-link], pinned to a commit. +- Note: units built from August 2026 carry an ILI9342E panel controller rather than the ILI9342C. The bench unit draws correctly on the ILI9342C init, and an ILI9342E unit is untested. +- Note: the microSD slot is documented rather than configured. ESPHome core has no SD card component, and the one external SPI component opens its own bus, which collides with the display. +- An optional [camera overlay][m5stack-cores3-camera] configures the GC0308 camera, composed as a second `packages:` entry. The board template holds the camera in reset without it. + - Optional substitutions: + - `camera_clock_pin`: `GPIO10` by default, a spare M5-Bus pin for the clock ESPHome requires although the camera clocks itself. Move it where a module uses `GPIO10`. + - Note: the camera's pixel clock is GPIO45, the pin the ESP32-S3 reads at reset to pick its flash voltage. A reset taken while the camera streams can leave the board looping in its bootloader until it is power cycled. The overlay holds the camera in reset on every orderly shutdown, which covers OTA, restart, and API reboots. Turn the `Camera Enable` switch off before flashing over USB, since the reset after a USB flash is not an orderly one. Turning it back on resets the sensor's registers, so restart afterwards. + ## Devices My deployed devices use the templates and helpers, and are documented in [DEVICES.md][devices]. @@ -327,6 +348,8 @@ Licensed under the MIT License. See [LICENSE][license] for details. [konnected-blaq]: ./templates/konnected-blaq.yaml [license]: ./LICENSE [logger]: ./templates/logger.yaml +[m5stack-cores3]: ./templates/m5stack-cores3.yaml +[m5stack-cores3-camera]: ./templates/m5stack-cores3-camera.yaml [max17048]: ./templates/max17048.yaml [min-version]: ./templates/min-version.yaml [norvi-enet-ae06-r]: ./templates/norvi-enet-ae06-r.yaml @@ -344,6 +367,7 @@ Licensed under the MIT License. See [LICENSE][license] for details. [templates]: ./templates/ [test]: ./test/ [test-heltec-wifi-lora32-v4-r8]: ./test/heltec-wifi-lora32-v4-r8.yaml +[test-m5stack-cores3]: ./test/m5stack-cores3.yaml [test-norvi-enet-ae06-r]: ./test/norvi-enet-ae06-r.yaml [time]: ./templates/time.yaml [unexpectedmaker-pros3d]: ./templates/unexpectedmaker-pros3d.yaml @@ -376,6 +400,7 @@ Licensed under the MIT License. See [LICENSE][license] for details. [elecrow-thinknode-m7-wiki-link]: https://www.elecrow.com/wiki/ThinkNode_M7_LoRaWAN_Wireless_Communication_Gateway_Support_PoE_Power.html [esphome-components-ethernet-link]: https://esphome.io/components/ethernet/ [esphome-components-gps-link]: https://esphome.io/components/gps/ +[esphome-components-lvgl-link]: https://esphome.io/components/lvgl/ [esphome-components-status-led-link]: https://esphome.io/components/status_led/ [esphome-components-sx126x-link]: https://esphome.io/components/sx126x/ [esphome-link]: https://esphome.io @@ -387,6 +412,7 @@ Licensed under the MIT License. See [LICENSE][license] for details. [github-esphome-feature-requests-513-link]: https://github.com/esphome/feature-requests/issues/513 [github-esphome-network-util-link]: https://github.com/esphome/esphome/blob/dev/esphome/components/network/util.h [github-konnected-io-konnected-esphome-link]: https://github.com/konnected-io/konnected-esphome/blob/master/garage-door-GDOv2-Q.yaml +[github-m5stack-esphome-yaml-link]: https://github.com/m5stack/esphome-yaml [github-ptr727-esphome-nonroot-link]: https://github.com/ptr727/ESPHome-NonRoot [github-smarthomeshop-ceilsense-link]: https://github.com/smarthomeshop/ceilsense/blob/main/ceilsense-v1/ceilsense-complete-wifi-ld2412.yaml [gl-inet-products-gl-s10-link]: https://www.gl-inet.com/products/gl-s10/ @@ -397,6 +423,8 @@ Licensed under the MIT License. See [LICENSE][license] for details. [itead-product-sonoff-th-link]: https://itead.cc/product/sonoff-th/ [kincony-esp32-sd-card-rtc-ds3231-link]: https://www.kincony.com/esp32-sd-card-rtc-ds3231.html [konnected-products-smart-garage-door-opener-blaq-myq-alternative-link]: https://konnected.io/products/smart-garage-door-opener-blaq-myq-alternative +[m5stack-docs-cores3-link]: https://docs.m5stack.com/en/core/CoreS3 +[m5stack-shop-cores3-link]: https://shop.m5stack.com/products/m5stack-cores3-esp32s3-iotdevelopment-kit [markdown-all-in-one-link]: https://marketplace.visualstudio.com/items?itemName=yzhang.markdown-all-in-one [markdownlint-cli2-link]: https://github.com/DavidAnson/markdownlint-cli2 [microair-link]: https://www.microair.net diff --git a/cspell.json b/cspell.json index 5d302a2..d9ee2d6 100644 --- a/cspell.json +++ b/cspell.json @@ -134,6 +134,7 @@ "logfile", "LOLIN", "lsusb", + "LVGL", "macvlan", "mdio", "mdns", diff --git a/templates/m5stack-cores3-camera.yaml b/templates/m5stack-cores3-camera.yaml new file mode 100644 index 0000000..33d134b --- /dev/null +++ b/templates/m5stack-cores3-camera.yaml @@ -0,0 +1,81 @@ +# External usage: +# packages: +# device: github://ptr727/ESPHome-Config/templates/m5stack-cores3.yaml@main +# camera: github://ptr727/ESPHome-Config/templates/m5stack-cores3-camera.yaml@main +# - An overlay, not a standalone device, so it is composed on the board template. +# - Secrets resolve from a secrets.yaml beside the including config, see secrets._yaml for the names. + +# Optional substitutions: +# - camera_clock_pin - GPIO10, a spare M5-Bus pin for the clock ESPHome insists on, move it if a module uses GPIO10. + +# M5Stack CoreS3 GC0308 camera +# Camera overlay for m5stack-cores3.yaml, which holds the camera in reset until this releases it. + +# The pixel clock is GPIO45, which the ESP32-S3 samples at every reset to pick the flash voltage. +# - The PMU keeps the camera powered across a reset, so a streaming camera can strap the flash to 1.8V. +# - The bootloader then misreads the partition table and loops, and only a power cycle clears it. +# - This overlay holds the camera in reset on every orderly shutdown, which covers OTA, restart and API reboots. +# - A crash, a brownout or the reset after a USB flash bypasses the shutdown, and a power cycle recovers. +# - Turning Camera Enable off before a USB flash avoids that power cycle. +# - Burning VDD_SPI to 3.3V removes the hazard, once the flash is confirmed a 3.3V part, and it is permanent. + +# GPIO39, GPIO40, GPIO41, GPIO42, GPIO15, GPIO16, GPIO48, GPIO47 : d0 to d7 +# GPIO46 : vsync +# GPIO38 : href +# GPIO45 : pclk +# - The module clocks itself, so no GPIO carries xclk, and esp32_camera still requires one. +# - The sensor registers sit on the board I2C bus at 0x21. + +# https://esphome.io/components/substitutions +substitutions: + camera_clock_pin: GPIO10 + +# https://esphome.io/components/esphome +esphome: + + # https://esphome.io/components/esphome#on-shutdown + on_shutdown: + - switch.turn_off: camera_reset + +# https://esphome.io/components/switch +switch: + + # The board template holds the camera in reset, and this releases it at boot. + # - Off holds the sensor in reset, and on resets its registers, so turning it back on needs a restart. + - id: !extend camera_reset + name: "Camera Enable" + restore_mode: ALWAYS_ON + internal: false + entity_category: config + +# https://esphome.io/components/esp32_camera +esp32_camera: + name: "Camera" + id: board_camera + i2c_id: board_i2c + data_pins: [GPIO39, GPIO40, GPIO41, GPIO42, GPIO15, GPIO16, GPIO48, GPIO47] + vsync_pin: + number: GPIO46 # Strapping pin, fixed board wiring, so the warning is acknowledged. + ignore_strapping_warning: true + href_pin: GPIO38 + pixel_clock_pin: + number: GPIO45 # Strapping pin, the flash voltage hazard above is why the shutdown hook exists. + ignore_strapping_warning: true + external_clock: + pin: ${camera_clock_pin} + + # The GC0308 has no JPEG encoder, so the driver refuses JPEG with ESP_ERR_NOT_SUPPORTED. + # - A quality turns on the software conversion to JPEG, which the API and the snapshot server need. + pixel_format: RGB565 + jpeg_quality: 12 + + # These are the values M5Stack's own Stack-chan firmware uses on this board. + # - VGA with auto exposure returned one flat frame on a bench unit, and these values return a real image. + # https://github.com/m5stack/esphome-yaml/blob/main/examples/kit/stackchan-bsp.factory.yaml + setup_priority: -200 + resolution: 320X240 + max_framerate: 15 fps + aec_mode: manual + aec_value: 800 + agc_mode: manual + agc_value: 30 diff --git a/templates/m5stack-cores3.yaml b/templates/m5stack-cores3.yaml new file mode 100644 index 0000000..fd2db3c --- /dev/null +++ b/templates/m5stack-cores3.yaml @@ -0,0 +1,467 @@ +# External usage: +# packages: +# device: github://ptr727/ESPHome-Config/templates/m5stack-cores3.yaml@main +# - Secrets resolve from a secrets.yaml beside the including config, see secrets._yaml for the names. + +# Required substitutions: +# - device_name - the ESPHome node name. +# - device_friendly_name - the Home Assistant display name. + +# Optional substitutions: +# - backlight_brightness - 80%, the level the backlight turns on at during boot. +# - bus_power_restore_mode - ALWAYS_OFF, ALWAYS_ON feeds 5V out to the M5-Bus and the Grove ports. +# - mic_gain - 36dB, the ES7210 microphone gain, M5Stack's own value. + +# M5Stack CoreS3 ESP32-S3 IoT Development Kit +# ESP32-S3, 16MB Quad Flash, 8MB Quad PSRAM +# https://shop.m5stack.com/products/m5stack-cores3-esp32s3-iotdevelopment-kit +# https://docs.m5stack.com/en/core/CoreS3 + +# Board tested: 2026-10-24, USB and OTA flash, display, touch, backlight, speaker, microphone, RTC write, light, proximity, IMU, PMU temperature. +# - The RTC read at boot is checked in code only. +# - Battery sensing is untested, since the bench unit had no cell fitted. +# - The bench unit draws correctly on the ILI9342C init, and a unit carrying an ILI9342E is untested. + +# The CoreS3-Lite carries the same hardware, the CoreS3-SE lacks the camera, the proximity sensor, the IMU and the battery. +# - On an SE those components report failed at boot and the rest runs, so remove them in the device. + +# The display, the touchscreen and the backlight are configured here, because each one depends on board plumbing. +# - The LCD and touch resets are AW9523B expander pins, and the backlight is an AXP2101 rail. +# - The display carries no lambda, so a device draws with LVGL, see test/m5stack-cores3.yaml. +# - A lambda device extends board_display with lambda, update_interval and auto_clear_enabled true instead. + +# ESPHome core carries no AXP2101, AW9523B or AW88298 driver, so those come from M5Stack's own components. +# - They are pinned to a commit, so an upstream change cannot alter a build that did not change. + +# GPIO19, GPIO20 : native USB, so flashing needs no adapter and no UART bridge +# GPIO43, GPIO44 : UART0, broken out to the M5-Bus, unused here + +# Internal I2C bus +# GPIO12 : sda +# GPIO11 : scl +# - 0x34 AXP2101 PMU, 0x58 AW9523B expander, 0x51 BM8563 RTC, 0x69 BMI270 IMU. +# - 0x23 LTR-553ALS light and proximity, 0x38 FT6336U touch, 0x21 GC0308 camera. +# - 0x40 ES7210 microphone ADC, 0x36 AW88298 speaker amplifier. +# - The BMI270 strap differs from the ESPHome default of 0x68, and the LTR-553 from 0x29. +# - The BMM150 magnetometer sits behind the BMI270 auxiliary bus, not on this one, and is not configured. + +# AW9523B expander pins +# 0 : touch reset +# 1 : BUS_OUT_EN, 5V out to the M5-Bus and Grove ports +# 2 : AW88298 reset +# 3 : ES7210 interrupt, unclaimed here +# 4 : microSD card detect, unclaimed here +# 5 : USB_OTG_EN +# 8 : camera reset +# 9 : LCD reset +# 10 : touch interrupt, unclaimed here +# 11 : AW88298 interrupt, unclaimed here +# 15 : BOOST_EN, the 5V boost +# - The expander's own interrupt output reaches GPIO21, so touch interrupts would need the expander to service them. +# - Touch is polled instead, which is what M5Stack's own configuration does. + +# AXP2101 rails +# DLDO1 : LCD backlight, its voltage is the brightness +# ALDO1 : 1.8V, AW88298 +# ALDO2 : 3.3V, ES7210 +# ALDO3 : 3.3V, camera +# ALDO4 : 3.3V, microSD slot +# BLDO1 : 2.8V, BLDO2 : 1.5V, M5Stack's values, which it does not document +# - The power button is wired to the PMU only, so it never reaches the ESP32. + +# SPI bus, shared by the LCD and the microSD slot +# GPIO36 : sclk +# GPIO37 : mosi +# GPIO35 : miso, which is also the LCD dc pin +# GPIO3 : LCD cs +# GPIO4 : microSD cs, documented rather than configured +# - ESPHome core has no SD card component, and the one SPI external component opens its own bus. +# - That bus would collide with the LCD on the same pins, so the slot is left unclaimed. + +# ILI9342 320x240 IPS, driven through the original Core's mipi_spi preset with the CoreS3 pins. +# - Units made from 2026-08-07 carry an ILI9342E, which M5Stack documents as needing a different init sequence. +# - The touch panel maps x 0 to 319 and y 0 to 239 with no offset, the same as M5Stack's own M5GFX driver. + +# I2S bus +# GPIO0 : mclk +# GPIO34 : bclk +# GPIO33 : lrclk +# GPIO13 : dout, to the AW88298 speaker amplifier +# GPIO14 : din, from the ES7210 microphone ADC +# - The microphone and the speaker share the bus, so only one of them runs at a time. + +# GC0308 camera, configured by the optional m5stack-cores3-camera.yaml overlay rather than here +# - Its pixel clock is GPIO45, the strap that picks the flash voltage at every reset. +# - The camera stays in reset here, so a board without the overlay boots whatever reset it takes. + +# Grove ports, left to the device +# Port A : GPIO2 sda, GPIO1 scl +# Port B : GPIO9, GPIO8 +# Port C : GPIO17 tx, GPIO18 rx +# - The Port 5V pins follow the Bus Power Out switch when the board runs from its battery. + +# https://esphome.io/components/substitutions +substitutions: + backlight_brightness: 80% + bus_power_restore_mode: ALWAYS_OFF + mic_gain: 36dB + +# https://esphome.io/components/esphome +esphome: + name: ${device_name} + friendly_name: ${device_friendly_name} + + # https://esphome.io/components/esphome#esphome-on_boot + on_boot: + + # The rail outputs set nothing until turned on, so a rail would otherwise keep whatever the last firmware left. + # - This runs after the PMU and before the ADC and the amplifier set up. + - priority: 800 + then: + - output.turn_on: amplifier_rail + - output.turn_on: microphone_rail + - output.turn_on: camera_rail + - output.turn_on: sd_card_rail + - output.turn_on: bldo1_rail + - output.turn_on: bldo2_rail + + - priority: 600 + then: + - light.turn_on: + id: lcd_backlight + brightness: ${backlight_brightness} + + # The RTC sets the clock once at boot, so it never overwrites a network time source later. + - bm8563.read_time: rtc_time + + # The FT6336 drops to a slow monitor scan a few seconds after the last touch, and a quick tap then goes unseen. + # - Register 0x86 at 0 keeps it scanning at the active rate, and this runs after the touchscreen setup resets it. + - priority: -100 + then: + - lambda: id(board_touch)->write_byte(0x86, 0x00); + +# https://esphome.io/components/esp32 +esp32: + # PlatformIO carries no CoreS3 board definition, and the DevKitC-1 profile matches the module. + # https://docs.platformio.org/en/latest/boards/espressif32/esp32-s3-devkitc-1.html + board: esp32-s3-devkitc-1 + + # The board definition does not set this, ESPHome defaults to 4MB. + flash_size: 16MB + + # https://esphome.io/components/esp32#esp-idf-framework + framework: + type: esp-idf + +# https://esphome.io/components/psram +# The PSRAM is a separate quad part, and ESPHome has no default mode on the S3. +psram: + mode: quad + speed: 80MHz + +# https://esphome.io/components/packages#local-packages +packages: + common: !include common.yaml + temperature: !include temperature.yaml + +# USB is the chip's own port, with no UART bridge on this board. +# https://esphome.io/components/logger +logger: + hardware_uart: USB_SERIAL_JTAG # UART0, UART1, USB_CDC, USB_SERIAL_JTAG + +# https://esphome.io/components/external_components +external_components: + + # https://github.com/m5stack/esphome-yaml/tree/main/components + - source: + type: git + url: https://github.com/m5stack/esphome-yaml + ref: 19eb639ca6dc9b31ea16ca1cd136a2ac262c6a03 + components: [axp2101, aw9523b, aw88298] + +# A list entry rather than the mapping shorthand, so a device's own bus merges instead of replacing this. +# https://esphome.io/components/i2c +i2c: + - id: board_i2c + sda: GPIO12 + scl: GPIO11 + scan: true + frequency: 400kHz + +# https://esphome.io/components/spi +spi: + - id: board_spi + clk_pin: GPIO36 + mosi_pin: GPIO37 + +# https://github.com/m5stack/esphome-yaml/tree/main/components/axp2101 +axp2101: + id: board_pmu + i2c_id: board_i2c + +# Port 0 drives the touch and amplifier resets, which need push-pull rather than the open drain default. +# https://github.com/m5stack/esphome-yaml/tree/main/components/aw9523b +aw9523b: + id: board_expander + i2c_id: board_i2c + p0_drive_mode: PUSH_PULL + +# https://esphome.io/components/output +output: + + # The range keeps the backlight visible at the light's lowest brightness. + - platform: axp2101 + id: lcd_backlight_output + type: range + channel: DLDO1 + min_voltage: 2600 + max_voltage: 3300 + + - platform: axp2101 + id: amplifier_rail + channel: ALDO1 + voltage: 1800 + + - platform: axp2101 + id: microphone_rail + channel: ALDO2 + voltage: 3300 + + - platform: axp2101 + id: camera_rail + channel: ALDO3 + voltage: 3300 + + - platform: axp2101 + id: sd_card_rail + channel: ALDO4 + voltage: 3300 + + - platform: axp2101 + id: bldo1_rail + channel: BLDO1 + voltage: 2800 + + - platform: axp2101 + id: bldo2_rail + channel: BLDO2 + voltage: 1500 + +# https://esphome.io/components/light/monochromatic +light: + - platform: monochromatic + name: "Backlight" + id: lcd_backlight + output: lcd_backlight_output + default_transition_length: 250ms + restore_mode: ALWAYS_OFF + +# https://esphome.io/components/switch +switch: + + # https://esphome.io/components/switch/gpio + - platform: gpio + name: "Boost Power" + id: boost_power + pin: + aw9523b_id: board_expander + number: 15 + restore_mode: ALWAYS_ON + entity_category: config + + # https://esphome.io/components/switch/gpio + - platform: gpio + name: "Bus Power Out" + id: bus_power_out + pin: + aw9523b_id: board_expander + number: 1 + restore_mode: ${bus_power_restore_mode} + entity_category: config + + # https://esphome.io/components/switch/gpio + - platform: gpio + name: "USB OTG Power" + id: usb_otg_power + pin: + aw9523b_id: board_expander + number: 5 + restore_mode: ALWAYS_OFF + entity_category: config + + # Low holds the amplifier in reset. + # https://esphome.io/components/switch/gpio + - platform: gpio + id: amplifier_reset + pin: + aw9523b_id: board_expander + number: 2 + restore_mode: ALWAYS_ON + internal: true + + # Low holds the camera in reset, and the camera overlay is what releases it. + # https://esphome.io/components/switch/gpio + - platform: gpio + id: camera_reset + pin: + aw9523b_id: board_expander + number: 8 + restore_mode: ALWAYS_OFF + internal: true + +# https://esphome.io/components/display/mipi_spi +display: + - platform: mipi_spi + id: board_display + spi_id: board_spi + model: M5CORE + dc_pin: GPIO35 + cs_pin: + number: GPIO3 # Strapping pin, fixed board wiring, so the warning is acknowledged. + ignore_strapping_warning: true + reset_pin: + aw9523b_id: board_expander + number: 9 + invert_colors: true + data_rate: 40MHz + auto_clear_enabled: false + update_interval: never + +# https://esphome.io/components/touchscreen/ft63x6 +touchscreen: + - platform: ft63x6 + id: board_touch + i2c_id: board_i2c + display: board_display + reset_pin: + aw9523b_id: board_expander + number: 0 + calibration: + x_min: 0 + x_max: 319 + y_min: 0 + y_max: 239 + +# https://esphome.io/components/time +time: + + # Home Assistant time writes through to the RTC, so the clock survives a boot with no Home Assistant. + - id: !extend homeassistant_time + on_time_sync: + - bm8563.write_time: rtc_time + + # https://esphome.io/components/time/bm8563 + - platform: bm8563 + id: rtc_time + i2c_id: board_i2c + update_interval: never + +# https://esphome.io/components/motion +motion: + - platform: bmi270 + id: board_motion + i2c_id: board_i2c + address: 0x69 + +# https://esphome.io/components/audio_adc +audio_adc: + - platform: es7210 + id: board_audio_adc + i2c_id: board_i2c + bits_per_sample: 16bit + sample_rate: 16000 + mic_gain: ${mic_gain} + +# https://github.com/m5stack/esphome-yaml/tree/main/components/aw88298 +audio_dac: + - platform: aw88298 + id: board_audio_dac + i2c_id: board_i2c + sample_rate: 16000 + +# https://esphome.io/components/i2s_audio +i2s_audio: + - id: board_i2s + i2s_lrclk_pin: GPIO33 + i2s_bclk_pin: GPIO34 + i2s_mclk_pin: + number: GPIO0 # BOOT strapping pin, fixed board wiring, so the warning is acknowledged. + ignore_strapping_warning: true + +# https://esphome.io/components/microphone/i2s_audio +microphone: + - platform: i2s_audio + id: board_mic + i2s_audio_id: board_i2s + i2s_din_pin: GPIO14 + adc_type: external + sample_rate: 16000 + bits_per_sample: 16bit + +# https://esphome.io/components/speaker/i2s_audio +speaker: + - platform: i2s_audio + id: board_speaker + i2s_audio_id: board_i2s + i2s_dout_pin: GPIO13 + dac_type: external + audio_dac: board_audio_dac + sample_rate: 16000 + +# https://esphome.io/components/sensor +sensor: + + # The platform sensor itself publishes nothing, only the named children do, the charging binary sensor included. + # https://github.com/m5stack/esphome-yaml/tree/main/components/axp2101 + - platform: axp2101 + id: pmu_sensors + internal: true + update_interval: 60s + battery_level: + name: "Battery Level" + id: battery_level + battery_voltage: + name: "Battery Voltage" + id: battery_voltage + unit_of_measurement: V + accuracy_decimals: 2 + filters: + - multiply: 0.001 + axp_temperature: + name: "PMU Temperature" + id: pmu_temperature + entity_category: diagnostic + battery_charging: + name: "Battery Charging" + id: battery_charging + + # https://esphome.io/components/sensor/ltr_als_ps + - platform: ltr_als_ps + i2c_id: board_i2c + address: 0x23 + type: ALS_PS + update_interval: 30s + ambient_light: + name: "Ambient Light" + id: ambient_light + ps_counts: + name: "Proximity" + id: proximity + + # The delta filters keep a sensor read four times a second from flooding Home Assistant. + # https://esphome.io/components/motion + - platform: motion + type: pitch + name: "Pitch" + id: pitch + motion_id: board_motion + filters: + - delta: 2.0 + + - platform: motion + type: roll + name: "Roll" + id: roll + motion_id: board_motion + filters: + - delta: 2.0 diff --git a/test/m5stack-cores3.yaml b/test/m5stack-cores3.yaml new file mode 100644 index 0000000..c4121c5 --- /dev/null +++ b/test/m5stack-cores3.yaml @@ -0,0 +1,456 @@ +# CI compile-test for templates/m5stack-cores3.yaml and its m5stack-cores3-camera.yaml overlay. +# The LVGL screens exercise each part of the board, so a flashed unit shows what works. +substitutions: + device_name: m5stack-cores3-test + device_friendly_name: "M5Stack CoreS3 Test" + +packages: + device: !include ../templates/m5stack-cores3.yaml + camera: !include ../templates/m5stack-cores3-camera.yaml + +# The camera's snapshot endpoint, which shows a frame without Home Assistant. +# https://esphome.io/components/esp32_camera_web_server +esp32_camera_web_server: + - port: 8081 + mode: snapshot + +# The bench unit has no Home Assistant, so SNTP sets the clock and writes it through to the RTC. +# - Each source names itself on the status tab, so a boot with no network shows the RTC restoring the time. +# https://esphome.io/components/time +time: + - id: !extend homeassistant_time + on_time_sync: + - lvgl.label.update: + id: status_source + text: "Time from Home Assistant" + + - id: !extend rtc_time + on_time_sync: + - lvgl.label.update: + id: status_source + text: "Time from RTC" + + # https://esphome.io/components/time/sntp + - platform: sntp + id: sntp_time + on_time_sync: + - bm8563.write_time: rtc_time + - lvgl.label.update: + id: status_source + text: "Time from SNTP, RTC written" + +# The sound level holds the shared I2S bus, so the beep releases the microphone before it plays. +# https://esphome.io/components/script +script: + - id: play_beep + mode: single + then: + - sound_level.stop: board_sound_level + - wait_until: + condition: + not: + microphone.is_capturing: board_mic + timeout: 1s + - rtttl.play: + rtttl: "beep:d=16,o=6,b=180:c,e,g,c7" + +# The same beep as a button entity, so the speaker can be tested without touching the screen. +# https://esphome.io/components/button/template +button: + - platform: template + name: "Beep" + on_press: + - script.execute: play_beep + +# https://esphome.io/components/rtttl +rtttl: + id: board_rtttl + speaker: board_speaker + on_finished_playback: + - sound_level.start: board_sound_level + +# https://esphome.io/components/sensor +sensor: + + # The microphone level in dBFS, which shows the microphone path works. + # https://esphome.io/components/sensor/sound_level + - platform: sound_level + id: board_sound_level + microphone: board_mic + passive: false + measurement_duration: 500ms + rms: + name: "Sound Level" + id: sound_level_rms + + # https://esphome.io/components/motion + - platform: motion + type: acceleration_x + id: accel_x + motion_id: board_motion + internal: true + + - platform: motion + type: acceleration_y + id: accel_y + motion_id: board_motion + internal: true + + - platform: motion + type: acceleration_z + id: accel_z + motion_id: board_motion + internal: true + +# Set once a press has changed tab, so one edge swipe moves one tab. +# https://esphome.io/components/globals +globals: + - id: edge_swipe_done + type: bool + initial_value: "false" + +# Coordinates land in the log, so a tap can be checked against the console. +# https://esphome.io/components/touchscreen +touchscreen: + - id: !extend board_touch + on_touch: + - globals.set: + id: edge_swipe_done + value: "false" + - logger.log: + format: "Touch at %d, %d, raw %d, %d" + args: [touch.x, touch.y, touch.x_raw, touch.y_raw] + on_update: + + # A drag that starts at the left or right edge changes tab, and one that starts inside the screen is left to LVGL. + - lambda: |- + if (touches.empty() || id(edge_swipe_done) || !id(lcd_backlight).remote_values.is_on()) return; + const auto &t = touches[0]; + const int edge = 24, travel = 60; + const int dx = t.x - t.x_org; + if (std::abs(t.y - t.y_org) > travel) return; + const int active = lv_tabview_get_tab_active(id(tabs)); + if (t.x_org < edge && dx > travel && active > 0) { + lv_tabview_set_active(id(tabs), active - 1, LV_ANIM_ON); + id(edge_swipe_done) = true; + } else if (t.x_org > id(board_display).get_width() - edge && dx < -travel && + active < (int) lv_tabview_get_tab_count(id(tabs)) - 1) { + lv_tabview_set_active(id(tabs), active + 1, LV_ANIM_ON); + id(edge_swipe_done) = true; + } + + - lambda: |- + // The dot sits inside the touch tab, so the screen coordinates are offset by that tab's origin. + if (touches.empty()) return; + lv_area_t pad; + lv_obj_get_coords(id(touch_pad), &pad); + const int x = touches[0].x - pad.x1 - lv_obj_get_width(id(touch_dot)) / 2; + const int y = touches[0].y - pad.y1 - lv_obj_get_height(id(touch_dot)) / 2; + lv_obj_set_pos(id(touch_dot), x, y); + lv_label_set_text_fmt(id(touch_coords), "%d, %d raw %d, %d", touches[0].x, touches[0].y, touches[0].x_raw, + touches[0].y_raw); + on_release: + - if: + condition: + lambda: return !id(lcd_backlight).remote_values.is_on(); + then: + - light.turn_on: lcd_backlight + +# LVGL sleeps once the backlight finishes turning off, whatever turned it off, and the slider follows the backlight. +# - The touch wake and the edge swipe read the target state, so a tap during the fade turns the backlight back on. +# - Resuming LVGL redraws the screen and restarts the idle timeout. +# - The target brightness is in remote_values, since current_values lags until the transition ends. +# https://esphome.io/components/light +light: + - id: !extend lcd_backlight + on_turn_on: + - lvgl.resume: + on_turn_off: + - lvgl.pause: + on_state: + - lvgl.slider.update: + id: controls_backlight + value: !lambda return id(lcd_backlight).remote_values.get_brightness() * 100.0f; + +# https://esphome.io/components/lvgl +lvgl: + displays: [board_display] + touchscreens: [board_touch] + # LVGL's own tab swipe also starts on the slider and the drawing pad, so the edge swipe above replaces it. + on_boot: + - lambda: lv_obj_remove_flag(lv_tabview_get_content(id(tabs)), LV_OBJ_FLAG_SCROLLABLE); + on_idle: + - timeout: 60s + then: + - light.turn_off: lcd_backlight + widgets: + - tabview: + id: tabs + position: top + size: 52 + tab_style: + items: + text_font: montserrat_20 + tabs: + + - name: "\uF015" # Home symbol, status + id: tab_status + layout: + type: flex + flex_flow: column + widgets: + - label: + id: status_clock + text_font: montserrat_40 + text: "--:--:--" + - label: + id: status_ip + text: "IP --" + - label: + id: status_date + text: "----------" + - label: + id: status_source + text: "Time not set" + - label: + id: status_uptime + text: "Up --" + + - name: "\uF00B" # List symbol, sensors + id: tab_sensors + layout: + type: flex + flex_flow: column + widgets: + - label: + id: sensors_battery + text: "Battery --" + - label: + id: sensors_light + text: "Light --" + - label: + id: sensors_temperature + text: "PMU --" + - label: + id: sensors_sound + text: "Sound --" + - bar: + id: sensors_sound_bar + width: 100% + height: 16 + min_value: 0 + max_value: 100 + value: 0 + + # The bubble moves opposite the tilt, as the bubble in a spirit level does. + - name: "\uF124" # Location symbol, level + id: tab_level + scrollable: false + widgets: + - obj: + id: level_ring + align: left_mid + width: 150 + height: 150 + radius: 75 + border_width: 2 + scrollable: false + widgets: + - obj: + id: level_bubble + align: center + width: 24 + height: 24 + radius: 12 + bg_color: 0x00C000 + border_width: 0 + - label: + id: level_accel + align: right_mid + text: "X --\nY --\nZ --" + + - name: "\uF304" # Edit symbol, touch + id: tab_touch + scrollable: false + pad_all: 0 + widgets: + - obj: + id: touch_pad + width: 100% + height: 100% + pad_all: 0 + border_width: 0 + scrollable: false + widgets: + - label: + id: touch_coords + align: top_mid + text: "Touch the screen" + # Targets 20 pixels in from each corner and one at the center, for checking the touch calibration. + - obj: + id: touch_target_1 + align: top_left + x: 14 + y: 14 + width: 12 + height: 12 + radius: 6 + bg_opa: transp + border_width: 2 + border_color: 0x0080FF + clickable: false + - obj: + id: touch_target_2 + align: top_right + x: -14 + y: 14 + width: 12 + height: 12 + radius: 6 + bg_opa: transp + border_width: 2 + border_color: 0x0080FF + clickable: false + - obj: + id: touch_target_3 + align: bottom_right + x: -14 + y: -14 + width: 12 + height: 12 + radius: 6 + bg_opa: transp + border_width: 2 + border_color: 0x0080FF + clickable: false + - obj: + id: touch_target_4 + align: bottom_left + x: 14 + y: -14 + width: 12 + height: 12 + radius: 6 + bg_opa: transp + border_width: 2 + border_color: 0x0080FF + clickable: false + - obj: + id: touch_target_5 + align: center + x: 0 + y: 0 + width: 12 + height: 12 + radius: 6 + bg_opa: transp + border_width: 2 + border_color: 0x0080FF + clickable: false + - obj: + id: touch_dot + width: 20 + height: 20 + radius: 10 + bg_color: 0xFF4000 + border_width: 0 + clickable: false + + - name: "\uF013" # Settings symbol, controls + id: tab_controls + layout: + type: flex + flex_flow: column + widgets: + - label: + text: "Backlight" + - slider: + id: controls_backlight + width: 90% + min_value: 10 + max_value: 100 + value: 80 + on_release: + - light.turn_on: + id: lcd_backlight + brightness: !lambda return x / 100.0; + - button: + id: controls_beep + widgets: + - label: + text: "Beep" + on_click: + - script.execute: play_beep + +# The labels redraw on a timer rather than per sensor, since several of them are children of a platform. +# https://esphome.io/components/interval +interval: + - interval: 1s + then: + - if: + condition: + time.has_time: + id: sntp_time + then: + - lvgl.label.update: + id: status_clock + text: + time_format: "%H:%M:%S" + time: sntp_time + - lvgl.label.update: + id: status_date + text: + time_format: "%A %Y-%m-%d" + time: sntp_time + - lvgl.label.update: + id: status_ip + text: + format: "IP %s" + args: ["id(wifi_ip).state.c_str()"] + - lvgl.label.update: + id: status_uptime + text: + format: "Up %u s" + args: ["(unsigned) (millis() / 1000)"] + - lvgl.label.update: + id: sensors_battery + text: + format: "Battery %.0f%% %.2f V %s" + args: + - "id(battery_level).state" + - "id(battery_voltage).state" + - "id(battery_charging).state ? \"charging\" : \"\"" + - lvgl.label.update: + id: sensors_light + text: + format: "Light %.0f lx Proximity %.0f" + args: ["id(ambient_light).state", "id(proximity).state"] + - lvgl.label.update: + id: sensors_temperature + text: + format: "PMU %.1f C" + args: ["id(pmu_temperature).state"] + - lvgl.label.update: + id: sensors_sound + text: + format: "Sound %.1f dB" + args: ["id(sound_level_rms).state"] + - lvgl.bar.update: + id: sensors_sound_bar + value: !lambda |- + return std::isnan(id(sound_level_rms).state) ? 0 : (int) (id(sound_level_rms).state + 100); + + # The level redraws faster than the labels, so tilting the board moves the bubble smoothly. + - interval: 250ms + then: + - lvgl.widget.update: + id: level_bubble + x: !lambda |- + return std::isnan(id(accel_x).state) ? 0 : (int) std::clamp(-id(accel_x).state * 60.0f, -60.0f, 60.0f); + y: !lambda |- + return std::isnan(id(accel_y).state) ? 0 : (int) std::clamp(id(accel_y).state * 60.0f, -60.0f, 60.0f); + - lvgl.label.update: + id: level_accel + text: + format: "X %.2f g\nY %.2f g\nZ %.2f g" + args: ["id(accel_x).state", "id(accel_y).state", "id(accel_z).state"] diff --git a/version.json b/version.json index 1053a96..590ac02 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "2.3", + "version": "2.4", "publicReleaseRefSpec": [ "^refs/heads/main$" ], From 5127e781adb7ae822511c00df0c6322323e63623 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sat, 24 Oct 2026 21:05:43 -0700 Subject: [PATCH 09/14] Add M5Stack Dial Templates (#260) * Add M5Stack Dial Templates A board template for the M5Stack Dial and an LVGL example device that exercises it on a bench unit: the encoder pages, the front button beeps, and the touch, status, inputs and controls pages show each part working. Co-Authored-By: Claude Opus 5.5 * Fix Dial Review Findings Before the First Push Read the backlight target state for wakes, count encoder turns as LVGL activity, move the buzzer onto its own LEDC timer, redraw the inputs per event rather than on a 100ms poll, fire a Home Assistant tag event per scan, and correct the power hold notes. Co-Authored-By: Claude Opus 5.5 * Redraw Dial Inputs After the Encoder Publishes Run show_inputs from the encoder's on_value, which fires after the new count is published, and move the buzzer to LEDC channel 7 so a device's own auto-assigned ledc output cannot land on it. Co-Authored-By: Claude Opus 5.5 * Clarify That the Dial Encoder Changes Pages Co-Authored-By: Claude Opus 5.5 --------- Co-authored-by: Claude Opus 5.5 --- HISTORY.md | 1 + OPERATIONS.md | 13 ++ README.md | 22 +++ cspell.json | 1 + templates/m5stack-dial.yaml | 270 ++++++++++++++++++++++++++ test/m5stack-dial.yaml | 374 ++++++++++++++++++++++++++++++++++++ 6 files changed, 681 insertions(+) create mode 100644 templates/m5stack-dial.yaml create mode 100644 test/m5stack-dial.yaml diff --git a/HISTORY.md b/HISTORY.md index 2479d5b..4fc7fce 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -7,6 +7,7 @@ ESPHome configuration templates and projects. - Version 2.4: - Added M5Stack CoreS3 support, the first touchscreen template, with an LVGL example device. - Added an M5Stack CoreS3 camera overlay for the GC0308 camera. + - Added M5Stack Dial support, with an LVGL example device that changes pages with the encoder. - Version 2.3: - Changed OTA to encryption only, using the API key, so only the upgrade bridge build still needs the `ota_password` secret. - Fixed the compile warnings the templates and the EasyStart component can address. diff --git a/OPERATIONS.md b/OPERATIONS.md index 676dd63..b2e178d 100644 --- a/OPERATIONS.md +++ b/OPERATIONS.md @@ -329,6 +329,18 @@ Do not `!remove` blocks from Apollo's package without checking what depends on t - **The FT6336 drops quick taps once it falls into its monitor scan.** It slows its scan a few seconds after the last touch. Waking a dark screen then took several taps, and the touchscreen component logged one of three. ESPHome's `ft63x6` driver sets the active scan rate in register 0x88 and leaves the power mode in register 0x86 at its default. The board template writes 0 to register 0x86 through the touchscreen's own I2C device. It does so in an `on_boot` at priority -100, since the driver's setup resets the controller. A single tap then wakes the screen. - **The example device swipes between tabs only from the screen edge.** LVGL's tabview swipe is a plain scroll that also starts on the slider and the drawing pad, so the example turns it off. A drag starting within 24 pixels of the left or right edge and moving 60 pixels inward changes tab instead. +### M5Stack Dial + +[`templates/m5stack-dial.yaml`][m5stack-dial-template] is a board template, with every part on the StampS3 GPIO pins rather than behind an expander or a PMU. + +- **GPIO46 is the power hold, an output.** On battery the front button or the RTC alarm powers the board on, and it stays on only while the firmware drives GPIO46 high. ESPHome's device page configures it as a "Hold Button" input instead, so a board on battery turns off as soon as the button is released. On USB or the DC input the pin does nothing, so the bench unit could not test it. +- **A restart releases the power hold until the switch sets up again.** The reset returns GPIO46 to an input with its pulldown. Whether a board on battery stays powered through an OTA reboot or a restart is untested. +- **GPIO8 resets both the LCD and the WS1850S RFID reader.** The display owns the pin, so the reader takes no `reset_pin`. On the bench the reader set up without an error after the display's reset pulse. +- **The FT3267 touch controller runs on the `ft5x06` driver.** The boot I2C scan lists only 0x28 and 0x51, but the driver then reads vendor ID 0x11 at 0x38, and touch works. Do not chase the missing address. +- **The touch mapping matches the display with no calibration.** Taps on rings 30 pixels in from each edge and at the center landed on them. The reported coordinates sit one pixel below the raw ones. +- **`resolution: 1` gives one encoder step per detent.** M5Stack's example uses 4, which counts four steps per click. +- **The RTC is a BM8563, which the docs also call an RTC8563 and M5Stack's example drives as a PCF8563.** The template uses the `bm8563` platform, as the CoreS3 template does. + ### RGB LED Status [`templates/rgb-led-status.yaml`][rgb-led-status-template] derives from [Flo-R1der's package][flo-r1der-link] but does not import it, because that version binds its state to `wifi:` `on_connect` triggers and so cannot run on an Ethernet board. @@ -693,6 +705,7 @@ Sharp edges in the tooling around this repository, each one learned by tripping [heltec-template]: ./templates/heltec-wifi-lora32-v4-r8.yaml [m5stack-cores3-camera-template]: ./templates/m5stack-cores3-camera.yaml [m5stack-cores3-template]: ./templates/m5stack-cores3.yaml +[m5stack-dial-template]: ./templates/m5stack-dial.yaml [max17048-template]: ./templates/max17048.yaml [min-version-template]: ./templates/min-version.yaml [office-bluetooth-proxy]: ./office-bluetooth-proxy.yaml diff --git a/README.md b/README.md index 58e30b4..e82e197 100755 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ ESPHome configuration templates and projects. - Added M5Stack CoreS3 support, the first touchscreen template, with an LVGL example device. - Added an M5Stack CoreS3 camera overlay for the GC0308 camera. +- Added M5Stack Dial support, with an LVGL example device that changes pages with the encoder. See [HISTORY.md][history] for the release notes. @@ -252,6 +253,23 @@ Shared building-block includes, composed via `packages:` by the device templates - `camera_clock_pin`: `GPIO10` by default, a spare M5-Bus pin for the clock ESPHome requires although the camera clocks itself. Move it where a module uses `GPIO10`. - Note: the camera's pixel clock is GPIO45, the pin the ESP32-S3 reads at reset to pick its flash voltage. A reset taken while the camera streams can leave the board looping in its bootloader until it is power cycled. The overlay holds the camera in reset on every orderly shutdown, which covers OTA, restart, and API reboots. Turn the `Camera Enable` switch off before flashing over USB, since the reset after a USB flash is not an orderly one. Turning it back on resets the sensor's registers, so restart afterwards. +#### M5Stack Dial + +- [Template][m5stack-dial] for the [M5Stack Dial][m5stack-shop-dial-link], an ESP32-S3 StampS3 with 8MB Quad Flash and no PSRAM. It carries a 240x240 round capacitive touch display, a rotary encoder, an RFID reader, and a passive buzzer. The [Dial docs][m5stack-docs-dial-link] carry the pin map the template header repeats. +- Includes the display, the touchscreen, and the backlight as a dimmable light. +- Includes the encoder as a step count, the front button that pressing the screen down operates, and the StampS3 BOOT button. +- Includes the RTC written through from Home Assistant time. +- Includes the RFID reader, which fires a Home Assistant tag event on each scan and holds the UID as text while the tag is present. +- Includes the buzzer as a PWM output, and the StampS3 RGB LED through the [RGB LED status][rgb-led-status] package. +- Includes a Power Off button, which releases the power hold pin and turns off a board running on its battery. +- Optional substitutions: + - `backlight_brightness`: `80%` by default, the level the backlight turns on at during boot. + - `rgb_led_pin`: `GPIO21` by default, the StampS3 RGB LED. +- Flash over USB-C. The port is the chip's native USB with no UART bridge, so no adapter is needed. +- The display carries no lambda, so a device draws on it with [LVGL][esphome-components-lvgl-link], as the [example device][test-m5stack-dial] does. There the encoder changes page and the front button plays a beep. +- Note: a V1 unit confirms USB and OTA flashing, the display, touch, the backlight, the encoder, the front button, the buzzer, and the RTC write. Reading an RFID tag, the power hold on battery, the BOOT button, and the RGB LED are untested. So is a V1.1 on the StampS3A. +- Note: the battery level is not sensed, because no ADC pin reaches the battery. + ## Devices My deployed devices use the templates and helpers, and are documented in [DEVICES.md][devices]. @@ -350,6 +368,7 @@ Licensed under the MIT License. See [LICENSE][license] for details. [logger]: ./templates/logger.yaml [m5stack-cores3]: ./templates/m5stack-cores3.yaml [m5stack-cores3-camera]: ./templates/m5stack-cores3-camera.yaml +[m5stack-dial]: ./templates/m5stack-dial.yaml [max17048]: ./templates/max17048.yaml [min-version]: ./templates/min-version.yaml [norvi-enet-ae06-r]: ./templates/norvi-enet-ae06-r.yaml @@ -368,6 +387,7 @@ Licensed under the MIT License. See [LICENSE][license] for details. [test]: ./test/ [test-heltec-wifi-lora32-v4-r8]: ./test/heltec-wifi-lora32-v4-r8.yaml [test-m5stack-cores3]: ./test/m5stack-cores3.yaml +[test-m5stack-dial]: ./test/m5stack-dial.yaml [test-norvi-enet-ae06-r]: ./test/norvi-enet-ae06-r.yaml [time]: ./templates/time.yaml [unexpectedmaker-pros3d]: ./templates/unexpectedmaker-pros3d.yaml @@ -424,7 +444,9 @@ Licensed under the MIT License. See [LICENSE][license] for details. [kincony-esp32-sd-card-rtc-ds3231-link]: https://www.kincony.com/esp32-sd-card-rtc-ds3231.html [konnected-products-smart-garage-door-opener-blaq-myq-alternative-link]: https://konnected.io/products/smart-garage-door-opener-blaq-myq-alternative [m5stack-docs-cores3-link]: https://docs.m5stack.com/en/core/CoreS3 +[m5stack-docs-dial-link]: https://docs.m5stack.com/en/core/Dial [m5stack-shop-cores3-link]: https://shop.m5stack.com/products/m5stack-cores3-esp32s3-iotdevelopment-kit +[m5stack-shop-dial-link]: https://shop.m5stack.com/products/m5stack-dial-v1-1 [markdown-all-in-one-link]: https://marketplace.visualstudio.com/items?itemName=yzhang.markdown-all-in-one [markdownlint-cli2-link]: https://github.com/DavidAnson/markdownlint-cli2 [microair-link]: https://www.microair.net diff --git a/cspell.json b/cspell.json index d9ee2d6..a859acf 100644 --- a/cspell.json +++ b/cspell.json @@ -197,6 +197,7 @@ "reflash", "regen", "resharper", + "RFID", "rhysd", "Rigol", "RMII", diff --git a/templates/m5stack-dial.yaml b/templates/m5stack-dial.yaml new file mode 100644 index 0000000..d3622bc --- /dev/null +++ b/templates/m5stack-dial.yaml @@ -0,0 +1,270 @@ +# External usage: +# packages: +# device: github://ptr727/ESPHome-Config/templates/m5stack-dial.yaml@main +# - Secrets resolve from a secrets.yaml beside the including config, see secrets._yaml for the names. + +# Required substitutions: +# - device_name - the ESPHome node name. +# - device_friendly_name - the Home Assistant display name. + +# Optional substitutions: +# - backlight_brightness - 80%, the level the backlight turns on at during boot. +# - rgb_led_pin - GPIO21, the StampS3 module's RGB LED, used by rgb-led-status.yaml. + +# M5Stack Dial ESP32-S3 Smart Rotary Knob, V1 on a StampS3, V1.1 on a StampS3A +# ESP32-S3FN8, 8MB Quad Flash, no PSRAM +# https://shop.m5stack.com/products/m5stack-dial-v1-1 +# https://docs.m5stack.com/en/core/Dial + +# Board tested: 2026-10-24, a V1 on a StampS3, USB and OTA flash, display, touch, backlight, encoder, front button, buzzer, RTC write. +# - The RFID reader answers on the bus, and reading a tag is untested. +# - The power hold and the Power Off button are untested, since the bench unit ran on USB. +# - The BOOT button and the RGB LED sit inside the case, so neither is tested. +# - A V1.1 on a StampS3A is untested. + +# The display, the touchscreen and the backlight are configured here. +# - The display carries no lambda, so a device draws with LVGL, see test/m5stack-dial.yaml. +# - A lambda device extends board_display with lambda, update_interval and auto_clear_enabled true instead. + +# GPIO19, GPIO20 : native USB, so flashing needs no adapter and no UART bridge +# GPIO0 : BOOT button on the StampS3, held at power on for download mode +# GPIO3 : passive buzzer +# GPIO21 : RGB LED on the StampS3, inside the case +# GPIO40, GPIO41 : rotary encoder A and B +# GPIO42 : front button, pressing the whole screen down +# GPIO46 : power hold, see below + +# Power hold +# - On battery the front button or the RTC alarm powers the board on, and the board stays on only while GPIO46 is high. +# - The Power Off button drives it low, which turns a board on battery off and does nothing on USB or the DC input. +# - A restart releases the pin until the switch sets up again, and whether a board on battery survives that is untested. +# - Battery level is not sensed, since no ADC pin reaches the battery. + +# Internal I2C bus +# GPIO11 : sda +# GPIO12 : scl +# - 0x38 FT3267 touch, 0x51 BM8563 RTC, 0x28 WS1850S RFID reader. + +# SPI bus, the LCD only +# GPIO6 : sclk +# GPIO5 : mosi +# GPIO4 : LCD dc +# GPIO7 : LCD cs +# GPIO8 : LCD reset, also wired to the WS1850S reset +# GPIO9 : LCD backlight, PWM + +# GC9A01 240x240 round IPS, with an FT3267 touch panel +# GPIO14 : touch interrupt +# GPIO10 : WS1850S interrupt, unclaimed here + +# Grove ports, left to the device +# Port A : GPIO13 sda, GPIO15 scl +# Port B : GPIO1, GPIO2 + +# https://esphome.io/components/substitutions +substitutions: + backlight_brightness: 80% + # Used by rgb-led-status.yaml + rgb_led_pin: GPIO21 + +# https://esphome.io/components/esphome +esphome: + name: ${device_name} + friendly_name: ${device_friendly_name} + + # https://esphome.io/components/esphome#esphome-on_boot + on_boot: + - priority: 600 + then: + - light.turn_on: + id: lcd_backlight + brightness: ${backlight_brightness} + + # The RTC sets the clock once at boot, so it never overwrites a network time source later. + - bm8563.read_time: rtc_time + +# https://esphome.io/components/esp32 +esp32: + # PlatformIO carries no Dial or StampS3 board definition, and the DevKitC-1 profile matches the module. + # https://docs.platformio.org/en/latest/boards/espressif32/esp32-s3-devkitc-1.html + board: esp32-s3-devkitc-1 + + # The board definition does not set this, ESPHome defaults to 4MB. + flash_size: 8MB + + # https://esphome.io/components/esp32#esp-idf-framework + framework: + type: esp-idf + +# https://esphome.io/components/packages#local-packages +packages: + common: !include common.yaml + temperature: !include temperature.yaml + rgbledstatus: !include rgb-led-status.yaml + +# USB is the chip's own port, with no UART bridge on this board. +# https://esphome.io/components/logger +logger: + hardware_uart: USB_SERIAL_JTAG # UART0, UART1, USB_CDC, USB_SERIAL_JTAG + +# A list entry rather than the mapping shorthand, so a device's own bus merges instead of replacing this. +# https://esphome.io/components/i2c +i2c: + - id: board_i2c + sda: GPIO11 + scl: GPIO12 + scan: true + frequency: 400kHz + +# https://esphome.io/components/spi +spi: + - id: board_spi + clk_pin: GPIO6 + mosi_pin: GPIO5 + +# https://esphome.io/components/output +output: + + # https://esphome.io/components/output/ledc + - platform: ledc + id: lcd_backlight_output + pin: GPIO9 + + # Channel 7 is on the last LEDC timer, so a buzzer note never retunes the backlight or a device's own ledc output. + # https://esphome.io/components/output/ledc + - platform: ledc + id: buzzer_output + channel: 7 + pin: + number: GPIO3 # Strapping pin, fixed board wiring, so the warning is acknowledged. + ignore_strapping_warning: true + +# https://esphome.io/components/light/monochromatic +light: + - platform: monochromatic + name: "Backlight" + id: lcd_backlight + output: lcd_backlight_output + default_transition_length: 250ms + restore_mode: ALWAYS_OFF + +# https://esphome.io/components/switch/gpio +switch: + - platform: gpio + id: power_hold + pin: + number: GPIO46 # Strapping pin, fixed board wiring, so the warning is acknowledged. + ignore_strapping_warning: true + restore_mode: ALWAYS_ON + internal: true + +# https://esphome.io/components/button/template +button: + - platform: template + name: "Power Off" + id: power_off + entity_category: config + on_press: + - switch.turn_off: power_hold + +# https://esphome.io/components/binary_sensor/gpio +binary_sensor: + - platform: gpio + name: "Button" + id: front_button + pin: + number: GPIO42 + inverted: true + mode: + input: true + pullup: true + + - platform: gpio + name: "Boot Button" + id: boot_button + pin: + number: GPIO0 # Strapping pin, fixed board wiring, so the warning is acknowledged. + ignore_strapping_warning: true + inverted: true + mode: + input: true + pullup: true + entity_category: diagnostic + +# https://esphome.io/components/sensor/rotary_encoder +sensor: + - platform: rotary_encoder + name: "Encoder" + id: board_encoder + pin_a: + number: GPIO40 + mode: + input: true + pullup: true + pin_b: + number: GPIO41 + mode: + input: true + pullup: true + resolution: 1 + publish_initial_value: true + +# https://esphome.io/components/display/mipi_spi +display: + - platform: mipi_spi + id: board_display + spi_id: board_spi + model: GC9A01A + dc_pin: GPIO4 + cs_pin: GPIO7 + reset_pin: GPIO8 + invert_colors: true + data_rate: 40MHz + auto_clear_enabled: false + update_interval: never + +# https://esphome.io/components/touchscreen/ft5x06 +touchscreen: + - platform: ft5x06 + id: board_touch + i2c_id: board_i2c + address: 0x38 + display: board_display + interrupt_pin: GPIO14 + +# https://esphome.io/components/time +time: + + # Home Assistant time writes through to the RTC, so the clock survives a boot with no Home Assistant. + - id: !extend homeassistant_time + on_time_sync: + - bm8563.write_time: rtc_time + + # https://esphome.io/components/time/bm8563 + - platform: bm8563 + id: rtc_time + i2c_id: board_i2c + update_interval: never + +# The WS1850S answers as an MFRC522. +# - Each scan fires a Home Assistant tag event, and the text sensor holds the UID until the tag leaves. +# https://esphome.io/components/binary_sensor/rc522 +rc522_i2c: + - id: board_rfid + i2c_id: board_i2c + address: 0x28 + on_tag: + - homeassistant.tag_scanned: !lambda return x; + - text_sensor.template.publish: + id: rfid_tag + state: !lambda return x; + on_tag_removed: + - text_sensor.template.publish: + id: rfid_tag + state: "" + +# https://esphome.io/components/text_sensor/template +text_sensor: + - platform: template + name: "RFID Tag" + id: rfid_tag diff --git a/test/m5stack-dial.yaml b/test/m5stack-dial.yaml new file mode 100644 index 0000000..6bbc4a6 --- /dev/null +++ b/test/m5stack-dial.yaml @@ -0,0 +1,374 @@ +# CI compile-test for templates/m5stack-dial.yaml. +# The LVGL pages exercise each part of the board, so a flashed unit shows what works. +# - Turning the encoder changes page, and pressing the screen down plays a beep. +substitutions: + device_name: m5stack-dial-test + device_friendly_name: "M5Stack Dial Test" + +packages: + device: !include ../templates/m5stack-dial.yaml + +# The bench unit has no Home Assistant, so SNTP sets the clock and writes it through to the RTC. +# - Each source names itself on the status page, so a boot with no network shows the RTC restoring the time. +# https://esphome.io/components/time +time: + - id: !extend homeassistant_time + on_time_sync: + - lvgl.label.update: + id: status_source + text: "Time from Home Assistant" + + - id: !extend rtc_time + on_time_sync: + - lvgl.label.update: + id: status_source + text: "Time from RTC" + + # https://esphome.io/components/time/sntp + - platform: sntp + id: sntp_time + on_time_sync: + - bm8563.write_time: rtc_time + - lvgl.label.update: + id: status_source + text: "Time from SNTP, RTC written" + +# https://esphome.io/components/rtttl +rtttl: + id: board_rtttl + output: buzzer_output + +# https://esphome.io/components/script +script: + - id: play_beep + then: + - rtttl.play: + rtttl: "beep:d=16,o=6,b=180:c,e,g,c7" + + # The inputs redraw on each count or press, since every one of them runs this. + - id: show_inputs + then: + - lvgl.label.update: + id: inputs_encoder + text: + format: "%.0f" + args: ["id(board_encoder).state"] + - lvgl.arc.update: + id: inputs_encoder_arc + value: !lambda |- + const int v = (int) id(board_encoder).state % 16; + return v < 0 ? v + 16 : v; + - lvgl.label.update: + id: inputs_buttons + text: + format: "Button %s Boot %s" + args: + - "id(front_button).state ? \"on\" : \"off\"" + - "id(boot_button).state ? \"on\" : \"off\"" + +# The same beep as a button entity, so the buzzer can be tested without touching the board. +# https://esphome.io/components/button/template +button: + - platform: template + name: "Beep" + on_press: + - script.execute: play_beep + +# A press or a turn on a dark screen only wakes it. +# - The wake reads the backlight's target state, so a press during the fade turns it back on. +# - A press or a turn on a lit screen counts as LVGL activity, since neither the button nor the encoder is an LVGL input device. +# https://esphome.io/components/binary_sensor +binary_sensor: + - id: !extend front_button + on_press: + - script.execute: show_inputs + - if: + condition: + lambda: return !id(lcd_backlight).remote_values.is_on(); + then: + - light.turn_on: lcd_backlight + else: + - lambda: lv_display_trigger_activity(nullptr); + - script.execute: play_beep + on_release: + - script.execute: show_inputs + + - id: !extend boot_button + on_press: + - script.execute: show_inputs + on_release: + - script.execute: show_inputs + +# https://esphome.io/components/sensor/rotary_encoder +sensor: + - id: !extend board_encoder + on_value: + - script.execute: show_inputs + on_clockwise: + - if: + condition: + lambda: return !id(lcd_backlight).remote_values.is_on(); + then: + - light.turn_on: lcd_backlight + else: + - lambda: lv_display_trigger_activity(nullptr); + - lvgl.page.next: + animation: MOVE_LEFT + time: 200ms + on_anticlockwise: + - if: + condition: + lambda: return !id(lcd_backlight).remote_values.is_on(); + then: + - light.turn_on: lcd_backlight + else: + - lambda: lv_display_trigger_activity(nullptr); + - lvgl.page.previous: + animation: MOVE_RIGHT + time: 200ms + +# https://esphome.io/components/text_sensor +text_sensor: + - id: !extend rfid_tag + on_value: + - lvgl.label.update: + id: inputs_rfid + text: + format: "Tag %s" + args: ["x.empty() ? \"--\" : x.c_str()"] + +# Coordinates land in the log, so a tap can be checked against the console. +# https://esphome.io/components/touchscreen +touchscreen: + - id: !extend board_touch + on_touch: + - logger.log: + format: "Touch at %d, %d, raw %d, %d" + args: [touch.x, touch.y, touch.x_raw, touch.y_raw] + on_update: + - lambda: |- + if (touches.empty()) return; + const int x = touches[0].x - lv_obj_get_width(id(touch_dot)) / 2; + const int y = touches[0].y - lv_obj_get_height(id(touch_dot)) / 2; + lv_obj_set_pos(id(touch_dot), x, y); + lv_label_set_text_fmt(id(touch_coords), "%d, %d", touches[0].x, touches[0].y); + on_release: + - if: + condition: + lambda: return !id(lcd_backlight).remote_values.is_on(); + then: + - light.turn_on: lcd_backlight + +# LVGL sleeps once the backlight is off, whatever turned it off, and the arc follows the backlight. +# https://esphome.io/components/light +light: + - id: !extend lcd_backlight + on_turn_on: + - lvgl.resume: + on_turn_off: + - lvgl.pause: + on_state: + - lvgl.arc.update: + id: controls_backlight + value: !lambda return id(lcd_backlight).remote_values.get_brightness() * 100.0f; + +# https://esphome.io/components/lvgl +lvgl: + displays: [board_display] + touchscreens: [board_touch] + on_idle: + - timeout: 60s + then: + - light.turn_off: lcd_backlight + # The round screen shows the middle of each page, so every page but the touch one centers a single column. + pages: + + - id: page_status + layout: + type: flex + flex_flow: column + flex_align_main: center + flex_align_cross: center + flex_align_track: center + widgets: + - label: + id: status_clock + text_font: montserrat_28 + text: "--:--:--" + - label: + id: status_date + text: "----------" + - label: + id: status_ip + text: "IP --" + - label: + id: status_source + text: "Time not set" + - label: + id: status_uptime + text: "Up --" + + - id: page_inputs + layout: + type: flex + flex_flow: column + flex_align_main: center + flex_align_cross: center + flex_align_track: center + widgets: + - arc: + id: inputs_encoder_arc + width: 120 + height: 120 + start_angle: 0 + end_angle: 360 + min_value: 0 + max_value: 16 + value: 0 + clickable: false + widgets: + - label: + id: inputs_encoder + align: center + text_font: montserrat_28 + text: "0" + - label: + id: inputs_buttons + text: "Button -- Boot --" + - label: + id: inputs_rfid + text: "Tag --" + + - id: page_touch + scrollable: false + pad_all: 0 + widgets: + - label: + id: touch_coords + align: center + y: -36 + text: "Touch the screen" + # Targets 30 pixels in from the edge on each axis and one at the center, for checking the touch mapping. + - obj: + id: touch_target_top + align: top_mid + y: 24 + width: 12 + height: 12 + radius: 6 + bg_opa: transp + border_width: 2 + border_color: 0x0080FF + clickable: false + - obj: + id: touch_target_right + align: right_mid + x: -24 + width: 12 + height: 12 + radius: 6 + bg_opa: transp + border_width: 2 + border_color: 0x0080FF + clickable: false + - obj: + id: touch_target_bottom + align: bottom_mid + y: -24 + width: 12 + height: 12 + radius: 6 + bg_opa: transp + border_width: 2 + border_color: 0x0080FF + clickable: false + - obj: + id: touch_target_left + align: left_mid + x: 24 + width: 12 + height: 12 + radius: 6 + bg_opa: transp + border_width: 2 + border_color: 0x0080FF + clickable: false + - obj: + id: touch_target_center + align: center + width: 12 + height: 12 + radius: 6 + bg_opa: transp + border_width: 2 + border_color: 0x0080FF + clickable: false + - obj: + id: touch_dot + width: 20 + height: 20 + radius: 10 + bg_color: 0xFF4000 + border_width: 0 + clickable: false + + - id: page_controls + layout: + type: flex + flex_flow: column + flex_align_main: center + flex_align_cross: center + flex_align_track: center + widgets: + - arc: + id: controls_backlight + width: 160 + height: 160 + adjustable: true + min_value: 10 + max_value: 100 + value: 80 + on_release: + - light.turn_on: + id: lcd_backlight + brightness: !lambda return x / 100.0; + widgets: + - button: + id: controls_beep + align: center + widgets: + - label: + text: "Beep" + on_click: + - script.execute: play_beep + +# The status labels redraw on a timer, since the clock and the uptime change every second. +# https://esphome.io/components/interval +interval: + - interval: 1s + then: + - if: + condition: + time.has_time: + id: sntp_time + then: + - lvgl.label.update: + id: status_clock + text: + time_format: "%H:%M:%S" + time: sntp_time + - lvgl.label.update: + id: status_date + text: + time_format: "%a %Y-%m-%d" + time: sntp_time + - lvgl.label.update: + id: status_ip + text: + format: "IP %s" + args: ["id(wifi_ip).state.c_str()"] + - lvgl.label.update: + id: status_uptime + text: + format: "Up %u s" + args: ["(unsigned) (millis() / 1000)"] From aef38b0ae681fcf86b78722bfc7366513751e887 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sun, 25 Oct 2026 07:48:14 -0700 Subject: [PATCH 10/14] Update release notes for version 2.4 to clarify M5Stack CoreS3 and Dial support details --- HISTORY.md | 6 +++--- README.md | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 4fc7fce..30041f9 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -5,9 +5,9 @@ ESPHome configuration templates and projects. ## Release History - Version 2.4: - - Added M5Stack CoreS3 support, the first touchscreen template, with an LVGL example device. - - Added an M5Stack CoreS3 camera overlay for the GC0308 camera. - - Added M5Stack Dial support, with an LVGL example device that changes pages with the encoder. + - Added M5Stack CoreS3 support, with an LVGL touch screen example device. + - Added M5Stack GC0308 camera overlay support. + - Added M5Stack Dial support, with an LVGL and rotary encoder example device. - Version 2.3: - Changed OTA to encryption only, using the API key, so only the upgrade bridge build still needs the `ota_password` secret. - Fixed the compile warnings the templates and the EasyStart component can address. diff --git a/README.md b/README.md index e82e197..7fbc3b7 100755 --- a/README.md +++ b/README.md @@ -21,11 +21,11 @@ ESPHome configuration templates and projects. **Version 2.4**: -- Added M5Stack CoreS3 support, the first touchscreen template, with an LVGL example device. -- Added an M5Stack CoreS3 camera overlay for the GC0308 camera. -- Added M5Stack Dial support, with an LVGL example device that changes pages with the encoder. +- Added M5Stack CoreS3 support, with an LVGL touch screen example device. +- Added M5Stack GC0308 camera overlay support. +- Added M5Stack Dial support, with an LVGL and rotary encoder example device. -See [HISTORY.md][history] for the release notes. +See [Release History][history] for complete release notes and older versions. ## Table of Contents From 0db406ed176329a9ec5dd3aeb737ba7be6f185a6 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sun, 25 Oct 2026 08:46:19 -0700 Subject: [PATCH 11/14] Disable the Reboot Timeouts on the Example Devices (#261) * Disable the Reboot Timeouts on the Example Devices An example device flashed to a bench unit is never adopted by Home Assistant, so the API's default 15 minute reboot_timeout restarted it every 15 minutes. Each example device now sets api_reboot_timeout and wifi_reboot_timeout to 0s, and the change-detection job fails one that does not, so a new example device cannot regress. * Accept a Quoted Timeout and Mirror the Release Note in the README * Correct the Reboot Timeout Comment and the Ethernet Board Claim * Accept a Single-Quoted Zero Timeout in the Example Device Guard * Match a Real Single Quote in the Example Device Guard --- .github/workflows/test-pull-request.yml | 9 +++++++++ HISTORY.md | 1 + OPERATIONS.md | 3 ++- README.md | 1 + test/adafruit-esp32-s3-feather.yaml | 2 ++ test/aoycocr-x5p.yaml | 2 ++ test/apollo-plt-1b.yaml | 2 ++ test/easystart.yaml | 2 ++ test/efun-sh331.yaml | 2 ++ test/elecrow-thinknode-m7.yaml | 2 ++ test/esp32-s3-devkitc.yaml | 2 ++ test/esp32-s3-wroom-1-n16r8.yaml | 2 ++ test/esp32-s3-wroom-2-n16r8v.yaml | 2 ++ test/esp32-s3-wroom-2-n32r8v.yaml | 2 ++ test/gls10-bluetooth-proxy.yaml | 2 ++ test/heltec-wifi-lora32-v4-r8.yaml | 2 ++ test/kincony-kc868-asr.yaml | 2 ++ test/konnected-blaq.yaml | 2 ++ test/m5stack-cores3.yaml | 2 ++ test/m5stack-dial.yaml | 2 ++ test/norvi-enet-ae06-r.yaml | 2 ++ test/rocket-astra.yaml | 2 ++ test/smarthome-ceilsense.yaml | 2 ++ test/sonoff-s31.yaml | 2 ++ test/sonoff-th10.yaml | 2 ++ test/unexpectedmaker-pros3d.yaml | 2 ++ test/waveshare-esp32-s3-eth.yaml | 2 ++ test/wemos-lolin32-lite.yaml | 2 ++ 28 files changed, 61 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test-pull-request.yml b/.github/workflows/test-pull-request.yml index 41021bb..1c579c5 100644 --- a/.github/workflows/test-pull-request.yml +++ b/.github/workflows/test-pull-request.yml @@ -105,6 +105,15 @@ jobs: echo "Templates that no example device in test/ includes: ${missing[*]}" exit 1 fi + # A flashed example device runs on a bench with no Home Assistant and maybe no network, so it must not reboot for lack of either. + rebooting=() + for path in test/*.yaml; do + grep -qE "^ api_reboot_timeout: [\"']?0s[\"']?( *#.*)?\$" "$path" && grep -qE "^ wifi_reboot_timeout: [\"']?0s[\"']?( *#.*)?\$" "$path" || rebooting+=("$path") + done + if (( ${#rebooting[@]} > 0 )); then + echo "Example devices not setting api_reboot_timeout and wifi_reboot_timeout to 0s: ${rebooting[*]}" + exit 1 + fi CONFIGS="$(basename -a -s .yaml test/*.yaml | jq -Rsc 'split("\n") | map(select(length > 0))')" echo "Template matrix: $CONFIGS" echo "template-configs=$CONFIGS" >> "$GITHUB_OUTPUT" diff --git a/HISTORY.md b/HISTORY.md index 30041f9..be89848 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -8,6 +8,7 @@ ESPHome configuration templates and projects. - Added M5Stack CoreS3 support, with an LVGL touch screen example device. - Added M5Stack GC0308 camera overlay support. - Added M5Stack Dial support, with an LVGL and rotary encoder example device. + - Changed the example devices to disable the API and WiFi reboot timeouts, so a flashed one runs on a bench without Home Assistant. - Version 2.3: - Changed OTA to encryption only, using the API key, so only the upgrade bridge build still needs the `ota_password` secret. - Fixed the compile warnings the templates and the EasyStart component can address. diff --git a/OPERATIONS.md b/OPERATIONS.md index b2e178d..308ef9c 100644 --- a/OPERATIONS.md +++ b/OPERATIONS.md @@ -134,6 +134,7 @@ fi - **Point `config_dir` at a checkout rather than at the deployed tree.** The dashboard's container already serves that tree, so compile it there with `docker exec` rather than in a second container. - **`latest` gives CI parity, not dashboard parity.** CI pulls `latest` fresh on every run, so run `docker pull ptr727/esphome-nonroot:latest` first for parity. That can be newer than the dashboard's last pull. Before an install, rerun with the image tag set to the dashboard's ESPHome version, per [Running the ESPHome CLI][running-the-esphome-cli]. The image's Docker Hub tag list carries each ESPHome release as a tag, such as `2026.9.1`. - **Compile a [`test/`][test] example device to gate a template change.** A top-level device config works the same way, but the example device is what CI builds. +- **An example device sets `api_reboot_timeout` and `wifi_reboot_timeout` to `0s`.** A flashed one then keeps running on a bench with no Home Assistant and no network, rather than rebooting every 15 minutes. Every template that sets a reboot timeout reads the matching substitution. A template with no `wifi:` block, such as an ethernet-only board's, ignores the WiFi half. CI's change-detection job fails an example device that leaves either one out. - **`secrets.yaml` belongs at the repository root**, not beside the config being compiled, because [`templates/secrets.yaml`][secrets-template] re-exports the root file and every template resolves `!secret` through it. Generate one from [`secrets._yaml`][secrets-example], and the guard above keeps a real one from being overwritten. - **`/entrypoint/cache.sh` runs first**, and it prepares the `/cache` mount the image expects. - **`/cache` holds the toolchain and the build tree, so keep it between runs.** A fresh directory each time makes every local compile a cold ESP-IDF build of several minutes. It also holds ESPHome's ccache store, `home/.cache/esphome/idf/ccache`, so a kept directory turns most repeat compiles into cache hits. CI uses one regardless, since it discards the runner. @@ -303,7 +304,7 @@ Do not `!remove` blocks from Apollo's package without checking what depends on t - **The 15% figure applies to a raw count read against the hardware scale.** A framework that normalizes a calibrated reading onto a fixed 1100mV scale is a separate case, and its constant is correct there. Check which scale a count is on before applying either number. - **The status LED is dark when healthy, deliberately.** ESPHome drives the pin low when healthy, which lights an active low LED. This board's LED is active high and bright white on a unit that runs from a cell. It blinks through a PWM output capped at `status_led_brightness`, 50% by default, since full drive glares. - **The SX1262 cannot be driven through ESPHome on this board.** The `sx126x` component reaches an RF switch only through the radio's own DIO2. The KCT8103L front-end is wired to three ESP32 pins instead, one selecting the transmit or receive path per packet. Those pins are documented and left unclaimed. -- **A bench unit needs both reboot timeouts disabled.** Override `api_reboot_timeout` and `wifi_reboot_timeout` to `0s`. Every template that sets a reboot timeout reads the matching substitution, so this reaches far more than this board. An ethernet board carries no `wifi:` block, so the WiFi half does nothing there. A unit on a desk with no Home Assistant restarts part way through a cold GNSS acquisition. +- **A bench unit needs both reboot timeouts disabled**, as every example device does per [Compiling the Way CI Does][compiling-the-way-ci-does]. On this board a unit on a desk with no Home Assistant otherwise restarts part way through a cold GNSS acquisition. ### M5Stack CoreS3 diff --git a/README.md b/README.md index 7fbc3b7..cffd04a 100755 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ ESPHome configuration templates and projects. - Added M5Stack CoreS3 support, with an LVGL touch screen example device. - Added M5Stack GC0308 camera overlay support. - Added M5Stack Dial support, with an LVGL and rotary encoder example device. +- Changed the example devices to disable the API and WiFi reboot timeouts, so a flashed one runs on a bench without Home Assistant. See [Release History][history] for complete release notes and older versions. diff --git a/test/adafruit-esp32-s3-feather.yaml b/test/adafruit-esp32-s3-feather.yaml index a76f0cb..488f2da 100644 --- a/test/adafruit-esp32-s3-feather.yaml +++ b/test/adafruit-esp32-s3-feather.yaml @@ -2,6 +2,8 @@ substitutions: device_name: adafruit-esp32-s3-feather-test device_friendly_name: "Adafruit ESP32-S3 Feather Test" + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/adafruit-esp32-s3-feather.yaml diff --git a/test/aoycocr-x5p.yaml b/test/aoycocr-x5p.yaml index a258275..cd3f202 100644 --- a/test/aoycocr-x5p.yaml +++ b/test/aoycocr-x5p.yaml @@ -2,6 +2,8 @@ substitutions: device_name: aoycocr-x5p-test device_friendly_name: "Aoycocr X5P Test" + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/aoycocr-x5p.yaml diff --git a/test/apollo-plt-1b.yaml b/test/apollo-plt-1b.yaml index 2c60dc3..25249f0 100644 --- a/test/apollo-plt-1b.yaml +++ b/test/apollo-plt-1b.yaml @@ -2,6 +2,8 @@ substitutions: device_name: apollo-plt-1b-test device_friendly_name: "Apollo PLT-1B Test" + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/apollo-plt-1b.yaml diff --git a/test/easystart.yaml b/test/easystart.yaml index a4b0ada..7e4c2f9 100644 --- a/test/easystart.yaml +++ b/test/easystart.yaml @@ -5,6 +5,8 @@ substitutions: device_name: easystart-test device_friendly_name: "EasyStart Test" + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/waveshare-esp32-s3-eth.yaml diff --git a/test/efun-sh331.yaml b/test/efun-sh331.yaml index d2db792..206c34d 100644 --- a/test/efun-sh331.yaml +++ b/test/efun-sh331.yaml @@ -2,6 +2,8 @@ substitutions: device_name: efun-sh331-test device_friendly_name: "EFUN SH331 Test" + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/efun-sh331.yaml diff --git a/test/elecrow-thinknode-m7.yaml b/test/elecrow-thinknode-m7.yaml index 23e75ba..b964c92 100644 --- a/test/elecrow-thinknode-m7.yaml +++ b/test/elecrow-thinknode-m7.yaml @@ -2,6 +2,8 @@ substitutions: device_name: elecrow-thinknode-m7-test device_friendly_name: "Elecrow ThinkNode M7 Test" + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/elecrow-thinknode-m7.yaml diff --git a/test/esp32-s3-devkitc.yaml b/test/esp32-s3-devkitc.yaml index 9fd858b..49e1d65 100644 --- a/test/esp32-s3-devkitc.yaml +++ b/test/esp32-s3-devkitc.yaml @@ -2,6 +2,8 @@ substitutions: device_name: esp32-s3-devkitc-test device_friendly_name: "ESP32-S3-DevKitC Test" + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/esp32-s3-devkitc.yaml diff --git a/test/esp32-s3-wroom-1-n16r8.yaml b/test/esp32-s3-wroom-1-n16r8.yaml index 901f96a..baa65e2 100644 --- a/test/esp32-s3-wroom-1-n16r8.yaml +++ b/test/esp32-s3-wroom-1-n16r8.yaml @@ -6,6 +6,8 @@ substitutions: device_friendly_name: "ESP32-S3-WROOM-1-N16R8 Test" # Third-party N16R8 boards commonly follow the DevKitC-1 v1.0 layout, RGB LED on GPIO48 not GPIO38. rgb_led_pin: GPIO48 + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/esp32-s3-devkitc.yaml diff --git a/test/esp32-s3-wroom-2-n16r8v.yaml b/test/esp32-s3-wroom-2-n16r8v.yaml index 0a98bea..7241a07 100644 --- a/test/esp32-s3-wroom-2-n16r8v.yaml +++ b/test/esp32-s3-wroom-2-n16r8v.yaml @@ -4,6 +4,8 @@ substitutions: device_name: esp32-s3-wroom2-n16r8v-test device_friendly_name: "ESP32-S3-WROOM-2-N16R8V Test" + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/esp32-s3-devkitc.yaml diff --git a/test/esp32-s3-wroom-2-n32r8v.yaml b/test/esp32-s3-wroom-2-n32r8v.yaml index aeb6adb..cc70123 100644 --- a/test/esp32-s3-wroom-2-n32r8v.yaml +++ b/test/esp32-s3-wroom-2-n32r8v.yaml @@ -4,6 +4,8 @@ substitutions: device_name: esp32-s3-wroom2-n32r8v-test device_friendly_name: "ESP32-S3-WROOM-2-N32R8V Test" + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/esp32-s3-devkitc.yaml diff --git a/test/gls10-bluetooth-proxy.yaml b/test/gls10-bluetooth-proxy.yaml index 3f4ecf3..3a1a2c8 100644 --- a/test/gls10-bluetooth-proxy.yaml +++ b/test/gls10-bluetooth-proxy.yaml @@ -2,6 +2,8 @@ substitutions: device_name: gls10-bluetooth-proxy-test device_friendly_name: "GL-S10 Bluetooth Proxy Test" + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/gls10-bluetooth-proxy.yaml diff --git a/test/heltec-wifi-lora32-v4-r8.yaml b/test/heltec-wifi-lora32-v4-r8.yaml index 2d17e91..c56e2b3 100644 --- a/test/heltec-wifi-lora32-v4-r8.yaml +++ b/test/heltec-wifi-lora32-v4-r8.yaml @@ -3,6 +3,8 @@ substitutions: device_name: heltec-wifi-lora32-v4-r8-test device_friendly_name: "Heltec WiFi LoRa 32 V4-R8 Test" + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/heltec-wifi-lora32-v4-r8.yaml diff --git a/test/kincony-kc868-asr.yaml b/test/kincony-kc868-asr.yaml index c6d48bf..4a774c4 100644 --- a/test/kincony-kc868-asr.yaml +++ b/test/kincony-kc868-asr.yaml @@ -2,6 +2,8 @@ substitutions: device_name: kincony-kc868-asr-test device_friendly_name: "Kincony KC868-ASR Test" + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/kincony-kc868-asr.yaml diff --git a/test/konnected-blaq.yaml b/test/konnected-blaq.yaml index 94ea609..29b7770 100644 --- a/test/konnected-blaq.yaml +++ b/test/konnected-blaq.yaml @@ -2,6 +2,8 @@ substitutions: device_name: konnected-blaq-test device_friendly_name: "Konnected blaQ Test" + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/konnected-blaq.yaml diff --git a/test/m5stack-cores3.yaml b/test/m5stack-cores3.yaml index c4121c5..53daa39 100644 --- a/test/m5stack-cores3.yaml +++ b/test/m5stack-cores3.yaml @@ -3,6 +3,8 @@ substitutions: device_name: m5stack-cores3-test device_friendly_name: "M5Stack CoreS3 Test" + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/m5stack-cores3.yaml diff --git a/test/m5stack-dial.yaml b/test/m5stack-dial.yaml index 6bbc4a6..5f6fd38 100644 --- a/test/m5stack-dial.yaml +++ b/test/m5stack-dial.yaml @@ -4,6 +4,8 @@ substitutions: device_name: m5stack-dial-test device_friendly_name: "M5Stack Dial Test" + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/m5stack-dial.yaml diff --git a/test/norvi-enet-ae06-r.yaml b/test/norvi-enet-ae06-r.yaml index 384d21b..f4ddf65 100644 --- a/test/norvi-enet-ae06-r.yaml +++ b/test/norvi-enet-ae06-r.yaml @@ -2,6 +2,8 @@ substitutions: device_name: norvi-enet-ae06-r-test device_friendly_name: "Norvi ENET-AE06-R Test" + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/norvi-enet-ae06-r.yaml diff --git a/test/rocket-astra.yaml b/test/rocket-astra.yaml index 4a83b68..cf79d5e 100644 --- a/test/rocket-astra.yaml +++ b/test/rocket-astra.yaml @@ -2,6 +2,8 @@ substitutions: device_name: rocket-astra-test device_friendly_name: "RocketController ASTRA Test" + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/rocket-astra.yaml diff --git a/test/smarthome-ceilsense.yaml b/test/smarthome-ceilsense.yaml index 99dd488..c68412d 100644 --- a/test/smarthome-ceilsense.yaml +++ b/test/smarthome-ceilsense.yaml @@ -2,6 +2,8 @@ substitutions: device_name: smarthome-ceilsense-test device_friendly_name: "SmartHomeShop CeilSense Test" + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/smarthome-ceilsense.yaml diff --git a/test/sonoff-s31.yaml b/test/sonoff-s31.yaml index 3c15c39..fb64bfc 100644 --- a/test/sonoff-s31.yaml +++ b/test/sonoff-s31.yaml @@ -2,6 +2,8 @@ substitutions: device_name: sonoff-s31-test device_friendly_name: "Sonoff S31 Test" + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/sonoff-s31.yaml diff --git a/test/sonoff-th10.yaml b/test/sonoff-th10.yaml index 10ff2c2..2eaa176 100644 --- a/test/sonoff-th10.yaml +++ b/test/sonoff-th10.yaml @@ -2,6 +2,8 @@ substitutions: device_name: sonoff-th10-test device_friendly_name: "Sonoff TH10 Test" + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/sonoff-th10.yaml diff --git a/test/unexpectedmaker-pros3d.yaml b/test/unexpectedmaker-pros3d.yaml index b4bb6b1..9b8fc7e 100644 --- a/test/unexpectedmaker-pros3d.yaml +++ b/test/unexpectedmaker-pros3d.yaml @@ -2,6 +2,8 @@ substitutions: device_name: unexpectedmaker-pros3d-test device_friendly_name: "Unexpected Maker ProS3D Test" + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/unexpectedmaker-pros3d.yaml diff --git a/test/waveshare-esp32-s3-eth.yaml b/test/waveshare-esp32-s3-eth.yaml index aef3103..6a3c8f0 100644 --- a/test/waveshare-esp32-s3-eth.yaml +++ b/test/waveshare-esp32-s3-eth.yaml @@ -2,6 +2,8 @@ substitutions: device_name: waveshare-esp32-s3-eth-test device_friendly_name: "Waveshare ESP32-S3-ETH Test" + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/waveshare-esp32-s3-eth.yaml diff --git a/test/wemos-lolin32-lite.yaml b/test/wemos-lolin32-lite.yaml index 8aa8b36..be2996d 100644 --- a/test/wemos-lolin32-lite.yaml +++ b/test/wemos-lolin32-lite.yaml @@ -2,6 +2,8 @@ substitutions: device_name: wemos-lolin32-lite-test device_friendly_name: "WEMOS LOLIN32 Lite Test" + api_reboot_timeout: 0s + wifi_reboot_timeout: 0s packages: device: !include ../templates/wemos-lolin32-lite.yaml From 337ef0d2a6e24b1b2e5e7a025a1d32ebedc8cb14 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sun, 25 Oct 2026 09:32:00 -0700 Subject: [PATCH 12/14] Lint Compiler Warnings in Lambdas on the Generated Code (#262) * Lint Compiler Warnings in Lambdas on the Generated Code Rechecks each compiled example device's generated main.cpp syntax-only with the build's own flags plus -Wdouble-promotion, and fails the compile leg on a warning that lands in this repository's code: a YAML lambda line, a lambda ESPHome inlined as an expression, or a component file copied into the build. Generated and third-party code is set aside. A VS Code task runs the same lint after a local compile and puts findings in the Problems pane. Fixes the two double-promotion lambdas it finds on the builds checked so far. Closes #256 * Tighten Lambda Attribution and Fix the Remaining Float Math Inline search matches whole tokens, skips an expression that is only an id(), searches only the YAML the build includes, and reports a block lambda's code line. Code after a remote package's #line is set aside. Comments are stripped with ESPHome's own remover, and a global's member access matches its real rewrite. The YAML is read before a --compile run starts, and an unreadable file fails the run. Adds f suffixes to the remaining double literals in the templates and the Dial example, and casts the EasyStart component's float log arguments. * Skip an Unused Broken YAML at Preload and Locate a Return Past a Comment * Fall Back When a Comment Hides the Return and Note the Linter Changes * Map Lambdas the Way ESPHome Emits Them and Read the Build Path From the Log Comments are removed across a whole lambda, with each emitted line mapped back to its YAML line, and a plain, quoted or folded lambda is one logical line. Code after a remote package's #line is set aside only where it copies that package's lambda. The inline search covers the build's config, found from ESPHome's storage file, and every repository YAML it includes. A recheck that fails outside this repository's code fails the run. CI reads the build path from the compile log, since a package can set its own build_path. Casts the float printf arguments in the Heltec and NORVI examples. * Lint Format Arguments and Take the Build Config From the Compile * Attribute a Bare Format Argument by Its Column * Match a Format Argument Only Beside Its Own Format String * Leave a Substitution Open When Matching a Format Literal --- .github/workflows/test-pull-request.yml | 27 +- .vscode/tasks.json | 36 + CODESTYLE.md | 5 +- GOVERNANCE.md | 1 + HISTORY.md | 5 + OPERATIONS.md | 13 + README.md | 5 + easystart/components/easystart/easystart.h | 5 +- pyproject.toml | 13 +- templates/apollo-plt-1b.yaml | 2 +- templates/basic.yaml | 2 +- templates/efun-sh331.yaml | 4 +- templates/konnected-blaq.yaml | 2 +- templates/smarthome-ceilsense.yaml | 2 +- test/heltec-wifi-lora32-v4-r8.yaml | 20 +- test/m5stack-cores3.yaml | 17 +- test/m5stack-dial.yaml | 4 +- test/norvi-enet-ae06-r.yaml | 3 +- tests/compile_lint/test_lint.py | 519 +++++++++++ tools/compile-lint/pyproject.toml | 17 + .../compile-lint/src/compile_lint/__init__.py | 1 + .../compile-lint/src/compile_lint/__main__.py | 5 + tools/compile-lint/src/compile_lint/lint.py | 848 ++++++++++++++++++ tools/compile-lint/src/compile_lint/py.typed | 0 uv.lock | 61 +- 25 files changed, 1580 insertions(+), 37 deletions(-) create mode 100644 tests/compile_lint/test_lint.py create mode 100644 tools/compile-lint/pyproject.toml create mode 100644 tools/compile-lint/src/compile_lint/__init__.py create mode 100644 tools/compile-lint/src/compile_lint/__main__.py create mode 100644 tools/compile-lint/src/compile_lint/lint.py create mode 100644 tools/compile-lint/src/compile_lint/py.typed diff --git a/.github/workflows/test-pull-request.yml b/.github/workflows/test-pull-request.yml index 1c579c5..134db23 100644 --- a/.github/workflows/test-pull-request.yml +++ b/.github/workflows/test-pull-request.yml @@ -3,6 +3,7 @@ name: Test pull request action # Source lint plus a change-gated compile test, run on the ptr727/esphome-nonroot image the repo deploys. # - template-compile-test builds the example devices, which between them compose every published template. # - test/easystart.yaml also covers the easystart external component. +# - Each leg then lints the build it compiled, failing on a compiler warning in this repository's own code. # It costs minutes of esp-idf build, so it runs only when what it covers moves. # Operational model. # - develop takes direct signed commits, where push runs advisory CI. @@ -80,7 +81,8 @@ jobs: fi WORKFLOW='\.github/workflows/test-pull-request\.yml$' # The test/ tree hosts the example devices, and test/easystart.yaml compiles easystart/components. - if grep -qE "^(templates/|test/|easystart/components/|$WORKFLOW)" <<<"$DIFF"; then + # The compile lint runs in the same legs, so a change to it reruns them too. + if grep -qE "^(templates/|test/|easystart/components/|tools/compile-lint/|$WORKFLOW)" <<<"$DIFF"; then echo "template-test=true" >> "$GITHUB_OUTPUT" else echo "template-test=false" >> "$GITHUB_OUTPUT" @@ -149,10 +151,12 @@ jobs: key="$(python3 -c 'import base64, os; print(base64.b64encode(os.urandom(32)).decode())')" sed -i "s|REPLACE_WITH_BASE64_32_BYTE_KEY|$key|" secrets.yaml + # The cache sits at a fixed path, and the log there names the build path, so the lint step finds the build. - name: Compile template test config step run: | set -Eeuo pipefail - cache="$(mktemp -d)" + cache="$RUNNER_TEMP/esphome-cache" + mkdir -p "$cache" chmod -R 0777 "$cache" . docker run --rm --user 1000:1000 \ --volume "$PWD":/config \ @@ -162,7 +166,24 @@ jobs: set -Eeuo pipefail /entrypoint/cache.sh esphome config /config/test/${{ matrix.config }}.yaml > /dev/null - esphome compile /config/test/${{ matrix.config }}.yaml + esphome compile /config/test/${{ matrix.config }}.yaml 2>&1 | tee /cache/compile.log + ' + + # Recompile the generated main.cpp syntax-only with stricter warnings, keeping what lands in this repository's code. + # A finding annotates its YAML lambda line or component file, and fails the leg, per OPERATIONS.md "Compile Lint". + # It runs as the runner's own user, so it can append to the step summary file mounted into the container. + - name: Lint compiled code step + run: | + set -Eeuo pipefail + docker run --rm --user "$(id -u):$(id -g)" \ + --volume "$PWD":/config \ + --volume "$RUNNER_TEMP/esphome-cache":/cache \ + --volume "$GITHUB_STEP_SUMMARY":/summary.md \ + --env PYTHONPATH=/config/tools/compile-lint/src \ + ptr727/esphome-nonroot:latest \ + bash -c ' + set -Eeuo pipefail + python3 -m compile_lint --config-root /config --annotate --summary /summary.md --from-log /cache/compile.log ' # GitHub Actions does not support required status checks on conditional jobs. diff --git a/.vscode/tasks.json b/.vscode/tasks.json index ae4d2b4..f14e4d3 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -45,6 +45,42 @@ "clear": false } }, + { + // The compile above, then the compile lint over the build it produced, per OPERATIONS.md "Compile Lint". + // A warning in a lambda or component lands in the Problems pane on its YAML or source line. + "label": "ESPHome: Compile and Lint", + "type": "process", + "command": "docker", + "args": [ + "exec", + "--env", + "PYTHONPATH=/config/tools/compile-lint/src", + "esphome", + "python3", + "-m", + "compile_lint", + "--config-root", + "/config", + "--compile", + "/config/${relativeFile}" + ], + "problemMatcher": { + "owner": "compile-lint", + "fileLocation": ["relative", "${workspaceFolder}"], + "pattern": { + "regexp": "^[^:\\s]+: (.+):(\\d+):(\\d+): (warning|error): (.*)$", + "file": 1, + "line": 2, + "column": 3, + "severity": 4, + "message": 5 + } + }, + "presentation": { + "showReuseMessage": false, + "clear": false + } + }, { "label": "ESPHome: Clean", "type": "process", diff --git a/CODESTYLE.md b/CODESTYLE.md index 7978963..4abfd27 100644 --- a/CODESTYLE.md +++ b/CODESTYLE.md @@ -53,7 +53,7 @@ This is packaged as the `python-codestyle` Skill at `.agents/skills/python-codes ### This Repository's Python -All Python in this repository lives in one **uv workspace** rooted at the repository root, and every project in it takes the `build` profile. The root [`pyproject.toml`][root-pyproject] is a virtual workspace root with no `[project]` of its own: it lists the member projects, holds the shared `dev` dependency group, and carries the one `[tool.ruff]`, `[tool.pyright]`, and `[tool.pytest.ini_options]` configuration every member uses. The single root `uv.lock` pins every member's dependencies and the dev tools together. The one member today is [`easystart/python`][easystart-python], the EasyStart BLE monitor, a `src`-layout project with its own `[project]`, `[build-system]`, and an `easystart-monitor` console script. +All Python in this repository lives in one **uv workspace** rooted at the repository root, and every project in it takes the `build` profile. The root [`pyproject.toml`][root-pyproject] is a virtual workspace root with no `[project]` of its own: it lists the member projects, holds the shared `dev` dependency group, and carries the one `[tool.ruff]`, `[tool.pyright]`, and `[tool.pytest.ini_options]` configuration every member uses. The single root `uv.lock` pins every member's dependencies and the dev tools together. The members are [`easystart/python`][easystart-python], the EasyStart BLE monitor, and [`tools/compile-lint`][compile-lint], the compile lint. Each is a `src`-layout project with its own `[project]`, `[build-system]`, and console script. The compile lint also runs inside the ESPHome image, so it needs only the standard library and ESPHome's own PyYAML. - **uv is the only supported toolchain.** Run everything from the repository root with `uv run`, which syncs the workspace first: `uv run ruff check`, `uv run ruff format --check`, `uv run pyright`, `uv run pytest`, and a member's own script such as `uv run easystart-monitor`. There is no pip or `requirements.txt` path. - **The hub validator is the CI gate.** A root `pyproject.toml`, `uv.lock`, and `tests/` are what its tree detection keys on, so it runs `uv sync --all-groups --frozen`, ruff, pyright, and pytest with coverage uploaded to Codecov, with no repository-specific CI step. The Codecov statuses are informational, per `codecov.yml`. @@ -80,7 +80,7 @@ The C++ here is a small amount of hand-written code that an ESPHome build compil The style derives from ESPHome upstream (LLVM-based, 2-space indent, 120-column limit) so the committed sources format clean and do not churn against upstream conventions. A repository's `.h` is read as C++ by context, which is what ESPHome and Arduino headers are. -Semantic and static analysis is deliberately out of scope. The downstream ESPHome build compiles this code and does the compile-time checking, and it holds the compile database that a `clang-tidy` pass would need. Adding a second, weaker analysis here would duplicate that without the context to be correct. +Semantic checking runs on the generated code rather than on the YAML. A lambda only compiles inside the `main.cpp` an ESPHome build produces. The compile lint rechecks that build with stricter warnings, per `OPERATIONS.md` "Compile Lint". Checking the YAML text directly would mean reimplementing ESPHome's code generation. ### Conventions @@ -90,6 +90,7 @@ Semantic and static analysis is deliberately out of scope. The downstream ESPHom [clang-format]: ./.clang-format +[compile-lint]: ./tools/compile-lint/ [cspell]: ./cspell.json [devices]: ./DEVICES.md [easystart-python]: ./easystart/python/ diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 5619d33..405551e 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -276,6 +276,7 @@ Every repo's GitHub repository details (the About panel) follow a fixed conventi - [`test/`](./test/), example device configs that between them compose every published template, each overlay on its board's example. CI compiles these against the current ESPHome release, so a published template is known to build, and they double as usage samples. - [`easystart/`](./easystart/), the Micro-Air EasyStart soft-starter project: the ESPHome external component, its protocol and reverse-engineering notes, and the standalone BLE monitor utility under `easystart/python`. - [`archive/`](./archive/), retired device configs kept for reference, neither built nor validated. +- [`tools/`](./tools/), repository tooling. [`tools/compile-lint`](./tools/compile-lint/) is the compile lint the compile test runs over each build, per [`OPERATIONS.md`](./OPERATIONS.md) "Compile Lint". - This repo carries no local `repo-config/` or `spec/secrets.json`: the hub hosts the branch ruleset and settings payloads and the secret-name ground truth, and [`AUDIT.md`](./AUDIT.md) "General Settings and Rulesets" and "Secrets" check against the hub's checkout rather than a local copy. - [`host-tools.json`](./host-tools.json), what a development host needs beyond the fleet baseline, layered over it tighten-only. Empty here, because every tool this repo's procedures need is already declared at its own floor. - [`.devcontainer/`](./.devcontainer/), the offline debugging container described above. diff --git a/HISTORY.md b/HISTORY.md index be89848..ed1417f 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -9,6 +9,11 @@ ESPHome configuration templates and projects. - Added M5Stack GC0308 camera overlay support. - Added M5Stack Dial support, with an LVGL and rotary encoder example device. - Changed the example devices to disable the API and WiFi reboot timeouts, so a flashed one runs on a bench without Home Assistant. + - Added a compile lint for compiler warnings in a lambda or the EasyStart component, so a warning surfaces while developing and at pull request time. + - Added a VS Code task that compiles the open config and lists each lint finding in the Problems pane on its YAML or source line. + - Added a pull request check that fails a compile leg on a lint finding, annotating its YAML or source line. + - Added a pull request check that fails an example device that does not disable both reboot timeouts. + - Fixed the float math warnings the compile lint finds in the templates and the EasyStart component. - Version 2.3: - Changed OTA to encryption only, using the API key, so only the upgrade bridge build still needs the `ota_password` secret. - Fixed the compile warnings the templates and the EasyStart component can address. diff --git a/OPERATIONS.md b/OPERATIONS.md index 308ef9c..2beb419 100644 --- a/OPERATIONS.md +++ b/OPERATIONS.md @@ -141,6 +141,18 @@ fi - **The container runs as the invoking user**, so what it writes stays owned by that user. The image points its build tree and its data directory at `/cache`. The only thing it leaves in the checkout is `test/.gitignore`, which is ignored. CI pins uid 1000 and chmods the checkout instead, because its runner owns the files differently. - **A local compile is not the whole gate.** CI runs source lint too. Its change-detection job also fails when no example device in [`test/`][test] includes a device template. An overlay composes into its board's example device rather than getting one of its own. A new utility include joins that job's exemption list instead. +### Compile Lint + +A lambda is C++ that only compiles inside the `main.cpp` ESPHome generates. Its warnings land in a compile log among hundreds from ESPHome and the framework, where they are easy to miss. The compile lint in [`tools/compile-lint`][compile-lint] reruns the compile of `main.cpp` from the build ESPHome left behind. The rerun is syntax-only, uses the build's own flags plus `-Wdouble-promotion`, and keeps only the diagnostics located in this repository's code. It never builds firmware and never changes what gets flashed. + +- **Run it with every compile.** The VS Code task "ESPHome: Compile and Lint" compiles the open file in the dashboard's container, then lints that build. Each finding lands in the Problems pane on its YAML or source line. On a throwaway container, replace the compile call in [Compiling the Way CI Does][compiling-the-way-ci-does] with `PYTHONPATH=/config/tools/compile-lint/src python3 -m compile_lint --config-root /config --compile /config/test/