Skip to content

Model

Core data structures and types for defining diagrams.

model

Core data model for Raspberry Pi GPIO diagrams.

Board dataclass

Board(
    name: str,
    pins: list[HeaderPin],
    svg_asset_path: str = "",
    width: float = 340.0,
    height: float = 220.0,
    header_offset: Point = (lambda: Point(297.0, 52.0))(),
    layout: BoardLayout | None = None,
    style_overrides: dict = dict(),
    svg_scale: float = 1.0,
)

A Raspberry Pi board with GPIO header.

Represents a physical Raspberry Pi board including its GPIO header pins, dimensions, and rendering information.

ATTRIBUTE DESCRIPTION
name

Board display name (e.g., "Raspberry Pi 5")

TYPE: str

pins

List of all GPIO header pins (40 pins for standard boards)

TYPE: list[HeaderPin]

svg_asset_path

Path to board SVG image file (legacy, optional)

TYPE: str

width

Board width in SVG units (legacy, used if layout is None)

TYPE: float

height

Board height in SVG units (legacy, used if layout is None)

TYPE: float

header_offset

Position of GPIO header pin 1 (legacy)

TYPE: Point

layout

Optional BoardLayout for standardized rendering (preferred)

TYPE: BoardLayout | None

style_overrides

Optional style customizations (e.g., custom PCB color)

TYPE: dict

get_pin_by_bcm

get_pin_by_bcm(bcm_number: int) -> HeaderPin | None

Get a pin by its BCM GPIO number.

Only applies to GPIO pins. Power and ground pins don't have BCM numbers.

PARAMETER DESCRIPTION
bcm_number

BCM GPIO number (0-27 for Raspberry Pi)

TYPE: int

RETURNS DESCRIPTION
HeaderPin | None

HeaderPin if found, None otherwise

Examples:

>>> board = boards.raspberry_pi_5()
>>> pin = board.get_pin_by_bcm(2)
>>> print(f"{pin.name} is on physical pin {pin.number}")
GPIO2 is on physical pin 3
Source code in src/pinviz/model.py
def get_pin_by_bcm(self, bcm_number: int) -> HeaderPin | None:
    """
    Get a pin by its BCM GPIO number.

    Only applies to GPIO pins. Power and ground pins don't have BCM numbers.

    Args:
        bcm_number: BCM GPIO number (0-27 for Raspberry Pi)

    Returns:
        HeaderPin if found, None otherwise

    Examples:
        >>> board = boards.raspberry_pi_5()
        >>> pin = board.get_pin_by_bcm(2)
        >>> print(f"{pin.name} is on physical pin {pin.number}")
        GPIO2 is on physical pin 3
    """
    return next((p for p in self.pins if p.gpio_bcm == bcm_number), None)

get_pin_by_name

get_pin_by_name(name: str) -> HeaderPin | None

Get a pin by its name.

PARAMETER DESCRIPTION
name

Pin name (e.g., "GPIO2", "3V3", "GND")

TYPE: str

RETURNS DESCRIPTION
HeaderPin | None

HeaderPin if found, None otherwise

Examples:

>>> board = boards.raspberry_pi_5()
>>> pin = board.get_pin_by_name("GPIO2")
>>> print(f"Pin {pin.number} - {pin.name}")
Pin 3 - GPIO2
Source code in src/pinviz/model.py
def get_pin_by_name(self, name: str) -> HeaderPin | None:
    """
    Get a pin by its name.

    Args:
        name: Pin name (e.g., "GPIO2", "3V3", "GND")

    Returns:
        HeaderPin if found, None otherwise

    Examples:
        >>> board = boards.raspberry_pi_5()
        >>> pin = board.get_pin_by_name("GPIO2")
        >>> print(f"Pin {pin.number} - {pin.name}")
        Pin 3 - GPIO2
    """
    return next((p for p in self.pins if p.name == name), None)

get_pin_by_number

get_pin_by_number(pin_number: int) -> HeaderPin | None

Get a pin by its physical pin number.

PARAMETER DESCRIPTION
pin_number

