ESP Platform Phase 1E Validation
Required checks:
- Backend is active through systemd.
- Web is available at "http://192.168.1.62:88/".
- ".bin" upload works.
- Firmware binary is stored under "/srv/esp-platform/firmware".
- Metadata is stored in PostgreSQL table "firmware".
- Size and SHA-256 are calculated automatically.
- Firmware list displays the uploaded firmware.
- Download endpoint returns the stored ".bin".
API endpoints:
GET  /api/v1/firmware
GET  /api/v1/firmware/{id}
POST /api/v1/firmware
GET  /api/v1/firmware/{id}/download
Phase 1E Validation Run - 2026-07-06
Validation was executed directly inside VM "esp-platform".
Result: passed.
Observed test values:
- Uploaded firmware id: "1"
- Uploaded filename: "validation.bin"
- Metadata name: "Validation Firmware"
- Application: "AZ-TEST"
- Hardware: "ESP32 DevKit"
- Stored filesystem path: "/srv/esp-platform/firmware/038cfdcf-3ff2-45c5-ba5f-d7ec5515593c.bin"
- Size: "33" bytes
- SHA-256: "82d3c475f7f8e72e521e38e41e60efc6b6375452fbf2ea336e4430daea54c4f8"
Checks passed:
- "esp-platform-backend" active through systemd.
- Web accessible through Nginx at "http://192.168.1.62:88/".
- API accessible through Nginx at "http://192.168.1.62:88/api/v1/firmware".
- Upload stored metadata in PostgreSQL.
- Upload stored ".bin" in filesystem.
- Downloaded file matched original size and SHA-256.
Phase 2 Browser Validation - 2026-07-06
Validation performed:
- "http://192.168.1.62:88/flasher" loads successfully.
- "GET /api/v1/firmware" is consumed by the page.
- Direct HTTP access by VM IP is not a secure Web Serial context; the page shows a warning and disables serial connection.
- Temporary local tunnel tested:
ssh -N -L 18888:127.0.0.1:88 codex@192.168.1.62
- "http://localhost:18888/flasher" loads successfully.
- Through localhost, the secure-context warning is not shown and the Connect ESP32 button is enabled.
Pending physical validation:
- Upload real firmware ".bin" from the administration page.
- Select the firmware in "/flasher".
- Connect ESP32 by USB in Chrome/Edge.
- Grant serial-port permission.
- Flash firmware and confirm ESP32 boots.
Phase 2 HTTPS Pre-Validation - 2026-07-06
NPM HTTPS route configured by project owner.
Checked URL:
https://iot.aeizoon.com/flasher
Result:
- Page loads correctly through HTTPS.
- Insecure-context warning is not displayed.
- Connect ESP32 button is enabled by the page.
- Firmware catalog loads successfully.
- Catalog currently contains no firmware after removal of the Phase 1E validation test firmware.
Physical validation remains pending with a real firmware ".bin" and ESP32 connected to the Mac.
Phase 2 Physical Validation Attempt - ESP32 Ethernet/WiFi AliExpress - 2026-07-06
Hardware and connection:
- Board: ESP32 Ethernet/WiFi AliExpress board.
- USB connection: external USB-TTL adapter.
- Browser route: "https://iot.aeizoon.com/flasher".
Observed successful steps:
- Web Serial works through HTTPS.
- Flasher connects to the ESP32.
- Chip detected: "ESP32-D0WD rev 1".
- MAC address detected.
- Stub upload works.
- Flash ID is read.
- Firmware download from backend works.
- Downloaded firmware size: "1,274,512" bytes.
Observed baudrate behavior:
- "115200" is stable.
- "921600" fails after baudrate change with errors similar to:
   - "Unable to verify flash chip connection"
   - "Serial data stream stopped"
