Skip to content

Boards

Predefined board templates and factory functions.

boards

Predefined Raspberry Pi board templates.

esp32_devkit_v1

esp32_devkit_v1() -> Board

Create an ESP32 DevKit V1 board with 30-pin GPIO header.

The ESP32 DevKit V1 is one of the most popular IoT development boards, featuring Wi-Fi and Bluetooth connectivity. It has a dual-row vertical pin layout with 15 pins on each side (30 total).

Pin layout (physical pin numbers): Standard 2x15 header layout: - Left column (odd pins): 1, 3, 5, ..., 29 (top to bottom) - Right column (even pins): 2, 4, 6, ..., 30 (top to bottom)

RETURNS DESCRIPTION
Board

Configured ESP32 DevKit V1 board with all pins positioned

TYPE: Board

Examples:

>>> board = esp32_devkit_v1()
>>> print(board.name)
ESP32 DevKit V1
>>> print(len(board.pins))
30
Source code in src/pinviz/boards.py
def esp32_devkit_v1() -> Board:
    """
    Create an ESP32 DevKit V1 board with 30-pin GPIO header.

    The ESP32 DevKit V1 is one of the most popular IoT development boards,
    featuring Wi-Fi and Bluetooth connectivity. It has a dual-row vertical
    pin layout with 15 pins on each side (30 total).

    Pin layout (physical pin numbers):
    Standard 2x15 header layout:
    - Left column (odd pins): 1, 3, 5, ..., 29 (top to bottom)
    - Right column (even pins): 2, 4, 6, ..., 30 (top to bottom)

    Returns:
        Board: Configured ESP32 DevKit V1 board with all pins positioned

    Examples:
        >>> board = esp32_devkit_v1()
        >>> print(board.name)
        ESP32 DevKit V1
        >>> print(len(board.pins))
        30
    """
    return load_board_from_config("esp32_devkit_v1")

esp8266_nodemcu

esp8266_nodemcu() -> Board

Create an ESP8266 NodeMCU board with 30-pin GPIO header.

The NodeMCU is one of the most popular ESP8266 development boards with built-in USB-to-serial and 30 pins in a dual-row layout.

RETURNS DESCRIPTION
Board

Configured ESP8266 NodeMCU board with all pins positioned

TYPE: Board

Examples:

>>> board = esp8266_nodemcu()
>>> print(board.name)
ESP8266 NodeMCU
>>> print(len(board.pins))
30
Source code in src/pinviz/boards.py
def esp8266_nodemcu() -> Board:
    """
    Create an ESP8266 NodeMCU board with 30-pin GPIO header.

    The NodeMCU is one of the most popular ESP8266 development boards
    with built-in USB-to-serial and 30 pins in a dual-row layout.

    Returns:
        Board: Configured ESP8266 NodeMCU board with all pins positioned

    Examples:
        >>> board = esp8266_nodemcu()
        >>> print(board.name)
        ESP8266 NodeMCU
        >>> print(len(board.pins))
        30
    """
    return load_board_from_config("esp8266_nodemcu")

get_available_boards

get_available_boards() -> list[dict[str, str | list[str]]]

Get a list of all available board configurations.

Scans the board_configs directory and returns metadata for each board including its canonical name and aliases.

RETURNS DESCRIPTION
list[dict[str, str | list[str]]]

List of dictionaries with board information:

list[dict[str, str | list[str]]]
  • name: Canonical board configuration name (e.g., "raspberry_pi_5")
list[dict[str, str | list[str]]]
  • display_name: Human-readable board name (e.g., "Raspberry Pi 5")
list[dict[str, str | list[str]]]
  • aliases: List of alternative names for the board

Examples:

>>> boards_info = get_available_boards()
>>> for board in boards_info:
...     print(f"{board['name']}: {board['aliases']}")
raspberry_pi_5: ['rpi5', 'rpi']
raspberry_pi_4: ['rpi4', 'pi4']
raspberry_pi_pico: ['pico']
Note

This function dynamically discovers boards from the board_configs directory, so it will automatically include any newly added boards.

