# Getting started Install `pycosmicsdk`, satisfy the per-platform device prerequisites, and open a first adapter. ## Install Installing from PyPI is the whole setup: ```bash pip install pycosmicsdk ``` The native CosmicSDK library is **statically linked** into the compiled extension the wheel ships, so there is no shared library to place on a search path and no environment variable to point at one. {py:obj}`~pycosmicsdk.Device` takes no `lib_path`, and no `COSMICSDK_LIB_PATH` is read. Python ≥ 3.10 is required. Binary wheels are built for CPython 3.10–3.14 on Linux `x86_64`, macOS `arm64` and Windows `AMD64`; `musllinux` is skipped, because CosmicSDK needs `libudev`. No sdist is uploaded, so on any other interpreter or platform `pip install pycosmicsdk` finds nothing. The only route there is a source checkout with the CosmicSDK submodule and a C++17 toolchain (gcc / clang / MSVC); see the README's "Building from source". Verify the install and the version you got: ```bash python -c "import pycosmicsdk; print(pycosmicsdk.__version__)" ``` ### From a source checkout For contributors. It additionally needs [`uv`](https://docs.astral.sh/uv/) for environment management, CMake `>= 3.18`, and access to the **CosmicSDK git submodule**, which is a separate private repository. ```bash export SKBUILD_CMAKE_ARGS=-DCMAKE_POLICY_VERSION_MINIMUM=3.5 # required: hidapi predates CMake 4 git clone --recurse-submodules git@github.com:binhollc/pycosmicsdk.git cd pycosmicsdk uv sync ``` Without the `SKBUILD_CMAKE_ARGS` export a fresh configure fails inside CMake; the repository `README.md` explains why and gives the HTTPS clone variant. Development is pinned to Python 3.12 via `.python-version`. ## Platform prerequisites Supernova and Pulsar are **USB HID** devices: the operating system's own HID stack claims them, so no vendor driver and no kernel module is involved on any platform. ### Linux Two things are needed. **A udev rule**, so the adapter is reachable without `root`. From a checkout, `./submodules/CosmicSDK/install_udev_rules.sh` installs it for you; the file it writes to `/etc/udev/rules.d/` is: ``` # Binho Supernova (HID) SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1fc9", ATTRS{idProduct}=="82fc", MODE="0660", GROUP="plugdev" # Binho Supernova (USB/libusb) SUBSYSTEM=="usb", ATTRS{idVendor}=="1fc9", ATTRS{idProduct}=="82fc", MODE="0660", GROUP="plugdev" # Binho Pulsar (HID) SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1fc9", ATTRS{idProduct}=="82fd", MODE="0660", GROUP="plugdev" # Binho Pulsar (USB/libusb) SUBSYSTEM=="usb", ATTRS{idVendor}=="1fc9", ATTRS{idProduct}=="82fd", MODE="0660", GROUP="plugdev" # Binho Atira (HID) SUBSYSTEM=="hidraw", ATTRS{idVendor}=="1fc9", ATTRS{idProduct}=="8347", MODE="0660", GROUP="plugdev", TAG+="uaccess" # Binho Atira (USB/libusb) SUBSYSTEM=="usb", ATTRS{idVendor}=="1fc9", ATTRS{idProduct}=="8347", MODE="0660", GROUP="plugdev", TAG+="uaccess" ``` Reload and re-plug the adapter afterwards: ```bash sudo udevadm control --reload-rules && sudo udevadm trigger ``` The rule grants access to the `plugdev` group, so your user has to be in it (`sudo usermod -aG plugdev "$USER"`, then log out and back in). The `usb` lines are not redundant with the `hidraw` ones: the bulk transfer plane reaches the adapter's vendor interface through libusb rather than the HID stack, so without them bulk is unavailable even though ordinary transfers work. **libusb and libudev at runtime.** The extension links both dynamically, and the `manylinux` wheel bundles its own copies under `pycosmicsdk.libs/`, so neither is a runtime prerequisite of a wheel install. Building from source needs the libraries and their headers: `pkg-config`, `libusb-1.0-0-dev`, `libudev-dev` on Debian / Ubuntu. ### macOS Nothing to install. HID access needs no special permission for these devices. ### Windows Nothing to install — the built-in HID class driver handles the adapter. ## First device — blocking The rest of this page opens a Supernova or Pulsar. For a Binho Atira, open a {py:obj}`~pycosmicsdk.DebugProbe` instead; the [Atira debug probe guide](guides/debugprobe.md) starts from there. {py:obj}`~pycosmicsdk.Device` is the blocking API: every call returns when the firmware has answered. Open it as a context manager and the handle closes on the way out, including when an exception unwinds the block. ```python from pycosmicsdk import Device with Device.open() as dev: info = dev.info print(f"{info.manufacturer} {info.product_name}") print(f"model {info.model.name}, serial {info.serial_number}") print(f"firmware {info.fw_version}, hardware {info.hw_version}") ``` {py:obj}`~pycosmicsdk.Device.open` with no arguments takes the first Binho adapter it enumerates and raises {py:obj}`~pycosmicsdk.DeviceNotFoundError` when none matches. With more than one plugged in, name the one you want: ```python with Device.open(serial="1234ABCD") as dev: # or path=..., or model=... ... ``` The selectors are mutually exclusive; passing two raises {py:obj}`~pycosmicsdk.ValidationError` before any USB traffic happens. {py:obj}`~pycosmicsdk.Device.info` is the snapshot taken at open time — free to read, no device round-trip. {py:obj}`~pycosmicsdk.Device.capabilities` reports what this adapter can do, also without touching the wire: ```python from pycosmicsdk import Device, I2cBus, ProtocolInterface with Device.open() as dev: caps = dev.capabilities print(ProtocolInterface.I2C in caps.protocols) # True on both models print(caps.has_i2c_bus(I2cBus.B)) # Pulsar only ``` Without a `with` block, close the handle yourself: ```python dev = Device.open() try: ... finally: dev.close() ``` {py:obj}`~pycosmicsdk.Device.close` is idempotent — calling it twice is harmless — while any *protocol* call made after it raises {py:obj}`~pycosmicsdk.DisconnectError` instead of reaching for a stale handle. {py:obj}`~pycosmicsdk.Device.state` reports where in the lifecycle the object currently is. [The device lifecycle](guides/lifecycle.md) covers discovery, all three selectors, the state machine and rebooting in full. ## First device — async {py:obj}`~pycosmicsdk.AsyncDevice` is the same surface for `asyncio`: the device is opened with `async with`, and every operation is awaited. Use it when the program has other work (or other devices) to interleave, or when you want to consume notifications as an async stream. ```python import asyncio from pycosmicsdk import AsyncDevice async def main() -> None: async with AsyncDevice.open() as dev: info = dev.info print(f"{info.manufacturer} {info.product_name}") print(f"model {info.model.name}, serial {info.serial_number}") asyncio.run(main()) ``` The two APIs differ only where they must: properties that read cached state (`info`, `model`, `capabilities`, `state`) stay plain attributes on `AsyncDevice`, while everything that talks to the device — including {py:obj}`~pycosmicsdk.AsyncDevice.close` — is a coroutine. The four places where they are genuinely not mirror images are in [Blocking and asyncio](asyncio.md). ## When something fails Every error the SDK raises derives from {py:obj}`~pycosmicsdk.CosmicError` and carries the `status_code` that produced it, so one `except` keeps a script from crashing. The subclasses are what you act on: {py:obj}`~pycosmicsdk.ValidationError` : the call was rejected before any USB traffic happened. {py:obj}`~pycosmicsdk.CapabilityError` : the connected model, hardware revision or host cannot do it. Nothing was sent, so it is **not** a {py:obj}`~pycosmicsdk.FirmwareError` and `except FirmwareError` does not catch it. {py:obj}`~pycosmicsdk.FirmwareError` : the device answered and the answer was a refusal. `exc.status_code` is the {py:obj}`~pycosmicsdk.StatusCode` it reported — `FW_I2C_NACK_ADDRESS` for a target that is not on the bus, say. The per-protocol subclasses ({py:obj}`~pycosmicsdk.I2cError`, {py:obj}`~pycosmicsdk.UartError` and the rest) are what you usually catch. {py:obj}`~pycosmicsdk.CosmicTimeoutError` : also catchable as the built-in `TimeoutError`. The full hierarchy is in the [errors reference](reference/errors.md), and `examples/blocking_api/i2c/error_handling.py` provokes one of each on purpose. ## Where to go next - **[Protocol guides](guides/index.md)** — one page per protocol, plus the bulk transfer plane and target mode. - **[The device lifecycle](guides/lifecycle.md)** — the handle every protocol guide sits inside. - **[Migrating from the legacy packages](migration.md)** — for existing `binhosupernova`, `binhopulsar` or `supernovacontroller` code. - **[API reference](reference/index.md)** — generated from the docstrings.