mirror of
https://github.com/deepmodeling/Uni-Lab-OS
synced 2026-05-22 23:14:54 +00:00
env installation fix
fix pack install 2 fix pip install & git install failed fix pack build 1 Update SKILL.md Update Skills Update registry for all param desc
This commit is contained in:
450
.cursor/skills/filter-workflow-by-tags/SKILL.md
Normal file
450
.cursor/skills/filter-workflow-by-tags/SKILL.md
Normal file
@@ -0,0 +1,450 @@
|
||||
---
|
||||
name: filter-workflow-by-tags
|
||||
description: Query backend workflow list, aggregate all tags, and filter workflows by domain/scenario requirements using tags. Use when the user wants to search workflows, find workflows by tags, list available workflow tags, filter workflows by category/domain/scenario, or mentions 工作流筛选/标签查询/workflow tags/按领域查找工作流.
|
||||
---
|
||||
# Uni-Lab 工作流标签筛选指南
|
||||
|
||||
通过 Uni-Lab 云端 API 查询工作流列表,汇总所有可用标签(tags),并根据领域和场景要求筛选工作流。
|
||||
|
||||
> **重要**:本指南中的 `Authorization: Lab <token>` 是 **Uni-Lab 平台专用的认证方式**,`Lab` 是 Uni-Lab 的 auth scheme 关键字,**不是** HTTP Basic 认证。请勿将其替换为 `Basic`。
|
||||
|
||||
## 使用模式识别
|
||||
|
||||
**用户可能一开始就给出场景目标**(如"我要做有机合成实验"、"找柱层析相关的 protocol")。此时:
|
||||
|
||||
1. **识别场景关键词** → 映射到可能的 tags(如 synthesis、organic、chromatography、purification)
|
||||
2. **直接执行完整流程**(获取 ak/sk/addr → 拉取所有工作流 → 汇总 tags → 按场景筛选)
|
||||
3. **展示筛选结果** → 引导用户从候选 workflow 中**选择明确的实验 protocol**
|
||||
4. **如果用户确认某个 workflow** → 记录 `workflow_uuid`,准备对接“与其他 Skill 的协作”
|
||||
|
||||
**如果用户未给场景目标**,则按标准 checklist 询问筛选条件。
|
||||
|
||||
---
|
||||
|
||||
## 前置条件
|
||||
|
||||
使用本指南前,**必须**先确认以下信息。如果缺少任何一项,**立即向用户询问并终止**,等补齐后再继续。
|
||||
|
||||
### 1. ak / sk → AUTH
|
||||
|
||||
询问用户的启动参数,从 `--ak` `--sk` 或 config.py 中获取。
|
||||
|
||||
生成 AUTH token:
|
||||
|
||||
```bash
|
||||
python -c "import base64,sys; print('Authorization: Lab ' + base64.b64encode(f'{sys.argv[1]}:{sys.argv[2]}'.encode()).decode())" <ak> <sk>
|
||||
```
|
||||
|
||||
### 2. --addr → BASE URL
|
||||
|
||||
| `--addr` 值 | BASE |
|
||||
| ------------- | ------------------------------------- |
|
||||
| `test` | `https://leap-lab.test.bohrium.com` |
|
||||
| `uat` | `https://leap-lab.uat.bohrium.com` |
|
||||
| `local` | `http://127.0.0.1:48197` |
|
||||
| 不传(默认) | `https://leap-lab.bohrium.com` |
|
||||
|
||||
确认后设置:
|
||||
|
||||
```bash
|
||||
BASE="<根据 addr 确定的 URL>"
|
||||
AUTH="Authorization: Lab <上面命令输出的 token>"
|
||||
```
|
||||
|
||||
### 3. lab_uuid(实验室 UUID)
|
||||
|
||||
如果用户未提供 `lab_uuid`,通过以下 API 自动获取:
|
||||
|
||||
```bash
|
||||
curl -s -X GET "$BASE/api/v1/edge/lab/info" -H "$AUTH"
|
||||
```
|
||||
|
||||
返回 `data.uuid` 即为 `lab_uuid`。
|
||||
|
||||
**三项全部就绪后才可开始。**
|
||||
|
||||
## Session State
|
||||
|
||||
在整个对话过程中,agent 需要记住以下状态:
|
||||
|
||||
- `lab_uuid` — 实验室 UUID
|
||||
- `all_workflows` — 完整工作流列表(分页获取后缓存到内存或临时文件)
|
||||
- `all_tags` — 所有工作流的标签汇总
|
||||
|
||||
---
|
||||
|
||||
## API 端点
|
||||
|
||||
### 查询工作流列表(支持分页)
|
||||
|
||||
```
|
||||
GET $BASE/api/v1/lab/workflow/owner/list?page=<page>&page_size=<page_size>&lab_uuid=$lab_uuid
|
||||
```
|
||||
|
||||
**参数:**
|
||||
|
||||
- `page` — 页码,从 1 开始
|
||||
- `page_size` — 每页数量,建议 1000
|
||||
- `lab_uuid` — 实验室 UUID
|
||||
|
||||
**返回结构:**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"has_more": true,
|
||||
"data": [
|
||||
{
|
||||
"uuid": "9661bba2-1b9f-4687-a63d-910245df174b",
|
||||
"name": "Untitled",
|
||||
"description": "",
|
||||
"user_id": "114211",
|
||||
"published": false,
|
||||
"tags": null
|
||||
},
|
||||
{
|
||||
"uuid": "e0436638-190b-46bc-b1a1-2711d9602f6a",
|
||||
"name": "Synthesis v2",
|
||||
"user_id": "114211",
|
||||
"published": true,
|
||||
"tags": ["synthesis", "organic"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**字段说明:**
|
||||
|
||||
- `has_more` — 若为 `true`,需要继续请求 `page+1`
|
||||
- `tags` — 可能为 `null`、空数组或字符串数组;聚合时必须容忍 `null`
|
||||
|
||||
### 启动工作流(直接运行)
|
||||
|
||||
```
|
||||
POST $BASE/api/v1/lab/workflow/<workflow_uuid>/run
|
||||
```
|
||||
|
||||
**用途:** 直接启动一个 workflow 的默认执行(使用模板中预设的参数),无需创建 notebook。适用于快速测试或无参数变化的重复执行。
|
||||
|
||||
**请求体:** 空 JSON `{}` 或省略
|
||||
|
||||
**返回:**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": "<run_uuid>"
|
||||
}
|
||||
```
|
||||
|
||||
- `run_uuid` — 本次执行的唯一标识(不是 notebook UUID)
|
||||
|
||||
**注意:**
|
||||
|
||||
- 该接口会使用 workflow 模板中保存的默认参数直接执行
|
||||
- 如果 workflow 需要动态参数(如 CSV 路径、样品 UUID),应使用 `POST /lab/notebook` 创建 notebook 并传入 `node_params`
|
||||
- 返回的 `run_uuid` 可直接传入下方「查询任务状态」接口查询实时进度
|
||||
|
||||
### 查询任务状态
|
||||
|
||||
```
|
||||
GET $BASE/api/v1/lab/mcp/task/<task_uuid>
|
||||
```
|
||||
|
||||
**用途:** 查询由 `POST /lab/workflow/<uuid>/run` 返回的 `run_uuid`(即 task_uuid)的实时执行状态,包括整体状态和每个节点(JOS:Job On Station)的执行详情。
|
||||
|
||||
**路径参数:**
|
||||
|
||||
- `task_uuid` — 等同于启动工作流接口返回的 `run_uuid`
|
||||
|
||||
**返回:**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"data": {
|
||||
"status": "running",
|
||||
"jos_status": [
|
||||
{
|
||||
"uuid": "d0e24bfe-8d99-450e-b19d-f25849dfbaad",
|
||||
"node_name": "PRCXI_BioER_96_wellplate_slot_1",
|
||||
"action_name": "create_resource",
|
||||
"status": "success",
|
||||
"return_info": {
|
||||
"suc": true,
|
||||
"error": "",
|
||||
"return_value": { ... }
|
||||
}
|
||||
},
|
||||
{
|
||||
"uuid": "...",
|
||||
"node_name": "...",
|
||||
"action_name": "transfer_liquid",
|
||||
"status": "pending",
|
||||
"return_info": null
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**字段说明:**
|
||||
|
||||
- `data.status` — 整体任务状态
|
||||
- `running` — 正在执行(至少一个节点 pending 或 running)
|
||||
- `success` — 全部节点成功
|
||||
- `failed` — 有节点失败
|
||||
- `data.jos_status[]` — 节点级执行列表(按执行顺序)
|
||||
- `uuid` — 节点执行实例 UUID
|
||||
- `node_name` — 节点名称(资源/设备名或工位名)
|
||||
- `action_name` — 动作类型(`create_resource`、`transfer_liquid`、`centrifuge`、等)
|
||||
- `status` — 节点状态:`success`、`pending`、`running`、`failed`
|
||||
- `return_info` — 执行返回,失败时 `suc=false` 且 `error` 有错误信息
|
||||
|
||||
**注意:**
|
||||
|
||||
- 此接口的 `task_uuid` **是** `POST /lab/workflow/<uuid>/run` 返回的 `run_uuid`,二者为同一个 ID 的不同称呼
|
||||
- **不要**把 notebook UUID(`POST /lab/notebook` 返回)传进来——那条路径用 `GET /lab/notebook/status` 查询
|
||||
- `jos_status` 数组按节点执行顺序给出;从 pending 数量可以估算剩余进度
|
||||
- 返回体可能较大(`return_info.return_value` 中可能包含完整 resource tree),可在脚本中只提取 `status` + `node_name` + `action_name` 做摘要
|
||||
|
||||
**状态轮询示例:**
|
||||
|
||||
```bash
|
||||
# 每 5 秒轮询一次直至完成
|
||||
TASK="b183d97e-d2b5-4b24-b14b-820df04d87c0"
|
||||
while :; do
|
||||
st=$(curl -s -X GET "$BASE/api/v1/lab/mcp/task/$TASK" -H "$AUTH" \
|
||||
| python3 -c "import json,sys; d=json.load(sys.stdin)['data']; \
|
||||
print(d['status'], '|', sum(1 for j in d['jos_status'] if j['status']=='success'), '/', len(d['jos_status']))")
|
||||
echo "$(date +%H:%M:%S) $st"
|
||||
[[ "$st" == success* || "$st" == failed* ]] && break
|
||||
sleep 5
|
||||
done
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 完整工作流 Checklist
|
||||
|
||||
```
|
||||
Task Progress:
|
||||
- [ ] Step 0: 识别用户是否已给出场景目标(如"有机合成"、"柱层析")
|
||||
- 若已给出 → 记录场景关键词,自动进入后续步骤
|
||||
- 若未给出 → 在 Step 6 询问用户
|
||||
- [ ] Step 1: 确认 ak/sk → 生成 AUTH token
|
||||
- [ ] Step 2: 确认 --addr → 设置 BASE URL
|
||||
- [ ] Step 3: GET /edge/lab/info → 获取 lab_uuid(如用户未提供)
|
||||
- [ ] Step 4: 分页获取所有工作流(从 page=1 开始直到 has_more=false)
|
||||
- [ ] Step 5: 汇总所有非空 tags → 生成 all_tags(去重、排序、附出现次数)
|
||||
- [ ] Step 6: 根据场景关键词(Step 0 或新询问)在 all_tags 中做语义映射 → 确定候选 tags
|
||||
- 若语义映射不唯一,列出候选 tags 让用户确认
|
||||
- [ ] Step 7: 按候选 tags 筛选工作流(默认 any 模式,召回优先)
|
||||
- [ ] Step 8: 展示筛选结果(uuid、name、description、tags、published)
|
||||
- [ ] Step 9: 引导用户从结果中选择**明确的实验 protocol**
|
||||
- 若结果只有 1 条 → 直接确认该 workflow_uuid
|
||||
- 若结果 2–10 条 → 让用户按编号选择
|
||||
- 若结果过多 → 提示收紧条件(加 tag、切换 all 模式、仅 published)
|
||||
- 若结果为空 → 放宽条件(去掉最稀有 tag)或提示用户换关键词
|
||||
- [ ] Step 10: 记录用户选中的 workflow_uuid,并提示提交实验或查看详情
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 推荐路径:使用脚本
|
||||
|
||||
同目录下提供 `scripts/filter_workflows.py`,一次完成分页抓取、标签聚合与筛选:
|
||||
|
||||
```bash
|
||||
# 1. 仅汇总标签(不筛选)
|
||||
python scripts/filter_workflows.py \
|
||||
--auth "<Lab base64token>" \
|
||||
--base "$BASE" \
|
||||
--lab-uuid "$lab_uuid" \
|
||||
--summary-only
|
||||
|
||||
# 2. 按标签筛选(ANY 模式:包含任一)
|
||||
python scripts/filter_workflows.py \
|
||||
--auth "<Lab base64token>" \
|
||||
--base "$BASE" \
|
||||
--lab-uuid "$lab_uuid" \
|
||||
--tags synthesis organic \
|
||||
--mode any
|
||||
|
||||
# 3. 按标签筛选(ALL 模式:必须同时包含)
|
||||
python scripts/filter_workflows.py \
|
||||
--auth "<Lab base64token>" \
|
||||
--base "$BASE" \
|
||||
--lab-uuid "$lab_uuid" \
|
||||
--tags synthesis organic \
|
||||
--mode all \
|
||||
--output filtered.json
|
||||
|
||||
# 4. 仅筛选已发布
|
||||
python scripts/filter_workflows.py \
|
||||
--auth "<Lab base64token>" \
|
||||
--base "$BASE" \
|
||||
--lab-uuid "$lab_uuid" \
|
||||
--tags synthesis \
|
||||
--published-only
|
||||
```
|
||||
|
||||
**`--auth` 参数说明**:传入 `Authorization` 头中 `Lab` 之后的 base64 token(不带 `Lab ` 前缀),脚本内部会自动补上 scheme。
|
||||
|
||||
**输出结构:**
|
||||
|
||||
```json
|
||||
{
|
||||
"total_workflows": 150,
|
||||
"tag_counts": {"synthesis": 12, "organic": 8, "analysis": 5},
|
||||
"all_tags": ["analysis", "organic", "synthesis"],
|
||||
"filter": {"tags": ["synthesis", "organic"], "mode": "any"},
|
||||
"filtered_workflows": [
|
||||
{"uuid": "...", "name": "...", "description": "...", "tags": [...], "published": true}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 手动路径:curl + jq
|
||||
|
||||
如果脚本不可用或环境缺少 Python,可用 shell 实现。
|
||||
|
||||
### 1. 分页抓取(写入 `all_workflows.json`)
|
||||
|
||||
```bash
|
||||
page=1
|
||||
echo "[]" > all_workflows.json
|
||||
|
||||
while :; do
|
||||
resp=$(curl -s -X GET \
|
||||
"$BASE/api/v1/lab/workflow/owner/list?page=$page&page_size=1000&lab_uuid=$lab_uuid" \
|
||||
-H "$AUTH")
|
||||
|
||||
page_data=$(echo "$resp" | jq -c '.data.data // []')
|
||||
jq -c --argjson p "$page_data" '. + $p' all_workflows.json > _tmp.json && mv _tmp.json all_workflows.json
|
||||
|
||||
has_more=$(echo "$resp" | jq -r '.data.has_more')
|
||||
[ "$has_more" != "true" ] && break
|
||||
page=$((page + 1))
|
||||
done
|
||||
|
||||
echo "Total: $(jq 'length' all_workflows.json)"
|
||||
```
|
||||
|
||||
### 2. 汇总所有标签(含出现次数)
|
||||
|
||||
```bash
|
||||
jq '[.[].tags // [] | .[]] | group_by(.) | map({tag: .[0], count: length}) | sort_by(-.count)' \
|
||||
all_workflows.json
|
||||
```
|
||||
|
||||
### 3. 按标签筛选
|
||||
|
||||
```bash
|
||||
# ANY:包含任一指定标签
|
||||
jq --argjson want '["synthesis","organic"]' \
|
||||
'[.[] | select((.tags // []) | any(. as $t | $want | index($t)))]' \
|
||||
all_workflows.json
|
||||
|
||||
# ALL:同时包含所有指定标签
|
||||
jq --argjson want '["synthesis","organic"]' \
|
||||
'[.[] | select(($want | all(. as $w | (.tags // []) | index($w))))]' \
|
||||
all_workflows.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 筛选策略
|
||||
|
||||
agent 拿到用户的「领域 + 场景」自然语言描述时,按如下顺序选择 tag:
|
||||
|
||||
1. **优先用户显式指定的 tags**:若用户明确给出标签词,直接精确匹配。
|
||||
2. **从 all_tags 中做语义映射**:若用户描述是自然语言(如"有机合成、柱层析"),在 all_tags 中找语义相关项(如 `synthesis`、`organic`、`chromatography`)。必要时展示候选 tag 让用户确认。
|
||||
3. **模式选择**:
|
||||
- 默认 `any`(更多召回),给出 tag 集合的并集匹配
|
||||
- 用户强调"必须同时满足"时用 `all`
|
||||
4. **空结果兜底**:如果筛选为空,放宽条件(去掉最稀有 tag、切换 any 模式),并提醒用户。
|
||||
|
||||
---
|
||||
|
||||
## 引导到明确的 Protocol
|
||||
|
||||
筛选完成后,**最终目标是让用户确认一个具体的 workflow_uuid**,而不是停留在"一堆候选"上。按结果数量采取不同策略:
|
||||
|
||||
| 结果数量 | 策略 |
|
||||
| --------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| 0 条 | 放宽筛选(去掉最稀有 tag → 切换 any 模式 → 去掉 `--published-only`)。仍为空则提示换关键词,或列出 `all_tags` 让用户重新选。 |
|
||||
| 1 条 | 直接确认:"找到唯一匹配:`<name>` (uuid `<uuid>`),是否用它?"用户确认后记录 `workflow_uuid`。 |
|
||||
| 2–10 条 | 编号列表展示,让用户选编号。每项给出 name、tags、description 摘要、published 状态。 |
|
||||
| 10–30 条 | 先展示 tag 分布帮助用户进一步收紧:列出匹配结果中最常见的子标签,提示"加一个 tag 可将结果缩小到 N 条"。 |
|
||||
| >30 条 | 强制要求用户补充条件:仅 published、指定具体 tag 组合、或按名称关键词过滤。 |
|
||||
|
||||
**确认 workflow 后**:
|
||||
|
||||
1. 将 `workflow_uuid` 写入 session state
|
||||
2. 提示用户下一步可用的 skill:
|
||||
- 提交实验 → 引导到“与其他 Skill 的协作”
|
||||
- 查看 workflow 详细节点 → `GET /api/v1/lab/workflow/template/detail/<workflow_uuid>`
|
||||
3. 若用户想换一个,回到筛选步骤。
|
||||
|
||||
---
|
||||
|
||||
## 展示结果
|
||||
|
||||
推荐格式(表格 + 汇总统计):
|
||||
|
||||
```
|
||||
共 150 个工作流,其中 32 个匹配筛选条件 [tags: synthesis OR organic]
|
||||
|
||||
| UUID (短) | 名称 | Tags | 已发布 |
|
||||
|-----------|--------------------------|------------------------------|--------|
|
||||
| e0436638 | Synthesis v2 | synthesis, organic | ✓ |
|
||||
| 5b60dbb8 | Grignard Protocol | synthesis, organometallic | ✓ |
|
||||
| ... | ... | ... | ... |
|
||||
|
||||
所有可用标签(按频次):
|
||||
synthesis (12), organic (8), analysis (5), purification (4), ...
|
||||
```
|
||||
|
||||
如果用户下一步想执行某工作流 → 引导到“与其他 Skill 的协作”。
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: tags 为 null 的工作流要不要展示?
|
||||
|
||||
默认**不展示**在筛选结果中(因为无法按 tag 匹配)。但在 `--summary-only` 或无筛选条件时,这些工作流仍会计入总数,并在输出中单独列出"未打标签"计数。
|
||||
|
||||
### Q: 如何按名称/描述做模糊匹配?
|
||||
|
||||
脚本未内置,但可在 jq 中组合:
|
||||
|
||||
```bash
|
||||
jq '[.[] | select((.name + " " + (.description // "")) | test("organic"; "i"))]' all_workflows.json
|
||||
```
|
||||
|
||||
### Q: `page_size=1000` 是否会被服务端限制?
|
||||
|
||||
接口通常允许最大 1000;如果返回量少于 1000 且 `has_more=false`,说明已到末页。极端情况下若服务端返回错误,可降到 200 或 500 再试。
|
||||
|
||||
### Q: 工作流数量极大(>10k)怎么办?
|
||||
|
||||
1. 先跑 `--summary-only` 了解 tag 分布
|
||||
2. 提示用户先限定 `--published-only` 或指定 tag
|
||||
3. 考虑将 `all_workflows.json` 缓存到本地,下次直接复用
|
||||
|
||||
---
|
||||
|
||||
## 与其他 Skill 的协作
|
||||
|
||||
- 正常情况下,找到 workflow 之后可以直接用它提交实验(启动工作流的 api 端点 POST $BASE/api/v1/lab/workflow/<workflow_uuid>/run,不用别的 skill)
|
||||
- **仅当需要进行多次实验时,使用 batch-submit-experiment** — 筛选到目标工作流后,`workflow_uuid` 直接用于实验提交
|
||||
|
||||
## 脚本依赖
|
||||
|
||||
`scripts/filter_workflows.py` 仅使用 Python 标准库(`urllib`、`json`、`argparse`),无需额外安装。
|
||||
191
.cursor/skills/filter-workflow-by-tags/scripts/filter_workflows.py
Executable file
191
.cursor/skills/filter-workflow-by-tags/scripts/filter_workflows.py
Executable file
@@ -0,0 +1,191 @@
|
||||
#!/usr/bin/env python3
|
||||
"""分页拉取 Uni-Lab 工作流列表,汇总 tags 并按 tag 筛选。
|
||||
|
||||
使用示例:
|
||||
python filter_workflows.py \
|
||||
--auth <base64token> \
|
||||
--base https://leap-lab.test.bohrium.com \
|
||||
--lab-uuid a9059772-... \
|
||||
--tags synthesis organic --mode any
|
||||
|
||||
仅依赖 Python 标准库。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
from collections import Counter
|
||||
|
||||
|
||||
def fetch_all_workflows(base: str, auth_token: str, lab_uuid: str, page_size: int = 1000) -> list[dict]:
|
||||
"""分页拉取所有 owner 工作流,直到 has_more=false。"""
|
||||
workflows: list[dict] = []
|
||||
page = 1
|
||||
while True:
|
||||
query = urllib.parse.urlencode(
|
||||
{"page": page, "page_size": page_size, "lab_uuid": lab_uuid}
|
||||
)
|
||||
url = f"{base.rstrip('/')}/api/v1/lab/workflow/owner/list?{query}"
|
||||
req = urllib.request.Request(
|
||||
url,
|
||||
headers={
|
||||
"Authorization": f"Lab {auth_token}",
|
||||
"Accept": "application/json",
|
||||
},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||
payload = json.loads(resp.read().decode("utf-8"))
|
||||
except urllib.error.HTTPError as e:
|
||||
sys.exit(f"[ERROR] HTTP {e.code} on page {page}: {e.read().decode('utf-8', 'ignore')}")
|
||||
except urllib.error.URLError as e:
|
||||
sys.exit(f"[ERROR] URL error on page {page}: {e.reason}")
|
||||
|
||||
if payload.get("code") != 0:
|
||||
sys.exit(f"[ERROR] API returned non-zero code: {payload}")
|
||||
|
||||
data = payload.get("data") or {}
|
||||
page_items = data.get("data") or []
|
||||
workflows.extend(page_items)
|
||||
|
||||
if not data.get("has_more"):
|
||||
break
|
||||
page += 1
|
||||
# 防御性兜底,避免接口异常导致无限循环
|
||||
if page > 1000:
|
||||
print(f"[WARN] page count exceeded 1000, stopping early", file=sys.stderr)
|
||||
break
|
||||
|
||||
return workflows
|
||||
|
||||
|
||||
def aggregate_tags(workflows: list[dict]) -> tuple[list[str], dict[str, int], int]:
|
||||
"""返回 (sorted_tags, tag_counts, untagged_count)。"""
|
||||
counter: Counter[str] = Counter()
|
||||
untagged = 0
|
||||
for wf in workflows:
|
||||
tags = wf.get("tags")
|
||||
if not tags:
|
||||
untagged += 1
|
||||
continue
|
||||
for t in tags:
|
||||
if isinstance(t, str) and t.strip():
|
||||
counter[t.strip()] += 1
|
||||
return sorted(counter.keys()), dict(counter), untagged
|
||||
|
||||
|
||||
def filter_workflows(
|
||||
workflows: list[dict],
|
||||
want_tags: list[str],
|
||||
mode: str,
|
||||
published_only: bool,
|
||||
) -> list[dict]:
|
||||
"""按 tag 筛选。mode 取值 any / all。"""
|
||||
want_set = {t.strip() for t in want_tags if t.strip()}
|
||||
out: list[dict] = []
|
||||
for wf in workflows:
|
||||
if published_only and not wf.get("published"):
|
||||
continue
|
||||
if not want_set:
|
||||
out.append(wf)
|
||||
continue
|
||||
tags = wf.get("tags") or []
|
||||
tag_set = {t for t in tags if isinstance(t, str)}
|
||||
if mode == "all":
|
||||
if want_set.issubset(tag_set):
|
||||
out.append(wf)
|
||||
else: # any
|
||||
if want_set & tag_set:
|
||||
out.append(wf)
|
||||
return out
|
||||
|
||||
|
||||
def project_workflow(wf: dict) -> dict:
|
||||
"""精简输出字段。"""
|
||||
return {
|
||||
"uuid": wf.get("uuid"),
|
||||
"name": wf.get("name"),
|
||||
"description": wf.get("description", ""),
|
||||
"tags": wf.get("tags") or [],
|
||||
"published": bool(wf.get("published")),
|
||||
"user_id": wf.get("user_id"),
|
||||
}
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
p = argparse.ArgumentParser(description="Fetch & filter Uni-Lab workflows by tags.")
|
||||
p.add_argument("--auth", required=True, help="Base64 token (the part after `Lab `).")
|
||||
p.add_argument("--base", required=True, help="Base URL, e.g. https://leap-lab.test.bohrium.com")
|
||||
p.add_argument("--lab-uuid", required=True, help="Lab UUID.")
|
||||
p.add_argument("--tags", nargs="*", default=[], help="Tags to filter by (space separated).")
|
||||
p.add_argument(
|
||||
"--mode",
|
||||
choices=["any", "all"],
|
||||
default="any",
|
||||
help="any: workflow contains at least one tag; all: workflow contains every tag.",
|
||||
)
|
||||
p.add_argument("--published-only", action="store_true", help="Only include published workflows.")
|
||||
p.add_argument("--page-size", type=int, default=1000, help="Page size, default 1000.")
|
||||
p.add_argument(
|
||||
"--summary-only",
|
||||
action="store_true",
|
||||
help="Print tag summary without applying filter (still fetches everything).",
|
||||
)
|
||||
p.add_argument("--output", help="Write JSON result to this path. If omitted, print to stdout.")
|
||||
return p.parse_args()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = parse_args()
|
||||
workflows = fetch_all_workflows(
|
||||
base=args.base,
|
||||
auth_token=args.auth,
|
||||
lab_uuid=args.lab_uuid,
|
||||
page_size=args.page_size,
|
||||
)
|
||||
sorted_tags, tag_counts, untagged = aggregate_tags(workflows)
|
||||
|
||||
if args.summary_only:
|
||||
result = {
|
||||
"total_workflows": len(workflows),
|
||||
"untagged_count": untagged,
|
||||
"tag_counts": tag_counts,
|
||||
"all_tags": sorted_tags,
|
||||
}
|
||||
else:
|
||||
filtered = filter_workflows(
|
||||
workflows,
|
||||
want_tags=args.tags,
|
||||
mode=args.mode,
|
||||
published_only=args.published_only,
|
||||
)
|
||||
result = {
|
||||
"total_workflows": len(workflows),
|
||||
"untagged_count": untagged,
|
||||
"tag_counts": tag_counts,
|
||||
"all_tags": sorted_tags,
|
||||
"filter": {
|
||||
"tags": args.tags,
|
||||
"mode": args.mode,
|
||||
"published_only": args.published_only,
|
||||
},
|
||||
"matched_count": len(filtered),
|
||||
"filtered_workflows": [project_workflow(wf) for wf in filtered],
|
||||
}
|
||||
|
||||
payload = json.dumps(result, ensure_ascii=False, indent=2)
|
||||
if args.output:
|
||||
with open(args.output, "w", encoding="utf-8") as f:
|
||||
f.write(payload)
|
||||
print(f"Wrote {len(workflows)} workflows summary → {args.output}", file=sys.stderr)
|
||||
else:
|
||||
print(payload)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
2
.github/workflows/ci-check.yml
vendored
2
.github/workflows/ci-check.yml
vendored
@@ -38,7 +38,7 @@ jobs:
|
||||
- name: Install ROS dependencies, uv and unilabos-msgs
|
||||
run: |
|
||||
echo Installing ROS dependencies...
|
||||
mamba install -n check-env conda-forge::uv conda-forge::opencv robostack-staging::ros-humble-ros-core robostack-staging::ros-humble-action-msgs robostack-staging::ros-humble-std-msgs robostack-staging::ros-humble-geometry-msgs robostack-staging::ros-humble-control-msgs robostack-staging::ros-humble-nav2-msgs uni-lab::ros-humble-unilabos-msgs robostack-staging::ros-humble-cv-bridge robostack-staging::ros-humble-vision-opencv robostack-staging::ros-humble-tf-transformations robostack-staging::ros-humble-moveit-msgs robostack-staging::ros-humble-tf2-ros robostack-staging::ros-humble-tf2-ros-py conda-forge::transforms3d -c robostack-staging -c conda-forge -c uni-lab -y
|
||||
mamba install -n check-env --override-channels -c robostack-staging -c conda-forge -c uni-lab conda-forge::uv conda-forge::opencv robostack-staging::ros-humble-ros-core robostack-staging::ros-humble-action-msgs robostack-staging::ros-humble-std-msgs robostack-staging::ros-humble-geometry-msgs robostack-staging::ros-humble-control-msgs robostack-staging::ros-humble-nav2-msgs uni-lab::ros-humble-unilabos-msgs robostack-staging::ros-humble-cv-bridge robostack-staging::ros-humble-vision-opencv robostack-staging::ros-humble-tf-transformations robostack-staging::ros-humble-moveit-msgs robostack-staging::ros-humble-tf2-ros robostack-staging::ros-humble-tf2-ros-py conda-forge::transforms3d -y
|
||||
|
||||
- name: Install pip dependencies and unilabos
|
||||
run: |
|
||||
|
||||
77
.github/workflows/conda-pack-build.yml
vendored
77
.github/workflows/conda-pack-build.yml
vendored
@@ -1,6 +1,10 @@
|
||||
name: Build Conda-Pack Environment
|
||||
|
||||
on:
|
||||
# 在 UniLabOS Conda Build 成功上传后自动构建非全量 conda-pack
|
||||
workflow_run:
|
||||
workflows: ["UniLabOS Conda Build"]
|
||||
types: [completed]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
branch:
|
||||
@@ -21,6 +25,16 @@ on:
|
||||
|
||||
jobs:
|
||||
build-conda-pack:
|
||||
if: |
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(
|
||||
github.event_name == 'workflow_run' &&
|
||||
github.event.workflow_run.conclusion == 'success' &&
|
||||
github.event.workflow_run.event == 'workflow_run'
|
||||
)
|
||||
env:
|
||||
BUILD_FULL: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.build_full == 'true' }}
|
||||
PACKAGE_REF: ${{ github.event.inputs.branch || github.event.workflow_run.head_sha || github.ref_name }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -54,7 +68,9 @@ jobs:
|
||||
id: should_build
|
||||
shell: bash
|
||||
run: |
|
||||
if [[ -z "${{ github.event.inputs.platforms }}" ]]; then
|
||||
if [[ "${{ github.event_name }}" != "workflow_dispatch" ]]; then
|
||||
echo "should_build=true" >> $GITHUB_OUTPUT
|
||||
elif [[ -z "${{ github.event.inputs.platforms }}" ]]; then
|
||||
echo "should_build=true" >> $GITHUB_OUTPUT
|
||||
elif [[ "${{ github.event.inputs.platforms }}" == *"${{ matrix.platform }}"* ]]; then
|
||||
echo "should_build=true" >> $GITHUB_OUTPUT
|
||||
@@ -65,7 +81,7 @@ jobs:
|
||||
- uses: actions/checkout@v6
|
||||
if: steps.should_build.outputs.should_build == 'true'
|
||||
with:
|
||||
ref: ${{ github.event.inputs.branch }}
|
||||
ref: ${{ github.event.inputs.branch || github.event.workflow_run.head_sha || github.ref }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Miniforge (with mamba)
|
||||
@@ -75,7 +91,7 @@ jobs:
|
||||
miniforge-version: latest
|
||||
use-mamba: true
|
||||
python-version: '3.11.14'
|
||||
channels: conda-forge,robostack-staging,uni-lab,defaults
|
||||
channels: conda-forge,robostack-staging,uni-lab
|
||||
channel-priority: flexible
|
||||
activate-environment: unilab
|
||||
auto-update-conda: false
|
||||
@@ -86,13 +102,13 @@ jobs:
|
||||
run: |
|
||||
echo Installing unilabos and dependencies to unilab environment...
|
||||
echo Using mamba for faster and more reliable dependency resolution...
|
||||
echo Build full: ${{ github.event.inputs.build_full }}
|
||||
if "${{ github.event.inputs.build_full }}"=="true" (
|
||||
echo Build full: ${{ env.BUILD_FULL }}
|
||||
if "${{ env.BUILD_FULL }}"=="true" (
|
||||
echo Installing unilabos-full ^(complete package^)...
|
||||
mamba install -n unilab uni-lab::unilabos-full conda-pack -c uni-lab -c robostack-staging -c conda-forge -y
|
||||
mamba install -n unilab --override-channels -c uni-lab -c robostack-staging -c conda-forge uni-lab::unilabos-full conda-pack zstandard -y
|
||||
) else (
|
||||
echo Installing unilabos ^(minimal package^)...
|
||||
mamba install -n unilab uni-lab::unilabos conda-pack -c uni-lab -c robostack-staging -c conda-forge -y
|
||||
mamba install -n unilab --override-channels -c uni-lab -c robostack-staging -c conda-forge uni-lab::unilabos conda-pack zstandard -y
|
||||
)
|
||||
|
||||
- name: Install conda-pack, unilabos and dependencies (Unix)
|
||||
@@ -101,13 +117,13 @@ jobs:
|
||||
run: |
|
||||
echo "Installing unilabos and dependencies to unilab environment..."
|
||||
echo "Using mamba for faster and more reliable dependency resolution..."
|
||||
echo "Build full: ${{ github.event.inputs.build_full }}"
|
||||
if [[ "${{ github.event.inputs.build_full }}" == "true" ]]; then
|
||||
echo "Build full: ${{ env.BUILD_FULL }}"
|
||||
if [[ "${{ env.BUILD_FULL }}" == "true" ]]; then
|
||||
echo "Installing unilabos-full (complete package)..."
|
||||
mamba install -n unilab uni-lab::unilabos-full conda-pack -c uni-lab -c robostack-staging -c conda-forge -y
|
||||
mamba install -n unilab --override-channels -c uni-lab -c robostack-staging -c conda-forge uni-lab::unilabos-full conda-pack zstandard -y
|
||||
else
|
||||
echo "Installing unilabos (minimal package)..."
|
||||
mamba install -n unilab uni-lab::unilabos conda-pack -c uni-lab -c robostack-staging -c conda-forge -y
|
||||
mamba install -n unilab --override-channels -c uni-lab -c robostack-staging -c conda-forge uni-lab::unilabos conda-pack zstandard -y
|
||||
fi
|
||||
|
||||
- name: Get latest ros-humble-unilabos-msgs version (Windows)
|
||||
@@ -134,27 +150,27 @@ jobs:
|
||||
if: steps.should_build.outputs.should_build == 'true' && matrix.platform == 'win-64'
|
||||
run: |
|
||||
echo Checking for available ros-humble-unilabos-msgs versions...
|
||||
mamba search ros-humble-unilabos-msgs -c uni-lab -c robostack-staging -c conda-forge || echo Search completed
|
||||
mamba search --override-channels -c uni-lab -c robostack-staging -c conda-forge ros-humble-unilabos-msgs || echo Search completed
|
||||
echo.
|
||||
echo Updating ros-humble-unilabos-msgs to latest version...
|
||||
mamba update -n unilab ros-humble-unilabos-msgs -c uni-lab -c robostack-staging -c conda-forge -y || echo Already at latest version
|
||||
mamba update -n unilab --override-channels -c uni-lab -c robostack-staging -c conda-forge ros-humble-unilabos-msgs -y || echo Already at latest version
|
||||
|
||||
- name: Check for newer ros-humble-unilabos-msgs (Unix)
|
||||
if: steps.should_build.outputs.should_build == 'true' && matrix.platform != 'win-64'
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Checking for available ros-humble-unilabos-msgs versions..."
|
||||
mamba search ros-humble-unilabos-msgs -c uni-lab -c robostack-staging -c conda-forge || echo "Search completed"
|
||||
mamba search --override-channels -c uni-lab -c robostack-staging -c conda-forge ros-humble-unilabos-msgs || echo "Search completed"
|
||||
echo ""
|
||||
echo "Updating ros-humble-unilabos-msgs to latest version..."
|
||||
mamba update -n unilab ros-humble-unilabos-msgs -c uni-lab -c robostack-staging -c conda-forge -y || echo "Already at latest version"
|
||||
mamba update -n unilab --override-channels -c uni-lab -c robostack-staging -c conda-forge ros-humble-unilabos-msgs -y || echo "Already at latest version"
|
||||
|
||||
- name: Install latest unilabos from source (Windows)
|
||||
if: steps.should_build.outputs.should_build == 'true' && matrix.platform == 'win-64'
|
||||
run: |
|
||||
echo Uninstalling existing unilabos...
|
||||
mamba run -n unilab pip uninstall unilabos -y || echo unilabos not installed via pip
|
||||
echo Installing unilabos from source (branch: ${{ github.event.inputs.branch }})...
|
||||
echo Installing unilabos from source (ref: ${{ env.PACKAGE_REF }})...
|
||||
mamba run -n unilab pip install .
|
||||
echo Verifying installation...
|
||||
mamba run -n unilab pip show unilabos
|
||||
@@ -165,7 +181,7 @@ jobs:
|
||||
run: |
|
||||
echo "Uninstalling existing unilabos..."
|
||||
mamba run -n unilab pip uninstall unilabos -y || echo "unilabos not installed via pip"
|
||||
echo "Installing unilabos from source (branch: ${{ github.event.inputs.branch }})..."
|
||||
echo "Installing unilabos from source (ref: ${{ env.PACKAGE_REF }})..."
|
||||
mamba run -n unilab pip install .
|
||||
echo "Verifying installation..."
|
||||
mamba run -n unilab pip show unilabos
|
||||
@@ -226,7 +242,9 @@ jobs:
|
||||
if: steps.should_build.outputs.should_build == 'true' && matrix.platform == 'win-64'
|
||||
run: |
|
||||
echo Packing unilab environment with conda-pack...
|
||||
mamba activate unilab && conda pack -n unilab -o unilab-env-${{ matrix.platform }}.tar.gz --ignore-missing-files
|
||||
for /f "delims=" %%i in ('mamba run -n unilab python -c "import os; print(os.environ['CONDA_PREFIX'])"') do set "UNILAB_PREFIX=%%i"
|
||||
echo Packing environment at: %UNILAB_PREFIX%
|
||||
mamba run -n unilab conda-pack -p "%UNILAB_PREFIX%" -o unilab-env-${{ matrix.platform }}.tar.gz --ignore-missing-files
|
||||
echo Pack file created:
|
||||
dir unilab-env-${{ matrix.platform }}.tar.gz
|
||||
|
||||
@@ -235,8 +253,9 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Packing unilab environment with conda-pack..."
|
||||
mamba install conda-pack -c conda-forge -y
|
||||
conda pack -n unilab -o unilab-env-${{ matrix.platform }}.tar.gz --ignore-missing-files
|
||||
UNILAB_PREFIX="$(mamba run -n unilab python -c 'import os; print(os.environ["CONDA_PREFIX"])')"
|
||||
echo "Packing environment at: $UNILAB_PREFIX"
|
||||
mamba run -n unilab conda-pack -p "$UNILAB_PREFIX" -o unilab-env-${{ matrix.platform }}.tar.gz --ignore-missing-files
|
||||
echo "Pack file created:"
|
||||
ls -lh unilab-env-${{ matrix.platform }}.tar.gz
|
||||
|
||||
@@ -267,7 +286,7 @@ jobs:
|
||||
|
||||
rem Create README using Python script
|
||||
echo Creating: README.txt
|
||||
python scripts\create_readme.py ${{ matrix.platform }} ${{ github.event.inputs.branch }} dist-package\README.txt
|
||||
python scripts\create_readme.py ${{ matrix.platform }} ${{ env.PACKAGE_REF }} dist-package\README.txt
|
||||
|
||||
echo.
|
||||
echo Distribution package contents:
|
||||
@@ -303,7 +322,7 @@ jobs:
|
||||
|
||||
# Create README using Python script
|
||||
echo "Creating: README.txt"
|
||||
python scripts/create_readme.py ${{ matrix.platform }} ${{ github.event.inputs.branch }} dist-package/README.txt
|
||||
python scripts/create_readme.py ${{ matrix.platform }} ${{ env.PACKAGE_REF }} dist-package/README.txt
|
||||
|
||||
echo ""
|
||||
echo "Distribution package contents:"
|
||||
@@ -314,7 +333,7 @@ jobs:
|
||||
if: steps.should_build.outputs.should_build == 'true'
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: unilab-pack-${{ matrix.platform }}-${{ github.event.inputs.branch }}
|
||||
name: unilab-pack-${{ matrix.platform }}-${{ env.PACKAGE_REF }}
|
||||
path: dist-package/
|
||||
retention-days: 90
|
||||
if-no-files-found: error
|
||||
@@ -326,9 +345,9 @@ jobs:
|
||||
echo Build Summary
|
||||
echo ==========================================
|
||||
echo Platform: ${{ matrix.platform }}
|
||||
echo Branch: ${{ github.event.inputs.branch }}
|
||||
echo Branch: ${{ env.PACKAGE_REF }}
|
||||
echo Python version: 3.11.14
|
||||
if "${{ github.event.inputs.build_full }}"=="true" (
|
||||
if "${{ env.BUILD_FULL }}"=="true" (
|
||||
echo Package: unilabos-full ^(complete^)
|
||||
) else (
|
||||
echo Package: unilabos ^(minimal^)
|
||||
@@ -337,7 +356,7 @@ jobs:
|
||||
echo Distribution package contents:
|
||||
dir dist-package
|
||||
echo.
|
||||
echo Artifact name: unilab-pack-${{ matrix.platform }}-${{ github.event.inputs.branch }}
|
||||
echo Artifact name: unilab-pack-${{ matrix.platform }}-${{ env.PACKAGE_REF }}
|
||||
echo.
|
||||
echo After download, extract the ZIP and run:
|
||||
echo install_unilab.bat
|
||||
@@ -351,9 +370,9 @@ jobs:
|
||||
echo "Build Summary"
|
||||
echo "=========================================="
|
||||
echo "Platform: ${{ matrix.platform }}"
|
||||
echo "Branch: ${{ github.event.inputs.branch }}"
|
||||
echo "Branch: ${{ env.PACKAGE_REF }}"
|
||||
echo "Python version: 3.11.14"
|
||||
if [[ "${{ github.event.inputs.build_full }}" == "true" ]]; then
|
||||
if [[ "${{ env.BUILD_FULL }}" == "true" ]]; then
|
||||
echo "Package: unilabos-full (complete)"
|
||||
else
|
||||
echo "Package: unilabos (minimal)"
|
||||
@@ -362,7 +381,7 @@ jobs:
|
||||
echo "Distribution package contents:"
|
||||
ls -lh dist-package/
|
||||
echo ""
|
||||
echo "Artifact name: unilab-pack-${{ matrix.platform }}-${{ github.event.inputs.branch }}"
|
||||
echo "Artifact name: unilab-pack-${{ matrix.platform }}-${{ env.PACKAGE_REF }}"
|
||||
echo ""
|
||||
echo "After download:"
|
||||
echo " install_unilab.sh"
|
||||
|
||||
4
.github/workflows/deploy-docs.yml
vendored
4
.github/workflows/deploy-docs.yml
vendored
@@ -56,7 +56,7 @@ jobs:
|
||||
miniforge-version: latest
|
||||
use-mamba: true
|
||||
python-version: '3.11.14'
|
||||
channels: conda-forge,robostack-staging,uni-lab,defaults
|
||||
channels: conda-forge,robostack-staging,uni-lab
|
||||
channel-priority: flexible
|
||||
activate-environment: unilab
|
||||
auto-update-conda: false
|
||||
@@ -66,7 +66,7 @@ jobs:
|
||||
run: |
|
||||
echo "Installing unilabos and dependencies to unilab environment..."
|
||||
echo "Using mamba for faster and more reliable dependency resolution..."
|
||||
mamba install -n unilab uni-lab::unilabos -c uni-lab -c robostack-staging -c conda-forge -y
|
||||
mamba install -n unilab --override-channels -c uni-lab -c robostack-staging -c conda-forge uni-lab::unilabos -y
|
||||
|
||||
- name: Install latest unilabos from source
|
||||
run: |
|
||||
|
||||
22
.github/workflows/multi-platform-build.yml
vendored
22
.github/workflows/multi-platform-build.yml
vendored
@@ -10,6 +10,9 @@ on:
|
||||
# 支持 tag 推送(不依赖 CI Check)
|
||||
push:
|
||||
tags: ['v*']
|
||||
# GitHub Release 发布时自动构建并上传
|
||||
release:
|
||||
types: [published]
|
||||
# 手动触发
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
@@ -80,7 +83,7 @@ jobs:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
# 如果是 workflow_run 触发,使用触发 CI Check 的 commit
|
||||
ref: ${{ github.event.workflow_run.head_sha || github.ref }}
|
||||
ref: ${{ github.event.workflow_run.head_sha || github.event.release.tag_name || github.ref }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Check if platform should be built
|
||||
@@ -96,12 +99,13 @@ jobs:
|
||||
echo "should_build=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Setup Miniconda
|
||||
- name: Setup Miniforge
|
||||
if: steps.should_build.outputs.should_build == 'true'
|
||||
uses: conda-incubator/setup-miniconda@v3
|
||||
with:
|
||||
miniconda-version: 'latest'
|
||||
channels: conda-forge,robostack-staging,defaults
|
||||
miniforge-version: latest
|
||||
use-mamba: true
|
||||
channels: conda-forge,robostack-staging
|
||||
channel-priority: strict
|
||||
activate-environment: build-env
|
||||
auto-update-conda: false
|
||||
@@ -110,7 +114,7 @@ jobs:
|
||||
- name: Install rattler-build and anaconda-client
|
||||
if: steps.should_build.outputs.should_build == 'true'
|
||||
run: |
|
||||
conda install -c conda-forge rattler-build anaconda-client
|
||||
mamba install --override-channels -c conda-forge rattler-build anaconda-client -y
|
||||
|
||||
- name: Show environment info
|
||||
if: steps.should_build.outputs.should_build == 'true'
|
||||
@@ -157,7 +161,13 @@ jobs:
|
||||
retention-days: 30
|
||||
|
||||
- name: Upload to Anaconda.org (unilab organization)
|
||||
if: steps.should_build.outputs.should_build == 'true' && github.event.inputs.upload_to_anaconda == 'true'
|
||||
if: |
|
||||
steps.should_build.outputs.should_build == 'true' &&
|
||||
(
|
||||
github.event_name == 'release' ||
|
||||
startsWith(github.ref, 'refs/tags/') ||
|
||||
github.event.inputs.upload_to_anaconda == 'true'
|
||||
)
|
||||
run: |
|
||||
for package in $(find ./output -name "*.conda"); do
|
||||
echo "Uploading $package to unilab organization..."
|
||||
|
||||
57
.github/workflows/unilabos-conda-build.yml
vendored
57
.github/workflows/unilabos-conda-build.yml
vendored
@@ -1,14 +1,10 @@
|
||||
name: UniLabOS Conda Build
|
||||
|
||||
on:
|
||||
# 在 CI Check 成功后自动触发
|
||||
# 在 Multi-Platform Conda Build 成功上传 msgs 后自动触发
|
||||
workflow_run:
|
||||
workflows: ["CI Check"]
|
||||
workflows: ["Multi-Platform Conda Build"]
|
||||
types: [completed]
|
||||
branches: [main, dev]
|
||||
# 标签推送时直接触发(发布版本)
|
||||
push:
|
||||
tags: ['v*']
|
||||
# 手动触发
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
@@ -33,30 +29,30 @@ on:
|
||||
type: boolean
|
||||
|
||||
jobs:
|
||||
# 等待 CI Check 完成的 job (仅用于 workflow_run 触发)
|
||||
wait-for-ci:
|
||||
# 等待上游 msgs 构建完成的 job (仅用于 workflow_run 触发)
|
||||
wait-for-upstream:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'workflow_run'
|
||||
outputs:
|
||||
should_continue: ${{ steps.check.outputs.should_continue }}
|
||||
steps:
|
||||
- name: Check CI status
|
||||
- name: Check upstream workflow status
|
||||
id: check
|
||||
run: |
|
||||
if [[ "${{ github.event.workflow_run.conclusion }}" == "success" ]]; then
|
||||
if [[ "${{ github.event.workflow_run.conclusion }}" == "success" && ( "${{ github.event.workflow_run.event }}" == "release" || "${{ github.event.workflow_run.event }}" == "push" ) ]]; then
|
||||
echo "should_continue=true" >> $GITHUB_OUTPUT
|
||||
echo "CI Check passed, proceeding with build"
|
||||
echo "Multi-Platform Conda Build passed for release/tag, proceeding with UniLabOS build"
|
||||
else
|
||||
echo "should_continue=false" >> $GITHUB_OUTPUT
|
||||
echo "CI Check did not succeed (status: ${{ github.event.workflow_run.conclusion }}), skipping build"
|
||||
echo "Upstream workflow is not a successful release/tag build (status: ${{ github.event.workflow_run.conclusion }}, event: ${{ github.event.workflow_run.event }}), skipping build"
|
||||
fi
|
||||
|
||||
build:
|
||||
needs: [wait-for-ci]
|
||||
# 运行条件:workflow_run 触发且 CI 成功,或者其他触发方式
|
||||
needs: [wait-for-upstream]
|
||||
# 运行条件:workflow_run 触发且上游成功,或者手动触发
|
||||
if: |
|
||||
always() &&
|
||||
(needs.wait-for-ci.result == 'skipped' || needs.wait-for-ci.outputs.should_continue == 'true')
|
||||
(needs.wait-for-upstream.result == 'skipped' || needs.wait-for-upstream.outputs.should_continue == 'true')
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -79,7 +75,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
# 如果是 workflow_run 触发,使用触发 CI Check 的 commit
|
||||
# 如果是 workflow_run 触发,使用上游 conda 包构建的 commit
|
||||
ref: ${{ github.event.workflow_run.head_sha || github.ref }}
|
||||
fetch-depth: 0
|
||||
|
||||
@@ -96,12 +92,13 @@ jobs:
|
||||
echo "should_build=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Setup Miniconda
|
||||
- name: Setup Miniforge
|
||||
if: steps.should_build.outputs.should_build == 'true'
|
||||
uses: conda-incubator/setup-miniconda@v3
|
||||
with:
|
||||
miniconda-version: 'latest'
|
||||
channels: conda-forge,robostack-staging,uni-lab,defaults
|
||||
miniforge-version: latest
|
||||
use-mamba: true
|
||||
channels: conda-forge,robostack-staging,uni-lab
|
||||
channel-priority: strict
|
||||
activate-environment: build-env
|
||||
auto-update-conda: false
|
||||
@@ -110,7 +107,7 @@ jobs:
|
||||
- name: Install rattler-build and anaconda-client
|
||||
if: steps.should_build.outputs.should_build == 'true'
|
||||
run: |
|
||||
conda install -c conda-forge rattler-build anaconda-client
|
||||
mamba install --override-channels -c conda-forge rattler-build anaconda-client -y
|
||||
|
||||
- name: Show environment info
|
||||
if: steps.should_build.outputs.should_build == 'true'
|
||||
@@ -119,11 +116,11 @@ jobs:
|
||||
conda list | grep -E "(rattler-build|anaconda-client)"
|
||||
echo "Platform: ${{ matrix.platform }}"
|
||||
echo "OS: ${{ matrix.os }}"
|
||||
echo "Build full package: ${{ github.event.inputs.build_full || 'false' }}"
|
||||
echo "Build full package: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.build_full == 'true' }}"
|
||||
echo "Building packages:"
|
||||
echo " - unilabos-env (environment dependencies)"
|
||||
echo " - unilabos (with pip package)"
|
||||
if [[ "${{ github.event.inputs.build_full }}" == "true" ]]; then
|
||||
if [[ "${{ github.event_name == 'workflow_dispatch' && github.event.inputs.build_full == 'true' }}" == "true" ]]; then
|
||||
echo " - unilabos-full (complete package)"
|
||||
fi
|
||||
|
||||
@@ -134,7 +131,12 @@ jobs:
|
||||
rattler-build build -r .conda/environment/recipe.yaml -c uni-lab -c robostack-staging -c conda-forge
|
||||
|
||||
- name: Upload unilabos-env to Anaconda.org (if enabled)
|
||||
if: steps.should_build.outputs.should_build == 'true' && github.event.inputs.upload_to_anaconda == 'true'
|
||||
if: |
|
||||
steps.should_build.outputs.should_build == 'true' &&
|
||||
(
|
||||
github.event_name == 'workflow_run' ||
|
||||
github.event.inputs.upload_to_anaconda == 'true'
|
||||
)
|
||||
run: |
|
||||
echo "Uploading unilabos-env to uni-lab organization..."
|
||||
for package in $(find ./output -name "unilabos-env*.conda"); do
|
||||
@@ -149,7 +151,12 @@ jobs:
|
||||
rattler-build build -r .conda/base/recipe.yaml -c uni-lab -c robostack-staging -c conda-forge --channel ./output
|
||||
|
||||
- name: Upload unilabos to Anaconda.org (if enabled)
|
||||
if: steps.should_build.outputs.should_build == 'true' && github.event.inputs.upload_to_anaconda == 'true'
|
||||
if: |
|
||||
steps.should_build.outputs.should_build == 'true' &&
|
||||
(
|
||||
github.event_name == 'workflow_run' ||
|
||||
github.event.inputs.upload_to_anaconda == 'true'
|
||||
)
|
||||
run: |
|
||||
echo "Uploading unilabos to uni-lab organization..."
|
||||
for package in $(find ./output -name "unilabos-0*.conda" -o -name "unilabos-[0-9]*.conda"); do
|
||||
@@ -159,6 +166,7 @@ jobs:
|
||||
- name: Build unilabos-full - Only when explicitly requested
|
||||
if: |
|
||||
steps.should_build.outputs.should_build == 'true' &&
|
||||
github.event_name == 'workflow_dispatch' &&
|
||||
github.event.inputs.build_full == 'true'
|
||||
run: |
|
||||
echo "Building unilabos-full package on ${{ matrix.platform }}..."
|
||||
@@ -167,6 +175,7 @@ jobs:
|
||||
- name: Upload unilabos-full to Anaconda.org (if enabled)
|
||||
if: |
|
||||
steps.should_build.outputs.should_build == 'true' &&
|
||||
github.event_name == 'workflow_dispatch' &&
|
||||
github.event.inputs.build_full == 'true' &&
|
||||
github.event.inputs.upload_to_anaconda == 'true'
|
||||
run: |
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
channel_sources:
|
||||
- robostack,robostack-staging,conda-forge,defaults
|
||||
- robostack,robostack-staging,conda-forge
|
||||
|
||||
gazebo:
|
||||
- '11'
|
||||
|
||||
@@ -10,29 +10,170 @@ import shutil
|
||||
import sys
|
||||
|
||||
|
||||
_PATCH_MARKER = "# UniLabOS DLL Patch"
|
||||
_PATCH_END_MARKER = "# End UniLabOS DLL Patch"
|
||||
|
||||
# 75 = EX_TEMPFAIL: 临时失败、重试即可,避免与业务退出码冲突
|
||||
_RESTART_EXIT_CODE = 75
|
||||
|
||||
|
||||
def _build_dll_patch(lib_bin: str, preload_pyd: str = "") -> str:
|
||||
"""生成一段加在目标文件顶部的 DLL 加载补丁源码。
|
||||
|
||||
- 始终把 ``lib_bin`` 加入 DLL 搜索路径,并把 handle 挂在模块属性上,
|
||||
防止 GC 清掉搜索路径(``os.add_dll_directory`` 的句柄被回收时
|
||||
目录会被移除)。
|
||||
- 可选地用 ``ctypes.CDLL`` 预加载一个 .pyd,把它的依赖 DLL 提前装入
|
||||
进程内存,作为 ``rclpy._rclpy_pybind11`` 这类首次加载点的兜底。
|
||||
"""
|
||||
# 用 repr() 序列化路径:Python 解析 repr 的结果会还原成原始字符串,
|
||||
# 不需要也不能再叠加 raw-string 前缀(叠了反而会让 \\ 变成两个反斜杠)。
|
||||
lines = [
|
||||
_PATCH_MARKER,
|
||||
"import os as _ulab_os",
|
||||
f"_ulab_p = {lib_bin!r}",
|
||||
'if hasattr(_ulab_os, "add_dll_directory") and _ulab_os.path.isdir(_ulab_p):',
|
||||
" try: _UNILAB_DLL_HANDLE = _ulab_os.add_dll_directory(_ulab_p)",
|
||||
" except Exception: _UNILAB_DLL_HANDLE = None",
|
||||
]
|
||||
if preload_pyd:
|
||||
lines.extend(
|
||||
[
|
||||
"import ctypes as _ulab_ctypes",
|
||||
f"try: _ulab_ctypes.CDLL({preload_pyd!r})",
|
||||
"except Exception: pass",
|
||||
]
|
||||
)
|
||||
lines.append(_PATCH_END_MARKER)
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
def _apply_dll_patch(file_path: str, lib_bin: str, preload_pyd: str = "") -> bool:
|
||||
"""把 DLL 补丁前置到 ``file_path``。文件不存在或已打过补丁则返回 False。"""
|
||||
if not os.path.isfile(file_path):
|
||||
return False
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
if _PATCH_MARKER in content:
|
||||
return False
|
||||
shutil.copy2(file_path, file_path + ".bak")
|
||||
with open(file_path, "w", encoding="utf-8") as f:
|
||||
f.write(_build_dll_patch(lib_bin, preload_pyd) + content)
|
||||
return True
|
||||
|
||||
|
||||
def _print_restart_banner(patched_files):
|
||||
"""打印重启提示并以 EX_TEMPFAIL 退出。
|
||||
|
||||
- 不使用 ANSI 颜色码:Windows 旧版 cmd / PowerShell 5 默认不开 VT 处理,
|
||||
会把 ``\\033[1;33m`` 当做字面字符显示,反而让用户看不到正文。
|
||||
- 同时写入 stderr 与 stdout:某些上层 launcher / supervisor 只重定向
|
||||
其中一路,写两遍能保证用户至少看到一份。
|
||||
- 写入前防御性把流切到 UTF-8 with replace:``main.py`` 里已经做过一次,
|
||||
但本模块也可能被绕过 ``main.py`` 的代码路径直接 import;reconfigure
|
||||
失败也只是退回 errors=replace,不影响整体流程。
|
||||
"""
|
||||
if sys.platform == "win32":
|
||||
for _stream in (sys.stdout, sys.stderr):
|
||||
try:
|
||||
_stream.reconfigure(encoding="utf-8", errors="replace") # type: ignore[attr-defined]
|
||||
except (AttributeError, OSError):
|
||||
pass
|
||||
|
||||
bar = "#" * 78
|
||||
files_lines = [f"[UniLabOS] - {p}" for p in patched_files]
|
||||
body = "\n".join(
|
||||
[
|
||||
"",
|
||||
bar,
|
||||
bar,
|
||||
"##",
|
||||
"## [UniLabOS] Windows + conda 下检测到 DLL 加载失败,已自动打补丁。",
|
||||
"## [UniLabOS] DLL load failure detected on Windows + conda;",
|
||||
"## [UniLabOS] the following files have been auto-patched:",
|
||||
"##",
|
||||
*[f"## {line}" for line in files_lines],
|
||||
"##",
|
||||
"## [UniLabOS] 当前进程的 rclpy 状态已损坏,补丁需要在新进程才生效。",
|
||||
"## [UniLabOS] The current process is unusable; the patch only takes",
|
||||
"## [UniLabOS] effect on a fresh process.",
|
||||
"##",
|
||||
"## >>> 请重新运行刚才的命令 / Please re-run the same command. <<<",
|
||||
"##",
|
||||
bar,
|
||||
bar,
|
||||
"",
|
||||
]
|
||||
)
|
||||
|
||||
for stream in (sys.stderr, sys.stdout):
|
||||
try:
|
||||
stream.write(body)
|
||||
stream.flush()
|
||||
except Exception:
|
||||
try:
|
||||
print(body, file=stream)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
sys.exit(_RESTART_EXIT_CODE)
|
||||
|
||||
|
||||
def patch_rclpy_dll_windows():
|
||||
"""在 Windows + conda 环境下为 rclpy 打 DLL 加载补丁"""
|
||||
"""在 Windows + conda 环境下修复 rclpy / rosidl typesupport 的 DLL 加载。
|
||||
|
||||
背景:conda 安装的 ros 系列包,其原生扩展依赖 ``$CONDA_PREFIX/Library/bin``
|
||||
下的 DLL;只有 conda 环境被正确激活、且 PATH 中含 ``Library/bin`` 时,
|
||||
``os.add_dll_directory`` 才能找到它们。当从快捷方式 / IDE / 子进程 /
|
||||
没激活的 shell 启动 ``unilab`` 时,会出现 ``DLL load failed``。
|
||||
|
||||
本函数会:
|
||||
1) 修补 ``rclpy/impl/implementation_singleton.py`` —— rclpy 自身的 C 扩展入口;
|
||||
2) 修补 ``rpyutils/add_dll_directories.py`` —— 所有 ``*_s__rosidl_typesupport_c.pyd``
|
||||
(``geometry_msgs`` / ``std_msgs`` / ``sensor_msgs`` 等)的统一加载入口。
|
||||
|
||||
打完补丁后**必须重启进程**才能生效(当前进程的 rclpy 已经发生过
|
||||
``ImportError``,子模块仍处于损坏状态)。因此函数会主动退出,并在
|
||||
stdout/stderr 同时打印明显的重启提示,避免用户被后续报错淹没。
|
||||
"""
|
||||
if sys.platform != "win32" or not os.environ.get("CONDA_PREFIX"):
|
||||
return
|
||||
|
||||
try:
|
||||
import rclpy
|
||||
import rclpy # noqa: F401
|
||||
|
||||
return
|
||||
except ImportError as e:
|
||||
if not str(e).startswith("DLL load failed"):
|
||||
return
|
||||
|
||||
cp = os.environ["CONDA_PREFIX"]
|
||||
impl = os.path.join(cp, "Lib", "site-packages", "rclpy", "impl", "implementation_singleton.py")
|
||||
pyd = glob.glob(os.path.join(cp, "Lib", "site-packages", "rclpy", "_rclpy_pybind11*.pyd"))
|
||||
if not os.path.exists(impl) or not pyd:
|
||||
lib_bin = os.path.join(cp, "Library", "bin")
|
||||
site_packages = os.path.join(cp, "Lib", "site-packages")
|
||||
if not os.path.isdir(lib_bin):
|
||||
return
|
||||
with open(impl, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
lib_bin = os.path.join(cp, "Library", "bin").replace("\\", "/")
|
||||
patch = f'# UniLabOS DLL Patch\nimport os,ctypes\nos.add_dll_directory("{lib_bin}") if hasattr(os,"add_dll_directory") else None\ntry: ctypes.CDLL("{pyd[0].replace(chr(92),"/")}")\nexcept: pass\n# End Patch\n'
|
||||
shutil.copy2(impl, impl + ".bak")
|
||||
with open(impl, "w", encoding="utf-8") as f:
|
||||
f.write(patch + content)
|
||||
|
||||
patched = []
|
||||
|
||||
# 1) rclpy 自身的入口
|
||||
rclpy_impl = os.path.join(site_packages, "rclpy", "impl", "implementation_singleton.py")
|
||||
rclpy_pyd_matches = glob.glob(os.path.join(site_packages, "rclpy", "_rclpy_pybind11*.pyd"))
|
||||
rclpy_pyd = rclpy_pyd_matches[0] if rclpy_pyd_matches else ""
|
||||
if rclpy_pyd and _apply_dll_patch(rclpy_impl, lib_bin, preload_pyd=rclpy_pyd):
|
||||
patched.append(rclpy_impl)
|
||||
|
||||
# 2) rpyutils —— 所有 rosidl typesupport pyd 的加载点;放在 rclpy 之后
|
||||
# 例:geometry_msgs/geometry_msgs_s__rosidl_typesupport_c.pyd
|
||||
rpyutils_dll = os.path.join(site_packages, "rpyutils", "add_dll_directories.py")
|
||||
if _apply_dll_patch(rpyutils_dll, lib_bin):
|
||||
patched.append(rpyutils_dll)
|
||||
|
||||
if not patched:
|
||||
# 已经打过补丁但 rclpy 仍然加载失败:原因不是缺 DLL 搜索路径,
|
||||
# 不要再次打补丁污染文件,让上层看到真实的 ImportError。
|
||||
return
|
||||
|
||||
_print_restart_banner(patched)
|
||||
|
||||
|
||||
patch_rclpy_dll_windows()
|
||||
|
||||
@@ -51,14 +51,18 @@ Qone_nmr:
|
||||
properties:
|
||||
check_interval:
|
||||
default: 60
|
||||
description: 检查间隔时间(秒),默认60秒
|
||||
type: string
|
||||
expected_count:
|
||||
default: 1
|
||||
description: 期望生成的.nmr文件数量,默认1个
|
||||
type: string
|
||||
monitor_dir:
|
||||
description: 要监督的目录路径,如果未指定则使用self.monitor_directory
|
||||
type: string
|
||||
stability_checks:
|
||||
default: 3
|
||||
description: 文件大小稳定性检查次数,默认3次
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
@@ -85,11 +89,14 @@ Qone_nmr:
|
||||
goal:
|
||||
properties:
|
||||
output_dir:
|
||||
description: 输出目录(如果未指定,使用self.output_directory)
|
||||
type: string
|
||||
string_list:
|
||||
description: 字符串列表
|
||||
type: string
|
||||
txt_encoding:
|
||||
default: utf-8
|
||||
description: 文件编码
|
||||
type: string
|
||||
required:
|
||||
- string_list
|
||||
@@ -151,6 +158,13 @@ Qone_nmr:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
string:
|
||||
description: '包含多个字符串的输入数据,支持两种格式:
|
||||
|
||||
1. 逗号分隔:如 "A 1 B 2 C 3, X 10 Y 20 Z 30"
|
||||
|
||||
2. 换行分隔:如 "A 1 B 2 C 3
|
||||
|
||||
X 10 Y 20 Z 30"'
|
||||
type: string
|
||||
title: StrSingleInput_Goal
|
||||
type: object
|
||||
|
||||
@@ -491,14 +491,17 @@ bioyond_cell:
|
||||
goal:
|
||||
properties:
|
||||
material_names:
|
||||
description: 物料名称列表;默认使用 [LiPF6, LiDFOB, DTD, LiFSI, LiPO2F2]
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type_id:
|
||||
default: 3a190ca0-b2f6-9aeb-8067-547e72c11469
|
||||
description: 物料类型ID
|
||||
type: string
|
||||
warehouse_name:
|
||||
default: 粉末加样头堆栈
|
||||
description: 目标仓库名(用于取位置信息)
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
@@ -527,12 +530,16 @@ bioyond_cell:
|
||||
goal:
|
||||
properties:
|
||||
location_name_or_id:
|
||||
description: 具体库位名称(如 A01)或库位 UUID,由用户指定。
|
||||
type: string
|
||||
material_name:
|
||||
description: 物料名称(会优先匹配配置模板)。
|
||||
type: string
|
||||
type_id:
|
||||
description: 物料类型 ID(若为空则尝试从配置推断)。
|
||||
type: string
|
||||
warehouse_name:
|
||||
description: 需要入库的仓库名称;若为空则仅创建不入库。
|
||||
type: string
|
||||
required:
|
||||
- material_name
|
||||
@@ -661,15 +668,20 @@ bioyond_cell:
|
||||
goal:
|
||||
properties:
|
||||
board_type:
|
||||
description: 板类型,如 "5ml分液瓶板"、"配液瓶(小)板"
|
||||
type: string
|
||||
bottle_type:
|
||||
description: 瓶类型,如 "5ml分液瓶"、"配液瓶(小)"
|
||||
type: string
|
||||
location_code:
|
||||
description: 库位编号,例如 "A01"
|
||||
type: string
|
||||
name:
|
||||
description: 物料名称
|
||||
type: string
|
||||
warehouse_name:
|
||||
default: 手动堆栈
|
||||
description: 仓库名称,默认为 "手动堆栈",支持 "自动堆栈-左"、"自动堆栈-右" 等
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
@@ -1956,19 +1968,19 @@ bioyond_cell:
|
||||
properties:
|
||||
source_wh_id:
|
||||
default: 3a19debc-84b4-0359-e2d4-b3beea49348b
|
||||
description: 来源仓库ID
|
||||
description: 来源仓库 Id (默认为3号仓库)
|
||||
type: string
|
||||
source_x:
|
||||
default: 1
|
||||
description: 来源位置X坐标
|
||||
description: 来源位置 X 坐标
|
||||
type: integer
|
||||
source_y:
|
||||
default: 1
|
||||
description: 来源位置Y坐标
|
||||
description: 来源位置 Y 坐标
|
||||
type: integer
|
||||
source_z:
|
||||
default: 1
|
||||
description: 来源位置Z坐标
|
||||
description: 来源位置 Z 坐标
|
||||
type: integer
|
||||
required: []
|
||||
type: object
|
||||
@@ -2061,9 +2073,11 @@ bioyond_cell:
|
||||
goal:
|
||||
properties:
|
||||
order_code:
|
||||
description: 任务编号
|
||||
type: string
|
||||
timeout:
|
||||
default: 36000
|
||||
description: 超时时间(秒)
|
||||
type: integer
|
||||
required:
|
||||
- order_code
|
||||
@@ -2092,12 +2106,15 @@ bioyond_cell:
|
||||
goal:
|
||||
properties:
|
||||
order_code:
|
||||
description: 任务编号
|
||||
type: string
|
||||
poll_interval:
|
||||
default: 0.5
|
||||
description: 轮询间隔(秒),默认 0.5 秒
|
||||
type: number
|
||||
timeout:
|
||||
default: 36000
|
||||
description: 超时时间(秒)
|
||||
type: integer
|
||||
required:
|
||||
- order_code
|
||||
@@ -2154,10 +2171,15 @@ bioyond_cell:
|
||||
config:
|
||||
properties:
|
||||
bioyond_config:
|
||||
description: '从 JSON 文件加载的 bioyond 配置字典
|
||||
|
||||
包含 api_host, api_key, HTTP_host, HTTP_port 等配置'
|
||||
type: object
|
||||
deck:
|
||||
description: Deck 配置(可选,会从 JSON 中自动处理)
|
||||
type: string
|
||||
protocol_type:
|
||||
description: 协议类型(可选)
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
|
||||
@@ -47,8 +47,10 @@ bioyond_dispensing_station:
|
||||
goal:
|
||||
properties:
|
||||
report_request:
|
||||
description: WorkstationReportRequest 对象,包含任务完成信息
|
||||
type: string
|
||||
used_materials:
|
||||
description: 物料使用记录列表
|
||||
type: string
|
||||
required:
|
||||
- report_request
|
||||
@@ -102,6 +104,7 @@ bioyond_dispensing_station:
|
||||
goal:
|
||||
properties:
|
||||
material_name:
|
||||
description: 物料名称
|
||||
type: string
|
||||
required:
|
||||
- material_name
|
||||
@@ -611,10 +614,10 @@ bioyond_dispensing_station:
|
||||
goal:
|
||||
properties:
|
||||
target_device_id:
|
||||
description: 目标反应站设备ID(从设备列表中选择,所有转移组都使用同一个目标设备)
|
||||
description: 目标反应站设备ID(所有转移组使用同一个设备)
|
||||
type: string
|
||||
transfer_groups:
|
||||
description: 转移任务组列表,每组包含物料名称、目标堆栈和目标库位,可以添加多组
|
||||
description: '转移任务组列表,每组包含:'
|
||||
type: array
|
||||
required:
|
||||
- target_device_id
|
||||
@@ -694,10 +697,13 @@ bioyond_dispensing_station:
|
||||
config:
|
||||
properties:
|
||||
config:
|
||||
description: 配置字典,应包含material_type_mappings等配置
|
||||
type: object
|
||||
deck:
|
||||
description: Deck对象
|
||||
type: string
|
||||
protocol_type:
|
||||
description: 协议类型(由ROS系统传递,此处忽略)
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
|
||||
@@ -150,15 +150,15 @@ coincellassemblyworkstation_device:
|
||||
properties:
|
||||
assembly_pressure:
|
||||
default: 4200
|
||||
description: 电池压制力(N)
|
||||
description: 电池压制力 (N)
|
||||
type: integer
|
||||
assembly_type:
|
||||
default: 7
|
||||
description: 组装类型(7=不用铝箔垫, 8=使用铝箔垫)
|
||||
description: 组装类型 (7=不用铝箔垫, 8=使用铝箔垫)
|
||||
type: integer
|
||||
battery_clean_ignore:
|
||||
default: false
|
||||
description: 是否忽略电池清洁步骤
|
||||
description: 是否忽略电池清洁
|
||||
type: boolean
|
||||
battery_pressure_mode:
|
||||
default: true
|
||||
@@ -166,29 +166,29 @@ coincellassemblyworkstation_device:
|
||||
type: boolean
|
||||
dual_drop_first_volume:
|
||||
default: 25
|
||||
description: 二次滴液第一次排液体积(μL)
|
||||
description: 二次滴液第一次排液体积 (μL)
|
||||
type: integer
|
||||
dual_drop_mode:
|
||||
default: false
|
||||
description: 电解液添加模式(false=单次滴液, true=二次滴液)
|
||||
description: 电解液添加模式 (False=单次滴液, True=二次滴液)
|
||||
type: boolean
|
||||
dual_drop_start_timing:
|
||||
default: false
|
||||
description: 二次滴液开始滴液时机(false=正极片前, true=正极片后)
|
||||
description: 二次滴液开始滴液时机 (False=正极片前, True=正极片后)
|
||||
type: boolean
|
||||
dual_drop_suction_timing:
|
||||
default: false
|
||||
description: 二次滴液吸液时机(false=正常吸液, true=先吸液)
|
||||
description: 二次滴液吸液时机 (False=正常吸液, True=先吸液)
|
||||
type: boolean
|
||||
elec_num:
|
||||
description: 电解液瓶数
|
||||
type: string
|
||||
elec_use_num:
|
||||
description: 每瓶电解液组装电池数
|
||||
description: 每瓶电解液组装的电池数
|
||||
type: string
|
||||
elec_vol:
|
||||
default: 50
|
||||
description: 电解液吸液量(μL)
|
||||
description: 电解液吸液量 (μL)
|
||||
type: integer
|
||||
file_path:
|
||||
default: /Users/sml/work
|
||||
@@ -196,7 +196,7 @@ coincellassemblyworkstation_device:
|
||||
type: string
|
||||
fujipian_juzhendianwei:
|
||||
default: 0
|
||||
description: 负极片矩阵点位。盘位置从1开始计数,有效范围:1-8, 13-20 (写入值比实际位置少1,例如:写0取盘位1,写1取盘位2)
|
||||
description: 负极片矩阵点位
|
||||
type: integer
|
||||
fujipian_panshu:
|
||||
default: 0
|
||||
@@ -204,7 +204,7 @@ coincellassemblyworkstation_device:
|
||||
type: integer
|
||||
gemo_juzhendianwei:
|
||||
default: 0
|
||||
description: 隔膜矩阵点位。盘位置从1开始计数,有效范围:1-8, 13-20 (写入值比实际位置少1,例如:写0取盘位1,写1取盘位2)
|
||||
description: 隔膜矩阵点位
|
||||
type: integer
|
||||
gemopanshu:
|
||||
default: 0
|
||||
@@ -216,7 +216,7 @@ coincellassemblyworkstation_device:
|
||||
type: boolean
|
||||
qiangtou_juzhendianwei:
|
||||
default: 0
|
||||
description: 枪头盒矩阵点位。盘位置从1开始计数,有效范围:1-32, 64-96 (写入值比实际位置少1,例如:写0取盘位1,写1取盘位2)
|
||||
description: 枪头盒矩阵点位
|
||||
type: integer
|
||||
required:
|
||||
- elec_num
|
||||
@@ -308,7 +308,13 @@ coincellassemblyworkstation_device:
|
||||
properties:
|
||||
material_search_enable:
|
||||
default: false
|
||||
description: 是否启用物料搜寻功能。设备初始化后会弹出物料搜寻确认弹窗,此参数控制自动点击"是"(启用)或"否"(不启用)。默认为false(不启用物料搜寻)
|
||||
description: '是否启用物料搜寻功能。
|
||||
|
||||
设备初始化后会弹出物料搜寻确认弹窗,
|
||||
|
||||
此参数控制自动点击''是''(启用)或''否''(不启用)。
|
||||
|
||||
默认为False(不启用物料搜寻)。'
|
||||
type: boolean
|
||||
required: []
|
||||
type: object
|
||||
@@ -547,15 +553,15 @@ coincellassemblyworkstation_device:
|
||||
properties:
|
||||
assembly_pressure:
|
||||
default: 4200
|
||||
description: 电池压制力(N)
|
||||
description: 电池压制力 (N)
|
||||
type: integer
|
||||
assembly_type:
|
||||
default: 7
|
||||
description: 组装类型(7=不用铝箔垫, 8=使用铝箔垫)
|
||||
description: 组装类型 (7=不用铝箔垫, 8=使用铝箔垫)
|
||||
type: integer
|
||||
battery_clean_ignore:
|
||||
default: false
|
||||
description: 是否忽略电池清洁步骤
|
||||
description: 是否忽略电池清洁
|
||||
type: boolean
|
||||
battery_pressure_mode:
|
||||
default: true
|
||||
@@ -563,29 +569,29 @@ coincellassemblyworkstation_device:
|
||||
type: boolean
|
||||
dual_drop_first_volume:
|
||||
default: 25
|
||||
description: 二次滴液第一次排液体积(μL)
|
||||
description: 二次滴液第一次排液体积 (μL)
|
||||
type: integer
|
||||
dual_drop_mode:
|
||||
default: false
|
||||
description: 电解液添加模式(false=单次滴液, true=二次滴液)
|
||||
description: 电解液添加模式 (False=单次滴液, True=二次滴液)
|
||||
type: boolean
|
||||
dual_drop_start_timing:
|
||||
default: false
|
||||
description: 二次滴液开始滴液时机(false=正极片前, true=正极片后)
|
||||
description: 二次滴液开始滴液时机 (False=正极片前, True=正极片后)
|
||||
type: boolean
|
||||
dual_drop_suction_timing:
|
||||
default: false
|
||||
description: 二次滴液吸液时机(false=正常吸液, true=先吸液)
|
||||
description: 二次滴液吸液时机 (False=正常吸液, True=先吸液)
|
||||
type: boolean
|
||||
elec_num:
|
||||
description: 电解液瓶数,如果在workflow中已通过handles连接上游(create_orders的bottle_count输出),则此参数会自动从上游获取,无需手动填写;如果单独使用此函数(没有上游连接),则必须手动填写电解液瓶数
|
||||
description: 电解液瓶数
|
||||
type: string
|
||||
elec_use_num:
|
||||
description: 每瓶电解液组装电池数
|
||||
description: 每瓶电解液组装的电池数
|
||||
type: string
|
||||
elec_vol:
|
||||
default: 50
|
||||
description: 电解液吸液量(μL)
|
||||
description: 电解液吸液量 (μL)
|
||||
type: integer
|
||||
file_path:
|
||||
default: /Users/sml/work
|
||||
@@ -593,7 +599,7 @@ coincellassemblyworkstation_device:
|
||||
type: string
|
||||
fujipian_juzhendianwei:
|
||||
default: 0
|
||||
description: 负极片矩阵点位。盘位置从1开始计数,有效范围:1-8, 13-20 (写入值比实际位置少1,例如:写0取盘位1,写1取盘位2)
|
||||
description: 负极片矩阵点位
|
||||
type: integer
|
||||
fujipian_panshu:
|
||||
default: 0
|
||||
@@ -601,7 +607,7 @@ coincellassemblyworkstation_device:
|
||||
type: integer
|
||||
gemo_juzhendianwei:
|
||||
default: 0
|
||||
description: 隔膜矩阵点位。盘位置从1开始计数,有效范围:1-8, 13-20 (写入值比实际位置少1,例如:写0取盘位1,写1取盘位2)
|
||||
description: 隔膜矩阵点位
|
||||
type: integer
|
||||
gemopanshu:
|
||||
default: 0
|
||||
@@ -613,7 +619,7 @@ coincellassemblyworkstation_device:
|
||||
type: boolean
|
||||
qiangtou_juzhendianwei:
|
||||
default: 0
|
||||
description: 枪头盒矩阵点位。盘位置从1开始计数,有效范围:1-32, 64-96 (写入值比实际位置少1,例如:写0取盘位1,写1取盘位2)
|
||||
description: 枪头盒矩阵点位
|
||||
type: integer
|
||||
required:
|
||||
- elec_num
|
||||
|
||||
@@ -18,6 +18,7 @@ xyz_stepper_controller:
|
||||
goal:
|
||||
properties:
|
||||
degrees:
|
||||
description: 角度值
|
||||
type: number
|
||||
required:
|
||||
- degrees
|
||||
@@ -44,6 +45,7 @@ xyz_stepper_controller:
|
||||
goal:
|
||||
properties:
|
||||
axis:
|
||||
description: 电机轴
|
||||
type: object
|
||||
required:
|
||||
- axis
|
||||
@@ -71,6 +73,7 @@ xyz_stepper_controller:
|
||||
properties:
|
||||
enable:
|
||||
default: true
|
||||
description: True为使能,False为失能
|
||||
type: boolean
|
||||
required: []
|
||||
type: object
|
||||
@@ -99,9 +102,11 @@ xyz_stepper_controller:
|
||||
goal:
|
||||
properties:
|
||||
axis:
|
||||
description: 电机轴
|
||||
type: object
|
||||
enable:
|
||||
default: true
|
||||
description: True为使能,False为失能
|
||||
type: boolean
|
||||
required:
|
||||
- axis
|
||||
@@ -152,6 +157,7 @@ xyz_stepper_controller:
|
||||
goal:
|
||||
properties:
|
||||
axis:
|
||||
description: 电机轴
|
||||
type: object
|
||||
required:
|
||||
- axis
|
||||
@@ -183,16 +189,21 @@ xyz_stepper_controller:
|
||||
properties:
|
||||
acceleration:
|
||||
default: 1000
|
||||
description: 加速度(rpm/s)
|
||||
type: integer
|
||||
axis:
|
||||
description: 电机轴
|
||||
type: object
|
||||
position:
|
||||
description: 目标位置(步数)
|
||||
type: integer
|
||||
precision:
|
||||
default: 100
|
||||
description: 到位精度
|
||||
type: integer
|
||||
speed:
|
||||
default: 5000
|
||||
description: 运行速度(rpm)
|
||||
type: integer
|
||||
required:
|
||||
- axis
|
||||
@@ -225,16 +236,21 @@ xyz_stepper_controller:
|
||||
properties:
|
||||
acceleration:
|
||||
default: 1000
|
||||
description: 加速度
|
||||
type: integer
|
||||
axis:
|
||||
description: 电机轴
|
||||
type: object
|
||||
degrees:
|
||||
description: 目标角度(度)
|
||||
type: number
|
||||
precision:
|
||||
default: 100
|
||||
description: 精度
|
||||
type: integer
|
||||
speed:
|
||||
default: 5000
|
||||
description: 移动速度
|
||||
type: integer
|
||||
required:
|
||||
- axis
|
||||
@@ -267,16 +283,21 @@ xyz_stepper_controller:
|
||||
properties:
|
||||
acceleration:
|
||||
default: 1000
|
||||
description: 加速度
|
||||
type: integer
|
||||
axis:
|
||||
description: 电机轴
|
||||
type: object
|
||||
precision:
|
||||
default: 100
|
||||
description: 精度
|
||||
type: integer
|
||||
revolutions:
|
||||
description: 目标圈数
|
||||
type: number
|
||||
speed:
|
||||
default: 5000
|
||||
description: 移动速度
|
||||
type: integer
|
||||
required:
|
||||
- axis
|
||||
@@ -309,15 +330,20 @@ xyz_stepper_controller:
|
||||
properties:
|
||||
acceleration:
|
||||
default: 1000
|
||||
description: 加速度
|
||||
type: integer
|
||||
speed:
|
||||
default: 5000
|
||||
description: 运行速度
|
||||
type: integer
|
||||
x:
|
||||
description: X轴目标位置
|
||||
type: integer
|
||||
y:
|
||||
description: Y轴目标位置
|
||||
type: integer
|
||||
z:
|
||||
description: Z轴目标位置
|
||||
type: integer
|
||||
required: []
|
||||
type: object
|
||||
@@ -350,15 +376,20 @@ xyz_stepper_controller:
|
||||
properties:
|
||||
acceleration:
|
||||
default: 1000
|
||||
description: 加速度
|
||||
type: integer
|
||||
speed:
|
||||
default: 5000
|
||||
description: 移动速度
|
||||
type: integer
|
||||
x_deg:
|
||||
description: X轴目标角度(度)
|
||||
type: number
|
||||
y_deg:
|
||||
description: Y轴目标角度(度)
|
||||
type: number
|
||||
z_deg:
|
||||
description: Z轴目标角度(度)
|
||||
type: number
|
||||
required: []
|
||||
type: object
|
||||
@@ -391,15 +422,20 @@ xyz_stepper_controller:
|
||||
properties:
|
||||
acceleration:
|
||||
default: 1000
|
||||
description: 加速度
|
||||
type: integer
|
||||
speed:
|
||||
default: 5000
|
||||
description: 移动速度
|
||||
type: integer
|
||||
x_rev:
|
||||
description: X轴目标圈数
|
||||
type: number
|
||||
y_rev:
|
||||
description: Y轴目标圈数
|
||||
type: number
|
||||
z_rev:
|
||||
description: Z轴目标圈数
|
||||
type: number
|
||||
required: []
|
||||
type: object
|
||||
@@ -427,6 +463,7 @@ xyz_stepper_controller:
|
||||
goal:
|
||||
properties:
|
||||
revolutions:
|
||||
description: 圈数
|
||||
type: number
|
||||
required:
|
||||
- revolutions
|
||||
@@ -456,10 +493,13 @@ xyz_stepper_controller:
|
||||
properties:
|
||||
acceleration:
|
||||
default: 1000
|
||||
description: 加速度(rpm/s)
|
||||
type: integer
|
||||
axis:
|
||||
description: 电机轴
|
||||
type: object
|
||||
speed:
|
||||
description: 运行速度(rpm),正值正转,负值反转
|
||||
type: integer
|
||||
required:
|
||||
- axis
|
||||
@@ -487,6 +527,7 @@ xyz_stepper_controller:
|
||||
goal:
|
||||
properties:
|
||||
steps:
|
||||
description: 步数
|
||||
type: integer
|
||||
required:
|
||||
- steps
|
||||
@@ -513,6 +554,7 @@ xyz_stepper_controller:
|
||||
goal:
|
||||
properties:
|
||||
steps:
|
||||
description: 步数
|
||||
type: integer
|
||||
required:
|
||||
- steps
|
||||
@@ -564,9 +606,11 @@ xyz_stepper_controller:
|
||||
goal:
|
||||
properties:
|
||||
axis:
|
||||
description: 电机轴
|
||||
type: object
|
||||
timeout:
|
||||
default: 30.0
|
||||
description: 超时时间(秒)
|
||||
type: number
|
||||
required:
|
||||
- axis
|
||||
@@ -591,11 +635,14 @@ xyz_stepper_controller:
|
||||
properties:
|
||||
baudrate:
|
||||
default: 115200
|
||||
description: 波特率
|
||||
type: integer
|
||||
port:
|
||||
description: 串口端口名
|
||||
type: string
|
||||
timeout:
|
||||
default: 1.0
|
||||
description: 通信超时时间
|
||||
type: number
|
||||
required:
|
||||
- port
|
||||
|
||||
@@ -510,9 +510,11 @@ liquid_handler:
|
||||
goal:
|
||||
properties:
|
||||
msg:
|
||||
description: information to be printed
|
||||
type: string
|
||||
seconds:
|
||||
default: 0
|
||||
description: seconds to wait
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
@@ -2963,15 +2965,22 @@ liquid_handler:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
channel:
|
||||
description: int
|
||||
maximum: 2147483647
|
||||
minimum: -2147483648
|
||||
type: integer
|
||||
dis_to_top:
|
||||
description: 'float
|
||||
|
||||
Height in mm to move to relative to the well top.'
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
well:
|
||||
additionalProperties: false
|
||||
description: 'Well
|
||||
|
||||
The target well.'
|
||||
properties:
|
||||
category:
|
||||
type: string
|
||||
@@ -4829,11 +4838,13 @@ liquid_handler:
|
||||
config:
|
||||
properties:
|
||||
backend:
|
||||
description: Backend to use.
|
||||
type: object
|
||||
channel_num:
|
||||
default: 8
|
||||
type: integer
|
||||
deck:
|
||||
description: Deck to use.
|
||||
type: object
|
||||
simulator:
|
||||
default: false
|
||||
@@ -4883,14 +4894,17 @@ liquid_handler.biomek:
|
||||
bind_parent_id:
|
||||
type: string
|
||||
liquid_input_slot:
|
||||
description: 液体输入槽列表
|
||||
items:
|
||||
type: integer
|
||||
type: array
|
||||
liquid_type:
|
||||
description: 液体类型列表
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
liquid_volume:
|
||||
description: 液体体积列表
|
||||
items:
|
||||
type: integer
|
||||
type: array
|
||||
@@ -4901,6 +4915,7 @@ liquid_handler.biomek:
|
||||
type: object
|
||||
type: array
|
||||
slot_on_deck:
|
||||
description: 甲板上的槽位
|
||||
type: integer
|
||||
required:
|
||||
- resource_tracker
|
||||
@@ -5036,20 +5051,27 @@ liquid_handler.biomek:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
none_keys:
|
||||
description: 需要设置为None的键列表
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
protocol_author:
|
||||
description: 协议作者
|
||||
type: string
|
||||
protocol_date:
|
||||
description: 协议日期
|
||||
type: string
|
||||
protocol_description:
|
||||
description: 协议描述
|
||||
type: string
|
||||
protocol_name:
|
||||
description: 协议名称
|
||||
type: string
|
||||
protocol_type:
|
||||
description: 协议类型
|
||||
type: string
|
||||
protocol_version:
|
||||
description: 协议版本
|
||||
type: string
|
||||
title: LiquidHandlerProtocolCreation_Goal
|
||||
type: object
|
||||
|
||||
@@ -87,7 +87,7 @@ neware_battery_test_system:
|
||||
properties:
|
||||
filepath:
|
||||
default: bts_status.json
|
||||
description: 输出JSON文件路径
|
||||
description: 输出文件路径
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
@@ -146,7 +146,7 @@ neware_battery_test_system:
|
||||
goal:
|
||||
properties:
|
||||
plate_num:
|
||||
description: 盘号 (1 或 2),如果为null则返回所有盘的状态
|
||||
description: 盘号 (1 或 2),如果为None则返回所有盘的状态
|
||||
type: integer
|
||||
required: []
|
||||
type: object
|
||||
@@ -237,11 +237,11 @@ neware_battery_test_system:
|
||||
goal:
|
||||
properties:
|
||||
csv_path:
|
||||
description: 输入CSV文件的绝对路径
|
||||
description: 输入CSV文件路径
|
||||
type: string
|
||||
output_dir:
|
||||
default: .
|
||||
description: 输出目录(用于存储XML和备份文件),默认当前目录
|
||||
description: 输出目录,用于存储XML文件和备份,默认当前目录
|
||||
type: string
|
||||
required:
|
||||
- csv_path
|
||||
@@ -302,14 +302,14 @@ neware_battery_test_system:
|
||||
goal:
|
||||
properties:
|
||||
backup_dir:
|
||||
description: 备份目录路径(默认使用最近一次submit_from_csv的backup_dir)
|
||||
description: 备份目录路径,默认使用最近一次 submit_from_csv 的 backup_dir
|
||||
type: string
|
||||
file_pattern:
|
||||
default: '*'
|
||||
description: 文件通配符模式,例如 *.csv 或 Battery_*.nda
|
||||
description: 文件通配符模式,默认 "*" 上传所有文件(例如 "*.csv" 仅上传 CSV 文件)
|
||||
type: string
|
||||
oss_prefix:
|
||||
description: OSS对象路径前缀(默认使用self.oss_prefix)
|
||||
description: OSS 对象前缀,默认使用类初始化时的配置
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
@@ -336,19 +336,25 @@ neware_battery_test_system:
|
||||
config:
|
||||
properties:
|
||||
devtype:
|
||||
description: 设备类型标识
|
||||
type: string
|
||||
ip:
|
||||
description: TCP服务器IP地址
|
||||
type: string
|
||||
machine_id:
|
||||
default: 1
|
||||
description: 机器ID
|
||||
type: integer
|
||||
oss_prefix:
|
||||
default: neware_backup
|
||||
description: OSS对象路径前缀,默认"neware_backup"
|
||||
type: string
|
||||
oss_upload_enabled:
|
||||
default: false
|
||||
description: 是否启用OSS上传功能,默认False
|
||||
type: boolean
|
||||
port:
|
||||
description: TCP端口
|
||||
type: integer
|
||||
size_x:
|
||||
default: 50
|
||||
@@ -360,6 +366,7 @@ neware_battery_test_system:
|
||||
default: 20
|
||||
type: number
|
||||
timeout:
|
||||
description: 通信超时时间(秒)
|
||||
type: integer
|
||||
required: []
|
||||
type: object
|
||||
|
||||
@@ -207,8 +207,12 @@ separator.homemade:
|
||||
goal:
|
||||
properties:
|
||||
condition:
|
||||
description: The condition to be monitored, either 'delta' or 'time'.
|
||||
type: string
|
||||
value:
|
||||
description: 'The threshold value for the condition.
|
||||
|
||||
`delta > 0.05`, `time > 60`'
|
||||
type: string
|
||||
required:
|
||||
- condition
|
||||
@@ -305,12 +309,17 @@ separator.homemade:
|
||||
event:
|
||||
type: string
|
||||
settling_time:
|
||||
description: The duration for which to settle after stirring, in
|
||||
seconds. Defaults to 10.
|
||||
type: string
|
||||
stir_speed:
|
||||
description: The speed of stirring, in RPM. Defaults to 300.
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
stir_time:
|
||||
description: The duration for which to stir, in seconds. Defaults
|
||||
to 10.
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
|
||||
@@ -456,6 +456,7 @@ syringe_pump_with_valve.runze.SY03B-T06:
|
||||
goal:
|
||||
properties:
|
||||
volume:
|
||||
description: 'absolute position of the plunger, unit: mL'
|
||||
type: number
|
||||
required:
|
||||
- volume
|
||||
@@ -481,6 +482,7 @@ syringe_pump_with_valve.runze.SY03B-T06:
|
||||
goal:
|
||||
properties:
|
||||
volume:
|
||||
description: 'absolute position of the plunger, unit: mL'
|
||||
type: number
|
||||
required:
|
||||
- volume
|
||||
@@ -687,8 +689,10 @@ syringe_pump_with_valve.runze.SY03B-T06:
|
||||
goal:
|
||||
properties:
|
||||
max_velocity:
|
||||
description: 'maximum velocity of the plunger, unit: ml/s'
|
||||
type: number
|
||||
position:
|
||||
description: 'absolute position of the plunger, unit: ml'
|
||||
type: number
|
||||
required:
|
||||
- position
|
||||
@@ -1003,6 +1007,7 @@ syringe_pump_with_valve.runze.SY03B-T08:
|
||||
goal:
|
||||
properties:
|
||||
volume:
|
||||
description: 'absolute position of the plunger, unit: mL'
|
||||
type: number
|
||||
required:
|
||||
- volume
|
||||
@@ -1028,6 +1033,7 @@ syringe_pump_with_valve.runze.SY03B-T08:
|
||||
goal:
|
||||
properties:
|
||||
volume:
|
||||
description: 'absolute position of the plunger, unit: mL'
|
||||
type: number
|
||||
required:
|
||||
- volume
|
||||
@@ -1234,8 +1240,10 @@ syringe_pump_with_valve.runze.SY03B-T08:
|
||||
goal:
|
||||
properties:
|
||||
max_velocity:
|
||||
description: 'maximum velocity of the plunger, unit: ml/s'
|
||||
type: number
|
||||
position:
|
||||
description: 'absolute position of the plunger, unit: ml'
|
||||
type: number
|
||||
required:
|
||||
- position
|
||||
|
||||
@@ -32,7 +32,7 @@ reaction_station.bioyond:
|
||||
type: integer
|
||||
end_point:
|
||||
default: 0
|
||||
description: 终点计时点 (Start=开始前, End=结束后)
|
||||
description: 终点计时点 (Start=0, End=1)
|
||||
type: integer
|
||||
end_step_key:
|
||||
default: ''
|
||||
@@ -40,11 +40,11 @@ reaction_station.bioyond:
|
||||
type: string
|
||||
start_point:
|
||||
default: 0
|
||||
description: 起点计时点 (Start=开始前, End=结束后)
|
||||
description: 起点计时点 (Start=0, End=1)
|
||||
type: integer
|
||||
start_step_key:
|
||||
default: ''
|
||||
description: 起点步骤Key (例如 "feeding", "liquid", 可选, 默认为空则自动选择)
|
||||
description: 起点步骤Key (可选, 默认为空则自动选择)
|
||||
type: string
|
||||
required:
|
||||
- duration
|
||||
@@ -91,6 +91,7 @@ reaction_station.bioyond:
|
||||
goal:
|
||||
properties:
|
||||
json_str:
|
||||
description: 订单参数的JSON字符串
|
||||
type: string
|
||||
required:
|
||||
- json_str
|
||||
@@ -117,6 +118,7 @@ reaction_station.bioyond:
|
||||
goal:
|
||||
properties:
|
||||
workflow_ids:
|
||||
description: 要删除的工作流ID数组
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
@@ -145,6 +147,7 @@ reaction_station.bioyond:
|
||||
goal:
|
||||
properties:
|
||||
json_str:
|
||||
description: 'JSON格式的字符串,包含:'
|
||||
type: string
|
||||
required:
|
||||
- json_str
|
||||
@@ -197,6 +200,7 @@ reaction_station.bioyond:
|
||||
goal:
|
||||
properties:
|
||||
web_workflow_json:
|
||||
description: JSON 格式的网页工作流列表
|
||||
type: string
|
||||
required:
|
||||
- web_workflow_json
|
||||
@@ -228,8 +232,10 @@ reaction_station.bioyond:
|
||||
goal:
|
||||
properties:
|
||||
reactor_id:
|
||||
description: 反应器编号 (1-5)
|
||||
type: integer
|
||||
temperature:
|
||||
description: 目标温度 (°C)
|
||||
type: number
|
||||
required:
|
||||
- reactor_id
|
||||
@@ -257,6 +263,7 @@ reaction_station.bioyond:
|
||||
goal:
|
||||
properties:
|
||||
preintake_id:
|
||||
description: 通量ID
|
||||
type: string
|
||||
required:
|
||||
- preintake_id
|
||||
@@ -338,6 +345,7 @@ reaction_station.bioyond:
|
||||
goal:
|
||||
properties:
|
||||
value:
|
||||
description: 工作流 ID 列表
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
@@ -365,6 +373,7 @@ reaction_station.bioyond:
|
||||
goal:
|
||||
properties:
|
||||
workflow_id:
|
||||
description: 工作流ID
|
||||
type: string
|
||||
required:
|
||||
- workflow_id
|
||||
@@ -424,11 +433,11 @@ reaction_station.bioyond:
|
||||
goal:
|
||||
properties:
|
||||
assign_material_name:
|
||||
description: 物料名称(不能为空)
|
||||
description: 物料名称(液体种类)
|
||||
type: string
|
||||
temperature:
|
||||
default: 25.0
|
||||
description: 温度设定(°C)
|
||||
description: 温度(C)
|
||||
type: number
|
||||
time:
|
||||
default: '90'
|
||||
@@ -436,14 +445,14 @@ reaction_station.bioyond:
|
||||
type: string
|
||||
titration_type:
|
||||
default: '1'
|
||||
description: 是否滴定(NO=否, YES=是)
|
||||
description: 是否滴定(NO=1, YES=2)
|
||||
type: string
|
||||
torque_variation:
|
||||
default: 2
|
||||
description: 是否观察 (NO=否, YES=是)
|
||||
description: 是否观察(NO=1, YES=2)
|
||||
type: integer
|
||||
volume:
|
||||
description: 分液公式(mL)
|
||||
description: 分液量(μL)
|
||||
type: string
|
||||
required:
|
||||
- assign_material_name
|
||||
@@ -525,11 +534,11 @@ reaction_station.bioyond:
|
||||
properties:
|
||||
assign_material_name:
|
||||
default: BAPP
|
||||
description: 物料名称
|
||||
description: 物料名称(试剂瓶位)
|
||||
type: string
|
||||
temperature:
|
||||
default: 25.0
|
||||
description: 温度设定(°C)
|
||||
description: 温度设定(C)
|
||||
type: number
|
||||
time:
|
||||
default: '0'
|
||||
@@ -537,15 +546,15 @@ reaction_station.bioyond:
|
||||
type: string
|
||||
titration_type:
|
||||
default: '1'
|
||||
description: 是否滴定(NO=否, YES=是)
|
||||
description: 是否滴定(NO=1, YES=2)
|
||||
type: string
|
||||
torque_variation:
|
||||
default: 1
|
||||
description: 是否观察 (NO=否, YES=是)
|
||||
description: 是否观察(int类型, 1=否, 2=是)
|
||||
type: integer
|
||||
volume:
|
||||
default: '350'
|
||||
description: 分液公式(mL)
|
||||
description: 分液质量(g)
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
@@ -593,26 +602,28 @@ reaction_station.bioyond:
|
||||
description: 物料名称
|
||||
type: string
|
||||
solvents:
|
||||
description: '溶剂信息对象(可选),包含: additional_solvent(溶剂体积mL), total_liquid_volume(总液体体积mL)。如果提供,将自动计算volume'
|
||||
description: '溶剂信息的字典或JSON字符串(可选),格式如下:
|
||||
|
||||
{'
|
||||
type: string
|
||||
temperature:
|
||||
default: 25.0
|
||||
description: 温度设定(°C),默认25.00
|
||||
description: 温度设定(C)
|
||||
type: number
|
||||
time:
|
||||
default: '360'
|
||||
description: 观察时间(分钟),默认360
|
||||
description: 观察时间(分钟)
|
||||
type: string
|
||||
titration_type:
|
||||
default: '1'
|
||||
description: 是否滴定(NO=否, YES=是),默认NO
|
||||
description: 是否滴定(NO=1, YES=2)
|
||||
type: string
|
||||
torque_variation:
|
||||
default: 2
|
||||
description: 是否观察 (NO=否, YES=是),默认YES
|
||||
description: 是否观察(NO=1, YES=2)
|
||||
type: integer
|
||||
volume:
|
||||
description: 分液量(mL)。可直接提供,或通过solvents参数自动计算
|
||||
description: 分液量(μL),直接指定体积(可选,如果提供solvents则自动计算)
|
||||
type: string
|
||||
required:
|
||||
- assign_material_name
|
||||
@@ -671,33 +682,32 @@ reaction_station.bioyond:
|
||||
description: 物料名称
|
||||
type: string
|
||||
extracted_actuals:
|
||||
description: 从报告提取的实际加料量JSON字符串,包含actualTargetWeigh(m二酐滴定)和actualVolume(V二酐滴定)
|
||||
description: 从报告提取的实际加料量JSON字符串,包含actualTargetWeigh和actualVolume
|
||||
type: string
|
||||
feeding_order_data:
|
||||
description: 'feeding_order JSON对象,用于获取m二酐值(type为main_anhydride的amount)。示例:
|
||||
{"feeding_order": [{"type": "main_anhydride", "amount": 1.915}]}'
|
||||
description: feeding_order JSON字符串或对象,用于获取m二酐值
|
||||
type: string
|
||||
temperature:
|
||||
default: 25.0
|
||||
description: 温度设定(°C),默认25.00
|
||||
description: 温度(C)
|
||||
type: number
|
||||
time:
|
||||
default: '90'
|
||||
description: 观察时间(分钟),默认90
|
||||
description: 观察时间(分钟)
|
||||
type: string
|
||||
titration_type:
|
||||
default: '2'
|
||||
description: 是否滴定(NO=否, YES=是),默认YES
|
||||
description: 是否滴定(NO=1, YES=2),默认2
|
||||
type: string
|
||||
torque_variation:
|
||||
default: 2
|
||||
description: 是否观察 (NO=否, YES=是),默认YES
|
||||
description: 是否观察(NO=1, YES=2)
|
||||
type: integer
|
||||
volume_formula:
|
||||
description: 分液公式(mL)。可直接提供固定公式,或留空由系统根据x_value、feeding_order_data、extracted_actuals自动生成
|
||||
description: 分液公式(μL),如果提供则直接使用,否则自动计算
|
||||
type: string
|
||||
x_value:
|
||||
description: 公式中的x值,手工输入,格式为"{{1-2-3}}"(包含双花括号)。用于自动公式计算
|
||||
description: 手工输入的x值,格式如 "1-2-3"
|
||||
type: string
|
||||
required:
|
||||
- assign_material_name
|
||||
@@ -738,7 +748,7 @@ reaction_station.bioyond:
|
||||
type: string
|
||||
temperature:
|
||||
default: 25.0
|
||||
description: 温度设定(°C)
|
||||
description: 温度(C)
|
||||
type: number
|
||||
time:
|
||||
default: '0'
|
||||
@@ -746,14 +756,14 @@ reaction_station.bioyond:
|
||||
type: string
|
||||
titration_type:
|
||||
default: '1'
|
||||
description: 是否滴定(NO=否, YES=是)
|
||||
description: 是否滴定(NO=1, YES=2)
|
||||
type: string
|
||||
torque_variation:
|
||||
default: 1
|
||||
description: 是否观察 (NO=否, YES=是)
|
||||
description: 是否观察(NO=1, YES=2)
|
||||
type: integer
|
||||
volume_formula:
|
||||
description: 分液公式(mL)
|
||||
description: 分液公式(μL)
|
||||
type: string
|
||||
required:
|
||||
- volume_formula
|
||||
@@ -786,7 +796,7 @@ reaction_station.bioyond:
|
||||
description: 任务名称
|
||||
type: string
|
||||
workflow_name:
|
||||
description: 工作流名称
|
||||
description: 合并后的工作流名称
|
||||
type: string
|
||||
required:
|
||||
- workflow_name
|
||||
@@ -819,15 +829,15 @@ reaction_station.bioyond:
|
||||
goal:
|
||||
properties:
|
||||
assign_material_name:
|
||||
description: 物料名称
|
||||
description: 物料名称(不能为空)
|
||||
type: string
|
||||
cutoff:
|
||||
default: '900000'
|
||||
description: 粘度上限
|
||||
description: 粘度上限(需为有效数字字符串,默认 "900000")
|
||||
type: string
|
||||
temperature:
|
||||
default: -10.0
|
||||
description: 温度设定(°C)
|
||||
description: 温度设定(C,范围:-50.00 至 100.00)
|
||||
type: number
|
||||
required:
|
||||
- assign_material_name
|
||||
@@ -909,11 +919,11 @@ reaction_station.bioyond:
|
||||
description: 物料名称(用于获取试剂瓶位ID)
|
||||
type: string
|
||||
material_id:
|
||||
description: 粉末类型ID,Salt=盐(21分钟),Flour=面粉(27分钟),BTDA=BTDA(38分钟)
|
||||
description: 粉末类型ID, Salt=1, Flour=2, BTDA=3
|
||||
type: string
|
||||
temperature:
|
||||
default: 25.0
|
||||
description: 温度设定(°C)
|
||||
description: 温度设定(C)
|
||||
type: number
|
||||
time:
|
||||
default: '0'
|
||||
@@ -921,7 +931,7 @@ reaction_station.bioyond:
|
||||
type: string
|
||||
torque_variation:
|
||||
default: 1
|
||||
description: 是否观察 (NO=否, YES=是)
|
||||
description: 是否观察(NO=1, YES=2)
|
||||
type: integer
|
||||
required:
|
||||
- material_id
|
||||
@@ -945,10 +955,13 @@ reaction_station.bioyond:
|
||||
config:
|
||||
properties:
|
||||
config:
|
||||
description: 配置字典,应包含workflow_mappings等配置
|
||||
type: object
|
||||
deck:
|
||||
description: Deck对象
|
||||
type: string
|
||||
protocol_type:
|
||||
description: 协议类型(由ROS系统传递,此处忽略)
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
|
||||
@@ -198,6 +198,8 @@ robotic_arm.SCARA_with_slider.moveit.virtual:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
command:
|
||||
description: A JSON-formatted string that includes option, target,
|
||||
speed, lift_height, mt_height
|
||||
type: string
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
@@ -241,6 +243,8 @@ robotic_arm.SCARA_with_slider.moveit.virtual:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
command:
|
||||
description: A JSON-formatted string that includes quaternion, speed,
|
||||
position
|
||||
type: string
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
@@ -284,6 +288,7 @@ robotic_arm.SCARA_with_slider.moveit.virtual:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
command:
|
||||
description: A JSON-formatted string that includes speed
|
||||
type: string
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
|
||||
@@ -709,6 +709,8 @@ linear_motion.toyo_xyz.sim:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
command:
|
||||
description: A JSON-formatted string that includes option, target,
|
||||
speed, lift_height, mt_height
|
||||
type: string
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
@@ -752,6 +754,8 @@ linear_motion.toyo_xyz.sim:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
command:
|
||||
description: A JSON-formatted string that includes quaternion, speed,
|
||||
position
|
||||
type: string
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
@@ -795,6 +799,7 @@ linear_motion.toyo_xyz.sim:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
command:
|
||||
description: A JSON-formatted string that includes speed
|
||||
type: string
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
|
||||
@@ -2179,6 +2179,7 @@ virtual_multiway_valve:
|
||||
goal:
|
||||
properties:
|
||||
port_number:
|
||||
description: 端口号 (1-8)
|
||||
type: integer
|
||||
required:
|
||||
- port_number
|
||||
@@ -2225,6 +2226,7 @@ virtual_multiway_valve:
|
||||
goal:
|
||||
properties:
|
||||
port_number:
|
||||
description: 目标端口号 (1-8)
|
||||
type: integer
|
||||
required:
|
||||
- port_number
|
||||
@@ -2261,6 +2263,7 @@ virtual_multiway_valve:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
command:
|
||||
description: 目标位置 (0-8) 或位置字符串
|
||||
type: string
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
@@ -2304,6 +2307,7 @@ virtual_multiway_valve:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
command:
|
||||
description: 目标位置 (0-8) 或位置字符串
|
||||
type: string
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
@@ -4215,6 +4219,7 @@ virtual_solenoid_valve:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
string:
|
||||
description: '"ON"/"OFF" 或 "OPEN"/"CLOSED"'
|
||||
type: string
|
||||
title: StrSingleInput_Goal
|
||||
type: object
|
||||
@@ -4258,6 +4263,7 @@ virtual_solenoid_valve:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
command:
|
||||
description: '"OPEN"/"CLOSED" 或其他控制命令'
|
||||
type: string
|
||||
title: SendCmd_Goal
|
||||
type: object
|
||||
@@ -4418,16 +4424,20 @@ virtual_solid_dispenser:
|
||||
event:
|
||||
type: string
|
||||
mass:
|
||||
description: 质量字符串 (如 "2.9 g")
|
||||
type: string
|
||||
mol:
|
||||
description: 摩尔数字符串 (如 "0.12 mol")
|
||||
type: string
|
||||
purpose:
|
||||
description: 添加目的
|
||||
type: string
|
||||
rate_spec:
|
||||
type: string
|
||||
ratio:
|
||||
type: string
|
||||
reagent:
|
||||
description: 试剂名称
|
||||
type: string
|
||||
stir:
|
||||
type: boolean
|
||||
@@ -4439,6 +4449,7 @@ virtual_solid_dispenser:
|
||||
type: string
|
||||
vessel:
|
||||
additionalProperties: false
|
||||
description: 目标容器
|
||||
properties:
|
||||
category:
|
||||
type: string
|
||||
@@ -5568,8 +5579,10 @@ virtual_transfer_pump:
|
||||
goal:
|
||||
properties:
|
||||
velocity:
|
||||
description: 拉取速度 (ml/s)
|
||||
type: number
|
||||
volume:
|
||||
description: 要拉取的体积 (ml)
|
||||
type: number
|
||||
required:
|
||||
- volume
|
||||
@@ -5596,8 +5609,10 @@ virtual_transfer_pump:
|
||||
goal:
|
||||
properties:
|
||||
velocity:
|
||||
description: 推出速度 (ml/s)
|
||||
type: number
|
||||
volume:
|
||||
description: 要推出的体积 (ml)
|
||||
type: number
|
||||
required:
|
||||
- volume
|
||||
@@ -5693,10 +5708,12 @@ virtual_transfer_pump:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
max_velocity:
|
||||
description: 移动速度 (ml/s)
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
position:
|
||||
description: 目标位置 (ml)
|
||||
maximum: 1.7976931348623157e+308
|
||||
minimum: -1.7976931348623157e+308
|
||||
type: number
|
||||
@@ -5845,8 +5862,10 @@ virtual_transfer_pump:
|
||||
config:
|
||||
properties:
|
||||
config:
|
||||
description: 配置字典,包含max_volume, port等参数
|
||||
type: object
|
||||
device_id:
|
||||
description: 设备ID
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
|
||||
@@ -409,11 +409,11 @@ xrd_d7mate:
|
||||
properties:
|
||||
end_theta:
|
||||
default: 80.0
|
||||
description: 结束角度(≥5.5°,且必须大于start_theta)
|
||||
description: 结束角度(≥5.5°,且必须大于 start_theta)
|
||||
type: number
|
||||
exp_time:
|
||||
default: 0.1
|
||||
description: 曝光时间(0.1-5.0秒)
|
||||
description: 曝光时间(0.1-5.0 秒)
|
||||
type: number
|
||||
increment:
|
||||
default: 0.05
|
||||
@@ -421,7 +421,7 @@ xrd_d7mate:
|
||||
type: number
|
||||
sample_id:
|
||||
default: ''
|
||||
description: 样品标识符
|
||||
description: 样品名称
|
||||
type: string
|
||||
start_theta:
|
||||
default: 10.0
|
||||
@@ -433,7 +433,7 @@ xrd_d7mate:
|
||||
type: string
|
||||
wait_minutes:
|
||||
default: 3.0
|
||||
description: 允许上样后等待分钟数
|
||||
description: 在允许上样后、发送样品准备完成前的等待分钟数(默认 3 分钟)
|
||||
type: number
|
||||
required: []
|
||||
title: StartWorkflow_Goal
|
||||
@@ -492,12 +492,15 @@ xrd_d7mate:
|
||||
properties:
|
||||
host:
|
||||
default: 127.0.0.1
|
||||
description: 设备IP地址
|
||||
type: string
|
||||
port:
|
||||
default: 6001
|
||||
description: 通信端口,默认6001
|
||||
type: string
|
||||
timeout:
|
||||
default: 10.0
|
||||
description: 超时时间,单位秒
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
|
||||
@@ -217,6 +217,7 @@ zhida_gcms:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
string:
|
||||
description: Base64编码的CSV数据(ROS2参数名)
|
||||
type: string
|
||||
title: StrSingleInput_Goal
|
||||
type: object
|
||||
@@ -257,6 +258,7 @@ zhida_gcms:
|
||||
additionalProperties: false
|
||||
properties:
|
||||
string:
|
||||
description: CSV文件路径(ROS2参数名)
|
||||
type: string
|
||||
title: StrSingleInput_Goal
|
||||
type: object
|
||||
@@ -289,12 +291,15 @@ zhida_gcms:
|
||||
properties:
|
||||
host:
|
||||
default: 192.168.3.184
|
||||
description: 设备IP地址,本地部署时可使用'127.0.0.1'
|
||||
type: string
|
||||
port:
|
||||
default: 5792
|
||||
description: 通信端口,默认5792
|
||||
type: string
|
||||
timeout:
|
||||
default: 10.0
|
||||
description: 超时时间,单位秒
|
||||
type: string
|
||||
required: []
|
||||
type: object
|
||||
|
||||
@@ -571,6 +571,7 @@ class Registry:
|
||||
schema: Dict[str, Any],
|
||||
doc_info: Dict[str, Any],
|
||||
field_to_param: Optional[Dict[str, str]] = None,
|
||||
apply_defaults: bool = False,
|
||||
) -> None:
|
||||
"""Apply parsed docstring display names and descriptions to schema properties."""
|
||||
if not schema or not doc_info:
|
||||
@@ -589,12 +590,20 @@ class Registry:
|
||||
if not isinstance(param_name, str):
|
||||
continue
|
||||
param_name = param_name.removesuffix("[]")
|
||||
prop_schema["title"] = param_display_names.get(param_name, prop_schema.get("title") or field_name)
|
||||
prop_schema["description"] = param_descs.get(param_name, prop_schema.get("description") or "")
|
||||
if param_name in param_display_names:
|
||||
prop_schema["title"] = param_display_names[param_name]
|
||||
elif apply_defaults and not prop_schema.get("title"):
|
||||
prop_schema["title"] = field_name
|
||||
|
||||
if param_name in param_descs:
|
||||
prop_schema["description"] = param_descs[param_name]
|
||||
elif apply_defaults and "description" not in prop_schema:
|
||||
prop_schema["description"] = ""
|
||||
|
||||
def _generate_unilab_json_command_schema(
|
||||
self, method_args: list, docstring: Optional[str] = None,
|
||||
import_map: Optional[Dict[str, str]] = None,
|
||||
apply_doc_defaults: bool = False,
|
||||
) -> Dict[str, Any]:
|
||||
"""根据方法参数和 docstring 生成 UniLabJsonCommand schema"""
|
||||
doc_info = parse_docstring(docstring)
|
||||
@@ -631,7 +640,7 @@ class Registry:
|
||||
if param_required:
|
||||
schema["required"].append(param_name)
|
||||
|
||||
self._apply_docstring_param_metadata(schema, doc_info)
|
||||
self._apply_docstring_param_metadata(schema, doc_info, apply_defaults=apply_doc_defaults)
|
||||
return schema
|
||||
|
||||
def _generate_status_types_schema(self, status_methods: Dict[str, Any]) -> Dict[str, Any]:
|
||||
@@ -1038,6 +1047,7 @@ class Registry:
|
||||
goal_schema_for_docs,
|
||||
parse_docstring(method_info.get("docstring")),
|
||||
goal,
|
||||
apply_defaults=True,
|
||||
)
|
||||
action_value_mappings[action_name] = action_entry
|
||||
|
||||
@@ -1127,7 +1137,7 @@ class Registry:
|
||||
if prequired:
|
||||
schema["required"].append(pname)
|
||||
|
||||
self._apply_docstring_param_metadata(schema, doc_info)
|
||||
self._apply_docstring_param_metadata(schema, doc_info, apply_defaults=True)
|
||||
return schema
|
||||
|
||||
def _generate_status_schema_from_ast(
|
||||
@@ -2211,10 +2221,6 @@ class Registry:
|
||||
},
|
||||
**schema["properties"]["goal"]["properties"],
|
||||
}
|
||||
for field_name, field_schema in schema["properties"]["goal"]["properties"].items():
|
||||
if isinstance(field_schema, dict):
|
||||
field_schema.setdefault("title", field_name)
|
||||
field_schema.setdefault("description", "")
|
||||
# 将 placeholder_keys 信息添加到 schema 中
|
||||
if "placeholder_keys" in action_config and action_config.get("schema", {}).get(
|
||||
"properties", {}
|
||||
|
||||
@@ -33,10 +33,83 @@ _USE_UV: Optional[bool] = None
|
||||
def _has_uv() -> bool:
|
||||
global _USE_UV
|
||||
if _USE_UV is None:
|
||||
_USE_UV = shutil.which("uv") is not None
|
||||
uv_path = shutil.which("uv")
|
||||
if not uv_path:
|
||||
_USE_UV = False
|
||||
else:
|
||||
try:
|
||||
result = subprocess.run([uv_path, "--version"], capture_output=True, text=True, timeout=10)
|
||||
_USE_UV = result.returncode == 0
|
||||
except Exception:
|
||||
_USE_UV = False
|
||||
return _USE_UV
|
||||
|
||||
|
||||
def _install_command(installer: str, package: str, upgrade: bool, is_chinese: bool) -> List[str]:
|
||||
if installer == "uv":
|
||||
# uv >= 0.5 默认要求虚拟环境,对 conda env 会报 "No virtual environment found"。
|
||||
# 显式 --python sys.executable 让 uv 把当前解释器(conda/venv/system 都行)
|
||||
# 视为目标环境,绕开 venv 检测。
|
||||
cmd = ["uv", "pip", "install", "--python", sys.executable]
|
||||
if upgrade:
|
||||
cmd.append("--upgrade")
|
||||
cmd.append(package)
|
||||
if is_chinese:
|
||||
cmd.extend(["--index-url", "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple"])
|
||||
return cmd
|
||||
|
||||
cmd = [sys.executable, "-m", "pip", "install", "--disable-pip-version-check"]
|
||||
if upgrade:
|
||||
cmd.append("--upgrade")
|
||||
cmd.append(package)
|
||||
if is_chinese:
|
||||
cmd.extend(["-i", "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple"])
|
||||
return cmd
|
||||
|
||||
|
||||
def _installer_candidates() -> List[str]:
|
||||
installers: List[str] = []
|
||||
if _has_uv():
|
||||
installers.append("uv")
|
||||
installers.append("pip")
|
||||
return installers
|
||||
|
||||
|
||||
def _git_url_from_requirement(requirement: str) -> Optional[str]:
|
||||
if not requirement.startswith("git+"):
|
||||
return None
|
||||
return requirement[4:].split("#", 1)[0]
|
||||
|
||||
|
||||
def _repo_dir_name(git_url: str) -> str:
|
||||
repo_name = git_url.rstrip("/").rsplit("/", 1)[-1]
|
||||
return repo_name[:-4] if repo_name.endswith(".git") else repo_name
|
||||
|
||||
|
||||
def _print_manual_git_install_hint(requirement: str) -> None:
|
||||
git_url = _git_url_from_requirement(requirement)
|
||||
if not git_url:
|
||||
return
|
||||
|
||||
repo_dir = _repo_dir_name(git_url)
|
||||
install_cmd = (
|
||||
f'uv pip install --python "{sys.executable}" -e .'
|
||||
if _has_uv()
|
||||
else f"{sys.executable} -m pip install -e ."
|
||||
)
|
||||
if _is_chinese_locale() and not _has_uv():
|
||||
install_cmd += " -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple"
|
||||
|
||||
print_status("Git 依赖自动安装失败,通常是网络连接被重置或代码托管站点暂时不可达。", "warning")
|
||||
print_status("可以手动拉取代码后在本地安装:", "warning")
|
||||
print_status(f" git clone {git_url}", "warning")
|
||||
print_status(f" cd {repo_dir}", "warning")
|
||||
print_status(" git pull", "warning")
|
||||
print_status(f" {install_cmd}", "warning")
|
||||
print_status(f"如果目录 {repo_dir} 已存在,直接进入该目录执行 git pull 后再安装。", "warning")
|
||||
print_status("如果 git clone 仍失败,请切换网络/代理,或从浏览器下载源码后进入源码目录执行本地安装命令。", "warning")
|
||||
|
||||
|
||||
def _install_packages(
|
||||
packages: List[str],
|
||||
upgrade: bool = False,
|
||||
@@ -53,7 +126,7 @@ def _install_packages(
|
||||
return True
|
||||
|
||||
is_chinese = _is_chinese_locale()
|
||||
use_uv = _has_uv()
|
||||
installers = _installer_candidates()
|
||||
failed: List[str] = []
|
||||
|
||||
for pkg in packages:
|
||||
@@ -63,35 +136,30 @@ def _install_packages(
|
||||
else:
|
||||
print_status(f"正在{action_word} {pkg}...", "info")
|
||||
|
||||
if use_uv:
|
||||
cmd = ["uv", "pip", "install"]
|
||||
if upgrade:
|
||||
cmd.append("--upgrade")
|
||||
cmd.append(pkg)
|
||||
if is_chinese:
|
||||
cmd.extend(["--index-url", "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple"])
|
||||
else:
|
||||
cmd = [sys.executable, "-m", "pip", "install"]
|
||||
if upgrade:
|
||||
cmd.append("--upgrade")
|
||||
cmd.append(pkg)
|
||||
if is_chinese:
|
||||
cmd.extend(["-i", "https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple"])
|
||||
pkg_installed = False
|
||||
last_error = "unknown error"
|
||||
|
||||
try:
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
|
||||
if result.returncode == 0:
|
||||
installer = "uv" if use_uv else "pip"
|
||||
print_status(f"✓ {pkg} {action_word}成功 (via {installer})", "success")
|
||||
else:
|
||||
stderr_short = result.stderr.strip().split("\n")[-1] if result.stderr else "unknown error"
|
||||
print_status(f"× {pkg} {action_word}失败: {stderr_short}", "error")
|
||||
failed.append(pkg)
|
||||
except subprocess.TimeoutExpired:
|
||||
print_status(f"× {pkg} {action_word}超时 (300s)", "error")
|
||||
failed.append(pkg)
|
||||
except Exception as e:
|
||||
print_status(f"× {pkg} {action_word}异常: {e}", "error")
|
||||
for installer in installers:
|
||||
cmd = _install_command(installer, pkg, upgrade, is_chinese)
|
||||
try:
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
|
||||
if result.returncode == 0:
|
||||
print_status(f"✓ {pkg} {action_word}成功 (via {installer})", "success")
|
||||
pkg_installed = True
|
||||
break
|
||||
|
||||
last_error = result.stderr.strip().split("\n")[-1] if result.stderr else "unknown error"
|
||||
print_status(f"× {pkg} {action_word}失败 (via {installer}): {last_error}", "warning")
|
||||
except subprocess.TimeoutExpired:
|
||||
last_error = "timeout after 300s"
|
||||
print_status(f"× {pkg} {action_word}超时 (via {installer}, 300s)", "warning")
|
||||
except Exception as e:
|
||||
last_error = str(e)
|
||||
print_status(f"× {pkg} {action_word}异常 (via {installer}): {e}", "warning")
|
||||
|
||||
if not pkg_installed:
|
||||
print_status(f"× {pkg} {action_word}失败: {last_error}", "error")
|
||||
_print_manual_git_install_hint(pkg)
|
||||
failed.append(pkg)
|
||||
|
||||
if failed:
|
||||
|
||||
@@ -206,6 +206,7 @@ class ImportManager:
|
||||
"ast_analysis_success": False,
|
||||
"import_map": {},
|
||||
"init_params": [],
|
||||
"init_docstring": None,
|
||||
"status_methods": {},
|
||||
"action_methods": {},
|
||||
}
|
||||
@@ -251,6 +252,7 @@ class ImportManager:
|
||||
|
||||
# 映射到统一字段名(与 registry.py complete_registry 消费端一致)
|
||||
result["init_params"] = body.get("init_params", [])
|
||||
result["init_docstring"] = body.get("init_docstring")
|
||||
result["status_methods"] = body.get("status_properties", {})
|
||||
result["action_methods"] = {
|
||||
k: {
|
||||
|
||||
Reference in New Issue
Block a user