Physical pin number (1-40)

TYPE: int

RETURNS DESCRIPTION
HeaderPin | None

HeaderPin if found, None otherwise

Examples:

>>> board = boards.raspberry_pi_5()
>>> pin = board.get_pin_by_number(1)
>>> print(pin.name)
3V3
Source code in src/pinviz/model.py
def get_pin_by_number(self, pin_number: int) -> HeaderPin | None:
    """
    Get a pin by its physical pin number.

    Args:
        pin_number: Physical pin number (1-40)

    Returns:
        HeaderPin if found, None otherwise

    Examples:
        >>> board = boards.raspberry_pi_5()
        >>> pin = board.get_pin_by_number(1)
        >>> print(pin.name)
        3V3
    """
    return next((p for p in self.pins if p.number == pin_number), None)

Component dataclass

Component(
    type: ComponentType, value: str, position: float = 0.55
)

An inline component placed on a wire connection.

Represents a component (resistor, capacitor, diode) that sits on a wire between the board and device. Useful for showing pull-up resistors, current limiting resistors, decoupling capacitors, etc.

ATTRIBUTE DESCRIPTION
type

Type of component (resistor, capacitor, diode)

TYPE: ComponentType

value

Component value with units (e.g., "220Ω", "100µF", "1N4148")

TYPE: str

position

Position along wire path from source to destination (0.0-1.0, default 0.55)

TYPE: float

ComponentType

Bases: StrEnum

Type of inline component on a wire.

Defines types of electronic components that can be placed along a wire connection between board and device.

ATTRIBUTE DESCRIPTION
RESISTOR

Resistor (e.g., current limiting, pull-up/down)

CAPACITOR

Capacitor (e.g., decoupling, filtering)

DIODE

Diode (e.g., flyback protection)

Connection dataclass

Connection(
    board_pin: int | None = None,
    device_name: str | None = None,
    device_pin_name: str | None = None,
    source_device: str | None = None,
    source_pin: str | None = None,
    color: str | None = None,
    net_name: str | None = None,
    style: WireStyle = WireStyle.MIXED,
    components: list[Component] = list(),
)

A wire connection between a board pin and a device pin, or between two devices.

Represents a physical wire connecting either: 1. A GPIO header pin to a device pin (board-to-device connection) 2. A pin on one device to a pin on another device (device-to-device connection)

Wire color is automatically assigned based on pin role unless explicitly specified.

ATTRIBUTE DESCRIPTION
board_pin

Physical pin number on the GPIO header (1-40). Required for board connections.

TYPE: int | None

device_name

Name of the target device (must match Device.name)

TYPE: str | None

device_pin_name

Name of the target pin on the device

TYPE: str | None

source_device

Name of the source device for device-to-device connections

TYPE: str | None

source_pin

Name of the source pin for device-to-device connections

TYPE: str | None

color

Wire color as hex code (auto-assigned from pin role if None)

TYPE: str | None

net_name

Optional logical net name for documentation (e.g., "I2C_BUS")

TYPE: str | None

style

Wire routing style (orthogonal, curved, or mixed)

TYPE: WireStyle

components

List of inline components on this wire (resistors, capacitors, etc.)

TYPE: list[Component]

Examples:

>>> # Board-to-device connection with auto-assigned color
>>> conn = Connection(board_pin=1, device_name="Sensor", device_pin_name="VCC")
>>>
>>> # Device-to-device connection
>>> conn = Connection(
...     source_device="TP4056",
...     source_pin="OUT+",
...     device_name="ESP32",
...     device_pin_name="VIN"
... )
>>>
>>> # Connection with custom color and resistor
>>> conn = Connection(
...     board_pin=11,
...     device_name="LED",
...     device_pin_name="Anode",
...     color="#FF0000",
...     components=[Component(ComponentType.RESISTOR, "220Ω")]
... )

__post_init__

__post_init__() -> None

Validate that exactly one source type is specified.

