Protocol interfaces
Nine interface classes, each with an Async twin — eighteen classes in all.
None of them is constructed directly. Eight come from an accessor on
Device or AsyncDevice, which
caches it per device (and, for I2C, per bus — the controller and the target for
one bus are cached under distinct keys, so they are two objects rather than
one). The ninth, I3cCcc, is a sub-interface reached
through the I3cController.ccc property
rather than from the device.
Three of the accessors are themselves capability-guarded, and refusing at the
accessor is deliberate: a caller who cannot obtain the object cannot write the
rest of the program against it. Device.i3c() refuses on a Pulsar, and
Device.spi_target() refuses on a
hardware revision that cannot receive a chip select.
Accessor |
Blocking |
asyncio |
|---|---|---|
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
I2cTarget is reached per bus by design —
i2cTargetInit takes a bus id as its first parameter, which is forced by the
upstream surface rather than chosen. SpiTarget and
I3cTarget are not, because their peripherals are not.
The narrative counterparts are the protocol guides, and the two roles that share the bulk plane have their own page: Target mode.
I2C
- class pycosmicsdk.I2cController(*, device: Device, bus: I2cBus)
Bases:
objectBlocking controller for a single I2C bus of an open device.
Obtain one from
Device.i2c; the constructor is package-private. Instances are cached per bus on the device, so repeateddev.i2c(bus=...)calls return the same controller.- property max_transfer_bytes: int
Largest payload one
writeorreadcan carry on this connection.1024– the ordinary HID route’s ceiling – unless this host and device can both carry the I2C controller bulk pair, in which case it is the ceiling the device advertised for its I2C command group (32768 on a Supernova, 16384 on a Pulsar).Reading it runs the bulk negotiation if it has not run yet, which is at most one device query per connection.
- Returns:
The per-call ceiling in bytes.
- Raises:
DisconnectError – If the device is no longer open.
- initialize(frequency_hz: int, pull_up: I2cPullUp) I2cController
Bring the bus up as a controller and return
selffor chaining.Not idempotent: an already-initialised bus reports
FW_INTERFACE_ALREADY_INITIALIZED. The bus stays initialised across reconnects – the role lives in firmware, so it outlives this handle and this process – and a re-run hits this unless the bus is released or the device is reset first. Preferbring_up, which absorbs the status and reports whether the settings took, and useconfigureto change parameters of an already-initialised bus.Where a genuine reset of unknown state is wanted, release the bus rather than rebooting:
i2cDeinitis role-agnostic and per bus, soI2cTarget.releaseclears a controller on this bus too, even though it is spelled through the target factory –dev.i2c_target(bus=...).release(). The factories issue no commands, so reaching for that handle costs nothing, andFW_INTERFACE_NOT_INITIALIZEDfrom the release just means the bus was already idle, which is the state the caller wanted.- Parameters:
frequency_hz – SCL clock in Hz (e.g.
400_000for 400 kHz). Both supported adapters accept up to 1 MHz; an unsupported rate is rejected by the firmware asFW_INVALID_PARAMETERand surfaced asFirmwareError. Restores the default timing: on firmware 4.6.2 it runs at or below the request (550 kHz runs at 500 kHz), earlier firmware may run above it (550 kHz runs at 600 kHz).set_duty_cyclemakes it exact.pull_up – On-board pull-up resistor setting. Which members are accepted depends on the model, not on all thirteen being universal: firmware carries a different pull-up table per adapter, so ask
dev.capabilities.i2c_pull_ups(orDeviceCapabilities.supports_i2c_pull_up) when the code has to run on both.
- Returns:
This controller, to allow chaining.
- Raises:
ValidationError – If
frequency_hzdoes not fit auint32.CapabilityError – If the connected model’s firmware has no pull-up table row for
pull_up–I2cPullUp.KOHM_3_3,I2cPullUp.KOHM_4_7andI2cPullUp.KOHM_10_0are Supernova-only,I2cPullUp.KOHM_4_0is Pulsar-only. No facade call is made, which is the point: firmware validates the pull-up before bringing the bus up, so the round trip this replaces would have failed with a bareFW_INVALID_PARAMETERand left the bus down.DisconnectError – If the device is no longer open.
FirmwareError – If the bus is already initialised (
status_codeFW_INTERFACE_ALREADY_INITIALIZED), the bus id is unsupported (FW_BUS_NOT_SUPPORTED), or the firmware rejects the configuration (FW_INVALID_PARAMETER). Upstreami2cControllerInitreports no 0x02xx code, so bring-up never surfaces asI2cError.
- bring_up(frequency_hz: int, pull_up: I2cPullUp) BringUpResult
Bring the bus up as a controller, re-runnably, and report what took effect.
The composite over
initializeandconfigurethat a re-runnable script wants, and the recommended way to bring an I2C bus up. Call it instead ofinitializeunless you specifically need the primitive’s faithfulFW_INTERFACE_ALREADY_INITIALIZED.initializeis not idempotent and an I2C interface stays initialised across aclose()/reopen cycle, so the second run of any script is the common case rather than an edge case. This triesinitializefirst; onFW_INTERFACE_ALREADY_INITIALIZEDit reconfigures the live interface withconfigureinstead, and reports a refused reconfigure through the return value rather than raising.Refusal is not hypothetical and not one-shaped: a Supernova earlier than revision
Ccannot reconfigure a live I2C interface at all and is refused byconfigure’s capability guard before anything reaches the device, while a Pulsar can reconfigure but rejects some pull-up values. Both leave the bus up and usable, so both are reported. A pull-up this model has no firmware table row for is a different thing entirely – a bad argument for this adapter, which no fallback repairs – and itsCapabilityErrorpropagates frominitializeuntouched.One reported refusal is not a refused reconfigure, and it is the only one that can arrive with
already_initialized=False: apull_upasked for on a unit whosecapabilities.i2c_init_pull_up_effectiveisFalsecomes backsettings_applied=Falseeven on a fresh bring-up. Firmware answersSUCCESSand enables the board’s fixed network regardless, so nothing was refused on the wire – but the capability table knew the answer before the call, and a field documented as the one to consult must not affirm a setting this SDK knows is not in force. The bus is up andfrequency_hzdid apply. Every pull-up value is reported on such a unit,I2cPullUp.KOHM_10_0included;_init_pull_up_refusalargues why, and says what would settle it.- Parameters:
frequency_hz – SCL clock in Hz requested for the bus (e.g.
400_000for 400 kHz). With the default timing, on firmware 4.6.2 it runs at or below the request (550 kHz runs at 500 kHz), earlier firmware may run above it (550 kHz runs at 600 kHz).set_duty_cyclemakes it exact.pull_up – On-board pull-up resistor setting requested for the bus. Checked against
dev.capabilities.i2c_pull_upsbefore anything is sent, exactly asinitializechecks it.
- Returns:
A
BringUpResult. On return the bus is up and usable; consultsettings_appliedbefore relying onfrequency_hzorpull_upactually being in force, andrefusalfor why they are not. It is a report, not a promise: nothing here guarantees a setting the device accepted is in force, only that a refusal this SDK can see is not reported as a success.- Raises:
ValidationError – If
frequency_hzdoes not fit auint32.CapabilityError – If the connected model’s firmware has no pull-up table row for
pull_up. A refusal to reconfigure is reported in the result instead; this one is a bad argument, so it propagates.DisconnectError – If the device is no longer open.
FirmwareError – On any firmware failure other than the two the fallback absorbs – an unsupported bus id (
FW_BUS_NOT_SUPPORTED), for instance, or a frequencyinitializeitself rejects.I2cError – If the pull-up resistors could not be applied during the reconfigure (
FW_I2C_PULLUP_RESISTOR_SETTING_FAILURE).
- configure(frequency_hz: int, pull_up: I2cPullUp) None
Reconfigure an already-initialised bus at runtime.
The bus must have been initialised with
initialize; an uninitialised bus reportsFW_INTERFACE_NOT_INITIALIZEDand surfaces asFirmwareError. The interface stays up – no re-initializeis needed afterwards – but the peripheral itself does not: the firmware de-initialises the I2C block, applies the new baud rate, re-initialises it, then sets the pull-up. This is therefore the only lever on the public surface that resets the I2C peripheral (and so releases the bus lines) without restarting the firmware.Not every adapter can do this at all, and it depends on the hardware revision rather than the model. Ask
dev.capabilities.i2c_runtime_reconfigurablebefore calling: on a Supernova whose revision is anything butCit isFalse, and this method raisesCapabilityErrorbefore anything reaches the device.initializeis unaffected, so on such a unit the bus still comes up – it simply keeps the parameters that first bring-up chose, and the peripheral-reset lever is not reachable there.- Parameters:
frequency_hz – New SCL clock in Hz. Both supported adapters accept up to 1 MHz; an unsupported rate is rejected by the firmware as
FW_INVALID_PARAMETERand surfaced asFirmwareError. With the default timing, on firmware 4.6.2 it runs at or below the request (550 kHz runs at 500 kHz), earlier firmware may run above it (550 kHz runs at 600 kHz). A ratio set withset_duty_cycleis kept, and makes the rate exact.pull_up – New on-board pull-up resistor setting.
- Raises:
ValidationError – If
frequency_hzdoes not fit auint32.CapabilityError – If the adapter’s hardware revision cannot reconfigure a live I2C interface (a Supernova whose revision is anything but
C), or if the connected model’s firmware has no pull-up table row forpull_up. No facade call is made in either case, so this replaces the firmware round trip rather than following it.DisconnectError – If the device is no longer open.
FirmwareError – If the bus has not been initialised (
status_codeFW_INTERFACE_NOT_INITIALIZED), the bus id is unsupported (FW_BUS_NOT_SUPPORTED), the hardware lacks the feature (FW_FEATURE_NOT_SUPPORTED_BY_HARDWARE– pre-empted for every Supernova revision butC, so reachable only on hardware whose gate this SDK does not model), or the firmware rejects a parameter (FW_INVALID_PARAMETER). None of these is a 0x02xx code, so none surfaces asI2cError.I2cError – If the pull-up resistors could not be applied (
status_codeFW_I2C_PULLUP_RESISTOR_SETTING_FAILURE).
- set_duty_cycle(high_pct: int) I2cDutyCycle
Set the share of each SCL period the clock is held high.
Any ratio,
50included, also moves the bus to an exact clock: 550 kHz runs at 549.8, not the default timing’s 600.I2C_DUTY_CYCLE_DEFAULTandinitializereturn to the default timing.The frequency from
initializeorconfigurestays; only the high:low split changes, and the ratio survives laterconfigurecalls. With a ratio the frequency is never above the request and at most about 0.3 % under it.Values are not clamped to the I2C specification, so out-of-spec timing is available for margin testing:
30at 100 kHz gives a tHIGH of 3.0 us, under Standard-mode’s 4.0 us.- Parameters:
high_pct – SCL high time in percent of the period:
20to80in steps of10, orI2C_DUTY_CYCLE_DEFAULT(0) for the default timing. The firmware decides what is valid.- Returns:
The settings in effect after the call.
- Raises:
ValidationError – If
high_pctdoes not fit auint8. No facade call is made.DisconnectError – If the device is no longer open.
I2cDutyCycleRefusedError – If firmware refuses the value (
FW_INVALID_PARAMETER). The setting in effect is kept, and the error’sfrequency_hzandhigh_pctsay what it is.FirmwareError – If the bus is not an initialised controller (
FW_INTERFACE_NOT_INITIALIZED), or the firmware predates the command (FW_UNSUPPORTED_COMMAND, 4.5.1 and older).ProtocolError – If a
SUCCESSreply is too short to carry the settings (SDK_ERROR_WRONG_RESPONSE).
- set_pull_up(pull_up: I2cPullUp) None
Change the on-board pull-up resistors for this bus.
Gated by the same hardware-revision limit as
configure, so askdev.capabilities.i2c_runtime_reconfigurablefirst: on a Supernova whose revision is anything butCit isFalse, and this method raisesCapabilityErrorbefore anything reaches the device – the bus keeps the pull-upsinitializeselected.pull_upis additionally checked againstdev.capabilities.i2c_pull_ups, the per-model set firmware will accept.- Parameters:
pull_up – New pull-up resistor setting.
- Raises:
CapabilityError – If the adapter’s hardware revision cannot reconfigure a live I2C interface (a Supernova whose revision is anything but
C), or if the connected model’s firmware has no pull-up table row forpull_up. No facade call is made.DisconnectError – If the device is no longer open.
FirmwareError – If the bus has not been initialised (
FW_INTERFACE_NOT_INITIALIZED), the bus id is unsupported (FW_BUS_NOT_SUPPORTED), the hardware lacks the feature (FW_FEATURE_NOT_SUPPORTED_BY_HARDWARE– pre-empted for every Supernova revision butC, so reachable only on hardware whose gate this SDK does not model), or the value is rejected (FW_INVALID_PARAMETER).I2cError – If the pull-up resistors could not be applied (
status_codeFW_I2C_PULLUP_RESISTOR_SETTING_FAILURE).
- set_voltage(voltage_mv: int, *, persist: bool = False) None
Set the bus target voltage.
- Parameters:
voltage_mv – Target voltage in millivolts. The multifunction port accepts
0(rail off) and 1200-3300 mV; an out-of-range value is rejected by the firmware and surfaced asSystemFirmwareError. Setting0and then a voltage again power-cycles a DUT the adapter powers, the only way to recover a peripheral hung holding SDA low. That works only if nothing else drives the net: on a multi-adapter bench hand every other source off withuse_external_voltagefirst, then read the rail back. The rail is shared by I2C, SPI, UART and GPIO.persist – When
True, store the setting across power cycles.
- Raises:
ValidationError – If
voltage_mvdoes not fit auint16.DisconnectError – If the device is no longer open.
SystemFirmwareError – If the firmware rejects the voltage. Voltage rejection is
FW_VOLTAGE_OUT_OF_RANGE(0x0101), which sits in the 0x01xx system band shared by every interface, so it does not surface asI2cError. With firmware 4.7.0 or newer, a port that another source already powers is refused withFW_EXTERNAL_VOLTAGE_DETECTED(0x0102) and the rail stays off; calluse_external_voltageinstead.
- use_external_voltage(*, persist: bool = False) None
Hand the I2C rail over to an externally supplied voltage.
Tells the adapter that the multifunction VTARG rail — shared with SPI, UART and GPIO — comes from outside, so it stops driving it itself and measures the supplied rail instead. Read back what it measured with
get_bus_voltage.- Parameters:
persist – When
True, store the voltage measured now. After a power cycle the adapter drives the rail itself at that voltage; it does not store “external”.- Raises:
DisconnectError – If the device is no longer open.
SystemFirmwareError – If the firmware rejects the switch — a 0x01xx system-band code, the same band
set_voltagedocuments, so it does not surface asI2cError.
- get_bus_voltage() int
Read the voltage measured on the shared multifunction VTARG rail.
This is not an I2C-specific measurement. The adapter has one multifunction VTARG rail shared by I2C, SPI, UART and GPIO, and this reads that single rail, so the reading reflects whatever last set or is driving it — pair this with
use_external_voltage.SpiController.get_bus_voltage,Uart.get_bus_voltageandGpio.get_voltagereturn the very same number. Upstream implements all four by issuing onegetAnalogMeasurementscommand and returning itsvmeasVtargI2cSpiUartGpio_mVfield (CosmicSDK.cpp); the four names exist to keep each protocol’s surface self-contained, not because four rails are measured.I3cController.get_bus_voltageis the genuine exception: I3C has its own HV and LV rails and reports both.- Returns:
The measured voltage in millivolts.
- Raises:
DisconnectError – If the device is no longer open.
FirmwareError – On a firmware-reported failure. The measurement command reports no 0x02xx code, so this never surfaces as
I2cError.
- write(address: int, data: bytes, *, subaddress: bytes = b'', non_stop: bool = False, transport: I2cTransport = I2cTransport.AUTO) None
Write
datato a 7-bit target address.- Parameters:
address – 7-bit target address (
0x00–0x7F).data – Payload bytes. Must not exceed the active transport’s ceiling – 1024 bytes on the ordinary HID route, or the device’s advertised I2C bulk ceiling.
max_transfer_bytesreports which applies; a larger value raisesValidationErrorand is never split across two calls.subaddress – Optional register/sub-address sent before
data. The host adapter accepts at most 4 bytes; a longer value raisesProtocolError. It travels inside the request on either transport.non_stop – When
True, hold the bus with a repeated START instead of issuing a STOP after the transfer.transport – Which host transport to use.
I2cTransport.AUTO, the default, keeps the ordinary route for anything that fits its ceiling and reaches for the bulk plane only above it – seeI2cTransportfor why that is narrower thanSpiTransport.AUTO.
- Raises:
ValidationError – If
addressis not a 7-bit value, ordataexceeds the active transport’s ceiling.ProtocolError – If
subaddressis longer than 4 bytes.CapabilityError – If
transportisI2cTransport.BULKand the bulk transport cannot serve it – no vendor interface claimed here, firmware that does not advertise the command, or a build of this package with no I2C bulk binding. No facade call is made.DisconnectError – If the device is no longer open.
I2cError – On a firmware-reported failure (e.g. address NACK).
Note
The bulk route is blocking-only.
AsyncI2cController.writekeeps the 1024-byte ceiling and takes notransportargument – seeAsyncI2cController.
- read(address: int, length: int, *, subaddress: bytes = b'', non_stop: bool = False, transport: I2cTransport = I2cTransport.AUTO) bytes
Read
lengthbytes from a 7-bit target address.- Parameters:
address – 7-bit target address (
0x00–0x7F).length – Number of bytes to read. Must not exceed the active transport’s ceiling;
max_transfer_bytesreports which applies. A larger value raisesValidationErrorand the read is never split.subaddress – Optional register/sub-address sent before the read. The host adapter accepts at most 4 bytes; a longer value raises
ProtocolError.non_stop – When
True, hold the bus with a repeated START instead of issuing a STOP after the transfer. Carried on both transports – the bulk read takes it as a parameter exactly as the ordinary one does.transport – Which host transport to use.
I2cTransport.AUTO, the default, keeps the ordinary route for anything that fits its ceiling and reaches for the bulk plane only above it.
- Returns:
The bytes read from the target. May be shorter than ``length`` on a NACKed or short transfer: the count is the one the device announced, not the one that was requested, and upstream does not clamp the two together.
- Raises:
ValidationError – If
addressis not a 7-bit value, orlengthis invalid or exceeds the active transport’s ceiling.ProtocolError – If
subaddressis longer than 4 bytes, or – on the bulk route – if the reply was too short to carry its own announced length.CapabilityError – If
transportisI2cTransport.BULKand the bulk transport cannot serve it. No facade call is made.DisconnectError – If the device is no longer open.
I2cError – On a firmware-reported failure (e.g. address NACK).
Note
The bulk route is blocking-only, and for a second reason on top of the write’s: the reply only announces the length, and the data phase is then collected from the vendor IN endpoint by a call that blocks until it arrives.
AsyncI2cController.readtherefore keeps the 1024-byte ceiling – seeAsyncI2cController.
- scan(*, include_10bit: bool = False) I2cScanResult
Scan the bus and return the addresses that acknowledged.
- Parameters:
include_10bit – When
True, also probe 10-bit addresses.- Returns:
An
I2cScanResultwith the discovered 7-bit (and optionally 10-bit) addresses. Empty when nobody answered.- Raises:
DisconnectError – If the device is no longer open.
I2cError – On a firmware-reported failure.
- class pycosmicsdk.AsyncI2cController(*, device: AsyncDevice, bus: I2cBus)
Bases:
objectAsyncio I2C controller for a single I2C bus of an open device.
The asyncio twin of
I2cController, returned byAsyncDevice.i2c; the constructor is package-private. Instances are cached per bus on the device, so repeateddev.i2c(bus=...)calls return the same controller. Argument validation matches the blocking controller; commands are awaited and correlated through the device’s engine rather than serialized under a lock.Note
The rail surface matches
I2cControllerin full,get_bus_voltageincluded.CosmicAsyncSDKdeclares noi2cGetBusVoltage, but it does declaregetAnalogMeasurements, which carries the shared I2C/SPI/UART/GPIO rail — and that is the same command upstream’s blockingi2cGetBusVoltageissues internally. So the async reader is built the same way as the blocking one, and reports the same value.Note
The bulk plane is deliberately absent here.
writeandreadcarry notransportargument and keep the 1024-byte HID ceiling, where their blocking twins can reach the device’s advertised I2C bulk ceiling. That is a decision, not an oversight, and it is the same oneAsyncSpiControllerrecords for the SPI bulk pair: upstream’s async bulk write callssendPayloadinside the submit, so what looks like a non-blocking submit blocks the calling thread for the whole transfer, and the async bulk read cannot be expressed as one submit at all – the reply only announces the length, and a second, blocking call collects the data phase. On the asyncio path either one stalls the event loop thread.Use
I2cController.writeandI2cController.readfor a payload above 1024 bytes – from a thread, or throughloop.run_in_executorif the surrounding code is async.docs_dev/designs/async-bulk-transfer-shape.mdhas the analysis.- async initialize(frequency_hz: int, pull_up: I2cPullUp) AsyncI2cController
Bring the bus up as a controller and return
selffor chaining.Not idempotent: an already-initialised bus reports
FW_INTERFACE_ALREADY_INITIALIZED. The bus stays initialised across reconnects – the role lives in firmware, so it outlives this handle and this process – and a re-run hits this unless the bus is released or the device is reset first. Preferbring_up, which absorbs the status and reports whether the settings took, and useconfigureto change parameters of an already-initialised bus.Where a genuine reset of unknown state is wanted, release the bus rather than rebooting:
i2cDeinitis role-agnostic and per bus, soAsyncI2cTarget.releaseclears a controller on this bus too, even though it is spelled through the target factory –dev.i2c_target(bus=...).release(). The factories issue no commands, so reaching for that handle costs nothing, andFW_INTERFACE_NOT_INITIALIZEDfrom the release just means the bus was already idle, which is the state the caller wanted.- Parameters:
frequency_hz – SCL clock in Hz (e.g.
400_000for 400 kHz). Both supported adapters accept up to 1 MHz; an unsupported rate is rejected by the firmware asFW_INVALID_PARAMETERand surfaced asFirmwareError. Restores the default timing: on firmware 4.6.2 it runs at or below the request (550 kHz runs at 500 kHz), earlier firmware may run above it (550 kHz runs at 600 kHz).set_duty_cyclemakes it exact.pull_up – On-board pull-up resistor setting. Which members are accepted depends on the model, not on all thirteen being universal: firmware carries a different pull-up table per adapter, so ask
dev.capabilities.i2c_pull_ups(orDeviceCapabilities.supports_i2c_pull_up) when the code has to run on both.
- Returns:
This controller, to allow chaining.
- Raises:
ValidationError – If
frequency_hzdoes not fit auint32.CapabilityError – If the connected model’s firmware has no pull-up table row for
pull_up–I2cPullUp.KOHM_3_3,I2cPullUp.KOHM_4_7andI2cPullUp.KOHM_10_0are Supernova-only,I2cPullUp.KOHM_4_0is Pulsar-only. No facade call is made, which is the point: firmware validates the pull-up before bringing the bus up, so the round trip this replaces would have failed with a bareFW_INVALID_PARAMETERand left the bus down.DisconnectError – If the device is no longer open.
FirmwareError – If the bus is already initialised (
status_codeFW_INTERFACE_ALREADY_INITIALIZED), the bus id is unsupported (FW_BUS_NOT_SUPPORTED), or the firmware rejects the configuration (FW_INVALID_PARAMETER). Upstreami2cControllerInitreports no 0x02xx code, so bring-up never surfaces asI2cError.
- async bring_up(frequency_hz: int, pull_up: I2cPullUp) BringUpResult
Bring the bus up as a controller, re-runnably, and report what took effect.
The composite over
initializeandconfigurethat a re-runnable script wants, and the recommended way to bring an I2C bus up. Call it instead ofinitializeunless you specifically need the primitive’s faithfulFW_INTERFACE_ALREADY_INITIALIZED.initializeis not idempotent and an I2C interface stays initialised across aclose()/reopen cycle, so the second run of any script is the common case rather than an edge case. This triesinitializefirst; onFW_INTERFACE_ALREADY_INITIALIZEDit reconfigures the live interface withconfigureinstead, and reports a refused reconfigure through the return value rather than raising.Refusal is not hypothetical and not one-shaped: a Supernova earlier than revision
Ccannot reconfigure a live I2C interface at all and is refused byconfigure’s capability guard before anything reaches the device, while a Pulsar can reconfigure but rejects some pull-up values. Both leave the bus up and usable, so both are reported. A pull-up this model has no firmware table row for is a different thing entirely – a bad argument for this adapter, which no fallback repairs – and itsCapabilityErrorpropagates frominitializeuntouched.One reported refusal is not a refused reconfigure, and it is the only one that can arrive with
already_initialized=False: apull_upasked for on a unit whosecapabilities.i2c_init_pull_up_effectiveisFalsecomes backsettings_applied=Falseeven on a fresh bring-up. Firmware answersSUCCESSand enables the board’s fixed network regardless, so nothing was refused on the wire – but the capability table knew the answer before the call, and a field documented as the one to consult must not affirm a setting this SDK knows is not in force. The bus is up andfrequency_hzdid apply. Every pull-up value is reported on such a unit,I2cPullUp.KOHM_10_0included;_init_pull_up_refusalargues why, and says what would settle it.- Parameters:
frequency_hz – SCL clock in Hz requested for the bus (e.g.
400_000for 400 kHz). With the default timing, on firmware 4.6.2 it runs at or below the request (550 kHz runs at 500 kHz), earlier firmware may run above it (550 kHz runs at 600 kHz).set_duty_cyclemakes it exact.pull_up – On-board pull-up resistor setting requested for the bus. Checked against
dev.capabilities.i2c_pull_upsbefore anything is sent, exactly asinitializechecks it.
- Returns:
A
BringUpResult. On return the bus is up and usable; consultsettings_appliedbefore relying onfrequency_hzorpull_upactually being in force, andrefusalfor why they are not. It is a report, not a promise: nothing here guarantees a setting the device accepted is in force, only that a refusal this SDK can see is not reported as a success.- Raises:
ValidationError – If
frequency_hzdoes not fit auint32.CapabilityError – If the connected model’s firmware has no pull-up table row for
pull_up. A refusal to reconfigure is reported in the result instead; this one is a bad argument, so it propagates.DisconnectError – If the device is no longer open.
FirmwareError – On any firmware failure other than the two the fallback absorbs – an unsupported bus id (
FW_BUS_NOT_SUPPORTED), for instance, or a frequencyinitializeitself rejects.I2cError – If the pull-up resistors could not be applied during the reconfigure (
FW_I2C_PULLUP_RESISTOR_SETTING_FAILURE).
- async configure(frequency_hz: int, pull_up: I2cPullUp) None
Reconfigure an already-initialised bus at runtime.
The bus must have been initialised with
initialize; an uninitialised bus reportsFW_INTERFACE_NOT_INITIALIZEDand surfaces asFirmwareError. The interface stays up – no re-initializeis needed afterwards – but the peripheral itself does not: the firmware de-initialises the I2C block, applies the new baud rate, re-initialises it, then sets the pull-up. This is therefore the only lever on the public surface that resets the I2C peripheral (and so releases the bus lines) without restarting the firmware.Not every adapter can do this at all, and it depends on the hardware revision rather than the model. Ask
dev.capabilities.i2c_runtime_reconfigurablebefore calling: on a Supernova whose revision is anything butCit isFalse, and this method raisesCapabilityErrorbefore anything reaches the device.initializeis unaffected, so on such a unit the bus still comes up – it simply keeps the parameters that first bring-up chose, and the peripheral-reset lever is not reachable there.- Parameters:
frequency_hz – New SCL clock in Hz. Both supported adapters accept up to 1 MHz; an unsupported rate is rejected by the firmware as
FW_INVALID_PARAMETERand surfaced asFirmwareError. With the default timing, on firmware 4.6.2 it runs at or below the request (550 kHz runs at 500 kHz), earlier firmware may run above it (550 kHz runs at 600 kHz). A ratio set withset_duty_cycleis kept, and makes the rate exact.pull_up – New on-board pull-up resistor setting.
- Raises:
ValidationError – If
frequency_hzdoes not fit auint32.CapabilityError – If the adapter’s hardware revision cannot reconfigure a live I2C interface (a Supernova whose revision is anything but
C), or if the connected model’s firmware has no pull-up table row forpull_up. No facade call is made in either case, so this replaces the firmware round trip rather than following it.DisconnectError – If the device is no longer open.
FirmwareError – If the bus has not been initialised (
status_codeFW_INTERFACE_NOT_INITIALIZED), the bus id is unsupported (FW_BUS_NOT_SUPPORTED), the hardware lacks the feature (FW_FEATURE_NOT_SUPPORTED_BY_HARDWARE– pre-empted for every Supernova revision butC, so reachable only on hardware whose gate this SDK does not model), or the firmware rejects a parameter (FW_INVALID_PARAMETER). None of these is a 0x02xx code, so none surfaces asI2cError.I2cError – If the pull-up resistors could not be applied (
status_codeFW_I2C_PULLUP_RESISTOR_SETTING_FAILURE).
- async set_duty_cycle(high_pct: int) I2cDutyCycle
Set the share of each SCL period the clock is held high.
Any ratio,
50included, also moves the bus to an exact clock: 550 kHz runs at 549.8, not the default timing’s 600.I2C_DUTY_CYCLE_DEFAULTandinitializereturn to the default timing.The frequency from
initializeorconfigurestays; only the high:low split changes, and the ratio survives laterconfigurecalls. With a ratio the frequency is never above the request and at most about 0.3 % under it.Values are not clamped to the I2C specification, so out-of-spec timing is available for margin testing:
30at 100 kHz gives a tHIGH of 3.0 us, under Standard-mode’s 4.0 us.- Parameters:
high_pct – SCL high time in percent of the period:
20to80in steps of10, orI2C_DUTY_CYCLE_DEFAULT(0) for the default timing. The firmware decides what is valid.- Returns:
The settings in effect after the call.
- Raises:
ValidationError – If
high_pctdoes not fit auint8. No facade call is made.DisconnectError – If the device is no longer open.
I2cDutyCycleRefusedError – If firmware refuses the value (
FW_INVALID_PARAMETER). The setting in effect is kept, and the error’sfrequency_hzandhigh_pctsay what it is.FirmwareError – If the bus is not an initialised controller (
FW_INTERFACE_NOT_INITIALIZED), or the firmware predates the command (FW_UNSUPPORTED_COMMAND, 4.5.1 and older).ProtocolError – If a
SUCCESSreply is too short to carry the settings (SDK_ERROR_WRONG_RESPONSE).
- async set_pull_up(pull_up: I2cPullUp) None
Change the on-board pull-up resistors for this bus.
Gated by the same hardware-revision limit as
configure, so askdev.capabilities.i2c_runtime_reconfigurablefirst: on a Supernova whose revision is anything butCit isFalse, and this method raisesCapabilityErrorbefore anything reaches the device – the bus keeps the pull-upsinitializeselected.pull_upis additionally checked againstdev.capabilities.i2c_pull_ups, the per-model set firmware will accept.- Parameters:
pull_up – New pull-up resistor setting.
- Raises:
CapabilityError – If the adapter’s hardware revision cannot reconfigure a live I2C interface (a Supernova whose revision is anything but
C), or if the connected model’s firmware has no pull-up table row forpull_up. No facade call is made.DisconnectError – If the device is no longer open.
FirmwareError – If the bus has not been initialised (
FW_INTERFACE_NOT_INITIALIZED), the bus id is unsupported (FW_BUS_NOT_SUPPORTED), the hardware lacks the feature (FW_FEATURE_NOT_SUPPORTED_BY_HARDWARE– pre-empted for every Supernova revision butC, so reachable only on hardware whose gate this SDK does not model), or the value is rejected (FW_INVALID_PARAMETER).I2cError – If the pull-up resistors could not be applied (
status_codeFW_I2C_PULLUP_RESISTOR_SETTING_FAILURE).
- async set_voltage(voltage_mv: int, *, persist: bool = False) None
Set the bus target voltage.
- Parameters:
voltage_mv – Target voltage in millivolts. The multifunction port accepts
0(rail off) and 1200-3300 mV; an out-of-range value is rejected by the firmware and surfaced asSystemFirmwareError. Setting0and then a voltage again power-cycles a DUT the adapter powers, the only way to recover a peripheral hung holding SDA low. That works only if nothing else drives the net: on a multi-adapter bench hand every other source off withuse_external_voltagefirst, then read the rail back. The rail is shared by I2C, SPI, UART and GPIO.persist – When
True, store the setting across power cycles.
- Raises:
ValidationError – If
voltage_mvdoes not fit auint16.DisconnectError – If the device is no longer open.
SystemFirmwareError – If the firmware rejects the voltage. Voltage rejection is
FW_VOLTAGE_OUT_OF_RANGE(0x0101), which sits in the 0x01xx system band shared by every interface, so it does not surface asI2cError. With firmware 4.7.0 or newer, a port that another source already powers is refused withFW_EXTERNAL_VOLTAGE_DETECTED(0x0102) and the rail stays off; calluse_external_voltageinstead.
- async use_external_voltage(*, persist: bool = False) None
Hand the I2C rail over to an externally supplied voltage.
Tells the adapter that the multifunction VTARG rail — shared with SPI, UART and GPIO — comes from outside, so it stops driving it itself and measures the supplied rail instead.
- Parameters:
persist – When
True, store the voltage measured now. After a power cycle the adapter drives the rail itself at that voltage; it does not store “external”.- Raises:
DisconnectError – If the device is no longer open.
SystemFirmwareError – If the firmware rejects the switch — a 0x01xx system-band code, the same band
set_voltagedocuments, so it does not surface asI2cError.
Note
Read back what the adapter measured with
get_bus_voltage.
- async get_bus_voltage() int
Read the voltage measured on the shared multifunction VTARG rail.
The asyncio twin of
I2cController.get_bus_voltage, with the same return type and the same meaning. It is not an I2C-specific measurement: the adapter has one multifunction VTARG rail shared by I2C, SPI, UART and GPIO, andAsyncSpiController.get_bus_voltage,AsyncUart.get_bus_voltageandAsyncGpio.get_voltagereturn the very same number. Pair this withuse_external_voltage.Implemented over
getAnalogMeasurements, which reads every rail in one transaction; this keeps the shared-rail field and discards the rest, exactly as upstream’s blockingi2cGetBusVoltagedoes. UseAsyncDevice.get_analog_measurementsto get all six rails at once instead of one field per call.- Returns:
The measured voltage in millivolts.
- Raises:
DisconnectError – If the device is no longer open.
ProtocolError – If the response was too short to carry the measurements, which is also how firmware that does not implement the command reports itself.
CosmicTimeoutError – If the request exceeds the per-request timeout.
FirmwareError – On a firmware-reported failure. The measurement command reports no 0x02xx code, so this never surfaces as
I2cError.
- async write(address: int, data: bytes, *, subaddress: bytes = b'', non_stop: bool = False) None
Write
datato a 7-bit target address.- Parameters:
address – 7-bit target address (
0x00–0x7F).data – Payload bytes. Must not exceed the device’s maximum I2C payload (
AsyncDevice.capabilities.max_i2c_payload_bytes); a larger value raisesValidationError.subaddress – Optional register/sub-address sent before
data. The host adapter accepts at most 4 bytes; a longer value raisesProtocolError.non_stop – When
True, hold the bus with a repeated START instead of issuing a STOP after the transfer.
- Raises:
ValidationError – If
addressis not a 7-bit value, ordataexceeds the device’s maximum payload.ProtocolError – If
subaddressis longer than 4 bytes.DisconnectError – If the device is no longer open.
I2cError – On a firmware-reported failure (e.g. address NACK).
- async read(address: int, length: int, *, subaddress: bytes = b'', non_stop: bool = False) bytes
Read
lengthbytes from a 7-bit target address.- Parameters:
address – 7-bit target address (
0x00–0x7F).length – Number of bytes to read. Must not exceed the device’s maximum I2C payload (
AsyncDevice.capabilities.max_i2c_payload_bytes); a larger value raisesValidationError.subaddress – Optional register/sub-address sent before the read. The host adapter accepts at most 4 bytes; a longer value raises
ProtocolError.non_stop – When
True, hold the bus with a repeated START instead of issuing a STOP after the transfer.
- Returns:
The bytes read from the target.
- Raises:
ValidationError – If
addressis not a 7-bit value, orlengthis invalid or exceeds the device’s maximum payload.ProtocolError – If
subaddressis longer than 4 bytes.DisconnectError – If the device is no longer open.
I2cError – On a firmware-reported failure (e.g. address NACK).
- async scan(*, include_10bit: bool = False) I2cScanResult
Scan the bus and return the addresses that acknowledged.
- Parameters:
include_10bit – When
True, also probe 10-bit addresses.- Returns:
An
I2cScanResultwith the discovered 7-bit (and optionally 10-bit) addresses. Empty when nobody answered.- Raises:
DisconnectError – If the device is no longer open.
I2cError – On a firmware-reported failure.
I2C — target mode
- class pycosmicsdk.I2cTarget(*, device: Device, bus: I2cBus)
Bases:
objectBlocking I2C target-mode interface for one I2C bus of an open device.
Obtain one from
Device.i2c_target; the constructor is package-private. Instances are cached per bus on the device, so repeateddev.i2c_target(bus=...)calls return the same interface.In target mode the adapter answers an external controller instead of driving one, serving reads and writes out of an arena drawn from the shared bulk-staging pool – 32 KB on a Supernova, 16 KB on a Pulsar, reported by
initializeand readable fromarena_size. Preload it withwrite_memoryand read back what the controller wrote withread_memory. Which of three device kinds the arena presents on the bus is themodelargument’s job: a flat memory, a 24Cxx-style EEPROM, or a register map, each configured by its own follow-up method.The accessor is per bus because
i2cTargetInittakesbusIdas its first parameter – forced by the surface, not chosen. Note that getting this object is free and `initialize` is the act that takes the role;releaseis what gives it back, and a caller who is not sure should read the comment block above this class before calling either.Three ways this differs from
I3cTarget, each of which will bite a reader who assumes the two are the same shape:``write_memory``’s ``address`` is a byte offset, where
I3cTarget’s is a register index the firmware multiplies by the layout’s register size. Same name, same type, different unit.``address_width`` scales nothing here. It is upstream’s
memoryLayout, and it selects how many address bytes the external controller sends on the bus: 8-bit confines the bus-visible window to arena bytes 0..255, 16-bit exposes the whole arena. The name was changed deliberately, becausememory_layoutwould collide in a reader’s head with the I3C concept that does scale.The arena is bulk-only, so
initializecan refuse on a host thatI3cTarget.initializewould have served – see item 3 of the comment block above.
Note
Failures here surface as
I2cError, not the bareFirmwareErrorthe I3C target’s docstrings have to promise: every target-mode status firmware reports sits in the 0x02xx band both translators map toI2cError. The exceptions areFW_INTERFACE_ALREADY_INITIALIZED(0x0007, generic, soFirmwareError) andSDK_ERROR_BULK_UNAVAILABLE, which this interface pre-empts with aCapabilityErrorbefore anything is sent.Note
One target at a time, and one role per bus – released explicitly. The arena is drawn from the device’s shared bulk-staging pool and is held while a target is armed, so an I2C target and a SPI target cannot both be up: the second answers
FW_SPI_TARGET_STAGING_UNAVAILABLE. And on a single adapter an I2C target and an ordinary I2C controller cannot both be up in either order:initializerefuses while any role is up on either bus, and once it holds the roleI2cController.initializerefuses in turn. Both are firmware behaviour, and both are cleared byreleaserather than by a power cycle – so either pairing works sequentially in one session. Mind the asymmetry when recovering: the target guard is per device whilereleaseis per bus, so freeing a two-bus adapter for a target bring-up takes areleaseon each. The comment block above this class has the citations, and says which part of the ordering is a reading of the guard sites rather than a bench result.Note
I2cTargetBusEventNotification– subscribe to it throughDevice.subscribe– declares metadata only:event,target_address,memory_addressandpayload_length. Firmware appends the transferred bytes only to the I3C bus event, so they are not in this one; usepayload_lengthto learn how much moved andread_memoryto fetch them, accepting that the arena is live, so the region may already have been overwritten by the time the read lands.Do not read the inherited ``payload``. It exists, typed
bytes, and it is neither empty nor the transferred data: it is the entire raw notification frame, twelve bytes for every well-formed event, and its length is unrelated topayload_length. The I3C twin’spayloadgenuinely is the transferred data, which is exactly why this one misleads. The comment block above this class has the citations, andI2cTargetBusEvent– whatwait_for_eventreturns – deliberately has nopayloadfield so the mistake is unreachable from it.- property arena_size: int | None
Capacity of this target’s arena in bytes, as the device reported it.
Noneuntil this object has brought the interface up, which is the same fail-open postureI3cTarget.write_memorytakes: an adapter another process left in target mode has an arena whose size this object cannot know, andwrite_memorythen bounds the call on the transport ceiling alone rather than refusing.One consequence worth knowing: a bare
initializethat raisesFW_INTERFACE_ALREADY_INITIALIZEDleaves this unset, because upstream leavesmemorySizeat 0 on that path.bring_upis the only single call that always populates it, which is one more reason it is the recommended entry point.
- property max_transfer_bytes: int
Largest payload one
write_memoryorread_memorycall may carry.min(arena_size, advertised ceiling)– the pair, never upstream’s compile-timeI2C_BULK_MAX_PAYLOAD, because the ceiling differs between products and a host that assumes one product’s number gets its oversized requests refused on another.0when the arena is unreachable on this connection, since no size is honest then.Reading it runs the bulk negotiation if it has not run yet, which is at most one device query per interface.
- Returns:
The per-call ceiling in bytes, or
0.- Raises:
DisconnectError – If the device is no longer open.
- initialize(*, address: int, address_mode: I2cTargetAddressMode, address_width: I2cTargetMemoryLayout, model: I2cTargetModel, require_arena_access: bool = True) I2cTarget
Arm the adapter as an I2C target on this bus and return
self.The faithful primitive. Not idempotent: an adapter already in I2C target mode reports
FW_INTERFACE_ALREADY_INITIALIZED, and an I2C interface stays initialised across aclose()/reopen cycle, so a re-run hits that. Preferbring_upunless you specifically need the primitive’s faithfulFW_INTERFACE_ALREADY_INITIALIZED; useconfigureto change parameters of a target that is already up.This takes the device’s I2C role, and
releaseis what gives it back. The refusal a controller gets meanwhile isFW_I2C_ROLE_CONFLICT, and for the target direction the exclusivity is per device, not per bus: a controller up on either bus refuses this call, and clearing that takes areleaseon the bus holding it. The comment block above this class has the detail.Bus-event capture is armed before the role switch, so a transaction driven the instant the target comes up is queued rather than lost, and a switch that then fails leaves nothing installed behind it: the arm is undone unless the failure is one that may still have left a target servicing the bus.
_role_may_be_livenames those two and says why.- Parameters:
address – Address the target answers to –
0x08-0x77in 7-bit mode (the ends are the I2C reserved blocks), or0x000-0x3FFin 10-bit mode.address_mode – 7-bit or 10-bit addressing.
address_width – How many address bytes the external controller sends (upstream’s
memoryLayout). 8-bit confines the bus-visible window to arena bytes 0..255; 16-bit exposes the whole arena. It does not scalewrite_memory’s address.model – Which device kind the arena presents on the bus. Configure it afterwards with
configure_eepromoradd_register_region; the flat-memory model needs no follow-up.require_arena_access – When
True(the default), refuse before arming if this host and device cannot carry arena traffic. PassFalseto arm a target the host will never seed or read – a real but narrow case, and the reason the check is a default rather than a hard rule.
- Returns:
This interface, to allow chaining.
- Raises:
ValidationError – If
addressis outside the range its mode allows, or does not fit auint16. No facade call is made.CapabilityError – If
require_arena_accessis set and the vendor bulk transport cannot carry arena traffic on this connection, or if this build carries no I2C target-mode surface. The role is not taken in either case.DisconnectError – If the device is no longer open.
FirmwareError – If the adapter is already an I2C target (
FW_INTERFACE_ALREADY_INITIALIZED).I2cError – If the other I2C role owns the peripheral (
FW_I2C_ROLE_CONFLICT) or the arena could not be drawn from the shared staging pool (FW_I2C_TARGET_STAGING_UNAVAILABLE).
- configure(*, address: int, address_mode: I2cTargetAddressMode, address_width: I2cTargetMemoryLayout, model: I2cTargetModel) None
Reconfigure a live I2C target in place, keeping the arena contents.
The idempotent way to re-run configuration, in upstream’s own words, and the reason
bring_uphas a working fallback where the I3C target has none. It re-arms the target with a new address, width or model without discarding what is in the arena – but it does reset the register-map region table and the address pointer, so everyadd_register_regioncall has to be re-made afterwards.- Parameters:
address – New address the target answers to.
address_mode – 7-bit or 10-bit addressing.
address_width – How many address bytes the external controller sends. See
initialize.model – Which device kind the arena presents on the bus.
- Raises:
ValidationError – If
addressis outside the range its mode allows. No facade call is made.CapabilityError – If this build carries no I2C target-mode surface.
DisconnectError – If the device is no longer open.
FirmwareError – On a firmware-reported failure – notably when no I2C target is up on this bus. Deliberately the base class and not
I2cError:i2cTargetSetParametersdocuments no failure beyond the two transport errors, so nothing is known to land in the 0x02xx band here. That absence is also whybring_up’s reconfigure arm absorbs nothing.
- bring_up(*, address: int, address_mode: I2cTargetAddressMode, address_width: I2cTargetMemoryLayout, model: I2cTargetModel, require_arena_access: bool = True) BringUpResult
Arm the target re-runnably, and report whether it was already up.
The composite over
initializeandconfigure, and the recommended way to bring an I2C target up. Call it instead ofinitializeunless you specifically need the primitive’s faithfulFW_INTERFACE_ALREADY_INITIALIZED.It is also the only single call that always leaves
arena_sizepopulated: upstream leavesmemorySizeat 0 on the already-initialised path, and the reconfigure this falls back to reports it.Unlike the four controller composites this has no refused-status filter, because
i2cTargetSetParametersdocuments no refusal status to put in one.settings_appliedis therefore alwaysTruewhilealready_initializedvaries – and the varying field is the one a re-runnable script cares about. The comment block above this class argues it at length; do not add a filter out of symmetry withI2cController.bring_up.FW_I2C_ROLE_CONFLICTis never absorbed. A result claiming the target is up while the adapter drives the bus as a controller would be a lie no caller can detect, so it propagates asI2cError.- Parameters:
address – Address the target answers to.
address_mode – 7-bit or 10-bit addressing.
address_width – How many address bytes the external controller sends. See
initialize.model – Which device kind the arena presents on the bus.
require_arena_access – Passed through to
initialize.
- Returns:
A
BringUpResult. On return the target is armed;already_initializedsays whether this call armed it or found it armed already.- Raises:
ValidationError – If
addressis outside the range its mode allows.CapabilityError – If the arena is unreachable and
require_arena_accessis set, or if this build carries no I2C target-mode surface.DisconnectError – If the device is no longer open.
FirmwareError – On any firmware failure other than the one the fallback absorbs.
I2cError – If the other I2C role owns the peripheral (
FW_I2C_ROLE_CONFLICT) or the staging arena was unavailable (FW_I2C_TARGET_STAGING_UNAVAILABLE). Neither is absorbed: the first would misreport the bus role and the second leaves no arena to reconfigure.
- release() None
Release this bus’s I2C role, and the arena with it.
i2cDeinitin upstream’s terms, and the call that makes a role conflict recoverable. After it the bus holds no role, so the other role initialises normally – an in-session release, with no reboot and no freshDevice.It releases whichever role the bus holds, not specifically a target one. Firmware dispatches on its own per-bus role variable: a bus up as a controller is torn down by this same call. The method lives here because this is where the need arises, and because it is the escape from the per-device guard described below – not because the role has to be a target’s.
The exclusivity is per device but the release is per bus, which is the asymmetry to plan around.
i2cTargetInitrefuses while any role is up on either bus, whilei2cDeinitclears one bus. So freeing a two-bus adapter enough for a target bring-up means releasing both buses, each through its own interface:dev.i2c_target(bus=I2cBus.A).release() dev.i2c_target(bus=I2cBus.B).release()
A controller bring-up needs less: it tests only its own bus, so releasing that one bus is enough. The comment block above this class has the firmware citations, and flags which part of the ordering is a reading of the guard sites rather than a bench result.
What this object forgets.
arena_sizeand the remembered model go back toNone, because the arena they describe is gone – firmware’s teardown hands the shared bulk-staging pool back, which is what lets the other protocol enter target mode. They are cleared even when the call fails, since a refusal or a timeout leaves the arena’s fate unknown andNoneis this class’s word for unknown.max_transfer_bytestherefore falls back to the advertised ceiling, exactly as it does before a bring-up.Event capture is torn down too – the subscription is cancelled and any thread parked in
wait_for_eventis woken withDisconnectError, followingUart.deinit– but only when the release succeeded. A release that failed may have left a live target whose events are still the only record that a transaction happened, and those must not be dropped.What it does not forget is the bulk negotiation. That latch records whether this host holds the adapter’s vendor interface and what the device advertises per group; neither changes when a role is released, and the latch is shared with
I2cController.Note
Nothing is absorbed. In particular a bus holding no role answers
FW_INTERFACE_NOT_INITIALIZEDrather than succeeding quietly, matchingUart.deinit; a caller who wants an idempotent release catches that one code.- Raises:
DisconnectError – If the device is no longer open.
FirmwareError – If the bus held no role (
FW_INTERFACE_NOT_INITIALIZED), the device has no such bus (FW_BUS_NOT_SUPPORTED), or the firmware predates the command (FW_UNSUPPORTED_COMMAND). All three are generic codes below every protocol band, so none surfaces asI2cError. The last is the one a caller cannot pre-empt: firmware’s version string is a compile-time constant, so a build that dispatches the command and one that does not both report the same version.
- configure_eeprom(*, total_size: int, page_size: int, write_cycle_us: int, erase_cycle_us: int, busy_behavior: I2cTargetEepromBusyBehavior, erased_value: int = 0xFF, write_protect: tuple[int, int] | None = None) None
Configure the EEPROM emulation model: page wrap, busy timing, write protect.
Valid only while the active model is
EEPROM. When this object chose the model, a wrong one is refused here before anything is sent; when the model is unknown – an adapter another process left in target mode – the command is sent and firmware answersFW_I2C_TARGET_WRONG_MODEL.The timing arguments keep upstream’s microseconds and say so in their names, deliberately breaking with
wait_for_event(timeout=...)’s float seconds: these are the emulated part’s datasheet numbers, so a user copying “5 ms write cycle” from a 24Cxx datasheet types5000.- Parameters:
total_size – Addressable size in bytes; must not exceed the arena. Reads and writes wrap here.
page_size – Page-write wrap granularity in bytes. Must be a power of two.
write_cycle_us – Program time in microseconds. A data write is busy for
erase_cycle_us + write_cycle_us.erase_cycle_us – Erase time in microseconds. An explicit
eraseis busy for this alone.busy_behavior – Whether the target NACKs its address until done (ack-polling) or holds SCL low.
erased_value – Value of an erased byte, typically
0xFF. Also the fill value a write-only or reserved register region reads back.write_protect –
(start, end)withendexclusive, orNonefor no write protection.Nonesends the zero-length span upstream documents as disabling it, rather than a sentinel.
- Raises:
ValidationError – If a numeric argument does not fit its field, if
page_sizeis not a power of two, if the write-protect span is inverted, or if this object knows the active model is notEEPROM. No facade call is made.CapabilityError – If this build carries no I2C target-mode surface.
DisconnectError – If the device is no longer open.
I2cError – If the active model is not
EEPROM(FW_I2C_TARGET_WRONG_MODEL) or firmware rejects the configuration (FW_I2C_TARGET_INVALID_CONFIG).
- erase(*, scope: I2cTargetEraseScope, address: int = 0) None
Fill a page or the whole arena with the configured erased value.
Valid only in the EEPROM model, and gated the same way
configure_eepromis. The target enters its busy window forerase_cycle_usafterwards, so an external controller sees the behaviourbusy_behaviorselected.- Parameters:
scope – Erase one page or the whole arena.
address – For page scope, any address inside the page to erase. Ignored for chip scope.
- Raises:
ValidationError – If
addressdoes not fit auint16, or if this object knows the active model is notEEPROM. No facade call is made.CapabilityError – If this build carries no I2C target-mode surface.
DisconnectError – If the device is no longer open.
I2cError – If the active model is not
EEPROM(FW_I2C_TARGET_WRONG_MODEL).
- add_register_region(*, start: int, end: int, access: I2cTargetRegisterAccess, width: int, auto_increment: bool = True) None
Add one region descriptor to the REGISTER_MAP model.
“Add” rather than “set”, because upstream adds one descriptor per call, up to sixteen, and the table is cleared only by
configure. Bytes outside every defined region default to read/write. The register values themselves live in the arena, so preload their defaults withwrite_memory.- Parameters:
start – Region start address, inclusive.
end – Region end address, exclusive. Must exceed
startand must not exceed the arena.access – Access policy for the span.
width – Register width in bytes: 1 or 2.
auto_increment – When
True(the default) the register pointer advances through the region as the controller clocks bytes.Falsepins it, and a pinned region serves the region’swidthbytes per access rather than one – so a 16-bit-wide pinned region still returns two bytes before wrapping to itself. Exposed as a boolean because upstream’sflagsbyte reserves every other bit and asks callers to send zero.
- Raises:
ValidationError – If the span is empty or inverted, if
widthis neither 1 nor 2, or if this object knows the active model is notREGISTER_MAP. No facade call is made.CapabilityError – If this build carries no I2C target-mode surface.
DisconnectError – If the device is no longer open.
I2cError – If the active model is not
REGISTER_MAP(FW_I2C_TARGET_WRONG_MODEL), the sixteen-region limit is reached (FW_I2C_TARGET_REGION_TABLE_FULL), or the descriptor is rejected (FW_I2C_TARGET_INVALID_CONFIG).
- write_memory(address: int, data: bytes) None
Preload
datainto the target’s arena over the vendor bulk endpoint.Seeds the bytes an external controller will read back. ``address`` is a byte offset into the arena, not a register index – the trap this interface shares a signature with and not a unit:
I3cTargetscales its address by the register size and this does not.The whole arena moves in one call on both products, because each model’s advertised ceiling equals its arena. Nothing is ever split, and the refusal says so – but unlike an SPI controller transfer you may safely split this yourself by address, because an arena write is a host-to-adapter memory write that generates no bus traffic at all.
Note
Once this returns, the arena is guaranteed bus-ready. Firmware defers its reply until the target’s DMA descriptor chain has re-armed, precisely so a controller cannot clock stale bytes after a preload, so no caller-side settle delay is ever necessary – needing one indicates a firmware regression rather than something to work around here.
Note
Blocking-only. There is deliberately no
AsyncI2cTarget.write_memory– seeAsyncI2cTarget.- Parameters:
address – Byte offset into the arena to start writing at.
data – Payload bytes. Must not be empty, must fit the arena from
addressonwards, and must not exceedmax_transfer_bytes.
- Raises:
ValidationError – If
datais empty, exceeds the negotiated ceiling, or would run past the end of the arena. No facade call is made.CapabilityError – If the vendor bulk transport cannot carry arena traffic on this connection – there is no HID fallback – or if this build carries no I2C target-mode surface.
DisconnectError – If the device is no longer open.
I2cError – On a firmware-reported failure, notably
FW_I2C_TARGET_SURPASSED_MEMORY_SIZEwhen the arena is smaller than this object believes.
- read_memory(address: int, length: int) bytes
Read
lengthbytes back out of the target’s arena.Reads what an external controller has written.
addressis a byte offset, exactly as inwrite_memory.Note
Nothing stops the external controller overwriting the region between a bus event and this read: the arena is live while the target is armed. A read reports what the arena holds now, which is not necessarily what any one transaction put there.
Note
Blocking-only. There is deliberately no
AsyncI2cTarget.read_memory– seeAsyncI2cTarget.- Parameters:
address – Byte offset into the arena to start reading from.
length – Number of bytes to read. Must be at least 1, must fit the arena from
addressonwards, and must not exceedmax_transfer_bytes.
- Returns:
The bytes read out of the arena.
- Raises:
ValidationError – If
lengthis zero, exceeds the negotiated ceiling, or would run past the end of the arena. No facade call is made.CapabilityError – If the vendor bulk transport cannot carry arena traffic on this connection, or if this build carries no I2C target-mode surface.
DisconnectError – If the device is no longer open.
I2cError – On a firmware-reported failure, notably
FW_I2C_TARGET_SURPASSED_MEMORY_SIZE.
- wait_for_event(*, timeout: float | None = None) I2cTargetBusEvent
Return the oldest queued bus event, waiting for one if none is queued.
The pull path for what an external controller does to this target. Events are queued as the firmware reports them and each call pops exactly one, oldest first. For push delivery subscribe to
I2cTargetBusEventNotificationviaDevice.subscribe; for the whole queue at once usedrain_events.One completed transaction produces exactly one event: firmware emits one per transfer phase and flushes at the STOP, and an ordinary write or read is a single phase. An address-only transaction produces none, and nothing arrives while a
non_stop=Truetransfer holds the bus, sotimeout=Noneafter such a write blocks until unrelated code issues the STOP. The event carries no bytes; read them from the arena, which costs a bulk round trip and races the controller:event = target.wait_for_event(timeout=1.0) data = target.read_memory(event.memory_address, event.payload_length)
There is deliberately no
read_event_payloadconvenience: it would hide that round trip and its race behind what reads like a field access.Note
Safe to call concurrently with competing-consumer semantics; each event is delivered to exactly one waiter, non-deterministically.
- Parameters:
timeout – Maximum seconds to wait when nothing is queued.
Nonewaits indefinitely;0returns immediately; a positive value waits up to that long. A negative value is rejected.- Returns:
The oldest queued bus event.
- Raises:
ValidationError – If
timeoutis negative.DisconnectError – If the device is not open, or if
Device.closeis called while this call is blocked waiting for an event.CosmicTimeoutError – If no event arrives within
timeout(only whentimeoutis notNone).
- drain_events() list[I2cTargetBusEvent]
Return every queued bus event at once, oldest first, without waiting.
The batch counterpart of
wait_for_event, and the reason not to write the loop it replaces:while True: # don't try: handle(target.wait_for_event(timeout=0)) except CosmicTimeoutError: break for event in target.drain_events(): # do handle(event)
Order is preserved. One completed transaction is exactly one event (see
wait_for_event), so the sequence is a sequence of transactions. Order still carries meaning in the one case where a controller-level operation does yield two: anon_stoptransfer defers both its phases to whatever eventually issues the STOP, and they arrive together, in phase order. Either way a burst serviced between two calls comes back whole.It also arms event capture, which is the point of having it. Capture is installed lazily:
initializearms before the role switch, so a script that brings its own target up never has a gap. A script that attaches to a target another process armed has no such moment, and until something subscribes the device has installed no notification bridge at all – so a transaction driven before the firstwait_for_eventis not queued, not counted, and not reported. Calling this first is the fix, and it is worth calling for its own sake anyway: it establishes a known-empty baseline, so the events that follow are attributable to what you did next:target = dev.i2c_target(bus=I2cBus.A) # already up elsewhere target.drain_events() # arm, and discard history drive_the_bus() for event in target.drain_events(): ...
There is deliberately no ``arm_events()``. An arming call with no observable result fails the same silent way the missing arm does – nothing tells you that you forgot it – so every arm on this surface is a side effect of a call that does something a caller can see.
releaseis the mirror: it tears capture down on success and does not re-arm, because no bus event can arrive for a role the device no longer holds. After a release, this method (or the nextinitialize) arms a fresh queue.Note
Safe to call concurrently, and alongside
wait_for_event, with the same competing-consumer semantics: each event is delivered once, to exactly one caller.- Returns:
The queued events, oldest first; an empty list when nothing is queued. Never raises
CosmicTimeoutError– an empty bus is an empty list, not an exception.- Raises:
DisconnectError – If the device is not open, or if it closed or the role was released while events were queued and none had been drained yet. Whatever had been drained is returned instead of being lost to the exception, since a bus event is the only record that a transaction happened; the next call raises.
- class pycosmicsdk.AsyncI2cTarget(*, device: AsyncDevice, bus: I2cBus)
Bases:
objectAsyncio I2C target-mode interface for one I2C bus of an open device.
The asyncio twin of
I2cTarget, returned byAsyncDevice.i2c_target; the constructor is package-private. Argument validation matches the blocking interface; commands are awaited and correlated through the device’s engine rather than serialized under a lock.Note
The arena is deliberately missing here. There is no
write_memoryand noread_memory, and that is a decision rather than an omission. Upstream’s async write callssendPayloadinside the submit, with a 2000 ms budget, so what looks like a non-blocking submit blocks the calling thread for the whole transfer – roughly 25 ms for a 16 KB preload on upstream’s own throughput estimate. The read is worse: it cannot be expressed as one submit at all, because the reply only announces the length and the payload is then collected by a second, blocking call. On the asyncio path either one stalls the event loop thread, which is not something a library advertising an asyncio API may do quietly.So use
I2cTarget.write_memoryandI2cTarget.read_memory– from a thread, or throughloop.run_in_executorif the surrounding code is async.AsyncSpiControllercarries the same asymmetry for the SPI bulk pair and for the same reason, anddocs_dev/designs/async-bulk-transfer-shape.mdhas the analysis.Everything that is a genuine non-blocking submit is here: arming, reconfiguring, the bring-up composite, and all three emulation-model commands.
arena_sizeis here too, decoded off the init response packet – upstream’s async wrapper drops the value, but it is on the wire, and an async surface whose arena size were permanentlyNonewould be strictly worse at the one number a caller needs in order to size a preload.- property arena_size: int | None
Capacity of this target’s arena in bytes, as the device reported it.
Noneuntil this object has armed or reconfigured the interface, matchingI2cTarget.arena_size. Decoded from the init response’s{uint8 busId; uint32 memorySize}parameters, becauseCosmicAsyncSDKhas no out-parameter to carry it.
- async initialize(*, address: int, address_mode: I2cTargetAddressMode, address_width: I2cTargetMemoryLayout, model: I2cTargetModel, require_arena_access: bool = True) AsyncI2cTarget
Arm the adapter as an I2C target on this bus and return
self.The asyncio twin of
I2cTarget.initialize, with the same guards, the same non-idempotence and the same warning: this takes the device’s I2C role and this SDK cannot give it back. See the comment block aboveI2cTarget.Bus-event capture is armed before the role switch and unwound if that switch then fails, exactly as on the blocking twin;
_role_may_be_livenames the two failures that keep it.- Parameters:
address – Address the target answers to –
0x08-0x77in 7-bit mode,0x000-0x3FFin 10-bit mode.address_mode – 7-bit or 10-bit addressing.
address_width – How many address bytes the external controller sends. It does not scale an arena offset.
model – Which device kind the arena presents on the bus.
require_arena_access – When
True(the default), refuse before arming if this host holds no vendor bulk transport – the only half of that question the async facade can ask.
- Returns:
This interface, to allow chaining.
- Raises:
ValidationError – If
addressis outside the range its mode allows. No submit is made.CapabilityError – If
require_arena_accessis set and this host holds no vendor bulk transport. The role is not taken.DisconnectError – If the device is no longer open.
ProtocolError – If the response is too short to carry the arena size.
CosmicTimeoutError – If the request exceeds the per-request timeout.
FirmwareError – If the adapter is already an I2C target (
FW_INTERFACE_ALREADY_INITIALIZED).I2cError – If the other I2C role owns the peripheral (
FW_I2C_ROLE_CONFLICT) or the arena could not be drawn from the shared staging pool (FW_I2C_TARGET_STAGING_UNAVAILABLE).
- async configure(*, address: int, address_mode: I2cTargetAddressMode, address_width: I2cTargetMemoryLayout, model: I2cTargetModel) None
Reconfigure a live I2C target in place, keeping the arena contents.
The asyncio twin of
I2cTarget.configure. Idempotent, and it resets the register-map region table and the address pointer, so everyadd_register_regioncall has to be re-made afterwards.- Parameters:
address – New address the target answers to.
address_mode – 7-bit or 10-bit addressing.
address_width – How many address bytes the external controller sends.
model – Which device kind the arena presents on the bus.
- Raises:
ValidationError – If
addressis outside the range its mode allows. No submit is made.DisconnectError – If the device is no longer open.
ProtocolError – If the response is too short to carry the arena size.
CosmicTimeoutError – If the request exceeds the per-request timeout.
FirmwareError – On a firmware-reported failure – notably when no I2C target is up on this bus. The base class and not
I2cError, for the reasonI2cTarget.configuregives:i2cTargetSetParametersdocuments no failure beyond the two transport errors.
- async bring_up(*, address: int, address_mode: I2cTargetAddressMode, address_width: I2cTargetMemoryLayout, model: I2cTargetModel, require_arena_access: bool = True) BringUpResult
Arm the target re-runnably, and report whether it was already up.
The asyncio twin of
I2cTarget.bring_up, with the same live fallback arm, the same absent refused-status filter, and the same rule thatFW_I2C_ROLE_CONFLICTis never absorbed.- Parameters:
address – Address the target answers to.
address_mode – 7-bit or 10-bit addressing.
address_width – How many address bytes the external controller sends.
model – Which device kind the arena presents on the bus.
require_arena_access – Passed through to
initialize.
- Returns:
A
BringUpResult.settings_appliedis alwaysTrue;already_initializedsays whether this call armed the target or found it armed already.- Raises:
ValidationError – If
addressis outside the range its mode allows.CapabilityError – If this host holds no vendor bulk transport and
require_arena_accessis set.DisconnectError – If the device is no longer open.
ProtocolError – If a response is too short to carry the arena size.
CosmicTimeoutError – If a request exceeds the per-request timeout.
FirmwareError – On any firmware failure other than the one the fallback absorbs.
I2cError – If the other I2C role owns the peripheral (
FW_I2C_ROLE_CONFLICT) or the staging arena was unavailable (FW_I2C_TARGET_STAGING_UNAVAILABLE).
- async release() None
Release this bus’s I2C role, and the arena with it.
The asyncio twin of
I2cTarget.release, with the same semantics: the command releases whichever role the bus holds,arena_sizeand the remembered model go back toNoneon every path, event capture is cancelled only on success, and the shared bulk negotiation is left alone. Read that method for the per-device-guard-versus-per-bus-release asymmetry – the reason freeing a two-bus adapter for a target bring-up takes two calls.- Raises:
DisconnectError – If the device is no longer open.
CosmicTimeoutError – If the request exceeds the per-request timeout.
FirmwareError – If the bus held no role (
FW_INTERFACE_NOT_INITIALIZED), the device has no such bus (FW_BUS_NOT_SUPPORTED), or the firmware predates the command (FW_UNSUPPORTED_COMMAND). None is absorbed, and all three are generic codes rather thanI2cError.
- async configure_eeprom(*, total_size: int, page_size: int, write_cycle_us: int, erase_cycle_us: int, busy_behavior: I2cTargetEepromBusyBehavior, erased_value: int = 0xFF, write_protect: tuple[int, int] | None = None) None
Configure the EEPROM emulation model.
The asyncio twin of
I2cTarget.configure_eeprom, with the same model pre-guard and the same fail-open behaviour when the model is unknown.- Parameters:
total_size – Addressable size in bytes; must not exceed the arena.
page_size – Page-write wrap granularity. Must be a power of two.
write_cycle_us – Program time in microseconds.
erase_cycle_us – Erase time in microseconds.
busy_behavior – NACK the address until done, or hold SCL low.
erased_value – Value of an erased byte, typically
0xFF.write_protect –
(start, end)withendexclusive, orNone.
- Raises:
ValidationError – If a numeric argument does not fit its field, if
page_sizeis not a power of two, if the write-protect span is inverted, or if this object knows the active model is notEEPROM. No submit is made.DisconnectError – If the device is no longer open.
CosmicTimeoutError – If the request exceeds the per-request timeout.
I2cError – If the active model is not
EEPROM(FW_I2C_TARGET_WRONG_MODEL) or firmware rejects the configuration (FW_I2C_TARGET_INVALID_CONFIG).
- async erase(*, scope: I2cTargetEraseScope, address: int = 0) None
Fill a page or the whole arena with the configured erased value.
The asyncio twin of
I2cTarget.erase.Note
The submit returns as soon as firmware accepts the command, but a chip-scope erase holds the emulated part busy for
erase_cycle_us, during which an external controller sees whateverbusy_behaviorselected. A long erase cycle is therefore visible to the bus rather than to this coroutine.- Parameters:
scope – Erase one page or the whole arena.
address – For page scope, any address inside the page to erase.
- Raises:
ValidationError – If
addressdoes not fit auint16, or if this object knows the active model is notEEPROM. No submit is made.DisconnectError – If the device is no longer open.
CosmicTimeoutError – If the request exceeds the per-request timeout.
I2cError – If the active model is not
EEPROM(FW_I2C_TARGET_WRONG_MODEL).
- async add_register_region(*, start: int, end: int, access: I2cTargetRegisterAccess, width: int, auto_increment: bool = True) None
Add one region descriptor to the REGISTER_MAP model.
The asyncio twin of
I2cTarget.add_register_region. One descriptor per call, up to sixteen, andconfigureis the only thing that clears the table.- Parameters:
start – Region start address, inclusive.
end – Region end address, exclusive.
access – Access policy for the span.
width – Register width in bytes: 1 or 2.
auto_increment – When
Truethe register pointer advances through the region;Falsepins it, and a pinned region still serveswidthbytes per access.
- Raises:
ValidationError – If the span is empty or inverted, if
widthis neither 1 nor 2, or if this object knows the active model is notREGISTER_MAP. No submit is made.DisconnectError – If the device is no longer open.
CosmicTimeoutError – If the request exceeds the per-request timeout.
I2cError – If the active model is not
REGISTER_MAP(FW_I2C_TARGET_WRONG_MODEL), the sixteen-region limit is reached (FW_I2C_TARGET_REGION_TABLE_FULL), or the descriptor is rejected (FW_I2C_TARGET_INVALID_CONFIG).
- async wait_for_event(*, timeout: float | None = None) I2cTargetBusEvent
Return the oldest queued bus event, awaiting one if none is queued.
The asyncio twin of
I2cTarget.wait_for_event, and the half of the target surface the async side is genuinely good at: observation costs no payload transfer, so nothing here blocks the event loop. Events are queued as the firmware reports them and each call pops exactly one, oldest first; the queue is never trimmed. For push delivery, subscribe toI2cTargetBusEventNotificationviaAsyncDevice.subscribe, or iterateAsyncDevice.iter_notifications, whoseasync forbody runs on the event loop rather than on the dispatcher thread.For the whole queue at once, await `drain_events` rather than looping this with
timeout=0and catchingCosmicTimeoutError; it is also the arm point for a target this process did notinitialize.The bytes are not here, and this class cannot fetch them. The event carries metadata only by the device’s design – and the notification’s inherited
payloadis the raw frame rather than the data, which is whyI2cTargetBusEventcarries no such field. This interface has no arena at all either: see the class docstring for whyread_memoryis deliberately absent from the asyncio surface. So an async target reports that a phase completed, at which arena offset, and how many bytes it moved; reading the bytes back is a blockingI2cTarget’s job.memory_addressis real here, unlike the SPI twin’s, so it is the offset to hand that blocking handle.Note
One completed transaction produces exactly one event: firmware emits one per transfer phase and flushes them at the STOP, and an ordinary transaction is one phase. The trap is a
non_stoptransfer: it emits nothing at all while it holds the bus, so anawait wait_for_event()after one hangs until unrelated code issues the STOP.I2cTarget.wait_for_eventhas the measurement.Note
Safe to await concurrently with competing-consumer semantics; each event is delivered to exactly one waiter, non-deterministically.
- Parameters:
timeout – Maximum seconds to wait when nothing is queued.
Nonewaits indefinitely;0returns immediately; a positive value waits up to that long. A negative value is rejected.- Returns:
The oldest queued bus event.
- Raises:
ValidationError – If
timeoutis negative.DisconnectError – If the device is not open, or if
AsyncDevice.closeis called while this call is awaiting an event.CosmicTimeoutError – If no event arrives within
timeout(only whentimeoutis notNone).
- async drain_events() list[I2cTargetBusEvent]
Return every queued bus event at once, oldest first, without awaiting.
The asyncio twin of
I2cTarget.drain_events, with the same contract: it replaces await_for_event(timeout=0)loop that has to catchCosmicTimeoutErrorto terminate, it preserves order – one completed transaction is one event, so ordinarily the sequence is a sequence of transactions, and the exception is anon_stoptransfer’s two phases arriving together at the STOP that releases them – and it arms event capture, which is the arm point a script attaching to a target another process brought up otherwise does not have.I2cTarget.drain_eventsargues why there is noarm_events()and whyreleasedoes not re-arm.It is a coroutine although nothing in it awaits: arming captures the running loop, so this must be called from one, and a sync method that silently required a loop would be the worse of the two shapes.
Note
Safe to await concurrently, and alongside
wait_for_event, with the same competing-consumer semantics: each event is delivered once, to exactly one caller.- Returns:
The queued events, oldest first; an empty list when nothing is queued. Never raises
CosmicTimeoutError.- Raises:
DisconnectError – If the device is not open, or if it closed or the role was released while events were queued and none had been drained yet; whatever had been drained is returned rather than lost, and the next call raises.
I3C — controller
- class pycosmicsdk.I3cController(*, device: Device)
Bases:
objectBlocking I3C controller for an open device.
Obtain one from
Device.i3c; the constructor is package-private. The interface is cached on the device, so repeateddev.i3c()calls return the same controller.- initialize(*, push_pull_rate: I3cPushPullRate, open_drain_rate: I3cOpenDrainRate, i2c_open_drain_rate: I2cOpenDrainRate, drive_strength: I3cDriveStrength = I3cDriveStrength.FAST) I3cController
Bring the bus up as an I3C controller and return
selffor chaining.Not idempotent: an already-initialised bus reports
FW_INTERFACE_ALREADY_INITIALIZED. The bus stays initialised across reconnects, so a re-run hits this unless the device is reset first. Useconfigureto change parameters of an already-initialised bus.- Parameters:
push_pull_rate – Push-pull clock rate for I3C SDR traffic.
open_drain_rate – Open-drain clock rate used for broadcast and CCC frames.
i2c_open_drain_rate – Open-drain clock rate used for legacy I2C targets on the bus.
drive_strength – Output driver strength of the push-pull stage.
- Returns:
This controller, to allow chaining.
- Raises:
DisconnectError – If the device is no longer open.
FirmwareError – If the bus is already initialised (
status_codeFW_INTERFACE_ALREADY_INITIALIZED) or the firmware rejects the configuration (FW_INVALID_PARAMETER). Those are the only two codes upstreami3cControllerInitreports and neither is in the 0x06xx band, so bring-up never surfaces asI3cError.
- bring_up(*, push_pull_rate: I3cPushPullRate, open_drain_rate: I3cOpenDrainRate, i2c_open_drain_rate: I2cOpenDrainRate, drive_strength: I3cDriveStrength = I3cDriveStrength.FAST) BringUpResult
Bring the bus up as an I3C controller, re-runnably, and report what took effect.
The composite over
initializeandconfigurethat a re-runnable script wants, and the recommended way to bring an I3C bus up. Call it instead ofinitializeunless you specifically need the primitive’s faithfulFW_INTERFACE_ALREADY_INITIALIZED.initializeis not idempotent and the bus stays initialised across aclose()/reopen cycle, so the second run of any script is the common case rather than an edge case. This triesinitializefirst; onFW_INTERFACE_ALREADY_INITIALIZEDit reconfigures the live bus withconfigureinstead, and reports a refused reconfigure through the return value rather than raising.Semantics are identical to
I2cController.bring_up, and so is the return type, deliberately: one contract across protocols is worth more than a per-protocol shape. Three I3C-specific details are worth knowing:configurereaches every parameterinitializetakes, so the fallback path requests exactly what the fresh path would have.No capability guard gates I3C reconfiguration today, so in practice the fallback succeeds and
settings_appliedisFalseonly if firmware rejects a rate it accepted at first bring-up. TheCapabilityErrorarm below is not dead weight: it is what keeps this contract protocol-independent without an edit here if a guard lands.Bringing the controller up is not the same as enumerating the bus.
init_busstill has to run afterwards, exactly as it does after a bareinitialize, and this method neither calls it nor reports on it.
A refusal raised by
initializeitself is a different thing from a refused reconfigure and propagates untouched, whatever its type: nothing reached the bus, so the interface is not up, and the first half ofbring_up’s contract – on return the interface is usable – cannot be kept by a return value. Only the already-up path can report.- Parameters:
push_pull_rate – Push-pull clock rate requested for I3C SDR traffic.
open_drain_rate – Open-drain clock rate requested for broadcast and CCC frames.
i2c_open_drain_rate – Open-drain clock rate requested for legacy I2C targets on the bus.
drive_strength – Output driver strength requested for the push-pull stage.
- Returns:
A
BringUpResult. On return the bus is up and usable; consultsettings_appliedbefore relying on the requested rates actually being in force.- Raises:
DisconnectError – If the device is no longer open.
FirmwareError – On any firmware failure other than the two the fallback absorbs.
- configure(*, push_pull_rate: I3cPushPullRate, open_drain_rate: I3cOpenDrainRate, i2c_open_drain_rate: I2cOpenDrainRate, drive_strength: I3cDriveStrength = I3cDriveStrength.FAST) None
Reconfigure an already-initialised bus at runtime.
Updates controller parameters without tearing the bus down. The bus must have been initialised with
initialize; an uninitialised bus reportsFW_INTERFACE_NOT_INITIALIZEDand surfaces asFirmwareError.- Parameters:
push_pull_rate – Push-pull clock rate for I3C SDR traffic.
open_drain_rate – Open-drain clock rate used for broadcast and CCC frames.
i2c_open_drain_rate – Open-drain clock rate used for legacy I2C targets on the bus.
drive_strength – Output driver strength of the push-pull stage.
- Raises:
DisconnectError – If the device is no longer open.
FirmwareError – If the bus has not been initialised (
status_codeFW_INTERFACE_NOT_INITIALIZED) or the firmware rejects the configuration (FW_INVALID_COMMAND,FW_INVALID_PARAMETER). None of the three is in the 0x06xx band, so this never surfaces asI3cError.
- enable_i2c_clock_stretching(frequency_hz: int = 100_000) I2cClockStretching
Let legacy I2C transfers wait for a target that stretches SCL.
The I3C engine keeps clocking while a target holds SCL low, so a target that stretches the clock (SMBus and MCTP devices, for example) loses bits. With stretching on,
legacy_i2c_writeandlegacy_i2c_readare clocked in software and wait for the target.initializeandconfigureturn it off.Example
With
i3c = dev.i3c()andratesholding the three bus rates:i3c.bring_up(**rates) i3c.enable_i2c_clock_stretching(100_000) i3c.legacy_i2c_write(0x50, bytes([0x00, 0x01]))
- Parameters:
frequency_hz – SCL rate in Hz, 10 kHz to 1 MHz; the firmware refuses anything else. The rate it runs at is at or below the request, and the ceiling depends on the wiring: 746 kHz on one bench, 385 kHz on another. Read it from the return value.
- Returns:
enabledtrue and the actual SCL rate.- Return type:
The state in effect
- Raises:
ValidationError – If
frequency_hzdoes not fit auint32. No facade call is made.DisconnectError – If the device is no longer open.
FirmwareError – If
frequency_hzis out of range (FW_INVALID_PARAMETER), the controller is not initialised (FW_INTERFACE_NOT_INITIALIZED), or the firmware is too old for this feature (FW_UNSUPPORTED_COMMAND, 4.5.x and older).ProtocolError – If a
SUCCESSreply is too short to carry the state.
Note
While on, a stretch longer than the SMBus 35 ms timeout ends the legacy transfer with
FW_BUS_TIMEOUT, and SDR/HDR/CCC calls,init_busandreset_busraiseI3cError(FW_I3C_I2C_CLOCK_STRETCHING_ACTIVE) untildisable_i2c_clock_stretching.
- disable_i2c_clock_stretching() I2cClockStretching
Return legacy I2C transfers to the I3C engine, which cannot wait for a stretched clock.
Also what makes SDR, HDR and CCC calls work again after
enable_i2c_clock_stretching.- Returns:
enabledfalse andfrequency_hz0.- Return type:
The state in effect
- Raises:
DisconnectError – If the device is no longer open.
FirmwareError – If the controller is not initialised (
FW_INTERFACE_NOT_INITIALIZED) or the firmware is too old for this feature (FW_UNSUPPORTED_COMMAND, 4.5.x and older).ProtocolError – If a
SUCCESSreply is too short to carry the state.
- hold_scl_low(duration_us: int) int
Hold SCL low for
duration_us, then release it (SMBus interface reset, T2wrst).SDA is released for the whole hold. Works with I2C clock stretching on or off; the mode is left as it was. SCL has no on-board pull-up: an external one is needed for SCL to rise after the release. The call waits out the hold: its response timeout is extended by
duration_usplus 136 ms.Example
With
i3c = dev.i3c()brought up as a controller:held_us = i3c.hold_scl_low(35_000)
- Parameters:
duration_us – Hold time in microseconds, 1 to 1 000 000; the firmware refuses anything else.
- Returns:
How long SCL was held, in microseconds, as the firmware measured it.
- Raises:
ValidationError – If
duration_usdoes not fit auint32. No facade call is made.DisconnectError – If the device is no longer open.
FirmwareError – If
duration_usis 0 or above 1 s (FW_INVALID_PARAMETER), the controller is not initialised (FW_INTERFACE_NOT_INITIALIZED), SCL was still low 35 ms after the release – a target holds it or SCL has no pull-up (FW_BUS_TIMEOUT), or the firmware predates the command (FW_UNSUPPORTED_COMMAND).ProtocolError – If a
SUCCESSreply is too short to carry the hold time.
- set_sda_pull_up(mode: I3cSdaPullUp) I3cSdaPullUp
Set the 1 kOhm pull-up on the I3C port’s SDA line.
AUTO(default, and every earlier firmware’s behaviour) lets the I3C engine drive it;OFFandONdisconnect or connect it while the I2C clock-stretching mode is on (the I3C engine drives it otherwise). SCL has no on-board pull-up. The setting survives I3C re-inits until changed or the adapter resets.Example
With
i3c = dev.i3c():i3c.set_sda_pull_up(I3cSdaPullUp.ON)
- Parameters:
mode – The setting to apply.
- Returns:
The stored setting after the call. It governs the pull-up only while clock stretching is on; with it off the I3C engine drives the pull-up whatever this returns.
- Raises:
DisconnectError – If the device is no longer open.
FirmwareError – If the firmware refuses the value (
FW_INVALID_PARAMETER) or predates the command (FW_UNSUPPORTED_COMMAND).ProtocolError – If a
SUCCESSreply is too short to carry the setting.
- set_voltage(voltage_mv: int) None
Set the target voltage of the I3C bus.
The voltage alone selects the port: 800-1199 mV drives the low-voltage connector, 1200-3300 mV the high-voltage one. Unlike I2C and UART, the firmware offers no way to persist the setting.
- Parameters:
voltage_mv – Target voltage in millivolts:
0switches VTARG off and powers down the devices on the bus, otherwise 800-3300 mV.- Raises:
ValidationError – If
voltage_mvis neither0nor within 800-3300 mV.DisconnectError – If the device is no longer open.
SystemFirmwareError – If the firmware rejects the voltage. Voltage rejection is
FW_VOLTAGE_OUT_OF_RANGE(0x0101), which sits in the 0x01xx system band shared by every interface, so it does not surface asI3cError. With firmware 4.7.0 or newer, a port that another source already powers is refused withFW_EXTERNAL_VOLTAGE_DETECTED(0x0102) and the rail stays off; calluse_external_voltageinstead.
- use_external_voltage(*, persist: bool = False) None
Hand the I3C rail over to an externally supplied voltage.
Tells the adapter that the I3C bus voltage comes from outside, so it stops driving VTARG itself and measures the supplied rail instead. Exactly one of the two I3C ports must be powered when this runs: with neither powered the firmware reports
FW_I3C_PORTS_NOT_POWERED, with bothFW_BOTH_I3C_PORTS_POWERED. Read back what it measured withget_bus_voltage. The HV reading saturates at ~3299 mV, so an HV supply above 3.3 V is accepted, not refused: keep it at or below 3.3 V.- Parameters:
persist – Ignored by the firmware: the I3C rails come from DACs that store nothing, so the switch lasts for this session only.
- Raises:
DisconnectError – If the device is no longer open.
SystemFirmwareError – If the firmware rejects the switch. Firmware classifies in a fixed order and reports only the first match:
FW_BOTH_I3C_PORTS_POWERED(0x0105), thenFW_I3C_PORTS_NOT_POWERED(0x0104), thenFW_VOLTAGE_OUT_OF_RANGE_ON_I3C_HV(0x0107), thenFW_VOLTAGE_OUT_OF_RANGE_ON_I3C_LV(0x0106) — so a board with both ports powered never reports an out-of-range code.0x0107means the HV port read below 1200 mV: the upper-bound half of firmware’s HV test reads the low-voltage rail by mistake (sys_mgr.c:836), so an HV port driven above 3300 mV is accepted as valid rather than reported. All four are 0x01xx system-band codes, so they do not surface asI3cError.
- get_bus_voltage() tuple[int, int]
Read the externally supplied voltage measured on each I3C rail.
The I3C interface has two independent rails, and this reports the VTARG measurement for both: high-voltage (1200-3300 mV) and low-voltage (800-1199 mV). Only the port that is actually powered reads back a meaningful value, so pair this with
use_external_voltage.Unlike the four multifunction readers —
I2cController.get_bus_voltage,SpiController.get_bus_voltage,Uart.get_bus_voltageandGpio.get_voltage, which are four names for one shared-rail reading — this reports rails that are genuinely I3C’s own. Upstream reads all of them from the samegetAnalogMeasurementscommand but takes itsvmeasVtargI3cHV_mVandvmeasVtargI3cLV_mVfields rather than the sharedvmeasVtargI2cSpiUartGpio_mVone (CosmicSDK.cpp).- Returns:
(hv_mv, lv_mv)— the high- and low-voltage rail measurements in millivolts.- Raises:
DisconnectError – If the device is no longer open.
FirmwareError – On a firmware-reported failure. The measurement command reports no 0x06xx code, so this never surfaces as
I3cError.
- init_bus(*, targets: Sequence[I3cTargetInfo] | None = None) list[I3cTargetInfo]
Run dynamic address assignment and return the resulting device table.
Issues RSTDAA followed by ENTDAA. Passing
targetspre-seeds the controller’s table so known devices keep a deterministic dynamic address; anything else on the bus is assigned by the firmware.- Parameters:
targets – Known targets to seed the table with, or
Nonefor pure discovery.- Returns:
Every target the controller now has in its device table.
- Raises:
DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- reset_bus() None
Issue RSTDAA and clear the controller’s device table.
- Raises:
DisconnectError – If the device is no longer open.
FirmwareError – On a firmware-reported failure. Upstream
i3cResetBusreports onlyFW_BUS_TIMEOUT,FW_INTERFACE_NOT_INITIALIZEDandFW_INVALID_COMMAND, none in the 0x06xx band, so this never surfaces asI3cError.
- get_device_table() list[I3cTargetInfo]
Return the controller’s cached table of known targets.
A pure query of controller state; no traffic is put on the bus.
- Returns:
Every target currently in the controller’s device table.
- Raises:
DisconnectError – If the device is no longer open.
FirmwareError – On a firmware-reported failure. A controller-state query reports no 0x06xx code, so this never surfaces as
I3cError.
- set_target_configuration(address: int, configuration: I3cTargetConfiguration) None
Update the stored configuration byte of a known target.
The target must already be in the controller’s device table, so run
init_busorget_device_tablefirst.- Parameters:
address – Dynamic address of the target to reconfigure (
0x00-0x7F).configuration – Behaviour flags to store for the target; packed into the firmware’s configuration byte.
- Raises:
ValidationError – If
addressis not a 7-bit value.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- property max_transfer_bytes: int
Largest payload one SDR or legacy-I2C transfer can carry, on this connection.
1024–capabilities.max_i3c_payload_bytes, the ordinary route’s ceiling – unless this build, this host and this device can all serve I3C controller bulk, in which case it is the ceiling the device advertised for its I3C command group, clamped toI3C_BULK_MAX_PAYLOAD_BYTES.On a Supernova that is 32767, and the odd number is the answer rather than a rounding error: the I3C peripheral’s frame-length field is 15 bits, so
0x7FFFis the longest payload one frame can describe – one byte short of the 32768-byte staging arena the device advertises and that I2C and SPI bulk transfers do reach in full. Nobody guesses that number, which is the reason this property exists. On a Pulsar it is always 1024: the part has no I3C peripheral and advertises no I3C group, so no firmware update moves it.It is exactly the ceiling the transfer methods enforce, taken from the same
_select_transportcall they make, so this and their refusals cannot disagree.Note
Two things this one number does not cover, both of them real.
A bulk *write* shares its frame with the register sub-address, so its usable payload is
max_transfer_bytes - len(subaddress)while a read takes no deduction at all. The deduction is bulk-only – on the ordinary route the sub-address rides outside the payload – and it cannot be folded in here, because a property takes no arguments and the sub-address is per call. Readtransportto find out which route is in force;sdr_writestates the arithmetic, and its refusal repeats it.HDR-DDR is capped lower, on *both* routes, and the advertised group ceiling does not apply to it at all. Sending it this number would not cost a refused transfer, it would cost the caller their bus until re-enumeration – which is why that limit gets its own accessor,
max_hdr_ddr_transfer_bytes, rather than a sentence here. A single number per controller genuinely cannot describe SDR and DDR both; these are the two numbers.
Reading it runs the bulk negotiation if it has not run yet, which is at most one device query per connection.
- Returns:
The per-call ceiling in bytes for
sdr_write(before any sub-address deduction),sdr_read,legacy_i2c_writeandlegacy_i2c_read.- Raises:
DisconnectError – If the device is no longer open.
- property max_hdr_ddr_transfer_bytes: int
Largest payload one
hdr_ddr_writeorhdr_ddr_readmay carry.I3C_HDR_DDR_MAX_PAYLOAD_BYTES(1024), on either transport and on every connection – so unlikemax_transfer_bytesthis needs no negotiation, reads no capability, and produces no device traffic.It is a separate property because the number is separate in kind, not just in value. The cap is bus safety rather than buffer size: upstream measures DDR reliable to 1024 bytes and finds that at 2048 and above the target stops answering its dynamic address and stays that way until re-enumeration. So the advertised I3C group ceiling – 32767 on a Supernova, which
max_transfer_bytesreports and SDR really does reach – does not apply here at all, and a caller who sized a DDR transfer from it would lose their bus rather than get an error. The capability reply has oneuint16_tper group and nowhere to carry this second number, which is precisely why the SDK has to.The practical consequence, stated because a caller will expect otherwise: HDR-DDR gains nothing from the bulk route today. The ordinary route already reaches 1024. The bulk entry points exist so the route covers every mode and lifting the cap later is not a new API.
- Returns:
The per-call ceiling in bytes for the HDR-DDR pair. A constant.
- property transport: I3cTransport
Which transport the six private-transfer methods use on this connection.
The answer to “did my transfer go over the fast path”, askable before the transfer rather than after it. It is a stable property of the connection, not a record of the last call: the negotiation is latched, and transport selection is otherwise a pure function of that latch, this build’s bindings and the
transport=argument.Reading it runs the negotiation if it has not run yet, which is the only device traffic this property can cause and happens at most once per connection.
It answers
I3cTransport.HIDfor four independent reasons, and they need four different responses: this build binds no I3C bulk command, this host never claimed the adapter’s vendor USB interface, this device’s firmware advertises no I3C bulk, or the adapter is a Pulsar and has no I3C hardware at all. The second is the common one in practice. Note thatHIDhere is not inconsistent withcapabilities.supports_i3c_bulk()beingTrue: that reports the device’s answer, and on a build whose extension predates the six bindings the two legitimately disagree. Readguard_i3c_bulk’s message, orcapabilities.bulk, to tell them apart.Note
BULKhere does not mean every mode got faster. HDR-DDR is capped atI3C_HDR_DDR_MAX_PAYLOAD_BYTESon both routes and gains nothing; legacy I2C is bounded by its own bus rate. SDR is the mode with a real gain.- Returns:
I3cTransport.BULKorI3cTransport.HID– neverI3cTransport.AUTO.- Raises:
DisconnectError – If the device is no longer open.
- legacy_i2c_write(address: int, data: bytes, *, subaddress: bytes = b'', flags: I3cTransferFlags = I3cTransferFlags.NONE, transport: I3cTransport = I3cTransport.AUTO) None
Write
datato a legacy I2C target on the I3C bus.This is how a script reaches an I2C or SMBus device through the Supernova’s I3C port, alongside I3C targets and without moving to the I2C port. The rate is
i2c_open_drain_ratefrombring_up.A target that stretches SCL needs
enable_i2c_clock_stretchingfirst: the I3C engine itself cannot wait for a stretched clock.Note
The bulk route works here and buys less than it does for SDR. A legacy I2C target on an I3C bus is bounded by its own open-drain bus rate long before the transport matters, so a larger single call saves USB round trips rather than bus time. See §”Transport negotiation”.
- Parameters:
address – 7-bit target address (
0x00-0x7F).data – Payload bytes. Must not exceed the active transport’s ceiling –
Device.capabilities.max_i3c_payload_byteson the ordinary route, or the advertised I3C bulk ceiling minuslen(subaddress)on the bulk one, since the sub-address shares the frame. A larger value raisesValidationError; it is never split.subaddress – Optional register/sub-address sent before
data. The host adapter accepts at most 4 bytes; a longer value raisesProtocolError.flags – Transfer flags for the frame.
transport – Which host transport carries the payload.
I3cTransport.AUTO, the default, takes the bulk plane when this build, this host and this device can all serve it and the ordinary route otherwise.I3cTransport.BULKdemands it and raisesCapabilityErrorwhen it is unavailable.
- Raises:
ValidationError – If
addressis not a 7-bit value, ordataexceeds the active transport’s ceiling.CapabilityError – If
transportisI3cTransport.BULKand the bulk transport is unavailable. No facade call is made.ProtocolError – If
subaddressis longer than 4 bytes.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure (e.g. address NACK).
- legacy_i2c_read(address: int, length: int, *, subaddress: bytes = b'', flags: I3cTransferFlags = I3cTransferFlags.NONE, transport: I3cTransport = I3cTransport.AUTO) bytes
Read
lengthbytes from a legacy I2C target on the I3C bus.A target that stretches SCL needs
enable_i2c_clock_stretchingfirst: the I3C engine itself cannot wait for a stretched clock.Note
The bulk route works here and buys less than it does for SDR, for the reason
legacy_i2c_writegives.- Parameters:
address – 7-bit target address (
0x00-0x7F).length – Number of bytes to read. Must not exceed the active transport’s ceiling –
Device.capabilities.max_i3c_payload_byteson the ordinary route, or the advertised I3C bulk ceiling on the bulk one. Unlike a write, a read takes no deduction forsubaddress. A larger value raisesValidationError; the read is never split.subaddress – Optional register/sub-address sent before the read. The host adapter accepts at most 4 bytes; a longer value raises
ProtocolError.flags – Transfer flags for the frame.
transport – Which host transport carries the payload.
I3cTransport.AUTO, the default, takes the bulk plane when this build, this host and this device can all serve it and the ordinary route otherwise.I3cTransport.BULKdemands it and raisesCapabilityErrorwhen it is unavailable.
- Returns:
The bytes read from the target. On the bulk route the buffer is sized to the length the device announced, which may be shorter than
length; treat a short result as a failed transfer rather than retrying, since the stream has already moved on.- Raises:
ValidationError – If
addressorlengthis out of range, orlengthexceeds the active transport’s ceiling.CapabilityError – If
transportisI3cTransport.BULKand the bulk transport is unavailable. No facade call is made.ProtocolError – If
subaddressis longer than 4 bytes.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure (e.g. address NACK).
- sdr_write(address: int, data: bytes, *, subaddress: bytes = b'', flags: I3cTransferFlags = I3cTransferFlags.START_WITH_7E, transport: I3cTransport = I3cTransport.AUTO) None
Write
datato an I3C target in SDR mode.Note
SDR is the mode the bulk route is worth taking for. The ordinary route carries its bytes inside the request and so cannot exceed
Device.capabilities.max_i3c_payload_bytes(1024), while the bulk route reaches whatever the device advertises for the I3C command group – 32767 bytes on a Supernova. See §”Transport negotiation” for why the other two modes gain less or nothing.- Parameters:
address – Dynamic address of the target (
0x00-0x7F).data – Payload bytes. Must not exceed the active transport’s ceiling –
Device.capabilities.max_i3c_payload_byteson the ordinary route, or the advertised I3C bulk ceiling minuslen(subaddress)on the bulk one, since the register sub-address shares the frame with the payload on a write. A larger value raisesValidationError; it is never split.subaddress – Optional register/sub-address sent before
data. The host adapter accepts at most 4 bytes; a longer value raisesProtocolError.flags – Transfer flags for the frame. The default opens the frame with the broadcast address, matching the firmware default.
transport – Which host transport carries the payload.
I3cTransport.AUTO, the default, takes the bulk plane when this build, this host and this device can all serve it and the ordinary route otherwise.I3cTransport.BULKdemands it and raisesCapabilityErrorwhen it is unavailable.
- Raises:
ValidationError – If
addressis not a 7-bit value, ordataexceeds the active transport’s ceiling.CapabilityError – If
transportisI3cTransport.BULKand the bulk transport is unavailable. The message names which of the four gaps applies – this build’s bindings, this host’s driver, this device’s firmware, or a model with no I3C hardware. No facade call is made.ProtocolError – If
subaddressis longer than 4 bytes.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure (e.g. address NACK).
Note
Blocking-only.
AsyncI3cController.sdr_writetakes notransportargument and always uses the ordinary route – seeAsyncI3cController.
- sdr_read(address: int, length: int, *, subaddress: bytes = b'', flags: I3cTransferFlags = I3cTransferFlags.START_WITH_7E, transport: I3cTransport = I3cTransport.AUTO) bytes
Read
lengthbytes from an I3C target in SDR mode.Note
SDR is the mode the bulk route is worth taking for, on the read side as on the write side – and a read takes no deduction for its sub-address, so it reaches the full advertised ceiling.
- Parameters:
address – Dynamic address of the target (
0x00-0x7F).length – Number of bytes to read. Must not exceed the active transport’s ceiling –
Device.capabilities.max_i3c_payload_byteson the ordinary route, or the advertised I3C bulk ceiling on the bulk one. A larger value raisesValidationError; the read is never split, because continuing one requires re-issuing the target’s own register address and this SDK does not know that encoding.subaddress – Optional register/sub-address sent before the read. The host adapter accepts at most 4 bytes; a longer value raises
ProtocolError.flags – Transfer flags for the frame. The default opens the frame with the broadcast address, matching the firmware default.
transport – Which host transport carries the payload.
I3cTransport.AUTO, the default, takes the bulk plane when this build, this host and this device can all serve it and the ordinary route otherwise.I3cTransport.BULKdemands it and raisesCapabilityErrorwhen it is unavailable.
- Returns:
The bytes read from the target. On the bulk route the buffer is sized to the length the device announced, which may be shorter than
length; treat a short result as a failed transfer rather than retrying, since the bulk stream carries no framing to resynchronise against.- Raises:
ValidationError – If
addressorlengthis out of range, orlengthexceeds the active transport’s ceiling.CapabilityError – If
transportisI3cTransport.BULKand the bulk transport is unavailable. No facade call is made.ProtocolError – If
subaddressis longer than 4 bytes.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure (e.g. address NACK).
Note
Blocking-only, like
sdr_write.
- hdr_ddr_write(address: int, command: int, data: bytes, *, flags: I3cTransferFlags = I3cTransferFlags.NONE, transport: I3cTransport = I3cTransport.AUTO) None
Write
datato an I3C target in HDR-DDR mode.The bus enters HDR-DDR automatically and leaves it again with an HDR exit pattern unless
flagscarriesSKIP_EXIT_PATTERN.Warning
HDR-DDR is capped at
I3C_HDR_DDR_MAX_PAYLOAD_BYTES(1024) on both transports, and the cap protects the bus rather than a buffer: above roughly 2048 bytes the target stops answering its dynamic address until re-enumeration. The bulk ceiling the device advertises for the I3C group does not apply here, sotransportchanges where the bytes travel and nothing else.- Parameters:
address – Dynamic address of the target (
0x00-0x7F).command – HDR-DDR write command code,
0x00-0xFF. Bit 7 is the I3C direction convention (clear for a write); the adapter does not enforce it, so the whole byte is accepted.data – Payload bytes; the length must be a multiple of two, because DDR moves 16-bit words and an odd-length frame cannot be expressed. Must not exceed
I3C_HDR_DDR_MAX_PAYLOAD_BYTES(1024) on either transport.flags – Transfer flags for the frame. Only
NONEandSKIP_EXIT_PATTERNare meaningful in HDR-DDR.transport – Which host transport carries the payload. Accepted for symmetry with the other two modes and with no effect on the ceiling, per the warning above.
I3cTransport.BULKstill raisesCapabilityErrorwhen the bulk transport is unavailable, which makes it usable as a benchmark control.
- Raises:
ValidationError – If
addressorcommandis out of range,datahas an odd length, ordataexceedsI3C_HDR_DDR_MAX_PAYLOAD_BYTES.CapabilityError – If
transportisI3cTransport.BULKand the bulk transport is unavailable. No facade call is made.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- hdr_ddr_read(address: int, command: int, length: int, *, flags: I3cTransferFlags = I3cTransferFlags.NONE, transport: I3cTransport = I3cTransport.AUTO) bytes
Read
lengthbytes from an I3C target in HDR-DDR mode.The bus enters HDR-DDR automatically and leaves it again with an HDR exit pattern unless
flagscarriesSKIP_EXIT_PATTERN.Warning
HDR-DDR is capped at
I3C_HDR_DDR_MAX_PAYLOAD_BYTES(1024) on both transports, and the cap protects the bus rather than a buffer: above roughly 2048 bytes the target stops answering its dynamic address until re-enumeration. The bulk ceiling the device advertises for the I3C group does not apply here, sotransportchanges where the bytes travel and nothing else.- Parameters:
address – Dynamic address of the target (
0x00-0x7F).command – HDR-DDR read command code,
0x80-0xFF(bit 7 set marks a read). The bound is this SDK’s, not the device’s.length – Number of bytes to read; must be a multiple of two, because DDR moves 16-bit words. Must not exceed
I3C_HDR_DDR_MAX_PAYLOAD_BYTES(1024) on either transport.flags – Transfer flags for the frame. Only
NONEandSKIP_EXIT_PATTERNare meaningful in HDR-DDR.transport – Which host transport carries the payload. Accepted for symmetry with the other two modes and with no effect on the ceiling, per the warning above.
I3cTransport.BULKstill raisesCapabilityErrorwhen the bulk transport is unavailable, which makes it usable as a benchmark control.
- Returns:
The bytes read from the target. On the bulk route the buffer is sized to the length the device announced, which may be shorter than
length.- Raises:
ValidationError – If
address,commandorlengthis out of range,lengthis odd, orlengthexceedsI3C_HDR_DDR_MAX_PAYLOAD_BYTES.CapabilityError – If
transportisI3cTransport.BULKand the bulk transport is unavailable. No facade call is made.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- target_reset_pattern() None
Emit the I3C target reset pattern on the bus.
- Raises:
DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- hdr_exit_pattern() None
Emit the HDR exit pattern to return the bus to SDR mode.
- Raises:
DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- class pycosmicsdk.AsyncI3cController(*, device: AsyncDevice)
Bases:
objectAsyncio I3C controller for an open device.
Obtain one from
AsyncDevice.i3c; the constructor is package-private. The interface is cached on the device, so repeateddev.i3c()calls return the same controller.Note
The rail surface matches
I3cControllerin full,get_bus_voltageincluded.CosmicAsyncSDKdeclares none of the per-protocol voltage readers, but it does declaregetAnalogMeasurements, whose response carries the I3C HV and LV rails alongside the shared I2C/SPI/UART/GPIO one — and that is the same command upstream’s blockingi3cGetBusVoltageissues internally. So the async reader is built the same way as the blocking one, and reports the same pair.- property ccc: AsyncI3cCcc
The common-command-code sub-interface, built on first access.
- property max_hdr_ddr_transfer_bytes: int
Largest payload one
hdr_ddr_writeorhdr_ddr_readmay carry.The asyncio twin of
I3cController.max_hdr_ddr_transfer_bytes, which argues the number at length:I3C_HDR_DDR_MAX_PAYLOAD_BYTES(1024), a bus-safety cap rather than a buffer size, because above it the target stops answering its dynamic address until re-enumeration.It is here although `transport` and `max_transfer_bytes` are not, and the difference is real rather than an inconsistency. Those two describe the bulk route, which this surface cannot take, so on the async side they would be answering about a road that is not there. This one describes neither route: it needs no negotiation, reads no capability and produces no device traffic, and it answers 1024 on the bulk plane too. Leaving it off cost the async caller the one number whose whole purpose is to stop them wedging their bus, and left hard-coding 1024 as the only way to get it – which is exactly what the property exists to prevent.
guard_max_lengthstill refuses an over-long DDR transfer either way; being refused is not the same as being able to ask.- Returns:
The per-call ceiling in bytes for the HDR-DDR pair. A constant.
- async initialize(*, push_pull_rate: I3cPushPullRate, open_drain_rate: I3cOpenDrainRate, i2c_open_drain_rate: I2cOpenDrainRate, drive_strength: I3cDriveStrength = I3cDriveStrength.FAST) AsyncI3cController
Bring the bus up as an I3C controller and return
selffor chaining.Not idempotent: an already-initialised bus reports
FW_INTERFACE_ALREADY_INITIALIZED. The bus stays initialised across reconnects, so a re-run hits this unless the device is reset first. Useconfigureto change parameters of an already-initialised bus.- Parameters:
push_pull_rate – Push-pull clock rate for I3C SDR traffic.
open_drain_rate – Open-drain clock rate used for broadcast and CCC frames.
i2c_open_drain_rate – Open-drain clock rate used for legacy I2C targets on the bus.
drive_strength – Output driver strength of the push-pull stage.
- Returns:
This controller, to allow chaining.
- Raises:
DisconnectError – If the device is no longer open.
FirmwareError – If the bus is already initialised (
status_codeFW_INTERFACE_ALREADY_INITIALIZED) or the firmware rejects the configuration (FW_INVALID_PARAMETER). Those are the only two codes upstreami3cControllerInitreports and neither is in the 0x06xx band, so bring-up never surfaces asI3cError.
- async bring_up(*, push_pull_rate: I3cPushPullRate, open_drain_rate: I3cOpenDrainRate, i2c_open_drain_rate: I2cOpenDrainRate, drive_strength: I3cDriveStrength = I3cDriveStrength.FAST) BringUpResult
Bring the bus up as an I3C controller, re-runnably, and report what took effect.
The composite over
initializeandconfigurethat a re-runnable script wants, and the recommended way to bring an I3C bus up. Call it instead ofinitializeunless you specifically need the primitive’s faithfulFW_INTERFACE_ALREADY_INITIALIZED.initializeis not idempotent and the bus stays initialised across aclose()/reopen cycle, so the second run of any script is the common case rather than an edge case. This triesinitializefirst; onFW_INTERFACE_ALREADY_INITIALIZEDit reconfigures the live bus withconfigureinstead, and reports a refused reconfigure through the return value rather than raising.Semantics are identical to
I2cController.bring_up, and so is the return type, deliberately: one contract across protocols is worth more than a per-protocol shape. Three I3C-specific details are worth knowing:configurereaches every parameterinitializetakes, so the fallback path requests exactly what the fresh path would have.No capability guard gates I3C reconfiguration today, so in practice the fallback succeeds and
settings_appliedisFalseonly if firmware rejects a rate it accepted at first bring-up. TheCapabilityErrorarm below is not dead weight: it is what keeps this contract protocol-independent without an edit here if a guard lands.Bringing the controller up is not the same as enumerating the bus.
init_busstill has to run afterwards, exactly as it does after a bareinitialize, and this method neither calls it nor reports on it.
A refusal raised by
initializeitself is a different thing from a refused reconfigure and propagates untouched, whatever its type: nothing reached the bus, so the interface is not up, and the first half ofbring_up’s contract – on return the interface is usable – cannot be kept by a return value. Only the already-up path can report.- Parameters:
push_pull_rate – Push-pull clock rate requested for I3C SDR traffic.
open_drain_rate – Open-drain clock rate requested for broadcast and CCC frames.
i2c_open_drain_rate – Open-drain clock rate requested for legacy I2C targets on the bus.
drive_strength – Output driver strength requested for the push-pull stage.
- Returns:
A
BringUpResult. On return the bus is up and usable; consultsettings_appliedbefore relying on the requested rates actually being in force.- Raises:
DisconnectError – If the device is no longer open.
FirmwareError – On any firmware failure other than the two the fallback absorbs.
- async configure(*, push_pull_rate: I3cPushPullRate, open_drain_rate: I3cOpenDrainRate, i2c_open_drain_rate: I2cOpenDrainRate, drive_strength: I3cDriveStrength = I3cDriveStrength.FAST) None
Reconfigure an already-initialised bus at runtime.
Updates controller parameters without tearing the bus down. The bus must have been initialised with
initialize; an uninitialised bus reportsFW_INTERFACE_NOT_INITIALIZEDand surfaces asFirmwareError.- Parameters:
push_pull_rate – Push-pull clock rate for I3C SDR traffic.
open_drain_rate – Open-drain clock rate used for broadcast and CCC frames.
i2c_open_drain_rate – Open-drain clock rate used for legacy I2C targets on the bus.
drive_strength – Output driver strength of the push-pull stage.
- Raises:
DisconnectError – If the device is no longer open.
FirmwareError – If the bus has not been initialised (
status_codeFW_INTERFACE_NOT_INITIALIZED) or the firmware rejects the configuration (FW_INVALID_COMMAND,FW_INVALID_PARAMETER). None of the three is in the 0x06xx band, so this never surfaces asI3cError.
- async enable_i2c_clock_stretching(frequency_hz: int = 100_000) I2cClockStretching
Let legacy I2C transfers wait for a target that stretches SCL.
The I3C engine keeps clocking while a target holds SCL low, so a target that stretches the clock (SMBus and MCTP devices, for example) loses bits. With stretching on,
legacy_i2c_writeandlegacy_i2c_readare clocked in software and wait for the target.initializeandconfigureturn it off.Example
With
i3c = dev.i3c()andratesholding the three bus rates:await i3c.bring_up(**rates) await i3c.enable_i2c_clock_stretching(100_000) await i3c.legacy_i2c_write(0x50, bytes([0x00, 0x01]))
- Parameters:
frequency_hz – SCL rate in Hz, 10 kHz to 1 MHz; the firmware refuses anything else. The rate it runs at is at or below the request, and the ceiling depends on the wiring: 746 kHz on one bench, 385 kHz on another. Read it from the return value.
- Returns:
enabledtrue and the actual SCL rate.- Return type:
The state in effect
- Raises:
ValidationError – If
frequency_hzdoes not fit auint32. No facade call is made.DisconnectError – If the device is no longer open.
FirmwareError – If
frequency_hzis out of range (FW_INVALID_PARAMETER), the controller is not initialised (FW_INTERFACE_NOT_INITIALIZED), or the firmware is too old for this feature (FW_UNSUPPORTED_COMMAND, 4.5.x and older).ProtocolError – If a
SUCCESSreply is too short to carry the state.
Note
While on, a stretch longer than the SMBus 35 ms timeout ends the legacy transfer with
FW_BUS_TIMEOUT, and SDR/HDR/CCC calls,init_busandreset_busraiseI3cError(FW_I3C_I2C_CLOCK_STRETCHING_ACTIVE) untildisable_i2c_clock_stretching.
- async disable_i2c_clock_stretching() I2cClockStretching
Return legacy I2C transfers to the I3C engine, which cannot wait for a stretched clock.
Also what makes SDR, HDR and CCC calls work again after
enable_i2c_clock_stretching.- Returns:
enabledfalse andfrequency_hz0.- Return type:
The state in effect
- Raises:
DisconnectError – If the device is no longer open.
FirmwareError – If the controller is not initialised (
FW_INTERFACE_NOT_INITIALIZED) or the firmware is too old for this feature (FW_UNSUPPORTED_COMMAND, 4.5.x and older).ProtocolError – If a
SUCCESSreply is too short to carry the state.
- async hold_scl_low(duration_us: int) int
Hold SCL low for
duration_us, then release it (SMBus interface reset, T2wrst).SDA is released for the whole hold. Works with I2C clock stretching on or off; the mode is left as it was. SCL has no on-board pull-up: an external one is needed for SCL to rise after the release. The call waits out the hold: its response timeout is extended by
duration_usplus 136 ms.Example
With
i3c = dev.i3c()brought up as a controller:held_us = await i3c.hold_scl_low(35_000)
- Parameters:
duration_us – Hold time in microseconds, 1 to 1 000 000; the firmware refuses anything else.
- Returns:
How long SCL was held, in microseconds, as the firmware measured it.
- Raises:
ValidationError – If
duration_usdoes not fit auint32. No facade call is made.DisconnectError – If the device is no longer open.
FirmwareError – If
duration_usis 0 or above 1 s (FW_INVALID_PARAMETER), the controller is not initialised (FW_INTERFACE_NOT_INITIALIZED), SCL was still low 35 ms after the release – a target holds it or SCL has no pull-up (FW_BUS_TIMEOUT), or the firmware predates the command (FW_UNSUPPORTED_COMMAND).ProtocolError – If a
SUCCESSreply is too short to carry the hold time.
- async set_sda_pull_up(mode: I3cSdaPullUp) I3cSdaPullUp
Set the 1 kOhm pull-up on the I3C port’s SDA line.
AUTO(default, and every earlier firmware’s behaviour) lets the I3C engine drive it;OFFandONdisconnect or connect it while the I2C clock-stretching mode is on (the I3C engine drives it otherwise). SCL has no on-board pull-up. The setting survives I3C re-inits until changed or the adapter resets.Example
With
i3c = dev.i3c():await i3c.set_sda_pull_up(I3cSdaPullUp.ON)
- Parameters:
mode – The setting to apply.
- Returns:
The stored setting after the call. It governs the pull-up only while clock stretching is on; with it off the I3C engine drives the pull-up whatever this returns.
- Raises:
DisconnectError – If the device is no longer open.
FirmwareError – If the firmware refuses the value (
FW_INVALID_PARAMETER) or predates the command (FW_UNSUPPORTED_COMMAND).ProtocolError – If a
SUCCESSreply is too short to carry the setting.
- async set_voltage(voltage_mv: int) None
Set the target voltage of the I3C bus.
The voltage alone selects the port: 800-1199 mV drives the low-voltage connector, 1200-3300 mV the high-voltage one. Unlike I2C and UART, the firmware offers no way to persist the setting.
- Parameters:
voltage_mv – Target voltage in millivolts:
0switches VTARG off and powers down the devices on the bus, otherwise 800-3300 mV.- Raises:
ValidationError – If
voltage_mvis neither0nor within 800-3300 mV.DisconnectError – If the device is no longer open.
SystemFirmwareError – If the firmware rejects the voltage. Voltage rejection is
FW_VOLTAGE_OUT_OF_RANGE(0x0101), which sits in the 0x01xx system band shared by every interface, so it does not surface asI3cError. With firmware 4.7.0 or newer, a port that another source already powers is refused withFW_EXTERNAL_VOLTAGE_DETECTED(0x0102) and the rail stays off; calluse_external_voltageinstead.
- async use_external_voltage(*, persist: bool = False) None
Hand the I3C rail over to an externally supplied voltage.
Tells the adapter that the I3C bus voltage comes from outside, so it stops driving VTARG itself and measures the supplied rail instead. Exactly one of the two I3C ports must be powered when this runs: with neither powered the firmware reports
FW_I3C_PORTS_NOT_POWERED, with bothFW_BOTH_I3C_PORTS_POWERED. The HV reading saturates at ~3299 mV, so an HV supply above 3.3 V is accepted, not refused: keep it at or below 3.3 V.- Parameters:
persist – Ignored by the firmware: the I3C rails come from DACs that store nothing, so the switch lasts for this session only.
- Raises:
DisconnectError – If the device is no longer open.
SystemFirmwareError – If the firmware rejects the switch. Firmware classifies in a fixed order and reports only the first match:
FW_BOTH_I3C_PORTS_POWERED(0x0105), thenFW_I3C_PORTS_NOT_POWERED(0x0104), thenFW_VOLTAGE_OUT_OF_RANGE_ON_I3C_HV(0x0107), thenFW_VOLTAGE_OUT_OF_RANGE_ON_I3C_LV(0x0106) — so a board with both ports powered never reports an out-of-range code.0x0107means the HV port read below 1200 mV: the upper-bound half of firmware’s HV test reads the low-voltage rail by mistake (sys_mgr.c:836), so an HV port driven above 3300 mV is accepted as valid rather than reported. All four are 0x01xx system-band codes, so they do not surface asI3cError.
Note
Read back what the adapter measured with
get_bus_voltage.
- async get_bus_voltage() tuple[int, int]
Read the externally supplied voltage measured on each I3C rail.
The asyncio twin of
I3cController.get_bus_voltage, with the same return type and the same meaning. The I3C interface has two independent rails, and this reports the VTARG measurement for both: high-voltage (1200-3300 mV) and low-voltage (800-1199 mV). Only the port that is actually powered reads back a meaningful value, so pair this withuse_external_voltage.Implemented over
getAnalogMeasurements, which reads every rail in one transaction; this keeps the two I3C fields and discards the rest, exactly as upstream’s blockingi3cGetBusVoltagedoes. UseAsyncDevice.get_analog_measurementsto get all six rails at once instead of a pair per call.- Returns:
(hv_mv, lv_mv)— the high- and low-voltage rail measurements in millivolts.- Raises:
DisconnectError – If the device is no longer open.
ProtocolError – If the response was too short to carry the measurements, which is also how firmware that does not implement the command reports itself.
CosmicTimeoutError – If the request exceeds the per-request timeout.
FirmwareError – On a firmware-reported failure. The measurement command reports no 0x06xx code, so this never surfaces as
I3cError.
- async init_bus(*, targets: Sequence[I3cTargetInfo] | None = None) list[I3cTargetInfo]
Run dynamic address assignment and return the resulting device table.
Issues RSTDAA followed by ENTDAA. Passing
targetspre-seeds the controller’s table so known devices keep a deterministic dynamic address; anything else on the bus is assigned by the firmware.- Parameters:
targets – Known targets to seed the table with, or
Nonefor pure discovery.- Returns:
Every target the controller now has in its device table.
- Raises:
DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- async reset_bus() None
Issue RSTDAA and clear the controller’s device table.
- Raises:
DisconnectError – If the device is no longer open.
FirmwareError – On a firmware-reported failure. Upstream
i3cResetBusreports onlyFW_BUS_TIMEOUT,FW_INTERFACE_NOT_INITIALIZEDandFW_INVALID_COMMAND, none in the 0x06xx band, so this never surfaces asI3cError.
- async get_device_table() list[I3cTargetInfo]
Return the controller’s cached table of known targets.
A pure query of controller state; no traffic is put on the bus.
- Returns:
Every target currently in the controller’s device table.
- Raises:
DisconnectError – If the device is no longer open.
FirmwareError – On a firmware-reported failure. A controller-state query reports no 0x06xx code, so this never surfaces as
I3cError.
- async set_target_configuration(address: int, configuration: I3cTargetConfiguration) None
Update the stored configuration byte of a known target.
The target must already be in the controller’s device table, so run
init_busorget_device_tablefirst.- Parameters:
address – Dynamic address of the target to reconfigure (
0x00-0x7F).configuration – Behaviour flags to store for the target; packed into the firmware’s configuration byte.
- Raises:
ValidationError – If
addressis not a 7-bit value.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- async legacy_i2c_write(address: int, data: bytes, *, subaddress: bytes = b'', flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Write
datato a legacy I2C target on the I3C bus.A target that stretches SCL needs
enable_i2c_clock_stretchingfirst: the I3C engine itself cannot wait for a stretched clock.- Parameters:
address – 7-bit target address (
0x00-0x7F).data – Payload bytes. Must not exceed the device’s maximum I3C payload (
AsyncDevice.capabilities.max_i3c_payload_bytes).subaddress – Optional register/sub-address sent before
data. The host adapter accepts at most 4 bytes; a longer value raisesProtocolError.flags – Transfer flags for the frame.
- Raises:
ValidationError – If
addressis not a 7-bit value, ordataexceeds the device’s maximum payload.ProtocolError – If
subaddressis longer than 4 bytes.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure (e.g. address NACK).
- async legacy_i2c_read(address: int, length: int, *, subaddress: bytes = b'', flags: I3cTransferFlags = I3cTransferFlags.NONE) bytes
Read
lengthbytes from a legacy I2C target on the I3C bus.A target that stretches SCL needs
enable_i2c_clock_stretchingfirst: the I3C engine itself cannot wait for a stretched clock.- Parameters:
address – 7-bit target address (
0x00-0x7F).length – Number of bytes to read. Must not exceed the device’s maximum I3C payload (
AsyncDevice.capabilities.max_i3c_payload_bytes).subaddress – Optional register/sub-address sent before the read. The host adapter accepts at most 4 bytes; a longer value raises
ProtocolError.flags – Transfer flags for the frame.
- Returns:
The bytes read from the target.
- Raises:
ValidationError – If
addressorlengthis out of range, orlengthexceeds the device’s maximum payload.ProtocolError – If
subaddressis longer than 4 bytes.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure (e.g. address NACK).
- async sdr_write(address: int, data: bytes, *, subaddress: bytes = b'', flags: I3cTransferFlags = I3cTransferFlags.START_WITH_7E) None
Write
datato an I3C target in SDR mode.- Parameters:
address – Dynamic address of the target (
0x00-0x7F).data – Payload bytes. Must not exceed the device’s maximum I3C payload (
AsyncDevice.capabilities.max_i3c_payload_bytes).subaddress – Optional register/sub-address sent before
data. The host adapter accepts at most 4 bytes; a longer value raisesProtocolError.flags – Transfer flags for the frame. The default opens the frame with the broadcast address, matching the firmware default.
- Raises:
ValidationError – If
addressis not a 7-bit value, ordataexceeds the device’s maximum payload.ProtocolError – If
subaddressis longer than 4 bytes.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure (e.g. address NACK).
- async sdr_read(address: int, length: int, *, subaddress: bytes = b'', flags: I3cTransferFlags = I3cTransferFlags.START_WITH_7E) bytes
Read
lengthbytes from an I3C target in SDR mode.- Parameters:
address – Dynamic address of the target (
0x00-0x7F).length – Number of bytes to read. Must not exceed the device’s maximum I3C payload (
AsyncDevice.capabilities.max_i3c_payload_bytes).subaddress – Optional register/sub-address sent before the read. The host adapter accepts at most 4 bytes; a longer value raises
ProtocolError.flags – Transfer flags for the frame. The default opens the frame with the broadcast address, matching the firmware default.
- Returns:
The bytes read from the target.
- Raises:
ValidationError – If
addressorlengthis out of range, orlengthexceeds the device’s maximum payload.ProtocolError – If
subaddressis longer than 4 bytes.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure (e.g. address NACK).
- async hdr_ddr_write(address: int, command: int, data: bytes, *, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Write
datato an I3C target in HDR-DDR mode.The bus enters HDR-DDR automatically and leaves it again with an HDR exit pattern unless
flagscarriesSKIP_EXIT_PATTERN.Warning
HDR-DDR is capped at
I3C_HDR_DDR_MAX_PAYLOAD_BYTES(1024) on both transports, and the cap protects the bus rather than a buffer: above roughly 2048 bytes the target stops answering its dynamic address until re-enumeration. The bulk ceiling the device advertises for the I3C group does not apply here, sotransportchanges where the bytes travel and nothing else.- Parameters:
address – Dynamic address of the target (
0x00-0x7F).command – HDR-DDR write command code,
0x00-0xFF. Bit 7 is the I3C direction convention (clear for a write); the adapter does not enforce it, so the whole byte is accepted.data – Payload bytes; the length must be a multiple of two, because DDR moves 16-bit words and an odd-length frame cannot be expressed. Must not exceed
I3C_HDR_DDR_MAX_PAYLOAD_BYTES(1024).flags – Transfer flags for the frame. Only
NONEandSKIP_EXIT_PATTERNare meaningful in HDR-DDR.
- Raises:
ValidationError – If
addressorcommandis out of range,datahas an odd length, ordataexceedsI3C_HDR_DDR_MAX_PAYLOAD_BYTES.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- async hdr_ddr_read(address: int, command: int, length: int, *, flags: I3cTransferFlags = I3cTransferFlags.NONE) bytes
Read
lengthbytes from an I3C target in HDR-DDR mode.The bus enters HDR-DDR automatically and leaves it again with an HDR exit pattern unless
flagscarriesSKIP_EXIT_PATTERN.Warning
HDR-DDR is capped at
I3C_HDR_DDR_MAX_PAYLOAD_BYTES(1024) on both transports, and the cap protects the bus rather than a buffer: above roughly 2048 bytes the target stops answering its dynamic address until re-enumeration. The bulk ceiling the device advertises for the I3C group does not apply here, sotransportchanges where the bytes travel and nothing else.- Parameters:
address – Dynamic address of the target (
0x00-0x7F).command – HDR-DDR read command code,
0x80-0xFF(bit 7 set marks a read). The bound is this SDK’s, not the device’s.length – Number of bytes to read; must be a multiple of two, because DDR moves 16-bit words. Must not exceed
I3C_HDR_DDR_MAX_PAYLOAD_BYTES(1024).flags – Transfer flags for the frame. Only
NONEandSKIP_EXIT_PATTERNare meaningful in HDR-DDR.
- Returns:
The bytes read from the target.
- Raises:
ValidationError – If
address,commandorlengthis out of range,lengthis odd, orlengthexceedsI3C_HDR_DDR_MAX_PAYLOAD_BYTES.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- async target_reset_pattern() None
Emit the I3C target reset pattern on the bus.
- Raises:
DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- async hdr_exit_pattern() None
Emit the HDR exit pattern to return the bus to SDR mode.
- Raises:
DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
I3C — common command codes
- class pycosmicsdk.I3cCcc(*, device: Device)
Bases:
objectBlocking common-command-code interface of an I3C controller.
Obtain one from
I3cController.ccc; the constructor is package-private. Every method issues one common command code and shares the owning controller’s locking and lifecycle guarantees.Note
There is deliberately no
transferhere, and unlike the rail readers that absence is forced by upstream rather than an oversight.AsyncI3cCcc.transferexposesCosmicAsyncSDK::i3cCccTransfer, the raw by-opcode primitive;CosmicSDKdeclares no generic CCC entry point at all — only the ~40 named per-opcode wrappers, each of which reachesi3cCccTransferthrough the privateasyncSDKmember (CosmicSDK.hpp,private:section). So there is no blocking route to an opcode the SDK does not name, and this interface offers the named wrappers alone. Reaching an unnamed or vendor-defined CCC requires anAsyncDevice.- rstdaa(*, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Broadcast RSTDAA to clear every assigned dynamic address.
Table entries survive: only their dynamic-address fields are reset, unlike
I3cController.reset_bus, which drops the table entirely.- Parameters:
flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.- Raises:
DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- entdaa(*, targets: Sequence[I3cTargetInfo] | None = None) None
Broadcast ENTDAA to assign a dynamic address to every target on the bus.
Alone among the common command codes, ENTDAA carries no transfer flags. The controller’s device table holds the outcome; read it back with
I3cController.get_device_table.- Parameters:
targets – Known targets matched on PID, BCR and DCR so each keeps its requested dynamic address; anything unmatched is assigned by the firmware.
- Raises:
DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- setdasa(*, static: int, dynamic: int, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Assign a dynamic address to a target that has a static address.
- Parameters:
static – Static address the target answers to today.
dynamic – Dynamic address to assign.
flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Raises:
ValidationError – If
staticordynamicis not a 7-bit value.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- setaasa(static_addresses: Sequence[int], *, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Broadcast SETAASA so targets adopt their static address as dynamic.
- Parameters:
static_addresses – Static addresses expected to answer; an entry is added to the device table for each responder.
flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Raises:
ValidationError – If any entry of
static_addressesis not a 7-bit value.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- setnewda(*, old: int, new: int, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Move a target from one dynamic address to another.
- Parameters:
old – Dynamic address the target holds today.
new – Dynamic address to move it to.
flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Raises:
ValidationError – If
oldornewis not a 7-bit value.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- getpid(address: int, *, flags: I3cTransferFlags = I3cTransferFlags.NONE) bytes
Read a target’s 48-bit provisional ID.
- Parameters:
address – Dynamic address of the target (
0x00-0x7F).flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Returns:
The PID bytes, most significant first — six for a target that answers in full. A target that returns fewer is reported as it answered, without raising, so check
len()before unpacking.- Raises:
ValidationError – If
addressis not a 7-bit value.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- getbcr(address: int, *, flags: I3cTransferFlags = I3cTransferFlags.NONE) int
Read a target’s bus characteristics register.
- Parameters:
address – Dynamic address of the target (
0x00-0x7F).flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Returns:
The BCR byte.
- Raises:
ValidationError – If
addressis not a 7-bit value.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- getdcr(address: int, *, flags: I3cTransferFlags = I3cTransferFlags.NONE) int
Read a target’s device characteristics register.
- Parameters:
address – Dynamic address of the target (
0x00-0x7F).flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Returns:
The DCR byte.
- Raises:
ValidationError – If
addressis not a 7-bit value.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- getstatus(address: int, *, def_byte: int | None = None, flags: I3cTransferFlags = I3cTransferFlags.NONE) bytes
Read a target’s status register.
- Parameters:
address – Dynamic address of the target (
0x00-0x7F).def_byte – Optional defining byte selecting the format-2 variant of the CCC;
Nonesends the format-1 variant.flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Returns:
The status bytes reported by the target.
- Raises:
ValidationError – If
addressordef_byteis out of range.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- getcaps(address: int, *, def_byte: int | None = None, flags: I3cTransferFlags = I3cTransferFlags.NONE) bytes
Read a target’s capability register.
- Parameters:
address – Dynamic address of the target (
0x00-0x7F).def_byte – Optional defining byte selecting the format-2 variant of the CCC;
Nonesends the format-1 variant.flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Returns:
The capability bytes reported by the target.
- Raises:
ValidationError – If
addressordef_byteis out of range.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- getmxds(address: int, *, flags: I3cTransferFlags = I3cTransferFlags.NONE) bytes
Read a target’s maximum sustained data-rate descriptor.
- Parameters:
address – Dynamic address of the target (
0x00-0x7F).flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Returns:
The MXDS bytes reported by the target.
- Raises:
ValidationError – If
addressis not a 7-bit value.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- getxtime(address: int, *, flags: I3cTransferFlags = I3cTransferFlags.NONE) bytes
Read a target’s exchange-timing support information.
- Parameters:
address – Dynamic address of the target (
0x00-0x7F).flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Returns:
The four XTIME bytes reported by the target.
- Raises:
ValidationError – If
addressis not a 7-bit value.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- rstact_broadcast(def_byte: I3cTargetResetDefByte, *, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Broadcast RSTACT to arm a target-reset action on every target.
The matching target reset pattern is emitted in the same frame.
- Parameters:
def_byte – Reset action to arm.
flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Raises:
DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- rstgrpa_broadcast(*, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Broadcast RSTGRPA to clear every assigned group address.
- Parameters:
flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.- Raises:
DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- entas0_broadcast(*, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Broadcast ENTAS0 to put every target in activity state 0.
- Parameters:
flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.- Raises:
DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- entas1_broadcast(*, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Broadcast ENTAS1 to put every target in activity state 1.
- Parameters:
flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.- Raises:
DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- entas2_broadcast(*, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Broadcast ENTAS2 to put every target in activity state 2.
- Parameters:
flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.- Raises:
DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- entas3_broadcast(*, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Broadcast ENTAS3 to put every target in activity state 3.
- Parameters:
flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.- Raises:
DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- entas3_direct(address: int, *, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Put one target in activity state 3.
- Parameters:
address – Dynamic address of the target (
0x00-0x7F).flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Raises:
ValidationError – If
addressis not a 7-bit value.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- entas2_direct(address: int, *, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Put one target in activity state 2.
- Parameters:
address – Dynamic address of the target (
0x00-0x7F).flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Raises:
ValidationError – If
addressis not a 7-bit value.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- entas1_direct(address: int, *, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Put one target in activity state 1.
- Parameters:
address – Dynamic address of the target (
0x00-0x7F).flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Raises:
ValidationError – If
addressis not a 7-bit value.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- entas0_direct(address: int, *, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Put one target in activity state 0.
- Parameters:
address – Dynamic address of the target (
0x00-0x7F).flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Raises:
ValidationError – If
addressis not a 7-bit value.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- rstgrpa_direct(address: int, *, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Clear the group address of one target.
- Parameters:
address – Dynamic address of the target (
0x00-0x7F).flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Raises:
ValidationError – If
addressis not a 7-bit value.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- rstact_direct(address: int, def_byte: I3cTargetResetDefByte, direction: I3cTransferDirection, *, flags: I3cTransferFlags = I3cTransferFlags.NONE) int | None
Arm a target-reset action on one target, optionally reading it back.
- Parameters:
address – Dynamic address of the target (
0x00-0x7F).def_byte – Reset action to arm or value to read.
direction –
WRITEto arm the action,READto read the value back.flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Returns:
The byte returned by a
READ, orNonefor aWRITE.- Raises:
ValidationError – If
addressis not a 7-bit value.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- setgrpa_direct(address: int, grpa: int, *, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Assign a group address to one target.
- Parameters:
address – Dynamic address of the target (
0x00-0x7F).grpa – Group address to assign (
0x00-0x7F).flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Raises:
ValidationError – If
addressorgrpais not a 7-bit value.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- enec_broadcast(events: I3cEvent, *, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Broadcast ENEC to enable bus events on every target.
- Parameters:
events – Bit mask of the bus events to act on; members may be combined with
|.flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Raises:
DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- enec_direct(address: int, events: I3cEvent, *, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Enable bus events on one target.
- Parameters:
address – Dynamic address of the target (
0x00-0x7F).events – Bit mask of the bus events to act on; members may be combined with
|.flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Raises:
ValidationError – If
addressis not a 7-bit value.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- disec_broadcast(events: I3cEvent, *, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Broadcast DISEC to disable bus events on every target.
- Parameters:
events – Bit mask of the bus events to act on; members may be combined with
|.flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Raises:
DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- disec_direct(address: int, events: I3cEvent, *, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Disable bus events on one target.
- Parameters:
address – Dynamic address of the target (
0x00-0x7F).events – Bit mask of the bus events to act on; members may be combined with
|.flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Raises:
ValidationError – If
addressis not a 7-bit value.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- getmwl(address: int, *, flags: I3cTransferFlags = I3cTransferFlags.NONE) int
Read a target’s maximum write length.
- Parameters:
address – Dynamic address of the target (
0x00-0x7F).flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Returns:
The maximum number of bytes the target accepts in one write.
- Raises:
ValidationError – If
addressis not a 7-bit value.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- setmwl_broadcast(mwl: int, *, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Broadcast SETMWL to set the maximum write length on every target.
- Parameters:
mwl – Maximum write length in bytes.
flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Raises:
ValidationError – If
mwldoes not fit auint16.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- setmwl_direct(address: int, mwl: int, *, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Set the maximum write length of one target.
- Parameters:
address – Dynamic address of the target (
0x00-0x7F).mwl – Maximum write length in bytes.
flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Raises:
ValidationError – If
addressormwlis out of range.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- getmrl(address: int, *, flags: I3cTransferFlags = I3cTransferFlags.NONE) I3cGetMrlResult
Read a target’s maximum read length and optional IBI payload size.
- Parameters:
address – Dynamic address of the target (
0x00-0x7F).flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Returns:
The maximum read length, plus the IBI payload size when the target reported one.
- Raises:
ValidationError – If
addressis not a 7-bit value.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- setmrl_broadcast(mrl: int, *, ibi_length: int | None = None, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Broadcast SETMRL to set the maximum read length on every target.
- Parameters:
mrl – Maximum read length in bytes.
ibi_length – IBI payload size to announce alongside the length;
Noneomits the optional field.flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Raises:
ValidationError – If
mrloribi_lengthis out of range.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- setmrl_direct(address: int, mrl: int, *, ibi_length: int | None = None, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Set the maximum read length of one target.
- Parameters:
address – Dynamic address of the target (
0x00-0x7F).mrl – Maximum read length in bytes.
ibi_length – IBI payload size to announce alongside the length;
Noneomits the optional field.flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Raises:
ValidationError – If
address,mrloribi_lengthis out of range.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- setxtime_broadcast(sub_command: int, *, data: bytes | None = None, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Broadcast SETXTIME to configure exchange timing on every target.
- Parameters:
sub_command – SETXTIME sub-command byte.
data – Optional payload following the sub-command byte.
flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Raises:
ValidationError – If
sub_commanddoes not fit auint8.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- setxtime_direct(address: int, sub_command: int, *, data: bytes | None = None, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Configure exchange timing on one target.
- Parameters:
address – Dynamic address of the target (
0x00-0x7F).sub_command – SETXTIME sub-command byte.
data – Optional payload following the sub-command byte.
flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Raises:
ValidationError – If
addressorsub_commandis out of range.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- setbuscon_broadcast(context: int, *, data: bytes | None = None, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Broadcast SETBUSCON to announce the bus context to every target.
- Parameters:
context – Higher-level protocol context byte.
data – Optional payload following the context byte.
flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Raises:
ValidationError – If
contextdoes not fit auint8.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- class pycosmicsdk.AsyncI3cCcc(*, device: AsyncDevice)
Bases:
objectAsyncio common-command-code interface of an I3C controller.
Obtain one from
AsyncI3cController.ccc; the constructor is package-private. Every method issues one common command code and shares the owning controller’s locking and lifecycle guarantees.transferis the raw primitive the named methods are wrappers around, and the only route to an opcode the SDK does not name.Note
transferhas no blocking twin, and that is forced by upstream.i3cCccTransferis declared onCosmicAsyncSDKonly;CosmicSDKexposes no generic CCC entry point, and the ~40 named wrappers it does expose reachi3cCccTransferthrough its privateasyncSDKmember (CosmicSDK.hpp,private:section), which the blocking facade cannot call. SoI3cCccoffers the named wrappers alone, and an unnamed or vendor-defined CCC needs this async surface.- async transfer(ccc: int, address: int, direction: I3cTransferDirection, *, data: bytes = b'', length: int = 0, def_byte: int | None = None, flags: I3cTransferFlags = I3cTransferFlags.NONE) bytes
Issue one common command code by raw opcode.
The escape hatch under every other method on this interface: each of those wraps one specific opcode, so this is the only route to a vendor-defined or MIPI-reserved CCC the SDK does not name. Prefer the named wrapper where one exists — it fills in the payload length, the direction and the defining byte for you.
cccis a plain integer rather than an enum on purpose. Upstream’sCCCtable is dense: all 256 opcodes carry an enumerator, and the reserved and vendor ranges get placeholder names that merely restate their own value (B_VENCCC_62is 0x62). Writing0x62is how a vendor datasheet spells it, and it is what the wire carries.Two arguments upstream exposes are not offered here. The transfer mode is fixed to SDR, which is the only mode a CCC is defined in. The CCC-type nibble is derived from
def_byte, so it cannot contradict the byte actually sent.- Parameters:
ccc – CCC opcode (
0x00-0xFF). Broadcast opcodes are0x00-0x7Fand are addressed to0x7E; direct opcodes are0x80-0xFFand are addressed to one target.address – Address the frame is directed at:
0x7Efor a broadcast CCC, the target’s 7-bit dynamic address for a direct one.direction –
WRITEsendsdata;READrequestslengthbytes back.data – Payload for a
WRITE, at most 255 bytes. Must be empty on aREAD.length – Bytes to request on a
READ, at most 255. Must be0on aWRITE, whose payload length is taken fromdata.def_byte – Defining byte for a CCC that takes one.
Nonesends the frame without one, which is what every CCC the SDK names does bar the format-2 GETCAPS/GETSTATUS variants.flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Returns:
The data field of the CCC response – the bytes the target returned on a
READ, and empty on aWRITE.- Raises:
ValidationError – If
ccc,address,def_byteorlengthis out of range, ifdataexceeds 255 bytes, or ifdataandlengthcontradictdirection.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure (e.g. the target NACKed the opcode).
- async rstdaa(*, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Broadcast RSTDAA to clear every assigned dynamic address.
Table entries survive: only their dynamic-address fields are reset, unlike
I3cController.reset_bus, which drops the table entirely.- Parameters:
flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.- Raises:
DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- async entdaa(*, targets: Sequence[I3cTargetInfo] | None = None) None
Broadcast ENTDAA to assign a dynamic address to every target on the bus.
Alone among the common command codes, ENTDAA carries no transfer flags. The controller’s device table holds the outcome; read it back with
I3cController.get_device_table.- Parameters:
targets – Known targets matched on PID, BCR and DCR so each keeps its requested dynamic address; anything unmatched is assigned by the firmware.
- Raises:
DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- async setdasa(*, static: int, dynamic: int, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Assign a dynamic address to a target that has a static address.
- Parameters:
static – Static address the target answers to today.
dynamic – Dynamic address to assign.
flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Raises:
ValidationError – If
staticordynamicis not a 7-bit value.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- async setaasa(static_addresses: Sequence[int], *, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Broadcast SETAASA so targets adopt their static address as dynamic.
- Parameters:
static_addresses – Static addresses expected to answer; an entry is added to the device table for each responder.
flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Raises:
ValidationError – If any entry of
static_addressesis not a 7-bit value.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- async setnewda(*, old: int, new: int, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Move a target from one dynamic address to another.
- Parameters:
old – Dynamic address the target holds today.
new – Dynamic address to move it to.
flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Raises:
ValidationError – If
oldornewis not a 7-bit value.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- async getpid(address: int, *, flags: I3cTransferFlags = I3cTransferFlags.NONE) bytes
Read a target’s 48-bit provisional ID.
- Parameters:
address – Dynamic address of the target (
0x00-0x7F).flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Returns:
The PID bytes, most significant first — six for a target that answers in full. A target that returns fewer is reported as it answered, without raising, so check
len()before unpacking.- Raises:
ValidationError – If
addressis not a 7-bit value.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- async getbcr(address: int, *, flags: I3cTransferFlags = I3cTransferFlags.NONE) int
Read a target’s bus characteristics register.
- Parameters:
address – Dynamic address of the target (
0x00-0x7F).flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Returns:
The BCR byte.
- Raises:
ValidationError – If
addressis not a 7-bit value.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- async getdcr(address: int, *, flags: I3cTransferFlags = I3cTransferFlags.NONE) int
Read a target’s device characteristics register.
- Parameters:
address – Dynamic address of the target (
0x00-0x7F).flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Returns:
The DCR byte.
- Raises:
ValidationError – If
addressis not a 7-bit value.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- async getstatus(address: int, *, def_byte: int | None = None, flags: I3cTransferFlags = I3cTransferFlags.NONE) bytes
Read a target’s status register.
- Parameters:
address – Dynamic address of the target (
0x00-0x7F).def_byte – Optional defining byte selecting the format-2 variant of the CCC;
Nonesends the format-1 variant.flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Returns:
The status bytes reported by the target.
- Raises:
ValidationError – If
addressordef_byteis out of range.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- async getcaps(address: int, *, def_byte: int | None = None, flags: I3cTransferFlags = I3cTransferFlags.NONE) bytes
Read a target’s capability register.
- Parameters:
address – Dynamic address of the target (
0x00-0x7F).def_byte – Optional defining byte selecting the format-2 variant of the CCC;
Nonesends the format-1 variant.flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Returns:
The capability bytes reported by the target.
- Raises:
ValidationError – If
addressordef_byteis out of range.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- async getmxds(address: int, *, flags: I3cTransferFlags = I3cTransferFlags.NONE) bytes
Read a target’s maximum sustained data-rate descriptor.
- Parameters:
address – Dynamic address of the target (
0x00-0x7F).flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Returns:
The MXDS bytes reported by the target.
- Raises:
ValidationError – If
addressis not a 7-bit value.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- async getxtime(address: int, *, flags: I3cTransferFlags = I3cTransferFlags.NONE) bytes
Read a target’s exchange-timing support information.
- Parameters:
address – Dynamic address of the target (
0x00-0x7F).flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Returns:
The four XTIME bytes reported by the target.
- Raises:
ValidationError – If
addressis not a 7-bit value.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- async rstact_broadcast(def_byte: I3cTargetResetDefByte, *, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Broadcast RSTACT to arm a target-reset action on every target.
The matching target reset pattern is emitted in the same frame.
- Parameters:
def_byte – Reset action to arm.
flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Raises:
DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- async rstgrpa_broadcast(*, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Broadcast RSTGRPA to clear every assigned group address.
- Parameters:
flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.- Raises:
DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- async entas0_broadcast(*, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Broadcast ENTAS0 to put every target in activity state 0.
- Parameters:
flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.- Raises:
DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- async entas1_broadcast(*, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Broadcast ENTAS1 to put every target in activity state 1.
- Parameters:
flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.- Raises:
DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- async entas2_broadcast(*, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Broadcast ENTAS2 to put every target in activity state 2.
- Parameters:
flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.- Raises:
DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- async entas3_broadcast(*, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Broadcast ENTAS3 to put every target in activity state 3.
- Parameters:
flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.- Raises:
DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- async entas3_direct(address: int, *, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Put one target in activity state 3.
- Parameters:
address – Dynamic address of the target (
0x00-0x7F).flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Raises:
ValidationError – If
addressis not a 7-bit value.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- async entas2_direct(address: int, *, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Put one target in activity state 2.
- Parameters:
address – Dynamic address of the target (
0x00-0x7F).flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Raises:
ValidationError – If
addressis not a 7-bit value.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- async entas1_direct(address: int, *, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Put one target in activity state 1.
- Parameters:
address – Dynamic address of the target (
0x00-0x7F).flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Raises:
ValidationError – If
addressis not a 7-bit value.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- async entas0_direct(address: int, *, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Put one target in activity state 0.
- Parameters:
address – Dynamic address of the target (
0x00-0x7F).flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Raises:
ValidationError – If
addressis not a 7-bit value.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- async rstgrpa_direct(address: int, *, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Clear the group address of one target.
- Parameters:
address – Dynamic address of the target (
0x00-0x7F).flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Raises:
ValidationError – If
addressis not a 7-bit value.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- async rstact_direct(address: int, def_byte: I3cTargetResetDefByte, direction: I3cTransferDirection, *, flags: I3cTransferFlags = I3cTransferFlags.NONE) int | None
Arm a target-reset action on one target, optionally reading it back.
- Parameters:
address – Dynamic address of the target (
0x00-0x7F).def_byte – Reset action to arm or value to read.
direction –
WRITEto arm the action,READto read the value back.flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Returns:
The byte returned by a
READ, orNonefor aWRITE.- Raises:
ValidationError – If
addressis not a 7-bit value.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- async setgrpa_direct(address: int, grpa: int, *, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Assign a group address to one target.
- Parameters:
address – Dynamic address of the target (
0x00-0x7F).grpa – Group address to assign (
0x00-0x7F).flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Raises:
ValidationError – If
addressorgrpais not a 7-bit value.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- async enec_broadcast(events: I3cEvent, *, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Broadcast ENEC to enable bus events on every target.
- Parameters:
events – Bit mask of the bus events to act on; members may be combined with
|.flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Raises:
DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- async enec_direct(address: int, events: I3cEvent, *, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Enable bus events on one target.
- Parameters:
address – Dynamic address of the target (
0x00-0x7F).events – Bit mask of the bus events to act on; members may be combined with
|.flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Raises:
ValidationError – If
addressis not a 7-bit value.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- async disec_broadcast(events: I3cEvent, *, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Broadcast DISEC to disable bus events on every target.
- Parameters:
events – Bit mask of the bus events to act on; members may be combined with
|.flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Raises:
DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- async disec_direct(address: int, events: I3cEvent, *, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Disable bus events on one target.
- Parameters:
address – Dynamic address of the target (
0x00-0x7F).events – Bit mask of the bus events to act on; members may be combined with
|.flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Raises:
ValidationError – If
addressis not a 7-bit value.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- async getmwl(address: int, *, flags: I3cTransferFlags = I3cTransferFlags.NONE) int
Read a target’s maximum write length.
- Parameters:
address – Dynamic address of the target (
0x00-0x7F).flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Returns:
The maximum number of bytes the target accepts in one write.
- Raises:
ValidationError – If
addressis not a 7-bit value.ProtocolError – If the target’s data field carries fewer than the two bytes MWL needs; slicing a short frame would report 1 where the target meant 256.
DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- async setmwl_broadcast(mwl: int, *, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Broadcast SETMWL to set the maximum write length on every target.
- Parameters:
mwl – Maximum write length in bytes.
flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Raises:
ValidationError – If
mwldoes not fit auint16.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- async setmwl_direct(address: int, mwl: int, *, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Set the maximum write length of one target.
- Parameters:
address – Dynamic address of the target (
0x00-0x7F).mwl – Maximum write length in bytes.
flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Raises:
ValidationError – If
addressormwlis out of range.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- async getmrl(address: int, *, flags: I3cTransferFlags = I3cTransferFlags.NONE) I3cGetMrlResult
Read a target’s maximum read length and optional IBI payload size.
- Parameters:
address – Dynamic address of the target (
0x00-0x7F).flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Returns:
The maximum read length, plus the IBI payload size when the target reported one.
- Raises:
ValidationError – If
addressis not a 7-bit value.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- async setmrl_broadcast(mrl: int, *, ibi_length: int | None = None, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Broadcast SETMRL to set the maximum read length on every target.
- Parameters:
mrl – Maximum read length in bytes.
ibi_length – IBI payload size to announce alongside the length;
Noneomits the optional field.flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Raises:
ValidationError – If
mrloribi_lengthis out of range.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- async setmrl_direct(address: int, mrl: int, *, ibi_length: int | None = None, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Set the maximum read length of one target.
- Parameters:
address – Dynamic address of the target (
0x00-0x7F).mrl – Maximum read length in bytes.
ibi_length – IBI payload size to announce alongside the length;
Noneomits the optional field.flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Raises:
ValidationError – If
address,mrloribi_lengthis out of range.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- async setxtime_broadcast(sub_command: int, *, data: bytes | None = None, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Broadcast SETXTIME to configure exchange timing on every target.
- Parameters:
sub_command – SETXTIME sub-command byte.
data – Optional payload following the sub-command byte.
flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Raises:
ValidationError – If
sub_commanddoes not fit auint8.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- async setxtime_direct(address: int, sub_command: int, *, data: bytes | None = None, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Configure exchange timing on one target.
- Parameters:
address – Dynamic address of the target (
0x00-0x7F).sub_command – SETXTIME sub-command byte.
data – Optional payload following the sub-command byte.
flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Raises:
ValidationError – If
addressorsub_commandis out of range.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
- async setbuscon_broadcast(context: int, *, data: bytes | None = None, flags: I3cTransferFlags = I3cTransferFlags.NONE) None
Broadcast SETBUSCON to announce the bus context to every target.
- Parameters:
context – Higher-level protocol context byte.
data – Optional payload following the context byte.
flags – Transfer flags for the frame. Only
NONEandNONSTOPare valid on a CCC transfer;START_WITH_7Ehas no effect.
- Raises:
ValidationError – If
contextdoes not fit auint8.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure.
I3C — target mode
- class pycosmicsdk.I3cTarget(*, device: Device)
Bases:
objectBlocking I3C target-mode interface for an open device.
Obtain one from
Device.i3c_target; the constructor is package-private. The interface is cached on the device, so repeateddev.i3c_target()calls return the same instance. In target mode the adapter answers an external controller out of a 1024-byte register arena, and reports every transfer, CCC and dynamic-address change it services as a bus event.- initialize(*, memory_layout: I3cTargetMemoryLayout, pid: bytes, bcr: int, dcr: int, static_address: int, max_write_length: int = 1024, max_read_length: int = 1024) I3cTarget
Switch the adapter into I3C target mode and return
selffor chaining.Takes effect whatever the current bus role: an adapter already acting as a controller is re-initialised as a target. Event capture is armed before the switch, so a transfer the controller drives the instant target mode comes up is queued rather than lost – unconditionally, so a caller who reads events by subscription rather than through
wait_for_eventshould callstop_event_captureafterwards instead of leaving an unread queue to grow. Useconfigureto change these parameters once target mode is up.Unlike every controller ``initialize`` in this SDK, this one is idempotent: it re-initialises the peripheral unconditionally and never reports
FW_INTERFACE_ALREADY_INITIALIZED. Call it on every run – there is nobring_upcomposite for target mode and no need for one; the comment block aboveI3cTargetsays why at length.- Parameters:
memory_layout – How the 1024-byte register arena is grouped into registers.
pid – 48-bit Provisional ID as exactly 6 bytes, most significant first.
bcr – Bus Characteristics Register value. Bits 7-6 encode the device role, bit 1 the IBI-request capability.
dcr – Device Characteristics Register value, advertising the kind of device this target presents itself as.
static_address – 7-bit I2C static address, or
0x00for none.max_write_length – Largest write the target accepts, answered to GETMWL (0-1024).
max_read_length – Largest read the target allows, answered to GETMRL (0-1024).
- Returns:
This interface, to allow chaining.
- Raises:
ValidationError – If
pidis not 6 bytes, or a numeric argument is out of range.DisconnectError – If the device is no longer open.
FirmwareError – If the firmware rejects the configuration. Upstream
i3cTargetInitreports onlyFW_UNSUPPORTED_COMMAND, which is not in the 0x06xx band, so this never surfaces asI3cError.
- configure(*, memory_layout: I3cTargetMemoryLayout, pid: bytes, bcr: int, dcr: int, static_address: int, max_write_length: int = 1024, max_read_length: int = 1024) None
Reapply the target parameters of an adapter already in target mode.
The adapter must have been switched to the target role with
initializefirst: one still acting as a controller reportsFW_INVALID_COMMANDand one with no I3C role yetFW_INTERFACE_NOT_INITIALIZED, both surfacing asI3cError.- Parameters:
memory_layout – How the 1024-byte register arena is grouped into registers.
pid – 48-bit Provisional ID as exactly 6 bytes, most significant first.
bcr – Bus Characteristics Register value. Bits 7-6 encode the device role, bit 1 the IBI-request capability.
dcr – Device Characteristics Register value, advertising the kind of device this target presents itself as.
static_address – 7-bit I2C static address, or
0x00for none.max_write_length – Largest write the target accepts, answered to GETMWL (0-1024).
max_read_length – Largest read the target allows, answered to GETMRL (0-1024).
- Raises:
ValidationError – If
pidis not 6 bytes, or a numeric argument is out of range.DisconnectError – If the device is no longer open.
FirmwareError – If the firmware rejects the configuration, notably when the adapter is not in target mode. Upstream
i3cTargetSetParametersreports onlyFW_INTERFACE_NOT_INITIALIZED,FW_INVALID_COMMANDandFW_UNSUPPORTED_COMMAND, none in the 0x06xx band, so this never surfaces asI3cError.
- write_memory(address: int, data: bytes) None
Write
datainto the target’s register memory from the host side.Seeds the bytes an external controller will read back.
addressis a register index, not a byte offset: the firmware multiplies it by the register size of the configured memory layout (1, 2 or 4 bytes) to get a byte offset into the 1024-byte arena, so the usable range is 0-1023 for a 1-byte layout, 0-511 for 2 bytes and 0-255 for 4 bytes. Untilinitializehas picked a layout, the widest range is assumed.- Parameters:
address – Register index to start writing at.
data – Payload bytes. Must fit the target memory from
addressonwards.
- Raises:
ValidationError – If
addressis not a valid register index for the configured layout, ordatawould run past the end of the target memory.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure, notably when the adapter is not in target mode.
- read_memory(address: int, length: int) bytes
Read
lengthbytes back out of the target’s register memory.Reads what an external controller has written.
addressis a register index scaled by the configured layout’s register size, exactly as inwrite_memory.- Parameters:
address – Register index to start reading at.
length – Number of bytes to read. Must fit the target memory from
addressonwards.
- Returns:
The bytes read out of the target memory.
- Raises:
ValidationError – If
addressis not a valid register index for the configured layout, orlengthwould run past the end of the target memory.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure, notably when the adapter is not in target mode.
- wait_for_event(*, timeout: float | None = None) I3cTargetBusEvent
Return the oldest queued bus event, waiting for one if the queue is empty.
The pull path for what the external controller does to this target. Events are queued as the firmware reports them and each call pops exactly one, oldest first; the queue is never trimmed, so a burst serviced between two calls is delivered in full. For push delivery, subscribe to
I3cTargetBusEventNotificationviaDevice.subscribe– and then callstop_event_capture, becauseinitializearms this queue whether or not anything ever reads it.Note
Safe to call concurrently with competing-consumer semantics; each event is delivered to exactly one waiter, non-deterministically.
- Parameters:
timeout – Maximum seconds to wait when no event is queued.
Nonewaits indefinitely;0returns immediately; a positive value waits up to that long. A negative value is rejected.- Returns:
The oldest queued bus event.
- Raises:
ValidationError – If
timeoutis negative.DisconnectError – If the device is not open, or if
Device.closeis called while this call is blocked waiting for an event.CosmicTimeoutError – If no event arrives within
timeout(only whentimeoutis notNone).
- stop_event_capture() None
Stop queueing bus events for
wait_for_event, and drop the queue.This does not release the target role, and cannot. There is no
i3cTargetDeinitupstream – the comment block aboveI3cTargetsays why target mode has noreleaseand nobring_up– so after this call the adapter still answers the external controller and firmware still reports every transfer it services. What stops is this SDK queueing those reports. ADevice.subscribesubscription of the caller’s own is untouched, because it is a different subscription.It exists because the queue is the one part of target-mode teardown that needs no firmware command, and because
initializearms capture unconditionally – before the role switch, so that a transfer driven the instant target mode comes up is not lost. A caller who then takes the push route (wait_for_eventrecommends it) never reads what that arming built, and the queue is unbounded, so a second unread copy of every event grows for the life of the device with its payload attached. This is the call that ends it.Why the queue stays unbounded rather than gaining UART’s drop-oldest cap.
Uart’sQUEUE_MAXSIZEbounds a byte stream that arrives whether anyone asked for it or not, where the loss of an old chunk is a lesser harm than unbounded growth. A bus event is a discrete record of a transaction this target already serviced, and it is the only record – dropping one silently would put a caller’s event loop out of step with the bus it is meant to be following, which is the failureI2cTargetandSpiTargetboth refuse for the same reason. Bounding it here and nowhere else would also make three sibling roles disagree about what an event queue is. So the cap is this method instead: growth a caller can end, rather than losses a caller cannot see.Capture is lazy, so this is not a one-way door: a later
wait_for_eventbuilds a fresh queue and resumes. Idempotent – a target whose capture was never armed has nothing to tear down.The device need not still be open. Nothing is issued, the work is an
unsubscribe– already idempotent, and already safe after a close – and refusing here would fail the one call afinallyblock has, in the one state it most wants it. That is the same shapeSubscription.unsubscribehas and the opposite ofwrite_memory’s.One consequence to know before calling it: a thread parked in
wait_for_event(timeout=None)is woken withDisconnectErroron a device that is still open.I2cTarget._release_eventsargues that choice at length – it is_wake_closed’s sentinel, and inventing a second “capture stopped” sentinel for one caller buys less than it costs. Stopping capture with a waiter parked is a race the caller created; leaving that waiter parked on a queue nothing will refill is the only alternative.
- class pycosmicsdk.AsyncI3cTarget(*, device: AsyncDevice)
Bases:
objectAsyncio I3C target-mode interface for an open device.
The asyncio twin of
I3cTarget, returned byAsyncDevice.i3c_target; the constructor is package-private. The interface is cached on the device, so repeateddev.i3c_target()calls return the same instance. Argument validation matches the blocking interface; commands are awaited and correlated through the device’s engine rather than serialized under a lock.- async initialize(*, memory_layout: I3cTargetMemoryLayout, pid: bytes, bcr: int, dcr: int, static_address: int, max_write_length: int = 1024, max_read_length: int = 1024) AsyncI3cTarget
Switch the adapter into I3C target mode and return
selffor chaining.Takes effect whatever the current bus role: an adapter already acting as a controller is re-initialised as a target. Event capture is armed before the switch, so a transfer the controller drives the instant target mode comes up is queued rather than lost – unconditionally, so a caller who reads events by subscription rather than through
wait_for_eventshould callstop_event_captureafterwards instead of leaving an unread queue to grow. Useconfigureto change these parameters once target mode is up.Unlike every controller ``initialize`` in this SDK, this one is idempotent: it re-initialises the peripheral unconditionally and never reports
FW_INTERFACE_ALREADY_INITIALIZED. Call it on every run – there is nobring_upcomposite for target mode and no need for one; the comment block aboveI3cTargetsays why at length.- Parameters:
memory_layout – How the 1024-byte register arena is grouped into registers.
pid – 48-bit Provisional ID as exactly 6 bytes, most significant first.
bcr – Bus Characteristics Register value. Bits 7-6 encode the device role, bit 1 the IBI-request capability.
dcr – Device Characteristics Register value, advertising the kind of device this target presents itself as.
static_address – 7-bit I2C static address, or
0x00for none.max_write_length – Largest write the target accepts, answered to GETMWL (0-1024).
max_read_length – Largest read the target allows, answered to GETMRL (0-1024).
- Returns:
This interface, to allow chaining.
- Raises:
ValidationError – If
pidis not 6 bytes, or a numeric argument is out of range.DisconnectError – If the device is no longer open.
FirmwareError – If the firmware rejects the configuration. Upstream
i3cTargetInitreports onlyFW_UNSUPPORTED_COMMAND, which is not in the 0x06xx band, so this never surfaces asI3cError.
- async configure(*, memory_layout: I3cTargetMemoryLayout, pid: bytes, bcr: int, dcr: int, static_address: int, max_write_length: int = 1024, max_read_length: int = 1024) None
Reapply the target parameters of an adapter already in target mode.
The adapter must have been switched to the target role with
initializefirst: one still acting as a controller reportsFW_INVALID_COMMANDand one with no I3C role yetFW_INTERFACE_NOT_INITIALIZED, both surfacing asI3cError.- Parameters:
memory_layout – How the 1024-byte register arena is grouped into registers.
pid – 48-bit Provisional ID as exactly 6 bytes, most significant first.
bcr – Bus Characteristics Register value. Bits 7-6 encode the device role, bit 1 the IBI-request capability.
dcr – Device Characteristics Register value, advertising the kind of device this target presents itself as.
static_address – 7-bit I2C static address, or
0x00for none.max_write_length – Largest write the target accepts, answered to GETMWL (0-1024).
max_read_length – Largest read the target allows, answered to GETMRL (0-1024).
- Raises:
ValidationError – If
pidis not 6 bytes, or a numeric argument is out of range.DisconnectError – If the device is no longer open.
FirmwareError – If the firmware rejects the configuration, notably when the adapter is not in target mode. Upstream
i3cTargetSetParametersreports onlyFW_INTERFACE_NOT_INITIALIZED,FW_INVALID_COMMANDandFW_UNSUPPORTED_COMMAND, none in the 0x06xx band, so this never surfaces asI3cError.
- async write_memory(address: int, data: bytes) None
Write
datainto the target’s register memory from the host side.Seeds the bytes an external controller will read back.
addressis a register index, not a byte offset: the firmware multiplies it by the register size of the configured memory layout (1, 2 or 4 bytes) to get a byte offset into the 1024-byte arena, so the usable range is 0-1023 for a 1-byte layout, 0-511 for 2 bytes and 0-255 for 4 bytes. Untilinitializehas picked a layout, the widest range is assumed.- Parameters:
address – Register index to start writing at.
data – Payload bytes. Must fit the target memory from
addressonwards.
- Raises:
ValidationError – If
addressis not a valid register index for the configured layout, ordatawould run past the end of the target memory.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure, notably when the adapter is not in target mode.
- async read_memory(address: int, length: int) bytes
Read
lengthbytes back out of the target’s register memory.Reads what an external controller has written.
addressis a register index scaled by the configured layout’s register size, exactly as inwrite_memory.- Parameters:
address – Register index to start reading at.
length – Number of bytes to read. Must fit the target memory from
addressonwards.
- Returns:
The bytes read out of the target memory.
- Raises:
ValidationError – If
addressis not a valid register index for the configured layout, orlengthwould run past the end of the target memory.DisconnectError – If the device is no longer open.
I3cError – On a firmware-reported failure, notably when the adapter is not in target mode.
- async wait_for_event(*, timeout: float | None = None) I3cTargetBusEvent
Return the oldest queued bus event, waiting for one if the queue is empty.
The pull path for what the external controller does to this target. Events are queued as the firmware reports them and each call pops exactly one, oldest first; the queue is never trimmed, so a burst serviced between two calls is delivered in full. For push delivery, subscribe to
I3cTargetBusEventNotificationviaAsyncDevice.subscribe– and then awaitstop_event_capture, becauseinitializearms this queue whether or not anything ever reads it.Note
Safe to await concurrently with competing-consumer semantics; each event is delivered to exactly one waiter, non-deterministically.
- Parameters:
timeout – Maximum seconds to wait when no event is queued.
Nonewaits indefinitely;0returns immediately; a positive value waits up to that long. A negative value is rejected.- Returns:
The oldest queued bus event.
- Raises:
ValidationError – If
timeoutis negative.DisconnectError – If the device is not open, or if
AsyncDevice.closeis called while this call is awaiting an event.CosmicTimeoutError – If no event arrives within
timeout(only whentimeoutis notNone).
- async stop_event_capture() None
Stop queueing bus events for
wait_for_event, and drop the queue.The asyncio twin of
I3cTarget.stop_event_capture, with the same contract and the same three arguments behind it: it tears down only the event queue and never the target role, because there is noi3cTargetDeinitfor it to call; it exists becauseinitializearms capture whether or not anything reads it, and the queue is unbounded; and the queue stays unbounded on purpose, with this method as the bound rather than a drop-oldest cap that would lose the only record of a transaction the target already serviced.It is a coroutine although nothing in it awaits, for
AsyncI2cTarget.drain_events’s reason turned around: the sentinel goes onto anasyncio.Queuewithput_nowait, which is only safe from the loop that owns it, and a sync method that silently required a loop would be the worse of the two shapes.A coroutine parked in
wait_for_event(timeout=None)is woken withDisconnectErroron a device that is still open;_release_eventssays why that sentinel rather than a second one invented here.
UART
- class pycosmicsdk.Uart(*, device: Device)
Bases:
objectBlocking UART interface for an open device.
Obtain one from
Device.uart; the constructor is package-private. The interface is cached on the device, so repeateddev.uart()calls return the same instance.- initialize(*, baudrate: UartBaudRate, hw_handshake: UartHwHandshake = UartHwHandshake.DISABLED, parity: UartParity = UartParity.NONE, data_size: UartDataSize = UartDataSize.BITS_8, stop_bit: UartStopBit = UartStopBit.ONE, send_only_mode: bool = False) Uart
Bring the UART up and return
selffor chaining.Not idempotent: a re-run on an already-up UART raises
FirmwareErrorwithFW_INTERFACE_ALREADY_INITIALIZED. The UART stays initialised across reconnects, so useconfigureto change parameters instead.Reception is armed here by default: the RX queue and its subscription are installed before bring-up, so data the peer emits the instant the UART comes up is captured, not dropped. Pass
send_only_mode=Trueto skip it for pure transmitters.send_only_modeselects the RX path in both directions, which is the only way the flag can mean anything on a re-run: on a UART whose reception is already armed – the normal state on the second run of a script, since the flag used to be write-only –Truetears the subscription and queue down again rather than leaving them running behind a flag that says otherwise. It is purely host-side either way: no firmware parameter is behind it, and the device keeps receiving.- Parameters:
baudrate – Line speed.
hw_handshake – RTS/CTS hardware flow control.
parity – Parity bit mode.
data_size – Bits per character.
stop_bit – Number of stop bits.
send_only_mode – When
True, do not arm reception at bring-up — no RX queue or dispatcher thread, for write-only traffic, and release them if an earlier bring-up armed them. A laterreceive/querystill works (armed lazily on first use) but may have missed data that arrived before that call; a caller parked in one when reception is released is woken withDisconnectError, exactly asdeinitwakes it.
- Returns:
This interface, to allow chaining.
- Raises:
DisconnectError – If the device is no longer open.
FirmwareError – If the UART is already initialised (
status_codeFW_INTERFACE_ALREADY_INITIALIZED), or the firmware rejects the configuration (FW_INVALID_PARAMETER). Neither code sits in the 0x04xx UART band, so neither surfaces asUartError.UartError – If the port is currently held by the USB CDC (
status_codeFW_UART_IN_VCOM_PORT_MODE). This is the only firmware code in the UART band, and the only UART call upstream documents it for.
- bring_up(*, baudrate: UartBaudRate, hw_handshake: UartHwHandshake = UartHwHandshake.DISABLED, parity: UartParity = UartParity.NONE, data_size: UartDataSize = UartDataSize.BITS_8, stop_bit: UartStopBit = UartStopBit.ONE, send_only_mode: bool = False) BringUpResult
Bring the UART up, re-runnably, and report what took effect.
The composite over
initializeandconfigurethat a re-runnable script wants, and the recommended way to bring the UART up. Call it instead ofinitializeunless you specifically need the primitive’s faithfulFW_INTERFACE_ALREADY_INITIALIZED.initializeis not idempotent and the UART stays initialised across aclose()/reopen cycle, so the second run of any script is the common case rather than an edge case. This triesinitializefirst; onFW_INTERFACE_ALREADY_INITIALIZEDit reconfigures the live UART withconfigureinstead, and reports a refused reconfigure through the return value rather than raising.Semantics are identical to
I2cController.bring_up, and so is the return type, deliberately: one contract across protocols is worth more than a per-protocol shape. Two UART-specific details are worth knowing:send_only_modeis a this-SDK setting, not a firmware one –configuredoes not take it, because it selects whether the RX queue and its dispatcher subscription are armed.initializearms or releases them before it talks to the device, so the choice is honoured whichever path runs, in both directions, and is never the thingsettings_appliedis reporting about.Unlike I2C, no capability guard gates UART reconfiguration and the UART does expose
deinit, so in practice the fallback here succeeds.settings_appliedisFalsein two cases: firmware rejected a line parameter it accepted at first bring-up, orhw_handshakewas requested on a unit whosecapabilities.uart_hw_handshake_effectiveisFalse– the one UART setting whose refusal is knowable before the call, and whichinitializecan only warn about because it returns nothing. Either way the UART is up, the other line parameters did apply, andrefusalsays which case it was. The two are independent and can both hold on one call;refusalthen names both, keeping the reconfigure refusal’s own class andstatus_codeso a caller branching on either still matches. Seerefused_with.
- Parameters:
baudrate – Line speed requested for the UART.
hw_handshake – RTS/CTS hardware flow control.
parity – Parity bit mode.
data_size – Bits per character.
stop_bit – Number of stop bits.
send_only_mode – When
True, do not arm reception, for write-only traffic. Honoured whichever path runs; see above.
- Returns:
A
BringUpResult. On return the UART is up and usable; consultsettings_appliedbefore relying on the requested line parameters actually being in force, andrefusalfor why they are not. It is a report, not a promise: nothing here guarantees a setting the device accepted is in force, only that a refusal this SDK can see is not reported as a success.- Raises:
DisconnectError – If the device is no longer open.
FirmwareError – On any firmware failure other than the two the fallback absorbs.
UartError – If the port is currently held by the USB CDC (
status_codeFW_UART_IN_VCOM_PORT_MODE), which onlyinitializereports.
- deinit() None
Release the UART peripheral on the firmware.
After this call the UART is uninitialized on the firmware side; any subsequent UART call will return
FW_INTERFACE_NOT_INITIALIZEDuntilinitializeis called again. Also cancels the internal RX subscription (if one exists) so no stale notifications fire after teardown.- Raises:
DisconnectError – If the device is no longer open.
FirmwareError – If the UART was not initialized (
status_codeFW_INTERFACE_NOT_INITIALIZED,0x0006). A generic firmware code, not a 0x04xx one, so it does not surface asUartError.
- configure(*, baudrate: UartBaudRate, hw_handshake: UartHwHandshake = UartHwHandshake.DISABLED, parity: UartParity = UartParity.NONE, data_size: UartDataSize = UartDataSize.BITS_8, stop_bit: UartStopBit = UartStopBit.ONE) None
Reconfigure an already-initialised UART at runtime.
Updates the line parameters without tearing the UART down. It must have been brought up first with
initialize; calling this on an uninitialised UART comes back asFW_INTERFACE_NOT_INITIALIZEDand surfaces asFirmwareError.- Parameters:
baudrate – New line speed.
hw_handshake – New RTS/CTS hardware flow control setting.
parity – New parity bit mode.
data_size – New bits per character.
stop_bit – New number of stop bits.
- Raises:
DisconnectError – If the device is no longer open.
FirmwareError – If the firmware rejects the request —
FW_INTERFACE_NOT_INITIALIZEDwhen the UART has not been brought up,FW_INVALID_PARAMETERwhen a line parameter is unsupported. Both are generic codes below the 0x04xx UART band, so neither surfaces asUartError.
- set_voltage(voltage_mv: int, *, persist: bool = False) None
Set the UART bus target voltage.
- Parameters:
voltage_mv – Target voltage in millivolts. An out-of-range value is rejected by the firmware and surfaced as
SystemFirmwareError.persist – When
True, store the setting across power cycles.
- Raises:
ValidationError – If
voltage_mvdoes not fit auint16.DisconnectError – If the device is no longer open.
SystemFirmwareError – If the firmware rejects the voltage. Voltage rejection is
FW_VOLTAGE_OUT_OF_RANGE(0x0101), which sits in the 0x01xx system band shared by every interface, so it does not surface asUartError. With firmware 4.7.0 or newer, a port that another source already powers is refused withFW_EXTERNAL_VOLTAGE_DETECTED(0x0102) and the rail stays off; calluse_external_voltageinstead.
- use_external_voltage(*, persist: bool = False) None
Hand the UART rail over to an externally supplied voltage.
Tells the adapter that the multifunction VTARG rail — shared with I2C, SPI and GPIO — comes from outside, so it stops driving it itself and measures the supplied rail instead. Read back what it measured with
get_bus_voltage.- Parameters:
persist – When
True, store the voltage measured now. After a power cycle the adapter drives the rail itself at that voltage; it does not store “external”.- Raises:
DisconnectError – If the device is no longer open.
SystemFirmwareError – If the firmware rejects the switch — a 0x01xx system-band code, the same band
set_voltagedocuments, so it does not surface asUartError.
- get_bus_voltage() int
Read the voltage measured on the shared multifunction VTARG rail.
This is not a UART-specific measurement. The adapter has one multifunction VTARG rail shared by I2C, SPI, UART and GPIO, and this reads that single rail, so the reading reflects whatever last set or is driving it — pair this with
use_external_voltage.I2cController.get_bus_voltage,SpiController.get_bus_voltageandGpio.get_voltagereturn the very same number. Upstream implements all four by issuing onegetAnalogMeasurementscommand and returning itsvmeasVtargI2cSpiUartGpio_mVfield (CosmicSDK.cpp); the four names exist to keep each protocol’s surface self-contained, not because four rails are measured.I3cController.get_bus_voltageis the genuine exception: I3C has its own HV and LV rails and reports both.- Returns:
The measured voltage in millivolts.
- Raises:
DisconnectError – If the device is no longer open.
FirmwareError – On a firmware-reported failure. The measurement command reports no 0x04xx code, so this never surfaces as
UartError.
- send(data: bytes) None
Transmit
dataover the UART.A plain transmit. Reception is decided once at bring-up via
initialize(armed by default; opt out withsend_only_mode=True), sosenddoes not touch the RX path — it behaves the same whether or not reception is armed.- Parameters:
data – Payload bytes. Must not exceed the device’s maximum UART message size (
Device.capabilities.max_uart_message_bytes); a larger value raisesValidationError.- Raises:
ValidationError – If
dataexceeds the device’s maximum message size.DisconnectError – If the device is no longer open.
FirmwareError – On a firmware-reported failure —
FW_INTERFACE_NOT_INITIALIZEDwhen the UART has not been brought up,FW_INVALID_PARAMETERotherwise. Neither is a 0x04xx code, so neither surfaces asUartError.
- query(data: bytes, *, timeout: float | None = None) bytes
Send
dataand return the reply, a request/response round-trip.The primitive for command/response protocols. It arms reception, drains any RX buffered before the request, transmits, then waits for the next message, so the bytes returned are the reply to this
dataand not a stale unsolicited message. Prefer this oversend+receive, which does not drain and so can return a stale message instead of the reply.Note
Not safe to call concurrently, nor to mix with
receive: the drain step would discard or steal data across overlapping callers.- Parameters:
data – Payload bytes to transmit. Must not exceed the device’s maximum UART message size; a larger value raises
ValidationError.timeout – Maximum seconds to wait for the reply.
Nonewaits indefinitely;0returns immediately; a positive value waits up to that long. A negative value is rejected.
- Returns:
The payload of the reply message.
- Raises:
ValidationError – If
dataexceeds the device’s maximum message size, ortimeoutis negative.DisconnectError – If the device is not open, or if
Device.closeis called while this call is blocked waiting for the reply.CosmicTimeoutError – If no reply arrives within
timeout(only whentimeoutis notNone).FirmwareError – On a firmware-reported transmit failure —
FW_INTERFACE_NOT_INITIALIZEDwhen the UART has not been brought up,FW_INVALID_PARAMETERotherwise. Neither is a 0x04xx code, so neither surfaces asUartError.
- receive(*, timeout: float | None = None) bytes
Return the payload of the oldest queued received message.
The pull path for unsolicited inbound data. UART reception is push-driven: the firmware reports incoming bytes as notifications buffered in an internal queue, and each call pops exactly one message (oldest first), never concatenating or draining. For a command/response round-trip use
queryinstead; for continuous push delivery subscribe toUartRxNotificationviaDevice.subscribe.Note
Safe to call concurrently with competing-consumer semantics; each message is delivered to exactly one waiter, non-deterministically.
- Parameters:
timeout – Maximum seconds to wait for a message when the queue is empty.
Nonewaits indefinitely;0returns immediately; a positive value waits up to that long. A negative value is rejected.- Returns:
The payload of the oldest queued message.
- Raises:
ValidationError – If
timeoutis negative.DisconnectError – If the device is not open, or if
Device.closeis called while this call is blocked waiting for data.CosmicTimeoutError – If no message arrives within
timeout(only whentimeoutis notNone).
- class pycosmicsdk.AsyncUart(*, device: AsyncDevice)
Bases:
objectAsyncio UART interface for an open device.
The asyncio twin of
Uart, returned byAsyncDevice.uart; the constructor is package-private. The interface is cached on the device, so repeateddev.uart()calls return the same instance. Argument validation matches the blocking interface; commands are awaited and correlated through the device’s engine rather than serialized under a lock.Note
The rail surface matches
Uartin full,get_bus_voltageincluded.CosmicAsyncSDKdeclares nouartGetBusVoltage, but it does declaregetAnalogMeasurements, which carries the shared I2C/SPI/UART/GPIO rail — and that is the same command upstream’s blockinguartGetBusVoltageissues internally. So the async reader is built the same way as the blocking one, and reports the same value.- async initialize(*, baudrate: UartBaudRate, hw_handshake: UartHwHandshake = UartHwHandshake.DISABLED, parity: UartParity = UartParity.NONE, data_size: UartDataSize = UartDataSize.BITS_8, stop_bit: UartStopBit = UartStopBit.ONE, send_only_mode: bool = False) AsyncUart
Bring the UART up and return
selffor chaining.Not idempotent: a re-run on an already-up UART raises
FirmwareErrorwithFW_INTERFACE_ALREADY_INITIALIZED. The UART stays initialised across reconnects, so useconfigureto change parameters instead.- Parameters:
baudrate – Line speed.
hw_handshake – RTS/CTS hardware flow control.
parity – Parity bit mode.
data_size – Bits per character.
stop_bit – Number of stop bits.
send_only_mode – When
True, do not arm reception at bring-up, for write-only traffic, and release the queue and subscription if an earlier bring-up armed them. A laterreceive/querystill works but may have missed data that arrived before that call; a coroutine parked in one when reception is released is woken withDisconnectError, exactly asdeinitwakes it.
- Returns:
This interface, to allow chaining.
- Raises:
DisconnectError – If the device is no longer open.
FirmwareError – If the UART is already initialised (
status_codeFW_INTERFACE_ALREADY_INITIALIZED), or the firmware rejects the configuration (FW_INVALID_PARAMETER). Neither code sits in the 0x04xx UART band, so neither surfaces asUartError.UartError – If the port is currently held by the USB CDC (
status_codeFW_UART_IN_VCOM_PORT_MODE). This is the only firmware code in the UART band, and the only UART call upstream documents it for.
- async bring_up(*, baudrate: UartBaudRate, hw_handshake: UartHwHandshake = UartHwHandshake.DISABLED, parity: UartParity = UartParity.NONE, data_size: UartDataSize = UartDataSize.BITS_8, stop_bit: UartStopBit = UartStopBit.ONE, send_only_mode: bool = False) BringUpResult
Bring the UART up, re-runnably, and report what took effect.
The composite over
initializeandconfigurethat a re-runnable script wants, and the recommended way to bring the UART up. Call it instead ofinitializeunless you specifically need the primitive’s faithfulFW_INTERFACE_ALREADY_INITIALIZED.initializeis not idempotent and the UART stays initialised across aclose()/reopen cycle, so the second run of any script is the common case rather than an edge case. This triesinitializefirst; onFW_INTERFACE_ALREADY_INITIALIZEDit reconfigures the live UART withconfigureinstead, and reports a refused reconfigure through the return value rather than raising.Semantics are identical to
I2cController.bring_up, and so is the return type, deliberately: one contract across protocols is worth more than a per-protocol shape. Two UART-specific details are worth knowing:send_only_modeis a this-SDK setting, not a firmware one –configuredoes not take it, because it selects whether the RX queue and its dispatcher subscription are armed.initializearms or releases them before it talks to the device, so the choice is honoured whichever path runs, in both directions, and is never the thingsettings_appliedis reporting about.Unlike I2C, no capability guard gates UART reconfiguration and the UART does expose
deinit, so in practice the fallback here succeeds.settings_appliedisFalsein two cases: firmware rejected a line parameter it accepted at first bring-up, orhw_handshakewas requested on a unit whosecapabilities.uart_hw_handshake_effectiveisFalse– the one UART setting whose refusal is knowable before the call, and whichinitializecan only warn about because it returns nothing. Either way the UART is up, the other line parameters did apply, andrefusalsays which case it was. The two are independent and can both hold on one call;refusalthen names both, keeping the reconfigure refusal’s own class andstatus_codeso a caller branching on either still matches. Seerefused_with.
- Parameters:
baudrate – Line speed requested for the UART.
hw_handshake – RTS/CTS hardware flow control.
parity – Parity bit mode.
data_size – Bits per character.
stop_bit – Number of stop bits.
send_only_mode – When
True, do not arm reception, for write-only traffic. Honoured whichever path runs; see above.
- Returns:
A
BringUpResult. On return the UART is up and usable; consultsettings_appliedbefore relying on the requested line parameters actually being in force, andrefusalfor why they are not. It is a report, not a promise: nothing here guarantees a setting the device accepted is in force, only that a refusal this SDK can see is not reported as a success.- Raises:
DisconnectError – If the device is no longer open.
FirmwareError – On any firmware failure other than the two the fallback absorbs.
UartError – If the port is currently held by the USB CDC (
status_codeFW_UART_IN_VCOM_PORT_MODE), which onlyinitializereports.
- async deinit() None
Release the UART peripheral on the firmware.
After this call the UART is uninitialized on the firmware side; any subsequent UART call comes back as
FW_INTERFACE_NOT_INITIALIZEDuntilinitializeis called again. Also cancels the internal RX subscription (if one exists) so no stale notifications fire after teardown, and wakes any coroutine parked inreceive/query.- Raises:
DisconnectError – If the device is no longer open.
FirmwareError – If the UART was not initialized (
status_codeFW_INTERFACE_NOT_INITIALIZED,0x0006). A generic firmware code, not a 0x04xx one, so it does not surface asUartError.
- async configure(*, baudrate: UartBaudRate, hw_handshake: UartHwHandshake = UartHwHandshake.DISABLED, parity: UartParity = UartParity.NONE, data_size: UartDataSize = UartDataSize.BITS_8, stop_bit: UartStopBit = UartStopBit.ONE) None
Reconfigure an already-initialised UART at runtime.
Updates the line parameters without tearing the UART down. It must have been brought up first with
initialize; calling this on an uninitialised UART comes back asFW_INTERFACE_NOT_INITIALIZEDand surfaces asFirmwareError.- Parameters:
baudrate – New line speed.
hw_handshake – New RTS/CTS hardware flow control setting.
parity – New parity bit mode.
data_size – New bits per character.
stop_bit – New number of stop bits.
- Raises:
DisconnectError – If the device is no longer open.
FirmwareError – If the firmware rejects the request —
FW_INTERFACE_NOT_INITIALIZEDwhen the UART has not been brought up,FW_INVALID_PARAMETERwhen a line parameter is unsupported. Both are generic codes below the 0x04xx UART band, so neither surfaces asUartError.
- async set_voltage(voltage_mv: int, *, persist: bool = False) None
Set the UART bus target voltage.
- Parameters:
voltage_mv – Target voltage in millivolts. An out-of-range value is rejected by the firmware and surfaced as
SystemFirmwareError.persist – When
True, store the setting across power cycles.
- Raises:
ValidationError – If
voltage_mvdoes not fit auint16.DisconnectError – If the device is no longer open.
SystemFirmwareError – If the firmware rejects the voltage. Voltage rejection is
FW_VOLTAGE_OUT_OF_RANGE(0x0101), which sits in the 0x01xx system band shared by every interface, so it does not surface asUartError. With firmware 4.7.0 or newer, a port that another source already powers is refused withFW_EXTERNAL_VOLTAGE_DETECTED(0x0102) and the rail stays off; calluse_external_voltageinstead.
- async use_external_voltage(*, persist: bool = False) None
Hand the UART rail over to an externally supplied voltage.
Tells the adapter that the multifunction VTARG rail — shared with I2C, SPI and GPIO — comes from outside, so it stops driving it itself and measures the supplied rail instead.
- Parameters:
persist – When
True, store the voltage measured now. After a power cycle the adapter drives the rail itself at that voltage; it does not store “external”.- Raises:
DisconnectError – If the device is no longer open.
SystemFirmwareError – If the firmware rejects the switch — a 0x01xx system-band code, the same band
set_voltagedocuments, so it does not surface asUartError.
Note
Read back what the adapter measured with
get_bus_voltage.
- async get_bus_voltage() int
Read the voltage measured on the shared multifunction VTARG rail.
The asyncio twin of
Uart.get_bus_voltage, with the same return type and the same meaning. It is not a UART-specific measurement: the adapter has one multifunction VTARG rail shared by I2C, SPI, UART and GPIO, andAsyncI2cController.get_bus_voltage,AsyncSpiController.get_bus_voltageandAsyncGpio.get_voltagereturn the very same number. Pair this withuse_external_voltage.Implemented over
getAnalogMeasurements, which reads every rail in one transaction; this keeps the shared-rail field and discards the rest, exactly as upstream’s blockinguartGetBusVoltagedoes. UseAsyncDevice.get_analog_measurementsto get all six rails at once instead of one field per call.- Returns:
The measured voltage in millivolts.
- Raises:
DisconnectError – If the device is no longer open.
ProtocolError – If the response was too short to carry the measurements, which is also how firmware that does not implement the command reports itself.
CosmicTimeoutError – If the request exceeds the per-request timeout.
FirmwareError – On a firmware-reported failure. The measurement command reports no 0x04xx code, so this never surfaces as
UartError.
- async send(data: bytes) None
Transmit
dataover the UART.A plain transmit. Reception is decided once at bring-up via
initialize(armed by default; opt out withsend_only_mode=True), sosenddoes not touch the RX path.- Parameters:
data – Payload bytes. Must not exceed the device’s maximum UART message size (
AsyncDevice.capabilities.max_uart_message_bytes); a larger value raisesValidationError.- Raises:
ValidationError – If
dataexceeds the device’s maximum message size.DisconnectError – If the device is no longer open.
FirmwareError – On a firmware-reported failure —
FW_INTERFACE_NOT_INITIALIZEDwhen the UART has not been brought up,FW_INVALID_PARAMETERotherwise. Neither is a 0x04xx code, so neither surfaces asUartError.
- async query(data: bytes, *, timeout: float | None = None) bytes
Send
dataand return the reply, a request/response round-trip.The primitive for command/response protocols. It arms reception, drains any RX buffered before the request, transmits, then waits for the next message, so the bytes returned are the reply to this
dataand not a stale unsolicited message. Prefer this oversend+receive, which does not drain and so can return a stale message instead of the reply.Note
Not safe to call concurrently, nor to mix with
receive: the drain step would discard or steal data across overlapping callers.- Parameters:
data – Payload bytes to transmit. Must not exceed the device’s maximum UART message size; a larger value raises
ValidationError.timeout – Maximum seconds to wait for the reply.
Nonewaits indefinitely;0returns immediately; a positive value waits up to that long. A negative value is rejected.
- Returns:
The payload of the reply message.
- Raises:
ValidationError – If
dataexceeds the device’s maximum message size, ortimeoutis negative.DisconnectError – If the device is not open, or if
AsyncDevice.closeis called while this call is awaiting the reply.CosmicTimeoutError – If no reply arrives within
timeout(only whentimeoutis notNone).FirmwareError – On a firmware-reported transmit failure —
FW_INTERFACE_NOT_INITIALIZEDwhen the UART has not been brought up,FW_INVALID_PARAMETERotherwise. Neither is a 0x04xx code, so neither surfaces asUartError.
- async receive(*, timeout: float | None = None) bytes
Return the payload of the oldest queued received message.
The pull path for unsolicited inbound data. UART reception is push-driven: the firmware reports incoming bytes as notifications buffered in an internal queue, and each call pops exactly one message (oldest first), never concatenating or draining. For a command/response round-trip use
queryinstead; for continuous push delivery useiter_receiveor subscribe viaAsyncDevice.subscribe.Note
Safe to call concurrently with competing-consumer semantics; each message is delivered to exactly one waiter, non-deterministically.
- Parameters:
timeout – Maximum seconds to wait for a message when the queue is empty.
Nonewaits indefinitely;0returns immediately; a positive value waits up to that long. A negative value is rejected.- Returns:
The payload of the oldest queued message.
- Raises:
ValidationError – If
timeoutis negative.DisconnectError – If the device is not open, or if
AsyncDevice.closeis called while this call is awaiting data.CosmicTimeoutError – If no message arrives within
timeout(only whentimeoutis notNone).
- iter_receive() _AsyncUartReceiveStream
Return an async iterator over received message payloads.
Use as an async context manager so the underlying subscription is detached on every exit path (normal,
break, exception, cancellation):async with uart.iter_receive() as stream: async for message in stream: ...
The stream installs its own subscription and queue, independent of
receive/query, so iterating does not compete with those for messages.- Returns:
An async context manager / iterator yielding message payloads.
- 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, which is how
receiveends in the same situation. The stream has no other ending. 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 over theUartRxNotificationsubscription that both surfaces expose — there is no separate firmware command and nothing async-specific in L1 or L2. The blockingUartcovers the same ground withUart.receiveandDevice.subscribe. The same holds forAsyncDevice.iter_notifications.
SPI
- class pycosmicsdk.SpiController(*, device: Device)
Bases:
objectBlocking SPI controller for an open device.
Obtain one from
Device.spi; the constructor is package-private. The interface is cached on the device, so repeateddev.spi()calls return the same controller – which is also what makes “once per connection” hold for the transport negotiation and its log line: the controller lives as long as the open device does, andDevice.closedrops it.- initialize(*, bit_order: SpiBitOrder, mode: SpiMode, data_width: SpiDataWidth, chip_select: SpiChipSelect, chip_select_polarity: SpiChipSelectPolarity, frequency_hz: int) SpiController
Bring the SPI controller up and return
selffor chaining.Not idempotent: a re-run on an already-initialised interface reports
FW_INTERFACE_ALREADY_INITIALIZED. The interface stays initialised across reconnects, so a re-run hits this unless the device is reset first. Useconfigureto change parameters of a live interface.- Parameters:
bit_order – Whether each frame goes out most- or least-significant bit first.
mode – Clock polarity and phase pair (SPI modes 0 to 3).
data_width – Frame size, 8 or 16 bits.
chip_select – Which chip-select line to drive.
chip_select_polarity – Whether the chip-select line is active low or active high.
frequency_hz – SCLK frequency in Hz. Both adapters accept
10_000-50_000_000, with the usable maximum depending on model and hardware revision; a rejected rate surfaces asFirmwareError.
- Returns:
This controller, to allow chaining.
- Raises:
ValidationError – If
frequency_hzdoes not fit auint32.DisconnectError – If the device is no longer open.
FirmwareError – If the interface is already initialised (
status_codeFW_INTERFACE_ALREADY_INITIALIZED), or if the firmware rejects the configuration.
- bring_up(*, bit_order: SpiBitOrder, mode: SpiMode, data_width: SpiDataWidth, chip_select: SpiChipSelect, chip_select_polarity: SpiChipSelectPolarity, frequency_hz: int) BringUpResult
Bring the SPI controller up, re-runnably, and report what took effect.
The composite over
initializeandconfigurethat a re-runnable script wants, and the recommended way to bring the SPI controller up. Call it instead ofinitializeunless you specifically need the primitive’s faithfulFW_INTERFACE_ALREADY_INITIALIZED.initializeis not idempotent and the interface stays initialised across aclose()/reopen cycle, so the second run of any script is the common case rather than an edge case. This triesinitializefirst; onFW_INTERFACE_ALREADY_INITIALIZEDit reconfigures the live controller withconfigureinstead, and reports a refused reconfigure through the return value rather than raising.Semantics are identical to
I2cController.bring_up, and so is the return type, deliberately: one contract across protocols is worth more than a per-protocol shape. Two SPI-specific details are worth knowing:configurereaches every parameterinitializetakes, so the fallback path requests exactly what the fresh path would have. There is no SPI setting that survives only theinitializeroute.No capability guard gates SPI reconfiguration today, so in practice the fallback succeeds and
settings_appliedisFalseonly if firmware rejects a value it accepted at first bring-up. TheCapabilityErrorarm below is not dead weight: it is what keeps this contract protocol-independent without an edit here if a guard lands.
A refusal raised by
initializeitself is a different thing from a refused reconfigure and propagates untouched, whatever its type: nothing reached the device, so the interface is not up, and the first half ofbring_up’s contract – on return the interface is usable – cannot be kept by a return value. Only the already-up path can report.- Parameters:
bit_order – Bit order requested for each frame – most- or least-significant bit first.
mode – Clock polarity and phase pair requested (SPI modes 0 to 3).
data_width – Frame size requested, 8 or 16 bits.
chip_select – Chip-select line requested.
chip_select_polarity – Chip-select active polarity requested.
frequency_hz – SCLK frequency in Hz requested. Both adapters accept
10_000-50_000_000, with the usable maximum depending on model and hardware revision.
- Returns:
A
BringUpResult. On return the controller is up and usable; consultsettings_appliedbefore relying on the requested parameters actually being in force.- Raises:
ValidationError – If
frequency_hzdoes not fit auint32.DisconnectError – If the device is no longer open.
FirmwareError – On any firmware failure other than the two the fallback absorbs – a frequency
initializeitself rejects, for instance.
- configure(*, bit_order: SpiBitOrder, mode: SpiMode, data_width: SpiDataWidth, chip_select: SpiChipSelect, chip_select_polarity: SpiChipSelectPolarity, frequency_hz: int) None
Reconfigure an already-initialised SPI controller at runtime.
Updates every controller parameter without tearing the interface down. It must have been brought up first with
initialize; an uninitialised interface reportsFW_INTERFACE_NOT_INITIALIZEDand surfaces asFirmwareError.- Parameters:
bit_order – New frame bit order.
mode – New clock polarity and phase pair.
data_width – New frame size, 8 or 16 bits.
chip_select – New chip-select line to drive.
chip_select_polarity – New chip-select active polarity.
frequency_hz – New SCLK frequency in Hz. Both adapters accept
10_000-50_000_000; a rejected rate surfaces asFirmwareError.
- Raises:
ValidationError – If
frequency_hzdoes not fit auint32.DisconnectError – If the device is no longer open.
FirmwareError – If the firmware rejects the request — notably if the interface has not been initialised.
- release() None
Release the SPI role this peripheral holds.
spiDeinitin upstream’s terms – the same parameterless commandSpiTarget.releasesends, because there is only one. Firmware dispatches it on the peripheral’s own stored role and tears down whichever role it finds, so it is not two commands and this is not a controller-specific one: after it the peripheral holds no role, and the other role initialises normally.Why the method exists on this class as well. Not for symmetry. The release is role-agnostic, but the only wrapper over it used to be
SpiTarget.release, andDevice.spi_targetrefuses a rev-B Supernova outright – that revision’s SPI level shifter has its chip-select direction pin fixed as an output, so the board cannot receive an incoming chip select. On the pairing that revision forces, with the Supernova as the controller because it cannot be the target, there was no way from Python to hand back a role the device would happily have released.guard_spi_targetsays a unit cannot serve as a target; it says nothing about relinquishing a role, and firmware applies no revision check to the teardown. So the release belongs on the object that models the role such a unit can actually hold.I2cControllerhas no counterpart today, and the asymmetry is a reachability one rather than a design one:Device.i2c_targetcarries no revision gate, soI2cTarget.releaseis reachable on every unit that has the bus and already serves both of that bus’s roles.This object keeps no role state, so nothing here is invalidated. The controller’s one latch is the once-per-connection transport log line, which describes the negotiation rather than the role, and the bulk negotiation itself is deliberately untouched for the reason
SpiTarget.releasegives – it records this host’s transport and the device’s advertised ceilings, neither of which a role release changes.Note
A `SpiTarget` handle you also hold is not updated by this call. Releasing through here tears down a target role just as effectively, but the target object goes on reporting the
SpiTarget.arena_sizeit was told and keeps its event capture armed for events that can no longer arrive. CallSpiTarget.releasewhen a target is what is armed; this method is for the role this object drives, and for the unit where noSpiTargetcan be built at all.Note
Nothing is absorbed. A peripheral holding no role answers
FW_INTERFACE_NOT_INITIALIZEDrather than succeeding quietly, matchingUart.deinitandSpiTarget.release; a caller who wants an idempotent release catches that one code.Note
The first transfer after a release is not proven byte-clean. Firmware carries an open issue on a first-transfer byte skew reachable only through controller → deinit → target –
docs_dev/hardware_validation/firmware-role-and-target-mode.md§4 – and this method is one of the two ways to reach that order.- Raises:
DisconnectError – If the device is no longer open.
FirmwareError – If the peripheral held no role (
FW_INTERFACE_NOT_INITIALIZED) or the firmware predates the command (FW_UNSUPPORTED_COMMAND). Both are generic codes below the 0x03xx band, so neither surfaces asSpiError, and there is noFW_BUS_NOT_SUPPORTEDin the list because there is no bus to name.
- set_voltage(voltage_mv: int, *, persist: bool = False) None
Set the SPI bus target voltage.
- Parameters:
voltage_mv – Target voltage in millivolts. The multifunction port accepts
0(rail off) and 1200-3300 mV; an out-of-range value is rejected by the firmware.persist – When
True, store the setting across power cycles.
- Raises:
ValidationError – If
voltage_mvdoes not fit auint16.DisconnectError – If the device is no longer open.
SystemFirmwareError – If the firmware rejects the voltage. Voltage rejection is
FW_VOLTAGE_OUT_OF_RANGE(0x0101), which sits in the 0x01xx system band shared by every interface — a different band from (and unrelated to) the emptyFW_SPI_*one described above. With firmware 4.7.0 or newer, a port that another source already powers is refused withFW_EXTERNAL_VOLTAGE_DETECTED(0x0102) and the rail stays off; calluse_external_voltageinstead.
- use_external_voltage(*, persist: bool = False) None
Hand the SPI rail over to an externally supplied voltage.
Tells the adapter that the multifunction VTARG rail — shared with I2C, UART and GPIO — comes from outside, so it stops driving it itself and measures the supplied rail instead. Read back what it measured with
get_bus_voltage.- Parameters:
persist – When
True, store the voltage measured now. After a power cycle the adapter drives the rail itself at that voltage; it does not store “external”.- Raises:
DisconnectError – If the device is no longer open.
SystemFirmwareError – If the firmware rejects the switch — a 0x01xx system-band code, the same band
set_voltagedocuments.
- get_bus_voltage() int
Read the voltage measured on the shared multifunction VTARG rail.
This is not an SPI-specific measurement. The adapter has one multifunction VTARG rail shared by I2C, SPI, UART and GPIO, and this reads that single rail, so the reading reflects whatever last set or is driving it — pair this with
use_external_voltage.I2cController.get_bus_voltage,Uart.get_bus_voltageandGpio.get_voltagereturn the very same number. Upstream implements all four by issuing onegetAnalogMeasurementscommand and returning itsvmeasVtargI2cSpiUartGpio_mVfield (CosmicSDK.cpp); the four names exist to keep each protocol’s surface self-contained, not because four rails are measured.I3cController.get_bus_voltageis the genuine exception: I3C has its own HV and LV rails and reports both.- Returns:
The measured voltage in millivolts.
- Raises:
DisconnectError – If the device is no longer open.
FirmwareError – On a firmware-reported failure.
- transfer(data: bytes) bytes
Clock
dataout on MOSI and return what came back on MISO.One full-duplex exchange: the transfer is as long as
data, so the returned buffer carries the same number of frames. To read from a device that answers a command in later frames, append padding bytes todataand slice the reply out of the result.- Parameters:
data – Frames to transmit. Must not exceed the adapter’s maximum SPI transfer (1024 bytes); a larger value raises
ValidationError.- Returns:
The frames received while
datawas clocked out.- Raises:
ValidationError – If
dataexceeds the maximum transfer size.DisconnectError – If the device is no longer open.
FirmwareError – On a firmware-reported failure — notably if the interface has not been initialised.
- property max_transfer_bytes: int
Largest payload one
writeorreadcan carry on this connection.1024–MAX_SPI_PAYLOAD_BYTES, the ordinary HID route’s ceiling – unless this host and device can both carry the SPI controller bulk pair, in which case it is the ceiling the device advertised for its SPI command group. That figure differs by model – 32768 on a Supernova, 16384 on a Pulsar, each equal to that product’s whole bulk staging arena – which is why this is a property to read rather than a constant to assume, and why it is not upstream’s compile-timeSPI_BULK_MAX_PAYLOAD: a host that hard-codes one product’s number has its oversized requests refused on the other.It is exactly the ceiling
writeandreadenforce, taken from the same_select_transportcall they make, so the number cannot disagree with the refusal. A device that advertised less than the HID route’s 1024 is therefore reported as it advertised, not raised to 1024: the bulk route is the oneSpiTransport.AUTOtakes when it is available, and that route’s own bound is what applies.`transfer` is not described by this number and cannot be. It is full-duplex, the bulk write command carries no MISO to hand back, and so there is no bulk command for it to ride:
transferis capped atMAX_SPI_PAYLOAD_BYTES(1024) on every connection, whatever this reports. One number per controller cannot cover a method with a transport choice and a method without one, so this covers the two that have one and names the third rather than averaging them.Reading it runs the bulk negotiation if it has not run yet, which is at most one device query per connection – and none at all on a build whose extension has no bulk surface, since the seam is a
getattr.- Returns:
- Raises:
DisconnectError – If the device is no longer open.
- property transport: SpiTransport
Which transport
writeandreaduse on this connection.The answer to “did my transfer go over the fast path”, askable before the transfer rather than after it. It is a stable property of the connection, not a record of the last call: the negotiation is latched, and transport selection is otherwise a pure function of that latch and the
transport=argument, so this cannot go stale between reading it and callingwrite.Reading it runs the negotiation if it has not run yet, which is the only device traffic this property can cause and happens at most once per connection – and none at all on a build whose extension has no bulk surface, or once
SpiTransport.HIDhas been forced, since both short-circuit before the query.- Returns:
SpiTransport.BULKorSpiTransport.HID– neverSpiTransport.AUTO.- Raises:
DisconnectError – If the device is no longer open.
- write(data: bytes, *, transport: SpiTransport = SpiTransport.AUTO) None
Clock
dataout on MOSI and discard what comes back on MISO.The half-duplex write a flash page program or a register store actually is. Chip select is asserted across the whole payload, exactly once, on either transport.
Prefer this over
transferwhen the reply frames are not wanted:transferis fine and unchanged, but onlywritecan reach the bulk transport’s larger payload ceiling, becausetransferhas to hand back MISO and the bulk write command does not carry it.- Parameters:
data – Frames to transmit. Must not be empty. Must not exceed the active transport’s ceiling – 1024 bytes on the HID path, or
capabilities.bulk.max_spi_payload_byteson the bulk one. A larger value raisesValidationError; it is never split across two calls.transport – Which host transport to use.
SpiTransport.AUTO, the default, takes the bulk plane when it is available on this host and device and the HID path otherwise.
- Raises:
ValidationError – If
datais empty or exceeds the active transport’s ceiling. No facade call is made.CapabilityError – If
transportisSpiTransport.BULKand the bulk transport is unavailable – most often because this host has not claimed the adapter’s vendor USB interface. No facade call is made.DisconnectError – If the device is no longer open.
FirmwareError – On a firmware-reported failure – notably if the interface has not been initialised.
Note
Blocking-only. There is deliberately no
AsyncSpiController.write– seeAsyncSpiControllerfor the reason.
- read(command: bytes, length: int, *, transport: SpiTransport = SpiTransport.AUTO) bytes
Clock
commandout, then readlengthbytes back under the same CS.The half-duplex read a target’s “read register” or “read page” command actually is: a command phase out, then a data phase in, with chip select asserted across both. Only the data phase is returned – the command bytes are not echoed back, so no slicing is needed at the call site.
- Parameters:
command – The command bytes to clock out first. May be empty for a target that needs no command phase. On the HID path
len(command) + lengthmust fit the 1024-byte transfer.length – How many bytes to read back. Must be at least 1, and must not exceed the active transport’s ceiling. A larger value raises
ValidationError; the read is never split, because continuing one requires re-issuing the target’s own read command at an incremented address and this SDK does not know that encoding.transport – Which host transport to use.
SpiTransport.AUTO, the default, takes the bulk plane when it is available on this host and device and the HID path otherwise.
- Returns:
Exactly
lengthbytes from the data phase.- Raises:
ValidationError – If
lengthis less than 1, or the request exceeds the active transport’s ceiling. No facade call is made.CapabilityError – If
transportisSpiTransport.BULKand the bulk transport is unavailable – most often because this host has not claimed the adapter’s vendor USB interface. No facade call is made.DisconnectError – If the device is no longer open.
FirmwareError – On a firmware-reported failure – notably if the interface has not been initialised.
ProtocolError – If the HID path’s reply is not as long as the frame that produced it, so the data phase this promises is not in it.
Note
The HID fallback issues a single
spiTransferofcommand + bytes(length)and slices the reply fromlen(command). That is believed to be an exact equivalent of the bulk read, because onespiTransferis one frame and holds chip select across all of it – which is the property the bulk read’s two phases exist to preserve. Inferred from the transport’s structure, not confirmed on a bench, and the confirmation is a scope-comparison against a real target once the bulk path is reachable.Note
⚠ The slice offset is not bench-confirmed either, and there is evidence against it. #99 records two sightings where the data phase did not start at
len(command)on the HID path – once as an all-zero read whiletransfer()returned the target’s id on the same part. The length check above catches a reply that is the wrong size; it cannot catch one that is the right size with the data in the wrong place. Until #99 is settled,transfer()is the honest choice when a read must not be silently wrong, and the bulk path was the stable one in both sightings.Note
Blocking-only. There is deliberately no
AsyncSpiController.read– seeAsyncSpiControllerfor the reason.
- class pycosmicsdk.AsyncSpiController(*, device: AsyncDevice)
Bases:
objectAsyncio SPI controller for an open device.
The asyncio twin of
SpiController, returned byAsyncDevice.spi; the constructor is package-private. The interface is cached on the device, so repeateddev.spi()calls return the same controller. Argument validation matches the blocking controller; commands are awaited and correlated through the device’s engine rather than serialized under a lock.Warning
This class is deliberately NOT a complete mirror of `SpiController`, and it is the first such asymmetry in this package. Do not “finish the surface”.
SpiController.write,SpiController.readandSpiController.transporthave no counterpart here, and that is a design decision with a specific cause, not an oversight or an unfinished task.The reason is in
CosmicAsyncSDK. ItsspiWriteBulkpushes the payload by callingsendPayloadinside the submit, immediately aftersendRequestand before any reply, with a 2000 ms timeout (SPI_BULK_PAYLOAD_TIMEOUT_MS). So the “submit” can block for up to two seconds. Every*_asyncsubmit in this package is required to be non-blocking – one non-blocking submit per public operation is the whole contract ofAsyncDeviceFacade, and it is what lets an awaiting coroutine yield instead of pinning a thread. A submit that can stall for two seconds would stall the asyncio event loop itself, and every other coroutine on it with it, which is worse than not offering the method.Exposing the half-duplex pair here over the HID path only would be worse still:
SpiController.writeandSpiController.readare transport-negotiating methods, so an asyncio twin that could never reach the bulk plane would silently mean something different from its blocking namesake.If an asyncio surface is wanted later, the honest shape is
loop.run_in_executoraround the blocking call – not a facade*_asyncsubmit.AsyncSpiController.transferis unaffected and stays a full mirror ofSpiController.transfer.Note
The rail surface matches
SpiControllerin full,get_bus_voltageincluded.CosmicAsyncSDKdeclares nospiGetBusVoltage, but it does declaregetAnalogMeasurements, which carries the shared I2C/SPI/UART/GPIO rail — and that is the same command upstream’s blockingspiGetBusVoltageissues internally. So the async reader is built the same way as the blocking one, and reports the same value.- async initialize(*, bit_order: SpiBitOrder, mode: SpiMode, data_width: SpiDataWidth, chip_select: SpiChipSelect, chip_select_polarity: SpiChipSelectPolarity, frequency_hz: int) AsyncSpiController
Bring the SPI controller up and return
selffor chaining.Not idempotent: a re-run on an already-initialised interface reports
FW_INTERFACE_ALREADY_INITIALIZED. The interface stays initialised across reconnects, so a re-run hits this unless the device is reset first. Useconfigureto change parameters of a live interface.- Parameters:
bit_order – Whether each frame goes out most- or least-significant bit first.
mode – Clock polarity and phase pair (SPI modes 0 to 3).
data_width – Frame size, 8 or 16 bits.
chip_select – Which chip-select line to drive.
chip_select_polarity – Whether the chip-select line is active low or active high.
frequency_hz – SCLK frequency in Hz. Both adapters accept
10_000-50_000_000, with the usable maximum depending on model and hardware revision; a rejected rate surfaces asFirmwareError.
- Returns:
This controller, to allow chaining.
- Raises:
ValidationError – If
frequency_hzdoes not fit auint32.DisconnectError – If the device is no longer open.
FirmwareError – If the interface is already initialised (
status_codeFW_INTERFACE_ALREADY_INITIALIZED), or if the firmware rejects the configuration.
- async bring_up(*, bit_order: SpiBitOrder, mode: SpiMode, data_width: SpiDataWidth, chip_select: SpiChipSelect, chip_select_polarity: SpiChipSelectPolarity, frequency_hz: int) BringUpResult
Bring the SPI controller up, re-runnably, and report what took effect.
The composite over
initializeandconfigurethat a re-runnable script wants, and the recommended way to bring the SPI controller up. Call it instead ofinitializeunless you specifically need the primitive’s faithfulFW_INTERFACE_ALREADY_INITIALIZED.initializeis not idempotent and the interface stays initialised across aclose()/reopen cycle, so the second run of any script is the common case rather than an edge case. This triesinitializefirst; onFW_INTERFACE_ALREADY_INITIALIZEDit reconfigures the live controller withconfigureinstead, and reports a refused reconfigure through the return value rather than raising.Semantics are identical to
I2cController.bring_up, and so is the return type, deliberately: one contract across protocols is worth more than a per-protocol shape. Two SPI-specific details are worth knowing:configurereaches every parameterinitializetakes, so the fallback path requests exactly what the fresh path would have. There is no SPI setting that survives only theinitializeroute.No capability guard gates SPI reconfiguration today, so in practice the fallback succeeds and
settings_appliedisFalseonly if firmware rejects a value it accepted at first bring-up. TheCapabilityErrorarm below is not dead weight: it is what keeps this contract protocol-independent without an edit here if a guard lands.
A refusal raised by
initializeitself is a different thing from a refused reconfigure and propagates untouched, whatever its type: nothing reached the device, so the interface is not up, and the first half ofbring_up’s contract – on return the interface is usable – cannot be kept by a return value. Only the already-up path can report.- Parameters:
bit_order – Bit order requested for each frame – most- or least-significant bit first.
mode – Clock polarity and phase pair requested (SPI modes 0 to 3).
data_width – Frame size requested, 8 or 16 bits.
chip_select – Chip-select line requested.
chip_select_polarity – Chip-select active polarity requested.
frequency_hz – SCLK frequency in Hz requested. Both adapters accept
10_000-50_000_000, with the usable maximum depending on model and hardware revision.
- Returns:
A
BringUpResult. On return the controller is up and usable; consultsettings_appliedbefore relying on the requested parameters actually being in force.- Raises:
ValidationError – If
frequency_hzdoes not fit auint32.DisconnectError – If the device is no longer open.
FirmwareError – On any firmware failure other than the two the fallback absorbs – a frequency
initializeitself rejects, for instance.
- async configure(*, bit_order: SpiBitOrder, mode: SpiMode, data_width: SpiDataWidth, chip_select: SpiChipSelect, chip_select_polarity: SpiChipSelectPolarity, frequency_hz: int) None
Reconfigure an already-initialised SPI controller at runtime.
Updates every controller parameter without tearing the interface down. It must have been brought up first with
initialize; an uninitialised interface reportsFW_INTERFACE_NOT_INITIALIZEDand surfaces asFirmwareError.- Parameters:
bit_order – New frame bit order.
mode – New clock polarity and phase pair.
data_width – New frame size, 8 or 16 bits.
chip_select – New chip-select line to drive.
chip_select_polarity – New chip-select active polarity.
frequency_hz – New SCLK frequency in Hz. Both adapters accept
10_000-50_000_000; a rejected rate surfaces asFirmwareError.
- Raises:
ValidationError – If
frequency_hzdoes not fit auint32.DisconnectError – If the device is no longer open.
FirmwareError – If the firmware rejects the request — notably if the interface has not been initialised.
- async release() None
Release the SPI role this peripheral holds.
The asyncio twin of
SpiController.release, with the same semantics: no argument, the role released is whichever one the peripheral holds, this object keeps no role state to invalidate, and the shared bulk negotiation is left alone. Read that method for why the release lives on the controller as well as on the target – the short version is thatAsyncDevice.spi_targetrefuses a rev-B Supernova, which leaves this the only way to hand back that unit’s SPI role – and for the first-transfer caveat, which is a bench question this call makes reachable.Note
An
AsyncSpiTargethandle you also hold is not updated by this call; useAsyncSpiTarget.releasewhen a target is what is armed.- Raises:
DisconnectError – If the device is no longer open.
CosmicTimeoutError – If the request exceeds the per-request timeout.
FirmwareError – If the peripheral held no role (
FW_INTERFACE_NOT_INITIALIZED) or the firmware predates the command (FW_UNSUPPORTED_COMMAND). Neither is absorbed, and both are generic codes rather thanSpiError.
- async set_voltage(voltage_mv: int, *, persist: bool = False) None
Set the SPI bus target voltage.
- Parameters:
voltage_mv – Target voltage in millivolts. The multifunction port accepts
0(rail off) and 1200-3300 mV; an out-of-range value is rejected by the firmware and surfaced asFirmwareError.persist – When
True, store the setting across power cycles.
- Raises:
ValidationError – If
voltage_mvdoes not fit auint16.DisconnectError – If the device is no longer open.
SystemFirmwareError – If the firmware rejects the voltage. Voltage rejection is
FW_VOLTAGE_OUT_OF_RANGE(0x0101), which sits in the 0x01xx system band shared by every interface — a different band from (and unrelated to) the emptyFW_SPI_*one described in the module docstring. With firmware 4.7.0 or newer, a port that another source already powers is refused withFW_EXTERNAL_VOLTAGE_DETECTED(0x0102) and the rail stays off; calluse_external_voltageinstead.
- async use_external_voltage(*, persist: bool = False) None
Hand the SPI rail over to an externally supplied voltage.
Tells the adapter that the multifunction VTARG rail — shared with I2C, UART and GPIO — comes from outside, so it stops driving it itself and measures the supplied rail instead.
- Parameters:
persist – When
True, store the voltage measured now. After a power cycle the adapter drives the rail itself at that voltage; it does not store “external”.- Raises:
DisconnectError – If the device is no longer open.
SystemFirmwareError – If the firmware rejects the switch — a 0x01xx system-band code, the same band
set_voltagedocuments.
Note
Read back what the adapter measured with
get_bus_voltage.
- async get_bus_voltage() int
Read the voltage measured on the shared multifunction VTARG rail.
The asyncio twin of
SpiController.get_bus_voltage, with the same return type and the same meaning. It is not an SPI-specific measurement: the adapter has one multifunction VTARG rail shared by I2C, SPI, UART and GPIO, andAsyncI2cController.get_bus_voltage,AsyncUart.get_bus_voltageandAsyncGpio.get_voltagereturn the very same number. Pair this withuse_external_voltage.Implemented over
getAnalogMeasurements, which reads every rail in one transaction; this keeps the shared-rail field and discards the rest, exactly as upstream’s blockingspiGetBusVoltagedoes. UseAsyncDevice.get_analog_measurementsto get all six rails at once instead of one field per call.- Returns:
The measured voltage in millivolts.
- Raises:
DisconnectError – If the device is no longer open.
ProtocolError – If the response was too short to carry the measurements, which is also how firmware that does not implement the command reports itself.
CosmicTimeoutError – If the request exceeds the per-request timeout.
FirmwareError – On a firmware-reported failure.
- async transfer(data: bytes) bytes
Clock
dataout on MOSI and return what came back on MISO.One full-duplex exchange: the transfer is as long as
data, so the returned buffer carries the same number of frames. To read from a device that answers a command in later frames, append padding bytes todataand slice the reply out of the result.- Parameters:
data – Frames to transmit. Must not exceed the adapter’s maximum SPI transfer (1024 bytes); a larger value raises
ValidationError.- Returns:
The frames received while
datawas clocked out.- Raises:
ValidationError – If
dataexceeds the maximum transfer size.ProtocolError – If the response is too short to carry the frames it claims.
DisconnectError – If the device is no longer open.
FirmwareError – On a firmware-reported failure — notably if the interface has not been initialised.
SPI — target mode
- class pycosmicsdk.SpiTarget(*, device: Device)
Bases:
objectBlocking SPI target-mode interface for an open device.
In target mode the adapter stops driving the bus and answers an external SPI controller out of a memory arena drawn from the device’s bulk staging pool – 32 KB on a Supernova, 16 KB on a Pulsar, reported by bring-up and readable through
arena_size. The constructor is package-private.Read the comment block above this class before calling
initialize: the role switch is exclusive,releaseis what reverses it, and because the arena is the device’s shared staging pool, an adapter serves one target protocol at a time – an I2C target and a SPI target cannot be armed at once, though either order works in one session with areleasebetween.Three differences from
SpiController, each of which is the difference between the two roles rather than a gap to be tidied up later:No ``frequency_hz`` and no ``data_width``. A target neither generates the clock nor chooses the frame width; the external controller does both.
modeandbit_orderare still here because the target has to agree with the controller about them.The arena is bulk-only. There is no HID path for
write_memory/read_memory, soinitializerefuses outright on a host that has not claimed the adapter’s vendor USB interface.SpiController.transferis unaffected and needs no bulk plane at all.There is no ``add_register_region``.
SpiTargetModelhas aREGISTER_MAPmember and firmware has a command code for the region table, but no CosmicSDK revision wraps it, and upstream lists it under known gaps with a reason: SPI’s register-map model is driven in band by the command byte, since SPI has no dedicated read/write address bit like I2C’s. A method raisingNotImplementedErrorwould advertise a feature the SDK cannot perform, so there is none.I2cTargethas one because I2C’s wrapper exists.
Note
releaseis the counterpart ofinitialize, and it is the one method here whose shape was decided before it could be written: this class was left deliberately ready for it.spiDeinit()turned out to take no argument, so the pre-planned signature stands – but two things about it were not anticipated. It releases whichever role the peripheral holds, a controller’s included, so it is not “the target role’s release”; and because firmware’s teardown hands the shared arena back with it,arena_sizeand the remembered model have to be invalidated rather than merely left alone.releasedocuments both.- property arena_size: int | None
Bytes of staging arena the device reported for this target.
The value that decides whether a preload fits: 32768 on a Supernova and 16384 on a Pulsar today, but a runtime figure the device reports rather than a constant, so read it instead of assuming either. On a Pulsar a 16 KB write fills the arena exactly at offset 0 and overruns it at any other offset.
Noneuntil this object has brought the interface up – the same fail-open posture the I3C target takes towards its memory layout. Note that a bareinitializewhich raisedFW_INTERFACE_ALREADY_INITIALIZEDleaves it unset, because upstream leavesmemorySizeat 0 on that path;configurereports it, which makesbring_upthe only single call that always leaves this populated.- Returns:
The reported arena size in bytes, or
Noneif unknown.
- property max_transfer_bytes: int
Largest payload one
write_memoryorread_memorycall may carry.min(arena_size, advertised ceiling)– the pair, never upstream’s compile-timeSPI_BULK_MAX_PAYLOAD, because the advertised ceiling differs between products (32768 on a Supernova, 16384 on a Pulsar) and a host that assumes one product’s number gets its oversized requests refused on another.0when the arena is unreachable on this connection, since no size is honest then – which also makes this the one-call answer to “can this target’s memory be touched at all”, rather than leaving a caller to assemble it out ofBulkTransportCapability.supports.Reading it runs the bulk negotiation if it has not run yet, which is at most one device query per connection.
- Returns:
The per-call ceiling in bytes, or
0.- Raises:
DisconnectError – If the device is no longer open.
- initialize(*, mode: SpiMode, bit_order: SpiBitOrder, chip_select: SpiChipSelect, chip_select_polarity: SpiChipSelectPolarity, model: SpiTargetModel, engine: SpiTargetEngine, require_arena_access: bool = True) SpiTarget
Switch the adapter into SPI target mode and return
selffor chaining.This takes the SPI role, exclusively, and
releaseis what gives it back – the comment block above this class says what that costs while it is held.initializeis also not idempotent: a re-run on an adapter already in SPI target mode reportsFW_INTERFACE_ALREADY_INITIALIZED, so preferbring_upunless you specifically need that faithful failure.Two refusals happen before anything reaches the device, and both are
CapabilityError:A hardware revision that cannot receive a chip select at all. 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.A host or device that cannot reach the arena over the vendor bulk endpoints. Target memory has no HID fallback, so a target brought up without them would be inert. Read
capabilities.bulk.
- Parameters:
mode – Clock polarity and phase pair the external controller uses (SPI modes 0 to 3). The target does not generate the clock, but it has to sample on the same edge.
bit_order – Whether each frame arrives most- or least-significant bit first.
chip_select – Which chip-select line this target answers on. Echoed back in the bus-event notifications.
chip_select_polarity – Whether that line is active low or high.
model – Which part the target emulates –
SpiTargetModel.MEMORY,SpiTargetModel.FLASHorSpiTargetModel.REGISTER_MAP. It is remembered, so a laterconfigure_flashon a non-flash target is refused here rather than on the wire.engine – Which servicing engine to use –
SpiTargetEngine.DMAorSpiTargetEngine.ISR. Firmware reads it for theMEMORYmodel only and ignores it for the other two, which is why there is no validation pairing the two arguments: passing an engine with aFLASHmodel is legal and inert.require_arena_access – Whether to refuse when this host and device cannot reach the target’s memory arena.
True, the default, is right for every target the host seeds or drains, which is almost all of them: without the vendor bulk endpoints such a target services the bus perfectly whilewrite_memoryandread_memoryare both unusable, and there is no HID fallback to demote to. PassFalseto arm a bus-only target deliberately – one whose contents the external controller writes and this host never touches again.
- Returns:
This interface, to allow chaining.
- Raises:
CapabilityError – If this hardware revision cannot act as a SPI target, or if the arena would be unreachable from this host and device and
require_arena_accessis set. No facade call is made in either case.DisconnectError – If the device is no longer open.
SpiError – If the SPI peripheral is owned by the controller role (
status_codeFW_SPI_ROLE_CONFLICT), or the shared bulk staging pool the arena comes from is already held –FW_SPI_TARGET_STAGING_UNAVAILABLE, which is what an I2C target currently armed looks like from here – release that one and this call succeeds. Both sit in the 0x03xx band, so unlike the I3C target’s failures these do surface as the protocol-specific class.FirmwareError – If the adapter is already in SPI target mode (
status_codeFW_INTERFACE_ALREADY_INITIALIZED, a generic code rather than a 0x03xx one), or the firmware rejects the configuration.
- bring_up(*, mode: SpiMode, bit_order: SpiBitOrder, chip_select: SpiChipSelect, chip_select_polarity: SpiChipSelectPolarity, model: SpiTargetModel, engine: SpiTargetEngine, require_arena_access: bool = True) BringUpResult
Bring SPI target mode up, re-runnably, and report what took effect.
The composite over
initializeandconfigurethat a re-runnable script wants, and the recommended way in. Call it instead ofinitializeunless you specifically need the primitive’s faithfulFW_INTERFACE_ALREADY_INITIALIZED.It is also the only single call that always leaves
arena_sizepopulated: upstream leavesmemorySizeat 0 on the already-up path, so a bareinitializethat raises there reports no capacity, while the reconfigure this falls back to does.settings_appliedis alwaysTruehere, and that is honest rather than a placeholder –spiTargetSetParametersdocuments no refusal status to absorb and no capability gates it, so there is nothing for the second call to report.already_initializedvaries, and it is the bit a re-run cares about. The comment block above this class explains at length why no refused-status filter belongs here, andtests/unit/test_spi_target.pypins its absence.A firmware failure
initializeitself raises propagates untouched – notablyFW_SPI_ROLE_CONFLICT, which must never be absorbed: the adapter would still be driving the bus as a controller, so no return value could keep this method’s contract.- Parameters:
mode – Clock polarity and phase pair requested.
bit_order – Frame bit order requested.
chip_select – Chip-select line this target answers on.
chip_select_polarity – Chip-select active polarity requested.
model – Which part the target emulates. See
initialize.engine – Which servicing engine to use. See
initialize.require_arena_access – Whether to refuse when the target’s memory arena would be unreachable. See
initialize.
- Returns:
A
BringUpResult. On return the target is up and the requested parameters are in force.- Raises:
CapabilityError – As
initialize, and for the same two reasons. No facade call is made.DisconnectError – If the device is no longer open.
SpiError – On a 0x03xx failure from either call – a role conflict or an unavailable staging pool.
FirmwareError – On any firmware failure other than the one the fallback absorbs.
- release() None
Release the SPI role, and the arena with it.
spiDeinitin upstream’s terms, and the call the comment block above this class was written waiting for. After it the peripheral holds no role, so a controller bring-up succeeds – an in-session release, with no reboot and no freshDevice.No argument, because the adapter has one SPI peripheral. That is the asymmetry with
I2cTarget.release, which takes its bus from the interface: upstream follows the firmware’s request structs rather than symmetry, andspi_mgr.creads only the header.It releases whichever role the peripheral holds, not specifically a target one. A controller is torn down by this same call, with the armed transfer aborted and the FIFOs emptied. The method lives here because this is where the need arises.
What this object forgets.
arena_sizeand the remembered model go back toNone, because the arena they describe is gone – firmware’s teardown hands the shared bulk-staging pool back, which is what lets an I2C target arm afterwards. They are cleared even when the call fails, since a refusal or a timeout leaves the arena’s fate unknown andNoneis this class’s word for unknown.max_transfer_bytestherefore falls back to the advertised ceiling, exactly as before a bring-up.Event capture is torn down too – the subscription is cancelled and any thread parked in
wait_for_eventis woken withDisconnectError, followingUart.deinit– but only when the release succeeded. A release that failed may have left a live target whose events are the only record that a transaction happened.The bulk negotiation is deliberately not dropped: it records this host’s transport and the device’s advertised ceilings, neither of which a role release changes, and the latch is shared with
SpiController.Note
Nothing is absorbed. A peripheral holding no role answers
FW_INTERFACE_NOT_INITIALIZEDrather than succeeding quietly, matchingUart.deinit; a caller who wants an idempotent release catches that one code.Note
The first transfer after a release is not proven byte-clean. Firmware carries an open issue on a first-transfer byte skew reachable only through controller → deinit → target, and the candidate fix is not confirmed closed –
docs_dev/hardware_validation/firmware-role-and-target-mode.md§4. That is a bench question, and this method makes it reachable for the first time from Python.- Raises:
DisconnectError – If the device is no longer open.
FirmwareError – If the peripheral held no role (
FW_INTERFACE_NOT_INITIALIZED) or the firmware predates the command (FW_UNSUPPORTED_COMMAND). Both are generic codes below the 0x03xx band, so neither surfaces asSpiError, and there is noFW_BUS_NOT_SUPPORTEDin the list because there is no bus to name. The second is the one a caller cannot pre-empt: firmware’s version string is a compile-time constant, so a build that dispatches the command and one that does not both report the same version.
- configure(*, mode: SpiMode, bit_order: SpiBitOrder, chip_select: SpiChipSelect, chip_select_polarity: SpiChipSelectPolarity, model: SpiTargetModel, engine: SpiTargetEngine) None
Reapply the target parameters of an adapter already in SPI target mode.
The idempotent counterpart of
initialize, and the call upstream names whenspiTargetInitreportsFW_INTERFACE_ALREADY_INITIALIZED. It keeps the arena’s contents and reports the arena’s size, soarena_sizeis populated afterwards either way.The adapter must already hold the SPI target role; one that does not reports
FW_INTERFACE_NOT_INITIALIZED.- Parameters:
mode – New clock polarity and phase pair.
bit_order – New frame bit order.
chip_select – New chip-select line to answer on.
chip_select_polarity – New chip-select active polarity.
model – Which part the target emulates. See
initialize.engine – Which servicing engine to use. See
initialize.
- Raises:
DisconnectError – If the device is no longer open.
FirmwareError – If the firmware rejects the request – notably when the adapter is not in SPI target mode (
FW_INTERFACE_NOT_INITIALIZED). No `SpiError` here, and the absence is the point:spiTargetSetParametersdocuments no 0x03xx status at all – no refusal, no role conflict, no staging failure – which is exactly whybring_up’s reconfigure arm absorbs nothing.
- configure_flash(*, address_bytes: int, page_size: int, write_cycle_us: int, jedec_id: bytes = b'', require_write_enable: bool = True) None
Describe the flash part a
FLASH-model target emulates.Only meaningful on a target brought up with the
FLASHmodel. When this object selected the model itself and it was a different one, the call is refused locally withValidationErrorrather than sent for firmware to answerFW_SPI_TARGET_WRONG_MODEL; when the model is unknown – an adapter another process left in target mode – it is sent, and firmware answers.- Parameters:
address_bytes – Width of the address phase the emulated part expects, 2 or 3.
page_size – Program page size in bytes. Must be a power of two.
write_cycle_us – How long a page program takes, in microseconds, so a datasheet’s “5 ms write cycle” is
5000. Deliberately not float seconds like a host timeout: this is the emulated part’s own datasheet number.jedec_id – The bytes answered to a read-id command, 0 to 8 of them. One argument rather than upstream’s
(idLength, id[8])pair, exactly as the I3C target’spidalready resolves – the length is derived from what you pass, so the two cannot disagree.require_write_enable – Whether a program needs a write-enable latch set first, which is real flash behaviour. This is
flagsbit 0; every other bit of that byte is reserved and documented “send 0”, which is why this is a boolean and not a raw int.
- Raises:
ValidationError – If
address_bytesis not 2 or 3,page_sizeis not a positive power of two,jedec_idis longer than 8 bytes,page_sizeorwrite_cycle_usdoes not fit its field, or this target is known to be running a different model. No facade call is made.DisconnectError – If the device is no longer open.
SpiError – If firmware rejects the configuration (
FW_SPI_TARGET_INVALID_CONFIG) or the target is running a different model (FW_SPI_TARGET_WRONG_MODEL).
- write_memory(address: int, data: bytes) None
Preload
datainto the target’s arena at byte offsetaddress.Seeds the bytes the external controller will read back. ``address`` is a byte offset into the arena, not a scaled register index – the opposite of
I3cTarget.write_memory, which shares this parameter’s name and type and multiplies it by the configured register width. Nothing is scaled here.Once this returns successfully the arena is bus-ready. Firmware defers its reply until the servicing engine has re-armed, which was a real race found during hardware validation and fixed there, so no caller-side settle delay should ever be necessary – needing one indicates a firmware regression rather than something to work around on the host.
- Parameters:
address – Byte offset into the arena to start writing at. Must fit a
uint16.data – Payload bytes. Must not be empty, and must fit the arena from
addressonwards without exceeding the advertised bulk payload ceiling. It is never split.
- Raises:
ValidationError – If
addressdoes not fit auint16,datais empty, or the access runs past the smaller of the arena and the ceiling. No facade call is made.DisconnectError – If the device is no longer open.
SpiError – On a 0x03xx firmware failure – notably
FW_SPI_TARGET_SURPASSED_MEMORY_SIZEwhen the device’s own bound is tighter than this SDK could know, orFW_SPI_TARGET_PAYLOAD_TRANSFER_FAILED.FirmwareError – If the adapter is not in SPI target mode.
Note
Blocking-only, and there is deliberately no asyncio twin – see
AsyncSpiTargetfor the reason.Note
Do not call this from inside a notification subscriber callback. Subscriber callbacks run on the dispatcher thread, which delivers every notification for the connection in order; a bulk transfer can hold it for the full payload timeout (2 s per phase), during which nothing else is delivered and the bounded notification queue starts dropping its oldest entries. It is not a deadlock and it is not forbidden, but it is congestion the caller controls and the SDK cannot. Record the event in the callback and move the bytes on your own thread.
- read_memory(address: int, length: int) bytes
Read
lengthbytes back out of the target’s arena fromaddress.Reads what the external controller wrote.
addressis a byte offset, exactly as inwrite_memory, and is scaled by nothing.- Parameters:
address – Byte offset into the arena to start reading at. Must fit a
uint16.length – Number of bytes to read. Must be at least 1, and must fit the arena from
addressonwards without exceeding the advertised bulk payload ceiling.
- Returns:
Exactly
lengthbytes from the arena.- Raises:
ValidationError – If
addressdoes not fit auint16,lengthis zero, or the access runs past the smaller of the arena and the ceiling. No facade call is made.DisconnectError – If the device is no longer open.
SpiError – On a 0x03xx firmware failure – notably
FW_SPI_TARGET_SURPASSED_MEMORY_SIZE.FirmwareError – If the adapter is not in SPI target mode.
Note
Blocking-only, and there is deliberately no asyncio twin – see
AsyncSpiTargetfor the reason.Note
Do not call this from inside a notification subscriber callback, for the reason
write_memorygives. The bus-event notification carries only metadata – chip select and payload length; its arena offset is a firmware placeholder, seeSpiTargetBusEvent– and never the bytes, so fetching them always means a call like this one, which makes the dispatcher-thread trap easy to fall into here.Note
The bytes may have changed by the time you read them. Nothing stops the external controller writing that region again between the notification and this call; the notification reports what happened, not a snapshot.
- wait_for_event(*, timeout: float | None = None) SpiTargetBusEvent
Return the oldest queued bus transaction, waiting for one if none is queued.
The pull path for what the external controller does to this target. Events are queued as the firmware reports them and each call pops exactly one, oldest first; the queue is never trimmed, so a burst serviced between two calls is delivered in full. For push delivery, subscribe to
SpiTargetBusEventNotificationviaDevice.subscribe.The event carries no bytes, and on current firmware it does not carry a usable offset either. Both are the device’s shape rather than omissions here. The notification is metadata only – upstream says so outright – and its
memory_addressis hard-coded to0at both firmware emit sites regardless of where in the arena the transaction landed, because theFLASHandREGISTER_MAPmodels track their address internally and never report it. So the event tells you that a transaction completed, on which chip select, and how many bytes it moved; where it landed is something you know from the protocol you are emulating, and you pass that offset toread_memoryyourself.There is deliberately no ``read_event_payload`` convenience. One was designed, and it cannot be written honestly while the offset is a placeholder: it would read arena byte 0 on every call while its signature implied it read the region the event named. It becomes worth adding if firmware starts populating the field.
Note
Safe to call concurrently with competing-consumer semantics; each event is delivered to exactly one waiter, non-deterministically.
Warning
The bus-event path has no upstream test. Both of upstream’s own hardware tests bypass the bus-event accessors and poll the arena directly, and upstream says so in its known-gaps list: anyone building a real target-mode application is the first to exercise this path. Test it against a real controller before trusting it, and do not assume the I3C target’s experience transfers.
- Parameters:
timeout – Maximum seconds to wait when nothing is queued.
Nonewaits indefinitely;0returns immediately; a positive value waits up to that long. A negative value is rejected.- Returns:
The oldest queued bus event.
- Raises:
ValidationError – If
timeoutis negative.DisconnectError – If the device is not open, or if
Device.closeis called while this call is blocked waiting for an event.CosmicTimeoutError – If no event arrives within
timeout(only whentimeoutis notNone).
- class pycosmicsdk.AsyncSpiTarget(*, device: AsyncDevice)
Bases:
objectAsyncio SPI target-mode interface for an open device.
The asyncio twin of
SpiTarget, minus its arena’s bytes. Configuration in full,arena_sizeincluded; memory access not at all. Read the comment block aboveSpiTargetbefore callinginitialize– the role switch is exclusive, andreleaseis what reverses it.Warning
The bulk payload methods are deliberately absent from every async class in this package, and this is a design decision with a specific cause. Do not “finish the surface”.
The vendor bulk endpoint is an unframed byte stream. A bulk reply announces a length on the HID path and the bytes then arrive on the bulk endpoint with no transaction id, no header and no delimiter; the transport drains whatever is left over once the announced count has been collected. So the only thing that associates a payload with the command that asked for it is that nothing else was in flight.
One bulk transfer therefore owns the connection from submit to drain. Two overlapping bulk reads do not merely queue: the first one’s drain discards the head of the second one’s payload and the second times out. The read after that can be prefixed by up to one endpoint packet of bytes that were never meant for it – they come back plausible, the status comes back successful, and there is nothing on the endpoint to detect it with.
That last sentence used to be justified by “a timed-out collection does not drain”, which stopped being true when CosmicSDK
712e433made both failure paths fall through to the shareddrain(). The exposure is narrower now and it is not gone: the drain is guarded only byreadPayloadhaving been entered, and a bulk read that firmware refuses on the command channel returns before that (CosmicSDK.cpp:2956), so nothing drains on that path at all. A firmware refusal on a bulk read is the live case, not a timeout.An
async defthat has to hold a connection-exclusive lock across its ownawaitis a blocking call with a coroutine’s syntax, and it would be advertising concurrency the transport cannot provide. Use the blockingDevicefor arena traffic, where the per-device lock already provides that exclusion and the signature does not promise otherwise.Note that one adapter has one bulk plane: the vendor interface is claimed exclusively by whichever handle opened first, so a
Deviceand anAsyncDeviceon the same adapter cannot both use it. Choose one handle style per adapter.Two consequences of that absence worth stating outright, because they are where this class and its blocking twin actually diverge – and the first is a narrower divergence than it looks, because the arena’s size survives even though its bytes do not:
`arena_size` is here, but `SpiTarget.max_transfer_bytes` is not. The size is reachable and the ceiling is not, and the split is the bulk plane’s asymmetry rather than an oversight.
CosmicAsyncSDK’s target init carries nomemorySizeout-parameter, but the number is on the wire –SpiTargetInitResponseParametersis a packed{uint32 memorySize}immediately after the generic response header, and the async engine already delivers every response’s post-header bytes to this layer – so_decode_arena_sizereads it here in pure Python, exactly asAsyncI2cTarget.arena_sizereads its own{uint8 busId; uint32 memorySize}.max_transfer_bytescannot follow: it ismin(arena_size, advertised ceiling)or0, and both of its other terms – the advertised per-call ceiling and the arena operation bits – live in the negotiated capability record, which no async facade can decode (next bullet). A property here could only relabel the arena size as a transfer ceiling, and its0could never carry the “unreachable arena” meaning that is the blocking property’s main reason to exist. UseSpiTarget.max_transfer_byteson the handle that will move the bytes, which is the handle that can answer.This class gates on only the *host* half of the bulk plane, where
SpiTarget.initializegates on the whole negotiated capability. That is not a preference:CosmicAsyncSDK’sgetBulkCapabilitiesis a bare submit whose reply only the blockingCosmicSDKwrapper decodes, so no capability record is reachable from an async facade at all.initializecan therefore ask whether this machine holds the adapter’s vendor interface and nothing else. That is the half carrying the information – firmware derivessupportedfrom a group count that is never zero – but it is a real asymmetry between the two surfaces, and it is why a snapshot only ever touched through the asyncio surface never has its bulk capability negotiated at all. Passrequire_arena_access=Falseto arm a bus-only target deliberately, accepting an arena nobody can reach – which on a single adapter is the situation regardless, since this handle would hold the plane. Bring the target up here, watch its bus events here, and do arena traffic from a blockingDeviceon a different adapter; or do the whole thing blocking.
- property arena_size: int | None
Bytes of staging arena the device reported for this target.
The same figure and the same semantics as
SpiTarget.arena_size, and deliberately not a weaker version of them:Noneuntil this object has armed or reconfigured the interface, then whatever the most recent init or reconfigure announced. Decoded from the response packet’s{uint32 memorySize}parameters, becauseCosmicAsyncSDKhas no out-parameter to carry it.A bare
initializethat raisedFW_INTERFACE_ALREADY_INITIALIZEDleaves it unset – firmware announces no size on that path and the response never reaches the decode – whileconfigurereports it, which makesbring_upthe only single call that always leaves it populated. Identical to the blocking twin down to that case.- Returns:
The reported arena size in bytes, or
Noneif unknown.
- async initialize(*, mode: SpiMode, bit_order: SpiBitOrder, chip_select: SpiChipSelect, chip_select_polarity: SpiChipSelectPolarity, model: SpiTargetModel, engine: SpiTargetEngine, require_arena_access: bool = True) AsyncSpiTarget
Switch the adapter into SPI target mode and return
selffor chaining.The asyncio twin of
SpiTarget.initialize, with the same arguments and the same non-idempotence: preferbring_up. The hardware-revision refusal is identical and fires before the submit; the bulk-plane refusal is not made here, for the reason in this class’s docstring.- Parameters:
mode – Clock polarity and phase pair the external controller uses.
bit_order – Whether each frame arrives most- or least-significant bit first.
chip_select – Which chip-select line this target answers on.
chip_select_polarity – Whether that line is active low or high.
model – Which part the target emulates. See
SpiTarget.initialize.engine – Which servicing engine to use. Same.
require_arena_access – Whether to refuse when the target’s memory arena would be unreachable. See
initialize.
- Returns:
This interface, to allow chaining.
- Raises:
CapabilityError – If this hardware revision cannot act as a SPI target. The refusal is this SDK’s own – firmware would answer
SUCCESS– and nothing is submitted.DisconnectError – If the device is no longer open.
ProtocolError – If the response is too short to carry the arena size. The role was taken – the submit succeeded – so
configureis the way to recover a figure forarena_size.SpiError – On a 0x03xx failure –
FW_SPI_ROLE_CONFLICTorFW_SPI_TARGET_STAGING_UNAVAILABLE.FirmwareError – If the adapter is already in SPI target mode (
FW_INTERFACE_ALREADY_INITIALIZED), or the firmware rejects the configuration.
- async bring_up(*, mode: SpiMode, bit_order: SpiBitOrder, chip_select: SpiChipSelect, chip_select_polarity: SpiChipSelectPolarity, model: SpiTargetModel, engine: SpiTargetEngine, require_arena_access: bool = True) BringUpResult
Bring SPI target mode up, re-runnably, and report what took effect.
The asyncio twin of
SpiTarget.bring_up, with the same contract: one absorbed status, no refused-status filter, andsettings_appliedalwaysTruebecausespiTargetSetParametersdocuments no refusal to absorb.FW_SPI_ROLE_CONFLICTpropagates. It is also, here as there, the only single call that always leavesarena_sizepopulated.- Parameters:
mode – Clock polarity and phase pair requested.
bit_order – Frame bit order requested.
chip_select – Chip-select line this target answers on.
chip_select_polarity – Chip-select active polarity requested.
model – Which part the target emulates. See
SpiTarget.initialize.engine – Which servicing engine to use. See
SpiTarget.initialize.require_arena_access – Whether to refuse when the target’s memory arena would be unreachable from this host. See
initialize.
- Returns:
A
BringUpResult. On return the target is up and the requested parameters are in force.- Raises:
CapabilityError – If this hardware revision cannot act as a SPI target. Nothing is submitted.
DisconnectError – If the device is no longer open.
ProtocolError – If either response is too short to carry the arena size.
SpiError – On a 0x03xx failure from either call.
FirmwareError – On any firmware failure other than the one the fallback absorbs.
- async release() None
Release the SPI role, and the arena with it.
The asyncio twin of
SpiTarget.release, with the same semantics: no argument, the role released is whichever one the peripheral holds,arena_sizeand the remembered model go back toNoneon every path, event capture is cancelled only on success, and the shared bulk negotiation is left alone. Read that method for the first-transfer caveat, which is a bench question this call makes reachable.- Raises:
DisconnectError – If the device is no longer open.
CosmicTimeoutError – If the request exceeds the per-request timeout.
FirmwareError – If the peripheral held no role (
FW_INTERFACE_NOT_INITIALIZED) or the firmware predates the command (FW_UNSUPPORTED_COMMAND). Neither is absorbed, and both are generic codes rather thanSpiError.
- async configure(*, mode: SpiMode, bit_order: SpiBitOrder, chip_select: SpiChipSelect, chip_select_polarity: SpiChipSelectPolarity, model: SpiTargetModel, engine: SpiTargetEngine) None
Reapply the target parameters of an adapter already in SPI target mode.
The asyncio twin of
SpiTarget.configure. It keeps the arena’s contents and, like the blocking twin, reports the arena’s size: the figure is on the response packet and_decode_arena_sizereads it, soarena_sizeis populated afterwards either way.- Parameters:
mode – New clock polarity and phase pair.
bit_order – New frame bit order.
chip_select – New chip-select line to answer on.
chip_select_polarity – New chip-select active polarity.
model – Which part the target emulates. See
SpiTarget.initialize.engine – Which servicing engine to use. See
SpiTarget.initialize.
- Raises:
DisconnectError – If the device is no longer open.
ProtocolError – If the response is too short to carry the arena size.
CosmicTimeoutError – If the request exceeds the per-request timeout.
FirmwareError – If the firmware rejects the request – notably when the adapter is not in SPI target mode (
FW_INTERFACE_NOT_INITIALIZED). No `SpiError` here, for the reasonSpiTarget.configuregives: this command documents no 0x03xx status at all.
- async configure_flash(*, address_bytes: int, page_size: int, write_cycle_us: int, jedec_id: bytes = b'', require_write_enable: bool = True) None
Describe the flash part a
FLASH-model target emulates.The asyncio twin of
SpiTarget.configure_flash, with the same validation and the same model pre-guard. It carries no payload, so it is an ordinary non-blocking submit.- Parameters:
address_bytes – Width of the address phase the emulated part expects, 2 or 3.
page_size – Program page size in bytes. Must be a power of two.
write_cycle_us – How long a page program takes, in microseconds.
jedec_id – The bytes answered to a read-id command, 0 to 8 of them.
require_write_enable – Whether a program needs a write-enable latch set first. This is
flagsbit 0.
- Raises:
ValidationError – If any argument is outside its documented range, or this target is known to be running a different model. Nothing is submitted.
DisconnectError – If the device is no longer open.
SpiError – If firmware rejects the configuration (
FW_SPI_TARGET_INVALID_CONFIG) or the target is running a different model (FW_SPI_TARGET_WRONG_MODEL).
- async wait_for_event(*, timeout: float | None = None) SpiTargetBusEvent
Return the oldest queued bus transaction, awaiting one if none is queued.
The asyncio twin of
SpiTarget.wait_for_event, and the half of the target surface the async side is genuinely good at: observation costs no payload transfer, so nothing here blocks the event loop. Events are queued as the firmware reports them and each call pops exactly one, oldest first; the queue is never trimmed. For push delivery, subscribe toSpiTargetBusEventNotificationviaAsyncDevice.subscribe, or iterateAsyncDevice.iter_notifications, whoseasync forbody runs on the event loop rather than on the dispatcher thread.The bytes are not here, and this class cannot fetch them. The event carries metadata only, by the device’s design, and the arena is reachable only from a blocking
Device– see this class’s docstring. Nor ismemory_addressusable: firmware hard-codes it to0. So an async target reports that a transaction completed, on which chip select, and how many bytes it moved; reading the bytes back is a blocking handle’s job, and knowing where they landed is the emulated protocol’s.Note
Safe to await concurrently with competing-consumer semantics; each event is delivered to exactly one waiter, non-deterministically.
Warning
The bus-event path has no upstream test, for the reason
SpiTarget.wait_for_eventgives at length.- Parameters:
timeout – Maximum seconds to wait when nothing is queued.
Nonewaits indefinitely;0returns immediately; a positive value waits up to that long. A negative value is rejected.- Returns:
The oldest queued bus event.
- Raises:
ValidationError – If
timeoutis negative.DisconnectError – If the device is not open, or if
AsyncDevice.closeis called while this call is awaiting an event.CosmicTimeoutError – If no event arrives within
timeout(only whentimeoutis notNone).
GPIO
- class pycosmicsdk.Gpio(*, device: Device)
Bases:
objectBlocking GPIO interface for an open device.
Obtain one from
Device.gpio; the constructor is package-private. The interface is cached on the device, so repeateddev.gpio()calls return the same instance — and with it the recorded pin configuration.- set_voltage(voltage_mv: int, *, persist: bool = False) None
Set the GPIO pins’ target voltage.
The GPIO pins share the multifunction rail with I2C, SPI and UART, so this moves all four together.
- Parameters:
voltage_mv – Target voltage in millivolts. The multifunction port accepts
0(rail off) and 1200-3300 mV; an out-of-range value is rejected by the firmware.persist – When
True, store the setting across power cycles, so downstream hardware is powered from the moment the adapter starts up.
- Raises:
ValidationError – If
voltage_mvdoes not fit auint16.DisconnectError – If the device is no longer open.
SystemFirmwareError – If the firmware rejects the voltage. Voltage rejection is
FW_VOLTAGE_OUT_OF_RANGE(0x0101), which sits in the 0x01xx system band shared by every interface, so it does not surface asGpioError. With firmware 4.7.0 or newer, a port that another source already powers is refused withFW_EXTERNAL_VOLTAGE_DETECTED(0x0102) and the rail stays off; calluse_external_voltageinstead.
- use_external_voltage(*, persist: bool = False) None
Hand the GPIO rail over to an externally supplied voltage.
Tells the adapter that the multifunction VTARG rail — shared with I2C, SPI and UART — comes from outside, so it stops driving it itself and measures the supplied rail instead. Read back what it measured with
get_voltage.- Parameters:
persist – When
True, store the voltage measured now. After a power cycle the adapter drives the rail itself at that voltage; it does not store “external”.- Raises:
DisconnectError – If the device is no longer open.
SystemFirmwareError – If the firmware rejects the switch — a 0x01xx system-band code, the same band
set_voltagedocuments.
- get_voltage() int
Read the voltage measured on the shared multifunction VTARG rail.
This is not a GPIO-specific measurement. The adapter has one multifunction VTARG rail shared by I2C, SPI, UART and GPIO, and this reads that single rail, so the reading reflects whatever last set or is driving it — pair this with
use_external_voltage.I2cController.get_bus_voltage,SpiController.get_bus_voltageandUart.get_bus_voltagereturn the very same number. Upstream implements all four by issuing onegetAnalogMeasurementscommand and returning itsvmeasVtargI2cSpiUartGpio_mVfield (CosmicSDK.cpp); the four names exist to keep each protocol’s surface self-contained, not because four rails are measured. The name differs here only because upstream calls itgpioGetVoltage— GPIO pins are not a bus — but the reading is identical.I3cController.get_bus_voltageis the genuine exception: I3C has its own HV and LV rails and reports both.- Returns:
The measured voltage in millivolts.
- Raises:
DisconnectError – If the device is no longer open.
FirmwareError – On a firmware-reported failure. This is a system measurement command, so it reports no 0x05xx code and never surfaces as
GpioError– catch the base class.
- configure_pin(pin: GpioPinNumber, *, mode: GpioFunctionality, initial: GpioLogicLevel | None = None) None
Configure
pinas a digital input or output.Required before any other operation on the pin: an unconfigured pin is rejected by
write,readandconfigure_interrupt, and by the firmware withFW_GPIO_NOT_CONFIGURED. Re-running it on a configured pin is allowed and switches the direction, which drops any interrupt armed on it.- Parameters:
pin – The pin to configure.
mode – Whether the pin becomes a digital input or a digital output.
initial – Level to drive as soon as an output pin is configured. Defaults to
GpioLogicLevel.LOW, and is ignored by the firmware for an input pin.
- Raises:
DisconnectError – If the device is no longer open.
FirmwareError – If the firmware rejects the configuration (
FW_INVALID_PARAMETER). UpstreamgpioConfigurePinreports no 0x05xx code – the two that exist,FW_GPIO_NOT_CONFIGUREDandFW_GPIO_WRONG_CONFIGURATION, are answers to operating a pin, not to configuring one – so this never surfaces asGpioError.
- write(pin: GpioPinNumber, level: GpioLogicLevel) None
Drive
pintolevel.- Parameters:
pin – The pin to drive. Must be configured as
GpioFunctionality.DIGITAL_OUTPUT.level – The logic level to drive.
- Raises:
ValidationError – If
pinis unconfigured or configured as an input. No facade call is made.DisconnectError – If the device is no longer open.
GpioError – On a firmware-reported failure.
- read(pin: GpioPinNumber) GpioLogicLevel
Return the logic level present on
pin.- Parameters:
pin – The pin to sample. Must be configured as
GpioFunctionality.DIGITAL_INPUT.- Returns:
The level the firmware read on the pin.
- Raises:
ValidationError – If
pinis unconfigured or configured as an output. No facade call is made.DisconnectError – If the device is no longer open.
GpioError – On a firmware-reported failure.
- configure_interrupt(pin: GpioPinNumber, trigger: GpioTriggerType) None
Arm the firmware interrupt on
pinfortrigger.This configures the interrupt only. To receive the events, subscribe to
GpioInterruptNotificationon the device and filter by pin:gpio.configure_interrupt(GpioPinNumber.PIN_3, GpioTriggerType.RISING_EDGE) sub = dev.subscribe( GpioInterruptNotification, on_irq, where=lambda n: n.pin_number == GpioPinNumber.PIN_3, )
Not every pin supports interrupts, and which do is a property of the adapter model and hardware revision rather than of this API. Ask
dev.capabilities.supports_gpio_interrupt(pin)rather than discovering it by failing; a pin that answersFalseis refused here withCapabilityErrorbefore anything is sent to the device.One such limit is bench-confirmed: on a Supernova hardware Rev B,
PIN_3cannot be armed for any trigger type — its firmware hard-codes the pin as interrupt-incapable, so no argument change makes it work; pick another pin. Seedocs_dev/hardware_validation/gpio-interrupt-pin3-supernova-revb.md. Other revisions and models read as supportingPIN_3in the firmware source, and are therefore attempted rather than refused — but that reading is unconfirmed on hardware, so aFirmwareErrornamingFW_FEATURE_NOT_SUPPORTED_BY_HARDWAREremains possible for a pin this SDK reports as capable.- Parameters:
pin – The pin to arm. Must be configured as
GpioFunctionality.DIGITAL_INPUT, and must be one ofdev.capabilities.interrupt_capable_gpio_pins.trigger – Which edge (or both) raises the interrupt.
- Raises:
CapabilityError – If the adapter’s model and hardware revision are known not to support an interrupt on
pin— the Rev BPIN_3limit above. The message names the pin, the model and the revision, and lists the pins that do work. No facade call is made, so this replaces the firmware round trip rather than following it. Checked before the direction guard, because a revision limit is unfixable while a missing or wrongconfigure_pinis not: reporting the fixable problem first would send the caller away to do work this method then refuses.ValidationError – If
pinis unconfigured or configured as an output. No facade call is made. On a pin this unit’s revision cannot arm at all,CapabilityErroris raised instead — that guard runs first.DisconnectError – If the device is no longer open.
FirmwareError – If the pin does not support interrupts (
FW_FEATURE_NOT_SUPPORTED_BY_HARDWARE,0x0005) or the pin or trigger is rejected (FW_INVALID_PARAMETER,0x0004). Both are generic codes that sit below the 0x05xx GPIO band, so they arrive as a plainFirmwareErrorand are not caught byexcept GpioError. A pin whose limit this SDK already records raisesCapabilityErrorinstead, so reaching the0x0005case here means an interrupt limit the revision table does not yet know about.GpioError – If the firmware reports the pin unconfigured (
FW_GPIO_NOT_CONFIGURED,0x0501) or configured as an output (FW_GPIO_WRONG_CONFIGURATION,0x0502) — the 0x05xx GPIO band. Reaching these takes the device disagreeing with the pin state this interface mirrors, since a locally known-bad mode raisesValidationErrorbefore any call is made; a pin reconfigured out from under this handle does it.
Note
To catch every firmware-reported failure of this method in one clause, catch
FirmwareError—GpioErroris a subclass of it.CapabilityErroris not: it is raised before any request is sent, and is a sibling ofFirmwareErrorunderCosmicError.
- disable_interrupt(pin: GpioPinNumber) None
Disarm the firmware interrupt on
pin.Leaves any
GpioInterruptNotificationsubscription in place; it simply stops receiving events for this pin.- Parameters:
pin – The pin to disarm. Must be configured as
GpioFunctionality.DIGITAL_INPUT.- Raises:
ValidationError – If
pinis unconfigured or configured as an output. No facade call is made.DisconnectError – If the device is no longer open.
GpioError – On a firmware-reported failure.
- class pycosmicsdk.AsyncGpio(*, device: AsyncDevice)
Bases:
objectAsyncio GPIO interface for an open device.
The asyncio twin of
Gpio, returned byAsyncDevice.gpio; the constructor is package-private. The interface is cached on the device, so repeateddev.gpio()calls return the same instance. Direction enforcement and argument validation match the blocking interface; commands are awaited and correlated through the device’s engine rather than serialized under a lock. Interrupt events are delivered throughAsyncDevice.subscribe, which is synchronous on the async device too.Note
The rail surface matches
Gpioin full,get_voltageincluded.CosmicAsyncSDKdeclares nogpioGetVoltage, but it does declaregetAnalogMeasurements, which carries the shared I2C/SPI/UART/GPIO rail — and that is the same command upstream’s blockinggpioGetVoltageissues internally. So the async reader is built the same way as the blocking one, and reports the same value.- async set_voltage(voltage_mv: int, *, persist: bool = False) None
Set the GPIO pins’ target voltage.
The GPIO pins share the multifunction rail with I2C, SPI and UART, so this moves all four together.
- Parameters:
voltage_mv – Target voltage in millivolts. The multifunction port accepts
0(rail off) and 1200-3300 mV; an out-of-range value is rejected by the firmware.persist – When
True, store the setting across power cycles, so downstream hardware is powered from the moment the adapter starts up.
- Raises:
ValidationError – If
voltage_mvdoes not fit auint16.DisconnectError – If the device is no longer open.
SystemFirmwareError – If the firmware rejects the voltage. Voltage rejection is
FW_VOLTAGE_OUT_OF_RANGE(0x0101), which sits in the 0x01xx system band shared by every interface, so it does not surface asGpioError. With firmware 4.7.0 or newer, a port that another source already powers is refused withFW_EXTERNAL_VOLTAGE_DETECTED(0x0102) and the rail stays off; calluse_external_voltageinstead.
- async use_external_voltage(*, persist: bool = False) None
Hand the GPIO rail over to an externally supplied voltage.
Tells the adapter that the multifunction VTARG rail — shared with I2C, SPI and UART — comes from outside, so it stops driving it itself and measures the supplied rail instead.
- Parameters:
persist – When
True, store the voltage measured now. After a power cycle the adapter drives the rail itself at that voltage; it does not store “external”.- Raises:
DisconnectError – If the device is no longer open.
SystemFirmwareError – If the firmware rejects the switch — a 0x01xx system-band code, the same band
set_voltagedocuments.
Note
Read back what the adapter measured with
get_voltage.
- async get_voltage() int
Read the voltage measured on the shared multifunction VTARG rail.
The asyncio twin of
Gpio.get_voltage, with the same return type and the same meaning. It is not a GPIO-specific measurement: the adapter has one multifunction VTARG rail shared by I2C, SPI, UART and GPIO, andAsyncI2cController.get_bus_voltage,AsyncSpiController.get_bus_voltageandAsyncUart.get_bus_voltagereturn the very same number. Pair this withuse_external_voltage.Implemented over
getAnalogMeasurements, which reads every rail in one transaction; this keeps the shared-rail field and discards the rest, exactly as upstream’s blockinggpioGetVoltagedoes. UseAsyncDevice.get_analog_measurementsto get all six rails at once instead of one field per call.- Returns:
The measured voltage in millivolts.
- Raises:
DisconnectError – If the device is no longer open.
ProtocolError – If the response was too short to carry the measurements, which is also how firmware that does not implement the command reports itself.
CosmicTimeoutError – If the request exceeds the per-request timeout.
FirmwareError – On a firmware-reported failure. This is a system measurement command, so it reports no 0x05xx code and never surfaces as
GpioError– catch the base class.
- async configure_pin(pin: GpioPinNumber, *, mode: GpioFunctionality, initial: GpioLogicLevel | None = None) None
Configure
pinas a digital input or output.Required before any other operation on the pin: an unconfigured pin is rejected by
write,readandconfigure_interrupt, and by the firmware withFW_GPIO_NOT_CONFIGURED. Re-running it on a configured pin is allowed and switches the direction, which drops any interrupt armed on it.- Parameters:
pin – The pin to configure.
mode – Whether the pin becomes a digital input or a digital output.
initial – Level to drive as soon as an output pin is configured. Defaults to
GpioLogicLevel.LOW, and is ignored by the firmware for an input pin.
- Raises:
DisconnectError – If the device is no longer open.
FirmwareError – If the firmware rejects the configuration (
FW_INVALID_PARAMETER). UpstreamgpioConfigurePinreports no 0x05xx code – the two that exist,FW_GPIO_NOT_CONFIGUREDandFW_GPIO_WRONG_CONFIGURATION, are answers to operating a pin, not to configuring one – so this never surfaces asGpioError.
- async write(pin: GpioPinNumber, level: GpioLogicLevel) None
Drive
pintolevel.- Parameters:
pin – The pin to drive. Must be configured as
GpioFunctionality.DIGITAL_OUTPUT.level – The logic level to drive.
- Raises:
ValidationError – If
pinis unconfigured or configured as an input. No facade call is made.DisconnectError – If the device is no longer open.
GpioError – On a firmware-reported failure.
- async read(pin: GpioPinNumber) GpioLogicLevel
Return the logic level present on
pin.- Parameters:
pin – The pin to sample. Must be configured as
GpioFunctionality.DIGITAL_INPUT.- Returns:
The level the firmware read on the pin.
- Raises:
ValidationError – If
pinis unconfigured or configured as an output. No facade call is made.ProtocolError – If the response carries no logic-level byte.
DisconnectError – If the device is no longer open.
GpioError – On a firmware-reported failure.
- async configure_interrupt(pin: GpioPinNumber, trigger: GpioTriggerType) None
Arm the firmware interrupt on
pinfortrigger.This configures the interrupt only. To receive the events, subscribe to
GpioInterruptNotificationon the device and filter by pin:await gpio.configure_interrupt(GpioPinNumber.PIN_3, GpioTriggerType.RISING_EDGE) sub = dev.subscribe( GpioInterruptNotification, on_irq, where=lambda n: n.pin_number == GpioPinNumber.PIN_3, )
Not every pin supports interrupts, and which do is a property of the adapter model and hardware revision rather than of this API. Ask
dev.capabilities.supports_gpio_interrupt(pin)rather than discovering it by failing; a pin that answersFalseis refused here withCapabilityErrorbefore anything is sent to the device.One such limit is bench-confirmed: on a Supernova hardware Rev B,
PIN_3cannot be armed for any trigger type — its firmware hard-codes the pin as interrupt-incapable, so no argument change makes it work; pick another pin. Seedocs_dev/hardware_validation/gpio-interrupt-pin3-supernova-revb.md. Other revisions and models read as supportingPIN_3in the firmware source, and are therefore attempted rather than refused — but that reading is unconfirmed on hardware, so aFirmwareErrornamingFW_FEATURE_NOT_SUPPORTED_BY_HARDWAREremains possible for a pin this SDK reports as capable.- Parameters:
pin – The pin to arm. Must be configured as
GpioFunctionality.DIGITAL_INPUT, and must be one ofdev.capabilities.interrupt_capable_gpio_pins.trigger – Which edge (or both) raises the interrupt.
- Raises:
CapabilityError – If the adapter’s model and hardware revision are known not to support an interrupt on
pin— the Rev BPIN_3limit above. The message names the pin, the model and the revision, and lists the pins that do work. No facade call is made, so this replaces the firmware round trip rather than following it. Checked before the direction guard, because a revision limit is unfixable while a missing or wrongconfigure_pinis not: reporting the fixable problem first would send the caller away to do work this method then refuses.ValidationError – If
pinis unconfigured or configured as an output. No facade call is made. On a pin this unit’s revision cannot arm at all,CapabilityErroris raised instead — that guard runs first.DisconnectError – If the device is no longer open.
CosmicTimeoutError – If the request exceeds the per-request timeout.
FirmwareError – If the pin does not support interrupts (
FW_FEATURE_NOT_SUPPORTED_BY_HARDWARE,0x0005) or the pin or trigger is rejected (FW_INVALID_PARAMETER,0x0004). Both are generic codes that sit below the 0x05xx GPIO band, so they arrive as a plainFirmwareErrorand are not caught byexcept GpioError. A pin whose limit this SDK already records raisesCapabilityErrorinstead, so reaching the0x0005case here means an interrupt limit the revision table does not yet know about.GpioError – If the firmware reports the pin unconfigured (
FW_GPIO_NOT_CONFIGURED,0x0501) or configured as an output (FW_GPIO_WRONG_CONFIGURATION,0x0502) — the 0x05xx GPIO band. Reaching these takes the device disagreeing with the pin state this interface mirrors, since a locally known-bad mode raisesValidationErrorbefore any call is made; a pin reconfigured out from under this handle does it.
Note
To catch every firmware-reported failure of this method in one clause, catch
FirmwareError—GpioErroris a subclass of it.CapabilityErroris not: it is raised before any request is sent, and is a sibling ofFirmwareErrorunderCosmicError.
- async disable_interrupt(pin: GpioPinNumber) None
Disarm the firmware interrupt on
pin.Leaves any
GpioInterruptNotificationsubscription in place; it simply stops receiving events for this pin.- Parameters:
pin – The pin to disarm. Must be configured as
GpioFunctionality.DIGITAL_INPUT.- Raises:
ValidationError – If
pinis unconfigured or configured as an output. No facade call is made.DisconnectError – If the device is no longer open.
GpioError – On a firmware-reported failure.