- 
                Notifications
    You must be signed in to change notification settings 
- Fork 0
Architecture
This document provides a comprehensive overview of BenchMesh's architecture, design decisions, and component interactions.
BenchMesh follows a layered architecture with clear separation of concerns:
┌─────────────────────────────────────────────────────────┐
│                   Browser (User Interface)              │
│                React + TypeScript + Vite                │
└───────────────┬────────────────────┬────────────────────┘
                │                    │
                │ HTTP/REST          │ WebSocket
                │                    │ (Real-time updates)
                ▼                    ▼
┌───────────────────────────────────────────────────────┐
│              FastAPI Backend Service                  │
│          (benchmesh-serial-service)                   │
│                                                       │
│  ┌─────────────────────────────────────────────────┐ │
│  │           SerialManager                         │ │
│  │  (Connection orchestration & device registry)  │ │
│  └───────┬──────────────────────────┬──────────────┘ │
│          │                          │                 │
│          │ Device Workers           │ Device Threads │
│          │ (per-device polling)     │ (concurrency)  │
│          ▼                          ▼                 │
│  ┌──────────────────────────────────────────────┐   │
│  │         Modular Driver Layer                 │   │
│  │  (tenma_72, owon_spm, owon_xdm, etc.)       │   │
│  └──────────────┬───────────────────────────────┘   │
│                 │                                     │
│                 │ Serial Commands                     │
│                 ▼                                     │
│  ┌──────────────────────────────────────────────┐   │
│  │         SerialTransport                      │   │
│  │  (pyserial abstraction with EOL handling)   │   │
│  └──────────────┬───────────────────────────────┘   │
└─────────────────┼───────────────────────────────────┘
                  │
                  ▼
         ┌────────────────────┐
         │  Physical Devices  │
         │  (/dev/ttyUSB*, COM*) │
         └────────────────────┘
Location: benchmesh-serial-service/src/benchmesh_service/
The FastAPI application serves as the central hub, providing:
- RESTful API endpoints for device control
- WebSocket connections for real-time status updates
- Static file serving for the frontend
- API documentation via Swagger/OpenAPI
Key files:
- 
api.py- FastAPI application and route definitions
- Main routes include /status,/instruments,/instruments/{class}/{id}/...
Location: serial_manager.py
The SerialManager is the heart of BenchMesh's device management:
Responsibilities:
- Loads device configurations from config.yaml
- Resolves driver manifests and instantiates appropriate drivers
- Spawns and manages per-device worker threads
- Maintains the device registry (IDN and status data)
- Implements reconnection logic with exponential backoff
- Provides thread-safe access to devices via per-device locks
Key data structures:
connections: dict[str, Driver]        # Active driver instances
registry: dict[str, dict]              # {IDN, status, metadata}
dev_locks: dict[str, RLock]           # Per-device thread locks
dev_threads: dict[str, Thread]        # Worker thread handlesLocation: drivers/
Each driver is a self-contained package that implements device-specific protocols:
Required interface:
- 
query_identify()- Returns device identification string (IDN)
- 
poll_status()- Returns current device status as JSON
- Device-specific methods following naming convention:
- 
query_*methods for reading values
- 
set_*methods for writing/controlling
 
- 
Driver structure:
drivers/
├── tenma_72/
│   ├── __init__.py
│   ├── driver.py          # Driver implementation
│   └── manifest.json      # Models, classes, polling config
├── owon_spm/
│   ├── __init__.py
│   ├── driver.py
│   └── manifest.json
└── classes.json           # Device class definitions
Manifest system:
Each driver includes a manifest.json that defines:
- Supported models and their device classes (PSU, DMM, AWG, etc.)
- Per-class polling configuration (methods and intervals)
- Serial communication settings (EOL characters)
- Connection parameters
Location: transport.py
The SerialTransport class provides a clean abstraction over pyserial:
Features:
- Automatic EOL character handling (send_eol, recv_eol)
- Connection management (open, close, reconnect)
- Read/write operations with timeout handling
- Thread-safe serial communication
Usage:
transport = SerialTransport("/dev/ttyUSB0", 9600)
transport.open()
transport.write_line("*IDN?")
response = transport.read_until_reol()Location: benchmesh-serial-service/frontend/
Technology stack:
- React 18 with TypeScript
- Vite for build tooling
- React Query for state management
- WebSocket for real-time updates
Key features:
- Dashboard view with device status cards
- Configuration panel for device management
- Device-specific control interfaces (PSU, DMM, etc.)
- Real-time status updates via WebSocket
Location: node-red-contrib-benchmesh/
Custom Node-RED nodes for automation:
- Device control nodes
- Status monitoring nodes
- Event-driven automation workflows