Source code in src/pinviz/model.py
def __post_init__(self) -> None:
    """Validate that exactly one source type is specified."""
    # Validate target device fields are always present
    if self.device_name is None:
        raise ValueError(
            format_connection_error(
                "missing_device_name",
                device_name=self.device_name,
                device_pin_name=self.device_pin_name,
            )
        )
    if self.device_pin_name is None:
        raise ValueError(
            format_connection_error(
                "missing_device_pin",
                device_name=self.device_name,
                device_pin_name=self.device_pin_name,
            )
        )

    # Validate source: exactly one source type must be specified
    has_board_source = self.board_pin is not None
    has_device_source = self.source_device is not None and self.source_pin is not None

    # Check for incomplete device-to-device connection specification
    has_partial_device_source = (self.source_device is None) != (self.source_pin is None)

    if has_partial_device_source:
        raise ValueError(
            format_connection_error(
                "incomplete_device_source",
                source_device=self.source_device,
                source_pin=self.source_pin,
                device_name=self.device_name,
                device_pin_name=self.device_pin_name,
            )
        )

    if has_board_source and has_device_source:
        raise ValueError(
            format_connection_error(
                "both_sources",
                board_pin=self.board_pin,
                source_device=self.source_device,
                source_pin=self.source_pin,
                device_name=self.device_name,
                device_pin_name=self.device_pin_name,
            )
        )

    if not has_board_source and not has_device_source:
        raise ValueError(
            format_connection_error(
                "no_source",
                board_pin=self.board_pin,
                source_device=self.source_device,
                source_pin=self.source_pin,
                device_name=self.device_name,
                device_pin_name=self.device_pin_name,
            )
        )

from_board classmethod

from_board(
    board_pin: int,
    device_name: str,
    device_pin_name: str,
    color: str | None = None,
    net_name: str | None = None,
    style: WireStyle = WireStyle.MIXED,
    components: list[Component] | None = None,
) -> Connection

Create a board-to-device connection.

PARAMETER DESCRIPTION
board_pin

Physical pin number on the GPIO header (1-40)

TYPE: int

device_name

Name of the target device

TYPE: str

device_pin_name

Name of the target pin on the device

TYPE: str

color

Optional wire color as hex code

TYPE: str | None DEFAULT: None

net_name

Optional logical net name for documentation

TYPE: str | None DEFAULT: None

style

Wire routing style (default: MIXED)

TYPE: WireStyle DEFAULT: MIXED

components

Optional list of inline components

TYPE: list[Component] | None DEFAULT: None

RETURNS DESCRIPTION
Connection

A new Connection instance for a board-to-device connection.

Examples:

>>> conn = Connection.from_board(1, "Sensor", "VCC", color="#FF0000")
Source code in src/pinviz/model.py
@classmethod
def from_board(
    cls,
    board_pin: int,
    device_name: str,
    device_pin_name: str,
    color: str | None = None,
    net_name: str | None = None,
    style: WireStyle = WireStyle.MIXED,
    components: list[Component] | None = None,
) -> Connection:
    """
    Create a board-to-device connection.

    Args:
        board_pin: Physical pin number on the GPIO header (1-40)
        device_name: Name of the target device
        device_pin_name: Name of the target pin on the device
        color: Optional wire color as hex code
        net_name: Optional logical net name for documentation
        style: Wire routing style (default: MIXED)
        components: Optional list of inline components

    Returns:
        A new Connection instance for a board-to-device connection.

    Examples:
        >>> conn = Connection.from_board(1, "Sensor", "VCC", color="#FF0000")
    """
    return cls(
        board_pin=board_pin,
        device_name=device_name,
        device_pin_name=device_pin_name,
        color=color,
        net_name=net_name,
        style=style,
        components=components or [],
    )

from_device classmethod

from_device(
    source_device: str,
    source_pin: str,
    target_device: str,
    target_pin: str,
    color: str | None = None,
    net_name: str | None = None,
    style: WireStyle = WireStyle.MIXED,
    components: list[Component] | None = None,
) -> Connection

Create a device-to-device connection.

PARAMETER DESCRIPTION
source_device

Name of the source device