Source code in src/pinviz/boards.py
def get_available_boards() -> list[dict[str, str | list[str]]]:
    """
    Get a list of all available board configurations.

    Scans the board_configs directory and returns metadata for each board
    including its canonical name and aliases.

    Returns:
        List of dictionaries with board information:
        - name: Canonical board configuration name (e.g., "raspberry_pi_5")
        - display_name: Human-readable board name (e.g., "Raspberry Pi 5")
        - aliases: List of alternative names for the board

    Examples:
        >>> boards_info = get_available_boards()
        >>> for board in boards_info:
        ...     print(f"{board['name']}: {board['aliases']}")
        raspberry_pi_5: ['rpi5', 'rpi']
        raspberry_pi_4: ['rpi4', 'pi4']
        raspberry_pi_pico: ['pico']

    Note:
        This function dynamically discovers boards from the board_configs
        directory, so it will automatically include any newly added boards.
    """
    # Map of board config names to their aliases
    # This could be extracted to a separate config file if needed
    board_aliases = {
        "raspberry_pi_5": ["rpi5", "rpi"],
        "raspberry_pi_4": ["rpi4", "pi4"],
        "raspberry_pi_pico": ["pico"],
        "esp32_devkit_v1": ["esp32", "esp32dev", "esp32_devkit"],
        "wemos_d1_mini": ["d1mini", "d1_mini", "wemos"],
        "esp8266_nodemcu": ["esp8266", "nodemcu"],
    }

    boards_list = []
    module_dir = Path(__file__).parent
    config_dir = module_dir / "board_configs"

    if config_dir.exists():
        for config_file in sorted(config_dir.glob("*.json")):
            board_name = config_file.stem  # e.g., "raspberry_pi_5"

            # Load the config to get the display name
            try:
                with open(config_file) as f:
                    config_dict = json.load(f)
                display_name = config_dict.get("name", board_name)
            except (json.JSONDecodeError, OSError):
                display_name = board_name

            boards_list.append(
                {
                    "name": board_name,
                    "display_name": display_name,
                    "aliases": board_aliases.get(board_name, []),
                }
            )

    return boards_list

load_board_from_config

load_board_from_config(config_name: str) -> Board

Load a board definition from a JSON configuration file.

This function reads a board configuration from the board_configs directory, validates it against the BoardConfigSchema, calculates pin positions based on layout parameters, and returns a fully configured Board object.

The configuration file must specify: - Board metadata (name, SVG asset, dimensions) - GPIO header layout parameters (column positions, spacing) - Pin definitions (physical pin number, name, role, BCM GPIO number)

Pin positions are calculated automatically based on the layout parameters to align with the board's SVG asset.

PARAMETER DESCRIPTION
config_name

Name of the board configuration file (without .json extension) For example, "raspberry_pi_5" will load "raspberry_pi_5.json"

TYPE: str

RETURNS DESCRIPTION
Board

Configured board with all pins positioned

TYPE: Board

RAISES DESCRIPTION
FileNotFoundError

If the configuration file doesn't exist

ValueError

If the configuration is invalid or fails validation

JSONDecodeError

If the JSON file is malformed

Examples:

>>> board = load_board_from_config("raspberry_pi_5")
>>> print(board.name)
Raspberry Pi 5
>>> print(len(board.pins))
40
Note

This is the recommended way to add new board types. Simply create a new JSON configuration file in the board_configs directory following the schema defined in BoardConfigSchema.