Decision applied:
- Default baudrate changed to "115200".
- Higher baudrates remain available under the advanced speed group.
Observed flashing failure before correction:
File 1 doesn't fit in the available flash
The error occurred with both:
- flash address "0x0"
- flash address "0x10000"
Review result:
- "esptool-js" expects "fileArray: [{ data: Uint8Array, address: number }]"; the implementation uses "Uint8Array" from "response.arrayBuffer()".
- The library performs a fit check when "flashSize !== "keep"".
- Passing "flashSize: "detect"" directly to "writeFlash()" is unsafe because "flashSizeBytes("detect")" does not resolve to the detected byte size for the fit check.
- The Flasher now calls "detectFlashSize()" after connecting and sends the resolved size string, such as "4MB", to "writeFlash()".
- If auto-detection is unavailable, the Flasher sends "keep" to avoid the false fit check.
Additional logging added before flashing:
- detected flash size;
- configured flash size sent to "esptool-js";
- firmware byte length;
- binary data type and "Uint8Array" check;
- flash start address;
- calculated end address.
Second physical validation result:
- The same "File 1 doesn't fit in the available flash" error was observed again.
- The browser output did not include the newly added diagnostic lines, which confirmed that the HTTPS route was still serving a cached older "flasher.js".
Correction applied after second attempt:
- Flasher assets now use cache-busting version "phase2-20260706-3".
- The Flasher logs its build at page load: "Flasher build: phase2-20260706-3".
- Internal Spring/Nginx delivery for static assets now uses no-cache headers.
- NPM still adds external cache headers, so every Flasher JS change must also bump the asset version query string.
- Default Flash size changed to "Keep firmware setting" to bypass the "esptool-js" pre-write fit check.
- "Auto-detected size check" remains available as a diagnostic option, not the default.
Current status:
- Ready for another physical validation attempt at "115200" baud.
- Confirm the Flasher console starts with "Flasher build: phase2-20260706-3".
- Use Flash size "Keep firmware setting".
- If it still fails, capture the Flasher console log and browser console output.
Phase 2 Physical Flash Success - 2026-07-06
Physical flashing succeeded through "https://iot.aeizoon.com/flasher" using Flasher build "phase2-20260706-3".
Observed result:
- Firmware "freepocket 0.3.5j" downloaded from backend.
- Firmware size: "1,274,512" bytes.
- ESP32 detected: "ESP32-D0WD revision 1".
- Flash size detected: "4MB".
- Flash address: "0x0".
- Configured flash size sent to "esptool-js": "keep".
- Written bytes: "1,274,512".
- Compressed bytes: "804,935".
- Write duration: "72.507" seconds at "115200".
- "esptool-js" requested hard reset via RTS after flashing.
Follow-up UI adjustment:
- Flasher build "phase2-20260706-4" adds an "Erase ESP32" button.
- The erase button is enabled only after a serial connection is established.
- It asks for confirmation before calling "eraseFlash()".
- The button is visually separated from "Disconnect" with a "4cm" left margin on desktop layouts.
Phase 2 Serial Monitor and Hardware Reset - 2026-07-06
Flasher build "phase2-20260706-6" adds post-flash serial diagnostics:
- "Start Serial Monitor" opens the selected Web Serial port at the currently selected baudrate.
- If the esptool flasher connection is still open, it is closed first and the same port is reopened for serial reading.
- Serial bytes are written directly into the Flasher console so the ESP32 boot log can be inspected.
- "Stop Monitor" closes the serial monitor and releases the port.
- "Hardware Reset" sends reset through esptool when the flasher connection is active.
- When the serial monitor is active, "Hardware Reset" pulses RTS through Web Serial so boot output can be observed immediately.
Operational test path:
1. Flash firmware successfully.
2. Click "Start Serial Monitor".
3. Click "Hardware Reset" or press the board reset button.
4. Confirm boot output appears in the console.
Phase 2 FreePocket Firmware Layout Finding - 2026-07-07
A comparison with "https://freepocket.chrisoft.io/" showed that the external FreePocket installer is powered by ESP Web Tools and uses a manifest with multiple flash parts, not a single binary written at "0x0".
Observed external manifest layout:
- bootloader: "0x1000"
- partitions: "0x8000"
- boot_app0: "0xE000"
- application: "0x10000"
- LittleFS: "0x290000"
The firmware uploaded to ESP Platform as "freepocket 0.3.5j" has size "1,274,512" bytes and starts with an ESP image header at offset "0x0":
- magic: "0xE9"
- segments: "6"
- flash mode byte: "0x02"
- flash size/frequency byte: "0x2f"
- entry point: "0x400827ac"
This indicates that the uploaded file is likely an application image intended to be written at "0x10000", not a complete flash image intended for "0x0".
Important consequence:
- Flashing this app image at "0x0" overwrites the bootloader area and can prevent the ESP32 from booting correctly.
- Flashing it at "0x10000" only works if the target ESP32 already has compatible bootloader, partition table, boot_app0 and filesystem/data partitions.
- A clean device or a previously erased device requires a multi-part firmware package/manifest, not only the application ".bin".
Next required platform correction:
- ESP Platform should distinguish between single full-image firmware and multi-part ESP32 firmware packages.
- For ESP-IDF/Arduino app-only binaries, the flasher should require or infer address "0x10000" instead of defaulting to "0x0".
- For FreePocket-style installs, ESP Platform needs manifest/multi-part upload support before it can reproduce the external installer behavior exactly.
Phase 2 External FreePocket Files Retrieved - 2026-07-07
The public files referenced by "https://freepocket.chrisoft.io/manifest_latest.json" were downloaded for technical comparison and validation reference into:
/opt/esp-platform/repo/firmware/reference/freepocket
Downloaded layout:
| Offset | File | Size | SHA-256 |
| --- | --- | ---: | --- |
| "0x1000" | "pincho_solax.bootloader.bin" | "18,992" | "644de0067047e22380034b8989c39e5d2882f7538c698788866ca5130427322e" |
| "0x8000" | "pincho_solax.partitions.bin" | "3,072" | "148b959cbff1c38aa8e1d5c0ba9d612c54997b945e56a63f41223eef650653a1" |
| "0xE000" | "boot_app0.bin" | "8,192" | "f94c5d786a7a8fab06ac5d10e33bf37711a6697636dc037559ea19cc410a17f0" |
| "0x10000" | "pincho_solax.bin" | "1,072,320" | "026f043621c2338c3172751f54e2c9ca9bb9f1741b78c0b8799cf78f6843f9ac" |
| "0x290000" | "pincho_solax.littlefs.bin" | "1,441,792" | "0edc7b60e8162ab7177992783648dd8d5857dab463c2f0a60715aa40af57a0e1" |
Important version note:
- The public manifest reports version "0.3.5f".
- The firmware currently uploaded to ESP Platform is labeled "0.3.5j" and has size "1,274,512" bytes.
- These should not be treated as the same firmware package.
Use of the retrieved files:
- They are suitable as a reference to reproduce the external installer behavior in a validation environment.
- ESP Platform still needs explicit multi-part package support before these files are represented cleanly in the database and web UI.
Phase 2 Multi-Part Firmware Support - 2026-07-07
ESP Platform now supports a minimal multi-part firmware package model for Phase 2 validation.
Backend changes:
- Added "firmware.package_type" with values "SIMPLE" and "MULTIPART".
- Added "firmware_part" table with per-part metadata:
   - part order;
   - display name;
   - original filename;
   - filesystem path;
   - flash offset;
   - size;
   - SHA-256 checksum.
- Added API download endpoint for parts:
   - "GET /api/v1/firmware/{id}/parts/{partId}/download"
- "GET /api/v1/firmware" now returns "packageType", "partCount" and "parts[]".
Seeded validation package:
- Firmware id: "3"
- Name: "FreePocket full install"
- Version: "0.3.5f"
- Type: "MULTIPART"
- Total size: "2,544,368" bytes
- Source reference: public FreePocket ESP Web Tools manifest.
Flasher changes:
- Flasher build: "phase2-20260706-7".
- Multi-part firmware cards are displayed with a distinct blue "Multi-part" badge.
- Simple ".bin" firmware remains supported as "Single .bin".
- For "MULTIPART", the Flasher downloads all parts and sends a multi-entry "fileArray" to "esptool-js".
- For "MULTIPART", the Flasher uses:
   - "eraseAll: true"
   - "flashMode: keep"
   - "flashFreq: keep"
   - configured flash size from the UI, default "keep".