Decision: Each device runs in its own dedicated thread.
Rationale:
- Isolates device failures (one device crash doesn't affect others)
- Simplifies polling logic (no complex async coordination)
- Natural fit for blocking serial I/O operations
- Easy to implement device-specific polling intervals
Thread Safety:
- Each device has a dedicated RLockfor synchronized access
- Registry updates are protected by locks
- No shared mutable state between devices
Decision: Drivers are configured via manifest.json files.
Rationale:
- Declarative configuration separates data from code
- Easy to add new device models without code changes
- Supports driver aliasing and model variations
- Centralized location for connection parameters
Decision: API endpoints automatically resolve partial method names to query_* or set_* methods.
Rationale:
- Prevents arbitrary method execution (security)
- Clean, intuitive API URLs
- HTTP verb semantics (GET for queries, POST for setters)
- Protection against accidental method calls
Example:
- 
GET /instruments/PSU/psu-1/1/voltage→driver.query_voltage(1)
- 
POST /instruments/PSU/psu-1/1/current/2.5→driver.set_current(1, 2.5)
Decision: Devices automatically attempt reconnection on failure.
Rationale:
- Improves reliability for flaky connections
- Reduces manual intervention
- Graceful handling of device power cycles
- User-friendly experience (devices "just work")
Implementation:
- ~2 second backoff between reconnection attempts
- Tracks last connection attempt timestamp
- Preserves device configuration across reconnections
Decision: Central registry stores IDN and status for all devices.
Rationale:
- Single source of truth for device state
- Efficient API queries (no device polling on every request)
- WebSocket updates driven by registry changes
- Historical context for debugging
Registry structure:
{
  "device-id": {
    "IDN": "TENMA 72-2540 V2.1",
    "status": {
      "voltage": 12.0,
      "current": 0.5,
      "output": "ON"
    },
    "class": "PSU",
    "connected": true
  }
}Main Thread
├── FastAPI Server (handles HTTP/WebSocket)
├── DeviceWorker Thread 1 (device-1)
├── DeviceWorker Thread 2 (device-2)
├── DeviceWorker Thread 3 (device-3)
└── ...
- 
Per-device locks: Each device has an RLockpreventing concurrent access
- Registry access: Thread-safe dict operations
- Connection management: Protected by device-specific locks
Worker threads implement try-except blocks:
- Poll device status
- On error: close connection, mark device as disconnected
- Retry connection after backoff period
- Log errors for debugging
version: 1
devices:
  - id: psu-1                    # Unique identifier
    name: "TENMA PSU"            # Display name
    driver: tenma_72             # Driver package name
    port: /dev/ttyUSB0           # Serial port
    baud: 9600                   # Baud rate
    serial: 8N1                  # Data/parity/stop bits
    model: 72-2540               # Optional model override{
  "models": {
    "72-2540": {
      "class": "PSU",
      "description": "TENMA 72-2540 Power Supply"
    }
  },
  "classes": {
    "PSU": {
      "polling": {
        "methods": ["poll_status"],
        "interval": 2.0
      }
    }
  },
  "connection": {
    "send_eol": "\r\n",
    "recv_eol": "\r\n"
  }
}- 
GET /status- Overall system status (connected/total counts)
- 
GET /instruments- List all configured devices with metadata
- 
GET /instruments/{class}/{id}/{channel}/{parameter}- Query device value
- 
POST /instruments/{class}/{id}/{channel}/{parameter}/{value}- Set device value
- 
WS /ws- WebSocket for real-time updates
The API implements smart method resolution:
- Extract partial method name from URL path
- 
Apply prefix based on HTTP verb (GET → query_, POST →set_)
- Validate method exists on driver
- 
Reject private methods (starting with _)
- Execute with parameters from URL path
This provides security while maintaining clean URLs.
- Default: 2 seconds per device
- Configurable per device class in manifest
- Balance between responsiveness and CPU usage
- Broadcast only on status changes
- Throttle update frequency to prevent flooding
- Efficient JSON serialization
- Minimal state stored in registry
- Driver instances are lightweight
- No historical data retention (use external logging)
- Create driver package in drivers/
- Implement required methods (query_identify,poll_status)
- Create manifest.jsonwith model/class definitions
- Add tests in tests/
- Update documentation
See Driver Development Guide for details.
The API is fully accessible via HTTP/REST:
- Build custom UIs in any framework
- Integrate with external systems
- Automation via shell scripts or other tools
- Node-RED for visual programming
- Direct API access for scripting
- WebSocket for event-driven automation
- Database integration - Persistent storage of device history
- Plugin system - Dynamic driver loading without restart
- Multi-user support - Authentication and authorization
- Device groups - Coordinated control of related devices
- MQTT integration - Pub/sub messaging for IoT ecosystems
These enhancements would require careful design to maintain simplicity and reliability.
- Configuration Guide - Device and system configuration
- Driver Development - Creating new drivers
- API Reference - Complete API documentation
- Testing Guide - Running and writing tests