Source code in src/pinviz/boards.py
def load_board_from_config(config_name: str) -> Board:
    """
    Load a board definition from a JSON configuration file.

    This function reads a board configuration from the board_configs directory,
    validates it against the BoardConfigSchema, calculates pin positions based
    on layout parameters, and returns a fully configured Board object.

    The configuration file must specify:
    - Board metadata (name, SVG asset, dimensions)
    - GPIO header layout parameters (column positions, spacing)
    - Pin definitions (physical pin number, name, role, BCM GPIO number)

    Pin positions are calculated automatically based on the layout parameters
    to align with the board's SVG asset.

    Args:
        config_name: Name of the board configuration file (without .json extension)
                    For example, "raspberry_pi_5" will load "raspberry_pi_5.json"

    Returns:
        Board: Configured board with all pins positioned

    Raises:
        FileNotFoundError: If the configuration file doesn't exist
        ValueError: If the configuration is invalid or fails validation
        json.JSONDecodeError: If the JSON file is malformed

    Examples:
        >>> board = load_board_from_config("raspberry_pi_5")
        >>> print(board.name)
        Raspberry Pi 5
        >>> print(len(board.pins))
        40

    Note:
        This is the recommended way to add new board types. Simply create a new
        JSON configuration file in the board_configs directory following the
        schema defined in BoardConfigSchema.
    """
    config_path = _get_board_config_path(config_name)

    if not config_path.exists():
        raise FileNotFoundError(
            f"Board configuration file not found: {config_path}. "
            f"Available configurations should be placed in the board_configs directory."
        )

    try:
        with open(config_path) as f:
            config_dict = json.load(f)
    except json.JSONDecodeError as e:
        raise ValueError(f"Invalid JSON in board configuration file {config_path}: {e}") from e

    # Validate configuration against schema
    try:
        config: BoardConfigSchema = validate_board_config(config_dict)
    except Exception as e:
        raise ValueError(f"Invalid board configuration in {config_path}: {e}") from e

    # Calculate pin positions based on layout parameters
    # Check if this is a dual-header board (like Pico) or single-header (like Pi 5)
    layout_dict = config.layout if isinstance(config.layout, dict) else config.layout.__dict__
    is_dual_header = (
        layout_dict.get("top_header") is not None and layout_dict.get("bottom_header") is not None
    )

    # Use appropriate position calculation method based on board type
    if is_dual_header:
        pin_positions = _calculate_dual_header_positions(layout_dict, config.pins)
    else:
        pin_positions = _calculate_single_header_positions(layout_dict, config.pins)

    # Create HeaderPin objects from configuration
    pins = []
    for pin_config in config.pins:
        pin_role = PinRole(pin_config.role)  # Convert string to PinRole enum
        position = pin_positions.get(pin_config.physical_pin)

        if position is None:
            raise ValueError(
                f"Could not calculate position for pin {pin_config.physical_pin}. "
                f"Pin number may be out of range for the configured layout."
            )

        header_pin = HeaderPin(
            number=pin_config.physical_pin,
            name=pin_config.name,
            role=pin_role,
            gpio_bcm=pin_config.gpio_bcm,
            position=position,
        )
        pins.append(header_pin)

    # Sort pins by physical pin number for consistency
    pins.sort(key=lambda p: p.number)

    # Check render mode: svg_asset mode skips BoardLayout so the legacy SVG
    # embedding path is used (board.layout == None triggers SVG asset rendering).
    render_mode = getattr(config, "render_mode", "programmatic")
    if render_mode == "svg_asset":
        svg_scale = getattr(config, "svg_scale", 1.0)
        # Scale pin positions to match SVG scaling
        if svg_scale != 1.0:
            for pin in pins:
                if pin.position:
                    pin.position = Point(pin.position.x * svg_scale, pin.position.y * svg_scale)
        return Board(
            name=config.name,
            pins=pins,
            svg_asset_path=_get_asset_path(config.svg_asset),
            width=config.width * svg_scale,
            height=config.height * svg_scale,
            header_offset=Point(config.header_offset.x, config.header_offset.y),
            layout=None,
            svg_scale=svg_scale,
        )

    # Build BoardLayout from config dimensions for programmatic rendering.
    # Config width/height are in pixels; BoardLayout expects mm.
    # Use default scale_factor (3.0 px/mm) to convert.
    from .board_renderer import BoardStyle

    scale = BoardStyle().scale_factor
    num_rows = len(config.pins) // 2  # pins per side

    # Header position and size from layout parameters
    if is_dual_header:
        # Dual-header boards (like Pico) — compute from top/bottom header params
        header_x = layout_dict["top_header"]["start_x"] / scale
        header_y = layout_dict["top_header"]["y"] / scale
        header_w = (num_rows * layout_dict["top_header"]["pin_spacing"]) / scale
        header_h = (layout_dict["bottom_header"]["y"] - layout_dict["top_header"]["y"]) / scale
    else:
        # Single-header boards (Pi, ESP) — vertical two-column layout
        header_x = layout_dict["left_col_x"] / scale
        header_y = layout_dict["start_y"] / scale
        header_w = (layout_dict["right_col_x"] - layout_dict["left_col_x"]) / scale
        header_h = ((num_rows - 1) * layout_dict["row_spacing"]) / scale

    board_layout = BoardLayout(
        width_mm=config.width / scale,
        height_mm=config.height / scale,
        header_x_mm=header_x,
        header_y_mm=header_y,
        header_width_mm=header_w,
        header_height_mm=header_h,
    )

    return Board(
        name=config.name,
        pins=pins,
        svg_asset_path=_get_asset_path(config.svg_asset),
        width=config.width,
        height=config.height,
        header_offset=Point(config.header_offset.x, config.header_offset.y),
        layout=board_layout,
    )

raspberry_pi

raspberry_pi() -> Board

Create a Raspberry Pi board (alias for raspberry_pi_5()).

Convenience function that returns the latest Raspberry Pi board. Currently points to Raspberry Pi 5.

RETURNS DESCRIPTION
Board

Configured Raspberry Pi board

