Skip to the content
PCBKitNew board
4 layers74 × 90 mmBay node, the printed circuit board in 3D

3D-printed, tilted test shelf for 16 dev boards: sliding drawers, per-bay ESP32-S3 running Zephyr (USB/IP, power switching, metering), KiCad PCBs, OpenSCAD parts, BOM and an interactive 3D viewer

Size
74 × 90 mm
Copper layers
4
Thickness
1.6 mm
Parts
135
Nets
93
Pads
435
Tracks, vias
1,082, 446
Pours
22
Routed
Yes
KiCad
KiCad 10.0 (file 20260206)
Created
16 Oct 2026
Updated
4 Nov 2026

Overview

Bay node is an open-source printed circuit board by kartben, from the kartben/hw-testing-rack repository on GitHub. It is a 4-layer board of 74 × 90 mm with 135 parts on 93 nets, routed with 1,082 tracks and 446 vias.

It is built around the ESP32-S3-WROOM-1-N8R8, TJA1462AT/0Z and INA226AIDGSR, with the USBLC6-2SC6, AP2114H-3.3TRG1 and FSUSB42MUX among its other parts. Its parts include 37 resistors, 27 capacitors, 13 ICs, 7 diodes and 6 connectors. It belongs with the ESP32 boards of this library.

Main components

12

README

README.md

hw-testing-rack

An all-printed, tilted test rack for development boards (12 in its default 4 × 3 layout) — every board in its own sliding drawer, power-switched, current-metered and exposed over the network with USB/IP by a Zephyr-powered ESP32-S3 in each bay.

The rack on a desk, a laptop wired to one bay's front port

▶ Interactive 3D model · Logic analyzer · BOM · Architecture · Mechanics · Cabling · Power · Electronics · Assembly · Firmware · Standalone bays · CI

Highlights

  • Printed panel frame, no aluminium — side walls ("ladders") shared by neighbouring bays, stacked row on row with snap hooks; trays slide into their grooves and click in; a taut X brace behind every bay keeps it square. Rows tilt 20° so every board, LED and label faces you, and the grid grows in both directions.
  • Drawers that stay connected — each bay is a drawer sliding 140 mm along the slope; a drag chain hidden in the floor carries the cable, so you can pull a board out to probe it while it keeps running.
  • One plate for every board — a honeycomb plate in each drawer; clip-in pegs (stops, cams, pins) hold any board on it, placed by a tool from a small board library (UNO, Nucleo-64/144, Pico, nRF DKs, FRDM, …): up to four small boards on one plate.
  • One ESP32-S3 per bay — switches the board's 5 V (current-limited, default on), measures V/I (INA226), drives UART/RESET/BOOT lines, and exports the board's USB with Zephyr's USB/IP host service. Plug a laptop into the bay's front port and the USB data path switches to it — the "classic" way still works, even with no firmware.
  • One USB-C charger — a printed power box under the first column holds the hub: a USB PD 3.1 sink (20 or 28 V from one charger, no mains in the rack) and an eFuse per row; a 60 V-rated buck in every bay. The same row cables carry a CAN bus for the rack controller (can.md).
  • Cameras — an optional Arducam Mega (SPI, ~120° M12 lens) and white LEDs behind a diffuser hang from a bay's ceiling, driven by the bay's own node.
  • Everything is generated from config/rack.yaml: OpenSCAD parts, KiCad schematics and PCBs (schematic-as-code, Freerouting), the BOM with LCSC part numbers, and the interactive viewer.
drawerboards
Bay B3 pulled out, still connected: a DATA ×4 at the back serves two XIAOs and two Pico 2sThe DATA ×4 up close: a USB-C lead per board, Dupont jumpers wiring a UART between two of them (real models)
row Bbay node
Row B: the GIGA's LVGL screen in B2, between two DATA ×4 baysBay node — ESP32-S3 (8 MB PSRAM), 2 USB muxes, TPS26600 eFuse, INA226, CAN, 60 V buck

Neutral technical renders: the rack, one bay exploded, side view.

Repository layout

