All notable changes to the Zebrabox ESP32 Locker firmware are documented in this file.
This project follows Semantic Versioning and the format is based on Keep a Changelog.
Author: Thomas Joss, keynexis AG
_boot.py, inisetup.py, flashbdev.py, and file_handler.py. Devices running firmware 1.6.1 (which uses LFS2) previously failed after an OTA update because the 1.6.5 firmware attempted to mount the existing LFS2 partition as FAT, causing a full reformat and loss of all user data (wifi.cfg, TLS certificates).Partition.mark_app_valid_cancel_rollback() in _boot.py to confirm the running firmware as valid after each boot. Without this call, the ESP-IDF bootloader treated OTA-updated firmware as unverified and reverted to the previous version on the next reset.block_size=512 parameter from flashbdev.py when locating the vfs partition. LFS2 requires the default flash sector size (4096 bytes); the incorrect block size caused mount failures._boot.py now removes boot.py and main.py from the writable filesystem on every boot, ensuring that only the frozen (firmware-embedded) versions execute. This guarantees OTA updates always apply the latest code.ensure_filesystem(): Replaced format_filesystem() with ensure_filesystem() in file_handler.py. The new function mounts the existing filesystem without reformatting and ensures the cert/ directory exists. All user data is preserved across reboots.timeout=30 to the OTA firmware download request, preventing indefinite hangs on network issues.gc.collect() every 50 blocks during OTA partition writes to prevent heap fragmentation and MemoryError on the ESP32-C3 (320 KB RAM).ota_upgrade.py (block_id > SEC_COUNT changed to block_id >= SEC_COUNT) to prevent writing beyond the partition boundary.try/except/finally with a guaranteed machine.reset(), ensuring the device always reboots even if the download fails.while True loop when writing ota_upgrade.txt with a bounded for _attempt in range(3) to prevent the device from hanging.chunk = bytearray(CHUNK_SIZE) in write_to_partition() that was immediately overwritten, reducing unnecessary memory pressure.connect_and_subscribe() is now an async function, allowing proper await restart_and_reconnect() on connection failure instead of nesting uasyncio.run().