Skip to content
binho.io (opens in a new tab)Contact Us

PyCosmicSDK Examples

The PyCosmicSDK examples are more than 50 runnable Python scripts. Each one opens an adapter, drives one protocol, and prints what happened. This page shows how to download and run them. It also has seven short snippets to paste into other code.

For the full API, see the PyCosmicSDK documentation. To install the SDK, see PyCosmicSDK.

Download pycosmicsdk-examples.zip and unzip it.

The zip holds:

FolderContents
examples/blocking_api/Scripts for the blocking Device API, one folder per protocol
examples/async_api/The same scenarios for the asyncio AsyncDevice API
examples/legacy/Scripts that use the BinhoSupernova, BinhoPulsar and SupernovaController compatibility layers
examples/helpers.pyShared code the scripts import (console output, bring-up, skip handling)

Python 3.10 to 3.14 and a Supernova or Pulsar running firmware 4.x, or an Atira running Atira firmware 1.0 or newer, are required. Use the latest firmware: see Updating Firmware.

Windows

python -m venv .venv
.venv\Scripts\activate
pip install pycosmicsdk
tar -xf pycosmicsdk-examples.zip
cd pycosmicsdk-examples
python examples\blocking_api\system\get_device_info.py

macOS / Linux

python3 -m venv .venv
source .venv/bin/activate
pip install pycosmicsdk
unzip pycosmicsdk-examples.zip
cd pycosmicsdk-examples
python examples/blocking_api/system/get_device_info.py

On Linux, set up USB access once first: Solving USB Connection Issues on Linux.

Keep the folder layout as it is. Each script finds helpers.py by searching its parent folders.

Docstrings show uv run python; plain python works the same.

CodeMeaning
0Success
1The adapter could not be opened, or a check failed
2Another SDK error
3A scenario-specific failure, such as a timeout waiting for data
4Skipped: the script needs hardware that is not connected, and says which

Code 4 is not a failure. For example, the I2C round-trip script needs an EEPROM at address 0x50. With no EEPROM connected, it prints what it needs and exits with 4.

The list below covers examples/blocking_api/. Most scenarios also exist in examples/async_api/ with the same file name.

ScriptWhat it showsNeeds
system/get_device_info.pyOpen the first adapter and print model, serial number and firmware versionAny adapter
system/list_devices.pyList connected adapters, then open one by path and by serial numberAny adapter
system/usb_config.pyRead the adapter’s saved USB interface configurationSupernova
i2c/blocking.pyWrite and read back an I2C memory, then scan the busEEPROM or FRAM at 0x50, 2-byte addressing
i2c/blocking_manual_close.pyThe same round trip with explicit open() and close()EEPROM or FRAM at 0x50
i2c/duty_cycle.pySet the SCL duty cycle and print the timing the adapter reportsFirmware 4.6.0 or later
i2c/error_handling.pyCause I2C errors on purpose and show how each one is reportedAny adapter
i2c/target_eeprom_driven.pyOne adapter emulates a 24Cxx EEPROM, a second adapter reads itTwo adapters: SDA, SCL and GND wired together, external 2.2 kΩ pull-ups
i2c/target_arena_bulk.pyMove 16 KB in and out of the adapter’s I2C target memoryAny adapter
i3c/discovery.pyBring up the I3C bus and assign dynamic addressesSupernova; I3C targets optional
i3c/ccc_queries.pyRead GETPID, GETBCR and GETDCR from each targetSupernova, I3C target
i3c/legacy_i2c_target.pyTalk to an I2C or SMBus device on the I3C portSupernova, I2C target on the I3C port
i3c/i2c_clock_stretching.pyTalk to a clock-stretching I2C target on the I3C portSupernova on firmware 4.6.0 or later, I2C target at 0x50 on the I3C port
i3c/target_mode_setup.pyRun the Supernova as an I3C target and use its memorySupernova
i3c/target_mode_bus_event.pyWait for a bus event while acting as an I3C targetSupernova
i3c/capability_rejection.pyShow the error raised when asking a Pulsar for I3CPulsar
spi/transfer.pyBring up SPI and run a full-duplex transferAny adapter
spi/mode_frequency_sweep.pySweep SPI modes, clock rates and chip selectsSupernova
spi/transport_selection.pyShow which USB path a large SPI payload takesAny adapter
spi/bulk_flash_read.pyRead 16 KB from a SPI NOR flash in one callSPI flash on CS0
spi/target_flash_driven.pyA Pulsar emulates a SPI NOR flash, a Supernova reads itSupernova and Pulsar: MOSI (13), MISO (15), CLK (17), CS0 (19) and GND wired straight through
uart/query.pySend a request and wait for the reply, at two baud ratesTX-to-RX loopback wire
uart/send_receive.pySeparate send() and receive() calls, like a serial consoleTX-to-RX loopback wire
uart/receive_stream.pyRead a continuous stream from a device that pushes dataTX-to-RX loopback wire
uart/subscribe.pyReceive UART data through a callbackTX-to-RX loopback wire
uart/send_only.pyTransmit only, then closeAny adapter
gpio/digital_io.pyConfigure pins, drive outputs and read inputsSupernova
gpio/interrupt_subscribe.pyArm a GPIO interrupt and wait for itPulsar; a signal on GPIO 3 to see an event

