#!/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 from config.tf_api_config import MQTT_TOPIC_CONFIG 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 TopicManager: """MQTT Topic管理器""" def __init__(self): self.topic_config = MQTT_TOPIC_CONFIG self.instance_id = self.topic_config["instance_id"] self.supported_brands = self.topic_config["supported_brands"] self.command_directions = self.topic_config["command_directions"] def validate_brand(self, brand_name: str) -> bool: """验证品牌是否支持""" return brand_name.lower() in self.supported_brands def get_supported_brands(self) -> List[str]: """获取支持的品牌列表""" return list(self.supported_brands.keys()) def generate_listen_topics(self, device_ids: List[str], brand_name: str, command_types: List[str] = None, device_type: DeviceType = DeviceType.VEHICLE) -> List[str]: """生成监听topics Args: device_ids: 设备ID列表 brand_name: 品牌名称 command_types: 指令类型列表,如果为None则使用默认指令 device_type: 设备类型 Returns: 监听topic列表 """ if not self.validate_brand(brand_name): raise ValueError(f"不支持的品牌: {brand_name},支持的品牌: {self.get_supported_brands()}") brand_config = self.supported_brands[brand_name.lower()] brand_suffix = brand_config["brand_suffix"] oagv_version = brand_config["oagv_version"] if command_types is None: if device_type == DeviceType.VEHICLE: command_types = self.topic_config["default_command_types"]["vehicle"] else: command_types = self.topic_config["default_command_types"]["other"] listen_topics = [] for command_type in command_types: if command_type not in self.command_directions: logger.warning(f"未知的指令类型: {command_type}") continue direction_config = self.command_directions[command_type] listen_queue = direction_config["listen"] if listen_queue == "oagv": # oagv格式: oagv/v2/{instanceId}_IRAYPLE/{deviceId}/order for device_id in device_ids: topic = f"oagv/{oagv_version}/{self.instance_id}{brand_suffix}/{device_id}/{command_type}" listen_topics.append(topic) elif listen_queue == "uagv": # uagv格式: uagv/v2.0.0/IRAYPLE/{deviceId}/state uagv_version = brand_config["uagv_version"] brand_name_only = brand_suffix[1:] # 去掉前缀下划线 for device_id in device_ids: topic = f"uagv/{uagv_version}/{brand_name_only}/{device_id}/{command_type}" listen_topics.append(topic) return listen_topics def generate_forward_topics(self, device_ids: List[str], brand_name: str, command_types: List[str] = None, device_type: DeviceType = DeviceType.VEHICLE) -> List[str]: """生成转发topics Args: device_ids: 设备ID列表 brand_name: 品牌名称 command_types: 指令类型列表,如果为None则使用默认指令 device_type: 设备类型 Returns: 转发topic列表 """ if not self.validate_brand(brand_name): raise ValueError(f"不支持的品牌: {brand_name},支持的品牌: {self.get_supported_brands()}") brand_config = self.supported_brands[brand_name.lower()] brand_suffix = brand_config["brand_suffix"] uagv_version = brand_config["uagv_version"] oagv_version = brand_config["oagv_version"] if command_types is None: if device_type == DeviceType.VEHICLE: command_types = self.topic_config["default_command_types"]["vehicle"] else: command_types = self.topic_config["default_command_types"]["other"] forward_topics = [] for command_type in command_types: if command_type not in self.command_directions: logger.warning(f"未知的指令类型: {command_type}") continue direction_config = self.command_directions[command_type] forward_queue = direction_config["forward"] if forward_queue == "uagv": # uagv格式: uagv/v2.0.0/IRAYPLE/{deviceId}/order brand_name_only = brand_suffix[1:] # 去掉前缀下划线 for device_id in device_ids: topic = f"uagv/{uagv_version}/{brand_name_only}/{device_id}/{command_type}" forward_topics.append(topic) elif forward_queue == "oagv": # oagv格式: oagv/v2/{instanceId}_IRAYPLE/{deviceId}/state for device_id in device_ids: topic = f"oagv/{oagv_version}/{self.instance_id}{brand_suffix}/{device_id}/{command_type}" forward_topics.append(topic) return forward_topics def extract_vehicle_id_from_topic(self, topic: str) -> Optional[str]: """从topic中提取车辆ID""" parts = topic.split('/') if len(parts) >= 4: if parts[0] == "oagv": # oagv格式: oagv/v2/{instanceId}_BRAND/{vehicleId}/command return parts[3] if parts[3] != "+" else None elif parts[0] == "uagv": # uagv格式: uagv/v2.0.0/BRAND/{vehicleId}/command return parts[3] return None 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() # 已移除协议注册中心 # Topic管理器 self.topic_manager = TopicManager() # 系统级监听状态管理 self.system_listen_topics: Dict[str, str] = {} # topic -> handler_id self.custom_device_handlers: Dict[str, str] = {} # device_id -> handler_id self.is_system_listening: bool = False def register_device_handler(self, config: DeviceHandlerConfig, processor: Callable): """注册并运行设备处理器""" device_id = config.device_id # 标记为自定义设备处理器(从系统监听中排除) self.custom_device_handlers[device_id] = config.script_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("消息透传模式: 已启用") logger.info(f"设备 {device_id} 已从系统级监听中移除,由自定义处理器接管") # 如果MQTT服务已连接,立即订阅topics if self.mqtt_service.connected: # 过滤掉已经被系统级监听覆盖的topics,避免重复订阅 topics_to_subscribe = self._filter_duplicate_topics(config.listen_topics, config.device_brand.value) if topics_to_subscribe: if self.use_async_mqtt: # 异步MQTT需要在异步上下文中订阅 asyncio.create_task(self._subscribe_topics_async(topics_to_subscribe)) else: self._subscribe_topics(topics_to_subscribe) logger.info(f"设备 {device_id} 订阅了 {len(topics_to_subscribe)} 个新topics") else: logger.info(f"设备 {device_id} 的所有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] # 从自定义设备列表中移除(恢复系统级监听) if device_id in self.custom_device_handlers: del self.custom_device_handlers[device_id] logger.info(f"设备 {device_id} 已恢复系统级监听") # 清理注册信息 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添加了我们的消息处理器 topic_handlers = getattr(self.mqtt_service, 'message_handlers', {}).get(topic, []) sync_handler = self._handle_mqtt_message # 只有当处理器不在列表中时才添加 if sync_handler not in topic_handlers: self.mqtt_service.add_message_handler(topic, sync_handler) self.mqtt_service.subscribe(topic) async def _subscribe_topics_async(self, topics: List[str]): """订阅MQTT topics(异步版本)""" for topic in topics: # 检查是否已经为该topic添加了我们的消息处理器 topic_handlers = getattr(self.mqtt_service, 'message_handlers', {}).get(topic, []) async_handler = self._handle_mqtt_message_async # 只有当处理器不在列表中时才添加 if async_handler not in topic_handlers: self.mqtt_service.add_message_handler(topic, async_handler) await self.mqtt_service.subscribe(topic) def _filter_duplicate_topics(self, topics: List[str], brand_name: str) -> List[str]: """过滤掉已经被系统级监听覆盖的topics Args: topics: 要检查的topic列表 brand_name: 设备品牌名称 Returns: 需要订阅的topic列表(排除已被系统级监听覆盖的) """ if not self.is_system_listening: # 如果系统级监听未启动,则所有topics都需要订阅 return topics filtered_topics = [] for topic in topics: # 检查是否已被系统级监听覆盖 is_covered_by_system = False # 检查当前topic是否被任何系统级通配符topic覆盖 for system_topic in self.system_listen_topics.keys(): if self._is_topic_covered_by_wildcard(topic, system_topic, brand_name): is_covered_by_system = True logger.debug(f"Topic {topic} 已被系统级监听 {system_topic} 覆盖") break if not is_covered_by_system: filtered_topics.append(topic) logger.debug(f"Topic {topic} 需要单独订阅") return filtered_topics def _is_topic_covered_by_wildcard(self, specific_topic: str, wildcard_topic: str, brand_name: str) -> bool: """检查特定topic是否被通配符topic覆盖 Args: specific_topic: 具体的topic,如 "oagv/v2/asbm2_IRAYPLE/AGV001/order" wildcard_topic: 通配符topic,如 "oagv/v2/asbm2_IRAYPLE/+/order" brand_name: 品牌名称 Returns: 是否被覆盖 """ specific_parts = specific_topic.split('/') wildcard_parts = wildcard_topic.split('/') # 长度必须相同 if len(specific_parts) != len(wildcard_parts): return False # 检查每个部分是否匹配 for i, (specific_part, wildcard_part) in enumerate(zip(specific_parts, wildcard_parts)): if wildcard_part == '+': # 通配符匹配任意值 continue elif specific_part != wildcard_part: # 非通配符部分必须完全匹配 return False # 额外检查:确保品牌匹配 # 从topic中提取品牌信息进行验证 if len(specific_parts) >= 3: topic_brand_instance = specific_parts[2] # 检查品牌是否匹配 brand_config = self.topic_manager.supported_brands.get(brand_name.lower()) if brand_config: brand_suffix = brand_config["brand_suffix"] # 对于oagv格式,检查实例+品牌后缀是否匹配 if specific_parts[0] == "oagv": expected_instance_brand = f"{self.topic_manager.instance_id}{brand_suffix}" if topic_brand_instance == expected_instance_brand: return True # 对于uagv格式,检查品牌名是否匹配 elif specific_parts[0] == "uagv": brand_name_only = brand_suffix[1:] if brand_suffix.startswith('_') else brand_suffix if topic_brand_instance == brand_name_only: return True return False 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列表(支持通配符匹配)""" handlers = [] # 首先尝试精确匹配 exact_handlers = self.topic_mappings.get(topic, []) handlers.extend(exact_handlers) # 然后尝试通配符匹配 topic_parts = topic.split('/') for registered_topic, topic_handlers in self.topic_mappings.items(): # 跳过已经精确匹配的topic if registered_topic == topic: continue registered_parts = registered_topic.split('/') # 检查路径长度是否匹配 if len(topic_parts) == len(registered_parts): # 检查每个部分是否匹配(支持+通配符) match = True for i, (topic_part, registered_part) in enumerate(zip(topic_parts, registered_parts)): if registered_part != '+' and registered_part != topic_part: match = False break if match: handlers.extend(topic_handlers) return handlers 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消息""" logger.debug(f"收到MQTT消息: topic={topic}") try: # 解析payload if isinstance(payload, (str, bytes)): payload_data = json.loads(payload) else: payload_data = payload # 获取处理该topic的设备列表 handler_ids = self.get_handlers_for_topic(topic) logger.debug(f"处理topic {topic} 的handler_ids: {handler_ids}") # 分离自定义处理器和系统级处理器 custom_handlers = [hid for hid in handler_ids if not hid.startswith("system_")] system_handlers = [hid for hid in handler_ids if hid.startswith("system_")] # 优先处理自定义处理器,如果有自定义处理器就跳过系统级处理器 if custom_handlers: logger.debug(f"使用自定义处理器: {custom_handlers}") for handler_id in custom_handlers: # 用户自定义处理器 config = self.get_device_config(handler_id) if config and config.enabled: # 创建设备消息 message = DeviceMessage( device_id=handler_id, device_type=config.device_type, topic=topic, payload=payload_data, timestamp=time.time(), source_topic=topic, target_topics=config.forward_topics ) # 立即处理消息 await self._process_device_message(message) else: # 只有在没有自定义处理器时才使用系统级处理器 logger.debug(f"使用系统级处理器: {system_handlers}") for handler_id in system_handlers: # 系统级处理器:从topic中提取设备ID device_id = self.topic_manager.extract_vehicle_id_from_topic(topic) if device_id: # 根据收到的topic自动生成转发topic forward_topic = self._generate_forward_topic_from_listen_topic(topic) target_topics = [forward_topic] if forward_topic else [] # 创建系统级设备消息 message = DeviceMessage( device_id=device_id, device_type=DeviceType.VEHICLE, # 系统级默认为vehicle topic=topic, payload=payload_data, timestamp=time.time(), source_topic=topic, target_topics=target_topics ) # 使用系统级处理器处理 processor = self.message_processors.get(handler_id) if processor: await self._process_system_message(handler_id, message, processor) 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_system_message(self, handler_id: str, message: DeviceMessage, processor: Callable): """处理系统级消息""" try: # 执行系统级处理器 if asyncio.iscoroutinefunction(processor): result = await processor(message) else: result = processor(message) # 处理转发逻辑 if result and isinstance(result, dict): await self._handle_system_message_result(message, result) except Exception as e: logger.error(f"系统级消息处理失败: handler_id={handler_id}, device_id={message.device_id}, 错误: {e}", exc_info=True) async def _handle_system_message_result(self, original_message: DeviceMessage, result: Dict[str, Any]): """处理系统级消息处理结果""" try: # 检查是否需要转发 if result.get("forward", True): # 确定转发的payload forward_payload = result.get("payload", original_message.payload) # 确定转发的topic response_topic = result.get("response_topic") if response_topic: self._publish_mqtt_message(response_topic, forward_payload) logger.debug(f"系统级消息已转发: {original_message.device_id} -> {response_topic}") except Exception as e: logger.error(f"处理系统级消息结果失败: {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]): """处理消息处理结果""" # print(original_message) 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 = config.forward_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服务已启动") # 启动系统级监听所有设备 await self.start_system_listening() # 为已注册的设备订阅topics(只订阅,不重复添加消息处理器) all_topics = set() for config in self.device_handlers.values(): all_topics.update(config.listen_topics) if all_topics: # 直接订阅topic,不添加消息处理器(消息处理器在注册时已添加) for topic in all_topics: if self.use_async_mqtt: await self.mqtt_service.subscribe(topic) else: self.mqtt_service.subscribe(topic) 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: # 先停止系统级监听 await self.stop_system_listening() 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)} 个设备注册") async def start_system_listening(self): """启动系统级监听所有设备""" if self.is_system_listening: logger.info("系统监听已经在运行中") return logger.info("启动系统级设备监听...") # 生成监听所有设备的topics(使用通配符+) system_topics = self._generate_system_listen_topics() # 创建系统级透传处理器 system_handler = self._create_system_forward_handler() # 为每个topic创建系统级处理器 for topic in system_topics: handler_id = f"system_{topic.replace('/', '_').replace('+', 'all')}" # 注册系统级处理器 self.system_listen_topics[topic] = handler_id self.message_processors[handler_id] = system_handler # 添加topic映射 if topic not in self.topic_mappings: self.topic_mappings[topic] = [] self.topic_mappings[topic].append(handler_id) # 订阅topic(只有在第一次添加时才添加消息处理器) if self.mqtt_service.connected: # 检查是否已经为该topic添加了我们的消息处理器 topic_handlers = getattr(self.mqtt_service, 'message_handlers', {}).get(topic, []) async_handler = self._handle_mqtt_message_async sync_handler = self._handle_mqtt_message if self.use_async_mqtt: await self.mqtt_service.subscribe(topic) # 只有当处理器不在列表中时才添加 if async_handler not in topic_handlers: self.mqtt_service.add_message_handler(topic, async_handler) else: self.mqtt_service.subscribe(topic) # 只有当处理器不在列表中时才添加 if sync_handler not in topic_handlers: self.mqtt_service.add_message_handler(topic, sync_handler) self.is_system_listening = True logger.info(f"系统级监听已启动,监听topics: {system_topics}") def _generate_system_listen_topics(self) -> List[str]: """生成系统级监听所有设备的topics""" topics = [] # 遍历所有支持的品牌 for brand_name in self.topic_manager.get_supported_brands(): brand_config = self.topic_manager.supported_brands[brand_name.lower()] brand_suffix = brand_config["brand_suffix"] oagv_version = brand_config["oagv_version"] uagv_version = brand_config["uagv_version"] # 为每种指令类型生成监听topic(使用+通配符匹配所有设备) for command_type, direction_config in self.topic_manager.command_directions.items(): listen_queue = direction_config["listen"] if listen_queue == "oagv": # oagv格式: oagv/v2/{instanceId}_BRAND/+/command topic = f"oagv/{oagv_version}/{self.topic_manager.instance_id}{brand_suffix}/+/{command_type}" topics.append(topic) elif listen_queue == "uagv": # uagv格式: uagv/v2.0.0/BRAND/+/command brand_name_only = brand_suffix[1:] # 去掉前缀下划线 topic = f"uagv/{uagv_version}/{brand_name_only}/+/{command_type}" topics.append(topic) return topics def _create_system_forward_handler(self) -> Callable: """创建系统级透传处理器""" async def system_forward_handler(message: DeviceMessage) -> Dict[str, Any]: """系统级透传处理器:直接转发消息,不做任何处理""" # 提取设备ID device_id = self.topic_manager.extract_vehicle_id_from_topic(message.topic) if not device_id: logger.warning(f"无法从topic中提取设备ID: {message.topic}") return {"forward": False} # 检查是否为自定义处理的设备 - 关键修复:排除自定义设备 if device_id in self.custom_device_handlers: logger.debug(f"设备 {device_id} 由自定义处理器处理,跳过系统转发") return {"forward": False} # 解析topic信息以确定转发目标 forward_topic = self._generate_forward_topic_from_listen_topic(message.topic) if not forward_topic: logger.warning(f"无法生成转发topic: {message.topic}") return {"forward": False} logger.info(f"系统透传转发: {message.topic} -> {forward_topic}, 设备: {device_id}") # 直接透传消息 return { "forward": True, "payload": message.payload, "response_topic": forward_topic } return system_forward_handler def _generate_forward_topic_from_listen_topic(self, listen_topic: str) -> Optional[str]: """根据监听topic生成对应的转发topic""" # logger.debug(f"开始解析转发topic: {listen_topic}") parts = listen_topic.split('/') if len(parts) < 5: logger.warning(f"Topic格式不正确,部分数量: {len(parts)}, topic: {listen_topic}") return None queue_type = parts[0] # oagv 或 uagv version = parts[1] brand_instance = parts[2] device_id = parts[3] command_type = parts[4] # logger.debug(f"解析结果: queue_type={queue_type}, version={version}, brand_instance={brand_instance}, device_id={device_id}, command_type={command_type}") # 如果device_id是通配符,无法转发 if device_id == "+": logger.debug("设备ID为通配符,无法转发") return None # 查找指令的转发方向 if command_type not in self.topic_manager.command_directions: logger.warning(f"未知的指令类型: {command_type}") return None direction_config = self.topic_manager.command_directions[command_type] forward_queue = direction_config["forward"] # logger.debug(f"指令 {command_type} 的转发方向: {forward_queue}") # 解析品牌信息 - 改进品牌匹配逻辑 brand_name = None # logger.debug(f"开始匹配品牌,当前brand_instance: {brand_instance}") for brand, config in self.topic_manager.supported_brands.items(): brand_suffix = config["brand_suffix"] # logger.debug(f"检查品牌 {brand}: suffix={brand_suffix}") if queue_type == "oagv": # oagv格式: oagv/v2/{instanceId}_BRAND/{deviceId}/command # brand_instance 应该是 "asbm2_IRAYPLE" 这样的格式 if brand_instance.endswith(brand_suffix): brand_name = brand # logger.debug(f"成功匹配oagv品牌: {brand}") break elif queue_type == "uagv": # uagv格式: uagv/v2/BRAND/{deviceId}/command # brand_instance 应该是 "IRAYPLE" 这样的格式(不含下划线前缀) brand_name_only = brand_suffix[1:] if brand_suffix.startswith('_') else brand_suffix if brand_instance == brand_name_only: brand_name = brand # logger.debug(f"成功匹配uagv品牌: {brand}") break if not brand_name: logger.warning(f"无法识别品牌信息: queue_type={queue_type}, brand_instance={brand_instance}") logger.debug(f"可用的品牌配置: {list(self.topic_manager.supported_brands.keys())}") return None brand_config = self.topic_manager.supported_brands[brand_name] # logger.debug(f"使用品牌配置: {brand_name} -> {brand_config}") # 生成转发topic forward_topic = None if forward_queue == "uagv": # 转发到uagv uagv_version = brand_config["uagv_version"] brand_name_only = brand_config["brand_suffix"][1:] if brand_config["brand_suffix"].startswith('_') else brand_config["brand_suffix"] forward_topic = f"uagv/{uagv_version}/{brand_name_only}/{device_id}/{command_type}" # logger.debug(f"生成uagv转发topic: {forward_topic}") elif forward_queue == "oagv": # 转发到oagv oagv_version = brand_config["oagv_version"] brand_suffix = brand_config["brand_suffix"] forward_topic = f"oagv/{oagv_version}/{self.topic_manager.instance_id}{brand_suffix}/{device_id}/{command_type}" # logger.debug(f"生成oagv转发topic: {forward_topic}") else: logger.warning(f"未知的转发队列类型: {forward_queue}") # logger.info(f"转发topic生成完成: {listen_topic} -> {forward_topic}") return forward_topic async def stop_system_listening(self): """停止系统级监听""" if not self.is_system_listening: return logger.info("停止系统级设备监听...") # 取消订阅所有系统级topics for topic, handler_id in self.system_listen_topics.items(): if self.mqtt_service.connected: if self.use_async_mqtt: await self.mqtt_service.unsubscribe(topic) else: self.mqtt_service.unsubscribe(topic) # 清理映射 if topic in self.topic_mappings: if handler_id in self.topic_mappings[topic]: self.topic_mappings[topic].remove(handler_id) if not self.topic_mappings[topic]: del self.topic_mappings[topic] # 清理处理器 if handler_id in self.message_processors: del self.message_processors[handler_id] self.system_listen_topics.clear() self.is_system_listening = False logger.info("系统级监听已停止") def get_system_listening_status(self) -> Dict[str, Any]: """获取系统级监听状态""" return { "is_listening": self.is_system_listening, "system_topics": list(self.system_listen_topics.keys()), "custom_devices": list(self.custom_device_handlers.keys()), "excluded_devices_count": len(self.custom_device_handlers) } # 全局设备处理器注册中心 _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_ids: List[str], device_type: Union[str, DeviceType] = DeviceType.VEHICLE, brand_name: str = "huarui", command_type: str = None, handler: Callable = None, script_id: str = "", description: str = "", protocol_type: Union[str, ProtocolType] = ProtocolType.VDA5050, **kwargs): """注册并运行设备处理器(通用设备注册方法) Args: device_ids: 设备ID列表(必需)- 支持批量注册多个设备 device_type: 设备类型(默认vehicle)- 支持所有设备类型 brand_name: 设备品牌(huarui或seer,默认huarui) command_type: 指令类型(必需)- 只能处理一种指令类型,如"order"、"state"等 handler: 消息处理器函数(必需) script_id: 脚本ID(必需) description: 描述信息 protocol_type: 协议类型(默认VDA5050) **kwargs: 其他配置参数 Examples: # 注册多辆小车 register_and_run( device_ids=["AGV001", "AGV002", "AGV003"], device_type="vehicle", brand_name="huarui", handler=my_handler, script_id="script_001" ) # 注册多个门设备 register_and_run( device_ids=["DOOR_001", "DOOR_002"], device_type="door", brand_name="standard", handler=door_handler, script_id="script_002" ) """ # 参数验证 if not device_ids or len(device_ids) == 0: raise ValueError("device_ids参数是必需的,且不能为空") if handler is None: raise ValueError("handler参数是必需的") if not script_id: raise ValueError("script_id参数是必需的") if not command_type: raise ValueError("command_type参数是必需的,且不能为空") # 验证command_type是否为有效的指令类型 valid_commands = ["order", "state", "factsheet", "instantActions"] if command_type not in valid_commands: raise ValueError(f"command_type参数错误:'{command_type}' 不是有效的指令类型。支持的类型有:{', '.join(valid_commands)}") # 确保device_type是DeviceType枚举 if isinstance(device_type, str): device_type = DeviceType(device_type) # 验证品牌 if not self.registry.topic_manager.validate_brand(brand_name): supported_brands = self.registry.topic_manager.get_supported_brands() raise ValueError(f"不支持的品牌: {brand_name},支持的品牌: {supported_brands}") # 处理协议类型 if isinstance(protocol_type, str): try: protocol_type = ProtocolType(protocol_type) except ValueError: logger.warning(f"未知的协议类型: {protocol_type}, 使用VDA5050协议") protocol_type = ProtocolType.VDA5050 # 设置品牌(从brand_name转换) if brand_name.lower() == "huarui": final_device_brand = DeviceBrand.HUARUI elif brand_name.lower() == "seer": final_device_brand = DeviceBrand.SEER elif brand_name.lower() == "standard": final_device_brand = DeviceBrand.STANDARD else: final_device_brand = DeviceBrand.CUSTOM # 过滤kwargs中的兼容性参数 filtered_kwargs = {k: v for k, v in kwargs.items() if k not in ['protocol_key', 'auto_encode', 'listen_topics', 'forward_topics']} logger.info(f"批量注册设备: {len(device_ids)}个 {device_type.value} 设备,指令类型: {command_type}") # 为每个设备创建单独的处理器注册(每个设备有独立的监听和转发topics) registered_device_ids = [] for device_id in device_ids: # 为当前设备生成独立的监听topics(只监听自己对应的topic) device_listen_topics = self.registry.topic_manager.generate_listen_topics( [device_id], brand_name, [command_type], device_type ) # 为当前设备生成独立的转发topics(只转发到自己对应的topic) device_forward_topics = self.registry.topic_manager.generate_forward_topics( [device_id], brand_name, [command_type], device_type ) logger.info(f"设备 {device_id} - 监听topics: {device_listen_topics}") logger.info(f"设备 {device_id} - 转发topics: {device_forward_topics}") # 创建配置 config = DeviceHandlerConfig( device_id=device_id, device_type=device_type, listen_topics=device_listen_topics, # 监听topics必须独立 forward_topics=device_forward_topics, # 转发topics必须独立 handler_function=handler.__name__, script_id=script_id, description=description or f"{final_device_brand.value} {device_type.value} 设备处理器", device_brand=final_device_brand, protocol_type=protocol_type, **filtered_kwargs ) self.registry.register_device_handler(config, handler) registered_device_ids.append(device_id) logger.info(f"批量注册完成: {len(registered_device_ids)}个设备已注册") return registered_device_ids def register_default_vehicle_handler(self, vehicle_ids: List[str], brand_name: str, handler: Callable, script_id: str, description: str = "") -> List[str]: """注册默认的小车处理器(最简化接口) Args: vehicle_ids: 监听的小车ID列表 brand_name: 小车品牌(huarui或seer) handler: 消息处理器函数 script_id: 脚本ID description: 描述信息 Returns: 注册的设备ID列表 """ return self.register_and_run( device_ids=vehicle_ids, device_type=DeviceType.VEHICLE, brand_name=brand_name, handler=handler, script_id=script_id, description=description or f"默认{brand_name}小车处理器,监听{len(vehicle_ids)}辆车" ) 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) def get_system_listening_status(self) -> Dict[str, Any]: """获取系统级监听状态""" return self.registry.get_system_listening_status() 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) @staticmethod def create_default_forward_handler(): """创建默认的透传处理器""" def default_handler(message: DeviceMessage) -> Dict[str, Any]: """默认消息处理器:直接透传所有消息""" logger.info(f"默认处理器收到消息: device_id={message.device_id}, " f"topic={message.topic}, payload大小={len(str(message.payload))}") # 透传所有消息到转发topics return { "forward": True, "payload": message.payload, "topics": message.target_topics } return default_handler # 全局设备处理服务实例 _global_device_service = DeviceHandlerService() def get_device_service() -> DeviceHandlerService: """获取全局设备处理服务实例""" return _global_device_service