Files
Cognitive_Agent/src/cognitive_agent/application/calibration/mqtt_client.py
T

131 lines
4.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Optional MQTT 3.1.1 client used by the browser workstation."""
from __future__ import annotations
import json
import threading
import uuid
from collections.abc import Callable
from typing import Any
StateCallback = Callable[[str, str], None]
PayloadCallback = Callable[[str, str], None]
class MqttSeatPoseClient:
def __init__(
self,
host: str,
port: int,
topic: str,
on_state: StateCallback,
on_payload: PayloadCallback,
) -> None:
self.host = host
self.port = port
self.topic = topic
self.test_topic = f"n7calibration/seatpose_test/{uuid.uuid4().hex[:12]}"
self.on_state = on_state
self.on_payload = on_payload
self._client: Any = None
self._lock = threading.Lock()
def connect(self) -> None:
try:
import paho.mqtt.client as mqtt # type: ignore
except ImportError as error:
raise RuntimeError("缺少 paho-mqtt;请安装项目的 ir 可选依赖") from error
self.close()
try:
client = mqtt.Client(
callback_api_version=mqtt.CallbackAPIVersion.VERSION2,
client_id=f"n7cal_{uuid.uuid4().hex[:12]}",
protocol=mqtt.MQTTv311,
)
except (AttributeError, TypeError):
client = mqtt.Client(client_id=f"n7cal_{uuid.uuid4().hex[:12]}")
client.on_connect = self._on_connect
client.on_disconnect = self._on_disconnect
client.on_message = self._on_message
with self._lock:
self._client = client
self.on_state("connecting", f"正在连接 {self.host}:{self.port}")
client.connect_async(self.host, self.port, keepalive=60)
client.loop_start()
def publish_test(self, payload: dict[str, Any] | None = None) -> str:
with self._lock:
client = self._client
if client is None or not client.is_connected():
raise RuntimeError("MQTT 尚未连接")
body = payload or sample_payload()
result = client.publish(self.test_topic, json.dumps(body, ensure_ascii=False), qos=0)
if result.rc != 0:
raise RuntimeError(f"MQTT 发送失败,返回码 {result.rc}")
return self.test_topic
def close(self) -> None:
with self._lock:
client, self._client = self._client, None
if client is not None:
try:
client.disconnect()
except Exception:
pass
try:
client.loop_stop()
except Exception:
pass
def _on_connect(
self,
client: Any,
_userdata: Any,
_flags: Any,
reason_code: Any,
*_: Any,
) -> None:
if int(reason_code) != 0:
self.on_state("error", f"MQTT 连接被拒绝:{reason_code}")
return
client.subscribe([(self.topic, 0), (self.test_topic, 0)])
self.on_state("subscribed", f"已订阅 {self.topic}")
def _on_disconnect(self, _client: Any, _userdata: Any, *args: Any) -> None:
reason = args[-2] if len(args) >= 2 else args[-1] if args else ""
self.on_state("disconnected", f"MQTT 已断开{f':{reason}' if reason else ''}")
def _on_message(self, _client: Any, _userdata: Any, message: Any) -> None:
try:
payload = message.payload.decode("utf-8")
except UnicodeDecodeError:
self.on_state("error", "MQTT 收到非 UTF-8 数据")
return
self.on_payload(message.topic, payload)
def sample_payload(frame: int = 1) -> dict[str, Any]:
return {
"type": "seatpose",
"frame": frame,
"timestamp_us": frame * 33_333,
"area_poses": [
{
"area": "Seat_cushion",
"axis_angle": {"valid": True, "degree": 0.0},
"current_frame_to_reference": {"valid": True, "T": [0.12, 0.04, 0.01]},
},
{
"area": "Seat_backrest",
"axis_angle": {"valid": True, "degree": 34.0},
"current_frame_to_reference": {"valid": True, "T": [0.0, 0.0, 0.0]},
},
{
"area": "Seat_leg_rest",
"axis_angle": {"valid": True, "degree": 22.0},
"current_frame_to_reference": {"valid": True, "T": [0.0, 0.0, 0.0]},
},
],
}