Skip to content

Error Handling

Espen Hovland edited this page Jun 5, 2026 · 1 revision

Error Handling

PyCommander turns failed Commander invocations into typed exceptions so your automation scripts and production-test rigs can react in a structured way. The exception types live in pycommander_core.errors.

The exception hierarchy

Exception
└── PyCommanderError          # any non-zero Commander return code (base class)
    ├── PyCommanderInputError     # Commander rejected the arguments  (return code -1 / 255)
    └── PyCommanderRuntimeError   # Commander failed at runtime        (return code -2 / 254)
Exception Raised when Return code
PyCommanderInputError Commander rejected the arguments (bad/invalid input) -1 or 255
PyCommanderRuntimeError Commander failed at runtime — e.g. the adapter could not be reached, or the operation timed out internally -2 or 254
PyCommanderError Any other non-zero return code; also the base class of the two above other

The exception message contains Commander's captured output, which usually explains the failure.

from pathlib import Path
from pycommander import Adapter
from pycommander_core.errors import PyCommanderError, PyCommanderRuntimeError

adapter = Adapter(serial_number="440055955", target_device="EFR32MG24")

try:
    adapter.target.flashApplication(filenames=[Path("firmware.hex")])
except PyCommanderRuntimeError as e:
    print(f"Commander failed at runtime: {e}")
except PyCommanderError as e:
    print(f"Other Commander error: {e}")

Because PyCommanderInputError and PyCommanderRuntimeError both inherit from PyCommanderError, catching the base class catches everything; order your except clauses from specific to general.

Timeouts

Synchronous commands run with a timeout (default 300 secondsCommanderBase.default_timeout_s). If a command exceeds it, a standard Python TimeoutError is raised (not a PyCommanderError). Handle it separately if your flows can run long:

try:
    adapter.target.flashApplication(filenames=[Path("firmware.hex")])
except TimeoutError:
    print("Commander timed out")
except PyCommanderError as e:
    print(f"Commander error: {e}")

Validation errors (raised before Commander runs)

The high-level helpers validate arguments before invoking Commander, so some failures surface as ordinary Python exceptions rather than PyCommanderError:

  • ValueError — e.g. passing more than one of serial_number/ip_address/serial_port; an invalid VcomHandshake; an empty range/region list where one is required; an out-of-range region index; an invalid TrustZone string; an out-of-range code version.
  • FileNotFoundError — e.g. flashing or writing from a file path that does not exist, or pointing executable_path at a missing executable.
  • RuntimeError — e.g. attempting to write a public signing/command key when one already exists in OTP; reopening an already-open AemStream.

The success flag vs. exceptions

There are two distinct failure surfaces, and it helps to keep them separate:

  • A non-zero Commander exit raises an exception (as above). This is for genuine command failures.
  • Commander command methods return the parsed JSON dict, which typically includes a success boolean. The higher-level Adapter-Class and Target-Class helpers read this flag and translate it into return values: a query that "failed gracefully" generally returns None, and an action returns True/False. So a Target.info() returning None means Commander ran but reported no usable result — not that an exception occurred.

A robust pattern combines both:

info = adapter.target.info()
if info is None:
    print("Could not read device info")
else:
    print(info.part_number)

See also

  • Logging — record every invocation (including failures) with timestamps.
  • Architecture-and-Packages — how the subprocess Runner maps return codes to these exceptions.

Clone this wiki locally