STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
ota_upload Namespace Reference

Classes

class  OtaError
class  ConnectError

Functions

 print_banner ()
str find_pio_command ()
str compile_firmware (str message)
 upload_firmware (str bin_path, str ip, int port)
 main ()
bytes parse_mac (str mac)
str recovery_address (str mac)
tuple board_from_config (str name, Path config)
list candidate_addresses (Optional[str] ip, Optional[str] mac)
str firmware_sha256 (bytes data)
Optional[str] board_reported_sha256 (bytes data)
str upload (bytes data, str ip, int port=DEFAULT_PORT, Optional[ProgressFn] on_progress=None, float connect_timeout=CONNECT_TIMEOUT, float transfer_timeout=TRANSFER_TIMEOUT)
str upload_to_first (bytes data, list hosts, int port=DEFAULT_PORT, **kwargs)
str find_pio ()
Path build (Path project, Optional[str] env=None, Optional[str] message=None)
ProgressFn _progress_printer ()
int main (argv=None)
None _self_test ()

Variables

str DEFAULT_IP = "192.168.2.5"
int DEFAULT_PORT = 3232
int CHUNK_SIZE = 4096
int CONNECT_TIMEOUT = 5
int TRANSFER_TIMEOUT = 30
 SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
 PROJECT_DIR = os.path.join(SCRIPT_DIR, "OTA_Test_Firmware")
 PIO_BUILD_DIR = os.path.join(PROJECT_DIR, ".pio", "build", "adafruit_feather_esp32s3")
 FIRMWARE_BIN = os.path.join(PIO_BUILD_DIR, "firmware.bin")
int MAX_IMAGE_BYTES = 0x200000
 ProgressFn = Callable[[int, int], None]
 REPO_ROOT = Path(__file__).resolve().parents[2]
str DEFAULT_CONFIG = REPO_ROOT / "daq-server" / "config" / "config.toml"

Detailed Description

ESP32 Ethernet OTA Upload Script
=================================
Recompiles the OTA_Test_Firmware with a custom serial message,
then pushes the resulting binary to the ESP32 over TCP (W5500 Ethernet).

Usage:
    python ota_upload.py                          # Uses timestamped default message
    python ota_upload.py --message "Hello world"  # Custom message
    python ota_upload.py --ip 192.168.2.10        # Override target IP

Protocol:
    1. Send 4-byte firmware size (big-endian)
    2. Send raw firmware binary in 4 KB chunks
Push firmware to a STAR board over Ethernet.

Board-agnostic: anything running ``libraries/STAR_EthernetOTA`` speaks this
protocol, which is all of them (sense boards, actuator, stacklight, encoder,
environmental tracker).

    # upload a binary you already built
    python firmware/tools/ota_upload.py --ip 192.168.2.41 \
        --bin firmware/Hotfire_Code/LC_Hotfire/.pio/build/adafruit_feather_esp32s3/firmware.bin

    # build the project first, then upload it
    python firmware/tools/ota_upload.py --ip 192.168.2.41 \
        --project firmware/Hotfire_Code/LC_Hotfire

    # by MAC or by config name: tries the board's DHCP address, then its
    # recovery address 169.254.x.y (a board whose DHCP failed sits there)
    python firmware/tools/ota_upload.py --mac 4a:27:e2:3a:ad:71 --bin rtd.bin
    python firmware/tools/ota_upload.py --board rtd_board --bin rtd.bin

    # bake a marker into the build so you can SEE the update land on serial:
    # the board starts printing "[OTA-MSG] third try" instead of whatever it
    # printed before
    python firmware/tools/ota_upload.py --ip 192.168.2.41 \
        --project firmware/Hotfire_Code/LC_Hotfire --message "third try"

Wire protocol (see libraries/STAR_EthernetOTA/src/STAR_EthernetOTA.h):

    client -> board : [4-byte big-endian image size][raw firmware bytes]
    board  -> client: "OK\\r\\n", then reboot

