Compare commits

...

20 Commits

Author SHA1 Message Date
Xuwznln
a1783f489e Merge remote-tracking branch 'origin/workstation_dev_YB2' into dev
# Conflicts:
#	unilabos/devices/workstation/bioyond_studio/bioyond_rpc.py
#	unilabos/devices/workstation/bioyond_studio/station.py
#	unilabos/resources/graphio.py
2025-10-10 15:38:45 +08:00
Xuwznln
a8f6527de9 修复to_plr_resources 2025-10-10 15:30:26 +08:00
ZiWei
54cfaf15f3 Workstation dev yb2 (#100)
* Refactor and extend reaction station action messages

* Refactor dispensing station tasks to enhance parameter clarity and add batch processing capabilities

- Updated `create_90_10_vial_feeding_task` to include detailed parameters for 90%/10% vial feeding, improving clarity and usability.
- Introduced `create_batch_90_10_vial_feeding_task` for batch processing of 90%/10% vial feeding tasks with JSON formatted input.
- Added `create_batch_diamine_solution_task` for batch preparation of diamine solution, also utilizing JSON formatted input.
- Refined `create_diamine_solution_task` to include additional parameters for better task configuration.
- Enhanced schema descriptions and default values for improved user guidance.
2025-10-10 15:25:50 +08:00
Junhan Chang
1c9d2ee98a fix bioyond resource io 2025-09-30 17:02:38 +08:00
Junhan Chang
3fe8f4ca44 add child_size for itemized_carrier 2025-09-30 12:58:42 +08:00
Junhan Chang
2476821dcc update bioyond launch json 2025-09-30 12:25:21 +08:00
Junhan Chang
7b426ed5ae create warehouse by factory func 2025-09-30 11:57:34 +08:00
Junhan Chang
9bbae96447 Merge branch 'workstation_dev_YB2' of https://github.com/dptech-corp/Uni-Lab-OS into workstation_dev_YB2 2025-09-29 21:02:05 +08:00
Junhan Chang
10aabb7592 refactor: add itemized_carrier instead of carrier consists of ResourceHolder 2025-09-29 20:36:45 +08:00
Junhan Chang
a5397ffe12 create/update resources with POST/PUT for big amount/ small amount data 2025-09-26 23:25:34 +08:00
Junhan Chang
196e0f7e2b fix bioyond station and registry 2025-09-26 08:12:41 +08:00
Junhan Chang
a632fd495e bioyond station with communication init and resource sync 2025-09-25 20:56:29 +08:00
Junhan Chang
a8cc02a126 add bioyond studio draft 2025-09-25 20:36:52 +08:00
Xie Qiming
ad2e1432c6 feat: 将新威电池测试系统驱动与配置文件并入 workstation_dev_YB2 (#92)
* feat: 新威电池测试系统驱动与注册文件

* feat: bring neware driver & battery.json into workstation_dev_YB2
2025-09-25 18:53:04 +08:00
Junhan Chang
c3b9583eac fix: update resource with correct structure; remove deprecated liquid_handler set_group action 2025-09-25 15:27:05 +08:00
Junhan Chang
5c47cd0c8a add BIOYOND deck assignment and pass all tests 2025-09-25 08:41:41 +08:00
Junhan Chang
63ab1af45d refactor and add BIOYOND resources tests 2025-09-25 08:14:48 +08:00
Junhan Chang
a8419dc0c3 add standardized BIOYOND resources: bottle_carrier, bottle 2025-09-25 03:49:07 +08:00
Junhan Chang
34f05f2e25 refactor: rename "station_resource" to "deck" 2025-09-24 10:53:11 +08:00
h840473807
0dc2488f02 coin_cell_station draft 2025-09-23 01:18:04 +08:00
21 changed files with 4286 additions and 341 deletions

View File

@@ -127,16 +127,16 @@ add_action_files(
```bash
mamba remove --force ros-humble-unilabos-msgs
mamba config set safety_checks disabled # 如果没有提升版本号会触发md5与网络上md5不一致是正常现象因此通过本指令关闭md5检查
mamba install xxx.conda2 --offline
mamba install xxx.conda --offline
```
## 常见问题
**Q: 构建失败怎么办?**
**Q: 构建失败怎么办?**
A: 检查 Actions 日志中的错误信息,通常是语法错误或依赖问题。修复后重新推送代码即可自动触发新的构建。
**Q: 如何测试特定平台?**
**Q: 如何测试特定平台?**
A: 在手动触发构建时,在平台选择中只填写你需要的平台,如 `linux-64` 或 `win-64`。
**Q: 构建包在哪里下载?**
**Q: 构建包在哪里下载?**
A: 在 Actions 页面的构建结果中,查找 "Artifacts" 部分,每个平台都有对应的构建包可供下载。

View File

@@ -0,0 +1,60 @@
{
"nodes": [
{
"id": "dispensing_station_bioyond",
"name": "dispensing_station_bioyond",
"children": [
"Bioyond_Dispensing_Deck"
],
"parent": null,
"type": "device",
"class": "dispensing_station.bioyond",
"config": {
"config": {
"api_key": "DE9BDDA0",
"api_host": "http://192.168.1.200:44388"
},
"deck": {
"data": {
"_resource_child_name": "Bioyond_Dispensing_Deck",
"_resource_type": "unilabos.resources.bioyond.decks:BIOYOND_PolymerPreparationStation_Deck"
}
},
"station_config": {
"station_type": "dispensing_station",
"enable_dispensing_station": true,
"enable_reaction_station": false,
"station_name": "DispensingStation_001",
"description": "Bioyond配液工作站"
},
"protocol_type": []
},
"data": {}
},
{
"id": "Bioyond_Dispensing_Deck",
"name": "Bioyond_Dispensing_Deck",
"sample_id": null,
"children": [],
"parent": "dispensing_station_bioyond",
"type": "deck",
"class": "BIOYOND_PolymerPreparationStation_Deck",
"position": {
"x": 0,
"y": 0,
"z": 0
},
"config": {
"type": "BIOYOND_PolymerPreparationStation_Deck",
"setup": true,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
}
},
"data": {}
}
]
}

View File

@@ -0,0 +1,69 @@
{
"nodes": [
{
"id": "reaction_station_bioyond",
"name": "reaction_station_bioyond",
"parent": null,
"children": [
"Bioyond_Deck"
],
"type": "device",
"class": "reaction_station.bioyond",
"config": {
"bioyond_config": {
"api_key": "DE9BDDA0",
"api_host": "http://192.168.1.200:44402",
"workflow_mappings": {
"reactor_taken_out": "3a16081e-4788-ca37-eff4-ceed8d7019d1",
"reactor_taken_in": "3a160df6-76b3-0957-9eb0-cb496d5721c6",
"Solid_feeding_vials": "3a160877-87e7-7699-7bc6-ec72b05eb5e6",
"Liquid_feeding_vials(non-titration)": "3a167d99-6158-c6f0-15b5-eb030f7d8e47",
"Liquid_feeding_solvents": "3a160824-0665-01ed-285a-51ef817a9046",
"Liquid_feeding(titration)": "3a160824-0665-01ed-285a-51ef817a9046",
"Liquid_feeding_beaker": "3a16087e-124f-8ddb-8ec1-c2dff09ca784",
"Drip_back": "3a162cf9-6aac-565a-ddd7-682ba1796a4a"
},
"material_type_mappings": {
"烧杯": "BIOYOND_PolymerStation_1FlaskCarrier",
"试剂瓶": "BIOYOND_PolymerStation_1BottleCarrier",
"样品板": "BIOYOND_PolymerStation_6VialCarrier"
}
},
"deck": {
"data": {
"_resource_child_name": "Bioyond_Deck",
"_resource_type": "unilabos.resources.bioyond.decks:BIOYOND_PolymerReactionStation_Deck"
}
},
"protocol_type": []
},
"data": {}
},
{
"id": "Bioyond_Deck",
"name": "Bioyond_Deck",
"sample_id": null,
"children": [
],
"parent": "reaction_station_bioyond",
"type": "deck",
"class": "BIOYOND_PolymerReactionStation_Deck",
"position": {
"x": 0,
"y": 0,
"z": 0
},
"config": {
"type": "BIOYOND_PolymerReactionStation_Deck",
"setup": true,
"rotation": {
"x": 0,
"y": 0,
"z": 0,
"type": "Rotation"
}
},
"data": {}
}
]
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,506 @@
dispensing_station.bioyond:
category:
- work_station
- dispensing_station_bioyond
class:
action_value_mappings:
bioyond_sync:
feedback: {}
goal:
force_sync: force_sync
sync_type: sync_type
goal_default:
force_sync: false
sync_type: full
handles: {}
result: {}
schema:
description: 从Bioyond系统同步物料
properties:
feedback: {}
goal:
properties:
force_sync:
description: 是否强制同步
type: boolean
sync_type:
description: 同步类型
enum:
- full
- incremental
type: string
required:
- sync_type
type: object
result: {}
required:
- goal
title: bioyond_sync参数
type: object
type: UniLabJsonCommand
bioyond_update:
feedback: {}
goal:
material_ids: material_ids
sync_all: sync_all
goal_default:
material_ids: []
sync_all: true
handles: {}
result: {}
schema:
description: 将本地物料变更同步到Bioyond
properties:
feedback: {}
goal:
properties:
material_ids:
description: 要同步的物料ID列表
items:
type: string
type: array
sync_all:
description: 是否同步所有物料
type: boolean
required:
- sync_all
type: object
result: {}
required:
- goal
title: bioyond_update参数
type: object
type: UniLabJsonCommand
create_90_10_vial_feeding_task:
feedback: {}
goal:
delay_time: delay_time
order_name: order_name
percent_10_1_assign_material_name: percent_10_1_assign_material_name
percent_10_1_liquid_material_name: percent_10_1_liquid_material_name
percent_10_1_target_weigh: percent_10_1_target_weigh
percent_10_1_volume: percent_10_1_volume
percent_10_2_assign_material_name: percent_10_2_assign_material_name
percent_10_2_liquid_material_name: percent_10_2_liquid_material_name
percent_10_2_target_weigh: percent_10_2_target_weigh
percent_10_2_volume: percent_10_2_volume
percent_90_1_assign_material_name: percent_90_1_assign_material_name
percent_90_1_target_weigh: percent_90_1_target_weigh
percent_90_2_assign_material_name: percent_90_2_assign_material_name
percent_90_2_target_weigh: percent_90_2_target_weigh
percent_90_3_assign_material_name: percent_90_3_assign_material_name
percent_90_3_target_weigh: percent_90_3_target_weigh
speed: speed
temperature: temperature
goal_default:
delay_time: '600'
order_name: ''
percent_10_1_assign_material_name: ''
percent_10_1_liquid_material_name: ''
percent_10_1_target_weigh: ''
percent_10_1_volume: ''
percent_10_2_assign_material_name: ''
percent_10_2_liquid_material_name: ''
percent_10_2_target_weigh: ''
percent_10_2_volume: ''
percent_90_1_assign_material_name: ''
percent_90_1_target_weigh: ''
percent_90_2_assign_material_name: ''
percent_90_2_target_weigh: ''
percent_90_3_assign_material_name: ''
percent_90_3_target_weigh: ''
speed: '400'
temperature: '20'
handles: {}
result: {}
schema:
description: 创建90%/10%小瓶投料任务
properties:
feedback: {}
goal:
properties:
delay_time:
default: '600'
description: 延迟时间(s)
type: string
order_name:
description: 任务名称
type: string
percent_10_1_assign_material_name:
description: 10%组分1物料名称
type: string
percent_10_1_liquid_material_name:
description: 10%组分1液体物料名称
type: string
percent_10_1_target_weigh:
description: 10%组分1目标重量(g)
type: string
percent_10_1_volume:
description: 10%组分1液体体积(mL)
type: string
percent_10_2_assign_material_name:
description: 10%组分2物料名称
type: string
percent_10_2_liquid_material_name:
description: 10%组分2液体物料名称
type: string
percent_10_2_target_weigh:
description: 10%组分2目标重量(g)
type: string
percent_10_2_volume:
description: 10%组分2液体体积(mL)
type: string
percent_90_1_assign_material_name:
description: 90%组分1物料名称
type: string
percent_90_1_target_weigh:
description: 90%组分1目标重量(g)
type: string
percent_90_2_assign_material_name:
description: 90%组分2物料名称
type: string
percent_90_2_target_weigh:
description: 90%组分2目标重量(g)
type: string
percent_90_3_assign_material_name:
description: 90%组分3物料名称
type: string
percent_90_3_target_weigh:
description: 90%组分3目标重量(g)
type: string
speed:
default: '400'
description: 搅拌速度(rpm)
type: string
temperature:
default: '20'
description: 温度(°C)
type: string
type: object
result: {}
required:
- goal
title: create_90_10_vial_feeding_task参数
type: object
type: UniLabJsonCommand
create_batch_90_10_vial_feeding_task:
feedback: {}
goal:
batch_data: batch_data
goal_default:
batch_data: '{}'
handles: {}
result: {}
schema:
description: 创建批量90%10%小瓶投料任务
properties:
feedback: {}
goal:
properties:
batch_data:
description: 批量90%10%小瓶投料任务数据(JSON格式)包含batch_name、tasks列表和global_settings
type: string
required:
- batch_data
type: object
result: {}
required:
- goal
title: create_batch_90_10_vial_feeding_task参数
type: object
type: UniLabJsonCommand
create_batch_diamine_solution_task:
feedback: {}
goal:
batch_data: batch_data
goal_default:
batch_data: '{}'
handles: {}
result: {}
schema:
description: 创建批量二胺溶液配制任务
properties:
feedback: {}
goal:
properties:
batch_data:
description: 批量二胺溶液配制任务数据(JSON格式)包含batch_name、tasks列表和global_settings
type: string
required:
- batch_data
type: object
result: {}
required:
- goal
title: create_batch_diamine_solution_task参数
type: object
type: UniLabJsonCommand
create_diamine_solution_task:
feedback: {}
goal:
delay_time: delay_time
hold_m_name: hold_m_name
liquid_material_name: liquid_material_name
material_name: material_name
order_name: order_name
speed: speed
target_weigh: target_weigh
temperature: temperature
volume: volume
goal_default:
delay_time: '600'
hold_m_name: ''
liquid_material_name: NMP
material_name: ''
order_name: ''
speed: '400'
target_weigh: ''
temperature: '20'
volume: ''
handles: {}
result: {}
schema:
description: 创建二胺溶液配制任务
properties:
feedback: {}
goal:
properties:
delay_time:
default: '600'
description: 延迟时间(s)
type: string
hold_m_name:
description: 库位名称(如ODA-1)
type: string
liquid_material_name:
default: NMP
description: 液体物料名称
type: string
material_name:
description: 固体物料名称
type: string
order_name:
description: 任务名称
type: string
speed:
default: '400'
description: 搅拌速度(rpm)
type: string
target_weigh:
description: 固体目标重量(g)
type: string
temperature:
default: '20'
description: 温度(°C)
type: string
volume:
description: 液体体积(mL)
type: string
required:
- material_name
- target_weigh
- volume
type: object
result: {}
required:
- goal
title: create_diamine_solution_task参数
type: object
type: UniLabJsonCommand
create_resource:
feedback: {}
goal:
resource_config: resource_config
resource_type: resource_type
goal_default:
resource_config: {}
resource_type: ''
handles: {}
result: {}
schema:
description: 创建资源操作
properties:
feedback: {}
goal:
properties:
resource_config:
description: 资源配置
type: object
resource_type:
description: 资源类型
type: string
required:
- resource_type
- resource_config
type: object
result: {}
required:
- goal
title: create_resource参数
type: object
type: UniLabJsonCommand
dispensing_material_inbound:
feedback: {}
goal:
location: location
material_id: material_id
goal_default:
location: ''
material_id: ''
handles: {}
result: {}
schema:
description: 配液站物料入库操作
properties:
feedback: {}
goal:
properties:
location:
description: 存储位置
type: string
material_id:
description: 物料ID
type: string
required:
- material_id
- location
type: object
result: {}
required:
- goal
title: dispensing_material_inbound参数
type: object
type: UniLabJsonCommand
dispensing_material_outbound:
feedback: {}
goal:
material_id: material_id
quantity: quantity
goal_default:
material_id: ''
quantity: 0.0
handles: {}
result: {}
schema:
description: 配液站物料出库操作
properties:
feedback: {}
goal:
properties:
material_id:
description: 物料ID
type: string
quantity:
description: 出库数量
type: number
required:
- material_id
- quantity
type: object
result: {}
required:
- goal
title: dispensing_material_outbound参数
type: object
type: UniLabJsonCommand
sample_waste_removal:
feedback: {}
goal:
sample_id: sample_id
waste_type: waste_type
goal_default:
sample_id: ''
waste_type: general
handles: {}
result: {}
schema:
description: 样品废料移除操作
properties:
feedback: {}
goal:
properties:
sample_id:
description: 样品ID
type: string
waste_type:
description: 废料类型
enum:
- general
- hazardous
- organic
- inorganic
type: string
required:
- sample_id
type: object
result: {}
required:
- goal
title: sample_waste_removal参数
type: object
type: UniLabJsonCommand
module: unilabos.devices.workstation.bioyond_studio.station:BioyondWorkstation
protocol_type: []
status_types:
bioyond_status: dict
enable_dispensing_station: bool
enable_reaction_station: bool
station_type: str
type: python
config_info: []
description: Bioyond配液站 - 专门用于物料配制和管理的工作站
handles: []
icon: 配液站.webp
init_param_schema:
config:
properties:
bioyond_config:
description: Bioyond API配置
properties:
api_host:
description: Bioyond API主机地址
type: string
api_key:
description: Bioyond API密钥
type: string
material_type_mappings:
description: 物料类型映射配置
type: object
workflow_mappings:
description: 工作流映射配置
type: object
type: object
deck:
description: Deck配置
type: object
station_config:
description: 配液站配置
properties:
description:
description: 配液站描述
type: string
enable_dispensing_station:
default: true
description: 启用配液站功能
type: boolean
enable_reaction_station:
default: false
description: 禁用反应站功能
type: boolean
station_name:
description: 配液站名称
type: string
station_type:
default: dispensing_station
description: 站点类型 - 配液站
enum:
- dispensing_station
type: string
type: object
required: []
type: object
data:
properties: {}
required: []
type: object
version: 1.0.0

View File

@@ -0,0 +1,384 @@
reaction_station.bioyond:
category:
- work_station
- reaction_station_bioyond
class:
action_value_mappings:
bioyond_sync:
feedback: {}
goal:
force_sync: force_sync
sync_type: sync_type
goal_default:
force_sync: false
sync_type: full
handles: {}
result: {}
schema:
description: 从Bioyond系统同步物料
properties:
feedback: {}
goal:
properties:
force_sync:
description: 是否强制同步
type: boolean
sync_type:
description: 同步类型
enum:
- full
- incremental
type: string
required:
- sync_type
type: object
result: {}
required:
- goal
title: bioyond_sync参数
type: object
type: UniLabJsonCommand
bioyond_update:
feedback: {}
goal:
material_ids: material_ids
sync_all: sync_all
goal_default:
material_ids: []
sync_all: true
handles: {}
result: {}
schema:
description: 将本地物料变更同步到Bioyond
properties:
feedback: {}
goal:
properties:
material_ids:
description: 要同步的物料ID列表
items:
type: string
type: array
sync_all:
description: 是否同步所有物料
type: boolean
required:
- sync_all
type: object
result: {}
required:
- goal
title: bioyond_update参数
type: object
type: UniLabJsonCommand
reaction_station_drip_back:
feedback: {}
goal:
assign_material_name: assign_material_name
time: time
torque_variation: torque_variation
volume: volume
goal_default:
assign_material_name: ''
time: ''
torque_variation: ''
volume: ''
handles: {}
result: {}
schema:
description: 反应站滴回操作
properties:
feedback: {}
goal:
properties:
assign_material_name:
description: 溶剂名称
type: string
time:
description: 观察时间单位min
type: string
torque_variation:
description: 是否观察1否2是
type: string
volume:
description: 投料体积
type: string
required:
- volume
- assign_material_name
- time
- torque_variation
type: object
result: {}
required:
- goal
title: reaction_station_drip_back参数
type: object
type: UniLabJsonCommand
reaction_station_liquid_feed:
feedback: {}
goal:
assign_material_name: assign_material_name
time: time
titration_type: titration_type
torque_variation: torque_variation
volume: volume
goal_default:
assign_material_name: ''
time: ''
titration_type: ''
torque_variation: ''
volume: ''
handles: {}
result: {}
schema:
description: 反应站液体进料操作
properties:
feedback: {}
goal:
properties:
assign_material_name:
description: 溶剂名称
type: string
time:
description: 观察时间单位min
type: string
titration_type:
description: 滴定类型1否2是
type: string
torque_variation:
description: 是否观察1否2是
type: string
volume:
description: 投料体积
type: string
required:
- titration_type
- volume
- assign_material_name
- time
- torque_variation
type: object
result: {}
required:
- goal
title: reaction_station_liquid_feed参数
type: object
type: UniLabJsonCommand
reaction_station_process_execute:
feedback: {}
goal:
task_name: task_name
workflow_name: workflow_name
goal_default:
task_name: ''
workflow_name: ''
handles: {}
result: {}
schema:
description: 反应站流程执行
properties:
feedback: {}
goal:
properties:
task_name:
description: 任务名称
type: string
workflow_name:
description: 工作流名称
type: string
required:
- workflow_name
- task_name
type: object
result: {}
required:
- goal
title: reaction_station_process_execute参数
type: object
type: UniLabJsonCommand
reaction_station_reactor_taken_out:
feedback: {}
goal:
order_id: order_id
preintake_id: preintake_id
goal_default:
order_id: ''
preintake_id: ''
handles: {}
result: {}
schema:
description: 反应站反应器取出操作 - 通过订单ID和预取样ID进行精确控制
properties:
feedback: {}
goal:
properties:
order_id:
description: 订单ID用于标识要取出的订单
type: string
preintake_id:
description: 预取样ID用于标识具体的取样任务
type: string
required: []
type: object
result:
properties:
code:
description: 操作结果代码1表示成功0表示失败
type: integer
return_info:
description: 操作结果详细信息
type: string
type: object
required:
- goal
title: reaction_station_reactor_taken_out参数
type: object
type: UniLabJsonCommand
reaction_station_solid_feed_vial:
feedback: {}
goal:
assign_material_name: assign_material_name
material_id: material_id
time: time
torque_variation: torque_variation
goal_default:
assign_material_name: ''
material_id: ''
time: ''
torque_variation: ''
handles: {}
result: {}
schema:
description: 反应站固体进料操作
properties:
feedback: {}
goal:
properties:
assign_material_name:
description: 固体名称_粉末加样模块-投料
type: string
material_id:
description: 固体投料类型_粉末加样模块-投料
type: string
time:
description: 观察时间_反应模块-观察搅拌结果
type: string
torque_variation:
description: 是否观察1否2是_反应模块-观察搅拌结果
type: string
required:
- assign_material_name
- material_id
- time
- torque_variation
type: object
result: {}
required:
- goal
title: reaction_station_solid_feed_vial参数
type: object
type: UniLabJsonCommand
reaction_station_take_in:
feedback: {}
goal:
assign_material_name: assign_material_name
cutoff: cutoff
temperature: temperature
goal_default:
assign_material_name: ''
cutoff: ''
temperature: ''
handles: {}
result: {}
schema:
description: 反应站取入操作
properties:
feedback: {}
goal:
properties:
assign_material_name:
description: 物料名称
type: string
cutoff:
description: 截止参数
type: string
temperature:
description: 温度
type: string
required:
- cutoff
- temperature
- assign_material_name
type: object
result: {}
required:
- goal
title: reaction_station_take_in参数
type: object
type: UniLabJsonCommand
module: unilabos.devices.workstation.bioyond_studio.station:BioyondWorkstation
protocol_type: []
status_types:
bioyond_status: dict
enable_dispensing_station: bool
enable_reaction_station: bool
station_type: str
type: python
config_info: []
description: Bioyond反应站 - 专门用于化学反应操作的工作站
handles: []
icon: 反应站.webp
init_param_schema:
config:
properties:
bioyond_config:
description: Bioyond API配置
properties:
api_host:
description: Bioyond API主机地址
type: string
api_key:
description: Bioyond API密钥
type: string
material_type_mappings:
description: 物料类型映射配置
type: object
workflow_mappings:
description: 工作流映射配置
type: object
type: object
deck:
description: Deck配置
type: object
station_config:
description: 反应站配置
properties:
description:
description: 反应站描述
type: string
enable_dispensing_station:
default: false
description: 禁用配液站功能
type: boolean
enable_reaction_station:
default: true
description: 启用反应站功能
type: boolean
station_name:
description: 反应站名称
type: string
station_type:
default: reaction_station
description: 站点类型 - 反应站
enum:
- reaction_station
type: string
type: object
required: []
type: object
data:
properties: {}
required: []
type: object
version: 1.0.0

View File

@@ -594,7 +594,7 @@ def resource_bioyond_to_plr(bioyond_materials: list[dict], type_mapping: dict =
for material in bioyond_materials:
className = type_mapping.get(material.get("typeName"), "RegularContainer") if type_mapping else "RegularContainer"
plr_material: ResourcePLR = initialize_resource({"name": material["name"], "class": className}, resource_type=ResourcePLR)
plr_material.code = material.get("code", "") and material.get("barCode", "") or ""

View File

@@ -6,8 +6,8 @@ from typing import List, Tuple, Any, Dict, Literal, Optional, cast, TYPE_CHECKIN
from unilabos.utils.log import logger
if TYPE_CHECKING:
# from unilabos.devices.workstation.workstation_base import WorkstationBase
from pylabrobot.resources import Resource as PLRResource, corning_6_wellplate_16point8ml_flat
from unilabos.devices.workstation.workstation_base import WorkstationBase
from pylabrobot.resources import Resource as PLRResource
class ResourceDictPositionSize(BaseModel):
@@ -372,49 +372,79 @@ class ResourceTreeSet(object):
- PLR 资源实例列表
- 每个资源对应的 name_to_uuid 映射字典列表
"""
from unilabos.resources.graphio import resource_ulab_to_plr
from pylabrobot.resources import Resource as PLRResource
from pylabrobot.utils.object_parsing import find_subclass
import inspect
# 类型映射
TYPE_MAP = {"plate": "plate", "well": "well", "container": "tip_spot", "deck": "deck", "tip_rack": "tip_rack"}
def collect_node_data(node: ResourceDictInstance, name_to_uuid: dict, all_states: dict):
"""一次遍历收集 name_to_uuid 和 all_states"""
name_to_uuid[node.res_content.name] = node.res_content.uuid
all_states[node.res_content.name] = node.res_content.data
for child in node.children:
collect_node_data(child, name_to_uuid, all_states)
def node_to_plr_dict(node: ResourceDictInstance, has_model: bool):
"""转换节点为 PLR 字典格式"""
res = node.res_content
plr_type = TYPE_MAP.get(res.type, "tip_spot")
if res.type not in TYPE_MAP:
logger.warning(f"未知类型 {res.type},使用默认类型 tip_spot")
d = {
"name": res.name,
"type": plr_type,
"size_x": res.config.get("size_x", 0),
"size_y": res.config.get("size_y", 0),
"size_z": res.config.get("size_z", 0),
"location": {
"x": res.position.position.x,
"y": res.position.position.y,
"z": res.position.position.z,
"type": "Coordinate",
},
"rotation": {"x": 0, "y": 0, "z": 0, "type": "Rotation"},
"category": plr_type,
"children": [node_to_plr_dict(child, has_model) for child in node.children],
"parent_name": res.parent_name,
**res.config,
}
if has_model:
d["model"] = res.config.get("model", None)
return d
plr_resources = []
name_to_uuid_maps = []
def build_name_to_uuid_map(node: ResourceDictInstance, result: Dict[str, str]):
"""递归构建 name 到 uuid 的映射"""
result[node.res_content.name] = node.res_content.uuid
for child in node.children:
build_name_to_uuid_map(child, result)
tracker = DeviceNodeResourceTracker()
for tree in self.trees:
# 构建 name_to_uuid 映射
name_to_uuid = {}
build_name_to_uuid_map(tree.root_node, name_to_uuid)
name_to_uuid: Dict[str, str] = {}
all_states: Dict[str, Any] = {}
collect_node_data(tree.root_node, name_to_uuid, all_states)
# 使用 get_nested_dict 获取字典表示
resource_dict = tree.root_node.get_nested_dict()
# 判断是否包含 modelDeck 下没有 model
plr_model = tree.root_node.res_content.type != "deck"
has_model = tree.root_node.res_content.type != "deck"
plr_dict = node_to_plr_dict(tree.root_node, has_model)
try:
# 使用 resource_ulab_to_plr 创建 PLR 资源实例
plr_resource = resource_ulab_to_plr(resource_dict, plr_model=plr_model)
sub_cls = find_subclass(plr_dict["type"], PLRResource)
if sub_cls is None:
raise ValueError(f"无法找到类型 {plr_dict['type']} 对应的 PLR 资源类")
# 设置 unilabos_uuid 属性到资源及其所有子节点
def set_uuid_recursive(plr_res: "PLRResource", node: ResourceDictInstance):
"""递归设置 PLR 资源的 unilabos_uuid 属性"""
setattr(plr_res, "unilabos_uuid", node.res_content.uuid)
# 匹配子节点(通过 name
for plr_child in plr_res.children:
matching_node = next(
(child for child in node.children if child.res_content.name == plr_child.name),
None,
)
if matching_node:
set_uuid_recursive(plr_child, matching_node)
spec = inspect.signature(sub_cls)
if "category" not in spec.parameters:
plr_dict.pop("category", None)
set_uuid_recursive(plr_resource, tree.root_node)
plr_resource = sub_cls.deserialize(plr_dict, allow_marshal=True)
plr_resource.load_all_state(all_states)
# 使用 DeviceNodeResourceTracker 设置 UUID
tracker.loop_set_uuid(plr_resource, name_to_uuid)
plr_resources.append(plr_resource)
name_to_uuid_maps.append(name_to_uuid)
except Exception as e:
logger.error(f"转换 PLR 资源失败: {e}")
import traceback
@@ -685,9 +715,7 @@ class DeviceNodeResourceTracker(object):
res_list = []
for r in self.resources:
if isinstance(query_resource, dict):
res_list.extend(
self.loop_find_resource(r, object, identifier_key, query_resource[identifier_key])
)
res_list.extend(self.loop_find_resource(r, object, identifier_key, query_resource[identifier_key]))
else:
res_list.extend(
self.loop_find_resource(
@@ -735,134 +763,84 @@ class DeviceNodeResourceTracker(object):
if __name__ == "__main__":
import sys
import os
from pylabrobot.resources import corning_6_wellplate_16point8ml_flat
a = corning_6_wellplate_16point8ml_flat("a").serialize()
# 尝试导入 pylabrobot如果失败则尝试从本地 pylabrobot_repo 导入
try:
from pylabrobot.resources import Resource, Coordinate
except ImportError:
# 尝试添加本地 pylabrobot_repo 路径
# __file__ is unilabos/ros/nodes/resource_tracker.py
# We need to go up 4 levels to get to project root
current_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
pylabrobot_path = os.path.join(current_dir, "pylabrobot_repo")
if os.path.exists(pylabrobot_path):
sys.path.insert(0, pylabrobot_path)
try:
from pylabrobot.resources import Resource, Coordinate
except ImportError:
print("pylabrobot 未安装,且无法从本地 pylabrobot_repo 导入")
print("如需运行测试,请先安装: pip install pylabrobot")
exit(0)
else:
print("pylabrobot 未安装,跳过测试")
print("如需运行测试,请先安装: pip install pylabrobot")
exit(0)
# 测试 from_plr_resources 和 to_plr_resources 的往返转换
print("=" * 60)
print("测试 PLR 资源转换往返")
print("=" * 60)
# 创建一个简单的测试资源
def create_test_resource(name: str):
"""创建一个简单的测试用资源"""
# 创建父资源
parent = Resource(name=name, size_x=100.0, size_y=100.0, size_z=50.0, category="container")
# 1. 创建一个 PLR 资源并设置 UUID
original_plate = corning_6_wellplate_16point8ml_flat("test_plate")
# 添加一些子资源
for i in range(3):
child = Resource(name=f"{name}_child_{i}", size_x=20.0, size_y=20.0, size_z=10.0, category="container")
child.location = Coordinate(x=i * 30, y=0, z=0)
parent.assign_child_resource(child, location=child.location)
# 使用 DeviceNodeResourceTracker 设置 UUID
tracker = DeviceNodeResourceTracker()
name_to_uuid = {}
return parent
# 递归生成 name_to_uuid 映射
def build_uuid_map(resource):
name_to_uuid[resource.name] = str(uuid.uuid4())
for child in resource.children:
build_uuid_map(child)
print("=" * 80)
print("测试 1: 基本序列化和反序列化")
print("=" * 80)
build_uuid_map(original_plate)
# 创建原始 PLR 资源
original_resource = create_test_resource("test_resource")
print(f"\n1. 创建原始 PLR 资源: {original_resource.name}")
print(f" 子节点数量: {len(original_resource.children)}")
# 使用 tracker 的 loop_set_uuid 方法设置 UUID
tracker.loop_set_uuid(original_plate, name_to_uuid)
# 手动设置 unilabos_uuid模拟实际使用场景
def set_test_uuid(res: "PLRResource", prefix="uuid"):
"""递归设置测试用的 uuid"""
import uuid as uuid_module
print(f"\n1. 原始 PLR 资源: {original_plate.name}")
print(f" - UUID: {getattr(original_plate, 'unilabos_uuid', 'N/A')}")
print(f" - 子节点数量: {len(original_plate.children)}")
if original_plate.children:
print(f" - 第一个子节点: {original_plate.children[0].name}")
print(f" - 第一个子节点 UUID: {getattr(original_plate.children[0], 'unilabos_uuid', 'N/A')}")
setattr(res, "unilabos_uuid", f"{prefix}-{uuid_module.uuid4()}")
for i, child in enumerate(res.children):
set_test_uuid(child, f"{prefix}-{i}")
# 2. 将 PLR 资源转换为 ResourceTreeSet
resource_tree_set = ResourceTreeSet.from_plr_resources([original_plate])
print(f"\n2. 转换为 ResourceTreeSet:")
print(f" - 树的数量: {len(resource_tree_set.trees)}")
print(f" - 根节点: {resource_tree_set.root_nodes[0].res_content.name}")
print(f" - 所有节点数量: {len(resource_tree_set.all_nodes)}")
set_test_uuid(original_resource, "root")
print(f" 根节点 UUID: {getattr(original_resource, 'unilabos_uuid', 'None')}")
# 3. 将 ResourceTreeSet 转换回 PLR 资源
plr_resources, name_to_uuid_maps = resource_tree_set.to_plr_resources()
converted_plate = plr_resources[0]
print(f"\n3. 转换回 PLR 资源: {converted_plate.name}")
print(f" - 子节点数量: {len(converted_plate.children)}")
if converted_plate.children:
print(f" - 第一个子节点: {converted_plate.children[0].name}")
# 转换为 ResourceTreeSet (from_plr_resources)
print("\n2. 使用 from_plr_resources 转换为 ResourceTreeSet")
resource_tree_set = ResourceTreeSet.from_plr_resources([original_resource])
print(f" 树的数量: {len(resource_tree_set.trees)}")
print(f" 根节点名称: {resource_tree_set.root_nodes[0].res_content.name}")
print(f" 根节点 UUID: {resource_tree_set.root_nodes[0].res_content.uuid}")
print(f" 总节点数: {len(resource_tree_set.all_nodes)}")
# 转换回 PLR 资源 (to_plr_resources)
print("\n3. 使用 to_plr_resources 转换回 PLR 资源")
try:
plr_resources, name_to_uuid_maps = resource_tree_set.to_plr_resources()
except ModuleNotFoundError as e:
print(f" ❌ 缺少依赖模块: {e}")
print(" 提示: to_plr_resources 方法实现完成,但需要安装额外的依赖(如 networkx")
print("\n测试部分完成from_plr_resources 已验证正常工作。")
exit(0)
print(f" PLR 资源数量: {len(plr_resources)}")
print(f" name_to_uuid 映射数量: {len(name_to_uuid_maps)}")
restored_resource = plr_resources[0]
# 4. 验证 UUID 映射
name_to_uuid = name_to_uuid_maps[0]
print(f"\n4. UUID 映射:")
print(f" - 映射条目数: {len(name_to_uuid)}")
print(f" - 示例映射: {list(name_to_uuid.items())[:3]}")
print(f" 恢复的资源名称: {restored_resource.name}")
print(f" 恢复的资源子节点数: {len(restored_resource.children)}")
print(f" 恢复的资源 UUID: {getattr(restored_resource, 'unilabos_uuid', 'None')}")
print(f" name_to_uuid 映射条目数: {len(name_to_uuid)}")
# 5. 验证 unilabos_uuid 属性
print(f"\n5. 验证 unilabos_uuid 设置:")
if hasattr(converted_plate, "unilabos_uuid"):
print(f" - 根节点 UUID: {getattr(converted_plate, 'unilabos_uuid')}")
if converted_plate.children and hasattr(converted_plate.children[0], "unilabos_uuid"):
print(f" - 第一个子节点 UUID: {getattr(converted_plate.children[0], 'unilabos_uuid')}")
else:
print(" - 警告: unilabos_uuid 未设置")
# 验证 UUID 映射
print("\n4. 验证 UUID 映射")
original_uuid = getattr(original_resource, "unilabos_uuid", None)
restored_uuid = getattr(restored_resource, "unilabos_uuid", None)
print(f" 原始根节点 UUID: {original_uuid}")
print(f" 恢复后根节点 UUID: {restored_uuid}")
print(f" UUID 匹配: {original_uuid == restored_uuid}")
# 6. 验证 UUID 保持不变
print(f"\n6. 验证 UUID 在往返过程中保持不变:")
original_uuid = getattr(original_plate, "unilabos_uuid")
converted_uuid = getattr(converted_plate, "unilabos_uuid")
print(f" - 原始 UUID: {original_uuid}")
print(f" - 转换后 UUID: {converted_uuid}")
print(f" - UUID 保持不变: {original_uuid == converted_uuid}")
# 验证 name_to_uuid 映射完整
def count_all_nodes(res: "PLRResource") -> int:
"""递归统计节点总数"""
return 1 + sum(count_all_nodes(child) for child in res.children)
# 7. 再次往返转换,验证稳定
resource_tree_set_2 = ResourceTreeSet.from_plr_resources([converted_plate])
plr_resources_2, name_to_uuid_maps_2 = resource_tree_set_2.to_plr_resources()
print(f"\n7. 第二次往返转换:")
print(f" - 资源名称: {plr_resources_2[0].name}")
print(f" - 子节点数量: {len(plr_resources_2[0].children)}")
print(f" - UUID 依然保持: {getattr(plr_resources_2[0], 'unilabos_uuid') == original_uuid}")
original_node_count = count_all_nodes(original_resource)
restored_node_count = count_all_nodes(restored_resource)
mapping_count = len(name_to_uuid)
print(f"\n 原始资源节点总数: {original_node_count}")
print(f" 恢复资源节点总数: {restored_node_count}")
print(f" 映射字典条目数: {mapping_count}")
print(f" 节点数量匹配: {original_node_count == restored_node_count == mapping_count}")
# 验证子节点的 UUID
print("\n5. 验证子节点 UUID (前3个)")
for i, (original_child, restored_child) in enumerate(
zip(original_resource.children[:3], restored_resource.children[:3])
):
orig_uuid = getattr(original_child, "unilabos_uuid", None)
rest_uuid = getattr(restored_child, "unilabos_uuid", None)
print(f" 子节点 {i}: {original_child.name}")
print(f" 原始 UUID: {orig_uuid}")
print(f" 恢复 UUID: {rest_uuid}")
print(f" 匹配: {orig_uuid == rest_uuid}")
# 测试 name_to_uuid 映射的正确性
print("\n6. 验证 name_to_uuid 映射内容 (前5个)")
for i, (name, uuid_val) in enumerate(list(name_to_uuid.items())[:5]):
print(f" {name} -> {uuid_val}")
print("\n" + "=" * 80)
print("测试完成!")
print("=" * 80)
print("\n" + "=" * 60)
print("✅ 测试完成! 所有转换正常工作")
print("=" * 60)

View File

@@ -150,7 +150,7 @@ class PyLabRobotCreator(DeviceClassCreator[T]):
target_type = import_manager.get_class(type_path)
contain_model = not issubclass(target_type, Deck)
resource, target_type = self._process_resource_mapping(resource, target_type)
resource_instance: Resource = resource_ulab_to_plr(resource, contain_model)
resource_instance: Resource = resource_ulab_to_plr(resource, contain_model) # 带state
states[prefix_path] = resource_instance.serialize_all_state()
# 使用 prefix_path 作为 key 存储资源状态
if to_dict:
@@ -159,7 +159,7 @@ class PyLabRobotCreator(DeviceClassCreator[T]):
return serialized
else:
self.resource_tracker.add_resource(resource_instance)
# 立即设置UUID
# 立即设置UUIDstate已经在resource_ulab_to_plr中处理过了
if name_to_uuid:
self.resource_tracker.loop_set_uuid(resource_instance, name_to_uuid)
return resource_instance
@@ -244,7 +244,7 @@ class PyLabRobotCreator(DeviceClassCreator[T]):
v[kk] = vv
self.device_instance.load_all_state(v)
self.resource_tracker.add_resource(self.device_instance)
self.post_create()
self.post_create() # 对应DeviceClassCreator进行调用
return self.device_instance # type: ignore
except Exception as e:
# 先静默继续,尝试另外一种创建方法
@@ -265,7 +265,7 @@ class PyLabRobotCreator(DeviceClassCreator[T]):
data[param_name]["_resource_type"] = self.device_cls.__module__ + ":" + arg_value
logger.debug(f"自动补充 _resource_type: {data[param_name]['_resource_type']}")
processed_data = self._process_resource_references(data, to_dict=False, name_to_uuid=name_to_uuid)
self.device_instance = super(PyLabRobotCreator, self).create_instance(processed_data)
self.device_instance = super(PyLabRobotCreator, self).create_instance(processed_data) # 补全变量后直接调用调用的自身的attach_resource
except Exception as e:
logger.error(f"PyLabRobot创建实例失败: {e}")
logger.error(f"PyLabRobot创建实例堆栈: {traceback.format_exc()}")

View File

@@ -103,10 +103,14 @@ set(action_files
"action/PostProcessGrab.action"
"action/PostProcessTriggerClean.action"
"action/PostProcessTriggerPostPro.action"
"action/ReactionStationDripBack.action"
"action/ReactionStationLiquidFeed.action"
"action/ReactionStationLiquidFeedBeaker.action"
"action/ReactionStationLiquidFeedSolvents.action"
"action/ReactionStationLiquidFeedTitration.action"
"action/ReactionStationLiquidFeedVialsNonTitration.action"
"action/ReactionStationProExecu.action"
"action/ReactionStationReactorTakenOut.action"
"action/ReactionStationReaTackIn.action"
"action/ReactionStationSolidFeedVial.action"
)

View File

@@ -1,11 +1,13 @@
# Goal - 滴回去
string volume # 投料体积
string assign_material_name # 溶剂名称
string time # 观察时间单位min
string torque_variation #是否观察1否2是
# Goal - 滴回去操作参数
string volume # 投料体积
string assign_material_name # 溶剂名称
string time # 观察时间单位min
string torque_variation # 是否观察1否2是
---
# Result - 操作结果
string return_info # 结果消息
# Result - 操作结果
bool success # 操作是否成功
string return_info # 结果消息
int32 code # 操作结果代码1表示成功0表示失败
---
# Feedback - 实时反馈
# Feedback - 实时反馈
string feedback # 操作过程中的反馈信息

View File

@@ -1,11 +0,0 @@
# Goal - 液体投料
string titration_type # 滴定类型1否2是
string volume # 投料体积
string assign_material_name # 溶剂名称
string time # 观察时间单位min
string torque_variation #是否观察1否2是
---
# Result - 操作结果
string return_info # 结果消息
---
# Feedback - 实时反馈

View File

@@ -0,0 +1,15 @@
# Goal - 液体投料-烧杯操作参数
string volume # 投料体积
string assign_material_name # 溶剂名称
string titration_type # 滴定类型1否2是
string time # 观察时间单位min
string torque_variation # 是否观察1否2是
string temperature # 温度设置
---
# Result - 操作结果
bool success # 操作是否成功
string return_info # 结果消息
int32 code # 操作结果代码1表示成功0表示失败
---
# Feedback - 实时反馈
string feedback # 操作过程中的反馈信息

View File

@@ -0,0 +1,15 @@
# Goal - 液体投料-溶剂操作参数
string volume # 投料体积
string assign_material_name # 溶剂名称
string titration_type # 滴定类型1否2是
string time # 观察时间单位min
string torque_variation # 是否观察1否2是
string temperature # 温度设置
---
# Result - 操作结果
bool success # 操作是否成功
string return_info # 结果消息
int32 code # 操作结果代码1表示成功0表示失败
---
# Feedback - 实时反馈
string feedback # 操作过程中的反馈信息

View File

@@ -0,0 +1,15 @@
# Goal - 液体投料滴定操作参数
string volume_formula # 投料体积公式
string assign_material_name # 溶剂名称
string titration_type # 滴定类型1否2是
string time # 观察时间单位min
string torque_variation # 是否观察1否2是
string temperature # 温度设置
---
# Result - 操作结果
bool success # 操作是否成功
string return_info # 结果消息
int32 code # 操作结果代码1表示成功0表示失败
---
# Feedback - 实时反馈
string feedback # 操作过程中的反馈信息

View File

@@ -0,0 +1,15 @@
# Goal - 液体投料-小瓶非滴定操作参数
string volume_formula # 投料体积公式
string assign_material_name # 溶剂名称
string titration_type # 滴定类型1否2是
string time # 观察时间单位min
string torque_variation # 是否观察1否2是
string temperature # 温度设置
---
# Result - 操作结果
bool success # 操作是否成功
string return_info # 结果消息
int32 code # 操作结果代码1表示成功0表示失败
---
# Feedback - 实时反馈
string feedback # 操作过程中的反馈信息

View File

@@ -1,8 +1,11 @@
# Goal - 合并工作流+执行
string workflow_name # 工作流名称
string task_name # 任务名称
# Goal - 合并工作流+执行参数
string workflow_name # 工作流名称
string task_name # 任务名称
---
# Result - 操作结果
string return_info # 结果消息
# Result - 操作结果
bool success # 操作是否成功
string return_info # 结果消息
int32 code # 操作结果代码1表示成功0表示失败
---
# Feedback - 实时反馈
# Feedback - 实时反馈
string feedback # 操作过程中的反馈信息

View File

@@ -1,9 +1,12 @@
# Goal - 通量-配置
string cutoff # 黏度_通量-配置
string temperature # 温度_通量-配
string assign_material_name # 分液类型_通量-配置
# Goal - 反应器放入操作参数
string cutoff # 黏度设置
string temperature # 温度
string assign_material_name # 分液类型
---
# Result - 操作结果
string return_info # 结果消息
# Result - 操作结果
bool success # 操作是否成功
string return_info # 结果消息
int32 code # 操作结果代码1表示成功0表示失败
---
# Feedback - 实时反馈
# Feedback - 实时反馈
string feedback # 操作过程中的反馈信息

View File

@@ -0,0 +1,12 @@
# Goal - 反应器取出操作参数
# 反应器取出操作不需要任何参数
---
# Result - 操作结果
# 反应器取出操作的结果
bool success # 要求必须包含success以便回传执行结果
string return_info # 要求必须包含return_info以便回传执行结果
int32 code # 操作结果代码1表示成功0表示失败
---
# Feedback - 实时反馈
# 反应器取出操作的反馈
string feedback # 操作过程中的反馈信息

View File

@@ -1,10 +1,13 @@
# Goal - 固体投料-小瓶
string assign_material_name # 固体名称_粉末加样模块-投料
string material_id # 固体投料类型_粉末加样模块-投料
string time # 观察时间_反应模块-观察搅拌结果
string torque_variation #是否观察1否2是_反应模块-观察搅拌结果
# Goal - 固体投料-小瓶操作参数
string assign_material_name # 固体名称
string material_id # 固体投料类型
string time # 观察时间单位min
string torque_variation # 是否观察1否2是
---
# Result - 操作结果
string return_info # 结果消息
# Result - 操作结果
bool success # 操作是否成功
string return_info # 结果消息
int32 code # 操作结果代码1表示成功0表示失败
---
# Feedback - 实时反馈
# Feedback - 实时反馈
string feedback # 操作过程中的反馈信息