mqttxx 2.0.3__py3-none-any.whl → 3.1.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
mqttxx/rpc.py CHANGED
@@ -1,9 +1,8 @@
1
1
  # MQTT RPC 模块 - 基于 aiomqtt 的双向对等 RPC 调用
2
2
 
3
3
  import asyncio
4
- import json
5
4
  import uuid
6
- from typing import Any, Callable, Optional
5
+ from typing import Any, Callable, Optional, Type
7
6
  from loguru import logger
8
7
 
9
8
  from .client import MQTTClient
@@ -14,8 +13,9 @@ from .exceptions import (
14
13
  TooManyConcurrentCallsError,
15
14
  MQTTXError,
16
15
  ErrorCode,
16
+ MessageError,
17
17
  )
18
- from .protocol import RPCRequest, RPCResponse
18
+ from .protocol import RPCRequest, RPCResponse, parse_message_from_bytes, Codec, JSONCodec
19
19
 
20
20
 
21
21
  # 权限检查回调类型
@@ -76,6 +76,7 @@ class RPCManager:
76
76
  client: MQTTClient,
77
77
  config: Optional[RPCConfig] = None,
78
78
  auth_callback: Optional[AuthCallback] = None,
79
+ codec: Type[Codec] = JSONCodec, # 新增:可插拔编解码器
79
80
  ):
80
81
  """初始化 RPC 管理器
81
82
 
@@ -85,6 +86,7 @@ class RPCManager:
85
86
  auth_callback: 权限检查回调函数(可选)
86
87
  签名:async def auth_callback(caller_id: str, method: str, request: RPCRequest) -> bool
87
88
  返回:True = 允许,False = 拒绝
89
+ codec: 编解码器(默认 JSONCodec)
88
90
 
89
91
  使用示例:
90
92
  client = MQTTClient(...)
@@ -92,6 +94,7 @@ class RPCManager:
92
94
 
93
95
  # 基础用法(无权限控制)
94
96
  rpc = RPCManager(client)
97
+ rpc.setup("my/rpc/responses") # 设置响应主题
95
98
 
96
99
  # 带权限控制
97
100
  async def auth_check(caller_id, method, request):
@@ -102,6 +105,7 @@ class RPCManager:
102
105
  self._client = client
103
106
  self.config = config or RPCConfig()
104
107
  self._auth_callback = auth_callback
108
+ self._codec = codec # 保存编解码器
105
109
 
106
110
  # RPC 状态
107
111
  self._pending_calls: dict[str, asyncio.Future] = {} # request_id → Future
@@ -112,6 +116,40 @@ class RPCManager:
112
116
 
113
117
  logger.info("RPCManager 已初始化")
114
118
 
119
+ def setup(self, reply_topic: str):
120
+ """设置 RPC 响应主题并自动订阅
121
+
122
+ 这个方法会:
123
+ 1. 订阅 reply_topic(接收 RPC 响应)
124
+ 2. 注册消息处理器(自动解码 + 分发)
125
+
126
+ Args:
127
+ reply_topic: 响应主题(例如:server/rpc_responses)
128
+
129
+ 示例:
130
+ rpc = RPCManager(client)
131
+ rpc.setup("my/rpc/responses")
132
+ """
133
+ async def handle_bytes(topic: str, payload: bytes):
134
+ """bytes → RPC message → handle"""
135
+ try:
136
+ # 解码
137
+ message = parse_message_from_bytes(payload, self._codec)
138
+
139
+ # 路由
140
+ if isinstance(message, RPCRequest):
141
+ await self._handle_request(topic, message)
142
+ elif isinstance(message, RPCResponse):
143
+ await self._handle_response(topic, message)
144
+ except MessageError as e:
145
+ logger.debug(f"非 RPC 消息 - topic: {topic}, reason: {e}")
146
+ except Exception as e:
147
+ logger.exception(f"RPC 消息处理失败: {e}")
148
+
149
+ # 订阅 raw bytes
150
+ self._client.raw.subscribe(reply_topic, handle_bytes)
151
+
152
+
115
153
  def register(self, method_name: str):
116
154
  """装饰器:注册本地 RPC 方法供远程调用
117
155
 
@@ -257,8 +295,9 @@ class RPCManager:
257
295
  future = asyncio.get_event_loop().create_future()
258
296
  self._pending_calls[request_id] = future
259
297
 
260
- # 发送请求
261
- await self._client.publish(topic, json.dumps(request.to_dict()), qos=1)
298
+ # 发送请求(使用编解码器)
299
+ payload = request.encode(self._codec)
300
+ await self._client.raw.publish(topic, payload, qos=1)
262
301
  logger.debug(f"RPC 请求已发送 - method: {method}, request_id: {request_id[:8]}")
263
302
 
264
303
  try:
@@ -406,5 +445,6 @@ class RPCManager:
406
445
  topic: 响应主题
407
446
  response: RPC 响应消息
408
447
  """
409
- await self._client.publish(topic, json.dumps(response.to_dict()), qos=1)
448
+ payload = response.encode(self._codec)
449
+ await self._client.raw.publish(topic, payload, qos=1)
410
450
  logger.debug(f"RPC 响应已发送 - request_id: {response.request_id[:8]}")