# UART {py:obj}`~pycosmicsdk.Uart` drives the UART peripheral of an open adapter, from {py:obj}`Device.uart() `. Both the Supernova and the Pulsar have it. The async twin is {py:obj}`~pycosmicsdk.AsyncUart`. ## Bring-up ```python from pycosmicsdk import Device, UartBaudRate, UartDataSize, UartParity, UartStopBit with Device.open() as dev: uart = dev.uart() uart.set_voltage(voltage_mv=3300) uart.bring_up( baudrate=UartBaudRate.BAUD_115200, parity=UartParity.NONE, data_size=UartDataSize.BITS_8, stop_bit=UartStopBit.ONE, ) ``` {py:obj}`~pycosmicsdk.Uart.bring_up` is the re-runnable bring-up and the one to reach for: it tries {py:obj}`~pycosmicsdk.Uart.initialize`, falls back to {py:obj}`~pycosmicsdk.Uart.configure` when the UART is already up, and returns a {py:obj}`~pycosmicsdk.BringUpResult`. On return the UART is up and usable. The contract is identical across the four protocols that have it and is explained once under [Bring-up is `bring_up()`](index.md); check `settings_applied` when the baud rate is something your test depends on: ```python result = uart.bring_up(baudrate=UartBaudRate.BAUD_9600) if not result.settings_applied: raise result.refusal ``` Every bring-up argument is keyword-only. `baudrate` is required; `parity`, `data_size`, `stop_bit` and `hw_handshake` default to 8-N-1 with handshaking disabled. Baud rates come from the {py:obj}`~pycosmicsdk.UartBaudRate` ladder (600 through 115200) rather than a free integer. :::{warning} **`hw_handshake=UartHwHandshake.ENABLED` is believed to have no effect on a Supernova Rev B.** Firmware's UART setup branches on the hardware revision and the Rev B branch does not wire RTS/CTS up; the call still returns successfully, so a script asking for flow control on such a board gets none. The RTS/CTS pins stay muxed to their alternate function, so they look configured on a scope while the peripheral ignores them. The reading is from firmware source alone — **no bench run has confirmed it**, and confirming it needs a peer that actually asserts RTS/CTS plus enough traffic to force flow control. This SDK does not refuse the request — refusing one that might work is the worse error (see [Refusals fail open](../reference/errors.md)) — but it does say so, in three places. `dev.capabilities.uart_hw_handshake_effective` is `False` on such a unit, so you can branch on it before asking. {py:obj}`~pycosmicsdk.Uart.initialize` and {py:obj}`~pycosmicsdk.Uart.configure` log a warning, which is all a call returning `None` can do. And {py:obj}`~pycosmicsdk.Uart.bring_up` reports it: `settings_applied` is `False` with the reason in `refusal`, even on a fresh bring-up where nothing was refused on the wire. It is the one line parameter whose answer is known from the capability table before the call, so a field documented as the one to consult must not affirm it. Rev C Supernovas and Pulsars honour the request and report `settings_applied=True` as usual. ::: **`send_only_mode` is bring-up only — but `bring_up` does take it.** `send_only_mode=True` arms no reception: no RX queue, no dispatcher subscription. It is a host-side setting with no firmware parameter behind it, which is why {py:obj}`~pycosmicsdk.Uart.configure` has no such argument — and why {py:obj}`~pycosmicsdk.Uart.bring_up` can still honour it on either path, including the fallback onto a live UART: it selects the RX path in both directions, so on a UART whose reception an earlier run armed, `True` releases the queue and subscription again rather than flipping a flag behind machinery that keeps running. A caller parked in `receive`/`query` when that happens is woken with {py:obj}`~pycosmicsdk.DisconnectError`, exactly as {py:obj}`~pycosmicsdk.Uart.deinit` wakes it. The choice is never what `settings_applied` is reporting about. See `examples/blocking_api/uart/send_only.py`. Underneath, {py:obj}`~pycosmicsdk.Uart.initialize` is **not idempotent**: a re-run on an already-up UART raises {py:obj}`~pycosmicsdk.FirmwareError` with `FW_INTERFACE_ALREADY_INITIALIZED`, and the UART stays initialised across reconnects. {py:obj}`~pycosmicsdk.Uart.configure` re-configures a live interface; {py:obj}`~pycosmicsdk.Uart.deinit` tears the peripheral down without closing the device — UART is the only protocol here that offers one, which is one reason `bring_up` reports rather than promising to reach any given settings. `examples/helpers.py`'s `uart_bring_up()` delegates to {py:obj}`~pycosmicsdk.Uart.bring_up` and adds the examples' console lines. The rail is the shared multifunction VTARG — the same supply as I2C, SPI and GPIO. {py:obj}`~pycosmicsdk.Uart.get_bus_voltage` reads it back, and {py:obj}`~pycosmicsdk.AsyncUart` mirrors it. :::{note} **{py:obj}`~pycosmicsdk.Uart.set_voltage` takes `voltage_mv` keyword-only**, where the I2C, SPI and GPIO setters accept it positionally. So `uart.set_voltage(3300)` is a `TypeError` while `spi.set_voltage(3300)` is fine, and `uart.set_voltage(voltage_mv=3300)` is the form to use. This is an inconsistency in this SDK rather than anything upstream imposes; the keyword-only form works on all four, so write it that way everywhere and the question does not arise. ::: ## Three reception patterns — pick one per interface This is the part that bites. The RX path is a **single internal queue** shared by `receive` and `query`, and the three patterns below do not mix. {py:obj}`~pycosmicsdk.Uart.query` : Request/response. Drains the queue, sends, waits for a reply. **Not concurrency-safe** — overlapping calls discard each other's data and can return each other's replies. Use it when the exchange is strictly one-at-a-time. `examples/blocking_api/uart/query.py`. {py:obj}`~pycosmicsdk.Uart.receive` : Pull. Blocks for the next message, with an optional `timeout`. Safe to call concurrently with *competing-consumer* semantics: each message goes to exactly one waiter, non-deterministically — UART has no request/response correlation, so there is nothing to correlate on. `examples/blocking_api/uart/receive_stream.py`. {py:obj}`Device.subscribe ` with {py:obj}`~pycosmicsdk.UartRxNotification` : Push. Register a callback once and the dispatcher invokes it — on its own thread — for every message received. The natural style for telemetry or reacting to each line a device emits. `examples/blocking_api/uart/subscribe.py`. Mixing `query` with `receive` is unsound. Pick one pattern per interface. On the async side, {py:obj}`~pycosmicsdk.AsyncUart.iter_receive` is a fourth option with no blocking equivalent: an async iterator that installs its **own** subscription and queue, so iterating does not compete with `receive` or `query` for messages. ```python async with uart.iter_receive() as stream: async for message in stream: print(message) ``` {py:obj}`~pycosmicsdk.Uart.send` takes `bytes`. {py:obj}`DeviceCapabilities.max_uart_message_bytes ` reports the advisory message cap (1024). ## Errors {py:obj}`~pycosmicsdk.UartError` is **much** narrower than its name suggests. The 0x04xx band maps to it, and upstream declares exactly one code in that band: `FW_UART_IN_VCOM_PORT_MODE` (`0x0401`), reported by `uartInit` alone, and only when the port is held by the USB CDC. Everything else lands outside the band: | Failure | Code | Exception | |---|---|---| | Port held by USB CDC | `FW_UART_IN_VCOM_PORT_MODE` (`0x0401`) | {py:obj}`~pycosmicsdk.UartError` | | Rejected configuration | `FW_INVALID_PARAMETER` (`0x0004`) | {py:obj}`~pycosmicsdk.FirmwareError` | | Interface not initialised | `FW_INTERFACE_NOT_INITIALIZED` (`0x0006`) | {py:obj}`~pycosmicsdk.FirmwareError` | | Rejected voltage | `FW_VOLTAGE_OUT_OF_RANGE` (`0x0101`) | {py:obj}`~pycosmicsdk.SystemFirmwareError` | Catch {py:obj}`~pycosmicsdk.FirmwareError` to cover every firmware-reported UART failure. A `receive`/`query` that runs out of time raises {py:obj}`~pycosmicsdk.CosmicTimeoutError`, which also satisfies `except TimeoutError:`. ## Examples `examples/blocking_api/uart/` : `query.py` (request/response), `send_receive.py` (separate send and receive calls — a console session), `receive_stream.py` (pull loop), `subscribe.py` (push path), `send_only.py` (`send_only_mode=True` plus explicit teardown). `examples/async_api/uart/` : `query.py`, `send_receive.py`, and `receive_stream.py` — the last built on `iter_receive()`. ## Reference - {py:obj}`pycosmicsdk.Uart` / {py:obj}`pycosmicsdk.AsyncUart` - {py:obj}`pycosmicsdk.UartBaudRate`, {py:obj}`pycosmicsdk.UartParity`, {py:obj}`pycosmicsdk.UartDataSize`, {py:obj}`pycosmicsdk.UartStopBit`, {py:obj}`pycosmicsdk.UartHwHandshake` - {py:obj}`pycosmicsdk.UartRxNotification` - {py:obj}`pycosmicsdk.BringUpResult`