Compare commits

...

108 Commits

Author SHA1 Message Date
Guangxin Zhang
bef44b2293 Update Prcxi 2025-07-05 22:03:49 +08:00
Guangxin Zhang
b78c6c6ba9 Update prcxi.py 2025-07-05 18:02:58 +08:00
Guangxin Zhang
0d512c9e38 Create prcxi.py 2025-07-05 17:16:59 +08:00
Kongchang Feng
c8c755057c Update work_station.yaml (#60)
* Update work_station.yaml

* Checklist里面有XDL跟protocol之间没对齐的问题,工作量有点大找时间写完
2025-07-05 15:13:14 +08:00
Xuwznln
a6ec20e279 转换到ros消息时,要进行基础类型转换 2025-07-05 12:43:27 +08:00
Xuwznln
b69aceaff3 Merge branch 'dev' of https://github.com/dptech-corp/Uni-Lab-OS into dev 2025-07-04 21:23:38 +08:00
Kongchang Feng
21afdb62bc Create 5 new protocols & bump version 0.9.8 (#59)
* 添加了5个缺失的protocol,验证了可以运行

* bump version to 0.9.8

* 修复新增的Action的字段缺失

---------

Co-authored-by: Xuwznln <18435084+Xuwznln@users.noreply.github.com>
2025-07-04 13:58:27 +08:00
Xuwznln
d7d43af40a 修复任务执行传参 2025-07-04 13:52:23 +08:00
ZiWei
132955617d Add channel_sources config in conda_build_config.yaml (#58) 2025-07-03 23:59:08 +08:00
Xuwznln
e7521972e4 匹配init param schema格式 2025-06-30 12:29:30 +08:00
Xuwznln
f2753fc69a Merge branch 'main' into dev 2025-06-29 19:18:17 +08:00
Xuwznln
09ad905280 修复auto-的Action在protocol node下错误注册 2025-06-29 19:12:32 +08:00
Junhan Chang
7714c71cd2 add camera and dependency (#56) 2025-06-29 17:35:32 +08:00
Junhan Chang
64832718be Fix handle names (#55)
* fix handle names

* improve evacuateAndRefill gas source finding
2025-06-29 17:31:44 +08:00
Xuwznln
68871358c2 成功动态导入的不再需要使用静态导入 2025-06-29 11:52:59 +08:00
Xuwznln
498b3cad6a 支持通过list[int],list[float]进行Int64MultiArray,Float64MultiArray的替换 2025-06-29 11:52:24 +08:00
Xuwznln
157da1759d status types对于嵌套类型返回的对象,暂时处理成字符串,无法直接进行转换 2025-06-29 11:26:35 +08:00
Xuwznln
be0a73eb19 修复静态方法识别get status,注册表支持python类型 2025-06-28 12:18:30 +08:00
Xuwznln
9be6e1069a 修复部分识别error 2025-06-28 10:52:34 +08:00
Xuwznln
817e88cfc4 修复不启用注册表补充就无法启动的bug 2025-06-28 01:32:10 +08:00
Xuwznln
15f3f8518b 支持通过导入方式补全注册表,新增工作流unilabos_device_id字段 2025-06-28 01:19:54 +08:00
Xuwznln
bbc49e9aab 新增注册表补全功能,修复Protocol执行失败 2025-06-27 23:45:05 +08:00
Xuwznln
f9a9e91d56 Merge remote-tracking branch 'origin/main' into dev
# Conflicts:
#	test/experiments/Protocol_Test_Station/clean_vessel_protocol_test_station.json
#	test/experiments/comprehensive_protocol/comprehensive_station.json
#	unilabos/compile/__init__.py
#	unilabos/compile/add_protocol.py
#	unilabos/compile/clean_vessel_protocol.py
#	unilabos/registry/devices/virtual_device.yaml
#	unilabos/registry/resources/organic/container.yaml
2025-06-22 18:32:46 +08:00
Kongchang Feng
96e9c76709 添加了两个protocol的检索功能 (#51)
* 添加了两个protocol的检索liquid type功能

* fix workstation registry

* 修复了没连接的几个仪器的link,添加了container的icon

* 修改了json和注册表,现在大图全部的device都链接上了

* 修复了小图的json图,线全部连上了

* add work_station protocol handles (ports)

* fix workstation action handle

---------

Co-authored-by: Xuwznln <18435084+Xuwznln@users.noreply.github.com>
Co-authored-by: Junhan Chang <changjh@dp.tech>
2025-06-22 18:30:09 +08:00
Xuwznln
06b7962ef9 更新workstation注册表 2025-06-22 14:52:40 +08:00
Xuwznln
6faa19a250 Merge branch 'main' into dev 2025-06-22 13:01:37 +08:00
Kongchang Feng
46cec82a51 Device registry port (#49)
* Update README and MQTTClient for installation instructions and code improvements

* feat: 支持local_config启动
add: 增加对crt path的说明,为传入config.py的相对路径
move: web component

* add: registry description

* add 3d visualization

* 完成在main中启动设备可视化

完成在main中启动设备可视化,并输出物料ID:mesh的对应关系resource_model

添加物料模型管理类,遍历物料与resource_model,完成TF数据收集

* 完成TF发布

* 修改模型方向,在yaml中添加变换属性

* 添加物料tf变化时,发送topic到前端

另外修改了物料初始化的方法,防止在tf还未发布时提前建立物料模型与发布话题

* 添加关节发布节点与物料可视化节点进入unilab

* 使用json启动plr与3D模型仿真

* feat: node_info_update srv
fix: OTDeck cant create

* close #12
feat: slave node registry

* feat: show machine name
fix: host node registry not uploaded

* feat: add hplc registry

* feat: add hplc registry

* fix: hplc status typo

* fix: devices/

* 完成启动OT并联动rviz

* add 3d visualization

* 完成在main中启动设备可视化

完成在main中启动设备可视化,并输出物料ID:mesh的对应关系resource_model

添加物料模型管理类,遍历物料与resource_model,完成TF数据收集

* 完成TF发布

* 修改模型方向,在yaml中添加变换属性

* 添加物料tf变化时,发送topic到前端

另外修改了物料初始化的方法,防止在tf还未发布时提前建立物料模型与发布话题

* 添加关节发布节点与物料可视化节点进入unilab

* 使用json启动plr与3D模型仿真

* 完成启动OT并联动rviz

* fix: device.class possible null

* fix: HPLC additions with online service

* fix: slave mode spin not working

* fix: slave mode spin not working

* 修复rviz位置问题,

修复rviz位置问题,
在无tf变动时减缓发送频率
在backend中添加物料跟随方法

* feat: 多ProtocolNode 允许子设备ID相同
feat: 上报发现的ActionClient
feat: Host重启动,通过discover机制要求slaveNode重新注册,实现信息及时上报

* feat: 支持env设置config

* fix: running logic

* fix: running logic

* fix: missing ot

* 在main中直接初始化republisher和物料的mesh节点

* 将joint_republisher和resource_mesh_manager添加进 main_slave_run.py中

* Device visualization (#14)

* add 3d visualization

* 完成在main中启动设备可视化

完成在main中启动设备可视化,并输出物料ID:mesh的对应关系resource_model

添加物料模型管理类,遍历物料与resource_model,完成TF数据收集

* 完成TF发布

* 修改模型方向,在yaml中添加变换属性

* 添加物料tf变化时,发送topic到前端

另外修改了物料初始化的方法,防止在tf还未发布时提前建立物料模型与发布话题

* 添加关节发布节点与物料可视化节点进入unilab

* 使用json启动plr与3D模型仿真

* 完成启动OT并联动rviz

* add 3d visualization

* 完成在main中启动设备可视化

完成在main中启动设备可视化,并输出物料ID:mesh的对应关系resource_model

添加物料模型管理类,遍历物料与resource_model,完成TF数据收集

* 完成TF发布

* 修改模型方向,在yaml中添加变换属性

* 添加物料tf变化时,发送topic到前端

另外修改了物料初始化的方法,防止在tf还未发布时提前建立物料模型与发布话题

* 添加关节发布节点与物料可视化节点进入unilab

* 使用json启动plr与3D模型仿真

* 完成启动OT并联动rviz

* 修复rviz位置问题,

修复rviz位置问题,
在无tf变动时减缓发送频率
在backend中添加物料跟随方法

* fix: running logic

* fix: running logic

* fix: missing ot

* 在main中直接初始化republisher和物料的mesh节点

* 将joint_republisher和resource_mesh_manager添加进 main_slave_run.py中

---------

Co-authored-by: zhangshixiang <@zhangshixiang>
Co-authored-by: wznln <18435084+Xuwznln@users.noreply.github.com>

* fix: missing hostname in devices_names
fix: upload_file for model file

* fix: missing paho-mqtt package
bump version to 0.9.0

* fix startup
add ResourceCreateFromOuter.action

* fix type hint

* update actions

* update actions

* host node add_resource_from_outer
fix cmake list

* pass device config to device class

* add: bind_parent_ids to resource create action
fix: message convert string

* fix: host node should not be re_discovered

* feat: resource tracker support dict

* feat: add more necessary params

* feat: fix boolean null in registry action data

* feat: add outer resource

* 编写mesh添加action

* feat: append resource

* add action

* feat: vis 2d for plr

* fix

* fix: browser on rviz

* fix: cloud bridge error fallback to local

* fix: salve auto run rviz

* 初始化两个plate

* Device visualization (#22)

* add 3d visualization

* 完成在main中启动设备可视化

完成在main中启动设备可视化,并输出物料ID:mesh的对应关系resource_model

添加物料模型管理类,遍历物料与resource_model,完成TF数据收集

* 完成TF发布

* 修改模型方向,在yaml中添加变换属性

* 添加物料tf变化时,发送topic到前端

另外修改了物料初始化的方法,防止在tf还未发布时提前建立物料模型与发布话题

* 添加关节发布节点与物料可视化节点进入unilab

* 使用json启动plr与3D模型仿真

* 完成启动OT并联动rviz

* add 3d visualization

* 完成在main中启动设备可视化

完成在main中启动设备可视化,并输出物料ID:mesh的对应关系resource_model

添加物料模型管理类,遍历物料与resource_model,完成TF数据收集

* 完成TF发布

* 修改模型方向,在yaml中添加变换属性

* 添加物料tf变化时,发送topic到前端

另外修改了物料初始化的方法,防止在tf还未发布时提前建立物料模型与发布话题

* 添加关节发布节点与物料可视化节点进入unilab

* 使用json启动plr与3D模型仿真

* 完成启动OT并联动rviz

* 修复rviz位置问题,

修复rviz位置问题,
在无tf变动时减缓发送频率
在backend中添加物料跟随方法

* fix: running logic

* fix: running logic

* fix: missing ot

* 在main中直接初始化republisher和物料的mesh节点

* 将joint_republisher和resource_mesh_manager添加进 main_slave_run.py中

* 编写mesh添加action

* add action

* fix

* fix: browser on rviz

* fix: cloud bridge error fallback to local

* fix: salve auto run rviz

* 初始化两个plate

---------

Co-authored-by: zhangshixiang <@zhangshixiang>
Co-authored-by: wznln <18435084+Xuwznln@users.noreply.github.com>

* fix: multi channel

* fix: aspirate

* fix: aspirate

* fix: aspirate

* fix: aspirate

* 提交

* fix: jobadd

* fix: jobadd

* fix: msg converter

* tijiao

* add resource creat easy action

* identify debug msg

* mq client id

* unify liquid_handler definition

* Update virtual_device.yaml

* 更正了stir和heater的连接方式

* 区分了虚拟仪器中的八通阀和电磁阀,添加了两个阀门的驱动

* 修改了add protocol

* 修复了阀门更新版的bug

* 修复了添加protocol前缀导致的不能启动的bug

* Fix handles

* bump version to 0.9.6

* add resource edge upload

* update container registry and handles

* add virtual_separator virtual_rotavap
fix transfer_pump

* fix container value
add parent_name to edge device id

* 大图的问题都修复好了,添加了gassource和vacuum pump的驱动以及注册表

* default resource upload mode is false

* 添加了icon的文件名在注册表里面

* 修改了json图中link的格式

* fix resource and edge upload

* fix device ports

* Fix edge id

* 移除device的父节点关联

* separate registry sync and resource_add

* 默认不进行注册表报送,通过命令unilabos-register或者增加启动参数

* 完善tip

* protocol node不再嵌套显示

* bump version to 0.9.7  新增一个测试PumpTransferProtocol的teststation,亲测可以运行,将八通阀们和转移泵与pump_protocol适配

* protocol node 执行action不应携带自身device id

* 添加了一套简易双八通阀工作站JSON,亲测能跑

* 修复了很多protocol,亲测能跑

* 添加了run column和filter through的protocol,亲测能跑

* fix mock_reactor

* 修改了大图和小图的json,但是在前端上没看到改变

---------

Co-authored-by: Harvey Que <Q-Query@outlook.com>
Co-authored-by: wznln <18435084+Xuwznln@users.noreply.github.com>
Co-authored-by: zhangshixiang <@zhangshixiang>
Co-authored-by: q434343 <73513873+q434343@users.noreply.github.com>
Co-authored-by: Junhan Chang <changjh@pku.edu.cn>
2025-06-22 12:59:59 +08:00
Xuwznln
f7db8d17c5 container 添加和更新完成 2025-06-15 17:37:38 +08:00
Xuwznln
a354965f8e Merge branch 'dev' of https://github.com/dptech-corp/Uni-Lab-OS into dev 2025-06-15 12:51:48 +08:00
Xuwznln
934276d2f7 create container 2025-06-15 12:51:37 +08:00
Harvey Que
803809480b hotfix: Add .certs in .gitignore 2025-06-15 09:09:06 +08:00
Xuwznln
5478ba3237 test artifacts 2025-06-13 14:13:41 +08:00
Xuwznln
49f1aa9c28 try build 2025-06-13 14:05:58 +08:00
Xuwznln
d5d516f0ef try build fix 2025-06-13 13:52:45 +08:00
Xuwznln
4471fed4b8 测试自动构建 2025-06-13 13:47:08 +08:00
Xuwznln
30d143e1a5 Merge branch 'dev' of https://github.com/dptech-corp/Uni-Lab-OS into dev 2025-06-13 13:12:46 +08:00
Xuwznln
75ea45f21e include device_mesh when pip install 2025-06-13 00:32:15 +08:00
hh.
66af337d6c hotfix: Add macos_sdk_config (#46)
Co-authored-by: quehh <scienceol@outlook.com>
2025-06-12 22:46:44 +08:00
Xuwznln
ae3c65c1d3 Merge remote-tracking branch 'origin/main' into dev
# Conflicts:
#	README.md
#	README_zh.md
#	recipes/ros-humble-unilabos-msgs/recipe.yaml
#	recipes/unilabos/recipe.yaml
#	setup.py
#	unilabos/compile/pump_protocol.py
#	unilabos/registry/devices/pump_and_valve.yaml
#	unilabos/ros/nodes/presets/protocol_node.py
2025-06-12 21:27:07 +08:00
Xuwznln
11e4f053f1 bump version & protocol fix 2025-06-12 21:21:25 +08:00
Kongchang Feng
96f37b3b0d Add Mock Device for Organic Synthesis\添加有机合成的虚拟仪器和Protocol (#43)
* Add Device MockChiller

Add device MockChiller

* Add Device MockFilter

* Add Device MockPump

* Add Device MockRotavap

* Add Device MockSeparator

* Add Device MockStirrer

* Add Device MockHeater

* Add Device MockVacuum

* Add Device MockSolenoidValve

* Add Device Mock \_init_.py

* 规范模拟设备代码与注册表信息

* 更改Mock大写文件夹名

* 删除大写目录

* Edited Mock device json

* Match mock device with action

* Edit mock device yaml

* Add new action

* Add Virtual Device, Action, YAML, Protocol for Organic Syn

* 单独分类测试的protocol文件夹

* 更名Action

---------

Co-authored-by: Xuwznln <18435084+Xuwznln@users.noreply.github.com>
2025-06-12 20:58:39 +08:00
Xuwznln
d7d0a27976 Device visualization (#42)
* Update README and MQTTClient for installation instructions and code improvements

* feat: 支持local_config启动
add: 增加对crt path的说明,为传入config.py的相对路径
move: web component

* add: registry description

* add 3d visualization

* 完成在main中启动设备可视化

完成在main中启动设备可视化,并输出物料ID:mesh的对应关系resource_model

添加物料模型管理类,遍历物料与resource_model,完成TF数据收集

* 完成TF发布

* 修改模型方向,在yaml中添加变换属性

* 添加物料tf变化时,发送topic到前端

另外修改了物料初始化的方法,防止在tf还未发布时提前建立物料模型与发布话题

* 添加关节发布节点与物料可视化节点进入unilab

* 使用json启动plr与3D模型仿真

* feat: node_info_update srv
fix: OTDeck cant create

* close #12
feat: slave node registry

* feat: show machine name
fix: host node registry not uploaded

* feat: add hplc registry

* feat: add hplc registry

* fix: hplc status typo

* fix: devices/

* 完成启动OT并联动rviz

* add 3d visualization

* 完成在main中启动设备可视化

完成在main中启动设备可视化,并输出物料ID:mesh的对应关系resource_model

添加物料模型管理类,遍历物料与resource_model,完成TF数据收集

* 完成TF发布

* 修改模型方向,在yaml中添加变换属性

* 添加物料tf变化时,发送topic到前端

另外修改了物料初始化的方法,防止在tf还未发布时提前建立物料模型与发布话题

* 添加关节发布节点与物料可视化节点进入unilab

* 使用json启动plr与3D模型仿真

* 完成启动OT并联动rviz

* fix: device.class possible null

* fix: HPLC additions with online service

* fix: slave mode spin not working

* fix: slave mode spin not working

* 修复rviz位置问题,

修复rviz位置问题,
在无tf变动时减缓发送频率
在backend中添加物料跟随方法

* feat: 多ProtocolNode 允许子设备ID相同
feat: 上报发现的ActionClient
feat: Host重启动,通过discover机制要求slaveNode重新注册,实现信息及时上报

* feat: 支持env设置config

* fix: running logic

* fix: running logic

* fix: missing ot

* 在main中直接初始化republisher和物料的mesh节点

* 将joint_republisher和resource_mesh_manager添加进 main_slave_run.py中

* Device visualization (#14)

* add 3d visualization

* 完成在main中启动设备可视化

完成在main中启动设备可视化,并输出物料ID:mesh的对应关系resource_model

添加物料模型管理类,遍历物料与resource_model,完成TF数据收集

* 完成TF发布

* 修改模型方向,在yaml中添加变换属性

* 添加物料tf变化时,发送topic到前端

另外修改了物料初始化的方法,防止在tf还未发布时提前建立物料模型与发布话题

* 添加关节发布节点与物料可视化节点进入unilab

* 使用json启动plr与3D模型仿真

* 完成启动OT并联动rviz

* add 3d visualization

* 完成在main中启动设备可视化

完成在main中启动设备可视化,并输出物料ID:mesh的对应关系resource_model

添加物料模型管理类,遍历物料与resource_model,完成TF数据收集

* 完成TF发布

* 修改模型方向,在yaml中添加变换属性

* 添加物料tf变化时,发送topic到前端

另外修改了物料初始化的方法,防止在tf还未发布时提前建立物料模型与发布话题

* 添加关节发布节点与物料可视化节点进入unilab

* 使用json启动plr与3D模型仿真

* 完成启动OT并联动rviz

* 修复rviz位置问题,

修复rviz位置问题,
在无tf变动时减缓发送频率
在backend中添加物料跟随方法

* fix: running logic

* fix: running logic

* fix: missing ot

* 在main中直接初始化republisher和物料的mesh节点

* 将joint_republisher和resource_mesh_manager添加进 main_slave_run.py中

---------

Co-authored-by: zhangshixiang <@zhangshixiang>
Co-authored-by: wznln <18435084+Xuwznln@users.noreply.github.com>

* fix: missing hostname in devices_names
fix: upload_file for model file

* fix: missing paho-mqtt package
bump version to 0.9.0

* fix startup
add ResourceCreateFromOuter.action

* fix type hint

* update actions

* update actions

* host node add_resource_from_outer
fix cmake list

* pass device config to device class

* add: bind_parent_ids to resource create action
fix: message convert string

* fix: host node should not be re_discovered

* feat: resource tracker support dict

* feat: add more necessary params

* feat: fix boolean null in registry action data

* feat: add outer resource

* 编写mesh添加action

* feat: append resource

* add action

* feat: vis 2d for plr

* fix

* fix: browser on rviz

* fix: cloud bridge error fallback to local

* fix: salve auto run rviz

* 初始化两个plate

* Device visualization (#22)

* add 3d visualization

* 完成在main中启动设备可视化

完成在main中启动设备可视化,并输出物料ID:mesh的对应关系resource_model

添加物料模型管理类,遍历物料与resource_model,完成TF数据收集

* 完成TF发布

* 修改模型方向,在yaml中添加变换属性

* 添加物料tf变化时,发送topic到前端

另外修改了物料初始化的方法,防止在tf还未发布时提前建立物料模型与发布话题

* 添加关节发布节点与物料可视化节点进入unilab

* 使用json启动plr与3D模型仿真

* 完成启动OT并联动rviz

* add 3d visualization

* 完成在main中启动设备可视化

完成在main中启动设备可视化,并输出物料ID:mesh的对应关系resource_model

添加物料模型管理类,遍历物料与resource_model,完成TF数据收集

* 完成TF发布

* 修改模型方向,在yaml中添加变换属性

* 添加物料tf变化时,发送topic到前端

另外修改了物料初始化的方法,防止在tf还未发布时提前建立物料模型与发布话题

* 添加关节发布节点与物料可视化节点进入unilab

* 使用json启动plr与3D模型仿真

* 完成启动OT并联动rviz

* 修复rviz位置问题,

修复rviz位置问题,
在无tf变动时减缓发送频率
在backend中添加物料跟随方法

* fix: running logic

* fix: running logic

* fix: missing ot

* 在main中直接初始化republisher和物料的mesh节点

* 将joint_republisher和resource_mesh_manager添加进 main_slave_run.py中

* 编写mesh添加action

* add action

* fix

* fix: browser on rviz

* fix: cloud bridge error fallback to local

* fix: salve auto run rviz

* 初始化两个plate

---------

Co-authored-by: zhangshixiang <@zhangshixiang>
Co-authored-by: wznln <18435084+Xuwznln@users.noreply.github.com>

* fix: multi channel

* fix: aspirate

* fix: aspirate

* fix: aspirate

* fix: aspirate

* 提交

* fix: jobadd

* fix: jobadd

* fix: msg converter

* tijiao

* add resource creat easy action

* identify debug msg

* mq client id

* 提取lh的joint发布

* unify liquid_handler definition

* 修改物料跟随与物料添加逻辑

修改物料跟随与物料添加逻辑
将joint_publisher类移出lh的backends,但仍需要对lh的backends进行一些改写

* Revert "修改物料跟随与物料添加逻辑"

This reverts commit 498c997ad7.

* Reapply "修改物料跟随与物料添加逻辑"

This reverts commit 3a60d2ae81.

* Revert "Merge remote-tracking branch 'upstream/dev' into device_visualization"

This reverts commit fa727220af, reversing
changes made to 498c997ad7.

* 修改物料放下时的方法,如果选择

修改物料放下时的方法,
如果选择drop_trash,则删除物料显示
如果选择drop,则让其解除连接

* unilab添加moveit启动

1,整合所有moveit节点到一个move_group中,并整合所有的controller依次激活
2,添加pymoveit2的节点,使用json可直接启动
3,修改机械臂规划方式,添加约束,让冗余关节不会进行过多移动

* 修改物体attach时,多次赋值当前时间导致卡顿问题,

* Revert "修改物体attach时,多次赋值当前时间导致卡顿问题,"

This reverts commit 56d45b94f5.

* Reapply "修改物体attach时,多次赋值当前时间导致卡顿问题,"

This reverts commit 07d9db20c3.

* 添加缺少物料:"plate_well_G12",

* add

* fix tip resource data

* liquid states

* change to debug level

* Revert "change to debug level"

This reverts commit 5d9953c3e5.

* Reapply "change to debug level"

This reverts commit 2487bb6ffc.

* fix tip resource data

* add full device

* add moveit yaml

* 修复moveit
增加post_init阶段,给予ros_node反向

* remove necessary node

* fix moveit action client

* remove necessary imports

* Update moveit_interface.py

* fix handler_key uppercase

* json add liquids

* fix setup

* add

* change to "sources" and "targets" for lh

* bump version

* remove parent's parent link

* change arm's name

* change name

* fix ik error

---------

Co-authored-by: Harvey Que <Q-Query@outlook.com>
Co-authored-by: zhangshixiang <@zhangshixiang>
Co-authored-by: q434343 <73513873+q434343@users.noreply.github.com>
Co-authored-by: Junhan Chang <changjh@pku.edu.cn>
2025-06-12 20:58:18 +08:00
Xuwznln
34151f5cb2 补充日志 2025-06-10 22:13:35 +08:00
Xuwznln
369a21b904 调整protocol node以更好支持多种类型的read和write 2025-06-10 21:54:23 +08:00
Xuwznln
90169981c1 增加modbus支持
调整protocol node以更好支持多种类型的read和write
2025-06-10 21:46:49 +08:00
Xuwznln
d297abfd19 bump ver
modify slot type
2025-06-10 03:46:28 +08:00
Xuwznln
9c515a252a create_resource 2025-06-10 02:55:29 +08:00
Xuwznln
ea5e7a5ce2 Merge branch '37-biomek-i5i7' into dev
# Conflicts:
#	README.md
#	README_zh.md
#	recipes/ros-humble-unilabos-msgs/recipe.yaml
#	recipes/unilabos/recipe.yaml
#	setup.py
#	unilabos/devices/liquid_handling/biomek.py
#	unilabos/devices/liquid_handling/biomek_test.py
#	unilabos/registry/devices/liquid_handler.yaml
#	unilabos/registry/registry.py
#	unilabos/ros/msgs/message_converter.py
#	unilabos_msgs/action/LiquidHandlerMoveBiomek.action
#	unilabos_msgs/action/LiquidHandlerTransferBiomek.action
2025-06-10 02:00:43 +08:00
Xuwznln
2e9a0a4677 fix move it 2025-06-10 01:55:39 +08:00
Xuwznln
4c7aa8a89a fix move it 2025-06-10 01:53:58 +08:00
Xuwznln
d8a0c5e715 Device visualization (#41)
* Update README and MQTTClient for installation instructions and code improvements

* feat: 支持local_config启动
add: 增加对crt path的说明,为传入config.py的相对路径
move: web component

* add: registry description

* add 3d visualization

* 完成在main中启动设备可视化

完成在main中启动设备可视化,并输出物料ID:mesh的对应关系resource_model

添加物料模型管理类,遍历物料与resource_model,完成TF数据收集

* 完成TF发布

* 修改模型方向,在yaml中添加变换属性

* 添加物料tf变化时,发送topic到前端

另外修改了物料初始化的方法,防止在tf还未发布时提前建立物料模型与发布话题

* 添加关节发布节点与物料可视化节点进入unilab

* 使用json启动plr与3D模型仿真

* feat: node_info_update srv
fix: OTDeck cant create

* close #12
feat: slave node registry

* feat: show machine name
fix: host node registry not uploaded

* feat: add hplc registry

* feat: add hplc registry

* fix: hplc status typo

* fix: devices/

* 完成启动OT并联动rviz

* add 3d visualization

* 完成在main中启动设备可视化

完成在main中启动设备可视化,并输出物料ID:mesh的对应关系resource_model

添加物料模型管理类,遍历物料与resource_model,完成TF数据收集

* 完成TF发布

* 修改模型方向,在yaml中添加变换属性

* 添加物料tf变化时,发送topic到前端

另外修改了物料初始化的方法,防止在tf还未发布时提前建立物料模型与发布话题

* 添加关节发布节点与物料可视化节点进入unilab

* 使用json启动plr与3D模型仿真

* 完成启动OT并联动rviz

* fix: device.class possible null

* fix: HPLC additions with online service

* fix: slave mode spin not working

* fix: slave mode spin not working

* 修复rviz位置问题,

修复rviz位置问题,
在无tf变动时减缓发送频率
在backend中添加物料跟随方法

* feat: 多ProtocolNode 允许子设备ID相同
feat: 上报发现的ActionClient
feat: Host重启动,通过discover机制要求slaveNode重新注册,实现信息及时上报

* feat: 支持env设置config

* fix: running logic

* fix: running logic

* fix: missing ot

* 在main中直接初始化republisher和物料的mesh节点

* 将joint_republisher和resource_mesh_manager添加进 main_slave_run.py中

* Device visualization (#14)

* add 3d visualization

* 完成在main中启动设备可视化

完成在main中启动设备可视化,并输出物料ID:mesh的对应关系resource_model

添加物料模型管理类,遍历物料与resource_model,完成TF数据收集

* 完成TF发布

* 修改模型方向,在yaml中添加变换属性

* 添加物料tf变化时,发送topic到前端

另外修改了物料初始化的方法,防止在tf还未发布时提前建立物料模型与发布话题

* 添加关节发布节点与物料可视化节点进入unilab

* 使用json启动plr与3D模型仿真

* 完成启动OT并联动rviz

* add 3d visualization

* 完成在main中启动设备可视化

完成在main中启动设备可视化,并输出物料ID:mesh的对应关系resource_model

添加物料模型管理类,遍历物料与resource_model,完成TF数据收集

* 完成TF发布

* 修改模型方向,在yaml中添加变换属性

* 添加物料tf变化时,发送topic到前端

另外修改了物料初始化的方法,防止在tf还未发布时提前建立物料模型与发布话题

* 添加关节发布节点与物料可视化节点进入unilab

* 使用json启动plr与3D模型仿真

* 完成启动OT并联动rviz

* 修复rviz位置问题,

修复rviz位置问题,
在无tf变动时减缓发送频率
在backend中添加物料跟随方法

* fix: running logic

* fix: running logic

* fix: missing ot

* 在main中直接初始化republisher和物料的mesh节点

* 将joint_republisher和resource_mesh_manager添加进 main_slave_run.py中

---------

Co-authored-by: zhangshixiang <@zhangshixiang>
Co-authored-by: wznln <18435084+Xuwznln@users.noreply.github.com>

* fix: missing hostname in devices_names
fix: upload_file for model file

* fix: missing paho-mqtt package
bump version to 0.9.0

* fix startup
add ResourceCreateFromOuter.action

* fix type hint

* update actions

* update actions

* host node add_resource_from_outer
fix cmake list

* pass device config to device class

* add: bind_parent_ids to resource create action
fix: message convert string

* fix: host node should not be re_discovered

* feat: resource tracker support dict

* feat: add more necessary params

* feat: fix boolean null in registry action data

* feat: add outer resource

* 编写mesh添加action

* feat: append resource

* add action

* feat: vis 2d for plr

* fix

* fix: browser on rviz

* fix: cloud bridge error fallback to local

* fix: salve auto run rviz

* 初始化两个plate

* Device visualization (#22)

* add 3d visualization

* 完成在main中启动设备可视化

完成在main中启动设备可视化,并输出物料ID:mesh的对应关系resource_model

添加物料模型管理类,遍历物料与resource_model,完成TF数据收集

* 完成TF发布

* 修改模型方向,在yaml中添加变换属性

* 添加物料tf变化时,发送topic到前端

另外修改了物料初始化的方法,防止在tf还未发布时提前建立物料模型与发布话题

* 添加关节发布节点与物料可视化节点进入unilab

* 使用json启动plr与3D模型仿真

* 完成启动OT并联动rviz

* add 3d visualization

* 完成在main中启动设备可视化

完成在main中启动设备可视化,并输出物料ID:mesh的对应关系resource_model

添加物料模型管理类,遍历物料与resource_model,完成TF数据收集

* 完成TF发布

* 修改模型方向,在yaml中添加变换属性

* 添加物料tf变化时,发送topic到前端

另外修改了物料初始化的方法,防止在tf还未发布时提前建立物料模型与发布话题

* 添加关节发布节点与物料可视化节点进入unilab

* 使用json启动plr与3D模型仿真

* 完成启动OT并联动rviz

* 修复rviz位置问题,

修复rviz位置问题,
在无tf变动时减缓发送频率
在backend中添加物料跟随方法

* fix: running logic

* fix: running logic

* fix: missing ot

* 在main中直接初始化republisher和物料的mesh节点

* 将joint_republisher和resource_mesh_manager添加进 main_slave_run.py中

* 编写mesh添加action

* add action

* fix

* fix: browser on rviz

* fix: cloud bridge error fallback to local

* fix: salve auto run rviz

* 初始化两个plate

---------

Co-authored-by: zhangshixiang <@zhangshixiang>
Co-authored-by: wznln <18435084+Xuwznln@users.noreply.github.com>

* fix: multi channel

* fix: aspirate

* fix: aspirate

* fix: aspirate

* fix: aspirate

* 提交

* fix: jobadd

* fix: jobadd

* fix: msg converter

* tijiao

* add resource creat easy action

* identify debug msg

* mq client id

* 提取lh的joint发布

* unify liquid_handler definition

* 修改物料跟随与物料添加逻辑

修改物料跟随与物料添加逻辑
将joint_publisher类移出lh的backends,但仍需要对lh的backends进行一些改写

* Revert "修改物料跟随与物料添加逻辑"

This reverts commit 498c997ad7.

* Reapply "修改物料跟随与物料添加逻辑"

This reverts commit 3a60d2ae81.

* Revert "Merge remote-tracking branch 'upstream/dev' into device_visualization"

This reverts commit fa727220af, reversing
changes made to 498c997ad7.

* 修改物料放下时的方法,如果选择

修改物料放下时的方法,
如果选择drop_trash,则删除物料显示
如果选择drop,则让其解除连接

* add biomek.py demo implementation

* 更新LiquidHandlerBiomek类,添加资源创建功能,优化协议创建方法,修复部分代码格式问题,更新YAML配置以支持新功能。

* Test

* fix biomek success type

* Convert LH action to biomek.

* Update biomek.py

* 注册表上报handle和schema (param input)

* 修复biomek缺少的字段

* delete 's'

* Remove warnings

* Update biomek.py

* Biomek test

* Update biomek.py

* 新增transfer_biomek的msg

* New transfer_biomek

* Updated transfer_biomek

* 更新transfer_biomek的msg

* 更新transfer_biomek的msg

* 支持Biomek创建

* new action

* fix key name typo

* New parameter for biomek to run.

* Refine

* Update

* new actions

* new actions

* 1

* registry

* fix biomek startup
add action handles

* fix handles not as default entry

* unilab添加moveit启动

1,整合所有moveit节点到一个move_group中,并整合所有的controller依次激活
2,添加pymoveit2的节点,使用json可直接启动
3,修改机械臂规划方式,添加约束,让冗余关节不会进行过多移动

* biomek_test.py

biomek_test.py是最新的版本,运行它会生成complete_biomek_protocol.json

* Update biomek.py

* biomek_test.py

* fix liquid_handler.biomek handles

* 修改物体attach时,多次赋值当前时间导致卡顿问题,

* Revert "修改物体attach时,多次赋值当前时间导致卡顿问题,"

This reverts commit 56d45b94f5.

* Reapply "修改物体attach时,多次赋值当前时间导致卡顿问题,"

This reverts commit 07d9db20c3.

* 添加缺少物料:"plate_well_G12",

* host node新增resource add时间统计
create_resource新增handle
bump version to 0.9.2

* 修正物料上传时间
改用biomek_test
增加ResultInfoEncoder
支持返回结果上传

* 正确发送return_info结果

* 同步执行状态信息

* 取消raiseValueError提示

* Update biomek_test.py

* 0608 DONE

* 同步了Biomek.py 现在应可用

* biomek switch back to non-test

* temp disable initialize resource

* add

* fix tip resource data

* liquid states

* change to debug level

* Revert "change to debug level"

This reverts commit 5d9953c3e5.

* Reapply "change to debug level"

This reverts commit 2487bb6ffc.

* fix tip resource data

* add full device

* add moveit yaml

* 修复moveit
增加post_init阶段,给予ros_node反向

* remove necessary node

* fix moveit action client

* remove necessary imports

* Update moveit_interface.py

* fix handler_key uppercase

* json add liquids

* fix setup

* add

* change to "sources" and "targets" for lh

* bump version

* remove parent's parent link

* change arm's name

* change name

---------

Co-authored-by: Harvey Que <Q-Query@outlook.com>
Co-authored-by: zhangshixiang <@zhangshixiang>
Co-authored-by: q434343 <73513873+q434343@users.noreply.github.com>
Co-authored-by: Junhan Chang <changjh@pku.edu.cn>
Co-authored-by: Guangxin Zhang <guangxin.zhang.bio@gmail.com>
Co-authored-by: qxw138 <qxw@stu.pku.edu.cn>
2025-06-10 01:28:09 +08:00
q434343
133ffaac17 Device visualization (#39)
* Update README and MQTTClient for installation instructions and code improvements

* feat: 支持local_config启动
add: 增加对crt path的说明,为传入config.py的相对路径
move: web component

* add: registry description

* add 3d visualization

* 完成在main中启动设备可视化

完成在main中启动设备可视化,并输出物料ID:mesh的对应关系resource_model

添加物料模型管理类,遍历物料与resource_model,完成TF数据收集

* 完成TF发布

* 修改模型方向,在yaml中添加变换属性

* 添加物料tf变化时,发送topic到前端

另外修改了物料初始化的方法,防止在tf还未发布时提前建立物料模型与发布话题

* 添加关节发布节点与物料可视化节点进入unilab

* 使用json启动plr与3D模型仿真

* feat: node_info_update srv
fix: OTDeck cant create

* close #12
feat: slave node registry

* feat: show machine name
fix: host node registry not uploaded

* feat: add hplc registry

* feat: add hplc registry

* fix: hplc status typo

* fix: devices/

* 完成启动OT并联动rviz

* add 3d visualization

* 完成在main中启动设备可视化

完成在main中启动设备可视化,并输出物料ID:mesh的对应关系resource_model

添加物料模型管理类,遍历物料与resource_model,完成TF数据收集

* 完成TF发布

* 修改模型方向,在yaml中添加变换属性

* 添加物料tf变化时,发送topic到前端

另外修改了物料初始化的方法,防止在tf还未发布时提前建立物料模型与发布话题

* 添加关节发布节点与物料可视化节点进入unilab

* 使用json启动plr与3D模型仿真

* 完成启动OT并联动rviz

* fix: device.class possible null

* fix: HPLC additions with online service

* fix: slave mode spin not working

* fix: slave mode spin not working

* 修复rviz位置问题,

修复rviz位置问题,
在无tf变动时减缓发送频率
在backend中添加物料跟随方法

* feat: 多ProtocolNode 允许子设备ID相同
feat: 上报发现的ActionClient
feat: Host重启动,通过discover机制要求slaveNode重新注册,实现信息及时上报

* feat: 支持env设置config

* fix: running logic

* fix: running logic

* fix: missing ot

* 在main中直接初始化republisher和物料的mesh节点

* 将joint_republisher和resource_mesh_manager添加进 main_slave_run.py中

* Device visualization (#14)

* add 3d visualization

* 完成在main中启动设备可视化

完成在main中启动设备可视化,并输出物料ID:mesh的对应关系resource_model

添加物料模型管理类,遍历物料与resource_model,完成TF数据收集

* 完成TF发布

* 修改模型方向,在yaml中添加变换属性

* 添加物料tf变化时,发送topic到前端

另外修改了物料初始化的方法,防止在tf还未发布时提前建立物料模型与发布话题

* 添加关节发布节点与物料可视化节点进入unilab

* 使用json启动plr与3D模型仿真

* 完成启动OT并联动rviz

* add 3d visualization

* 完成在main中启动设备可视化

完成在main中启动设备可视化,并输出物料ID:mesh的对应关系resource_model

添加物料模型管理类,遍历物料与resource_model,完成TF数据收集

* 完成TF发布

* 修改模型方向,在yaml中添加变换属性

* 添加物料tf变化时,发送topic到前端

另外修改了物料初始化的方法,防止在tf还未发布时提前建立物料模型与发布话题

* 添加关节发布节点与物料可视化节点进入unilab

* 使用json启动plr与3D模型仿真

* 完成启动OT并联动rviz

* 修复rviz位置问题,

修复rviz位置问题,
在无tf变动时减缓发送频率
在backend中添加物料跟随方法

* fix: running logic

* fix: running logic

* fix: missing ot

* 在main中直接初始化republisher和物料的mesh节点

* 将joint_republisher和resource_mesh_manager添加进 main_slave_run.py中

---------

Co-authored-by: zhangshixiang <@zhangshixiang>
Co-authored-by: wznln <18435084+Xuwznln@users.noreply.github.com>

* fix: missing hostname in devices_names
fix: upload_file for model file

* fix: missing paho-mqtt package
bump version to 0.9.0

* fix startup
add ResourceCreateFromOuter.action

* fix type hint

* update actions

* update actions

* host node add_resource_from_outer
fix cmake list

* pass device config to device class

* add: bind_parent_ids to resource create action
fix: message convert string

* fix: host node should not be re_discovered

* feat: resource tracker support dict

* feat: add more necessary params

* feat: fix boolean null in registry action data

* feat: add outer resource

* 编写mesh添加action

* feat: append resource

* add action

* feat: vis 2d for plr

* fix

* fix: browser on rviz

* fix: cloud bridge error fallback to local

* fix: salve auto run rviz

* 初始化两个plate

* Device visualization (#22)

* add 3d visualization

* 完成在main中启动设备可视化

完成在main中启动设备可视化,并输出物料ID:mesh的对应关系resource_model

添加物料模型管理类,遍历物料与resource_model,完成TF数据收集

* 完成TF发布

* 修改模型方向,在yaml中添加变换属性

* 添加物料tf变化时,发送topic到前端

另外修改了物料初始化的方法,防止在tf还未发布时提前建立物料模型与发布话题

* 添加关节发布节点与物料可视化节点进入unilab

* 使用json启动plr与3D模型仿真

* 完成启动OT并联动rviz

* add 3d visualization

* 完成在main中启动设备可视化

完成在main中启动设备可视化,并输出物料ID:mesh的对应关系resource_model

添加物料模型管理类,遍历物料与resource_model,完成TF数据收集

* 完成TF发布

* 修改模型方向,在yaml中添加变换属性

* 添加物料tf变化时,发送topic到前端

另外修改了物料初始化的方法,防止在tf还未发布时提前建立物料模型与发布话题

* 添加关节发布节点与物料可视化节点进入unilab

* 使用json启动plr与3D模型仿真

* 完成启动OT并联动rviz

* 修复rviz位置问题,

修复rviz位置问题,
在无tf变动时减缓发送频率
在backend中添加物料跟随方法

* fix: running logic

* fix: running logic

* fix: missing ot

* 在main中直接初始化republisher和物料的mesh节点

* 将joint_republisher和resource_mesh_manager添加进 main_slave_run.py中

* 编写mesh添加action

* add action

* fix

* fix: browser on rviz

* fix: cloud bridge error fallback to local

* fix: salve auto run rviz

* 初始化两个plate

---------

Co-authored-by: zhangshixiang <@zhangshixiang>
Co-authored-by: wznln <18435084+Xuwznln@users.noreply.github.com>

* fix: multi channel

* fix: aspirate

* fix: aspirate

* fix: aspirate

* fix: aspirate

* 提交

* fix: jobadd

* fix: jobadd

* fix: msg converter

* tijiao

* add resource creat easy action

* identify debug msg

* mq client id

* 提取lh的joint发布

* unify liquid_handler definition

* 修改物料跟随与物料添加逻辑

修改物料跟随与物料添加逻辑
将joint_publisher类移出lh的backends,但仍需要对lh的backends进行一些改写

* Revert "修改物料跟随与物料添加逻辑"

This reverts commit 498c997ad7.

* Reapply "修改物料跟随与物料添加逻辑"

This reverts commit 3a60d2ae81.

* Revert "Merge remote-tracking branch 'upstream/dev' into device_visualization"

This reverts commit fa727220af, reversing
changes made to 498c997ad7.

* 修改物料放下时的方法,如果选择

修改物料放下时的方法,
如果选择drop_trash,则删除物料显示
如果选择drop,则让其解除连接

* add biomek.py demo implementation

* 更新LiquidHandlerBiomek类,添加资源创建功能,优化协议创建方法,修复部分代码格式问题,更新YAML配置以支持新功能。

* Test

* fix biomek success type

* Convert LH action to biomek.

* Update biomek.py

* 注册表上报handle和schema (param input)

* 修复biomek缺少的字段

* delete 's'

* Remove warnings

* Update biomek.py

* Biomek test

* Update biomek.py

* 新增transfer_biomek的msg

* New transfer_biomek

* Updated transfer_biomek

* 更新transfer_biomek的msg

* 更新transfer_biomek的msg

* 支持Biomek创建

* new action

* fix key name typo

* New parameter for biomek to run.

* Refine

* Update

* new actions

* new actions

* 1

* registry

* fix biomek startup
add action handles

* fix handles not as default entry

* unilab添加moveit启动

1,整合所有moveit节点到一个move_group中,并整合所有的controller依次激活
2,添加pymoveit2的节点,使用json可直接启动
3,修改机械臂规划方式,添加约束,让冗余关节不会进行过多移动

* biomek_test.py

biomek_test.py是最新的版本,运行它会生成complete_biomek_protocol.json

* Update biomek.py

* biomek_test.py

* fix liquid_handler.biomek handles

* 修改物体attach时,多次赋值当前时间导致卡顿问题,

* Revert "修改物体attach时,多次赋值当前时间导致卡顿问题,"

This reverts commit 56d45b94f5.

* Reapply "修改物体attach时,多次赋值当前时间导致卡顿问题,"

This reverts commit 07d9db20c3.

* 添加缺少物料:"plate_well_G12",

* host node新增resource add时间统计
create_resource新增handle
bump version to 0.9.2

* 修正物料上传时间
改用biomek_test
增加ResultInfoEncoder
支持返回结果上传

* 正确发送return_info结果

* 同步执行状态信息

* 取消raiseValueError提示

* Update biomek_test.py

* 0608 DONE

* 同步了Biomek.py 现在应可用

* biomek switch back to non-test

* temp disable initialize resource

* add

* fix tip resource data

* liquid states

* change to debug level

* Revert "change to debug level"

This reverts commit 5d9953c3e5.

* Reapply "change to debug level"

This reverts commit 2487bb6ffc.

* fix tip resource data

* add full device

* add moveit yaml

* 修复moveit
增加post_init阶段,给予ros_node反向

* remove necessary node

* fix moveit action client

* remove necessary imports

* Update moveit_interface.py

* fix handler_key uppercase

* json add liquids

* fix setup

* add

* change to "sources" and "targets" for lh

* bump version

* remove parent's parent link

---------

Co-authored-by: Harvey Que <Q-Query@outlook.com>
Co-authored-by: wznln <18435084+Xuwznln@users.noreply.github.com>
Co-authored-by: zhangshixiang <@zhangshixiang>
Co-authored-by: Junhan Chang <changjh@pku.edu.cn>
Co-authored-by: Guangxin Zhang <guangxin.zhang.bio@gmail.com>
Co-authored-by: qxw138 <qxw@stu.pku.edu.cn>
2025-06-09 17:06:04 +08:00
Xuwznln
729a0fcf0c 37-biomek-i5i7 (#40)
* add biomek.py demo implementation

* 更新LiquidHandlerBiomek类,添加资源创建功能,优化协议创建方法,修复部分代码格式问题,更新YAML配置以支持新功能。

* Test

* fix biomek success type

* Convert LH action to biomek.

* Update biomek.py

* 注册表上报handle和schema (param input)

* 修复biomek缺少的字段

* delete 's'

* Remove warnings

* Update biomek.py

* Biomek test

* Update biomek.py

* 新增transfer_biomek的msg

* New transfer_biomek

* Updated transfer_biomek

* 更新transfer_biomek的msg

* 更新transfer_biomek的msg

* 支持Biomek创建

* new action

* fix key name typo

* New parameter for biomek to run.

* Refine

* Update

* new actions

* new actions

* 1

* registry

* fix biomek startup
add action handles

* fix handles not as default entry

* biomek_test.py

biomek_test.py是最新的版本,运行它会生成complete_biomek_protocol.json

* Update biomek.py

* biomek_test.py

* fix liquid_handler.biomek handles

* host node新增resource add时间统计
create_resource新增handle
bump version to 0.9.2

* 修正物料上传时间
改用biomek_test
增加ResultInfoEncoder
支持返回结果上传

* 正确发送return_info结果

* 同步执行状态信息

* 取消raiseValueError提示

* Update biomek_test.py

* 0608 DONE

* 同步了Biomek.py 现在应可用

* biomek switch back to non-test

* temp disable initialize resource

* Refine biomek

* Refine copy issue

* Refine

---------

Co-authored-by: Junhan Chang <changjh@pku.edu.cn>
Co-authored-by: Guangxin Zhang <guangxin.zhang.bio@gmail.com>
Co-authored-by: qxw138 <qxw@stu.pku.edu.cn>
2025-06-09 16:57:42 +08:00
Xuwznln
6ae77e0408 temp disable initialize resource 2025-06-08 17:07:48 +08:00
Xuwznln
bab4b1d67a biomek switch back to non-test 2025-06-08 17:05:48 +08:00
Guangxin Zhang
12c17ec26e 同步了Biomek.py 现在应可用 2025-06-08 16:58:19 +08:00
Guangxin Zhang
6577fe12eb 0608 DONE 2025-06-08 16:49:11 +08:00
qxw138
f1fee5fad9 Merge branch '37-biomek-i5i7' of https://github.com/dptech-corp/Uni-Lab-OS into 37-biomek-i5i7 2025-06-08 15:52:31 +08:00
qxw138
9b3377aedb Update biomek_test.py 2025-06-08 15:52:20 +08:00
Xuwznln
526327727d 取消raiseValueError提示 2025-06-08 15:34:56 +08:00
Xuwznln
aaa86314e3 同步执行状态信息 2025-06-08 15:34:16 +08:00
Xuwznln
6a14104e6b 正确发送return_info结果 2025-06-08 15:06:38 +08:00
Xuwznln
ab0c4b708b 修正物料上传时间
改用biomek_test
增加ResultInfoEncoder
支持返回结果上传
2025-06-08 14:43:07 +08:00
Xuwznln
c0b7f2decd host node新增resource add时间统计
create_resource新增handle
bump version to 0.9.2
2025-06-08 13:23:55 +08:00
Junhan Chang
b6c9530c61 Merge branch '37-biomek-i5i7' of https://github.com/dptech-corp/Uni-Lab-OS into 37-biomek-i5i7 2025-06-07 18:52:23 +08:00
Junhan Chang
8698821c52 fix liquid_handler.biomek handles 2025-06-07 18:52:20 +08:00
qxw138
3f53f88390 biomek_test.py 2025-06-07 15:21:20 +08:00
qxw138
e840516ba4 Update biomek.py 2025-06-06 22:50:11 +08:00
qxw138
146d8c5296 Merge branch '37-biomek-i5i7' of https://github.com/dptech-corp/Uni-Lab-OS into 37-biomek-i5i7 2025-06-06 22:49:35 +08:00
qxw138
6573c9e02e biomek_test.py
biomek_test.py是最新的版本,运行它会生成complete_biomek_protocol.json
2025-06-06 22:42:06 +08:00
Xuwznln
c7b9c6a825 fix handles not as default entry 2025-06-06 18:13:53 +08:00
Xuwznln
48c43d3303 fix biomek startup
add action handles
2025-06-06 17:45:54 +08:00
Xuwznln
55be5e8188 registry 2025-06-06 17:21:19 +08:00
qxw138
1b9f3c666d 1 2025-06-06 14:44:17 +08:00
qxw138
097114d38c new actions 2025-06-06 14:31:10 +08:00
qxw138
5bec899479 new actions 2025-06-06 13:56:39 +08:00
Guangxin Zhang
5e86112ebf Merge branch '37-biomek-i5i7' of https://github.com/dptech-corp/Uni-Lab-OS into 37-biomek-i5i7 2025-06-06 13:25:34 +08:00
Guangxin Zhang
24ecb13b79 Update 2025-06-06 13:22:15 +08:00
qxw138
2573d34713 Merge branch '37-biomek-i5i7' of https://github.com/dptech-corp/Uni-Lab-OS into 37-biomek-i5i7 2025-06-06 13:18:42 +08:00
Guangxin Zhang
106d71e1db Refine 2025-06-06 11:11:17 +08:00
Guangxin Zhang
3c2a4a64ac Merge branch '37-biomek-i5i7' of https://github.com/dptech-corp/Uni-Lab-OS into 37-biomek-i5i7 2025-06-06 11:11:10 +08:00
Guangxin Zhang
1e00a66a65 New parameter for biomek to run. 2025-06-06 11:05:36 +08:00
qxw138
46da42deef Merge branch '37-biomek-i5i7' of https://github.com/dptech-corp/Uni-Lab-OS into 37-biomek-i5i7 2025-06-06 00:13:11 +08:00
Xuwznln
101c1bc3cc fix key name typo 2025-06-05 22:15:57 +08:00
qxw138
a62112ae26 new action 2025-06-05 17:26:36 +08:00
Xuwznln
dd5a7cab75 支持Biomek创建 2025-06-05 16:04:44 +08:00
Xuwznln
39de3ac58e 更新transfer_biomek的msg 2025-06-05 15:41:16 +08:00
Xuwznln
b99969278c 更新transfer_biomek的msg 2025-06-05 15:30:51 +08:00
Guangxin Zhang
b957ad2f71 Merge branch '37-biomek-i5i7' of https://github.com/dptech-corp/Uni-Lab-OS into 37-biomek-i5i7 2025-06-04 21:49:27 +08:00
Guangxin Zhang
e1a7c3a103 Updated transfer_biomek 2025-06-04 21:49:22 +08:00
Guangxin Zhang
e63c15997c New transfer_biomek 2025-06-04 21:29:54 +08:00
Xuwznln
c5a495f409 新增transfer_biomek的msg 2025-06-04 19:03:00 +08:00
Guangxin Zhang
5b240cb0ea Update biomek.py 2025-06-04 17:30:53 +08:00
Guangxin Zhang
147b8f47c0 Biomek test 2025-06-04 16:38:18 +08:00
Guangxin Zhang
6d2489af5f Merge branch '37-biomek-i5i7' of https://github.com/dptech-corp/Uni-Lab-OS into 37-biomek-i5i7 2025-06-04 13:27:11 +08:00
Guangxin Zhang
807dcdd226 Update biomek.py 2025-06-04 13:27:05 +08:00
Guangxin Zhang
8a29bc5597 Remove warnings 2025-06-04 13:20:12 +08:00
Guangxin Zhang
6f6c70ee57 delete 's' 2025-06-04 13:11:45 +08:00
Xuwznln
478a85951c 修复biomek缺少的字段 2025-05-31 00:00:55 +08:00
Xuwznln
0f2555c90c 注册表上报handle和schema (param input) 2025-05-31 00:00:39 +08:00
Guangxin Zhang
d2dda6ee03 Merge branch '37-biomek-i5i7' of https://github.com/dptech-corp/Uni-Lab-OS into 37-biomek-i5i7 2025-05-30 17:11:23 +08:00
Guangxin Zhang
208540b307 Update biomek.py 2025-05-30 17:08:19 +08:00
Guangxin Zhang
cb7c56a1d9 Convert LH action to biomek. 2025-05-30 17:00:06 +08:00
Xuwznln
ea2e9c3e3a fix biomek success type 2025-05-30 16:50:13 +08:00
Guangxin Zhang
0452a68180 Test 2025-05-30 16:03:49 +08:00
Xuwznln
90a0f3db9b merge 2025-05-30 15:40:14 +08:00
Junhan Chang
055d120ba8 更新LiquidHandlerBiomek类,添加资源创建功能,优化协议创建方法,修复部分代码格式问题,更新YAML配置以支持新功能。 2025-05-30 15:38:23 +08:00
Junhan Chang
a948f09f60 add biomek.py demo implementation 2025-05-30 13:33:10 +08:00
68 changed files with 27603 additions and 3391 deletions

View File

@@ -49,7 +49,7 @@ conda env update --file unilabos-[YOUR_OS].yml -n environment_name
# Currently, you need to install the `unilabos_msgs` package # Currently, you need to install the `unilabos_msgs` package
# You can download the system-specific package from the Release page # You can download the system-specific package from the Release page
conda install ros-humble-unilabos-msgs-0.9.7-xxxxx.tar.bz2 conda install ros-humble-unilabos-msgs-0.9.8-xxxxx.tar.bz2
# Install PyLabRobot and other prerequisites # Install PyLabRobot and other prerequisites
git clone https://github.com/PyLabRobot/pylabrobot plr_repo git clone https://github.com/PyLabRobot/pylabrobot plr_repo

View File

@@ -49,7 +49,7 @@ conda env update --file unilabos-[YOUR_OS].yml -n 环境名
# 现阶段,需要安装 `unilabos_msgs` 包 # 现阶段,需要安装 `unilabos_msgs` 包
# 可以前往 Release 页面下载系统对应的包进行安装 # 可以前往 Release 页面下载系统对应的包进行安装
conda install ros-humble-unilabos-msgs-0.9.7-xxxxx.tar.bz2 conda install ros-humble-unilabos-msgs-0.9.8-xxxxx.tar.bz2
# 安装PyLabRobot等前置 # 安装PyLabRobot等前置
git clone https://github.com/PyLabRobot/pylabrobot plr_repo git clone https://github.com/PyLabRobot/pylabrobot plr_repo

View File

@@ -1,3 +1,6 @@
channel_sources:
- robostack,robostack-staging,conda-forge,defaults
gazebo: gazebo:
- '11' - '11'
libpqxx: libpqxx:

View File

@@ -1,6 +1,6 @@
package: package:
name: ros-humble-unilabos-msgs name: ros-humble-unilabos-msgs
version: 0.9.7 version: 0.9.8
source: source:
path: ../../unilabos_msgs path: ../../unilabos_msgs
folder: ros-humble-unilabos-msgs/src/work folder: ros-humble-unilabos-msgs/src/work

View File

@@ -1,6 +1,6 @@
package: package:
name: unilabos name: unilabos
version: "0.9.7" version: "0.9.8"
source: source:
path: ../.. path: ../..

View File

@@ -4,7 +4,7 @@ package_name = 'unilabos'
setup( setup(
name=package_name, name=package_name,
version='0.9.7', version='0.9.8',
packages=find_packages(), packages=find_packages(),
include_package_data=True, include_package_data=True,
install_requires=['setuptools'], install_requires=['setuptools'],

View File

@@ -0,0 +1,45 @@
{
"nodes": [
{
"id": "Camera",
"name": "摄像头",
"children": [
],
"parent": null,
"type": "device",
"class": "camera",
"position": {
"x": 0,
"y": 0,
"z": 0
},
"config": {
"camera_index": 0,
"period": 0.05
},
"data": {
}
},
{
"id": "Gripper1",
"name": "假夹爪",
"children": [
],
"parent": null,
"type": "device",
"class": "gripper.mock",
"position": {
"x": 0,
"y": 0,
"z": 0
},
"config": {
},
"data": {
}
}
],
"links": [
]
}

View File

@@ -23,6 +23,7 @@
HeatChillProtocol: generate_heat_chill_protocol, (√) HeatChillProtocol: generate_heat_chill_protocol, (√)
HeatChillStartProtocol: generate_heat_chill_start_protocol, (√) HeatChillStartProtocol: generate_heat_chill_start_protocol, (√)
HeatChillStopProtocol: generate_heat_chill_stop_protocol, (√) HeatChillStopProtocol: generate_heat_chill_stop_protocol, (√)
HeatChillToTempProtocol:
StirProtocol: generate_stir_protocol, (√) StirProtocol: generate_stir_protocol, (√)
StartStirProtocol: generate_start_stir_protocol, (√) StartStirProtocol: generate_start_stir_protocol, (√)
StopStirProtocol: generate_stop_stir_protocol, (√) StopStirProtocol: generate_stop_stir_protocol, (√)
@@ -30,7 +31,192 @@
CleanVesselProtocol: generate_clean_vessel_protocol, (√) CleanVesselProtocol: generate_clean_vessel_protocol, (√)
DissolveProtocol: generate_dissolve_protocol, (√) DissolveProtocol: generate_dissolve_protocol, (√)
FilterThroughProtocol: generate_filter_through_protocol, (√) FilterThroughProtocol: generate_filter_through_protocol, (√)
RunColumnProtocol: generate_run_column_protocol, (×) RunColumnProtocol: generate_run_column_protocol, (√)<RunColumn Rf="?" column="column" from_vessel="rotavap" ratio="5:95" solvent1="methanol" solvent2="chloroform" to_vessel="rotavap"/>
WashSolidProtocol: generate_wash_solid_protocol, (×)
上下文体积搜索 上下文体积搜索
3. 还没创建的protocol
ResetHandling 写完了 <ResetHandling solvent="methanol"/>
Dry 写完了 <Dry compound="product" vessel="filter"/>
AdjustPH 写完了 <AdjustPH pH="8.0" reagent="hydrochloric acid" vessel="main_reactor"/>
Recrystallize 写完了 <Recrystallize ratio="?" solvent1="dichloromethane" solvent2="methanol" vessel="filter" volume="?"/>
TakeSample <TakeSample id="a" vessel="rotavap"/>
Hydrogenate <Hydrogenate temp="45 °C" time="?" vessel="main_reactor"/>
4. 参数对齐
class PumpTransferProtocol(BaseModel):
from_vessel: str
to_vessel: str
volume: float
amount: str = ""
time: float = 0
viscous: bool = False
rinsing_solvent: str = "air" <Transfer from_vessel="main_reactor" to_vessel="rotavap"/>
rinsing_volume: float = 5000
rinsing_repeats: int = 2
solid: bool = False
flowrate: float = 500
transfer_flowrate: float = 2500
class SeparateProtocol(BaseModel):
purpose: str
product_phase: str
from_vessel: str
separation_vessel: str
to_vessel: str
waste_phase_to_vessel: str
solvent: str
solvent_volume: float <Separate product_phase="bottom" purpose="wash" solvent="water" vessel="separator" volume="?"/>
through: str
repeats: int
stir_time: float
stir_speed: float
settling_time: float
class EvaporateProtocol(BaseModel):
vessel: str
pressure: float
temp: float <Evaporate solvent="ethanol" vessel="rotavap"/>
time: float
stir_speed: float
class EvacuateAndRefillProtocol(BaseModel):
vessel: str
gas: str <EvacuateAndRefill gas="nitrogen" vessel="main_reactor"/>
repeats: int
class AddProtocol(BaseModel):
vessel: str
reagent: str
volume: float
mass: float
amount: str
time: float
stir: bool
stir_speed: float <Add reagent="ethanol" vessel="main_reactor" volume="2.7 mL"/>
viscous: bool
purpose: str
class CentrifugeProtocol(BaseModel):
vessel: str
speed: float
time: float 自创的
temp: float
class FilterProtocol(BaseModel):
vessel: str
filtrate_vessel: str
stir: bool <Filter vessel="filter"/>
stir_speed: float
temp: float
continue_heatchill: bool
volume: float
class HeatChillProtocol(BaseModel):
vessel: str
temp: float
time: float <HeatChill pressure="1 mbar" temp_spec="room temperature" time="?" vessel="main_reactor"/>
<HeatChill temp_spec="room temperature" time_spec="overnight" vessel="main_reactor"/>
stir: bool
stir_speed: float
purpose: str
class HeatChillStartProtocol(BaseModel):
vessel: str
temp: float 疑似没有
purpose: str
class HeatChillStopProtocol(BaseModel):
vessel: str 疑似没有
class StirProtocol(BaseModel):
stir_time: float
stir_speed: float <Stir time="0.5 h" vessel="main_reactor"/>
settling_time: float
class StartStirProtocol(BaseModel):
vessel: str
stir_speed: float 疑似没有
purpose: str
class StopStirProtocol(BaseModel):
vessel: str 疑似没有
class TransferProtocol(BaseModel):
from_vessel: str
to_vessel: str
volume: float
amount: str = ""
time: float = 0
viscous: bool = False <Transfer from_vessel="main_reactor" to_vessel="rotavap"/>
rinsing_solvent: str = ""
rinsing_volume: float = 0.0
rinsing_repeats: int = 0
solid: bool = False
class CleanVesselProtocol(BaseModel):
vessel: str
solvent: str
volume: float
temp: float
repeats: int = 1 <CleanVessel vessel="centrifuge"/>
class DissolveProtocol(BaseModel):
vessel: str
solvent: str
volume: float <Dissolve mass="2.9 g" mol="0.12 mol" reagent="magnesium" vessel="main_reactor"/>
amount: str = ""
temp: float = 25.0
time: float = 0.0
stir_speed: float = 0.0
class FilterThroughProtocol(BaseModel):
from_vessel: str
to_vessel: str
filter_through: str
eluting_solvent: str = ""
eluting_volume: float = 0.0 疑似没有
eluting_repeats: int = 0
residence_time: float = 0.0
class RunColumnProtocol(BaseModel):
from_vessel: str
to_vessel: str <RunColumn Rf="?" column="column" from_vessel="rotavap" pct1="40 %" pct2="50 %" solvent1="ethyl acetate" solvent2="hexane" to_vessel="rotavap"/>
column: str
class WashSolidProtocol(BaseModel):
vessel: str
solvent: str
volume: float
filtrate_vessel: str = ""
temp: float = 25.0 <WashSolid filtrate_vessel="rotavap" solvent="formic acid" vessel="main_reactor" volume="?"/>
stir: bool = False
stir_speed: float = 0.0
time: float = 0.0
repeats: int = 1
class AdjustPHProtocol(BaseModel):
vessel: str = Field(..., description="目标容器")
ph_value: float = Field(..., description="目标pH值") # 改为 ph_value
reagent: str = Field(..., description="酸碱试剂名称")
# 移除其他可选参数,使用默认值 <新写的没问题>
class ResetHandlingProtocol(BaseModel):
solvent: str = Field(..., description="溶剂名称") <新写的没问题>
class DryProtocol(BaseModel):
compound: str = Field(..., description="化合物名称") <新写的没问题>
vessel: str = Field(..., description="目标容器")
class RecrystallizeProtocol(BaseModel):
ratio: str = Field(..., description="溶剂比例(如 '1:1', '3:7'")
solvent1: str = Field(..., description="第一种溶剂名称") <新写的没问题>
solvent2: str = Field(..., description="第二种溶剂名称")
vessel: str = Field(..., description="目标容器")
volume: float = Field(..., description="总体积 (mL)")
class HydrogenateProtocol(BaseModel):
temp: str = Field(..., description="反应温度(如 '45 °C'")
time: str = Field(..., description="反应时间(如 '2 h'") <新写的没问题>
vessel: str = Field(..., description="反应容器")

View File

@@ -23,8 +23,10 @@
"waste_bottle_2", "waste_bottle_2",
"solenoid_valve_1", "solenoid_valve_1",
"solenoid_valve_2", "solenoid_valve_2",
"solenoid_valve_3",
"vacuum_pump_1", "vacuum_pump_1",
"gas_source_1", "gas_source_1",
"h2_gas_source",
"filter_1", "filter_1",
"column_1", "column_1",
"separator_1", "separator_1",
@@ -60,7 +62,12 @@
"HeatChillStartProtocol", "HeatChillStartProtocol",
"HeatChillStopProtocol", "HeatChillStopProtocol",
"EvacuateAndRefillProtocol", "EvacuateAndRefillProtocol",
"PumpTransferProtocol" "PumpTransferProtocol",
"AdjustPHProtocol",
"ResetHandlingProtocol",
"DryProtocol",
"HydrogenateProtocol",
"RecrystallizeProtocol"
] ]
}, },
"data": {} "data": {}
@@ -461,6 +468,28 @@
"is_open": false "is_open": false
} }
}, },
{
"id": "solenoid_valve_3",
"name": "氢气电磁阀",
"children": [],
"parent": "OrganicSynthesisStation",
"type": "device",
"class": "virtual_solenoid_valve",
"position": {
"x": 450,
"y": 400,
"z": 0
},
"config": {
"voltage": 12.0,
"response_time": 0.1,
"gas_compatible": true
},
"data": {
"valve_state": "Closed",
"is_open": false
}
},
{ {
"id": "vacuum_pump_1", "id": "vacuum_pump_1",
"name": "真空泵", "name": "真空泵",
@@ -500,6 +529,29 @@
"max_pressure": 5.0 "max_pressure": 5.0
} }
}, },
{
"id": "h2_gas_source",
"name": "氢气气源",
"children": [],
"parent": "OrganicSynthesisStation",
"type": "device",
"class": "virtual_gas_source",
"position": {
"x": 500,
"y": 350,
"z": 0
},
"config": {
"max_pressure": 10.0,
"gas_type": "hydrogen"
},
"data": {
"gas_type": "hydrogen",
"max_pressure": 10.0,
"current_pressure": 0.0,
"status": "OFF"
}
},
{ {
"id": "filter_1", "id": "filter_1",
"name": "过滤器", "name": "过滤器",
@@ -680,7 +732,7 @@
"type": "fluid", "type": "fluid",
"port": { "port": {
"multiway_valve_1": "5", "multiway_valve_1": "5",
"rotavap_1": "sample_in" "rotavap_1": "samplein"
} }
}, },
{ {
@@ -770,7 +822,7 @@
"type": "transport", "type": "transport",
"port": { "port": {
"multiway_valve_2": "4", "multiway_valve_2": "4",
"filter_1": "filter_in" "filter_1": "filterin"
} }
}, },
{ {
@@ -800,7 +852,7 @@
"type": "fluid", "type": "fluid",
"port": { "port": {
"multiway_valve_2": "6", "multiway_valve_2": "6",
"separator_1": "separator_in" "separator_1": "separatorin"
} }
}, },
{ {
@@ -809,7 +861,7 @@
"target": "collection_bottle_3", "target": "collection_bottle_3",
"type": "fluid", "type": "fluid",
"port": { "port": {
"separator_1": "bottom_phase_out", "separator_1": "bottomphaseout",
"collection_bottle_3": "top" "collection_bottle_3": "top"
} }
}, },
@@ -859,7 +911,7 @@
"target": "waste_bottle_2", "target": "waste_bottle_2",
"type": "fluid", "type": "fluid",
"port": { "port": {
"separator_1": "top_phase_out", "separator_1": "topphaseout",
"waste_bottle_2": "top" "waste_bottle_2": "top"
} }
}, },
@@ -879,7 +931,7 @@
"target": "collection_bottle_1", "target": "collection_bottle_1",
"type": "transport", "type": "transport",
"port": { "port": {
"filter_1": "filtrate_out", "filter_1": "filtrateout",
"collection_bottle_1": "top" "collection_bottle_1": "top"
} }
}, },
@@ -889,9 +941,29 @@
"target": "waste_bottle_1", "target": "waste_bottle_1",
"type": "transport", "type": "transport",
"port": { "port": {
"filter_1": "retentate_out", "filter_1": "retentateout",
"waste_bottle_1": "top" "waste_bottle_1": "top"
} }
},
{
"id": "link_h2_gas_to_valve3",
"source": "h2_gas_source",
"target": "solenoid_valve_3",
"type": "fluid",
"port": {
"h2_gas_source": "gassource",
"solenoid_valve_3": "in"
}
},
{
"id": "link_valve3_to_reactor",
"source": "solenoid_valve_3",
"target": "main_reactor",
"type": "fluid",
"port": {
"solenoid_valve_3": "out",
"main_reactor": "top"
}
} }
] ]
} }

View File

@@ -48,8 +48,9 @@ dependencies:
- ros-humble-ros2-control - ros-humble-ros2-control
- ros-humble-robot-state-publisher - ros-humble-robot-state-publisher
- ros-humble-joint-state-publisher - ros-humble-joint-state-publisher
# web # web and visualization
- ros-humble-rosbridge-server - ros-humble-rosbridge-server
- ros-humble-cv-bridge
# geometry & motion planning # geometry & motion planning
- ros-humble-tf2 - ros-humble-tf2
- ros-humble-moveit - ros-humble-moveit

View File

@@ -50,8 +50,9 @@ dependencies:
- ros-humble-ros2-control - ros-humble-ros2-control
- ros-humble-robot-state-publisher - ros-humble-robot-state-publisher
- ros-humble-joint-state-publisher - ros-humble-joint-state-publisher
# web # web and visualization
- ros-humble-rosbridge-server - ros-humble-rosbridge-server
- ros-humble-cv-bridge
# geometry & motion planning # geometry & motion planning
- ros-humble-tf2 - ros-humble-tf2
- ros-humble-moveit - ros-humble-moveit

View File

@@ -6,12 +6,12 @@ channels:
dependencies: dependencies:
# Basics # Basics
- python=3.11.11 - python=3.11.11
- compilers # - compilers
- cmake # - cmake
- make # - make
- ninja # - ninja
- sphinx # - sphinx
- sphinx_rtd_theme # - sphinx_rtd_theme
# Data Visualization # Data Visualization
- numpy - numpy
- scipy - scipy
@@ -23,7 +23,7 @@ dependencies:
- pyserial - pyserial
- pyusb - pyusb
- pylibftdi - pylibftdi
- pymodbus - pymodbus==3.6.9
- python-can - python-can
- pyvisa - pyvisa
- opencv - opencv
@@ -48,8 +48,9 @@ dependencies:
- ros-humble-ros2-control - ros-humble-ros2-control
- ros-humble-robot-state-publisher - ros-humble-robot-state-publisher
- ros-humble-joint-state-publisher - ros-humble-joint-state-publisher
# web # web and visualization
- ros-humble-rosbridge-server - ros-humble-rosbridge-server
- ros-humble-cv-bridge
# geometry & motion planning # geometry & motion planning
- ros-humble-tf2 - ros-humble-tf2
- ros-humble-moveit - ros-humble-moveit
@@ -61,5 +62,12 @@ dependencies:
# ros-humble-gazebo-ros // ignored because of the conflict with ign-gazebo # ros-humble-gazebo-ros // ignored because of the conflict with ign-gazebo
# ilab equipments # ilab equipments
# ros-humble-unilabos-msgs # ros-humble-unilabos-msgs
# driver
#- crcmod
- pip: - pip:
- paho-mqtt - paho-mqtt
# driver
#- ur-rtde # set PYTHONUTF8=1
#- pyautogui
#- pywinauto
#- pywinauto_recorder

View File

@@ -25,12 +25,13 @@ def job_add(req: JobAddReq) -> JobData:
if req.job_id is None: if req.job_id is None:
req.job_id = str(uuid.uuid4()) req.job_id = str(uuid.uuid4())
action_name = req.data["action"] action_name = req.data["action"]
action_kwargs = req.data["action_kwargs"] action_type = req.data.get("action_type", "LocalUnknown")
req.data['action'] = action_name action_args = req.data.get("action_kwargs", None) # 兼容老版本,后续删除
if action_name == "execute_command_from_outer": if action_args is None:
action_kwargs = {"command": json.dumps(action_kwargs)} action_args = req.data.get("action_args")
elif "command" in action_kwargs: else:
action_kwargs = action_kwargs["command"] if "command" in action_args:
action_args = action_args["command"]
# print(f"job_add:{req.device_id} {action_name} {action_kwargs}") # print(f"job_add:{req.device_id} {action_name} {action_kwargs}")
HostNode.get_instance().send_goal(req.device_id, action_name=action_name, action_kwargs=action_kwargs, goal_uuid=req.job_id, server_info=req.server_info) HostNode.get_instance().send_goal(req.device_id, action_type=action_type, action_name=action_name, action_kwargs=action_args, goal_uuid=req.job_id, server_info=req.server_info)
return JobData(jobId=req.job_id) return JobData(jobId=req.job_id)

View File

@@ -56,6 +56,10 @@ class MQTTClient:
payload_json["data"] = {} payload_json["data"] = {}
if "action" in payload_json: if "action" in payload_json:
payload_json["data"]["action"] = payload_json.pop("action") payload_json["data"]["action"] = payload_json.pop("action")
if "action_type" in payload_json:
payload_json["data"]["action_type"] = payload_json.pop("action_type")
if "action_args" in payload_json:
payload_json["data"]["action_args"] = payload_json.pop("action_args")
if "action_kwargs" in payload_json: if "action_kwargs" in payload_json:
payload_json["data"]["action_kwargs"] = payload_json.pop("action_kwargs") payload_json["data"]["action_kwargs"] = payload_json.pop("action_kwargs")
job_req = JobAddReq.model_validate(payload_json) job_req = JobAddReq.model_validate(payload_json)

View File

@@ -34,7 +34,7 @@ def main():
""" """
parser = argparse.ArgumentParser(description="注册设备和资源到 MQTT") parser = argparse.ArgumentParser(description="注册设备和资源到 MQTT")
parser.add_argument( parser.add_argument(
"--registry_path", "--registry",
type=str, type=str,
default=None, default=None,
action="append", action="append",
@@ -46,10 +46,16 @@ def main():
default=None, default=None,
help="配置文件路径,支持.py格式的Python配置文件", help="配置文件路径,支持.py格式的Python配置文件",
) )
parser.add_argument(
"--complete_registry",
action="store_true",
default=False,
help="是否补全注册表",
)
args = parser.parse_args() args = parser.parse_args()
# 构建注册表 # 构建注册表
build_registry(args.registry_path) build_registry(args.registry, args.complete_registry)
load_config_from_file(args.config) load_config_from_file(args.config)
from unilabos.app.mq import mqtt_client from unilabos.app.mq import mqtt_client

View File

@@ -7,6 +7,7 @@ Web页面模块
import json import json
import os import os
import sys import sys
import traceback
from pathlib import Path from pathlib import Path
from typing import Dict from typing import Dict
@@ -17,7 +18,7 @@ from jinja2 import Environment, FileSystemLoader
from unilabos.config.config import BasicConfig from unilabos.config.config import BasicConfig
from unilabos.registry.registry import lab_registry from unilabos.registry.registry import lab_registry
from unilabos.ros.msgs.message_converter import msg_converter_manager from unilabos.ros.msgs.message_converter import msg_converter_manager
from unilabos.utils.log import error from unilabos.utils.log import error, debug
from unilabos.utils.type_check import TypeEncoder from unilabos.utils.type_check import TypeEncoder
from unilabos.app.web.utils.device_utils import get_registry_info from unilabos.app.web.utils.device_utils import get_registry_info
from unilabos.app.web.utils.host_utils import get_host_node_info from unilabos.app.web.utils.host_utils import get_host_node_info
@@ -123,6 +124,7 @@ def setup_web_pages(router: APIRouter) -> None:
return html return html
except Exception as e: except Exception as e:
debug(traceback.format_exc())
error(f"生成状态页面时出错: {str(e)}") error(f"生成状态页面时出错: {str(e)}")
raise HTTPException(status_code=500, detail=f"Error generating status page: {str(e)}") raise HTTPException(status_code=500, detail=f"Error generating status page: {str(e)}")

View File

@@ -65,6 +65,8 @@ def get_yaml_from_goal_type(goal_type) -> str:
Returns: Returns:
str: 默认Goal参数的YAML格式字符串 str: 默认Goal参数的YAML格式字符串
""" """
if isinstance(goal_type, str):
return "{}"
if not goal_type: if not goal_type:
return "{}" return "{}"

View File

@@ -21,16 +21,23 @@ from .dissolve_protocol import generate_dissolve_protocol
from .filter_through_protocol import generate_filter_through_protocol from .filter_through_protocol import generate_filter_through_protocol
from .run_column_protocol import generate_run_column_protocol from .run_column_protocol import generate_run_column_protocol
from .wash_solid_protocol import generate_wash_solid_protocol from .wash_solid_protocol import generate_wash_solid_protocol
from .adjustph_protocol import generate_adjust_ph_protocol
from .reset_handling_protocol import generate_reset_handling_protocol
from .dry_protocol import generate_dry_protocol
from .recrystallize_protocol import generate_recrystallize_protocol
from .hydrogenate_protocol import generate_hydrogenate_protocol
# Define a dictionary of protocol generators. # Define a dictionary of protocol generators.
action_protocol_generators = { action_protocol_generators = {
AddProtocol: generate_add_protocol, AddProtocol: generate_add_protocol,
AGVTransferProtocol: generate_agv_transfer_protocol, AGVTransferProtocol: generate_agv_transfer_protocol,
AdjustPHProtocol: generate_adjust_ph_protocol,
CentrifugeProtocol: generate_centrifuge_protocol, CentrifugeProtocol: generate_centrifuge_protocol,
CleanProtocol: generate_clean_protocol, CleanProtocol: generate_clean_protocol,
CleanVesselProtocol: generate_clean_vessel_protocol, CleanVesselProtocol: generate_clean_vessel_protocol,
DissolveProtocol: generate_dissolve_protocol, DissolveProtocol: generate_dissolve_protocol,
DryProtocol: generate_dry_protocol,
EvacuateAndRefillProtocol: generate_evacuateandrefill_protocol, EvacuateAndRefillProtocol: generate_evacuateandrefill_protocol,
EvaporateProtocol: generate_evaporate_protocol, EvaporateProtocol: generate_evaporate_protocol,
FilterProtocol: generate_filter_protocol, FilterProtocol: generate_filter_protocol,
@@ -38,7 +45,10 @@ action_protocol_generators = {
HeatChillProtocol: generate_heat_chill_protocol, HeatChillProtocol: generate_heat_chill_protocol,
HeatChillStartProtocol: generate_heat_chill_start_protocol, HeatChillStartProtocol: generate_heat_chill_start_protocol,
HeatChillStopProtocol: generate_heat_chill_stop_protocol, HeatChillStopProtocol: generate_heat_chill_stop_protocol,
HydrogenateProtocol: generate_hydrogenate_protocol,
PumpTransferProtocol: generate_pump_protocol_with_rinsing, PumpTransferProtocol: generate_pump_protocol_with_rinsing,
RecrystallizeProtocol: generate_recrystallize_protocol,
ResetHandlingProtocol: generate_reset_handling_protocol,
RunColumnProtocol: generate_run_column_protocol, RunColumnProtocol: generate_run_column_protocol,
SeparateProtocol: generate_separate_protocol, SeparateProtocol: generate_separate_protocol,
StartStirProtocol: generate_start_stir_protocol, StartStirProtocol: generate_start_stir_protocol,

View File

@@ -0,0 +1,411 @@
import networkx as nx
from typing import List, Dict, Any
from .pump_protocol import generate_pump_protocol_with_rinsing
def find_acid_base_vessel(G: nx.DiGraph, reagent: str) -> str:
"""
查找酸碱试剂容器,支持多种匹配模式
Args:
G: 网络图
reagent: 试剂名称(如 "hydrochloric acid", "sodium hydroxide"
Returns:
str: 试剂容器ID
"""
print(f"ADJUST_PH: 正在查找试剂 '{reagent}' 的容器...")
# 常见酸碱试剂的别名映射
reagent_aliases = {
"hydrochloric acid": ["HCl", "hydrochloric_acid", "hcl", "muriatic_acid"],
"sodium hydroxide": ["NaOH", "sodium_hydroxide", "naoh", "caustic_soda"],
"sulfuric acid": ["H2SO4", "sulfuric_acid", "h2so4"],
"nitric acid": ["HNO3", "nitric_acid", "hno3"],
"acetic acid": ["CH3COOH", "acetic_acid", "glacial_acetic_acid"],
"ammonia": ["NH3", "ammonium_hydroxide", "nh3"],
"potassium hydroxide": ["KOH", "potassium_hydroxide", "koh"]
}
# 构建搜索名称列表
search_names = [reagent.lower()]
# 添加别名
for base_name, aliases in reagent_aliases.items():
if reagent.lower() in base_name.lower() or base_name.lower() in reagent.lower():
search_names.extend([alias.lower() for alias in aliases])
# 构建可能的容器名称
possible_names = []
for name in search_names:
name_clean = name.replace(" ", "_").replace("-", "_")
possible_names.extend([
f"flask_{name_clean}",
f"bottle_{name_clean}",
f"reagent_{name_clean}",
f"acid_{name_clean}" if "acid" in name else f"base_{name_clean}",
f"{name_clean}_bottle",
f"{name_clean}_flask",
name_clean
])
# 第一步:通过容器名称匹配
for vessel_name in possible_names:
if vessel_name in G.nodes():
print(f"ADJUST_PH: 通过名称匹配找到容器: {vessel_name}")
return vessel_name
# 第二步:通过模糊匹配
for node_id in G.nodes():
if G.nodes[node_id].get('type') == 'container':
node_name = G.nodes[node_id].get('name', '').lower()
# 检查是否包含任何搜索名称
for search_name in search_names:
if search_name in node_id.lower() or search_name in node_name:
print(f"ADJUST_PH: 通过模糊匹配找到容器: {node_id}")
return node_id
# 第三步:通过液体类型匹配
for node_id in G.nodes():
if G.nodes[node_id].get('type') == 'container':
vessel_data = G.nodes[node_id].get('data', {})
liquids = vessel_data.get('liquid', [])
for liquid in liquids:
if isinstance(liquid, dict):
liquid_type = (liquid.get('liquid_type') or liquid.get('name', '')).lower()
reagent_name = vessel_data.get('reagent_name', '').lower()
for search_name in search_names:
if search_name in liquid_type or search_name in reagent_name:
print(f"ADJUST_PH: 通过液体类型匹配找到容器: {node_id}")
return node_id
# 列出可用容器帮助调试
available_containers = []
for node_id in G.nodes():
if G.nodes[node_id].get('type') == 'container':
vessel_data = G.nodes[node_id].get('data', {})
liquids = vessel_data.get('liquid', [])
liquid_types = [liquid.get('liquid_type', '') or liquid.get('name', '')
for liquid in liquids if isinstance(liquid, dict)]
available_containers.append({
'id': node_id,
'name': G.nodes[node_id].get('name', ''),
'liquids': liquid_types,
'reagent_name': vessel_data.get('reagent_name', '')
})
print(f"ADJUST_PH: 可用容器列表:")
for container in available_containers:
print(f" - {container['id']}: {container['name']}")
print(f" 液体: {container['liquids']}")
print(f" 试剂: {container['reagent_name']}")
raise ValueError(f"找不到试剂 '{reagent}' 对应的容器。尝试了: {possible_names}")
def find_connected_stirrer(G: nx.DiGraph, vessel: str) -> str:
"""查找与容器相连的搅拌器"""
stirrer_nodes = [node for node in G.nodes()
if (G.nodes[node].get('class') or '') == 'virtual_stirrer']
for stirrer in stirrer_nodes:
if G.has_edge(stirrer, vessel) or G.has_edge(vessel, stirrer):
return stirrer
return stirrer_nodes[0] if stirrer_nodes else None
def calculate_reagent_volume(target_ph_value: float, reagent: str, vessel_volume: float = 100.0) -> float: # 改为 target_ph_value
"""
估算需要的试剂体积来调节pH
Args:
target_ph_value: 目标pH值 # 改为 target_ph_value
reagent: 试剂名称
vessel_volume: 容器体积 (mL)
Returns:
float: 估算的试剂体积 (mL)
"""
# 简化的pH调节体积估算实际应用中需要更精确的计算
if "acid" in reagent.lower() or "hcl" in reagent.lower():
# 酸性试剂pH越低需要的体积越大
if target_ph_value < 3: # 改为 target_ph_value
return vessel_volume * 0.05 # 5%
elif target_ph_value < 5: # 改为 target_ph_value
return vessel_volume * 0.02 # 2%
else:
return vessel_volume * 0.01 # 1%
elif "hydroxide" in reagent.lower() or "naoh" in reagent.lower():
# 碱性试剂pH越高需要的体积越大
if target_ph_value > 11: # 改为 target_ph_value
return vessel_volume * 0.05 # 5%
elif target_ph_value > 9: # 改为 target_ph_value
return vessel_volume * 0.02 # 2%
else:
return vessel_volume * 0.01 # 1%
else:
# 未知试剂,使用默认值
return vessel_volume * 0.01
def generate_adjust_ph_protocol(
G: nx.DiGraph,
vessel: str,
ph_value: float, # 改为 ph_value
reagent: str,
**kwargs
) -> List[Dict[str, Any]]:
"""
生成调节pH的协议序列
Args:
G: 有向图,节点为容器和设备
vessel: 目标容器需要调节pH的容器
ph_value: 目标pH值从XDL传入 # 改为 ph_value
reagent: 酸碱试剂名称从XDL传入
**kwargs: 其他可选参数,使用默认值
Returns:
List[Dict[str, Any]]: 动作序列
"""
action_sequence = []
# 从kwargs中获取可选参数如果没有则使用默认值
volume = kwargs.get('volume', 0.0) # 自动估算体积
stir = kwargs.get('stir', True) # 默认搅拌
stir_speed = kwargs.get('stir_speed', 300.0) # 默认搅拌速度
stir_time = kwargs.get('stir_time', 60.0) # 默认搅拌时间
settling_time = kwargs.get('settling_time', 30.0) # 默认平衡时间
print(f"ADJUST_PH: 开始生成pH调节协议")
print(f" - 目标容器: {vessel}")
print(f" - 目标pH: {ph_value}") # 改为 ph_value
print(f" - 试剂: {reagent}")
print(f" - 使用默认参数: 体积=自动估算, 搅拌=True, 搅拌速度=300RPM")
# 1. 验证目标容器存在
if vessel not in G.nodes():
raise ValueError(f"目标容器 '{vessel}' 不存在于系统中")
# 2. 查找酸碱试剂容器
try:
reagent_vessel = find_acid_base_vessel(G, reagent)
print(f"ADJUST_PH: 找到试剂容器: {reagent_vessel}")
except ValueError as e:
raise ValueError(f"无法找到试剂 '{reagent}': {str(e)}")
# 3. 如果未指定体积,自动估算
if volume <= 0:
# 获取目标容器的体积信息
vessel_data = G.nodes[vessel].get('data', {})
vessel_volume = vessel_data.get('max_volume', 100.0) # 默认100mL
estimated_volume = calculate_reagent_volume(ph_value, reagent, vessel_volume) # 改为 ph_value
volume = estimated_volume
print(f"ADJUST_PH: 自动估算试剂体积: {volume:.2f} mL")
# 4. 验证路径存在
try:
path = nx.shortest_path(G, source=reagent_vessel, target=vessel)
print(f"ADJUST_PH: 找到路径: {''.join(path)}")
except nx.NetworkXNoPath:
raise ValueError(f"从试剂容器 '{reagent_vessel}' 到目标容器 '{vessel}' 没有可用路径")
# 5. 先启动搅拌(如果需要)
stirrer_id = None
if stir:
try:
stirrer_id = find_connected_stirrer(G, vessel)
if stirrer_id:
print(f"ADJUST_PH: 找到搅拌器 {stirrer_id},启动搅拌")
action_sequence.append({
"device_id": stirrer_id,
"action_name": "start_stir",
"action_kwargs": {
"vessel": vessel,
"stir_speed": stir_speed,
"purpose": f"pH调节: 启动搅拌,准备添加 {reagent}"
}
})
# 等待搅拌稳定
action_sequence.append({
"action_name": "wait",
"action_kwargs": {"time": 5}
})
else:
print(f"ADJUST_PH: 警告 - 未找到搅拌器,继续执行")
except Exception as e:
print(f"ADJUST_PH: 搅拌器配置出错: {str(e)}")
# 6. 缓慢添加试剂 - 使用pump_protocol
print(f"ADJUST_PH: 开始添加试剂 {volume:.2f} mL")
# 计算添加时间pH调节需要缓慢添加
addition_time = max(30.0, volume * 2.0) # 至少30秒每mL需要2秒
try:
pump_actions = generate_pump_protocol_with_rinsing(
G=G,
from_vessel=reagent_vessel,
to_vessel=vessel,
volume=volume,
amount="",
time=addition_time,
viscous=False,
rinsing_solvent="", # pH调节不需要清洗
rinsing_volume=0.0,
rinsing_repeats=0,
solid=False,
flowrate=0.5 # 缓慢注入
)
action_sequence.extend(pump_actions)
except Exception as e:
raise ValueError(f"生成泵协议时出错: {str(e)}")
# 7. 持续搅拌以混合和平衡
if stir and stirrer_id:
print(f"ADJUST_PH: 持续搅拌 {stir_time} 秒以混合试剂")
action_sequence.append({
"device_id": stirrer_id,
"action_name": "stir",
"action_kwargs": {
"stir_time": stir_time,
"stir_speed": stir_speed,
"settling_time": settling_time,
"purpose": f"pH调节: 混合试剂目标pH={ph_value}" # 改为 ph_value
}
})
# 8. 等待反应平衡
action_sequence.append({
"action_name": "wait",
"action_kwargs": {
"time": settling_time,
"description": f"等待pH平衡到目标值 {ph_value}" # 改为 ph_value
}
})
print(f"ADJUST_PH: 协议生成完成,共 {len(action_sequence)} 个动作")
print(f"ADJUST_PH: 预计总时间: {addition_time + stir_time + settling_time:.0f}")
return action_sequence
def generate_adjust_ph_protocol_stepwise(
G: nx.DiGraph,
vessel: str,
ph_value: float,
reagent: str,
max_volume: float = 10.0,
steps: int = 3
) -> List[Dict[str, Any]]:
"""
分步调节pH的协议更安全避免过度调节
Args:
G: 网络图
vessel: 目标容器
pH: 目标pH值
reagent: 酸碱试剂
max_volume: 最大试剂体积
steps: 分步数量
Returns:
List[Dict[str, Any]]: 动作序列
"""
action_sequence = []
print(f"ADJUST_PH: 开始分步pH调节{steps}步)")
# 每步添加的体积
step_volume = max_volume / steps
for i in range(steps):
print(f"ADJUST_PH: 第 {i+1}/{steps} 步,添加 {step_volume} mL")
# 生成单步协议
step_actions = generate_adjust_ph_protocol(
G=G,
vessel=vessel,
ph_value=ph_value,
reagent=reagent,
volume=step_volume,
stir=True,
stir_speed=300.0,
stir_time=30.0,
settling_time=20.0
)
action_sequence.extend(step_actions)
# 步骤间等待
if i < steps - 1:
action_sequence.append({
"action_name": "wait",
"action_kwargs": {
"time": 30,
"description": f"pH调节第{i+1}步完成,等待下一步"
}
})
print(f"ADJUST_PH: 分步pH调节完成")
return action_sequence
# 便捷函数常用pH调节
def generate_acidify_protocol(
G: nx.DiGraph,
vessel: str,
target_ph: float = 2.0,
acid: str = "hydrochloric acid"
) -> List[Dict[str, Any]]:
"""酸化协议"""
return generate_adjust_ph_protocol(
G, vessel, target_ph, acid, 0.0, True, 300.0, 120.0, 60.0
)
def generate_basify_protocol(
G: nx.DiGraph,
vessel: str,
target_ph: float = 12.0,
base: str = "sodium hydroxide"
) -> List[Dict[str, Any]]:
"""碱化协议"""
return generate_adjust_ph_protocol(
G, vessel, target_ph, base, 0.0, True, 300.0, 120.0, 60.0
)
def generate_neutralize_protocol(
G: nx.DiGraph,
vessel: str,
reagent: str = "sodium hydroxide"
) -> List[Dict[str, Any]]:
"""中和协议pH=7"""
return generate_adjust_ph_protocol(
G, vessel, 7.0, reagent, 0.0, True, 350.0, 180.0, 90.0
)
# 测试函数
def test_adjust_ph_protocol():
"""测试pH调节协议"""
print("=== ADJUST PH PROTOCOL 测试 ===")
print("测试完成")
if __name__ == "__main__":
test_adjust_ph_protocol()

View File

@@ -0,0 +1,165 @@
import networkx as nx
from typing import List, Dict, Any
def find_connected_heater(G: nx.DiGraph, vessel: str) -> str:
"""
查找与容器相连的加热器
Args:
G: 网络图
vessel: 容器名称
Returns:
str: 加热器ID如果没有则返回None
"""
print(f"DRY: 正在查找与容器 '{vessel}' 相连的加热器...")
# 查找所有加热器节点
heater_nodes = [node for node in G.nodes()
if ('heater' in node.lower() or
'heat' in node.lower() or
G.nodes[node].get('class') == 'virtual_heatchill' or
G.nodes[node].get('type') == 'heater')]
print(f"DRY: 找到的加热器节点: {heater_nodes}")
# 检查是否有加热器与目标容器相连
for heater in heater_nodes:
if G.has_edge(heater, vessel) or G.has_edge(vessel, heater):
print(f"DRY: 找到与容器 '{vessel}' 相连的加热器: {heater}")
return heater
# 如果没有直接连接,查找距离最近的加热器
for heater in heater_nodes:
try:
path = nx.shortest_path(G, source=heater, target=vessel)
if len(path) <= 3: # 最多2个中间节点
print(f"DRY: 找到距离较近的加热器: {heater}, 路径: {''.join(path)}")
return heater
except nx.NetworkXNoPath:
continue
print(f"DRY: 未找到与容器 '{vessel}' 相连的加热器")
return None
def generate_dry_protocol(
G: nx.DiGraph,
compound: str,
vessel: str,
**kwargs # 接收其他可能的参数但不使用
) -> List[Dict[str, Any]]:
"""
生成干燥协议序列
Args:
G: 有向图,节点为容器和设备
compound: 化合物名称从XDL传入
vessel: 目标容器从XDL传入
**kwargs: 其他可选参数,但不使用
Returns:
List[Dict[str, Any]]: 动作序列
"""
action_sequence = []
# 默认参数
dry_temp = 60.0 # 默认干燥温度 60°C
dry_time = 3600.0 # 默认干燥时间 1小时3600秒
print(f"DRY: 开始生成干燥协议")
print(f" - 化合物: {compound}")
print(f" - 容器: {vessel}")
print(f" - 干燥温度: {dry_temp}°C")
print(f" - 干燥时间: {dry_time/60:.0f} 分钟")
# 1. 验证目标容器存在
if vessel not in G.nodes():
print(f"DRY: 警告 - 容器 '{vessel}' 不存在于系统中,跳过干燥")
return action_sequence
# 2. 查找相连的加热器
heater_id = find_connected_heater(G, vessel)
if heater_id is None:
print(f"DRY: 警告 - 未找到与容器 '{vessel}' 相连的加热器,跳过干燥")
# 添加一个等待动作,表示干燥过程(模拟)
action_sequence.append({
"action_name": "wait",
"action_kwargs": {
"time": 60.0, # 等待1分钟
"description": f"模拟干燥 {compound} (无加热器可用)"
}
})
return action_sequence
# 3. 启动加热器进行干燥
print(f"DRY: 启动加热器 {heater_id} 进行干燥")
# 3.1 启动加热
action_sequence.append({
"device_id": heater_id,
"action_name": "heat_chill_start",
"action_kwargs": {
"vessel": vessel,
"temp": dry_temp,
"purpose": f"干燥 {compound}"
}
})
# 3.2 等待温度稳定
action_sequence.append({
"action_name": "wait",
"action_kwargs": {
"time": 60.0,
"description": f"等待温度稳定到 {dry_temp}°C"
}
})
# 3.3 保持干燥温度
action_sequence.append({
"device_id": heater_id,
"action_name": "heat_chill",
"action_kwargs": {
"vessel": vessel,
"temp": dry_temp,
"time": dry_time,
"purpose": f"干燥 {compound},保持温度 {dry_temp}°C"
}
})
# 3.4 停止加热
action_sequence.append({
"device_id": heater_id,
"action_name": "heat_chill_stop",
"action_kwargs": {
"vessel": vessel,
"purpose": f"干燥完成,停止加热"
}
})
# 3.5 等待冷却
action_sequence.append({
"action_name": "wait",
"action_kwargs": {
"time": 300.0, # 等待5分钟冷却
"description": f"等待 {compound} 冷却"
}
})
print(f"DRY: 协议生成完成,共 {len(action_sequence)} 个动作")
print(f"DRY: 预计总时间: {(dry_time + 360)/60:.0f} 分钟")
return action_sequence
# 测试函数
def test_dry_protocol():
"""测试干燥协议"""
print("=== DRY PROTOCOL 测试 ===")
print("测试完成")
if __name__ == "__main__":
test_dry_protocol()

View File

@@ -5,38 +5,138 @@ from .pump_protocol import generate_pump_protocol_with_rinsing, generate_pump_pr
def find_gas_source(G: nx.DiGraph, gas: str) -> str: def find_gas_source(G: nx.DiGraph, gas: str) -> str:
"""根据气体名称查找对应的气源""" """
# 按照命名规则查找气源 根据气体名称查找对应的气源,支持多种匹配模式:
1. 容器名称匹配
2. 气体类型匹配data.gas_type
3. 默认气源
"""
print(f"EVACUATE_REFILL: 正在查找气体 '{gas}' 的气源...")
# 第一步:通过容器名称匹配
gas_source_patterns = [ gas_source_patterns = [
f"gas_source_{gas}", f"gas_source_{gas}",
f"gas_{gas}", f"gas_{gas}",
f"flask_{gas}", f"flask_{gas}",
f"{gas}_source" f"{gas}_source",
f"source_{gas}",
f"reagent_bottle_{gas}",
f"bottle_{gas}"
] ]
for pattern in gas_source_patterns: for pattern in gas_source_patterns:
if pattern in G.nodes(): if pattern in G.nodes():
print(f"EVACUATE_REFILL: 通过名称匹配找到气源: {pattern}")
return pattern return pattern
# 模糊匹配 # 第二步:通过气体类型匹配 (data.gas_type)
for node in G.nodes(): for node_id in G.nodes():
node_class = G.nodes[node].get('class', '') or '' node_data = G.nodes[node_id]
if 'gas_source' in node_class and gas.lower() in node.lower(): node_class = node_data.get('class', '') or ''
return node
if node.startswith('flask_') and gas.lower() in node.lower():
return node
# 查找所有可用的气源 # 检查是否是气源设备
available_gas_sources = [ if ('gas_source' in node_class or
'gas' in node_id.lower() or
node_id.startswith('flask_')):
# 检查 data.gas_type
data = node_data.get('data', {})
gas_type = data.get('gas_type', '')
if gas_type.lower() == gas.lower():
print(f"EVACUATE_REFILL: 通过气体类型匹配找到气源: {node_id} (gas_type: {gas_type})")
return node_id
# 检查 config.gas_type
config = node_data.get('config', {})
config_gas_type = config.get('gas_type', '')
if config_gas_type.lower() == gas.lower():
print(f"EVACUATE_REFILL: 通过配置气体类型匹配找到气源: {node_id} (config.gas_type: {config_gas_type})")
return node_id
# 第三步:查找所有可用的气源设备
available_gas_sources = []
for node_id in G.nodes():
node_data = G.nodes[node_id]
node_class = node_data.get('class', '') or ''
if ('gas_source' in node_class or
'gas' in node_id.lower() or
(node_id.startswith('flask_') and any(g in node_id.lower() for g in ['air', 'nitrogen', 'argon']))):
data = node_data.get('data', {})
gas_type = data.get('gas_type', 'unknown')
available_gas_sources.append(f"{node_id} (gas_type: {gas_type})")
print(f"EVACUATE_REFILL: 可用气源列表: {available_gas_sources}")
# 第四步:如果找不到特定气体,使用默认的第一个气源
default_gas_sources = [
node for node in G.nodes() node for node in G.nodes()
if ((G.nodes[node].get('class') or '').startswith('virtual_gas_source') if ((G.nodes[node].get('class') or '').startswith('virtual_gas_source')
or ('gas' in node and 'source' in node) or 'gas_source' in node)
or (node.startswith('flask_') and any(g in node.lower() for g in ['air', 'nitrogen', 'argon', 'vacuum'])))
] ]
if default_gas_sources:
default_source = default_gas_sources[0]
print(f"EVACUATE_REFILL: ⚠️ 未找到特定气体 '{gas}',使用默认气源: {default_source}")
return default_source
raise ValueError(f"找不到气体 '{gas}' 对应的气源。可用气源: {available_gas_sources}") raise ValueError(f"找不到气体 '{gas}' 对应的气源。可用气源: {available_gas_sources}")
def find_gas_source_by_any_match(G: nx.DiGraph, gas: str) -> str:
"""
增强版气源查找,支持各种匹配方式的别名函数
"""
return find_gas_source(G, gas)
def get_gas_source_type(G: nx.DiGraph, gas_source: str) -> str:
"""获取气源的气体类型"""
if gas_source not in G.nodes():
return "unknown"
node_data = G.nodes[gas_source]
data = node_data.get('data', {})
config = node_data.get('config', {})
# 检查多个可能的字段
gas_type = (data.get('gas_type') or
config.get('gas_type') or
data.get('gas') or
config.get('gas') or
"air") # 默认为空气
return gas_type
def find_vessels_by_gas_type(G: nx.DiGraph, gas: str) -> List[str]:
"""
根据气体类型查找所有匹配的容器/气源
"""
matching_vessels = []
for node_id in G.nodes():
node_data = G.nodes[node_id]
# 检查容器名称匹配
if gas.lower() in node_id.lower():
matching_vessels.append(f"{node_id} (名称匹配)")
continue
# 检查气体类型匹配
data = node_data.get('data', {})
config = node_data.get('config', {})
gas_type = data.get('gas_type', '') or config.get('gas_type', '')
if gas_type.lower() == gas.lower():
matching_vessels.append(f"{node_id} (gas_type: {gas_type})")
return matching_vessels
def find_vacuum_pump(G: nx.DiGraph) -> str: def find_vacuum_pump(G: nx.DiGraph) -> str:
"""查找真空泵设备""" """查找真空泵设备"""
vacuum_pumps = [ vacuum_pumps = [

View File

@@ -0,0 +1,366 @@
import networkx as nx
from typing import List, Dict, Any, Optional
def parse_temperature(temp_str: str) -> float:
"""
解析温度字符串,支持多种格式
Args:
temp_str: 温度字符串(如 "45 °C", "45°C", "45"
Returns:
float: 温度值(摄氏度)
"""
try:
# 移除常见的温度单位和符号
temp_clean = temp_str.replace("°C", "").replace("°", "").replace("C", "").strip()
return float(temp_clean)
except ValueError:
print(f"HYDROGENATE: 无法解析温度 '{temp_str}',使用默认温度 25°C")
return 25.0
def parse_time(time_str: str) -> float:
"""
解析时间字符串,支持多种格式
Args:
time_str: 时间字符串(如 "2 h", "120 min", "7200 s"
Returns:
float: 时间值(秒)
"""
try:
time_clean = time_str.lower().strip()
# 处理小时
if "h" in time_clean:
hours = float(time_clean.replace("h", "").strip())
return hours * 3600.0
# 处理分钟
if "min" in time_clean:
minutes = float(time_clean.replace("min", "").strip())
return minutes * 60.0
# 处理秒
if "s" in time_clean:
seconds = float(time_clean.replace("s", "").strip())
return seconds
# 默认按小时处理
return float(time_clean) * 3600.0
except ValueError:
print(f"HYDROGENATE: 无法解析时间 '{time_str}',使用默认时间 2小时")
return 7200.0 # 2小时
def find_associated_solenoid_valve(G: nx.DiGraph, device_id: str) -> Optional[str]:
"""查找与指定设备相关联的电磁阀"""
solenoid_valves = [
node for node in G.nodes()
if ('solenoid' in (G.nodes[node].get('class') or '').lower()
or 'solenoid_valve' in node)
]
# 通过网络连接查找直接相连的电磁阀
for solenoid in solenoid_valves:
if G.has_edge(device_id, solenoid) or G.has_edge(solenoid, device_id):
return solenoid
# 通过命名规则查找关联的电磁阀
device_type = ""
if 'gas' in device_id.lower():
device_type = "gas"
elif 'h2' in device_id.lower() or 'hydrogen' in device_id.lower():
device_type = "gas"
if device_type:
for solenoid in solenoid_valves:
if device_type in solenoid.lower():
return solenoid
return None
def find_connected_device(G: nx.DiGraph, vessel: str, device_type: str) -> str:
"""
查找与容器相连的指定类型设备
Args:
G: 网络图
vessel: 容器名称
device_type: 设备类型 ('heater', 'stirrer', 'gas_source')
Returns:
str: 设备ID如果没有则返回None
"""
print(f"HYDROGENATE: 正在查找与容器 '{vessel}' 相连的 {device_type}...")
# 根据设备类型定义搜索关键词
if device_type == 'heater':
keywords = ['heater', 'heat', 'heatchill']
device_class = 'virtual_heatchill'
elif device_type == 'stirrer':
keywords = ['stirrer', 'stir']
device_class = 'virtual_stirrer'
elif device_type == 'gas_source':
keywords = ['gas', 'h2', 'hydrogen']
device_class = 'virtual_gas_source'
else:
return None
# 查找设备节点
device_nodes = []
for node in G.nodes():
node_data = G.nodes[node]
node_name = node.lower()
node_class = node_data.get('class', '').lower()
# 通过名称匹配
if any(keyword in node_name for keyword in keywords):
device_nodes.append(node)
# 通过类型匹配
elif device_class in node_class:
device_nodes.append(node)
print(f"HYDROGENATE: 找到的{device_type}节点: {device_nodes}")
# 检查是否有设备与目标容器相连
for device in device_nodes:
if G.has_edge(device, vessel) or G.has_edge(vessel, device):
print(f"HYDROGENATE: 找到与容器 '{vessel}' 相连的{device_type}: {device}")
return device
# 如果没有直接连接,查找距离最近的设备
for device in device_nodes:
try:
path = nx.shortest_path(G, source=device, target=vessel)
if len(path) <= 3: # 最多2个中间节点
print(f"HYDROGENATE: 找到距离较近的{device_type}: {device}")
return device
except nx.NetworkXNoPath:
continue
print(f"HYDROGENATE: 未找到与容器 '{vessel}' 相连的{device_type}")
return None
def generate_hydrogenate_protocol(
G: nx.DiGraph,
temp: str,
time: str,
vessel: str,
**kwargs # 接收其他可能的参数但不使用
) -> List[Dict[str, Any]]:
"""
生成氢化反应协议序列
Args:
G: 有向图,节点为容器和设备
temp: 反应温度(如 "45 °C"
time: 反应时间(如 "2 h"
vessel: 反应容器
**kwargs: 其他可选参数,但不使用
Returns:
List[Dict[str, Any]]: 动作序列
"""
action_sequence = []
# 解析参数
temperature = parse_temperature(temp)
reaction_time = parse_time(time)
print(f"HYDROGENATE: 开始生成氢化反应协议")
print(f" - 反应温度: {temperature}°C")
print(f" - 反应时间: {reaction_time/3600:.1f} 小时")
print(f" - 反应容器: {vessel}")
# 1. 验证目标容器存在
if vessel not in G.nodes():
print(f"HYDROGENATE: 警告 - 容器 '{vessel}' 不存在于系统中,跳过氢化反应")
return action_sequence
# 2. 查找相连的设备
heater_id = find_connected_device(G, vessel, 'heater')
stirrer_id = find_connected_device(G, vessel, 'stirrer')
gas_source_id = find_connected_device(G, vessel, 'gas_source')
# 3. 启动搅拌器
if stirrer_id:
print(f"HYDROGENATE: 启动搅拌器 {stirrer_id}")
action_sequence.append({
"device_id": stirrer_id,
"action_name": "start_stir",
"action_kwargs": {
"vessel": vessel,
"stir_speed": 300.0,
"purpose": "氢化反应: 开始搅拌"
}
})
else:
print(f"HYDROGENATE: 警告 - 未找到搅拌器,继续执行")
# 4. 启动气源(氢气)- 修复版本
if gas_source_id:
print(f"HYDROGENATE: 启动气源 {gas_source_id} (氢气)")
action_sequence.append({
"device_id": gas_source_id,
"action_name": "set_status", # 修改为 set_status
"action_kwargs": {
"string": "ON" # 修改参数格式
}
})
# 查找相关的电磁阀
gas_solenoid = find_associated_solenoid_valve(G, gas_source_id)
if gas_solenoid:
print(f"HYDROGENATE: 开启气源电磁阀 {gas_solenoid}")
action_sequence.append({
"device_id": gas_solenoid,
"action_name": "set_valve_position",
"action_kwargs": {
"command": "OPEN"
}
})
else:
print(f"HYDROGENATE: 警告 - 未找到气源,继续执行")
# 5. 等待气体稳定
action_sequence.append({
"action_name": "wait",
"action_kwargs": {
"time": 30.0,
"description": "等待氢气环境稳定"
}
})
# 6. 启动加热器
if heater_id:
print(f"HYDROGENATE: 启动加热器 {heater_id}{temperature}°C")
action_sequence.append({
"device_id": heater_id,
"action_name": "heat_chill_start",
"action_kwargs": {
"vessel": vessel,
"temp": temperature,
"purpose": f"氢化反应: 加热到 {temperature}°C"
}
})
# 等待温度稳定
action_sequence.append({
"action_name": "wait",
"action_kwargs": {
"time": 120.0,
"description": f"等待温度稳定到 {temperature}°C"
}
})
# 保持反应温度
action_sequence.append({
"device_id": heater_id,
"action_name": "heat_chill",
"action_kwargs": {
"vessel": vessel,
"temp": temperature,
"time": reaction_time,
"purpose": f"氢化反应: 保持 {temperature}°C反应 {reaction_time/3600:.1f} 小时"
}
})
else:
print(f"HYDROGENATE: 警告 - 未找到加热器,使用室温反应")
# 室温反应,只等待时间
action_sequence.append({
"action_name": "wait",
"action_kwargs": {
"time": reaction_time,
"description": f"室温氢化反应 {reaction_time/3600:.1f} 小时"
}
})
# 7. 停止加热
if heater_id:
action_sequence.append({
"device_id": heater_id,
"action_name": "heat_chill_stop",
"action_kwargs": {
"vessel": vessel,
"purpose": "氢化反应完成,停止加热"
}
})
# 8. 等待冷却
action_sequence.append({
"action_name": "wait",
"action_kwargs": {
"time": 300.0,
"description": "等待反应混合物冷却"
}
})
# 9. 停止气源 - 修复版本
if gas_source_id:
# 先关闭电磁阀
gas_solenoid = find_associated_solenoid_valve(G, gas_source_id)
if gas_solenoid:
print(f"HYDROGENATE: 关闭气源电磁阀 {gas_solenoid}")
action_sequence.append({
"device_id": gas_solenoid,
"action_name": "set_valve_position",
"action_kwargs": {
"command": "CLOSED"
}
})
# 再关闭气源
action_sequence.append({
"device_id": gas_source_id,
"action_name": "set_status", # 修改为 set_status
"action_kwargs": {
"string": "OFF" # 修改参数格式
}
})
# 10. 停止搅拌
if stirrer_id:
action_sequence.append({
"device_id": stirrer_id,
"action_name": "stop_stir",
"action_kwargs": {
"vessel": vessel,
"purpose": "氢化反应完成,停止搅拌"
}
})
print(f"HYDROGENATE: 协议生成完成,共 {len(action_sequence)} 个动作")
print(f"HYDROGENATE: 预计总时间: {(reaction_time + 450)/3600:.1f} 小时")
return action_sequence
# 测试函数
def test_hydrogenate_protocol():
"""测试氢化反应协议"""
print("=== HYDROGENATE PROTOCOL 测试 ===")
# 测试温度解析
test_temps = ["45 °C", "45°C", "45", "25 C", "invalid"]
for temp in test_temps:
parsed = parse_temperature(temp)
print(f"温度 '{temp}' -> {parsed}°C")
# 测试时间解析
test_times = ["2 h", "120 min", "7200 s", "2", "invalid"]
for time in test_times:
parsed = parse_time(time)
print(f"时间 '{time}' -> {parsed/3600:.1f} 小时")
print("测试完成")
if __name__ == "__main__":
test_hydrogenate_protocol()

View File

@@ -0,0 +1,281 @@
import networkx as nx
from typing import List, Dict, Any, Tuple
from .pump_protocol import generate_pump_protocol_with_rinsing
def parse_ratio(ratio_str: str) -> Tuple[float, float]:
"""
解析比例字符串,支持多种格式
Args:
ratio_str: 比例字符串(如 "1:1", "3:7", "50:50"
Returns:
Tuple[float, float]: 比例元组 (ratio1, ratio2)
"""
try:
# 处理 "1:1", "3:7", "50:50" 等格式
if ":" in ratio_str:
parts = ratio_str.split(":")
if len(parts) == 2:
ratio1 = float(parts[0])
ratio2 = float(parts[1])
return ratio1, ratio2
# 处理 "1-1", "3-7" 等格式
if "-" in ratio_str:
parts = ratio_str.split("-")
if len(parts) == 2:
ratio1 = float(parts[0])
ratio2 = float(parts[1])
return ratio1, ratio2
# 处理 "1,1", "3,7" 等格式
if "," in ratio_str:
parts = ratio_str.split(",")
if len(parts) == 2:
ratio1 = float(parts[0])
ratio2 = float(parts[1])
return ratio1, ratio2
# 默认 1:1
print(f"RECRYSTALLIZE: 无法解析比例 '{ratio_str}',使用默认比例 1:1")
return 1.0, 1.0
except ValueError:
print(f"RECRYSTALLIZE: 比例解析错误 '{ratio_str}',使用默认比例 1:1")
return 1.0, 1.0
def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
"""
查找溶剂容器
Args:
G: 网络图
solvent: 溶剂名称
Returns:
str: 溶剂容器ID
"""
print(f"RECRYSTALLIZE: 正在查找溶剂 '{solvent}' 的容器...")
# 构建可能的容器名称
possible_names = [
f"flask_{solvent}",
f"bottle_{solvent}",
f"reagent_{solvent}",
f"reagent_bottle_{solvent}",
f"{solvent}_flask",
f"{solvent}_bottle",
f"{solvent}",
f"vessel_{solvent}",
]
# 第一步:通过容器名称匹配
for vessel_name in possible_names:
if vessel_name in G.nodes():
print(f"RECRYSTALLIZE: 通过名称匹配找到容器: {vessel_name}")
return vessel_name
# 第二步:通过模糊匹配
for node_id in G.nodes():
if G.nodes[node_id].get('type') == 'container':
node_name = G.nodes[node_id].get('name', '').lower()
if solvent.lower() in node_id.lower() or solvent.lower() in node_name:
print(f"RECRYSTALLIZE: 通过模糊匹配找到容器: {node_id}")
return node_id
# 第三步:通过液体类型匹配
for node_id in G.nodes():
if G.nodes[node_id].get('type') == 'container':
vessel_data = G.nodes[node_id].get('data', {})
liquids = vessel_data.get('liquid', [])
for liquid in liquids:
if isinstance(liquid, dict):
liquid_type = (liquid.get('liquid_type') or liquid.get('name', '')).lower()
reagent_name = vessel_data.get('reagent_name', '').lower()
if solvent.lower() in liquid_type or solvent.lower() in reagent_name:
print(f"RECRYSTALLIZE: 通过液体类型匹配找到容器: {node_id}")
return node_id
raise ValueError(f"找不到溶剂 '{solvent}' 对应的容器")
def generate_recrystallize_protocol(
G: nx.DiGraph,
ratio: str,
solvent1: str,
solvent2: str,
vessel: str,
volume: float,
**kwargs # 接收其他可能的参数但不使用
) -> List[Dict[str, Any]]:
"""
生成重结晶协议序列
Args:
G: 有向图,节点为容器和设备
ratio: 溶剂比例(如 "1:1", "3:7"
solvent1: 第一种溶剂名称
solvent2: 第二种溶剂名称
vessel: 目标容器
volume: 总体积 (mL)
**kwargs: 其他可选参数,但不使用
Returns:
List[Dict[str, Any]]: 动作序列
"""
action_sequence = []
print(f"RECRYSTALLIZE: 开始生成重结晶协议")
print(f" - 比例: {ratio}")
print(f" - 溶剂1: {solvent1}")
print(f" - 溶剂2: {solvent2}")
print(f" - 容器: {vessel}")
print(f" - 总体积: {volume} mL")
# 1. 验证目标容器存在
if vessel not in G.nodes():
raise ValueError(f"目标容器 '{vessel}' 不存在于系统中")
# 2. 解析比例
ratio1, ratio2 = parse_ratio(ratio)
total_ratio = ratio1 + ratio2
# 3. 计算各溶剂体积
volume1 = volume * (ratio1 / total_ratio)
volume2 = volume * (ratio2 / total_ratio)
print(f"RECRYSTALLIZE: 解析比例: {ratio1}:{ratio2}")
print(f"RECRYSTALLIZE: {solvent1} 体积: {volume1:.2f} mL")
print(f"RECRYSTALLIZE: {solvent2} 体积: {volume2:.2f} mL")
# 4. 查找溶剂容器
try:
solvent1_vessel = find_solvent_vessel(G, solvent1)
print(f"RECRYSTALLIZE: 找到溶剂1容器: {solvent1_vessel}")
except ValueError as e:
raise ValueError(f"无法找到溶剂1 '{solvent1}': {str(e)}")
try:
solvent2_vessel = find_solvent_vessel(G, solvent2)
print(f"RECRYSTALLIZE: 找到溶剂2容器: {solvent2_vessel}")
except ValueError as e:
raise ValueError(f"无法找到溶剂2 '{solvent2}': {str(e)}")
# 5. 验证路径存在
try:
path1 = nx.shortest_path(G, source=solvent1_vessel, target=vessel)
print(f"RECRYSTALLIZE: 溶剂1路径: {''.join(path1)}")
except nx.NetworkXNoPath:
raise ValueError(f"从溶剂1容器 '{solvent1_vessel}' 到目标容器 '{vessel}' 没有可用路径")
try:
path2 = nx.shortest_path(G, source=solvent2_vessel, target=vessel)
print(f"RECRYSTALLIZE: 溶剂2路径: {''.join(path2)}")
except nx.NetworkXNoPath:
raise ValueError(f"从溶剂2容器 '{solvent2_vessel}' 到目标容器 '{vessel}' 没有可用路径")
# 6. 添加第一种溶剂
print(f"RECRYSTALLIZE: 开始添加溶剂1 {volume1:.2f} mL")
try:
pump_actions1 = generate_pump_protocol_with_rinsing(
G=G,
from_vessel=solvent1_vessel,
to_vessel=vessel,
volume=volume1,
amount="",
time=0.0,
viscous=False,
rinsing_solvent="", # 重结晶不需要清洗
rinsing_volume=0.0,
rinsing_repeats=0,
solid=False,
flowrate=2.0, # 正常流速
transfer_flowrate=0.5
)
action_sequence.extend(pump_actions1)
except Exception as e:
raise ValueError(f"生成溶剂1泵协议时出错: {str(e)}")
# 7. 等待溶剂1稳定
action_sequence.append({
"action_name": "wait",
"action_kwargs": {
"time": 10.0,
"description": f"等待溶剂1 {solvent1} 稳定"
}
})
# 8. 添加第二种溶剂
print(f"RECRYSTALLIZE: 开始添加溶剂2 {volume2:.2f} mL")
try:
pump_actions2 = generate_pump_protocol_with_rinsing(
G=G,
from_vessel=solvent2_vessel,
to_vessel=vessel,
volume=volume2,
amount="",
time=0.0,
viscous=False,
rinsing_solvent="", # 重结晶不需要清洗
rinsing_volume=0.0,
rinsing_repeats=0,
solid=False,
flowrate=2.0, # 正常流速
transfer_flowrate=0.5
)
action_sequence.extend(pump_actions2)
except Exception as e:
raise ValueError(f"生成溶剂2泵协议时出错: {str(e)}")
# 9. 等待溶剂2稳定
action_sequence.append({
"action_name": "wait",
"action_kwargs": {
"time": 10.0,
"description": f"等待溶剂2 {solvent2} 稳定"
}
})
# 10. 等待重结晶完成
action_sequence.append({
"action_name": "wait",
"action_kwargs": {
"time": 600.0, # 等待10分钟进行重结晶
"description": f"等待重结晶完成({solvent1}:{solvent2} = {ratio}"
}
})
print(f"RECRYSTALLIZE: 协议生成完成,共 {len(action_sequence)} 个动作")
print(f"RECRYSTALLIZE: 预计总时间: {620/60:.1f} 分钟")
return action_sequence
# 测试函数
def test_recrystallize_protocol():
"""测试重结晶协议"""
print("=== RECRYSTALLIZE PROTOCOL 测试 ===")
# 测试比例解析
test_ratios = ["1:1", "3:7", "50:50", "1-1", "2,8", "invalid"]
for ratio in test_ratios:
r1, r2 = parse_ratio(ratio)
print(f"比例 '{ratio}' -> {r1}:{r2}")
print("测试完成")
if __name__ == "__main__":
test_recrystallize_protocol()

View File

@@ -0,0 +1,180 @@
import networkx as nx
from typing import List, Dict, Any
from .pump_protocol import generate_pump_protocol_with_rinsing
def find_solvent_vessel(G: nx.DiGraph, solvent: str) -> str:
"""
查找溶剂容器,支持多种匹配模式
Args:
G: 网络图
solvent: 溶剂名称(如 "methanol", "ethanol", "water"
Returns:
str: 溶剂容器ID
"""
print(f"RESET_HANDLING: 正在查找溶剂 '{solvent}' 的容器...")
# 构建可能的容器名称
possible_names = [
f"flask_{solvent}", # flask_methanol
f"bottle_{solvent}", # bottle_methanol
f"reagent_{solvent}", # reagent_methanol
f"reagent_bottle_{solvent}", # reagent_bottle_methanol
f"{solvent}_flask", # methanol_flask
f"{solvent}_bottle", # methanol_bottle
f"{solvent}", # methanol
f"vessel_{solvent}", # vessel_methanol
]
# 第一步:通过容器名称匹配
for vessel_name in possible_names:
if vessel_name in G.nodes():
print(f"RESET_HANDLING: 通过名称匹配找到容器: {vessel_name}")
return vessel_name
# 第二步:通过模糊匹配
for node_id in G.nodes():
if G.nodes[node_id].get('type') == 'container':
node_name = G.nodes[node_id].get('name', '').lower()
# 检查是否包含溶剂名称
if solvent.lower() in node_id.lower() or solvent.lower() in node_name:
print(f"RESET_HANDLING: 通过模糊匹配找到容器: {node_id}")
return node_id
# 第三步:通过液体类型匹配
for node_id in G.nodes():
if G.nodes[node_id].get('type') == 'container':
vessel_data = G.nodes[node_id].get('data', {})
liquids = vessel_data.get('liquid', [])
for liquid in liquids:
if isinstance(liquid, dict):
liquid_type = (liquid.get('liquid_type') or liquid.get('name', '')).lower()
reagent_name = vessel_data.get('reagent_name', '').lower()
if solvent.lower() in liquid_type or solvent.lower() in reagent_name:
print(f"RESET_HANDLING: 通过液体类型匹配找到容器: {node_id}")
return node_id
# 列出可用容器帮助调试
available_containers = []
for node_id in G.nodes():
if G.nodes[node_id].get('type') == 'container':
vessel_data = G.nodes[node_id].get('data', {})
liquids = vessel_data.get('liquid', [])
liquid_types = [liquid.get('liquid_type', '') or liquid.get('name', '')
for liquid in liquids if isinstance(liquid, dict)]
available_containers.append({
'id': node_id,
'name': G.nodes[node_id].get('name', ''),
'liquids': liquid_types,
'reagent_name': vessel_data.get('reagent_name', '')
})
print(f"RESET_HANDLING: 可用容器列表:")
for container in available_containers:
print(f" - {container['id']}: {container['name']}")
print(f" 液体: {container['liquids']}")
print(f" 试剂: {container['reagent_name']}")
raise ValueError(f"找不到溶剂 '{solvent}' 对应的容器。尝试了: {possible_names}")
def generate_reset_handling_protocol(
G: nx.DiGraph,
solvent: str,
**kwargs # 接收其他可能的参数但不使用
) -> List[Dict[str, Any]]:
"""
生成重置处理协议序列
Args:
G: 有向图,节点为容器和设备
solvent: 溶剂名称从XDL传入
**kwargs: 其他可选参数,但不使用
Returns:
List[Dict[str, Any]]: 动作序列
"""
action_sequence = []
# 固定参数
target_vessel = "main_reactor" # 默认目标容器
volume = 100.0 # 默认体积 100 mL
print(f"RESET_HANDLING: 开始生成重置处理协议")
print(f" - 溶剂: {solvent}")
print(f" - 目标容器: {target_vessel}")
print(f" - 体积: {volume} mL")
# 1. 验证目标容器存在
if target_vessel not in G.nodes():
raise ValueError(f"目标容器 '{target_vessel}' 不存在于系统中")
# 2. 查找溶剂容器
try:
solvent_vessel = find_solvent_vessel(G, solvent)
print(f"RESET_HANDLING: 找到溶剂容器: {solvent_vessel}")
except ValueError as e:
raise ValueError(f"无法找到溶剂 '{solvent}': {str(e)}")
# 3. 验证路径存在
try:
path = nx.shortest_path(G, source=solvent_vessel, target=target_vessel)
print(f"RESET_HANDLING: 找到路径: {''.join(path)}")
except nx.NetworkXNoPath:
raise ValueError(f"从溶剂容器 '{solvent_vessel}' 到目标容器 '{target_vessel}' 没有可用路径")
# 4. 使用pump_protocol转移溶剂
print(f"RESET_HANDLING: 开始转移溶剂 {volume} mL")
try:
pump_actions = generate_pump_protocol_with_rinsing(
G=G,
from_vessel=solvent_vessel,
to_vessel=target_vessel,
volume=volume,
amount="",
time=0.0,
viscous=False,
rinsing_solvent="", # 重置处理不需要清洗
rinsing_volume=0.0,
rinsing_repeats=0,
solid=False,
flowrate=2.5, # 正常流速
transfer_flowrate=0.5 # 正常转移流速
)
action_sequence.extend(pump_actions)
except Exception as e:
raise ValueError(f"生成泵协议时出错: {str(e)}")
# 5. 等待溶剂稳定
action_sequence.append({
"action_name": "wait",
"action_kwargs": {
"time": 10.0,
"description": f"等待溶剂 {solvent} 稳定"
}
})
print(f"RESET_HANDLING: 协议生成完成,共 {len(action_sequence)} 个动作")
print(f"RESET_HANDLING: 已添加 {volume} mL {solvent}{target_vessel}")
return action_sequence
# 测试函数
def test_reset_handling_protocol():
"""测试重置处理协议"""
print("=== RESET HANDLING PROTOCOL 测试 ===")
print("测试完成")
if __name__ == "__main__":
test_reset_handling_protocol()

View File

@@ -1,9 +1,12 @@
try:
import rtde_control import rtde_control
import dashboard_client import dashboard_client
import rtde_receive
except ImportError as ex:
print("Import Error, Please Install Packages in ur_arm_task.py First!", ex)
import time import time
import json import json
from unilabos.devices.agv.robotiq_gripper import RobotiqGripper from unilabos.devices.agv.robotiq_gripper import RobotiqGripper
import rtde_receive
from std_msgs.msg import Float64MultiArray from std_msgs.msg import Float64MultiArray
from pydantic import BaseModel from pydantic import BaseModel

View File

@@ -234,7 +234,7 @@ class Laiyu:
resp_reset = self.reset() resp_reset = self.reset()
return actual_mass_mg return actual_mass_mg
if __name__ == "__main__":
''' '''
样例:对单个粉筒进行称量 样例:对单个粉筒进行称量

View File

@@ -0,0 +1,328 @@
import socket, json, contextlib
from typing import Any, List, Dict, Optional
class PRCXIError(RuntimeError):
"""Lilith 返回 Success=false 时抛出的业务异常"""
class PRCXI9300:
def __init__(self, host: str = "127.0.0.1", port: int = 9999,
timeout: float = 10.0) -> None:
self.host, self.port, self.timeout = host, port, timeout
@staticmethod
def _len_prefix(n: int) -> bytes:
return bytes.fromhex(format(n, "016x"))
def _raw_request(self, payload: str) -> str:
with contextlib.closing(socket.socket()) as sock:
sock.settimeout(self.timeout)
sock.connect((self.host, self.port))
data = payload.encode()
sock.sendall(self._len_prefix(len(data)) + data)
chunks, first = [], True
while True:
chunk = sock.recv(4096)
if not chunk:
break
if first:
chunk, first = chunk[8:], False
chunks.append(chunk)
return b"".join(chunks).decode()
def _call(self, service: str, method: str,
params: Optional[list] = None) -> Any:
payload = json.dumps(
{"ServiceName": service,
"MethodName": method,
"Paramters": params or []},
separators=(",", ":")
)
resp = json.loads(self._raw_request(payload))
if not resp.get("Success", False):
raise PRCXIError(resp.get("Msg", "Unknown error"))
data = resp.get("Data")
try:
return json.loads(data)
except (TypeError, json.JSONDecodeError):
return data
# ---------------------------------------------------- 方案相关ISolution
def list_solutions(self) -> List[Dict[str, Any]]:
"""GetSolutionList"""
return self._call("ISolution", "GetSolutionList")
def load_solution(self, solution_id: str) -> bool:
"""LoadSolution"""
return self._call("ISolution", "LoadSolution", [solution_id])
def add_solution(self, name: str, matrix_id: str,
steps: List[Dict[str, Any]]) -> str:
"""AddSolution → 返回新方案 GUID"""
return self._call("ISolution", "AddSolution",
[name, matrix_id, steps])
# ---------------------------------------------------- 自动化控制IAutomation
def start(self) -> bool:
return self._call("IAutomation", "Start")
def stop(self) -> bool:
"""Stop"""
return self._call("IAutomation", "Stop")
def reset(self) -> bool:
"""Reset"""
return self._call("IAutomation", "Reset")
def pause(self) -> bool:
"""Pause"""
return self._call("IAutomation", "Pause")
def resume(self) -> bool:
"""Resume"""
return self._call("IAutomation", "Resume")
def get_error_code(self) -> Optional[str]:
"""GetErrorCode"""
return self._call("IAutomation", "GetErrorCode")
def clear_error_code(self) -> bool:
"""RemoveErrorCodet"""
return self._call("IAutomation", "RemoveErrorCodet")
# ---------------------------------------------------- 运行状态IMachineState
def step_state_list(self) -> List[Dict[str, Any]]:
"""GetStepStateList"""
return self._call("IMachineState", "GetStepStateList")
def step_status(self, seq_num: int) -> Dict[str, Any]:
"""GetStepStatus"""
return self._call("IMachineState", "GetStepStatus", [seq_num])
def step_state(self, seq_num: int) -> Dict[str, Any]:
"""GetStepState"""
return self._call("IMachineState", "GetStepState", [seq_num])
def axis_location(self, axis_num: int = 1) -> Dict[str, Any]:
"""GetLocation"""
return self._call("IMachineState", "GetLocation", [axis_num])
# ---------------------------------------------------- 版位矩阵IMatrix
def list_matrices(self) -> List[Dict[str, Any]]:
"""GetWorkTabletMatrices"""
return self._call("IMatrix", "GetWorkTabletMatrices")
def matrix_by_id(self, matrix_id: str) -> Dict[str, Any]:
"""GetWorkTabletMatrixById"""
return self._call("IMatrix", "GetWorkTabletMatrixById", [matrix_id])
def add_WorkTablet_Matrix(self,matrix):
return self._call("IMatrix", "AddWorkTabletMatrix", [matrix])
# ---------------------------------------------------- 一键运行
def run_solution(self, solution_id: str, channel_idx: int = 1) -> None:
self.load_solution(solution_id)
self.start(channel_idx)
# ---------------------------------------------------- 单点动作
def Load(
self,
axis: str,
dosage: int,
plate_no: int,
is_whole_plate: bool,
hole_row: int,
hole_col: int,
blending_times: int,
balance_height: int,
plate_or_hole: str,
hole_numbers: str,
assist_fun1: str = "",
assist_fun2: str = "",
assist_fun3: str = "",
assist_fun4: str = "",
assist_fun5: str = "",
liquid_method: str = "NormalDispense"
) -> Dict[str, Any]:
return {
"StepAxis": axis,
"Function": "Load",
"DosageNum": dosage,
"PlateNo": plate_no,
"IsWholePlate": is_whole_plate,
"HoleRow": hole_row,
"HoleCol": hole_col,
"BlendingTimes": blending_times,
"BalanceHeight": balance_height,
"PlateOrHoleNum": plate_or_hole,
"AssistFun1": assist_fun1,
"AssistFun2": assist_fun2,
"AssistFun3": assist_fun3,
"AssistFun4": assist_fun4,
"AssistFun5": assist_fun5,
"HoleNumbers": hole_numbers,
"LiquidDispensingMethod": liquid_method
}
def Imbibing(
self,
axis: str,
dosage: int,
plate_no: int,
is_whole_plate: bool,
hole_row: int,
hole_col: int,
blending_times: int,
balance_height: int,
plate_or_hole: str,
hole_numbers: str,
assist_fun1: str = "",
assist_fun2: str = "",
assist_fun3: str = "",
assist_fun4: str = "",
assist_fun5: str = "",
liquid_method: str = "NormalDispense"
) -> Dict[str, Any]:
return {
"StepAxis": axis,
"Function": "Imbibing",
"DosageNum": dosage,
"PlateNo": plate_no,
"IsWholePlate": is_whole_plate,
"HoleRow": hole_row,
"HoleCol": hole_col,
"BlendingTimes": blending_times,
"BalanceHeight": balance_height,
"PlateOrHoleNum": plate_or_hole,
"AssistFun1": assist_fun1,
"AssistFun2": assist_fun2,
"AssistFun3": assist_fun3,
"AssistFun4": assist_fun4,
"AssistFun5": assist_fun5,
"HoleNumbers": hole_numbers,
"LiquidDispensingMethod": liquid_method
}
def Tapping(
self,
axis: str,
dosage: int,
plate_no: int,
is_whole_plate: bool,
hole_row: int,
hole_col: int,
blending_times: int,
balance_height: int,
plate_or_hole: str,
hole_numbers: str,
assist_fun1: str = "",
assist_fun2: str = "",
assist_fun3: str = "",
assist_fun4: str = "",
assist_fun5: str = "",
liquid_method: str = "NormalDispense"
) -> Dict[str, Any]:
return {
"StepAxis": axis,
"Function": "Tapping",
"DosageNum": dosage,
"PlateNo": plate_no,
"IsWholePlate": is_whole_plate,
"HoleRow": hole_row,
"HoleCol": hole_col,
"BlendingTimes": blending_times,
"BalanceHeight": balance_height,
"PlateOrHoleNum": plate_or_hole,
"AssistFun1": assist_fun1,
"AssistFun2": assist_fun2,
"AssistFun3": assist_fun3,
"AssistFun4": assist_fun4,
"AssistFun5": assist_fun5,
"HoleNumbers": hole_numbers,
"LiquidDispensingMethod": liquid_method
}
def Blending(
self,
axis: str,
dosage: int,
plate_no: int,
is_whole_plate: bool,
hole_row: int,
hole_col: int,
blending_times: int,
balance_height: int,
plate_or_hole: str,
hole_numbers: str,
assist_fun1: str = "",
assist_fun2: str = "",
assist_fun3: str = "",
assist_fun4: str = "",
assist_fun5: str = "",
liquid_method: str = "NormalDispense"
) -> Dict[str, Any]:
return {
"StepAxis": axis,
"Function": "Blending",
"DosageNum": dosage,
"PlateNo": plate_no,
"IsWholePlate": is_whole_plate,
"HoleRow": hole_row,
"HoleCol": hole_col,
"BlendingTimes": blending_times,
"BalanceHeight": balance_height,
"PlateOrHoleNum": plate_or_hole,
"AssistFun1": assist_fun1,
"AssistFun2": assist_fun2,
"AssistFun3": assist_fun3,
"AssistFun4": assist_fun4,
"AssistFun5": assist_fun5,
"HoleNumbers": hole_numbers,
"LiquidDispensingMethod": liquid_method
}
def UnLoad(
self,
axis: str,
dosage: int,
plate_no: int,
is_whole_plate: bool,
hole_row: int,
hole_col: int,
blending_times: int,
balance_height: int,
plate_or_hole: str,
hole_numbers: str,
assist_fun1: str = "",
assist_fun2: str = "",
assist_fun3: str = "",
assist_fun4: str = "",
assist_fun5: str = "",
liquid_method: str = "NormalDispense"
) -> Dict[str, Any]:
return {
"StepAxis": axis,
"Function": "UnLoad",
"DosageNum": dosage,
"PlateNo": plate_no,
"IsWholePlate": is_whole_plate,
"HoleRow": hole_row,
"HoleCol": hole_col,
"BlendingTimes": blending_times,
"BalanceHeight": balance_height,
"PlateOrHoleNum": plate_or_hole,
"AssistFun1": assist_fun1,
"AssistFun2": assist_fun2,
"AssistFun3": assist_fun3,
"AssistFun4": assist_fun4,
"AssistFun5": assist_fun5,
"HoleNumbers": hole_numbers,
"LiquidDispensingMethod": liquid_method
}

View File

@@ -3,7 +3,11 @@ import sys
import io import io
# sys.path.insert(0, r'C:\kui\winprep_cli\winprep_c_Uni-lab\x64\Debug') # sys.path.insert(0, r'C:\kui\winprep_cli\winprep_c_Uni-lab\x64\Debug')
try:
import winprep_c import winprep_c
except ImportError as e:
print("Error importing winprep_c:", e)
print("Please ensure that the winprep_c module is correctly installed and accessible.")
from queue import Queue from queue import Queue

View File

@@ -21,7 +21,7 @@ except Exception as e:
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", ".."))) sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "..")))
from unilabos.utils.pywinauto_util import connect_application, get_process_pid_by_name, get_ui_path_with_window_specification, print_wrapper_identifiers from unilabos.utils.pywinauto_util import connect_application, get_process_pid_by_name, get_ui_path_with_window_specification, print_wrapper_identifiers
from unilabos.device_comms.universal_driver import UniversalDriver, SingleRunningExecutor from unilabos.device_comms.universal_driver import UniversalDriver, SingleRunningExecutor
from unilabos.devices.template_driver import universal_driver as ud from unilabos.device_comms import universal_driver as ud
print(f"使用文件DEBUG运行: {e}") print(f"使用文件DEBUG运行: {e}")

View File

@@ -1,11 +1,9 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import socket
import json
import base64 import base64
import argparse import json
import sys import socket
import time import time
@@ -96,6 +94,9 @@ class ZhidaClient:
def abort(self) -> dict: def abort(self) -> dict:
return self._send_command({"command": "abort"}) return self._send_command({"command": "abort"})
if __name__ == "__main__":
""" """
a,b,c a,b,c
1,2,4 1,2,4

View File

@@ -178,6 +178,31 @@ class WashSolidProtocol(BaseModel):
time: float = 0.0 time: float = 0.0
repeats: int = 1 repeats: int = 1
class AdjustPHProtocol(BaseModel):
vessel: str = Field(..., description="目标容器")
ph_value: float = Field(..., description="目标pH值") # 改为 ph_value
reagent: str = Field(..., description="酸碱试剂名称")
# 移除其他可选参数,使用默认值
class ResetHandlingProtocol(BaseModel):
solvent: str = Field(..., description="溶剂名称")
class DryProtocol(BaseModel):
compound: str = Field(..., description="化合物名称")
vessel: str = Field(..., description="目标容器")
class RecrystallizeProtocol(BaseModel):
ratio: str = Field(..., description="溶剂比例(如 '1:1', '3:7'")
solvent1: str = Field(..., description="第一种溶剂名称")
solvent2: str = Field(..., description="第二种溶剂名称")
vessel: str = Field(..., description="目标容器")
volume: float = Field(..., description="总体积 (mL)")
class HydrogenateProtocol(BaseModel):
temp: str = Field(..., description="反应温度(如 '45 °C'")
time: str = Field(..., description="反应时间(如 '2 h'")
vessel: str = Field(..., description="反应容器")
__all__ = [ __all__ = [
"Point3D", "PumpTransferProtocol", "CleanProtocol", "SeparateProtocol", "Point3D", "PumpTransferProtocol", "CleanProtocol", "SeparateProtocol",
"EvaporateProtocol", "EvacuateAndRefillProtocol", "AGVTransferProtocol", "EvaporateProtocol", "EvacuateAndRefillProtocol", "AGVTransferProtocol",
@@ -185,6 +210,8 @@ __all__ = [
"HeatChillProtocol", "HeatChillStartProtocol", "HeatChillStopProtocol", "HeatChillProtocol", "HeatChillStartProtocol", "HeatChillStopProtocol",
"StirProtocol", "StartStirProtocol", "StopStirProtocol", "StirProtocol", "StartStirProtocol", "StopStirProtocol",
"TransferProtocol", "CleanVesselProtocol", "DissolveProtocol", "TransferProtocol", "CleanVesselProtocol", "DissolveProtocol",
"FilterThroughProtocol", "RunColumnProtocol", "WashSolidProtocol" "FilterThroughProtocol", "RunColumnProtocol", "WashSolidProtocol",
"AdjustPHProtocol", "ResetHandlingProtocol", "DryProtocol",
"RecrystallizeProtocol", "HydrogenateProtocol"
] ]
# End Protocols # End Protocols

View File

@@ -1,10 +1,10 @@
io_snrd: #io_snrd:
description: IO Board with 16 IOs # description: IO Board with 16 IOs
class: # class:
module: ilabos.device_comms.SRND_16_IO:SRND_16_IO # module: unilabos.device_comms.SRND_16_IO:SRND_16_IO
type: python # type: python
hardware_interface: # hardware_interface:
name: modbus_client # name: modbus_client
extra_info: [] # extra_info: []
read: read_io_coil # read: read_io_coil
write: write_io_coil # write: write_io_coil

View File

@@ -1,7 +1,102 @@
serial: serial:
description: Serial communication interface, used when sharing same serial port for multiple devices
class: class:
module: unilabos.ros.nodes.presets.serial_node:ROS2SerialNode action_value_mappings:
type: ros2 auto-handle_serial_request:
feedback: {}
goal: {}
goal_default:
request: null
response: null
handles: []
result: {}
schema: schema:
description: handle_serial_request的参数schema
properties:
feedback: {}
goal:
properties:
request:
type: string
response:
type: string
required:
- request
- response
type: object
result: {}
required:
- goal
title: handle_serial_request参数
type: object
type: UniLabJsonCommand
auto-read_data:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: read_data的参数schema
properties:
feedback: {}
goal:
properties: {} properties: {}
required: []
type: object
result: {}
required:
- goal
title: read_data参数
type: object
type: UniLabJsonCommand
auto-send_command:
feedback: {}
goal: {}
goal_default:
command: null
handles: []
result: {}
schema:
description: send_command的参数schema
properties:
feedback: {}
goal:
properties:
command:
type: string
required:
- command
type: object
result: {}
required:
- goal
title: send_command参数
type: object
type: UniLabJsonCommand
module: unilabos.ros.nodes.presets.serial_node:ROS2SerialNode
status_types: {}
type: ros2
description: Serial communication interface, used when sharing same serial port
for multiple devices
handles: []
icon: ''
init_param_schema:
config:
properties:
baudrate:
default: 9600
type: integer
device_id:
type: string
port:
type: string
resource_tracker:
type: string
required:
- device_id
- port
type: object
data:
properties: {}
required: []
type: object

View File

@@ -0,0 +1,69 @@
camera:
class:
action_value_mappings:
auto-destroy_node:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: destroy_node的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: destroy_node参数
type: object
type: UniLabJsonCommand
auto-timer_callback:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: timer_callback的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: timer_callback参数
type: object
type: UniLabJsonCommand
module: unilabos.ros.nodes.presets.camera:VideoPublisher
status_types: {}
type: ros2
description: ''
handles: []
icon: ''
init_param_schema:
config:
properties:
camera_index:
default: 0
type: string
device_id:
default: video_publisher
type: string
period:
default: 0.1
type: number
resource_tracker:
type: string
required: []
type: object
data:
properties: {}
required: []
type: object

View File

@@ -1,67 +1,433 @@
# 光学表征设备:红外、紫外可见、拉曼等 hplc.agilent:
raman_home_made:
description: Raman spectroscopy device
class: class:
module: unilabos.devices.raman_uv.home_made_raman:RamanObj
type: python
status_types:
status: String
action_value_mappings: action_value_mappings:
raman_cmd: auto-check_status:
type: SendCmd feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: check_status的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: check_status参数
type: object
type: UniLabJsonCommand
auto-extract_data_from_txt:
feedback: {}
goal: {}
goal_default:
file_path: null
handles: []
result: {}
schema:
description: extract_data_from_txt的参数schema
properties:
feedback: {}
goal:
properties:
file_path:
type: string
required:
- file_path
type: object
result: {}
required:
- goal
title: extract_data_from_txt参数
type: object
type: UniLabJsonCommand
auto-start_sequence:
feedback: {}
goal: {}
goal_default:
params: null
resource: null
wf_name: null
handles: []
result: {}
schema:
description: start_sequence的参数schema
properties:
feedback: {}
goal:
properties:
params:
type: string
resource:
type: object
wf_name:
type: string
required:
- wf_name
type: object
result: {}
required:
- goal
title: start_sequence参数
type: object
type: UniLabJsonCommand
auto-try_close_sub_device:
feedback: {}
goal: {}
goal_default:
device_name: null
handles: []
result: {}
schema:
description: try_close_sub_device的参数schema
properties:
feedback: {}
goal:
properties:
device_name:
type: string
required: []
type: object
result: {}
required:
- goal
title: try_close_sub_device参数
type: object
type: UniLabJsonCommand
auto-try_open_sub_device:
feedback: {}
goal: {}
goal_default:
device_name: null
handles: []
result: {}
schema:
description: try_open_sub_device的参数schema
properties:
feedback: {}
goal:
properties:
device_name:
type: string
required: []
type: object
result: {}
required:
- goal
title: try_open_sub_device参数
type: object
type: UniLabJsonCommand
execute_command_from_outer:
feedback: {}
goal: goal:
command: command command: command
feedback: {} goal_default:
command: ''
handles: []
result: result:
success: success success: success
schema: schema:
description: ROS Action SendCmd 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties: properties:
status: status:
type: string type: string
required: required:
- status - status
additionalProperties: false title: SendCmd_Feedback
type: object type: object
hplc.agilent:
description: HPLC device
class:
module: unilabos.devices.hplc.AgilentHPLC:HPLCDriver
type: python
status_types:
device_status: String
could_run: Bool
driver_init_ok: Bool
is_running: Bool
finish_status: String
status_text: String
action_value_mappings:
execute_command_from_outer:
type: SendCmd
goal: goal:
command: command description: Action 目标 - 从客户端发送到服务器
feedback: {}
result:
success: success
schema:
properties: properties:
device_status: command:
type: string type: string
required:
- command
title: SendCmd_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: SendCmd_Result
type: object
required:
- goal
title: SendCmd
type: object
type: SendCmd
module: unilabos.devices.hplc.AgilentHPLC:HPLCDriver
status_types:
could_run: bool
data_file: list
device_status: str
driver_init_ok: bool
finish_status: str
is_running: bool
status_text: str
success: bool
type: python
description: HPLC device
handles: []
icon: ''
init_param_schema:
config:
properties:
driver_debug:
default: false
type: string
required: []
type: object
data:
properties:
could_run: could_run:
type: boolean type: boolean
data_file:
type: array
device_status:
type: string
driver_init_ok: driver_init_ok:
type: boolean type: boolean
is_running:
type: boolean
finish_status: finish_status:
type: string type: string
is_running:
type: boolean
status_text: status_text:
type: string type: string
success:
type: boolean
required: required:
- status_text
- device_status - device_status
- could_run - could_run
- driver_init_ok - driver_init_ok
- is_running - is_running
- success
- finish_status - finish_status
- status_text - data_file
additionalProperties: false type: object
raman_home_made:
class:
action_value_mappings:
auto-ccd_time:
feedback: {}
goal: {}
goal_default:
int_time: null
handles: []
result: {}
schema:
description: ccd_time的参数schema
properties:
feedback: {}
goal:
properties:
int_time:
type: string
required:
- int_time
type: object
result: {}
required:
- goal
title: ccd_time参数
type: object
type: UniLabJsonCommand
auto-laser_on_power:
feedback: {}
goal: {}
goal_default:
output_voltage_laser: null
handles: []
result: {}
schema:
description: laser_on_power的参数schema
properties:
feedback: {}
goal:
properties:
output_voltage_laser:
type: string
required:
- output_voltage_laser
type: object
result: {}
required:
- goal
title: laser_on_power参数
type: object
type: UniLabJsonCommand
auto-raman_cmd:
feedback: {}
goal: {}
goal_default:
command: null
handles: []
result: {}
schema:
description: raman_cmd的参数schema
properties:
feedback: {}
goal:
properties:
command:
type: string
required:
- command
type: object
result: {}
required:
- goal
title: raman_cmd参数
type: object
type: UniLabJsonCommand
auto-raman_without_background:
feedback: {}
goal: {}
goal_default:
int_time: null
laser_power: null
handles: []
result: {}
schema:
description: raman_without_background的参数schema
properties:
feedback: {}
goal:
properties:
int_time:
type: string
laser_power:
type: string
required:
- int_time
- laser_power
type: object
result: {}
required:
- goal
title: raman_without_background参数
type: object
type: UniLabJsonCommand
auto-raman_without_background_average:
feedback: {}
goal: {}
goal_default:
average: null
int_time: null
laser_power: null
sample_name: null
handles: []
result: {}
schema:
description: raman_without_background_average的参数schema
properties:
feedback: {}
goal:
properties:
average:
type: string
int_time:
type: string
laser_power:
type: string
sample_name:
type: string
required:
- sample_name
- int_time
- laser_power
- average
type: object
result: {}
required:
- goal
title: raman_without_background_average参数
type: object
type: UniLabJsonCommand
raman_cmd:
feedback: {}
goal:
command: command
goal_default:
command: ''
handles: []
result:
success: success
schema:
description: ROS Action SendCmd 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties:
status:
type: string
required:
- status
title: SendCmd_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
command:
type: string
required:
- command
title: SendCmd_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: SendCmd_Result
type: object
required:
- goal
title: SendCmd
type: object
type: SendCmd
module: unilabos.devices.raman_uv.home_made_raman:RamanObj
status_types: {}
type: python
description: Raman spectroscopy device
handles: []
icon: ''
init_param_schema:
config:
properties:
baudrate_ccd:
default: 921600
type: string
baudrate_laser:
default: 9600
type: string
port_ccd:
type: string
port_laser:
type: string
required:
- port_laser
- port_ccd
type: object
data:
properties: {}
required: []
type: object type: object

View File

@@ -1,9 +1,31 @@
hotel.thermo_orbitor_rs2_hotel: hotel.thermo_orbitor_rs2_hotel:
description: Thermo Orbitor RS2 Hotel
class: class:
action_value_mappings: {}
module: unilabos.devices.resource_container.container:HotelContainer module: unilabos.devices.resource_container.container:HotelContainer
status_types:
rotation: String
type: python type: python
description: Thermo Orbitor RS2 Hotel
handles: []
icon: ''
init_param_schema:
config:
properties:
device_config:
type: object
rotation:
type: object
required:
- rotation
- device_config
type: object
data:
properties:
rotation:
type: string
required:
- rotation
type: object
model: model:
type: device
mesh: thermo_orbitor_rs2_hotel mesh: thermo_orbitor_rs2_hotel
type: device

View File

@@ -1,56 +1,582 @@
laiyu_add_solid: laiyu_add_solid:
description: Laiyu Add Solid
class: class:
module: unilabos.devices.laiyu_add_solid.laiyu:Laiyu
type: python
status_types: {}
action_value_mappings: action_value_mappings:
add_powder_tube:
feedback: {}
goal:
compound_mass: compound_mass
powder_tube_number: powder_tube_number
target_tube_position: target_tube_position
goal_default:
compound_mass: 0.0
powder_tube_number: 0
target_tube_position: ''
handles: []
result:
actual_mass_mg: actual_mass_mg
schema:
description: ROS Action SolidDispenseAddPowderTube 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties: {}
required: []
title: SolidDispenseAddPowderTube_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
compound_mass:
type: number
powder_tube_number:
maximum: 2147483647
minimum: -2147483648
type: integer
target_tube_position:
type: string
required:
- powder_tube_number
- target_tube_position
- compound_mass
title: SolidDispenseAddPowderTube_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
actual_mass_mg:
type: number
return_info:
type: string
success:
type: boolean
required:
- return_info
- actual_mass_mg
- success
title: SolidDispenseAddPowderTube_Result
type: object
required:
- goal
title: SolidDispenseAddPowderTube
type: object
type: SolidDispenseAddPowderTube
auto-add_powder_tube:
feedback: {}
goal: {}
goal_default:
compound_mass: null
powder_tube_number: null
target_tube_position: null
handles: []
result: {}
schema:
description: add_powder_tube的参数schema
properties:
feedback: {}
goal:
properties:
compound_mass:
type: string
powder_tube_number:
type: string
target_tube_position:
type: string
required:
- powder_tube_number
- target_tube_position
- compound_mass
type: object
result: {}
required:
- goal
title: add_powder_tube参数
type: object
type: UniLabJsonCommand
auto-calculate_crc:
feedback: {}
goal: {}
goal_default:
data: null
handles: []
result: {}
schema:
description: calculate_crc的参数schema
properties:
feedback: {}
goal:
properties:
data:
type: string
required:
- data
type: object
result: {}
required:
- goal
title: calculate_crc参数
type: object
type: UniLabJsonCommand
auto-discharge:
feedback: {}
goal: {}
goal_default:
float_in: null
handles: []
result: {}
schema:
description: discharge的参数schema
properties:
feedback: {}
goal:
properties:
float_in:
type: number
required:
- float_in
type: object
result: {}
required:
- goal
title: discharge参数
type: object
type: UniLabJsonCommand
auto-move_to_plate:
feedback: {}
goal: {}
goal_default:
string: null
handles: []
result: {}
schema:
description: move_to_plate的参数schema
properties:
feedback: {}
goal:
properties:
string:
type: string
required:
- string
type: object
result: {}
required:
- goal
title: move_to_plate参数
type: object
type: UniLabJsonCommand
auto-move_to_xyz:
feedback: {}
goal: {}
goal_default:
x: null
y: null
z: null
handles: []
result: {}
schema:
description: move_to_xyz的参数schema
properties:
feedback: {}
goal:
properties:
x:
type: number
y:
type: number
z:
type: number
required:
- x
- y
- z
type: object
result: {}
required:
- goal
title: move_to_xyz参数
type: object
type: UniLabJsonCommand
auto-pick_powder_tube:
feedback: {}
goal: {}
goal_default:
int_input: null
handles: []
result: {}
schema:
description: pick_powder_tube的参数schema
properties:
feedback: {}
goal:
properties:
int_input:
type: integer
required:
- int_input
type: object
result: {}
required:
- goal
title: pick_powder_tube参数
type: object
type: UniLabJsonCommand
auto-put_powder_tube:
feedback: {}
goal: {}
goal_default:
int_input: null
handles: []
result: {}
schema:
description: put_powder_tube的参数schema
properties:
feedback: {}
goal:
properties:
int_input:
type: integer
required:
- int_input
type: object
result: {}
required:
- goal
title: put_powder_tube参数
type: object
type: UniLabJsonCommand
auto-reset:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: reset的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: reset参数
type: object
type: UniLabJsonCommand
auto-send_command:
feedback: {}
goal: {}
goal_default:
command: null
handles: []
result: {}
schema:
description: send_command的参数schema
properties:
feedback: {}
goal:
properties:
command:
type: string
required:
- command
type: object
result: {}
required:
- goal
title: send_command参数
type: object
type: UniLabJsonCommand
discharge:
feedback: {}
goal:
float_input: float_input
goal_default:
float_in: 0.0
handles: []
result: {}
schema:
description: ROS Action FloatSingleInput 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties: {}
required: []
title: FloatSingleInput_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
float_in:
type: number
required:
- float_in
title: FloatSingleInput_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: FloatSingleInput_Result
type: object
required:
- goal
title: FloatSingleInput
type: object
type: FloatSingleInput
move_to_plate:
feedback: {}
goal:
string: string
goal_default:
string: ''
handles: []
result: {}
schema:
description: ROS Action StrSingleInput 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties: {}
required: []
title: StrSingleInput_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
string:
type: string
required:
- string
title: StrSingleInput_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: StrSingleInput_Result
type: object
required:
- goal
title: StrSingleInput
type: object
type: StrSingleInput
move_to_xyz: move_to_xyz:
type: Point3DSeparateInput feedback: {}
goal: goal:
x: x x: x
y: y y: y
z: z z: z
feedback: {} goal_default:
x: 0.0
y: 0.0
z: 0.0
handles: []
result: {} result: {}
pick_powder_tube:
type: IntSingleInput
goal:
int_input: int_input
feedback: {}
result: {}
put_powder_tube:
type: IntSingleInput
goal:
int_input: int_input
feedback: {}
result: {}
reset:
type: EmptyIn
goal: {}
feedback: {}
result: {}
add_powder_tube:
type: SolidDispenseAddPowderTube
goal:
powder_tube_number: powder_tube_number
target_tube_position: target_tube_position
compound_mass: compound_mass
feedback: {}
result:
actual_mass_mg: actual_mass_mg
move_to_plate:
type: StrSingleInput
goal:
string: string
feedback: {}
result: {}
discharge:
type: FloatSingleInput
goal:
float_input: float_input
feedback: {}
result: {}
schema: schema:
description: ROS Action Point3DSeparateInput 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties: {} properties: {}
required: []
title: Point3DSeparateInput_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
x:
type: number
y:
type: number
z:
type: number
required:
- x
- y
- z
title: Point3DSeparateInput_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: Point3DSeparateInput_Result
type: object
required:
- goal
title: Point3DSeparateInput
type: object
type: Point3DSeparateInput
pick_powder_tube:
feedback: {}
goal:
int_input: int_input
goal_default:
int_input: 0
handles: []
result: {}
schema:
description: ROS Action IntSingleInput 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties: {}
required: []
title: IntSingleInput_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
int_input:
maximum: 2147483647
minimum: -2147483648
type: integer
required:
- int_input
title: IntSingleInput_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: IntSingleInput_Result
type: object
required:
- goal
title: IntSingleInput
type: object
type: IntSingleInput
put_powder_tube:
feedback: {}
goal:
int_input: int_input
goal_default:
int_input: 0
handles: []
result: {}
schema:
description: ROS Action IntSingleInput 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties: {}
required: []
title: IntSingleInput_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
int_input:
maximum: 2147483647
minimum: -2147483648
type: integer
required:
- int_input
title: IntSingleInput_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: IntSingleInput_Result
type: object
required:
- goal
title: IntSingleInput
type: object
type: IntSingleInput
reset:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: ROS Action EmptyIn 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties: {}
required: []
title: EmptyIn_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties: {}
required: []
title: EmptyIn_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
required:
- return_info
title: EmptyIn_Result
type: object
required:
- goal
title: EmptyIn
type: object
type: EmptyIn
module: unilabos.devices.laiyu_add_solid.laiyu:Laiyu
status_types:
status: str
type: python
description: Laiyu Add Solid
handles: []
icon: ''
init_param_schema:
config:
properties:
baudrate:
default: 115200
type: string
port:
type: string
timeout:
default: 0.5
type: string
required:
- port
type: object
data:
properties:
status:
type: string
required:
- status
type: object

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,56 +1,858 @@
moveit.toyo_xyz:
description: Toyo XYZ
class:
module: unilabos.devices.ros_dev.moveit_interface:MoveitInterface
type: python
action_value_mappings:
set_position:
type: SendCmd
goal:
command: command
feedback: { }
result: { }
pick_and_place:
type: SendCmd
goal:
command: command
feedback: { }
result: { }
set_status:
type: SendCmd
goal:
command: command
feedback: { }
result: { }
model:
type: device
mesh: toyo_xyz
moveit.arm_slider: moveit.arm_slider:
description: Arm with Slider
model:
type: device
mesh: arm_slider
class: class:
module: unilabos.devices.ros_dev.moveit_interface:MoveitInterface
type: python
action_value_mappings: action_value_mappings:
set_position: auto-check_tf_update_actions:
type: SendCmd
goal:
command: command
feedback: {} feedback: {}
goal: {}
goal_default: {}
handles: []
result: {} result: {}
schema:
description: check_tf_update_actions的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: check_tf_update_actions参数
type: object
type: UniLabJsonCommand
auto-moveit_joint_task:
feedback: {}
goal: {}
goal_default:
joint_names: null
joint_positions: null
move_group: null
retry: 10
speed: 1
handles: []
result: {}
schema:
description: moveit_joint_task的参数schema
properties:
feedback: {}
goal:
properties:
joint_names:
type: string
joint_positions:
type: string
move_group:
type: string
retry:
default: 10
type: string
speed:
default: 1
type: string
required:
- move_group
- joint_positions
type: object
result: {}
required:
- goal
title: moveit_joint_task参数
type: object
type: UniLabJsonCommand
auto-moveit_task:
feedback: {}
goal: {}
goal_default:
cartesian: false
move_group: null
offsets:
- 0
- 0
- 0
position: null
quaternion: null
retry: 10
speed: 1
target_link: null
handles: []
result: {}
schema:
description: moveit_task的参数schema
properties:
feedback: {}
goal:
properties:
cartesian:
default: false
type: string
move_group:
type: string
offsets:
default:
- 0
- 0
- 0
type: string
position:
type: string
quaternion:
type: string
retry:
default: 10
type: string
speed:
default: 1
type: string
target_link:
type: string
required:
- move_group
- position
- quaternion
type: object
result: {}
required:
- goal
title: moveit_task参数
type: object
type: UniLabJsonCommand
auto-pick_and_place:
feedback: {}
goal: {}
goal_default:
command: null
handles: []
result: {}
schema:
description: pick_and_place的参数schema
properties:
feedback: {}
goal:
properties:
command:
type: string
required:
- command
type: object
result: {}
required:
- goal
title: pick_and_place参数
type: object
type: UniLabJsonCommand
auto-post_init:
feedback: {}
goal: {}
goal_default:
ros_node: null
handles: []
result: {}
schema:
description: post_init的参数schema
properties:
feedback: {}
goal:
properties:
ros_node:
type: string
required:
- ros_node
type: object
result: {}
required:
- goal
title: post_init参数
type: object
type: UniLabJsonCommand
auto-resource_manager:
feedback: {}
goal: {}
goal_default:
parent_link: null
resource: null
handles: []
result: {}
schema:
description: resource_manager的参数schema
properties:
feedback: {}
goal:
properties:
parent_link:
type: string
resource:
type: string
required:
- resource
- parent_link
type: object
result: {}
required:
- goal
title: resource_manager参数
type: object
type: UniLabJsonCommand
auto-set_position:
feedback: {}
goal: {}
goal_default:
command: null
handles: []
result: {}
schema:
description: set_position的参数schema
properties:
feedback: {}
goal:
properties:
command:
type: string
required:
- command
type: object
result: {}
required:
- goal
title: set_position参数
type: object
type: UniLabJsonCommand
auto-set_status:
feedback: {}
goal: {}
goal_default:
command: null
handles: []
result: {}
schema:
description: set_status的参数schema
properties:
feedback: {}
goal:
properties:
command:
type: string
required:
- command
type: object
result: {}
required:
- goal
title: set_status参数
type: object
type: UniLabJsonCommand
auto-wait_for_resource_action:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: wait_for_resource_action的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: wait_for_resource_action参数
type: object
type: UniLabJsonCommand
pick_and_place: pick_and_place:
type: SendCmd feedback: {}
goal: goal:
command: command command: command
feedback: {} goal_default:
command: ''
handles: []
result: {} result: {}
schema:
description: ROS Action SendCmd 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties:
status:
type: string
required:
- status
title: SendCmd_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
command:
type: string
required:
- command
title: SendCmd_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: SendCmd_Result
type: object
required:
- goal
title: SendCmd
type: object
type: SendCmd
set_position:
feedback: {}
goal:
command: command
goal_default:
command: ''
handles: []
result: {}
schema:
description: ROS Action SendCmd 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties:
status:
type: string
required:
- status
title: SendCmd_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
command:
type: string
required:
- command
title: SendCmd_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: SendCmd_Result
type: object
required:
- goal
title: SendCmd
type: object
type: SendCmd
set_status: set_status:
type: SendCmd feedback: {}
goal: goal:
command: command command: command
feedback: {} goal_default:
command: ''
handles: []
result: {} result: {}
schema:
description: ROS Action SendCmd 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties:
status:
type: string
required:
- status
title: SendCmd_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
command:
type: string
required:
- command
title: SendCmd_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: SendCmd_Result
type: object
required:
- goal
title: SendCmd
type: object
type: SendCmd
module: unilabos.devices.ros_dev.moveit_interface:MoveitInterface
status_types: {}
type: python
description: Arm with Slider
handles: []
icon: ''
init_param_schema:
config:
properties:
device_config:
type: string
joint_poses:
type: string
moveit_type:
type: string
rotation:
type: string
required:
- moveit_type
- joint_poses
type: object
data:
properties: {}
required: []
type: object
model:
mesh: arm_slider
type: device
moveit.toyo_xyz:
class:
action_value_mappings:
auto-check_tf_update_actions:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: check_tf_update_actions的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: check_tf_update_actions参数
type: object
type: UniLabJsonCommand
auto-moveit_joint_task:
feedback: {}
goal: {}
goal_default:
joint_names: null
joint_positions: null
move_group: null
retry: 10
speed: 1
handles: []
result: {}
schema:
description: moveit_joint_task的参数schema
properties:
feedback: {}
goal:
properties:
joint_names:
type: string
joint_positions:
type: string
move_group:
type: string
retry:
default: 10
type: string
speed:
default: 1
type: string
required:
- move_group
- joint_positions
type: object
result: {}
required:
- goal
title: moveit_joint_task参数
type: object
type: UniLabJsonCommand
auto-moveit_task:
feedback: {}
goal: {}
goal_default:
cartesian: false
move_group: null
offsets:
- 0
- 0
- 0
position: null
quaternion: null
retry: 10
speed: 1
target_link: null
handles: []
result: {}
schema:
description: moveit_task的参数schema
properties:
feedback: {}
goal:
properties:
cartesian:
default: false
type: string
move_group:
type: string
offsets:
default:
- 0
- 0
- 0
type: string
position:
type: string
quaternion:
type: string
retry:
default: 10
type: string
speed:
default: 1
type: string
target_link:
type: string
required:
- move_group
- position
- quaternion
type: object
result: {}
required:
- goal
title: moveit_task参数
type: object
type: UniLabJsonCommand
auto-pick_and_place:
feedback: {}
goal: {}
goal_default:
command: null
handles: []
result: {}
schema:
description: pick_and_place的参数schema
properties:
feedback: {}
goal:
properties:
command:
type: string
required:
- command
type: object
result: {}
required:
- goal
title: pick_and_place参数
type: object
type: UniLabJsonCommand
auto-post_init:
feedback: {}
goal: {}
goal_default:
ros_node: null
handles: []
result: {}
schema:
description: post_init的参数schema
properties:
feedback: {}
goal:
properties:
ros_node:
type: string
required:
- ros_node
type: object
result: {}
required:
- goal
title: post_init参数
type: object
type: UniLabJsonCommand
auto-resource_manager:
feedback: {}
goal: {}
goal_default:
parent_link: null
resource: null
handles: []
result: {}
schema:
description: resource_manager的参数schema
properties:
feedback: {}
goal:
properties:
parent_link:
type: string
resource:
type: string
required:
- resource
- parent_link
type: object
result: {}
required:
- goal
title: resource_manager参数
type: object
type: UniLabJsonCommand
auto-set_position:
feedback: {}
goal: {}
goal_default:
command: null
handles: []
result: {}
schema:
description: set_position的参数schema
properties:
feedback: {}
goal:
properties:
command:
type: string
required:
- command
type: object
result: {}
required:
- goal
title: set_position参数
type: object
type: UniLabJsonCommand
auto-set_status:
feedback: {}
goal: {}
goal_default:
command: null
handles: []
result: {}
schema:
description: set_status的参数schema
properties:
feedback: {}
goal:
properties:
command:
type: string
required:
- command
type: object
result: {}
required:
- goal
title: set_status参数
type: object
type: UniLabJsonCommand
auto-wait_for_resource_action:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: wait_for_resource_action的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: wait_for_resource_action参数
type: object
type: UniLabJsonCommand
pick_and_place:
feedback: {}
goal:
command: command
goal_default:
command: ''
handles: []
result: {}
schema:
description: ROS Action SendCmd 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties:
status:
type: string
required:
- status
title: SendCmd_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
command:
type: string
required:
- command
title: SendCmd_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: SendCmd_Result
type: object
required:
- goal
title: SendCmd
type: object
type: SendCmd
set_position:
feedback: {}
goal:
command: command
goal_default:
command: ''
handles: []
result: {}
schema:
description: ROS Action SendCmd 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties:
status:
type: string
required:
- status
title: SendCmd_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
command:
type: string
required:
- command
title: SendCmd_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: SendCmd_Result
type: object
required:
- goal
title: SendCmd
type: object
type: SendCmd
set_status:
feedback: {}
goal:
command: command
goal_default:
command: ''
handles: []
result: {}
schema:
description: ROS Action SendCmd 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties:
status:
type: string
required:
- status
title: SendCmd_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
command:
type: string
required:
- command
title: SendCmd_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: SendCmd_Result
type: object
required:
- goal
title: SendCmd
type: object
type: SendCmd
module: unilabos.devices.ros_dev.moveit_interface:MoveitInterface
status_types: {}
type: python
description: Toyo XYZ
handles: []
icon: ''
init_param_schema:
config:
properties:
device_config:
type: string
joint_poses:
type: string
moveit_type:
type: string
rotation:
type: string
required:
- moveit_type
- joint_poses
type: object
data:
properties: {}
required: []
type: object
model:
mesh: toyo_xyz
type: device

View File

@@ -1,73 +1,451 @@
separator.homemade: rotavap.one:
description: Separator device with homemade grbl controller
class: class:
module: unilabos.devices.separator.homemade_grbl_conductivity:SeparatorController
type: python
status_types:
sensordata: Float64
status: String
action_value_mappings: action_value_mappings:
stir: auto-cmd_write:
type: Stir feedback: {}
goal: {}
goal_default:
cmd: null
handles: []
result: {}
schema:
description: cmd_write的参数schema
properties:
feedback: {}
goal: goal:
stir_time: stir_time, properties:
stir_speed: stir_speed cmd:
settling_time: settling_time type: string
feedback: required:
status: status - cmd
result: type: object
success: success result: {}
valve_open_cmd: required:
type: SendCmd - goal
title: cmd_write参数
type: object
type: UniLabJsonCommand
auto-main_loop:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: main_loop的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: main_loop参数
type: object
type: UniLabJsonCommand
auto-set_pump_time:
feedback: {}
goal: {}
goal_default:
time: null
handles: []
result: {}
schema:
description: set_pump_time的参数schema
properties:
feedback: {}
goal:
properties:
time:
type: string
required:
- time
type: object
result: {}
required:
- goal
title: set_pump_time参数
type: object
type: UniLabJsonCommand
auto-set_rotate_time:
feedback: {}
goal: {}
goal_default:
time: null
handles: []
result: {}
schema:
description: set_rotate_time的参数schema
properties:
feedback: {}
goal:
properties:
time:
type: string
required:
- time
type: object
result: {}
required:
- goal
title: set_rotate_time参数
type: object
type: UniLabJsonCommand
auto-set_timer:
feedback: {}
goal: {}
goal_default:
command: null
handles: []
result: {}
schema:
description: set_timer的参数schema
properties:
feedback: {}
goal:
properties:
command:
type: string
required:
- command
type: object
result: {}
required:
- goal
title: set_timer参数
type: object
type: UniLabJsonCommand
set_timer:
feedback: {}
goal: goal:
command: command command: command
feedback: goal_default:
status: status command: ''
result": handles: []
result:
success: success success: success
schema: schema:
type: object description: ROS Action SendCmd 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties: properties:
status: status:
type: string type: string
description: The status of the device
sensordata:
type: number
description: 电导传感器数据
required: required:
- status - status
- sensordata title: SendCmd_Feedback
additionalProperties: false type: object
rotavap.one:
description: Rotavap device
class:
module: unilabos.devices.rotavap.rotavap_one:RotavapOne
type: python
status_types:
pump_time: Float64
rotate_time: Float64
action_value_mappings:
set_timer:
type: SendCmd
goal: goal:
command: command description: Action 目标 - 从客户端发送到服务器
properties:
command:
type: string
required:
- command
title: SendCmd_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: SendCmd_Result
type: object
required:
- goal
title: SendCmd
type: object
type: SendCmd
module: unilabos.devices.rotavap.rotavap_one:RotavapOne
status_types: {}
type: python
description: Rotavap device
handles: []
icon: ''
init_param_schema:
config:
properties:
port:
type: string
rate:
default: 9600
type: string
required:
- port
type: object
data:
properties: {}
required: []
type: object
separator.homemade:
class:
action_value_mappings:
auto-read_sensor_loop:
feedback: {} feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: read_sensor_loop的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: read_sensor_loop参数
type: object
type: UniLabJsonCommand
auto-stir:
feedback: {}
goal: {}
goal_default:
settling_time: 10
stir_speed: 300
stir_time: 10
handles: []
result: {}
schema:
description: stir的参数schema
properties:
feedback: {}
goal:
properties:
settling_time:
default: 10
type: number
stir_speed:
default: 300
type: number
stir_time:
default: 10
type: number
required: []
type: object
result: {}
required:
- goal
title: stir参数
type: object
type: UniLabJsonCommand
auto-valve_open:
feedback: {}
goal: {}
goal_default:
condition: null
value: null
handles: []
result: {}
schema:
description: valve_open的参数schema
properties:
feedback: {}
goal:
properties:
condition:
type: string
value:
type: string
required:
- condition
- value
type: object
result: {}
required:
- goal
title: valve_open参数
type: object
type: UniLabJsonCommand
auto-valve_open_cmd:
feedback: {}
goal: {}
goal_default:
command: null
handles: []
result: {}
schema:
description: valve_open_cmd的参数schema
properties:
feedback: {}
goal:
properties:
command:
type: string
required:
- command
type: object
result: {}
required:
- goal
title: valve_open_cmd参数
type: object
type: UniLabJsonCommand
auto-write:
feedback: {}
goal: {}
goal_default:
data: null
handles: []
result: {}
schema:
description: write的参数schema
properties:
feedback: {}
goal:
properties:
data:
type: string
required:
- data
type: object
result: {}
required:
- goal
title: write参数
type: object
type: UniLabJsonCommand
stir:
feedback:
status: status
goal:
settling_time: settling_time
stir_speed: stir_speed
stir_time: stir_time,
goal_default:
settling_time: 0.0
stir_speed: 0.0
stir_time: 0.0
handles: []
result: result:
success: success success: success
schema: schema:
type: object description: ROS Action Stir 的 JSON Schema
properties: properties:
temperature: feedback:
type: number description: Action 反馈 - 执行过程中从服务器发送到客户端
description: 旋蒸水浴温度 properties:
pump_time: status:
type: number type: string
description: The pump time of the device
rotate_time:
type: number
description: The rotate time of the device
required: required:
- pump_time - status
- rotate_time title: Stir_Feedback
additionalProperties: false type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
settling_time:
type: number
stir_speed:
type: number
stir_time:
type: number
required:
- stir_time
- stir_speed
- settling_time
title: Stir_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: Stir_Result
type: object
required:
- goal
title: Stir
type: object
type: Stir
valve_open_cmd:
feedback:
status: status
goal:
command: command
goal_default:
command: ''
handles: []
result":
success: success
schema:
description: ROS Action SendCmd 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties:
status:
type: string
required:
- status
title: SendCmd_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
command:
type: string
required:
- command
title: SendCmd_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: SendCmd_Result
type: object
required:
- goal
title: SendCmd
type: object
type: SendCmd
module: unilabos.devices.separator.homemade_grbl_conductivity:SeparatorController
status_types: {}
type: python
description: Separator device with homemade grbl controller
handles: []
icon: ''
init_param_schema:
config:
properties:
baudrate_executor:
default: 115200
type: integer
baudrate_sensor:
default: 115200
type: integer
port_executor:
type: string
port_sensor:
type: string
required:
- port_executor
- port_sensor
type: object
data:
properties: {}
required: []
type: object

View File

@@ -1,85 +1,877 @@
syringe_pump_with_valve.runze: solenoid_valve:
description: Runze Syringe pump with valve
class: class:
module: unilabos.devices.pump_and_valve.runze_backbone:RunzeSyringePump action_value_mappings:
auto-close:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: close的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: close参数
type: object
type: UniLabJsonCommand
auto-is_closed:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: is_closed的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: is_closed参数
type: object
type: UniLabJsonCommand
auto-is_open:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: is_open的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: is_open参数
type: object
type: UniLabJsonCommand
auto-open:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: open的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: open参数
type: object
type: UniLabJsonCommand
auto-read_data:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: read_data的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: read_data参数
type: object
type: UniLabJsonCommand
auto-send_command:
feedback: {}
goal: {}
goal_default:
command: null
handles: []
result: {}
schema:
description: send_command的参数schema
properties:
feedback: {}
goal:
properties:
command:
type: string
required:
- command
type: object
result: {}
required:
- goal
title: send_command参数
type: object
type: UniLabJsonCommand
auto-set_valve_position:
feedback: {}
goal: {}
goal_default:
position: null
handles: []
result: {}
schema:
description: set_valve_position的参数schema
properties:
feedback: {}
goal:
properties:
position:
type: string
required:
- position
type: object
result: {}
required:
- goal
title: set_valve_position参数
type: object
type: UniLabJsonCommand
set_valve_position:
feedback: {}
goal:
string: position
goal_default:
string: ''
handles: []
result: {}
schema:
description: ROS Action StrSingleInput 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties: {}
required: []
title: StrSingleInput_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
string:
type: string
required:
- string
title: StrSingleInput_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: StrSingleInput_Result
type: object
required:
- goal
title: StrSingleInput
type: object
type: StrSingleInput
module: unilabos.devices.pump_and_valve.solenoid_valve:SolenoidValve
status_types:
status: str
valve_position: str
type: python type: python
description: Solenoid valve
handles: []
icon: ''
init_param_schema:
config:
properties:
io_device_port:
type: string
required:
- io_device_port
type: object
data:
properties:
status:
type: string
valve_position:
type: string
required:
- status
- valve_position
type: object
solenoid_valve.mock:
class:
action_value_mappings:
auto-close:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: close的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: close参数
type: object
type: UniLabJsonCommand
auto-is_closed:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: is_closed的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: is_closed参数
type: object
type: UniLabJsonCommand
auto-is_open:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: is_open的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: is_open参数
type: object
type: UniLabJsonCommand
auto-open:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: open的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: open参数
type: object
type: UniLabJsonCommand
auto-set_valve_position:
feedback: {}
goal: {}
goal_default:
position: null
handles: []
result: {}
schema:
description: set_valve_position的参数schema
properties:
feedback: {}
goal:
properties:
position:
type: string
required:
- position
type: object
result: {}
required:
- goal
title: set_valve_position参数
type: object
type: UniLabJsonCommand
close:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: ROS Action EmptyIn 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties: {}
required: []
title: EmptyIn_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties: {}
required: []
title: EmptyIn_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
required:
- return_info
title: EmptyIn_Result
type: object
required:
- goal
title: EmptyIn
type: object
type: EmptyIn
open:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: ROS Action EmptyIn 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties: {}
required: []
title: EmptyIn_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties: {}
required: []
title: EmptyIn_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
required:
- return_info
title: EmptyIn_Result
type: object
required:
- goal
title: EmptyIn
type: object
type: EmptyIn
module: unilabos.devices.pump_and_valve.solenoid_valve_mock:SolenoidValveMock
status_types:
status: str
valve_position: str
type: python
description: Mock solenoid valve
handles:
- data_type: fluid
handler_key: in
io_type: target
label: in
side: NORTH
- data_type: fluid
handler_key: out
io_type: source
label: out
side: SOUTH
icon: ''
init_param_schema:
config:
properties:
port:
default: COM6
type: string
required: []
type: object
data:
properties:
status:
type: string
valve_position:
type: string
required:
- status
- valve_position
type: object
syringe_pump_with_valve.runze:
class:
action_value_mappings:
auto-close:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: close的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: close参数
type: object
type: UniLabJsonCommand
auto-initialize:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: initialize的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: initialize参数
type: object
type: UniLabJsonCommand
auto-pull_plunger:
feedback: {}
goal: {}
goal_default:
volume: null
handles: []
result: {}
schema:
description: pull_plunger的参数schema
properties:
feedback: {}
goal:
properties:
volume:
type: number
required:
- volume
type: object
result: {}
required:
- goal
title: pull_plunger参数
type: object
type: UniLabJsonCommand
auto-push_plunger:
feedback: {}
goal: {}
goal_default:
volume: null
handles: []
result: {}
schema:
description: push_plunger的参数schema
properties:
feedback: {}
goal:
properties:
volume:
type: number
required:
- volume
type: object
result: {}
required:
- goal
title: push_plunger参数
type: object
type: UniLabJsonCommand
auto-query_aux_input_status_1:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: query_aux_input_status_1的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: query_aux_input_status_1参数
type: object
type: UniLabJsonCommand
auto-query_aux_input_status_2:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: query_aux_input_status_2的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: query_aux_input_status_2参数
type: object
type: UniLabJsonCommand
auto-query_backlash_position:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: query_backlash_position的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: query_backlash_position参数
type: object
type: UniLabJsonCommand
auto-query_command_buffer_status:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: query_command_buffer_status的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: query_command_buffer_status参数
type: object
type: UniLabJsonCommand
auto-query_software_version:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: query_software_version的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: query_software_version参数
type: object
type: UniLabJsonCommand
auto-send_command:
feedback: {}
goal: {}
goal_default:
full_command: null
handles: []
result: {}
schema:
description: send_command的参数schema
properties:
feedback: {}
goal:
properties:
full_command:
type: string
required:
- full_command
type: object
result: {}
required:
- goal
title: send_command参数
type: object
type: UniLabJsonCommand
auto-set_baudrate:
feedback: {}
goal: {}
goal_default:
baudrate: null
handles: []
result: {}
schema:
description: set_baudrate的参数schema
properties:
feedback: {}
goal:
properties:
baudrate:
type: string
required:
- baudrate
type: object
result: {}
required:
- goal
title: set_baudrate参数
type: object
type: UniLabJsonCommand
auto-set_max_velocity:
feedback: {}
goal: {}
goal_default:
velocity: null
handles: []
result: {}
schema:
description: set_max_velocity的参数schema
properties:
feedback: {}
goal:
properties:
velocity:
type: number
required:
- velocity
type: object
result: {}
required:
- goal
title: set_max_velocity参数
type: object
type: UniLabJsonCommand
auto-set_position:
feedback: {}
goal: {}
goal_default:
max_velocity: null
position: null
handles: []
result: {}
schema:
description: set_position的参数schema
properties:
feedback: {}
goal:
properties:
max_velocity:
type: number
position:
type: number
required:
- position
type: object
result: {}
required:
- goal
title: set_position参数
type: object
type: UniLabJsonCommand
auto-set_valve_position:
feedback: {}
goal: {}
goal_default:
position: null
handles: []
result: {}
schema:
description: set_valve_position的参数schema
properties:
feedback: {}
goal:
properties:
position:
type: string
required:
- position
type: object
result: {}
required:
- goal
title: set_valve_position参数
type: object
type: UniLabJsonCommand
auto-set_velocity_grade:
feedback: {}
goal: {}
goal_default:
velocity: null
handles: []
result: {}
schema:
description: set_velocity_grade的参数schema
properties:
feedback: {}
goal:
properties:
velocity:
type: string
required:
- velocity
type: object
result: {}
required:
- goal
title: set_velocity_grade参数
type: object
type: UniLabJsonCommand
auto-stop_operation:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: stop_operation的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: stop_operation参数
type: object
type: UniLabJsonCommand
auto-wait_error:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: wait_error的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: wait_error参数
type: object
type: UniLabJsonCommand
hardware_interface: hardware_interface:
name: hardware_interface name: hardware_interface
read: send_command read: send_command
write: send_command write: send_command
schema: module: unilabos.devices.pump_and_valve.runze_backbone:RunzeSyringePump
type: object
properties:
status:
type: string
description: The status of the device
position:
type: number
description: The volume of the syringe
speed_max:
type: number
description: The speed of the syringe
valve_position:
type: string
description: The position of the valve
required:
- status
- position
- valve_position
additionalProperties: false
solenoid_valve.mock:
description: Mock solenoid valve
class:
module: unilabos.devices.pump_and_valve.solenoid_valve_mock:SolenoidValveMock
type: python
status_types: status_types:
status: String max_velocity: float
valve_position: String mode: int
action_value_mappings: plunger_position: String
open: position: float
type: EmptyIn status: str
goal: {} valve_position: str
feedback: {} velocity_end: String
result: {} velocity_grade: String
close: velocity_init: String
type: EmptyIn type: python
goal: {} description: Runze Syringe pump with valve
feedback: {} handles: []
result: {} icon: ''
handles:
- handler_key: in
label: in
io_type: target
data_type: fluid
side: NORTH
- handler_key: out
label: out
io_type: source
data_type: fluid
side: SOUTH
init_param_schema: init_param_schema:
type: object config:
properties: properties:
address:
default: '1'
type: string
max_volume:
default: 25.0
type: number
mode:
type: string
port: port:
type: string type: string
description: "通信端口"
default: "COM6"
required: required:
- port - port
type: object
solenoid_valve: data:
description: Solenoid valve properties:
class: max_velocity:
module: unilabos.devices.pump_and_valve.solenoid_valve:SolenoidValve type: number
type: python mode:
status_types: type: integer
status: String plunger_position:
valve_position: String type: string
action_value_mappings: position:
set_valve_position: type: number
type: StrSingleInput status:
goal: type: string
string: position valve_position:
feedback: {} type: string
result: {} velocity_end:
type: string
velocity_grade:
type: string
velocity_init:
type: string
required:
- status
- mode
- max_velocity
- velocity_grade
- velocity_init
- velocity_end
- valve_position
- position
- plunger_position
type: object

View File

@@ -1,29 +1,132 @@
# 仙工智能底盘(知行使用)
agv.SEER: agv.SEER:
description: SEER AGV
class: class:
module: unilabos.devices.agv.agv_navigator:AgvNavigator
type: python
status_types:
pose: Float64MultiArray
status: String
action_value_mappings: action_value_mappings:
auto-send:
feedback: {}
goal: {}
goal_default:
cmd: null
ex_data: ''
obj: receive_socket
handles: []
result: {}
schema:
description: send的参数schema
properties:
feedback: {}
goal:
properties:
cmd:
type: string
ex_data:
default: ''
type: string
obj:
default: receive_socket
type: string
required:
- cmd
type: object
result: {}
required:
- goal
title: send参数
type: object
type: UniLabJsonCommand
auto-send_nav_task:
feedback: {}
goal: {}
goal_default:
command: null
handles: []
result: {}
schema:
description: send_nav_task的参数schema
properties:
feedback: {}
goal:
properties:
command:
type: string
required:
- command
type: object
result: {}
required:
- goal
title: send_nav_task参数
type: object
type: UniLabJsonCommand
send_nav_task: send_nav_task:
type: SendCmd feedback: {}
goal: goal:
command: command command: command
feedback: {} goal_default:
command: ''
handles: []
result: result:
success: success success: success
schema: schema:
description: ROS Action SendCmd 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties: properties:
pose:
type: array
items:
type: number
status: status:
type: string type: string
required: required:
- status - status
additionalProperties: false title: SendCmd_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
command:
type: string
required:
- command
title: SendCmd_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: SendCmd_Result
type: object
required:
- goal
title: SendCmd
type: object
type: SendCmd
module: unilabos.devices.agv.agv_navigator:AgvNavigator
status_types:
pose: list
status: str
type: python
description: SEER AGV
handles: []
icon: ''
init_param_schema:
config:
properties:
host:
type: string
required:
- host
type: object
data:
properties:
pose:
type: array
status:
type: string
required:
- pose
- status
type: object type: object

View File

@@ -1,37 +1,199 @@
robotic_arm.UR: robotic_arm.UR:
description: UR robotic arm
class: class:
module: unilabos.devices.agv.ur_arm_task:UrArmTask
type: python
status_types:
arm_pose: Float64MultiArray
gripper_pose: Float64
arm_status: String
gripper_status: String
action_value_mappings: action_value_mappings:
auto-arm_init:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: arm_init的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: arm_init参数
type: object
type: UniLabJsonCommand
auto-load_pose_data:
feedback: {}
goal: {}
goal_default:
data: null
handles: []
result: {}
schema:
description: load_pose_data的参数schema
properties:
feedback: {}
goal:
properties:
data:
type: string
required:
- data
type: object
result: {}
required:
- goal
title: load_pose_data参数
type: object
type: UniLabJsonCommand
auto-load_pose_file:
feedback: {}
goal: {}
goal_default:
file: null
handles: []
result: {}
schema:
description: load_pose_file的参数schema
properties:
feedback: {}
goal:
properties:
file:
type: string
required:
- file
type: object
result: {}
required:
- goal
title: load_pose_file参数
type: object
type: UniLabJsonCommand
auto-move_pos_task:
feedback: {}
goal: {}
goal_default:
command: null
handles: []
result: {}
schema:
description: move_pos_task的参数schema
properties:
feedback: {}
goal:
properties:
command:
type: string
required:
- command
type: object
result: {}
required:
- goal
title: move_pos_task参数
type: object
type: UniLabJsonCommand
auto-reload_pose:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: reload_pose的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: reload_pose参数
type: object
type: UniLabJsonCommand
move_pos_task: move_pos_task:
type: SendCmd feedback: {}
goal: goal:
command: command command: command
feedback: {} goal_default:
command: ''
handles: []
result: result:
success: success success: success
schema: schema:
description: ROS Action SendCmd 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties:
status:
type: string
required:
- status
title: SendCmd_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
command:
type: string
required:
- command
title: SendCmd_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: SendCmd_Result
type: object
required:
- goal
title: SendCmd
type: object
type: SendCmd
module: unilabos.devices.agv.ur_arm_task:UrArmTask
status_types:
arm_pose: list
arm_status: str
gripper_pose: float
gripper_status: str
type: python
description: UR robotic arm
handles: []
icon: ''
init_param_schema:
config:
properties:
host:
type: string
retry:
default: 30
type: string
required:
- host
type: object
data:
properties: properties:
arm_pose: arm_pose:
type: array type: array
items:
type: number
gripper_pose:
type: number
arm_status: arm_status:
type: string type: string
description: 机械臂设备状态 gripper_pose:
type: number
gripper_status: gripper_status:
type: string type: string
description: 机械爪设备状态
required: required:
- arm_pose
- gripper_pose
- arm_status - arm_status
- gripper_status - gripper_status
additionalProperties: false
type: object type: object

View File

@@ -1,37 +1,626 @@
gripper.mock:
description: Mock gripper
class:
module: unilabos.devices.gripper.mock:MockGripper
type: python
status_types:
position: Float64
torque: Float64
status: String
action_value_mappings:
push_to:
type: GripperCommand
goal:
command.position: position
command.max_effort: torque
feedback:
position: position
effort: torque
result:
position: position
effort: torque
gripper.misumi_rz: gripper.misumi_rz:
description: Misumi RZ gripper
class: class:
module: unilabos.devices.motor:Grasp.EleGripper
type: python
status_types:
status: String
action_value_mappings: action_value_mappings:
auto-data_loop:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: data_loop的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: data_loop参数
type: object
type: UniLabJsonCommand
auto-data_reader:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: data_reader的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: data_reader参数
type: object
type: UniLabJsonCommand
auto-gripper_move:
feedback: {}
goal: {}
goal_default:
force: null
pos: null
speed: null
handles: []
result: {}
schema:
description: gripper_move的参数schema
properties:
feedback: {}
goal:
properties:
force:
type: string
pos:
type: string
speed:
type: string
required:
- pos
- speed
- force
type: object
result: {}
required:
- goal
title: gripper_move参数
type: object
type: UniLabJsonCommand
auto-init_gripper:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: init_gripper的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: init_gripper参数
type: object
type: UniLabJsonCommand
auto-modbus_crc:
feedback: {}
goal: {}
goal_default:
data: null
handles: []
result: {}
schema:
description: modbus_crc的参数schema
properties:
feedback: {}
goal:
properties:
data:
type: string
required:
- data
type: object
result: {}
required:
- goal
title: modbus_crc参数
type: object
type: UniLabJsonCommand
auto-move_and_rotate:
feedback: {}
goal: {}
goal_default:
grasp_F: null
grasp_pos: null
grasp_v: null
spin_F: null
spin_pos: null
spin_v: null
handles: []
result: {}
schema:
description: move_and_rotate的参数schema
properties:
feedback: {}
goal:
properties:
grasp_F:
type: string
grasp_pos:
type: string
grasp_v:
type: string
spin_F:
type: string
spin_pos:
type: string
spin_v:
type: string
required:
- spin_pos
- grasp_pos
- spin_v
- grasp_v
- spin_F
- grasp_F
type: object
result: {}
required:
- goal
title: move_and_rotate参数
type: object
type: UniLabJsonCommand
auto-node_gripper_move:
feedback: {}
goal: {}
goal_default:
cmd: null
handles: []
result: {}
schema:
description: node_gripper_move的参数schema
properties:
feedback: {}
goal:
properties:
cmd:
type: string
required:
- cmd
type: object
result: {}
required:
- goal
title: node_gripper_move参数
type: object
type: UniLabJsonCommand
auto-node_rotate_move:
feedback: {}
goal: {}
goal_default:
cmd: null
handles: []
result: {}
schema:
description: node_rotate_move的参数schema
properties:
feedback: {}
goal:
properties:
cmd:
type: string
required:
- cmd
type: object
result: {}
required:
- goal
title: node_rotate_move参数
type: object
type: UniLabJsonCommand
auto-read_address:
feedback: {}
goal: {}
goal_default:
address: null
data_len: null
id: null
handles: []
result: {}
schema:
description: read_address的参数schema
properties:
feedback: {}
goal:
properties:
address:
type: string
data_len:
type: string
id:
type: string
required:
- id
- address
- data_len
type: object
result: {}
required:
- goal
title: read_address参数
type: object
type: UniLabJsonCommand
auto-rotate_move_abs:
feedback: {}
goal: {}
goal_default:
force: null
pos: null
speed: null
handles: []
result: {}
schema:
description: rotate_move_abs的参数schema
properties:
feedback: {}
goal:
properties:
force:
type: string
pos:
type: string
speed:
type: string
required:
- pos
- speed
- force
type: object
result: {}
required:
- goal
title: rotate_move_abs参数
type: object
type: UniLabJsonCommand
auto-send_cmd:
feedback: {}
goal: {}
goal_default:
address: null
data: null
fun: null
id: null
handles: []
result: {}
schema:
description: send_cmd的参数schema
properties:
feedback: {}
goal:
properties:
address:
type: string
data:
type: string
fun:
type: string
id:
type: string
required:
- id
- fun
- address
- data
type: object
result: {}
required:
- goal
title: send_cmd参数
type: object
type: UniLabJsonCommand
auto-wait_for_gripper:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: wait_for_gripper的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: wait_for_gripper参数
type: object
type: UniLabJsonCommand
auto-wait_for_gripper_init:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: wait_for_gripper_init的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: wait_for_gripper_init参数
type: object
type: UniLabJsonCommand
auto-wait_for_rotate:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: wait_for_rotate的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: wait_for_rotate参数
type: object
type: UniLabJsonCommand
execute_command_from_outer: execute_command_from_outer:
type: SendCmd feedback: {}
goal: goal:
command: command command: command
feedback: {} goal_default:
command: ''
handles: []
result: result:
success: success success: success
schema:
description: ROS Action SendCmd 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties:
status:
type: string
required:
- status
title: SendCmd_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
command:
type: string
required:
- command
title: SendCmd_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: SendCmd_Result
type: object
required:
- goal
title: SendCmd
type: object
type: SendCmd
module: unilabos.devices.motor.Grasp:EleGripper
status_types:
status: str
type: python
description: Misumi RZ gripper
handles: []
icon: ''
init_param_schema:
config:
properties:
baudrate:
default: 115200
type: string
id:
default: 9
type: string
port:
type: string
pos_error:
default: -11
type: string
required:
- port
type: object
data:
properties:
status:
type: string
required:
- status
type: object
gripper.mock:
class:
action_value_mappings:
auto-edit_id:
feedback: {}
goal: {}
goal_default:
params: '{}'
resource:
Gripper1: {}
wf_name: gripper_run
handles: []
result: {}
schema:
description: edit_id的参数schema
properties:
feedback: {}
goal:
properties:
params:
default: '{}'
type: string
resource:
default:
Gripper1: {}
type: object
wf_name:
default: gripper_run
type: string
required: []
type: object
result: {}
required:
- goal
title: edit_id参数
type: object
type: UniLabJsonCommand
auto-push_to:
feedback: {}
goal: {}
goal_default:
position: null
torque: null
velocity: 0.0
handles: []
result: {}
schema:
description: push_to的参数schema
properties:
feedback: {}
goal:
properties:
position:
type: number
torque:
type: number
velocity:
default: 0.0
type: number
required:
- position
- torque
type: object
result: {}
required:
- goal
title: push_to参数
type: object
type: UniLabJsonCommand
push_to:
feedback:
effort: torque
position: position
goal:
command.max_effort: torque
command.position: position
goal_default:
command:
max_effort: 0.0
position: 0.0
handles: []
result:
effort: torque
position: position
schema:
description: ROS Action GripperCommand 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties:
effort:
type: number
position:
type: number
reached_goal:
type: boolean
stalled:
type: boolean
required:
- position
- effort
- stalled
- reached_goal
title: GripperCommand_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
command:
properties:
max_effort:
type: number
position:
type: number
required:
- position
- max_effort
title: GripperCommand
type: object
required:
- command
title: GripperCommand_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
effort:
type: number
position:
type: number
reached_goal:
type: boolean
stalled:
type: boolean
required:
- position
- effort
- stalled
- reached_goal
title: GripperCommand_Result
type: object
required:
- goal
title: GripperCommand
type: object
type: GripperCommand
module: unilabos.devices.gripper.mock:MockGripper
status_types:
position: float
status: str
torque: float
velocity: float
type: python
description: Mock gripper
handles: []
icon: ''
init_param_schema:
config:
properties: {}
required: []
type: object
data:
properties:
position:
type: number
status:
type: string
torque:
type: number
velocity:
type: number
required:
- position
- velocity
- torque
- status
type: object

View File

@@ -1,57 +1,693 @@
linear_motion.grbl: linear_motion.grbl:
description: Grbl CNC
class: class:
module: unilabos.devices.cnc.grbl_sync:GrblCNC
type: python
action_value_mappings: action_value_mappings:
move_through_points: &move_through_points auto-initialize:
type: NavigateThroughPoses feedback: {}
goal: goal: {}
poses[].pose.position: positions[] goal_default: {}
feedback: handles: []
current_pose.pose.position: position
navigation_time.sec: time_spent
estimated_time_remaining.sec: time_remaining
number_of_poses_remaining: pose_number_remaining
result: {}
set_spindle_speed:
type: SingleJointPosition
goal:
position: spindle_speed
feedback:
position: spindle_speed
result: {} result: {}
schema: schema:
description: initialize的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object type: object
result: {}
required:
- goal
title: initialize参数
type: object
type: UniLabJsonCommand
auto-move_through_points:
feedback: {}
goal: {}
goal_default:
positions: null
handles: []
result: {}
schema:
description: move_through_points的参数schema
properties:
feedback: {}
goal:
properties:
positions:
type: array
required:
- positions
type: object
result: {}
required:
- goal
title: move_through_points参数
type: object
type: UniLabJsonCommand
auto-set_position:
feedback: {}
goal: {}
goal_default:
position: null
handles: []
result: {}
schema:
description: set_position的参数schema
properties:
feedback: {}
goal:
properties: properties:
position: position:
type: array type: string
items:
type: number
description: The position of the device
spindle_speed:
type: number
description: The spindle speed of the device
required: required:
- position - position
type: object
result: {}
required:
- goal
title: set_position参数
type: object
type: UniLabJsonCommand
auto-set_spindle_speed:
feedback: {}
goal: {}
goal_default:
max_velocity: 500
spindle_speed: null
handles: []
result: {}
schema:
description: set_spindle_speed的参数schema
properties:
feedback: {}
goal:
properties:
max_velocity:
default: 500
type: number
spindle_speed:
type: number
required:
- spindle_speed - spindle_speed
additionalProperties: false type: object
result: {}
required:
motor.iCL42: - goal
description: iCL42 motor title: set_spindle_speed参数
class: type: object
module: unilabos.devices.motor.iCL42:iCL42Driver type: UniLabJsonCommand
type: python auto-stop_operation:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: stop_operation的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: stop_operation参数
type: object
type: UniLabJsonCommand
auto-wait_error:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: wait_error的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: wait_error参数
type: object
type: UniLabJsonCommandAsync
move_through_points:
feedback:
current_pose.pose.position: position
estimated_time_remaining.sec: time_remaining
navigation_time.sec: time_spent
number_of_poses_remaining: pose_number_remaining
goal:
poses[].pose.position: positions[]
goal_default:
behavior_tree: ''
poses:
- header:
frame_id: ''
stamp:
nanosec: 0
sec: 0
pose:
orientation:
w: 1.0
x: 0.0
y: 0.0
z: 0.0
position:
x: 0.0
y: 0.0
z: 0.0
handles: []
result: {}
schema:
description: ROS Action NavigateThroughPoses 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties:
current_pose:
properties:
header:
properties:
frame_id:
type: string
stamp:
properties:
nanosec:
maximum: 4294967295
minimum: 0
type: integer
sec:
maximum: 2147483647
minimum: -2147483648
type: integer
required:
- sec
- nanosec
title: Time
type: object
required:
- stamp
- frame_id
title: Header
type: object
pose:
properties:
orientation:
properties:
w:
type: number
x:
type: number
y:
type: number
z:
type: number
required:
- x
- y
- z
- w
title: Quaternion
type: object
position:
properties:
x:
type: number
y:
type: number
z:
type: number
required:
- x
- y
- z
title: Point
type: object
required:
- position
- orientation
title: Pose
type: object
required:
- header
- pose
title: PoseStamped
type: object
distance_remaining:
type: number
estimated_time_remaining:
properties:
nanosec:
maximum: 4294967295
minimum: 0
type: integer
sec:
maximum: 2147483647
minimum: -2147483648
type: integer
required:
- sec
- nanosec
title: Duration
type: object
navigation_time:
properties:
nanosec:
maximum: 4294967295
minimum: 0
type: integer
sec:
maximum: 2147483647
minimum: -2147483648
type: integer
required:
- sec
- nanosec
title: Duration
type: object
number_of_poses_remaining:
maximum: 32767
minimum: -32768
type: integer
number_of_recoveries:
maximum: 32767
minimum: -32768
type: integer
required:
- current_pose
- navigation_time
- estimated_time_remaining
- number_of_recoveries
- distance_remaining
- number_of_poses_remaining
title: NavigateThroughPoses_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
behavior_tree:
type: string
poses:
items:
properties:
header:
properties:
frame_id:
type: string
stamp:
properties:
nanosec:
maximum: 4294967295
minimum: 0
type: integer
sec:
maximum: 2147483647
minimum: -2147483648
type: integer
required:
- sec
- nanosec
title: Time
type: object
required:
- stamp
- frame_id
title: Header
type: object
pose:
properties:
orientation:
properties:
w:
type: number
x:
type: number
y:
type: number
z:
type: number
required:
- x
- y
- z
- w
title: Quaternion
type: object
position:
properties:
x:
type: number
y:
type: number
z:
type: number
required:
- x
- y
- z
title: Point
type: object
required:
- position
- orientation
title: Pose
type: object
required:
- header
- pose
title: PoseStamped
type: object
type: array
required:
- poses
- behavior_tree
title: NavigateThroughPoses_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
result:
properties: {}
required: []
title: Empty
type: object
required:
- result
title: NavigateThroughPoses_Result
type: object
required:
- goal
title: NavigateThroughPoses
type: object
type: NavigateThroughPoses
set_spindle_speed:
feedback:
position: spindle_speed
goal:
position: spindle_speed
goal_default:
max_velocity: 0.0
min_duration:
nanosec: 0
sec: 0
position: 0.0
handles: []
result: {}
schema:
description: ROS Action SingleJointPosition 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties:
error:
type: number
header:
properties:
frame_id:
type: string
stamp:
properties:
nanosec:
maximum: 4294967295
minimum: 0
type: integer
sec:
maximum: 2147483647
minimum: -2147483648
type: integer
required:
- sec
- nanosec
title: Time
type: object
required:
- stamp
- frame_id
title: Header
type: object
position:
type: number
velocity:
type: number
required:
- header
- position
- velocity
- error
title: SingleJointPosition_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
max_velocity:
type: number
min_duration:
properties:
nanosec:
maximum: 4294967295
minimum: 0
type: integer
sec:
maximum: 2147483647
minimum: -2147483648
type: integer
required:
- sec
- nanosec
title: Duration
type: object
position:
type: number
required:
- position
- min_duration
- max_velocity
title: SingleJointPosition_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties: {}
required: []
title: SingleJointPosition_Result
type: object
required:
- goal
title: SingleJointPosition
type: object
type: SingleJointPosition
module: unilabos.devices.cnc.grbl_sync:GrblCNC
status_types: status_types:
motor_position: Int64 position: unilabos.messages:Point3D
is_executing_run: Bool spindle_speed: float
success: Bool status: str
type: python
description: Grbl CNC
handles: []
icon: ''
init_param_schema:
config:
properties:
address:
default: '1'
type: string
limits:
default:
- -150
- 150
- -200
- 0
- -80
- 0
type: array
port:
type: string
required:
- port
type: object
data:
properties:
position:
type: string
spindle_speed:
type: number
status:
type: string
required:
- status
- position
- spindle_speed
type: object
motor.iCL42:
class:
action_value_mappings: action_value_mappings:
auto-execute_run_motor:
feedback: {}
goal: {}
goal_default:
mode: null
position: null
velocity: null
handles: []
result: {}
schema:
description: execute_run_motor的参数schema
properties:
feedback: {}
goal:
properties:
mode:
type: string
position:
type: number
velocity:
type: integer
required:
- mode
- position
- velocity
type: object
result: {}
required:
- goal
title: execute_run_motor参数
type: object
type: UniLabJsonCommand
auto-init_device:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: init_device的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: init_device参数
type: object
type: UniLabJsonCommand
auto-run_motor:
feedback: {}
goal: {}
goal_default:
mode: null
position: null
velocity: null
handles: []
result: {}
schema:
description: run_motor的参数schema
properties:
feedback: {}
goal:
properties:
mode:
type: string
position:
type: number
velocity:
type: integer
required:
- mode
- position
- velocity
type: object
result: {}
required:
- goal
title: run_motor参数
type: object
type: UniLabJsonCommand
execute_command_from_outer: execute_command_from_outer:
type: SendCmd feedback: {}
goal: goal:
command: command command: command
feedback: {} goal_default:
command: ''
handles: []
result: result:
success: success success: success
schema:
description: ROS Action SendCmd 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties:
status:
type: string
required:
- status
title: SendCmd_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
command:
type: string
required:
- command
title: SendCmd_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: SendCmd_Result
type: object
required:
- goal
title: SendCmd
type: object
type: SendCmd
module: unilabos.devices.motor.iCL42:iCL42Driver
status_types:
is_executing_run: bool
motor_position: int
success: bool
type: python
description: iCL42 motor
handles: []
icon: ''
init_param_schema:
config:
properties:
device_address:
default: 1
type: integer
device_com:
default: COM9
type: string
required: []
type: object
data:
properties:
is_executing_run:
type: boolean
motor_position:
type: integer
success:
type: boolean
required:
- motor_position
- is_executing_run
- success
type: object

View File

@@ -1,5 +1,311 @@
lh_joint_publisher: lh_joint_publisher:
class: class:
action_value_mappings:
auto-check_tf_update_actions:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: check_tf_update_actions的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: check_tf_update_actions参数
type: object
type: UniLabJsonCommand
auto-find_resource_parent:
feedback: {}
goal: {}
goal_default:
resource_id: null
handles: []
result: {}
schema:
description: find_resource_parent的参数schema
properties:
feedback: {}
goal:
properties:
resource_id:
type: string
required:
- resource_id
type: object
result: {}
required:
- goal
title: find_resource_parent参数
type: object
type: UniLabJsonCommand
auto-inverse_kinematics:
feedback: {}
goal: {}
goal_default:
parent_id: null
x: null
x_joint: null
y: null
y_joint: null
z: null
z_joint: null
handles: []
result: {}
schema:
description: inverse_kinematics的参数schema
properties:
feedback: {}
goal:
properties:
parent_id:
type: string
x:
type: string
x_joint:
type: object
y:
type: string
y_joint:
type: object
z:
type: string
z_joint:
type: object
required:
- x
- y
- z
- parent_id
- x_joint
- y_joint
- z_joint
type: object
result: {}
required:
- goal
title: inverse_kinematics参数
type: object
type: UniLabJsonCommand
auto-lh_joint_action_callback:
feedback: {}
goal: {}
goal_default:
goal_handle: null
handles: []
result: {}
schema:
description: lh_joint_action_callback的参数schema
properties:
feedback: {}
goal:
properties:
goal_handle:
type: string
required:
- goal_handle
type: object
result: {}
required:
- goal
title: lh_joint_action_callback参数
type: object
type: UniLabJsonCommand
auto-lh_joint_pub_callback:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: lh_joint_pub_callback的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: lh_joint_pub_callback参数
type: object
type: UniLabJsonCommand
auto-move_joints:
feedback: {}
goal: {}
goal_default:
option: null
resource_names: null
speed: 0.1
x: null
x_joint: null
y: null
y_joint: null
z: null
z_joint: null
handles: []
result: {}
schema:
description: move_joints的参数schema
properties:
feedback: {}
goal:
properties:
option:
type: string
resource_names:
type: string
speed:
default: 0.1
type: string
x:
type: string
x_joint:
type: string
y:
type: string
y_joint:
type: string
z:
type: string
z_joint:
type: string
required:
- resource_names
- x
- y
- z
- option
type: object
result: {}
required:
- goal
title: move_joints参数
type: object
type: UniLabJsonCommand
auto-move_to:
feedback: {}
goal: {}
goal_default:
joint_positions: null
parent_id: null
speed: null
handles: []
result: {}
schema:
description: move_to的参数schema
properties:
feedback: {}
goal:
properties:
joint_positions:
type: string
parent_id:
type: string
speed:
type: string
required:
- joint_positions
- speed
- parent_id
type: object
result: {}
required:
- goal
title: move_to参数
type: object
type: UniLabJsonCommand
auto-resource_move:
feedback: {}
goal: {}
goal_default:
channels: null
link_name: null
resource_id: null
handles: []
result: {}
schema:
description: resource_move的参数schema
properties:
feedback: {}
goal:
properties:
channels:
type: array
link_name:
type: string
resource_id:
type: string
required:
- resource_id
- link_name
- channels
type: object
result: {}
required:
- goal
title: resource_move参数
type: object
type: UniLabJsonCommand
auto-send_resource_action:
feedback: {}
goal: {}
goal_default:
link_name: null
resource_id_list: null
handles: []
result: {}
schema:
description: send_resource_action的参数schema
properties:
feedback: {}
goal:
properties:
link_name:
type: string
resource_id_list:
type: array
required:
- resource_id_list
- link_name
type: object
result: {}
required:
- goal
title: send_resource_action参数
type: object
type: UniLabJsonCommand
module: unilabos.devices.ros_dev.liquid_handler_joint_publisher:LiquidHandlerJointPublisher module: unilabos.devices.ros_dev.liquid_handler_joint_publisher:LiquidHandlerJointPublisher
status_types: {}
type: ros2 type: ros2
description: ''
handles: []
icon: ''
init_param_schema:
config:
properties:
device_id:
default: lh_joint_publisher
type: string
rate:
default: 50
type: string
resource_tracker:
type: string
resources_config:
type: array
required:
- resources_config
- resource_tracker
type: object
data:
properties: {}
required: []
type: object

View File

@@ -1,65 +1,792 @@
heaterstirrer.dalong: chiller:
description: DaLong heater stirrer
class: class:
module: unilabos.devices.heaterstirrer.dalong:HeaterStirrer_DaLong
type: python
status_types:
temp: Float64
temp_warning: Float64
stir_speed: Float64
action_value_mappings: action_value_mappings:
set_temp_warning: auto-build_modbus_frame:
type: SendCmd
goal:
command: temp
feedback: {} feedback: {}
goal: {}
goal_default:
device_address: null
function_code: null
register_address: null
value: null
handles: []
result: {}
schema:
description: build_modbus_frame的参数schema
properties:
feedback: {}
goal:
properties:
device_address:
type: integer
function_code:
type: integer
register_address:
type: integer
value:
type: integer
required:
- device_address
- function_code
- register_address
- value
type: object
result: {}
required:
- goal
title: build_modbus_frame参数
type: object
type: UniLabJsonCommand
auto-convert_temperature_to_modbus_value:
feedback: {}
goal: {}
goal_default:
decimal_points: 1
temperature: null
handles: []
result: {}
schema:
description: convert_temperature_to_modbus_value的参数schema
properties:
feedback: {}
goal:
properties:
decimal_points:
default: 1
type: integer
temperature:
type: number
required:
- temperature
type: object
result: {}
required:
- goal
title: convert_temperature_to_modbus_value参数
type: object
type: UniLabJsonCommand
auto-modbus_crc:
feedback: {}
goal: {}
goal_default:
data: null
handles: []
result: {}
schema:
description: modbus_crc的参数schema
properties:
feedback: {}
goal:
properties:
data:
type: string
required:
- data
type: object
result: {}
required:
- goal
title: modbus_crc参数
type: object
type: UniLabJsonCommand
auto-set_temperature:
feedback: {}
goal: {}
goal_default:
command: null
handles: []
result: {}
schema:
description: set_temperature的参数schema
properties:
feedback: {}
goal:
properties:
command:
type: string
required:
- command
type: object
result: {}
required:
- goal
title: set_temperature参数
type: object
type: UniLabJsonCommand
auto-stop:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: stop的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: stop参数
type: object
type: UniLabJsonCommand
set_temperature:
feedback: {}
goal:
command: command
goal_default:
command: ''
handles: []
result: result:
success: success success: success
set_temp_target: schema:
type: SendCmd description: ROS Action SendCmd 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties:
status:
type: string
required:
- status
title: SendCmd_Feedback
type: object
goal: goal:
command: temp description: Action 目标 - 从客户端发送到服务器
feedback: {} properties:
command:
type: string
required:
- command
title: SendCmd_Goal
type: object
result: result:
success: success description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: SendCmd_Result
type: object
required:
- goal
title: SendCmd
type: object
type: SendCmd
module: unilabos.devices.temperature.chiller:Chiller
status_types: {}
type: python
description: Chiller
handles: []
icon: ''
init_param_schema:
config:
properties:
port:
type: string
rate:
default: 9600
type: string
required:
- port
type: object
data:
properties: {}
required: []
type: object
heaterstirrer.dalong:
class:
action_value_mappings:
auto-close:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: close的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: close参数
type: object
type: UniLabJsonCommand
auto-heatchill:
feedback: {}
goal: {}
goal_default:
purpose: reaction
stir: true
stir_speed: 300
temp: null
time: 3600
vessel: null
handles: []
result: {}
schema:
description: heatchill的参数schema
properties:
feedback: {}
goal:
properties:
purpose:
default: reaction
type: string
stir:
default: true
type: boolean
stir_speed:
default: 300
type: number
temp:
type: number
time:
default: 3600
type: number
vessel:
type: string
required:
- vessel
- temp
type: object
result: {}
required:
- goal
title: heatchill参数
type: object
type: UniLabJsonCommand
auto-set_stir_speed:
feedback: {}
goal: {}
goal_default:
speed: null
handles: []
result: {}
schema:
description: set_stir_speed的参数schema
properties:
feedback: {}
goal:
properties:
speed:
type: number
required:
- speed
type: object
result: {}
required:
- goal
title: set_stir_speed参数
type: object
type: UniLabJsonCommand
auto-set_temp_inner:
feedback: {}
goal: {}
goal_default:
temp: null
type: warning
handles: []
result: {}
schema:
description: set_temp_inner的参数schema
properties:
feedback: {}
goal:
properties:
temp:
type: number
type:
default: warning
type: string
required:
- temp
type: object
result: {}
required:
- goal
title: set_temp_inner参数
type: object
type: UniLabJsonCommand
auto-set_temp_target:
feedback: {}
goal: {}
goal_default:
temp: null
handles: []
result: {}
schema:
description: set_temp_target的参数schema
properties:
feedback: {}
goal:
properties:
temp:
type: string
required:
- temp
type: object
result: {}
required:
- goal
title: set_temp_target参数
type: object
type: UniLabJsonCommand
auto-set_temp_warning:
feedback: {}
goal: {}
goal_default:
temp: null
handles: []
result: {}
schema:
description: set_temp_warning的参数schema
properties:
feedback: {}
goal:
properties:
temp:
type: string
required:
- temp
type: object
result: {}
required:
- goal
title: set_temp_warning参数
type: object
type: UniLabJsonCommand
heatchill: heatchill:
type: HeatChill
goal:
vessel: vessel
temp: temp
time: time
purpose: purpose
feedback: feedback:
status: status status: status
result:
success: success
chiller:
description: Chiller
class:
module: unilabos.devices.temperature.chiller:Chiller
type: python
action_value_mappings:
set_temperature:
type: SendCmd
goal: goal:
command: command purpose: purpose
feedback: {} temp: temp
time: time
vessel: vessel
goal_default:
purpose: ''
stir: false
stir_speed: 0.0
temp: 0.0
time: 0.0
vessel: ''
handles: []
result: result:
success: success success: success
tempsensor: schema:
description: Temperature sensor description: ROS Action HeatChill 的 JSON Schema
class: properties:
module: unilabos.devices.temperature.sensor_node:TempSensorNode feedback:
type: python description: Action 反馈 - 执行过程中从服务器发送到客户端
properties:
status:
type: string
required:
- status
title: HeatChill_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
purpose:
type: string
stir:
type: boolean
stir_speed:
type: number
temp:
type: number
time:
type: number
vessel:
type: string
required:
- vessel
- temp
- time
- stir
- stir_speed
- purpose
title: HeatChill_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: HeatChill_Result
type: object
required:
- goal
title: HeatChill
type: object
type: HeatChill
set_temp_target:
feedback: {}
goal:
command: temp
goal_default:
command: ''
handles: []
result:
success: success
schema:
description: ROS Action SendCmd 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties:
status:
type: string
required:
- status
title: SendCmd_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
command:
type: string
required:
- command
title: SendCmd_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: SendCmd_Result
type: object
required:
- goal
title: SendCmd
type: object
type: SendCmd
set_temp_warning:
feedback: {}
goal:
command: temp
goal_default:
command: ''
handles: []
result:
success: success
schema:
description: ROS Action SendCmd 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties:
status:
type: string
required:
- status
title: SendCmd_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
command:
type: string
required:
- command
title: SendCmd_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: SendCmd_Result
type: object
required:
- goal
title: SendCmd
type: object
type: SendCmd
module: unilabos.devices.heaterstirrer.dalong:HeaterStirrer_DaLong
status_types: status_types:
value: Float64 status: str
warning: Float64 stir_speed: float
temp: float
temp_target: float
temp_warning: float
type: python
description: DaLong heater stirrer
handles: []
icon: ''
init_param_schema:
config:
properties:
baudrate:
default: 9600
type: integer
port:
default: COM6
type: string
temp_warning:
default: 50.0
type: string
required: []
type: object
data:
properties:
status:
type: string
stir_speed:
type: number
temp:
type: number
temp_target:
type: number
temp_warning:
type: number
required:
- status
- stir_speed
- temp
- temp_warning
- temp_target
type: object
tempsensor:
class:
action_value_mappings: action_value_mappings:
auto-build_modbus_request:
feedback: {}
goal: {}
goal_default:
device_id: null
function_code: null
register_address: null
register_count: null
handles: []
result: {}
schema:
description: build_modbus_request的参数schema
properties:
feedback: {}
goal:
properties:
device_id:
type: string
function_code:
type: string
register_address:
type: string
register_count:
type: string
required:
- device_id
- function_code
- register_address
- register_count
type: object
result: {}
required:
- goal
title: build_modbus_request参数
type: object
type: UniLabJsonCommand
auto-calculate_crc:
feedback: {}
goal: {}
goal_default:
data: null
handles: []
result: {}
schema:
description: calculate_crc的参数schema
properties:
feedback: {}
goal:
properties:
data:
type: string
required:
- data
type: object
result: {}
required:
- goal
title: calculate_crc参数
type: object
type: UniLabJsonCommand
auto-read_modbus_response:
feedback: {}
goal: {}
goal_default:
response: null
handles: []
result: {}
schema:
description: read_modbus_response的参数schema
properties:
feedback: {}
goal:
properties:
response:
type: string
required:
- response
type: object
result: {}
required:
- goal
title: read_modbus_response参数
type: object
type: UniLabJsonCommand
auto-send_prototype_command:
feedback: {}
goal: {}
goal_default:
command: null
handles: []
result: {}
schema:
description: send_prototype_command的参数schema
properties:
feedback: {}
goal:
properties:
command:
type: string
required:
- command
type: object
result: {}
required:
- goal
title: send_prototype_command参数
type: object
type: UniLabJsonCommand
auto-set_warning:
feedback: {}
goal: {}
goal_default:
command: null
handles: []
result: {}
schema:
description: set_warning的参数schema
properties:
feedback: {}
goal:
properties:
command:
type: string
required:
- command
type: object
result: {}
required:
- goal
title: set_warning参数
type: object
type: UniLabJsonCommand
set_warning: set_warning:
type: SendCmd feedback: {}
goal: goal:
command: command command: command
feedback: {} goal_default:
command: ''
handles: []
result: result:
success: success success: success
schema:
description: ROS Action SendCmd 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties:
status:
type: string
required:
- status
title: SendCmd_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
command:
type: string
required:
- command
title: SendCmd_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: SendCmd_Result
type: object
required:
- goal
title: SendCmd
type: object
type: SendCmd
module: unilabos.devices.temperature.sensor_node:TempSensorNode
status_types:
value: float
type: python
description: Temperature sensor
handles: []
icon: ''
init_param_schema:
config:
properties:
address:
type: string
baudrate:
default: 9600
type: string
port:
type: string
warning:
type: string
required:
- port
- warning
- address
type: object
data:
properties:
value:
type: number
required:
- value
type: object

View File

@@ -1,81 +1,496 @@
vacuum_pump.mock:
description: Mock vacuum pump
class:
module: unilabos.devices.pump_and_valve.vacuum_pump_mock:VacuumPumpMock
type: python
status_types:
status: String
action_value_mappings:
open:
type: EmptyIn
goal: {}
feedback: {}
result: {}
close:
type: EmptyIn
goal: {}
feedback: {}
result: {}
set_status:
type: StrSingleInput
goal:
string: string
feedback: {}
result: {}
handles:
- handler_key: out
label: out
data_type: fluid
io_type: source
data_source: handle
data_key: fluid_in
init_param_schema:
type: object
properties:
port:
type: string
description: "通信端口"
default: "COM6"
required:
- port
gas_source.mock: gas_source.mock:
description: Mock gas source
class: class:
module: unilabos.devices.pump_and_valve.vacuum_pump_mock:VacuumPumpMock
type: python
status_types:
status: String
action_value_mappings: action_value_mappings:
open: auto-close:
type: EmptyIn
goal: {}
feedback: {} feedback: {}
goal: {}
goal_default: {}
handles: []
result: {} result: {}
schema:
description: close的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: close参数
type: object
type: UniLabJsonCommand
auto-is_closed:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: is_closed的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: is_closed参数
type: object
type: UniLabJsonCommand
auto-is_open:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: is_open的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: is_open参数
type: object
type: UniLabJsonCommand
auto-open:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: open的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: open参数
type: object
type: UniLabJsonCommand
auto-set_status:
feedback: {}
goal: {}
goal_default:
string: null
handles: []
result: {}
schema:
description: set_status的参数schema
properties:
feedback: {}
goal:
properties:
string:
type: string
required:
- string
type: object
result: {}
required:
- goal
title: set_status参数
type: object
type: UniLabJsonCommand
close: close:
type: EmptyIn
goal: {}
feedback: {} feedback: {}
goal: {}
goal_default: {}
handles: []
result: {} result: {}
schema:
description: ROS Action EmptyIn 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties: {}
required: []
title: EmptyIn_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties: {}
required: []
title: EmptyIn_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
required:
- return_info
title: EmptyIn_Result
type: object
required:
- goal
title: EmptyIn
type: object
type: EmptyIn
open:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: ROS Action EmptyIn 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties: {}
required: []
title: EmptyIn_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties: {}
required: []
title: EmptyIn_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
required:
- return_info
title: EmptyIn_Result
type: object
required:
- goal
title: EmptyIn
type: object
type: EmptyIn
set_status: set_status:
type: StrSingleInput feedback: {}
goal: goal:
string: string string: string
feedback: {} goal_default:
string: ''
handles: []
result: {} result: {}
handles: schema:
- handler_key: out description: ROS Action StrSingleInput 的 JSON Schema
label: out properties:
data_type: fluid feedback:
io_type: source description: Action 反馈 - 执行过程中从服务器发送到客户端
data_source: executor properties: {}
data_key: fluid_out required: []
init_param_schema: title: StrSingleInput_Feedback
type: object type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
string:
type: string
required:
- string
title: StrSingleInput_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: StrSingleInput_Result
type: object
required:
- goal
title: StrSingleInput
type: object
type: StrSingleInput
module: unilabos.devices.pump_and_valve.vacuum_pump_mock:VacuumPumpMock
status_types:
status: str
type: python
description: Mock gas source
handles:
- data_key: fluid_out
data_source: executor
data_type: fluid
handler_key: out
io_type: source
label: out
icon: ''
init_param_schema:
config:
properties: properties:
port: port:
default: COM6
type: string
required: []
type: object
data:
properties:
status:
type: string type: string
description: "通信端口"
default: "COM6"
required: required:
- port - status
type: object
vacuum_pump.mock:
class:
action_value_mappings:
auto-close:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: close的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: close参数
type: object
type: UniLabJsonCommand
auto-is_closed:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: is_closed的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: is_closed参数
type: object
type: UniLabJsonCommand
auto-is_open:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: is_open的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: is_open参数
type: object
type: UniLabJsonCommand
auto-open:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: open的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: open参数
type: object
type: UniLabJsonCommand
auto-set_status:
feedback: {}
goal: {}
goal_default:
string: null
handles: []
result: {}
schema:
description: set_status的参数schema
properties:
feedback: {}
goal:
properties:
string:
type: string
required:
- string
type: object
result: {}
required:
- goal
title: set_status参数
type: object
type: UniLabJsonCommand
close:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: ROS Action EmptyIn 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties: {}
required: []
title: EmptyIn_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties: {}
required: []
title: EmptyIn_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
required:
- return_info
title: EmptyIn_Result
type: object
required:
- goal
title: EmptyIn
type: object
type: EmptyIn
open:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: ROS Action EmptyIn 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties: {}
required: []
title: EmptyIn_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties: {}
required: []
title: EmptyIn_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
required:
- return_info
title: EmptyIn_Result
type: object
required:
- goal
title: EmptyIn
type: object
type: EmptyIn
set_status:
feedback: {}
goal:
string: string
goal_default:
string: ''
handles: []
result: {}
schema:
description: ROS Action StrSingleInput 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties: {}
required: []
title: StrSingleInput_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
string:
type: string
required:
- string
title: StrSingleInput_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: StrSingleInput_Result
type: object
required:
- goal
title: StrSingleInput
type: object
type: StrSingleInput
module: unilabos.devices.pump_and_valve.vacuum_pump_mock:VacuumPumpMock
status_types:
status: str
type: python
description: Mock vacuum pump
handles:
- data_key: fluid_in
data_source: handle
data_type: fluid
handler_key: out
io_type: source
label: out
icon: ''
init_param_schema:
config:
properties:
port:
default: COM6
type: string
required: []
type: object
data:
properties:
status:
type: string
required:
- status
type: object

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,27 +1,232 @@
zhida_hplc: zhida_hplc:
description: Zhida HPLC
class: class:
module: unilabos.devices.zhida_hplc.zhida:ZhidaClient
type: python
status_types:
status: String
action_value_mappings: action_value_mappings:
abort:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: ROS Action EmptyIn 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties: {}
required: []
title: EmptyIn_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties: {}
required: []
title: EmptyIn_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
required:
- return_info
title: EmptyIn_Result
type: object
required:
- goal
title: EmptyIn
type: object
type: EmptyIn
auto-abort:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: abort的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: abort参数
type: object
type: UniLabJsonCommand
auto-close:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: close的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: close参数
type: object
type: UniLabJsonCommand
auto-connect:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: connect的参数schema
properties:
feedback: {}
goal:
properties: {}
required: []
type: object
result: {}
required:
- goal
title: connect参数
type: object
type: UniLabJsonCommand
auto-start:
feedback: {}
goal: {}
goal_default:
text: null
handles: []
result: {}
schema:
description: start的参数schema
properties:
feedback: {}
goal:
properties:
text:
type: string
required:
- text
type: object
result: {}
required:
- goal
title: start参数
type: object
type: UniLabJsonCommand
get_methods:
feedback: {}
goal: {}
goal_default: {}
handles: []
result: {}
schema:
description: ROS Action EmptyIn 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties: {}
required: []
title: EmptyIn_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties: {}
required: []
title: EmptyIn_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
required:
- return_info
title: EmptyIn_Result
type: object
required:
- goal
title: EmptyIn
type: object
type: EmptyIn
start: start:
type: StrSingleInput feedback: {}
goal: goal:
string: string string: string
feedback: {} goal_default:
string: ''
handles: []
result: {} result: {}
abort:
type: EmptyIn
goal: {}
feedback: {}
result: {}
get_methods:
type: EmptyIn
goal: {}
feedback: {}
result: {}
schema: schema:
description: ROS Action StrSingleInput 的 JSON Schema
properties:
feedback:
description: Action 反馈 - 执行过程中从服务器发送到客户端
properties: {} properties: {}
required: []
title: StrSingleInput_Feedback
type: object
goal:
description: Action 目标 - 从客户端发送到服务器
properties:
string:
type: string
required:
- string
title: StrSingleInput_Goal
type: object
result:
description: Action 结果 - 完成后从服务器发送到客户端
properties:
return_info:
type: string
success:
type: boolean
required:
- return_info
- success
title: StrSingleInput_Result
type: object
required:
- goal
title: StrSingleInput
type: object
type: StrSingleInput
module: unilabos.devices.zhida_hplc.zhida:ZhidaClient
status_types:
methods: dict
status: dict
type: python
description: Zhida HPLC
handles: []
icon: ''
init_param_schema:
config:
properties:
host:
default: 192.168.1.47
type: string
port:
default: 5792
type: string
timeout:
default: 10.0
type: string
required: []
type: object
data:
properties:
methods:
type: object
status:
type: object
required:
- status
- methods
type: object

View File

@@ -1,14 +1,17 @@
import copy
import io import io
import os import os
import sys import sys
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any, Dict, List
import yaml import yaml
from unilabos.ros.msgs.message_converter import msg_converter_manager, ros_action_to_json_schema from unilabos.ros.msgs.message_converter import msg_converter_manager, ros_action_to_json_schema, String
from unilabos.utils import logger from unilabos.utils import logger
from unilabos.utils.decorator import singleton from unilabos.utils.decorator import singleton
from unilabos.utils.import_manager import get_enhanced_class_info
from unilabos.utils.type_check import NoAliasDumper
DEFAULT_PATHS = [Path(__file__).absolute().parent] DEFAULT_PATHS = [Path(__file__).absolute().parent]
@@ -32,7 +35,7 @@ class Registry:
# 其他状态变量 # 其他状态变量
# self.is_host_mode = False # 移至BasicConfig中 # self.is_host_mode = False # 移至BasicConfig中
def setup(self): def setup(self, complete_registry=False):
# 检查是否已调用过setup # 检查是否已调用过setup
if self._setup_called: if self._setup_called:
logger.critical("[UniLab Registry] setup方法已被调用过不允许多次调用") logger.critical("[UniLab Registry] setup方法已被调用过不允许多次调用")
@@ -86,13 +89,15 @@ class Registry:
io.StringIO(get_yaml_from_goal_type(self.ResourceCreateFromOuterEasy.Goal)) io.StringIO(get_yaml_from_goal_type(self.ResourceCreateFromOuterEasy.Goal))
), ),
"handles": { "handles": {
"output": [{ "output": [
{
"handler_key": "labware", "handler_key": "labware",
"label": "Labware", "label": "Labware",
"data_type": "resource", "data_type": "resource",
"data_source": "handle", "data_source": "handle",
"data_key": "liquid" "data_key": "liquid",
}] }
]
}, },
}, },
"test_latency": { "test_latency": {
@@ -110,7 +115,6 @@ class Registry:
"registry_type": "device", "registry_type": "device",
"handles": [], "handles": [],
"init_param_schema": {}, "init_param_schema": {},
"schema": {"properties": {}, "additionalProperties": False, "type": "object"},
"file_path": "/", "file_path": "/",
} }
} }
@@ -121,13 +125,13 @@ class Registry:
sys_path = path.parent sys_path = path.parent
logger.debug(f"[UniLab Registry] Path {i+1}/{len(self.registry_paths)}: {sys_path}") logger.debug(f"[UniLab Registry] Path {i+1}/{len(self.registry_paths)}: {sys_path}")
sys.path.append(str(sys_path)) sys.path.append(str(sys_path))
self.load_device_types(path) self.load_device_types(path, complete_registry)
self.load_resource_types(path) self.load_resource_types(path, complete_registry)
logger.info("[UniLab Registry] 注册表设置完成") logger.info("[UniLab Registry] 注册表设置完成")
# 标记setup已被调用 # 标记setup已被调用
self._setup_called = True self._setup_called = True
def load_resource_types(self, path: os.PathLike): def load_resource_types(self, path: os.PathLike, complete_registry: bool):
abs_path = Path(path).absolute() abs_path = Path(path).absolute()
resource_path = abs_path / "resources" resource_path = abs_path / "resources"
files = list(resource_path.glob("*/*.yaml")) files = list(resource_path.glob("*/*.yaml"))
@@ -176,7 +180,14 @@ class Registry:
if not type_name or type_name == "": if not type_name or type_name == "":
logger.warning(f"[UniLab Registry] 设备 {device_id}{field_name} 类型为空,跳过替换") logger.warning(f"[UniLab Registry] 设备 {device_id}{field_name} 类型为空,跳过替换")
return type_name return type_name
if "." in type_name: convert_manager = { # 将python基本对象转为ros2基本对象
"str": "String",
"bool": "Bool",
"int": "Int64",
"float": "Float64",
}
type_name = convert_manager.get(type_name, type_name) # 替换为ROS2类型
if ":" in type_name:
type_class = msg_converter_manager.get_class(type_name) type_class = msg_converter_manager.get_class(type_name)
else: else:
type_class = msg_converter_manager.search_class(type_name) type_class = msg_converter_manager.search_class(type_name)
@@ -186,7 +197,98 @@ class Registry:
logger.error(f"[UniLab Registry] 无法找到类型 '{type_name}' 用于设备 {device_id}{field_name}") logger.error(f"[UniLab Registry] 无法找到类型 '{type_name}' 用于设备 {device_id}{field_name}")
sys.exit(1) sys.exit(1)
def load_device_types(self, path: os.PathLike): def _generate_schema_from_info(
self,
param_name: str,
param_type: str,
param_default: Any,
) -> Dict[str, Any]:
"""
根据参数信息生成JSON Schema
"""
prop_schema = {}
# 根据类型设置schema FIXME 不完整
if param_type:
param_type_lower = param_type.lower()
if param_type_lower in ["str", "string"]:
prop_schema["type"] = "string"
elif param_type_lower in ["int", "integer"]:
prop_schema["type"] = "integer"
elif param_type_lower in ["float", "number"]:
prop_schema["type"] = "number"
elif param_type_lower in ["bool", "boolean"]:
prop_schema["type"] = "boolean"
elif param_type_lower in ["list", "array"]:
prop_schema["type"] = "array"
elif param_type_lower in ["dict", "object"]:
prop_schema["type"] = "object"
else:
# 默认为字符串类型
prop_schema["type"] = "string"
else:
# 如果没有类型信息,默认为字符串
prop_schema["type"] = "string"
# 设置默认值
if param_default is not None:
prop_schema["default"] = param_default
return prop_schema
def _generate_status_types_schema(self, status_types: Dict[str, Any]) -> Dict[str, Any]:
"""
根据状态类型生成JSON Schema
"""
status_schema = {
"type": "object",
"properties": {},
"required": [],
}
for status_name, status_type in status_types.items():
status_schema["properties"][status_name] = self._generate_schema_from_info(
status_name, status_type["return_type"], None
)
status_schema["required"].append(status_name)
return status_schema
def _generate_unilab_json_command_schema(
self, method_args: List[Dict[str, Any]], method_name: str
) -> Dict[str, Any]:
"""
根据UniLabJsonCommand方法信息生成JSON Schema暂不支持嵌套类型
Args:
method_args: 方法信息字典包含args等
method_name: 方法名称
Returns:
JSON Schema格式的参数schema
"""
schema = {
"type": "object",
"properties": {},
"required": [],
}
for arg_info in method_args:
param_name = arg_info.get("name", "")
param_type = arg_info.get("type", "")
param_default = arg_info.get("default")
param_required = arg_info.get("required", True)
schema["properties"][param_name] = self._generate_schema_from_info(
param_name, param_type, param_default
)
if param_required:
schema["required"].append(param_name)
return {
"title": f"{method_name}参数",
"description": f"{method_name}的参数schema",
"type": "object",
"properties": {"goal": schema, "feedback": {}, "result": {}},
"required": ["goal"],
}
def load_device_types(self, path: os.PathLike, complete_registry: bool):
abs_path = Path(path).absolute() abs_path = Path(path).absolute()
devices_path = abs_path / "devices" devices_path = abs_path / "devices"
device_comms_path = abs_path / "device_comms" device_comms_path = abs_path / "device_comms"
@@ -199,12 +301,18 @@ class Registry:
from unilabos.app.web.utils.action_utils import get_yaml_from_goal_type from unilabos.app.web.utils.action_utils import get_yaml_from_goal_type
for i, file in enumerate(files): for i, file in enumerate(files):
data = yaml.safe_load(open(file, encoding="utf-8")) with open(file, encoding="utf-8", mode="r") as f:
data = yaml.safe_load(io.StringIO(f.read()))
complete_data = {}
action_str_type_mapping = {
"UniLabJsonCommand": "UniLabJsonCommand",
"UniLabJsonCommandAsync": "UniLabJsonCommandAsync",
}
status_str_type_mapping = {}
if data: if data:
# 在添加到注册表前处理类型替换 # 在添加到注册表前处理类型替换
for device_id, device_config in data.items(): for device_id, device_config in data.items():
# 添加文件路径信息 - 使用规范化的完整文件路径 # 添加文件路径信息 - 使用规范化的完整文件路径
device_config["file_path"] = str(file.absolute()).replace("\\", "/")
if "description" not in device_config: if "description" not in device_config:
device_config["description"] = "" device_config["description"] = ""
if "icon" not in device_config: if "icon" not in device_config:
@@ -213,42 +321,128 @@ class Registry:
device_config["handles"] = [] device_config["handles"] = []
if "init_param_schema" not in device_config: if "init_param_schema" not in device_config:
device_config["init_param_schema"] = {} device_config["init_param_schema"] = {}
device_config["registry_type"] = "device"
if "class" in device_config: if "class" in device_config:
# 处理状态类型 if "status_types" not in device_config["class"]:
if "status_types" in device_config["class"]: device_config["class"]["status_types"] = {}
if "action_value_mappings" not in device_config["class"]:
device_config["class"]["action_value_mappings"] = {}
enhanced_info = {}
if complete_registry:
device_config["class"]["status_types"].clear()
enhanced_info = get_enhanced_class_info(device_config["class"]["module"], use_dynamic=True)
device_config["class"]["status_types"].update(
{k: v["return_type"] for k, v in enhanced_info["status_methods"].items()}
)
for status_name, status_type in device_config["class"]["status_types"].items(): for status_name, status_type in device_config["class"]["status_types"].items():
device_config["class"]["status_types"][status_name] = self._replace_type_with_class( if status_type in ["Any", "None", "Unknown"]:
status_type, device_id, f"状态 {status_name}" status_type = "String" # 替换成ROS的String便于显示
device_config["class"]["status_types"][status_name] = status_type
target_type = self._replace_type_with_class(status_type, device_id, f"状态 {status_name}")
if target_type in [
dict,
list,
]: # 对于嵌套类型返回的对象,暂时处理成字符串,无法直接进行转换
target_type = String
status_str_type_mapping[status_type] = target_type
device_config["class"]["status_types"] = dict(
sorted(device_config["class"]["status_types"].items())
)
if complete_registry:
device_config["class"]["action_value_mappings"] = {
k: v
for k, v in device_config["class"]["action_value_mappings"].items()
if not k.startswith("auto-")
}
# 处理动作值映射
device_config["class"]["action_value_mappings"].update(
{
f"auto-{k}": {
"type": "UniLabJsonCommandAsync" if v["is_async"] else "UniLabJsonCommand",
"goal": {},
"feedback": {},
"result": {},
"schema": self._generate_unilab_json_command_schema(v["args"], k),
"goal_default": {i["name"]: i["default"] for i in v["args"]},
"handles": [],
}
for k, v in enhanced_info["action_methods"].items()
}
)
device_config["init_param_schema"] = {}
device_config["init_param_schema"]["config"] = self._generate_unilab_json_command_schema(
enhanced_info["init_params"], "__init__"
)["properties"]["goal"]
device_config["init_param_schema"]["data"] = self._generate_status_types_schema(
enhanced_info["status_methods"]
) )
# 处理动作值映射 device_config.pop("schema", None)
if "action_value_mappings" in device_config["class"]: device_config["class"]["action_value_mappings"] = dict(
sorted(device_config["class"]["action_value_mappings"].items())
)
for action_name, action_config in device_config["class"]["action_value_mappings"].items(): for action_name, action_config in device_config["class"]["action_value_mappings"].items():
if "handles" not in action_config: if "handles" not in action_config:
action_config["handles"] = [] action_config["handles"] = []
if "type" in action_config: if "type" in action_config:
action_config["type"] = self._replace_type_with_class( action_type_str: str = action_config["type"]
action_config["type"], device_id, f"动作 {action_name}" # 通过Json发放指令而不是通过特殊的ros action进行处理
if not action_type_str.startswith("UniLabJsonCommand"):
target_type = self._replace_type_with_class(
action_type_str, device_id, f"动作 {action_name}"
) )
if action_config["type"] is not None: action_str_type_mapping[action_type_str] = target_type
if target_type is not None:
action_config["goal_default"] = yaml.safe_load( action_config["goal_default"] = yaml.safe_load(
io.StringIO(get_yaml_from_goal_type(action_config["type"].Goal)) io.StringIO(get_yaml_from_goal_type(target_type.Goal))
) )
action_config["schema"] = ros_action_to_json_schema(action_config["type"]) action_config["schema"] = ros_action_to_json_schema(target_type)
else: else:
logger.warning( logger.warning(
f"[UniLab Registry] 设备 {device_id} 的动作 {action_name} 类型为空,跳过替换" f"[UniLab Registry] 设备 {device_id} 的动作 {action_name} 类型为空,跳过替换"
) )
complete_data[device_id] = copy.deepcopy(dict(sorted(device_config.items()))) # 稍后dump到文件
self.device_type_registry.update(data) for status_name, status_type in device_config["class"]["status_types"].items():
device_config["class"]["status_types"][status_name] = status_str_type_mapping[status_type]
for device_id in data.keys(): for action_name, action_config in device_config["class"]["action_value_mappings"].items():
action_config["type"] = action_str_type_mapping[action_config["type"]]
for additional_action in ["_execute_driver_command", "_execute_driver_command_async"]:
device_config["class"]["action_value_mappings"][additional_action] = {
"type": self._replace_type_with_class(
"StrSingleInput", device_id, f"动作 {additional_action}"
),
"goal": {"string": "string"},
"feedback": {},
"result": {},
"schema": ros_action_to_json_schema(
self._replace_type_with_class(
"StrSingleInput", device_id, f"动作 {additional_action}"
)
),
"goal_default": yaml.safe_load(
io.StringIO(
get_yaml_from_goal_type(
self._replace_type_with_class(
"StrSingleInput", device_id, f"动作 {additional_action}"
).Goal
)
)
),
"handles": [],
}
if "registry_type" not in device_config:
device_config["registry_type"] = "device"
device_config["file_path"] = str(file.absolute()).replace("\\", "/")
device_config["registry_type"] = "device"
logger.debug( logger.debug(
f"[UniLab Registry] Device-{current_device_number} File-{i+1}/{len(files)} Add {device_id} " f"[UniLab Registry] Device-{current_device_number} File-{i+1}/{len(files)} Add {device_id} "
+ f"[{data[device_id].get('name', '未命名设备')}]" + f"[{data[device_id].get('name', '未命名设备')}]"
) )
current_device_number += 1 current_device_number += 1
complete_data = dict(sorted(complete_data.items()))
complete_data = copy.deepcopy(complete_data)
with open(file, "w", encoding="utf-8") as f:
yaml.dump(complete_data, f, allow_unicode=True, default_flow_style=False, Dumper=NoAliasDumper)
self.device_type_registry.update(data)
else: else:
logger.debug( logger.debug(
f"[UniLab Registry] Device File-{i+1}/{len(files)} Not Valid YAML File: {file.absolute()}" f"[UniLab Registry] Device File-{i+1}/{len(files)} Not Valid YAML File: {file.absolute()}"
@@ -257,7 +451,28 @@ class Registry:
def obtain_registry_device_info(self): def obtain_registry_device_info(self):
devices = [] devices = []
for device_id, device_info in self.device_type_registry.items(): for device_id, device_info in self.device_type_registry.items():
msg = {"id": device_id, **device_info} device_info_copy = copy.deepcopy(device_info)
if "class" in device_info_copy and "action_value_mappings" in device_info_copy["class"]:
action_mappings = device_info_copy["class"]["action_value_mappings"]
for action_name, action_config in action_mappings.items():
if "schema" in action_config and action_config["schema"]:
schema = action_config["schema"]
# 确保schema结构存在
if (
"properties" in schema
and "goal" in schema["properties"]
and "properties" in schema["properties"]["goal"]
):
schema["properties"]["goal"]["properties"] = {
"unilabos_device_id": {
"type": "string",
"default": "",
"description": "UniLabOS设备ID用于指定执行动作的具体设备实例",
},
**schema["properties"]["goal"]["properties"],
}
msg = {"id": device_id, **device_info_copy}
devices.append(msg) devices.append(msg)
return devices return devices
@@ -273,7 +488,7 @@ class Registry:
lab_registry = Registry() lab_registry = Registry()
def build_registry(registry_paths=None): def build_registry(registry_paths=None, complete_registry=False):
""" """
构建或获取Registry单例实例 构建或获取Registry单例实例
@@ -297,6 +512,6 @@ def build_registry(registry_paths=None):
lab_registry.registry_paths.append(path) lab_registry.registry_paths.append(path)
# 初始化注册表 # 初始化注册表
lab_registry.setup() lab_registry.setup(complete_registry)
return lab_registry return lab_registry

View File

@@ -55,7 +55,7 @@ def ros2_device_node(
"read": "read_data", "read": "read_data",
"extra_info": [], "extra_info": [],
} }
# FIXME 后面要删除
for k, v in cls.__dict__.items(): for k, v in cls.__dict__.items():
if not k.startswith("_") and isinstance(v, property): if not k.startswith("_") and isinstance(v, property):
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences

View File

@@ -343,7 +343,7 @@ def convert_to_ros_msg(ros_msg_type: Union[Type, Any], obj: Any) -> Any:
if hasattr(ros_msg, key): if hasattr(ros_msg, key):
attr = getattr(ros_msg, key) attr = getattr(ros_msg, key)
if isinstance(attr, (float, int, str, bool)): if isinstance(attr, (float, int, str, bool)):
setattr(ros_msg, key, value) setattr(ros_msg, key, type(attr)(value))
elif isinstance(attr, (list, tuple)) and isinstance(value, Iterable): elif isinstance(attr, (list, tuple)) and isinstance(value, Iterable):
td = ros_msg.SLOT_TYPES[ind].value_type td = ros_msg.SLOT_TYPES[ind].value_type
if isinstance(td, NamespacedType): if isinstance(td, NamespacedType):
@@ -727,7 +727,6 @@ def ros_action_to_json_schema(action_class: Any) -> Dict[str, Any]:
# 创建基础 schema # 创建基础 schema
schema = { schema = {
'$schema': 'http://json-schema.org/draft-07/schema#',
'title': action_class.__name__, 'title': action_class.__name__,
'description': f"ROS Action {action_class.__name__} 的 JSON Schema", 'description': f"ROS Action {action_class.__name__} 的 JSON Schema",
'type': 'object', 'type': 'object',

View File

@@ -1,4 +1,5 @@
import copy import copy
import io
import json import json
import threading import threading
import time import time
@@ -10,6 +11,7 @@ from concurrent.futures import ThreadPoolExecutor
import asyncio import asyncio
import rclpy import rclpy
import yaml
from rclpy.node import Node from rclpy.node import Node
from rclpy.action import ActionServer, ActionClient from rclpy.action import ActionServer, ActionClient
from rclpy.action.server import ServerGoalHandle from rclpy.action.server import ServerGoalHandle
@@ -166,7 +168,10 @@ class PropertyPublisher:
self.print_publish = print_publish self.print_publish = print_publish
self._value = None self._value = None
try:
self.publisher_ = node.create_publisher(msg_type, f"{name}", 10) self.publisher_ = node.create_publisher(msg_type, f"{name}", 10)
except AttributeError as ex:
logger.error(f"创建发布者失败,可能由于注册表有误,类型: {msg_type},错误: {ex}\n{traceback.format_exc()}")
self.timer = node.create_timer(self.timer_period, self.publish_property) self.timer = node.create_timer(self.timer_period, self.publish_property)
self.__loop = get_event_loop() self.__loop = get_event_loop()
str_msg_type = str(msg_type)[8:-2] str_msg_type = str(msg_type)[8:-2]
@@ -302,6 +307,8 @@ class BaseROS2DeviceNode(Node, Generic[T]):
# 创建动作服务 # 创建动作服务
if self.create_action_server: if self.create_action_server:
for action_name, action_value_mapping in self._action_value_mappings.items(): for action_name, action_value_mapping in self._action_value_mappings.items():
if action_name.startswith("auto-"):
continue
self.create_ros_action_server(action_name, action_value_mapping) self.create_ros_action_server(action_name, action_value_mapping)
# 创建线程池执行器 # 创建线程池执行器
@@ -838,6 +845,8 @@ class BaseROS2DeviceNode(Node, Generic[T]):
class DeviceInitError(Exception): class DeviceInitError(Exception):
pass pass
class JsonCommandInitError(Exception):
pass
class ROS2DeviceNode: class ROS2DeviceNode:
""" """
@@ -954,12 +963,51 @@ class ROS2DeviceNode:
self._ros_node: BaseROS2DeviceNode self._ros_node: BaseROS2DeviceNode
self._ros_node.lab_logger().info(f"初始化完成 {self._ros_node.uuid} {self.driver_is_ros}") self._ros_node.lab_logger().info(f"初始化完成 {self._ros_node.uuid} {self.driver_is_ros}")
self.driver_instance._ros_node = self._ros_node # type: ignore self.driver_instance._ros_node = self._ros_node # type: ignore
self.driver_instance._execute_driver_command = self._execute_driver_command # type: ignore
self.driver_instance._execute_driver_command_async = self._execute_driver_command_async # type: ignore
if hasattr(self.driver_instance, "post_init"): if hasattr(self.driver_instance, "post_init"):
try: try:
self.driver_instance.post_init(self._ros_node) # type: ignore self.driver_instance.post_init(self._ros_node) # type: ignore
except Exception as e: except Exception as e:
self._ros_node.lab_logger().error(f"设备后初始化失败: {e}") self._ros_node.lab_logger().error(f"设备后初始化失败: {e}")
def _execute_driver_command(self, string: str):
try:
target = json.loads(string)
except Exception as ex:
try:
target = yaml.safe_load(io.StringIO(string))
except Exception as ex2:
raise JsonCommandInitError(f"执行动作时JSON/YAML解析失败: \n{ex}\n{ex2}\n原内容: {string}\n{traceback.format_exc()}")
try:
function_name = target["function_name"]
function_args = target["function_args"]
assert isinstance(function_args, dict), "执行动作时JSON必须为dict类型\n原JSON: {string}"
function = getattr(self.driver_instance, function_name)
assert callable(function), f"执行动作时JSON中的function_name对应的函数不可调用: {function_name}\n原JSON: {string}"
return function(**function_args)
except KeyError as ex:
raise JsonCommandInitError(f"执行动作时JSON缺少function_name或function_args: {ex}\n原JSON: {string}\n{traceback.format_exc()}")
async def _execute_driver_command_async(self, string: str):
try:
target = json.loads(string)
except Exception as ex:
try:
target = yaml.safe_load(io.StringIO(string))
except Exception as ex2:
raise JsonCommandInitError(f"执行动作时JSON/YAML解析失败: \n{ex}\n{ex2}\n原内容: {string}\n{traceback.format_exc()}")
try:
function_name = target["function_name"]
function_args = target["function_args"]
assert isinstance(function_args, dict), "执行动作时JSON必须为dict类型\n原JSON: {string}"
function = getattr(self.driver_instance, function_name)
assert callable(function), f"执行动作时JSON中的function_name对应的函数不可调用: {function_name}\n原JSON: {string}"
assert asyncio.iscoroutinefunction(function), f"执行动作时JSON中的function并非异步: {function_name}\n原JSON: {string}"
return await function(**function_args)
except KeyError as ex:
raise JsonCommandInitError(f"执行动作时JSON缺少function_name或function_args: {ex}\n原JSON: {string}\n{traceback.format_exc()}")
def _start_loop(self): def _start_loop(self):
def run_event_loop(): def run_event_loop():
loop = asyncio.new_event_loop() loop = asyncio.new_event_loop()

View File

@@ -0,0 +1,61 @@
import rclpy
from rclpy.node import Node
import cv2
from sensor_msgs.msg import Image
from cv_bridge import CvBridge
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode, DeviceNodeResourceTracker
class VideoPublisher(BaseROS2DeviceNode):
def __init__(self, device_id='video_publisher', camera_index=0, period: float = 0.1, resource_tracker: DeviceNodeResourceTracker = None):
# 初始化BaseROS2DeviceNode使用自身作为driver_instance
BaseROS2DeviceNode.__init__(
self,
driver_instance=self,
device_id=device_id,
status_types={},
action_value_mappings={},
hardware_interface="camera",
print_publish=False,
resource_tracker=resource_tracker,
)
# 创建一个发布者,发布到 /video 话题,消息类型为 sensor_msgs/Image队列长度设为 10
self.publisher_ = self.create_publisher(Image, f'/{device_id}/video', 10)
# 初始化摄像头(默认设备索引为 0
self.cap = cv2.VideoCapture(camera_index)
if not self.cap.isOpened():
self.get_logger().error("无法打开摄像头")
# 用于将 OpenCV 的图像转换为 ROS 图像消息
self.bridge = CvBridge()
# 设置定时器10 Hz 发布一次
timer_period = period # 单位:秒
self.timer = self.create_timer(timer_period, self.timer_callback)
def timer_callback(self):
ret, frame = self.cap.read()
if not ret:
self.get_logger().error("读取视频帧失败")
return
# 将 OpenCV 图像转换为 ROS Image 消息,注意图像编码需与摄像头数据匹配,这里使用 bgr8
img_msg = self.bridge.cv2_to_imgmsg(frame, encoding="bgr8")
self.publisher_.publish(img_msg)
# self.get_logger().info("已发布视频帧")
def destroy_node(self):
# 释放摄像头资源
if self.cap.isOpened():
self.cap.release()
super().destroy_node()
def main(args=None):
rclpy.init(args=args)
node = VideoPublisher()
try:
rclpy.spin(node)
except KeyboardInterrupt:
pass
finally:
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()

View File

@@ -459,6 +459,8 @@ class HostNode(BaseROS2DeviceNode):
self.devices_instances[device_id] = d self.devices_instances[device_id] = d
# noinspection PyProtectedMember # noinspection PyProtectedMember
for action_name, action_value_mapping in d._ros_node._action_value_mappings.items(): for action_name, action_value_mapping in d._ros_node._action_value_mappings.items():
if action_name.startswith("auto-"):
continue
action_id = f"/devices/{device_id}/{action_name}" action_id = f"/devices/{device_id}/{action_name}"
if action_id not in self._action_clients: if action_id not in self._action_clients:
action_type = action_value_mapping["type"] action_type = action_value_mapping["type"]
@@ -567,6 +569,7 @@ class HostNode(BaseROS2DeviceNode):
def send_goal( def send_goal(
self, self,
device_id: str, device_id: str,
action_type: str,
action_name: str, action_name: str,
action_kwargs: Dict[str, Any], action_kwargs: Dict[str, Any],
goal_uuid: Optional[str] = None, goal_uuid: Optional[str] = None,
@@ -577,10 +580,25 @@ class HostNode(BaseROS2DeviceNode):
Args: Args:
device_id: 设备ID device_id: 设备ID
action_type: 动作类型
action_name: 动作名称 action_name: 动作名称
action_kwargs: 动作参数 action_kwargs: 动作参数
goal_uuid: 目标UUID如果为None则自动生成 goal_uuid: 目标UUID如果为None则自动生成
server_info: 服务器发送信息,包含发送时间戳等
""" """
if action_type.startswith("UniLabJsonCommand"):
if action_name.startswith("auto-"):
action_name = action_name[5:]
action_id = f"/devices/{device_id}/_execute_driver_command"
action_kwargs = {
"string": json.dumps({
"function_name": action_name,
"function_args": action_kwargs,
})
}
if action_type.startswith("UniLabJsonCommandAsync"):
action_id = f"/devices/{device_id}/_execute_driver_command_async"
else:
action_id = f"/devices/{device_id}/{action_name}" action_id = f"/devices/{device_id}/{action_name}"
if action_name == "test_latency" and server_info is not None: if action_name == "test_latency" and server_info is not None:
self.server_latest_timestamp = server_info.get("send_timestamp", 0.0) self.server_latest_timestamp = server_info.get("send_timestamp", 0.0)

View File

@@ -1,7 +1,5 @@
import time import time
import asyncio
import traceback import traceback
from types import MethodType
from typing import Union from typing import Union
import rclpy import rclpy
@@ -19,9 +17,11 @@ from unilabos.ros.msgs.message_converter import (
get_action_type, get_action_type,
convert_to_ros_msg, convert_to_ros_msg,
convert_from_ros_msg, convert_from_ros_msg,
convert_from_ros_msg_with_mapping, convert_from_ros_msg_with_mapping, String,
) )
from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode, DeviceNodeResourceTracker, ROS2DeviceNode from unilabos.ros.nodes.base_device_node import BaseROS2DeviceNode, DeviceNodeResourceTracker, ROS2DeviceNode
from unilabos.utils.log import error
from unilabos.utils.type_check import serialize_result_info
class ROS2ProtocolNode(BaseROS2DeviceNode): class ROS2ProtocolNode(BaseROS2DeviceNode):
@@ -33,7 +33,15 @@ class ROS2ProtocolNode(BaseROS2DeviceNode):
# create_action_server = False # Action Server要自己创建 # create_action_server = False # Action Server要自己创建
def __init__(self, device_id: str, children: dict, protocol_type: Union[str, list[str]], resource_tracker: DeviceNodeResourceTracker, *args, **kwargs): def __init__(
self,
device_id: str,
children: dict,
protocol_type: Union[str, list[str]],
resource_tracker: DeviceNodeResourceTracker,
*args,
**kwargs,
):
self._setup_protocol_names(protocol_type) self._setup_protocol_names(protocol_type)
# 初始化其它属性 # 初始化其它属性
@@ -60,12 +68,14 @@ class ROS2ProtocolNode(BaseROS2DeviceNode):
for device_id, device_config in self.children.items(): for device_id, device_config in self.children.items():
if device_config.get("type", "device") != "device": if device_config.get("type", "device") != "device":
self.lab_logger().debug(f"[Protocol Node] Skipping type {device_config['type']} {device_id} already existed, skipping.") self.lab_logger().debug(
f"[Protocol Node] Skipping type {device_config['type']} {device_id} already existed, skipping."
)
continue continue
try: try:
d = self.initialize_device(device_id, device_config) d = self.initialize_device(device_id, device_config)
except Exception as ex: except Exception as ex:
self.lab_logger().error(f"[Protocol Node] Failed to initialize device {device_id}: {ex}") self.lab_logger().error(f"[Protocol Node] Failed to initialize device {device_id}: {ex}\n{traceback.format_exc()}")
d = None d = None
if d is None: if d is None:
continue continue
@@ -76,22 +86,27 @@ class ROS2ProtocolNode(BaseROS2DeviceNode):
# 设置硬件接口代理 # 设置硬件接口代理
if d: if d:
hardware_interface = d.ros_node_instance._hardware_interface
if ( if (
hasattr(d.driver_instance, d.ros_node_instance._hardware_interface["name"]) hasattr(d.driver_instance, hardware_interface["name"])
and hasattr(d.driver_instance, d.ros_node_instance._hardware_interface["write"]) and hasattr(d.driver_instance, hardware_interface["write"])
and (d.ros_node_instance._hardware_interface["read"] is None or hasattr(d.driver_instance, d.ros_node_instance._hardware_interface["read"])) and (hardware_interface["read"] is None or hasattr(d.driver_instance, hardware_interface["read"]))
): ):
name = getattr(d.driver_instance, d.ros_node_instance._hardware_interface["name"]) name = getattr(d.driver_instance, hardware_interface["name"])
read = d.ros_node_instance._hardware_interface.get("read", None) read = hardware_interface.get("read", None)
write = d.ros_node_instance._hardware_interface.get("write", None) write = hardware_interface.get("write", None)
# 如果硬件接口是字符串,通过通信设备提供 # 如果硬件接口是字符串,通过通信设备提供
if isinstance(name, str) and name in self.sub_devices: if isinstance(name, str) and name in self.sub_devices:
communicate_device = self.sub_devices[name] communicate_device = self.sub_devices[name]
communicate_hardware_info = communicate_device.ros_node_instance._hardware_interface communicate_hardware_info = communicate_device.ros_node_instance._hardware_interface
self._setup_hardware_proxy(d, self.sub_devices[name], read, write) self._setup_hardware_proxy(d, self.sub_devices[name], read, write)
self.lab_logger().info(f"\n通信代理:为子设备{device_id}\n 添加了{read}方法(来源:{name} {communicate_hardware_info['write']}) \n 添加了{write}方法(来源:{name} {communicate_hardware_info['read']})") self.lab_logger().info(
f"\n通信代理:为子设备{device_id}\n "
f"添加了{read}方法(来源:{name} {communicate_hardware_info['write']}) \n "
f"添加了{write}方法(来源:{name} {communicate_hardware_info['read']})"
)
def _setup_protocol_names(self, protocol_type): def _setup_protocol_names(self, protocol_type):
# 处理协议类型 # 处理协议类型
@@ -119,11 +134,17 @@ class ROS2ProtocolNode(BaseROS2DeviceNode):
if d is not None and hasattr(d, "ros_node_instance"): if d is not None and hasattr(d, "ros_node_instance"):
node = d.ros_node_instance node = d.ros_node_instance
for action_name, action_mapping in node._action_value_mappings.items(): for action_name, action_mapping in node._action_value_mappings.items():
if action_name.startswith("auto-"):
continue
action_id = f"/devices/{device_id_abs}/{action_name}" action_id = f"/devices/{device_id_abs}/{action_name}"
if action_id not in self._action_clients: if action_id not in self._action_clients:
try:
self._action_clients[action_id] = ActionClient( self._action_clients[action_id] = ActionClient(
self, action_mapping["type"], action_id, callback_group=self.callback_group self, action_mapping["type"], action_id, callback_group=self.callback_group
) )
except Exception as ex:
self.lab_logger().error(f"创建动作客户端失败: {action_id}, 错误: {ex}")
continue
self.lab_logger().debug(f"为子设备 {device_id} 创建动作客户端: {action_name}") self.lab_logger().debug(f"为子设备 {device_id} 创建动作客户端: {action_name}")
return d return d
@@ -149,9 +170,14 @@ class ROS2ProtocolNode(BaseROS2DeviceNode):
def _create_protocol_execute_callback(self, protocol_name, protocol_steps_generator): def _create_protocol_execute_callback(self, protocol_name, protocol_steps_generator):
async def execute_protocol(goal_handle: ServerGoalHandle): async def execute_protocol(goal_handle: ServerGoalHandle):
"""执行完整的工作流""" """执行完整的工作流"""
self.get_logger().info(f'Executing {protocol_name} action...') # 初始化结果信息变量
execution_error = ""
execution_success = False
protocol_return_value = None
self.get_logger().info(f"Executing {protocol_name} action...")
action_value_mapping = self._action_value_mappings[protocol_name] action_value_mapping = self._action_value_mappings[protocol_name]
print('+'*30) try:
print("+" * 30)
print(protocol_steps_generator) print(protocol_steps_generator)
# 从目标消息中提取参数, 并调用Protocol生成器(根据设备连接图)生成action步骤 # 从目标消息中提取参数, 并调用Protocol生成器(根据设备连接图)生成action步骤
goal = goal_handle.request goal = goal_handle.request
@@ -161,35 +187,53 @@ class ROS2ProtocolNode(BaseROS2DeviceNode):
for k, v in goal.get_fields_and_field_types().items(): for k, v in goal.get_fields_and_field_types().items():
if v in ["unilabos_msgs/Resource", "sequence<unilabos_msgs/Resource>"]: if v in ["unilabos_msgs/Resource", "sequence<unilabos_msgs/Resource>"]:
r = ResourceGet.Request() r = ResourceGet.Request()
r.id = protocol_kwargs[k]["id"] if v == "unilabos_msgs/Resource" else protocol_kwargs[k][0]["id"] resource_id = (
protocol_kwargs[k]["id"] if v == "unilabos_msgs/Resource" else protocol_kwargs[k][0]["id"]
)
r.id = resource_id
r.with_children = True r.with_children = True
response = await self._resource_clients["resource_get"].call_async(r) response = await self._resource_clients["resource_get"].call_async(r)
protocol_kwargs[k] = list_to_nested_dict([convert_from_ros_msg(rs) for rs in response.resources]) protocol_kwargs[k] = list_to_nested_dict(
[convert_from_ros_msg(rs) for rs in response.resources]
)
from unilabos.resources.graphio import physical_setup_graph from unilabos.resources.graphio import physical_setup_graph
self.get_logger().info(f'Working on physical setup: {physical_setup_graph}')
self.lab_logger().info(f"Working on physical setup: {physical_setup_graph}")
protocol_steps = protocol_steps_generator(G=physical_setup_graph, **protocol_kwargs) protocol_steps = protocol_steps_generator(G=physical_setup_graph, **protocol_kwargs)
self.get_logger().info(f'Goal received: {protocol_kwargs}, running steps: \n{protocol_steps}') self.lab_logger().info(f"Goal received: {protocol_kwargs}, running steps: \n{protocol_steps}")
time_start = time.time() time_start = time.time()
time_overall = 100 time_overall = 100
self._busy = True self._busy = True
# 逐步执行工作流 # 逐步执行工作流
step_results = []
for i, action in enumerate(protocol_steps): for i, action in enumerate(protocol_steps):
self.get_logger().info(f'Running step {i+1}: {action}') self.get_logger().info(f"Running step {i + 1}: {action}")
if type(action) == dict: if isinstance(action, dict):
# 如果是单个动作,直接执行 # 如果是单个动作,直接执行
if action["action_name"] == "wait": if action["action_name"] == "wait":
time.sleep(action["action_kwargs"]["time"]) time.sleep(action["action_kwargs"]["time"])
step_results.append({"step": i + 1, "action": "wait", "result": "completed"})
else: else:
result = await self.execute_single_action(**action) result = await self.execute_single_action(**action)
elif type(action) == list: step_results.append({"step": i + 1, "action": action["action_name"], "result": result})
elif isinstance(action, list):
# 如果是并行动作,同时执行 # 如果是并行动作,同时执行
actions = action actions = action
futures = [rclpy.get_global_executor().create_task(self.execute_single_action(**a)) for a in actions] futures = [
rclpy.get_global_executor().create_task(self.execute_single_action(**a)) for a in actions
]
results = [await f for f in futures] results = [await f for f in futures]
step_results.append(
{
"step": i + 1,
"parallel_actions": [a["action_name"] for a in actions],
"results": results,
}
)
# 向Host更新物料当前状态 # 向Host更新物料当前状态
for k, v in goal.get_fields_and_field_types().items(): for k, v in goal.get_fields_and_field_types().items():
@@ -200,12 +244,52 @@ class ROS2ProtocolNode(BaseROS2DeviceNode):
] ]
response = await self._resource_clients["resource_update"].call_async(r) response = await self._resource_clients["resource_update"].call_async(r)
goal_handle.succeed() # 设置成功状态和返回值
result = action_value_mapping["type"].Result() execution_success = True
result.success = True protocol_return_value = {
"protocol_name": protocol_name,
"steps_executed": len(protocol_steps),
"step_results": step_results,
"total_time": time.time() - time_start,
}
goal_handle.succeed()
except Exception as e:
# 捕获并记录错误信息
execution_error = traceback.format_exc()
execution_success = False
error(f"协议 {protocol_name} 执行失败")
error(traceback.format_exc())
self.lab_logger().error(f"协议执行出错: {str(e)}")
# 设置动作失败
goal_handle.abort()
finally:
self._busy = False self._busy = False
# 创建结果消息
result = action_value_mapping["type"].Result()
result.success = execution_success
# 获取结果消息类型信息检查是否有return_info字段
result_msg_types = action_value_mapping["type"].Result.get_fields_and_field_types()
# 设置return_info字段如果存在
for attr_name in result_msg_types.keys():
if attr_name in ["success", "reached_goal"]:
setattr(result, attr_name, execution_success)
elif attr_name == "return_info":
setattr(
result,
attr_name,
serialize_result_info(execution_error, execution_success, protocol_return_value),
)
self.lab_logger().info(f"协议 {protocol_name} 完成并返回结果")
return result return result
return execute_protocol return execute_protocol
async def execute_single_action(self, device_id, action_name, action_kwargs): async def execute_single_action(self, device_id, action_name, action_kwargs):
@@ -241,14 +325,19 @@ class ROS2ProtocolNode(BaseROS2DeviceNode):
return result_future.result return result_future.result
"""还没有改过的部分""" """还没有改过的部分"""
def _setup_hardware_proxy(self, device: ROS2DeviceNode, communication_device: ROS2DeviceNode, read_method, write_method): def _setup_hardware_proxy(
self, device: ROS2DeviceNode, communication_device: ROS2DeviceNode, read_method, write_method
):
"""为设备设置硬件接口代理""" """为设备设置硬件接口代理"""
# extra_info = [getattr(device.driver_instance, info) for info in communication_device.ros_node_instance._hardware_interface.get("extra_info", [])] # extra_info = [getattr(device.driver_instance, info) for info in communication_device.ros_node_instance._hardware_interface.get("extra_info", [])]
write_func = getattr(communication_device.driver_instance, communication_device.ros_node_instance._hardware_interface["write"]) write_func = getattr(
read_func = getattr(communication_device.driver_instance, communication_device.ros_node_instance._hardware_interface["read"]) communication_device.driver_instance, communication_device.ros_node_instance._hardware_interface["write"]
)
read_func = getattr(
communication_device.driver_instance, communication_device.ros_node_instance._hardware_interface["read"]
)
def _read(*args, **kwargs): def _read(*args, **kwargs):
return read_func(*args, **kwargs) return read_func(*args, **kwargs)
@@ -264,7 +353,6 @@ class ROS2ProtocolNode(BaseROS2DeviceNode):
# bound_write = MethodType(_write, device.driver_instance) # bound_write = MethodType(_write, device.driver_instance)
setattr(device.driver_instance, write_method, _write) setattr(device.driver_instance, write_method, _write)
async def _update_resources(self, goal, protocol_kwargs): async def _update_resources(self, goal, protocol_kwargs):
"""更新资源状态""" """更新资源状态"""
for k, v in goal.get_fields_and_field_types().items(): for k, v in goal.get_fields_and_field_types().items():

View File

@@ -7,7 +7,11 @@
import builtins import builtins
import importlib import importlib
import inspect import inspect
import sys
import traceback import traceback
import ast
import os
from pathlib import Path
from typing import Dict, List, Any, Optional, Callable, Type from typing import Dict, List, Any, Optional, Callable, Type
@@ -18,8 +22,12 @@ __all__ = [
"get_class", "get_class",
"get_module", "get_module",
"init_from_list", "init_from_list",
"get_class_info_static",
"get_registry_class_info",
] ]
from ast import Constant
from unilabos.utils import logger from unilabos.utils import logger
@@ -114,15 +122,16 @@ class ImportManager:
# 尝试动态导入 # 尝试动态导入
if ":" in class_name: if ":" in class_name:
module_path, cls_name = class_name.rsplit(":", 1) module_path, cls_name = class_name.rsplit(":", 1)
# 如果cls_name是builtins中的关键字则返回对应类
if cls_name in builtins.__dict__:
return builtins.__dict__[cls_name]
module = self.load_module(module_path) module = self.load_module(module_path)
if hasattr(module, cls_name): if hasattr(module, cls_name):
cls = getattr(module, cls_name) cls = getattr(module, cls_name)
self._classes[class_name] = cls self._classes[class_name] = cls
self._classes[cls_name] = cls self._classes[cls_name] = cls
return cls return cls
else:
# 如果cls_name是builtins中的关键字则返回对应类
if class_name in builtins.__dict__:
return builtins.__dict__[class_name]
raise KeyError(f"找不到类: {class_name}") raise KeyError(f"找不到类: {class_name}")
@@ -149,6 +158,9 @@ class ImportManager:
Returns: Returns:
找到的类对象如果未找到则返回None 找到的类对象如果未找到则返回None
""" """
# 如果cls_name是builtins中的关键字则返回对应类
if class_name in builtins.__dict__:
return builtins.__dict__[class_name]
# 首先在已索引的类中查找 # 首先在已索引的类中查找
if class_name in self._classes: if class_name in self._classes:
return self._classes[class_name] return self._classes[class_name]
@@ -161,7 +173,9 @@ class ImportManager:
# 遍历所有已加载的模块进行搜索 # 遍历所有已加载的模块进行搜索
for module_path, module in self._modules.items(): for module_path, module in self._modules.items():
for name, obj in inspect.getmembers(module): for name, obj in inspect.getmembers(module):
if inspect.isclass(obj) and ((name.lower() == class_name.lower()) if search_lower else (name == class_name)): if inspect.isclass(obj) and (
(name.lower() == class_name.lower()) if search_lower else (name == class_name)
):
# 将找到的类添加到索引中 # 将找到的类添加到索引中
self._classes[name] = obj self._classes[name] = obj
self._classes[f"{module_path}:{name}"] = obj self._classes[f"{module_path}:{name}"] = obj
@@ -169,6 +183,562 @@ class ImportManager:
return None return None
def get_enhanced_class_info(self, module_path: str, use_dynamic: bool = True) -> Dict[str, Any]:
"""
获取增强的类信息,支持动态导入和静态分析
Args:
module_path: 模块路径,格式为 "module.path""module.path:ClassName"
use_dynamic: 是否优先使用动态导入
Returns:
包含详细类信息的字典
"""
result = {
"module_path": module_path,
"dynamic_import_success": False,
"static_analysis_success": False,
"init_params": {},
"status_methods": {}, # get_ 开头和 @property 方法
"action_methods": {}, # set_ 开头和其他非_开头方法
}
# 尝试动态导入
dynamic_info = None
static_info = None
if use_dynamic:
try:
dynamic_info = self._get_dynamic_class_info(module_path)
result["dynamic_import_success"] = True
logger.debug(f"[ImportManager] 动态导入类 {module_path} 成功")
except Exception as e:
logger.warning(
f"[UniLab Registry] 在补充注册表时,动态导入类 "
f"{module_path} 失败(将使用静态分析,"
f"建议修复导入错误,以实现更好的注册表识别效果!): {e}"
)
use_dynamic = False
if not use_dynamic:
# 尝试静态分析
try:
static_info = self._get_static_class_info(module_path)
result["static_analysis_success"] = True
logger.debug(f"[ImportManager] 静态分析类 {module_path} 成功")
except Exception as e:
logger.warning(f"[ImportManager] 静态分析类 {module_path} 失败: {e}")
# 合并信息(优先使用动态导入的信息)
if dynamic_info:
result.update(dynamic_info)
elif static_info:
result.update(static_info)
return result
def _get_dynamic_class_info(self, class_path: str) -> Dict[str, Any]:
"""使用inspect模块动态获取类信息"""
cls = get_class(class_path)
class_name = cls.__name__
result = {"class_name": class_name, "init_params": self._analyze_method_signature(cls.__init__)["args"],
"status_methods": {}, "action_methods": {}}
# 分析类的所有成员
for name, method in cls.__dict__.items():
if name.startswith("_"):
continue
# 检查是否是property
if isinstance(method, property):
# @property 装饰的方法
# noinspection PyTypeChecker
return_type = self._get_return_type_from_method(method.fget) if method.fget else "Any"
prop_info = {
"name": name,
"return_type": return_type,
}
result["status_methods"][name] = prop_info
# 检查是否有对应的setter
if method.fset:
setter_info = self._analyze_method_signature(method.fset)
result["action_methods"][name] = setter_info
elif inspect.ismethod(method) or inspect.isfunction(method):
if name.startswith("get_"):
actual_name = name[4:] # 去掉get_前缀
if actual_name in result["status_methods"]:
continue
# get_ 开头的方法归类为status
method_info = self._analyze_method_signature(method)
result["status_methods"][actual_name] = method_info
elif not name.startswith("_"):
# 其他非_开头的方法归类为action
method_info = self._analyze_method_signature(method)
result["action_methods"][name] = method_info
return result
def _get_static_class_info(self, module_path: str) -> Dict[str, Any]:
"""使用AST静态分析获取类信息"""
module_name, class_name = module_path.rsplit(":", 1)
# 将模块路径转换为文件路径
file_path = self._module_path_to_file_path(module_name)
if not file_path or not os.path.exists(file_path):
raise FileNotFoundError(f"找不到模块文件: {module_name} -> {file_path}")
with open(file_path, "r", encoding="utf-8") as f:
source_code = f.read()
tree = ast.parse(source_code)
# 查找目标类
target_class = None
for node in ast.walk(tree):
if isinstance(node, ast.ClassDef):
if node.name == class_name:
target_class = node
break
if target_class is None:
raise AttributeError(f"在文件 {file_path} 中找不到类 {class_name}")
result = {
"class_name": class_name,
"init_params": {},
"status_methods": {},
"action_methods": {},
}
# 分析类的方法
for node in target_class.body:
if isinstance(node, ast.FunctionDef):
method_info = self._analyze_method_node(node)
method_name = node.name
if method_name == "__init__":
result["init_params"] = method_info["args"]
elif method_name.startswith("_"):
continue
elif self._is_property_method(node):
# @property 装饰的方法
result["status_methods"][method_name] = method_info
elif method_name.startswith("get_"):
# get_ 开头的方法归类为status
actual_name = method_name[4:] # 去掉get_前缀
if actual_name not in result["status_methods"]:
result["status_methods"][actual_name] = method_info
else:
# 其他非_开头的方法归类为action
result["action_methods"][method_name] = method_info
return result
def _analyze_method_signature(self, method) -> Dict[str, Any]:
"""
分析方法签名,提取具体的命名参数信息
注意:此方法会跳过*args和**kwargs只提取具体的命名参数
这样可以确保通过**dict方式传参时的准确性
示例用法:
method_info = self._analyze_method_signature(some_method)
params = {"param1": "value1", "param2": "value2"}
result = some_method(**params) # 安全的参数传递
"""
signature = inspect.signature(method)
args = []
num_required = 0
for param_name, param in signature.parameters.items():
# 跳过self参数
if param_name == "self":
continue
# 跳过*args和**kwargs参数
if param.kind == param.VAR_POSITIONAL: # *args
continue
if param.kind == param.VAR_KEYWORD: # **kwargs
continue
is_required = param.default == inspect.Parameter.empty
if is_required:
num_required += 1
args.append(
{
"name": param_name,
"type": self._get_type_string(param.annotation),
"required": is_required,
"default": None if param.default == inspect.Parameter.empty else param.default,
}
)
return {
"name": method.__name__,
"args": args,
"return_type": self._get_type_string(signature.return_annotation),
"is_async": inspect.iscoroutinefunction(method),
}
def _get_return_type_from_method(self, method) -> str:
"""从方法中获取返回类型"""
signature = inspect.signature(method)
return self._get_type_string(signature.return_annotation)
def _get_type_string(self, annotation) -> str:
"""将类型注解转换为Class Library中可搜索的类名"""
if annotation == inspect.Parameter.empty:
return "Any" # 如果没有注解返回Any
if annotation is None:
return "None" # 明确的None类型
if hasattr(annotation, "__origin__"):
# 处理typing模块的类型
origin = annotation.__origin__
if origin in (list, set, tuple):
if hasattr(annotation, "__args__") and annotation.__args__:
if len(annotation.__args__):
arg0 = annotation.__args__[0]
if isinstance(arg0, int):
return "Int64MultiArray"
elif isinstance(arg0, float):
return "Float64MultiArray"
return "list"
elif origin is dict:
return "dict"
elif origin is Optional:
return "Unknown"
return f"Unknown"
annotation_str = str(annotation)
# 处理typing模块的复杂类型
if "typing." in annotation_str:
# 简化typing类型显示
return (
annotation_str.replace("typing.", "")
if getattr(annotation, "_name", None) is None
else annotation._name.lower()
)
# 如果是类型对象
if hasattr(annotation, "__name__"):
# 如果是内置类型
if annotation.__module__ == "builtins":
return annotation.__name__
else:
# 如果是自定义类,返回完整路径
return f"{annotation.__module__}:{annotation.__name__}"
# 如果是typing模块的类型
elif hasattr(annotation, "_name"):
return annotation._name
# 如果是字符串形式的类型注解
elif isinstance(annotation, str):
return annotation
else:
return annotation_str
def _is_property_method(self, node: ast.FunctionDef) -> bool:
"""检查是否是@property装饰的方法"""
for decorator in node.decorator_list:
if isinstance(decorator, ast.Name) and decorator.id == "property":
return True
return False
def _is_setter_method(self, node: ast.FunctionDef) -> bool:
"""检查是否是@xxx.setter装饰的方法"""
for decorator in node.decorator_list:
if isinstance(decorator, ast.Attribute) and decorator.attr == "setter":
return True
return False
def _get_property_name_from_setter(self, node: ast.FunctionDef) -> str:
"""从setter装饰器中获取属性名"""
for decorator in node.decorator_list:
if isinstance(decorator, ast.Attribute) and decorator.attr == "setter":
if isinstance(decorator.value, ast.Name):
return decorator.value.id
return node.name
def get_class_info_static(self, module_class_path: str) -> Dict[str, Any]:
"""
静态分析获取类的方法信息,不需要实际导入模块
Args:
module_class_path: 格式为 "module.path:ClassName" 的字符串
Returns:
包含类方法信息的字典
"""
try:
if ":" not in module_class_path:
raise ValueError("module_class_path必须是 'module.path:ClassName' 格式")
module_path, class_name = module_class_path.rsplit(":", 1)
# 将模块路径转换为文件路径
file_path = self._module_path_to_file_path(module_path)
if not file_path or not os.path.exists(file_path):
logger.warning(f"找不到模块文件: {module_path} -> {file_path}")
return {}
# 解析源码
with open(file_path, "r", encoding="utf-8") as f:
source_code = f.read()
tree = ast.parse(source_code)
# 查找目标类
class_node = None
for node in ast.walk(tree):
if isinstance(node, ast.ClassDef) and node.name == class_name:
class_node = node
break
if not class_node:
logger.warning(f"在模块 {module_path} 中找不到类 {class_name}")
return {}
# 分析类的方法
methods_info = {}
for node in class_node.body:
if isinstance(node, ast.FunctionDef):
method_info = self._analyze_method_node(node)
methods_info[node.name] = method_info
return {
"class_name": class_name,
"module_path": module_path,
"file_path": file_path,
"methods": methods_info,
}
except Exception as e:
logger.error(f"静态分析类 {module_class_path} 时出错: {str(e)}")
return {}
def _module_path_to_file_path(self, module_path: str) -> Optional[str]:
for path in sys.path:
potential_path = Path(path) / module_path.replace(".", "/")
# 检查是否为包
if (potential_path / "__init__.py").exists():
return str(potential_path / "__init__.py")
# 检查是否为模块文件
if (potential_path.parent / f"{potential_path.name}.py").exists():
return str(potential_path.parent / f"{potential_path.name}.py")
return None
def _analyze_method_node(self, node: ast.FunctionDef) -> Dict[str, Any]:
"""分析方法节点,提取参数和返回类型信息"""
method_info = {
"name": node.name,
"args": [],
"return_type": None,
"is_async": isinstance(node, ast.AsyncFunctionDef),
}
# 获取默认值列表
defaults = node.args.defaults
num_defaults = len(defaults)
# 计算必需参数数量
total_args = len(node.args.args)
num_required = total_args - num_defaults
# 提取参数信息
for i, arg in enumerate(node.args.args):
if arg.arg == "self":
continue
arg_info = {
"name": arg.arg,
"type": None,
"default": None,
"required": i < num_required,
}
# 提取类型注解
if arg.annotation:
arg_info["type"] = ast.unparse(arg.annotation) if hasattr(ast, "unparse") else str(arg.annotation)
# 提取默认值并推断类型
if i >= num_required:
default_index = i - num_required
if default_index < len(defaults):
default_value: Constant = defaults[default_index] # type: ignore
assert isinstance(default_value, Constant), "暂不支持对非常量类型进行推断,可反馈开源仓库"
arg_info["default"] = default_value.value
# 如果没有类型注解,尝试从默认值推断类型
if not arg_info["type"]:
arg_info["type"] = self._get_type_string(type(arg_info["default"]))
method_info["args"].append(arg_info)
# 提取返回类型
if node.returns:
method_info["return_type"] = ast.unparse(node.returns) if hasattr(ast, "unparse") else str(node.returns)
return method_info
def _infer_type_from_default(self, node: ast.AST) -> Optional[str]:
"""从默认值推断参数类型"""
if isinstance(node, ast.Constant):
value = node.value
if isinstance(value, bool):
return "bool"
elif isinstance(value, int):
return "int"
elif isinstance(value, float):
return "float"
elif isinstance(value, str):
return "str"
elif value is None:
return "Optional[Any]"
elif isinstance(node, ast.List):
return "List"
elif isinstance(node, ast.Dict):
return "Dict"
elif isinstance(node, ast.Tuple):
return "Tuple"
elif isinstance(node, ast.Set):
return "Set"
elif isinstance(node, ast.Name):
# 常见的默认值模式
if node.id in ["None"]:
return "Optional[Any]"
elif node.id in ["True", "False"]:
return "bool"
return None
def _infer_types_from_docstring(self, method_info: Dict[str, Any]) -> None:
"""从docstring中推断参数类型"""
docstring = method_info.get("docstring", "")
if not docstring:
return
lines = docstring.split("\n")
in_args_section = False
for line in lines:
line = line.strip()
# 检测Args或Arguments段落
if line.lower().startswith(("args:", "arguments:")):
in_args_section = True
continue
elif line.startswith(("returns:", "return:", "yields:", "raises:")):
in_args_section = False
continue
elif not line or not in_args_section:
continue
# 解析参数行,格式通常是: param_name (type): description 或 param_name: description
if ":" in line:
parts = line.split(":", 1)
param_part = parts[0].strip()
# 提取参数名和类型
param_name = None
param_type = None
if "(" in param_part and ")" in param_part:
# 格式: param_name (type)
param_name = param_part.split("(")[0].strip()
type_part = param_part.split("(")[1].split(")")[0].strip()
param_type = type_part
else:
# 格式: param_name
param_name = param_part
# 更新对应参数的类型信息
if param_name:
for arg_info in method_info["args"]:
if arg_info["name"] == param_name and not arg_info["type"]:
if param_type:
arg_info["inferred_type"] = param_type
elif not arg_info["inferred_type"]:
# 从描述中推断类型
description = parts[1].strip().lower()
if any(word in description for word in ["path", "file", "directory", "filename"]):
arg_info["inferred_type"] = "str"
elif any(
word in description for word in ["port", "number", "count", "size", "length"]
):
arg_info["inferred_type"] = "int"
elif any(
word in description for word in ["rate", "ratio", "percentage", "temperature"]
):
arg_info["inferred_type"] = "float"
elif any(word in description for word in ["flag", "enable", "disable", "option"]):
arg_info["inferred_type"] = "bool"
def get_registry_class_info(self, module_class_path: str) -> Dict[str, Any]:
"""
获取适用于注册表的类信息,包含完整的类型推断
Args:
module_class_path: 格式为 "module.path:ClassName" 的字符串
Returns:
适用于注册表的类信息字典
"""
class_info = self.get_class_info_static(module_class_path)
if not class_info:
return {}
registry_info = {
"class_name": class_info["class_name"],
"module_path": class_info["module_path"],
"file_path": class_info["file_path"],
"methods": {},
"properties": [],
"init_params": {},
"action_methods": {},
}
for method_name, method_info in class_info["methods"].items():
# 分类处理不同类型的方法
if method_info["is_property"]:
registry_info["properties"].append(
{
"name": method_name,
"return_type": method_info.get("return_type"),
"docstring": method_info.get("docstring"),
}
)
elif method_name == "__init__":
# 处理初始化参数
init_params = {}
for arg in method_info["args"]:
if arg["name"] != "self":
param_info = {
"name": arg["name"],
"type": arg.get("type") or arg.get("inferred_type"),
"required": arg.get("is_required", True),
"default": arg.get("default"),
}
init_params[arg["name"]] = param_info
registry_info["init_params"] = init_params
elif not method_name.startswith("_"):
# 处理公共方法可能的action方法
action_info = {
"name": method_name,
"params": {},
"return_type": method_info.get("return_type"),
"docstring": method_info.get("docstring"),
"num_required": method_info.get("num_required", 0) - 1, # 减去self
"num_defaults": method_info.get("num_defaults", 0),
}
for arg in method_info["args"]:
if arg["name"] != "self":
param_info = {
"name": arg["name"],
"type": arg.get("type") or arg.get("inferred_type"),
"required": arg.get("is_required", True),
"default": arg.get("default"),
}
action_info["params"][arg["name"]] = param_info
registry_info["action_methods"][method_name] = action_info
return registry_info
# 全局实例,便于直接使用 # 全局实例,便于直接使用
default_manager = ImportManager() default_manager = ImportManager()
@@ -193,3 +763,18 @@ def init_from_list(module_list: List[str]) -> None:
"""从模块列表初始化默认管理器""" """从模块列表初始化默认管理器"""
global default_manager global default_manager
default_manager = ImportManager(module_list) default_manager = ImportManager(module_list)
def get_class_info_static(module_class_path: str) -> Dict[str, Any]:
"""静态分析获取类信息的便捷函数"""
return default_manager.get_class_info_static(module_class_path)
def get_registry_class_info(module_class_path: str) -> Dict[str, Any]:
"""获取适用于注册表的类信息的便捷函数"""
return default_manager.get_registry_class_info(module_class_path)
def get_enhanced_class_info(module_path: str, use_dynamic: bool = True) -> Dict[str, Any]:
"""获取增强的类信息的便捷函数"""
return default_manager.get_enhanced_class_info(module_path, use_dynamic)

View File

@@ -2,6 +2,8 @@ import collections.abc
import json import json
from typing import get_origin, get_args from typing import get_origin, get_args
import yaml
def get_type_class(type_hint): def get_type_class(type_hint):
origin = get_origin(type_hint) origin = get_origin(type_hint)
@@ -22,6 +24,12 @@ class TypeEncoder(json.JSONEncoder):
return super().default(obj) return super().default(obj)
class NoAliasDumper(yaml.SafeDumper):
def ignore_aliases(self, data):
return True
class ResultInfoEncoder(json.JSONEncoder): class ResultInfoEncoder(json.JSONEncoder):
"""专门用于处理任务执行结果信息的JSON编码器""" """专门用于处理任务执行结果信息的JSON编码器"""

View File

@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.5) cmake_minimum_required(VERSION 3.16)
project(unilabos_msgs) project(unilabos_msgs)
# Default to C99 # Default to C99
@@ -28,7 +28,11 @@ set(action_files
"action/HeatChill.action" "action/HeatChill.action"
"action/HeatChillStart.action" "action/HeatChillStart.action"
"action/HeatChillStop.action" "action/HeatChillStop.action"
"action/AdjustPH.action"
"action/ResetHandling.action"
"action/Dry.action"
"action/Hydrogenate.action"
"action/Recrystallize.action"
"action/CleanVessel.action" "action/CleanVessel.action"
"action/Dissolve.action" "action/Dissolve.action"
"action/FilterThrough.action" "action/FilterThrough.action"
@@ -39,7 +43,6 @@ set(action_files
"action/Add.action" "action/Add.action"
"action/Centrifuge.action" "action/Centrifuge.action"
"action/Crystallize.action" "action/Crystallize.action"
"action/Dry.action"
"action/Purge.action" "action/Purge.action"
"action/StartPurge.action" "action/StartPurge.action"
"action/StartStir.action" "action/StartStir.action"

View File

@@ -0,0 +1,13 @@
# Request - 与您的 AdjustPHProtocol 类匹配
string vessel
float64 ph_value
string reagent
---
# Result - 标准结果格式
bool success
string message
string return_info
---
# Feedback - 标准反馈格式
string status
float64 progress

View File

@@ -1,17 +1,12 @@
# Goal - 干燥操作的目标参数 # Request
string compound # 化合物
string vessel # 干燥容器 string vessel # 干燥容器
float64 time # 干燥时间 (可选,秒)
float64 pressure # 压力 (可选Pa)
float64 temp # 温度 (可选,摄氏度)
bool continue_heatchill # 是否继续加热冷却
--- ---
# Result - 操作结果 # Result
bool success # 操作是否成功 bool success # 操作是否成功
string message # 结果消息 string message # 结果消息
string return_info string return_info
--- ---
# Feedback - 实时反馈 # Feedback
string status # 当前状态描述
float64 progress # 进度百分比 (0-100) float64 progress # 进度百分比 (0-100)
float64 current_temp # 当前温度
float64 current_pressure # 当前压力
string current_status # 当前状态描述

View File

@@ -0,0 +1,13 @@
# Request
string temp
string time
string vessel
---
# Result
bool success
string message
string return_info
---
# Feedback
string status
float64 progress

View File

@@ -0,0 +1,15 @@
# Request
string ratio
string solvent1
string solvent2
string vessel
float64 volume
---
# Result
bool success
string message
string return_info
---
# Feedback
string status
float64 progress

View File

@@ -0,0 +1,11 @@
# Request
string solvent
---
# Result
bool success
string message
string return_info
---
# Feedback
string status
float64 progress