TYPE: str

source_pin

Name of the source pin

TYPE: str

target_device

Name of the target device

TYPE: str

target_pin

Name of the target pin

TYPE: str

color

Optional wire color as hex code

TYPE: str | None DEFAULT: None

net_name

Optional logical net name for documentation

TYPE: str | None DEFAULT: None

style

Wire routing style (default: MIXED)

TYPE: WireStyle DEFAULT: MIXED

components

Optional list of inline components

TYPE: list[Component] | None DEFAULT: None

RETURNS DESCRIPTION
Connection

A new Connection instance for a device-to-device connection.

Examples:

>>> conn = Connection.from_device("TP4056", "OUT+", "ESP32", "VIN")
Source code in src/pinviz/model.py
@classmethod
def from_device(
    cls,
    source_device: str,
    source_pin: str,
    target_device: str,
    target_pin: str,
    color: str | None = None,
    net_name: str | None = None,
    style: WireStyle = WireStyle.MIXED,
    components: list[Component] | None = None,
) -> Connection:
    """
    Create a device-to-device connection.

    Args:
        source_device: Name of the source device
        source_pin: Name of the source pin
        target_device: Name of the target device
        target_pin: Name of the target pin
        color: Optional wire color as hex code
        net_name: Optional logical net name for documentation
        style: Wire routing style (default: MIXED)
        components: Optional list of inline components

    Returns:
        A new Connection instance for a device-to-device connection.

    Examples:
        >>> conn = Connection.from_device("TP4056", "OUT+", "ESP32", "VIN")
    """
    return cls(
        source_device=source_device,
        source_pin=source_pin,
        device_name=target_device,
        device_pin_name=target_pin,
        color=color,
        net_name=net_name,
        style=style,
        components=components or [],
    )

get_source

get_source() -> tuple[str, str]

Get the source of this connection as a (name, pin) tuple.

RETURNS DESCRIPTION
str

For board connections: ("board", str(board_pin))

str

For device connections: (source_device, source_pin)

RAISES DESCRIPTION
ValueError

If the connection has invalid state (should not happen after post_init).

Source code in src/pinviz/model.py
def get_source(self) -> tuple[str, str]:
    """
    Get the source of this connection as a (name, pin) tuple.

    Returns:
        For board connections: ("board", str(board_pin))
        For device connections: (source_device, source_pin)

    Raises:
        ValueError: If the connection has invalid state (should not happen after __post_init__).
    """
    if self.is_board_connection():
        return ("board", str(self.board_pin))
    elif self.is_device_connection():
        return (self.source_device, self.source_pin)  # type: ignore
    else:
        raise ValueError("Connection has invalid state: no source specified")

is_board_connection

is_board_connection() -> bool

Check if this is a board-to-device connection.

RETURNS DESCRIPTION
bool

True if the connection source is a board pin, False otherwise.

Source code in src/pinviz/model.py
def is_board_connection(self) -> bool:
    """
    Check if this is a board-to-device connection.

    Returns:
        True if the connection source is a board pin, False otherwise.
    """
    return self.board_pin is not None

is_device_connection

is_device_connection() -> bool

Check if this is a device-to-device connection.

RETURNS DESCRIPTION
bool

True if the connection source is another device, False otherwise.

Source code in src/pinviz/model.py
def is_device_connection(self) -> bool:
    """
    Check if this is a device-to-device connection.

    Returns:
        True if the connection source is another device, False otherwise.
    """
    return self.source_device is not None

Device dataclass

Device(
    name: str,
    pins: list[DevicePin],
    width: float = 80.0,
    height: float = 40.0,
    position: Point = (lambda: Point(0, 0))(),
    color: str = "#4A90E2",
    type_id: str | None = None,
    description: str | None = None,
    url: str | None = None,
    category: str | None = None,
    i2c_address: int | None = None,
)

An electronic device or module to be connected to the Raspberry Pi.

Represents an external component (sensor, LED, button, etc.) that will be wired to the GPIO header. Devices have named pins and are rendered as colored rectangles in the diagram.

