From 17529fd58f3df8dba0d283052c593b87e2082217 Mon Sep 17 00:00:00 2001 From: Bas Meerman <30606346+basmeerman@users.noreply.github.com> Date: Fri, 26 Jun 2026 21:14:49 +0200 Subject: [PATCH 1/2] feat: switch HA transport to MQTT and move fan PWM to GPIO 16 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two hardware-driven changes, both verified on the assembled rig during the first USB flash (2026-06-26): - Home Assistant integration moved from the native encrypted ESPHome api: to mqtt:. The api: block is commented out; mqtt: is enabled with broker auto-discovery, the fancontrol topic prefix, and online/offline birth & will messages. mqtt_host/mqtt_user/mqtt_password come from secrets.yaml (mqtt_host is a bare IP — the port is the separate port: key). - Fan PWM output moved GPIO 25 -> GPIO 16 to match the wiring. GPIO 16 is free on the plain LOLIN D32 (WROOM-32, no PSRAM); only the D32 Pro (WROVER) reserves 16/17 for PSRAM. Pin map updated in CLAUDE.md, PROJECT_PLAN.md, and docs/wiring_diagram.md. Verified: device boots, joins WiFi, connects to the broker, and publishes live temperature/humidity/fan-speed plus diagnostics. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 15 +++++++++++++ CLAUDE.md | 4 ++-- PROJECT_PLAN.md | 2 +- docs/wiring_diagram.md | 6 +++--- fancontrol.yaml | 49 +++++++++++++++++++++--------------------- 5 files changed, 46 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c489c0c..8222b44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,21 @@ Release notes for each tagged version are extracted from this file by the ## [Unreleased] +### Changed +- Home Assistant transport switched from the native ESPHome **`api:` to + `mqtt:`** in `fancontrol.yaml`. The encrypted native-API block is now + commented out and the MQTT block (broker auto-discovery, `fancontrol` + topic prefix, online/offline birth & will messages) is enabled. Set + `mqtt_host` (bare IP, **no port** — the port is the separate `port:` key), + `mqtt_user`, and `mqtt_password` in `secrets.yaml`. Revert by swapping the + two blocks back. Verified on hardware: device connects to the broker and + publishes temperature, humidity, fan speed, and diagnostics. +- Fan PWM output moved from **GPIO 25 → GPIO 16** in `fancontrol.yaml` to + match the assembled hardware. GPIO 16 is free on the plain LOLIN D32 + (WROOM-32, no PSRAM); only the D32 *Pro* (WROVER) reserves 16/17 for PSRAM. + Pin map updated in `CLAUDE.md`, `PROJECT_PLAN.md`, and + `docs/wiring_diagram.md`. + ### Fixed - `.github/dependabot.yml`: dropped the `pip` ecosystem. v0.2.0 removed `platformio.ini` and there are no Python manifests left, so Dependabot's diff --git a/CLAUDE.md b/CLAUDE.md index f9be632..10090f3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -20,14 +20,14 @@ HA integration) is declared there. - **Board:** LOLIN D32 (ESP32-WROOM-32) - **Sensor:** DHT22 with onboard 10 kΩ pull-up - **Fan:** Ruck EM 125L EC 02 — PWM input 5–10 V, 1–5 kHz (datasheet) -- **PWM path:** ESP32 GPIO 25 → TXS0108E / 74HCT125 level shifter → MT3608 boost → 9 V PWM line +- **PWM path:** ESP32 GPIO 16 → TXS0108E / 74HCT125 level shifter → MT3608 boost → 9 V PWM line Pin map, locked in `fancontrol.yaml`: | GPIO | Role | Notes | |---|---|---| | 4 | DHT22 data | module has its own pull-up; firmware treats pin as plain input | -| 25 | Fan PWM (LEDC ch0) | default 1 kHz, 10-bit auto | +| 16 | Fan PWM (LEDC ch0) | default 1 kHz, 10-bit auto | | 5 | Status LED | onboard LOLIN D32 LED, active LOW | | 0 | Boot button | reserved for factory reset long-press (not yet wired in YAML) | diff --git a/PROJECT_PLAN.md b/PROJECT_PLAN.md index 219d472..5495ca5 100644 --- a/PROJECT_PLAN.md +++ b/PROJECT_PLAN.md @@ -19,7 +19,7 @@ Everything runtime-visible lives in a single file: [`fancontrol.yaml`](fancontro ## 2. Features ### F1 — Ventilation core -- F1.1 PWM output on GPIO 25. Default 1 kHz (Ruck EM 125L EC 02 datasheet: +- F1.1 PWM output on GPIO 16. Default 1 kHz (Ruck EM 125L EC 02 datasheet: PWM 5–10 V, 1–5 kHz), runtime-tunable via HA / web UI `number` component (clamped 1000–5000 Hz, 100 Hz step). - F1.2 Temperature-driven fan speed using a 5-point piecewise-linear curve diff --git a/docs/wiring_diagram.md b/docs/wiring_diagram.md index 5561a7e..458ef98 100644 --- a/docs/wiring_diagram.md +++ b/docs/wiring_diagram.md @@ -16,7 +16,7 @@ | ESP32 GPIO | Role | Direction | Notes | |---|---|---|---| | **4** | `PIN_DHT22` | Input (pulled up on module) | Avoids ADC2 (conflicts with WiFi). | -| **25** | `PIN_FAN_PWM` | LEDC PWM output | Channel `FAN_LEDC_CHANNEL = 0`, default 1 kHz, 10-bit resolution. Drives the level-shifter LV side. | +| **16** | `PIN_FAN_PWM` | LEDC PWM output | Channel `FAN_LEDC_CHANNEL = 0`, default 1 kHz, 10-bit resolution. Drives the level-shifter LV side. | | **5** | `PIN_STATUS_LED` | Output (active LOW) | Onboard LED. Reserved — not yet driven by firmware. | | **0** | `PIN_FACTORY_RESET` | Input (boot button) | Hold at boot to trigger factory reset (to be wired into `storage::factoryReset()` as a future hook — currently only web UI calls it). | | 3V3, GND, VIN (5 V) | Power rails | — | VIN accepts 5 V USB. 3V3 from the onboard regulator feeds the DHT22 module. | @@ -24,7 +24,7 @@ ## Rationale - **GPIO 4 for DHT22**: input-capable, outside the ADC2 block, not a strapping pin. Widely used for DHT sensors in ESP32 examples. -- **GPIO 25 for fan PWM**: LEDC-capable, not a strapping pin, no ADC2 conflict. Next to the DAC pins but we're using LEDC (digital PWM) not DAC. +- **GPIO 16 for fan PWM**: LEDC-capable, not a strapping pin, not an ADC pin. Free on the plain LOLIN D32 (ESP32-WROOM-32, no PSRAM). Note: GPIO 16/17 are reserved for PSRAM on the LOLIN D32 *Pro* (WROVER) — not applicable to this board. - **Avoid GPIO 12**: strapping pin (MTDI); pulled HIGH during boot can brick flash in some boards. - **Avoid GPIO 2 / 15**: boot-time strapping; fine for outputs after boot but safer to leave alone. @@ -35,7 +35,7 @@ │ LOLIN D32 │ │ │ │ GPIO 4 ──── DHT22 DAT (module has built-in 10 kΩ pull-up to VCC) -│ GPIO 25 ──── LevelShift A1 (LV) ── LevelShift B1 (HV) ── Fan PWM+ +│ GPIO 16 ──── LevelShift A1 (LV) ── LevelShift B1 (HV) ── Fan PWM+ │ 3V3 ──── DHT22 VCC, LevelShift LV │ GND ──── DHT22 GND, LevelShift GND (both sides), Fan GND, │ MT3608 input GND, 5 V PSU GND diff --git a/fancontrol.yaml b/fancontrol.yaml index d473e0f..b17dd82 100644 --- a/fancontrol.yaml +++ b/fancontrol.yaml @@ -45,11 +45,12 @@ logger: level: INFO baud_rate: 115200 -# Prefer ESPHome native API for Home Assistant (encrypted, fast). MQTT -# left optional below for users who want to keep the v0.1.x topic shape. -api: - encryption: - key: !secret api_encryption_key +# Native API disabled for this deployment — Home Assistant integration is +# via MQTT only (see the mqtt: block below). Re-enable by uncommenting and +# commenting out mqtt: to go back to the encrypted native-API transport. +# api: +# encryption: +# key: !secret api_encryption_key ota: - platform: esphome @@ -68,24 +69,24 @@ captive_portal: mdns: # Advertises the device as fancontrol.local on the LAN. -# Optional MQTT. Uncomment + fill secrets.yaml to publish to an external -# broker in addition to (or instead of) the native API. -# mqtt: -# broker: !secret mqtt_host -# port: 1883 -# username: !secret mqtt_user -# password: !secret mqtt_password -# topic_prefix: fancontrol -# discovery: true -# discovery_retain: true -# birth_message: -# topic: fancontrol/status -# payload: online -# retain: true -# will_message: -# topic: fancontrol/status -# payload: offline -# retain: true +# MQTT transport — Home Assistant integration via broker auto-discovery. +# Fill mqtt_host / mqtt_user / mqtt_password in secrets.yaml. +mqtt: + broker: !secret mqtt_host + port: 1883 + username: !secret mqtt_user + password: !secret mqtt_password + topic_prefix: fancontrol + discovery: true + discovery_retain: true + birth_message: + topic: fancontrol/status + payload: online + retain: true + will_message: + topic: fancontrol/status + payload: offline + retain: true web_server: port: 80 @@ -252,7 +253,7 @@ number: output: - platform: ledc - pin: GPIO25 + pin: GPIO16 id: fan_pwm_output frequency: 1000Hz channel: 0 From 3fecc89bc173152e46a9813d43ab9ba7139bafda Mon Sep 17 00:00:00 2001 From: Bas Meerman <30606346+basmeerman@users.noreply.github.com> Date: Fri, 26 Jun 2026 21:22:02 +0200 Subject: [PATCH 2/2] ci: add dummy MQTT secrets to build workflows The MQTT transport switch added mqtt_host/mqtt_user/mqtt_password !secret references. CI/release generate a throwaway secrets.yaml inline and didn't include these, so `esphome config` failed with "Secret 'mqtt_host' not defined". Seed dummy values (192.0.2.1, RFC 5737 documentation IP) in both ci.yml and release.yml. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/ci.yml | 3 +++ .github/workflows/release.yml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aff5268..adce4e2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,6 +23,9 @@ jobs: wifi_ssid: "CiDummyNet" wifi_password: "ci-dummy-password" ota_password: "ci-dummy-ota-password" + mqtt_host: "192.0.2.1" + mqtt_user: "ci-dummy-user" + mqtt_password: "ci-dummy-password" EOF - name: Validate ESPHome config diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2171f2b..a2e5a5b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,6 +28,9 @@ jobs: wifi_ssid: "ReleaseDummyNet" wifi_password: "release-dummy-password" ota_password: "release-dummy-ota-password" + mqtt_host: "192.0.2.1" + mqtt_user: "release-dummy-user" + mqtt_password: "release-dummy-password" EOF - name: Build ESPHome firmware