The I2C and SPI target-mode scripts (i2c/target_eeprom_driven.py, i2c/target_arena_bulk.py, spi/target_flash_driven.py) skip with code 4 unless PYCOSMICSDK_EXAMPLES_ALLOW_TARGET_MODE=1 is set. They name the adapters they use by serial number, in the environment variables PYCOSMICSDK_EXAMPLES_TARGET_SERIAL and PYCOSMICSDK_EXAMPLES_CONTROLLER_SERIAL. Each script’s docstring lists what it needs.

Guides behind the scripts that go beyond one protocol:

ScriptsGuide
i2c/target_*.py, i3c/target_mode_*.py, spi/target_flash_driven.pyTarget mode
i2c/target_arena_bulk.py, spi/bulk_flash_read.py, spi/transport_selection.pyLarge transfers
i2c/error_handling.py, i3c/capability_rejection.pyErrors
system/list_devices.py, i2c/blocking_manual_close.pyDevice lifecycle
examples/legacy/Migrating from the legacy packages

Each snippet is complete: save it as a .py file and run it with python. They use the blocking API unless the heading says otherwise.

I2C: Scan the Bus and Read and Write an EEPROM

Section titled “I2C: Scan the Bus and Read and Write an EEPROM”

This scans the bus, writes 4 bytes to an I2C EEPROM at address 0x50, and reads them back. It assumes a 24-series EEPROM with a 2-byte memory address, such as a 24LC256.

Supernova

import time
from pycosmicsdk import Device, I2cBus, I2cError, I2cPullUp, StatusCode
EEPROM = 0x50
MEMORY_ADDRESS = b"\x00\x10" # 2-byte memory address 0x0010
with Device.open() as dev:
i2c = dev.i2c(bus=I2cBus.A) # the Supernova has one I2C bus
i2c.set_voltage(voltage_mv=3300) # set the target voltage (VTARG) before bring-up
i2c.bring_up(frequency_hz=400_000, pull_up=I2cPullUp.KOHM_2_2)
try:
found = i2c.scan().addresses_7bit
except I2cError as exc:
if exc.status_code is not StatusCode.FW_I2C_BUS_WITH_NO_TARGETS_CONNECTED:
raise
found = []
print("Targets:", [hex(a) for a in found])
i2c.write(EEPROM, b"\xde\xad\xbe\xef", subaddress=MEMORY_ADDRESS)
time.sleep(0.01) # the EEPROM ignores its address until the write cycle ends
data = i2c.read(EEPROM, 4, subaddress=MEMORY_ADDRESS, non_stop=True)
print("Read back:", data.hex())

Pulsar

