# CLAUDE.md Firmware for a desk clock / thermometer on the **WaveShare ESP32-S3-RLCD-4.2**. `README.md` is the product spec — layout, update rates and error behaviour are defined there and should be treated as requirements, not suggestions. ## Build ```sh pio run # build pio run -t upload # flash over USB-C pio device monitor # 115200 baud ``` `include/secrets.h` is gitignored. Copy `include/secrets.h.example` to it before the first build; the build fails without it. ## Hardware facts Confirmed from the vendor examples in [waveshareteam/ESP32-S3-RLCD-4.2](https://github.com/waveshareteam/ESP32-S3-RLCD-4.2) (`02_Example/Arduino/`), not from the wiki — the wiki does not publish a pinout. | Function | Pin / value | | --- | --- | | LCD controller | **ST7305**, 300 × 400 mono reflective, SPI | | LCD SCK / MOSI / DC / CS / RST / TE | 11 / 12 / 5 / 40 / 41 / 6 | | I²C SDA / SCL | 13 / 14 | | PCF85063 RTC | 0x51 | | SHTC3 temp/humidity | 0x70 | | Module | ESP32-S3-WROOM-1-N16R8 — 16 MB flash, 8 MB **octal** PSRAM | The panel is native **300 wide × 400 high**. The app drives it rotated (`U8G2_R1`), so all drawing code works in a **400 × 300** landscape frame. Any new layout constant belongs in `include/config.h`, not inline in `display.cpp`. `board_build.arduino.memory_type = qio_opi` in `platformio.ini` is what selects octal PSRAM. Dropping it silently disables PSRAM at boot. ## Layout `lib/ST7305_U8g2/` is vendored verbatim from the vendor's `10_U8G2_Test` example (Apache-2.0). Treat it as third party: fix bugs upstream-style, keep local changes minimal and commented. ``` src/main.cpp startup order + the loop; owns the indoor reading src/display.{h,cpp} the only code that touches the panel src/rtc.{h,cpp} PCF85063 driver, stores UTC src/temperature.{h,cpp} SHTC3 driver src/network.{h,cpp} Wi-Fi association + NTP, writes the RTC src/outdoor.{h,cpp} HTTP poll of the remote sensor include/config.h pins, layout, intervals, timezone ``` ## Design decisions worth preserving **The RTC holds UTC, never local time.** Presentation goes through `localtime()` with `TIMEZONE_POSIX`, so a daylight-saving change never requires a write to the chip. Anything that writes the RTC must convert to UTC first. **Boot order is deliberate.** Panel → RTC → SHTC3 → first draw → *then* Wi-Fi. A useful screen appears in about a second even if the network never associates. Do not move `network::begin()` earlier. **Nothing in `loop()` blocks on the network.** `network::loop()` owns its own retry timing (`WIFI_RETRY_MS`); the clock and indoor reading must keep updating through an outage. **The panel is refreshed only when a *rendered string* changes**, not when a value changes. `display::update()` formats everything to text and compares against what is on screen, which is why a 21.54 → 21.51 drift causes no refresh. This is the mechanism behind the README's "avoid unnecessary full-screen refreshes"; keep it if you rework the renderer. **Missing data draws as `--.-` / `--:--`, never as a zero or a blank.** A stale outdoor reading keeps its last value on screen with a dashed rule under it — the README is explicit that the old number is preferable to an error value. ## Fonts All from U8g2's built-in set; no font files are shipped. `FONT_TIME` is `logisoso92_tn`, which is **digits, colon, period and minus only** — it cannot render letters. `FONT_TEMP` must stay a `_tf` variant so `°` (U+00B0) resolves, and temperatures must be drawn with `drawUTF8`, not `drawStr`. ## Not implemented Everything under README "Future Features", plus: light sleep between refreshes (the "low power consumption" goal is currently unaddressed — the loop spins), partial-region panel updates, and the TE pin.