| name | deploy |
| description | Deployment workflow for the pool-controller ESP32 — build, serial flash, uploadfs, OTA update, release-please semver pipeline, version management, and post-deploy verification. Use when asked to deploy, flash, upload, release, or update firmware. 🇩🇪 Deutsche Trigger: Deployment, Flashen, Firmware einspielen, OTA Update, Release erstellen, Version verwalten, semver, uploadfs, ausrollen. |
| keywords | ["deployment","flashing","firmware upload","ota update","release","semver","version management","release-please","serial flash","uploadfs","web filesystem","littlefs","pio run","esptool","deploy firmware","pool-controller deploy","firmware release","version bump","ausrollen","flashen"] |
Deploy — Pool Controller
Complete deployment workflow for the pool-controller ESP32 firmware: build,
serial flash, web filesystem upload, OTA update, and semver release management.
Overview
┌─────────────┐ ┌──────────────┐ ┌──────────────────────┐ ┌─────────────────┐
│ Pre-flight │ → │ Build │ → │ Deploy │ → │ Verify │
│ - lint │ │ pio run │ │ │ │ - monitor logs │
│ - format │ │ -e norvi │ │ Serial: │ │ - version check│
│ - version │ │ │ │ flash + uploadfs │ │ - web UI │
└─────────────┘ └──────────────┘ │ OTA: │ └─────────────────┘
│ 1. flash firmware │
│ 2. upload web fs │
│ via /api/fs/ │
│ upload (6 files)│
└──────────────────────┘
Prerequisites
./venv/bin/pio --version
pio --version
If pio is not in PATH, use the project's virtual environment:
./venv/bin/pio <command>
Pre-flight Checks
Version consistency
Before deploying, verify the firmware version across all sources:
| Source | File | Check |
|---|
| Build flag | platformio.ini:18 | -D FW_VERSION="x.y.z" |
| Central fallback | src/Version.h:11 | #define FW_VERSION "0.0.0" (fallback only) |
| Release manifest | .release-please-manifest.json | "x.y.z" |
| Git tag | git tag -l | sort -V | vx.y.z exists |
Source of truth: platformio.ini build flag FW_VERSION. Release-please auto-updates it on release.
src/Version.h is a fallback and also auto-updated by release-please.
grep 'FW_VERSION' platformio.ini
cat .release-please-manifest.json
git tag --sort=-v:refname | head -5
Lint & Quality (optional)
./venv/bin/pio check --environment esp32dev --skip-packages
find src/ -name '*.cpp' -o -name '*.hpp' -o -name '*.h' | xargs clang-format --dry-run -Werror
Build
./venv/bin/pio run
./venv/bin/pio run -e esp32dev
./venv/bin/pio run --target clean && ./venv/bin/pio run
./venv/bin/pio run --verbose
Expected output:
RAM: [== ] 15.1% (used 49596 bytes from 327680 bytes)
Flash: [======== ] 83.7% (used 1096433 bytes from 1310720 bytes)
========================= [SUCCESS] Took X seconds =========================
Warning: If flash usage exceeds ~90%, the firmware may not fit. Consider
memory optimization (see cpp-memory-opt skill).
Deploy — Serial Flash
1. Find the serial port
ls /dev/ttyUSB* /dev/ttyACM* 2>/dev/null
ls -la /dev/serial/by-id/ 2>/dev/null
ls /dev/cu.usbserial-* /dev/cu.wchusbserial-* 2>/dev/null
Common ESP32 USB-to-UART chips:
- CP2102 (Silicon Labs) →
/dev/ttyUSB0
- CH340 →
/dev/ttyUSB0 or /dev/ttyCH341USB0
2. Flash firmware + Web filesystem
./venv/bin/pio run --target upload --upload-port /dev/ttyUSB0
./venv/bin/pio run --target uploadfs --upload-port /dev/ttyUSB0
Important: Both steps are required for a complete deployment.
- Step A flashes the compiled firmware (
firmware.bin)
- Step B uploads web assets (
data/web/ → LittleFS partition)
3. One-liner (if uploadfs is needed)
./venv/bin/pio run --target upload --upload-port /dev/ttyUSB0 && \
./venv/bin/pio run --target uploadfs --upload-port /dev/ttyUSB0
Serial upload troubleshooting
| Symptom | Fix |
|---|
Connecting..._____ (stuck) | Hold BOOT button on ESP32 during connection, release when flashing starts |
Failed to connect | Check cable (data, not charge-only), try different USB port |
Access denied | sudo chmod 666 /dev/ttyUSB0 or add user to dialout group |
No such file or directory | USB-to-UART driver missing — install CP210x/CH340 driver |
| Upload fails mid-way | Lower upload_speed in platformio.ini (e.g. 115200) |
Deploy — OTA Update (Web Upload)
This project uses web-based OTA via the device's REST API (POST /api/update).
ArduinoOTA (espota.py) is not supported — there is no ArduinoOTA server running on the device.
Requirement: The device must be online with WiFi configured and reachable on the network.
1. Build the firmware
./venv/bin/pio run -e norvi_ae01_r
./venv/bin/pio run -e esp32dev
2. Authenticate & upload in one step
curl -c /tmp/ota-cookie.txt \
-X POST http://<device-ip>/api/login \
-d "password=<admin-password>"
curl -b /tmp/ota-cookie.txt \
-F "firmware=@.pio/build/norvi_ae01_r/firmware.bin" \
http://<device-ip>/api/update
Response HTTP 200 OK with body OK signals success. The device reboots immediately after a successful upload.
Important: The session cookie expires after 600 seconds (10 minutes). Re-login if the upload fails with a 401.
3. One-liner (login + upload)
curl -c /tmp/ota-cookie.txt -s -X POST http://<device-ip>/api/login \
-d "password=<admin-password>" && \
curl -b /tmp/ota-cookie.txt -s -F "firmware=@.pio/build/norvi_ae01_r/firmware.bin" \
http://<device-ip>/api/update
4. OTA + uploadfs (if web files changed)
The /api/update endpoint only flashes the firmware binary — LittleFS web assets
(HTML/CSS/JS) are not included. After OTA, deploy web assets via the
/api/fs/upload endpoint (available since firmware v4.1.1, PR #155).
One-liner: firmware + all web assets
DEVICE=http://pool-controller.local
PASSWORD=admin
COOKIE_JAR=$(mktemp)
curl -c "$COOKIE_JAR" -s -X POST "$DEVICE/api/login" -d "password=$PASSWORD" > /dev/null
curl -b "$COOKIE_JAR" -s -F "firmware=@.pio/build/norvi_ae01_r/firmware.bin" \
"$DEVICE/api/update" && echo "Firmware flashed, waiting for reboot..."
sleep 20
curl -c "$COOKIE_JAR" -s -X POST "$DEVICE/api/login" -d "password=$PASSWORD" > /dev/null
for f in index.html app.js style.css sw.js manifest.json icon.svg; do
curl -b "$COOKIE_JAR" -s -X POST "$DEVICE/api/fs/upload" \
-F "path=/web/$f" -F "content=@data/web/$f"
done
rm -f "$COOKIE_JAR"
echo "Deployment complete!"
Manual upload of individual files
curl -b /tmp/ota-cookie.txt -X POST http://<device-ip>/api/fs/upload \
-F "path=/web/app.js" \
-F "content=@data/web/app.js"
Response 200 OK means the file was written to LittleFS. The endpoint:
- Requires authentication (valid session cookie)
- Only allows paths under
/web/ (security)
- Blocks path traversal (
..)
- Streams multipart uploads — no size limit
- Returns
200 OK on success (handled by the POST completion handler)
Note: Firmware versions without /api/fs/upload (pre-v4.1.1) require
serial uploadfs for web asset deployment. See "Serial Flash" above.
OTA troubleshooting
| Symptom | Fix |
|---|
HTTP 401 on upload | Session expired — re-login (cookie valid 10 min) |
HTTP 429 on login | Too many failed attempts — wait for lockout to expire |
curl: (7) Failed to connect | Device unreachable — check WiFi, ping the IP/hostname |
| Upload succeeds but device stays off | Wait ~30s for reboot, then check /api/status uptime |
| Device doesn't boot after OTA | Serial flash a known-good firmware (see Rollback) |
CI/CD Pipeline (GitHub Actions)
The project uses release-please for automated semver releases.
Release workflow
Trigger: Push to main branch.
Commit (Conventional Commits) → [release-please] → Release PR
→ Merge PR → [release-please] → GitHub Release + Tag
→ [Build Firmware] → Upload firmware.bin to Release Assets
Conventional Commits
| Prefix | Version bump | Example |
|---|
fix: | Patch (x.y.Z) | fix: correct temperature offset |
feat: | Minor (x.Y.0) | feat: add heater control |
feat!: or fix!: | Major (X.0.0) | feat!: drop ESP8266 support |
chore: | No release | chore: update dependencies |
Release-please configuration
"extra-files": [
{
"type": "generic",
"path": "platformio.ini",
"search-regex": "FW_VERSION=\"[^\"]*\""
},
{
"type": "generic",
"path": "src/Version.h",
"search-regex": "FW_VERSION \"[^\"]*\""
}
]
Release-please automatically updates FW_VERSION in both platformio.ini and
src/Version.h when a new release is created.
Manual version bump (without release-please)
sed -i 's/FW_VERSION="[^"]*"/FW_VERSION="X.Y.Z"/' platformio.ini
sed -i 's/FW_VERSION "[^"]*"/FW_VERSION "X.Y.Z"/' src/Version.h
echo '{".": "X.Y.Z"}' > .release-please-manifest.json
git tag -a vX.Y.Z -m "Release vX.Y.Z"
Post-deployment Verification
1. Serial monitor
./venv/bin/pio device monitor --port /dev/ttyUSB0 --baud 115200 \
--filter esp32_exception_decoder --filter time
Look for these boot patterns:
✓ Pin configuration validated
✓ Controller setup completed. Free heap: X B # Normal boot
✓ HA Discovery Device ID set to: pool_controller_...
✓ Web Server running on port 80.
→ Boot counter: N # Boot counter
2. Version check — Web UI
Open http://<device-ip>/ in browser → version displayed as vX.Y.Z below the title.
3. Version check — REST API
curl http://<device-ip>/api/status | python3 -m json.tool
4. Version check — Home Assistant
Check device info in Home Assistant → sw_version field matches the firmware version.
5. Full system diagnostics endpoint
The /api/status endpoint returns:
{
"fw_version": "3.2.0",
"pool_temp": 25.3,
"solar_temp": 58.1,
"ctrl_temp": 32.4,
"pool_pump": true,
"solar_pump": false,
"op_mode": "auto",
"uptime": 3600,
"free_heap": 180000,
"max_alloc": 45000,
"rssi": -65,
"wifi_connected": true,
"mqtt_connected": true,
"local_ip": "192.168.1.100"
}
Rollback
If a deployed firmware has issues:
- Serial flash the previous known-good
firmware.bin (download from GitHub Releases)
- Or rebuild from a specific git tag:
git checkout v3.1.0
./venv/bin/pio run --target upload --upload-port /dev/ttyUSB0
git checkout main
Skill Integration
This skill works alongside:
| Skill | Purpose |
|---|
platformio-workflow | General build/monitor/debug commands |
platformio-env | Deep platformio.ini configuration |
web-ui | Web interface changes (before deploying) |
release-please | Release pipeline internals (detailed) |
conventional-commits | Commit format for automatic releases |
cpp-code-quality | Linting before deployment |
esp32-reliability | 24/7 stability checks |
cpp-memory-opt | Flash/RAM optimization if space is tight |