Two different SHA-256 values are involved, and confusing them makes a good
update look like a failed one:

* ``sha256`` of the .bin file — what you get from ``shasum -a 256``.
* the **appended image hash**, the last 32 bytes of the .bin, which is what
  ``esp_partition_get_sha256()`` returns and therefore what the board prints
  at boot and reports in BOARD_HEARTBEAT (see
  ``Hotfire_Code/common/FIRMWARE_HASH_VERIFICATION.md``).

They are never equal. This tool prints both and labels which one to compare
against the board.

Pure standard library. ``python -m ota_upload --self-test`` runs an offline
round trip against a fake board.

Function Documentation

◆ _progress_printer()

ProgressFn ota_upload._progress_printer ( )
protected

Definition at line 321 of file ota_upload.py.

References time.time().

Referenced by main().

◆ _self_test()

None ota_upload._self_test ( )
protected
Round-trip against a fake board implementing the same wire protocol.

Definition at line 437 of file ota_upload.py.

References candidate_addresses(), firmware_sha256(), main(), min, recovery_address(), upload(), and upload_to_first().

Referenced by main().

◆ board_from_config()

tuple ota_upload.board_from_config ( str name,
Path config )
(mac, ip) of [boards.<name>] in a daq-server config.toml.

Definition at line 107 of file ota_upload.py.

Referenced by main().

◆ board_reported_sha256()

Optional[str] ota_upload.board_reported_sha256 ( bytes data)
The digest the board prints for this image, or None if it has none.

The board uses esp_partition_get_sha256(), which returns the 32-byte
SHA-256 that esptool appends to the image -- computed over the image
*excluding* those 32 bytes. Hashing the whole .bin gives a different
number that will never match, which makes a perfectly good update look
like a failed one. The appended hash is present when byte 23 of the
ESP32 image header (magic 0xE9) has the hash_appended flag set.

Definition at line 146 of file ota_upload.py.

Referenced by main().

◆ build()

Path ota_upload.build ( Path project,
Optional[str] env = None,
Optional[str] message = None )
Build `project` and return the path to firmware.bin.

`message` is injected as STAR_OTA_TEST_MESSAGE through
PLATFORMIO_BUILD_FLAGS, so nothing in the repo has to be edited to make
one build visibly different from the last.

Definition at line 271 of file ota_upload.py.

References find_pio().

Referenced by main().

◆ candidate_addresses()

list ota_upload.candidate_addresses ( Optional[str] ip,
Optional[str] mac )
Addresses to try, in order: the DHCP one, then recovery.

Definition at line 129 of file ota_upload.py.

References recovery_address().

Referenced by _self_test(), and main().

◆ compile_firmware()

str ota_upload.compile_firmware ( str message)
Compile the firmware with the given OTA_MESSAGE baked in.

Returns the path to the compiled .bin file.

Definition at line 81 of file ota_upload.py.

References find_pio_command(), and split().

Referenced by main().

◆ find_pio()

str ota_upload.find_pio ( )
Locate the PlatformIO CLI, including its default install locations.

Definition at line 253 of file ota_upload.py.

Referenced by build().

◆ find_pio_command()

str ota_upload.find_pio_command ( )
Try to find the 'pio' command in PATH or common installation locations.

Definition at line 49 of file ota_upload.py.

Referenced by compile_firmware().

◆ firmware_sha256()

str ota_upload.firmware_sha256 ( bytes data)
SHA-256 of the .bin file itself. NOT what the board reports.

Definition at line 141 of file ota_upload.py.

Referenced by _self_test(), and main().

◆ main() [1/2]

ota_upload.main ( void )

Definition at line 230 of file ota_upload.py.

References compile_firmware(), main(), print_banner(), and upload_firmware().

◆ main() [2/2]

◆ parse_mac()

bytes ota_upload.parse_mac ( str mac)

Definition at line 88 of file ota_upload.py.