text
config/rack.yaml          single source of truth (rows, bays, tilt, dimensions, population)
mechanical/               OpenSCAD: lib/, parts/, boards/boards.yaml, generated/ (from the config)
electronics/
  tools/                  schematic-as-code (eda.py), PCB builder (pcbgen.py), catalogue, LCSC resolver
  bay-node/               design.py + generated KiCad project, fab/, docs/
  power-hub/              design.py + generated KiCad project, fab/, docs/
  data-port-x6/           the drawer's DATA ×6, six USB-C, power only (the default): design.py + KiCad, fab/, docs/
  data-port-x4/           the DATA ×4 v2, four USB-C and the logic, for bays with wires: design.py + KiCad, fab/, docs/
  data-port/              the single-port board, retired from the rack, kept for the standalone Solo box
  camera-pod/             the camera pod's board (lead, camera header, 3.3 V, LEDs): design.py + KiCad, fab/, docs/
  lib/                    custom symbol(s), cached JLC basic passives
site/                     GitHub Pages: three.js viewer + BOM page (assets generated)
bom/                      full rack BOM (CSV + summary)
render/                   Blender scene + photoreal renders
tools/                    build.py (config/STL/web), kicad.sh, bom.py, jlc_mcp.py, notice/ (FreeCAD line art)
docs/                     design documentation

Building

Prerequisites: Python ≥ 3.10 with pyyaml numpy trimesh (pip install -r tools/requirements.txt), Docker (for KiCad 10, Freerouting and — if not installed locally — OpenSCAD).

bash
python tools/build.py all                    # config → STLs → web viewer assets
tools/kicad.sh bay-node --route --fab        # schematic, PCB, autoroute, Gerbers/BOM/CPL, renders
tools/kicad.sh power-hub --route --fab
tools/kicad.sh data-port-x6 --fab            # fab outputs of the routed board, no re-route
tools/kicad.sh data-port-x4 --fab
tools/kicad.sh camera-pod --route --fab
python tools/bom.py                          # full rack BOM
python -m http.server -d site 8000           # open http://localhost:8000

Generated outputs (STLs, Gerbers, renders, PDFs, GLBs, the BOM) are committed with the change that alters them, never for a date: python tools/keep_unchanged.py puts back every regenerated file whose content didn't change (tools/kicad.sh … --fab runs it on its board).

For screenshots the viewer takes URL parameters: ?bay=B4&openbay=1&view=dut&hideui pulls bay B4's drawer out and frames its DUT cable (DATA port to board), with no UI. ?bay=B2&view=camera&camlight=1&preview=B2 looks into a camera bay with its LEDs on and opens what its camera sees (an Arducam Mega with a ~120° lens: fisheye, noise, auto-exposure and JPEG, at 5 fps).