import time
from pycosmicsdk import Device, I2cBus, I2cError, I2cPullUp, StatusCode
EEPROM = 0x50
MEMORY_ADDRESS = b"\x00\x10" # 2-byte memory address 0x0010
with Device.open() as dev:
# The Pulsar has two separate I2C buses: A (Qwiic port) and B (header pins).
i2c = dev.i2c(bus=I2cBus.B)
i2c.set_voltage(voltage_mv=3300) # set the target voltage before bring-up
i2c.bring_up(frequency_hz=400_000, pull_up=I2cPullUp.KOHM_2_2)
try:
found = i2c.scan().addresses_7bit
except I2cError as exc:
if exc.status_code is not StatusCode.FW_I2C_BUS_WITH_NO_TARGETS_CONNECTED:
raise
found = []
print("Targets:", [hex(a) for a in found])
i2c.write(EEPROM, b"\xde\xad\xbe\xef", subaddress=MEMORY_ADDRESS)
time.sleep(0.01) # the EEPROM ignores its address until the write cycle ends
data = i2c.read(EEPROM, 4, subaddress=MEMORY_ADDRESS, non_stop=True)
print("Read back:", data.hex())
  • A scan of an empty bus raises I2cError with FW_I2C_BUS_WITH_NO_TARGETS_CONNECTED. It does not return an empty list, which is why the snippet catches it.
  • non_stop=True sends a repeated START between the address phase and the data phase. Most EEPROMs and FRAMs expect that for a random read.
  • bring_up() is safe to run again: if the bus is already up, it applies the new settings instead of failing.
  • Guide: I2C.

This reads the 3-byte JEDEC ID of a SPI NOR flash on chip select 0. A Winbond W25Q128JV answers ef4018. Both adapters run this code unchanged.

from pycosmicsdk import (
Device,
SpiBitOrder,
SpiChipSelect,
SpiChipSelectPolarity,
SpiDataWidth,
SpiMode,
)
with Device.open() as dev:
spi = dev.spi()
spi.set_voltage(voltage_mv=3300)
spi.bring_up(
bit_order=SpiBitOrder.MSB,
mode=SpiMode.MODE_0,
data_width=SpiDataWidth.BITS_8,
chip_select=SpiChipSelect.CS_0,
chip_select_polarity=SpiChipSelectPolarity.ACTIVE_LOW,
frequency_hz=10_000_000,
)
jedec_id = spi.read(b"\x9f", 3) # command 0x9F out, 3 bytes back
print("JEDEC ID:", jedec_id.hex())
  • spi.read() returns only the data phase. To get every byte clocked in on MISO, use spi.transfer(b"\x9f\x00\x00\x00"), which returns 4 bytes: the first byte answers the command and the last three are the ID.
  • With nothing connected, MISO reads a constant such as 000000 or ffffff.
  • Guide: SPI.

I3C: Assign Dynamic Addresses and Talk to a Target

Section titled “I3C: Assign Dynamic Addresses and Talk to a Target”

I3C is available on the Supernova only. This brings up the bus, runs dynamic address assignment (RSTDAA, then ENTDAA), and then writes and reads a register on the first target.

from pycosmicsdk import (
Device,
DeviceModel,
I2cOpenDrainRate,
I3cOpenDrainRate,
I3cPushPullRate,
)
REGISTER = b"\x00" # register address on your target
with Device.open(model=DeviceModel.SUPERNOVA) as dev:
i3c = dev.i3c()
i3c.set_voltage(voltage_mv=1800) # 1200-3300 mV selects the high-voltage I3C port
i3c.bring_up(
push_pull_rate=I3cPushPullRate.PUSH_PULL_5_MHZ_DC_50,
open_drain_rate=I3cOpenDrainRate.OPEN_DRAIN_400_KHZ,
i2c_open_drain_rate=I2cOpenDrainRate.FAST_MODE,
)
targets = i3c.init_bus()
for t in targets:
print(f"dynamic {t.dynamic_address:#04x} PID {t.pid.hex()} BCR {t.bcr:#04x} DCR {t.dcr:#04x}")
if targets:
address = targets[0].dynamic_address
i3c.sdr_write(address, b"\x01", subaddress=REGISTER)
print("Register:", i3c.sdr_read(address, 1, subaddress=REGISTER).hex())
  • The voltage selects the port: 800-1199 mV drives the low-voltage I3C port, 1200-3300 mV the high-voltage port. Set it to the target’s I/O voltage.
  • Common Command Codes are under i3c.ccc, for example i3c.ccc.getpid(address) and i3c.ccc.getbcr(address).
  • Guide: I3C.