ATTRIBUTE DESCRIPTION
name

Display name shown in diagram (e.g., "BH1750 Light Sensor")

TYPE: str

pins

List of connection points on the device

TYPE: list[DevicePin]

width

Device box width in SVG units (default: 80.0)

TYPE: float

height

Device box height in SVG units (default: 40.0)

TYPE: float

position

Device position in canvas (automatically calculated by layout engine)

TYPE: Point

color

Device box fill color as hex code (default: "#4A90E2" blue)

TYPE: str

type_id

Optional device template type ID (for registry lookup)

TYPE: str | None

description

Optional device description

TYPE: str | None

url

Optional URL to device documentation or datasheet

TYPE: str | None

category

Optional device category (sensors, displays, leds, etc.)

TYPE: str | None

i2c_address

Optional default I2C address (7-bit integer)

TYPE: int | None

get_pin_by_name

get_pin_by_name(name: str) -> DevicePin | None

Get a device pin by name.

PARAMETER DESCRIPTION
name

Pin name as labeled on device (e.g., "VCC", "SDA")

TYPE: str

RETURNS DESCRIPTION
DevicePin | None

DevicePin if found, None otherwise

Examples:

>>> sensor = devices.bh1750_light_sensor()
>>> vcc_pin = sensor.get_pin_by_name("VCC")
>>> print(vcc_pin.role)
PinRole.POWER_3V3
Source code in src/pinviz/model.py
def get_pin_by_name(self, name: str) -> DevicePin | None:
    """
    Get a device pin by name.

    Args:
        name: Pin name as labeled on device (e.g., "VCC", "SDA")

    Returns:
        DevicePin if found, None otherwise

    Examples:
        >>> sensor = devices.bh1750_light_sensor()
        >>> vcc_pin = sensor.get_pin_by_name("VCC")
        >>> print(vcc_pin.role)
        PinRole.POWER_3V3
    """
    return next((p for p in self.pins if p.name == name), None)

DevicePin dataclass

DevicePin(
    name: str,
    role: PinRole,
    position: Point = (lambda: Point(0, 0))(),
)

A pin on a device or module.

Represents a connection point on an external device (sensor, LED, button, etc.) that can be wired to the Raspberry Pi GPIO header.

ATTRIBUTE DESCRIPTION
name

Pin name as labeled on the device (e.g., "VCC", "GND", "SDA", "SCL")

TYPE: str

role

Functional role of the pin (determines wire color)

TYPE: PinRole

position

Pin position relative to device origin (set by device template)

TYPE: Point

Diagram dataclass

Diagram(
    title: str,
    board: Board,
    devices: list[Device],
    connections: list[Connection],
    show_legend: bool = False,
    show_gpio_diagram: bool = False,
    show_title: bool = True,
    show_board_name: bool = True,
    theme: Theme = Theme.LIGHT,
    canvas_width: float = 800.0,
    canvas_height: float = 600.0,
)

A complete GPIO wiring diagram.

Represents the entire diagram including the Raspberry Pi board, all connected devices, and all wire connections. This is the top-level object that gets rendered to SVG.

ATTRIBUTE DESCRIPTION
title

Diagram title displayed at the top

TYPE: str

board

The Raspberry Pi board

TYPE: Board

devices

List of all devices to be connected

TYPE: list[Device]

connections

List of all wire connections

TYPE: list[Connection]

show_legend

Whether to show the device specifications table (default: False)

TYPE: bool

show_gpio_diagram

Whether to show the GPIO pin reference diagram (default: False)

TYPE: bool

show_title

Whether to show the diagram title (default: True)

TYPE: bool

show_board_name

Whether to show the board name (default: True)

TYPE: bool

canvas_width

Canvas width in SVG units (auto-calculated by layout engine)

TYPE: float

canvas_height

Canvas height in SVG units (auto-calculated by layout engine)

TYPE: float

Examples:

