#!/usr/bin/env python # -*- coding: utf-8 -*- """ 设备处理服务模块 基于VDA5050协议的设备管理框架,支持MQTT通信 消息直接透传,不进行编解码处理 """ import asyncio import json import time import socket import threading from datetime import datetime from typing import Dict, Any, Optional, Callable, List, Union from dataclasses import dataclass, asdict from enum import Enum from utils.logger import get_logger from .mqtt_service import MQTTService from .async_mqtt_service import AsyncMQTTService logger = get_logger("services.device_handler") class DeviceType(Enum): """设备类型枚举""" VEHICLE = "vehicle" # 小车 DOOR = "door" # 门 CALLER = "caller" # 呼叫器 LIFT = "lift" # 电梯 CONVEYOR = "conveyor" # 输送带 SENSOR = "sensor" # 传感器 ROBOT = "robot" # 机器人 CAMERA = "camera" # 摄像头 SCANNER = "scanner" # 扫描器 CUSTOM = "custom" # 自定义设备 class VDA5050CommandType(Enum): """VDA5050协议指令类型""" ORDER = "order" # 订单指令(仅小车) STATE = "state" # 状态查询(仅小车) FACTSHEET = "factsheet" # 设备信息(仅小车) INSTANT_ACTIONS = "instantActions" # 即时动作(所有设备) CONNECTION = "connection" # 连接管理 VISUALIZATION = "visualization" # 可视化 class ProtocolType(Enum): """协议类型""" VDA5050 = "vda5050" # VDA5050协议(默认) TCP = "tcp" # TCP协议 class DeviceBrand(Enum): """设备品牌枚举""" HUARUI = "huarui" # 华睿 SEER = "seer" # 仙工 QUICKTRON = "quicktron" # 快仓 GEEK = "geek" # 极智嘉 MUSHINY = "mushiny" # 木蚁 FLASHHOLD = "flashhold" # 闪电匣 HIKROBOT = "hikrobot" # 海康机器人 STANDARD = "standard" # 标准协议 CUSTOM = "custom" # 自定义品牌 class DeviceProtocol: """设备协议定义基类""" def __init__(self, brand: DeviceBrand, device_type: DeviceType, protocol_type: ProtocolType = ProtocolType.VDA5050): self.brand = brand self.device_type = device_type self.protocol_type = protocol_type def encode_command(self, command_type: str, command: Dict[str, Any]) -> Dict[str, Any]: """将VDA5050标准指令编码为设备特定格式""" raise NotImplementedError("子类必须实现encode_command方法") def decode_response(self, message_type: str, response: Dict[str, Any]) -> Dict[str, Any]: """将设备响应解码为VDA5050标准格式""" raise NotImplementedError("子类必须实现decode_response方法") def validate_command(self, command_type: str, command: Dict[str, Any]) -> bool: """验证指令格式是否正确""" return True def get_supported_command_types(self) -> List[str]: """获取支持的VDA5050指令类型列表""" if self.device_type == DeviceType.VEHICLE: return [ VDA5050CommandType.ORDER.value, VDA5050CommandType.STATE.value, VDA5050CommandType.FACTSHEET.value, VDA5050CommandType.INSTANT_ACTIONS.value ] else: # 其他设备只支持即时动作 return [VDA5050CommandType.INSTANT_ACTIONS.value] class VDA5050Protocol(DeviceProtocol): """VDA5050协议实现(标准协议)""" def __init__(self, brand: DeviceBrand = DeviceBrand.STANDARD, device_type: DeviceType = DeviceType.VEHICLE): super().__init__(brand, device_type, ProtocolType.VDA5050) def encode_command(self, command_type: str, command: Dict[str, Any]) -> Dict[str, Any]: """VDA5050标准指令编码""" if command_type == VDA5050CommandType.ORDER.value: return self._encode_order(command) elif command_type == VDA5050CommandType.INSTANT_ACTIONS.value: return self._encode_instant_actions(command) elif command_type == VDA5050CommandType.STATE.value: return self._encode_state_request(command) elif command_type == VDA5050CommandType.FACTSHEET.value: return self._encode_factsheet_request(command) else: return command def decode_response(self, message_type: str, response: Dict[str, Any]) -> Dict[str, Any]: """VDA5050标准响应解码""" if message_type == VDA5050CommandType.STATE.value: return self._decode_state_response(response) elif message_type == VDA5050CommandType.FACTSHEET.value: return self._decode_factsheet_response(response) else: return response def _encode_order(self, command: Dict[str, Any]) -> Dict[str, Any]: """编码VDA5050订单指令""" return { "headerId": command.get("header_id", f"order_{int(time.time())}"), "timestamp": command.get("timestamp", datetime.now().isoformat()), "version": command.get("version", "2.0.0"), "manufacturer": self.brand.value, "serialNumber": command.get("serial_number", ""), "orderId": command.get("order_id", f"order_{int(time.time())}"), "orderUpdateId": command.get("order_update_id", 0), "zoneSetId": command.get("zone_set_id", ""), "nodes": command.get("nodes", []), "edges": command.get("edges", []) } def _encode_instant_actions(self, command: Dict[str, Any]) -> Dict[str, Any]: """编码VDA5050即时动作指令""" actions = command.get("actions", []) # 标准化动作格式 formatted_actions = [] for action in actions: if isinstance(action, str): # 简单字符串转换为标准VDA5050动作格式 formatted_actions.append({ "actionType": action, "actionId": f"{action}_{int(time.time())}", "actionDescription": f"{self.device_type.value} {action} action", "blockingType": "SOFT" }) else: formatted_actions.append(action) return { "headerId": command.get("header_id", f"instant_{int(time.time())}"), "timestamp": command.get("timestamp", datetime.now().isoformat()), "version": command.get("version", "2.0.0"), "manufacturer": self.brand.value, "serialNumber": command.get("serial_number", ""), "instantActions": formatted_actions } def _encode_state_request(self, command: Dict[str, Any]) -> Dict[str, Any]: """编码VDA5050状态请求""" return { "requestId": command.get("request_id", f"state_{int(time.time())}"), "timestamp": command.get("timestamp", datetime.now().isoformat()) } def _encode_factsheet_request(self, command: Dict[str, Any]) -> Dict[str, Any]: """编码VDA5050设备信息请求""" return { "requestId": command.get("request_id", f"factsheet_{int(time.time())}"), "timestamp": command.get("timestamp", datetime.now().isoformat()) } def _decode_state_response(self, response: Dict[str, Any]) -> Dict[str, Any]: """解码VDA5050状态响应""" return { "command_type": "state_response", "order_id": response.get("orderId", ""), "order_update_id": response.get("orderUpdateId", 0), "last_node_id": response.get("lastNodeId", ""), "last_node_sequence_id": response.get("lastNodeSequenceId", 0), "driving": response.get("driving", False), "paused": response.get("paused", False), "new_base_request": response.get("newBaseRequest", False), "distance_since_last_node": response.get("distanceSinceLastNode", 0.0), "operating_mode": response.get("operatingMode", ""), "node_states": response.get("nodeStates", []), "edge_states": response.get("edgeStates", []), "agv_position": response.get("agvPosition", {}), "velocity": response.get("velocity", {}), "loads": response.get("loads", []), "action_states": response.get("actionStates", []), "battery_state": response.get("batteryState", {}), "safety_state": response.get("safetyState", {}), "information": response.get("information", []), "errors": response.get("errors", []), "timestamp": response.get("timestamp", "") } def _decode_factsheet_response(self, response: Dict[str, Any]) -> Dict[str, Any]: """解码VDA5050设备信息响应""" return { "command_type": "factsheet_response", "manufacturer": response.get("manufacturer", ""), "serial_number": response.get("serialNumber", ""), "type_specification": response.get("typeSpecification", {}), "physical_parameters": response.get("physicalParameters", {}), "protocol_limits": response.get("protocolLimits", {}), "protocol_features": response.get("protocolFeatures", {}), "agv_geometry": response.get("agvGeometry", {}), "load_specification": response.get("loadSpecification", {}), "timestamp": response.get("timestamp", "") } class TCPProtocol(DeviceProtocol): """TCP协议实现(VDA5050通过TCP传输)""" def __init__(self, brand: DeviceBrand = DeviceBrand.STANDARD, device_type: DeviceType = DeviceType.VEHICLE): super().__init__(brand, device_type, ProtocolType.TCP) self.vda_protocol = VDA5050Protocol(brand, device_type) def encode_command(self, command_type: str, command: Dict[str, Any]) -> Dict[str, Any]: """TCP协议编码(基于VDA5050)""" # 先通过VDA5050协议编码 vda_data = self.vda_protocol.encode_command(command_type, command) # 添加TCP传输包装 tcp_packet = { "protocol": "vda5050", "transport": "tcp", "timestamp": datetime.now().isoformat(), "data": vda_data } return tcp_packet def decode_response(self, message_type: str, response: Dict[str, Any]) -> Dict[str, Any]: """TCP协议解码(基于VDA5050)""" # 如果是TCP包装格式,提取VDA5050数据 if "data" in response and response.get("protocol") == "vda5050": vda_data = response["data"] return self.vda_protocol.decode_response(message_type, vda_data) else: # 直接作为VDA5050数据处理 return self.vda_protocol.decode_response(message_type, response) class DeviceProtocolRegistry: """设备协议注册中心""" def __init__(self): self.protocols: Dict[str, DeviceProtocol] = {} self._register_builtin_protocols() def _register_builtin_protocols(self): """注册内置协议""" # 默认VDA5050协议(适用于所有设备) default_vda5050 = VDA5050Protocol() self.register_protocol("vda5050", default_vda5050) # 各品牌VDA5050协议 for brand in [DeviceBrand.HUARUI, DeviceBrand.SEER, DeviceBrand.QUICKTRON, DeviceBrand.GEEK, DeviceBrand.MUSHINY, DeviceBrand.FLASHHOLD, DeviceBrand.HIKROBOT]: # 小车VDA5050协议 vehicle_protocol = VDA5050Protocol(brand, DeviceType.VEHICLE) self.register_protocol(f"{brand.value}_vda5050", vehicle_protocol) # TCP协议(基于VDA5050) tcp_protocol = TCPProtocol(brand, DeviceType.VEHICLE) self.register_protocol(f"{brand.value}_tcp", tcp_protocol) # 非小车设备的VDA5050协议(即时动作) for device_type in [DeviceType.DOOR, DeviceType.CALLER, DeviceType.LIFT, DeviceType.CONVEYOR, DeviceType.SENSOR, DeviceType.CAMERA, DeviceType.SCANNER, DeviceType.ROBOT, DeviceType.CUSTOM]: vda_protocol = VDA5050Protocol(DeviceBrand.STANDARD, device_type) self.register_protocol(f"{device_type.value}_vda5050", vda_protocol) tcp_protocol = TCPProtocol(DeviceBrand.STANDARD, device_type) self.register_protocol(f"{device_type.value}_tcp", tcp_protocol) def register_protocol(self, protocol_key: str, protocol: DeviceProtocol): """注册设备协议""" self.protocols[protocol_key] = protocol logger.info(f"注册设备协议: {protocol_key} ({protocol.brand.value}_{protocol.device_type.value})") def get_protocol(self, protocol_key: str) -> Optional[DeviceProtocol]: """获取设备协议""" return self.protocols.get(protocol_key) def get_protocol_by_brand_type(self, brand: Union[str, DeviceBrand], device_type: Union[str, DeviceType]) -> Optional[DeviceProtocol]: """根据品牌和设备类型获取协议""" if isinstance(brand, str): brand = DeviceBrand(brand) if isinstance(device_type, str): device_type = DeviceType(device_type) for protocol in self.protocols.values(): if protocol.brand == brand and protocol.device_type == device_type: return protocol return None def list_protocols(self) -> Dict[str, Dict[str, str]]: """列出所有协议""" return { key: { "brand": protocol.brand.value, "device_type": protocol.device_type.value, "supported_commands": protocol.get_supported_commands() } for key, protocol in self.protocols.items() } def register_custom_protocol(self, protocol_key: str, brand: str, device_type: str, encode_func: Callable = None, decode_func: Callable = None): """注册自定义协议(基于VDA5050)""" # 解析设备类型 try: device_enum = DeviceType(device_type) except ValueError: device_enum = DeviceType.CUSTOM # 解析品牌 try: brand_enum = DeviceBrand(brand) except ValueError: brand_enum = DeviceBrand.CUSTOM # 创建基于VDA5050的自定义协议 custom_protocol = VDA5050Protocol(brand_enum, device_enum) # 如果提供了自定义编解码函数,包装协议 if encode_func or decode_func: original_encode = custom_protocol.encode_command original_decode = custom_protocol.decode_response def custom_encode(command_type: str, command: Dict[str, Any]) -> Dict[str, Any]: vda_result = original_encode(command_type, command) if encode_func: return encode_func(command_type, vda_result) return vda_result def custom_decode(message_type: str, response: Dict[str, Any]) -> Dict[str, Any]: if decode_func: processed_response = decode_func(message_type, response) return original_decode(message_type, processed_response) return original_decode(message_type, response) custom_protocol.encode_command = custom_encode custom_protocol.decode_response = custom_decode self.register_protocol(protocol_key, custom_protocol) # 全局协议注册中心 _global_protocol_registry = DeviceProtocolRegistry() def get_protocol_registry() -> DeviceProtocolRegistry: """获取全局协议注册中心""" return _global_protocol_registry class MessagePriority(Enum): """消息优先级""" LOW = 1 NORMAL = 2 HIGH = 3 CRITICAL = 4 @dataclass class DeviceMessage: """设备消息数据结构""" device_id: str device_type: DeviceType topic: str payload: Dict[str, Any] timestamp: float priority: MessagePriority = MessagePriority.NORMAL source_topic: Optional[str] = None target_topics: Optional[List[str]] = None processed: bool = False retry_count: int = 0 max_retries: int = 3 def to_dict(self) -> Dict[str, Any]: """转换为字典格式""" data = asdict(self) data['device_type'] = self.device_type.value data['priority'] = self.priority.value return data @classmethod def from_dict(cls, data: Dict[str, Any]) -> 'DeviceMessage': """从字典创建消息对象""" data['device_type'] = DeviceType(data['device_type']) data['priority'] = MessagePriority(data['priority']) return cls(**data) @dataclass class DeviceHandlerConfig: """设备处理器配置""" device_id: str device_type: DeviceType listen_topics: List[str] forward_topics: List[str] handler_function: str script_id: str device_brand: Optional[DeviceBrand] = DeviceBrand.STANDARD # 设备品牌(默认标准) protocol_type: ProtocolType = ProtocolType.VDA5050 # 协议类型(默认VDA5050) # auto_encode: bool = True # 编解码功能已移除,消息直接透传 enabled: bool = True auto_retry: bool = True timeout_seconds: int = 30 batch_size: int = 1 description: str = "" metadata: Dict[str, Any] = None def __post_init__(self): if self.metadata is None: self.metadata = {} # 已移除: protocol_key 方法,不再需要协议标识 class DeviceHandlerRegistry: """设备处理器注册中心""" def __init__(self, use_async_mqtt: bool =True): self.device_handlers: Dict[str, DeviceHandlerConfig] = {} self.message_processors: Dict[str, Callable] = {} self.topic_mappings: Dict[str, List[str]] = {} # topic -> device_ids self.active_devices: Dict[str, Dict[str, Any]] = {} self.message_queue: asyncio.Queue = asyncio.Queue() self.processing_stats: Dict[str, Dict[str, Any]] = {} # 选择MQTT服务类型 self.use_async_mqtt = use_async_mqtt if use_async_mqtt: self.mqtt_service = AsyncMQTTService() logger.info("使用异步MQTT服务") else: self.mqtt_service = MQTTService() logger.info("使用同步MQTT服务") # self.protocol_registry = get_protocol_registry() # 已移除协议注册中心 def register_device_handler(self, config: DeviceHandlerConfig, processor: Callable): """注册并运行设备处理器""" device_id = config.device_id # 注册设备配置 self.device_handlers[device_id] = config self.message_processors[device_id] = processor # 建立topic映射 for topic in config.listen_topics: if topic not in self.topic_mappings: self.topic_mappings[topic] = [] if device_id not in self.topic_mappings[topic]: self.topic_mappings[topic].append(device_id) # 初始化统计信息 self.processing_stats[device_id] = { "total_messages": 0, "success_count": 0, "error_count": 0, "last_processed": None, "average_processing_time_ms": None, "registered_at": datetime.now().isoformat(), "status": "running" } # 标记设备为活跃 self.active_devices[device_id] = { "config": config, "status": "running", "last_heartbeat": time.time(), "message_count": 0 } brand_info = f" 品牌: {config.device_brand.value}" if config.device_brand else "" # protocol_info = f" 协议: {config.protocol_key}" if config.protocol_key else "" # 已移除协议信息 logger.info(f"注册并运行设备处理器: {device_id} ({config.device_type.value}{brand_info})") logger.info(f"监听topics: {config.listen_topics}") logger.info(f"转发topics: {config.forward_topics}") logger.info("消息透传模式: 已启用") # 如果MQTT服务已连接,立即订阅topics if self.mqtt_service.connected: if self.use_async_mqtt: # 异步MQTT需要在异步上下文中订阅 asyncio.create_task(self._subscribe_topics_async(config.listen_topics)) else: self._subscribe_topics(config.listen_topics) def unregister_device_handler(self, device_id: str): """停止并注销设备处理器""" if device_id in self.device_handlers: config = self.device_handlers[device_id] # 只在MQTT连接时才尝试取消订阅topics(_unsubscribe_topics已内置连接检查) self._unsubscribe_topics(config.listen_topics, device_id) # 清理topic映射 for topic in config.listen_topics: if topic in self.topic_mappings: if device_id in self.topic_mappings[topic]: self.topic_mappings[topic].remove(device_id) if not self.topic_mappings[topic]: del self.topic_mappings[topic] # 清理注册信息 del self.device_handlers[device_id] del self.message_processors[device_id] if device_id in self.active_devices: del self.active_devices[device_id] if device_id in self.processing_stats: del self.processing_stats[device_id] logger.info(f"停止并注销设备处理器: {device_id}") def _subscribe_topics(self, topics: List[str]): """订阅MQTT topics(同步版本)""" for topic in topics: # 为每个topic添加消息处理器 self.mqtt_service.add_message_handler(topic, self._handle_mqtt_message) self.mqtt_service.subscribe(topic) async def _subscribe_topics_async(self, topics: List[str]): """订阅MQTT topics(异步版本)""" for topic in topics: # 为每个topic添加消息处理器 self.mqtt_service.add_message_handler(topic, self._handle_mqtt_message_async) await self.mqtt_service.subscribe(topic) def _unsubscribe_topics(self, topics: List[str], device_id: str): """取消订阅MQTT topics""" # 检查MQTT连接状态,避免在断开连接时产生警告 if not self.mqtt_service.connected: logger.debug(f"MQTT未连接,跳过取消订阅: {topics}") return for topic in topics: # 检查是否还有其他设备在监听这个topic other_devices = [d for d in self.topic_mappings.get(topic, []) if d != device_id] if not other_devices: if self.use_async_mqtt: # 异步MQTT需要创建任务来取消订阅 asyncio.create_task(self.mqtt_service.unsubscribe(topic)) else: self.mqtt_service.unsubscribe(topic) def get_handlers_for_topic(self, topic: str) -> List[str]: """获取处理指定topic的设备ID列表""" return self.topic_mappings.get(topic, []) def get_device_config(self, device_id: str) -> Optional[DeviceHandlerConfig]: """获取设备配置""" return self.device_handlers.get(device_id) def get_device_processor(self, device_id: str) -> Optional[Callable]: """获取设备处理器""" return self.message_processors.get(device_id) def _handle_mqtt_message(self, topic: str, payload: str): """处理MQTT消息""" try: # 获取当前事件循环,如果没有则创建新的 try: loop = asyncio.get_running_loop() # 在当前循环中创建任务 asyncio.create_task(self.handle_mqtt_message(topic, payload)) except RuntimeError: # 没有运行中的事件循环,使用线程安全的方式 def run_async(): # 在新线程中运行异步处理 asyncio.run(self.handle_mqtt_message(topic, payload)) # 创建并启动线程 thread = threading.Thread(target=run_async, daemon=True) thread.start() except Exception as e: logger.error(f"处理MQTT消息异常: {e}", exc_info=True) async def _handle_mqtt_message_async(self, topic: str, payload: str): """处理MQTT消息(异步版本)""" await self.handle_mqtt_message(topic, payload) async def handle_mqtt_message(self, topic: str, payload: Union[str, bytes, Dict[str, Any]]): """处理MQTT消息""" try: # 解析payload if isinstance(payload, (str, bytes)): payload_data = json.loads(payload) else: payload_data = payload # 获取处理该topic的设备列表 device_ids = self.get_handlers_for_topic(topic) for device_id in device_ids: config = self.get_device_config(device_id) if config and config.enabled: decoded_payload = payload_data # 创建设备消息 message = DeviceMessage( device_id=device_id, device_type=config.device_type, topic=topic, payload=decoded_payload, timestamp=time.time(), source_topic=topic, target_topics=config.forward_topics ) # 立即处理消息 await self._process_device_message(message) except json.JSONDecodeError as e: logger.error(f"MQTT消息JSON解析失败: {e}, topic: {topic}, payload: {payload}") except Exception as e: logger.error(f"处理MQTT消息失败: {e}", exc_info=True) async def _process_device_message(self, message: DeviceMessage): """处理单个设备消息""" device_id = message.device_id config = self.device_handlers.get(device_id) processor = self.message_processors.get(device_id) if not config or not processor: logger.warning(f"未找到设备处理器: {device_id}") return if not config.enabled: logger.debug(f"设备处理器已禁用: {device_id}") return start_time = time.time() try: # 更新统计信息 stats = self.processing_stats[device_id] stats["total_messages"] += 1 # 执行处理器 if asyncio.iscoroutinefunction(processor): result = await processor(message) else: result = processor(message) # 处理成功 stats["success_count"] += 1 stats["last_processed"] = datetime.now().isoformat() message.processed = True # 更新设备心跳 if device_id in self.active_devices: self.active_devices[device_id]["last_heartbeat"] = time.time() self.active_devices[device_id]["message_count"] += 1 # 计算平均处理时间 processing_time_ms = int((time.time() - start_time) * 1000) if stats["average_processing_time_ms"] is None: stats["average_processing_time_ms"] = processing_time_ms else: total_time = (stats["average_processing_time_ms"] * (stats["success_count"] - 1) + processing_time_ms) stats["average_processing_time_ms"] = int(total_time / stats["success_count"]) # 处理转发逻辑 if result and isinstance(result, dict): await self._handle_message_result(message, result) logger.debug(f"设备消息处理成功: {device_id}, 耗时: {processing_time_ms}ms") except Exception as e: # 处理失败 stats["error_count"] += 1 logger.error(f"设备消息处理失败: {device_id}, 错误: {e}", exc_info=True) async def _handle_message_result(self, original_message: DeviceMessage, result: Dict[str, Any]): """处理消息处理结果""" try: config = self.device_handlers.get(original_message.device_id) if not config: return # 检查是否需要转发 if result.get("forward", True): # 确定转发的payload forward_payload = result.get("payload", original_message.payload) # 直接使用转发payload,不进行编码 encoded_payload = forward_payload # 确定转发的topics forward_topics = result.get("topics", original_message.target_topics) if isinstance(forward_topics, str): forward_topics = [forward_topics] # 转发到所有目标topics if forward_topics: for topic in forward_topics: self._publish_mqtt_message(topic, encoded_payload) logger.debug(f"消息已转发: {original_message.device_id} -> {topic}") # 处理自定义响应 if "response" in result: response_topic = result.get("response_topic") response_payload = result["response"] # 直接使用响应payload,不进行编码 if response_topic: self._publish_mqtt_message(response_topic, response_payload) logger.debug(f"响应已发送: {original_message.device_id} -> {response_topic}") except Exception as e: logger.error(f"处理消息结果失败: {e}", exc_info=True) def _publish_mqtt_message(self, topic: str, payload: Any): """发布MQTT消息""" if self.use_async_mqtt: # 对于异步MQTT,创建异步任务 asyncio.create_task(self.mqtt_service.publish(topic, payload)) else: self.mqtt_service.publish(topic, payload) async def start_mqtt_service(self): """启动内部MQTT服务""" try: if self.use_async_mqtt: success = await self.mqtt_service.connect() else: success = self.mqtt_service.connect() if success: logger.info("内部MQTT服务已启动") # 为已注册的设备订阅topics all_topics = set() for config in self.device_handlers.values(): all_topics.update(config.listen_topics) if all_topics: if self.use_async_mqtt: await self._subscribe_topics_async(list(all_topics)) else: self._subscribe_topics(list(all_topics)) return True else: logger.error("启动内部MQTT服务失败") return False except Exception as e: logger.error(f"启动MQTT服务异常: {e}", exc_info=True) return False async def stop_mqtt_service(self): """停止内部MQTT服务""" try: if self.use_async_mqtt: await self.mqtt_service.disconnect() else: self.mqtt_service.disconnect() logger.info("内部MQTT服务已停止") except Exception as e: logger.error(f"停止MQTT服务异常: {e}", exc_info=True) def get_device_statistics(self, device_id: str = None) -> Dict[str, Any]: """获取设备统计信息""" if device_id: stats = self.processing_stats.get(device_id, {}).copy() if device_id in self.active_devices: stats.update({ "device_status": self.active_devices[device_id]["status"], "last_heartbeat": self.active_devices[device_id]["last_heartbeat"], "message_count": self.active_devices[device_id]["message_count"] }) return stats else: return { "device_count": len(self.device_handlers), "running_devices": len([d for d in self.active_devices.values() if d["status"] == "running"]), "total_topics": len(self.topic_mappings), "devices": {device_id: self.get_device_statistics(device_id) for device_id in self.device_handlers.keys()} } def clear_script_device_registrations(self, script_id: str): """清理指定脚本的所有设备注册""" devices_to_remove = [] for device_id, config in self.device_handlers.items(): if config.script_id == script_id: devices_to_remove.append(device_id) for device_id in devices_to_remove: self.unregister_device_handler(device_id) logger.info(f"已清理脚本 {script_id} 的 {len(devices_to_remove)} 个设备注册") # 全局设备处理器注册中心 _global_device_registry = DeviceHandlerRegistry() def get_device_registry() -> DeviceHandlerRegistry: """获取全局设备注册中心实例""" return _global_device_registry class DeviceHandlerService: """设备处理器服务""" def __init__(self): self.registry = get_device_registry() async def start_service(self): """启动设备处理服务""" if await self.registry.start_mqtt_service(): logger.info("设备处理服务已启动,MQTT服务已连接") else: logger.warning("设备处理服务已启动,但MQTT连接失败") async def stop_service(self): """停止设备处理服务""" # 先清理所有设备注册(这会取消订阅topics) device_ids = list(self.registry.device_handlers.keys()) for device_id in device_ids: self.registry.unregister_device_handler(device_id) # 最后停止MQTT服务 await self.registry.stop_mqtt_service() logger.info("设备处理服务已停止") def register_and_run(self, device_id: str, device_type: Union[str, DeviceType], listen_topics: List[str], forward_topics: List[str], handler: Callable, script_id: str, description: str = "", device_brand: Union[str, DeviceBrand] = DeviceBrand.STANDARD, protocol_type: Union[str, ProtocolType] = ProtocolType.VDA5050, protocol_key: str = "vda5050", # 兼容性参数,默认值 **kwargs): """注册并运行设备处理器(消息直接透传) Args: device_id: 设备ID device_type: 设备类型 listen_topics: 监听的MQTT topics forward_topics: 转发的MQTT topics handler: 消息处理器函数 script_id: 脚本ID description: 描述信息 device_brand: 设备品牌(默认standard) protocol_type: 协议类型(vda5050或tcp,默认vda5050) protocol_key: 协议标识(已弃用,兼容性参数) **kwargs: 其他配置参数 """ # 确保device_type是DeviceType枚举 if isinstance(device_type, str): device_type = DeviceType(device_type) # 处理设备品牌 if isinstance(device_brand, str): try: device_brand = DeviceBrand(device_brand) except ValueError: logger.warning(f"未知的设备品牌: {device_brand}, 使用STANDARD品牌") device_brand = DeviceBrand.STANDARD # 处理协议类型 if isinstance(protocol_type, str): try: protocol_type = ProtocolType(protocol_type) except ValueError: logger.warning(f"未知的协议类型: {protocol_type}, 使用VDA5050协议") protocol_type = ProtocolType.VDA5050 # 过滤掉已弃用的兼容性参数 if protocol_key != "vda5050" and protocol_key: logger.warning(f"protocol_key参数已弃用,使用默认VDA5050协议: {protocol_key}") # 过滤kwargs中的兼容性参数(包括可能传入的auto_encode) filtered_kwargs = {k: v for k, v in kwargs.items() if k not in ['protocol_key', 'auto_encode']} config = DeviceHandlerConfig( device_id=device_id, device_type=device_type, listen_topics=listen_topics, forward_topics=forward_topics, handler_function=handler.__name__, script_id=script_id, description=description, device_brand=device_brand, protocol_type=protocol_type, **filtered_kwargs ) self.registry.register_device_handler(config, handler) def stop_handler(self, device_id: str): """停止指定设备处理器""" self.registry.unregister_device_handler(device_id) def get_running_handlers(self) -> Dict[str, Any]: """获取所有正在运行的设备处理器""" return self.registry.get_device_statistics() def get_handler_status(self, device_id: str) -> Dict[str, Any]: """获取设备处理器状态""" return self.registry.get_device_statistics(device_id) async def publish_message(self, topic: str, payload: Any): """主动发布MQTT消息 Args: topic: MQTT主题 payload: 消息载荷 """ self.registry._publish_mqtt_message(topic, payload) def send_order(self, device_id: str, order_data: Dict[str, Any], topic: str = None) -> None: """发送订单消息 Args: device_id: 设备ID order_data: 订单数据 topic: 发布主题(可选,默认使用vda5050格式) """ import asyncio if not topic: topic = f"vda5050/{device_id}/order" asyncio.create_task( self.publish_message(topic, order_data) ) def send_instant_action(self, device_id: str, actions: Union[List[str], List[Dict[str, Any]]], topic: str = None) -> None: """发送即时动作消息 Args: device_id: 设备ID actions: 动作列表 topic: 发布主题(可选,默认使用vda5050格式) """ import asyncio if not topic: topic = f"vda5050/{device_id}/instantActions" action_data = {"actions": actions} asyncio.create_task( self.publish_message(topic, action_data) ) def request_state(self, device_id: str, topic: str = None) -> None: """请求设备状态 Args: device_id: 设备ID topic: 发布主题(可选,默认使用vda5050格式) """ import asyncio if not topic: topic = f"vda5050/{device_id}/state" state_request = {"request_type": "state"} asyncio.create_task( self.publish_message(topic, state_request) ) def request_factsheet(self, device_id: str, topic: str = None) -> None: """请求设备信息 Args: device_id: 设备ID topic: 发布主题(可选,默认使用vda5050格式) """ import asyncio if not topic: topic = f"vda5050/{device_id}/factsheet" factsheet_request = {"request_type": "factsheet"} asyncio.create_task( self.publish_message(topic, factsheet_request) ) # 保持兼容性的别名方法 def send_vda5050_order(self, device_id: str, order_data: Dict[str, Any], topic: str = None) -> None: """发送VDA5050订单指令(兼容性方法)""" self.send_order(device_id, order_data, topic) def send_vda5050_instant_action(self, device_id: str, actions: List[Dict[str, Any]], topic: str = None) -> None: """发送VDA5050即时动作指令(兼容性方法)""" self.send_instant_action(device_id, actions, topic) def request_vda5050_state(self, device_id: str, topic: str = None) -> None: """请求VDA5050状态(兼容性方法)""" self.request_state(device_id, topic) # 全局设备处理服务实例 _global_device_service = DeviceHandlerService() def get_device_service() -> DeviceHandlerService: """获取全局设备处理服务实例""" return _global_device_service