AirMD+ firmware 3.6.8 - classic ESP32-WROOM, 4 MB flash
======================================================

This is the latest firmware source release fetched from HatchingPoint/Point-Marketing
main on 2026-10-13 and built with the repository's pinned PlatformIO environment.
It fixes an offline-reading-buffer crash when the ring reaches 2,048 readings and
safely migrates the older queue after an OTA image passes its health gate.

Official downloads
------------------
Support and installation guidance: https://www.hatchingpoint.com/airmd/support
Complete package: https://www.hatchingpoint.com/downloads/airmd-plus-3.6.8.zip
App-only binary: https://www.hatchingpoint.com/downloads/airmd-plus-3.6.8.bin
App-only SHA-256: https://www.hatchingpoint.com/downloads/airmd-plus-3.6.8.sha256
The web app-only download has the same bytes as firmware.bin in this ZIP.

Package contents
----------------
firmware.bin       AirMD+ app image (1,929,552 bytes), version marker 3.6.8.
bootloader.bin     Matching classic ESP32 bootloader (17,568 bytes).
partitions.bin     Matching min_spiffs partition table (3,072 bytes).
boot_app0.bin      Arduino initial OTA selector data (8,192 bytes).
partition-table.csv  Human-readable copy of that exact framework partition table.
SHA256SUMS.txt     SHA-256 checksums for every other package file.
README.txt        This installation and provenance handoff.

firmware.bin is an application image, not a complete board-flash dump. It is not
suitable for ESP32-S2/S3/C3 or other non-classic ESP32 chips. Both executable images
were verified as classic ESP32, 4 MB, DIO, 80 MHz with valid image hashes/checksums.

Already commissioned monitors
-----------------------------
Contact AirMD+ support for a compatible update/recovery procedure. Do not run the
blank-board command below on an existing monitor, erase flash, or replace its
bootloader, partition table, OTA selector data, or NVS. Preserve device identity,
setup material, Wi-Fi, queued readings, and any diagnostic coredump. The active OTA
slot and boot-selection state must be resolved before a serial recovery write;
0x10000 is not a universal existing-device update address. The supported signed OTA
flow selects the inactive slot, authenticates the manifest and artifact, and
validates the new image before retaining it. Hosting these downloads does not
activate a signed OTA rollout. Legacy/partial/corrupt identities require the
reviewed support/factory migration path rather than identity regeneration.

First install: genuinely blank development/DIY boards only
--------------------------------------------------------
Use this section only for an uncommissioned, unprotected classic ESP32-WROOM board
with physically verified 4 MB flash. This is the repository's development/DIY
configuration. Secure Boot, Flash Encryption, encrypted NVS and manufacturing
qualification are separate production gates; this package is not a protected
manufacturing image. Do not modify eFuses or disable existing device protection.

1. Extract the ZIP and open a terminal in the AirMDPlus-firmware-3.6.8 folder.
2. Use Python with esptool 4.5.1 (the version used by the pinned build). Replace PORT
   below with the board's actual USB serial port. Confirm flash_id reports 4 MB;
   that check does not establish that a used board is blank.

   python -m esptool --chip esp32 --port PORT --baud 115200 flash_id

3. Only for the verified blank board, install all four images at these offsets:

   python -m esptool --chip esp32 --port PORT --baud 115200 --before default_reset --after hard_reset write_flash --flash_mode keep --flash_freq keep --flash_size keep 0x1000 bootloader.bin 0x8000 partitions.bin 0xe000 boot_app0.bin 0x10000 firmware.bin

   The keep options preserve the verified image headers and published hashes.
   No whole-flash erase is part of this procedure. esptool verifies written data.
4. Use the compatible AirMD+ app's nearby setup flow. A truly blank board generates
   one checksummed DIY identity on first boot. Follow the app's BOOT-button prompt:
   hold for at least 2 seconds and release before 5 seconds. A release after
   5 seconds clears Wi-Fi and restarts. No factory enrollment is created by flashing.

Layout (4 MB min_spiffs)
-----------------------
NVS       0x009000 / 0x005000
OTA data  0x00e000 / 0x002000
OTA app0  0x010000 / 0x1e0000
OTA app1  0x1f0000 / 0x1e0000
LittleFS  0x3d0000 / 0x020000 (table label spiffs)
Coredump  0x3f0000 / 0x010000
Each app slot is 1,966,080 bytes. This app fits both with 36,528 bytes spare.

Verify package integrity
------------------------
On macOS/Linux, from the extracted folder:

   shasum -a 256 -c SHA256SUMS.txt

firmware.bin SHA-256:
0868ba9f402b0802fc238dad0574767f8a0d523c61118eccd231c97082883493

Source and validation provenance
--------------------------------
Repository: https://github.com/HatchingPoint/Point-Marketing
Firmware source: apps/AirMDPlus/esp32
Full fetched/pulled source revision:
01b316122dc71dfec320edb434bc0bfbb9d6bb24
Last firmware release commit:
4aaec3d5d4e01f919c1005cfd4ae86b37f42f5d9 (2026-10-03, firmware 3.6.8)

Build: pio run, esp32dev release environment; PlatformIO Core 6.1.18.
Pinned platform: espressif32 6.11.0.
Framework: Arduino-ESP32 2.0.17, package 3.20017.241212, ESP-IDF 4.4.7.
Compiler: Xtensa ESP32 GCC 8.4.0+2021r2-patch5.
Libraries: OneWire 2.3.8; DallasTemperature 3.11.0; ArduinoJson 6.21.6;
AsyncTCP 3.5.0; ESPAsyncWebServer 3.12.0.
Host validation: ./scripts/run_host_tests.sh passed 8 Bun tests / 23 assertions
and 128 Python tests, including native production queue fault-injection tests.
Test runtimes: Bun 1.3.14; Python 3.12 supplied by uv.
Binary checks: esptool 4.5.1 image_info --version 2 passed for app and bootloader;
partition-table MD5/layout and both OTA-slot size bounds verified.
The AirMD version is the embedded AIRMD-FIRMWARE-VERSION:3.6.8 marker; the standard
ESP image app descriptor contains framework metadata rather than the AirMD version.
Build firmware.elf SHA-256 (retained in the source build folder, omitted from ZIP):
b4bab3f7cba6233ea74955903743032d367b41e5fa02bfb4c7ffcd7763c227a5

This packaging run did not flash a board, modify cloud services, enable OTA rollout,
or perform hardware/soak qualification. The package contains no per-device NVS,
identity/QR material, private signing keys, device backups, or cloud credentials.
