This folder contains the network API layer that exposes Rio hardware control and telemetry over HTTP/REST and WebSockets. It is designed to run alongside the existing Flask UI (../rio-webapp/) and provides a machine-readable interface for Jupyter notebooks, scripts, and external applications.
✅ Web of Things (WoT) Compatible: The API uses LabThings FastAPI to expose controllers as WoT-compliant Things.
- Controllers are wrapped as LabThings Things (FlowThing, HeaterThing, CameraThing, etc.)
- Auto-generated Thing Descriptions (TD) for each Thing
- Standard WoT properties, actions, and events
- OpenAPI/Swagger documentation + Thing Description JSON
- Custom endpoints remain at
/api/prefix for backward compatibility
-
Install dependencies:
cd software pip install -r requirements-api-full.txt -
Run the API server:
# Development (with auto-reload) uvicorn api.main:app --host 0.0.0.0 --port 8000 --reload # Or directly python -m api.main
Optional pump enable (USB serial):
export RIO_PUMP_ENABLED=true # export RIO_PUMP_PORT=/dev/ttyUSB0
-
Test the API:
curl http://localhost:8000/api/system/health # Or test WoT Thing: curl http://localhost:8000/flow/ -
View API documentation:
- Swagger UI: http://localhost:8000/docs
- ReDoc: http://localhost:8000/redoc
- Thing Descriptions: http://localhost:8000/thing_descriptions/
- Individual Thing: http://localhost:8000/flow/ (FlowThing TD)
Prerequisites: Base requirements (requirements-pi.txt) must be installed first.
-
Install API dependencies on Pi:
cd ~/rio-controller python3 -m pip install --user -r requirements-api.txt python3 -m pip install --user --no-deps --force-reinstall labthings-fastapi==0.0.6
Note:
labthings-fastapiis installed with--no-depsto avoid pullingnumpy>=2which breaks the apt OpenCV build on 32-bit Pi. -
Launch API server:
cd ~/rio-controller export RIO_SIMULATION=false python3 -m api.main
Optional pump enable (USB serial):
export RIO_PUMP_ENABLED=true # export RIO_PUMP_PORT=/dev/ttyUSB0
-
Test from Pi:
curl http://localhost:8000/api/system/health
-
Test from your computer:
# Replace with your Pi's IP or hostname curl http://raspberrypi.local:8000/api/system/health -
Access from Jupyter notebook:
from api.client import RioClient client = RioClient(base_url="http://raspberrypi.local:8000") health = client.health() print(health)
For detailed Pi installation and troubleshooting, see: ../../docs/pi_api_installation_guide.md
See client/README.md for the Python client library and example notebooks.
- Belongs here: LabThings Thing classes, ThingServer setup, WebSocket handlers, request/response schemas (Pydantic models), API configuration, and streaming aggregators.
- Does not belong here: Hardware drivers (belongs in
../drivers/), device controllers (belongs in../controllers/), Flask routes (belongs in../rio-webapp/), and browser JavaScript (belongs in../rio-webapp/static/).
The API layer sits above the device controllers (../controllers/) and below client applications (Jupyter notebooks, scripts, external UIs). It follows the same architectural pattern as the Flask UI:
┌─────────────────────────────────────┐
│ Client (Jupyter/script/external) │
└──────────────┬──────────────────────┘
│ HTTP/REST + WebSocket
┌──────────────▼──────────────────────┐
│ software/api/ (this folder) │
│ - LabThings ThingServer (WoT) │
│ - Thing classes (FlowThing, etc.) │
│ - WebSocket aggregator │
│ - Request/response schemas │
│ - Legacy /api/control/* routes │
└──────────────┬──────────────────────┘
│ Controller methods
┌──────────────▼──────────────────────┐
│ software/controllers/ │
│ - FlowWeb, heater_web, Camera, etc.│
└──────────────┬──────────────────────┘
│ Driver protocols
┌──────────────▼──────────────────────┐
│ software/drivers/ │
│ - PiFlow, PiHolder, PiStrobe, etc. │
└─────────────────────────────────────┘
The API does not call drivers directly; it calls into the controller layer, which ensures proper state management, safety guardrails, and consistent behavior with the Flask UI.
- Entry point:
create_app()returns a FastAPI instance with LabThings ThingServer integration. - Note: LabThings
ThingServeris imported fromlabthings_fastapi.server(0.0.6). - Controller initialization: Controllers are instantiated at import time (similar to
software/main.py) so capabilities reflect actual hardware availability. - WoT Things (auto-generated routes by LabThings):
/flow/— FlowThing (flow/pressure control)/heater/— HeaterThing (heater control)/camera/— CameraThing (camera and strobe control)/droplet/— DropletThing (droplet detection)/pump/— PumpThing (placeholder for future pump support)
- Custom endpoints (backward compatibility):
/api/system/health— Health check/api/system/capabilities— Available modules/api/config/channels— Channel metadata (names, liquid types, calibration factors)/api/streams/aggregate— WebSocket aggregator for sensor data/api/data/capture/*— On-demand CSV capture control
- WoT endpoints (auto-generated):
/thing_descriptions/— All Thing Descriptions/docs— OpenAPI/Swagger UI/openapi.json— OpenAPI specification
WoT-compliant Thing classes that wrap controllers:
flow_thing.py: FlowThing — flow/pressure control (properties:state, actions:set_pressure,set_flow,set_mode,set_pi_consts)heater_thing.py: HeaterThing — heater control (properties:state, actions:set_temp,set_pid,set_stir)camera_thing.py: CameraThing — camera/strobe control (actions:snapshot,set_resolution,set_roi,strobe_enable, etc.)droplet_thing.py: DropletThing — droplet detection (properties:status,statistics,histogram, actions:start,stop)pump_thing.py: PumpThing — placeholder for future pump support
Each Thing exposes:
- Properties: Readable state (e.g.,
GET /flow/state) - Actions: Invocable methods (e.g.,
POST /flow/set_pressure) - Thing Description: Machine-readable WoT TD at
/flow/,/heater/, etc.
Pydantic models for API requests and responses. Used by both Things and custom endpoints:
- Type validation: Automatic validation of request bodies and query parameters
- OpenAPI documentation: FastAPI auto-generates OpenAPI/Swagger docs from these models
- Thing Descriptions: LabThings uses these models in WoT TD generation
- Clear contracts: Explicit data structures for clients
Key models:
FlowState,FlowSetPressureRequest,FlowSetFlowRequest, etc.HeaterState,HeaterSetTempRequest, etc.ChannelConfig,ChannelMetadata(for channel naming/liquid types)CaptureStartRequest,CaptureStatusResponse(for data capture)- Camera/strobe/droplet/pump request models
Aggregatorclass: Multiplexes multiple sensor streams (flow, pressure, heater) over a single WebSocket connection.- Channel selection: Clients can subscribe to specific channels per topic (e.g., only flow channels 0 and 2).
- Calibration factors: Applies calibration factors from config YAML to flow/pressure values.
- On-demand capture: Optional CSV logging of streamed data (disabled by default, enabled via
/api/data/capture/start).
WebSocket endpoint: /api/streams/aggregate
Message format:
{
"topic": "flow",
"channel": 0,
"timestamp": 1234567890.123,
"value": 100.5,
"unit": "ul_hr"
}Settings for:
- Host/port (default:
0.0.0.0:8000) - CORS (default: allow all origins)
- Simulation mode detection
- Stream rates and buffer sizes
cd software
export RIO_SIMULATION=true # Optional: run without hardware
python -m api.mainOr use uvicorn directly:
cd software
uvicorn api.main:app --host 0.0.0.0 --port 8000The API and Flask UI can run in parallel, but only one process should own hardware per module. Configuration-driven "single-owner" rules (future) will prevent conflicts.
Current approach: API is the hardware owner; Flask UI uses adapters (future step) or runs on a different host.
Channel metadata (names, liquid types, calibration factors) is loaded from the main config file:
- Default:
rio-config.yamlinsoftware/ - Override:
RIO_CONFIG_FILEenvironment variable
Example config structure:
channels:
flow:
"0":
name: "oil"
liquid_type: "mineral_oil"
calibration_factor: 1.05
"1":
name: "cells"
liquid_type: "aqueous"
calibration_factor: 1.0GET /api/system/health— Health checkGET /api/system/capabilities— Available modules
GET /api/config/channels— Get channel metadataPOST /api/config/channels— Update channel metadata (runtime-only, not persisted)
GET /api/control/flow/state— Get current state (targets, actuals, modes)POST /api/control/flow/set_pressure— Set pressure target (mbar)POST /api/control/flow/set_flow— Set flow target (ul/hr)POST /api/control/flow/set_mode— Set control modePOST /api/control/flow/set_pi_consts— Set PI controller constants
GET /api/control/heater/state— Get heater statesPOST /api/control/heater/set_temp— Set target temperaturePOST /api/control/heater/pid— Enable/disable PIDPOST /api/control/heater/stir— Enable/disable stirrerPOST /api/control/heater/power_limit— Set heater power limit (%)POST /api/control/heater/autotune— Start/stop autotune
GET /api/streams/camera/snapshot— Get JPEG snapshotPOST /api/control/camera/set_resolution— Set display resolutionPOST /api/control/camera/set_snapshot_resolution— Set snapshot resolution modePOST /api/control/camera/roi— Set ROIPOST /api/control/camera/roi/clear— Clear ROIGET /api/control/camera/state— Get camera state (for remote UI)POST /api/control/camera/select— Select camera backendPOST /api/control/strobe/enable— Enable/disable strobePOST /api/control/strobe/hold— Enable/disable hold modePOST /api/control/strobe/timing— Set strobe timing (period, wait)GET /api/control/strobe/state— Get strobe state (for remote UI)
POST /api/control/droplet/start— Start detectionPOST /api/control/droplet/stop— Stop detectionGET /api/control/droplet/status— Get statusGET /api/control/droplet/histogram— Get histogramGET /api/control/droplet/statistics— Get statisticsGET /api/control/droplet/performance— Get performance metrics
GET /api/control/pump/state/{pump}— Get pump statePOST /api/control/pump/set_flow— Set flowPOST /api/control/pump/set_diameter— Set diameterPOST /api/control/pump/set_direction— Set direction (infuse/withdraw)POST /api/control/pump/set_state— Start/stopPOST /api/control/pump/set_unit— Set unitPOST /api/control/pump/set_gearbox— Set gearboxPOST /api/control/pump/set_microstep— Set microstepPOST /api/control/pump/set_threadrod— Set threadrodPOST /api/control/pump/set_enable— Enable/disable
WS /api/streams/aggregate— WebSocket aggregator (flow/pressure/heater telemetry)POST /api/data/capture/start— Start CSV capturePOST /api/data/capture/stop— Stop CSV captureGET /api/data/capture/status— Get capture status
A lightweight client library is available at client/api_client.py:
RioClient— REST API client with error handling and retry logicRioStreamClient— WebSocket aggregator client with thread-safe message queue
Example usage:
from api.client import RioClient, RioStreamClient
# REST client
client = RioClient(base_url="http://192.168.1.100:8000")
state = client.get_flow_state()
client.set_flow(0, 100.0) # Set channel 0 to 100 ul/hr
# WebSocket client
stream = RioStreamClient(base_url="http://192.168.1.100:8000")
stream.subscribe(["flow"], channels={"flow": [0, 1]})
for msg in stream.iter_messages(timeout=10.0):
print(f"{msg['topic']}: {msg['value']}")See client/README.md for complete documentation.
Two example notebooks are available in software/api/client/notebooks/ (repo root path):
-
tutorial.ipynb— Step-by-step learning notebook:- REST API control (flow, heater, camera)
- WebSocket telemetry streaming with batching
- On-demand data capture
- Data visualization
-
interactive_control.ipynb— Interactive UI with ipywidgets:- Real-time control sliders
- Live status updates
- Camera snapshot capture
- Emergency stop button
To test the API using Jupyter notebooks in simulation mode (without hardware):
-
Start the API server in simulation mode (in a terminal):
cd software export RIO_SIMULATION=true python -m api.main
The server will start on
http://localhost:8000. You should see:INFO: Uvicorn running on http://0.0.0.0:8000 -
Install Jupyter (if not already installed in your environment):
pip install jupyter jupyterlab ipywidgets matplotlib pandas numpy
-
Open a Jupyter notebook (in a new terminal, same environment):
cd software # Make sure you're in the same environment (rio-simulation) mamba activate rio-simulation # if not already activated jupyter notebook
Or use JupyterLab:
jupyter lab
-
Navigate to the client notebooks:
- Open
api/client/notebooks/tutorial.ipynborapi/client/notebooks/interactive_control.ipynb - Or create a new notebook
- Open
-
Set up the client in the notebook:
import sys # Add client library to path sys.path.insert(0, '/path/to/rio-controller/software') from api.client import RioClient, RioStreamClient # Connect to local API server API_BASE_URL = "http://localhost:8000" client = RioClient(base_url=API_BASE_URL) # Test connection health = client.health() print(f"API Status: {health['status']}") print(f"Simulation Mode: {health['simulation']}")
-
Run the notebook cells:
- The notebooks will work with the simulation API
- Flow, pressure, and heater controllers will use simulated hardware
- Camera will use simulated frames (synthetic droplets)
- All API endpoints are available and functional
Note: Keep the API server running in the terminal while using the notebook. If you stop the server, restart it and the notebook will reconnect automatically.
Troubleshooting:
- Connection refused: Make sure the API server is running (
python -m api.main) - Module not found: Make sure
software/is in your Python path (see step 4) - Import errors: Install client dependencies:
pip install requests websocket-client - Notebook dependencies: For plotting and widgets:
pip install matplotlib pandas numpy ipywidgets
API tests are in ../tests/test_api_streams.py. Run with:
cd software
export RIO_SIMULATION=true
pytest tests/test_api_streams.py -vNote: API tests require requirements-api-full.txt to be installed. Install with:
pip install -r requirements-api-full.txtFastAPI automatically generates OpenAPI documentation. When the API server is running, visit:
- Swagger UI:
http://localhost:8000/docs - ReDoc:
http://localhost:8000/redoc - OpenAPI JSON:
http://localhost:8000/openapi.json
API-specific dependencies are split:
requirements-api.txt— Pi-light dependencies (no LabThings; avoids numpy upgrades)requirements-api-full.txt— Desktop/full dependencies (includes LabThings + extras)
Current status: The API uses LabThings ThingServer to expose controllers as WoT-compliant Things. Each controller is wrapped in a Thing class that exposes properties and actions according to the Web of Things standard.
Install with:
pip install -r requirements-api-full.txt"Address already in use":
- Another process is using port 8000
- Find and kill:
lsof -ti:8000 | xargs kill -9 - Or use a different port:
uvicorn api.main:app --port 8001
"Module not found":
- Install dependencies:
pip install -r requirements-api-full.txt - Make sure you're running from
software/directory - Check Python path:
python -c "import api.main"
"Controller unavailable" (503 errors):
- Check capabilities:
GET /api/system/capabilities - Verify hardware is connected (or simulation mode is enabled)
- Check controller initialization logs for errors
- In simulation mode:
export RIO_SIMULATION=true
"WebSocket connection failed":
- Verify API server is running
- Check firewall/network settings
- Ensure WebSocket endpoint is accessible:
ws://<host>:8000/api/streams/aggregate - Check browser console or client logs for errors
Control flow from script:
from api.client import RioClient
client = RioClient(base_url="http://192.168.1.100:8000")
client.set_flow(0, 100.0) # Set channel 0 to 100 ul/hrMonitor sensors in real-time:
from api.client import RioStreamClient
stream = RioStreamClient(base_url="http://192.168.1.100:8000")
stream.subscribe(["flow", "pressure"])
for msg in stream.iter_messages(timeout=60.0):
print(f"{msg['topic']} ch{msg['channel']}: {msg['value']} {msg['unit']}")Capture data to CSV:
from api.client import RioClient
client = RioClient(base_url="http://192.168.1.100:8000")
client.capture_start(["flow", "pressure"], path="experiment.csv")
# ... run experiment ...
client.capture_stop()- Streaming rates: WebSocket supports 20-50 Hz per channel. Higher rates may require client-side decimation.
- Buffer sizes: Message queue defaults to 1000 messages. Adjust with
RioStreamClient(max_queue_size=...). - Concurrent connections: Multiple clients can connect simultaneously. Each WebSocket connection is independent.
- CPU usage: Streaming 4 channels at 50 Hz uses minimal CPU. Camera streaming is more intensive.
- Authentication: Token-based or basic auth for LAN security
- Remote adapters: Configuration-driven split-host deployment (Pi + external PC)
- Pump driver: Implement syringe pump driver/controller to enable PumpThing
- UI adapters: Flask UI calls API instead of controllers directly (single-owner rule)
- ThingClient migration: Update client library to use LabThings ThingClient for auto-generated clients
This file was AI-generated and may contain errors. Please verify against the source code and runtime behavior.
- Date: 2025-01-XX
- Maintenance: If you change API routes, schemas, or behavior, update this document.