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:
Xuwznln
2026-04-27 20:47:52 +08:00
parent f71ea2a258
commit 916a6dfc60
27 changed files with 1282 additions and 195 deletions

View 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的实时执行状态包括整体状态和每个节点JOSJob 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
- 若结果 210 条 → 让用户按编号选择
- 若结果过多 → 提示收紧条件(加 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`。 |
| 210 条 | 编号列表展示,让用户选编号。每项给出 name、tags、description 摘要、published 状态。 |
| 1030 条 | 先展示 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`),无需额外安装。

View 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()

View File

@@ -38,7 +38,7 @@ jobs:
- name: Install ROS dependencies, uv and unilabos-msgs - name: Install ROS dependencies, uv and unilabos-msgs
run: | run: |
echo Installing ROS dependencies... 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 - name: Install pip dependencies and unilabos
run: | run: |

View File

@@ -1,6 +1,10 @@
name: Build Conda-Pack Environment name: Build Conda-Pack Environment
on: on:
# 在 UniLabOS Conda Build 成功上传后自动构建非全量 conda-pack
workflow_run:
workflows: ["UniLabOS Conda Build"]
types: [completed]
workflow_dispatch: workflow_dispatch:
inputs: inputs:
branch: branch:
@@ -21,6 +25,16 @@ on:
jobs: jobs:
build-conda-pack: 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: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -54,7 +68,9 @@ jobs:
id: should_build id: should_build
shell: bash shell: bash
run: | 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 echo "should_build=true" >> $GITHUB_OUTPUT
elif [[ "${{ github.event.inputs.platforms }}" == *"${{ matrix.platform }}"* ]]; then elif [[ "${{ github.event.inputs.platforms }}" == *"${{ matrix.platform }}"* ]]; then
echo "should_build=true" >> $GITHUB_OUTPUT echo "should_build=true" >> $GITHUB_OUTPUT
@@ -65,7 +81,7 @@ jobs:
- uses: actions/checkout@v6 - uses: actions/checkout@v6
if: steps.should_build.outputs.should_build == 'true' if: steps.should_build.outputs.should_build == 'true'
with: with:
ref: ${{ github.event.inputs.branch }} ref: ${{ github.event.inputs.branch || github.event.workflow_run.head_sha || github.ref }}
fetch-depth: 0 fetch-depth: 0
- name: Setup Miniforge (with mamba) - name: Setup Miniforge (with mamba)
@@ -75,7 +91,7 @@ jobs:
miniforge-version: latest miniforge-version: latest
use-mamba: true use-mamba: true
python-version: '3.11.14' python-version: '3.11.14'
channels: conda-forge,robostack-staging,uni-lab,defaults channels: conda-forge,robostack-staging,uni-lab
channel-priority: flexible channel-priority: flexible
activate-environment: unilab activate-environment: unilab
auto-update-conda: false auto-update-conda: false
@@ -86,13 +102,13 @@ jobs:
run: | run: |
echo Installing unilabos and dependencies to unilab environment... echo Installing unilabos and dependencies to unilab environment...
echo Using mamba for faster and more reliable dependency resolution... echo Using mamba for faster and more reliable dependency resolution...
echo Build full: ${{ github.event.inputs.build_full }} echo Build full: ${{ env.BUILD_FULL }}
if "${{ github.event.inputs.build_full }}"=="true" ( if "${{ env.BUILD_FULL }}"=="true" (
echo Installing unilabos-full ^(complete package^)... 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 ( ) else (
echo Installing unilabos ^(minimal package^)... 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) - name: Install conda-pack, unilabos and dependencies (Unix)
@@ -101,13 +117,13 @@ jobs:
run: | run: |
echo "Installing unilabos and dependencies to unilab environment..." echo "Installing unilabos and dependencies to unilab environment..."
echo "Using mamba for faster and more reliable dependency resolution..." echo "Using mamba for faster and more reliable dependency resolution..."
echo "Build full: ${{ github.event.inputs.build_full }}" echo "Build full: ${{ env.BUILD_FULL }}"
if [[ "${{ github.event.inputs.build_full }}" == "true" ]]; then if [[ "${{ env.BUILD_FULL }}" == "true" ]]; then
echo "Installing unilabos-full (complete package)..." 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 else
echo "Installing unilabos (minimal package)..." 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 fi
- name: Get latest ros-humble-unilabos-msgs version (Windows) - 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' if: steps.should_build.outputs.should_build == 'true' && matrix.platform == 'win-64'
run: | run: |
echo Checking for available ros-humble-unilabos-msgs versions... 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.
echo Updating ros-humble-unilabos-msgs to latest version... 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) - name: Check for newer ros-humble-unilabos-msgs (Unix)
if: steps.should_build.outputs.should_build == 'true' && matrix.platform != 'win-64' if: steps.should_build.outputs.should_build == 'true' && matrix.platform != 'win-64'
shell: bash shell: bash
run: | run: |
echo "Checking for available ros-humble-unilabos-msgs versions..." 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 ""
echo "Updating ros-humble-unilabos-msgs to latest version..." 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) - name: Install latest unilabos from source (Windows)
if: steps.should_build.outputs.should_build == 'true' && matrix.platform == 'win-64' if: steps.should_build.outputs.should_build == 'true' && matrix.platform == 'win-64'
run: | run: |
echo Uninstalling existing unilabos... echo Uninstalling existing unilabos...
mamba run -n unilab pip uninstall unilabos -y || echo unilabos not installed via pip 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 . mamba run -n unilab pip install .
echo Verifying installation... echo Verifying installation...
mamba run -n unilab pip show unilabos mamba run -n unilab pip show unilabos
@@ -165,7 +181,7 @@ jobs:
run: | run: |
echo "Uninstalling existing unilabos..." echo "Uninstalling existing unilabos..."
mamba run -n unilab pip uninstall unilabos -y || echo "unilabos not installed via pip" 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 . mamba run -n unilab pip install .
echo "Verifying installation..." echo "Verifying installation..."
mamba run -n unilab pip show unilabos mamba run -n unilab pip show unilabos
@@ -226,7 +242,9 @@ jobs:
if: steps.should_build.outputs.should_build == 'true' && matrix.platform == 'win-64' if: steps.should_build.outputs.should_build == 'true' && matrix.platform == 'win-64'
run: | run: |
echo Packing unilab environment with conda-pack... 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: echo Pack file created:
dir unilab-env-${{ matrix.platform }}.tar.gz dir unilab-env-${{ matrix.platform }}.tar.gz
@@ -235,8 +253,9 @@ jobs:
shell: bash shell: bash
run: | run: |
echo "Packing unilab environment with conda-pack..." echo "Packing unilab environment with conda-pack..."
mamba install conda-pack -c conda-forge -y UNILAB_PREFIX="$(mamba run -n unilab python -c 'import os; print(os.environ["CONDA_PREFIX"])')"
conda pack -n unilab -o unilab-env-${{ matrix.platform }}.tar.gz --ignore-missing-files 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:" echo "Pack file created:"
ls -lh unilab-env-${{ matrix.platform }}.tar.gz ls -lh unilab-env-${{ matrix.platform }}.tar.gz
@@ -267,7 +286,7 @@ jobs:
rem Create README using Python script rem Create README using Python script
echo Creating: README.txt 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.
echo Distribution package contents: echo Distribution package contents:
@@ -303,7 +322,7 @@ jobs:
# Create README using Python script # Create README using Python script
echo "Creating: README.txt" 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 ""
echo "Distribution package contents:" echo "Distribution package contents:"
@@ -314,7 +333,7 @@ jobs:
if: steps.should_build.outputs.should_build == 'true' if: steps.should_build.outputs.should_build == 'true'
uses: actions/upload-artifact@v6 uses: actions/upload-artifact@v6
with: with:
name: unilab-pack-${{ matrix.platform }}-${{ github.event.inputs.branch }} name: unilab-pack-${{ matrix.platform }}-${{ env.PACKAGE_REF }}
path: dist-package/ path: dist-package/
retention-days: 90 retention-days: 90
if-no-files-found: error if-no-files-found: error
@@ -326,9 +345,9 @@ jobs:
echo Build Summary echo Build Summary
echo ========================================== echo ==========================================
echo Platform: ${{ matrix.platform }} echo Platform: ${{ matrix.platform }}
echo Branch: ${{ github.event.inputs.branch }} echo Branch: ${{ env.PACKAGE_REF }}
echo Python version: 3.11.14 echo Python version: 3.11.14
if "${{ github.event.inputs.build_full }}"=="true" ( if "${{ env.BUILD_FULL }}"=="true" (
echo Package: unilabos-full ^(complete^) echo Package: unilabos-full ^(complete^)
) else ( ) else (
echo Package: unilabos ^(minimal^) echo Package: unilabos ^(minimal^)
@@ -337,7 +356,7 @@ jobs:
echo Distribution package contents: echo Distribution package contents:
dir dist-package dir dist-package
echo. echo.
echo Artifact name: unilab-pack-${{ matrix.platform }}-${{ github.event.inputs.branch }} echo Artifact name: unilab-pack-${{ matrix.platform }}-${{ env.PACKAGE_REF }}
echo. echo.
echo After download, extract the ZIP and run: echo After download, extract the ZIP and run:
echo install_unilab.bat echo install_unilab.bat
@@ -351,9 +370,9 @@ jobs:
echo "Build Summary" echo "Build Summary"
echo "==========================================" echo "=========================================="
echo "Platform: ${{ matrix.platform }}" echo "Platform: ${{ matrix.platform }}"
echo "Branch: ${{ github.event.inputs.branch }}" echo "Branch: ${{ env.PACKAGE_REF }}"
echo "Python version: 3.11.14" 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)" echo "Package: unilabos-full (complete)"
else else
echo "Package: unilabos (minimal)" echo "Package: unilabos (minimal)"
@@ -362,7 +381,7 @@ jobs:
echo "Distribution package contents:" echo "Distribution package contents:"
ls -lh dist-package/ ls -lh dist-package/
echo "" echo ""
echo "Artifact name: unilab-pack-${{ matrix.platform }}-${{ github.event.inputs.branch }}" echo "Artifact name: unilab-pack-${{ matrix.platform }}-${{ env.PACKAGE_REF }}"
echo "" echo ""
echo "After download:" echo "After download:"
echo " install_unilab.sh" echo " install_unilab.sh"

View File

@@ -56,7 +56,7 @@ jobs:
miniforge-version: latest miniforge-version: latest
use-mamba: true use-mamba: true
python-version: '3.11.14' python-version: '3.11.14'
channels: conda-forge,robostack-staging,uni-lab,defaults channels: conda-forge,robostack-staging,uni-lab
channel-priority: flexible channel-priority: flexible
activate-environment: unilab activate-environment: unilab
auto-update-conda: false auto-update-conda: false
@@ -66,7 +66,7 @@ jobs:
run: | run: |
echo "Installing unilabos and dependencies to unilab environment..." echo "Installing unilabos and dependencies to unilab environment..."
echo "Using mamba for faster and more reliable dependency resolution..." 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 - name: Install latest unilabos from source
run: | run: |

View File

@@ -10,6 +10,9 @@ on:
# 支持 tag 推送(不依赖 CI Check # 支持 tag 推送(不依赖 CI Check
push: push:
tags: ['v*'] tags: ['v*']
# GitHub Release 发布时自动构建并上传
release:
types: [published]
# 手动触发 # 手动触发
workflow_dispatch: workflow_dispatch:
inputs: inputs:
@@ -80,7 +83,7 @@ jobs:
- uses: actions/checkout@v6 - uses: actions/checkout@v6
with: with:
# 如果是 workflow_run 触发,使用触发 CI Check 的 commit # 如果是 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 fetch-depth: 0
- name: Check if platform should be built - name: Check if platform should be built
@@ -96,12 +99,13 @@ jobs:
echo "should_build=false" >> $GITHUB_OUTPUT echo "should_build=false" >> $GITHUB_OUTPUT
fi fi
- name: Setup Miniconda - name: Setup Miniforge
if: steps.should_build.outputs.should_build == 'true' if: steps.should_build.outputs.should_build == 'true'
uses: conda-incubator/setup-miniconda@v3 uses: conda-incubator/setup-miniconda@v3
with: with:
miniconda-version: 'latest' miniforge-version: latest
channels: conda-forge,robostack-staging,defaults use-mamba: true
channels: conda-forge,robostack-staging
channel-priority: strict channel-priority: strict
activate-environment: build-env activate-environment: build-env
auto-update-conda: false auto-update-conda: false
@@ -110,7 +114,7 @@ jobs:
- name: Install rattler-build and anaconda-client - name: Install rattler-build and anaconda-client
if: steps.should_build.outputs.should_build == 'true' if: steps.should_build.outputs.should_build == 'true'
run: | 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 - name: Show environment info
if: steps.should_build.outputs.should_build == 'true' if: steps.should_build.outputs.should_build == 'true'
@@ -157,7 +161,13 @@ jobs:
retention-days: 30 retention-days: 30
- name: Upload to Anaconda.org (unilab organization) - 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: | run: |
for package in $(find ./output -name "*.conda"); do for package in $(find ./output -name "*.conda"); do
echo "Uploading $package to unilab organization..." echo "Uploading $package to unilab organization..."

View File

@@ -1,14 +1,10 @@
name: UniLabOS Conda Build name: UniLabOS Conda Build
on: on:
# 在 CI Check 成功后自动触发 # 在 Multi-Platform Conda Build 成功上传 msgs 后自动触发
workflow_run: workflow_run:
workflows: ["CI Check"] workflows: ["Multi-Platform Conda Build"]
types: [completed] types: [completed]
branches: [main, dev]
# 标签推送时直接触发(发布版本)
push:
tags: ['v*']
# 手动触发 # 手动触发
workflow_dispatch: workflow_dispatch:
inputs: inputs:
@@ -33,30 +29,30 @@ on:
type: boolean type: boolean
jobs: jobs:
# 等待 CI Check 完成的 job (仅用于 workflow_run 触发) # 等待上游 msgs 构建完成的 job (仅用于 workflow_run 触发)
wait-for-ci: wait-for-upstream:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.event_name == 'workflow_run' if: github.event_name == 'workflow_run'
outputs: outputs:
should_continue: ${{ steps.check.outputs.should_continue }} should_continue: ${{ steps.check.outputs.should_continue }}
steps: steps:
- name: Check CI status - name: Check upstream workflow status
id: check id: check
run: | 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 "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 else
echo "should_continue=false" >> $GITHUB_OUTPUT 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 fi
build: build:
needs: [wait-for-ci] needs: [wait-for-upstream]
# 运行条件workflow_run 触发且 CI 成功,或者其他触发方式 # 运行条件workflow_run 触发且上游成功,或者手动触发
if: | if: |
always() && 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: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -79,7 +75,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v6
with: with:
# 如果是 workflow_run 触发,使用触发 CI Check 的 commit # 如果是 workflow_run 触发,使用上游 conda 包构建的 commit
ref: ${{ github.event.workflow_run.head_sha || github.ref }} ref: ${{ github.event.workflow_run.head_sha || github.ref }}
fetch-depth: 0 fetch-depth: 0
@@ -96,12 +92,13 @@ jobs:
echo "should_build=false" >> $GITHUB_OUTPUT echo "should_build=false" >> $GITHUB_OUTPUT
fi fi
- name: Setup Miniconda - name: Setup Miniforge
if: steps.should_build.outputs.should_build == 'true' if: steps.should_build.outputs.should_build == 'true'
uses: conda-incubator/setup-miniconda@v3 uses: conda-incubator/setup-miniconda@v3
with: with:
miniconda-version: 'latest' miniforge-version: latest
channels: conda-forge,robostack-staging,uni-lab,defaults use-mamba: true
channels: conda-forge,robostack-staging,uni-lab
channel-priority: strict channel-priority: strict
activate-environment: build-env activate-environment: build-env
auto-update-conda: false auto-update-conda: false
@@ -110,7 +107,7 @@ jobs:
- name: Install rattler-build and anaconda-client - name: Install rattler-build and anaconda-client
if: steps.should_build.outputs.should_build == 'true' if: steps.should_build.outputs.should_build == 'true'
run: | 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 - name: Show environment info
if: steps.should_build.outputs.should_build == 'true' if: steps.should_build.outputs.should_build == 'true'
@@ -119,11 +116,11 @@ jobs:
conda list | grep -E "(rattler-build|anaconda-client)" conda list | grep -E "(rattler-build|anaconda-client)"
echo "Platform: ${{ matrix.platform }}" echo "Platform: ${{ matrix.platform }}"
echo "OS: ${{ matrix.os }}" 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 "Building packages:"
echo " - unilabos-env (environment dependencies)" echo " - unilabos-env (environment dependencies)"
echo " - unilabos (with pip package)" 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)" echo " - unilabos-full (complete package)"
fi fi
@@ -134,7 +131,12 @@ jobs:
rattler-build build -r .conda/environment/recipe.yaml -c uni-lab -c robostack-staging -c conda-forge 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) - 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: | run: |
echo "Uploading unilabos-env to uni-lab organization..." echo "Uploading unilabos-env to uni-lab organization..."
for package in $(find ./output -name "unilabos-env*.conda"); do 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 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) - 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: | run: |
echo "Uploading unilabos to uni-lab organization..." echo "Uploading unilabos to uni-lab organization..."
for package in $(find ./output -name "unilabos-0*.conda" -o -name "unilabos-[0-9]*.conda"); do 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 - name: Build unilabos-full - Only when explicitly requested
if: | if: |
steps.should_build.outputs.should_build == 'true' && steps.should_build.outputs.should_build == 'true' &&
github.event_name == 'workflow_dispatch' &&
github.event.inputs.build_full == 'true' github.event.inputs.build_full == 'true'
run: | run: |
echo "Building unilabos-full package on ${{ matrix.platform }}..." echo "Building unilabos-full package on ${{ matrix.platform }}..."
@@ -167,6 +175,7 @@ jobs:
- name: Upload unilabos-full to Anaconda.org (if enabled) - name: Upload unilabos-full to Anaconda.org (if enabled)
if: | if: |
steps.should_build.outputs.should_build == 'true' && steps.should_build.outputs.should_build == 'true' &&
github.event_name == 'workflow_dispatch' &&
github.event.inputs.build_full == 'true' && github.event.inputs.build_full == 'true' &&
github.event.inputs.upload_to_anaconda == 'true' github.event.inputs.upload_to_anaconda == 'true'
run: | run: |

