================================================================
 PoseLab Core (adv) - Arduino IDE Build Guide
================================================================

Updated : 2026-08-31

Target board : ESP32-S3-WROOM-1 N16R8 (16MB flash, 8MB OPI PSRAM)
USB-UART     : CH343P (CDC, driverless) - enumerates as a COM port
Transport    : UART0 @ 2,000,000 baud, 50fps, 96-byte AnalyzedFrame packet
               (86 bytes + a 10-byte on-device inference block)

----------------------------------------------------------------
 1. Prerequisites
----------------------------------------------------------------
- Arduino IDE 2.x
- Boards Manager: "esp32 by Espressif Systems" (verified with arduino-esp32 core 3.3.11)
    Tools > Board > Boards Manager, search "esp32" and install
- Libraries (install via Library Manager):
    * Adafruit NeoPixel        (v1.15.x)
    * ArduinoJson              (v7.x)     <- must be v7, NOT v6
    * Chirale_TensorFLowLite   (v2.0.0)   <- on-device inference (TFLite Micro)
    * NimBLE-Arduino           (v2.x)     <- must be v2, NOT v1. BLE output

  NOTE on the TensorFlow library: it must be Chirale_TensorFLowLite.
  The older TensorFlowLite_ESP32 (tanakamasayuki, 2022) does NOT compile under
  arduino-esp32 core 3.x - its bundled flatbuffers assigns to a const member,
  which the C++17 that core 3.x defaults to rejects outright.

----------------------------------------------------------------
 2. Opening the sketch
----------------------------------------------------------------
- Open PoseLabCore-adv/PoseLabCore-adv.ino in the IDE.
- The sources live in a tree under src/ (board/ sense/ calib/ proto/ tinyml/
  model/ ...). Only the .ino appears in the IDE tab bar, but everything under
  src/ is compiled along with it automatically.
  (To edit the sub-files, open them from the Explorer sidebar on the left.)

----------------------------------------------------------------
 3. Tools menu settings (* must match exactly)
----------------------------------------------------------------
  Board                : ESP32S3 Dev Module
  Port                 : (the COM port the board enumerated on)
  USB CDC On Boot      : Disabled          <- important. If Enabled, no data comes out
  CPU Frequency        : 240MHz (WiFi)
  Core Debug Level     : None
  Flash Mode           : QIO 80MHz
  Flash Size           : 16MB (128Mb)      <- not 4MB
  PSRAM                : OPI PSRAM         <- if Disabled, LED/PSRAM detection fails
  Partition Scheme     : Custom            <- * must be Custom (uses the sketch's partitions.csv)
  Upload Mode          : UART0 / Hardware CDC
  Upload Speed         : 921600
  USB Mode             : Hardware CDC and JTAG
  (leave every other item at its default)

  NOTE: Partition Scheme must be set to "Custom" so the sketch folder's
        partitions.csv is applied. It carries two partitions this firmware
        needs: "fit" (calibration values) and "models" (an uploaded model).
        Without Custom, streaming still works, but calibration is not
        persisted and no model can be stored.

  NOTE: PSRAM must be OPI. A model upload is buffered in PSRAM before being
        written to flash, so with PSRAM disabled the transfer fails.

----------------------------------------------------------------
 4. Upload & verify
----------------------------------------------------------------
- After uploading, open the Serial Monitor at baud 2000000.
- Example boot banner:
    ==== PoseLab Core ====
    Chip       : ESP32-S3 ... Flash 16MB, PSRAM 8189KB   <- PSRAM detected
    [model] SPIFFS 0/1920401 B used, model none          <- model partition mounted
    [tinyml] using embedded model (28060 B)
    [tinyml] selftest 5/5                                <- inference verified
    [tinyml] invoke 0.337 ms avg over 200 runs
    FIT calib loaded: USER params                        <- calibration persisted
    PUMP- ENTER ...                                      <- streaming started
- On boot the WS2812C LED runs a rainbow sequence, then holds amber = normal.

  The selftest runs the model against reference vectors computed on a PC and
  prints how many matched. 5/5 means inference is working correctly. It runs
  only for the built-in fallback model - an uploaded model has no reference
  vectors to compare against, so it is skipped.

----------------------------------------------------------------
 5. On-device inference
----------------------------------------------------------------
- Each 96-byte frame carries the device's own classification result in its
  last 10 bytes before the tail: an app/model id, and the top 3 labels with
  confidences.
- Labels travel as indices, not strings. The label text lives in the model
  file, so the host looks it up - re-sending the same string 50 times a second
  would just retransmit static metadata.
- The firmware ships with a built-in model as a fallback. To load your own,
  train one in the browser (the MLP lab page), convert it, and send it to the
  device from that same page. The conversion step runs in a Colab notebook;
  the lab page links to the instructions.
- A newly uploaded model takes effect after a reboot - the interpreter is
  built once at startup.

----------------------------------------------------------------
 6. Notes
----------------------------------------------------------------
- To build with PlatformIO instead, use the PoseLabCore-pio package
  (its entry point is src/main.cpp). The two packages are generated from one
  source; they differ only in the src/ include prefix and .ino vs .cpp.
  Runtime behaviour is identical.
- timer_50fps.cpp contains version guards so it builds on
  arduino-esp32 core 2.x and 3.x.
- Support: support@poselab.app