Validation performed:
- Backend service active after Flyway V3 migration.
- API lists both the existing simple firmware and the new multi-part FreePocket package.
- All five part download endpoints return HTTP 200 with expected sizes.
Physical validation pending:
- Select "FreePocket full install 0.3.5f" from "/flasher".
- Confirm the card is marked "Multi-part".
- Connect ESP32 at "115200".
- Flash selected firmware.
- Start Serial Monitor.
- Send "Hardware Reset" or press reset manually.
- Confirm complete boot log and device operation.
Phase 2 FreePocket AP Password and Version Search - 2026-07-07
The FreePocket documentation page states that, after first boot, the ESP32 creates an AP named "fp_xxxxxx" and the default password is "<redacted>". The same page states the default local IP is "192.168.4.1" with web user "admin" and password "<redacted>".
Binary inspection confirms related defaults inside the downloaded public package:
- LittleFS contains "WEB_USERNAME": "admin".
- LittleFS contains "WEB_PASSWORD": "<redacted>".
- Application strings contain AP creation format "fp_%02x%02x%02x" and log text "enabling AP: ssid:%s, password:****".
Public installer version search:
- "https://freepocket.chrisoft.io/" references only "manifest_latest.json".
- The public manifest reports version "0.3.5f".
- Tested common manifest/version names such as "manifest_0.3.5g.json", "manifest_0.3.5j.json", "manifest_stable.json", "manifest_beta.json": only "manifest_latest.json" is exposed.
Current conclusion:
- Public direct ESP Web Tools installer exposes only FreePocket "0.3.5f".
- The "0.3.5j" binary uploaded manually to ESP Platform appears to come from another source or unpublished package and should not be assumed to match the public multi-part package.
Additional version finding:
- "manifest_latest.json" reports "0.3.5f".
- The downloaded "pincho_solax.bin" application image contains internal string "0.3.5g".
- ESP Platform display metadata was updated to "manifest 0.3.5f / app 0.3.5g" to avoid confusing this package with the manually uploaded app-only "0.3.5j" firmware.
Phase 2 Patched AP Password Package - 2026-07-07
A test firmware package was created by binary patching the public FreePocket multi-part package from default string "<redacted>" to "<redacted>".
Reason:
- The ESP32 AP appears as "fp_xxxxxx", but the default password did not work reliably in physical testing.
- The string "<redacted>" appears once in the application image and once in the LittleFS image.
- The replacement "<redacted>" has the same length as "<redacted>", so the patch does not shift binary offsets.
Created package:
- Firmware id: "4"
- Name: "FreePocket full install AP <redacted>"
- Version label: "manifest 0.3.5f / app 0.3.5g / AP <redacted>"
- Type: "MULTIPART"
- Expected AP/web password for validation: "<redacted>"
Changed checksums:
- application "pincho_solax.bin": "4454f8d1ecb422741ff7fa535f664bb063286d687c98f1beeda98d6b2655a16b"
- littlefs "pincho_solax.littlefs.bin": "cabb88ab8b621e56eece8f74173ec4014cebb3e3965c8232ccc96fcc026ddbd0"
Unchanged parts:
- bootloader
- partition table
- boot_app0
Validation performed:
- The patched package appears in "/api/v1/firmware".
- All five part download endpoints return HTTP 200.
- The patched application and LittleFS files contain "<redacted>" and no longer expose "<redacted>" through simple string inspection.
Risk note:
- This is a binary patch for validation only. If LittleFS metadata or firmware logic validates file contents internally, the device may ignore the patched value or behave differently. A source-level firmware build would be cleaner once source/build inputs are available.
Phase 2 Patched AP Password Package Repair - 2026-07-07
Physical test result:
- Flashing "FreePocket full install AP <redacted>" caused repeated "SW_RESET" before FreePocket printed its normal "[general] starting program" log line.
- The bootloader reached "entry 0x400805f0", then the ESP32 reset repeatedly.
Cause found:
- Binary patching the application image changed bytes inside "pincho_solax.bin".
- The ESP32 application image footer still contained the original checksum and validation SHA-256 digest.
- "esptool image_info" reported:
   - checksum invalid;
   - validation hash invalid.
