diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..2f9efa06 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,87 @@ +# 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 diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..bd5ce566 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,4 @@ + +Please follow the rules defined in: + +@AGENTS.md