The viewer lights the rack like the photos: a real room (Poly Haven's "Small Empty Room 1", CC0, cut to 512 × 256 in site/assets/env/), AgX with Blender's "Medium High Contrast" look, PETG with its layer lines. Its High quality box (on by default on a desktop GPU, off on phones and small GPUs, and dropped by itself under 25 fps) adds ambient occlusion (GTAO), a glow on the status LEDs and the camera lights, 4× MSAA through the post-processing and 4096² sun shadows; ?quality=high or ?quality=low forces one for screenshots.

Photoreal renders (Blender + Cycles) start from render/assembly.glb, baked from the same scene as the viewer by python tools/assembly_glb.py --hq (--hq: full-resolution part meshes instead of the viewer's lighter ones). Either render headless in Docker (tools/render.sh hero) or drive a Blender you already have open — GPU and all — through the Blender MCP add-on's socket:

bash
python tools/blender_live.py exec render/live_scene.py                    # (re)build the scene
python tools/blender_live.py exec render/live_render.py hero              # → docs/img/hero.jpg
python tools/blender_live.py exec render/live_render.py drawer:B3 docs/img/drawer.jpg

python tools/assembly_glb.py --hq --explode-bay B2 --out render/bay-exploded.glb   # one bay, layers apart
python tools/blender_live.py exec render/live_scene.py render/bay-exploded.glb
python tools/blender_live.py exec render/live_render.py exploded

The photo shots (docs/img/photo-*.jpg) use their own scene: a small office lit through a window, PETG with 0.2 mm layer lines and rounded edges, scanned oak and plaster (CC0 textures from Poly Haven), a laptop showing a Zephyr shell, wired to bay B4 by a USB-C cable that hangs as a real one would (a catenary), depth of field, bloom and a vignette, rendered at 2× and averaged down:

bash
python tools/fetch_render_assets.py                     # CC0 textures + vendor board models → render/assets/
python tools/board_models.py                            # normalise the board models (docs/boards-3d.md)
python render/screen_terminal.py                        # the laptop's screen → render/assets/
node tools/lvgl_frame.mjs music 480 800                 # B2's GIGA display: one frame of the viewer's LVGL demo
                                                        # (the drawer fronts' status screens: assembly_glb.py draws them)
python tools/assembly_glb.py --hq --with-pc-cable --open-bays B3 --out render/assembly-photo.glb
python tools/blender_live.py exec render/live_photo.py build
python tools/blender_live.py exec render/live_photo.py shot desk          # desk, drawer, detail → docs/img/photo-*.jpg

Change the rack in config/rack.yaml (tilt, rows, columns, panel thickness, which bays get cameras, which board sits where) and rebuild.

Status

v0.2 — the printed panel frame replaces the aluminium frame, not yet built. Mechanics are printable and parametric, the power box included; still to design: double-width bays (see mechanical.md). Each drawer front carries the node's 1.9" touch display, waiting on the module to check its fit before a test print. Five PCBs (bay node, power hub, DATA port, DATA ×4 v2, camera pod) pass ERC and DRC, are routed and smoothed, and went through a schematic review (schematic-review-2026-10-25.md) and a datasheet audit; none is fabricated yet (see electronics.md). The node firmware: firmware.md. Board outlines marked verified: false in boards.yaml are approximate.

License

Hardware (mechanical and electronics design files): CERN-OHL-P-2.0. Software (tools, viewer, firmware): Apache-2.0. See LICENSE.

Parts

135
QtyValueFootprintReferences
14100nCapacitor_SMD:C_0603_1608MetricC4, C5, C14, C16, C17, C18, C19, C22, C23, C24, C25, C26, C27, C28
1010kResistor_SMD:R_0603_1608MetricR3, R6, R7, R11, R17, R18, R20, R28, R30, R37
8100kResistor_SMD:R_0603_1608MetricR1, R2, R4, R13, R26, R27, R36, R39
6GNDTestPoint:TestPoint_Pad_D1.5mmTP3, TP5, TP17, TP22, TP26, TP32
522uCapacitor_SMD:C_1206_3216MetricC7, C8, C9, C12, C13
533Resistor_SMD:R_0603_1608MetricR8, R31, R32, R33, R34
4M3MountingHole:MountingHole_3.2mm_M3H1, H2, H3, H4
4USBLC6-2SC6Package_TO_SOT_SMD:SOT-23-6U4, U8, U12, U13
31kResistor_SMD:R_0603_1608MetricR23, R24, R25
2+3V3TestPoint:TestPoint_Pad_D1.5mmTP4, TP8
210uCapacitor_SMD:C_1206_3216MetricC2, C3
2150kResistor_SMD:R_0603_1608MetricR29, R42
218kResistor_SMD:R_0603_1608MetricR5, R12
21uCapacitor_SMD:C_0805_2012MetricC20, C21
22.2kResistor_SMD:R_0603_1608MetricR21, R22
22N7002Package_TO_SOT_SMD:SOT-23Q2, Q3
25.1kResistor_SMD:R_0603_1608MetricR9, R10
2BAT54SPackage_TO_SOT_SMD:SOT-23D6, D7
1+5VTestPoint:TestPoint_Pad_D1.5mmTP7
10.75AFuse:Fuse_1206_3216MetricF1
1100u/50VCapacitor_SMD:CP_Elec_8x10C1
110uCapacitor_SMD:C_0805_2012MetricC11
11uCapacitor_SMD:C_0603_1608MetricC15
122nCapacitor_SMD:C_0603_1608MetricC29
14.7uInductor_SMD:L_Changjiang_FXL0630L1
150mbench:R_1206_3216Metric_KelvinR19
16.8kResistor_SMD:R_0603_1608MetricR16
1680Resistor_SMD:R_0603_1608MetricR35
1AO3400APackage_TO_SOT_SMD:SOT-23Q4
1AP2114H-3.3TRG1Package_TO_SOT_SMD:SOT-223-3_TabPin2U2
1AUXConnector_JST:JST_PH_S6B-PH-SM4-TB_1x06-1MP_P2.00mm_HorizontalJ5
1AUX_BOOT_CTLTestPoint:TestPoint_Pad_D1.0mmTP30
1AUX_RST_CTLTestPoint:TestPoint_Pad_D1.0mmTP29
1AUX_RXTestPoint:TestPoint_Pad_D1.0mmTP28
1AUX_TXTestPoint:TestPoint_Pad_D1.0mmTP27
1BOOTButton_Switch_SMD:SW_Push_1P1T_XKB_TS-1187ASW2
1BOOTTestPoint:TestPoint_Pad_D1.0mmTP12
1BZT52C12Diode_SMD:D_SOD-123D1
1CAMConnector_JST:JST_PH_B8B-PH-SM4-TB_1x08-1MP_P2.00mm_VerticalJ6
1CAM_5VTestPoint:TestPoint_Pad_D1.5mmTP10
1CAM_CS_PTestPoint:TestPoint_Pad_D1.0mmTP21
1CAM_MISO_PTestPoint:TestPoint_Pad_D1.0mmTP20
1CAM_MOSI_PTestPoint:TestPoint_Pad_D1.0mmTP19
1CAM_SCK_CTestPoint:TestPoint_Pad_D1.0mmTP18
1CAN_RXDTestPoint:TestPoint_Pad_D1.0mmTP24
1CAN_TXDTestPoint:TestPoint_Pad_D1.0mmTP23
1DATAConnector_JST:JST_PH_S8B-PH-SM4-TB_1x08-1MP_P2.00mm_HorizontalJ7
1DATA_VINTestPoint:TestPoint_Pad_D1.5mmTP9
1DFLS260-7Diode_SMD:D_PowerDI-123D8
1DUT_FLT_NTestPoint:TestPoint_Pad_D1.0mmTP13
1ENTestPoint:TestPoint_Pad_D1.0mmTP11
1ESP32-S3-WROOM-1-N8R8bench:ESP32-S3-WROOM-1_NoEPViasU3
1I2C_SCLTestPoint:TestPoint_Pad_D1.0mmTP16
1I2C_SDATestPoint:TestPoint_Pad_D1.0mmTP15
1INA226AIDGSRPackage_SO:TSSOP-10_3x3mm_P0.5mmU10
1INA_ALERT_NTestPoint:TestPoint_Pad_D1.0mmTP14
1LMR51625YDDCRPackage_TO_SOT_SMD:TSOT-23-6U1
1MUX_DUTPackage_SO:MSOP-10_3x3mm_P0.5mmU6
1MUX_PCPackage_SO:MSOP-10_3x3mm_P0.5mmU5
1PCConnector_USB:USB_C_Receptacle_GCT_USB4105-xx-A_16P_TopMnt_HorizontalJ3
1PESD2CANPackage_TO_SOT_SMD:SOT-23D5
1POS_INTestPoint:TestPoint_Pad_D1.0mmTP25
1POS_OUTTestPoint:TestPoint_Pad_D1.0mmTP31
1RESETButton_Switch_SMD:SW_Push_1P1T_XKB_TS-1187ASW1
1ROW INConnector_JST:JST_PH_S6B-PH-SM4-TB_1x06-1MP_P2.00mm_HorizontalJ1
1ROW OUTConnector_JST:JST_PH_S6B-PH-SM4-TB_1x06-1MP_P2.00mm_HorizontalJ2
1SERVICEButton_Switch_SMD:SW_SPDT_Shouhan_MSK12C02SW3
1SMBJ33ADiode_SMD:D_SMBD2
1SN74LVC1G32DBVRPackage_TO_SOT_SMD:SOT-23-5U7
1SUD50P06-15(UMW)Package_TO_SOT_SMD:TO-252-2Q1
1TJA1462ATPackage_SO:SOIC-8_3.9x4.9mm_P1.27mmU11
1TPS26600PWPRPackage_SO:HTSSOP-16-1EP_4.4x5mm_P0.65mm_EP3.4x5mm_Mask2.66x2.46mmU9
1U0RXTestPoint:TestPoint_Pad_D1.5mmTP2
1U0TXTestPoint:TestPoint_Pad_D1.5mmTP1
1VINTestPoint:TestPoint_Pad_D1.5mmTP6
1WS2812B-2020-V6LED_SMD:LED_WS2812B-2020_PLCC4_2.0x2.0mmD4

Questions

How big is the Bay node PCB?

It measures 74 × 90 mm and has 4 copper layers, 1.6 mm thick.

What parts does the Bay node use?

135 parts, built around the ESP32-S3-WROOM-1-N8R8, TJA1462AT/0Z, INA226AIDGSR and USBLC6-2SC6. The full list of its parts is on this page, with links to each part's symbol and footprint where PCBKit's library has them.

Where can I download the Bay node KiCad files?

From the kartben/hw-testing-rack repository on GitHub, at the commit this page was made from: the board is electronics/bay-node/bay-node.kicad_pcb and its schematic electronics/bay-node/bay-node.kicad_sch. PCBKit opens the board in the browser, with nothing to install.

Can I use the Bay node design in my own project?

Its repository states no licence, so its author keeps all rights: ask kartben before reusing the design.

More from kartben/hw-testing-rack

5
  • 4 layers100 × 70 mmData port x4, the printed circuit board in 3D

    Data port x4

    kartben/hw-testing-rack

    3D-printed, tilted test shelf for 16 dev boards: sliding drawers, per-bay ESP32-S3 running Zephyr (USB/IP, power switching, metering), KiCad PCBs, OpenSCAD parts, BOM and an interactive 3D viewer

    290 parts
  • 4 layers100 × 70 mmData port x6, the printed circuit board in 3D

    Data port x6

    kartben/hw-testing-rack

    3D-printed, tilted test shelf for 16 dev boards: sliding drawers, per-bay ESP32-S3 running Zephyr (USB/IP, power switching, metering), KiCad PCBs, OpenSCAD parts, BOM and an interactive 3D viewer

    187 parts
  • 4 layers76 × 100 mmPower hub, the printed circuit board in 3D

    Power hub

    kartben/hw-testing-rack

    3D-printed, tilted test shelf for 16 dev boards: sliding drawers, per-bay ESP32-S3 running Zephyr (USB/IP, power switching, metering), KiCad PCBs, OpenSCAD parts, BOM and an interactive 3D viewer

    181 parts
  • 2 layers40 × 27 mmCamera pod, the printed circuit board in 3D

    Camera pod

    kartben/hw-testing-rack

    3D-printed, tilted test shelf for 16 dev boards: sliding drawers, per-bay ESP32-S3 running Zephyr (USB/IP, power switching, metering), KiCad PCBs, OpenSCAD parts, BOM and an interactive 3D viewer

    25 parts
  • 2 layers40 × 17 mmData port, the printed circuit board in 3D

    Data port

    kartben/hw-testing-rack

    3D-printed, tilted test shelf for 16 dev boards: sliding drawers, per-bay ESP32-S3 running Zephyr (USB/IP, power switching, metering), KiCad PCBs, OpenSCAD parts, BOM and an interactive 3D viewer

    13 parts

Similar boards

  • 4 layers110 × 80 mmSpin rig controller, the printed circuit board in 3D

    Spin rig controller

    Tinkerbug-Robotics/TinkerRocket

    Model rocket flight computer, ground station, iOS app, and simulation

    170 parts1GPL-3.0
  • 4 layers91 × 91 mmOpen micro sister, the printed circuit board in 3D

    Open micro sister

    TomBonness/open-micro

    Open-source USB/BLE desktop programming controller with RGB status integration

    93 parts
  • 2 layers30.8 × 28 mmJST Adapter, the printed circuit board in 3D

    JST Adapter

    adamjvr/S1G-Mod

    A Sub 1GHZ Wireless Module for various Sub 1GHZ projects and applications

    11 parts7
  • 4 layers82.5 × 90 mmMeshpager, the printed circuit board in 3D

    Meshpager

    FakeApate/pager-kicad

    107 parts
  • 2 layers65 × 58.5 mmCarrier, the printed circuit board in 3D

    Carrier

    open-pasture/opencollar

    OpenCollar: a virtual-fence collar you can buy assembled or build from published parts.

    100 partsApache-2.0
  • 4 layers55.9 × 49.7 mm20200103 8SBMS A0, the printed circuit board in 3D

    20200103 8SBMS A0

    simple-ee/mBMS

    This is the modular BMS for use with 8S LiFePO4

    70 parts8MIT
  • 4 layers102.4 × 83.1 mmPLC4UNI, the printed circuit board in 3D

    PLC4UNI

    0sirys/Juan_Pimentel_Electiva_2026_C2

    209 partsMIT
  • 2 layers102.1 × 82.7 mmPLC Electiva, the printed circuit board in 3D

    PLC Electiva

    JosueHerrera05/Josue_Herrera_Electiva_2026_C2

    Repositorio Electíva Diseño Mecatrónico 2026-C2

    198 partsMIT