Repair applied:
- Recomputed the ESP image checksum byte from "0x82" to "0x87".
- Recomputed the appended validation SHA-256 digest.
- Replaced the application part for firmware id "4" with the repaired image.
Repaired application checksum:
- File SHA-256: "fff0335928c92bc3150b5e527d11543b1db28c2dca22bde69ce152f33e105e22"
- ESP image checksum: valid
- ESP validation hash: valid
Next physical validation:
- Flash "FreePocket full install AP <redacted>" again from "/flasher".
- Confirm the normal FreePocket boot log appears.
- Try AP password "<redacted>".
Admin Firmware UI Maintenance - 2026-07-07
Changes validated:
- "/admin/firmware" now exposes a delete action for stored firmware records and their filesystem files.
- "/admin/firmware" now exposes a multi-part ESP32 package upload form with explicit offsets for bootloader, partitions, boot_app0, application, and filesystem/LittleFS images.
- Multi-part firmware entries are visually distinguished with blue badges.
- The shared web palette was adjusted away from green success/console accents toward blue.
- "/flasher" and "/admin/firmware" were checked for horizontal page overflow after the layout/CSS changes.
Functional validation:
- Temporary single ".bin" upload returned HTTP 302, appeared through "/api/v1/firmware", and was deleted through "/admin/firmware/{id}/delete".
- Temporary multi-part upload with five ".bin" files returned HTTP 302, appeared through "/api/v1/firmware" as "MULTIPART" with "partCount=5", and was deleted through "/admin/firmware/{id}/delete".
- After deletion, no temporary validation firmware remained in "/api/v1/firmware".
- Backend service "esp-platform-backend" was rebuilt, redeployed, restarted, and remained active.
Browser validation through "https://iot.aeizoon.com":
- "/admin/firmware" loaded CSS "app.css?v=phase2-20260707-1".
- "/admin/firmware" showed the multi-part upload form and delete buttons.
- "/admin/firmware" reported no horizontal overflow at 1280 px viewport width.
- "/flasher" loaded CSS "app.css?v=phase2-20260707-1".
- "/flasher" showed "Erase ESP32", "Start Serial Monitor", "Stop Monitor", and "Hardware Reset" controls.
- "/flasher" reported no horizontal overflow at 1280 px viewport width.
Firmware Lock/Edit Administration - 2026-07-07
Changes validated:
- Firmware records now have an "enabled" state stored in PostgreSQL.
- Existing firmware records were migrated as enabled by default through Flyway "V4__firmware_enabled.sql".
- "/admin/firmware" shows firmware status as "Enabled" or "Locked".
- Clicking a firmware name in "Available firmware" opens "/admin/firmware/{id}/edit".
- The edit screen allows updating name, version, application, compatible hardware, description, and enabled state without replacing stored binaries.
- "/admin/firmware" provides quick "Lock" / "Unlock" actions.
- Locked firmware is hidden from "GET /api/v1/firmware", so it is not offered by the Flasher Web catalog.
- Locked firmware download endpoints return an error instead of serving the binary/parts.
Functional validation:
- Temporary firmware upload returned HTTP 302 and appeared in "/api/v1/firmware" as enabled.
- Lock action returned HTTP 200.
- After locking, the temporary firmware no longer appeared in "/api/v1/firmware".
- Direct download of the locked firmware returned HTTP 400.
- Unlock action returned HTTP 200.
- Edit action returned HTTP 200 and changed metadata visible from "/api/v1/firmware/{id}".
- Delete action returned HTTP 200 and removed the temporary firmware.
Browser validation through "https://iot.aeizoon.com":
- "/admin/firmware" loaded CSS "app.css?v=phase2-20260707-2".
- Firmware names rendered as edit links.
- Lock buttons rendered for enabled firmware.
- "/admin/firmware/{id}/edit" rendered the metadata form and enabled checkbox.
- "/admin/firmware" and "/admin/firmware/{id}/edit" reported no horizontal overflow at 1280 px viewport width.
Firmware Part Download And Replacement - 2026-07-08
Changes validated:
- Multipart firmware edit pages now expose a "Download" action for each current firmware part.
- Multipart firmware edit pages now expose a per-part ".bin" replacement form.
- Admin part downloads use "/admin/firmware/{id}/parts/{partId}/download" and are intended for maintenance/review.
- Public/API part downloads remain protected by firmware enabled state.
- Replacing a part stores the new binary in filesystem, updates original filename, size, SHA-256, and recalculates the parent package size/checksum.
Functional validation:
- Temporary multipart firmware upload returned HTTP 302.
- Admin download of the temporary bootloader part returned the expected original bytes.
- Replacing that bootloader part returned HTTP 200.
- The part filename, size, and SHA-256 changed to match the replacement ".bin".
- The parent multipart package total size changed and package SHA-256 was recalculated.
- Temporary multipart firmware was deleted successfully and did not remain in "/api/v1/firmware".
Browser validation through "https://iot.aeizoon.com":
- "/admin/firmware/4/edit" loaded CSS "app.css?v=phase2-20260708-1".
- The edit page rendered 5 part rows, 5 "Download" links, 5 file inputs, and 5 "Replace" buttons.
- The page reported no horizontal overflow at 1280 px viewport width.
ESP32 Merged Image Assembler - 2026-07-12
Implementation validated:
- Added derived artifact storage for merged ESP32 images linked to the source multipart firmware.
- Added PostgreSQL migration "V5__firmware_derived_artifacts.sql" with merge parameters on "firmware" and derived artifact metadata in "firmware_derived_artifact".
- Installed and exposed official "esptool" as "/usr/local/bin/esptool" on the VM.
- Backend generation uses "esptool --chip <chip> merge-bin --flash-mode <mode> --flash-freq <freq> --flash-size <size> -o <output> <offset> <file> ...".
- Backend validates segment existence, offsets, overlap, flash-size range, stored file sizes, SHA-256 checksums, and supported esptool parameters before merge.
- Merged output is stored under "/srv/esp-platform/firmware/derived/firmware-<id>/".
- Generated artifacts record source firmware, filename, size, SHA-256, generated date, flash address "0x0", parameters, status, error message, and command line.
- Admin firmware edit page now shows merge parameters, "Generar imagen unificada", derived artifacts, download, regeneration by creating a new artifact, and delete-artifact-only actions.
- Flasher Web catalog now exposes generated "MERGED_BIN" artifacts as selectable flashable items, using address "0x0" without manual offsets.
- Existing multipart flashing remains available and unchanged.
Validation results:
- Initial generation with FreePocket forced to filesystem/spiffs offset "0x310000" and flash size "4MB" failed correctly with: "Segment spiffs exceeds configured flash size 4MB."
- The existing FreePocket binaries stored in ESP Platform have a final filesystem image of "1,441,792" bytes. At "0x310000" that image exceeds 4 MB; at the previously validated "0x290000" layout it fits exactly inside 4 MB.
- FreePocket package id "4" was kept on its effective validated layout to preserve existing multipart flashing behavior.
- Generated merged artifact for firmware id "4":
   - Artifact id: "2"
   - Filename: "firmware-4-merged.bin"
   - Size: "4,128,768" bytes
   - SHA-256: "3b0eb453795260d52c0ce7eae731f618ced9a2cc98720b6a32e5aab279d2a516"
   - Flash address: "0x0"
   - Parameters: "ESP32 / dio / 80m / 4MB"
- Download of "/api/v1/firmware/4/artifacts/2/download" returned HTTP 200 and SHA-256 matched PostgreSQL.
- A temporary multipart package was created to validate derived artifact deletion:
   - upload returned HTTP 302;
   - generation returned HTTP 302 and produced a "GENERATED" artifact;
   - deleting only the derived artifact returned HTTP 302 and left the firmware record intact;
   - deleting the temporary firmware then removed the source package.