View File

@@ -1,5 +1,5 @@
channel_sources: channel_sources:
- robostack,robostack-staging,conda-forge,defaults - robostack,robostack-staging,conda-forge
gazebo: gazebo:
- '11' - '11'

View File

@@ -10,29 +10,170 @@ import shutil
import sys 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`` 的代码路径直接 importreconfigure
失败也只是退回 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(): 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"): if sys.platform != "win32" or not os.environ.get("CONDA_PREFIX"):
return return
try: try:
import rclpy import rclpy # noqa: F401
return return
except ImportError as e: except ImportError as e:
if not str(e).startswith("DLL load failed"): if not str(e).startswith("DLL load failed"):
return return
cp = os.environ["CONDA_PREFIX"] cp = os.environ["CONDA_PREFIX"]
impl = os.path.join(cp, "Lib", "site-packages", "rclpy", "impl", "implementation_singleton.py") lib_bin = os.path.join(cp, "Library", "bin")
pyd = glob.glob(os.path.join(cp, "Lib", "site-packages", "rclpy", "_rclpy_pybind11*.pyd")) site_packages = os.path.join(cp, "Lib", "site-packages")
if not os.path.exists(impl) or not pyd: if not os.path.isdir(lib_bin):
return return
with open(impl, "r", encoding="utf-8") as f:
content = f.read() patched = []
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' # 1) rclpy 自身的入口
shutil.copy2(impl, impl + ".bak") rclpy_impl = os.path.join(site_packages, "rclpy", "impl", "implementation_singleton.py")
with open(impl, "w", encoding="utf-8") as f: rclpy_pyd_matches = glob.glob(os.path.join(site_packages, "rclpy", "_rclpy_pybind11*.pyd"))
f.write(patch + content) 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() patch_rclpy_dll_windows()