TYPE: Board

Examples:

>>> from pinviz import boards
>>> board = boards.raspberry_pi()
>>> print(board.name)
Raspberry Pi
Source code in src/pinviz/boards.py
def raspberry_pi() -> Board:
    """
    Create a Raspberry Pi board (alias for raspberry_pi_5()).

    Convenience function that returns the latest Raspberry Pi board.
    Currently points to Raspberry Pi 5.

    Returns:
        Board: Configured Raspberry Pi board

    Examples:
        >>> from pinviz import boards
        >>> board = boards.raspberry_pi()
        >>> print(board.name)
        Raspberry Pi
    """
    return raspberry_pi_5()

raspberry_pi_4

raspberry_pi_4() -> Board

Create a Raspberry Pi 4 Model B board with 40-pin GPIO header.

Uses standard 40-pin GPIO pinout (same as Pi 2, 3, 5, Zero 2 W). All GPIO pins operate at 3.3V logic levels and are NOT 5V tolerant.

This function loads the board definition from a JSON configuration file (raspberry_pi_4.json) which specifies pin layout, positions, and metadata. Pin positions are calculated automatically to align with the board's SVG asset.

Pin layout (physical pin numbers): Standard 2x20 header layout: - Left column (odd pins): 1, 3, 5, ..., 39 (top to bottom) - Right column (even pins): 2, 4, 6, ..., 40 (top to bottom)

RETURNS DESCRIPTION
Board

Configured Raspberry Pi 4 Model B board with all pins positioned

TYPE: Board

Examples:

>>> board = raspberry_pi_4()
>>> print(board.name)
Raspberry Pi 4 Model B
>>> print(len(board.pins))
40
Note

WARNING: All Raspberry Pi GPIO pins operate at 3.3V logic levels. GPIO pins are NOT 5V tolerant. Applying 5V to any GPIO pin may permanently damage the board. Use level shifters for 5V devices.

Source code in src/pinviz/boards.py
def raspberry_pi_4() -> Board:
    """
    Create a Raspberry Pi 4 Model B board with 40-pin GPIO header.

    Uses standard 40-pin GPIO pinout (same as Pi 2, 3, 5, Zero 2 W).
    All GPIO pins operate at 3.3V logic levels and are NOT 5V tolerant.

    This function loads the board definition from a JSON configuration file
    (raspberry_pi_4.json) which specifies pin layout, positions, and metadata.
    Pin positions are calculated automatically to align with the board's SVG asset.

    Pin layout (physical pin numbers):
    Standard 2x20 header layout:
    - Left column (odd pins): 1, 3, 5, ..., 39 (top to bottom)
    - Right column (even pins): 2, 4, 6, ..., 40 (top to bottom)

    Returns:
        Board: Configured Raspberry Pi 4 Model B board with all pins positioned

    Examples:
        >>> board = raspberry_pi_4()
        >>> print(board.name)
        Raspberry Pi 4 Model B
        >>> print(len(board.pins))
        40

    Note:
        WARNING: All Raspberry Pi GPIO pins operate at 3.3V logic levels.
        GPIO pins are NOT 5V tolerant. Applying 5V to any GPIO pin may
        permanently damage the board. Use level shifters for 5V devices.
    """
    return load_board_from_config("raspberry_pi_4")

raspberry_pi_5

raspberry_pi_5() -> Board

Create a Raspberry Pi 5 board with 40-pin GPIO header.

This function loads the board definition from a JSON configuration file (raspberry_pi_5.json) which specifies pin layout, positions, and metadata. Pin positions are calculated automatically to align with the board's SVG asset.

Pin layout (physical pin numbers): Standard 2x20 header layout: - Left column (odd pins): 1, 3, 5, ..., 39 (top to bottom) - Right column (even pins): 2, 4, 6, ..., 40 (top to bottom)

RETURNS DESCRIPTION
Board

Configured Raspberry Pi 5 board with all pins positioned

TYPE: Board

Examples:

