# Setup ## Hardware - Adafruit Feather ESP32-S3 (4 MB flash, 2 MB PSRAM) - MAX17048 fuel gauge (on the Feather: I²C 0x36, SDA GPIO 3, SCL GPIO 4, powered via GPIO 7) - Waveshare 4.2" e-Paper Module (SKU 13353, 400×300, b/w, SSD1683), see [Wiring](wiring.md) - A LiPo battery (2000 mAh here) on the Feather's JST connector ## Flashing MicroPython MicroPython v1.29.0 (`ESP32_GENERIC_S3`); the standard variant fits 4 MB of flash: ```bash curl -o firmware/ESP32_GENERIC_S3-20260824-v1.29.0.bin \ https://micropython.org/resources/firmware/ESP32_GENERIC_S3-20260824-v1.29.0.bin # ROM bootloader: hold BOOT, press Reset, release BOOT uvx esptool --chip esp32s3 erase-flash uvx esptool --chip esp32s3 write-flash 0x0 firmware/ESP32_GENERIC_S3-20260824-v1.29.0.bin # then press Reset once ``` Debian's `esptool` 4.7 package is broken for the ESP32-S3 (missing stub), hence `uvx esptool`. ## Configuration The settings come in two parts: [`src/defaults.py`](#defaults) holds all defaults and is versioned; `src/config.py` belongs to your installation and is ignored by git. It starts with `from defaults import *` and adds the credentials (WLAN, Home Assistant, MQTT) and whatever should differ from the defaults. ```bash cp src/config_example.py src/config.py # fill it in; it is never committed make deploy make watch ``` `WIFI_NETWORKS` may list several networks (at home and on a boat, say). The list order is the priority: the first network in range wins, hidden networks (not in the scan) come last. If the first network in the list worked last time (remembered in `wlan_last.txt`), the board connects without a scan; otherwise it scans on every connect, so that at home it returns to the home network instead of, for example, a travel router brought along. ## Development tools Requires [uv](https://docs.astral.sh/uv/); `uv sync` installs the dependencies (pytest, ruff, mpremote, mpy-cross, …). ```bash make check # ruff + pytest (including the CPython/MicroPython comparison) make format # format the code make docs # build this documentation into docs/_build/html make deploy # compile the modules (.mpy), copy them to the board via USB, restart make deploy-wlan # upload changed files over the WLAN and restart (HOST=…) make monitor # print the battery state every 5 s via USB (Ctrl+C ends) make watch # query the battery state every 5 s via WLAN (HOST=epaper.local) make fonts # regenerate the bitmap fonts from DejaVu Sans make icons # regenerate the weather icons (Phosphor Icons Bold) make micropython # build the MicroPython unix port (for the comparison in make check) uv run tools/preview.py preview.png [--live] [--charging] [--link URL] [--alerts ID …] uv run tools/board_tiles.py pull|push # tile settings as a local file (tiles.json) uv run tools/serve_ui.py # try the web interface on the PC (http://localhost:8080/) uv run tools/qr_sticker.py sticker.png [--size-mm 30] # QR code to print uv run tools/battery_forecast.py [--since …] # discharge rate and runtime from Home Assistant ``` When deploying, all modules except [`boot.py`](#boot), [`main.py`](#main) and `config.py` are precompiled with `mpy-cross`; that saves import time on every wake (the large fonts load in 0.2 instead of 1.1 s). Replaced `.py` files are deleted on the board. ## Versions Versions follow [Semantic Versioning](https://semver.org) and are git tags (`v0.2.0`). When building the firmware, [`tools/firmware.py`](#firmware) adds `version.mpy` with the output of `git describe`: `0.2.0` for a release, `0.2.0-3-g6ec6236` three commits later, with `-dirty` for uncommitted changes. The board shows it in the boot sequence, in the web interface and as firmware version of the device in Home Assistant; the docs show the same version. To release, tag and push: ```bash git tag -a v0.2.0 -m "Version 0.2.0" git push origin main v0.2.0 ``` ## Updates over the WLAN `make deploy-wlan` works at any time. If the board is asleep, it switches on the "Maintenance mode" switch via MQTT and waits until the board sees it at its next data update (during the day within 10 minutes, at night or with a low battery up to an hour) and stays awake; afterwards it switches it off again. `--no-reset` skips the restart after the upload, for instance during a battery measurement. The update endpoints are protected with `UPDATE_PASSWORD` from `config.py` (or `WEBREPL_PASSWORD` as a fallback). A watchdog restarts the board if `main.py` hangs for longer than [`WATCHDOG_S`](#defaults.WATCHDOG_S), even when interrupted via mpremote.