# AGENTS.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. Also follow the monorepo-level rules in `../AGENTS.md`. ## Build & Development ```bash # Install in editable mode (requires mamba env with python 3.11) pip install -e . uv pip install -r unilabos/utils/requirements.txt # Run with a device graph unilab --graph --config --backend ros unilab --graph --config --backend simple # no ROS2 needed # Common CLI flags unilab --app_bridges websocket fastapi # communication bridges unilab --test_mode # simulate hardware, no real execution unilab --check_mode # CI validation of registry imports unilab --skip_env_check # skip auto-install of dependencies unilab --visual rviz|web|disable # visualization mode unilab --is_slave # run as slave node # Workflow upload subcommand unilab workflow_upload -f -n --tags tag1 tag2 # Tests pytest tests/ # all tests pytest tests/resources/test_resourcetreeset.py # single test file pytest tests/resources/test_resourcetreeset.py::TestClassName::test_method # single test ``` ## Architecture ### Startup Flow `unilab` CLI → `unilabos/app/main.py:main()` → loads config → builds registry → reads device graph (JSON/GraphML) → starts backend thread (ROS2/simple) → starts FastAPI web server + WebSocket client. ### Core Layers **Registry** (`unilabos/registry/`): Singleton `Registry` class discovers and catalogs all device types, resource types, and communication devices from YAML definitions. Device types live in `registry/devices/*.yaml`, resources in `registry/resources/`, comms in `registry/device_comms/`. The registry resolves class paths to actual Python classes via `utils/import_manager.py`. **Resource Tracking** (`unilabos/resources/resource_tracker.py`): Pydantic-based `ResourceDict` → `ResourceDictInstance` → `ResourceTreeSet` hierarchy. `ResourceTreeSet` is the canonical in-memory representation of all devices and resources, used throughout the system. Graph I/O is in `resources/graphio.py` (reads JSON/GraphML device topology files into `nx.Graph` + `ResourceTreeSet`). **Device Drivers** (`unilabos/devices/`): 30+ hardware drivers organized by device type (liquid_handling, hplc, balance, arm, etc.). Each driver is a Python class that gets wrapped by `ros/device_node_wrapper.py:ros2_device_node()` to become a ROS2 node with publishers, subscribers, and action servers. **ROS2 Layer** (`unilabos/ros/`): `device_node_wrapper.py` dynamically wraps any device class into `ROS2DeviceNode` (defined in `ros/nodes/base_device_node.py`). Preset node types in `ros/nodes/presets/` include `host_node`, `controller_node`, `workstation`, `serial_node`, `camera`. Messages use custom `unilabos_msgs` (pre-built, distributed via releases). **Protocol Compilation** (`unilabos/compile/`): 20+ protocol compilers (add, centrifuge, dissolve, filter, heatchill, stir, pump, etc.) that transform YAML protocol definitions into executable sequences. **Communication** (`unilabos/device_comms/`): Hardware communication adapters — OPC-UA client, Modbus PLC, RPC, and a universal driver. `app/communication.py` provides a factory pattern for WebSocket client connections to the cloud. **Web/API** (`unilabos/app/web/`): FastAPI server with REST API (`api.py`), Jinja2 template pages (`pages.py`), and HTTP client for cloud communication (`client.py`). Runs on port 8002 by default. ### Configuration System - **Config classes** in `unilabos/config/config.py`: `BasicConfig`, `WSConfig`, `HTTPConfig`, `ROSConfig` — all class-level attributes, loaded from Python config files - Config files are `.py` files with matching class names (see `config/example_config.py`) - Environment variables override with prefix `UNILABOS_` (e.g., `UNILABOS_BASICCONFIG_PORT=9000`) - Device topology defined in graph files (JSON with node-link format, or GraphML) ### Key Data Flow 1. Graph file → `graphio.read_node_link_json()` → `(nx.Graph, ResourceTreeSet, resource_links)` 2. `ResourceTreeSet` + `Registry` → `initialize_device.initialize_device_from_dict()` → `ROS2DeviceNode` instances 3. Device nodes communicate via ROS2 topics/actions or direct Python calls (simple backend) 4. Cloud sync via WebSocket (`app/ws_client.py`) and HTTP (`app/web/client.py`) ### Test Data Example device graphs and experiment configs are in `unilabos/test/experiments/` (not `tests/`). Registry test fixtures in `unilabos/test/registry/`. ## Code Conventions - Code comments and log messages in simplified Chinese - Python 3.11+, type hints expected - Pydantic models for data validation (`resource_tracker.py`) - Singleton pattern via `@singleton` decorator (`utils/decorator.py`) - Dynamic class loading via `utils/import_manager.py` — device classes resolved at runtime from registry YAML paths - CLI argument dashes auto-converted to underscores for consistency ## Licensing - Framework code: GPL-3.0 - Device drivers (`unilabos/devices/`): DP Technology Proprietary License — do not redistribute