diff --git a/.markdownlint-cli2.jsonc b/.markdownlint-cli2.jsonc index aac8a03..d1be627 100644 --- a/.markdownlint-cli2.jsonc +++ b/.markdownlint-cli2.jsonc @@ -3,7 +3,8 @@ // Prose paragraphs and data-heavy tables/URLs are intentionally long. // Reflowing at 80 cols hurts readability and churns diffs. "MD013": false, - // MD033 (inline HTML) stays enabled: HTML comments (reference-link dividers) pass it, and elements are flagged so native markdown wins. + // MD033 (inline HTML) stays enabled so native markdown wins - HTML comments (reference-link dividers) pass it, and details/summary are allowed for GitHub collapsibles, which have no markdown equivalent. Every other element still flags. + "MD033": { "allowed_elements": ["details", "summary"] }, // Require fenced code blocks over the legacy 4-space-indented style. "MD046": { "style": "fenced" }, // MD060 (table column style) is not enforced - allow both compact diff --git a/AGENTS.md b/AGENTS.md index 35ca472..0f1c460 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -60,7 +60,7 @@ A state-changing GitHub call is the highest-blast-radius thing an agent does her ## Release Model -The **two-phase model is the default**: PRs build fast, publishing is batched. See [README "Release Distribution Model"](./WORKFLOW.md) for the full rationale. The load-bearing rules follow. The auto-publish paths (bot push, schedule) apply to `release` repos. **Operational** repos differ - dispatch-only release, no auto-publish - see "Operational Repositories". +The **two-phase model is the default**: PRs build fast, publishing is batched. See [`WORKFLOW.md`](./WORKFLOW.md) for the full rationale. The load-bearing rules follow. The auto-publish paths (bot push, schedule) apply to `release` repos. **Operational** repos differ - dispatch-only release, no auto-publish - see "Operational Repositories". - **PRs smoke-test only.** [`test-pull-request.yml`](./.github/workflows/test-pull-request.yml) always runs unit tests, then a `dorny/paths-filter` `changes` job gates a **reduced** build of only the changed targets (Docker `linux/amd64` only, executable on a representative runtime subset), never pushing. Build-workflow files are intentionally not in the path filters - a filter can't tell a logic change from an action-version bump - so a workflow-only change isn't smoke-built; the reusable workflows are exercised by the next run that uses them (a later code PR's smoke build, or the scheduled/publish run). Workflow YAML is still linted in CI by the lint job's `actionlint` step; also run `actionlint` locally before pushing to catch issues early. - **A human merge never auto-publishes.** [`publish-release.yml`](./.github/workflows/publish-release.yml) is the sole publisher; each run builds the **single trigger branch** (`main` a release, `develop` a prerelease). A first `plan` job decides once whether the run publishes and every other job gates on its output. It publishes on a **`workflow_dispatch`** of `main`/`develop` (the human-initiated release), a **code-affecting bot push to `main`** (the codegen App merges every Dependabot/codegen PR, so `github.actor` is the gate - a human merge/promotion to `main` skips), or a **weekly `schedule`** (Docker only, to refresh the base image). The `push` is main-only and paths-filtered, so a develop bot merge and an Actions-only bump publish nothing. A source-only repo publishes on dispatch only. @@ -215,7 +215,7 @@ The repo runs a review loop on every PR: local agent iteration plus remote autom **Do not merge - and do not enable auto-merge - unless ALL of these hold:** 1. Required status checks are green (`mergeStateStatus: CLEAN`), **and** -2. A Copilot review is confirmed on the **current head SHA** (not an earlier push), **and** +2. A Copilot review is confirmed on the **current head SHA** by matching the review's commit SHA to the head, not an earlier push - a push makes required checks go green **before** the re-review lands, so a green merge-state can precede the current-head review and never signals readiness on its own, **and** 3. **Every** Copilot finding on that head SHA is closed out - all review threads resolved, **and** any issue-level Copilot comments (which have no resolve action) triaged and replied to - so zero outstanding findings remain, **and** 4. The maintainer has given **explicit** permission to merge. diff --git a/CODESTYLE.md b/CODESTYLE.md index 16a2f36..356190d 100644 --- a/CODESTYLE.md +++ b/CODESTYLE.md @@ -361,10 +361,10 @@ This is the style guide for any **Python project(s)** in this repo. - **Disabled markdownlint rules** - repo-specific; `.markdownlint-cli2.jsonc` at the repo root is the source of truth, not any example rule named here. - **VS Code config home** - editor **settings/extensions** may live in `.vscode/*.json` **or** the `.code-workspace`; **tasks / launch / debug** configs can only be external `.vscode/*.json` (they cannot live in the workspace file). A `[vscode-tasks]` reference must point wherever the repo actually keeps `tasks.json`. -**Two profiles.** A repo's Python is one of two shapes, and the rest of this section (uv project, `uv.lock`, `uv run`, `src` layout, pytest coverage) describes the **project** profile. The two differ by whether the Python has **third-party runtime dependencies**, which shows up structurally in `pyproject.toml`, so the audit detects the profile there (`python.profile.detect`): +**Two profiles.** A repo's Python is one of two shapes, declared as the `build` or `lint-only` profile and validated against the `pyproject.toml` shape. The rest of this section (uv project, `uv.lock`, `uv run`, `src` layout, pytest coverage) describes the **Project** shape (the `build` profile). The two differ by whether the Python has **third-party runtime dependencies**, which shows up structurally in `pyproject.toml`, so the audit reads the shape there (`python.profile.detect`): - **Project** - the Python has third-party runtime dependencies, or is the repo's deliverable. It is a PEP 621 uv project: `[project]` with `dependencies` (dev tools in `[project.optional-dependencies]` or `[dependency-groups]`), a `[build-system]`, and a committed `uv.lock` (pinned LF - see [Line Endings][line-endings]); CI runs `uv sync --frozen` + `uv run `, so the lockfile pins tool versions. -- **Scripts** - stdlib-only utility scripts embedded in a **non-Python** repo (e.g. a Python tooling subtree of a `csharp` app). Run the tools with **`uvx`** (no project install, no lockfile): the `pyproject.toml` carries **only** tool config (here `[tool.ruff]` plus a `[tool.pyright]` editor block) - no `[project]`, no `[build-system]`, no `uv.lock` (that metadata would misrepresent it as a shippable package). **In this repo** (`easystart/python`, the standalone BLE monitor) **ruff is the only CI gate**, and a `[tool.pyright]` block in **standard** mode keeps Pylance quiet in the editor. There is no mypy gate: the subtree is a single script importing an untyped BLE library, so there is nothing for a second type checker to add over Pylance. CI pins the tool version, **`uvx ruff@0.15.22`**, and the VS Code tasks carry the same pin so the two cannot drift. That pin is a deliberate divergence from the fleet default of running `uvx @latest` unpinned - the fleet reasons that a manual pin Dependabot does not track goes stale silently, while this repo reasons that an unpinned linter turns an upstream release into a surprise CI failure on an unrelated change. The divergence is raised with the fleet rather than settled locally, so expect this paragraph to move once that is decided. `.py` files follow the repo's line-ending default (LF here - see [Line Endings][line-endings]). There is no pytest suite, so the coverage expectation is N/A. +- **Scripts** (the `lint-only` profile) - stdlib-only utility scripts embedded in a **non-Python** repo (e.g. a Python tooling subtree of a `csharp` app). Run the tools with **`uvx`** (no project install, no lockfile): the `pyproject.toml` carries **only** tool config (here `[tool.ruff]` plus a `[tool.pyright]` editor block) - no `[project]`, no `[build-system]`, no `uv.lock` (that metadata would misrepresent it as a shippable package). **In this repo** (`easystart/python`, the standalone BLE monitor) **ruff is the only CI gate**, and a `[tool.pyright]` block in **standard** mode keeps Pylance quiet in the editor. There is no mypy gate: the subtree is a single script importing an untyped BLE library, so there is nothing for a second type checker to add over Pylance. CI pins the tool version, **`uvx ruff@0.15.22`**, and the VS Code tasks carry the same pin so the two cannot drift. That pin is a deliberate divergence from the fleet default of running `uvx @latest` unpinned - the fleet reasons that a manual pin Dependabot does not track goes stale silently, while this repo reasons that an unpinned linter turns an upstream release into a surprise CI failure on an unrelated change. The divergence is raised with the fleet rather than settled locally, so expect this paragraph to move once that is decided. `.py` files follow the repo's line-ending default (LF here - see [Line Endings][line-endings]). There is no pytest suite, so the coverage expectation is N/A. ### Toolchain diff --git a/ESPHome-Config.code-workspace b/ESPHome-Config.code-workspace index 6d6ab78..612eef8 100755 --- a/ESPHome-Config.code-workspace +++ b/ESPHome-Config.code-workspace @@ -41,7 +41,8 @@ "ms-vscode-remote.vscode-remote-extensionpack", "ms-vscode.vscode-serial-monitor", "redhat.vscode-yaml", - "streetsidesoftware.code-spell-checker" + "streetsidesoftware.code-spell-checker", + "xaver.clang-format" ] } -} \ No newline at end of file +} diff --git a/OPERATIONS.md b/OPERATIONS.md index afc6e91..33528b3 100644 --- a/OPERATIONS.md +++ b/OPERATIONS.md @@ -159,14 +159,14 @@ The steps below run ESPHome outside the live instance, on a workstation, which i ### Debugging in the DevContainer -- Complete the [VSCode setup](#vscode-setup). +- Complete the [VSCode setup][vscode-setup]. - Open the workspace and clone into the devcontainer volume. - TODO: serial port forwarding configuration. ### Debugging on Windows - Install Python from the Microsoft Store. -- Complete the [VSCode setup](#vscode-setup). +- Complete the [VSCode setup][vscode-setup]. - Compile a project: `esphome compile test/adafruit-esp32-s3-feather.yaml`. - Plug in the device, holding Boot and pressing Reset when required. - List COM ports from PowerShell: @@ -191,7 +191,7 @@ The steps below run ESPHome outside the live instance, on a workstation, which i - WSL: `ls /dev/tty*`, for example `/dev/ttyACM0`, `/dev/ttyUSB0` - Install VSCode and the Remote Explorer extension. - Open a VSCode Remote WSL Ubuntu session. - - Complete the [VSCode setup](#vscode-setup) in the remote WSL session. + - Complete the [VSCode setup][vscode-setup] in the remote WSL session. - List ports: `ls /dev/tty*`. - Upload firmware: `esphome run --device /dev/ttyUSB0 test/esp32-s3-devkitc.yaml` - Unbind the serial port. @@ -200,7 +200,7 @@ The steps below run ESPHome outside the live instance, on a workstation, which i ### Debugging in PlatformIO -- Complete the [VSCode setup](#vscode-setup) and install the PlatformIO extension. +- Complete the [VSCode setup][vscode-setup] and install the PlatformIO extension. - Compile the project with `esphome compile [project]`. - Launch the PlatformIO extension and open the project folder, for example `./.esphome/build/esp32-s3-test`. - PlatformIO opens a new instance of VSCode for that project. @@ -226,12 +226,13 @@ The steps below run ESPHome outside the live instance, on a workstation, which i [garage-presence-sensor]: ./garage-presence-sensor.yaml [templates]: ./templates/ [test-workflow]: ./.github/workflows/test-pull-request.yml +[vscode-setup]: #vscode-setup [dashboard-link]: http://localhost:6052/ -[esp-idf-framework-link]: https://esphome.io/components/esp32#esp-idf-framework -[esphome-cli-link]: https://esphome.io/guides/cli +[esp-idf-framework-link]: https://esphome.io/components/esp32/#esp-idf-framework +[esphome-cli-link]: https://esphome.io/guides/cli/ [esphome-nonroot-link]: https://github.com/ptr727/ESPHome-NonRoot [espressif32-versions-link]: https://registry.platformio.org/platforms/platformio/espressif32/versions [framework-espidf-link]: https://registry.platformio.org/tools/platformio/framework-espidf diff --git a/README.md b/README.md index f085c6d..0d54e13 100755 --- a/README.md +++ b/README.md @@ -8,131 +8,131 @@ A collection of ESPHome hardware templates and projects I use in my home automat ## Templates -A [collection](./templates/) of utility and device-specific configuration templates. +A [collection][templates] of utility and device-specific configuration templates. -Note that for devices with native ESPHome factory firmware, I opt to strip out the generic project and [Improv](https://www.improv-wifi.com/) configuration in favor of a custom configuration specific to my environment. This also cuts down on resource utilization by removing unused features. +Note that for devices with native ESPHome factory firmware, I opt to strip out the generic project and [Improv][improv-wifi-link] configuration in favor of a custom configuration specific to my environment. This also cuts down on resource utilization by removing unused features. -Every device template has an example configuration in [test](./test/) that CI compiles against the current ESPHome release, so a published template is known to build. The examples also serve as minimal usage samples. +Every device template has an example configuration in [test][test] that CI compiles against the current ESPHome release, so a published template is known to build. The examples also serve as minimal usage samples. ### Device Templates #### Ayococr X5P WiFi Plug -- [Template](./templates/aoycocr-x5p.yaml) for the [Ayococr X5P](https://www.amazon.com/dp/B07R6CT3G7) US 120V AC WiFi wall plug. -- See blog [post](https://blog.insanegenius.com/2020/09/10/tuya-to-tasmota-to-esphome/) for firmware conversion details. +- [Template][aoycocr-x5p] for the [Ayococr X5P][amazon-dp-b07r6ct3g7-link] US 120V AC WiFi wall plug. +- See blog [post][blog-tuya-to-esphome-link] for firmware conversion details. #### EFUN SH331 WiFi Power Monitoring Plug -- [Template](./templates/efun-sh331.yaml) for the [EFUN SH331W](https://www.amazon.com/gp/product/B07DCJ7TDR) US 120V AC WiFi power monitoring wall plug. -- See blog [post](https://blog.insanegenius.com/2020/09/10/tuya-to-tasmota-to-esphome/) for firmware conversion details. +- [Template][efun-sh331] for the [EFUN SH331W][efun-sh331w-link] US 120V AC WiFi power monitoring wall plug. +- See blog [post][blog-tuya-to-esphome-link] for firmware conversion details. - Note: No longer listed on Amazon. #### Sonoff TH10/TH16 WiFi Relay -- [Template](./templates/sonoff-th10.yaml) for the [Sonoff TH10](https://www.amazon.com/Sonoff-Temperature-Monitoring-Assistant-DS18B20/dp/B08DFQ2NP3) and [Sonoff TH16](https://www.amazon.com/Sonoff-Temperature-Humidity-Monitoring-Assistant/dp/B07TF5SYGL) WiFi relay. -- Follow the Tasmota [guide](https://tasmota.github.io/docs/devices/Sonoff-TH/) for flashing instructions. -- Note: Sonoff TH10 and TH16 have been replaced by the [SONOFF TH Origin](https://itead.cc/product/sonoff-th/), see the [Tasmota Templates](https://templates.blakadder.com/sonoff_THR316.html) for pin layouts. +- [Template][sonoff-th10] for the [Sonoff TH10][amazon-sonoff-temperature-monitoring-assistant-ds18b20-dp-link] and [Sonoff TH16][amazon-sonoff-temperature-humidity-monitoring-assistant-dp-link] WiFi relay. +- Follow the Tasmota [guide][tasmota-sonoff-th-link] for flashing instructions. +- Note: Sonoff TH10 and TH16 have been replaced by the [SONOFF TH Origin][itead-product-sonoff-th-link], see the [Tasmota Templates][templates-sonoff-thr316-link] for pin layouts. #### Sonoff S31 WiFi Power Monitoring Plug -- [Template](./templates/sonoff-s31.yaml) for the [Sonoff S31](https://www.amazon.com/Sonoff-Monitoring-Certified-Assistant-Supporting/dp/B08GKGS197) US 120V AC WiFi power monitoring wall plug. -- Follow the Tasmota [guide](https://tasmota.github.io/docs/devices/Sonoff-S31/) for flashing instructions. +- [Template][sonoff-s31] for the [Sonoff S31][amazon-sonoff-monitoring-certified-assistant-supporting-dp-link] US 120V AC WiFi power monitoring wall plug. +- Follow the Tasmota [guide][tasmota-sonoff-s31-link] for flashing instructions. #### Norvi NORVI-ENET-AE06-R DIN Controller -- [Template](./templates/norvi-enet-ae06-r.yaml) for the [Norvi NORVI-ENET-AE06-R](https://shop.norvi.lk/products/norvi-enet-ae06-r) or [SensOper SC-EN-I6-RO4](https://sensoper.com/shop/sc-en-i6-ro4/) ESP32 DIN form factor controllers. +- [Template][norvi-enet-ae06-r] for the [Norvi NORVI-ENET-AE06-R][shop-products-norvi-enet-ae06-r-link] or [SensOper SC-EN-I6-RO4][sensoper-shop-sc-en-i6-ro4-link] ESP32 DIN form factor controllers. - Flash over USB. - Note: - - Norvi devices are sold under the [SensOper Controls](https://sensoper.com/) brand in the US and available at the [SensOper store](https://sensoper.com/shop). - - Norvi branded devices can be shipped to the US from the Sri Lanka based [Norvi store](https://shop.norvi.lk), or the Germany based [CarTFT store](https://www.cartft.com). - - Norvi addressed PSU interference and drifting ADC issues through a design change (May 2024 date code), see the [notes](./templates/norvi-enet-ae06-r.yaml) section in YML for details. - - There is an ongoing issue where enabling output on GPIO15 turns the status LED's on and could interfere with input sensors, see the [notes](./templates/norvi-enet-ae06-r.yaml) section in YML for details. + - Norvi devices are sold under the [SensOper Controls][sensoper-link] brand in the US and available at the [SensOper store][sensoper-shop-link]. + - Norvi branded devices can be shipped to the US from the Sri Lanka based [Norvi store][shop-link], or the Germany based [CarTFT store][cartft-link]. + - Norvi addressed PSU interference and drifting ADC issues through a design change (May 2024 date code), see the [notes][norvi-enet-ae06-r] section in YML for details. + - There is an ongoing issue where enabling output on GPIO15 turns the status LEDs on and could interfere with input sensors, see the [notes][norvi-enet-ae06-r] section in YML for details. #### RocketController ASTRA DIN Controller -- [Template](./templates/rocket-astra.yaml) for the [RocketController / RocketDyn ASTRA](https://www.rocketcontroller.com) ESP32 DIN form factor controllers. -- Follow the RocketController [guide](https://www.rocketcontroller.com/docs/esphome) for flashing instructions. +- [Template][rocket-astra] for the [RocketController / RocketDyn ASTRA][rocketcontroller-link] ESP32 DIN form factor controllers. +- Follow the RocketController [guide][rocketcontroller-docs-esphome-link] for flashing instructions. #### Kincony KC868-ASR DIN Controller -- [Template](./templates/kincony-kc868-asr.yaml) for the [Kincony KC868-ASR](https://www.kincony.com/esp32-sd-card-rtc-ds3231.html) ESP32 DIN form factor controllers. +- [Template][kincony-kc868-asr] for the [Kincony KC868-ASR][kincony-esp32-sd-card-rtc-ds3231-link] ESP32 DIN form factor controllers. - Flash over USB. #### GL-S10 POE Ethernet Bluetooth Proxy -- [Template](./templates/gls10-bluetooth-proxy.yaml) to use the [GL-S10 IoT Gateway](https://www.gl-inet.com/products/gl-s10/) as a Bluetooth proxy. -- Follow the Blakadder [guide](https://blakadder.com/gl-s10/) for flashing instructions. +- [Template][gls10-bluetooth-proxy] to use the [GL-S10 IoT Gateway][gl-inet-products-gl-s10-link] as a Bluetooth proxy. +- Follow the Blakadder [guide][blakadder-gl-s10-link] for flashing instructions. - Warning: Do not connect USB power and POE power at the same time. #### Konnected blaQ Smart Garage Door Controller -- [Template](./templates/konnected-blaq.yaml) for the [Konnected blaQ](https://konnected.io/products/smart-garage-door-opener-blaq-myq-alternative) smart garage door controller. -- This is a Home Assistant friendly alternative to the Chamberlain myQ that [cut off HA access](https://www.home-assistant.io/blog/2023/11/06/removal-of-myq-integration/). -- Imports Konnected's upstream [firmware package](https://github.com/konnected-io/konnected-esphome/blob/master/garage-door-GDOv2-Q.yaml) and surgically strips stock provisioning, see the [template](./templates/konnected-blaq.yaml) for details. +- [Template][konnected-blaq] for the [Konnected blaQ][konnected-products-smart-garage-door-opener-blaq-myq-alternative-link] smart garage door controller. +- This is a Home Assistant friendly alternative to the Chamberlain myQ that [cut off HA access][home-assistant-blog-2023-link]. +- Imports Konnected's upstream [firmware package][github-konnected-io-konnected-esphome-link] and surgically strips stock provisioning, see the [template][konnected-blaq] for details. #### Apollo PLT-1B Plant Sensor -- [Template](./templates/apollo-plt-1b.yaml) for the [Apollo PLT-1B](https://apolloautomation.com/products/plt-1) plant soil sensor. -- Imports Apollo's upstream [firmware package](https://github.com/ApolloAutomation/PLT-1/blob/main/Integrations/ESPHome/PLT-1B.yaml) and surgically strips the stock provisioning, see the [template](./templates/apollo-plt-1b.yaml) for details. +- [Template][apollo-plt-1b] for the [Apollo PLT-1B][apolloautomation-products-plt-1-link] plant soil sensor. +- Imports Apollo's upstream [firmware package][github-apolloautomation-plt-1-link] and surgically strips the stock provisioning, see the [template][apollo-plt-1b] for details. #### SmartHomeShop CeilSense Presence and Air Sensor -- [Template](./templates/smarthome-ceilsense.yaml) for the [SmartHomeShop CeilSense v1 Complete](https://ceilsense.nl/en/) presence sensor. -- Imports SmartHomeShop's upstream [firmware package](https://github.com/smarthomeshop/ceilsense/blob/main/ceilsense-v1/ceilsense-complete-wifi-ld2412.yaml) and surgically strips the stock provisioning, see the [template](./templates/smarthome-ceilsense.yaml) for details. +- [Template][smarthome-ceilsense] for the [SmartHomeShop CeilSense v1 Complete][ceilsense-en-link] presence sensor. +- Imports SmartHomeShop's upstream [firmware package][github-smarthomeshop-ceilsense-link] and surgically strips the stock provisioning, see the [template][smarthome-ceilsense] for details. #### Micro-Air EasyStart Soft-Starter -- [Template](./templates/easystart.yaml) to attach a [Micro-Air EasyStart](https://www.microair.net) AC compressor soft-starter to a Bluetooth proxy as a `ble_client`, exposing live current, estimated power, line frequency, last-start peak, short-cycle delay, system state, running state, and start / fault / learned-start counters in Home Assistant, plus a BLE signal-strength diagnostic for the link. -- Used by [`office-bluetooth-proxy.yaml`](./office-bluetooth-proxy.yaml), which sits near the HVAC units and attaches both compressors. -- The reverse-engineered Bluetooth protocol, the ESPHome external component, and a live BLE monitor utility are in [easystart/](./easystart/), see [easystart/README.md](./easystart/README.md) for details. +- [Template][easystart-template] to attach a [Micro-Air EasyStart][microair-link] AC compressor soft-starter to a Bluetooth proxy as a `ble_client`, exposing live current, estimated power, line frequency, last-start peak, short-cycle delay, system state, running state, and start / fault / learned-start counters in Home Assistant, plus a BLE signal-strength diagnostic for the link. +- Used by [`office-bluetooth-proxy.yaml`][office-bluetooth-proxy], which sits near the HVAC units and attaches both compressors. +- The reverse-engineered Bluetooth protocol, the ESPHome external component, and a live BLE monitor utility are in [easystart/][easystart], see [easystart/README.md][easystart-readme] for details. ### Utility Templates Shared building-block includes, composed via `packages:` by the device templates and per-device configs: -- [`api.yaml`](./templates/api.yaml) - API with encryption and a configurable `api_reboot_timeout`. -- [`ota.yaml`](./templates/ota.yaml) - ESPHome OTA with password. -- [`logger.yaml`](./templates/logger.yaml) - logger configuration. -- [`time.yaml`](./templates/time.yaml) - Home Assistant time source. -- [`wifi.yaml`](./templates/wifi.yaml) - managed WiFi credentials from secrets. -- [`basic.yaml`](./templates/basic.yaml) - restart button plus status, uptime, and version sensors. -- [`common.yaml`](./templates/common.yaml) - bundles the api / ota / logger / time / wifi / basic includes for a typical device. -- [`debug.yaml`](./templates/debug.yaml) - debug component and debug text sensors. -- [`temperature.yaml`](./templates/temperature.yaml) - on-chip internal temperature sensor. -- [`ethernet-sensor.yaml`](./templates/ethernet-sensor.yaml) - Ethernet IP / MAC info text sensors. -- [`secrets.yaml`](./templates/secrets.yaml) - re-exports the root `secrets.yaml` so templates can resolve secrets. +- [`api.yaml`][api] - API with encryption and a configurable `api_reboot_timeout`. +- [`ota.yaml`][ota] - ESPHome OTA with password. +- [`logger.yaml`][logger] - logger configuration. +- [`time.yaml`][time] - Home Assistant time source. +- [`wifi.yaml`][wifi] - managed WiFi credentials from secrets. +- [`basic.yaml`][basic] - restart button plus status, uptime, and version sensors. +- [`common.yaml`][common] - bundles the api / ota / logger / time / wifi / basic includes for a typical device. +- [`debug.yaml`][debug] - debug component and debug text sensors. +- [`temperature.yaml`][temperature] - on-chip internal temperature sensor. +- [`ethernet-sensor.yaml`][ethernet-sensor] - Ethernet IP / MAC info text sensors. +- [`secrets.yaml`][secrets] - re-exports the root `secrets.yaml` so templates can resolve secrets. ### Board and Component Helpers #### RGB LED Status Component -- [Template](./templates/rgb-led-status.yaml) for boards that have an addressable RGB LED but no plain status LED. -- Serves as the [Status LED](https://esphome.io/components/status_led.html) component equivalent. +- [Template][rgb-led-status] for boards that have an addressable RGB LED but no plain status LED. +- Serves as the [Status LED][esphome-components-status-led-link] component equivalent. #### Espressif ESP32-S3-DevKitC-1 Devkit -- [Template](./templates/esp32-s3-devkitc.yaml) for the [ESP32-S3-DevKitC-1](https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-devkitc-1/index.html) and clone boards. +- [Template][esp32-s3-devkitc] for the [ESP32-S3-DevKitC-1][espressif-esp32-s3-devkitc-link] and clone boards. - Includes the on-chip temperature sensor and the RGB LED as status LED. -- The default [`esp32-s3-devkitc-1`](https://docs.platformio.org/en/latest/boards/espressif32/esp32-s3-devkitc-1.html) board is the `ESP32-S3-WROOM-1-N8`, 8MB Quad Flash and no PSRAM. +- The default [`esp32-s3-devkitc-1`][platformio-esp32-s3-devkitc-link] board is the `ESP32-S3-WROOM-1-N8`, 8MB Quad Flash and no PSRAM. - Compose the devkit template with the board definition matching the fitted module: - - [ESP32-S3-WROOM-1-N16R8](./templates/esp32-s3-wroom-1-n16r8.yaml): 16MB Quad Flash, 8MB Octal PSRAM, 3.3V. - - [ESP32-S3-WROOM-2-N16R8V](./templates/esp32-s3-wroom-2-n16r8v.yaml): 16MB Octal Flash, 8MB Octal PSRAM, 1.8V. - - [ESP32-S3-WROOM-2-N32R8V](./templates/esp32-s3-wroom-2-n32r8v.yaml): 32MB Octal Flash, 8MB Octal PSRAM, 1.8V. + - [ESP32-S3-WROOM-1-N16R8][esp32-s3-wroom-1-n16r8]: 16MB Quad Flash, 8MB Octal PSRAM, 3.3V. + - [ESP32-S3-WROOM-2-N16R8V][esp32-s3-wroom-2-n16r8v]: 16MB Octal Flash, 8MB Octal PSRAM, 1.8V. + - [ESP32-S3-WROOM-2-N32R8V][esp32-s3-wroom-2-n32r8v]: 32MB Octal Flash, 8MB Octal PSRAM, 1.8V. - Note: - The RGB LED moved from GPIO48 to GPIO38 at board revision v1.1, override the `rgb_led_pin` substitution if the LED stays dark. - WROOM-1 and WROOM-1U modules differ only in PCB antenna versus IPEX connector, and are identical to program. - - See the OTA and Octal flash mode notes in [esp32-s3-wroom-2-n32r8v.yaml](./templates/esp32-s3-wroom-2-n32r8v.yaml) before choosing a 32MB module. + - See the OTA and Octal flash mode notes in [esp32-s3-wroom-2-n32r8v.yaml][esp32-s3-wroom-2-n32r8v] before choosing a 32MB module. #### WEMOS LOLIN32 Lite Devkit -- [Template](./templates/wemos-lolin32-lite.yaml) for the [WEMOS LOLIN32 Lite](https://web.archive.org/web/20191002041532/https://wiki.wemos.cc/products:lolin32:lolin32_lite) and clone boards. +- [Template][wemos-lolin32-lite] for the [WEMOS LOLIN32 Lite][web-web-20191002041532-link] and clone boards. - Includes the LED as status LED. #### Adafruit ESP32-S3 Feather Devkit -- [Template](./templates/adafruit-esp32-s3-feather.yaml) for the [Adafruit ESP32-S3 Feather](https://www.adafruit.com/product/5323) board. +- [Template][adafruit-esp32-s3-feather] for the [Adafruit ESP32-S3 Feather][adafruit-product-5323-link] board. - Includes the on-chip temperature sensor and the RGB LED as status LED. -- Includes the [MAX17048](https://www.analog.com/en/products/max17048.html) I2C battery charge monitor. +- Includes the [MAX17048][analog-max17048-link] I2C battery charge monitor. ## Projects @@ -140,34 +140,34 @@ Per-device configs live in the repository root. Each sets `substitutions:` (devi ### Plant Sensors (Apollo PLT-1B) -- [`music-room-plant-sensor.yaml`](./music-room-plant-sensor.yaml), [`patio-plant-sensor.yaml`](./patio-plant-sensor.yaml), [`stairs-plant-sensor.yaml`](./stairs-plant-sensor.yaml), and [`upstairs-hallway-plant-sensor.yaml`](./upstairs-hallway-plant-sensor.yaml) use the [Apollo PLT-1B template](./templates/apollo-plt-1b.yaml). +- [`music-room-plant-sensor.yaml`][music-room-plant-sensor], [`patio-plant-sensor.yaml`][patio-plant-sensor], [`stairs-plant-sensor.yaml`][stairs-plant-sensor], and [`upstairs-hallway-plant-sensor.yaml`][upstairs-hallway-plant-sensor] use the [Apollo PLT-1B template][apollo-plt-1b]. ### Garage Presence and Air Sensor (CeilSense) -- [`garage-presence-sensor.yaml`](./garage-presence-sensor.yaml) uses the [SmartHomeShop CeilSense template](./templates/smarthome-ceilsense.yaml) for presence, CO2, temperature, humidity, lux, and pressure in the garage. +- [`garage-presence-sensor.yaml`][garage-presence-sensor] uses the [SmartHomeShop CeilSense template][smarthome-ceilsense] for presence, CO2, temperature, humidity, lux, and pressure in the garage. ### Garage Door Controller (Konnected blaQ) -- [`garage-door-controller.yaml`](./garage-door-controller.yaml) uses the [Konnected blaQ template](./templates/konnected-blaq.yaml). -- Note: currently powered off pending an upstream investigation into self-opening incidents, see the status block in the config and [issue #29](https://github.com/ptr727/ESPHome-Config/issues/29). +- [`garage-door-controller.yaml`][garage-door-controller] uses the [Konnected blaQ template][konnected-blaq]. +- Note: currently powered off pending an upstream investigation into self-opening incidents, see the status block in the config and [issue #29][issue-29-link]. ### Bluetooth Proxies (GL-S10) -- [`office-bluetooth-proxy.yaml`](./office-bluetooth-proxy.yaml) and [`pantry-bluetooth-proxy.yaml`](./pantry-bluetooth-proxy.yaml) use the [GL-S10 Bluetooth Proxy template](./templates/gls10-bluetooth-proxy.yaml). +- [`office-bluetooth-proxy.yaml`][office-bluetooth-proxy] and [`pantry-bluetooth-proxy.yaml`][pantry-bluetooth-proxy] use the [GL-S10 Bluetooth Proxy template][gls10-bluetooth-proxy]. ### Garage Fan Thermostats -- [`garage-door-fan-controller.yaml`](./garage-door-fan-controller.yaml) (Sonoff TH10) and [`garage-gate-fan-controller.yaml`](./garage-gate-fan-controller.yaml) (Norvi) control cool air ventilation fans in the garage based on temperature. -- See blog [post](https://blog.insanegenius.com/2021/08/11/trying-to-keep-my-garage-cool/) for project details. +- [`garage-door-fan-controller.yaml`][garage-door-fan-controller] (Sonoff TH10) and [`garage-gate-fan-controller.yaml`][garage-gate-fan-controller] (Norvi) control cool air ventilation fans in the garage based on temperature. +- See blog [post][blog-garage-cooling-link] for project details. ### Hot Water Recirculation Pump -- [`recirculation-pump-controller.yaml`](./recirculation-pump-controller.yaml) (Sonoff TH10) controls the whole home hot water recirculation pump using temperature probes on an interval / duration schedule. -- See blog [post](https://blog.insanegenius.com/2020/10/11/hot-water-recirculation-pump-controller/) for project details. +- [`recirculation-pump-controller.yaml`][recirculation-pump-controller] (Sonoff TH10) controls the whole home hot water recirculation pump using temperature probes on an interval / duration schedule. +- See blog [post][blog-recirculation-pump-link] for project details. ## Usage -- The standard [ESPHome](https://hub.docker.com/r/esphome/esphome) container does not support running as non-root. Deploy the [ESPHome-NonRoot](https://github.com/ptr727/ESPHome-NonRoot) container for non-root operation if desired. +- The standard [ESPHome][docker-hub-esphome-link] container does not support running as non-root. Deploy the [ESPHome-NonRoot][github-ptr727-esphome-nonroot-link] container for non-root operation if desired. - Set directory permissions: - `sudo chown -R nonroot:users /data/appdata/esphome` - `sudo chmod -R ug=rwx,o=rx /data/appdata/esphome` @@ -181,9 +181,105 @@ Per-device configs live in the repository root. Each sets `substitutions:` (devi ### Issues -- For general ESPHome support visit the [ESPHome Discord `#general-support`](https://discord.gg/dbwxp5R3). -- Only file an [issue](https://github.com/ptr727/ESPHome-Config/issues) if you believe there is a bug in a [template](./templates/) or one of my projects. +- For general ESPHome support visit the [ESPHome Discord `#general-support`][discord-dbwxp5r3-link]. +- Only file an [issue][issues-link] if you believe there is a bug in a [template][templates] or one of my projects. ## Debugging -Building, flashing, and debugging a device outside the live ESPHome instance is documented in [OPERATIONS.md](./OPERATIONS.md#debugging): VSCode setup, the devcontainer, Windows, Windows WSL, and PlatformIO. The framework and platform version notes are in [the same file](./OPERATIONS.md#framework-and-platform-versions). +Building, flashing, and debugging a device outside the live ESPHome instance is documented in [OPERATIONS.md][operations-debugging]: VSCode setup, the devcontainer, Windows, Windows WSL, and PlatformIO. The framework and platform version notes are in [the same file][operations-framework-and-platform-versions]. + + + +[adafruit-esp32-s3-feather]: ./templates/adafruit-esp32-s3-feather.yaml +[aoycocr-x5p]: ./templates/aoycocr-x5p.yaml +[api]: ./templates/api.yaml +[apollo-plt-1b]: ./templates/apollo-plt-1b.yaml +[basic]: ./templates/basic.yaml +[common]: ./templates/common.yaml +[debug]: ./templates/debug.yaml +[easystart-readme]: ./easystart/README.md +[easystart-template]: ./templates/easystart.yaml +[easystart]: ./easystart/ +[efun-sh331]: ./templates/efun-sh331.yaml +[esp32-s3-devkitc]: ./templates/esp32-s3-devkitc.yaml +[esp32-s3-wroom-1-n16r8]: ./templates/esp32-s3-wroom-1-n16r8.yaml +[esp32-s3-wroom-2-n16r8v]: ./templates/esp32-s3-wroom-2-n16r8v.yaml +[esp32-s3-wroom-2-n32r8v]: ./templates/esp32-s3-wroom-2-n32r8v.yaml +[ethernet-sensor]: ./templates/ethernet-sensor.yaml +[garage-door-controller]: ./garage-door-controller.yaml +[garage-door-fan-controller]: ./garage-door-fan-controller.yaml +[garage-gate-fan-controller]: ./garage-gate-fan-controller.yaml +[garage-presence-sensor]: ./garage-presence-sensor.yaml +[gls10-bluetooth-proxy]: ./templates/gls10-bluetooth-proxy.yaml +[kincony-kc868-asr]: ./templates/kincony-kc868-asr.yaml +[konnected-blaq]: ./templates/konnected-blaq.yaml +[logger]: ./templates/logger.yaml +[music-room-plant-sensor]: ./music-room-plant-sensor.yaml +[norvi-enet-ae06-r]: ./templates/norvi-enet-ae06-r.yaml +[office-bluetooth-proxy]: ./office-bluetooth-proxy.yaml +[operations-debugging]: ./OPERATIONS.md#debugging +[operations-framework-and-platform-versions]: ./OPERATIONS.md#framework-and-platform-versions +[ota]: ./templates/ota.yaml +[pantry-bluetooth-proxy]: ./pantry-bluetooth-proxy.yaml +[patio-plant-sensor]: ./patio-plant-sensor.yaml +[recirculation-pump-controller]: ./recirculation-pump-controller.yaml +[rgb-led-status]: ./templates/rgb-led-status.yaml +[rocket-astra]: ./templates/rocket-astra.yaml +[secrets]: ./templates/secrets.yaml +[smarthome-ceilsense]: ./templates/smarthome-ceilsense.yaml +[sonoff-s31]: ./templates/sonoff-s31.yaml +[sonoff-th10]: ./templates/sonoff-th10.yaml +[stairs-plant-sensor]: ./stairs-plant-sensor.yaml +[temperature]: ./templates/temperature.yaml +[templates]: ./templates/ +[test]: ./test/ +[time]: ./templates/time.yaml +[upstairs-hallway-plant-sensor]: ./upstairs-hallway-plant-sensor.yaml +[wemos-lolin32-lite]: ./templates/wemos-lolin32-lite.yaml +[wifi]: ./templates/wifi.yaml + + + +[adafruit-product-5323-link]: https://www.adafruit.com/product/5323 +[amazon-dp-b07r6ct3g7-link]: https://www.amazon.com/dp/B07R6CT3G7 +[amazon-sonoff-monitoring-certified-assistant-supporting-dp-link]: https://www.amazon.com/Sonoff-Monitoring-Certified-Assistant-Supporting/dp/B08GKGS197 +[amazon-sonoff-temperature-humidity-monitoring-assistant-dp-link]: https://www.amazon.com/Sonoff-Temperature-Humidity-Monitoring-Assistant/dp/B07TF5SYGL +[amazon-sonoff-temperature-monitoring-assistant-ds18b20-dp-link]: https://www.amazon.com/Sonoff-Temperature-Monitoring-Assistant-DS18B20/dp/B08DFQ2NP3 +[analog-max17048-link]: https://www.analog.com/en/products/max17048.html +[apolloautomation-products-plt-1-link]: https://apolloautomation.com/products/plt-1 +[blakadder-gl-s10-link]: https://blakadder.com/gl-s10/ +[blog-garage-cooling-link]: https://blog.insanegenius.com/2021/08/11/trying-to-keep-my-garage-cool/ +[blog-recirculation-pump-link]: https://blog.insanegenius.com/2020/10/11/hot-water-recirculation-pump-controller/ +[blog-tuya-to-esphome-link]: https://blog.insanegenius.com/2020/09/10/tuya-to-tasmota-to-esphome/ +[cartft-link]: https://www.cartft.com +[ceilsense-en-link]: https://ceilsense.nl/en/ +[discord-dbwxp5r3-link]: https://discord.gg/dbwxp5R3 +[docker-hub-esphome-link]: https://hub.docker.com/r/esphome/esphome +[efun-sh331w-link]: https://www.amazon.com/gp/product/B07DCJ7TDR +[esphome-components-status-led-link]: https://esphome.io/components/status_led/ +[espressif-esp32-s3-devkitc-link]: https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-devkitc-1/index.html +[github-apolloautomation-plt-1-link]: https://github.com/ApolloAutomation/PLT-1/blob/main/Integrations/ESPHome/PLT-1B.yaml +[github-konnected-io-konnected-esphome-link]: https://github.com/konnected-io/konnected-esphome/blob/master/garage-door-GDOv2-Q.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/ +[home-assistant-blog-2023-link]: https://www.home-assistant.io/blog/2023/11/06/removal-of-myq-integration/ +[improv-wifi-link]: https://www.improv-wifi.com/ +[issue-29-link]: https://github.com/ptr727/ESPHome-Config/issues/29 +[issues-link]: https://github.com/ptr727/ESPHome-Config/issues +[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 +[microair-link]: https://www.microair.net +[platformio-esp32-s3-devkitc-link]: https://docs.platformio.org/en/latest/boards/espressif32/esp32-s3-devkitc-1.html +[rocketcontroller-docs-esphome-link]: https://www.rocketcontroller.com/docs/esphome +[rocketcontroller-link]: https://www.rocketcontroller.com +[sensoper-link]: https://sensoper.com/ +[sensoper-shop-link]: https://sensoper.com/shop +[sensoper-shop-sc-en-i6-ro4-link]: https://sensoper.com/shop/sc-en-i6-ro4/ +[shop-link]: https://shop.norvi.lk +[shop-products-norvi-enet-ae06-r-link]: https://shop.norvi.lk/products/norvi-enet-ae06-r +[tasmota-sonoff-s31-link]: https://tasmota.github.io/docs/devices/Sonoff-S31/ +[tasmota-sonoff-th-link]: https://tasmota.github.io/docs/devices/Sonoff-TH/ +[templates-sonoff-thr316-link]: https://templates.blakadder.com/sonoff_THR316.html +[web-web-20191002041532-link]: https://web.archive.org/web/20191002041532/https://wiki.wemos.cc/products:lolin32:lolin32_lite diff --git a/easystart/BLE-RE-PLAYBOOK.md b/easystart/BLE-RE-PLAYBOOK.md index acfec2c..823c567 100755 --- a/easystart/BLE-RE-PLAYBOOK.md +++ b/easystart/BLE-RE-PLAYBOOK.md @@ -56,6 +56,18 @@ Then the agent greps the decompiled source for: service/characteristic **UUIDs** strings and byte-offset parsing. This alone usually yields the UUIDs, the command(s), and the frame layout - before touching any hardware. No screenshots or manual file transfers involved. +
What `pull-apk.sh` does under the hood (manual fallback) + +```shell +adb shell pm list packages | grep -i # find the package name +adb shell pm path # get APK path(s) - may be split APKs +adb pull /base.apk -.apk # pull each path returned, name by version +``` + +The helper adds the version naming, `package:`-prefix / `\r` stripping, and split-APK handling +described in the gotchas below. +
+ **Tool sourcing:** look for each tool (PATH -> winget package dirs -> local repo copies); if missing, self-source the official GitHub release / zip locally - but **don't auto-run `winget install`** or mutate system packages. Ask the user only if it can't be found or sourced. @@ -71,20 +83,9 @@ missing, self-source the official GitHub release / zip locally - but **don't aut `jadx-1.5.6/bin/jadx.bat`. `apktool` smali is usually enough; use jadx only for readable Java. - Split-APK apps return several paths - pull them all (the helper handles this). -### What `pull-apk.sh` does under the hood (manual fallback) - -```shell -adb shell pm list packages | grep -i # find the package name -adb shell pm path # get APK path(s) - may be split APKs -adb pull /base.apk -.apk # pull each path returned, name by version -``` - -The helper adds the version naming, `package:`-prefix / `\r` stripping, and split-APK handling -described in the gotchas above. - ## Phase 2 - Live validation from the computer (CLI, text only) -Use the **computer's own Bluetooth as the central** via [`bleak`](https://bleak.readthedocs.io/) +Use the **computer's own Bluetooth as the central** via [`bleak`][bleak-link] (cross-platform: Windows/macOS/Linux). No phone, no extra hardware. The agent writes a monitor script; the human runs one command and pastes the output. @@ -151,3 +152,7 @@ The agent then drives `adb` -> `apktool`/`jadx` -> analysis on its own. From the 3. Human runs it near the device, pastes the text. -> agent decodes, iterates, and (optionally) ground-truths against the vendor app's on-screen values. 4. "Now write the ESPHome component / integration." -> agent ports it. + + + +[bleak-link]: https://bleak.readthedocs.io/ diff --git a/easystart/PROTOCOL.md b/easystart/PROTOCOL.md index 7f69951..9e1b028 100755 --- a/easystart/PROTOCOL.md +++ b/easystart/PROTOCOL.md @@ -173,8 +173,8 @@ modules, with app-displayed values as ground truth: Characteristic roles, command string, framing, and the full 18-byte layout are all confirmed. **Independent cross-check:** this from-scratch decode matches the community ESPHome -implementations byte-for-byte - [Keen-coffee](https://github.com/Keen-coffee/home_assistant/blob/main/easyStart) -(original) and [DerekSeaman](https://github.com/DerekSeaman/ESPHome-Micro-Air-EasyStart) - including +implementations byte-for-byte - [Keen-coffee][github-keen-coffee-home-assistant-link] +(original) and [DerekSeaman][github-derekseaman-esphome-micro-air-easystart-link] - including the non-obvious `500000/period` frequency, the u32 total-starts, the two-notification framing, and write=`e2`/notify=`e1`. This decode additionally reads byte `[3]` = learned starts, which neither of those implementations decodes. @@ -188,3 +188,8 @@ The protocol is complete for monitoring. A few low-value details are not fully p - The other commands (`ReadEEP`, `NormMode`, `ProgMode`, OTA/flash) are identified but their request/response payloads were not reverse-engineered - not needed for read-only monitoring. - Exact poll interval the app uses (~1 s observed) - irrelevant for our own polling. + + + +[github-derekseaman-esphome-micro-air-easystart-link]: https://github.com/DerekSeaman/ESPHome-Micro-Air-EasyStart +[github-keen-coffee-home-assistant-link]: https://github.com/Keen-coffee/home_assistant/blob/main/easyStart diff --git a/easystart/README.md b/easystart/README.md index dd719fe..1f195cc 100755 --- a/easystart/README.md +++ b/easystart/README.md @@ -4,30 +4,30 @@ Bluetooth protocol for EasyStart AC compressors soft-starters. ## About -I have two [Micro-Air EasyStart](https://www.microair.net/products/easystart-flex-home-ac-soft-starter) 368 (now called Flex) soft-start modules installed for my two HVAC compressors. +I have two [Micro-Air EasyStart][microair-products-easystart-flex-home-ac-soft-starter-link] 368 (now called Flex) soft-start modules installed for my two HVAC compressors. I installed these units in 2023 to reduce the inrush current and avoid middle of summer high demand brownouts triggering my server UPSs whenever the compressors turned on. EasyStart supports a mobile app that connects over Bluetooth and shows status information like inrush current, line frequency, and load current. I wanted to add this telemetry to Home Assistant, I could not find any existing protocol decoding, I contacted Micro-Air support to ask for the protocol details, they were not forthcoming, and after a while I lost interest. -In the meantime I've been watching [Matt Brown's YouTube Channel](https://www.youtube.com/@mattbrwn) with great info on BT/BLE reverse engineering, and with some spare time and help from Claude, I now have both modules monitored in Home Assistant using an ESPHome Bluetooth Proxy client. +In the meantime I've been watching [Matt Brown's YouTube Channel][youtube-mattbrwn-link] with great info on BT/BLE reverse engineering, and with some spare time and help from Claude, I now have both modules monitored in Home Assistant using an ESPHome Bluetooth Proxy client. -Since completing the reverse engineering I have discovered similar work by [Keen-coffee](https://github.com/Keen-coffee/home_assistant/blob/main/easyStart) and [DerekSeaman](https://github.com/DerekSeaman/ESPHome-Micro-Air-EasyStart) (uses Keen-coffee's protocol). +Since completing the reverse engineering I have discovered similar work by [Keen-coffee][github-keen-coffee-home-assistant-link] and [DerekSeaman][github-derekseaman-esphome-micro-air-easystart-link] (uses Keen-coffee's protocol). My decoding adds the byte `[3]` = learned starts value, and I use ESPHome's Python codegen to create the sensor, but the remainder of the protocol decoding is similar. ## Reverse Engineering ### Tools -- Install [Android Platform Tools](https://developer.android.com/tools/releases/platform-tools). +- Install [Android Platform Tools][android-platform-tools-link]. - `winget install Google.PlatformTools` -- Install [JADX](https://github.com/skylot/jadx/releases/latest). -- Install [apktool](https://github.com/iBotPeaches/Apktool/releases/latest). -- Install [Wireshark](https://www.wireshark.org/download.html). +- Install [JADX][github-skylot-jadx-link]. +- Install [apktool][github-ibotpeaches-apktool-link]. +- Install [Wireshark][wireshark-download-link]. - `winget install WiresharkFoundation.Wireshark` -- Install [Java](https://www.oracle.com/java/technologies/downloads/). +- Install [Java][oracle-java-technologies-link]. - `winget install Microsoft.OpenJDK.25` -- Install [Claude Code](https://claude.com/product/claude-code). +- Install [Claude Code][claude-product-claude-code-link]. - `winget install Anthropic.ClaudeCode` - Android phone. - Computer with physical proximity near the EasyStart modules, their BT range is very limited. @@ -48,7 +48,7 @@ My decoding adds the byte `[3]` = learned starts value, and I use ESPHome's Pyth - Statically analyze the decompiled source: - Grep for the service / characteristic **UUIDs**, `writeCharacteristic` / `onCharacteristicChanged`, and the command / response strings and byte-offset parsing. - This alone yields the transport, the `{"Cmd": ReadLive}` command, and the 18-byte frame layout, before touching any hardware. - - See [PROTOCOL.md](./PROTOCOL.md). + - See [PROTOCOL.md][protocol]. - Validate live against a running module: - Eyeball the GATT table once in nRF Connect (to confirm which characteristic is write vs notify). - Capture with the Python monitor. @@ -57,14 +57,14 @@ My decoding adds the byte `[3]` = learned starts value, and I use ESPHome's Pyth - Ground-truth every decoded field against the official app's on-screen numbers. - Build the ESPHome integration: - Use external component, reusable template, and the Bluetooth-proxy config. - - See [ESPHome integration](#esphome-integration). + - See [ESPHome integration][esphome-integration]. - Cross-check against community work. - - [Keen-coffee](https://github.com/Keen-coffee/home_assistant/blob/main/easyStart) - - [DerekSeaman](https://github.com/DerekSeaman/ESPHome-Micro-Air-EasyStart) + - [Keen-coffee][github-keen-coffee-home-assistant-link] + - [DerekSeaman][github-derekseaman-esphome-micro-air-easystart-link] ### Claude Automation -I ran the whole reverse-engineering effort through [Claude Code](https://claude.com/product/claude-code) as the driver. +I ran the whole reverse-engineering effort through [Claude Code][claude-product-claude-code-link] as the driver. I started with manual steps learned from my online reverse engineering research, then asking Claude to automate that step, then the next manual step, automate, refine, repeat. I asked Claude to create AGENTS.md instructions that describe the process allowing for future reuse, and refining the process documentation along the way based on actual work and gotchas. The final validation was to delete all artifacts I manually created, and have Claude drive the complete protocol reverse engineering effort via a single prompt. @@ -72,14 +72,14 @@ Claude ran every CLI and analysis step itself, `adb`, `pull-apk.sh`, `apktool`, The method, the division of labor, and the gotchas are captured so it is repeatable: -- **[AGENTS.md](./AGENTS.md)** - session notes for the next agent: +- **[AGENTS.md][agents]** - session notes for the next agent: - Project status, the validated protocol cheat-sheet, verified tool invocations, and the tool-sourcing policy. -- **[BLE-RE-PLAYBOOK.md](./BLE-RE-PLAYBOOK.md)** - generalized, low-friction RE method: +- **[BLE-RE-PLAYBOOK.md][ble-re-playbook]** - generalized, low-friction RE method: - Agent drives `adb`/`apktool`/`jadx`, the human only plugs in the phone and runs the monitor. - Reusable for the next BLE device. -- **[tools/pull-apk.sh](./tools/pull-apk.sh)** - one-command APK extraction: +- **[tools/pull-apk.sh][pull-apk]** - one-command APK extraction: - Extract APK from phone and save with package name and version. -- **[python/easystart_monitor.py](./python/easystart_monitor.py)** - the `uv`-runnable `bleak` +- **[python/easystart_monitor.py][easystart-monitor]** - the `uv`-runnable `bleak` monitor: - Discover EasyStart devices with the `--discover` command. - Connect to and monitor devices with `--name` or `--address` command. @@ -95,7 +95,7 @@ The kickoff was essentially one prompt, from there the agent drove end to end: ### Protocol Summary -Full detail with the validated byte-offset table is in [PROTOCOL.md](./PROTOCOL.md). +Full detail with the validated byte-offset table is in [PROTOCOL.md][protocol]. - Transport is the **Laird VSP** service `d973f2e0-...`, no pairing / PIN / handshake: - `d973f2e1-...` = **notify** characteristic (module -> host, has the `0x2902` CCCD) @@ -123,9 +123,9 @@ To locate this in the decompiled app, the key classes are: The decode was confirmed end-to-end against real modules. Every field matched the official app's on-screen values (current, last-start-peak `24.5 A`, line frequency `59.8 Hz`, total starts `4947`).\ -See [PROTOCOL.md section 7](./PROTOCOL.md) for the validation table. +See [PROTOCOL.md section 7][protocol] for the validation table. -- **[`python/easystart_monitor.py`](./python/easystart_monitor.py)** - a `uv`-runnable ([bleak](https://bleak.readthedocs.io/)) monitor that connects over your computer's Bluetooth, polls `ReadLive`, and prints each frame raw + decoded in real time. See [`python/README.md`](./python/README.md). +- **[`python/easystart_monitor.py`][easystart-monitor]** - a `uv`-runnable ([bleak][bleak-link]) monitor that connects over your computer's Bluetooth, polls `ReadLive`, and prints each frame raw + decoded in real time. See [`python/README.md`][python-readme]. ```shell uv run python/easystart_monitor.py --discover # discover your units (name + MAC) @@ -147,13 +147,13 @@ An unfiltered `btsnoop_hci.log` would need root or a different device. A hardware-validated ESPHome external component (uses `ble_client`, derives compressor-running from the BLE connection, publishes current, an estimated power (`current * line_voltage * power_factor`, defaults 240 V / 1.0), line frequency, last-start peak, short-cycle delay, system state, and start/fault/learned-start counters). Flashed on a GL-S10 proxy and confirmed against both live modules: both clients connect, every decoded field matches, and both `running` sensors report `on` in Home Assistant: -- [`components/easystart/easystart.h`](./components/easystart/easystart.h) - the C++ component (BLE + frame parsing). -- [`components/easystart/__init__.py`](./components/easystart/__init__.py) - ESPHome codegen / config schema (one instance per module). +- [`components/easystart/easystart.h`][easystart-header] - the C++ component (BLE + frame parsing). +- [`components/easystart/__init__.py`][easystart-init] - ESPHome codegen / config schema (one instance per module). -Wire it into a Bluetooth proxy with the reusable template [`../templates/easystart.yaml`](../templates/easystart.yaml) (include once per module, with `vars` for the MAC + label). -A complete two-module example is [`../office-bluetooth-proxy.yaml`](../office-bluetooth-proxy.yaml). The office proxy sits near the HVAC units and attaches both compressors. Place the proxy close to the units: EasyStart BLE is very short range, it would not connect from across the room and needed the proxy relocated near the modules; an external-antenna ESP32 helps if the signal is marginal. +Wire it into a Bluetooth proxy with the reusable template [`../templates/easystart.yaml`][easystart-template] (include once per module, with `vars` for the MAC + label). +A complete two-module example is [`../office-bluetooth-proxy.yaml`][office-bluetooth-proxy]. The office proxy sits near the HVAC units and attaches both compressors. Place the proxy close to the units: EasyStart BLE is very short range, it would not connect from across the room and needed the proxy relocated near the modules; an external-antenna ESP32 helps if the signal is marginal. -The template also adds a **signal-strength diagnostic** per module, using ESPHome's built-in [`ble_client` RSSI sensor](https://esphome.io/components/sensor/ble_client) (`type: rssi`, `entity_category: diagnostic`, default 60s). It reads the RSSI of the **live connection** (`esp_ble_gap_read_rssi`), not of advertisements, which matters because a module stops advertising once connected. It reads blank while disconnected (which on its own does not separate "compressor off" from "out of range", since `running` is also derived from BLE presence). Its value is the **link margin while connected**: around -60 dBm is healthy, and approaching -90 dBm means the link is barely viable, which is what turns an intermittent "everything unavailable" into a diagnosable placement problem. The office proxy measured -93 dBm on a live connection, effectively at the sensitivity floor. Use it to site a proxy, dropping `easystart_rssi_update_interval` to a few seconds while walking the location. +The template also adds a **signal-strength diagnostic** per module, using ESPHome's built-in [`ble_client` RSSI sensor][esphome-ble-client-sensor-link] (`type: rssi`, `entity_category: diagnostic`, default 60s). It reads the RSSI of the **live connection** (`esp_ble_gap_read_rssi`), not of advertisements, which matters because a module stops advertising once connected. It reads blank while disconnected (which on its own does not separate "compressor off" from "out of range", since `running` is also derived from BLE presence). Its value is the **link margin while connected**: around -60 dBm is healthy, and approaching -90 dBm means the link is barely viable, which is what turns an intermittent "everything unavailable" into a diagnosable placement problem. The office proxy measured -93 dBm on a live connection, effectively at the sensitivity floor. Use it to site a proxy, dropping `easystart_rssi_update_interval` to a few seconds while walking the location. Two connection notes learned on hardware: @@ -165,19 +165,53 @@ The frame decode is logged at INFO (the `state=... current=...` line) and the ra ## References - Ordered August 2023 (old product name): EasyStart 368 / ASY-368-X48-BLUE. -- [EasyStart Flex](https://www.micro-air.com/support-documents/installation_resources/EasyStart_Specification_Sheet.pdf) -- [EasyStart Flex Home AC Soft Starter / ASY-398-X1S-BL](https://www.microair.net/products/easystart-flex-home-ac-soft-starter) -- [EasyStart Home AC Wiring](https://www.micro-air.com/support-documents/residential/current/EasyStart_HomeResidential_Installation_Guide.pdf) -- [Bluetooth Operation Manual](https://www.micro-air.com/support-documents/installation_resources/EasyStart_Bluetooth_Manual.pdf) -- [Google Play EasyStart App](https://play.google.com/store/apps/details?id=net.microair.easystart&hl=en_US) -- [Matt Brown's YouTube Channel](https://www.youtube.com/@mattbrwn) -- [Reddit Thread](https://www.reddit.com/r/homeassistant/comments/1dm00lg/integration_for_microair_easystart/) -- [Keen-coffee GitHub](https://github.com/Keen-coffee/home_assistant/blob/main/easyStart) -- [DerekSeaman GitHub](https://github.com/DerekSeaman/ESPHome-Micro-Air-EasyStart) -- [ESPHome BLE Client](https://esphome.io/components/ble_client/) +- [EasyStart Flex][easystart-spec-sheet-link] +- [EasyStart Flex Home AC Soft Starter / ASY-398-X1S-BL][microair-products-easystart-flex-home-ac-soft-starter-link] +- [EasyStart Home AC Wiring][micro-air-support-documents-residential-link] +- [Bluetooth Operation Manual][easystart-bluetooth-manual-link] +- [Google Play EasyStart App][play-store-easystart-link] +- [Matt Brown's YouTube Channel][youtube-mattbrwn-link] +- [Reddit Thread][reddit-r-homeassistant-link] +- [Keen-coffee GitHub][github-keen-coffee-home-assistant-link] +- [DerekSeaman GitHub][github-derekseaman-esphome-micro-air-easystart-link] +- [ESPHome BLE Client][esphome-components-ble-client-link] ## License See [LICENSE][license]. + + +[agents]: ./AGENTS.md +[ble-re-playbook]: ./BLE-RE-PLAYBOOK.md +[easystart-header]: ./components/easystart/easystart.h +[easystart-init]: ./components/easystart/__init__.py +[easystart-monitor]: ./python/easystart_monitor.py +[easystart-template]: ../templates/easystart.yaml +[esphome-integration]: #esphome-integration [license]: ../LICENSE +[office-bluetooth-proxy]: ../office-bluetooth-proxy.yaml +[protocol]: ./PROTOCOL.md +[pull-apk]: ./tools/pull-apk.sh +[python-readme]: ./python/README.md + + + +[android-platform-tools-link]: https://developer.android.com/tools/releases/platform-tools +[bleak-link]: https://bleak.readthedocs.io/ +[claude-product-claude-code-link]: https://claude.com/product/claude-code +[easystart-bluetooth-manual-link]: https://www.micro-air.com/support-documents/installation_resources/EasyStart_Bluetooth_Manual.pdf +[easystart-spec-sheet-link]: https://www.micro-air.com/support-documents/installation_resources/EasyStart_Specification_Sheet.pdf +[esphome-ble-client-sensor-link]: https://esphome.io/components/sensor/ble_client/ +[esphome-components-ble-client-link]: https://esphome.io/components/ble_client/ +[github-derekseaman-esphome-micro-air-easystart-link]: https://github.com/DerekSeaman/ESPHome-Micro-Air-EasyStart +[github-ibotpeaches-apktool-link]: https://github.com/iBotPeaches/Apktool/releases/latest +[github-keen-coffee-home-assistant-link]: https://github.com/Keen-coffee/home_assistant/blob/main/easyStart +[github-skylot-jadx-link]: https://github.com/skylot/jadx/releases/latest +[micro-air-support-documents-residential-link]: https://www.micro-air.com/support-documents/residential/current/EasyStart_HomeResidential_Installation_Guide.pdf +[microair-products-easystart-flex-home-ac-soft-starter-link]: https://www.microair.net/products/easystart-flex-home-ac-soft-starter +[oracle-java-technologies-link]: https://www.oracle.com/java/technologies/downloads/ +[play-store-easystart-link]: https://play.google.com/store/apps/details?id=net.microair.easystart&hl=en_US +[reddit-r-homeassistant-link]: https://www.reddit.com/r/homeassistant/comments/1dm00lg/integration_for_microair_easystart/ +[wireshark-download-link]: https://www.wireshark.org/download.html +[youtube-mattbrwn-link]: https://www.youtube.com/@mattbrwn diff --git a/easystart/python/README.md b/easystart/python/README.md index e6d9255..a8d7460 100755 --- a/easystart/python/README.md +++ b/easystart/python/README.md @@ -2,11 +2,11 @@ `easystart_monitor.py` connects to the soft-starter over your computer's Bluetooth, polls `{"Cmd": ReadLive}`, and prints the decoded live frame in real time - for validating the -protocol (see [../PROTOCOL.md](../PROTOCOL.md)) without flashing ESPHome firmware. +protocol (see [../PROTOCOL.md][protocol]) without flashing ESPHome firmware. ## Run with uv (recommended) -The script declares its dependency inline (PEP 723), so [uv](https://docs.astral.sh/uv/) +The script declares its dependency inline (PEP 723), so [uv][docs-uv-link] installs `bleak` and runs it in one step - no venv, no pip: ```shell @@ -57,3 +57,11 @@ current changes). Paste the raw hex lines back for confirmation of the decode. `requirements.txt` is provided for a classic `pip install -r requirements.txt` in a venv if you prefer, but uv is simpler on Windows. + + + +[protocol]: ../PROTOCOL.md + + + +[docs-uv-link]: https://docs.astral.sh/uv/ diff --git a/repo-config/configure.sh b/repo-config/configure.sh index e42a514..2f60bfa 100755 --- a/repo-config/configure.sh +++ b/repo-config/configure.sh @@ -175,6 +175,7 @@ gh_ok() { gh api "$@" >/dev/null 2>&1; } check_ruleset() { # payload-file - the live ruleset must match the committed policy, driven by the payload local file="$1" rname id live t want got wantc gotc want_enf + if [ ! -e "$file" ]; then fail "ruleset payload $file missing"; return; fi rname="$(jq -r '.name // empty' "$file")" if [ -z "$rname" ]; then fail "ruleset payload $file has no name"; return; fi if ! id="$(ruleset_id "$rname")"; then fail "ruleset '$rname' - could not resolve id"; return; fi diff --git a/templates/efun-sh331.yaml b/templates/efun-sh331.yaml index 982ea0e..06103a5 100755 --- a/templates/efun-sh331.yaml +++ b/templates/efun-sh331.yaml @@ -16,7 +16,7 @@ # https://esphome.io/guides/configuration-types.html#packages packages: - # Use the Aoycocr X5P as a generic Tuya template for the LED's, button, and relay + # Use the Aoycocr X5P as a generic Tuya template for the LEDs, button, and relay aoycocr_x5p: !include aoycocr-x5p.yaml # https://esphome.io/components/sensor diff --git a/templates/gls10-bluetooth-proxy.yaml b/templates/gls10-bluetooth-proxy.yaml index d5057c1..96048d0 100755 --- a/templates/gls10-bluetooth-proxy.yaml +++ b/templates/gls10-bluetooth-proxy.yaml @@ -100,7 +100,7 @@ binary_sensor: # https://github.com/esphome/esphome/blob/dev/esphome/core/application.h - lambda: App.safe_reboot(); -# LED's +# LEDs output: - platform: gpio id: power_led