單一事實來源(Single Source of Truth) 任何接口變更,先改本文件、再改代碼。 文件位置:master/task_manager/interfaces.md 維護人:蒲(Task Manager owner) 對齊文件:May_12/Task_Manager_架構教學.md 版本:v1.0(2026-05-12)


目錄


0. 約定(讀之前先看)

項目 約定
編碼 所有字串 UTF-8、所有 JSON 不含尾隨逗號
時間戳 ts 一律 Unix 秒(float),精確到毫秒
ID 規則 task_id / subtask_id 用 UUID4 hex(無連字號),package_id 由視覺端分配(見 §2.1)
座標系 機器人 base frame,X 向前、Y 向左、Z 向上,單位 公尺;角度單位 弧度
列舉值大小寫 全部 lowercase snake_case(例:barcode_face: "up"
必填 vs 可選 表格中 必填 欄為 Y 表示生產者必須提供N 表示可缺省
未知值 字串用 "unknown"、數值用 null不要用空字串
失敗回傳 同步調用走 HTTP,失敗回 4xx/5xx;非同步走 channel,失敗發 task_faults

1. 系統參與者與 Channel 總圖

┌─────────────┐   vision_events    ┌──────────────────┐
│ Algo-Ground │ ──────────────────►│                  │
│  (謝)        │                    │                  │
└─────────────┘                    │                  │
                                   │  Task Manager    │ ──── task_progress ────► UI
┌─────────────┐   skill_events     │     (蒲)         │
│ Skill Mgr   │ ──────────────────►│                  │ ──── task_faults  ────► UI
│  (李/朱)     │ ◄── subtask_cmd ──│                  │
└─────────────┘                    └──────────────────┘
                                            ▲
                                            │ HTTP / WS
                                            │
                                       ┌────┴────┐
                                       │   UI    │
                                       │  (蒲)   │
                                       └─────────┘
角色 進程 訂閱 發佈
Algo-Ground(謝) 獨立 GPU 進程 vision_events
Task Manager(蒲) master/run.py vision_events, skill_events, AGENT_REGISTRATION, {robot}_to_RoboOS task_progress, task_faults, roboos_to_{robot}
Skill Manager(李/朱,住在 slaver) slaver/run.py roboos_to_{robot} skill_events, {robot}_to_RoboOS
UI 瀏覽器 / pad WebSocket HTTP REST
任務開關(尹) 嵌入 master/run.py 提供 Python API

2. Redis Channel 規範(Pub/Sub)

2.1 vision_eventsAlgo-Ground(謝)發 / Task Monitor 收

頻率:10–15 Hz(建議) 負載上限:每幀 ≤ 32 KB(不含圖像,圖像走檔案路徑)

{
  "event": "vision_update",
  "ts": 1747040000.123,
  "frame_id": "img_000123",
  "camera": "head",
  "image_path": "/tmp/frames/img_000123.jpg",
  "objects": [
    {
      "id": "P_2026051200001",
      "class": "package_box",
      "bbox": [120, 80, 380, 290],
      "score": 0.93,
      "pose_3d": {
        "position": [0.42, 0.10, 0.85],
        "yaw": 0.30
      },
      "size": {"length": 0.22, "width": 0.15, "height": 0.10},
      "barcode_face": "down",
      "barcode_score": 0.81,
      "zone": "left_conveyor"
    }
  ]
}
欄位 型別 必填 列舉 / 範圍 說明
event str Y "vision_update" 固定字串
ts float Y Unix 秒
frame_id str Y 全局唯一
camera str Y head / left_wrist / right_wrist
image_path str N 共享檔案路徑(給日後 keyframe 回放)
objects[].id str Y P_yyyyMMddNNNNN 同一包裹跨幀必須穩定(謝負責 tracking)
objects[].class str Y package_box / barcode / obstacle 初版只關心 package_box
objects[].bbox int[4] Y [x1,y1,x2,y2] 像素座標
objects[].score float Y [0, 1] 檢測置信度
objects[].pose_3d.position float[3] Y base frame,m
objects[].pose_3d.yaw float N rad 可缺省(未做 6D 估計時)
objects[].size obj N m 用於選擇手與抓取點
objects[].barcode_face str Y up / down / side / unknown 最關鍵欄位
objects[].barcode_score float N [0, 1] 朝向判斷置信度
objects[].zone str Y left_conveyor / workspace / right_conveyor / unknown 用區域而非絕對座標做狀態跳轉

平滑策略:Task Monitor 端會做「連續 3 幀同一結果才確認」,所以謝那邊不需要過度濾波。


2.2 skill_eventsSkill Manager(李/朱)發 / Task Monitor 收

頻率:事件驅動(每個技能起 / 訖各一條) 負載上限:≤ 4 KB

{
  "event": "skill_update",
  "ts": 1747040001.456,
  "robot_name": "humanoid_01",
  "task_id": "9f1b2c...",
  "subtask_id": "t1",
  "skill_name": "G1_left_grasp",
  "phase": "end",
  "status": "ok",
  "fault_code": null,
  "details": {
    "duration_ms": 1340,
    "grasped_object": "P_2026051200001"
  }
}
欄位 型別 必填 列舉 說明
event str Y "skill_update"
ts float Y
robot_name str Y 必須與註冊時一致
task_id str Y
subtask_id str Y 對應 Task Manager 派發時的 ID
skill_name str Y 見 §6.2
phase str Y begin / progress / end
status str Y ok / fail / timeout / cancelled phase=end 必填有效值;phase=begin 一律 ok
fault_code str / null N SK-xxx status≠ok 時必填
details obj N 自由欄位

2.3 task_progressTask Monitor 發 / UI 收

頻率:每次 FSM 狀態跳轉、每秒至多 5 次心跳 負載上限:≤ 2 KB

{
  "event": "task_progress",
  "ts": 1747040002.000,
  "task_id": "9f1b2c...",
  "subtask_id": "t1",
  "package_id": "P_2026051200001",
  "fsm_state": "REORIENT",
  "fsm_prev_state": "PICK",
  "progress": 0.40,
  "barcode_face": "down",
  "zone": "workspace",
  "queue": {"active": 2, "pending": 5}
}
欄位 型別 必填 列舉 說明
fsm_state str Y PICK / REORIENT / PUSH / DONE / FAULT
progress float Y [0, 1] PICK=0.0, REORIENT=0.33, PUSH=0.66, DONE=1.0
queue.active int Y 當前活躍任務數
queue.pending int Y 等待中任務數

2.4 task_faultsTask Diagnostics 發 / UI 收

頻率:事件驅動 負載上限:≤ 8 KB

{
  "event": "task_fault",
  "ts": 1747040003.123,
  "code": "TM-004",
  "name": "REORIENT_FAIL",
  "severity": "warn",
  "task_id": "9f1b2c...",
  "subtask_id": "t2",
  "package_id": "P_2026051200001",
  "details": {
    "retry": 3,
    "last_barcode_face": "side",
    "trigger_evt": "vision_update"
  },
  "action_taken": "replan"
}
欄位 型別 必填 列舉 說明
code str Y TM-xxx(見 §7)
severity str Y info / warn / error / critical
action_taken str Y retry / replan / pause / abort / none 診斷模塊的決策

2.5 沿用 RoboOS 既有 channel

Channel 方向 Schema 文件
AGENT_REGISTRATION Slaver → Master {robot_name, robot_tool, robot_state, timestamp} slaver/run.py:238
roboos_to_{robot_name} Master → Slaver {task_id, task, order} master/agents/agent.py:266
{robot_name}_to_RoboOS Slaver → Master {robot_name, subtask_handle, subtask_result, tools, task_id} slaver/run.py:166

本期擴展roboos_to_{robot_name} 中的 task 欄位現階段仍是純自然語言(沿用),下期演進為結構化(§6.3)。


3. Redis Key 規範(持久化狀態)

Key 型別 TTL 寫入者 讀取者 內容
task:{task_id}:meta string (JSON) 24h Task Manager UI / 日誌 TaskContext.meta
task:{task_id}:queue string (JSON) 24h Task Manager Task Manager 子任務隊列快照
task:{task_id}:faults list (JSON 元素) 24h Task Diagnostics UI / 任務報告 append-only 故障歷史
task:{task_id}:keyframes list (JSON 元素) 24h Task Monitor 任務報告 {ts, image_path, fsm_state}
task:current_scene string (JSON) 60s Task Manager(從 vision 聚合) Task Planner 規劃時讀此 key 注入 prompt
task:stats hash Task Manager UI success_count / fail_count / avg_duration_ms
task:current_id string 24h Task Scheduler 全部模塊 當前活躍 task_id(單 robot 假設)

task:current_scene 範例:

{
  "ts": 1747040000.123,
  "packages": [
    {"id": "P_2026051200001", "zone": "left_conveyor", "barcode_face": "down", "size": {"length": 0.22, "width": 0.15, "height": 0.10}},
    {"id": "P_2026051200002", "zone": "left_conveyor", "barcode_face": "up",   "size": {"length": 0.18, "width": 0.12, "height": 0.08}}
  ],
  "right_conveyor_free_slots": 2
}

4. HTTP / WebSocket 接口(HMI)

4.1 HTTP(沿用 Flask,基底 http://{master_host}:5000

端點 方法 請求 回應 說明
/publish_task POST {"task": str \| str[], "refresh": bool, "task_id"?: str} {"status": "success", "data": {...subtask_list}} 沿用既有,作為自然語言任務入口
/control/start POST {} {"state": str} 啟動調度器(從 IDLERUNNING
/control/pause POST {} {"state": str}
/control/resume POST {} {"state": str}
/control/abort POST {"reason"?: str} {"state": str}
/control/reset POST {} {"state": str} 清空隊列、回 IDLE
/mode POST {"mode": "teleop" \| "auto" \| "semi"} {"mode": str}
/task_status GET 見下方 §4.1.1
/system_status GET {cpu_load, memory_usage} 沿用既有
/robot_status GET [{robot_name, robot_state}] 沿用既有
/faults GET ?task_id=...&limit=20 [FaultRecord] task:{task_id}:faults
/scene/snapshot GET task:current_scene 給 UI 顯示包裹列表

4.1.1 /task_status 回應

{
  "scheduler_state": "RUNNING",
  "mode": "auto",
  "current_task_id": "9f1b2c...",
  "queue": {
    "active": [
      {"subtask_id": "t1", "subtask": "...", "robot_name": "humanoid_01", "fsm_state": "REORIENT"}
    ],
    "pending": [
      {"subtask_id": "t4", "subtask": "...", "robot_name": "humanoid_01"}
    ]
  },
  "stats": {"success_count": 12, "fail_count": 1, "avg_duration_ms": 8200}
}

4.2 WebSocket(沿用 flask-socketio,socket.io 路徑 /

Event 名稱 方向 Payload 對應
text_update server→client {"data": str} 沿用既有(日誌串流)
task_progress server→client 見 §2.3 直接轉發 task_progress channel
task_fault server→client 見 §2.4 直接轉發 task_faults channel
scene_update server→client task:current_scene 內容 1 Hz 推送

5. 場景 Profile 規範

文件位置:master/scene/profile.yaml 讀取者:TaskPlanner 在規劃時注入 prompt;TaskMonitor 校驗 zone 是否合法。

version: "1.0"
domain: package_sorting

scene:
  - name: left_conveyor
    type: conveyor
    role: source
    bounds_xyz: [[0.20, -0.50, 0.78], [0.80, -0.10, 0.92]]
    contains: []

  - name: right_conveyor
    type: conveyor
    role: sink
    bounds_xyz: [[0.20,  0.10, 0.78], [0.80,  0.50, 0.92]]
    contains: []

  - name: workspace
    type: zone
    role: manipulation
    bounds_xyz: [[0.30, -0.10, 0.80], [0.70, 0.10, 1.20]]

properties:
  task_queue_max_length: 3
  barcode_face_required: up
  reorient_max_retry: 3
  grasp_timeout_sec: 8.0
  push_timeout_sec: 6.0
  vision_event_channel: vision_events
  skill_event_channel: skill_events
  vision_confirm_frames: 3       # 連續 N 幀相同才確認跳轉

barcode_face_enum: [up, down, side, unknown]
zone_enum:        [left_conveyor, workspace, right_conveyor, unknown]

規則:任何 zone / barcode_face 取值都必須在這裡的列舉內,否則 Task Monitor 視為 unknown


6. 子任務語意(給 Skill Manager)

6.1 階段 1:自然語言(首版,沿用 RoboOS)

派發到 roboos_to_{robot_name}task 欄位為以下三種固定模板之一:

模板 範例
Pick "Pick package {package_id} from left_conveyor"
Reorient "Rotate package {package_id} so barcode face is up"
Push "Push package {package_id} onto right_conveyor"

Skill Manager(李)在自家的 ToolCallingAgent 內用 LLM 解析 → 選擇 MCP tool。

6.2 技能命名規範(給李、朱)

按 PDF「結構化技能表」對齊:

編號 名稱 標準 skill_name(出現在 skill_events
P1 視覺定位 P1_visual_locate
P2 視覺檢測 P2_visual_detect
M1 頭部運動 M1_head_motion
M2 腰部運動 M2_waist_motion
G1 左手抓取 G1_left_grasp
G2 右手抓取 G2_right_grasp
O1 拨移包裹 O1_slide_package
O2 左手旋轉包裹 O2_left_rotate_package
O3 右手旋轉包裹 O3_right_rotate_package
C1 左右手傳遞 C1_hand_to_hand
C2 雙手搬移 C2_dual_arm_carry

6.3 階段 2:結構化(規劃中,下期演進)

{
  "task_id": "9f1b2c...",
  "subtask_id": "t1",
  "action": "pick",
  "target": {"package_id": "P_2026051200001"},
  "from_zone": "left_conveyor",
  "constraints": {"timeout_sec": 8.0, "prefer_hand": "left"}
}

階段 2 在 Sprint 4 之後啟用,現階段雙方都實作 §6.1 即可


7. 故障碼總表

7.1 任務級(由 Task Manager 維護)

名稱 severity 觸發條件 預設 action_taken
TM-001 PLAN_FAIL error LLM 連續 ≥5 次無法輸出合法 JSON abort
TM-002 SCENE_EMPTY info 視覺連續 ≥20 幀無 package none(標記任務完成)
TM-003 GRASP_TIMEOUT warn PICK 階段 > grasp_timeout_sec retry(一次)
TM-004 REORIENT_FAIL warn REORIENT 重試 ≥ reorient_max_retry replan
TM-005 PLACE_OUT_OF_ZONE warn PUSH 完但 zone ≠ right_conveyor retry(一次)
TM-006 SKILL_FAULT error 透傳上游 SK-xxx replan
TM-007 SOFTWARE_FAULT critical 內部 exception / 超時 pause
TM-008 MODE_BLOCKED info mode=teleop 時收到自主指令 none

7.2 技能級(由 Skill Manager 維護,李定義具體編號)

預留 SK-001 ~ SK-099 範圍。Task Manager 收到後一律轉成 TM-006,並把原碼放進 details.upstream_code

7.3 策略級(由 Skill Unit 維護,朱定義)

預留 PL-001 ~ PL-099 範圍。經由 Skill Manager 透傳。


8. 模塊間 Python 接口(同進程)

以下是 task_manager/6 個類對外暴露的方法簽名,只列公開 API

8.1 TaskContext

class TaskContext:
    task_id: str
    task_text: str
    state: str  # IDLE / RUNNING / PAUSED / FAULT / RESET
    queue: list[dict]
    faults: list[FaultRecord]
    stats: dict

    def to_redis(self, collaborator) -> None: ...
    @classmethod
    def from_redis(cls, collaborator, task_id: str) -> "TaskContext": ...

8.2 TaskQueueManager

class TaskQueueManager:
    def __init__(self, max_active: int = 3): ...
    def push(self, subtask: dict) -> None: ...
    def pop_next(self) -> dict | None: ...
    def remove(self, subtask_id: str) -> bool: ...
    def reorder(self, new_order: list[str]) -> None: ...
    def insert(self, idx: int, subtask: dict) -> None: ...
    def snapshot(self) -> dict: ...

8.3 TaskScheduler

class TaskScheduler:
    state: SchedState   # IDLE / RUNNING / PAUSED / FAULT / RESET
    mode:  str          # teleop / auto / semi

    def start_task(self, task_text: str) -> str: ...
    def dispatch_next(self) -> str | None: ...
    def pause(self) -> None: ...
    def resume(self) -> None: ...
    def abort(self, reason: str = "") -> None: ...
    def reset(self) -> None: ...
    def switch_mode(self, mode: str) -> None: ...
    def request_replan(self, task_id: str, reason: str) -> None: ...
    def retry_current(self, task_id: str) -> None: ...

8.4 TaskMonitor

class TaskMonitor:
    def attach(self, task_id: str, package_id: str) -> None: ...
    def detach(self, task_id: str) -> None: ...
    def start_listeners(self) -> None: ...
    # 內部回調:_on_vision / _on_skill

8.5 TaskDiagnostics

class TaskDiagnostics:
    def raise_fault(self, task_id: str, evt, code: str = "TM-006") -> None: ...
    def clear_fault(self, task_id: str, code: str) -> None: ...
    def list_faults(self, task_id: str) -> list[dict]: ...

8.6 任務開關(由尹實作,本模塊調用)

class TaskSwitch:
    def startup(self, config_path: str) -> dict:
        """系統初始化 + 參數加載 + 模型熱加載;回傳 {ready: bool, msg: str}"""
    def shutdown(self) -> dict:
        """持久化任務上下文 + 生成任務報告;回傳 {report_path: str}"""
    def generate_report(self, task_id: str) -> str:
        """生成單一任務的 Markdown 報告路徑"""

9. Mock 工具(測試 / 開發階段)

9.1 視覺事件 Mock(在 GroundingDINO 未就緒前)

tools/mock_vision.py

import json, time, random, redis

r = redis.Redis()
PKG_ID = "P_mock_001"
state = {"barcode_face": "down", "zone": "left_conveyor"}

def step():
    if state["zone"] == "left_conveyor":
        state["zone"] = "workspace"
    elif state["barcode_face"] == "down":
        state["barcode_face"] = random.choice(["down", "side", "up"])
    elif state["barcode_face"] == "up":
        state["zone"] = "right_conveyor"

while True:
    r.publish("vision_events", json.dumps({
        "event": "vision_update", "ts": time.time(),
        "frame_id": f"img_{int(time.time()*1000)}", "camera": "head",
        "objects": [{
            "id": PKG_ID, "class": "package_box",
            "bbox": [120,80,380,290], "score": 0.93,
            "pose_3d": {"position": [0.42,0.10,0.85], "yaw": 0.30},
            "size": {"length": 0.22,"width": 0.15,"height": 0.10},
            "barcode_face": state["barcode_face"],
            "barcode_score": 0.81, "zone": state["zone"],
        }],
    }))
    time.sleep(1.0)
    step()

9.2 技能事件 Mock

tools/mock_skill.py

import json, sys, time, redis
r = redis.Redis()
skill, status = sys.argv[1], sys.argv[2] if len(sys.argv) > 2 else "ok"
r.publish("skill_events", json.dumps({
    "event": "skill_update", "ts": time.time(),
    "robot_name": "humanoid_01", "task_id": "mock", "subtask_id": "t1",
    "skill_name": skill, "phase": "end", "status": status,
    "fault_code": None if status == "ok" else "SK-001",
    "details": {"duration_ms": 1000},
}))

用法:python tools/mock_skill.py G1_left_grasp ok

9.3 故障注入

直接用 redis-cli:

redis-cli publish skill_events '{"event":"skill_update","ts":0,"robot_name":"humanoid_01","task_id":"mock","subtask_id":"t1","skill_name":"O2_left_rotate_package","phase":"end","status":"fail","fault_code":"SK-005","details":{}}'

10. 版本與兼容性

規則 說明
版本號 本文件頂部 版本 欄位,跟隨 semver:MAJOR.MINOR
Major 變更 任何欄位刪除 / 重命名 / 列舉值刪除 → bump major,所有隊友必須升級。
Minor 變更 新增欄位(必須附 必填=N)、新增列舉值、新增 channel → bump minor,隊友可漸進升級。
兼容期 任何 channel schema 變更,舊欄位至少保留 2 個 sprint。
通告 任何變更發到團隊群並 @所有 stakeholders。

附錄 A:欄位字典

跨多個 schema 出現的欄位統一在這裡定義,避免歧義。

欄位 統一定義
task_id UUID4 hex(無 -),由 Task Scheduler 在 start_task 時生成
subtask_id 短字串 t1 / t2 / ...,僅在單一 task_id 內唯一
package_id P_yyyyMMddNNNNN,由視覺端在首次偵測時分配;同一包裹整個生命週期不變
robot_name slaver/config.yamlrobot.name 一致
zone left_conveyor / workspace / right_conveyor / unknown
barcode_face up / down / side / unknown
fsm_state PICK / REORIENT / PUSH / DONE / FAULT
scheduler_state IDLE / RUNNING / PAUSED / FAULT / RESET
mode teleop / auto / semi
severity info / warn / error / critical
phase begin / progress / end
status ok / fail / timeout / cancelled

附錄 B:變更紀錄

版本 日期 變更 提案人
v1.0 2026-05-12 初版,定義 5 個 channel + 7 個 Redis key + 11 個 HTTP 端點 + 7 個任務級故障碼