Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,32 @@ their SHA-256 checksums.
password + checksum. Pre-v0.2.0 releases (v0.1.0, v0.1.1) are still RSA-signed
and verifiable against `docs/signing_public_key.pem`; see [`SECURITY.md`](SECURITY.md).

## Updating the device

After first boot the device is on WiFi, so updates go **OTA over the network**
— no USB needed.

```bash
esphome config fancontrol.yaml # lint
esphome compile fancontrol.yaml # build (lambdas fail here, not at config)
esphome run fancontrol.yaml --device fancontrol.local # compile + OTA push (mDNS)
esphome logs fancontrol.yaml --device fancontrol.local # confirm it came back up
```

`esphome run` compiles and uploads in one step; the device authenticates the
upload with `ota_password` from `secrets.yaml`, reboots, and `restart_counter`
increments. The web UI (`http://fancontrol.local`) also accepts an OTA upload
of the built `.ota.bin`.

- **USB fallback** (OTA broken, or device won't join WiFi / is in safe mode):
`esphome run fancontrol.yaml --device /dev/cu.wchusbserial10`.
- **Never flash the GitHub Release `.bin`.** Release artifacts are built in CI
with **dummy secrets** (placeholder WiFi/MQTT), so they will not join the
network. Always build locally against the real `secrets.yaml`. Releases are
reference/reproducibility artifacts only.
- `secrets.yaml` is gitignored and lives locally; the backup is in the
maintainer's 1Password. Restore it there before building on a fresh machine.

## Architecture constraints (enforce these)

Because the project lives in one YAML file, "architecture" is mostly about
Expand Down
23 changes: 23 additions & 0 deletions PROJECT_PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,29 @@ native API and leave MQTT off.
### F5 — OTA updates
- `ota:` platform esphome, password-gated (`secrets.yaml` → `ota_password`).
- SHA-256 handshake; no RSA signing (see *History* below and `SECURITY.md`).
- **Push-based, not auto-pull.** The device never polls GitHub; there is no
`dashboard_import` / `http_request` update platform. You push firmware to it
from the LAN.

**Update procedure (after first boot — device is on WiFi):**

```bash
esphome config fancontrol.yaml # lint
esphome compile fancontrol.yaml # build
esphome run fancontrol.yaml --device fancontrol.local # compile + OTA over WiFi
esphome logs fancontrol.yaml --device fancontrol.local # verify boot
```

`esphome run` compiles and uploads in one step; the device authenticates with
`ota_password`, reboots, and `restart_counter` increments. Alternatives: the
web UI at `http://fancontrol.local` (OTA upload button, feed it the built
`.ota.bin`), or USB fallback `--device /dev/cu.wchusbserial10` if OTA is broken
or the device won't join WiFi.

> **Do not flash the GitHub Release `.bin`.** Release artifacts are CI-built
> with dummy secrets and will not join the network — they are reference /
> reproducibility artifacts only. Always build locally against the real
> `secrets.yaml` (gitignored; backed up in 1Password).

### F6 — Persistence
`globals` with `restore_value: true` for the boot counter. All `number`
Expand Down
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,36 @@ esphome run fancontrol.yaml
If WiFi credentials are wrong or missing, the device falls back to an open AP
named **`FanControl-Setup`** at `192.168.4.1` — connect, configure, save.

## Updating the device

After the first USB flash the device is on WiFi, so every later update goes
**over the air** — no cable needed:

```bash
# Build and push the new firmware over WiFi (password-gated OTA)
esphome compile fancontrol.yaml
esphome run fancontrol.yaml --device fancontrol.local

# Confirm it rebooted and reconnected
esphome logs fancontrol.yaml --device fancontrol.local
```

The device authenticates the upload with `ota_password`, reboots into the new
build, and bumps its restart counter. You can also upload a locally-built
`.ota.bin` through the web dashboard at `http://fancontrol.local`.

**If OTA fails** (or the device can't join WiFi / is in safe mode), reflash over
USB:

```bash
esphome run fancontrol.yaml --device /dev/cu.wchusbserial10 # adjust the serial port
```

> ⚠️ **Don't flash the binaries attached to a GitHub Release.** They are built
> in CI with placeholder secrets and will not connect to your WiFi. Always
> build locally against your own `secrets.yaml`. GitHub Release artifacts exist
> for reference and reproducibility only.

## Home Assistant integration

Once on the network, HA auto-discovers the device via the ESPHome integration.
Expand Down