Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
50ebcad
feat: add ZDT_X42 motor and XKC sensor drivers
ZiWei09 Jan 22, 2026
e110703
feat: add separation_step with sensor-motor linkage
ZiWei09 Jan 26, 2026
c4a3be1
feat: enhance separation_step logic with polling thread management an…
ZiWei09 Jan 27, 2026
6bf57f1
Collaboration With Cursor
ZiWei09 Jan 29, 2026
37ec49f
Refactor Bioyond resource handling: update warehouse mapping retrieva…
ZiWei09 Jan 29, 2026
e30c01d
Dev backward (#228)
Xuwznln Feb 2, 2026
f8a5286
Add BIOYOND deck imports and update JSON configurations with new UUID…
ZiWei09 Feb 3, 2026
15ff0e9
feat: add Bioyond deck imports to resource registration
ZiWei09 Feb 3, 2026
64c748d
Merge branch 'vibe/dev' into feature/organic-extraction
ZiWei09 Feb 3, 2026
e4e4bfb
Merge branch 'dev' into feature/organic-extraction
ZiWei09 Feb 4, 2026
9c6f7c7
Merge branch 'dev' into feature/organic-extraction
ZiWei09 Mar 2, 2026
47a29a0
add:skill&agent
ZiWei09 Mar 6, 2026
b61c818
Merge remote-tracking branch 'origin/dev' into feature/organic-extrac…
ZiWei09 Mar 9, 2026
92bfb06
feat: Implement Laiyu liquid handling station with enhanced device co…
ZiWei09 Mar 9, 2026
fe501c9
feat: Update workstation reference and templates with new PLC integra…
ZiWei09 Mar 11, 2026
9815961
feat: Add new developer guides for old devices and PLC framework inte…
ZiWei09 Mar 11, 2026
97996d3
Merge remote-tracking branch 'origin/dev' into feature/organic-extrac…
ZiWei09 Mar 13, 2026
3c80208
feat: 添加设备验证指南,确保设备实现符合接口契约和编码标准
ZiWei09 Mar 17, 2026
f0ea32f
Merge remote-tracking branch 'origin/dev' into feature/organic-extrac…
ZiWei09 Mar 24, 2026
d68fc5e
Merge remote-tracking branch 'origin/dev' into feature/organic-extrac…
ZiWei09 Mar 27, 2026
c4a2f68
Merge remote-tracking branch 'origin/dev' into feature/organic-extrac…
ZiWei09 Apr 27, 2026
8271698
Remove outdated templates and validation guides for device and experi…
ZiWei09 Apr 27, 2026
57f5c87
添加 BIOYOND_PolymerStation_TipBox 到瓶子和小提示盒分类
ZiWei09 Apr 27, 2026
7027bd5
防止 Deck 子类在 __init__ 中调用 setup() 预分配子资源,避免与 PLR deserialize 产生命名冲突
ZiWei09 Apr 28, 2026
70c6685
优化资源分配逻辑,避免在反序列化时因名称冲突导致的错误处理
ZiWei09 Apr 28, 2026
a34ffca
优化资源搜索逻辑,避免同一资源对象重复注册导致的错误
ZiWei09 Apr 28, 2026
44afc77
修复 WareHouse 反序列化时子资源命名冲突,并更新配液站测试 UUID
ZiWei09 Apr 28, 2026
96c4be1
merge_remote_resources: 以远端为准,移除本地已不在远端的物料
ZiWei09 Apr 28, 2026
e212dc7
node_to_plr_dict: WareHouse 子节点不写入 PLR children 列表
ZiWei09 Apr 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
328 changes: 328 additions & 0 deletions .cursor/rules/device-drivers.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,328 @@
---
description: 设备驱动开发规范
globs: ["unilabos/devices/**/*.py"]
---

# 设备驱动开发规范

## 目录结构

```
unilabos/devices/
├── virtual/ # 虚拟设备(用于测试)
│ ├── virtual_stirrer.py
│ └── virtual_centrifuge.py
├── liquid_handling/ # 液体处理设备
├── balance/ # 天平设备
├── hplc/ # HPLC设备
├── pump_and_valve/ # 泵和阀门
├── temperature/ # 温度控制设备
├── workstation/ # 工作站(组合设备)
└── ...
```

## 设备类完整模板

```python
import asyncio
import logging
import time as time_module
from typing import Dict, Any, Optional

from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode


class MyDevice:
"""
设备类描述

Attributes:
device_id: 设备唯一标识
config: 设备配置字典
data: 设备状态数据
"""

_ros_node: BaseROS2DeviceNode

def __init__(
self,
device_id: str = None,
config: Dict[str, Any] = None,
**kwargs
):
"""
初始化设备

Args:
device_id: 设备ID
config: 配置字典
**kwargs: 其他参数
"""
# 兼容不同调用方式
if device_id is None and 'id' in kwargs:
device_id = kwargs.pop('id')
if config is None and 'config' in kwargs:
config = kwargs.pop('config')

self.device_id = device_id or "unknown_device"
self.config = config or {}
self.data = {}

# 从config读取参数
self.port = self.config.get('port') or kwargs.get('port', 'COM1')
self._max_value = self.config.get('max_value', 1000.0)

# 初始化日志
self.logger = logging.getLogger(f"MyDevice.{self.device_id}")

self.logger.info(f"设备 {self.device_id} 已创建")

def post_init(self, ros_node: BaseROS2DeviceNode):
"""
ROS节点注入 - 在ROS节点创建后调用

Args:
ros_node: ROS2设备节点实例
"""
self._ros_node = ros_node

async def initialize(self) -> bool:
"""
初始化设备 - 连接硬件、设置初始状态

Returns:
bool: 初始化是否成功
"""
self.logger.info(f"初始化设备 {self.device_id}")

try:
# 执行硬件初始化
# await self._connect_hardware()

# 设置初始状态
self.data.update({
"status": "待机",
"is_running": False,
"current_value": 0.0,
})

self.logger.info(f"设备 {self.device_id} 初始化完成")
return True

except Exception as e:
self.logger.error(f"初始化失败: {e}")
self.data["status"] = f"错误: {e}"
return False

async def cleanup(self) -> bool:
"""
清理设备 - 断开连接、释放资源

Returns:
bool: 清理是否成功
"""
self.logger.info(f"清理设备 {self.device_id}")

self.data.update({
"status": "离线",
"is_running": False,
})

return True

# ==================== 设备动作 ====================

async def execute_action(
self,
param1: float,
param2: str = "",
**kwargs
) -> bool:
"""
执行设备动作

Args:
param1: 参数1
param2: 参数2(可选)

Returns:
bool: 动作是否成功
"""
# 类型转换和验证
try:
param1 = float(param1)
except (ValueError, TypeError) as e:
self.logger.error(f"参数类型错误: {e}")
return False

# 参数验证
if param1 > self._max_value:
self.logger.error(f"参数超出范围: {param1} > {self._max_value}")
return False

self.logger.info(f"执行动作: param1={param1}, param2={param2}")

# 更新状态
self.data.update({
"status": "运行中",
"is_running": True,
})

# 执行动作(带进度反馈)
duration = 10.0 # 秒
start_time = time_module.time()

while True:
elapsed = time_module.time() - start_time
remaining = max(0, duration - elapsed)
progress = min(100, (elapsed / duration) * 100)

self.data.update({
"status": f"运行中: {progress:.0f}%",
"remaining_time": remaining,
})

if remaining <= 0:
break

await self._ros_node.sleep(1.0)

# 完成
self.data.update({
"status": "完成",
"is_running": False,
})

self.logger.info("动作执行完成")
return True

# ==================== 状态属性 ====================

@property
def status(self) -> str:
"""设备状态 - 自动发布为ROS Topic"""
return self.data.get("status", "未知")

@property
def is_running(self) -> bool:
"""是否正在运行"""
return self.data.get("is_running", False)

@property
def current_value(self) -> float:
"""当前值"""
return self.data.get("current_value", 0.0)

# ==================== 辅助方法 ====================

def get_device_info(self) -> Dict[str, Any]:
"""获取设备信息"""
return {
"device_id": self.device_id,
"status": self.status,
"is_running": self.is_running,
"current_value": self.current_value,
}

def __str__(self) -> str:
return f"MyDevice({self.device_id}: {self.status})"
```

## 关键规则

### 1. 参数处理

所有动作方法的参数都可能以字符串形式传入,必须进行类型转换:

```python
async def my_action(self, value: float, **kwargs) -> bool:
# 始终进行类型转换
try:
value = float(value)
except (ValueError, TypeError) as e:
self.logger.error(f"参数类型错误: {e}")
return False
```

### 2. vessel 参数处理

vessel 参数可能是字符串ID或字典:

```python
def extract_vessel_id(vessel: Union[str, dict]) -> str:
if isinstance(vessel, dict):
return vessel.get("id", "")
return str(vessel) if vessel else ""
```

### 3. 状态更新

使用 `self.data` 字典存储状态,属性读取状态:

```python
# 更新状态
self.data["status"] = "运行中"
self.data["current_speed"] = 300.0

# 读取状态(通过属性)
@property
def status(self) -> str:
return self.data.get("status", "待机")
```

### 4. 异步等待

使用 ROS 节点的 sleep 方法:

```python
# 正确
await self._ros_node.sleep(1.0)

# 避免(除非在纯 Python 测试环境)
await asyncio.sleep(1.0)
```

### 5. 进度反馈

长时间运行的操作需要提供进度反馈:

```python
while remaining > 0:
progress = (elapsed / total_time) * 100
self.data["status"] = f"运行中: {progress:.0f}%"
self.data["remaining_time"] = remaining

await self._ros_node.sleep(1.0)
```

## 虚拟设备

虚拟设备用于测试和演示,放在 `unilabos/devices/virtual/` 目录:

- 类名以 `Virtual` 开头
- 文件名以 `virtual_` 开头
- 模拟真实设备的行为和时序
- 使用表情符号增强日志可读性(可选)

## 工作站设备

工作站是组合多个设备的复杂设备:

```python
from unilabos.devices.workstation.workstation_base import WorkstationBase

class MyWorkstation(WorkstationBase):
"""组合工作站"""

async def execute_workflow(self, workflow: Dict[str, Any]) -> bool:
"""执行工作流"""
pass
```

## 设备注册

设备类开发完成后,需要在注册表中注册:

1. 创建/编辑 `unilabos/registry/devices/my_category.yaml`
2. 添加设备配置(参考 `virtual_device.yaml`)
3. 运行 `--complete_registry` 自动生成 schema
Loading
Loading