I3C: Send an MCTP-over-SMBus Packet (Supernova)

Section titled “I3C: Send an MCTP-over-SMBus Packet (Supernova)”

Needs PyCosmicSDK 1.6.0 or later and Supernova firmware 4.6.0 or later. This sends an MCTP Get Endpoint ID request to an SSD on the I3C port, as an SMBus Block Write with PEC, with I2C clock stretching enabled. Set SSD_ADDR, HOST_ADDR and BUS_VOLTAGE_MV for the setup.

"""Send one MCTP-over-SMBus packet (DSP0237) through the Supernova's I3C port.
The packet is a Get Endpoint ID request (MCTP control message 0x02), sent as an
SMBus Block Write with PEC. The SSD's response is not read here: under MCTP over
SMBus the SSD sends it back as its own Block Write to HOST_ADDR, which the
Supernova's I3C port cannot receive today.
Requires pycosmicsdk >= 1.6.0 and Supernova firmware >= 4.6.0.
Run: python mctp_smbus_send.py
"""
import sys
from pycosmicsdk import (
CosmicError,
Device,
DeviceModel,
I2cOpenDrainRate,
I3cOpenDrainRate,
I3cPushPullRate,
)
# Adjust to your setup.
BUS_VOLTAGE_MV = 1800
SMBUS_CLOCK_HZ = 100_000
SSD_ADDR = 0x1D # SSD's MCTP SMBus address (7-bit)
HOST_ADDR = 0x10 # address the SSD should answer to (7-bit)
def pec(data: bytes) -> int:
"""SMBus PEC: CRC-8, polynomial 0x07, initial value 0."""
crc = 0
for b in data:
crc ^= b
for _ in range(8):
crc = ((crc << 1) ^ 0x07) & 0xFF if crc & 0x80 else (crc << 1) & 0xFF
return crc
def mctp_smbus_frame(dest_addr: int, src_addr: int, mctp_packet: bytes) -> bytes:
"""Wrap an MCTP packet (4-byte transport header + message) in an SMBus Block Write.
Returns everything after the address byte: command 0x0F, byte count,
source address, the MCTP packet, and the PEC.
"""
body = bytes([0x0F, len(mctp_packet) + 1, (src_addr << 1) | 1]) + mctp_packet
return body + bytes([pec(bytes([dest_addr << 1]) + body)])
# MCTP transport header: version 1, dest EID 0 (null), src EID 0 (null),
# SOM=1 EOM=1 seq=0 TO=1 tag=0. Message: type 0x00 (control), Rq=1 instance 0,
# command 0x02 (Get Endpoint ID).
GET_ENDPOINT_ID = bytes([0x01, 0x00, 0x00, 0xC8, 0x00, 0x80, 0x02])
def main() -> int:
frame = mctp_smbus_frame(SSD_ADDR, HOST_ADDR, GET_ENDPOINT_ID)
try:
with Device.open(model=DeviceModel.SUPERNOVA) as dev:
i3c = dev.i3c()
i3c.set_voltage(BUS_VOLTAGE_MV)
i3c.bring_up(
push_pull_rate=I3cPushPullRate.PUSH_PULL_5_MHZ_DC_50,
open_drain_rate=I3cOpenDrainRate.OPEN_DRAIN_400_KHZ,
i2c_open_drain_rate=I2cOpenDrainRate.STANDARD_MODE,
)
i3c.enable_i2c_clock_stretching(SMBUS_CLOCK_HZ)
try:
print(f"write to 0x{SSD_ADDR:02X}: {frame.hex(' ')}")
i3c.legacy_i2c_write(SSD_ADDR, frame)
print("sent (the SSD ACKed every byte)")
finally:
# I3C SDR, HDR and CCC calls are refused while stretching is on.
i3c.disable_i2c_clock_stretching()
except CosmicError as exc:
print(f"ERROR: {exc}", file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
sys.exit(main())
  • The response is not read. The SSD answers with its own Block Write to HOST_ADDR, and the I3C port cannot receive it today.
  • With no SSD attached, the write ends with a NACK and the script prints ERROR: ….

Connect one wire from pin 7 (TX) to pin 5 (RX) on the adapter’s multifunction port. The pinout is the same on the Supernova and the Pulsar. The adapter then receives every byte it sends.

from pycosmicsdk import Device, UartBaudRate
with Device.open() as dev:
uart = dev.uart()
uart.set_voltage(voltage_mv=3300)
uart.bring_up(baudrate=UartBaudRate.BAUD_115200) # 8-N-1, no flow control
reply = uart.query(b"Hello, world!", timeout=2.0)
print("Received:", reply)
  • query() clears old received data, sends, and waits for the reply. Use it for command-and-response protocols such as AT commands.
  • For a serial-console pattern, use uart.send() and uart.receive(). See uart/send_receive.py in the examples.
  • Guide: UART.

GPIO: Toggle an Output and Catch an Interrupt

Section titled “GPIO: Toggle an Output and Catch an Interrupt”

Connect a wire from GPIO 1 to GPIO 4. GPIO 1 is an output; GPIO 4 is an input with a rising-edge interrupt. Both adapters have the same six GPIO pins.

import threading
import time
from pycosmicsdk import (
Device,
GpioFunctionality,
GpioInterruptNotification,
GpioLogicLevel,
GpioPinNumber,
GpioTriggerType,
)
OUT = GpioPinNumber.PIN_1
IN = GpioPinNumber.PIN_4
with Device.open() as dev:
gpio = dev.gpio()
gpio.set_voltage(voltage_mv=3300)
gpio.configure_pin(OUT, mode=GpioFunctionality.DIGITAL_OUTPUT, initial=GpioLogicLevel.LOW)
gpio.configure_pin(IN, mode=GpioFunctionality.DIGITAL_INPUT)
fired = threading.Event()
with dev.subscribe(
GpioInterruptNotification,
lambda n: fired.set(),
where=lambda n: n.pin_number == IN, # compare with the enum, not the number 4
):
gpio.configure_interrupt(IN, GpioTriggerType.RISING_EDGE)
for _ in range(3):
gpio.write(OUT, GpioLogicLevel.HIGH)
time.sleep(0.1)
print("Input reads:", gpio.read(IN).name)
gpio.write(OUT, GpioLogicLevel.LOW)
time.sleep(0.1)
print("Interrupt seen:", fired.wait(timeout=1.0))
gpio.disable_interrupt(IN)
  • GPIO 3 cannot raise an interrupt on a Supernova hardware revision B; the SDK refuses it with CapabilityError. dev.capabilities.supports_gpio_interrupt(pin) reports this before the call.
  • Guide: GPIO.

The same I2C task with AsyncDevice. Every call that talks to the adapter is awaited.

import asyncio
from pycosmicsdk import AsyncDevice, I2cBus, I2cError, I2cPullUp, StatusCode
EEPROM = 0x50
async def main() -> None:
async with AsyncDevice.open() as dev:
i2c = dev.i2c(bus=I2cBus.A)
await i2c.set_voltage(voltage_mv=3300)
await i2c.bring_up(frequency_hz=400_000, pull_up=I2cPullUp.KOHM_2_2)
try:
found = (await i2c.scan()).addresses_7bit
except I2cError as exc:
if exc.status_code is not StatusCode.FW_I2C_BUS_WITH_NO_TARGETS_CONNECTED:
raise
found = []
print("Targets:", [hex(a) for a in found])
if EEPROM in found:
data = await i2c.read(EEPROM, 16, subaddress=b"\x00\x00", non_stop=True)
print("First 16 bytes:", data.hex())
asyncio.run(main())
  • AsyncDevice lets one program serve several adapters, or other I/O, while it waits on the bus. examples/async_api/i2c/concurrent_ops.py runs several I2C operations at once.
  • Guide: Blocking and asyncio.

If an example does not behave as described, contact us. Include the script name, its full output, and the output of python examples/blocking_api/system/get_device_info.py.