Add READMEs, docs, and LICENSE for publishing
Build and push server image / build-and-push (push) Successful in 32s
Build and push server image / build-and-push (push) Successful in 32s
- LICENSE: MIT, with attribution notes for the vendored qrcode/epaper_fonts/ dns_server code and the epd7in3e driver's transcription of Waveshare's register sequence. - Top-level README.md: project overview, hardware list, quick-start pointing at firmware/ and server/, repo layout, license, Claude Code attribution. - firmware/README.md: full rewrite (was still the stock ESP-IDF captive portal example's README) -- build/flash instructions, Kconfig reference table, first-boot walkthrough, and how to reset to provisioning mode via NVS erase (the only way in right now; a proper reconfigure trigger is a future addition). - docs/hardware.md: wiring table + parts list + strapping-pin/SPI-speed notes. - docs/architecture.md: sequence diagram and walkthrough of the full provision -> connect -> fetch -> display -> sleep cycle, plus the reasoning behind doing image processing server-side and reusing Immich's face detection instead of bundling a detector. - server/README.md: fixed stale endpoint docs (missing GET /frame/config, POST /api/config still describing removed immich_url/api_key fields).
This commit is contained in:
+74
-112
@@ -1,129 +1,91 @@
|
||||
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-S2 | ESP32-S3 |
|
||||
| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | -------- |
|
||||
# ESPresso Frame Firmware
|
||||
|
||||
# Captive Portal Example
|
||||
ESP-IDF firmware for the ESP32-C6. On first boot it provisions itself over
|
||||
a WiFi captive portal; after that it wakes on a timer, fetches an
|
||||
already-processed frame from the [server](../server/), streams it straight
|
||||
to the panel over SPI, and goes back to deep sleep.
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
See [`docs/architecture.md`](../docs/architecture.md) for the full boot/fetch
|
||||
cycle and [`docs/hardware.md`](../docs/hardware.md) for wiring.
|
||||
|
||||
This example demonstrates two methods of a captive portal, used to direct users to an authentication page or other necessary starting point before browsing.
|
||||
## Build and flash
|
||||
|
||||
One approach response to all DNS queries with the address of the softAP, and redirects all HTTP requests to the captive portal root page. This "funnelling" of DNS and traffic triggers the captive portal (sign in) to appear on Android, iOS, and Windows. Note that the example will not redirect HTTPS requests.
|
||||
|
||||
The other approach is a more modern method which includes a field in the DHCP offer (AKA DHCP Option 114), provided when the client is assigned an IP address, which specifies to the client where the captive portal is. This is advantageous because it doesn't require the overhead of DNS redirects and can work more reliably around HTTPS, HTST, and other security systems, as well as being more standards compliant. This feature is toggleable in the `Example Configuration`, but does not conflict with the DNS methodology -- these two methods work towards the same goal and can complement each other.
|
||||
|
||||
## How to Use Example
|
||||
|
||||
Before project configuration and build, be sure to set the correct chip target using `idf.py set-target <chip_name>`.
|
||||
|
||||
### Hardware Required
|
||||
|
||||
* A development board with ESP32/ESP32-S2/ESP32-C3 SoC (e.g., ESP32-DevKitC, ESP-WROVER-KIT, etc.)
|
||||
* A USB cable for power supply and programming
|
||||
* WiFi interface
|
||||
|
||||
### Configure the project
|
||||
|
||||
Open the project configuration menu (`idf.py menuconfig`).
|
||||
|
||||
In the `Example Configuration` menu:
|
||||
|
||||
* Set the Wi-Fi configuration.
|
||||
* Set `SoftAP SSID`
|
||||
* Set `SoftAP Password`
|
||||
* Set `Maximal STA connections`
|
||||
* Set `DHCP Captive portal` to enable or disable DHCP Option 114
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
Requires [ESP-IDF](https://docs.espressif.com/projects/esp-idf/en/stable/esp32c6/get-started/)
|
||||
(developed against v5.x/v6.x) with the environment sourced (`. $IDF_PATH/export.sh`
|
||||
or your distro's equivalent).
|
||||
|
||||
```
|
||||
idf.py set-target esp32c6
|
||||
idf.py build
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
(`Ctrl-]` exits the monitor.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
The committed [`sdkconfig.defaults`](sdkconfig.defaults) pins an 8MB flash
|
||||
size and a custom [`partitions.csv`](partitions.csv) (2MB app partition --
|
||||
the default "single app" ~1MB partition runs out of room once the HTTP
|
||||
client, TLS, and vendored fonts/QR library are linked in). If your board
|
||||
has less flash, you'll need to shrink the app partition and drop features
|
||||
to fit.
|
||||
|
||||
## Example Output
|
||||
## Configuration (`idf.py menuconfig`)
|
||||
|
||||
Under **ESPresso Frame Configuration**:
|
||||
|
||||
### ESP32 Output for DNS Redirect
|
||||
| Option | Default | What it does |
|
||||
| --- | --- | --- |
|
||||
| `ESP_AP_SSID` | `ESPRESSO` | Provisioning softAP SSID prefix (device appends `_XXXXXX` from its MAC) |
|
||||
| `ESP_MAX_STA_CONN` | 4 | Max clients on the provisioning softAP |
|
||||
| `ESP_ENABLE_DHCP_CAPTIVEPORTAL` | on | DHCP Option 114 captive portal detection |
|
||||
| `FRAME_STA_CONNECT_MAX_RETRIES` | 3 | Home WiFi connect attempts before falling back to provisioning |
|
||||
| `FRAME_STA_CONNECT_TIMEOUT_MS` | 15000 | Per-attempt WiFi connect timeout |
|
||||
| `FRAME_SERVER_CHECK_TIMEOUT_MS` | 3000 | Timeout for `GET /frame/config` (reachability check + refresh interval) |
|
||||
| `FRAME_FETCH_TIMEOUT_MS` | 15000 | Timeout for `GET /frame/image` |
|
||||
| `FRAME_SLEEP_INTERVAL_S` | 3600 | **Fallback only** -- the refresh interval is normally set server-side; see below |
|
||||
| `FRAME_RETRY_INTERVAL_S` | 300 | Sleep duration after a failed cycle, before retrying |
|
||||
|
||||
Under **E-Paper Display (epd7in3e) Configuration**: SPI/GPIO pin
|
||||
assignments and SPI clock speed -- see
|
||||
[`docs/hardware.md`](../docs/hardware.md) for the wiring these correspond to.
|
||||
|
||||
### Why `FRAME_SLEEP_INTERVAL_S` says "fallback"
|
||||
|
||||
The actual refresh interval is set from the server's web UI (see
|
||||
[`server/README.md`](../server/README.md)) and delivered to the device on
|
||||
every wake via `GET /frame/config`, so it can be changed without
|
||||
reflashing. The Kconfig value only applies before the device has ever
|
||||
successfully reached a configured server, or if the response doesn't
|
||||
include a valid interval.
|
||||
|
||||
## First boot
|
||||
|
||||
With no stored WiFi config (a fresh device, or after erasing NVS -- see
|
||||
below), the device brings up the display before anything else and shows a
|
||||
two-step setup screen:
|
||||
|
||||
1. **Connect to WiFi** -- a QR code encoding `WIFI:T:WPA;S:...;P:...;;`
|
||||
for the device's own `ESPRESSO_XXXXXX` softAP, with the SSID and
|
||||
password also printed underneath for anyone provisioning from a
|
||||
desktop/laptop that can't scan a QR code.
|
||||
2. **Configure device** -- a QR code linking straight to the captive
|
||||
portal's config page (`http://192.168.4.1/` by default), for a
|
||||
one-scan shortcut once you've joined the AP.
|
||||
|
||||
The config page asks for your home WiFi SSID/password and the "Tools
|
||||
Server" address (`host:port` of the [server](../server/) -- **not** your
|
||||
Immich server). Saving reboots the device, which then connects to your
|
||||
home network and starts its normal fetch/sleep cycle.
|
||||
|
||||
## Resetting to provisioning mode
|
||||
|
||||
There's currently no in-field way to force the device back into
|
||||
provisioning (a future addition) -- reconfiguring means erasing its NVS
|
||||
partition over USB:
|
||||
|
||||
```
|
||||
I (733) example: Set up softAP with IP: 192.168.4.1
|
||||
I (743) example: wifi_init_softap finished. SSID:'esp32_ssid' password:'esp32_pwd'
|
||||
I (753) example: Starting server on port: '80'
|
||||
I (753) example: Registering URI handlers
|
||||
I (763) example_dns_redirect_server: Socket created
|
||||
I (763) example_dns_redirect_server: Socket bound, port 53
|
||||
I (773) example_dns_redirect_server: Waiting for data
|
||||
I (1873) wifi:new:<1,1>, old:<1,1>, ap:<1,1>, sta:<255,255>, prof:1
|
||||
I (1873) wifi:station: e8:84:a5:18:8f:80 join, AID=1, bgn, 40U
|
||||
I (2203) example: station e8:84:a5:18:8f:80 join, AID=1
|
||||
I (2833) example_dns_redirect_server: Received 50 bytes from 192.168.4.2 | DNS reply with len: 66
|
||||
I (2843) example_dns_redirect_server: Waiting for data
|
||||
I (3043) example_dns_redirect_server: Received 39 bytes from 192.168.4.2 | DNS reply with len: 55
|
||||
I (3043) example_dns_redirect_server: Waiting for data
|
||||
I (3043) example_dns_redirect_server: Received 42 bytes from 192.168.4.2 | DNS reply with len: 58
|
||||
I (3053) example_dns_redirect_server: Waiting for data
|
||||
W (3203) wifi:<ba-add>idx:4 (ifx:1, e8:84:a5:18:8f:80), tid:0, ssn:9, winSize:64
|
||||
I (3533) example: Redirecting to root
|
||||
I (5693) example_dns_redirect_server: Received 37 bytes from 192.168.4.2 | DNS reply with len: 53
|
||||
I (5693) example_dns_redirect_server: Waiting for data
|
||||
I (5783) example_dns_redirect_server: Received 46 bytes from 192.168.4.2 | DNS reply with len: 62
|
||||
I (5783) example_dns_redirect_server: Waiting for data
|
||||
I (6303) example_dns_redirect_server: Received 41 bytes from 192.168.4.2 | DNS reply with len: 57
|
||||
I (6303) example_dns_redirect_server: Waiting for data
|
||||
I (6303) example_dns_redirect_server: Received 41 bytes from 192.168.4.2 | DNS reply with len: 57
|
||||
I (6313) example_dns_redirect_server: Waiting for data
|
||||
I (6593) example: Redirecting to root
|
||||
I (9623) example: Redirecting to root
|
||||
I (12913) example: Redirecting to root
|
||||
I (13263) example_dns_redirect_server: Received 34 bytes from 192.168.4.2 | DNS reply with len: 50
|
||||
I (13273) example_dns_redirect_server: Waiting for data
|
||||
I (13273) example_dns_redirect_server: Received 34 bytes from 192.168.4.2 | DNS reply with len: 50
|
||||
I (13283) example_dns_redirect_server: Waiting for data
|
||||
I (16303) example_dns_redirect_server: Received 32 bytes from 192.168.4.2 | DNS reply with len: 48
|
||||
I (16303) example_dns_redirect_server: Waiting for data
|
||||
I (18073) example: Redirecting to root
|
||||
I (18273) example_dns_redirect_server: Received 34 bytes from 192.168.4.2 | DNS reply with len: 50
|
||||
I (18273) example_dns_redirect_server: Waiting for data
|
||||
I (18273) example_dns_redirect_server: Received 34 bytes from 192.168.4.2 | DNS reply with len: 50
|
||||
I (18283) example_dns_redirect_server: Waiting for data
|
||||
I (20683) example_dns_redirect_server: Received 42 bytes from 192.168.4.2 | DNS reply with len: 58
|
||||
I (20683) example_dns_redirect_server: Waiting for data
|
||||
I (20753) example: Redirecting to root
|
||||
I (21323) example: Redirecting to root
|
||||
I (22683) example_dns_redirect_server: Received 48 bytes from 192.168.4.2 | DNS reply with len: 64
|
||||
I (22693) example_dns_redirect_server: Waiting for data
|
||||
I (23443) example_dns_redirect_server: Received 48 bytes from 192.168.4.2 | DNS reply with len: 64
|
||||
I (23453) example_dns_redirect_server: Waiting for data
|
||||
I (23473) example: Serve root
|
||||
I (23503) example_dns_redirect_server: Received 48 bytes from 192.168.4.2 | DNS reply with len: 64
|
||||
I (23513) example_dns_redirect_server: Waiting for data
|
||||
python -m esptool --chip esp32c6 -p PORT erase-region 0x9000 0x6000
|
||||
```
|
||||
|
||||
### `tcpdump` Output for DHCP Option 114
|
||||
|
||||
Note `URL (114)` with the AP address.
|
||||
|
||||
```
|
||||
19:14:20.522698 c8:yy:yy:yy:yy:yy > 74:xx:xx:xx:xx:xx, ethertype IPv4 (0x0800), length 590: (tos 0x0, ttl 64, id 243, offset 0, flags [none], proto UDP (17), length 576)
|
||||
192.168.4.1.67 > 192.168.4.2.68: [udp sum ok] BOOTP/DHCP, Reply, length 548, xid 0x76a26648, Flags [none] (0x0000)
|
||||
Your-IP 192.168.4.2
|
||||
Client-Ethernet-Address 74:xx:xx:xx:xx:xx
|
||||
Vendor-rfc1048 Extensions
|
||||
Magic Cookie 0x63825363
|
||||
DHCP-Message (53), length 1: Offer
|
||||
Subnet-Mask (1), length 4: 255.255.255.0
|
||||
Lease-Time (51), length 4: 7200
|
||||
Server-ID (54), length 4: 192.168.4.1
|
||||
Default-Gateway (3), length 4: 192.168.4.1
|
||||
Domain-Name-Server (6), length 4: 192.168.4.1
|
||||
BR (28), length 4: 192.168.4.255
|
||||
MTU (26), length 2: 1500
|
||||
URL (114), length 18: "http://192.168.4.1"
|
||||
Router-Discovery (31), length 1: N
|
||||
Vendor-Option (43), length 6: 1.4.0.0.0.2
|
||||
```
|
||||
(Offset/size match the `nvs` entry in [`partitions.csv`](partitions.csv);
|
||||
this only wipes the config, not the app itself.)
|
||||
|
||||
Reference in New Issue
Block a user