References split().

Referenced by recovery_address().

◆ print_banner()

ota_upload.print_banner ( )

Definition at line 42 of file ota_upload.py.

Referenced by main().

◆ recovery_address()

str ota_upload.recovery_address ( str mac)
Where a board sits when no DHCP server answered it:
169.254.<1 + mac[4] % 254>.<mac[5]>. Must match BoardNet::recoveryAddress
(firmware/Hotfire_Code/common/board_net.h) and daq-server's config check.

Definition at line 99 of file ota_upload.py.

References parse_mac().

Referenced by _self_test(), and candidate_addresses().

◆ upload()

str ota_upload.upload ( bytes data,
str ip,
int port = DEFAULT_PORT,
Optional[ProgressFn] on_progress = None,
float connect_timeout = CONNECT_TIMEOUT,
float transfer_timeout = TRANSFER_TIMEOUT )
Send `data` to the board and wait for its acknowledgement.

Returns the board's reply text. Raises OtaError with something actionable
on any failure.

Definition at line 161 of file ota_upload.py.

References decode().

Referenced by _self_test(), and upload_to_first().

◆ upload_firmware()

ota_upload.upload_firmware ( str bin_path,
str ip,
int port )
Send the firmware binary to the ESP32 over TCP.

Definition at line 142 of file ota_upload.py.

References decode(), and time.time().

Referenced by main().

◆ upload_to_first()

str ota_upload.upload_to_first ( bytes data,
list hosts,
int port = DEFAULT_PORT,
** kwargs )
Upload to the first of `hosts` that accepts a connection. Moves on only
when nothing answered; a board that answered and then failed is an error.

Definition at line 227 of file ota_upload.py.

References upload().

Referenced by _self_test(), and main().

Variable Documentation

◆ CHUNK_SIZE

int ota_upload.CHUNK_SIZE = 4096

Definition at line 31 of file ota_upload.py.

◆ CONNECT_TIMEOUT

float ota_upload.CONNECT_TIMEOUT = 5

Definition at line 32 of file ota_upload.py.

◆ DEFAULT_CONFIG

str ota_upload.DEFAULT_CONFIG = REPO_ROOT / "daq-server" / "config" / "config.toml"

Definition at line 85 of file ota_upload.py.

◆ DEFAULT_IP

str ota_upload.DEFAULT_IP = "192.168.2.5"

Definition at line 29 of file ota_upload.py.

◆ DEFAULT_PORT

int ota_upload.DEFAULT_PORT = 3232

Definition at line 30 of file ota_upload.py.

◆ FIRMWARE_BIN

ota_upload.FIRMWARE_BIN = os.path.join(PIO_BUILD_DIR, "firmware.bin")

Definition at line 39 of file ota_upload.py.

◆ MAX_IMAGE_BYTES

int ota_upload.MAX_IMAGE_BYTES = 0x200000

Definition at line 68 of file ota_upload.py.

◆ PIO_BUILD_DIR

ota_upload.PIO_BUILD_DIR = os.path.join(PROJECT_DIR, ".pio", "build", "adafruit_feather_esp32s3")

Definition at line 38 of file ota_upload.py.

◆ ProgressFn

ota_upload.ProgressFn = Callable[[int, int], None]

Definition at line 70 of file ota_upload.py.

◆ PROJECT_DIR

ota_upload.PROJECT_DIR = os.path.join(SCRIPT_DIR, "OTA_Test_Firmware")

Definition at line 37 of file ota_upload.py.

◆ REPO_ROOT

ota_upload.REPO_ROOT = Path(__file__).resolve().parents[2]

Definition at line 84 of file ota_upload.py.

◆ SCRIPT_DIR

ota_upload.SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))

Definition at line 36 of file ota_upload.py.

◆ TRANSFER_TIMEOUT

float ota_upload.TRANSFER_TIMEOUT = 30

Definition at line 33 of file ota_upload.py.