- Service was restarted after Flyway repair/checksum alignment; Flyway validates 5 migrations and backend starts successfully.
Operational note:
- A requested layout of "0x310000 -> spiffs.bin" with "4MB" is only valid when the spiffs/filesystem image size is small enough to end at or before "0x400000". The backend now rejects invalid combinations instead of producing a misleading merged image.
Merged Image Published As Autonomous Firmware - 2026-07-12
Decision update:
- A merged image generated from a multipart package is now published as a normal "SIMPLE" firmware record.
- The generated firmware is operationally autonomous: it can be edited, locked/unlocked, downloaded and selected by the Flasher Web like any single-block ".bin" uploaded manually.
- The origin is preserved only as descriptive context and generation audit metadata; the generated firmware is not behaviorally dependent on the source multipart package.
Validated generated firmware:
- Firmware id: "16"
- Name: "FreePocket full install AP <redacted> - merged image"
- Type: "SIMPLE"
- Original filename: "firmware-4-merged.bin"
- Size: "4,128,768" bytes
- SHA-256: "3b0eb453795260d52c0ce7eae731f618ced9a2cc98720b6a32e5aab279d2a516"
- Download endpoint: "/api/v1/firmware/16/download"
- Source firmware dependency: none ("sourceFirmwareId = null")
Validation:
- "/admin/firmware" shows "FreePocket full install AP <redacted> - merged image" as a normal firmware row.
- "/admin/firmware/16/edit" is available.
- "/api/v1/firmware/16/download" returned HTTP 200.
- Downloaded file size and SHA-256 matched the database metadata.
- Backend logs after deployment showed no new errors or Flyway validation failures.
Artifact Library And Firmware Clone Safety - 2026-07-12
Implementation validated:
- Added independent "artifact" storage library backed by "/srv/esp-platform/artifacts".
- Added "firmware_segment" as the active firmware-to-binary relation table.
- Removed legacy "firmware_part" with migration "V9__drop_legacy_firmware_part_table.sql"; the active model is now "firmware", "firmware_segment", and "artifact".
- Each "firmware_segment" references one "artifact" through a PostgreSQL foreign key.
- Artifacts are identified by SHA-256 and reused when the same binary is uploaded again.
- Existing firmware binaries were consolidated into "/srv/esp-platform/artifacts/<sha-prefix>/<sha>.bin".
- Firmware deletion now removes the firmware record and its segment relations only; it does not delete physical artifacts directly.
- Orphan physical artifact deletion is performed only through the explicit orphan cleanup operation.
- Firmware records can share the same artifact path after migration "V8__allow_firmware_records_to_share_artifacts.sql" removed the legacy "firmware.stored_filename" uniqueness constraint.
Validation script:
- Script: "/opt/esp-platform/repo/scripts/validate-artifact-library.sh"
- Base URL used: "http://127.0.0.1:8080"
Validated flow:
- Created a temporary single-file firmware through "/admin/firmware".
- Verified metadata in "firmware", "firmware_segment", and "artifact".
- Cloned the firmware through "/admin/firmware/{id}/clone".
- Verified clone and original shared the same "artifact_id".
- Deleted the original firmware.
- Verified the clone still downloaded successfully from "/api/v1/firmware/{cloneId}/download" and SHA-256 matched.
- Deleted the clone.
- Verified the artifact became orphaned but still existed physically.
- Ran "/admin/artifacts/cleanup-orphans".
- Verified the orphan artifact row and physical ".bin" were removed.
Result:
- "Artifact library validation passed. Test firmware was cloned, original deleted, clone download verified, clone deleted, orphan artifact cleaned."
- The same validation script was re-run successfully after applying "V9__drop_legacy_firmware_part_table.sql".
FreePocket NVS Diagnostic Preparation - 2026-07-12
Prepared diagnostic workflow after LittleFS preconfiguration failed to change runtime Wi-Fi behavior.
Findings:
- FreePocket NVS partition is "nvs" at offset "0x9000", size "0x5000" / 20480 bytes.
- Partition table source artifact: "/srv/esp-platform/artifacts/14/148b959cbff1c38aa8e1d5c0ba9d612c54997b945e56a63f41223eef650653a1.bin".
- Official ESP-IDF NVS parser installed under "/opt/esp-platform/tools/esp-idf/components/nvs_flash/nvs_partition_tool/nvs_tool.py".
- Repo scripts prepared:
   - "/opt/esp-platform/repo/scripts/freepocket-nvs-info.sh"
   - "/opt/esp-platform/repo/scripts/analyze-nvs-dump.sh"
   - "/opt/esp-platform/repo/scripts/compare-nvs-dumps.sh"
- Diagnostic guide: "/opt/esp-platform/repo/docs/diagnostics/freepocket-nvs-diagnostics.md".
Next required physical data:
- NVS dump immediately after flashing and before manual configuration.
- NVS dump after manual configuration with SSID "Solax", Wi-Fi password "<redacted>", web user "admin", and web password <redacted>.
Flasher Web Read Flash Region - 2026-07-12
Implemented an advanced read-only flash dump function in "/flasher" for NVS diagnostics.
Validated by HTTP/HTTPS delivery:
- "/flasher" now contains advanced section "Read flash region".
- Preset "FreePocket NVS" sets offset "0x9000" and size "0x5000".
- Suggested filenames are available:
   - "freepocket-nvs-before.bin"
   - "freepocket-nvs-after-solax-admin.bin"
- The operation uses the current Web Serial/esptool-js connection and calls "readFlash(offset, size, progressCallback)".
- The downloaded artifact is produced locally in the browser as an "application/octet-stream" ".bin" file.
- The function is disabled until an ESP32 is connected and is disabled during flashing, erasing, and serial monitor use.
- Baudrate default remains "115200".
- No firmware, LittleFS, or NVS image generation was added or modified.
Deployment validation:
- Backend service active after deployment.
- "https://iot.aeizoon.com/flasher" serves JS build "phase2-20260712-2".
- Backend logs after restart show Flyway validation success and no runtime errors.
Physical validation still pending:
- Read "freepocket-nvs-before.bin" from the real ESP32 immediately after flashing.
- Configure the device manually for "Solax" / "<redacted>" and web user "admin" with password <redacted>.
- Read "freepocket-nvs-after-solax-admin.bin" from the same board.
- Upload both dumps to "/opt/esp-platform/repo/diagnostics/nvs/" and run "scripts/compare-nvs-dumps.sh".
Backups Stage 1 - Read-Only Capture - 2026-07-12
Implemented Stage 1 only:
- Navigation now includes Firmware, Flasher, Diagnostics and Backups.
- "/backups" exposes five backup sections, with only Stage 1 operations enabled:
   - Read complete flash;
   - Read partitions.
