Device and discovery
The narrative version of everything on this page — how to choose a selector, what makes a handle stale, when to reboot — is The device lifecycle.
Discovery
Answers “what is plugged in?” without opening anything: it reads USB-HID
enumeration directly, so no adapter is opened and no firmware command is sent.
Every field of a DiscoveredDevice is ready to use as an
open() argument or a filter key. examples/blocking_api/system/list_devices.py
is the runnable version.
- pycosmicsdk.list_devices(*, model: DeviceModel | None = None) list[DiscoveredDevice]
List the Binho host adapters currently attached, without opening any.
Only adapters
Device.open()can actually open are listed: selection is by the(vendor id, product id)pairs the CosmicSDK driver accepts (CosmicDriver::binhoDeviceList), so a Binho HID device that is not a supported host adapter is never returned, and the product id doubles as the model marker – which is what makesmodela pre-open filter rather than a post-open check.Pair an entry with
Device.open()(orAsyncDevice.open) by serial or by path:for dev in list_devices(model=DeviceModel.SUPERNOVA): with Device.open(serial=dev.serial_number) as device: ...
Entries are grouped by model in
DeviceModeldeclaration order and, within a model, follow the host’s HID enumeration order. Neither is stable across replugs, and neither predicts which adapter an argument-lessDevice.open()picks. Duplicate HID interfaces of one physical adapter are collapsed to a single entry by serial number.- Parameters:
model – Optional host-adapter model to restrict the listing to, e.g.
DeviceModel.SUPERNOVA.Nonelists every supported model.- Returns:
One
DiscoveredDeviceper attached adapter, empty if none are attached. An entry whoseserial_numberis empty was found but could not be opened, so its string descriptors are blank and it cannot be selected by serial; a warning naming the likely cause is logged undercosmicpysdk.discovery.- Raises:
ValidationError – If
modelis not a selectable host-adapter model (i.e.DeviceModel.UNKNOWN).
- class pycosmicsdk.DiscoveredDevice(model: DeviceModel, serial_number: str, path: str, manufacturer: str, product_name: str, hw_version: str, fw_version: str)
Bases:
objectOne attached host adapter, as seen from USB enumeration alone.
Deliberately not
DeviceInfo.DeviceInforeports what an open adapter says about itself, and it reports it raw: the firmware’s USB string descriptors carry a three-character tag, so an opened Supernova reportsserial_number="SN-634F...",hw_version="HW-B",manufacturer="MN-Binho LLC". The legacysupernovacontrollershim depends on that raw form. Enumeration sees the same fields untagged, and only the untagged serial is accepted byDevice.open(serial=...)– the tagged one raisesDeviceNotFoundError. Putting both string conventions behind one type would makeopen(serial=info.serial_number)a coin flip on where theinfocame from, so discovery gets its own record.Two things this shape gains from being its own type:
path, whichDeviceInfostructurally cannot carry (CosmicSDK does not expose the resolved path of a connected handle, but enumeration has it), and amodeltaken from the USB product id rather than inferred from a product string.Every field is ready to use as an open() argument or a filter key; nothing here requires a round trip to the device.
- model
Host-adapter model, from the USB product id – the authoritative model marker at enumeration time.
- serial_number
Untagged USB serial number, ready to pass as
Device.open(serial=...). Empty if the adapter reports none.- Type:
- path
USB-HID device path, ready to pass as
Device.open(path=...). Valid only until the adapter is replugged.- Type:
- hw_version
Hardware revision letter decoded from the USB release number (
"B", not"HW-B"), or"?"for an adapter on firmware 3.x, whose encoding carries no revision.- Type:
- fw_version
Firmware version decoded from the USB release number (
"4.5.0", not"FW-4.5.0"). An adapter on firmware 3.x reports"3.x.x": that scheme is a fixed value rather than an encoding, so its minor and patch are not knowable here. An adapter waiting in its bootloader reports"bootloader".- Type:
Device
The blocking handle for one open adapter.
Note
The constructor signature in each class heading below is package-private —
Device and AsyncDevice are built by open /
open, never by calling the class. It is
shown because autodoc reads it off the class, not because it is API.
- class pycosmicsdk.Device(facade: Any, info: DeviceInfo)
Bases:
objectBlocking SDK handle for a single Binho USB host adapter.
Construct with
Device.open(). The explicit constructor is package-private; user code never calls it. Use as a context manager for deterministic cleanup;close()is idempotent.Thread safety: a per-instance
threading.RLockserializes every lifecycle mutation and every blocking FFI call. Two threads can share aDeviceinstance safely; sequences of calls that need to appear atomic to other threads (e.g. send + receive) require user-level locking on top.- classmethod open(*, serial: str | None = None, path: str | None = None, model: DeviceModel | None = None, timeout_ms: int = 2000) Device
Open a Binho host adapter and return a usable
Device.- Parameters:
serial – Optional device serial number to match. Mutually exclusive with
pathandmodel.path – Optional USB-HID device path to match. Mutually exclusive with
serialandmodel. Withserial,pathandmodelall set toNone, opens the first enumerated Binho adapter.model – Optional host-adapter model to match, e.g.
DeviceModel.SUPERNOVA. Opens the first enumerated adapter of that model — use it instead of a hard-coded serial when the scenario needs a specific model but any unit of it will do. Mutually exclusive withserialandpath.timeout_ms – Per-call firmware response timeout for the blocking facade. Default 2000 ms matches CosmicSDK.
- Returns:
A fully constructed Device in
DeviceState.OPEN.- Raises:
ValidationError – If more than one of
serial,pathandmodelis set, or ifmodelis not a selectable host adapter model.DeviceNotFoundError – If no adapter of the requested
modelis attached, or if the underlyingconnectcall never returnedTrue. Aconnectthat fails slowly means an adapter is attached but busy, so it is retried for up to two seconds before giving up; the error distinguishes the two cases.
- close() None
Close the device. Idempotent.
Routes through the same cleanup path as the GC finalizer (so there is exactly one teardown sequence). After
close(), any subsequent FFI-touching method raisesDisconnectError.
- property info: DeviceInfo
Cached
DeviceInfosnapshot taken atopen()time.Stable for the lifetime of the Device — even after
close(). Usefetch_device_info()to issue a fresh firmware query.
- property model: DeviceModel
Convenience accessor for
info.model.
- property capabilities: DeviceCapabilities
Capability set derived from
info.modelatopen()time.
- property state: DeviceState
Current lifecycle state. Locked read for memory-model clarity.
- property discarded_response_count: int
Responses this connection received and refused, since
open().A reply is discarded when no command is waiting for it, when it carries another command’s transaction id, when it duplicates one already consumed, or when it is too short to hold a response header. The count only rises.
Read it when a command times out and the cause is unclear: a rising count says another client is answering on this adapter, a static one says the device is simply slow. It stays readable after
close(), which is when a post-mortem usually wants it.Note
A non-zero value is not an error. Windows delivers every HID input report to every open handle, so a second process talking to the same adapter makes this climb on a healthy session. Diagnose with it; do not branch on it.
- Returns:
The number of responses discarded on this connection.
- property is_connected: bool
Truewhile the device is inDeviceState.OPEN.
- fetch_device_info() DeviceInfo
Issue a fresh
get_device_infoquery and return the result.Distinct from the
infoproperty, which returns the snapshot cached at open. Every field is fixed for the life of a connection:reboot()re-enumerates the adapter and marks this handleDeviceState.TERMINAL, so changed info has to be read from a newly openedDevice. This method re-issues the query on demand rather than reporting a change.- Returns:
A freshly queried
DeviceInfofrom the firmware.- Raises:
DisconnectError – If the device is no longer
OPEN.
- get_version() str
Return the CosmicSDK library version string.
- Returns:
The CosmicSDK library version string.
- Raises:
DisconnectError – If the device is no longer
OPEN.
- reboot(into: ResetTarget) None
Reset the host adapter into the requested mode.
On success the device re-enumerates over USB and this handle becomes stale:
statetransitions toDeviceState.TERMINALand a freshDevicemust be opened. Polllist_devicesrather than sleeping a fixed interval; on the bench an adapter is openable again 0.7-0.9 s after aRUNTIMEreset.A
RUNTIMEreset clears latched interface roles (an I2C controller role that survivedclose) and a wedged SPI bulk payload path; it does not clear the VTARG setpoint. It reaches the device only while its SYS manager still dispatches commands.- Parameters:
into – Reset target.
RUNTIMEperforms a normal device reset;BOOTjumps the device into bootloader mode (used for firmware flashing);ISPis reserved by CosmicSDK and not implemented yet.- Raises:
NotImplementedError – When
into is ResetTarget.ISP— ISP mode has no upstream implementation yet.DisconnectError – If the device is no longer
OPEN.
- get_usb_config() UsbConfig
Read the USB interface configuration stored on the adapter.
- Returns:
The WebUSB state, USB CDC state and CDC mode currently saved on the device. The no-change sentinels never appear in a read.
- Raises:
DisconnectError – If the device is no longer
OPEN.ProtocolError – If the response was too short to carry a configuration, or carried a value this SDK does not know. CosmicSDK maps a header-only firmware error response to
SDK_ERROR_WRONG_RESPONSE, so firmware predatingCMD_SYS_GET_USB_CONFIGalso reports itself this way rather than as an unsupported-command error.FirmwareError – On a firmware-reported failure. Reading the configuration saves nothing, so upstream
getUsbConfigreports no 0x01xx code and this never surfaces asSystemFirmwareError– unlikeset_usb_config, which writes to flash and can reportFW_SETTINGS_SAVE_FAILED.
- set_usb_config(config: UsbConfig) None
Save a USB interface configuration to the adapter’s flash.
The firmware writes the configuration to flash and replies; it does not reset. The new configuration takes effect on the next reset or power cycle – call
reboot()to apply it now. This handle staysOPEN, andget_usb_config()reads back the saved values.A firmware-reported rejection changes nothing and leaves the handle
OPEN. Any other failure (timeout, disconnect) leaves it unknown whether the adapter took the command, so the handle is markedTERMINAL.Disabling both interfaces at once is recoverable: CosmicSDK drives the adapter over its USB HID interface, which this command does not touch, so a device left with WebUSB and CDC disabled can still be reopened and reconfigured.
CdcModehas no no-change sentinel, so read the current configuration and modify it rather than composing one from scratch:current = dev.get_usb_config() dev.set_usb_config(dataclasses.replace(current, cdc_mode=CdcMode.RS485))
- Parameters:
config – The configuration to save.
WebusbState.NO_CHANGEandCdcState.NO_CHANGEleave their own field as it is.- Raises:
ValidationError – If a field is not a member of its enum. No facade call is made.
CapabilityError – If
config.cdc_modeis a mode this adapter model does not implement. No facade call is made.DisconnectError – If the device is no longer
OPEN.SystemFirmwareError – On a firmware-reported failure —
FW_SETTINGS_SAVE_FAILED(0x0108) when the settings write or the flash flush fails. The flush-failure reply is 10 bytes, not the usual 6: firmware appends a rawuint32_tflash diagnostic after the response header. Both decode paths tolerate it — each treats the 6-byte header as a minimum, never as an exact length — but the diagnostic word itself is not surfaced.
- subscribe(notification_type: type[N], callback: Callable[[N], None], *, where: Callable[[N], bool] | None = None) Subscription[N]
Register a callback for notifications of the given type.
The first call lazily builds the
NotificationDispatcherand installs one bridge on the facade’son_notificationslot; later calls reuse both. The bridge enqueues notifications and never propagates exceptions back into the C++ polling thread.Every part of that is host-side:
on_notificationsets a callback slot and returns, and nothing here reaches the adapter. So subscribing is exempt from the no-answer count in both directions – it is neither evidence that the adapter still answers nor refused on a handle that has stopped answering, which keeps it usable from the diagnostic path a caller writes after a command times out. See_OPERATIONS_THAT_ISSUE_NO_TRAFFIC.- Parameters:
notification_type – The concrete
Notificationsubclass to match (e.g.UartRxNotification). Used for bothisinstancefiltering and type narrowing.callback – Invoked on the dispatcher thread for every matching notification. Keep it short; for heavy work, queue onto your own loop / executor.
where – Optional secondary predicate applied after the type check. The dispatcher only invokes
callbackwhenisinstance(n, notification_type) and where(n)holds.
- Returns:
A
Subscriptionhandle. Useunsubscribe()to detach, or use it as a context manager to scope the subscription to awithblock.- Raises:
DisconnectError – If the device is no longer
OPEN.
- i2c(*, bus: I2cBus = I2cBus.A) I2cController
Return the
I2cControllerforbus, building it on first use.Controllers are cached per bus on this device, so repeated calls with the same bus return the same object. The cache is dropped on
close().- Parameters:
bus – Which I2C bus to control. Defaults to
I2cBus.A.- Returns:
The cached or newly built
I2cControllerforbus.- Raises:
CapabilityError – If the device does not expose
bus.
- i2c_target(*, bus: I2cBus = I2cBus.A) I2cTarget
Return the
I2cTargetforbus, building it on first use.Targets are cached per bus, under a key distinct from
i2c()’s, so repeated calls with the same bus return the same object and the target and controller interfaces for one bus are two objects rather than one. The cache is dropped onclose().The selector is per bus because the command is.
i2cTargetInittakesbusIdas its first parameter, which is what makes this accessor takebuswherespi_target()takes nothing – forced by the surface, not chosen for symmetry.Getting the object is free; :meth:`I2cTarget.initialize` is the act that takes the role, and :meth:`I2cTarget.release` is what gives it back. Two consequences a program hits rather than reads about, both of them now about what can be up at once rather than about the power cycle: on one adapter an I2C target and an ordinary I2C controller cannot both be up, in either order, and only one target protocol can be armed at a time, because the arena is the shared bulk-staging pool. A release clears either. Mind that the target guard is per device while the release is per bus, so freeing a two-bus adapter for a target bring-up takes a release on each. The comment block above
I2cTargetinpycosmicsdk._interfaces.i2ccarries the firmware citations; read it before arming a target on an adapter somebody else is using.The capability check is the same one
i2c()makes, and it is the only one there is to make: firmware refuses a target bring-up on a bus it does not have withFW_BUS_NOT_SUPPORTED, and the arena’s own gate – which is a bulk transport question rather than a model one – belongs atI2cTarget.initialize(), where it can be waived. So unlikespi_target()this accessor adds no SDK-side hardware gate of its own; a Pulsar has both I2C buses and can be an I2C target on either.- Parameters:
bus – Which I2C bus the target answers on. Defaults to
I2cBus.A.- Returns:
The cached or newly built
I2cTargetforbus.- Raises:
CapabilityError – If the device does not expose
bus.
- i3c() I3cController
Return the
I3cController, building it on first use.I3C has no bus selector: the firmware picks the low- or high-voltage port electrically from the bus voltage, so there is a single interface per device. It is cached, so repeated calls return the same object; the cache is dropped on
close().- Returns:
The cached or newly built
I3cController.- Raises:
CapabilityError – If the device does not expose I3C.
- i3c_target() I3cTarget
Return the
I3cTargetinterface, building it on first use.Target mode runs on the same I3C hardware as
i3c()and needs no bus selector, so there is a single interface per device. It is cached separately from the controller interface, so repeated calls return the same object; the cache is dropped onclose().- Returns:
The cached or newly built
I3cTarget.- Raises:
CapabilityError – If the device does not expose I3C.
- spi() SpiController
Return the
SpiController, building it on first use.SPI has a single controller per device — the chip-select line is a
SpiController.configure()parameter, not a separate interface. It is cached, so repeated calls return the same object; the cache is dropped onclose().- Returns:
The cached or newly built
SpiController.- Raises:
CapabilityError – If the device does not expose SPI.
- spi_target() SpiTarget
Return the
SpiTargetinterface, building it on first use.SPI target mode runs on the same SPI hardware as
spi()and takes no selector — the chip-select line this target answers on is anSpiTarget.initialize()parameter. It is cached separately from the controller interface, so repeated calls return the same object; the cache is dropped onclose().Getting the object is free; :meth:`SpiTarget.initialize` is the act that takes the role, and :meth:`SpiTarget.release` is what gives it back. Taking the SPI target role is exclusive, so while it is held a controller bring-up is refused with
FW_SPI_ROLE_CONFLICT; the release clears that in-session, andreboot()— after which this handle is terminal and a fresh device must be opened — is only the fallback on firmware predating the deinit dispatch. The comment block aboveSpiTargetinpycosmicsdk._interfaces.spisays why at length; read it before bringing a target up on an adapter somebody else is using.Refused here as well as at bring-up on a unit whose silicon cannot receive a chip select, because the whole interface is unusable on such a unit rather than one call of it — a caller who cannot obtain the object cannot write the rest of the program against it. That refusal is this SDK’s own: firmware applies no revision check and would answer
SUCCESS, after which the adapter arms and then reports nothing ever again. Readcapabilities.spi_target_capableto branch on it.That refusal does not cost such a unit the release. The guard is about a unit’s ability to serve as a target, and
spiDeinitis role-agnostic — so on a unit refused here, the peripheral’s role is handed back throughSpiController.release()instead. Read that method: it exists for exactly this case.- Returns:
The cached or newly built
SpiTarget.- Raises:
CapabilityError – If the device does not expose SPI, or if this hardware revision cannot act as a SPI target.
- uart() Uart
Return the
Uartinterface, building it on first use.UART has no bus selector, so there is a single interface per device. It is cached, so repeated calls return the same object; the cache is dropped on
close().- Returns:
The cached or newly built
Uartinterface.- Raises:
CapabilityError – If the device does not expose UART.
- gpio() Gpio
Return the
Gpiointerface, building it on first use.One interface covers every GPIO pin; the pin is an argument to each call. It is cached, so repeated calls return the same object — and with it the recorded per-pin configuration. The cache is dropped on
close().- Returns:
The cached or newly built
Gpiointerface.- Raises:
CapabilityError – If the device does not expose GPIO.
AsyncDevice
The asyncio twin. See Blocking and asyncio for how the two
relate and where they differ.
- class pycosmicsdk.AsyncDevice(facade: Any, engine: AsyncEngine, timeout_ms: int)
Bases:
objectAsyncio SDK handle for a single Binho USB host adapter.
Construct with
AsyncDevice.open(). The explicit constructor is package-private; user code never calls it. Use the opener as an async context manager for deterministic cleanup;close()is idempotent.Concurrency: a per-instance
threading.RLockserializes lifecycle mutations (open/close/reboot). Protocol commands take it only for the lifecycle guard and to report their outcome to the no-answer count, never across anawait— they correlate through theAsyncEngine, so concurrent awaits on one device interleave freely and the firmware processes them FIFO at the wire.- classmethod open(*, serial: str | None = None, path: str | None = None, model: DeviceModel | None = None, timeout_ms: int = 2000) _AsyncDeviceOpener
Open a Binho host adapter, returning an awaitable async context manager.
The returned opener connects on first
awaitorasync withentry; argument validation happens eagerly, here.- Parameters:
serial – Optional device serial number to match. Mutually exclusive with
pathandmodel.path – Optional USB-HID device path to match. Mutually exclusive with
serialandmodel. With all threeNone, opens the first enumerated adapter.model – Optional host-adapter model to match, e.g.
DeviceModel.SUPERNOVA. Connects to the first enumerated adapter of that model — use it instead of a hard-coded serial when the scenario needs a specific model but any unit of it will do. Mutually exclusive withserialandpath.timeout_ms – Per-request firmware response timeout applied to awaited commands. Default 2000 ms matches CosmicSDK.
- Returns:
An
_AsyncDeviceOpenerusable asasync withorawait.- Raises:
ValidationError – If more than one of
serial,pathandmodelis set. Failures that need the USB bus (no adapter ofmodelattached, an unselectablemodel) surface on connect, not here.
- async close() None
Close the device. Idempotent.
Routes through the same cleanup path as the GC finalizer (so there is exactly one teardown sequence): outstanding requests are failed with
DisconnectErrorand the native handle is disconnected. Afterclose(), any subsequent command raisesDisconnectError.
- property state: DeviceState
Current lifecycle state. Locked read for memory-model clarity.
- property is_connected: bool
Truewhile the device is inDeviceState.OPEN.
- property info: DeviceInfo
Cached
DeviceInfosnapshot taken at open time.Stable for the lifetime of the device — even after
close(). Usefetch_device_info()to issue a fresh firmware query.
- property model: DeviceModel
Convenience accessor for
info.model.
- property capabilities: DeviceCapabilities
Capability set derived from
info.modelat open time.
- async get_version() str
Return the CosmicSDK library version string.
- Returns:
The CosmicSDK library version string.
- Raises:
DisconnectError – If the device is no longer
OPEN.
- async fetch_device_info() DeviceInfo
Issue a fresh device-info query and return the result.
Composes five
get_usb_stringcommands (manufacturer, product, serial, firmware, hardware) and assembles the read-onlyDeviceInfo, derivingmodelfrom the product string. Distinct from theinfoproperty, which returns the snapshot cached at open.- Returns:
A freshly queried
DeviceInfofrom the firmware.- Raises:
DisconnectError – If the device is no longer
OPEN.CosmicTimeoutError – If a string query exceeds the per-request timeout.
- async reboot(into: ResetTarget) None
Reset the host adapter into the requested mode.
On success, the device re-enumerates over USB and the current handle becomes stale;
statetransitions toDeviceState.TERMINAL. Reconnecting requires opening a freshAsyncDevice.- Parameters:
into – Reset target.
RUNTIMEperforms a normal device reset;BOOTjumps the device into bootloader mode;ISPis reserved by CosmicSDK and not implemented yet.- Raises:
NotImplementedError – When
into is ResetTarget.ISP.DisconnectError – If the device is no longer
OPEN.
- async get_usb_config() UsbConfig
Read the USB interface configuration stored on the adapter.
- Returns:
The WebUSB state, USB CDC state and CDC mode currently saved on the device. The no-change sentinels never appear in a read.
- Raises:
DisconnectError – If the device is no longer
OPEN.ProtocolError – If the response was too short to carry a configuration, or carried a value this SDK does not know — which is also how firmware predating
CMD_SYS_GET_USB_CONFIGreports itself, since its error response carries the generic header only.CosmicTimeoutError – If the request exceeds the per-request timeout.
FirmwareError – On a firmware-reported failure. Reading the configuration saves nothing, so upstream
getUsbConfigreports no 0x01xx code and this never surfaces asSystemFirmwareError– unlikeset_usb_config, which writes to flash and can reportFW_SETTINGS_SAVE_FAILED.
- async set_usb_config(config: UsbConfig) None
Save a USB interface configuration to the adapter’s flash.
The firmware writes the configuration to flash and replies; it does not reset. The new configuration takes effect on the next reset or power cycle – call
reboot()to apply it now. This handle staysOPEN, andget_usb_config()reads back the saved values.A firmware-reported rejection changes nothing and leaves the handle
OPEN. Any other failure (timeout, disconnect) leaves it unknown whether the adapter took the command, so the handle is markedTERMINAL.Disabling both interfaces at once is recoverable: CosmicSDK drives the adapter over its USB HID interface, which this command does not touch, so a device left with WebUSB and CDC disabled can still be reopened and reconfigured.
CdcModehas no no-change sentinel, so read the current configuration and modify it rather than composing one from scratch:current = await dev.get_usb_config() await dev.set_usb_config(dataclasses.replace(current, cdc_mode=CdcMode.RS485))
- Parameters:
config – The configuration to save.
WebusbState.NO_CHANGEandCdcState.NO_CHANGEleave their own field as it is.- Raises:
ValidationError – If a field is not a member of its enum. No facade call is made.
CapabilityError – If
config.cdc_modeis a mode this adapter model does not implement. No facade call is made.DisconnectError – If the device is no longer
OPEN.CosmicTimeoutError – If the request exceeds the per-request timeout.
SystemFirmwareError – On a firmware-reported failure —
FW_SETTINGS_SAVE_FAILED(0x0108) when the settings write or the flash flush fails. The flush-failure reply is 10 bytes, not the usual 6: firmware appends a rawuint32_tflash diagnostic after the response header. Both decode paths tolerate it — each treats the 6-byte header as a minimum, never as an exact length — but the diagnostic word itself is not surfaced.
- async get_analog_measurements() AnalogMeasurements
Read every voltage rail the adapter measures, in one transaction.
One firmware command samples all six rails at once — the target rail shared by I2C, SPI, UART and GPIO, the I3C high- and low-voltage target rails, and the VCCA, VCCA-I3C and VDDIO2 supply rails — so this lives on the device rather than on a protocol interface: no single protocol owns the reading.
This method is async-only, and that asymmetry is forced: upstream declares
getAnalogMeasurementsonCosmicAsyncSDKalone, andCosmicSDKneither redeclares it nor exposes the privateasyncSDKmember it would have to go through, so a blockingDevicehas no way to read all six rails at once.The reverse is not true. Upstream’s five blocking per-protocol readers are each implemented on top of this one command, keeping the field they were asked for and discarding the rest (
CosmicSDK.cpp,i2cGetBusVoltageand friends). So the per-protocol readers are available on both surfaces — the async ones are built here, on top of this command, exactly as upstream builds the blocking ones.- Returns:
A snapshot of all six rails in millivolts. Rails the connected model does not fit still occupy their fields with whatever the firmware reports; see
AnalogMeasurements.- Raises:
DisconnectError – If the device is no longer
OPEN.ProtocolError – If the response was too short to carry the six measurements — which is also how firmware that does not implement the command reports itself, since its error response carries the generic header only.
CosmicTimeoutError – If the request exceeds the per-request timeout.
SystemFirmwareError – On a firmware-reported failure.
- i2c(*, bus: I2cBus = I2cBus.A) AsyncI2cController
Return the
AsyncI2cControllerforbus, building it on first use.Controllers are cached per bus on this device, so repeated calls with the same bus return the same object. The cache is dropped on
close().i2citself is synchronous (no I/O); await the controller’s methods to talk to the device.- Parameters:
bus – Which I2C bus to control. Defaults to
I2cBus.A.- Returns:
The cached or newly built
AsyncI2cControllerforbus.- Raises:
CapabilityError – If the device does not expose
bus.
- i2c_target(*, bus: I2cBus = I2cBus.A) AsyncI2cTarget
Return the
AsyncI2cTargetforbus, building it on first use.The asyncio twin of
Device.i2c_target(): same cache key (("i2c_target", bus)), same per-bus selector – forced byi2cTargetInittakingbusId– and the same capability check. Targets are cached separately from controllers, so repeated calls with the same bus return the same object; the cache is dropped onclose().i2c_targetitself is synchronous (no I/O); await the target’s methods to talk to the device.Getting the object is free; :meth:`AsyncI2cTarget.initialize` is the act that takes the role, and :meth:`AsyncI2cTarget.release` is what gives it back – per bus, while the target guard is per device. The comment block above
I2cTargetinpycosmicsdk._interfaces.i2ccarries the firmware citations.Note
The arena is bulk-only and the bulk plane is blocking-only, so an
AsyncI2cTargetcan arm a target and watch its bus events but cannot seed or read back its arena. That asymmetry lives on the interface, which documents it and which gates its bring-up on the host half of the transport alone; this accessor adds nothing to it.- Parameters:
bus – Which I2C bus the target answers on. Defaults to
I2cBus.A.- Returns:
The cached or newly built
AsyncI2cTargetforbus.- Raises:
CapabilityError – If the device does not expose
bus.
- i3c() AsyncI3cController
Return the
AsyncI3cController, building it on first use.I3C has no bus selector: the firmware picks the low- or high-voltage port electrically from the bus voltage, so there is a single interface per device. It is cached, so repeated calls return the same object; the cache is dropped on
close().i3citself is synchronous (no I/O); await the controller’s methods to talk to the device.- Returns:
The cached or newly built
AsyncI3cController.- Raises:
CapabilityError – If the device does not expose I3C.
- i3c_target() AsyncI3cTarget
Return the
AsyncI3cTargetinterface, building it on first use.Target mode runs on the same I3C hardware as
i3c()and needs no bus selector, so there is a single interface per device. It is cached separately from the controller interface, so repeated calls return the same object; the cache is dropped onclose().i3c_targetitself is synchronous (no I/O); await the interface’s methods to talk to the device.- Returns:
The cached or newly built
AsyncI3cTarget.- Raises:
CapabilityError – If the device does not expose I3C.
- spi() AsyncSpiController
Return the
AsyncSpiController, building it on first use.SPI has a single controller per device — the chip-select line is a
AsyncSpiController.configure()parameter, not a separate interface. It is cached, so repeated calls return the same object; the cache is dropped onclose().spiitself is synchronous (no I/O); await the controller’s methods to talk to the device.- Returns:
The cached or newly built
AsyncSpiController.- Raises:
CapabilityError – If the device does not expose SPI.
- spi_target() AsyncSpiTarget
Return the
AsyncSpiTargetinterface, building it on first use.SPI target mode runs on the same SPI hardware as
spi()and takes no selector — the chip-select line this target answers on is anAsyncSpiTarget.initialize()parameter. It is cached separately from the controller interface, so repeated calls return the same object; the cache is dropped onclose().Getting the object is free; :meth:`AsyncSpiTarget.initialize` is the act that takes the role, and :meth:`AsyncSpiTarget.release` is what gives it back. Taking the SPI target role is exclusive, so while it is held a controller bring-up is refused with
FW_SPI_ROLE_CONFLICT; the release clears that in-session, andreboot()— after which this handle is terminal and a fresh device must be opened — is only the fallback on firmware predating the deinit dispatch. The comment block aboveSpiTargetinpycosmicsdk._interfaces.spisays why at length; read it before bringing a target up on an adapter somebody else is using.Refused here as well as at bring-up on a unit whose silicon cannot receive a chip select, because the whole interface is unusable on such a unit rather than one call of it — a caller who cannot obtain the object cannot write the rest of the program against it. That refusal is this SDK’s own: firmware applies no revision check and would answer
SUCCESS, after which the adapter arms and then reports nothing ever again. Readcapabilities.spi_target_capableto branch on it.That refusal does not cost such a unit the release. The guard is about a unit’s ability to serve as a target, and
spiDeinitis role-agnostic — so on a unit refused here, the peripheral’s role is handed back throughAsyncSpiController.release()instead. Read that method: it exists for exactly this case.- Returns:
The cached or newly built
AsyncSpiTarget.- Raises:
CapabilityError – If the device does not expose SPI, or if this hardware revision cannot act as a SPI target.
- uart() AsyncUart
Return the
AsyncUartinterface, building it on first use.UART has no bus selector, so there is a single interface per device. It is cached, so repeated calls return the same object; the cache is dropped on
close().uartitself is synchronous (no I/O); await the interface’s methods to talk to the device.- Returns:
The cached or newly built
AsyncUartinterface.- Raises:
CapabilityError – If the device does not expose UART.
- gpio() AsyncGpio
Return the
AsyncGpiointerface, building it on first use.One interface covers every GPIO pin; the pin is an argument to each call. It is cached, so repeated calls return the same object — and with it the recorded per-pin configuration. The cache is dropped on
close().gpioitself is synchronous (no I/O); await the interface’s methods to talk to the device.- Returns:
The cached or newly built
AsyncGpiointerface.- Raises:
CapabilityError – If the device does not expose GPIO.
- subscribe(notification_type: type[N], callback: Callable[[N], Awaitable[None]], *, where: Callable[[N], bool] | None = None) Subscription[N]
Register an async callback for notifications of the given type.
subscribeitself is synchronous (registration only). The coroutinecallbackreturns is scheduled on the event loop running at call time viaasyncio.run_coroutine_threadsafe— the dispatcher thread never runs it inline. An exception from the coroutine is logged underpycosmicsdk.dispatcherand never propagates.- Parameters:
notification_type – The concrete
Notificationsubclass to match.callback – Async function invoked for every matching notification.
where – Optional secondary predicate applied after the type check.
- Returns:
A
Subscriptionhandle;unsubscribe()detaches it.- Raises:
DisconnectError – If the device is no longer
OPEN.
- iter_notifications(notification_type: type[N] = Notification, *, where: Callable[[N], bool] | None = None) _NotificationStream[N]
Return an async iterator over matching notifications.
Use as an async context manager so the underlying subscription is detached on every exit path (normal,
break, exception, cancellation):async with dev.iter_notifications(UartRxNotification) as stream: async for n in stream: ...
- Parameters:
notification_type – The
Notificationsubclass to yield. Defaults to the baseNotification(every notification).where – Optional secondary predicate applied after the type check.
- Returns:
A
_NotificationStreamasync context manager / iterator.- Raises:
DisconnectError – From the iteration — not from this call — if the device is closed, rebooted or reconfigured while a consumer is parked on the stream. The stream has no other ending: it is infinite until the handle goes terminal, and it says so rather than stopping quietly, exactly as
AsyncUart.receivedoes. Wrap theasync forinexcept DisconnectErrorif a consumer task should end cleanly when the device closes.
Note
Async-only, but not because of any upstream limit. This is an
async foridiom layered oversubscribe(), which exists on both surfaces and is the whole of the underlying mechanism — there is no separate firmware command and nothing async-specific in L1 or L2. A blockingDevicehas no counterpart simply because pull-style iteration is not how the blocking surface delivers notifications; useDevice.subscribewith a callback there. The same holds forAsyncUart.iter_receive.