315 lines
13 KiB
Markdown
315 lines
13 KiB
Markdown
# Boilerplate project for ESP32 (c3) development.
|
|
## Specifications
|
|
### Summary
|
|
This project is a starting point for ESP32-C3 development.
|
|
It provides basic "infrastructure" and "Framework" for specific developments.
|
|
|
|
### Functionality
|
|
- Enables the user to connect to their WiFi network
|
|
- Has a mechanism to "Factory reset" the device
|
|
- Eases updates by providing OTA update mechanism using update-server URL
|
|
- Role based User Management
|
|
- Standard Roles
|
|
- Sysadmin (Can administer the system, update, change settings)
|
|
- UserAdmin (Can administer user accounts)
|
|
- WebUIConnect (Allows logging in to the Web UI)
|
|
- Debugger (Allowed to use API Test UI in the Web UI)
|
|
- Secure, Role based Rest API for all functions
|
|
- API functions access can be configured either as "Public" or a user role can be assigned to determine access (only users who have the corresponding rolle can call that API).
|
|
- User authentication needs to return a token that is needed to be passed into all rest API requests (unless the called API endpoint is public)
|
|
- All admin functions (including HTTPS configuration, logs, password changes, ...) are available in the API by default
|
|
- All API calls will return a JSON that returns
|
|
- Result of the last call (either success or detailed error description)
|
|
- If applicable, the result of the call
|
|
- There are 3 boilerplate APIs that are available publicly by default
|
|
- Control the on-board LED (set brightness; 0-> off; 100->full brightness)
|
|
- Ping (that returns the current uptime as JSON)
|
|
- Add (takes 2 integers and returns the result of adding those integers)
|
|
- Admin Web-UI
|
|
- Role based security configuration
|
|
- Roles are centrally maintained
|
|
- User management
|
|
- Users can change their password
|
|
- Users can be assigned zero or more roles
|
|
- Allows to interactively calling of the Rest APIs via the web UI
|
|
- The corresponding user token is preset with the current user's token by decfault, but it can be overwritten
|
|
- This functionality is only accessible when the user has the role Debugger
|
|
- Enables secrity configuration of the exposed API functions
|
|
- Configuration of logging
|
|
- log level
|
|
- log max size in non volatile memory (default 50kb)
|
|
- Viewing- and management of logs
|
|
- filter
|
|
- search
|
|
- view
|
|
- clear logs
|
|
- HTTPS configuration
|
|
- By default a self-signed certificate is used
|
|
- Allows the set up of certificates for HTTPS (file upload)
|
|
- Firmware update handling
|
|
- Upload new firmware via file selector
|
|
- Check URL for new firmware button (this will reach out to a configurable (in the code) URL to to try and find new firmware. If new firmware is available it offers to install.)
|
|
|
|
|
|
### Technical details
|
|
- The target platform is ESP32-C3 with Arduino Framework
|
|
- Memory is to be treated as a sparse resource, so the size of the code must be kept small
|
|
- The API layer exclusively uses JSON messages to communicate in both direcitons
|
|
- The Web UI is to be implemented as a Vanilla JS single page web site UI that communicates with the backend via the REST API.
|
|
- All documentation goes into the README.md file
|
|
- The Admin UI is implemented in an extendable way, so that developers can easily use the same mechnism to implement their own UIs
|
|
- The "factory reset" function should execute when a definable PIN (choose a good default) is pulled high- or low on boot and held that way for 10 seconds.
|
|
- When starting after initial flashing or factory reset, the device needs to act as an unsecured access point with a defined SSID .
|
|
- The user can then connect to that AP and is presented with a basic user interface (setup screen) where the user can
|
|
- Choose their WIFI
|
|
- Enter the password of their WIFI (can be left empty for unsecuried wifi)
|
|
- Change the admin useranme (defaults to "admin")
|
|
- Enter an Admin password (can be left empty)
|
|
- A submit button that submits the entered information
|
|
- Once the user entered submitted information, the information is stored in non volatile memory, and the device is restarted.
|
|
- On subsequent startups, the device looks for configuraiton stored in non volatile memory
|
|
- The factory reset functionality deletes the information from the non volatile memory, which will lead to the setup screen.
|
|
- Logging is done based on log level.
|
|
- The logs are stored in non volatile memory
|
|
- they need to be implemented as a ring buffer that occupies a configurable space in non volatile memory
|
|
- There is a standard API endpoint that returns the logs (restricted to user role Debugger)
|
|
|
|
### Source organization
|
|
|
|
The firmware is split by responsibility:
|
|
|
|
| Path | Responsibility |
|
|
| --- | --- |
|
|
| `src/main.cpp` | Arduino `setup()`/`loop()` and boot orchestration |
|
|
| `src/app.h` | Shared constants, state, structs, and function declarations |
|
|
| `src/config/api_definitions.cpp` | Central API catalog, route handlers, and default role/public access mapping |
|
|
| `src/core/state.cpp` | Global state, project/device identity, hashing, persisted settings |
|
|
| `src/core/logging.cpp` | LittleFS log ring buffer |
|
|
| `src/core/auth.cpp` | Users, roles, tokens, and API authorization |
|
|
| `src/core/device.cpp` | LED control, factory reset, and WiFi connection |
|
|
| `src/util/json_utils.cpp` | Small JSON response and request parsing helpers |
|
|
| `src/web/ui.cpp` | LittleFS-backed HTML serving and captive-portal helper pages |
|
|
| `src/web/routes.cpp` | Page/captive route registration and generic API route registration from `apiDefs` |
|
|
| `src/handlers/handlers_setup.cpp` | Setup and WiFi scan route handlers |
|
|
| `src/handlers/handlers_api.cpp` | Public/device API handlers and API ACL handlers |
|
|
| `src/handlers/handlers_auth.cpp` | Login, users, roles, and password route handlers |
|
|
| `src/handlers/handlers_admin.cpp` | Settings, logs, and certificate route handlers |
|
|
| `src/handlers/handlers_ota.cpp` | Firmware upload and OTA route handlers |
|
|
| `data/setup.html` | First-run provisioning UI served from LittleFS |
|
|
| `data/admin.html` | Admin UI served from LittleFS |
|
|
|
|
## Current implementation
|
|
|
|
The firmware in `src/main.cpp` implements the boilerplate as a compact Arduino ESP32-C3 application using built-in ESP32 Arduino libraries only:
|
|
|
|
- WiFi provisioning access point and first-run setup UI.
|
|
- Factory reset on boot by holding `FACTORY_RESET_PIN` high/low default behavior: GPIO4 held LOW for 10 seconds.
|
|
- Admin Web UI served from `/`.
|
|
- Token-based login with role checks.
|
|
- Configurable API access control where each route can be `PUBLIC` or require one role.
|
|
- Public boilerplate APIs: ping, add, and LED brightness.
|
|
- LED brightness is persisted in non-volatile memory, applied on boot, and loaded into the Admin UI slider.
|
|
- User management with the standard roles `Sysadmin`, `UserAdmin`, `WebUIConnect`, and `Debugger`.
|
|
- Custom role management. System roles are protected and cannot be deleted.
|
|
- Active/inactive user accounts with role checkboxes in the Admin UI.
|
|
- API security management through a list/detail UI with a role dropdown per API.
|
|
- Ring-buffer logging in LittleFS with default maximum size of 50 KiB.
|
|
- Firmware and LittleFS filesystem upload OTA and update-from-URL hooks.
|
|
- HTTPS certificate storage API. The default Arduino `WebServer` runs HTTP; stored certificate material is available for applications that add TLS termination.
|
|
|
|
### Web UI files
|
|
|
|
The setup and admin pages are stored as editable HTML files in the PlatformIO `data/` directory and are served from LittleFS by request path:
|
|
|
|
| URL | File |
|
|
| --- | --- |
|
|
| `/` in setup mode | `/setup.html` |
|
|
| `/` in normal mode | `/admin.html` |
|
|
| `/setup.html` | `/setup.html` |
|
|
| `/admin.html` | `/admin.html` |
|
|
|
|
After changing files in `data/`, upload the filesystem image as well as the firmware:
|
|
|
|
```sh
|
|
pio run -t uploadfs
|
|
```
|
|
|
|
Uploading the filesystem image replaces the LittleFS contents, including stored log files. WiFi configuration, users, roles, and settings are stored in NVS preferences and are not part of that filesystem image.
|
|
|
|
### Provisioning
|
|
|
|
After initial flashing or factory reset, the device starts an unsecured access point:
|
|
|
|
```text
|
|
TSL-Embedded-XXXXXX
|
|
```
|
|
|
|
Connect to the access point and open:
|
|
|
|
```text
|
|
http://192.168.1.1/
|
|
```
|
|
|
|
While in setup mode, the device also runs a captive-portal DNS responder. Most phones and laptops will automatically open or suggest the setup page after joining the AP; unknown HTTP requests are redirected to `http://192.168.1.1/`.
|
|
|
|
The setup page lets you choose WiFi, set the WiFi password, set the admin username, and set the admin password. The default admin username is `admin`; the password may be empty. After submit, the device stores the configuration in non-volatile memory and restarts.
|
|
|
|
The default project name is `TSL-Embedded`. The firmware combines the project name with the chip suffix to form the device name, for example `TSL-Embedded-BDF5F0`. That name is used as the setup AP SSID, the station-mode WiFi hostname, and the mDNS hostname. Override the project name at compile time if needed:
|
|
|
|
```cpp
|
|
#define PROJECT_NAME "MyProject"
|
|
```
|
|
|
|
On normal boot, the device connects to the configured WiFi and serves the Admin UI at the IP printed to serial.
|
|
|
|
### Factory reset
|
|
|
|
The default reset pin is GPIO4. Hold GPIO4 LOW during boot for 10 seconds to clear stored configuration and logs, then the device restarts into setup mode.
|
|
|
|
The defaults can be changed at compile time:
|
|
|
|
```cpp
|
|
#define FACTORY_RESET_PIN 4
|
|
#define FACTORY_RESET_ACTIVE_LEVEL LOW
|
|
```
|
|
|
|
### Authentication
|
|
|
|
Login:
|
|
|
|
```http
|
|
POST /api/login
|
|
Content-Type: application/json
|
|
|
|
{"username":"admin","password":""}
|
|
```
|
|
|
|
The response contains a bearer token. Pass it to protected APIs:
|
|
|
|
```http
|
|
Authorization: Bearer <token>
|
|
```
|
|
|
|
### API response shape
|
|
|
|
Every API returns JSON with a `success` field. Errors include an `error` string:
|
|
|
|
```json
|
|
{"success":false,"error":"Authentication required"}
|
|
```
|
|
|
|
### Public boilerplate APIs
|
|
|
|
Ping:
|
|
|
|
```http
|
|
GET /api/ping
|
|
```
|
|
|
|
Add two integers:
|
|
|
|
```http
|
|
POST /api/add
|
|
Content-Type: application/json
|
|
|
|
{"a":1,"b":2}
|
|
```
|
|
|
|
Set LED brightness from 0 to 100:
|
|
|
|
```http
|
|
POST /api/led
|
|
Content-Type: application/json
|
|
|
|
{"brightness":50}
|
|
```
|
|
|
|
### Admin APIs
|
|
|
|
Protected APIs and their default roles:
|
|
|
|
| API | Role |
|
|
| --- | --- |
|
|
| `POST /api/logout` | `WebUIConnect` |
|
|
| `GET /api/me` | `WebUIConnect` |
|
|
| `GET /api/apis` | `Sysadmin` |
|
|
| `POST /api/apis` | `Sysadmin` |
|
|
| `GET /api/users` | `UserAdmin` |
|
|
| `POST /api/users` | `UserAdmin` |
|
|
| `POST /api/password` | `WebUIConnect` |
|
|
| `GET /api/settings` | `Sysadmin` |
|
|
| `POST /api/settings` | `Sysadmin` |
|
|
| `GET /api/logs` | `Debugger` |
|
|
| `POST /api/logs/clear` | `Debugger` |
|
|
| `POST /api/ota/check` | `Sysadmin` |
|
|
| `POST /api/ota/run` | `Sysadmin` |
|
|
| `POST /api/update` | `Sysadmin` |
|
|
| `GET /api/cert` | `Sysadmin` |
|
|
| `POST /api/cert` | `Sysadmin` |
|
|
|
|
Change API security:
|
|
|
|
```http
|
|
POST /api/apis
|
|
Authorization: Bearer <token>
|
|
Content-Type: application/json
|
|
|
|
{"path":"/api/led","method":"POST","role":"WebUIConnect"}
|
|
```
|
|
|
|
Use `"role":"PUBLIC"` to make a known API public.
|
|
|
|
### Firmware and filesystem updates
|
|
|
|
Because the Admin UI is stored in LittleFS, OTA releases are distributed as one combined update package. The package contains both the LittleFS filesystem image and the firmware image, so UI and firmware changes cannot drift apart during an update.
|
|
|
|
| Artifact | Purpose | Build output |
|
|
| --- | --- | --- |
|
|
| `firmware.bin` | Intermediate ESP32 application firmware | `.pio/build/seeed_xiao_esp32c3/firmware.bin` |
|
|
| `littlefs.bin` | Intermediate files from `data/`, including `admin.html` and `setup.html` | `.pio/build/seeed_xiao_esp32c3/littlefs.bin` |
|
|
| `update.tslpkg` | Single OTA package containing LittleFS plus firmware | `.pio/build/seeed_xiao_esp32c3/update.tslpkg` |
|
|
|
|
Create the single OTA package:
|
|
|
|
```sh
|
|
pio run
|
|
pio run -t buildfs
|
|
python scripts/create_update_package.py
|
|
```
|
|
|
|
For a direct USB flash during development:
|
|
|
|
```sh
|
|
pio run -t upload
|
|
pio run -t uploadfs
|
|
```
|
|
|
|
For OTA updates, host `update.tslpkg` on your update server. The Admin UI has one update package URL. The default is:
|
|
|
|
```text
|
|
http://example.com/update.tslpkg
|
|
```
|
|
|
|
The package format is intentionally small:
|
|
|
|
| Offset | Size | Value |
|
|
| --- | --- | --- |
|
|
| `0` | 8 | Magic bytes `TSLUPD1\0` |
|
|
| `8` | 4 | Header size, little-endian `uint32`, currently `32` |
|
|
| `12` | 4 | LittleFS image size, little-endian `uint32` |
|
|
| `16` | 4 | Firmware image size, little-endian `uint32` |
|
|
| `20` | 12 | Reserved, zero-filled |
|
|
| `32` | variable | LittleFS image bytes |
|
|
| `32 + littlefsSize` | variable | Firmware image bytes |
|
|
|
|
Upload an update package from the Admin UI or post multipart form data to:
|
|
|
|
```http
|
|
POST /api/update
|
|
```
|
|
|
|
`/api/ota/check` checks reachability and content length for the configured package URL. `/api/ota/run` streams the package from the URL, applies the LittleFS image first, applies the firmware image second, and restarts.
|
|
|
|
Installing an update package replaces the LittleFS partition, including stored log files. WiFi configuration, users, roles, API ACLs, and settings are stored in NVS preferences and are not part of the LittleFS image.
|
|
|