>>> board = raspberry_pi_5()
>>> print(board.name)
Raspberry Pi 5
>>> print(len(board.pins))
40
>>> # Get a specific pin by physical number
>>> pin_1 = board.get_pin(1)
>>> print(pin_1.name, pin_1.role)
3V3 PinRole.POWER_3V3
Source code in src/pinviz/boards.py
def raspberry_pi_5() -> Board:
    """
    Create a Raspberry Pi 5 board with 40-pin GPIO header.

    This function loads the board definition from a JSON configuration file
    (raspberry_pi_5.json) which specifies pin layout, positions, and metadata.
    Pin positions are calculated automatically to align with the board's SVG asset.

    Pin layout (physical pin numbers):
    Standard 2x20 header layout:
    - Left column (odd pins): 1, 3, 5, ..., 39 (top to bottom)
    - Right column (even pins): 2, 4, 6, ..., 40 (top to bottom)

    Returns:
        Board: Configured Raspberry Pi 5 board with all pins positioned

    Examples:
        >>> board = raspberry_pi_5()
        >>> print(board.name)
        Raspberry Pi 5
        >>> print(len(board.pins))
        40
        >>> # Get a specific pin by physical number
        >>> pin_1 = board.get_pin(1)
        >>> print(pin_1.name, pin_1.role)
        3V3 PinRole.POWER_3V3
    """
    return load_board_from_config("raspberry_pi_5")

raspberry_pi_pico

raspberry_pi_pico() -> Board

Create a Raspberry Pi Pico board with dual 20-pin GPIO headers.

The Pico has a unique dual-sided layout with 40 pins total: - Top header: pins 1-20 (single row along top edge) - Bottom header: pins 21-40 (single row along bottom edge)

Unlike the standard Raspberry Pi, the Pico uses RP2040 microcontroller with GP0-GP28 GPIO pins instead of BCM numbering.

This function loads the board definition from a JSON configuration file (raspberry_pi_pico.json) which specifies the dual-header layout.

Pin layout (physical pin numbers): - Top header: pins 1-20 (pin 20 on left, pin 1 on right - reversed order) - Bottom header: pins 21-40 (pin 21 on left, pin 40 on right - normal order)

RETURNS DESCRIPTION
Board

Configured Raspberry Pi Pico board with all pins positioned

TYPE: Board

Examples:

>>> board = raspberry_pi_pico()
>>> print(board.name)
Raspberry Pi Pico
>>> print(len(board.pins))
40
Note

The Pico operates at 3.3V logic levels. Some pins support 5V input with appropriate voltage dividers. Check the Pico datasheet for specific pin capabilities.

Source code in src/pinviz/boards.py
def raspberry_pi_pico() -> Board:
    """
    Create a Raspberry Pi Pico board with dual 20-pin GPIO headers.

    The Pico has a unique dual-sided layout with 40 pins total:
    - Top header: pins 1-20 (single row along top edge)
    - Bottom header: pins 21-40 (single row along bottom edge)

    Unlike the standard Raspberry Pi, the Pico uses RP2040 microcontroller
    with GP0-GP28 GPIO pins instead of BCM numbering.

    This function loads the board definition from a JSON configuration file
    (raspberry_pi_pico.json) which specifies the dual-header layout.

    Pin layout (physical pin numbers):
    - Top header: pins 1-20 (pin 20 on left, pin 1 on right - reversed order)
    - Bottom header: pins 21-40 (pin 21 on left, pin 40 on right - normal order)

    Returns:
        Board: Configured Raspberry Pi Pico board with all pins positioned

    Examples:
        >>> board = raspberry_pi_pico()
        >>> print(board.name)
        Raspberry Pi Pico
        >>> print(len(board.pins))
        40

    Note:
        The Pico operates at 3.3V logic levels. Some pins support 5V input
        with appropriate voltage dividers. Check the Pico datasheet for
        specific pin capabilities.
    """
    return load_board_from_config("raspberry_pi_pico")

wemos_d1_mini

wemos_d1_mini() -> Board

Create a Wemos D1 Mini board with 16-pin GPIO header.

The D1 Mini is a compact ESP8266-based board popular for small IoT projects. It has 8 pins on each side (16 total) with D0-D8 pin naming convention.

RETURNS DESCRIPTION
Board

Configured Wemos D1 Mini board with all pins positioned

TYPE: Board

Examples:

>>> board = wemos_d1_mini()
>>> print(board.name)
Wemos D1 Mini
>>> print(len(board.pins))
16
Source code in src/pinviz/boards.py
def wemos_d1_mini() -> Board:
    """
    Create a Wemos D1 Mini board with 16-pin GPIO header.

    The D1 Mini is a compact ESP8266-based board popular for small IoT projects.
    It has 8 pins on each side (16 total) with D0-D8 pin naming convention.

    Returns:
        Board: Configured Wemos D1 Mini board with all pins positioned

    Examples:
        >>> board = wemos_d1_mini()
        >>> print(board.name)
        Wemos D1 Mini
        >>> print(len(board.pins))
        16
    """
    return load_board_from_config("wemos_d1_mini")