- Stage 2, Stage 3 and Stage 4 actions are visible but disabled pending approval.
- "/diagnostics" provides an entry point for current NVS diagnostic workflow.
Implemented read-only behavior:
- Backups page connects to ESP32 through Web Serial and esptool-js.
- It detects chip, revision, MAC, flash ID and flash size when available.
- Complete flash reads use offset "0x0" and detected flash size.
- Partition table reads use offset "0x8000" and size "0x1000".
- Partition parser interprets ESP-IDF partition entries: name, type, subtype, offset, size and flags.
- Synthetic readable regions are added for bootloader and partition table.
- Downloads are local browser ".bin" files plus metadata JSON with SHA-256 values.
- No backend upload, ZIP creation, restore, firmware import, firmware modification, LittleFS modification or NVS modification was implemented.
Static validation:
- Maven package completed successfully.
- Parser logic was checked against the FreePocket partition artifact and detected 6 real partitions:
   - "nvs" "0x9000" / "0x5000";
   - "otadata" "0xE000" / "0x2000";
   - "app0" "0x10000" / "0x140000";
   - "app1" "0x150000" / "0x140000";
   - "spiffs" "0x290000" / "0x160000";
   - "coredump" "0x3F0000" / "0x10000".
Physical validation still pending:
- Detect flash size of 4 MB from a real ESP32.
- Read full flash and verify exact size "4,194,304" bytes.
- Verify SHA-256 from metadata.
- Read partition table through Web Serial.
- Download at least one selected partition.
- Confirm the ESP32 is unchanged after read-only operations.
Backups Stage 1 UX Review and Stage 2 Package Base - 2026-07-12
Implemented and deployed:
- UI terminology changed to "Create complete device backup", "Analyze flash layout" and "Download selected regions".
- Partition table now includes Description and Classification.
- Badges classify regions as Reusable, Sensitive, Device state or Diagnostic.
- Safe defaults exclude NVS, OTA data, coredump and NVS keys.
- Sensitive/manual selections show a warning.
- "Cancel operation" is available during reads and uploads.
- Destination selector added: local download or save in ESP Platform.
- Backend backup model added: "device_backup", "backup_artifact", "backup_region", "backup_capture_session", "backup_audit_log".
- Server backups stored in "/srv/esp-platform/backups".
- Backup ZIP package creation implemented for saved server backups.
- Restore and import remain disabled.
Backend validation completed:
POST /api/v1/backups/capture-sessions
POST /api/v1/backups/capture-sessions/{id}/chunks
POST /api/v1/backups/capture-sessions/{id}/verify
POST /api/v1/backups/capture-sessions/{id}/complete
POST /api/v1/backups/{id}/package
GET  /api/v1/backups/{id}/package/download
DELETE /api/v1/backups/{id}
Results:
- Flyway migration V10 applied successfully.
- Backend service active through systemd.
- "/backups" served correctly through backend.
- "/api/v1/backups" served correctly through Nginx on port 88.
- Temporary 4-byte validation capture saved, packaged and deleted.
- ZIP content validated with "unzip -l":
   - "manifest.json"
   - "full-flash.bin"
   - "checksums.sha256"
