SerialOTA host uploader (sxota) 1.0.4
=====================================

Sends an ESP32 firmware .bin over the serial port using the SerialOTA
protocol. The board must already be running firmware that calls
SerialOTA.handle() in loop().

This is not esptool / ROM download mode. Do not hold BOOT or enter
the serial bootloader.

Usage
-----
  Windows:     sxota.exe PORT FIRMWARE.bin [options]
  macOS/Linux: ./sxota PORT FIRMWARE.bin [options]

Basic options
-------------
  PORT              Serial port
                    Windows:  COM5
                    Linux:    /dev/ttyUSB0  or  /dev/ttyACM0
                    macOS:    /dev/cu.usbserial-*  or  /dev/cu.usbmodem*

  FIRMWARE.bin      Path to the firmware image to send

  --baud N          Baud rate (default: 115200).
                    Must match Serial.begin() / SerialOTA.begin() on the device.

  --timeout N       I/O timeout in seconds (default: 15, 30 with
                    --secondary)

  --sync-retries N  Handshake attempts before giving up (default: 40)

  --secondary       OTA through a first ESP32 hop. Sends pass_ab_prog,
                    waits 200 ms, then serialota. Uses 512-byte
                    chunks and a 30 s ACK timeout. Does not pace
                    writes (a large RX buffer on both chips is
                    enough).

  --chunk-size N    DATA payload bytes (default 1024, 512 with
                    --secondary). Max 1024.

  --burst N         Optional paced USB bursts. Default is off.
                    Do not enable this if the first ESP32 treats a
                    pause as end-of-frame.

  --list            List serial ports and exit

  --version         Print sxota version

  -h, --help        Built-in command help

Examples
--------
  List ports
    sxota --list
    sxota.exe --list

  Upload at the default 115200 baud
    sxota.exe COM5 firmware.bin
    ./sxota /dev/ttyUSB0 firmware.bin
    ./sxota /dev/cu.usbserial-10 firmware.bin

  Faster transfer (set the same baud in the sketch first)
    sxota.exe COM5 firmware.bin --baud 921600

  Secondary / hop through a first ESP32
    sxota.exe COM5 firmware.bin --secondary
    sxota.exe COM5 firmware.bin --secondary --chunk-size 256 --timeout 45

Before you start
----------------
  1. Flash SerialOTA firmware once with the normal USB / esptool path.
  2. Close any serial monitor or other program using the port.
  3. Use the same baud rate on the host and the device.
  4. Keep SerialOTA.handle() running in loop(). While a transfer is
     active, do not print on that serial port.

After a successful upload the device reboots into the new OTA slot.

Platform notes
--------------
  Windows
    Use Device Manager or "sxota.exe --list" to find the COM port.

  Linux
    If opening the port fails with "Permission denied", add your user
    to the dialout group, then log out and back in:
      sudo usermod -aG dialout $USER
    If the zip extracted without execute permission:
      chmod +x sxota python/bin/python3

  macOS
    Use the /dev/cu.* device, not /dev/tty.*.
    If macOS blocks the tool after download:
      xattr -d com.apple.quarantine sxota
      xattr -dr com.apple.quarantine python
    Apple Silicon: use the macos-arm64 package.
    Intel Mac: use the macos-x64 package.

Common errors
-------------
  could not sync
    Firmware is not calling SerialOTA.handle(), the port is wrong,
    or another program owns the port.

  BEGIN / WRITE / END
    The ESP32 Update API rejected the image (size, partition table,
    or flash write).

  MD5
    The image was corrupted in transit or is not the file you meant.

  CRC / SEQ
    Serial noise or a baud mismatch. Try a lower baud or a shorter
    cable.

  TIMEOUT / timed out waiting for device response
    No ACK came back through the hop. Call setRxBufferSize(2048)
    before Serial.begin() on both chips, keep --burst 0, and
    retry. If it still dies, try --timeout 45 --chunk-size 256.

  UNKNOWN (n)  where n is not 1-10
    Not a real SerialOTA error. Usually a garbled ACK through a
    UART hop. Enlarge both RX buffers and use --secondary.
