OpenHWBridge

An open-source toolkit that bridges hardware development with modern software workflows.

What it does

OpenHWBridge provides a unified interface for integrating, testing, and deploying firmware across hardware platforms. It's designed to reduce the friction hardware startups face when moving from prototype to production.

Freemium model

Open source (free) — The core OpenHWBridge toolkit is fully open source and free to use. Contributions, issues, and feedback are welcome from the community.

Custom solutions (paid) — Need something beyond the core? We build custom features, integrations, and tailored solutions on top of OpenHWBridge for your specific hardware stack. Submit a request and we'll scope it out.

View on GitHub Request custom work

Get in touch

Interested in using OpenHWBridge or contributing? We'd love to hear from you.

Contact us

Architecture overview

OpenHWBridge is split into three concerns: transport, protocol, and domain objects.

Transport layer

Handles raw byte exchange over UART (via pySerialTransfer) or BLE (via a BleuIO USB dongle). Transport objects are interchangeable behind a common Transport ABC — they contain no SPI, I2C, or sensor semantics.

Protocol layer

Transport-independent framing, codec, and session management. Encodes and decodes protocol frames, manages sequence IDs, applies timeout and retry policy, validates response structure, and raises typed exceptions. A background listener thread per transport continuously reads raw packets, classifies them by command byte, and dispatches IRQ data to registered callbacks.

Domain object layer

Models the hardware system directly. Host manages one or more boards, Device owns transports and peripherals, Interface objects (I2C, SPI) abstract bus-level communication, and Peripheral objects model attached ICs with register read/write. Sensor-specific drivers like BMM150 build on the generic peripheral layer.

Communication protocol

Each command is sent as a frame with a command byte, sequence number, and command-specific payload. Responses carry a status byte followed by command-specific data.

FieldSizeDescription
CMD1 byteCommand ID
SEQ1 byteTransaction ID
DATAN bytesCommand-specific payload

Supported interfaces

I2C

The most mature protocol in the codebase. Full support for multi-bus operation (Wire / Wire1), read, write, scan, write-then-read with restart, and frequency configuration (100 kHz, 400 kHz, 1 MHz). Detailed status codes for transmit buffer overflow, address NACK, data NACK, and timeout conditions.

SPI

Device and profile registration system with configurable frequency, bit order, and mode. Supports up to 8 profiles and 8 devices. Chip select management and full-duplex transfer via spi_transfer().

GPIO

Pin initialization, digital write, and digital read commands for general-purpose I/O control.

CAN

CAN bus initialization and frame read/write support for industrial and automotive communication.

BLE

Wireless host-to-board communication via a standard BLE GATT service with TX (notify) and RX (write) characteristics. BLE packets feed through the same protocol dispatcher as serial packets, giving both transports a unified command pipeline.

IRQ system

Interrupt-driven peripheral data collection supporting up to 8 IRQ slots with hardware interrupt or hardware timer backends. Each IRQ can run an OP-action script — a sequence of register reads, writes, delays, and notifications executed by a lightweight firmware interpreter. The Python SDK mirrors this with an IRQ class for script construction and callback registration.

Python SDK

The Python client provides a sync-first API. Devices are configured either programmatically or declaratively via YAML:

from openhwbridge.host import Host
from openhwbridge.config import YamlDeviceConfig

host = Host()
host.init()

yaml_config = YamlDeviceConfig("device_configs/my_board.yaml")
host.import_config(yaml_config=yaml_config)

device = host.get_device(name="my_board")
peripheral = device.get_peripheral(name="bmm150")
peripheral.set_up()

YAML device configuration

Devices are configured declaratively — transports, interfaces, and peripherals are defined in a YAML file:

device:
  name: "my_device"
  transports:
    default: serial
    serial:
      enabled: True
      port: COM7
      baud_rate: 115200
    ble:
      enabled: False
  interfaces:
    i2c:
      bus0: { enabled: True, frequency: 100000, hw_bus: wire0 }
  peripherals:
    - bmm150:
        object: BMM150
        name: "bmm150"
        interface: i2c
        address: 0x10
        bus: bus0