系列: Humanoid
python
198 行
· 更新于 2026-05-12
hmi.py
Humanoid/May_12/RoboOS/master/task_manager/hmi.py
# -*- coding: utf-8 -*-
"""HMI:Flask Blueprint + WebSocket 推送橋接器。
對應架構教學文件 §5.5、interfaces.md §4。
"""
from __future__ import annotations
import json
import logging
import threading
from typing import Any, Callable, Dict, Optional
from flask import Blueprint, Flask, jsonify, request
from .context import ContextStore
from .diagnostics import TaskDiagnostics
from .events import ProgressEvent
from .monitor import TaskMonitor
from .scheduler import TaskScheduler
logger = logging.getLogger(__name__)
def make_blueprint(
scheduler: TaskScheduler,
monitor: TaskMonitor,
diagnostics: TaskDiagnostics,
context_store: ContextStore,
) -> Blueprint:
bp = Blueprint("task_manager_hmi", __name__)
# --- 控制端點 -------------------------------------------------------
@bp.post("/control/start")
def control_start():
data = request.get_json(silent=True) or {}
task_text = data.get("task")
if not task_text:
return jsonify({"status": "error", "message": "missing 'task'"}), 400
if isinstance(task_text, list):
task_text = "\n".join(str(t) for t in task_text)
result = scheduler.start_task(task_text, refresh=bool(data.get("refresh", False)))
return jsonify(result), (200 if result.get("status") == "ok" else 400)
@bp.post("/control/pause")
def control_pause():
scheduler.pause()
return jsonify({"state": scheduler.state.value})
@bp.post("/control/resume")
def control_resume():
scheduler.resume()
return jsonify({"state": scheduler.state.value})
@bp.post("/control/abort")
def control_abort():
data = request.get_json(silent=True) or {}
scheduler.abort(reason=data.get("reason", "user_request"))
return jsonify({"state": scheduler.state.value})
@bp.post("/control/reset")
def control_reset():
scheduler.reset()
return jsonify({"state": scheduler.state.value})
@bp.post("/mode")
def set_mode():
data = request.get_json(silent=True) or {}
mode = data.get("mode")
if not mode:
return jsonify({"status": "error", "message": "missing 'mode'"}), 400
result = scheduler.switch_mode(mode)
return jsonify(result), (200 if result.get("status") == "ok" else 400)
# --- 查詢端點 -------------------------------------------------------
@bp.get("/task_status")
def task_status():
return jsonify(scheduler.status())
@bp.get("/faults")
def faults():
task_id = request.args.get("task_id") or scheduler.current_task_id
limit_raw = request.args.get("limit", "20")
try:
limit = max(0, int(limit_raw))
except ValueError:
limit = 20
if not task_id:
return jsonify([])
records = diagnostics.list_faults(task_id)
return jsonify(records[-limit:] if limit else records)
@bp.get("/scene/snapshot")
def scene_snapshot():
snap = context_store.read_current_scene()
return jsonify(snap or {})
@bp.get("/task/<task_id>/context")
def task_context(task_id: str):
ctx = context_store.get(task_id)
if not ctx:
return jsonify({"status": "error", "message": "task not found"}), 404
return jsonify(ctx.to_dict())
@bp.get("/runtimes")
def runtimes():
return jsonify(monitor.snapshot())
return bp
class WSBridge:
"""把 task_progress / task_faults channel 的訊息透過 socketio.emit 推給前端。
用法:
bridge = WSBridge(socketio, collaborator)
bridge.start()
"""
def __init__(
self,
socketio: Any,
collaborator: Any,
progress_channel: str = "task_progress",
fault_channel: str = "task_faults",
scene_channel: Optional[str] = None, # 預留:日後想推 scene_update 用
) -> None:
self.socketio = socketio
self.collaborator = collaborator
self.progress_channel = progress_channel
self.fault_channel = fault_channel
self.scene_channel = scene_channel
self._threads: list[threading.Thread] = []
self._stop = threading.Event()
def start(self) -> None:
for channel, event in (
(self.progress_channel, "task_progress"),
(self.fault_channel, "task_fault"),
):
t = threading.Thread(
target=self._loop,
args=(channel, event),
daemon=True,
name=f"ws-bridge-{channel}",
)
t.start()
self._threads.append(t)
logger.info(
"[WSBridge] bridging: %s",
[(t.name) for t in self._threads],
)
def stop(self) -> None:
self._stop.set()
def push_progress(self, evt: ProgressEvent) -> None:
"""供 monitor.on_progress 直接調用(避免再經 Redis 一圈)。"""
try:
self.socketio.emit("task_progress", evt.to_dict(), namespace="/")
except Exception as e: # noqa: BLE001
logger.warning("[WSBridge] emit progress fail: %s", e)
def _loop(self, channel: str, ws_event_name: str) -> None:
while not self._stop.is_set():
try:
self.collaborator.listen(channel, lambda raw: self._forward(ws_event_name, raw))
# listen 不會自然返回;若返回則認為訂閱結束
except Exception as e: # noqa: BLE001
logger.warning("[WSBridge] listen(%s) error: %s", channel, e)
self._stop.wait(2.0)
def _forward(self, ws_event_name: str, raw: str) -> None:
try:
payload = json.loads(raw)
except json.JSONDecodeError:
payload = {"raw": raw}
try:
self.socketio.emit(ws_event_name, payload, namespace="/")
except Exception as e: # noqa: BLE001
logger.warning("[WSBridge] emit(%s) fail: %s", ws_event_name, e)
def register(
app: Flask,
scheduler: TaskScheduler,
monitor: TaskMonitor,
diagnostics: TaskDiagnostics,
context_store: ContextStore,
url_prefix: str = "",
) -> Blueprint:
bp = make_blueprint(scheduler, monitor, diagnostics, context_store)
app.register_blueprint(bp, url_prefix=url_prefix)
logger.info("[HMI] blueprint registered at prefix=%r", url_prefix)
return bp
__all__ = ["register", "make_blueprint", "WSBridge"]
相关文章
Humanoid
python
更新于 2026-03-12
run.py
run.py — python source code from the Humanoid learning materials (Humanoid/May_12/RoboOS/deploy/run.py).
阅读文章 →
Humanoid
python
更新于 2026-03-12
utils.py
utils.py — python source code from the Humanoid learning materials (Humanoid/May_12/RoboOS/deploy/utils.py).
阅读文章 →
Humanoid
python
更新于 2026-03-12
agent.py
agent.py — python source code from the Humanoid learning materials (Humanoid/May_12/RoboOS/master/agents/agent.py).
阅读文章 →
Humanoid
python
更新于 2026-03-12
planner.py
planner.py — python source code from the Humanoid learning materials (Humanoid/May_12/RoboOS/master/agents/planner.py).
阅读文章 →
Humanoid
python
更新于 2026-05-12
prompts.py
prompts.py — python source code from the Humanoid learning materials (Humanoid/May_12/RoboOS/master/agents/prompts.py).
阅读文章 →
Humanoid
python
更新于 2026-05-12
run.py
run.py — python source code from the Humanoid learning materials (Humanoid/May_12/RoboOS/master/run.py).
阅读文章 →