View File

@@ -51,14 +51,18 @@ Qone_nmr:
properties: properties:
check_interval: check_interval:
default: 60 default: 60
description: 检查间隔时间默认60秒
type: string type: string
expected_count: expected_count:
default: 1 default: 1
description: 期望生成的.nmr文件数量默认1个
type: string type: string
monitor_dir: monitor_dir:
description: 要监督的目录路径如果未指定则使用self.monitor_directory
type: string type: string
stability_checks: stability_checks:
default: 3 default: 3
description: 文件大小稳定性检查次数默认3次
type: string type: string
required: [] required: []
type: object type: object
@@ -85,11 +89,14 @@ Qone_nmr:
goal: goal:
properties: properties:
output_dir: output_dir:
description: 输出目录如果未指定使用self.output_directory
type: string type: string
string_list: string_list:
description: 字符串列表
type: string type: string
txt_encoding: txt_encoding:
default: utf-8 default: utf-8
description: 文件编码
type: string type: string
required: required:
- string_list - string_list
@@ -151,6 +158,13 @@ Qone_nmr:
additionalProperties: false additionalProperties: false
properties: properties:
string: 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 type: string
title: StrSingleInput_Goal title: StrSingleInput_Goal
type: object type: object

View File

@@ -491,14 +491,17 @@ bioyond_cell:
goal: goal:
properties: properties:
material_names: material_names:
description: 物料名称列表;默认使用 [LiPF6, LiDFOB, DTD, LiFSI, LiPO2F2]
items: items:
type: string type: string
type: array type: array
type_id: type_id:
default: 3a190ca0-b2f6-9aeb-8067-547e72c11469 default: 3a190ca0-b2f6-9aeb-8067-547e72c11469
description: 物料类型ID
type: string type: string
warehouse_name: warehouse_name:
default: 粉末加样头堆栈 default: 粉末加样头堆栈
description: 目标仓库名(用于取位置信息)
type: string type: string
required: [] required: []
type: object type: object
@@ -527,12 +530,16 @@ bioyond_cell:
goal: goal:
properties: properties:
location_name_or_id: location_name_or_id:
description: 具体库位名称(如 A01或库位 UUID由用户指定。
type: string type: string
material_name: material_name:
description: 物料名称(会优先匹配配置模板)。
type: string type: string
type_id: type_id:
description: 物料类型 ID若为空则尝试从配置推断
type: string type: string
warehouse_name: warehouse_name:
description: 需要入库的仓库名称;若为空则仅创建不入库。
type: string type: string
required: required:
- material_name - material_name
@@ -661,15 +668,20 @@ bioyond_cell:
goal: goal:
properties: properties:
board_type: board_type:
description: 板类型,如 "5ml分液瓶板"、"配液瓶(小)板"
type: string type: string
bottle_type: bottle_type:
description: 瓶类型,如 "5ml分液瓶"、"配液瓶(小)"
type: string type: string
location_code: location_code:
description: 库位编号,例如 "A01"
type: string type: string
name: name:
description: 物料名称
type: string type: string
warehouse_name: warehouse_name:
default: 手动堆栈 default: 手动堆栈
description: 仓库名称,默认为 "手动堆栈",支持 "自动堆栈-左"、"自动堆栈-右" 等
type: string type: string
required: required:
- name - name
@@ -1956,7 +1968,7 @@ bioyond_cell:
properties: properties:
source_wh_id: source_wh_id:
default: 3a19debc-84b4-0359-e2d4-b3beea49348b default: 3a19debc-84b4-0359-e2d4-b3beea49348b
description: 来源仓库ID description: 来源仓库 Id (默认为3号仓库)
type: string type: string
source_x: source_x:
default: 1 default: 1
@@ -2061,9 +2073,11 @@ bioyond_cell:
goal: goal:
properties: properties:
order_code: order_code:
description: 任务编号
type: string type: string
timeout: timeout:
default: 36000 default: 36000
description: 超时时间(秒)
type: integer type: integer
required: required:
- order_code - order_code
@@ -2092,12 +2106,15 @@ bioyond_cell:
goal: goal:
properties: properties:
order_code: order_code:
description: 任务编号
type: string type: string
poll_interval: poll_interval:
default: 0.5 default: 0.5
description: 轮询间隔(秒),默认 0.5 秒
type: number type: number
timeout: timeout:
default: 36000 default: 36000
description: 超时时间(秒)
type: integer type: integer
required: required:
- order_code - order_code
@@ -2154,10 +2171,15 @@ bioyond_cell:
config: config:
properties: properties:
bioyond_config: bioyond_config:
description: '从 JSON 文件加载的 bioyond 配置字典
包含 api_host, api_key, HTTP_host, HTTP_port 等配置'
type: object type: object
deck: deck:
description: Deck 配置(可选,会从 JSON 中自动处理)
type: string type: string
protocol_type: protocol_type:
description: 协议类型(可选)
type: string type: string
required: [] required: []
type: object type: object

View File

@@ -47,8 +47,10 @@ bioyond_dispensing_station:
goal: goal:
properties: properties:
report_request: report_request:
description: WorkstationReportRequest 对象,包含任务完成信息
type: string type: string
used_materials: used_materials:
description: 物料使用记录列表
type: string type: string
required: required:
- report_request - report_request
@@ -102,6 +104,7 @@ bioyond_dispensing_station:
goal: goal:
properties: properties:
material_name: material_name:
description: 物料名称
type: string type: string
required: required:
- material_name - material_name
@@ -611,10 +614,10 @@ bioyond_dispensing_station:
goal: goal:
properties: properties:
target_device_id: target_device_id:
description: 目标反应站设备ID(从设备列表中选择,所有转移组使用同一个目标设备 description: 目标反应站设备ID(所有转移组使用同一个设备)
type: string type: string
transfer_groups: transfer_groups:
description: 转移任务组列表每组包含物料名称、目标堆栈和目标库位,可以添加多组 description: '转移任务组列表,每组包含:'
type: array type: array
required: required:
- target_device_id - target_device_id
@@ -694,10 +697,13 @@ bioyond_dispensing_station:
config: config:
properties: properties:
config: config:
description: 配置字典,应包含material_type_mappings等配置
type: object type: object
deck: deck:
description: Deck对象
type: string type: string
protocol_type: protocol_type:
description: 协议类型(由ROS系统传递,此处忽略)
type: string type: string
required: [] required: []
type: object type: object

View File

@@ -158,7 +158,7 @@ coincellassemblyworkstation_device:
type: integer type: integer
battery_clean_ignore: battery_clean_ignore:
default: false default: false
description: 是否忽略电池清洁步骤 description: 是否忽略电池清洁
type: boolean type: boolean
battery_pressure_mode: battery_pressure_mode:
default: true default: true
@@ -170,21 +170,21 @@ coincellassemblyworkstation_device:
type: integer type: integer
dual_drop_mode: dual_drop_mode:
default: false default: false
description: 电解液添加模式(false=单次滴液, true=二次滴液) description: 电解液添加模式 (False=单次滴液, True=二次滴液)
type: boolean type: boolean
dual_drop_start_timing: dual_drop_start_timing:
default: false default: false
description: 二次滴液开始滴液时机(false=正极片前, true=正极片后) description: 二次滴液开始滴液时机 (False=正极片前, True=正极片后)
type: boolean type: boolean
dual_drop_suction_timing: dual_drop_suction_timing:
default: false default: false
description: 二次滴液吸液时机(false=正常吸液, true=先吸液) description: 二次滴液吸液时机 (False=正常吸液, True=先吸液)
type: boolean type: boolean
elec_num: elec_num:
description: 电解液瓶数 description: 电解液瓶数
type: string type: string
elec_use_num: elec_use_num:
description: 每瓶电解液组装电池数 description: 每瓶电解液组装电池数
type: string type: string
elec_vol: elec_vol:
default: 50 default: 50
@@ -196,7 +196,7 @@ coincellassemblyworkstation_device:
type: string type: string
fujipian_juzhendianwei: fujipian_juzhendianwei:
default: 0 default: 0
description: 负极片矩阵点位。盘位置从1开始计数有效范围1-8, 13-20 (写入值比实际位置少1例如写0取盘位1写1取盘位2) description: 负极片矩阵点位
type: integer type: integer
fujipian_panshu: fujipian_panshu:
default: 0 default: 0
@@ -204,7 +204,7 @@ coincellassemblyworkstation_device:
type: integer type: integer
gemo_juzhendianwei: gemo_juzhendianwei:
default: 0 default: 0
description: 隔膜矩阵点位。盘位置从1开始计数有效范围1-8, 13-20 (写入值比实际位置少1例如写0取盘位1写1取盘位2) description: 隔膜矩阵点位
type: integer type: integer
gemopanshu: gemopanshu:
default: 0 default: 0
@@ -216,7 +216,7 @@ coincellassemblyworkstation_device:
type: boolean type: boolean
qiangtou_juzhendianwei: qiangtou_juzhendianwei:
default: 0 default: 0
description: 枪头盒矩阵点位。盘位置从1开始计数有效范围1-32, 64-96 (写入值比实际位置少1例如写0取盘位1写1取盘位2) description: 枪头盒矩阵点位
type: integer type: integer
required: required:
- elec_num - elec_num
@@ -308,7 +308,13 @@ coincellassemblyworkstation_device:
properties: properties:
material_search_enable: material_search_enable:
default: false default: false
description: 是否启用物料搜寻功能。设备初始化后会弹出物料搜寻确认弹窗,此参数控制自动点击"是"(启用)或"否"(不启用)。默认为false(不启用物料搜寻) description: '是否启用物料搜寻功能。
设备初始化后会弹出物料搜寻确认弹窗,
此参数控制自动点击''是''(启用)或''否''(不启用)。
默认为False不启用物料搜寻。'
type: boolean type: boolean
required: [] required: []
type: object type: object
@@ -555,7 +561,7 @@ coincellassemblyworkstation_device:
type: integer type: integer
battery_clean_ignore: battery_clean_ignore:
default: false default: false
description: 是否忽略电池清洁步骤 description: 是否忽略电池清洁
type: boolean type: boolean
battery_pressure_mode: battery_pressure_mode:
default: true default: true
@@ -567,21 +573,21 @@ coincellassemblyworkstation_device:
type: integer type: integer
dual_drop_mode: dual_drop_mode:
default: false default: false
description: 电解液添加模式(false=单次滴液, true=二次滴液) description: 电解液添加模式 (False=单次滴液, True=二次滴液)
type: boolean type: boolean
dual_drop_start_timing: dual_drop_start_timing:
default: false default: false
description: 二次滴液开始滴液时机(false=正极片前, true=正极片后) description: 二次滴液开始滴液时机 (False=正极片前, True=正极片后)
type: boolean type: boolean
dual_drop_suction_timing: dual_drop_suction_timing:
default: false default: false
description: 二次滴液吸液时机(false=正常吸液, true=先吸液) description: 二次滴液吸液时机 (False=正常吸液, True=先吸液)
type: boolean type: boolean
elec_num: elec_num:
description: 电解液瓶数如果在workflow中已通过handles连接上游(create_orders的bottle_count输出),则此参数会自动从上游获取,无需手动填写;如果单独使用此函数(没有上游连接),则必须手动填写电解液瓶数 description: 电解液瓶数
type: string type: string
elec_use_num: elec_use_num:
description: 每瓶电解液组装电池数 description: 每瓶电解液组装电池数
type: string type: string
elec_vol: elec_vol:
default: 50 default: 50
@@ -593,7 +599,7 @@ coincellassemblyworkstation_device:
type: string type: string
fujipian_juzhendianwei: fujipian_juzhendianwei:
default: 0 default: 0
description: 负极片矩阵点位。盘位置从1开始计数有效范围1-8, 13-20 (写入值比实际位置少1例如写0取盘位1写1取盘位2) description: 负极片矩阵点位
type: integer type: integer
fujipian_panshu: fujipian_panshu:
default: 0 default: 0
@@ -601,7 +607,7 @@ coincellassemblyworkstation_device:
type: integer type: integer
gemo_juzhendianwei: gemo_juzhendianwei:
default: 0 default: 0
description: 隔膜矩阵点位。盘位置从1开始计数有效范围1-8, 13-20 (写入值比实际位置少1例如写0取盘位1写1取盘位2) description: 隔膜矩阵点位
type: integer type: integer
gemopanshu: gemopanshu:
default: 0 default: 0
@@ -613,7 +619,7 @@ coincellassemblyworkstation_device:
type: boolean type: boolean
qiangtou_juzhendianwei: qiangtou_juzhendianwei:
default: 0 default: 0
description: 枪头盒矩阵点位。盘位置从1开始计数有效范围1-32, 64-96 (写入值比实际位置少1例如写0取盘位1写1取盘位2) description: 枪头盒矩阵点位
type: integer type: integer
required: required:
- elec_num - elec_num

View File

@@ -18,6 +18,7 @@ xyz_stepper_controller:
goal: goal:
properties: properties:
degrees: degrees:
description: 角度值
type: number type: number
required: required:
- degrees - degrees
@@ -44,6 +45,7 @@ xyz_stepper_controller:
goal: goal:
properties: properties:
axis: axis:
description: 电机轴
type: object type: object
required: required:
- axis - axis
@@ -71,6 +73,7 @@ xyz_stepper_controller:
properties: properties:
enable: enable:
default: true default: true
description: True为使能False为失能
type: boolean type: boolean
required: [] required: []
type: object type: object
@@ -99,9 +102,11 @@ xyz_stepper_controller:
goal: goal:
properties: properties:
axis: axis:
description: 电机轴
type: object type: object
enable: enable:
default: true default: true
description: True为使能False为失能
type: boolean type: boolean
required: required:
- axis - axis
@@ -152,6 +157,7 @@ xyz_stepper_controller:
goal: goal:
properties: properties:
axis: axis:
description: 电机轴
type: object type: object
required: required:
- axis - axis
@@ -183,16 +189,21 @@ xyz_stepper_controller:
properties: properties:
acceleration: acceleration:
default: 1000 default: 1000
description: 加速度(rpm/s)
type: integer type: integer
axis: axis:
description: 电机轴
type: object type: object
position: position:
description: 目标位置(步数)
type: integer type: integer
precision: precision:
default: 100 default: 100
description: 到位精度
type: integer type: integer
speed: speed:
default: 5000 default: 5000
description: 运行速度(rpm)
type: integer type: integer
required: required:
- axis - axis
@@ -225,16 +236,21 @@ xyz_stepper_controller:
properties: properties:
acceleration: acceleration:
default: 1000 default: 1000
description: 加速度
type: integer type: integer
axis: axis:
description: 电机轴
type: object type: object
degrees: degrees:
description: 目标角度(度)
type: number type: number
precision: precision:
default: 100 default: 100
description: 精度
type: integer type: integer
speed: speed:
default: 5000 default: 5000
description: 移动速度
type: integer type: integer
required: required:
- axis - axis
@@ -267,16 +283,21 @@ xyz_stepper_controller:
properties: properties:
acceleration: acceleration:
default: 1000 default: 1000
description: 加速度
type: integer type: integer
axis: axis:
description: 电机轴
type: object type: object
precision: precision:
default: 100 default: 100
description: 精度
type: integer type: integer
revolutions: revolutions:
description: 目标圈数
type: number type: number
speed: speed:
default: 5000 default: 5000
description: 移动速度
type: integer type: integer
required: required:
- axis - axis
@@ -309,15 +330,20 @@ xyz_stepper_controller:
properties: properties:
acceleration: acceleration:
default: 1000 default: 1000
description: 加速度
type: integer type: integer
speed: speed:
default: 5000 default: 5000
description: 运行速度
type: integer type: integer
x: x:
description: X轴目标位置
type: integer type: integer
y: y:
description: Y轴目标位置
type: integer type: integer
z: z:
description: Z轴目标位置
type: integer type: integer
required: [] required: []
type: object type: object
@@ -350,15 +376,20 @@ xyz_stepper_controller:
properties: properties:
acceleration: acceleration:
default: 1000 default: 1000
description: 加速度
type: integer type: integer
speed: speed:
default: 5000 default: 5000
description: 移动速度
type: integer type: integer
x_deg: x_deg:
description: X轴目标角度
type: number type: number
y_deg: y_deg:
description: Y轴目标角度
type: number type: number
z_deg: z_deg:
description: Z轴目标角度
type: number type: number
required: [] required: []
type: object type: object
@@ -391,15 +422,20 @@ xyz_stepper_controller:
properties: properties:
acceleration: acceleration:
default: 1000 default: 1000
description: 加速度
type: integer type: integer
speed: speed:
default: 5000 default: 5000
description: 移动速度
type: integer type: integer
x_rev: x_rev:
description: X轴目标圈数
type: number type: number
y_rev: y_rev:
description: Y轴目标圈数
type: number type: number
z_rev: z_rev:
description: Z轴目标圈数
type: number type: number
required: [] required: []
type: object type: object
@@ -427,6 +463,7 @@ xyz_stepper_controller:
goal: goal:
properties: properties:
revolutions: revolutions:
description: 圈数
type: number type: number
required: required:
- revolutions - revolutions
@@ -456,10 +493,13 @@ xyz_stepper_controller:
properties: properties:
acceleration: acceleration:
default: 1000 default: 1000
description: 加速度(rpm/s)
type: integer type: integer
axis: axis:
description: 电机轴
type: object type: object
speed: speed:
description: 运行速度(rpm),正值正转,负值反转
type: integer type: integer
required: required:
- axis - axis
@@ -487,6 +527,7 @@ xyz_stepper_controller:
goal: goal:
properties: properties:
steps: steps:
description: 步数
type: integer type: integer
required: required:
- steps - steps
@@ -513,6 +554,7 @@ xyz_stepper_controller:
goal: goal:
properties: properties:
steps: steps:
description: 步数
type: integer type: integer
required: required:
- steps - steps
@@ -564,9 +606,11 @@ xyz_stepper_controller:
goal: goal:
properties: properties:
axis: axis:
description: 电机轴
type: object type: object
timeout: timeout:
default: 30.0 default: 30.0
description: 超时时间(秒)
type: number type: number
required: required:
- axis - axis
@@ -591,11 +635,14 @@ xyz_stepper_controller:
properties: properties:
baudrate: baudrate:
default: 115200 default: 115200
description: 波特率
type: integer type: integer
port: port:
description: 串口端口名
type: string type: string
timeout: timeout:
default: 1.0 default: 1.0
description: 通信超时时间
type: number type: number
required: required:
- port - port

View File

@@ -510,9 +510,11 @@ liquid_handler:
goal: goal:
properties: properties:
msg: msg:
description: information to be printed
type: string type: string
seconds: seconds:
default: 0 default: 0
description: seconds to wait
type: string type: string
required: [] required: []
type: object type: object
@@ -2963,15 +2965,22 @@ liquid_handler:
additionalProperties: false additionalProperties: false
properties: properties:
channel: channel:
description: int
maximum: 2147483647 maximum: 2147483647
minimum: -2147483648 minimum: -2147483648
type: integer type: integer
dis_to_top: dis_to_top:
description: 'float
Height in mm to move to relative to the well top.'
maximum: 1.7976931348623157e+308 maximum: 1.7976931348623157e+308
minimum: -1.7976931348623157e+308 minimum: -1.7976931348623157e+308
type: number type: number
well: well:
additionalProperties: false additionalProperties: false
description: 'Well
The target well.'
properties: properties:
category: category:
type: string type: string
@@ -4829,11 +4838,13 @@ liquid_handler:
config: config:
properties: properties:
backend: backend:
description: Backend to use.
type: object type: object
channel_num: channel_num:
default: 8 default: 8
type: integer type: integer
deck: deck:
description: Deck to use.
type: object type: object
simulator: simulator:
default: false default: false
@@ -4883,14 +4894,17 @@ liquid_handler.biomek:
bind_parent_id: bind_parent_id:
type: string type: string
liquid_input_slot: liquid_input_slot:
description: 液体输入槽列表
items: items:
type: integer type: integer
type: array type: array
liquid_type: liquid_type:
description: 液体类型列表
items: items:
type: string type: string
type: array type: array
liquid_volume: liquid_volume:
description: 液体体积列表
items: items:
type: integer type: integer
type: array type: array
@@ -4901,6 +4915,7 @@ liquid_handler.biomek:
type: object type: object
type: array type: array
slot_on_deck: slot_on_deck:
description: 甲板上的槽位
type: integer type: integer
required: required:
- resource_tracker - resource_tracker
@@ -5036,20 +5051,27 @@ liquid_handler.biomek:
additionalProperties: false additionalProperties: false
properties: properties:
none_keys: none_keys:
description: 需要设置为None的键列表
items: items:
type: string type: string
type: array type: array
protocol_author: protocol_author:
description: 协议作者
type: string type: string
protocol_date: protocol_date:
description: 协议日期
type: string type: string
protocol_description: protocol_description:
description: 协议描述
type: string type: string
protocol_name: protocol_name:
description: 协议名称
type: string type: string
protocol_type: protocol_type:
description: 协议类型
type: string type: string
protocol_version: protocol_version:
description: 协议版本
type: string type: string
title: LiquidHandlerProtocolCreation_Goal title: LiquidHandlerProtocolCreation_Goal
type: object type: object

View File

@@ -87,7 +87,7 @@ neware_battery_test_system:
properties: properties:
filepath: filepath:
default: bts_status.json default: bts_status.json
description: 输出JSON文件路径 description: 输出文件路径
type: string type: string
required: [] required: []
type: object type: object
@@ -146,7 +146,7 @@ neware_battery_test_system:
goal: goal:
properties: properties:
plate_num: plate_num:
description: 盘号 (1 或 2),如果为null则返回所有盘的状态 description: 盘号 (1 或 2),如果为None则返回所有盘的状态
type: integer type: integer
required: [] required: []
type: object type: object
@@ -237,11 +237,11 @@ neware_battery_test_system:
goal: goal:
properties: properties:
csv_path: csv_path:
description: 输入CSV文件的绝对路径 description: 输入CSV文件路径
type: string type: string
output_dir: output_dir:
default: . default: .
description: 输出目录用于存储XML和备份文件),默认当前目录 description: 输出目录用于存储XML文件和备份,默认当前目录
type: string type: string
required: required:
- csv_path - csv_path
@@ -302,14 +302,14 @@ neware_battery_test_system:
goal: goal:
properties: properties:
backup_dir: backup_dir:
description: 备份目录路径默认使用最近一次submit_from_csvbackup_dir description: 备份目录路径默认使用最近一次 submit_from_csvbackup_dir
type: string type: string
file_pattern: file_pattern:
default: '*' default: '*'
description: 文件通配符模式,例如 *.csv 或 Battery_*.nda description: 文件通配符模式,默认 "*" 上传所有文件(例如 "*.csv" 仅上传 CSV 文件)
type: string type: string
oss_prefix: oss_prefix:
description: OSS对象路径前缀默认使用self.oss_prefix description: OSS 对象前缀默认使用类初始化时的配置
type: string type: string
required: [] required: []
type: object type: object
@@ -336,19 +336,25 @@ neware_battery_test_system:
config: config:
properties: properties:
devtype: devtype:
description: 设备类型标识
type: string type: string
ip: ip:
description: TCP服务器IP地址
type: string type: string
machine_id: machine_id:
default: 1 default: 1
description: 机器ID
type: integer type: integer
oss_prefix: oss_prefix:
default: neware_backup default: neware_backup
description: OSS对象路径前缀默认"neware_backup"
type: string type: string
oss_upload_enabled: oss_upload_enabled:
default: false default: false
description: 是否启用OSS上传功能默认False
type: boolean type: boolean
port: port:
description: TCP端口
type: integer type: integer
size_x: size_x:
default: 50 default: 50
@@ -360,6 +366,7 @@ neware_battery_test_system:
default: 20 default: 20
type: number type: number
timeout: timeout:
description: 通信超时时间(秒)
type: integer type: integer
required: [] required: []
type: object type: object

View File

@@ -207,8 +207,12 @@ separator.homemade:
goal: goal:
properties: properties:
condition: condition:
description: The condition to be monitored, either 'delta' or 'time'.
type: string type: string
value: value:
description: 'The threshold value for the condition.
`delta > 0.05`, `time > 60`'
type: string type: string
required: required:
- condition - condition
@@ -305,12 +309,17 @@ separator.homemade:
event: event:
type: string type: string
settling_time: settling_time:
description: The duration for which to settle after stirring, in
seconds. Defaults to 10.
type: string type: string
stir_speed: stir_speed:
description: The speed of stirring, in RPM. Defaults to 300.
maximum: 1.7976931348623157e+308 maximum: 1.7976931348623157e+308
minimum: -1.7976931348623157e+308 minimum: -1.7976931348623157e+308
type: number type: number
stir_time: stir_time:
description: The duration for which to stir, in seconds. Defaults
to 10.
maximum: 1.7976931348623157e+308 maximum: 1.7976931348623157e+308
minimum: -1.7976931348623157e+308 minimum: -1.7976931348623157e+308
type: number type: number

View File

@@ -456,6 +456,7 @@ syringe_pump_with_valve.runze.SY03B-T06:
goal: goal:
properties: properties:
volume: volume:
description: 'absolute position of the plunger, unit: mL'
type: number type: number
required: required:
- volume - volume
@@ -481,6 +482,7 @@ syringe_pump_with_valve.runze.SY03B-T06:
goal: goal:
properties: properties:
volume: volume:
description: 'absolute position of the plunger, unit: mL'
type: number type: number
required: required:
- volume - volume
@@ -687,8 +689,10 @@ syringe_pump_with_valve.runze.SY03B-T06:
goal: goal:
properties: properties:
max_velocity: max_velocity:
description: 'maximum velocity of the plunger, unit: ml/s'
type: number type: number
position: position:
description: 'absolute position of the plunger, unit: ml'
type: number type: number
required: required:
- position - position
@@ -1003,6 +1007,7 @@ syringe_pump_with_valve.runze.SY03B-T08:
goal: goal:
properties: properties:
volume: volume:
description: 'absolute position of the plunger, unit: mL'
type: number type: number
required: required:
- volume - volume
@@ -1028,6 +1033,7 @@ syringe_pump_with_valve.runze.SY03B-T08:
goal: goal:
properties: properties:
volume: volume:
description: 'absolute position of the plunger, unit: mL'
type: number type: number
required: required:
- volume - volume
@@ -1234,8 +1240,10 @@ syringe_pump_with_valve.runze.SY03B-T08:
goal: goal:
properties: properties:
max_velocity: max_velocity:
description: 'maximum velocity of the plunger, unit: ml/s'
type: number type: number
position: position:
description: 'absolute position of the plunger, unit: ml'
type: number type: number
required: required:
- position - position

View File

@@ -32,7 +32,7 @@ reaction_station.bioyond:
type: integer type: integer
end_point: end_point:
default: 0 default: 0
description: 终点计时点 (Start=开始前, End=结束后) description: 终点计时点 (Start=0, End=1)
type: integer type: integer
end_step_key: end_step_key:
default: '' default: ''
@@ -40,11 +40,11 @@ reaction_station.bioyond:
type: string type: string
start_point: start_point:
default: 0 default: 0
description: 起点计时点 (Start=开始前, End=结束后) description: 起点计时点 (Start=0, End=1)
type: integer type: integer
start_step_key: start_step_key:
default: '' default: ''
description: 起点步骤Key (例如 "feeding", "liquid", 可选, 默认为空则自动选择) description: 起点步骤Key (可选, 默认为空则自动选择)
type: string type: string
required: required:
- duration - duration
@@ -91,6 +91,7 @@ reaction_station.bioyond:
goal: goal:
properties: properties:
json_str: json_str:
description: 订单参数的JSON字符串
type: string type: string
required: required:
- json_str - json_str
@@ -117,6 +118,7 @@ reaction_station.bioyond:
goal: goal:
properties: properties:
workflow_ids: workflow_ids:
description: 要删除的工作流ID数组
items: items:
type: string type: string
type: array type: array
@@ -145,6 +147,7 @@ reaction_station.bioyond:
goal: goal:
properties: properties:
json_str: json_str:
description: 'JSON格式的字符串,包含:'
type: string type: string
required: required:
- json_str - json_str
@@ -197,6 +200,7 @@ reaction_station.bioyond:
goal: goal:
properties: properties:
web_workflow_json: web_workflow_json:
description: JSON 格式的网页工作流列表
type: string type: string
required: required:
- web_workflow_json - web_workflow_json
@@ -228,8 +232,10 @@ reaction_station.bioyond:
goal: goal:
properties: properties:
reactor_id: reactor_id:
description: 反应器编号 (1-5)
type: integer type: integer
temperature: temperature:
description: 目标温度 (°C)
type: number type: number
required: required:
- reactor_id - reactor_id
@@ -257,6 +263,7 @@ reaction_station.bioyond:
goal: goal:
properties: properties:
preintake_id: preintake_id:
description: 通量ID
type: string type: string
required: required:
- preintake_id - preintake_id
@@ -338,6 +345,7 @@ reaction_station.bioyond:
goal: goal:
properties: properties:
value: value:
description: 工作流 ID 列表
items: items:
type: string type: string
type: array type: array
@@ -365,6 +373,7 @@ reaction_station.bioyond:
goal: goal:
properties: properties:
workflow_id: workflow_id:
description: 工作流ID
type: string type: string
required: required:
- workflow_id - workflow_id
@@ -424,11 +433,11 @@ reaction_station.bioyond:
goal: goal:
properties: properties:
assign_material_name: assign_material_name:
description: 物料名称(不能为空) description: 物料名称(液体种类)
type: string type: string
temperature: temperature:
default: 25.0 default: 25.0
description: 温度设定(°C) description: 温度(C)
type: number type: number
time: time:
default: '90' default: '90'
@@ -436,14 +445,14 @@ reaction_station.bioyond:
type: string type: string
titration_type: titration_type:
default: '1' default: '1'
description: 是否滴定(NO=, YES=) description: 是否滴定(NO=1, YES=2)
type: string type: string
torque_variation: torque_variation:
default: 2 default: 2
description: 是否观察 (NO=, YES=) description: 是否观察(NO=1, YES=2)
type: integer type: integer
volume: volume:
description: 分液公式(mL) description: 分液量(μL)
type: string type: string
required: required:
- assign_material_name - assign_material_name
@@ -525,11 +534,11 @@ reaction_station.bioyond:
properties: properties:
assign_material_name: assign_material_name:
default: BAPP default: BAPP
description: 物料名称 description: 物料名称(试剂瓶位)
type: string type: string
temperature: temperature:
default: 25.0 default: 25.0
description: 温度设定(°C) description: 温度设定(C)
type: number type: number
time: time:
default: '0' default: '0'
@@ -537,15 +546,15 @@ reaction_station.bioyond:
type: string type: string
titration_type: titration_type:
default: '1' default: '1'
description: 是否滴定(NO=, YES=) description: 是否滴定(NO=1, YES=2)
type: string type: string
torque_variation: torque_variation:
default: 1 default: 1
description: 是否观察 (NO=否, YES=是) description: 是否观察(int类型, 1=否, 2=是)
type: integer type: integer
volume: volume:
default: '350' default: '350'
description: 分液公式(mL) description: 分液质量(g)
type: string type: string
required: [] required: []
type: object type: object
@@ -593,26 +602,28 @@ reaction_station.bioyond:
description: 物料名称 description: 物料名称
type: string type: string
solvents: solvents:
description: '溶剂信息对象(可选),包含: additional_solvent(溶剂体积mL), total_liquid_volume(总液体体积mL)。如果提供,将自动计算volume' description: '溶剂信息的字典或JSON字符串(可选),格式如下:
{'
type: string type: string
temperature: temperature:
default: 25.0 default: 25.0
description: 温度设定(°C),默认25.00 description: 温度设定(C)
type: number type: number
time: time:
default: '360' default: '360'
description: 观察时间(分钟),默认360 description: 观察时间(分钟)
type: string type: string
titration_type: titration_type:
default: '1' default: '1'
description: 是否滴定(NO=, YES=是),默认NO description: 是否滴定(NO=1, YES=2)
type: string type: string
torque_variation: torque_variation:
default: 2 default: 2
description: 是否观察 (NO=, YES=是),默认YES description: 是否观察(NO=1, YES=2)
type: integer type: integer
volume: volume:
description: 分液量(mL)。可直接提供,或通过solvents参数自动计算 description: 分液量(μL),直接指定体积(可选,如果提供solvents自动计算)
type: string type: string
required: required:
- assign_material_name - assign_material_name
@@ -671,33 +682,32 @@ reaction_station.bioyond:
description: 物料名称 description: 物料名称
type: string type: string
extracted_actuals: extracted_actuals:
description: 从报告提取的实际加料量JSON字符串,包含actualTargetWeigh(m二酐滴定)和actualVolume(V二酐滴定) description: 从报告提取的实际加料量JSON字符串,包含actualTargetWeigh和actualVolume
type: string type: string
feeding_order_data: feeding_order_data:
description: 'feeding_order JSON对象,用于获取m二酐值(type为main_anhydride的amount)。示例: description: feeding_order JSON字符串或对象,用于获取m二酐值
{"feeding_order": [{"type": "main_anhydride", "amount": 1.915}]}'
type: string type: string
temperature: temperature:
default: 25.0 default: 25.0
description: 温度设定(°C),默认25.00 description: 温度(C)
type: number type: number
time: time:
default: '90' default: '90'
description: 观察时间(分钟),默认90 description: 观察时间(分钟)
type: string type: string
titration_type: titration_type:
default: '2' default: '2'
description: 是否滴定(NO=, YES=),默认YES description: 是否滴定(NO=1, YES=2),默认2
type: string type: string
torque_variation: torque_variation:
default: 2 default: 2
description: 是否观察 (NO=, YES=是),默认YES description: 是否观察(NO=1, YES=2)
type: integer type: integer
volume_formula: volume_formula:
description: 分液公式(mL)。可直接提供固定公式,或留空由系统根据x_value、feeding_order_data、extracted_actuals自动生成 description: 分液公式(μL),如果提供则直接使用,否则自动计算
type: string type: string
x_value: x_value:
description: 公式中的x值,手工输入,格式为"{{1-2-3}}"(包含双花括号)。用于自动公式计算 description: 手工输入的x值,格式如 "1-2-3"
type: string type: string
required: required:
- assign_material_name - assign_material_name
@@ -738,7 +748,7 @@ reaction_station.bioyond:
type: string type: string
temperature: temperature:
default: 25.0 default: 25.0
description: 温度设定(°C) description: 温度(C)
type: number type: number
time: time:
default: '0' default: '0'
@@ -746,14 +756,14 @@ reaction_station.bioyond:
type: string type: string
titration_type: titration_type:
default: '1' default: '1'
description: 是否滴定(NO=, YES=) description: 是否滴定(NO=1, YES=2)
type: string type: string
torque_variation: torque_variation:
default: 1 default: 1
description: 是否观察 (NO=, YES=) description: 是否观察(NO=1, YES=2)
type: integer type: integer
volume_formula: volume_formula:
description: 分液公式(mL) description: 分液公式(μL)
type: string type: string
required: required:
- volume_formula - volume_formula
@@ -786,7 +796,7 @@ reaction_station.bioyond:
description: 任务名称 description: 任务名称
type: string type: string
workflow_name: workflow_name:
description: 工作流名称 description: 合并后的工作流名称
type: string type: string
required: required:
- workflow_name - workflow_name
@@ -819,15 +829,15 @@ reaction_station.bioyond:
goal: goal:
properties: properties:
assign_material_name: assign_material_name:
description: 物料名称 description: 物料名称(不能为空)
type: string type: string
cutoff: cutoff:
default: '900000' default: '900000'
description: 粘度上限 description: 粘度上限(需为有效数字字符串,默认 "900000")
type: string type: string
temperature: temperature:
default: -10.0 default: -10.0
description: 温度设定(°C) description: 温度设定(C,范围:-50.00 至 100.00)
type: number type: number
required: required:
- assign_material_name - assign_material_name
@@ -909,11 +919,11 @@ reaction_station.bioyond:
description: 物料名称(用于获取试剂瓶位ID) description: 物料名称(用于获取试剂瓶位ID)
type: string type: string
material_id: material_id:
description: 粉末类型IDSalt=21分钟Flour=面粉27分钟BTDA=BTDA38分钟 description: 粉末类型ID, Salt=1, Flour=2, BTDA=3
type: string type: string
temperature: temperature:
default: 25.0 default: 25.0
description: 温度设定(°C) description: 温度设定(C)
type: number type: number
time: time:
default: '0' default: '0'
@@ -921,7 +931,7 @@ reaction_station.bioyond:
type: string type: string
torque_variation: torque_variation:
default: 1 default: 1
description: 是否观察 (NO=, YES=) description: 是否观察(NO=1, YES=2)
type: integer type: integer
required: required:
- material_id - material_id
@@ -945,10 +955,13 @@ reaction_station.bioyond:
config: config:
properties: properties:
config: config:
description: 配置字典,应包含workflow_mappings等配置
type: object type: object
deck: deck:
description: Deck对象
type: string type: string
protocol_type: protocol_type:
description: 协议类型(由ROS系统传递,此处忽略)
type: string type: string
required: [] required: []
type: object type: object

View File

@@ -198,6 +198,8 @@ robotic_arm.SCARA_with_slider.moveit.virtual:
additionalProperties: false additionalProperties: false
properties: properties:
command: command:
description: A JSON-formatted string that includes option, target,
speed, lift_height, mt_height
type: string type: string
title: SendCmd_Goal title: SendCmd_Goal
type: object type: object
@@ -241,6 +243,8 @@ robotic_arm.SCARA_with_slider.moveit.virtual:
additionalProperties: false additionalProperties: false
properties: properties:
command: command:
description: A JSON-formatted string that includes quaternion, speed,
position
type: string type: string
title: SendCmd_Goal title: SendCmd_Goal
type: object type: object
@@ -284,6 +288,7 @@ robotic_arm.SCARA_with_slider.moveit.virtual:
additionalProperties: false additionalProperties: false
properties: properties:
command: command:
description: A JSON-formatted string that includes speed
type: string type: string
title: SendCmd_Goal title: SendCmd_Goal
type: object type: object

View File

@@ -709,6 +709,8 @@ linear_motion.toyo_xyz.sim:
additionalProperties: false additionalProperties: false
properties: properties:
command: command:
description: A JSON-formatted string that includes option, target,
speed, lift_height, mt_height
type: string type: string
title: SendCmd_Goal title: SendCmd_Goal
type: object type: object
@@ -752,6 +754,8 @@ linear_motion.toyo_xyz.sim:
additionalProperties: false additionalProperties: false
properties: properties:
command: command:
description: A JSON-formatted string that includes quaternion, speed,
position
type: string type: string
title: SendCmd_Goal title: SendCmd_Goal
type: object type: object
@@ -795,6 +799,7 @@ linear_motion.toyo_xyz.sim:
additionalProperties: false additionalProperties: false
properties: properties:
command: command:
description: A JSON-formatted string that includes speed
type: string type: string
title: SendCmd_Goal title: SendCmd_Goal
type: object type: object

View File

@@ -2179,6 +2179,7 @@ virtual_multiway_valve:
goal: goal:
properties: properties:
port_number: port_number:
description: 端口号 (1-8)
type: integer type: integer
required: required:
- port_number - port_number
@@ -2225,6 +2226,7 @@ virtual_multiway_valve:
goal: goal:
properties: properties:
port_number: port_number:
description: 目标端口号 (1-8)
type: integer type: integer
required: required:
- port_number - port_number
@@ -2261,6 +2263,7 @@ virtual_multiway_valve:
additionalProperties: false additionalProperties: false
properties: properties:
command: command:
description: 目标位置 (0-8) 或位置字符串
type: string type: string
title: SendCmd_Goal title: SendCmd_Goal
type: object type: object
@@ -2304,6 +2307,7 @@ virtual_multiway_valve:
additionalProperties: false additionalProperties: false
properties: properties:
command: command:
description: 目标位置 (0-8) 或位置字符串
type: string type: string
title: SendCmd_Goal title: SendCmd_Goal
type: object type: object
@@ -4215,6 +4219,7 @@ virtual_solenoid_valve:
additionalProperties: false additionalProperties: false
properties: properties:
string: string:
description: '"ON"/"OFF" 或 "OPEN"/"CLOSED"'
type: string type: string
title: StrSingleInput_Goal title: StrSingleInput_Goal
type: object type: object
@@ -4258,6 +4263,7 @@ virtual_solenoid_valve:
additionalProperties: false additionalProperties: false
properties: properties:
command: command:
description: '"OPEN"/"CLOSED" 或其他控制命令'
type: string type: string
title: SendCmd_Goal title: SendCmd_Goal
type: object type: object
@@ -4418,16 +4424,20 @@ virtual_solid_dispenser:
event: event:
type: string type: string
mass: mass:
description: 质量字符串 (如 "2.9 g")
type: string type: string
mol: mol:
description: 摩尔数字符串 (如 "0.12 mol")
type: string type: string
purpose: purpose:
description: 添加目的
type: string type: string
rate_spec: rate_spec:
type: string type: string
ratio: ratio:
type: string type: string
reagent: reagent:
description: 试剂名称
type: string type: string
stir: stir:
type: boolean type: boolean
@@ -4439,6 +4449,7 @@ virtual_solid_dispenser:
type: string type: string
vessel: vessel:
additionalProperties: false additionalProperties: false
description: 目标容器
properties: properties:
category: category:
type: string type: string
@@ -5568,8 +5579,10 @@ virtual_transfer_pump:
goal: goal:
properties: properties:
velocity: velocity:
description: 拉取速度 (ml/s)
type: number type: number
volume: volume:
description: 要拉取的体积 (ml)
type: number type: number
required: required:
- volume - volume
@@ -5596,8 +5609,10 @@ virtual_transfer_pump:
goal: goal:
properties: properties:
velocity: velocity:
description: 推出速度 (ml/s)
type: number type: number
volume: volume:
description: 要推出的体积 (ml)
type: number type: number
required: required:
- volume - volume
@@ -5693,10 +5708,12 @@ virtual_transfer_pump:
additionalProperties: false additionalProperties: false
properties: properties:
max_velocity: max_velocity:
description: 移动速度 (ml/s)
maximum: 1.7976931348623157e+308 maximum: 1.7976931348623157e+308
minimum: -1.7976931348623157e+308 minimum: -1.7976931348623157e+308
type: number type: number
position: position:
description: 目标位置 (ml)
maximum: 1.7976931348623157e+308 maximum: 1.7976931348623157e+308
minimum: -1.7976931348623157e+308 minimum: -1.7976931348623157e+308
type: number type: number
@@ -5845,8 +5862,10 @@ virtual_transfer_pump:
config: config:
properties: properties:
config: config:
description: 配置字典包含max_volume, port等参数
type: object type: object
device_id: device_id:
description: 设备ID
type: string type: string
required: [] required: []
type: object type: object

View File

@@ -421,7 +421,7 @@ xrd_d7mate:
type: number type: number
sample_id: sample_id:
default: '' default: ''
description: 样品标识符 description: 样品名称
type: string type: string
start_theta: start_theta:
default: 10.0 default: 10.0
@@ -433,7 +433,7 @@ xrd_d7mate:
type: string type: string
wait_minutes: wait_minutes:
default: 3.0 default: 3.0
description: 允许上样后等待分钟数 description: 允许上样后、发送样品准备完成前的等待分钟数(默认 3 分钟)
type: number type: number
required: [] required: []
title: StartWorkflow_Goal title: StartWorkflow_Goal
@@ -492,12 +492,15 @@ xrd_d7mate:
properties: properties:
host: host:
default: 127.0.0.1 default: 127.0.0.1
description: 设备IP地址
type: string type: string
port: port:
default: 6001 default: 6001
description: 通信端口默认6001
type: string type: string
timeout: timeout:
default: 10.0 default: 10.0
description: 超时时间,单位秒
type: string type: string
required: [] required: []
type: object type: object

View File

@@ -217,6 +217,7 @@ zhida_gcms:
additionalProperties: false additionalProperties: false
properties: properties:
string: string:
description: Base64编码的CSV数据ROS2参数名
type: string type: string
title: StrSingleInput_Goal title: StrSingleInput_Goal
type: object type: object
@@ -257,6 +258,7 @@ zhida_gcms:
additionalProperties: false additionalProperties: false
properties: properties:
string: string:
description: CSV文件路径ROS2参数名
type: string type: string
title: StrSingleInput_Goal title: StrSingleInput_Goal
type: object type: object
@@ -289,12 +291,15 @@ zhida_gcms:
properties: properties:
host: host:
default: 192.168.3.184 default: 192.168.3.184
description: 设备IP地址本地部署时可使用'127.0.0.1'
type: string type: string
port: port:
default: 5792 default: 5792
description: 通信端口默认5792
type: string type: string
timeout: timeout:
default: 10.0 default: 10.0
description: 超时时间,单位秒
type: string type: string
required: [] required: []
type: object type: object

View File

@@ -571,6 +571,7 @@ class Registry:
schema: Dict[str, Any], schema: Dict[str, Any],
doc_info: Dict[str, Any], doc_info: Dict[str, Any],
field_to_param: Optional[Dict[str, str]] = None, field_to_param: Optional[Dict[str, str]] = None,
apply_defaults: bool = False,
) -> None: ) -> None:
"""Apply parsed docstring display names and descriptions to schema properties.""" """Apply parsed docstring display names and descriptions to schema properties."""
if not schema or not doc_info: if not schema or not doc_info:
@@ -589,12 +590,20 @@ class Registry:
if not isinstance(param_name, str): if not isinstance(param_name, str):
continue continue
param_name = param_name.removesuffix("[]") param_name = param_name.removesuffix("[]")
prop_schema["title"] = param_display_names.get(param_name, prop_schema.get("title") or field_name) if param_name in param_display_names:
prop_schema["description"] = param_descs.get(param_name, prop_schema.get("description") or "") 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( def _generate_unilab_json_command_schema(
self, method_args: list, docstring: Optional[str] = None, self, method_args: list, docstring: Optional[str] = None,
import_map: Optional[Dict[str, str]] = None, import_map: Optional[Dict[str, str]] = None,
apply_doc_defaults: bool = False,
) -> Dict[str, Any]: ) -> Dict[str, Any]:
"""根据方法参数和 docstring 生成 UniLabJsonCommand schema""" """根据方法参数和 docstring 生成 UniLabJsonCommand schema"""
doc_info = parse_docstring(docstring) doc_info = parse_docstring(docstring)
@@ -631,7 +640,7 @@ class Registry:
if param_required: if param_required:
schema["required"].append(param_name) 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 return schema
def _generate_status_types_schema(self, status_methods: Dict[str, Any]) -> Dict[str, Any]: def _generate_status_types_schema(self, status_methods: Dict[str, Any]) -> Dict[str, Any]:
@@ -1038,6 +1047,7 @@ class Registry:
goal_schema_for_docs, goal_schema_for_docs,
parse_docstring(method_info.get("docstring")), parse_docstring(method_info.get("docstring")),
goal, goal,
apply_defaults=True,
) )
action_value_mappings[action_name] = action_entry action_value_mappings[action_name] = action_entry
@@ -1127,7 +1137,7 @@ class Registry:
if prequired: if prequired:
schema["required"].append(pname) 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 return schema
def _generate_status_schema_from_ast( def _generate_status_schema_from_ast(
@@ -2211,10 +2221,6 @@ class Registry:
}, },
**schema["properties"]["goal"]["properties"], **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 中 # 将 placeholder_keys 信息添加到 schema 中
if "placeholder_keys" in action_config and action_config.get("schema", {}).get( if "placeholder_keys" in action_config and action_config.get("schema", {}).get(
"properties", {} "properties", {}

View File

@@ -33,10 +33,83 @@ _USE_UV: Optional[bool] = None
def _has_uv() -> bool: def _has_uv() -> bool:
global _USE_UV global _USE_UV
if _USE_UV is None: 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 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( def _install_packages(
packages: List[str], packages: List[str],
upgrade: bool = False, upgrade: bool = False,
@@ -53,7 +126,7 @@ def _install_packages(
return True return True
is_chinese = _is_chinese_locale() is_chinese = _is_chinese_locale()
use_uv = _has_uv() installers = _installer_candidates()
failed: List[str] = [] failed: List[str] = []
for pkg in packages: for pkg in packages:
@@ -63,35 +136,30 @@ def _install_packages(
else: else:
print_status(f"正在{action_word} {pkg}...", "info") print_status(f"正在{action_word} {pkg}...", "info")
if use_uv: pkg_installed = False
cmd = ["uv", "pip", "install"] last_error = "unknown error"
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"])
for installer in installers:
cmd = _install_command(installer, pkg, upgrade, is_chinese)
try: try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300) result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
if result.returncode == 0: if result.returncode == 0:
installer = "uv" if use_uv else "pip"
print_status(f"{pkg} {action_word}成功 (via {installer})", "success") print_status(f"{pkg} {action_word}成功 (via {installer})", "success")
else: pkg_installed = True
stderr_short = result.stderr.strip().split("\n")[-1] if result.stderr else "unknown error" break
print_status(f"× {pkg} {action_word}失败: {stderr_short}", "error")
failed.append(pkg) 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: except subprocess.TimeoutExpired:
print_status(f"× {pkg} {action_word}超时 (300s)", "error") last_error = "timeout after 300s"
failed.append(pkg) print_status(f"× {pkg} {action_word}超时 (via {installer}, 300s)", "warning")
except Exception as e: except Exception as e:
print_status(f"× {pkg} {action_word}异常: {e}", "error") 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) failed.append(pkg)
if failed: if failed:

View File

@@ -206,6 +206,7 @@ class ImportManager:
"ast_analysis_success": False, "ast_analysis_success": False,
"import_map": {}, "import_map": {},
"init_params": [], "init_params": [],
"init_docstring": None,
"status_methods": {}, "status_methods": {},
"action_methods": {}, "action_methods": {},
} }
@@ -251,6 +252,7 @@ class ImportManager:
# 映射到统一字段名(与 registry.py complete_registry 消费端一致) # 映射到统一字段名(与 registry.py complete_registry 消费端一致)
result["init_params"] = body.get("init_params", []) result["init_params"] = body.get("init_params", [])
result["init_docstring"] = body.get("init_docstring")
result["status_methods"] = body.get("status_properties", {}) result["status_methods"] = body.get("status_properties", {})
result["action_methods"] = { result["action_methods"] = {
k: { k: {