- Partial capture discard validated: no temp file and no backup record remained.
Physical validation pending with ESP32 connected through browser:
1. Create a complete capture and save it in ESP Platform.
2. Cancel a capture and verify no partial file remains.
3. Download a complete local capture.
4. Verify size and SHA-256.
5. Select NVS manually and verify warning.
6. Save and discard a real temporary session.
7. Create ZIP from real backup and validate manifest/checksums.
8. Confirm the ESP32 is not modified by read operations.
Backups Physical Validation Attempt - 2026-07-14
Route used:
https://iot.aeizoon.com/backups
Browser build shown by the page:
stage1-stage2-20260712-2
Server-side real backup already present
A real ESP32 backup exists in ESP Platform and was validated from the VM:
- Backup id: "2"
- Name: "ESP32 2CBCBB75F274 backup 2026-07-13"
- Chip: "ESP32-D0WD (revision 1)"
- Chip revision: "1"
- MAC: "2CBCBB75F274"
- Flash ID: "16405e"
- Flash size: "4,194,304" bytes
- Stored file: "/srv/esp-platform/backups/device-backup-c403ea4c-51eb-4e65-9989-8f48e8df23bc/full-flash.bin"
- Stored size: "4,194,304" bytes
- SHA-256: "ca965603a5c02668e63721b34c05fba0dc2494e3050edcb36b1c621bcafe785a"
Checksum command result:
ca965603a5c02668e63721b34c05fba0dc2494e3050edcb36b1c621bcafe785a  full-flash.bin
Real ZIP package validation
Generated package:
/srv/esp-platform/backups/device-backup-c403ea4c-51eb-4e65-9989-8f48e8df23bc/esp-platform-backup-2.zip
ZIP artifact:
- Size: "773,498" bytes
- SHA-256: "a8f67972d6d8fa39cecb2a6032e4138de9942b0e17ec590f8ab13eead4cb4006e29767b0"
ZIP content:
manifest.json
full-flash.bin
checksums.sha256
"unzip -t" result:
No errors detected in compressed data
"checksums.sha256" content:
ca965603a5c02668e63721b34c05fba0dc2494e3050edcb36b1c621bcafe785a  full-flash.bin
74072f72c15855ecefd15e59497a09ac1c70d58796bed71b54b5141f31d16cf7  manifest.json
Session and partial-file state
After validation:
- "backup_capture_session": "0" open sessions.
- "/srv/esp-platform/backups/tmp": no partial capture files observed.
Browser/Web Serial attempt
Chrome was opened at "https://iot.aeizoon.com/backups".
First connect attempt:
No serial port was selected.
Second connect attempt:
Serial port WebSerial VendorID 0x403 ProductID 0x6001
ERROR: Could not connect to ESP32. Failed to execute open on SerialPort: Failed to open serial port.
Observed possible cause:
- Chrome showed several existing ESP Platform tabs, including "/flasher", "/diagnostics" and another "/backups" tab from previous work.
- The automation could not close those user tabs because Chrome denied that operation.
- The USB serial adapter was detected, but Chrome could not open it. This usually means another tab/application owns the port, the serial monitor is still active, or the USB adapter needs to be unplugged/replugged.
Pending physical checks after freeing the serial port:
1. Cancellation during a real read.
2. Local full dump download from browser.
3. Partition read and selected-region download.
4. Confirmation that the ESP32 continues booting normally after read-only operations.
Technical notes reviewed
Chunk size:
- Browser read/upload chunk size is "0x10000" bytes, i.e. "65,536" bytes.
Connection loss behavior:
- If Web Serial or esptool-js throws during a read/upload, the UI reports a readable error.
- For server captures, the current session is discarded in the error path when the session id is still active in the browser flow.
- Existing completed backups are not modified.
Retries:
- There is no automatic retry for failed flash read chunks or failed uploads.
- This is intentional for now because re-reading flash over Web Serial after an interruption may leave the port state ambiguous. Manual retry is required.
Duplicate detection:
- Backups currently record SHA-256 but do not prevent duplicate "device_backup" rows with the same full-flash checksum.
- Firmware artifacts have SHA-256 reuse logic; backup artifacts do not yet deduplicate storage.
Session timeout:
- Capture sessions are created with "expires_at = created_at + 6 hours".
- The timeout is recorded in PostgreSQL.
Automatic cleanup:
- Explicit discard removes temporary files.
- Successful complete moves the temp file into the backup directory and removes the temporary session directory.
- No scheduled automatic cleanup job for abandoned expired sessions exists yet.
- Operational cleanup should be added before relying on long unattended captures.
Backups Final Physical Validation Closure - 2026-07-14
Route used:
https://iot.aeizoon.com/backups
https://iot.aeizoon.com/flasher
Device:
- Chip: "ESP32-D0WD (revision 1)"
- Revision: "1"
- MAC: "2c:bc:bb:75:f2:74"
- Flash ID: "16405e"
- Flash size: "4MB" / "4,194,304" bytes
- Browser baudrate: "115200"
Complete local dump
Local dump downloaded successfully from the browser:
/Users/juan/Downloads/ESP32_2CBCBB75F274_full_20260713T222532Z.bin
Result:
- Size: "4,194,304" bytes
- SHA-256: "ed346457ca6b4be74719292c4761f7cdc39f7f3abdca6356e261d922c421e350"
Expected previous server backup SHA-256:
ca965603a5c02668e63721b34c05fba0dc2494e3050edcb36b1c621bcafe785a
The full-image SHA-256 does not match the previous server backup. Region comparison shows the difference is limited to NVS:
| Region | Result |
| --- | --- |
| bootloader | same |
| partition-table | same |
| nvs | different |
| otadata | same |
| app0 | same |
| app1 | same |
| spiffs | same |
| coredump | same |
Conclusion: firmware/application regions are unchanged. The complete dump differs because NVS changed between captures, which is expected for device state/configuration.
Partition download
Downloaded real partition:
/Users/juan/Downloads/ESP32_2CBCBB75F274_app0_0x10000_20260713T223331Z.bin
Result:
- Partition: "app0"
- Offset: "0x10000"
- Size: "1,310,720" bytes / "0x140000"
- SHA-256: "d79d4b3db73b490429122d64e076dedecf3c3a198497374ce3a8fa4b575fd948"
- Matches server backup app0 SHA-256: yes
Reset and boot confirmation
Reset method:
- Opened "/flasher".
- Connected to the same ESP32 at "115200".
- Started Serial Monitor.
- Pressed "Hardware Reset".
Observed serial boot output:
rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
mode:DIO, clock div:1
entry 0x400805f0
[general]     starting program (boot #0), version 0.3.5g (esp32)
[files]       reading config file
[io]          starting taskserial
[webif]       starting webserver
[wireless]    enabling AP: ssid:fp_75f274, password:****
[ethernet]    eth connected
[ethernet]    eth disconnected
Conclusion:
- ESP32 still boots after read-only backup operations.
- Application starts as before.
- Webserver starts.
- AP starts.
- Ethernet behavior remains the same observed device behavior and was not changed by Backups.
Final state
Server cleanup/state after validation:
- "backup_capture_session": "0" rows.
- "/srv/esp-platform/backups/tmp": no partial files.
- Persisted backup count: "1" real backup.
Validated requirements:
1. Complete 4 MB server capture: passed.
2. Server SHA-256 and exact size: passed.
3. Real ZIP creation/download/content validation: passed.
4. ZIP content: "manifest.json", "full-flash.bin", "checksums.sha256": passed.
5. Real cancellation during read: passed.
6. No partial files after cancellation: passed.
7. No orphan sessions: passed.
8. Local full dump download: passed, with expected NVS-only SHA drift from previous capture.
9. Real partition download: passed with "app0".
10. ESP32 unchanged and booting: passed.
Decision:
- Backups Stage 1 is closed and approved.
- Backups Stage 2 package creation is closed and approved.
- Restore backup package remains not implemented.
- Import firmware from connected ESP32 remains not implemented.
Backup Progress and Firmware Fingerprint - 2026-07-14
Implemented:
- Total progress bar for full-operation progress.
- Current-step progress bar for active block, selected region, upload chunk or flashing segment.
- Visual states: Waiting, Reading, Uploading, Processing, Completed, Cancelled, Failed.
- Backups UI no longer uses a chunk-local bar as the only progress indicator.
- Flasher UI uses the same dual-progress model for read flash region and multi-segment writes.
- Device Backup SHA-256 remains the full flash hash.
- Firmware Fingerprint SHA-256 excludes NVS, OTA data, coredump and diagnostic/device-specific regions.
- Firmware Fingerprint includes bootloader, partition table, app partitions and reusable filesystem regions.
- Saved backup rows show both hashes and repository match count.
Physical validation result: approved.
Date: 2026-07-14.
Environment:
- URL: "https://iot.aeizoon.com/backups"
- Build: "stage1-stage2-20260714-1"
- Device: ESP32-D0WD revision 1
- MAC: "2c:bc:bb:75:f2:74"
- Flash size: "4,194,304" bytes / 4 MB
- Baudrate: "115200"
Validated results:
1. Global progress remained continuous and monotonic during a complete 4 MB read.
2. Partial progress reset correctly by current block/region.
3. Complete local dump downloaded successfully.
4. Complete local dump size: "4,194,304" bytes.
5. Complete local dump SHA-256: "a9ea663ba7afc32f90e73bf25d4e535aeeb182e9285ec5e7c974e4cc67d58600".
6. Existing server backup SHA-256 remains: "ca965603a5c02668e63721b34c05fba0dc2494e3050edcb36b1c621bcafe785a".
7. SHA drift between complete dumps is accepted because mutable NVS/device state can change between captures.
8. Firmware Fingerprint SHA-256 is stored separately from Device Backup SHA-256.
9. Current Firmware Fingerprint SHA-256: "4ee0ccd14c1d85ed814cfe8067e6834bec7081a1bf5005768a087cd2e8e6283f".
10. Firmware Fingerprint size: "4,096,000" bytes.
11. Repository match count: "1".
12. Real "app0" partition downloaded successfully.
13. "app0" size: "1,310,720" bytes / "0x140000".
14. "app0" SHA-256: "d79d4b3db73b490429122d64e076dedecf3c3a198497374ce3a8fa4b575fd948".
15. Metadata JSON for local single-file captures now exposes top-level "sha256" and "sizeBytes", while preserving "files[]".
16. ESP32 reset was validated manually after read operations.
17. Firmware boot was validated manually after read operations.
18. Read-only complete dump and partition download did not alter the device.
Decision:
- Progress global continuo: approved.
- Progress parcial por bloque/region: approved.
- Complete 4 MB local download: approved.
- "app0" partition download: approved.
- Metadata JSON correction: approved.
- Firmware Fingerprint separated from full dump SHA: approved.
- ESP32 functional integrity after read-only operations: approved.
- Restore backup package remains not implemented.
- Import firmware from connected ESP32 remains not implemented.
Restore Backup Package - 2026-07-14
Status: implemented, software validated and physically validated on ESP32 test device.
Implemented:
- Restore from saved ESP Platform backup.
- Restore from uploaded "esp-platform-backup.zip".
- Backend package validation before restore options are shown.
- Complete restore from "full-flash.bin" at "0x0".
- Selective restore by region.
- Safe defaults excluding NVS, OTA data, coredump and sensitive/diagnostic/device-state regions.
- Explicit "RESTORE" confirmation.
- Optional safety backup before writing.
- Optional post-write verification by reading back written bytes.
- Best-effort Secure Boot and Flash Encryption detection warnings.
- Restore audit through "backup_restore_session" and "backup_audit_log".
Software validation completed:
1. Saved backup "#2" validates as restorable.
2. Restore validation returns ESP32 family, 4 MB flash size and 8 regions.
3. Safe defaults are "bootloader", "partition-table", "app0", "app1", "spiffs".
4. Backend restore-session download returns "4,194,304" bytes.
5. Backend restore-session download SHA-256 is "ca965603a5c02668e63721b34c05fba0dc2494e3050edcb36b1c621bcafe785a".
6. Real "esp-platform-backup-2.zip" upload validates successfully.
7. Corrupted checksum ZIP is rejected with HTTP 400 and message "Restore package validation failed: Checksum mismatch for full-flash.bin."
8. Restore UI loads without JavaScript console errors.
9. Restore UI renders package summary, compatibility report and region table.
Physical validation completed on 2026-07-14:
1. Complete restore from saved backup "#2": passed.
   - Target: ESP32-D0WD revision 1, MAC "2c:bc:bb:75:f2:74", Flash ID "16405e", detected flash size "4,194,304" bytes / 4 MB.
   - Safety backup was enabled and downloaded before writing.
   - Safety backup file: "ESP32_2CBCBB75F274_safety_before_restore_20260714T000935Z.bin".
   - Safety backup size: "4,194,304" bytes.
   - Safety backup SHA-256: "cd3517473707d59c3d915b52a3e16213cadce80d9ffb2b4371958fb7acb51a08".
   - Restore wrote "4,194,304" bytes from offset "0x0".
   - Restore source SHA-256: "ca965603a5c02668e63721b34c05fba0dc2494e3050edcb36b1c621bcafe785a".
   - Post-write verification read back "full-flash.bin" and matched SHA-256 "ca965603a5c02668e63721b34c05fba0dc2494e3050edcb36b1c621bcafe785a".
   - RTS hard reset was requested after restore.
2. Selective "app0" restore: passed.
   - Only "app0" was selected.
   - NVS, OTA data and filesystem were not selected.
   - "app0" write offset: "0x10000".
   - "app0" size: "1,310,720" bytes.
   - "app0" SHA-256 verified after write: "d79d4b3db73b490429122d64e076dedecf3c3a198497374ce3a8fa4b575fd948".
   - NVS before SHA-256: "83db022f60712229ae24ca0412897611d656ea5c66102d3f87d5eb3014619172".
   - NVS after SHA-256: "83db022f60712229ae24ca0412897611d656ea5c66102d3f87d5eb3014619172".
   - Result: NVS remained unchanged.
3. Cancellation during write: passed.
   - A complete restore was started with erase enabled.
   - Cancel was requested during the writing phase after erase and after writing had begun.
   - UI result: "Restore cancelled; device state may be incomplete."
   - No success state was shown for the cancelled operation.
   - Serial port was released and the device could be reconnected.
   - Audit event recorded: "RESTORE_CANCELLED" at "2026-07-14 00:46:44 UTC".
   - Device was recovered afterwards using a complete restore with verification.
4. Incompatibility blocking: passed.
   - Controlled package with valid checksums but invalid region metadata was rejected before writing.
   - Backend response: "Restore package validation failed: Region bootloader exceeds flash size.; Region bootloader overlaps coredump."
   - No restore execution session was allowed for that package.
5. Altered checksum ZIP: passed at restore validation boundary.
   - Controlled ZIP with altered "checksums.sha256" was rejected before writing.
   - Backend response: "Restore package validation failed: Checksum mismatch for full-flash.bin."
   - The package does not reach executable restore state.
   - Browser automation could not attach the ZIP through Chrome native file chooser because Chrome returned "Not allowed"; the same uploaded-package validation path was verified through the backend endpoint and had previously been validated with a good ZIP from the UI.
Operational observations:
- "Execute restore" is enabled only when an ESP32 is connected, a restore package is validated, compatibility is not "incompatible", and the confirmation field contains exactly "RESTORE".
- Selecting all partitions does not disable execution by itself; selecting NVS, OTA data or coredump triggers explicit warnings because those regions are device-specific or diagnostic.
- Historical restore events are tracked in "backup_audit_log"; repeated executions from the same validated package may update the same "backup_restore_session" row.
Decision:
- Restore backup package is physically validated and approved for MVP Flasher/Backups closure.
- Compare with repository remains postponed.
- Import firmware from connected ESP32 remains postponed.
- OTA, Provisioning, Fleet Management and Edge OS remain out of scope for this phase.