mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2025-12-17 21:11:12 +00:00
移除MQTT,更新launch文档,提供注册表示例文件,更新到0.10.5
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
package:
|
package:
|
||||||
name: unilabos
|
name: unilabos
|
||||||
version: 0.10.3
|
version: 0.10.5
|
||||||
|
|
||||||
source:
|
source:
|
||||||
path: ../unilabos
|
path: ../unilabos
|
||||||
@@ -10,7 +10,6 @@ build:
|
|||||||
python:
|
python:
|
||||||
entry_points:
|
entry_points:
|
||||||
- unilab = unilabos.app.main:main
|
- unilab = unilabos.app.main:main
|
||||||
- unilab-register = unilabos.app.register:main
|
|
||||||
script:
|
script:
|
||||||
- set PIP_NO_INDEX=
|
- set PIP_NO_INDEX=
|
||||||
- if: win
|
- if: win
|
||||||
|
|||||||
@@ -1,26 +1,64 @@
|
|||||||
## 简单单变量动作函数
|
## 简单单变量动作函数
|
||||||
|
|
||||||
|
|
||||||
### `SendCmd`
|
### `SendCmd`
|
||||||
|
|
||||||
```{literalinclude} ../../unilabos_msgs/action/SendCmd.action
|
```{literalinclude} ../../unilabos_msgs/action/SendCmd.action
|
||||||
:language: yaml
|
:language: yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
----
|
---
|
||||||
|
|
||||||
|
### `StrSingleInput`
|
||||||
|
|
||||||
|
```{literalinclude} ../../unilabos_msgs/action/StrSingleInput.action
|
||||||
|
:language: yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `IntSingleInput`
|
||||||
|
|
||||||
|
```{literalinclude} ../../unilabos_msgs/action/IntSingleInput.action
|
||||||
|
:language: yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `FloatSingleInput`
|
||||||
|
|
||||||
|
```{literalinclude} ../../unilabos_msgs/action/FloatSingleInput.action
|
||||||
|
:language: yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `Point3DSeparateInput`
|
||||||
|
|
||||||
|
```{literalinclude} ../../unilabos_msgs/action/Point3DSeparateInput.action
|
||||||
|
:language: yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `Wait`
|
||||||
|
|
||||||
|
```{literalinclude} ../../unilabos_msgs/action/Wait.action
|
||||||
|
:language: yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 常量有机化学操作
|
## 常量有机化学操作
|
||||||
|
|
||||||
Uni-Lab 常量有机化学指令集多数来自 [XDL](https://croningroup.gitlab.io/chemputer/xdl/standard/full_steps_specification.html#),包含有机合成实验中常见的操作,如加热、搅拌、冷却等。
|
Uni-Lab 常量有机化学指令集多数来自 [XDL](https://croningroup.gitlab.io/chemputer/xdl/standard/full_steps_specification.html#),包含有机合成实验中常见的操作,如加热、搅拌、冷却等。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### `Clean`
|
### `Clean`
|
||||||
|
|
||||||
```{literalinclude} ../../unilabos_msgs/action/Clean.action
|
```{literalinclude} ../../unilabos_msgs/action/Clean.action
|
||||||
:language: yaml
|
:language: yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
----
|
---
|
||||||
|
|
||||||
### `EvacuateAndRefill`
|
### `EvacuateAndRefill`
|
||||||
|
|
||||||
@@ -28,7 +66,7 @@ Uni-Lab 常量有机化学指令集多数来自 [XDL](https://croningroup.gitlab
|
|||||||
:language: yaml
|
:language: yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
----
|
---
|
||||||
|
|
||||||
### `Evaporate`
|
### `Evaporate`
|
||||||
|
|
||||||
@@ -36,7 +74,7 @@ Uni-Lab 常量有机化学指令集多数来自 [XDL](https://croningroup.gitlab
|
|||||||
:language: yaml
|
:language: yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
----
|
---
|
||||||
|
|
||||||
### `HeatChill`
|
### `HeatChill`
|
||||||
|
|
||||||
@@ -44,7 +82,7 @@ Uni-Lab 常量有机化学指令集多数来自 [XDL](https://croningroup.gitlab
|
|||||||
:language: yaml
|
:language: yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
----
|
---
|
||||||
|
|
||||||
### `HeatChillStart`
|
### `HeatChillStart`
|
||||||
|
|
||||||
@@ -52,7 +90,7 @@ Uni-Lab 常量有机化学指令集多数来自 [XDL](https://croningroup.gitlab
|
|||||||
:language: yaml
|
:language: yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
----
|
---
|
||||||
|
|
||||||
### `HeatChillStop`
|
### `HeatChillStop`
|
||||||
|
|
||||||
@@ -60,7 +98,7 @@ Uni-Lab 常量有机化学指令集多数来自 [XDL](https://croningroup.gitlab
|
|||||||
:language: yaml
|
:language: yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
----
|
---
|
||||||
|
|
||||||
### `PumpTransfer`
|
### `PumpTransfer`
|
||||||
|
|
||||||
@@ -68,7 +106,7 @@ Uni-Lab 常量有机化学指令集多数来自 [XDL](https://croningroup.gitlab
|
|||||||
:language: yaml
|
:language: yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
----
|
---
|
||||||
|
|
||||||
### `Separate`
|
### `Separate`
|
||||||
|
|
||||||
@@ -76,7 +114,7 @@ Uni-Lab 常量有机化学指令集多数来自 [XDL](https://croningroup.gitlab
|
|||||||
:language: yaml
|
:language: yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
----
|
---
|
||||||
|
|
||||||
### `Stir`
|
### `Stir`
|
||||||
|
|
||||||
@@ -84,20 +122,179 @@ Uni-Lab 常量有机化学指令集多数来自 [XDL](https://croningroup.gitlab
|
|||||||
:language: yaml
|
:language: yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
----
|
---
|
||||||
|
|
||||||
|
### `Add`
|
||||||
|
|
||||||
|
```{literalinclude} ../../unilabos_msgs/action/Add.action
|
||||||
|
:language: yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `AddSolid`
|
||||||
|
|
||||||
|
```{literalinclude} ../../unilabos_msgs/action/AddSolid.action
|
||||||
|
:language: yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `AdjustPH`
|
||||||
|
|
||||||
|
```{literalinclude} ../../unilabos_msgs/action/AdjustPH.action
|
||||||
|
:language: yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `Centrifuge`
|
||||||
|
|
||||||
|
```{literalinclude} ../../unilabos_msgs/action/Centrifuge.action
|
||||||
|
:language: yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `CleanVessel`
|
||||||
|
|
||||||
|
```{literalinclude} ../../unilabos_msgs/action/CleanVessel.action
|
||||||
|
:language: yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `Crystallize`
|
||||||
|
|
||||||
|
```{literalinclude} ../../unilabos_msgs/action/Crystallize.action
|
||||||
|
:language: yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `Dissolve`
|
||||||
|
|
||||||
|
```{literalinclude} ../../unilabos_msgs/action/Dissolve.action
|
||||||
|
:language: yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `Dry`
|
||||||
|
|
||||||
|
```{literalinclude} ../../unilabos_msgs/action/Dry.action
|
||||||
|
:language: yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `Filter`
|
||||||
|
|
||||||
|
```{literalinclude} ../../unilabos_msgs/action/Filter.action
|
||||||
|
:language: yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `FilterThrough`
|
||||||
|
|
||||||
|
```{literalinclude} ../../unilabos_msgs/action/FilterThrough.action
|
||||||
|
:language: yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `Hydrogenate`
|
||||||
|
|
||||||
|
```{literalinclude} ../../unilabos_msgs/action/Hydrogenate.action
|
||||||
|
:language: yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `Purge`
|
||||||
|
|
||||||
|
```{literalinclude} ../../unilabos_msgs/action/Purge.action
|
||||||
|
:language: yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `Recrystallize`
|
||||||
|
|
||||||
|
```{literalinclude} ../../unilabos_msgs/action/Recrystallize.action
|
||||||
|
:language: yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `RunColumn`
|
||||||
|
|
||||||
|
```{literalinclude} ../../unilabos_msgs/action/RunColumn.action
|
||||||
|
:language: yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `StartPurge`
|
||||||
|
|
||||||
|
```{literalinclude} ../../unilabos_msgs/action/StartPurge.action
|
||||||
|
:language: yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `StartStir`
|
||||||
|
|
||||||
|
```{literalinclude} ../../unilabos_msgs/action/StartStir.action
|
||||||
|
:language: yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `StopPurge`
|
||||||
|
|
||||||
|
```{literalinclude} ../../unilabos_msgs/action/StopPurge.action
|
||||||
|
:language: yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `StopStir`
|
||||||
|
|
||||||
|
```{literalinclude} ../../unilabos_msgs/action/StopStir.action
|
||||||
|
:language: yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `Transfer`
|
||||||
|
|
||||||
|
```{literalinclude} ../../unilabos_msgs/action/Transfer.action
|
||||||
|
:language: yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `WashSolid`
|
||||||
|
|
||||||
|
```{literalinclude} ../../unilabos_msgs/action/WashSolid.action
|
||||||
|
:language: yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 移液工作站及相关生物自动化设备操作
|
## 移液工作站及相关生物自动化设备操作
|
||||||
|
|
||||||
Uni-Lab 生物操作指令集多数来自 [PyLabRobot](https://docs.pylabrobot.org/user_guide/index.html),包含生物实验中常见的操作,如移液、混匀、离心等。
|
Uni-Lab 生物操作指令集多数来自 [PyLabRobot](https://docs.pylabrobot.org/user_guide/index.html),包含生物实验中常见的操作,如移液、混匀、离心等。
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### `LiquidHandlerAspirate`
|
### `LiquidHandlerAspirate`
|
||||||
|
|
||||||
```{literalinclude} ../../unilabos_msgs/action/LiquidHandlerAspirate.action
|
```{literalinclude} ../../unilabos_msgs/action/LiquidHandlerAspirate.action
|
||||||
:language: yaml
|
:language: yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
----
|
---
|
||||||
|
|
||||||
### `LiquidHandlerDiscardTips`
|
### `LiquidHandlerDiscardTips`
|
||||||
|
|
||||||
@@ -105,7 +302,7 @@ Uni-Lab 生物操作指令集多数来自 [PyLabRobot](https://docs.pylabrobot.o
|
|||||||
:language: yaml
|
:language: yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
----
|
---
|
||||||
|
|
||||||
### `LiquidHandlerDispense`
|
### `LiquidHandlerDispense`
|
||||||
|
|
||||||
@@ -113,7 +310,7 @@ Uni-Lab 生物操作指令集多数来自 [PyLabRobot](https://docs.pylabrobot.o
|
|||||||
:language: yaml
|
:language: yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
----
|
---
|
||||||
|
|
||||||
### `LiquidHandlerDropTips`
|
### `LiquidHandlerDropTips`
|
||||||
|
|
||||||
@@ -121,7 +318,7 @@ Uni-Lab 生物操作指令集多数来自 [PyLabRobot](https://docs.pylabrobot.o
|
|||||||
:language: yaml
|
:language: yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
----
|
---
|
||||||
|
|
||||||
### `LiquidHandlerDropTips96`
|
### `LiquidHandlerDropTips96`
|
||||||
|
|
||||||
@@ -129,7 +326,7 @@ Uni-Lab 生物操作指令集多数来自 [PyLabRobot](https://docs.pylabrobot.o
|
|||||||
:language: yaml
|
:language: yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
----
|
---
|
||||||
|
|
||||||
### `LiquidHandlerMoveLid`
|
### `LiquidHandlerMoveLid`
|
||||||
|
|
||||||
@@ -137,7 +334,7 @@ Uni-Lab 生物操作指令集多数来自 [PyLabRobot](https://docs.pylabrobot.o
|
|||||||
:language: yaml
|
:language: yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
----
|
---
|
||||||
|
|
||||||
### `LiquidHandlerMovePlate`
|
### `LiquidHandlerMovePlate`
|
||||||
|
|
||||||
@@ -145,7 +342,7 @@ Uni-Lab 生物操作指令集多数来自 [PyLabRobot](https://docs.pylabrobot.o
|
|||||||
:language: yaml
|
:language: yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
----
|
---
|
||||||
|
|
||||||
### `LiquidHandlerMoveResource`
|
### `LiquidHandlerMoveResource`
|
||||||
|
|
||||||
@@ -153,7 +350,7 @@ Uni-Lab 生物操作指令集多数来自 [PyLabRobot](https://docs.pylabrobot.o
|
|||||||
:language: yaml
|
:language: yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
----
|
---
|
||||||
|
|
||||||
### `LiquidHandlerPickUpTips`
|
### `LiquidHandlerPickUpTips`
|
||||||
|
|
||||||
@@ -161,7 +358,7 @@ Uni-Lab 生物操作指令集多数来自 [PyLabRobot](https://docs.pylabrobot.o
|
|||||||
:language: yaml
|
:language: yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
----
|
---
|
||||||
|
|
||||||
### `LiquidHandlerPickUpTips96`
|
### `LiquidHandlerPickUpTips96`
|
||||||
|
|
||||||
@@ -169,7 +366,7 @@ Uni-Lab 生物操作指令集多数来自 [PyLabRobot](https://docs.pylabrobot.o
|
|||||||
:language: yaml
|
:language: yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
----
|
---
|
||||||
|
|
||||||
### `LiquidHandlerReturnTips`
|
### `LiquidHandlerReturnTips`
|
||||||
|
|
||||||
@@ -177,7 +374,7 @@ Uni-Lab 生物操作指令集多数来自 [PyLabRobot](https://docs.pylabrobot.o
|
|||||||
:language: yaml
|
:language: yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
----
|
---
|
||||||
|
|
||||||
### `LiquidHandlerReturnTips96`
|
### `LiquidHandlerReturnTips96`
|
||||||
|
|
||||||
@@ -185,7 +382,7 @@ Uni-Lab 生物操作指令集多数来自 [PyLabRobot](https://docs.pylabrobot.o
|
|||||||
:language: yaml
|
:language: yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
----
|
---
|
||||||
|
|
||||||
### `LiquidHandlerStamp`
|
### `LiquidHandlerStamp`
|
||||||
|
|
||||||
@@ -193,7 +390,7 @@ Uni-Lab 生物操作指令集多数来自 [PyLabRobot](https://docs.pylabrobot.o
|
|||||||
:language: yaml
|
:language: yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
----
|
---
|
||||||
|
|
||||||
### `LiquidHandlerTransfer`
|
### `LiquidHandlerTransfer`
|
||||||
|
|
||||||
@@ -201,9 +398,113 @@ Uni-Lab 生物操作指令集多数来自 [PyLabRobot](https://docs.pylabrobot.o
|
|||||||
:language: yaml
|
:language: yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
----
|
---
|
||||||
## 多工作站及小车运行、物料转移
|
|
||||||
|
|
||||||
|
### `LiquidHandlerAdd`
|
||||||
|
|
||||||
|
```{literalinclude} ../../unilabos_msgs/action/LiquidHandlerAdd.action
|
||||||
|
:language: yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `LiquidHandlerIncubateBiomek`
|
||||||
|
|
||||||
|
```{literalinclude} ../../unilabos_msgs/action/LiquidHandlerIncubateBiomek.action
|
||||||
|
:language: yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `LiquidHandlerMix`
|
||||||
|
|
||||||
|
```{literalinclude} ../../unilabos_msgs/action/LiquidHandlerMix.action
|
||||||
|
:language: yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `LiquidHandlerMoveBiomek`
|
||||||
|
|
||||||
|
```{literalinclude} ../../unilabos_msgs/action/LiquidHandlerMoveBiomek.action
|
||||||
|
:language: yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `LiquidHandlerMoveTo`
|
||||||
|
|
||||||
|
```{literalinclude} ../../unilabos_msgs/action/LiquidHandlerMoveTo.action
|
||||||
|
:language: yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `LiquidHandlerOscillateBiomek`
|
||||||
|
|
||||||
|
```{literalinclude} ../../unilabos_msgs/action/LiquidHandlerOscillateBiomek.action
|
||||||
|
:language: yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `LiquidHandlerProtocolCreation`
|
||||||
|
|
||||||
|
```{literalinclude} ../../unilabos_msgs/action/LiquidHandlerProtocolCreation.action
|
||||||
|
:language: yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `LiquidHandlerRemove`
|
||||||
|
|
||||||
|
```{literalinclude} ../../unilabos_msgs/action/LiquidHandlerRemove.action
|
||||||
|
:language: yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `LiquidHandlerSetGroup`
|
||||||
|
|
||||||
|
```{literalinclude} ../../unilabos_msgs/action/LiquidHandlerSetGroup.action
|
||||||
|
:language: yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `LiquidHandlerSetLiquid`
|
||||||
|
|
||||||
|
```{literalinclude} ../../unilabos_msgs/action/LiquidHandlerSetLiquid.action
|
||||||
|
:language: yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `LiquidHandlerSetTipRack`
|
||||||
|
|
||||||
|
```{literalinclude} ../../unilabos_msgs/action/LiquidHandlerSetTipRack.action
|
||||||
|
:language: yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `LiquidHandlerTransferBiomek`
|
||||||
|
|
||||||
|
```{literalinclude} ../../unilabos_msgs/action/LiquidHandlerTransferBiomek.action
|
||||||
|
:language: yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `LiquidHandlerTransferGroup`
|
||||||
|
|
||||||
|
```{literalinclude} ../../unilabos_msgs/action/LiquidHandlerTransferGroup.action
|
||||||
|
:language: yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 多工作站及小车运行、物料转移
|
||||||
|
|
||||||
### `AGVTransfer`
|
### `AGVTransfer`
|
||||||
|
|
||||||
@@ -211,7 +512,7 @@ Uni-Lab 生物操作指令集多数来自 [PyLabRobot](https://docs.pylabrobot.o
|
|||||||
:language: yaml
|
:language: yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
----
|
---
|
||||||
|
|
||||||
### `WorkStationRun`
|
### `WorkStationRun`
|
||||||
|
|
||||||
@@ -219,12 +520,64 @@ Uni-Lab 生物操作指令集多数来自 [PyLabRobot](https://docs.pylabrobot.o
|
|||||||
:language: yaml
|
:language: yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
----
|
---
|
||||||
|
|
||||||
|
### `ResetHandling`
|
||||||
|
|
||||||
|
```{literalinclude} ../../unilabos_msgs/action/ResetHandling.action
|
||||||
|
:language: yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `ResourceCreateFromOuter`
|
||||||
|
|
||||||
|
```{literalinclude} ../../unilabos_msgs/action/ResourceCreateFromOuter.action
|
||||||
|
:language: yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `ResourceCreateFromOuterEasy`
|
||||||
|
|
||||||
|
```{literalinclude} ../../unilabos_msgs/action/ResourceCreateFromOuterEasy.action
|
||||||
|
:language: yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `SetPumpPosition`
|
||||||
|
|
||||||
|
```{literalinclude} ../../unilabos_msgs/action/SetPumpPosition.action
|
||||||
|
:language: yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 固体分配与处理设备操作
|
||||||
|
|
||||||
|
### `SolidDispenseAddPowderTube`
|
||||||
|
|
||||||
|
```{literalinclude} ../../unilabos_msgs/action/SolidDispenseAddPowderTube.action
|
||||||
|
:language: yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 其他设备操作
|
||||||
|
|
||||||
|
### `EmptyIn`
|
||||||
|
|
||||||
|
```{literalinclude} ../../unilabos_msgs/action/EmptyIn.action
|
||||||
|
:language: yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 机械臂、夹爪等机器人设备
|
## 机械臂、夹爪等机器人设备
|
||||||
|
|
||||||
Uni-Lab 机械臂、机器人、夹爪和导航指令集沿用 ROS2 的 `control_msgs` 和 `nav2_msgs`:
|
Uni-Lab 机械臂、机器人、夹爪和导航指令集沿用 ROS2 的 `control_msgs` 和 `nav2_msgs`:
|
||||||
|
|
||||||
|
|
||||||
### `FollowJointTrajectory`
|
### `FollowJointTrajectory`
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@@ -292,7 +645,8 @@ trajectory_msgs/MultiDOFJointTrajectoryPoint multi_dof_error
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
----
|
---
|
||||||
|
|
||||||
### `GripperCommand`
|
### `GripperCommand`
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@@ -310,17 +664,19 @@ bool reached_goal # True iff the gripper position has reached the commanded setp
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
----
|
---
|
||||||
|
|
||||||
### `JointTrajectory`
|
### `JointTrajectory`
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
trajectory_msgs/JointTrajectory trajectory
|
trajectory_msgs/JointTrajectory trajectory
|
||||||
---
|
---
|
||||||
---
|
|
||||||
|
|
||||||
|
---
|
||||||
```
|
```
|
||||||
|
|
||||||
----
|
---
|
||||||
|
|
||||||
### `PointHead`
|
### `PointHead`
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@@ -330,12 +686,13 @@ string pointing_frame
|
|||||||
builtin_interfaces/Duration min_duration
|
builtin_interfaces/Duration min_duration
|
||||||
float64 max_velocity
|
float64 max_velocity
|
||||||
---
|
---
|
||||||
|
|
||||||
---
|
---
|
||||||
float64 pointing_angle_error
|
float64 pointing_angle_error
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
----
|
---
|
||||||
|
|
||||||
### `SingleJointPosition`
|
### `SingleJointPosition`
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@@ -343,15 +700,16 @@ float64 position
|
|||||||
builtin_interfaces/Duration min_duration
|
builtin_interfaces/Duration min_duration
|
||||||
float64 max_velocity
|
float64 max_velocity
|
||||||
---
|
---
|
||||||
|
|
||||||
---
|
---
|
||||||
std_msgs/Header header
|
std_msgs/Header header
|
||||||
float64 position
|
float64 position
|
||||||
float64 velocity
|
float64 velocity
|
||||||
float64 error
|
float64 error
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
----
|
---
|
||||||
|
|
||||||
### `AssistedTeleop`
|
### `AssistedTeleop`
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@@ -363,10 +721,10 @@ builtin_interfaces/Duration total_elapsed_time
|
|||||||
---
|
---
|
||||||
#feedback
|
#feedback
|
||||||
builtin_interfaces/Duration current_teleop_duration
|
builtin_interfaces/Duration current_teleop_duration
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
----
|
---
|
||||||
|
|
||||||
### `BackUp`
|
### `BackUp`
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@@ -380,10 +738,10 @@ builtin_interfaces/Duration total_elapsed_time
|
|||||||
---
|
---
|
||||||
#feedback definition
|
#feedback definition
|
||||||
float32 distance_traveled
|
float32 distance_traveled
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
----
|
---
|
||||||
|
|
||||||
### `ComputePathThroughPoses`
|
### `ComputePathThroughPoses`
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@@ -398,10 +756,10 @@ nav_msgs/Path path
|
|||||||
builtin_interfaces/Duration planning_time
|
builtin_interfaces/Duration planning_time
|
||||||
---
|
---
|
||||||
#feedback definition
|
#feedback definition
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
----
|
---
|
||||||
|
|
||||||
### `ComputePathToPose`
|
### `ComputePathToPose`
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@@ -416,10 +774,10 @@ nav_msgs/Path path
|
|||||||
builtin_interfaces/Duration planning_time
|
builtin_interfaces/Duration planning_time
|
||||||
---
|
---
|
||||||
#feedback definition
|
#feedback definition
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
----
|
---
|
||||||
|
|
||||||
### `DriveOnHeading`
|
### `DriveOnHeading`
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@@ -433,10 +791,10 @@ builtin_interfaces/Duration total_elapsed_time
|
|||||||
---
|
---
|
||||||
#feedback definition
|
#feedback definition
|
||||||
float32 distance_traveled
|
float32 distance_traveled
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
----
|
---
|
||||||
|
|
||||||
### `DummyBehavior`
|
### `DummyBehavior`
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@@ -447,10 +805,10 @@ std_msgs/String command
|
|||||||
builtin_interfaces/Duration total_elapsed_time
|
builtin_interfaces/Duration total_elapsed_time
|
||||||
---
|
---
|
||||||
#feedback definition
|
#feedback definition
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
----
|
---
|
||||||
|
|
||||||
### `FollowPath`
|
### `FollowPath`
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@@ -465,10 +823,10 @@ std_msgs/Empty result
|
|||||||
#feedback definition
|
#feedback definition
|
||||||
float32 distance_to_goal
|
float32 distance_to_goal
|
||||||
float32 speed
|
float32 speed
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
----
|
---
|
||||||
|
|
||||||
### `FollowWaypoints`
|
### `FollowWaypoints`
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@@ -480,10 +838,10 @@ int32[] missed_waypoints
|
|||||||
---
|
---
|
||||||
#feedback definition
|
#feedback definition
|
||||||
uint32 current_waypoint
|
uint32 current_waypoint
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
----
|
---
|
||||||
|
|
||||||
### `NavigateThroughPoses`
|
### `NavigateThroughPoses`
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@@ -501,10 +859,10 @@ builtin_interfaces/Duration estimated_time_remaining
|
|||||||
int16 number_of_recoveries
|
int16 number_of_recoveries
|
||||||
float32 distance_remaining
|
float32 distance_remaining
|
||||||
int16 number_of_poses_remaining
|
int16 number_of_poses_remaining
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
----
|
---
|
||||||
|
|
||||||
### `NavigateToPose`
|
### `NavigateToPose`
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@@ -521,10 +879,10 @@ builtin_interfaces/Duration navigation_time
|
|||||||
builtin_interfaces/Duration estimated_time_remaining
|
builtin_interfaces/Duration estimated_time_remaining
|
||||||
int16 number_of_recoveries
|
int16 number_of_recoveries
|
||||||
float32 distance_remaining
|
float32 distance_remaining
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
----
|
---
|
||||||
|
|
||||||
### `SmoothPath`
|
### `SmoothPath`
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@@ -540,10 +898,10 @@ builtin_interfaces/Duration smoothing_duration
|
|||||||
bool was_completed
|
bool was_completed
|
||||||
---
|
---
|
||||||
#feedback definition
|
#feedback definition
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
----
|
---
|
||||||
|
|
||||||
### `Spin`
|
### `Spin`
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@@ -556,10 +914,10 @@ builtin_interfaces/Duration total_elapsed_time
|
|||||||
---
|
---
|
||||||
#feedback definition
|
#feedback definition
|
||||||
float32 angular_distance_traveled
|
float32 angular_distance_traveled
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
----
|
---
|
||||||
|
|
||||||
### `Wait`
|
### `Wait`
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@@ -571,7 +929,6 @@ builtin_interfaces/Duration total_elapsed_time
|
|||||||
---
|
---
|
||||||
#feedback definition
|
#feedback definition
|
||||||
builtin_interfaces/Duration time_left
|
builtin_interfaces/Duration time_left
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
----
|
---
|
||||||
|
|||||||
@@ -1,37 +1,142 @@
|
|||||||
# 添加新动作指令(Action)
|
# 添加新动作指令(Action)
|
||||||
|
|
||||||
1. 在 `unilabos_msgs/action` 中新建实验操作名和参数列表,如 `MyDeviceCmd.action`。一个 Action 定义由三个部分组成,分别是目标(Goal)、结果(Result)和反馈(Feedback),之间使用 `---` 分隔:
|
本指南将引导你完成添加新动作指令的整个流程,包括编写、在线构建和测试。
|
||||||
|
|
||||||
|
## 1. 编写新的 Action
|
||||||
|
|
||||||
|
### 1.1 创建 Action 文件
|
||||||
|
|
||||||
|
在 `unilabos_msgs/action` 目录中新建实验操作文件,如 `MyDeviceCmd.action`。一个 Action 定义由三个部分组成,分别是目标(Goal)、结果(Result)和反馈(Feedback),之间使用 `---` 分隔:
|
||||||
|
|
||||||
```action
|
```action
|
||||||
# 目标(Goal)
|
# 目标(Goal)- 定义动作执行所需的参数
|
||||||
string command
|
string command
|
||||||
|
float64 timeout
|
||||||
---
|
---
|
||||||
# 结果(Result)
|
# 结果(Result)- 定义动作完成后返回的结果
|
||||||
bool success
|
bool success # 要求必须包含success,以便回传执行结果
|
||||||
|
string return_info # 要求必须包含return_info,以便回传执行结果
|
||||||
|
... # 其他
|
||||||
---
|
---
|
||||||
# 反馈(Feedback)
|
# 反馈(Feedback)- 定义动作执行过程中的反馈信息
|
||||||
|
float64 progress
|
||||||
|
string status
|
||||||
```
|
```
|
||||||
|
|
||||||
2. 在 `unilabos_msgs/CMakeLists.txt` 中添加新定义的 action
|
### 1.2 更新 CMakeLists.txt
|
||||||
|
|
||||||
|
在 `unilabos_msgs/CMakeLists.txt` 中的 `add_action_files()` 部分添加新定义的 action:
|
||||||
|
|
||||||
```cmake
|
```cmake
|
||||||
add_action_files(
|
add_action_files(
|
||||||
FILES
|
FILES
|
||||||
MyDeviceCmd.action
|
MyDeviceCmd.action
|
||||||
|
# 其他已有的 action 文件...
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
3. 因为在指令集中新建了指令,因此调试时需要编译,并在终端环境中加载临时路径:
|
## 2. 在线构建和测试
|
||||||
|
|
||||||
|
为了简化开发流程并确保构建环境的一致性,我们使用 GitHub Actions 进行在线构建。
|
||||||
|
|
||||||
|
### 2.1 Fork 仓库并创建分支
|
||||||
|
|
||||||
|
1. **Fork 仓库**:在 GitHub 上 fork `Uni-Lab-OS` 仓库到你的个人账户
|
||||||
|
|
||||||
|
2. **Clone 你的 fork**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd unilabos_msgs
|
git clone https://github.com/YOUR_USERNAME/Uni-Lab-OS.git
|
||||||
colcon build
|
cd Uni-Lab-OS
|
||||||
source ./install/local_setup.sh
|
|
||||||
cd ..
|
|
||||||
```
|
```
|
||||||
|
|
||||||
调试成功后,发起 pull request,Uni-Lab 的 CI/CD 系统会自动将新的指令集编译打包,mamba执行升级即可永久生效:
|
3. **创建功能分支**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mamba update ros-humble-unilabos-msgs -c http://quetz.dp.tech:8088/get/unilab -c robostack-humble -c robostack-staging
|
git checkout -b add-my-device-action
|
||||||
```
|
```
|
||||||
|
|
||||||
|
4. **提交你的更改**:
|
||||||
|
```bash
|
||||||
|
git add unilabos_msgs/action/MyDeviceCmd.action
|
||||||
|
git add unilabos_msgs/CMakeLists.txt
|
||||||
|
git commit -m "Add MyDeviceCmd action for device control"
|
||||||
|
git push origin add-my-device-action
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 触发在线构建
|
||||||
|
|
||||||
|
1. **访问你的 fork 仓库**:在浏览器中打开你的 fork 仓库页面
|
||||||
|
|
||||||
|
2. **手动触发构建**:
|
||||||
|
|
||||||
|
- 点击 "Actions" 标签
|
||||||
|
- 选择 "Multi-Platform Conda Build" 工作流
|
||||||
|
- 点击 "Run workflow" 按钮
|
||||||
|
|
||||||
|
3. **监控构建状态**:
|
||||||
|
- 构建过程大约需要 5-10 分钟
|
||||||
|
- 在 Actions 页面可以实时查看构建日志
|
||||||
|
- 构建完成后,可以下载生成的 conda 包进行测试
|
||||||
|
|
||||||
|
### 2.3 下载和测试构建包
|
||||||
|
|
||||||
|
1. **下载构建产物**:
|
||||||
|
|
||||||
|
- 在构建完成的 Action 页面,找到 "Artifacts" 部分
|
||||||
|
- 下载对应平台的 `conda-package-*` 文件
|
||||||
|
|
||||||
|
2. **本地测试安装**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 解压下载的构建产物
|
||||||
|
unzip conda-package-linux-64.zip # 或其他平台
|
||||||
|
|
||||||
|
# 安装测试包
|
||||||
|
mamba install ./ros-humble-unilabos-msgs-*.conda
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **验证 Action 是否正确添加**:
|
||||||
|
```bash
|
||||||
|
# 检查 action 是否可用
|
||||||
|
ros2 interface show unilabos_msgs/action/MyDeviceCmd
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. 提交 Pull Request
|
||||||
|
|
||||||
|
测试成功后,向主仓库提交 Pull Request:
|
||||||
|
|
||||||
|
1. **创建 Pull Request**:
|
||||||
|
|
||||||
|
- 在你的 fork 仓库页面,点击 "New Pull Request"
|
||||||
|
- 选择你的功能分支作为源分支
|
||||||
|
- 填写详细的 PR 描述,包括:
|
||||||
|
- 添加的 Action 功能说明
|
||||||
|
- 测试结果
|
||||||
|
- 相关的设备或用例
|
||||||
|
|
||||||
|
2. **等待审核和合并**:
|
||||||
|
- 维护者会审核你的代码
|
||||||
|
- CI/CD 系统会自动运行完整的测试套件
|
||||||
|
- 合并后,新的指令集会自动发布到官方 conda 仓库
|
||||||
|
|
||||||
|
## 4. 使用新的 Action
|
||||||
|
|
||||||
|
如果采用自己构建的action包,可以通过以下命令更新安装:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mamba remove --force ros-humble-unilabos-msgs
|
||||||
|
mamba config set safety_checks disabled # 如果没有提升版本号,会触发md5与网络上md5不一致,是正常现象,因此通过本指令关闭md5检查
|
||||||
|
mamba install xxx.conda2 --offline
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
**Q: 构建失败怎么办?**
|
||||||
|
A: 检查 Actions 日志中的错误信息,通常是语法错误或依赖问题。修复后重新推送代码即可自动触发新的构建。
|
||||||
|
|
||||||
|
**Q: 如何测试特定平台?**
|
||||||
|
A: 在手动触发构建时,在平台选择中只填写你需要的平台,如 `linux-64` 或 `win-64`。
|
||||||
|
|
||||||
|
**Q: 构建包在哪里下载?**
|
||||||
|
A: 在 Actions 页面的构建结果中,查找 "Artifacts" 部分,每个平台都有对应的构建包可供下载。
|
||||||
|
|||||||
@@ -1,95 +1,610 @@
|
|||||||
# yaml 注册表编写指南
|
# yaml 注册表编写指南
|
||||||
|
|
||||||
`注册表的结构`
|
## 快速开始:使用注册表编辑器
|
||||||
|
|
||||||
1. 顶层名称:每个设备的注册表以设备名称开头,例如 new_device。
|
推荐使用 UniLabOS 自带的可视化编辑器,它能帮你自动生成大部分配置,省去手写的麻烦。
|
||||||
2. class 字段:定义设备的模块路径和类型。
|
|
||||||
3. schema 字段:定义设备的属性模式,包括属性类型、描述和必需字段。
|
|
||||||
4. action_value_mappings 字段:定义设备支持的动作及其目标、反馈和结果。
|
|
||||||
|
|
||||||
`创建新的注册表教程`
|
### 怎么用编辑器
|
||||||
1. 创建文件
|
|
||||||
在 devices 文件夹中创建一个新的 YAML 文件,例如 new_device.yaml。
|
|
||||||
|
|
||||||
2. 定义设备名称
|
1. 启动 UniLabOS
|
||||||
在文件中定义设备的顶层名称,例如:new_device
|
2. 在浏览器中打开"注册表编辑器"页面
|
||||||
|
3. 选择你的 Python 设备驱动文件
|
||||||
|
4. 点击"分析文件",让系统读取你的类信息
|
||||||
|
5. 填写一些基本信息(设备描述、图标啥的)
|
||||||
|
6. 点击"生成注册表",复制生成的内容
|
||||||
|
7. 把内容保存到 `devices/` 目录下
|
||||||
|
|
||||||
3. 定义设备的类信息
|
我们为你准备了一个测试驱动,用于在界面上尝试注册表生成,参见目录:test\registry\example_devices.py
|
||||||
添加设备的模块路径和类型:
|
|
||||||
|
|
||||||
```python
|
---
|
||||||
new_device: # 定义一个名为 linear_motion.grbl 的设备
|
|
||||||
|
|
||||||
|
## 手动编写指南
|
||||||
|
|
||||||
class: # 定义设备的类信息
|
如果你想自己写 yaml 文件,或者想深入了解结构,查阅下方说明。
|
||||||
module: unilabos.devices_names.new_device:NewDeviceClass # 指定模块路径和类名
|
|
||||||
type: python # 指定类型为 Python 类
|
|
||||||
status_types:
|
|
||||||
```
|
|
||||||
4. 定义设备支持的动作
|
|
||||||
添加设备支持的动作及其目标、反馈和结果:
|
|
||||||
```python
|
|
||||||
action_value_mappings:
|
|
||||||
set_speed:
|
|
||||||
type: SendCmd
|
|
||||||
goal:
|
|
||||||
command: speed
|
|
||||||
feedback: {}
|
|
||||||
result:
|
|
||||||
success: success
|
|
||||||
```
|
|
||||||
`如何编写action_valve_mappings`
|
|
||||||
1. 在 devices 文件夹中的 YAML 文件中,action_value_mappings 是用来定义设备支持的动作(actions)及其目标值(goal)、反馈值(feedback)和结果值(result)的映射规则。以下是规则和编写方法:
|
|
||||||
```python
|
|
||||||
action_value_mappings:
|
|
||||||
<action_name>: # <action_name>:动作的名称
|
|
||||||
# start:启动设备或某个功能。
|
|
||||||
# stop:停止设备或某个功能。
|
|
||||||
# set_speed:设置设备的速度。
|
|
||||||
# set_temperature:设置设备的温度。
|
|
||||||
# move_to_position:移动设备到指定位置。
|
|
||||||
# stir:执行搅拌操作。
|
|
||||||
# heatchill:执行加热或冷却操作。
|
|
||||||
# send_nav_task:发送导航任务(例如机器人导航)。
|
|
||||||
# set_timer:设置设备的计时器。
|
|
||||||
# valve_open_cmd:打开阀门。
|
|
||||||
# valve_close_cmd:关闭阀门。
|
|
||||||
# execute_command_from_outer:执行外部命令。
|
|
||||||
# push_to:控制设备推送到某个位置(例如机械爪)。
|
|
||||||
# move_through_points:导航设备通过多个点。
|
|
||||||
|
|
||||||
type: <ActionType> # 动作的类型,表示动作的功能
|
## 注册表的基本结构
|
||||||
# 根据动作的功能选择合适的类型:
|
|
||||||
# SendCmd:发送简单命令。
|
|
||||||
# NavigateThroughPoses:导航动作。
|
|
||||||
# SingleJointPosition:设置单一关节的位置。
|
|
||||||
# Stir:搅拌动作。
|
|
||||||
# HeatChill:加热或冷却动作。
|
|
||||||
|
|
||||||
goal: # 定义动作的目标值映射,表示需要传递给设备的参数。
|
yaml 注册表就是设备的配置文件,里面定义了设备怎么用、有什么功能。好消息是系统会自动帮你填大部分内容,你只需要写两个必需的东西:设备名和 class 信息。
|
||||||
<goal_key>: <mapped_value> #确定设备需要的输入参数,并将其映射到设备的字段。
|
|
||||||
|
|
||||||
feedback: # 定义动作的反馈值映射,表示设备执行动作时返回的实时状态。
|
### 各字段用途
|
||||||
<feedback_key>: <mapped_value>
|
|
||||||
result: # 定义动作的结果值映射,表示动作完成后返回的最终结果。
|
|
||||||
<result_key>: <mapped_value>
|
|
||||||
```
|
|
||||||
|
|
||||||
6. 定义设备的属性模式
|
| 字段名 | 类型 | 需要手写 | 说明 |
|
||||||
添加设备的属性模式,包括属性类型和描述:
|
| ----------------- | ------ | -------- | ----------------------------------- |
|
||||||
```python
|
| 设备标识符 | string | 是 | 设备的唯一名字,比如 `mock_chiller` |
|
||||||
schema:
|
| class | object | 部分 | 设备的核心信息,必须写 |
|
||||||
|
| description | string | 否 | 设备描述,系统默认给空字符串 |
|
||||||
|
| handles | array | 否 | 连接关系,默认是空的 |
|
||||||
|
| icon | string | 否 | 图标路径,默认为空 |
|
||||||
|
| init_param_schema | object | 否 | 初始化参数,系统自动分析生成 |
|
||||||
|
| version | string | 否 | 版本号,默认 "1.0.0" |
|
||||||
|
| category | array | 否 | 设备分类,默认用文件名 |
|
||||||
|
| config_info | array | 否 | 嵌套配置,默认为空 |
|
||||||
|
| file_path | string | 否 | 文件路径,系统自动设置 |
|
||||||
|
| registry_type | string | 否 | 注册表类型,自动设为 "device" |
|
||||||
|
|
||||||
|
### class 字段里有啥
|
||||||
|
|
||||||
|
class 是核心部分,包含这些内容:
|
||||||
|
|
||||||
|
| 字段名 | 类型 | 需要手写 | 说明 |
|
||||||
|
| --------------------- | ------ | -------- | ---------------------------------- |
|
||||||
|
| module | string | 是 | Python 类的路径,必须写 |
|
||||||
|
| type | string | 是 | 驱动类型,一般写 "python" |
|
||||||
|
| status_types | object | 否 | 状态类型,系统自动分析生成 |
|
||||||
|
| action_value_mappings | object | 部分 | 动作配置,系统会自动生成一些基础的 |
|
||||||
|
|
||||||
|
## 怎么创建新的注册表
|
||||||
|
|
||||||
|
### 创建文件
|
||||||
|
|
||||||
|
在 devices 文件夹里新建一个 yaml 文件,比如 `new_device.yaml`。
|
||||||
|
|
||||||
|
### 完整结构是什么样的
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
new_device: # 设备名,要唯一
|
||||||
|
class: # 核心配置
|
||||||
|
action_value_mappings: # 动作配置(后面会详细说)
|
||||||
|
action_name:
|
||||||
|
# 具体的动作设置
|
||||||
|
module: unilabos.devices.your_module.new_device:NewDeviceClass # 你的 Python 类
|
||||||
|
status_types: # 状态类型(系统会自动生成)
|
||||||
|
status: str
|
||||||
|
temperature: float
|
||||||
|
# 其他状态
|
||||||
|
type: python # 驱动类型,一般就是 python
|
||||||
|
|
||||||
|
description: New Device Description # 设备描述
|
||||||
|
handles: [] # 连接关系,通常是空的
|
||||||
|
icon: '' # 图标路径
|
||||||
|
init_param_schema: # 初始化参数(系统会自动生成)
|
||||||
|
config: # 初始化时需要的参数
|
||||||
|
properties:
|
||||||
|
port:
|
||||||
|
default: DEFAULT_PORT
|
||||||
|
type: string
|
||||||
|
required: []
|
||||||
type: object
|
type: object
|
||||||
|
data: # 前端显示用的数据类型
|
||||||
properties:
|
properties:
|
||||||
status:
|
status:
|
||||||
type: string
|
type: string
|
||||||
description: The status of the device
|
temperature:
|
||||||
speed:
|
|
||||||
type: number
|
type: number
|
||||||
description: The speed of the device
|
|
||||||
required:
|
required:
|
||||||
- status
|
- status
|
||||||
- speed
|
type: object
|
||||||
additionalProperties: false
|
|
||||||
|
version: 0.0.1 # 版本号
|
||||||
|
category:
|
||||||
|
- device_category # 设备类别
|
||||||
|
config_info: [] # 嵌套配置,通常为空
|
||||||
```
|
```
|
||||||
# 写完yaml注册表后需要添加到哪些其他文件?
|
|
||||||
|
## action_value_mappings 怎么写
|
||||||
|
|
||||||
|
这个部分定义设备能做哪些动作。好消息是系统会自动生成大部分动作,你通常只需要添加一些特殊的自定义动作。
|
||||||
|
|
||||||
|
### 系统自动生成哪些动作
|
||||||
|
|
||||||
|
系统会帮你生成这些:
|
||||||
|
|
||||||
|
1. 以 `auto-` 开头的动作:从你 Python 类的方法自动生成
|
||||||
|
2. 通用的驱动动作:
|
||||||
|
- `_execute_driver_command`:同步执行驱动命令
|
||||||
|
- `_execute_driver_command_async`:异步执行驱动命令
|
||||||
|
|
||||||
|
### 如果要手动定义动作
|
||||||
|
|
||||||
|
如果你需要自定义一些特殊动作,需要这些字段:
|
||||||
|
|
||||||
|
| 字段名 | 需要手写 | 说明 |
|
||||||
|
| ---------------- | -------- | -------------------------------- |
|
||||||
|
| type | 是 | 动作类型,必须指定 |
|
||||||
|
| goal | 是 | 输入参数怎么映射 |
|
||||||
|
| feedback | 否 | 实时反馈,通常为空 |
|
||||||
|
| result | 是 | 结果怎么返回 |
|
||||||
|
| goal_default | 部分 | 参数默认值,ROS 动作会自动生成 |
|
||||||
|
| schema | 部分 | 前端表单配置,ROS 动作会自动生成 |
|
||||||
|
| handles | 否 | 连接关系,默认为空 |
|
||||||
|
| placeholder_keys | 否 | 特殊输入字段配置 |
|
||||||
|
|
||||||
|
### 动作类型有哪些
|
||||||
|
|
||||||
|
| 类型 | 什么时候用 | 系统会自动生成什么 |
|
||||||
|
| ---------------------- | -------------------- | ---------------------- |
|
||||||
|
| UniLabJsonCommand | 自定义同步 JSON 命令 | 啥都不生成 |
|
||||||
|
| UniLabJsonCommandAsync | 自定义异步 JSON 命令 | 啥都不生成 |
|
||||||
|
| ROS 动作类型 | 标准 ROS 动作 | goal_default 和 schema |
|
||||||
|
|
||||||
|
常用的 ROS 动作类型:
|
||||||
|
|
||||||
|
- `SendCmd`:发送简单命令
|
||||||
|
- `NavigateThroughPoses`:导航动作
|
||||||
|
- `SingleJointPosition`:单关节位置控制
|
||||||
|
- `Stir`:搅拌动作
|
||||||
|
- `HeatChill`、`HeatChillStart`:加热冷却动作
|
||||||
|
|
||||||
|
### 复杂一点的例子
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
heat_chill_start:
|
||||||
|
type: HeatChillStart
|
||||||
|
goal:
|
||||||
|
purpose: purpose
|
||||||
|
temp: temp
|
||||||
|
goal_default: # ROS动作会自动生成,你也可以手动覆盖
|
||||||
|
purpose: ''
|
||||||
|
temp: 0.0
|
||||||
|
handles:
|
||||||
|
output:
|
||||||
|
- handler_key: labware
|
||||||
|
label: Labware
|
||||||
|
data_type: resource
|
||||||
|
data_source: handle
|
||||||
|
data_key: liquid
|
||||||
|
placeholder_keys:
|
||||||
|
purpose: unilabos_resources
|
||||||
|
result:
|
||||||
|
status: status
|
||||||
|
success: success
|
||||||
|
# schema 系统会自动生成,不用写
|
||||||
|
```
|
||||||
|
|
||||||
|
### 动作名字怎么起
|
||||||
|
|
||||||
|
根据设备用途来起名字:
|
||||||
|
|
||||||
|
- 启动停止类:`start`、`stop`、`pause`、`resume`
|
||||||
|
- 设置参数类:`set_speed`、`set_temperature`、`set_timer`
|
||||||
|
- 移动控制类:`move_to_position`、`move_through_points`
|
||||||
|
- 功能操作类:`stir`、`heat_chill_start`、`heat_chill_stop`
|
||||||
|
- 开关控制类:`valve_open_cmd`、`valve_close_cmd`、`push_to`
|
||||||
|
- 命令执行类:`send_nav_task`、`execute_command_from_outer`
|
||||||
|
|
||||||
|
### 常用的动作类型
|
||||||
|
|
||||||
|
- `UniLabJsonCommand`:自定义 JSON 命令(不走 ROS)
|
||||||
|
- `UniLabJsonCommandAsync`:异步 JSON 命令(不走 ROS)
|
||||||
|
- `SendCmd`:发送简单命令
|
||||||
|
- `NavigateThroughPoses`:导航相关
|
||||||
|
- `SingleJointPosition`:单关节控制
|
||||||
|
- `Stir`:搅拌
|
||||||
|
- `HeatChill`、`HeatChillStart`:加热冷却
|
||||||
|
- 其他的 ROS 动作类型:看具体的 ROS 服务
|
||||||
|
|
||||||
|
### 示例:完整的动作配置
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
heat_chill_start:
|
||||||
|
type: HeatChillStart
|
||||||
|
goal:
|
||||||
|
purpose: purpose
|
||||||
|
temp: temp
|
||||||
|
goal_default:
|
||||||
|
purpose: ''
|
||||||
|
temp: 0.0
|
||||||
|
handles:
|
||||||
|
output:
|
||||||
|
- handler_key: labware
|
||||||
|
label: Labware
|
||||||
|
data_type: resource
|
||||||
|
data_source: handle
|
||||||
|
data_key: liquid
|
||||||
|
placeholder_keys:
|
||||||
|
purpose: unilabos_resources
|
||||||
|
result:
|
||||||
|
status: status
|
||||||
|
success: success
|
||||||
|
schema:
|
||||||
|
description: '启动加热冷却功能'
|
||||||
|
properties:
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
purpose:
|
||||||
|
type: string
|
||||||
|
description: '用途说明'
|
||||||
|
temp:
|
||||||
|
type: number
|
||||||
|
description: '目标温度'
|
||||||
|
required:
|
||||||
|
- purpose
|
||||||
|
- temp
|
||||||
|
title: HeatChillStart_Goal
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: HeatChillStart
|
||||||
|
type: object
|
||||||
|
feedback: {}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 系统自动生成的字段
|
||||||
|
|
||||||
|
### status_types
|
||||||
|
|
||||||
|
系统会扫描你的 Python 类,从状态方法自动生成这部分:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
status_types:
|
||||||
|
current_temperature: float # 从 get_current_temperature() 方法来的
|
||||||
|
is_heating: bool # 从 get_is_heating() 方法来的
|
||||||
|
status: str # 从 get_status() 方法来的
|
||||||
|
```
|
||||||
|
|
||||||
|
注意几点:
|
||||||
|
|
||||||
|
- 系统会找所有 `get_` 开头的方法
|
||||||
|
- 类型会自动转成 ROS 类型(比如 `str` 变成 `String`)
|
||||||
|
- 如果类型是 `Any`、`None` 或者不知道的,就默认用 `String`
|
||||||
|
|
||||||
|
### init_param_schema
|
||||||
|
|
||||||
|
这个完全是系统自动生成的,你不用管:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
init_param_schema:
|
||||||
|
config: # 从你类的 __init__ 方法分析出来的
|
||||||
|
properties:
|
||||||
|
port:
|
||||||
|
type: string
|
||||||
|
default: '/dev/ttyUSB0'
|
||||||
|
baudrate:
|
||||||
|
type: integer
|
||||||
|
default: 9600
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
|
||||||
|
data: # 根据 status_types 生成的前端用的类型
|
||||||
|
properties:
|
||||||
|
current_temperature:
|
||||||
|
type: number
|
||||||
|
is_heating:
|
||||||
|
type: boolean
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- status
|
||||||
|
type: object
|
||||||
|
```
|
||||||
|
|
||||||
|
生成规则很简单:
|
||||||
|
|
||||||
|
- `config` 部分:看你类的 `__init__` 方法有什么参数,类型和默认值是啥
|
||||||
|
- `data` 部分:根据 `status_types` 生成前端显示用的类型定义
|
||||||
|
|
||||||
|
### 其他自动填充的字段
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: '1.0.0' # 默认版本
|
||||||
|
category: ['文件名'] # 用你的 yaml 文件名当类别
|
||||||
|
description: '' # 默认为空,你可以手动改
|
||||||
|
icon: '' # 默认为空,你可以加图标
|
||||||
|
handles: [] # 默认空数组
|
||||||
|
config_info: [] # 默认空数组
|
||||||
|
file_path: '/path/to/file' # 系统自动填文件路径
|
||||||
|
registry_type: 'device' # 自动设为设备类型
|
||||||
|
```
|
||||||
|
|
||||||
|
### handles 字段
|
||||||
|
|
||||||
|
这个是定义设备连接关系的,类似动作里的 handles 一样:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
handles: # 大多数时候都是空的,除非设备本身需要连接啥
|
||||||
|
- handler_key: device_output
|
||||||
|
label: Device Output
|
||||||
|
data_type: resource
|
||||||
|
data_source: value
|
||||||
|
data_key: default_value
|
||||||
|
```
|
||||||
|
|
||||||
|
### 其他可以配置的字段
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
description: '设备的详细描述' # 写清楚设备是干啥的
|
||||||
|
|
||||||
|
icon: 'device_icon.webp' # 设备图标,文件名(会上传到OSS)
|
||||||
|
|
||||||
|
version: '0.0.1' # 版本号
|
||||||
|
|
||||||
|
category: # 设备分类,前端会用这个分组
|
||||||
|
- 'heating'
|
||||||
|
- 'cooling'
|
||||||
|
- 'temperature_control'
|
||||||
|
|
||||||
|
config_info: # 嵌套配置,如果设备包含子设备
|
||||||
|
- children:
|
||||||
|
- opentrons_24_tuberack_nest_1point5ml_snapcap_A1
|
||||||
|
- other_nested_component
|
||||||
|
```
|
||||||
|
|
||||||
|
## 完整的例子
|
||||||
|
|
||||||
|
这里是一个比较完整的设备配置示例:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
my_temperature_controller:
|
||||||
|
class:
|
||||||
|
action_value_mappings:
|
||||||
|
heat_start:
|
||||||
|
type: HeatChillStart
|
||||||
|
goal:
|
||||||
|
target_temp: temp
|
||||||
|
vessel: vessel
|
||||||
|
goal_default:
|
||||||
|
target_temp: 25.0
|
||||||
|
vessel: ''
|
||||||
|
handles:
|
||||||
|
output:
|
||||||
|
- handler_key: heated_sample
|
||||||
|
label: Heated Sample
|
||||||
|
data_type: resource
|
||||||
|
data_source: handle
|
||||||
|
data_key: sample
|
||||||
|
placeholder_keys:
|
||||||
|
vessel: unilabos_resources
|
||||||
|
result:
|
||||||
|
status: status
|
||||||
|
success: success
|
||||||
|
schema:
|
||||||
|
description: '启动加热功能'
|
||||||
|
properties:
|
||||||
|
goal:
|
||||||
|
properties:
|
||||||
|
target_temp:
|
||||||
|
type: number
|
||||||
|
description: '目标温度'
|
||||||
|
vessel:
|
||||||
|
type: string
|
||||||
|
description: '容器标识'
|
||||||
|
required:
|
||||||
|
- target_temp
|
||||||
|
- vessel
|
||||||
|
title: HeatStart_Goal
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- goal
|
||||||
|
title: HeatStart
|
||||||
|
type: object
|
||||||
|
feedback: {}
|
||||||
|
|
||||||
|
stop:
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
goal: {}
|
||||||
|
goal_default: {}
|
||||||
|
handles: {}
|
||||||
|
result:
|
||||||
|
status: status
|
||||||
|
schema:
|
||||||
|
description: '停止设备'
|
||||||
|
properties:
|
||||||
|
goal:
|
||||||
|
type: object
|
||||||
|
title: Stop_Goal
|
||||||
|
title: Stop
|
||||||
|
type: object
|
||||||
|
feedback: {}
|
||||||
|
|
||||||
|
module: unilabos.devices.temperature.my_controller:MyTemperatureController
|
||||||
|
status_types:
|
||||||
|
current_temperature: float
|
||||||
|
target_temperature: float
|
||||||
|
is_heating: bool
|
||||||
|
is_cooling: bool
|
||||||
|
status: str
|
||||||
|
vessel: str
|
||||||
|
type: python
|
||||||
|
|
||||||
|
description: '我的温度控制器设备'
|
||||||
|
handles: []
|
||||||
|
icon: 'temperature_controller.webp'
|
||||||
|
init_param_schema:
|
||||||
|
config:
|
||||||
|
properties:
|
||||||
|
port:
|
||||||
|
default: '/dev/ttyUSB0'
|
||||||
|
type: string
|
||||||
|
baudrate:
|
||||||
|
default: 9600
|
||||||
|
type: number
|
||||||
|
required: []
|
||||||
|
type: object
|
||||||
|
data:
|
||||||
|
properties:
|
||||||
|
current_temperature:
|
||||||
|
type: number
|
||||||
|
target_temperature:
|
||||||
|
type: number
|
||||||
|
is_heating:
|
||||||
|
type: boolean
|
||||||
|
is_cooling:
|
||||||
|
type: boolean
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
vessel:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- current_temperature
|
||||||
|
- target_temperature
|
||||||
|
- status
|
||||||
|
type: object
|
||||||
|
|
||||||
|
version: '1.0.0'
|
||||||
|
category:
|
||||||
|
- 'temperature_control'
|
||||||
|
- 'heating'
|
||||||
|
config_info: []
|
||||||
|
```
|
||||||
|
|
||||||
|
## 怎么部署和使用
|
||||||
|
|
||||||
|
### 方法一:用编辑器(推荐)
|
||||||
|
|
||||||
|
1. 先写好你的 Python 驱动类
|
||||||
|
2. 用注册表编辑器自动生成 yaml 配置
|
||||||
|
3. 把生成的文件保存到 `devices/` 目录
|
||||||
|
4. 重启 UniLabOS 就能用了
|
||||||
|
|
||||||
|
### 方法二:手动写(简化版)
|
||||||
|
|
||||||
|
1. 创建最简配置:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# devices/my_device.yaml
|
||||||
|
my_device:
|
||||||
|
class:
|
||||||
|
module: unilabos.devices.my_module.my_device:MyDevice
|
||||||
|
type: python
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 启动系统时用 `complete_registry=True` 参数,让系统自动补全
|
||||||
|
|
||||||
|
3. 检查一下生成的配置是不是你想要的
|
||||||
|
|
||||||
|
### Python 驱动类要怎么写
|
||||||
|
|
||||||
|
你的设备类要符合这些要求:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from unilabos.common.device_base import DeviceBase
|
||||||
|
|
||||||
|
class MyDevice(DeviceBase):
|
||||||
|
def __init__(self, config):
|
||||||
|
"""初始化,参数会自动分析到 init_param_schema.config"""
|
||||||
|
super().__init__(config)
|
||||||
|
self.port = config.get('port', '/dev/ttyUSB0')
|
||||||
|
|
||||||
|
# 状态方法(会自动生成到 status_types)
|
||||||
|
def get_status(self):
|
||||||
|
"""返回设备状态"""
|
||||||
|
return "idle"
|
||||||
|
|
||||||
|
def get_temperature(self):
|
||||||
|
"""返回当前温度"""
|
||||||
|
return 25.0
|
||||||
|
|
||||||
|
# 动作方法(会自动生成 auto- 开头的动作)
|
||||||
|
async def start_heating(self, temperature: float):
|
||||||
|
"""开始加热到指定温度"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
"""停止操作"""
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
### 系统集成
|
||||||
|
|
||||||
|
1. 把 yaml 文件放到 `devices/` 目录下
|
||||||
|
2. 系统启动时会自动扫描并加载设备
|
||||||
|
3. 系统会自动补全所有缺失的字段
|
||||||
|
4. 设备马上就能在前端界面中使用
|
||||||
|
|
||||||
|
### 高级配置
|
||||||
|
|
||||||
|
如果需要特殊设置,可以手动加:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
my_device:
|
||||||
|
class:
|
||||||
|
module: unilabos.devices.my_module.my_device:MyDevice
|
||||||
|
type: python
|
||||||
|
action_value_mappings:
|
||||||
|
# 自定义动作
|
||||||
|
special_command:
|
||||||
|
type: UniLabJsonCommand
|
||||||
|
goal: {}
|
||||||
|
result: {}
|
||||||
|
|
||||||
|
# 可选的自定义配置
|
||||||
|
description: '我的特殊设备'
|
||||||
|
icon: 'my_device.webp'
|
||||||
|
category: ['temperature', 'heating']
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常见问题怎么排查
|
||||||
|
|
||||||
|
### 设备加载不了
|
||||||
|
|
||||||
|
1. 检查模块路径:确认 `class.module` 路径写对了
|
||||||
|
2. 确认类能导入:看看你的 Python 驱动类能不能正常导入
|
||||||
|
3. 检查语法:用 yaml 验证器看看文件格式对不对
|
||||||
|
4. 查看日志:看 UniLabOS 启动时有没有报错信息
|
||||||
|
|
||||||
|
### 自动生成失败了
|
||||||
|
|
||||||
|
1. 类分析出问题:确认你的类继承了正确的基类
|
||||||
|
2. 方法类型不明确:确保状态方法的返回类型写清楚了
|
||||||
|
3. 导入有问题:检查类能不能被动态导入
|
||||||
|
4. 没开完整注册:确认启用了 `complete_registry=True`
|
||||||
|
|
||||||
|
### 前端显示有问题
|
||||||
|
|
||||||
|
1. 重新生成:删掉旧的 yaml 文件,用编辑器重新生成
|
||||||
|
2. 清除缓存:清除浏览器缓存,重新加载页面
|
||||||
|
3. 检查字段:确认必需的字段(比如 `schema`)都有
|
||||||
|
4. 验证数据:检查 `goal_default` 和 `schema` 的数据类型是不是一致
|
||||||
|
|
||||||
|
### 动作执行出错
|
||||||
|
|
||||||
|
1. 方法名不对:确认动作方法名符合规范(比如 `execute_<action_name>`)
|
||||||
|
2. 参数映射错误:检查 `goal` 字段的参数映射是否正确
|
||||||
|
3. 返回格式不对:确认方法返回值格式符合 `result` 映射
|
||||||
|
4. 没异常处理:在驱动类里加上异常处理
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
### 开发流程
|
||||||
|
|
||||||
|
1. **优先使用编辑器**:除非有特殊需求,否则优先使用注册表编辑器
|
||||||
|
2. **最小化配置**:手动配置时只定义必要字段,让系统自动生成其他内容
|
||||||
|
3. **增量开发**:先创建基本配置,后续根据需要添加特殊动作
|
||||||
|
|
||||||
|
### 代码规范
|
||||||
|
|
||||||
|
1. **方法命名**:状态方法使用 `get_` 前缀,动作方法使用动词开头
|
||||||
|
2. **类型注解**:为方法参数和返回值添加类型注解
|
||||||
|
3. **文档字符串**:为类和方法添加详细的文档字符串
|
||||||
|
4. **异常处理**:实现完善的错误处理和日志记录
|
||||||
|
|
||||||
|
### 配置管理
|
||||||
|
|
||||||
|
1. **版本控制**:所有 yaml 文件纳入版本控制
|
||||||
|
2. **命名一致性**:设备 ID、文件名、类名保持一致的命名风格
|
||||||
|
3. **定期更新**:定期运行完整注册以更新自动生成的字段
|
||||||
|
4. **备份配置**:在修改前备份重要的手动配置
|
||||||
|
|
||||||
|
### 测试验证
|
||||||
|
|
||||||
|
1. **本地测试**:在本地环境充分测试后再部署
|
||||||
|
2. **渐进部署**:先部署到测试环境,验证无误后再上生产环境
|
||||||
|
3. **监控日志**:密切监控设备加载和运行日志
|
||||||
|
4. **回滚准备**:准备快速回滚机制,以应对紧急情况
|
||||||
|
|
||||||
|
### 性能优化
|
||||||
|
|
||||||
|
1. **按需加载**:只加载实际使用的设备类型
|
||||||
|
2. **缓存利用**:充分利用系统的注册表缓存机制
|
||||||
|
3. **资源管理**:合理管理设备连接和资源占用
|
||||||
|
4. **监控指标**:设置关键性能指标的监控和告警
|
||||||
|
|||||||
@@ -6,77 +6,68 @@ Uni-Lab支持通过Python配置文件进行灵活的系统配置。本指南将
|
|||||||
|
|
||||||
Uni-Lab 支持 Python 格式的配置文件,它比 YAML 或 JSON 提供更多的灵活性,包括支持注释、条件逻辑和复杂数据结构。
|
Uni-Lab 支持 Python 格式的配置文件,它比 YAML 或 JSON 提供更多的灵活性,包括支持注释、条件逻辑和复杂数据结构。
|
||||||
|
|
||||||
### 基本配置示例
|
### 默认配置示例
|
||||||
|
|
||||||
一个典型的配置文件包含以下部分:
|
首次使用时,系统会自动创建一个基础配置文件 `local_config.py`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# unilabos的配置文件
|
||||||
|
|
||||||
|
class BasicConfig:
|
||||||
|
ak = "" # 实验室网页给您提供的ak代码,您可以在配置文件中指定,也可以通过运行unilabos时以 --ak 传入,优先按照传入参数解析
|
||||||
|
sk = "" # 实验室网页给您提供的sk代码,您可以在配置文件中指定,也可以通过运行unilabos时以 --sk 传入,优先按照传入参数解析
|
||||||
|
|
||||||
|
|
||||||
|
# WebSocket配置,一般无需调整
|
||||||
|
class WSConfig:
|
||||||
|
reconnect_interval = 5 # 重连间隔(秒)
|
||||||
|
max_reconnect_attempts = 999 # 最大重连次数
|
||||||
|
ping_interval = 30 # ping间隔(秒)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 完整配置示例
|
||||||
|
|
||||||
|
您可以根据需要添加更多配置选项:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# coding=utf-8
|
# coding=utf-8
|
||||||
"""Uni-Lab 配置文件"""
|
"""Uni-Lab 配置文件"""
|
||||||
|
|
||||||
from dataclasses import dataclass
|
# 基础配置
|
||||||
|
class BasicConfig:
|
||||||
|
ak = "your_access_key" # 实验室访问密钥
|
||||||
|
sk = "your_secret_key" # 实验室私钥
|
||||||
|
working_dir = "" # 工作目录(通常自动设置)
|
||||||
|
config_path = "" # 配置文件路径(自动设置)
|
||||||
|
is_host_mode = True # 是否为主站模式
|
||||||
|
slave_no_host = False # 从站模式下是否跳过等待主机服务
|
||||||
|
upload_registry = False # 是否上传注册表
|
||||||
|
machine_name = "undefined" # 机器名称(自动获取)
|
||||||
|
vis_2d_enable = False # 是否启用2D可视化
|
||||||
|
enable_resource_load = True # 是否启用资源加载
|
||||||
|
communication_protocol = "websocket" # 通信协议
|
||||||
|
|
||||||
# 配置类定义
|
# WebSocket配置
|
||||||
|
class WSConfig:
|
||||||
|
reconnect_interval = 5 # 重连间隔(秒)
|
||||||
|
max_reconnect_attempts = 999 # 最大重连次数
|
||||||
|
ping_interval = 30 # ping间隔(秒)
|
||||||
|
|
||||||
class MQConfig:
|
# OSS上传配置
|
||||||
"""MQTT 配置类"""
|
class OSSUploadConfig:
|
||||||
lab_id: str = "YOUR_LAB_ID"
|
api_host = "" # API主机地址
|
||||||
# 更多配置...
|
authorization = "" # 授权信息
|
||||||
|
init_endpoint = "" # 初始化端点
|
||||||
|
complete_endpoint = "" # 完成端点
|
||||||
|
max_retries = 3 # 最大重试次数
|
||||||
|
|
||||||
# 其他配置类...
|
# HTTP配置
|
||||||
```
|
class HTTPConfig:
|
||||||
|
remote_addr = "http://127.0.0.1:48197/api/v1" # 远程地址
|
||||||
## 配置选项说明
|
|
||||||
|
|
||||||
### MQTT配置 (MQConfig)
|
|
||||||
|
|
||||||
MQTT配置用于连接消息队列服务,是Uni-Lab与云端通信的主要方式。
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
class MQConfig:
|
|
||||||
"""MQTT 配置类"""
|
|
||||||
lab_id: str = "7AAEDBEA" # 实验室唯一标识
|
|
||||||
instance_id: str = "mqtt-cn-instance"
|
|
||||||
access_key: str = "your-access-key"
|
|
||||||
secret_key: str = "your-secret-key"
|
|
||||||
group_id: str = "GID_labs"
|
|
||||||
broker_url: str = "mqtt-cn-instance.mqtt.aliyuncs.com"
|
|
||||||
port: int = 8883
|
|
||||||
|
|
||||||
# 可以直接提供证书文件路径
|
|
||||||
ca_file: str = "/path/to/ca.pem" # 相对config.py所在目录的路径
|
|
||||||
cert_file: str = "/path/to/cert.pem" # 相对config.py所在目录的路径
|
|
||||||
key_file: str = "/path/to/key.pem" # 相对config.py所在目录的路径
|
|
||||||
|
|
||||||
# 或者直接提供证书内容
|
|
||||||
ca_content: str = ""
|
|
||||||
cert_content: str = ""
|
|
||||||
key_content: str = ""
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 证书配置
|
|
||||||
|
|
||||||
MQTT连接支持两种方式配置证书:
|
|
||||||
|
|
||||||
1. **文件路径方式**(推荐):指定证书文件的路径,系统会自动读取文件内容
|
|
||||||
2. **直接内容方式**:直接在配置中提供证书内容
|
|
||||||
|
|
||||||
推荐使用文件路径方式,便于证书的更新和管理。
|
|
||||||
|
|
||||||
### HTTP客户端配置 (HTTPConfig)
|
|
||||||
|
|
||||||
即将开放 Uni-Lab 云端实验室。
|
|
||||||
|
|
||||||
### ROS模块配置 (ROSConfig)
|
|
||||||
|
|
||||||
配置ROS消息转换器需要加载的模块:
|
|
||||||
|
|
||||||
```python
|
|
||||||
|
|
||||||
|
# ROS配置
|
||||||
class ROSConfig:
|
class ROSConfig:
|
||||||
"""ROS模块配置"""
|
|
||||||
modules = [
|
modules = [
|
||||||
"std_msgs.msg",
|
"std_msgs.msg",
|
||||||
"geometry_msgs.msg",
|
"geometry_msgs.msg",
|
||||||
@@ -85,25 +76,365 @@ class ROSConfig:
|
|||||||
"nav2_msgs.action",
|
"nav2_msgs.action",
|
||||||
"unilabos_msgs.msg",
|
"unilabos_msgs.msg",
|
||||||
"unilabos_msgs.action",
|
"unilabos_msgs.action",
|
||||||
|
] # 需要加载的ROS模块
|
||||||
|
```
|
||||||
|
|
||||||
|
## 命令行参数覆盖配置
|
||||||
|
|
||||||
|
Uni-Lab 允许通过命令行参数覆盖配置文件中的设置,提供更灵活的配置方式。命令行参数的优先级高于配置文件。
|
||||||
|
|
||||||
|
### 支持命令行覆盖的配置项
|
||||||
|
|
||||||
|
以下配置项可以通过命令行参数进行覆盖:
|
||||||
|
|
||||||
|
| 配置类 | 配置字段 | 命令行参数 | 说明 |
|
||||||
|
| ------------- | ----------------- | ------------------- | -------------------------------- |
|
||||||
|
| `BasicConfig` | `ak` | `--ak` | 实验室访问密钥 |
|
||||||
|
| `BasicConfig` | `sk` | `--sk` | 实验室私钥 |
|
||||||
|
| `BasicConfig` | `working_dir` | `--working_dir` | 工作目录路径 |
|
||||||
|
| `BasicConfig` | `is_host_mode` | `--is_slave` | 主站模式(参数为从站模式,取反) |
|
||||||
|
| `BasicConfig` | `slave_no_host` | `--slave_no_host` | 从站模式下跳过等待主机服务 |
|
||||||
|
| `BasicConfig` | `upload_registry` | `--upload_registry` | 启动时上传注册表信息 |
|
||||||
|
| `BasicConfig` | `vis_2d_enable` | `--2d_vis` | 启用 2D 可视化 |
|
||||||
|
| `HTTPConfig` | `remote_addr` | `--addr` | 远程服务地址 |
|
||||||
|
|
||||||
|
### 特殊命令行参数
|
||||||
|
|
||||||
|
除了直接覆盖配置项的参数外,还有一些特殊的命令行参数:
|
||||||
|
|
||||||
|
| 参数 | 说明 |
|
||||||
|
| ------------------- | ------------------------------------ |
|
||||||
|
| `--config` | 指定配置文件路径 |
|
||||||
|
| `--port` | Web 服务端口(不影响配置文件) |
|
||||||
|
| `--disable_browser` | 禁用自动打开浏览器(不影响配置文件) |
|
||||||
|
| `--visual` | 可视化工具选择(不影响配置文件) |
|
||||||
|
| `--skip_env_check` | 跳过环境检查(不影响配置文件) |
|
||||||
|
|
||||||
|
### 配置优先级
|
||||||
|
|
||||||
|
配置项的生效优先级从高到低为:
|
||||||
|
|
||||||
|
1. **命令行参数**:最高优先级
|
||||||
|
2. **环境变量**:中等优先级
|
||||||
|
3. **配置文件**:基础优先级
|
||||||
|
|
||||||
|
### 使用示例
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 通过命令行覆盖认证信息
|
||||||
|
unilab --ak "new_access_key" --sk "new_secret_key"
|
||||||
|
|
||||||
|
# 覆盖服务器地址
|
||||||
|
unilab --addr "https://custom.server.com/api/v1"
|
||||||
|
|
||||||
|
# 启用从站模式并跳过等待主机
|
||||||
|
unilab --is_slave --slave_no_host
|
||||||
|
|
||||||
|
# 启用上传注册表和2D可视化
|
||||||
|
unilab --upload_registry --2d_vis
|
||||||
|
|
||||||
|
# 组合使用多个覆盖参数
|
||||||
|
unilab --ak "key" --sk "secret" --addr "test" --upload_registry --2d_vis
|
||||||
|
```
|
||||||
|
|
||||||
|
### 预设环境地址
|
||||||
|
|
||||||
|
`--addr` 参数支持以下预设值,会自动转换为对应的完整 URL:
|
||||||
|
|
||||||
|
- `test` → `https://uni-lab.test.bohrium.com/api/v1`
|
||||||
|
- `uat` → `https://uni-lab.uat.bohrium.com/api/v1`
|
||||||
|
- `local` → `http://127.0.0.1:48197/api/v1`
|
||||||
|
- 其他值 → 直接使用作为完整 URL
|
||||||
|
|
||||||
|
## 配置选项详解
|
||||||
|
|
||||||
|
### 基础配置 (BasicConfig)
|
||||||
|
|
||||||
|
基础配置包含了系统运行的核心参数:
|
||||||
|
|
||||||
|
| 参数 | 类型 | 默认值 | 说明 |
|
||||||
|
| ------------------------ | ---- | ------------- | ------------------------------------------ |
|
||||||
|
| `ak` | str | `""` | 实验室访问密钥(必需) |
|
||||||
|
| `sk` | str | `""` | 实验室私钥(必需) |
|
||||||
|
| `working_dir` | str | `""` | 工作目录,通常自动设置 |
|
||||||
|
| `is_host_mode` | bool | `True` | 是否为主站模式 |
|
||||||
|
| `slave_no_host` | bool | `False` | 从站模式下是否跳过等待主机服务 |
|
||||||
|
| `upload_registry` | bool | `False` | 启动时是否上传注册表信息 |
|
||||||
|
| `machine_name` | str | `"undefined"` | 机器名称,自动从 hostname 获取(不可配置) |
|
||||||
|
| `vis_2d_enable` | bool | `False` | 是否启用 2D 可视化 |
|
||||||
|
| `communication_protocol` | str | `"websocket"` | 通信协议,固定为 websocket |
|
||||||
|
|
||||||
|
#### 认证配置
|
||||||
|
|
||||||
|
`ak` 和 `sk` 是必需的认证参数:
|
||||||
|
|
||||||
|
1. **获取方式**:在 [Uni-Lab 官网](https://uni-lab.bohrium.com) 注册实验室后获得
|
||||||
|
2. **配置方式**:
|
||||||
|
- **命令行参数**:`--ak "your_key" --sk "your_secret"`(最高优先级)
|
||||||
|
- **配置文件**:在 `BasicConfig` 类中设置
|
||||||
|
- **环境变量**:`UNILABOS_BASICCONFIG_AK` 和 `UNILABOS_BASICCONFIG_SK`
|
||||||
|
3. **优先级顺序**:命令行参数 > 环境变量 > 配置文件
|
||||||
|
4. **安全注意**:请妥善保管您的密钥信息
|
||||||
|
|
||||||
|
**推荐做法**:
|
||||||
|
|
||||||
|
- 开发环境:使用配置文件
|
||||||
|
- 生产环境:使用环境变量或命令行参数
|
||||||
|
- 临时测试:使用命令行参数
|
||||||
|
|
||||||
|
### WebSocket 配置 (WSConfig)
|
||||||
|
|
||||||
|
WebSocket 是 Uni-Lab 的主要通信方式:
|
||||||
|
|
||||||
|
| 参数 | 类型 | 默认值 | 说明 |
|
||||||
|
| ------------------------ | ---- | ------ | ------------------ |
|
||||||
|
| `reconnect_interval` | int | `5` | 断线重连间隔(秒) |
|
||||||
|
| `max_reconnect_attempts` | int | `999` | 最大重连次数 |
|
||||||
|
| `ping_interval` | int | `30` | 心跳检测间隔(秒) |
|
||||||
|
|
||||||
|
### HTTP 配置 (HTTPConfig)
|
||||||
|
|
||||||
|
HTTP 客户端配置用于与云端服务通信:
|
||||||
|
|
||||||
|
| 参数 | 类型 | 默认值 | 说明 |
|
||||||
|
| ------------- | ---- | --------------------------------- | ------------ |
|
||||||
|
| `remote_addr` | str | `"http://127.0.0.1:48197/api/v1"` | 远程服务地址 |
|
||||||
|
|
||||||
|
**预设环境地址**:
|
||||||
|
|
||||||
|
- 生产环境:`https://uni-lab.bohrium.com/api/v1`
|
||||||
|
- 测试环境:`https://uni-lab.test.bohrium.com/api/v1`
|
||||||
|
- UAT 环境:`https://uni-lab.uat.bohrium.com/api/v1`
|
||||||
|
- 本地环境:`http://127.0.0.1:48197/api/v1`
|
||||||
|
|
||||||
|
### ROS 配置 (ROSConfig)
|
||||||
|
|
||||||
|
配置 ROS 消息转换器需要加载的模块:
|
||||||
|
|
||||||
|
```python
|
||||||
|
class ROSConfig:
|
||||||
|
modules = [
|
||||||
|
"std_msgs.msg", # 标准消息类型
|
||||||
|
"geometry_msgs.msg", # 几何消息类型
|
||||||
|
"control_msgs.msg", # 控制消息类型
|
||||||
|
"control_msgs.action", # 控制动作类型
|
||||||
|
"nav2_msgs.action", # 导航动作类型
|
||||||
|
"unilabos_msgs.msg", # UniLab 自定义消息类型
|
||||||
|
"unilabos_msgs.action", # UniLab 自定义动作类型
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
您可以根据需要添加其他ROS模块。
|
您可以根据实际使用的设备和功能添加其他 ROS 模块。
|
||||||
|
|
||||||
### 其他配置选项
|
### OSS 上传配置 (OSSUploadConfig)
|
||||||
|
|
||||||
- **OSSUploadConfig**: 对象存储上传配置
|
对象存储服务配置,用于文件上传功能:
|
||||||
|
|
||||||
## 如何使用配置文件
|
| 参数 | 类型 | 默认值 | 说明 |
|
||||||
|
| ------------------- | ---- | ------ | -------------------- |
|
||||||
|
| `api_host` | str | `""` | OSS API 主机地址 |
|
||||||
|
| `authorization` | str | `""` | 授权认证信息 |
|
||||||
|
| `init_endpoint` | str | `""` | 上传初始化端点 |
|
||||||
|
| `complete_endpoint` | str | `""` | 上传完成端点 |
|
||||||
|
| `max_retries` | int | `3` | 上传失败最大重试次数 |
|
||||||
|
|
||||||
启动Uni-Lab时通过`--config`参数指定配置文件路径:
|
## 环境变量支持
|
||||||
|
|
||||||
|
Uni-Lab 支持通过环境变量覆盖配置文件中的设置。环境变量格式为:
|
||||||
|
|
||||||
|
```
|
||||||
|
UNILABOS_{配置类名}_{字段名}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 环境变量示例
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
unilab --config path/to/your/config.py
|
# 设置基础配置
|
||||||
|
export UNILABOS_BASICCONFIG_AK="your_access_key"
|
||||||
|
export UNILABOS_BASICCONFIG_SK="your_secret_key"
|
||||||
|
export UNILABOS_BASICCONFIG_IS_HOST_MODE="true"
|
||||||
|
|
||||||
|
# 设置WebSocket配置
|
||||||
|
export UNILABOS_WSCONFIG_RECONNECT_INTERVAL="10"
|
||||||
|
export UNILABOS_WSCONFIG_MAX_RECONNECT_ATTEMPTS="500"
|
||||||
|
|
||||||
|
# 设置HTTP配置
|
||||||
|
export UNILABOS_HTTPCONFIG_REMOTE_ADDR="https://uni-lab.bohrium.com/api/v1"
|
||||||
```
|
```
|
||||||
|
|
||||||
如果您不涉及多环境开发,可以在unilabos的安装路径中手动添加local_config.py的文件
|
### 环境变量类型转换
|
||||||
|
|
||||||
# 启动Uni-Lab
|
- **布尔值**:`"true"`, `"1"`, `"yes"` → `True`;其他 → `False`
|
||||||
python -m unilabos.app.main --config path/to/your/config.py
|
- **整数**:自动转换为 `int` 类型
|
||||||
|
- **浮点数**:自动转换为 `float` 类型
|
||||||
|
- **字符串**:保持原值
|
||||||
|
|
||||||
|
## 配置文件使用方法
|
||||||
|
|
||||||
|
### 1. 指定配置文件启动
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 使用指定配置文件启动
|
||||||
|
unilab --config /path/to/your/config.py
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 2. 使用默认配置文件
|
||||||
|
|
||||||
|
如果不指定配置文件,系统会按以下顺序查找:
|
||||||
|
|
||||||
|
1. 环境变量 `UNILABOS_BASICCONFIG_CONFIG_PATH` 指定的路径
|
||||||
|
2. 工作目录下的 `local_config.py`
|
||||||
|
3. 首次使用时会引导创建配置文件
|
||||||
|
|
||||||
|
### 3. 配置文件验证
|
||||||
|
|
||||||
|
系统启动时会自动验证配置文件:
|
||||||
|
|
||||||
|
- **语法检查**:确保 Python 语法正确
|
||||||
|
- **类型检查**:验证配置项类型是否匹配
|
||||||
|
- **必需项检查**:确保 `ak` 和 `sk` 已配置
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
### 1. 安全配置
|
||||||
|
|
||||||
|
- 不要将包含密钥的配置文件提交到版本控制系统
|
||||||
|
- 使用环境变量或命令行参数在生产环境中配置敏感信息
|
||||||
|
- 定期更换访问密钥
|
||||||
|
- **推荐配置方式**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 生产环境 - 使用环境变量
|
||||||
|
export UNILABOS_BASICCONFIG_AK="your_access_key"
|
||||||
|
export UNILABOS_BASICCONFIG_SK="your_secret_key"
|
||||||
|
unilab
|
||||||
|
|
||||||
|
# 或使用命令行参数
|
||||||
|
unilab --ak "your_access_key" --sk "your_secret_key"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 多环境配置
|
||||||
|
|
||||||
|
为不同环境创建不同的配置文件并结合命令行参数:
|
||||||
|
|
||||||
|
```
|
||||||
|
configs/
|
||||||
|
├── local_config.py # 本地开发
|
||||||
|
├── test_config.py # 测试环境
|
||||||
|
├── prod_config.py # 生产环境
|
||||||
|
└── example_config.py # 示例配置
|
||||||
|
```
|
||||||
|
|
||||||
|
**环境切换示例**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 本地开发环境
|
||||||
|
unilab --config configs/local_config.py --addr local
|
||||||
|
|
||||||
|
# 测试环境
|
||||||
|
unilab --config configs/test_config.py --addr test --upload_registry
|
||||||
|
|
||||||
|
# 生产环境
|
||||||
|
unilab --config configs/prod_config.py --ak "$PROD_AK" --sk "$PROD_SK"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 配置管理
|
||||||
|
|
||||||
|
- 保持配置文件简洁,只包含需要修改的配置项
|
||||||
|
- 为配置项添加注释说明其作用
|
||||||
|
- 定期检查和更新配置文件
|
||||||
|
- **命令行参数优先使用场景**:
|
||||||
|
- 临时测试不同配置
|
||||||
|
- CI/CD 流水线中的动态配置
|
||||||
|
- 不同环境间快速切换
|
||||||
|
- 敏感信息的安全传递
|
||||||
|
|
||||||
|
### 4. 灵活配置策略
|
||||||
|
|
||||||
|
**基础配置文件 + 命令行覆盖**的推荐方式:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# base_config.py - 基础配置
|
||||||
|
class BasicConfig:
|
||||||
|
# 非敏感配置写在文件中
|
||||||
|
is_host_mode = True
|
||||||
|
upload_registry = False
|
||||||
|
vis_2d_enable = False
|
||||||
|
|
||||||
|
class WSConfig:
|
||||||
|
reconnect_interval = 5
|
||||||
|
max_reconnect_attempts = 999
|
||||||
|
ping_interval = 30
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 启动时通过命令行覆盖关键参数
|
||||||
|
unilab --config base_config.py \
|
||||||
|
--ak "$AK" \
|
||||||
|
--sk "$SK" \
|
||||||
|
--addr "test" \
|
||||||
|
--upload_registry \
|
||||||
|
--2d_vis
|
||||||
|
```
|
||||||
|
|
||||||
|
## 故障排除
|
||||||
|
|
||||||
|
### 1. 配置文件加载失败
|
||||||
|
|
||||||
|
**错误信息**:`[ENV] 配置文件 xxx 不存在`
|
||||||
|
|
||||||
|
**解决方法**:
|
||||||
|
|
||||||
|
- 确认配置文件路径正确
|
||||||
|
- 检查文件权限是否可读
|
||||||
|
- 确保配置文件是 `.py` 格式
|
||||||
|
|
||||||
|
### 2. 语法错误
|
||||||
|
|
||||||
|
**错误信息**:`[ENV] 加载配置文件 xxx 失败`
|
||||||
|
|
||||||
|
**解决方法**:
|
||||||
|
|
||||||
|
- 检查 Python 语法是否正确
|
||||||
|
- 确认类名和字段名拼写正确
|
||||||
|
- 验证缩进是否正确(使用空格而非制表符)
|
||||||
|
|
||||||
|
### 3. 认证失败
|
||||||
|
|
||||||
|
**错误信息**:`后续运行必须拥有一个实验室`
|
||||||
|
|
||||||
|
**解决方法**:
|
||||||
|
|
||||||
|
- 确认 `ak` 和 `sk` 已正确配置
|
||||||
|
- 检查密钥是否有效
|
||||||
|
- 确认网络连接正常
|
||||||
|
|
||||||
|
### 4. 环境变量不生效
|
||||||
|
|
||||||
|
**解决方法**:
|
||||||
|
|
||||||
|
- 确认环境变量名格式正确(`UNILABOS_CLASS_FIELD`)
|
||||||
|
- 检查环境变量是否已正确设置
|
||||||
|
- 重启系统或重新加载环境变量
|
||||||
|
|
||||||
|
### 5. 命令行参数不生效
|
||||||
|
|
||||||
|
**错误现象**:设置了命令行参数但配置没有生效
|
||||||
|
|
||||||
|
**解决方法**:
|
||||||
|
|
||||||
|
- 确认参数名拼写正确(如 `--ak` 而不是 `--access_key`)
|
||||||
|
- 检查参数格式是否正确(布尔参数如 `--is_slave` 不需要值)
|
||||||
|
- 确认参数位置正确(所有参数都应在 `unilab` 之后)
|
||||||
|
- 查看启动日志确认参数是否被正确解析
|
||||||
|
|
||||||
|
### 6. 配置优先级混淆
|
||||||
|
|
||||||
|
**错误现象**:不确定哪个配置生效
|
||||||
|
|
||||||
|
**解决方法**:
|
||||||
|
|
||||||
|
- 记住优先级:命令行参数 > 环境变量 > 配置文件
|
||||||
|
- 使用 `--ak` 和 `--sk` 参数时会看到提示信息
|
||||||
|
- 检查启动日志中的配置加载信息
|
||||||
|
- 临时移除低优先级配置来测试高优先级配置是否生效
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Uni-Lab 启动
|
# Uni-Lab 启动指南
|
||||||
|
|
||||||
安装完毕后,可以通过 `unilab` 命令行启动:
|
安装完毕后,可以通过 `unilab` 命令行启动:
|
||||||
|
|
||||||
@@ -8,23 +8,107 @@ Start Uni-Lab Edge server.
|
|||||||
options:
|
options:
|
||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
-g GRAPH, --graph GRAPH
|
-g GRAPH, --graph GRAPH
|
||||||
Physical setup graph.
|
Physical setup graph file path.
|
||||||
-d DEVICES, --devices DEVICES
|
|
||||||
Devices config file.
|
|
||||||
-r RESOURCES, --resources RESOURCES
|
|
||||||
Resources config file.
|
|
||||||
-c CONTROLLERS, --controllers CONTROLLERS
|
-c CONTROLLERS, --controllers CONTROLLERS
|
||||||
Controllers config file.
|
Controllers config file path.
|
||||||
--registry_path REGISTRY_PATH
|
--registry_path REGISTRY_PATH
|
||||||
Path to the registry
|
Path to the registry directory
|
||||||
|
--working_dir WORKING_DIR
|
||||||
|
Path to the working directory
|
||||||
--backend {ros,simple,automancer}
|
--backend {ros,simple,automancer}
|
||||||
Choose the backend to run with: 'ros', 'simple', or 'automancer'.
|
Choose the backend to run with: 'ros', 'simple', or 'automancer'.
|
||||||
--app_bridges APP_BRIDGES [APP_BRIDGES ...]
|
--app_bridges APP_BRIDGES [APP_BRIDGES ...]
|
||||||
Bridges to connect to. Now support 'mqtt' and 'fastapi'.
|
Bridges to connect to. Now support 'websocket' and 'fastapi'.
|
||||||
--without_host Run the backend as slave (without host).
|
--is_slave Run the backend as slave node (without host privileges).
|
||||||
--config CONFIG Configuration file path for system settings
|
--slave_no_host Skip waiting for host service in slave mode
|
||||||
|
--upload_registry Upload registry information when starting unilab
|
||||||
|
--use_remote_resource Use remote resources when starting unilab
|
||||||
|
--config CONFIG Configuration file path, supports .py format Python config files
|
||||||
|
--port PORT Port for web service information page
|
||||||
|
--disable_browser Disable opening information page on startup
|
||||||
|
--2d_vis Enable 2D visualization when starting pylabrobot instance
|
||||||
|
--visual {rviz,web,disable}
|
||||||
|
Choose visualization tool: rviz, web, or disable
|
||||||
|
--ak AK Access key for laboratory requests
|
||||||
|
--sk SK Secret key for laboratory requests
|
||||||
|
--addr ADDR Laboratory backend address
|
||||||
|
--skip_env_check Skip environment dependency check on startup
|
||||||
|
--complete_registry Complete registry information
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 启动流程详解
|
||||||
|
|
||||||
|
Uni-Lab 的启动过程分为以下几个阶段:
|
||||||
|
|
||||||
|
### 1. 参数解析阶段
|
||||||
|
|
||||||
|
- 解析命令行参数
|
||||||
|
- 处理参数格式转换(支持 dash 和 underscore 格式)
|
||||||
|
|
||||||
|
### 2. 环境检查阶段 (可选)
|
||||||
|
|
||||||
|
- 默认进行环境依赖检查并自动安装必需包
|
||||||
|
- 使用 `--skip_env_check` 可跳过此步骤
|
||||||
|
|
||||||
|
### 3. 配置文件处理阶段
|
||||||
|
|
||||||
|
您可以直接跟随 unilabos 的提示进行,无需查阅本节
|
||||||
|
|
||||||
|
- **工作目录设置**:
|
||||||
|
|
||||||
|
- 如果当前目录以 `unilabos_data` 结尾,则使用当前目录
|
||||||
|
- 否则使用 `当前目录/unilabos_data` 作为工作目录
|
||||||
|
- 可通过 `--working_dir` 指定自定义工作目录
|
||||||
|
|
||||||
|
- **配置文件查找顺序**:
|
||||||
|
1. 使用 `--config` 参数指定的配置文件
|
||||||
|
2. 在工作目录中查找 `local_config.py`
|
||||||
|
3. 首次使用时会引导创建配置文件
|
||||||
|
|
||||||
|
### 4. 服务器地址配置
|
||||||
|
|
||||||
|
支持多种后端环境:
|
||||||
|
|
||||||
|
- `--addr test`:测试环境 (`https://uni-lab.test.bohrium.com/api/v1`)
|
||||||
|
- `--addr uat`:UAT 环境 (`https://uni-lab.uat.bohrium.com/api/v1`)
|
||||||
|
- `--addr local`:本地环境 (`http://127.0.0.1:48197/api/v1`)
|
||||||
|
- 自定义地址:直接指定完整 URL
|
||||||
|
|
||||||
|
### 5. 认证配置
|
||||||
|
|
||||||
|
- **必需参数**:`--ak` 和 `--sk` 必须同时提供
|
||||||
|
- 命令行参数优先于配置文件中的设置
|
||||||
|
- 未提供认证信息会导致启动失败并提示注册实验室
|
||||||
|
|
||||||
|
### 6. 设备图谱加载
|
||||||
|
|
||||||
|
支持两种方式:
|
||||||
|
|
||||||
|
- **本地文件**:使用 `-g` 指定图谱文件(支持 JSON 和 GraphML 格式)
|
||||||
|
- **远程资源**:使用 `--use_remote_resource` 从云端获取
|
||||||
|
|
||||||
|
### 7. 注册表构建
|
||||||
|
|
||||||
|
- 构建设备和资源注册表
|
||||||
|
- 支持自定义注册表路径 (`--registry_path`)
|
||||||
|
- 可选择补全注册表信息 (`--complete_registry`)
|
||||||
|
|
||||||
|
### 8. 设备验证和注册
|
||||||
|
|
||||||
|
- 验证设备连接和端点配置
|
||||||
|
- 自动注册设备到云端服务
|
||||||
|
|
||||||
|
### 9. 通信桥接配置
|
||||||
|
|
||||||
|
- **WebSocket**:实时通信和任务下发
|
||||||
|
- **FastAPI**:HTTP API 服务和物料更新
|
||||||
|
|
||||||
|
### 10. 可视化和服务启动
|
||||||
|
|
||||||
|
- 可选启动可视化工具 (`--visual`)
|
||||||
|
- 启动 Web 信息服务 (默认端口 8002)
|
||||||
|
- 启动后端通信服务
|
||||||
|
|
||||||
## 使用配置文件
|
## 使用配置文件
|
||||||
|
|
||||||
Uni-Lab 支持使用 Python 格式的配置文件进行系统设置。通过 `--config` 参数指定配置文件路径:
|
Uni-Lab 支持使用 Python 格式的配置文件进行系统设置。通过 `--config` 参数指定配置文件路径:
|
||||||
@@ -34,44 +118,130 @@ Uni-Lab支持使用Python格式的配置文件进行系统设置。通过 `--con
|
|||||||
unilab --config path/to/your/config.py
|
unilab --config path/to/your/config.py
|
||||||
```
|
```
|
||||||
|
|
||||||
配置文件包含MQTT、HTTP、ROS等系统设置。有关配置文件的详细信息,请参阅[配置指南](configuration.md)。
|
配置文件包含实验室和 WebSocket 连接等设置。有关配置文件的详细信息,请参阅[配置指南](configuration.md)。
|
||||||
|
|
||||||
## 初始化信息来源
|
## 初始化信息来源
|
||||||
|
|
||||||
启动 Uni-Lab 时,可以选用两种方式之一配置实验室设备、耗材、通信、控制逻辑:
|
启动 Uni-Lab 时,可以选用两种方式之一配置实验室设备:
|
||||||
|
|
||||||
### 1. 组态&拓扑图
|
### 1. 组态&拓扑图
|
||||||
|
|
||||||
使用 `-g` 时,组态&拓扑图应包含实验室所有信息,详见{ref}`graph`。目前支持 graphml 和 node-link json 两种格式。格式可参照 `tests/experiments` 下的启动文件。
|
使用 `-g` 时,组态&拓扑图应包含实验室所有信息,详见{ref}`graph`。目前支持 GraphML 和 node-link JSON 两种格式。格式可参照 `tests/experiments` 下的启动文件。
|
||||||
|
|
||||||
### 2. 分别指定设备、耗材、控制逻辑
|
### 2. 分别指定控制逻辑
|
||||||
|
|
||||||
分别使用 `-d, -r, -c` 依次传入设备组态配置、耗材列表、控制逻辑。
|
使用 `-c` 传入控制逻辑配置。
|
||||||
|
|
||||||
可参照 `devices.json` 和 `resources.json`。
|
不管使用哪一种初始化方式,设备/物料字典均需包含 `class` 属性,用于查找注册表信息。默认查找范围都是 Uni-Lab 内部注册表 `unilabos/registry/{devices,device_comms,resources}`。要添加额外的注册表路径,可以使用 `--registry_path` 加入 `<your-registry-path>/{devices,device_comms,resources}`。
|
||||||
|
|
||||||
不管使用哪一种初始化方式,设备/物料字典均需包含 `class` 属性,用于查找注册表信息。默认查找范围都是 Uni-Lab 内部注册表 `unilabos/registry/{devices,device_comms,resources}`。要添加额外的注册表路径,可以使用 `--registry` 加入 `<your-registry-path>/{devices,device_comms,resources}`。
|
|
||||||
|
|
||||||
## 通信中间件 `--backend`
|
## 通信中间件 `--backend`
|
||||||
|
|
||||||
目前 Uni-Lab 仅支持 ros2 作为通信中间件。
|
目前 Uni-Lab 支持以下通信中间件:
|
||||||
|
|
||||||
|
- **ros** (默认):基于 ROS2 的通信
|
||||||
|
- **simple**:简化通信模式
|
||||||
|
- **automancer**:Automancer 兼容模式
|
||||||
|
|
||||||
## 端云桥接 `--app_bridges`
|
## 端云桥接 `--app_bridges`
|
||||||
|
|
||||||
目前 Uni-Lab 提供 FastAPI (http), MQTT 两种端云通信方式。其中默认 MQTT 负责端对云状态同步和云对端任务下发,FastAPI 负责端对云物料更新。
|
目前 Uni-Lab 提供 WebSocket、FastAPI (http) 两种端云通信方式:
|
||||||
|
|
||||||
|
- **WebSocket**:负责实时通信和任务下发
|
||||||
|
- **FastAPI**:负责端对云物料更新和 HTTP API
|
||||||
|
|
||||||
## 分布式组网
|
## 分布式组网
|
||||||
|
|
||||||
启动 Uni-Lab 时,加入 `--without_host` 将作为从站,不加将作为主站,主站 (host) 持有物料修改权以及对云端的通信。局域网内分别启动的 Uni-Lab 主站/从站将自动组网,互相能访问所有设备状态、传感器信息并发送指令。
|
启动 Uni-Lab 时,加入 `--is_slave` 将作为从站,不加将作为主站:
|
||||||
|
|
||||||
|
- **主站 (host)**:持有物料修改权以及对云端的通信
|
||||||
|
- **从站 (slave)**:无主机权限,可选择跳过等待主机服务 (`--slave_no_host`)
|
||||||
|
|
||||||
|
局域网内分别启动的 Uni-Lab 主站/从站将自动组网,互相能访问所有设备状态、传感器信息并发送指令。
|
||||||
|
|
||||||
|
## 可视化选项
|
||||||
|
|
||||||
|
### 2D 可视化
|
||||||
|
|
||||||
|
使用 `--2d_vis` 在 PyLabRobot 实例启动时同时启动 2D 可视化。
|
||||||
|
|
||||||
|
### 3D 可视化
|
||||||
|
|
||||||
|
通过 `--visual` 参数选择:
|
||||||
|
|
||||||
|
- **rviz**:使用 RViz 进行 3D 可视化
|
||||||
|
- **web**:使用 Web 界面进行可视化
|
||||||
|
- **disable** (默认):禁用可视化
|
||||||
|
|
||||||
|
## 实验室管理
|
||||||
|
|
||||||
|
### 首次使用
|
||||||
|
|
||||||
|
如果是首次使用,系统会:
|
||||||
|
|
||||||
|
1. 提示前往 https://uni-lab.bohrium.com 注册实验室
|
||||||
|
2. 引导创建配置文件
|
||||||
|
3. 设置工作目录
|
||||||
|
|
||||||
|
### 认证设置
|
||||||
|
|
||||||
|
- `--ak`:实验室访问密钥
|
||||||
|
- `--sk`:实验室私钥
|
||||||
|
- 两者必须同时提供才能正常启动
|
||||||
|
|
||||||
## 完整启动示例
|
## 完整启动示例
|
||||||
|
|
||||||
以下是一些常用的启动命令示例:
|
以下是一些常用的启动命令示例:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 使用配置文件和组态图启动
|
# 使用组态图启动,上传注册表
|
||||||
unilab -g path/to/graph.json
|
unilab --ak your_ak --sk your_sk -g path/to/graph.json --upload_registry
|
||||||
|
|
||||||
# 使用配置文件和分离的设备/资源文件启动
|
# 使用远程资源启动
|
||||||
unilab -d devices.json -r resources.json
|
unilab --ak your_ak --sk your_sk --use_remote_resource
|
||||||
|
|
||||||
|
# 更新注册表
|
||||||
|
unilab --ak your_ak --sk your_sk --complete_registry
|
||||||
|
|
||||||
|
# 启动从站模式
|
||||||
|
unilab --ak your_ak --sk your_sk --is_slave
|
||||||
|
|
||||||
|
# 启用可视化
|
||||||
|
unilab --ak your_ak --sk your_sk --visual web --2d_vis
|
||||||
|
|
||||||
|
# 指定本地信息网页服务端口和禁用自动跳出浏览器
|
||||||
|
unilab --ak your_ak --sk your_sk --port 8080 --disable_browser
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### 1. 认证失败
|
||||||
|
|
||||||
|
如果提示 "后续运行必须拥有一个实验室",请确保:
|
||||||
|
|
||||||
|
- 已在 https://uni-lab.bohrium.com 注册实验室
|
||||||
|
- 正确设置了 `--ak` 和 `--sk` 参数
|
||||||
|
- 配置文件中包含正确的认证信息
|
||||||
|
|
||||||
|
### 2. 配置文件问题
|
||||||
|
|
||||||
|
如果配置文件加载失败:
|
||||||
|
|
||||||
|
- 确保配置文件是 `.py` 格式
|
||||||
|
- 检查配置文件语法是否正确
|
||||||
|
- 首次使用可让系统自动创建示例配置文件
|
||||||
|
|
||||||
|
### 3. 网络连接问题
|
||||||
|
|
||||||
|
如果无法连接到服务器:
|
||||||
|
|
||||||
|
- 检查网络连接
|
||||||
|
- 确认服务器地址是否正确
|
||||||
|
- 尝试使用不同的环境地址(test、uat、local)
|
||||||
|
|
||||||
|
### 4. 设备图谱问题
|
||||||
|
|
||||||
|
如果设备加载失败:
|
||||||
|
|
||||||
|
- 检查图谱文件格式是否正确
|
||||||
|
- 验证设备连接和端点配置
|
||||||
|
- 确保注册表路径正确
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package:
|
package:
|
||||||
name: ros-humble-unilabos-msgs
|
name: ros-humble-unilabos-msgs
|
||||||
version: 0.10.3
|
version: 0.10.5
|
||||||
source:
|
source:
|
||||||
path: ../../unilabos_msgs
|
path: ../../unilabos_msgs
|
||||||
target_directory: src
|
target_directory: src
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package:
|
package:
|
||||||
name: unilabos
|
name: unilabos
|
||||||
version: "0.10.3"
|
version: "0.10.5"
|
||||||
|
|
||||||
source:
|
source:
|
||||||
path: ../..
|
path: ../..
|
||||||
|
|||||||
5
setup.py
5
setup.py
@@ -4,7 +4,7 @@ package_name = 'unilabos'
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name=package_name,
|
name=package_name,
|
||||||
version='0.10.3',
|
version='0.10.5',
|
||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
install_requires=['setuptools'],
|
install_requires=['setuptools'],
|
||||||
@@ -16,8 +16,7 @@ setup(
|
|||||||
tests_require=['pytest'],
|
tests_require=['pytest'],
|
||||||
entry_points={
|
entry_points={
|
||||||
'console_scripts': [
|
'console_scripts': [
|
||||||
"unilab = unilabos.app.main:main",
|
"unilab = unilabos.app.main:main"
|
||||||
"unilab-register = unilabos.app.register:main"
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -15,16 +15,16 @@ def start_backend(
|
|||||||
without_host: bool = False,
|
without_host: bool = False,
|
||||||
visual: str = "None",
|
visual: str = "None",
|
||||||
resources_mesh_config: dict = {},
|
resources_mesh_config: dict = {},
|
||||||
**kwargs
|
**kwargs,
|
||||||
):
|
):
|
||||||
if backend == "ros":
|
if backend == "ros":
|
||||||
# 假设 ros_main, simple_main, automancer_main 是不同 backend 的启动函数
|
# 假设 ros_main, simple_main, automancer_main 是不同 backend 的启动函数
|
||||||
from unilabos.ros.main_slave_run import main, slave # 如果选择 'ros' 作为 backend
|
from unilabos.ros.main_slave_run import main, slave # 如果选择 'ros' 作为 backend
|
||||||
elif backend == 'simple':
|
elif backend == "simple":
|
||||||
# 这里假设 simple_backend 和 automancer_backend 是你定义的其他两个后端
|
# 这里假设 simple_backend 和 automancer_backend 是你定义的其他两个后端
|
||||||
# from simple_backend import main as simple_main
|
# from simple_backend import main as simple_main
|
||||||
pass
|
pass
|
||||||
elif backend == 'automancer':
|
elif backend == "automancer":
|
||||||
# from automancer_backend import main as automancer_main
|
# from automancer_backend import main as automancer_main
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
@@ -32,7 +32,16 @@ def start_backend(
|
|||||||
|
|
||||||
backend_thread = threading.Thread(
|
backend_thread = threading.Thread(
|
||||||
target=main if not without_host else slave,
|
target=main if not without_host else slave,
|
||||||
args=(devices_config, resources_config, resources_edge_config, graph, controllers_config, bridges, visual, resources_mesh_config),
|
args=(
|
||||||
|
devices_config,
|
||||||
|
resources_config,
|
||||||
|
resources_edge_config,
|
||||||
|
graph,
|
||||||
|
controllers_config,
|
||||||
|
bridges,
|
||||||
|
visual,
|
||||||
|
resources_mesh_config,
|
||||||
|
),
|
||||||
name="backend_thread",
|
name="backend_thread",
|
||||||
daemon=True,
|
daemon=True,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"""
|
"""
|
||||||
通信模块
|
通信模块
|
||||||
|
|
||||||
提供MQTT和WebSocket的统一接口,支持通过配置选择通信协议。
|
提供WebSocket的统一接口,支持通过配置选择通信协议。
|
||||||
包含通信抽象层基类和通信客户端工厂。
|
包含通信抽象层基类和通信客户端工厂。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ class BaseCommunicationClient(ABC):
|
|||||||
"""
|
"""
|
||||||
通信客户端抽象基类
|
通信客户端抽象基类
|
||||||
|
|
||||||
定义了所有通信客户端(MQTT、WebSocket等)需要实现的接口。
|
定义了所有通信客户端(WebSocket等)需要实现的接口。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -121,14 +121,12 @@ class CommunicationClientFactory:
|
|||||||
|
|
||||||
protocol = protocol.lower()
|
protocol = protocol.lower()
|
||||||
|
|
||||||
if protocol == "mqtt":
|
if protocol == "websocket":
|
||||||
return cls._create_mqtt_client()
|
|
||||||
elif protocol == "websocket":
|
|
||||||
return cls._create_websocket_client()
|
return cls._create_websocket_client()
|
||||||
else:
|
else:
|
||||||
logger.error(f"[CommunicationFactory] Unsupported protocol: {protocol}")
|
logger.error(f"[CommunicationFactory] Unsupported protocol: {protocol}")
|
||||||
logger.warning(f"[CommunicationFactory] Falling back to MQTT")
|
logger.warning(f"[CommunicationFactory] Falling back to WebSocket")
|
||||||
return cls._create_mqtt_client()
|
return cls._create_websocket_client()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_client(cls, protocol: Optional[str] = None) -> BaseCommunicationClient:
|
def get_client(cls, protocol: Optional[str] = None) -> BaseCommunicationClient:
|
||||||
@@ -147,26 +145,16 @@ class CommunicationClientFactory:
|
|||||||
|
|
||||||
return cls._client_cache
|
return cls._client_cache
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _create_mqtt_client(cls) -> BaseCommunicationClient:
|
|
||||||
"""创建MQTT客户端"""
|
|
||||||
try:
|
|
||||||
from unilabos.app.mq import mqtt_client
|
|
||||||
return mqtt_client
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"[CommunicationFactory] Failed to create MQTT client: {str(e)}")
|
|
||||||
raise
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _create_websocket_client(cls) -> BaseCommunicationClient:
|
def _create_websocket_client(cls) -> BaseCommunicationClient:
|
||||||
"""创建WebSocket客户端"""
|
"""创建WebSocket客户端"""
|
||||||
try:
|
try:
|
||||||
from unilabos.app.ws_client import WebSocketClient
|
from unilabos.app.ws_client import WebSocketClient
|
||||||
|
|
||||||
return WebSocketClient()
|
return WebSocketClient()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"[CommunicationFactory] Failed to create WebSocket client: {str(e)}")
|
logger.error(f"[CommunicationFactory] Failed to create WebSocket client: {str(e)}")
|
||||||
logger.warning(f"[CommunicationFactory] Falling back to MQTT")
|
raise
|
||||||
return cls._create_mqtt_client()
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def reset_client(cls):
|
def reset_client(cls):
|
||||||
@@ -188,7 +176,7 @@ class CommunicationClientFactory:
|
|||||||
Returns:
|
Returns:
|
||||||
支持的协议列表
|
支持的协议列表
|
||||||
"""
|
"""
|
||||||
return ["mqtt", "websocket"]
|
return ["websocket"]
|
||||||
|
|
||||||
|
|
||||||
def get_communication_client(protocol: Optional[str] = None) -> BaseCommunicationClient:
|
def get_communication_client(protocol: Optional[str] = None) -> BaseCommunicationClient:
|
||||||
|
|||||||
@@ -51,14 +51,14 @@ def convert_argv_dashes_to_underscores(args: argparse.ArgumentParser):
|
|||||||
def parse_args():
|
def parse_args():
|
||||||
"""解析命令行参数"""
|
"""解析命令行参数"""
|
||||||
parser = argparse.ArgumentParser(description="Start Uni-Lab Edge server.")
|
parser = argparse.ArgumentParser(description="Start Uni-Lab Edge server.")
|
||||||
parser.add_argument("-g", "--graph", help="Physical setup graph.")
|
parser.add_argument("-g", "--graph", help="Physical setup graph file path.")
|
||||||
parser.add_argument("-c", "--controllers", default=None, help="Controllers config file.")
|
parser.add_argument("-c", "--controllers", default=None, help="Controllers config file path.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--registry_path",
|
"--registry_path",
|
||||||
type=str,
|
type=str,
|
||||||
default=None,
|
default=None,
|
||||||
action="append",
|
action="append",
|
||||||
help="Path to the registry",
|
help="Path to the registry directory",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--working_dir",
|
"--working_dir",
|
||||||
@@ -75,84 +75,85 @@ def parse_args():
|
|||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--app_bridges",
|
"--app_bridges",
|
||||||
nargs="+",
|
nargs="+",
|
||||||
default=["mqtt", "fastapi"],
|
default=["websocket", "fastapi"],
|
||||||
help="Bridges to connect to. Now support 'mqtt' and 'fastapi'.",
|
help="Bridges to connect to. Now support 'websocket' and 'fastapi'.",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--without_host",
|
"--is_slave",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="Run the backend as slave (without host).",
|
help="Run the backend as slave node (without host privileges).",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--slave_no_host",
|
"--slave_no_host",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="Slave模式下跳过等待host服务",
|
help="Skip waiting for host service in slave mode",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--upload_registry",
|
"--upload_registry",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="启动unilab时同时报送注册表信息",
|
help="Upload registry information when starting unilab",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--use_remote_resource",
|
"--use_remote_resource",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="启动unilab时使用远程资源启动",
|
help="Use remote resources when starting unilab",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--config",
|
"--config",
|
||||||
type=str,
|
type=str,
|
||||||
default=None,
|
default=None,
|
||||||
help="配置文件路径,支持.py格式的Python配置文件",
|
help="Configuration file path, supports .py format Python config files",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--port",
|
"--port",
|
||||||
type=int,
|
type=int,
|
||||||
default=8002,
|
default=8002,
|
||||||
help="信息页web服务的启动端口",
|
help="Port for web service information page",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--disable_browser",
|
"--disable_browser",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="是否在启动时关闭信息页",
|
help="Disable opening information page on startup",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--2d_vis",
|
"--2d_vis",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="是否在pylabrobot实例启动时,同时启动可视化",
|
help="Enable 2D visualization when starting pylabrobot instance",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--visual",
|
"--visual",
|
||||||
choices=["rviz", "web", "disable"],
|
choices=["rviz", "web", "disable"],
|
||||||
default="disable",
|
default="disable",
|
||||||
help="选择可视化工具: rviz, web",
|
help="Choose visualization tool: rviz, web, or disable",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--ak",
|
"--ak",
|
||||||
type=str,
|
type=str,
|
||||||
default="",
|
default="",
|
||||||
help="实验室请求的ak",
|
help="Access key for laboratory requests",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--sk",
|
"--sk",
|
||||||
type=str,
|
type=str,
|
||||||
default="",
|
default="",
|
||||||
help="实验室请求的sk",
|
help="Secret key for laboratory requests",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--addr",
|
"--addr",
|
||||||
type=str,
|
type=str,
|
||||||
default="https://uni-lab.bohrium.com/api/v1",
|
default="https://uni-lab.bohrium.com/api/v1",
|
||||||
help="实验室后端地址",
|
help="Laboratory backend address",
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--websocket",
|
|
||||||
action="store_true",
|
|
||||||
help="使用websocket而非mqtt作为通信协议",
|
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--skip_env_check",
|
"--skip_env_check",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
help="跳过启动时的环境依赖检查",
|
help="Skip environment dependency check on startup",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--complete_registry",
|
||||||
|
action="store_true",
|
||||||
|
default=False,
|
||||||
|
help="Complete registry information",
|
||||||
)
|
)
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
@@ -237,13 +238,17 @@ def main():
|
|||||||
print_status("远程资源不存在,本地将进行首次上报!", "info")
|
print_status("远程资源不存在,本地将进行首次上报!", "info")
|
||||||
|
|
||||||
# 设置BasicConfig参数
|
# 设置BasicConfig参数
|
||||||
|
if args_dict.get("ak", ""):
|
||||||
BasicConfig.ak = args_dict.get("ak", "")
|
BasicConfig.ak = args_dict.get("ak", "")
|
||||||
|
print_status("传入了ak参数,优先采用传入参数!", "info")
|
||||||
|
if args_dict.get("sk", ""):
|
||||||
BasicConfig.sk = args_dict.get("sk", "")
|
BasicConfig.sk = args_dict.get("sk", "")
|
||||||
|
print_status("传入了sk参数,优先采用传入参数!", "info")
|
||||||
BasicConfig.working_dir = working_dir
|
BasicConfig.working_dir = working_dir
|
||||||
BasicConfig.is_host_mode = not args_dict.get("without_host", False)
|
BasicConfig.is_host_mode = not args_dict.get("is_slave", False)
|
||||||
BasicConfig.slave_no_host = args_dict.get("slave_no_host", False)
|
BasicConfig.slave_no_host = args_dict.get("slave_no_host", False)
|
||||||
BasicConfig.upload_registry = args_dict.get("upload_registry", False)
|
BasicConfig.upload_registry = args_dict.get("upload_registry", False)
|
||||||
BasicConfig.communication_protocol = "websocket" if args_dict.get("websocket", False) else "mqtt"
|
BasicConfig.communication_protocol = "websocket"
|
||||||
machine_name = os.popen("hostname").read().strip()
|
machine_name = os.popen("hostname").read().strip()
|
||||||
machine_name = "".join([c if c.isalnum() or c == "_" else "_" for c in machine_name])
|
machine_name = "".join([c if c.isalnum() or c == "_" else "_" for c in machine_name])
|
||||||
BasicConfig.machine_name = machine_name
|
BasicConfig.machine_name = machine_name
|
||||||
@@ -261,12 +266,19 @@ def main():
|
|||||||
from unilabos.app.backend import start_backend
|
from unilabos.app.backend import start_backend
|
||||||
from unilabos.app.web import http_client
|
from unilabos.app.web import http_client
|
||||||
from unilabos.app.web import start_server
|
from unilabos.app.web import start_server
|
||||||
|
from unilabos.app.register import register_devices_and_resources
|
||||||
|
|
||||||
# 显示启动横幅
|
# 显示启动横幅
|
||||||
print_unilab_banner(args_dict)
|
print_unilab_banner(args_dict)
|
||||||
|
|
||||||
# 注册表
|
# 注册表
|
||||||
lab_registry = build_registry(args_dict["registry_path"], False, args_dict["upload_registry"])
|
lab_registry = build_registry(
|
||||||
|
args_dict["registry_path"], args_dict.get("complete_registry", False), args_dict["upload_registry"]
|
||||||
|
)
|
||||||
|
|
||||||
|
if not BasicConfig.ak or not BasicConfig.sk:
|
||||||
|
print_status("后续运行必须拥有一个实验室,请前往 https://uni-lab.bohrium.com 注册实验室!", "warning")
|
||||||
|
os._exit(1)
|
||||||
if args_dict["graph"] is None:
|
if args_dict["graph"] is None:
|
||||||
request_startup_json = http_client.request_startup_json()
|
request_startup_json = http_client.request_startup_json()
|
||||||
if not request_startup_json:
|
if not request_startup_json:
|
||||||
@@ -297,14 +309,24 @@ def main():
|
|||||||
target_node = nodes[i["target"]]
|
target_node = nodes[i["target"]]
|
||||||
source_handle = i["sourceHandle"]
|
source_handle = i["sourceHandle"]
|
||||||
target_handle = i["targetHandle"]
|
target_handle = i["targetHandle"]
|
||||||
source_handler_keys = [h["handler_key"] for h in materials[source_node["class"]]["handles"] if h["io_type"] == 'source']
|
source_handler_keys = [
|
||||||
target_handler_keys = [h["handler_key"] for h in materials[target_node["class"]]["handles"] if h["io_type"] == 'target']
|
h["handler_key"] for h in materials[source_node["class"]]["handles"] if h["io_type"] == "source"
|
||||||
if not source_handle in source_handler_keys:
|
]
|
||||||
print_status(f"节点 {source_node['id']} 的source端点 {source_handle} 不存在,请检查,支持的端点 {source_handler_keys}", "error")
|
target_handler_keys = [
|
||||||
|
h["handler_key"] for h in materials[target_node["class"]]["handles"] if h["io_type"] == "target"
|
||||||
|
]
|
||||||
|
if source_handle not in source_handler_keys:
|
||||||
|
print_status(
|
||||||
|
f"节点 {source_node['id']} 的source端点 {source_handle} 不存在,请检查,支持的端点 {source_handler_keys}",
|
||||||
|
"error",
|
||||||
|
)
|
||||||
resource_edge_info.pop(edge_info - ind - 1)
|
resource_edge_info.pop(edge_info - ind - 1)
|
||||||
continue
|
continue
|
||||||
if not target_handle in target_handler_keys:
|
if target_handle not in target_handler_keys:
|
||||||
print_status(f"节点 {target_node['id']} 的target端点 {target_handle} 不存在,请检查,支持的端点 {target_handler_keys}", "error")
|
print_status(
|
||||||
|
f"节点 {target_node['id']} 的target端点 {target_handle} 不存在,请检查,支持的端点 {target_handler_keys}",
|
||||||
|
"error",
|
||||||
|
)
|
||||||
resource_edge_info.pop(edge_info - ind - 1)
|
resource_edge_info.pop(edge_info - ind - 1)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -318,6 +340,19 @@ def main():
|
|||||||
for i in args_dict["resources_config"]:
|
for i in args_dict["resources_config"]:
|
||||||
print_status(f"DeviceId: {i['id']}, Class: {i['class']}", "info")
|
print_status(f"DeviceId: {i['id']}, Class: {i['class']}", "info")
|
||||||
|
|
||||||
|
# 设备注册到服务端 - 需要 ak 和 sk
|
||||||
|
if args_dict.get("ak") and args_dict.get("sk"):
|
||||||
|
print_status("检测到 ak 和 sk,开始注册设备到服务端...", "info")
|
||||||
|
try:
|
||||||
|
register_devices_and_resources(lab_registry)
|
||||||
|
print_status("设备注册完成", "info")
|
||||||
|
except Exception as e:
|
||||||
|
print_status(f"设备注册失败: {e}", "error")
|
||||||
|
elif args_dict.get("ak") or args_dict.get("sk"):
|
||||||
|
print_status("警告:ak 和 sk 必须同时提供才能注册设备到服务端", "warning")
|
||||||
|
else:
|
||||||
|
print_status("未提供 ak 和 sk,跳过设备注册", "info")
|
||||||
|
|
||||||
if args_dict["controllers"] is not None:
|
if args_dict["controllers"] is not None:
|
||||||
args_dict["controllers_config"] = yaml.safe_load(open(args_dict["controllers"], encoding="utf-8"))
|
args_dict["controllers_config"] = yaml.safe_load(open(args_dict["controllers"], encoding="utf-8"))
|
||||||
else:
|
else:
|
||||||
@@ -325,14 +360,14 @@ def main():
|
|||||||
|
|
||||||
args_dict["bridges"] = []
|
args_dict["bridges"] = []
|
||||||
|
|
||||||
# 获取通信客户端(根据配置选择MQTT或WebSocket)
|
# 获取通信客户端(仅支持WebSocket)
|
||||||
comm_client = get_communication_client()
|
comm_client = get_communication_client()
|
||||||
|
|
||||||
if "mqtt" in args_dict["app_bridges"]:
|
if "websocket" in args_dict["app_bridges"]:
|
||||||
args_dict["bridges"].append(comm_client)
|
args_dict["bridges"].append(comm_client)
|
||||||
if "fastapi" in args_dict["app_bridges"]:
|
if "fastapi" in args_dict["app_bridges"]:
|
||||||
args_dict["bridges"].append(http_client)
|
args_dict["bridges"].append(http_client)
|
||||||
if "mqtt" in args_dict["app_bridges"]:
|
if "websocket" in args_dict["app_bridges"]:
|
||||||
|
|
||||||
def _exit(signum, frame):
|
def _exit(signum, frame):
|
||||||
comm_client.stop()
|
comm_client.stop()
|
||||||
|
|||||||
@@ -1,225 +0,0 @@
|
|||||||
import json
|
|
||||||
import time
|
|
||||||
import traceback
|
|
||||||
from typing import Optional
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
import paho.mqtt.client as mqtt
|
|
||||||
import ssl
|
|
||||||
import base64
|
|
||||||
import hmac
|
|
||||||
from hashlib import sha1
|
|
||||||
import tempfile
|
|
||||||
import os
|
|
||||||
|
|
||||||
from unilabos.config.config import MQConfig
|
|
||||||
from unilabos.app.controler import job_add
|
|
||||||
from unilabos.app.model import JobAddReq
|
|
||||||
from unilabos.app.communication import BaseCommunicationClient
|
|
||||||
from unilabos.utils import logger
|
|
||||||
from unilabos.utils.type_check import TypeEncoder
|
|
||||||
|
|
||||||
from paho.mqtt.enums import CallbackAPIVersion
|
|
||||||
|
|
||||||
|
|
||||||
class MQTTClient(BaseCommunicationClient):
|
|
||||||
mqtt_disable = True
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
self.mqtt_disable = not MQConfig.lab_id
|
|
||||||
self.is_disabled = self.mqtt_disable # 更新父类属性
|
|
||||||
self.client_id = f"{MQConfig.group_id}@@@{MQConfig.lab_id}{uuid.uuid4()}"
|
|
||||||
logger.info("[MQTT] Client_id: " + self.client_id)
|
|
||||||
self.client = mqtt.Client(CallbackAPIVersion.VERSION2, client_id=self.client_id, protocol=mqtt.MQTTv5)
|
|
||||||
self._setup_callbacks()
|
|
||||||
|
|
||||||
def _setup_callbacks(self):
|
|
||||||
self.client.on_log = self._on_log
|
|
||||||
self.client.on_connect = self._on_connect
|
|
||||||
self.client.on_message = self._on_message
|
|
||||||
self.client.on_disconnect = self._on_disconnect
|
|
||||||
|
|
||||||
def _on_log(self, client, userdata, level, buf):
|
|
||||||
# logger.info(f"[MQTT] log: {buf}")
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _on_connect(self, client, userdata, flags, rc, properties=None):
|
|
||||||
logger.info("[MQTT] Connected with result code " + str(rc))
|
|
||||||
client.subscribe(f"labs/{MQConfig.lab_id}/job/start/", 0)
|
|
||||||
client.subscribe(f"labs/{MQConfig.lab_id}/pong/", 0)
|
|
||||||
|
|
||||||
def _on_message(self, client, userdata, msg) -> None:
|
|
||||||
# logger.info("[MQTT] on_message<<<< " + msg.topic + " " + str(msg.payload))
|
|
||||||
try:
|
|
||||||
payload_str = msg.payload.decode("utf-8")
|
|
||||||
payload_json = json.loads(payload_str)
|
|
||||||
if msg.topic == f"labs/{MQConfig.lab_id}/job/start/":
|
|
||||||
if "data" not in payload_json:
|
|
||||||
payload_json["data"] = {}
|
|
||||||
if "action" in payload_json:
|
|
||||||
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:
|
|
||||||
payload_json["data"]["action_kwargs"] = payload_json.pop("action_kwargs")
|
|
||||||
job_req = JobAddReq.model_validate(payload_json)
|
|
||||||
data = job_add(job_req)
|
|
||||||
return
|
|
||||||
elif msg.topic == f"labs/{MQConfig.lab_id}/pong/":
|
|
||||||
# 处理pong响应,通知HostNode
|
|
||||||
from unilabos.ros.nodes.presets.host_node import HostNode
|
|
||||||
|
|
||||||
host_instance = HostNode.get_instance(0)
|
|
||||||
if host_instance:
|
|
||||||
host_instance.handle_pong_response(payload_json)
|
|
||||||
return
|
|
||||||
|
|
||||||
except json.JSONDecodeError as e:
|
|
||||||
logger.error(f"[MQTT] JSON 解析错误: {e}")
|
|
||||||
logger.error(f"[MQTT] Raw message: {msg.payload}")
|
|
||||||
logger.error(traceback.format_exc())
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"[MQTT] 处理消息时出错: {e}")
|
|
||||||
logger.error(traceback.format_exc())
|
|
||||||
|
|
||||||
def _on_disconnect(self, client, userdata, rc, reasonCode=None, properties=None):
|
|
||||||
if rc != 0:
|
|
||||||
logger.error(f"[MQTT] Unexpected disconnection {rc}")
|
|
||||||
|
|
||||||
def _setup_ssl_context(self):
|
|
||||||
temp_files = []
|
|
||||||
try:
|
|
||||||
with tempfile.NamedTemporaryFile(mode="w", delete=False) as ca_temp:
|
|
||||||
ca_temp.write(MQConfig.ca_content)
|
|
||||||
temp_files.append(ca_temp.name)
|
|
||||||
|
|
||||||
with tempfile.NamedTemporaryFile(mode="w", delete=False) as cert_temp:
|
|
||||||
cert_temp.write(MQConfig.cert_content)
|
|
||||||
temp_files.append(cert_temp.name)
|
|
||||||
|
|
||||||
with tempfile.NamedTemporaryFile(mode="w", delete=False) as key_temp:
|
|
||||||
key_temp.write(MQConfig.key_content)
|
|
||||||
temp_files.append(key_temp.name)
|
|
||||||
|
|
||||||
context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
|
|
||||||
context.load_verify_locations(cafile=temp_files[0])
|
|
||||||
context.load_cert_chain(certfile=temp_files[1], keyfile=temp_files[2])
|
|
||||||
self.client.tls_set_context(context)
|
|
||||||
finally:
|
|
||||||
for temp_file in temp_files:
|
|
||||||
try:
|
|
||||||
os.unlink(temp_file)
|
|
||||||
except Exception as e:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
if self.mqtt_disable:
|
|
||||||
logger.warning("MQTT is disabled, skipping connection.")
|
|
||||||
return
|
|
||||||
userName = f"Signature|{MQConfig.access_key}|{MQConfig.instance_id}"
|
|
||||||
password = base64.b64encode(
|
|
||||||
hmac.new(MQConfig.secret_key.encode(), self.client_id.encode(), sha1).digest()
|
|
||||||
).decode()
|
|
||||||
|
|
||||||
self.client.username_pw_set(userName, password)
|
|
||||||
self._setup_ssl_context()
|
|
||||||
|
|
||||||
# 创建连接线程
|
|
||||||
def connect_thread_func():
|
|
||||||
try:
|
|
||||||
self.client.connect(MQConfig.broker_url, MQConfig.port, 60)
|
|
||||||
self.client.loop_start()
|
|
||||||
|
|
||||||
# 添加连接超时检测
|
|
||||||
max_attempts = 5
|
|
||||||
attempt = 0
|
|
||||||
while not self.client.is_connected() and attempt < max_attempts:
|
|
||||||
logger.info(
|
|
||||||
f"[MQTT] 正在连接到 {MQConfig.broker_url}:{MQConfig.port},尝试 {attempt+1}/{max_attempts}"
|
|
||||||
)
|
|
||||||
time.sleep(3)
|
|
||||||
attempt += 1
|
|
||||||
|
|
||||||
if self.client.is_connected():
|
|
||||||
logger.info(f"[MQTT] 已成功连接到 {MQConfig.broker_url}:{MQConfig.port}")
|
|
||||||
else:
|
|
||||||
logger.error(f"[MQTT] 连接超时,可能是账号密码错误或网络问题")
|
|
||||||
self.client.loop_stop()
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"[MQTT] 连接失败: {str(e)}")
|
|
||||||
|
|
||||||
connect_thread_func()
|
|
||||||
# connect_thread = threading.Thread(target=connect_thread_func)
|
|
||||||
# connect_thread.daemon = True
|
|
||||||
# connect_thread.start()
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
if self.mqtt_disable:
|
|
||||||
return
|
|
||||||
self.client.disconnect()
|
|
||||||
self.client.loop_stop()
|
|
||||||
|
|
||||||
def publish_device_status(self, device_status: dict, device_id, property_name):
|
|
||||||
# status = device_status.get(device_id, {})
|
|
||||||
if self.mqtt_disable:
|
|
||||||
return
|
|
||||||
status = {"data": device_status.get(device_id, {}), "device_id": device_id, "timestamp": time.time()}
|
|
||||||
address = f"labs/{MQConfig.lab_id}/devices/"
|
|
||||||
self.client.publish(address, json.dumps(status), qos=2)
|
|
||||||
# logger.info(f"Device {device_id} status published: address: {address}, {status}")
|
|
||||||
|
|
||||||
def publish_job_status(self, feedback_data: dict, job_id: str, status: str, return_info: Optional[dict] = None):
|
|
||||||
if self.mqtt_disable:
|
|
||||||
return
|
|
||||||
if return_info is None:
|
|
||||||
return_info = {}
|
|
||||||
jobdata = {"job_id": job_id, "data": feedback_data, "status": status, "return_info": return_info}
|
|
||||||
self.client.publish(f"labs/{MQConfig.lab_id}/job/list/", json.dumps(jobdata), qos=2)
|
|
||||||
|
|
||||||
def publish_registry(self, device_id: str, device_info: dict, print_debug: bool = True):
|
|
||||||
if self.mqtt_disable:
|
|
||||||
return
|
|
||||||
address = f"labs/{MQConfig.lab_id}/registry/"
|
|
||||||
registry_data = json.dumps({device_id: device_info}, ensure_ascii=False, cls=TypeEncoder)
|
|
||||||
self.client.publish(address, registry_data, qos=2)
|
|
||||||
if print_debug:
|
|
||||||
logger.debug(f"Registry data published: address: {address}, {registry_data}")
|
|
||||||
|
|
||||||
def publish_actions(self, action_id: str, action_info: dict):
|
|
||||||
if self.mqtt_disable:
|
|
||||||
return
|
|
||||||
address = f"labs/{MQConfig.lab_id}/actions/"
|
|
||||||
self.client.publish(address, json.dumps(action_info), qos=2)
|
|
||||||
logger.debug(f"Action data published: address: {address}, {action_id}, {action_info}")
|
|
||||||
|
|
||||||
def send_ping(self, ping_id: str, timestamp: float):
|
|
||||||
"""发送ping消息到服务端"""
|
|
||||||
if self.mqtt_disable:
|
|
||||||
return
|
|
||||||
address = f"labs/{MQConfig.lab_id}/ping/"
|
|
||||||
ping_data = {"ping_id": ping_id, "client_timestamp": timestamp, "type": "ping"}
|
|
||||||
self.client.publish(address, json.dumps(ping_data), qos=2)
|
|
||||||
|
|
||||||
def setup_pong_subscription(self):
|
|
||||||
"""设置pong消息订阅"""
|
|
||||||
if self.mqtt_disable:
|
|
||||||
return
|
|
||||||
pong_topic = f"labs/{MQConfig.lab_id}/pong/"
|
|
||||||
self.client.subscribe(pong_topic, 0)
|
|
||||||
logger.debug(f"Subscribed to pong topic: {pong_topic}")
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_connected(self) -> bool:
|
|
||||||
"""检查MQTT是否已连接"""
|
|
||||||
if self.is_disabled:
|
|
||||||
return False
|
|
||||||
return hasattr(self.client, "is_connected") and self.client.is_connected()
|
|
||||||
|
|
||||||
|
|
||||||
mqtt_client = MQTTClient()
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
mqtt_client.start()
|
|
||||||
@@ -10,16 +10,16 @@ from unilabos.utils.log import logger
|
|||||||
from unilabos.utils.type_check import TypeEncoder
|
from unilabos.utils.type_check import TypeEncoder
|
||||||
|
|
||||||
|
|
||||||
def register_devices_and_resources(comm_client, lab_registry):
|
def register_devices_and_resources(lab_registry):
|
||||||
"""
|
"""
|
||||||
注册设备和资源到通信服务器(MQTT/WebSocket)
|
注册设备和资源到服务器(仅支持HTTP)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# 注册资源信息 - 使用HTTP方式
|
# 注册资源信息 - 使用HTTP方式
|
||||||
from unilabos.app.web.client import http_client
|
from unilabos.app.web.client import http_client
|
||||||
|
|
||||||
logger.info("[UniLab Register] 开始注册设备和资源...")
|
logger.info("[UniLab Register] 开始注册设备和资源...")
|
||||||
if BasicConfig.auth_secret():
|
|
||||||
# 注册设备信息
|
# 注册设备信息
|
||||||
devices_to_register = {}
|
devices_to_register = {}
|
||||||
for device_info in lab_registry.obtain_registry_device_info():
|
for device_info in lab_registry.obtain_registry_device_info():
|
||||||
@@ -27,100 +27,36 @@ def register_devices_and_resources(comm_client, lab_registry):
|
|||||||
json.dumps(device_info, ensure_ascii=False, cls=TypeEncoder)
|
json.dumps(device_info, ensure_ascii=False, cls=TypeEncoder)
|
||||||
)
|
)
|
||||||
logger.debug(f"[UniLab Register] 收集设备: {device_info['id']}")
|
logger.debug(f"[UniLab Register] 收集设备: {device_info['id']}")
|
||||||
|
|
||||||
resources_to_register = {}
|
resources_to_register = {}
|
||||||
for resource_info in lab_registry.obtain_registry_resource_info():
|
for resource_info in lab_registry.obtain_registry_resource_info():
|
||||||
resources_to_register[resource_info["id"]] = resource_info
|
resources_to_register[resource_info["id"]] = resource_info
|
||||||
logger.debug(f"[UniLab Register] 收集资源: {resource_info['id']}")
|
logger.debug(f"[UniLab Register] 收集资源: {resource_info['id']}")
|
||||||
print(
|
|
||||||
"[UniLab Register] 设备注册",
|
|
||||||
http_client.resource_registry({"resources": list(devices_to_register.values())}).text,
|
|
||||||
)
|
|
||||||
print(
|
|
||||||
"[UniLab Register] 资源注册",
|
|
||||||
http_client.resource_registry({"resources": list(resources_to_register.values())}).text,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
# 注册设备信息
|
|
||||||
for device_info in lab_registry.obtain_registry_device_info():
|
|
||||||
comm_client.publish_registry(device_info["id"], device_info, False)
|
|
||||||
logger.debug(f"[UniLab Register] 注册设备: {device_info['id']}")
|
|
||||||
|
|
||||||
# # 注册资源信息
|
# 注册设备
|
||||||
# for resource_info in lab_registry.obtain_registry_resource_info():
|
if devices_to_register:
|
||||||
# comm_client.publish_registry(resource_info["id"], resource_info, False)
|
try:
|
||||||
# logger.debug(f"[UniLab Register] 注册资源: {resource_info['id']}")
|
|
||||||
|
|
||||||
resources_to_register = {}
|
|
||||||
for resource_info in lab_registry.obtain_registry_resource_info():
|
|
||||||
resources_to_register[resource_info["id"]] = resource_info
|
|
||||||
logger.debug(f"[UniLab Register] 准备注册资源: {resource_info['id']}")
|
|
||||||
|
|
||||||
if resources_to_register:
|
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
response = http_client.resource_registry(resources_to_register)
|
response = http_client.resource_registry({"resources": list(devices_to_register.values())})
|
||||||
cost_time = time.time() - start_time
|
cost_time = time.time() - start_time
|
||||||
if response.status_code in [200, 201]:
|
if response.status_code in [200, 201]:
|
||||||
logger.info(f"[UniLab Register] 成功通过HTTP注册 {len(resources_to_register)} 个资源 {cost_time}ms")
|
logger.info(f"[UniLab Register] 成功注册 {len(devices_to_register)} 个设备 {cost_time}ms")
|
||||||
else:
|
else:
|
||||||
logger.error(
|
logger.error(f"[UniLab Register] 设备注册失败: {response.status_code}, {response.text} {cost_time}ms")
|
||||||
f"[UniLab Register] HTTP注册资源失败: {response.status_code}, {response.text} {cost_time}ms"
|
except Exception as e:
|
||||||
)
|
logger.error(f"[UniLab Register] 设备注册异常: {e}")
|
||||||
|
|
||||||
|
# 注册资源
|
||||||
|
if resources_to_register:
|
||||||
|
try:
|
||||||
|
start_time = time.time()
|
||||||
|
response = http_client.resource_registry({"resources": list(resources_to_register.values())})
|
||||||
|
cost_time = time.time() - start_time
|
||||||
|
if response.status_code in [200, 201]:
|
||||||
|
logger.info(f"[UniLab Register] 成功注册 {len(resources_to_register)} 个资源 {cost_time}ms")
|
||||||
|
else:
|
||||||
|
logger.error(f"[UniLab Register] 资源注册失败: {response.status_code}, {response.text} {cost_time}ms")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"[UniLab Register] 资源注册异常: {e}")
|
||||||
|
|
||||||
logger.info("[UniLab Register] 设备和资源注册完成.")
|
logger.info("[UniLab Register] 设备和资源注册完成.")
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""
|
|
||||||
命令行入口函数
|
|
||||||
"""
|
|
||||||
parser = argparse.ArgumentParser(description="注册设备和资源到 MQTT")
|
|
||||||
parser.add_argument(
|
|
||||||
"--registry",
|
|
||||||
type=str,
|
|
||||||
default=None,
|
|
||||||
action="append",
|
|
||||||
help="注册表路径",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--config",
|
|
||||||
type=str,
|
|
||||||
default=None,
|
|
||||||
help="配置文件路径,支持.py格式的Python配置文件",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--ak",
|
|
||||||
type=str,
|
|
||||||
default="",
|
|
||||||
help="实验室请求的ak",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--sk",
|
|
||||||
type=str,
|
|
||||||
default="",
|
|
||||||
help="实验室请求的sk",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--complete_registry",
|
|
||||||
action="store_true",
|
|
||||||
default=False,
|
|
||||||
help="是否补全注册表",
|
|
||||||
)
|
|
||||||
args = parser.parse_args()
|
|
||||||
load_config_from_file(args.config)
|
|
||||||
BasicConfig.ak = args.ak
|
|
||||||
BasicConfig.sk = args.sk
|
|
||||||
# 构建注册表
|
|
||||||
build_registry(args.registry, args.complete_registry, True)
|
|
||||||
from unilabos.app.communication import get_communication_client
|
|
||||||
|
|
||||||
# 获取通信客户端并启动连接
|
|
||||||
comm_client = get_communication_client()
|
|
||||||
comm_client.start()
|
|
||||||
|
|
||||||
from unilabos.registry.registry import lab_registry
|
|
||||||
|
|
||||||
# 注册设备和资源
|
|
||||||
register_devices_and_resources(comm_client, lab_registry)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
|
|||||||
@@ -9,16 +9,13 @@ import asyncio
|
|||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from unilabos.app.controler import devices, job_add, job_info
|
from unilabos.app.web.controler import devices, job_add, job_info
|
||||||
from unilabos.app.model import (
|
from unilabos.app.model import (
|
||||||
Resp,
|
Resp,
|
||||||
RespCode,
|
RespCode,
|
||||||
JobStatusResp,
|
JobStatusResp,
|
||||||
JobAddResp,
|
JobAddResp,
|
||||||
JobAddReq,
|
JobAddReq,
|
||||||
JobStepFinishReq,
|
|
||||||
JobPreintakeFinishReq,
|
|
||||||
JobFinishReq,
|
|
||||||
)
|
)
|
||||||
from unilabos.app.web.utils.host_utils import get_host_node_info
|
from unilabos.app.web.utils.host_utils import get_host_node_info
|
||||||
from unilabos.registry.registry import lab_registry
|
from unilabos.registry.registry import lab_registry
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ HTTP客户端模块
|
|||||||
|
|
||||||
提供与远程服务器通信的客户端功能,只有host需要用
|
提供与远程服务器通信的客户端功能,只有host需要用
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from typing import List, Dict, Any, Optional
|
from typing import List, Dict, Any, Optional
|
||||||
@@ -15,7 +16,6 @@ from unilabos.utils import logger
|
|||||||
|
|
||||||
class HTTPClient:
|
class HTTPClient:
|
||||||
"""HTTP客户端,用于与远程服务器通信"""
|
"""HTTP客户端,用于与远程服务器通信"""
|
||||||
backend_go = False # 是否使用Go后端
|
|
||||||
|
|
||||||
def __init__(self, remote_addr: Optional[str] = None, auth: Optional[str] = None) -> None:
|
def __init__(self, remote_addr: Optional[str] = None, auth: Optional[str] = None) -> None:
|
||||||
"""
|
"""
|
||||||
@@ -32,7 +32,6 @@ class HTTPClient:
|
|||||||
auth_secret = BasicConfig.auth_secret()
|
auth_secret = BasicConfig.auth_secret()
|
||||||
if auth_secret:
|
if auth_secret:
|
||||||
self.auth = auth_secret
|
self.auth = auth_secret
|
||||||
self.backend_go = True
|
|
||||||
info(f"正在使用ak sk作为授权信息 {auth_secret}")
|
info(f"正在使用ak sk作为授权信息 {auth_secret}")
|
||||||
else:
|
else:
|
||||||
self.auth = MQConfig.lab_id
|
self.auth = MQConfig.lab_id
|
||||||
@@ -48,17 +47,15 @@ class HTTPClient:
|
|||||||
Returns:
|
Returns:
|
||||||
Response: API响应对象
|
Response: API响应对象
|
||||||
"""
|
"""
|
||||||
database_param = 1 if database_process_later else 0
|
|
||||||
response = requests.post(
|
response = requests.post(
|
||||||
f"{self.remote_addr}/lab/resource/edge/batch_create/?database_process_later={database_param}"
|
f"{self.remote_addr}/lab/material/edge",
|
||||||
if not self.backend_go else f"{self.remote_addr}/lab/material/edge",
|
|
||||||
json={
|
json={
|
||||||
"edges": resources,
|
"edges": resources,
|
||||||
} if self.backend_go else resources,
|
},
|
||||||
headers={"Authorization": f"{'lab' if not self.backend_go else 'Lab'} {self.auth}"},
|
headers={"Authorization": f"Lab {self.auth}"},
|
||||||
timeout=100,
|
timeout=100,
|
||||||
)
|
)
|
||||||
if self.backend_go and response.status_code == 200:
|
if response.status_code == 200:
|
||||||
res = response.json()
|
res = response.json()
|
||||||
if "code" in res and res["code"] != 0:
|
if "code" in res and res["code"] != 0:
|
||||||
logger.error(f"添加物料关系失败: {response.text}")
|
logger.error(f"添加物料关系失败: {response.text}")
|
||||||
@@ -77,12 +74,12 @@ class HTTPClient:
|
|||||||
Response: API响应对象
|
Response: API响应对象
|
||||||
"""
|
"""
|
||||||
response = requests.post(
|
response = requests.post(
|
||||||
f"{self.remote_addr}/lab/resource/?database_process_later={1 if database_process_later else 0}" if not self.backend_go else f"{self.remote_addr}/lab/material",
|
f"{self.remote_addr}/lab/material",
|
||||||
json=resources if not self.backend_go else {"nodes": resources},
|
json={"nodes": resources},
|
||||||
headers={"Authorization": f"{'lab' if not self.backend_go else 'Lab'} {self.auth}"},
|
headers={"Authorization": f"Lab {self.auth}"},
|
||||||
timeout=100,
|
timeout=100,
|
||||||
)
|
)
|
||||||
if self.backend_go and response.status_code == 200:
|
if response.status_code == 200:
|
||||||
res = response.json()
|
res = response.json()
|
||||||
if "code" in res and res["code"] != 0:
|
if "code" in res and res["code"] != 0:
|
||||||
logger.error(f"添加物料失败: {response.text}")
|
logger.error(f"添加物料失败: {response.text}")
|
||||||
@@ -102,9 +99,9 @@ class HTTPClient:
|
|||||||
Dict: 返回的资源数据
|
Dict: 返回的资源数据
|
||||||
"""
|
"""
|
||||||
response = requests.get(
|
response = requests.get(
|
||||||
f"{self.remote_addr}/lab/resource/?edge_format=1" if not self.backend_go else f"{self.remote_addr}/lab/material",
|
f"{self.remote_addr}/lab/material",
|
||||||
params={"id": id, "with_children": with_children},
|
params={"id": id, "with_children": with_children},
|
||||||
headers={"Authorization": f"{'lab' if not self.backend_go else 'Lab'} {self.auth}"},
|
headers={"Authorization": f"Lab {self.auth}"},
|
||||||
timeout=20,
|
timeout=20,
|
||||||
)
|
)
|
||||||
return response.json()
|
return response.json()
|
||||||
@@ -122,7 +119,7 @@ class HTTPClient:
|
|||||||
response = requests.delete(
|
response = requests.delete(
|
||||||
f"{self.remote_addr}/lab/resource/batch_delete/",
|
f"{self.remote_addr}/lab/resource/batch_delete/",
|
||||||
params={"id": id},
|
params={"id": id},
|
||||||
headers={"Authorization": f"{'lab' if not self.backend_go else 'Lab'} {self.auth}"},
|
headers={"Authorization": f"Lab {self.auth}"},
|
||||||
timeout=20,
|
timeout=20,
|
||||||
)
|
)
|
||||||
return response
|
return response
|
||||||
@@ -140,7 +137,7 @@ class HTTPClient:
|
|||||||
response = requests.patch(
|
response = requests.patch(
|
||||||
f"{self.remote_addr}/lab/resource/batch_update/?edge_format=1",
|
f"{self.remote_addr}/lab/resource/batch_update/?edge_format=1",
|
||||||
json=resources,
|
json=resources,
|
||||||
headers={"Authorization": f"{'lab' if not self.backend_go else 'Lab'} {self.auth}"},
|
headers={"Authorization": f"Lab {self.auth}"},
|
||||||
timeout=100,
|
timeout=100,
|
||||||
)
|
)
|
||||||
return response
|
return response
|
||||||
@@ -164,7 +161,7 @@ class HTTPClient:
|
|||||||
response = requests.post(
|
response = requests.post(
|
||||||
f"{self.remote_addr}/api/account/file_upload/{scene}",
|
f"{self.remote_addr}/api/account/file_upload/{scene}",
|
||||||
files=files,
|
files=files,
|
||||||
headers={"Authorization": f"{'lab' if not self.backend_go else 'Lab'} {self.auth}"},
|
headers={"Authorization": f"Lab {self.auth}"},
|
||||||
timeout=30, # 上传文件可能需要更长的超时时间
|
timeout=30, # 上传文件可能需要更长的超时时间
|
||||||
)
|
)
|
||||||
return response
|
return response
|
||||||
@@ -180,9 +177,9 @@ class HTTPClient:
|
|||||||
Response: API响应对象
|
Response: API响应对象
|
||||||
"""
|
"""
|
||||||
response = requests.post(
|
response = requests.post(
|
||||||
f"{self.remote_addr}/lab/registry/" if not self.backend_go else f"{self.remote_addr}/lab/resource",
|
f"{self.remote_addr}/lab/resource",
|
||||||
json=registry_data,
|
json=registry_data,
|
||||||
headers={"Authorization": f"{'lab' if not self.backend_go else 'Lab'} {self.auth}"},
|
headers={"Authorization": f"Lab {self.auth}"},
|
||||||
timeout=30,
|
timeout=30,
|
||||||
)
|
)
|
||||||
if response.status_code not in [200, 201]:
|
if response.status_code not in [200, 201]:
|
||||||
@@ -201,7 +198,7 @@ class HTTPClient:
|
|||||||
"""
|
"""
|
||||||
response = requests.get(
|
response = requests.get(
|
||||||
f"{self.remote_addr}/lab/resource/graph_info/",
|
f"{self.remote_addr}/lab/resource/graph_info/",
|
||||||
headers={"Authorization": f"{'lab' if not self.backend_go else 'Lab'} {self.auth}"},
|
headers={"Authorization": f"Lab {self.auth}"},
|
||||||
timeout=(3, 30),
|
timeout=(3, 30),
|
||||||
)
|
)
|
||||||
if response.status_code != 200:
|
if response.status_code != 200:
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# coding=utf-8
|
|
||||||
# 定义配置变量和加载函数
|
|
||||||
import base64
|
import base64
|
||||||
import traceback
|
import traceback
|
||||||
import os
|
import os
|
||||||
@@ -10,7 +7,6 @@ from unilabos.utils import logger
|
|||||||
|
|
||||||
|
|
||||||
class BasicConfig:
|
class BasicConfig:
|
||||||
ENV = "pro" # 'test'
|
|
||||||
ak = ""
|
ak = ""
|
||||||
sk = ""
|
sk = ""
|
||||||
working_dir = ""
|
working_dir = ""
|
||||||
@@ -21,12 +17,10 @@ class BasicConfig:
|
|||||||
machine_name = "undefined"
|
machine_name = "undefined"
|
||||||
vis_2d_enable = False
|
vis_2d_enable = False
|
||||||
enable_resource_load = True
|
enable_resource_load = True
|
||||||
# 通信协议配置
|
communication_protocol = "websocket"
|
||||||
communication_protocol = "mqtt" # 支持: "mqtt", "websocket"
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def auth_secret(cls):
|
def auth_secret(cls):
|
||||||
# base64编码
|
|
||||||
if not cls.ak or not cls.sk:
|
if not cls.ak or not cls.sk:
|
||||||
return ""
|
return ""
|
||||||
target = f"{cls.ak}:{cls.sk}"
|
target = f"{cls.ak}:{cls.sk}"
|
||||||
@@ -34,25 +28,6 @@ class BasicConfig:
|
|||||||
return base64_target
|
return base64_target
|
||||||
|
|
||||||
|
|
||||||
# MQTT配置
|
|
||||||
class MQConfig:
|
|
||||||
lab_id = ""
|
|
||||||
instance_id = ""
|
|
||||||
access_key = ""
|
|
||||||
secret_key = ""
|
|
||||||
group_id = ""
|
|
||||||
broker_url = ""
|
|
||||||
port = 1883
|
|
||||||
ca_content = ""
|
|
||||||
cert_content = ""
|
|
||||||
key_content = ""
|
|
||||||
|
|
||||||
# 指定
|
|
||||||
ca_file = "" # 相对config.py所在目录的路径
|
|
||||||
cert_file = "" # 相对config.py所在目录的路径
|
|
||||||
key_file = "" # 相对config.py所在目录的路径
|
|
||||||
|
|
||||||
|
|
||||||
# WebSocket配置
|
# WebSocket配置
|
||||||
class WSConfig:
|
class WSConfig:
|
||||||
reconnect_interval = 5 # 重连间隔(秒)
|
reconnect_interval = 5 # 重连间隔(秒)
|
||||||
@@ -94,9 +69,6 @@ def _update_config_from_module(module):
|
|||||||
for attr in dir(getattr(module, name)):
|
for attr in dir(getattr(module, name)):
|
||||||
if not attr.startswith("_"):
|
if not attr.startswith("_"):
|
||||||
setattr(obj, attr, getattr(getattr(module, name), attr))
|
setattr(obj, attr, getattr(getattr(module, name), attr))
|
||||||
# 更新OSS认证
|
|
||||||
if len(OSSUploadConfig.authorization) == 0:
|
|
||||||
OSSUploadConfig.authorization = f"Lab {MQConfig.lab_id}"
|
|
||||||
|
|
||||||
def _update_config_from_env():
|
def _update_config_from_env():
|
||||||
prefix = "UNILABOS_"
|
prefix = "UNILABOS_"
|
||||||
|
|||||||
@@ -1 +1,12 @@
|
|||||||
# 暂无配置
|
# unilabos的配置文件
|
||||||
|
|
||||||
|
class BasicConfig:
|
||||||
|
ak = "" # 实验室网页给您提供的ak代码,您可以在配置文件中指定,也可以通过运行unilabos时以 --ak 传入,优先按照传入参数解析
|
||||||
|
sk = "" # 实验室网页给您提供的sk代码,您可以在配置文件中指定,也可以通过运行unilabos时以 --sk 传入,优先按照传入参数解析
|
||||||
|
|
||||||
|
|
||||||
|
# WebSocket配置,一般无需调整
|
||||||
|
class WSConfig:
|
||||||
|
reconnect_interval = 5 # 重连间隔(秒)
|
||||||
|
max_reconnect_attempts = 999 # 最大重连次数
|
||||||
|
ping_interval = 30 # ping间隔(秒)
|
||||||
@@ -1 +0,0 @@
|
|||||||
from .eis_model import EISModelBasedController
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import numpy as np
|
|
||||||
|
|
||||||
|
|
||||||
def EISModelBasedController(eis: np.array) -> float:
|
|
||||||
return 0.0
|
|
||||||
@@ -164,13 +164,10 @@ class HostNode(BaseROS2DeviceNode):
|
|||||||
self.device_status = {} # 用来存储设备状态
|
self.device_status = {} # 用来存储设备状态
|
||||||
self.device_status_timestamps = {} # 用来存储设备状态最后更新时间
|
self.device_status_timestamps = {} # 用来存储设备状态最后更新时间
|
||||||
if BasicConfig.upload_registry:
|
if BasicConfig.upload_registry:
|
||||||
from unilabos.app.communication import get_communication_client
|
register_devices_and_resources(lab_registry)
|
||||||
|
|
||||||
comm_client = get_communication_client()
|
|
||||||
register_devices_and_resources(comm_client, lab_registry)
|
|
||||||
else:
|
else:
|
||||||
self.lab_logger().warning(
|
self.lab_logger().warning(
|
||||||
"本次启动注册表不报送云端,如果您需要联网调试,请使用unilab-register命令进行单独报送,或者在启动命令增加--upload_registry"
|
"本次启动注册表不报送云端,如果您需要联网调试,请在启动命令增加--upload_registry"
|
||||||
)
|
)
|
||||||
time.sleep(1) # 等待通信连接稳定
|
time.sleep(1) # 等待通信连接稳定
|
||||||
# 首次发现网络中的设备
|
# 首次发现网络中的设备
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
|
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
|
||||||
<package format="3">
|
<package format="3">
|
||||||
<name>unilabos_msgs</name>
|
<name>unilabos_msgs</name>
|
||||||
<version>0.10.3</version>
|
<version>0.10.5</version>
|
||||||
<description>ROS2 Messages package for unilabos devices</description>
|
<description>ROS2 Messages package for unilabos devices</description>
|
||||||
<maintainer email="changjh@pku.edu.cn">Junhan Chang</maintainer>
|
<maintainer email="changjh@pku.edu.cn">Junhan Chang</maintainer>
|
||||||
<license>MIT</license>
|
<license>MIT</license>
|
||||||
|
|||||||
Reference in New Issue
Block a user