>>> from pinviz import boards, devices, Connection, Diagram, SVGRenderer
>>>
>>> # Create diagram
>>> diagram = Diagram(
...     title="BH1750 Light Sensor",
...     board=boards.raspberry_pi_5(),
...     devices=[devices.bh1750_light_sensor()],
...     connections=[
...         Connection(1, "BH1750", "VCC"),
...         Connection(6, "BH1750", "GND"),
...         Connection(3, "BH1750", "SDA"),
...         Connection(5, "BH1750", "SCL"),
...     ]
... )
>>>
>>> # Render to SVG
>>> renderer = SVGRenderer()
>>> renderer.render(diagram, "output.svg")

HeaderPin dataclass

HeaderPin(
    number: int,
    name: str,
    role: PinRole,
    gpio_bcm: int | None = None,
    position: Point | None = None,
)

A pin on the Raspberry Pi GPIO header.

Represents a single pin on the 40-pin GPIO header, including its physical pin number, function/role, and optional BCM GPIO number for GPIO pins.

ATTRIBUTE DESCRIPTION
number

Physical pin number on the header (1-40)

TYPE: int

name

Pin name (e.g., "3V3", "GPIO2", "GND")

TYPE: str

role

Functional role of the pin (power, GPIO, I2C, SPI, etc.)

TYPE: PinRole

gpio_bcm

BCM GPIO number for GPIO pins (e.g., GPIO2 = BCM 2), None for non-GPIO pins

TYPE: int | None

position

Pin position in board coordinate space, set by board template

TYPE: Point | None

PinRole

Bases: StrEnum

Role or function of a GPIO pin.

Defines the various roles that pins can have on the Raspberry Pi GPIO header or on connected devices. Used for automatic wire color assignment and documentation purposes.

ATTRIBUTE DESCRIPTION
POWER_3V3

3.3V power supply pin

POWER_5V

5V power supply pin

GROUND

Ground (GND) pin

GPIO

General Purpose Input/Output pin

I2C_SDA

I2C Serial Data line

I2C_SCL

I2C Serial Clock line

SPI_MOSI

SPI Master Out Slave In

SPI_MISO

SPI Master In Slave Out

SPI_SCLK

SPI Serial Clock

SPI_CE0

SPI Chip Enable 0

SPI_CE1

SPI Chip Enable 1

UART_TX

UART Transmit

UART_RX

UART Receive

PWM

Pulse Width Modulation

PCM_CLK

PCM Audio Clock

PCM_FS

PCM Audio Frame Sync

PCM_DIN

PCM Audio Data In

PCM_DOUT

PCM Audio Data Out

I2C_EEPROM

I2C EEPROM identification pins

Point dataclass

Point(x: float, y: float)

A 2D point in SVG coordinate space.

Represents a position in the SVG canvas coordinate system. All measurements are in SVG units (typically pixels).

ATTRIBUTE DESCRIPTION
x

Horizontal position (left to right)

TYPE: float

y

Vertical position (top to bottom)

TYPE: float

WireColor

Bases: StrEnum

Standard wire colors for electronics projects.

Provides a set of commonly used wire colors as hex color codes. These can be used for explicit color assignment in connections.

ATTRIBUTE DESCRIPTION
RED

Red (#FF0000)

BLACK

Black (#000000)

WHITE

White (#FFFFFF)

GREEN

Green (#00FF00)

BLUE

Blue (#0000FF)

YELLOW

Yellow (#FFFF00)

ORANGE

Orange (#FF8C00)

PURPLE

Purple (#9370DB)

GRAY

Gray (#808080)

BROWN

Brown (#8B4513)

PINK

Pink (#FF69B4)

CYAN

Cyan (#00CED1)

MAGENTA

Magenta (#FF00FF)

LIME

Lime (#32CD32)

TURQUOISE

Turquoise (#40E0D0)

WireStyle

Bases: StrEnum

Wire routing style for connections.

Defines how wires are drawn between board pins and device pins.

ATTRIBUTE DESCRIPTION
ORTHOGONAL

Straight lines with right angles (no rounding)

CURVED

Smooth bezier curves throughout

MIXED

Orthogonal routing with rounded corners (default, recommended)