diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..ea35784 --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,7 @@ +{ + "permissions": { + "deny": [ + "Read(./devices/secrets.yaml)" + ] + } +} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3bb610c..886b03f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -102,7 +102,7 @@ jobs: path: | ~/.platformio ~/.esphome-idf - key: build-${{ matrix.platform }}-${{ hashFiles('devices/packages/device_base*.yaml') }} + key: build-${{ matrix.platform }}-${{ hashFiles('devices/packages/device_*.yaml') }} restore-keys: build-${{ matrix.platform }}- - name: Install ESPHome run: pip install esphome diff --git a/CLAUDE.md b/CLAUDE.md index c5bac7e..039297e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -79,7 +79,7 @@ Installed via `pipx install esphome`, which creates its own isolated virtualenv ``` On a fresh machine ESPHome downloads roughly 6 GB into that prefix on the first compile. To migrate an existing install instead of re-downloading, move `%LOCALAPPDATA%\esphome\Cache\idf` to the new prefix, then delete `tools\`, `penvs\` and `idf-env.json` inside it — those re-extract from the `dist\` archive cache that moves along with them. -**2. Build to a local drive, not the UNC share.** This repo lives on `\\nas\docker\esphome\config`, and `devices/packages/device_base.yaml` sets `build_path: ./build/${device_id}`, which resolves (relative to the device yaml) to `devices/build/${device_id}` on that share. `cmd.exe` cannot use a UNC path as a working directory, so ninja's *link* step runs from `C:\Windows` and fails with `ld.exe: cannot open output file ...: Permission denied`. The compile steps succeed first, which makes this read as a linker bug rather than a path problem. Override `build_path` to a local path (e.g. `C:/eh/bld/`) when compiling from Windows. Source files can stay on the share — gcc handles UNC paths fine; only the working directory is the problem. +**2. Build to a local drive, not the UNC share.** This repo lives on `\\nas\docker\esphome\config`, and `devices/packages/device_common.yaml` sets `build_path: ./build/${device_id}`, which resolves (relative to the device yaml) to `devices/build/${device_id}` on that share. `cmd.exe` cannot use a UNC path as a working directory, so ninja's *link* step runs from `C:\Windows` and fails with `ld.exe: cannot open output file ...: Permission denied`. The compile steps succeed first, which makes this read as a linker bug rather than a path problem. Override `build_path` to a local path (e.g. `C:/eh/bld/`) when compiling from Windows. Source files can stay on the share — gcc handles UNC paths fine; only the working directory is the problem. **3. Don't run any other `esphome` command while a compile is in progress.** Even `esphome config` rewrites state under `devices/.esphome/`, and because that directory is shared with the build, cmake sees a changed input and re-runs configure mid-build. The initial configure succeeds off the resolved dependency lock, but a *re-run* re-resolves component manifests and dies on `devices/.esphome/pio_components/idf/*/esphome/noise-c/idf_component.yml`, which contains `override_path: /config/.esphome/...` — a container path, written by the Docker ESPHome where `devices/` is mounted at `/config`. Windows resolves it against the UNC root as `\\nas\docker\config\...`, which doesn't exist, and the build dies with `The "override_path" field ... does not point to a directory`. That cache is gitignored and regenerable, so leave it alone rather than "fixing" it — the container needs those paths. Just validate before you build, then stay off the config directory until it finishes. @@ -98,10 +98,12 @@ The `no-commit-to-branch` pre-commit hook blocks commits directly on `master`. A `devices/` is the directory mounted as `/config` in the ESPHome Device Builder container — the dashboard only scans the top level of its config dir, which is why the device yamls, their `packages/`, and `secrets.yaml` all live together there while everything repo-level (`components/`, `docs/`, `scripts/`, lint config) stays at the repo root. `components/` is bind-mounted alongside it so that `path: ../components` resolves identically inside the container and from a host `esphome` CLI run. **`devices/packages/`** holds shared YAML fragments included by devices via the `packages:` key, layered like this: -- `device_base.yaml` — the common core every device gets: wifi, api, ota, logger, status/uptime/wifi sensors, restart button, version/wifi-info text sensors, time. -- `device_base_esp32.yaml` / `device_base_esp8266.yaml` — platform block (`esp32:`/`esp8266:`) plus an include of `device_base.yaml`. Devices include one of these, not `device_base.yaml` directly. -- `econet_base.yaml` — extends the esp32 base for Econet-driven HVAC boards, internalizing a couple of entities via `!extend`. -- `wifi/wifi_dhcp.yaml` vs `wifi/wifi.yaml` — DHCP vs static-IP wifi variants. +- Devices include exactly one *combo* package — `device_esp32_wifi.yaml`, `device_esp8266_wifi.yaml` or `device_esp32_eth.yaml` — never the layers below it directly. Each combo is nothing but a `packages:` block pulling in one common, one chip and one network layer. +- `device_common.yaml` — the core every device gets regardless of chip or network: `esphome:`/`preferences:` boilerplate plus api, ota, logger, status/uptime sensors, restart button, version text sensor, time. +- `device_esp32.yaml` / `device_esp8266.yaml` — just the platform block (`esp32:`/`esp8266:`) and its substitutions. +- `device_wifi.yaml` / `device_eth.yaml` — the network layer and its diagnostics: wifi pulls in `wifi/wifi.yaml`, the wifi-signal sensor and the wifi-info text sensor; eth pulls in the ethernet-info text sensor (the `ethernet:` block itself is board-specific and stays in the device yaml). +- There is no `device_esp8266_eth.yaml` — no such device exists here. +- `econet.yaml` — extends `device_esp32_wifi.yaml` for Econet-driven HVAC boards, internalizing a couple of entities via `!extend`. - `devices/secrets.yaml` (untracked, gitignored) holds actual secret values; every device/package references secrets with `!secret key_name`. `devices/packages/secrets.yaml` and the various per-device `secrets.yaml` files just include that one. The `!extend` tag is used throughout to modify entities defined by an included package/component (e.g. marking a base sensor `internal: true`, or attaching an `on_value`/`on_time` trigger to `wifi_rssi`/`time_source` in `pool.yaml`) instead of redefining them. @@ -125,3 +127,4 @@ Components are consumed either from disk (`external_components: - source: {type: - YAML: 2-space indent, double-quoted strings, LF line endings (`.gitattributes` forces LF for `*.yaml`/`*.yml` even on this Windows checkout). - Python (`components/**/*.py`): standard ESPHome component style; flake8 ignores docstring rules (`D1xx`) since ESPHome components aren't expected to have docstrings, and line length is capped at 120. +- Bash filesystem commands must be scoped. `devices/.esphome/` holds ~38k ESP-IDF toolchain/build-cache files, and this checkout lives on an SMB share. Grep/Glob honor `.gitignore` and skip it, but Bash does not — a bare `find`/`du`/`ls -R` from the repo root walks all of it and times out. Target a specific subdirectory, or use `-maxdepth` / `-prune`, instead of recursing from the root. diff --git a/devices/basement-bath-shower-light-heat.yaml b/devices/basement-bath-shower-light-heat.yaml index b421d00..609690e 100644 --- a/devices/basement-bath-shower-light-heat.yaml +++ b/devices/basement-bath-shower-light-heat.yaml @@ -6,7 +6,7 @@ substitutions: pwd: !secret basement_bathroom_shower_light_heat_pwd packages: - device_base: !include ./packages/device_base_esp8266.yaml + device: !include ./packages/device_esp8266_wifi.yaml binary_sensor: - platform: gpio diff --git a/devices/basement-fridge.yaml b/devices/basement-fridge.yaml index 72b5a3f..5da17f3 100644 --- a/devices/basement-fridge.yaml +++ b/devices/basement-fridge.yaml @@ -5,4 +5,4 @@ substitutions: pwd: !secret basement_fridge_pwd packages: - device_base: !include ./packages/topgreener_smart_plug.yaml + device: !include ./packages/topgreener_smart_plug.yaml diff --git a/devices/basement-tv.yaml b/devices/basement-tv.yaml index 7b032a2..2a2a873 100644 --- a/devices/basement-tv.yaml +++ b/devices/basement-tv.yaml @@ -5,4 +5,4 @@ substitutions: pwd: !secret basement_tv_pwd packages: - device_base: !include ./packages/topgreener_smart_plug.yaml + device: !include ./packages/topgreener_smart_plug.yaml diff --git a/devices/bt-proxy-basement.yaml b/devices/bt-proxy-basement.yaml index 1b96696..f4e0e16 100644 --- a/devices/bt-proxy-basement.yaml +++ b/devices/bt-proxy-basement.yaml @@ -5,9 +5,9 @@ substitutions: pwd: !secret bt_proxy_basement_pwd packages: - device_base: !include ./packages/device_base_esp32.yaml + device: !include ./packages/device_esp32_wifi.yaml bluetooth_proxy: active: true cache_services: true - connection_slots: 4 + connection_slots: 3 diff --git a/devices/coffee-maker.yaml b/devices/coffee-maker.yaml index b11f855..060dd6f 100644 --- a/devices/coffee-maker.yaml +++ b/devices/coffee-maker.yaml @@ -6,7 +6,7 @@ substitutions: pwd: !secret coffee_maker_pwd packages: - device_base: !include ./packages/device_base_esp8266.yaml + device: !include ./packages/device_esp8266_wifi.yaml binary_sensor: - platform: gpio diff --git a/devices/emporia-vue2.yaml b/devices/emporia-vue2.yaml index db8b656..98ccfeb 100644 --- a/devices/emporia-vue2.yaml +++ b/devices/emporia-vue2.yaml @@ -6,7 +6,7 @@ substitutions: pwd: !secret emporia_vue2_pwd packages: - device_base: !include ./packages/device_base_esp32.yaml + device: !include ./packages/device_esp32_wifi.yaml api: services: diff --git a/devices/family-room-tv.yaml b/devices/family-room-tv.yaml index a02e11e..889505f 100644 --- a/devices/family-room-tv.yaml +++ b/devices/family-room-tv.yaml @@ -5,4 +5,4 @@ substitutions: pwd: !secret family_room_tv_pwd packages: - device_base: !include ./packages/topgreener_smart_plug.yaml + device: !include ./packages/topgreener_smart_plug.yaml diff --git a/devices/fire-pit-fountain.yaml b/devices/fire-pit-fountain.yaml index a9567a9..41cafdb 100644 --- a/devices/fire-pit-fountain.yaml +++ b/devices/fire-pit-fountain.yaml @@ -6,7 +6,7 @@ substitutions: pwd: !secret fire_pit_fountain_pwd packages: - device_base: !include ./packages/device_base_esp8266.yaml + device: !include ./packages/device_esp8266_wifi.yaml binary_sensor: - platform: gpio diff --git a/devices/garage-fridge.yaml b/devices/garage-fridge.yaml index 2dec07b..6bc0149 100644 --- a/devices/garage-fridge.yaml +++ b/devices/garage-fridge.yaml @@ -6,7 +6,7 @@ substitutions: log_baud_rate: '0' packages: - device_base: !include ./packages/device_base_esp32.yaml + device: !include ./packages/device_esp32_wifi.yaml esp32: cpu_frequency: 160MHZ diff --git a/devices/garage-lights.yaml b/devices/garage-lights.yaml index c08baf3..b4465a3 100644 --- a/devices/garage-lights.yaml +++ b/devices/garage-lights.yaml @@ -6,4 +6,4 @@ substitutions: light_wattage: '378' packages: - device_base: !include ./packages/shelly_1l.yaml + device: !include ./packages/shelly_1l.yaml diff --git a/devices/hvac.yaml b/devices/hvac.yaml index 3913e78..b2a6124 100644 --- a/devices/hvac.yaml +++ b/devices/hvac.yaml @@ -24,7 +24,7 @@ packages: hvac-zone-yaml/econet_hvac_zone2.yaml, hvac-zone-yaml/econet_hvac_zone3.yaml ] - device_base: !include ./packages/econet_base.yaml + device: !include ./packages/econet.yaml climate: - platform: econet_zone_control diff --git a/devices/kitchen-fridge.yaml b/devices/kitchen-fridge.yaml index 35b7099..ffc932b 100644 --- a/devices/kitchen-fridge.yaml +++ b/devices/kitchen-fridge.yaml @@ -5,4 +5,4 @@ substitutions: pwd: !secret kitchen_fridge_pwd packages: - device_base: !include ./packages/topgreener_smart_plug.yaml + device: !include ./packages/topgreener_smart_plug.yaml diff --git a/devices/main-garage-door.yaml b/devices/main-garage-door.yaml index 880531d..95d9c55 100644 --- a/devices/main-garage-door.yaml +++ b/devices/main-garage-door.yaml @@ -8,7 +8,7 @@ substitutions: packages: ratgdo: github://ratgdo/esphome-ratgdo/v25iboard.yaml@main - device_base: !include ./packages/device_base_esp8266.yaml + device: !include ./packages/device_esp8266_wifi.yaml button: - id: !remove ${id_prefix}_restart diff --git a/devices/master-bed.yaml b/devices/master-bed.yaml index 51209d3..b86519a 100644 --- a/devices/master-bed.yaml +++ b/devices/master-bed.yaml @@ -6,7 +6,7 @@ substitutions: pwd: !secret master_bed_pwd packages: - device_base: !include ./packages/device_base_esp8266.yaml + device: !include ./packages/device_esp8266_wifi.yaml external_components: - source: diff --git a/devices/network-equipment.yaml b/devices/network-equipment.yaml index 1033165..0888763 100644 --- a/devices/network-equipment.yaml +++ b/devices/network-equipment.yaml @@ -5,4 +5,4 @@ substitutions: pwd: !secret network_equipment_pwd packages: - device_base: !include ./packages/topgreener_smart_plug.yaml + device: !include ./packages/topgreener_smart_plug.yaml diff --git a/devices/packages/device_base.yaml b/devices/packages/device_common.yaml similarity index 75% rename from devices/packages/device_base.yaml rename to devices/packages/device_common.yaml index 4f629f4..8c785fe 100644 --- a/devices/packages/device_base.yaml +++ b/devices/packages/device_common.yaml @@ -1,8 +1,6 @@ substitutions: log_level: none log_baud_rate: '115200' - wifi_ssid: !secret wifi_ssid - wifi_password: !secret wifi_password flash_write_interval: 10min esphome: @@ -15,14 +13,11 @@ preferences: flash_write_interval: ${flash_write_interval} packages: - wifi: !include wifi/wifi_dhcp.yaml api: !include api/api.yaml ota: !include ota/ota.yaml logger: !include logger/logger.yaml status_sensor: !include binary_sensor/status.yaml uptime_sensor: !include sensor/uptime.yaml - wifi_sensor: !include sensor/wifi.yaml restart_button: !include button/restart.yaml version_sensor: !include text_sensor/version.yaml - wifi_info_sensor: !include text_sensor/wifi_info.yaml time: !include time/time.yaml diff --git a/devices/packages/device_base_esp32.yaml b/devices/packages/device_esp32.yaml similarity index 75% rename from devices/packages/device_base_esp32.yaml rename to devices/packages/device_esp32.yaml index 71d3833..2ec4e51 100644 --- a/devices/packages/device_base_esp32.yaml +++ b/devices/packages/device_esp32.yaml @@ -6,6 +6,3 @@ esp32: variant: ${esp32_variant} framework: type: ${framework_type} - -packages: - base: !include device_base.yaml diff --git a/devices/packages/device_esp32_eth.yaml b/devices/packages/device_esp32_eth.yaml new file mode 100644 index 0000000..bce6524 --- /dev/null +++ b/devices/packages/device_esp32_eth.yaml @@ -0,0 +1,4 @@ +packages: + common: !include device_common.yaml + esp32: !include device_esp32.yaml + eth: !include device_eth.yaml diff --git a/devices/packages/device_esp32_wifi.yaml b/devices/packages/device_esp32_wifi.yaml new file mode 100644 index 0000000..5d287e7 --- /dev/null +++ b/devices/packages/device_esp32_wifi.yaml @@ -0,0 +1,4 @@ +packages: + common: !include device_common.yaml + esp32: !include device_esp32.yaml + wifi: !include device_wifi.yaml diff --git a/devices/packages/device_base_esp8266.yaml b/devices/packages/device_esp8266.yaml similarity index 66% rename from devices/packages/device_base_esp8266.yaml rename to devices/packages/device_esp8266.yaml index de07042..dcde1db 100644 --- a/devices/packages/device_base_esp8266.yaml +++ b/devices/packages/device_esp8266.yaml @@ -4,6 +4,3 @@ substitutions: esp8266: board: ${board} restore_from_flash: true - -packages: - base: !include device_base.yaml diff --git a/devices/packages/device_esp8266_wifi.yaml b/devices/packages/device_esp8266_wifi.yaml new file mode 100644 index 0000000..302aab1 --- /dev/null +++ b/devices/packages/device_esp8266_wifi.yaml @@ -0,0 +1,4 @@ +packages: + common: !include device_common.yaml + esp8266: !include device_esp8266.yaml + wifi: !include device_wifi.yaml diff --git a/devices/packages/device_eth.yaml b/devices/packages/device_eth.yaml new file mode 100644 index 0000000..ad132ef --- /dev/null +++ b/devices/packages/device_eth.yaml @@ -0,0 +1,2 @@ +packages: + eth_info_sensor: !include text_sensor/ethernet_info.yaml diff --git a/devices/packages/device_wifi.yaml b/devices/packages/device_wifi.yaml new file mode 100644 index 0000000..4067715 --- /dev/null +++ b/devices/packages/device_wifi.yaml @@ -0,0 +1,4 @@ +packages: + wifi: !include wifi/wifi.yaml + wifi_sensor: !include sensor/wifi.yaml + wifi_info_sensor: !include text_sensor/wifi_info.yaml diff --git a/devices/packages/econet_base.yaml b/devices/packages/econet.yaml similarity index 72% rename from devices/packages/econet_base.yaml rename to devices/packages/econet.yaml index 3c104c4..96a6536 100644 --- a/devices/packages/econet_base.yaml +++ b/devices/packages/econet.yaml @@ -1,5 +1,5 @@ packages: - device_base: !include ./device_base_esp32.yaml + device: !include ./device_esp32_wifi.yaml button: - id: !extend restart_esp diff --git a/devices/packages/ota/ota.yaml b/devices/packages/ota/ota.yaml index 2bd82b6..97e3dbc 100644 --- a/devices/packages/ota/ota.yaml +++ b/devices/packages/ota/ota.yaml @@ -1,4 +1,3 @@ ota: - platform: esphome id: esphome_ota - password: ${pwd} diff --git a/devices/packages/shelly_1l.yaml b/devices/packages/shelly_1l.yaml index a03dd20..4ddc1f0 100644 --- a/devices/packages/shelly_1l.yaml +++ b/devices/packages/shelly_1l.yaml @@ -3,7 +3,7 @@ substitutions: light_wattage: '0' packages: - base: !include device_base_esp8266.yaml + device: !include device_esp8266_wifi.yaml binary_sensor: - platform: gpio diff --git a/devices/packages/text_sensor/ethernet_info.yaml b/devices/packages/text_sensor/ethernet_info.yaml new file mode 100644 index 0000000..3e26c30 --- /dev/null +++ b/devices/packages/text_sensor/ethernet_info.yaml @@ -0,0 +1,5 @@ +text_sensor: + - platform: ethernet_info + ip_address: + name: IP Address + icon: mdi:ip-network diff --git a/devices/packages/topgreener_smart_plug.yaml b/devices/packages/topgreener_smart_plug.yaml index 3c59db5..e0f4b5d 100644 --- a/devices/packages/topgreener_smart_plug.yaml +++ b/devices/packages/topgreener_smart_plug.yaml @@ -5,7 +5,7 @@ substitutions: log_baud_rate: '0' packages: - base: !include device_base_esp8266.yaml + device: !include device_esp8266_wifi.yaml binary_sensor: - platform: gpio diff --git a/devices/packages/wifi/wifi.yaml b/devices/packages/wifi/wifi.yaml index 65efe95..bb7036a 100644 --- a/devices/packages/wifi/wifi.yaml +++ b/devices/packages/wifi/wifi.yaml @@ -1,16 +1,14 @@ +substitutions: + wifi_ssid: !secret wifi_ssid + wifi_password: !secret wifi_password + wifi: ssid: ${wifi_ssid} password: ${wifi_password} fast_connect: true power_save_mode: none min_auth_mode: WPA2 - manual_ip: - static_ip: ${ip_address} - subnet: ${ip_subnet} - gateway: ${ip_gateway} ap: ssid: ${device_id} - password: ${pwd} - ap_timeout: 5min captive_portal: diff --git a/devices/packages/wifi/wifi_dhcp.yaml b/devices/packages/wifi/wifi_dhcp.yaml deleted file mode 100644 index 010cc7e..0000000 --- a/devices/packages/wifi/wifi_dhcp.yaml +++ /dev/null @@ -1,10 +0,0 @@ -wifi: - ssid: ${wifi_ssid} - password: ${wifi_password} - fast_connect: true - power_save_mode: none - min_auth_mode: WPA2 - ap: - ssid: ${device_id} - -captive_portal: diff --git a/devices/pool-lights.yaml b/devices/pool-lights.yaml index 194fd03..bf38833 100644 --- a/devices/pool-lights.yaml +++ b/devices/pool-lights.yaml @@ -6,7 +6,7 @@ substitutions: pwd: !secret pool_lights_pwd packages: - device_base: !include ./packages/device_base_esp8266.yaml + device: !include ./packages/device_esp8266_wifi.yaml external_components: - source: diff --git a/devices/pool.yaml b/devices/pool.yaml index 684f962..011a6c1 100644 --- a/devices/pool.yaml +++ b/devices/pool.yaml @@ -9,16 +9,23 @@ substitutions: log_level: DEBUG packages: - device_base: !include ./packages/device_base_esp32.yaml + device: !include ./packages/device_esp32_wifi.yaml external_components: - source: type: local path: ../components components: [pool_controller] - - source: github://pr#17849 + # Pinned to the last pzem6l24 commit before upstream switched reset_energy to + # automation.register_apply_action (esphome/esphome#19469, merged 2026-09-23). + # That helper landed after the 2026.9 branch cut, so the PR head no longer + # validates against any released ESPHome. Unpin once a release carries it. + - source: + type: git + url: https://github.com/esphome/esphome.git + ref: 36d39416f0a7cdabb536d5a7434222de462c3e89 components: [pzem6l24] - refresh: 1h + refresh: never # esphome: # on_boot: diff --git a/devices/second-garage-door.yaml b/devices/second-garage-door.yaml index ca8400d..c74054b 100644 --- a/devices/second-garage-door.yaml +++ b/devices/second-garage-door.yaml @@ -19,7 +19,7 @@ packages: ref: main files: [base_drycontact.yaml] refresh: 1s - device_base: !include ./packages/device_base_esp8266.yaml + device: !include ./packages/device_esp8266_wifi.yaml button: - id: !remove ${id_prefix}_restart diff --git a/devices/sump-pump.yaml b/devices/sump-pump.yaml index 61d0667..3cabcd6 100644 --- a/devices/sump-pump.yaml +++ b/devices/sump-pump.yaml @@ -5,4 +5,4 @@ substitutions: pwd: !secret sump_pump_pwd packages: - device_base: !include ./packages/topgreener_smart_plug.yaml + device: !include ./packages/topgreener_smart_plug.yaml diff --git a/devices/washing-machine.yaml b/devices/washing-machine.yaml index cc1401c..e9e426b 100644 --- a/devices/washing-machine.yaml +++ b/devices/washing-machine.yaml @@ -5,4 +5,4 @@ substitutions: pwd: !secret washing_machine_pwd packages: - device_base: !include ./packages/topgreener_smart_plug.yaml + device: !include ./packages/topgreener_smart_plug.yaml diff --git a/devices/water-heater.yaml b/devices/water-heater.yaml index d5b0c64..e026ffd 100644 --- a/devices/water-heater.yaml +++ b/devices/water-heater.yaml @@ -15,7 +15,7 @@ packages: url: https://github.com/esphome-econet/esphome-econet ref: main file: econet_heatpump_water_heater.yaml - device_base: !include ./packages/econet_base.yaml + device: !include ./packages/econet.yaml climate: - id: !extend econet_climate