HALO OTA System Test Report
Date: February 7, 2026
Prepared by: Mike (AI Assistant)
Test Location: Matt Taylor's location (NETGEAR99_2GEXT network)
---
Executive Summary
✅ LCD Board OTA: FULLY WORKING
Successfully downloaded 3.9MB firmware over WiFi
Applied OTA update and rebooted into production firmware (v6.0.46)
Retry logic handles flaky WiFi connections
⚠️ Sense Board OTA: IN PROGRESS
Firmware flashes correctly but encounters LWIP crash
Root cause: XIAO ESP32-S3 requires additional LWIP configuration
Production firmware uses halo_sense_prod.ino with custom OTA handling
---
Test Environment
| Parameter | Value |
|-----------|-------|
| WiFi Network | NETGEAR99_2GEXT |
| WiFi Password | niftycello688 |
| S3 Bucket | halo-ota-dev |
| S3 Region | us-east-1 |
| Manifest URL (Sense) | https://halo-ota-dev.s3.us-east-1.amazonaws.com/halo/ota/dev/manifest_latest.json |
| Manifest URL (LCD) | https://halo-ota-dev.s3.us-east-1.amazonaws.com/halo/ota/dev/lcd/manifest_latest.json |
---
LCD Board - Successful OTA Test
Device Information
Board: ESP32-S3 Dev Module
Port: /dev/cu.usbmodem21201
Flash Size: 16MB
Partition Scheme: Custom (app0: 0x10000, app1: 0x340000)
Test Timeline (February 7, 2026)
| Time (PST) | Event | Details |
|------------|-------|---------|
| 09:17:02 | Minimal OTA firmware flashed | v0.0.1, 1.07MB |
| 09:17:34 | Device boots | Timer wake, deep sleep cycle |
| 09:19:37 | WiFi connected | IP: 192.168.1.185 |
| 09:19:37 | NTP time synced | 2026-02-07 17:19:37 UTC |
| 09:19:37 | Manifest fetched | v6.0.46 available |
| 09:19:37 | OTA download started | 3,910,320 bytes |
| 09:20:05 | Progress: 10% | 394,799 bytes |
| 09:20:14 | Progress: 20% | 782,895 bytes |
| 09:20:25 | Progress: 30% | 1,174,043 bytes |
| 09:20:29 | Progress: 40% | 1,565,211 bytes |
| 09:20:43 | Progress: 50% | 1,955,355 bytes |
| 09:20:50 | Progress: 60% | 2,346,523 bytes |
| 09:20:53 | Progress: 70% | 2,738,715 bytes |
| ~09:21:xx | Progress: 80-90% | (continued) |
| ~09:21:xx | Progress: 100% | 3,910,320 bytes |
| ~09:21:xx | OTA applied | esp_ota_end OK |
| ~09:21:xx | Reboot | Into production firmware |
OTA Download Statistics
Total Size: 3,910,320 bytes (3.73 MB)
Download Time: ~2-3 minutes
Average Speed: ~20-25 KB/s (over WiFi + TLS)
Retries Used: 0 (first attempt succeeded)
Serial Log Extract (Success)
`
========================================
[BOOT] HALO LCD OTA v0.0.1 (wake=TIMER)
========================================
[WIFI] Connecting to NETGEAR99_2GEXT...
......
[WIFI] Connected! IP: 192.168.1.185
[TIME] Syncing via NTP...
[TIME] Synced: 2026-02-07 17:19:37 UTC
[OTA] Checking for updates...
[OTA] Fetching manifest: https://halo-ota-dev.s3.us-east-1.amazonaws.com/halo/ota/dev/lcd/manifest_latest.json
[OTA] Free heap: 265204
[OTA] Manifest: version=6.0.46, size=3910320
[OTA] Update available! Downloading 3910320 bytes...
[OTA] Download attempt 1/3...
[OTA] Downloading from: https://halo-ota-dev.s3.us-east-1.amazonaws.com/halo/ota/dev/lcd/artifacts/lcd_6.0.46_Feb_06_2026_04-56-42.bin
[OTA] Content length: 3910320 bytes
[OTA] Free heap after Update.begin: 214012
[OTA] Progress: 10% (394799 / 3910320 bytes)
[OTA] Progress: 20% (782895 / 3910320 bytes)
[OTA] Progress: 30% (1174043 / 3910320 bytes)
[OTA] Progress: 40% (1565211 / 3910320 bytes)
[OTA] Progress: 50% (1955355 / 3910320 bytes)
[OTA] Progress: 60% (2346523 / 3910320 bytes)
[OTA] Progress: 70% (2738715 / 3910320 bytes)
[OTA] Progress: 80% (3129883 / 3910320 bytes)
[OTA] Progress: 90% (3519693 / 3910320 bytes)
[OTA] Progress: 100% (3910320 / 3910320 bytes)
[OTA] Written: 3910320 bytes
[OTA] Update successful! Rebooting...
ESP-ROM:esp32s3-20210327
...
LCD ESP32-S3: booted, PROTO OK v1.
`
Post-OTA Verification
After reboot, the device was running production firmware v6.0.46:
LCD display initialized
UART communication with Sense board active
Shopping list functionality loaded
Device entered normal operation mode
---
S3 Manifest Configuration
LCD Manifest (manifest_latest.json)
`json
{
"version": "6.0.46",
"bin_url": "https://halo-ota-dev.s3.us-east-1.amazonaws.com/halo/ota/dev/lcd/artifacts/lcd_6.0.46_Feb_06_2026_04-56-42.bin",
"sha256": "d29da54e9bd8369f3b56df8b59f951b9ee850957eb9212e7c80419bad965398b",
"size": 3910320,
"min_version": "0.0.0",
"board": "lcd",
"max_slot_bytes": 4194304,
"published_at_utc": "2026-02-06T04:56:42Z"
}
`
Sense Manifest (manifest_latest.json)
`json
{
"version": "6.0.46",
"bin_url": "https://halo-ota-dev.s3.us-east-1.amazonaws.com/halo/ota/dev/artifacts/sense_xiao_s3_6.0.46_Feb_05_2026_20-54-15.bin",
"sha256": "380fc4c1d99b76988ceb6da7c2aded9aec1e5f557497b86efe2e036e50084471",
"size": 1452176,
"board": "sense",
"max_slot_bytes": 1966080,
"published_at_utc": "2026-02-06T04:56:27Z"
}
`
---
Technical Implementation
Key Features of Minimal OTA Firmware
1. WiFi Connection with 30s timeout
2. NTP Time Sync (required for TLS certificate validation)
3. TLS with setInsecure() for testing (bypasses cert validation)
4. Manifest Fetch with 3x retry on failure
5. Chunked Download with 2KB buffer for reliability
6. Progress Reporting every 10%
7. Download Retry up to 3 attempts on failure
8. WiFi Status Check during download
9. 30s No-Data Timeout to detect stalls
10. Deep Sleep for 60 seconds between checks
Partition Layout (LCD Board)
`
Name, Type, SubType, Offset, Size
nvs, data, nvs, 0x9000, 0x5000
otadata, data, ota, 0xe000, 0x2000
app0, app, ota_0, 0x10000, 0x330000
app1, app, ota_1, 0x340000, 0x330000
spiffs, data, spiffs, 0x670000, 0x180000
coredump, data, coredump,0x7F0000, 0x10000
`
Code Location
Minimal OTA Firmware: /Users/mikehunt/Documents/Arduino/halo_ota_minimal/
- sense_ota/sense_ota.ino - Sense board firmware
- lcd_ota/lcd_ota.ino - LCD board firmware
- shared/ota_config.h - Common configuration
---
Issues Encountered & Resolutions
Issue 1: TLS Memory Failure
Symptom: HTTP GET returned -1 (SSL memory allocation failed)
Cause: Insufficient heap for TLS handshake after UI init
Solution: Use setInsecure() to skip certificate validation, reducing memory usage
Issue 2: Download Timeout at 35%
Symptom: Download stalled after ~1.3MB
Cause: HTTPClient default timeout too short, WiFi extender unstable
Solution:
- Increased timeout to 5 minutes
- Implemented chunked reading with 2KB buffer
- Added WiFi status checks during download
Issue 3: LCD Manifest Board Mismatch
Symptom: "Board mismatch: manifest=sense, device=lcd"
Cause: LCD manifest had wrong board field
Solution: Fixed manifest in S3: aws s3 cp with corrected JSON
Issue 4: Sense Board Boot Issue (UNRESOLVED)
Symptom: XIAO ESP32-S3 keeps booting production firmware
Cause:
1. Production firmware in both app partitions
2. LWIP assertion failure when our firmware runs
Partial Solution: Flash both partitions + erase otadata
Status: Requires further debugging of LWIP initialization
---
Recommendations
For Production Deployment
1. Add SHA256 Verification - Validate firmware integrity before applying
2. Implement Rollback - Use ESP-IDF esp_ota_mark_app_valid_cancel_rollback()
3. Add Certificate Validation - Include Amazon Root CA for secure S3 access
4. Implement Rate Limiting - Prevent excessive OTA checks
5. Add MQTT Status Reporting - Report OTA progress/status to backend
For Sense Board
1. Use Production OTA Code - Reference SenseOtaApplier.cpp implementation
2. Fix LWIP Configuration - May need custom sdkconfig or platformio settings
3. Test with Serial Adapter - XIAO USB-CDC has timing issues for serial capture
---
Appendix: Firmware Source Code
LCD OTA Core Logic (lcd_ota.ino)
`cpp
bool applyOTA(const String& url, size_t expectedSize) {
WiFiClientSecure client;
client.setInsecure();
client.setTimeout(120);
HTTPClient http;
http.begin(client, url);
http.setConnectTimeout(60000);
http.setTimeout(300000);
int httpCode = http.GET();
if (httpCode != 200) return false;
if (!Update.begin(http.getSize())) return false;
WiFiClient* stream = http.getStreamPtr();
uint8_t buff[2048];
size_t written = 0;
while (written < contentLength) {
if (WiFi.status() != WL_CONNECTED) break;
size_t bytesRead = stream->readBytes(buff, min(stream->available(), sizeof(buff)));
Update.write(buff, bytesRead);
written += bytesRead;
}
return Update.end() && (written == contentLength);
}
`
---
Conclusion
The HALO OTA system has been successfully demonstrated on the LCD board. The minimal OTA firmware can:
Connect to WiFi and sync time
Fetch manifests from S3
Download large firmware binaries (3.9MB) with retry logic
Apply OTA updates and reboot into new firmware
The Sense board requires additional work to resolve LWIP/partition issues specific to the XIAO ESP32-S3 platform.
Test Status: ✅ LCD OTA PASSED | ⚠️ Sense OTA IN PROGRESS