#!/usr/bin/env python # -*- coding: utf-8 -*- """ VWED.device 模块 - 设备处理器注册并运行 """ import asyncio from typing import Dict, Any, List, Union, Callable from ..device_handler_service import get_device_service, DeviceType, DeviceBrand, get_protocol_registry from ..script_registry_service import get_global_registry from utils.logger import get_logger class VWEDDeviceModule: """VWED.device 模块 - 设备处理器注册并运行""" def __init__(self, script_id: str): self.script_id = script_id self.device_service = get_device_service() self.registry = get_global_registry() self.logger = get_logger(f"script.{script_id}.device") 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, description: str = "", device_brand: str = None, protocol_key: str = None, auto_encode: bool = True, **kwargs): """批量注册并运行设备处理器(新接口) 系统会自动: 1. 根据设备品牌和类型生成MQTT topics 2. 订阅相应的topics 3. 开始监听消息 4. 消息到达时自动调用处理函数 5. 自动转发处理结果 使用方式: # 批量注册华瑞小车,处理order指令 VWED.device.register_and_run( device_ids=["AGV001", "AGV002", "AGV003"], device_type="vehicle", brand_name="huarui", command_type="order", handler=agv_order_processor, description="华瑞小车order指令处理器" ) # 批量注册仙工小车,处理state指令 VWED.device.register_and_run( device_ids=["SEER001", "SEER002"], device_type="vehicle", brand_name="seer", command_type="state", handler=seer_state_processor, description="仙工小车state指令处理器" ) # 批量注册门设备,处理instantActions指令 VWED.device.register_and_run( device_ids=["DOOR001", "DOOR002"], device_type="door", brand_name="huarui", command_type="instantActions", handler=door_processor, description="门设备instantActions指令处理器" ) Args: device_ids: 设备ID列表(必需)- 支持批量注册 device_type: 设备类型(默认vehicle) brand_name: 设备品牌(huarui或seer,默认huarui) command_type: 指令类型(必需)- 只能处理一种指令类型,如"order"、"state"、"factsheet"、"instantActions" handler: 消息处理函数(必需) description: 设备描述信息 device_brand: 设备品牌(兼容参数,建议使用brand_name) protocol_key: 自定义协议标识(已弃用) auto_encode: 是否自动编码(已弃用) **kwargs: 其他配置参数 """ if handler is None: raise ValueError("handler参数不能为空") if not device_ids or len(device_ids) == 0: raise ValueError("device_ids参数不能为空") # 确保device_type是DeviceType枚举 if isinstance(device_type, str): try: device_type = DeviceType(device_type) except ValueError: valid_types = [dt.value for dt in DeviceType] raise ValueError(f"device_type参数错误:'{device_type}' 不是有效的设备类型。支持的类型有:{', '.join(valid_types)}") # 兼容性处理:如果提供了device_brand,使用它覆盖brand_name if device_brand is not None: brand_name = device_brand # 过滤已弃用的参数 filtered_kwargs = {k: v for k, v in kwargs.items() if k not in ['listen_topics', 'forward_topics', 'protocol_key', 'auto_encode']} # 调用新的设备服务批量注册接口 registered_device_ids = self.device_service.register_and_run( device_ids=device_ids, device_type=device_type, brand_name=brand_name, command_type=command_type, handler=handler, script_id=self.script_id, description=description, **filtered_kwargs ) brand_info = f" (品牌: {brand_name})" if brand_name else "" self.logger.info(f"设备处理器批量注册成功: {len(registered_device_ids)}个 {device_type.value} 设备{brand_info}") return registered_device_ids def register_single_device(self, device_id: str, device_type: Union[str, DeviceType], listen_topics: List[str], forward_topics: List[str] = None, handler: Callable = None, description: str = "", device_brand: str = None, protocol_key: str = None, auto_encode: bool = True, **kwargs): """兼容旧接口:注册单个设备处理器 这是为了兼容旧版本代码而保留的接口,建议使用新的register_and_run方法 """ if handler is None: raise ValueError("handler参数不能为空") if not listen_topics: raise ValueError("listen_topics参数不能为空") # 这里可以实现旧版本的直接topic注册逻辑 # 为了简化,我们可以发出警告并建议用户升级到新接口 self.logger.warning(f"使用了已弃用的register_single_device接口,建议升级到新的register_and_run接口") # 简单实现:调用新接口 return self.register_and_run( device_ids=[device_id], device_type=device_type, brand_name=device_brand or "huarui", handler=handler, description=description, **kwargs ) def stop_handler(self, device_id: str): """停止指定设备处理器的运行""" self.device_service.stop_handler(device_id) self.logger.info(f"设备处理器已停止: {device_id}") def restart_handler(self, device_id: str): """重启设备处理器""" # 获取设备配置 handler_info = self.registry.get_device_handler(device_id) if not handler_info: raise ValueError(f"设备处理器不存在: {device_id}") # 先停止 self.stop_handler(device_id) # 重新注册并运行 self.register_and_run( device_id=handler_info["device_id"], device_type=handler_info["device_type"], listen_topics=handler_info["listen_topics"], forward_topics=handler_info["forward_topics"], handler=handler_info["handler"], description=handler_info["description"] ) self.logger.info(f"设备处理器已重启: {device_id}") def get_running_handlers(self) -> Dict[str, Any]: """获取所有正在运行的设备处理器""" return self.device_service.get_running_handlers() def get_handler_status(self, device_id: str) -> Dict[str, Any]: """获取设备处理器状态""" return self.device_service.get_handler_status(device_id) async def publish_message(self, topic: str, payload: Any): """主动发布MQTT消息 使用方式: await VWED.device.publish_message( topic="factory/agv/command", payload={"action": "move", "x": 100, "y": 200} ) """ await self.device_service.publish_message(topic, payload) self.logger.info(f"MQTT消息已发布: {topic}") def sync_publish_message(self, topic: str, payload: Any): """同步方式发布MQTT消息""" asyncio.create_task(self.publish_message(topic, payload)) def subscribe_topic(self, topic: str, handler: Callable): """订阅额外的MQTT topic 注意:这是临时订阅,建议使用register_and_run进行完整的设备注册 """ # 创建临时设备ID temp_device_id = f"temp_{self.script_id}_{topic.replace('/', '_')}" self.register_and_run( device_id=temp_device_id, device_type="custom", listen_topics=[topic], forward_topics=[], handler=handler, description=f"临时订阅topic: {topic}" ) return temp_device_id def unsubscribe_topic(self, device_id: str): """取消订阅topic(通过停止设备处理器)""" self.stop_handler(device_id) def get_device_types(self) -> List[str]: """获取支持的设备类型列表""" return [dt.value for dt in DeviceType] def get_device_brands(self) -> List[str]: """获取支持的设备品牌列表""" return [db.value for db in DeviceBrand] def get_protocols(self) -> Dict[str, Dict[str, Any]]: """获取所有可用的协议""" protocol_registry = get_protocol_registry() return protocol_registry.list_protocols() def register_custom_protocol(self, protocol_key: str, brand: str, device_type: str, encode_func: Callable, decode_func: Callable, supported_commands: List[str] = None): """注册自定义设备协议 使用方式: def my_encode(command): # 自定义编码逻辑 return {"custom_format": command} def my_decode(response): # 自定义解码逻辑 return {"decoded": response} VWED.device.register_custom_protocol( protocol_key="my_brand_vehicle", brand="my_brand", device_type="vehicle", encode_func=my_encode, decode_func=my_decode, supported_commands=["move", "stop"] ) """ protocol_registry = get_protocol_registry() protocol_registry.register_custom_protocol( protocol_key, brand, device_type, encode_func, decode_func, supported_commands ) self.logger.info(f"自定义协议已注册: {protocol_key} ({brand}_{device_type})") def test_protocol_encoding(self, protocol_key: str, test_command: Dict[str, Any]) -> Dict[str, Any]: """测试协议编码功能 使用方式: result = VWED.device.test_protocol_encoding( protocol_key="huarui_vehicle", test_command={"action": "move", "x": 100, "y": 200} ) """ protocol_registry = get_protocol_registry() protocol = protocol_registry.get_protocol(protocol_key) if not protocol: return { "success": False, "error": f"协议不存在: {protocol_key}" } try: encoded = protocol.encode_command(test_command) return { "success": True, "original": test_command, "encoded": encoded, "protocol": protocol_key } except Exception as e: return { "success": False, "error": f"编码失败: {str(e)}", "protocol": protocol_key } def create_message_filter(self, conditions: Dict[str, Any]) -> Callable: """创建消息过滤器 使用方式: filter_func = VWED.device.create_message_filter({ "device_type": "vehicle", "action": "move" }) def my_handler(message): if filter_func(message): # 处理符合条件的消息 pass """ def filter_func(message): payload = message.payload if hasattr(message, 'payload') else message for key, expected_value in conditions.items(): if key == "device_type": actual_value = message.device_type.value if hasattr(message, 'device_type') else None else: actual_value = payload.get(key) if actual_value != expected_value: return False return True return filter_func