#!/usr/bin/env python # -*- coding: utf-8 -*- """ VWED统一对象模块系统 提供脚本中可使用的VWED.api、VWED.function等统一接口 """ import asyncio from typing import Dict, Any, Optional, Callable, List, Union from .script_registry_service import get_global_registry from .script_websocket_service import get_websocket_manager from .device_handler_service import get_device_service, DeviceType, DeviceBrand, get_protocol_registry from utils.logger import get_logger logger = get_logger("services.script_vwed_objects") class VWEDApiModule: """VWED.api 模块 - API接口注册""" def __init__(self, script_id: str): self.script_id = script_id self.registry = get_global_registry() def register_route(self, path: str, method: str = "GET", handler: Callable = None, description: str = "", params: Dict = None, parameters: Dict = None, response_schema: Dict = None): """分离式API接口注册(用户要求的新格式) 使用方式: VWED.api.register_route( path="/calculate", method="POST", handler=calculate_handler, description="数学计算接口", params={"a": 0, "b": 0, "operation": "add"} # 简化的参数定义 ) """ # 验证HTTP方法 valid_methods = ["GET", "POST", "PUT", "DELETE"] if method.upper() not in valid_methods: raise ValueError(f"method参数错误:'{method}' 不是有效的HTTP请求方式。支持的方式有:{', '.join(valid_methods)}") if handler is None: raise ValueError("handler参数不能为空") self.registry.register_api_route( path=path, method=method.upper(), handler=handler, script_id=self.script_id, description=description, params=params, # 传递简化的参数格式 parameters=parameters, response_schema=response_schema ) return handler class VWEDFunctionModule: """VWED.function 模块 - 自定义函数注册""" def __init__(self, script_id: str): self.script_id = script_id self.registry = get_global_registry() def register(self, name: str, handler: Callable = None, description: str = "", params: Dict = None, parameters: List[Dict] = None, return_schema: Dict = None, tags: List[str] = None): """分离式自定义函数注册 使用方式: VWED.function.register( name="simple_add", handler=simple_add, description="简单加法函数", params={"a": 0, "b": 0} # 简化的参数定义 ) """ if handler is None: raise ValueError("handler参数不能为空") self.registry.register_function( name=name, handler=handler, script_id=self.script_id, description=description, params=params, # 传递简化的参数格式 parameters=parameters, return_schema=return_schema, tags=tags ) return handler class VWEDEventModule: """VWED.event 模块 - 事件系统""" def __init__(self, script_id: str): self.script_id = script_id self.registry = get_global_registry() def listen(self, event_name: str, handler: Callable, priority: int = 1): """注册事件监听器 分离式模式: VWED.event.listen( event_name="user_login", handler=on_user_login ) """ def decorator(func: Callable): self.registry.register_event_listener( event_name=event_name, handler=func, script_id=self.script_id, priority=priority ) return func return decorator(handler) async def emit(self, event_name: str, data: Dict[str, Any]): """触发事件""" await self.registry.emit_event(event_name, data) def on(self, event_name: str, priority: int = 1): """listen的别名""" return self.listen(event_name, priority=priority) class VWEDTimerModule: """VWED.timer 模块 - 定时任务""" def __init__(self, script_id: str): self.script_id = script_id self.registry = get_global_registry() def interval(self, seconds: int, handler: Callable, repeat: bool = True, delay: int = 0): """注册定时任务 分离式模式: VWED.timer.interval( seconds=60, handler=system_monitor ) """ def decorator(func: Callable): timer_id = f"{self.script_id}_{func.__name__}_{id(func)}" self.registry.register_timer( timer_id=timer_id, interval=seconds, handler=func, script_id=self.script_id, repeat=repeat, delay=delay ) return func # if handler is not None: # 分离式调用模式 return decorator(handler) # else: # # 装饰器模式 # return decorator def once(self, delay: int = 0, handler: Callable = None): """注册一次性定时任务 分离式模式: VWED.timer.once( delay=10, handler=initialization_task ) """ return self.interval(0, handler=handler, repeat=False, delay=delay) # def every(self, seconds: int): # """interval的别名""" # return self.interval(seconds) class VWEDLogModule: """VWED.log 模块 - 日志系统""" def __init__(self, script_id: str): self.script_id = script_id self.websocket_manager = get_websocket_manager() async def info(self, message: str, **kwargs): """输出信息日志""" await self.websocket_manager.broadcast_script_log( self.script_id, "INFO", message, **kwargs ) async def warning(self, message: str, **kwargs): """输出警告日志""" await self.websocket_manager.broadcast_script_log( self.script_id, "WARNING", message, **kwargs ) async def error(self, message: str, **kwargs): """输出错误日志""" await self.websocket_manager.broadcast_script_log( self.script_id, "ERROR", message, **kwargs ) async def debug(self, message: str, **kwargs): """输出调试日志""" await self.websocket_manager.broadcast_script_log( self.script_id, "DEBUG", message, **kwargs ) def sync_info(self, message: str, **kwargs): """同步方式输出信息日志""" asyncio.create_task(self.info(message, **kwargs)) def sync_warning(self, message: str, **kwargs): """同步方式输出警告日志""" asyncio.create_task(self.warning(message, **kwargs)) def sync_error(self, message: str, **kwargs): """同步方式输出错误日志""" asyncio.create_task(self.error(message, **kwargs)) def sync_debug(self, message: str, **kwargs): """同步方式输出调试日志""" asyncio.create_task(self.debug(message, **kwargs)) class VWEDTaskModule: """VWED.task 模块 - 任务系统集成""" def __init__(self, script_id: str): self.script_id = script_id self.logger = get_logger(f"script.{script_id}.task") async def create_task( self, label: str, task_type: int = 1, remark: Optional[str] = None, period: Optional[int] = None, delay: Optional[int] = 3000, release_sites: Optional[bool] = True, tenant_id: str = "default", map_id: Optional[str] = None, auto_get_token: bool = True ) -> Dict[str, Any]: """创建VWED任务 Args: label: 任务名称 task_type: 任务类型,1-普通任务,2-定时任务 remark: 任务备注 period: 周期时间(毫秒),定时任务必填 delay: 延迟时间(毫秒),默认3000 release_sites: 是否释放站点,默认true tenant_id: 租户ID,默认"default" map_id: 相关地图ID auto_get_token: 是否自动获取登录token,默认true Returns: Dict[str, Any]: 创建结果 """ try: from services.task_service import TaskService, TaskNameExistsError from data.session import get_db # 获取登录token token = None if auto_get_token: try: from services.sync_service import refresh_token_if_needed token = await refresh_token_if_needed() if token: self.logger.info(f"脚本 {self.script_id} 成功获取到登录token") else: self.logger.warning(f"脚本 {self.script_id} 获取登录token失败,继续使用空token") except Exception as e: self.logger.warning(f"脚本 {self.script_id} 获取token异常: {str(e)},继续使用空token") # 获取数据库会话 db = next(get_db()) try: # 调用TaskService.create_task方法 result = TaskService.create_task( db=db, label=label, task_type=task_type, remark=remark, period=period, delay=delay, release_sites=release_sites, token=token, # 使用获取到的token tenant_id=tenant_id, map_id=map_id ) self.logger.info(f"脚本 {self.script_id} 成功创建任务: {label} (ID: {result['id']})") return { "success": True, "message": "任务创建成功", "data": result } except TaskNameExistsError as e: self.logger.warning(f"脚本 {self.script_id} 创建任务失败: {str(e)}") return { "success": False, "message": f"任务名称已存在: {str(e)}", "error_type": "name_exists" } except Exception as e: self.logger.error(f"脚本 {self.script_id} 创建任务失败: {str(e)}") return { "success": False, "message": f"创建任务失败: {str(e)}", "error_type": "create_error" } finally: db.close() except Exception as e: self.logger.error(f"脚本 {self.script_id} 任务创建异常: {str(e)}") return { "success": False, "message": f"任务创建异常: {str(e)}", "error_type": "system_error" } async def execute_task( self, task_id: str, parameters: Optional[List[Dict[str, Any]]] = None, source_system: str = "script", source_device: Optional[str] = None, auto_get_token: bool = True ) -> Dict[str, Any]: """执行VWED任务 Args: task_id: 任务ID parameters: 任务输入参数列表 source_system: 来源系统,默认"script" source_device: 来源设备标识 auto_get_token: 是否自动获取登录token,默认true Returns: Dict[str, Any]: 执行结果 """ try: from services.task_edit_service import TaskEditService from routes.model.task_edit_model import TaskEditRunRequest, TaskInputParam from data.enum.task_record_enum import SourceType # 获取登录token token = None if auto_get_token: try: from services.sync_service import refresh_token_if_needed token = await refresh_token_if_needed() if token: self.logger.info(f"脚本 {self.script_id} 成功获取到登录token") else: self.logger.warning(f"脚本 {self.script_id} 获取登录token失败,继续使用空token") except Exception as e: self.logger.warning(f"脚本 {self.script_id} 获取token异常: {str(e)},继续使用空token") # 构建运行请求 input_params = [] if parameters: for param in parameters: if isinstance(param, dict) and "name" in param and "defaultValue" in param: input_params.append(TaskInputParam( name=param["name"], defaultValue=param["defaultValue"] )) run_request = TaskEditRunRequest( taskId=task_id, params=input_params, source_type=SourceType.SYSTEM_SCHEDULING, source_system=source_system, source_device=source_device or f"script_{self.script_id}" ) # 调用TaskEditService.run_task方法 result = await TaskEditService.run_task( run_request=run_request, client_ip="127.0.0.1", # 本地脚本调用 client_info=f"{{\"script_id\": \"{self.script_id}\", \"source\": \"VWED.task.execute_task\"}}", tf_api_token=token # 使用获取到的token ) if result and result.get("success"): self.logger.info(f"脚本 {self.script_id} 成功启动任务: {task_id}") return { "success": True, "message": "任务启动成功", "data": result } else: error_msg = result.get("message", "任务启动失败") if result else "任务启动失败" self.logger.error(f"脚本 {self.script_id} 启动任务失败: {error_msg}") return { "success": False, "message": error_msg, "error_type": "execute_error" } except Exception as e: self.logger.error(f"脚本 {self.script_id} 执行任务异常: {str(e)}") return { "success": False, "message": f"执行任务异常: {str(e)}", "error_type": "system_error" } async def get_task_status(self, task_record_id: str) -> Dict[str, Any]: """获取任务执行状态 Args: task_record_id: 任务记录ID(不是任务定义ID) Returns: Dict[str, Any]: 任务状态信息 """ try: from data.session import get_async_session from data.models.taskrecord import VWEDTaskRecord from sqlalchemy import select async with get_async_session() as session: result = await session.execute( select(VWEDTaskRecord).where(VWEDTaskRecord.id == task_record_id) ) task_record = result.scalar_one_or_none() if not task_record: return { "success": False, "message": "任务记录不存在", "error_type": "not_found" } # 构建状态信息 status_info = { "task_record_id": task_record.id, "task_def_id": task_record.task_def_id, "status": task_record.status, "progress": task_record.progress, "start_time": task_record.start_time.isoformat() if task_record.start_time else None, "end_time": task_record.end_time.isoformat() if task_record.end_time else None, "error_message": task_record.error_message, "variables": task_record.variables } self.logger.info(f"脚本 {self.script_id} 获取任务状态: {task_record_id}") return { "success": True, "message": "获取任务状态成功", "data": status_info } except Exception as e: self.logger.error(f"脚本 {self.script_id} 获取任务状态异常: {str(e)}") return { "success": False, "message": f"获取任务状态异常: {str(e)}", "error_type": "system_error" } class VWEDDataModule: """VWED.data 模块 - 数据存储和缓存""" def __init__(self, script_id: str): self.script_id = script_id self._storage: Dict[str, Any] = {} def get(self, key: str, default=None): """获取数据""" return self._storage.get(key, default) def set(self, key: str, value: Any): """设置数据""" self._storage[key] = value def delete(self, key: str): """删除数据""" if key in self._storage: del self._storage[key] def clear(self): """清空所有数据""" self._storage.clear() def keys(self): """获取所有键""" return list(self._storage.keys()) def has(self, key: str) -> bool: """检查键是否存在""" return key in self._storage class VWEDUtilModule: """VWED.util 模块 - 工具函数""" def __init__(self, script_id: str): self.script_id = script_id def sleep(self, seconds: float): """同步睡眠""" import time time.sleep(seconds) async def async_sleep(self, seconds: float): """异步睡眠""" await asyncio.sleep(seconds) def now(self) -> str: """获取当前时间""" from datetime import datetime return datetime.now().isoformat() def timestamp(self) -> float: """获取当前时间戳""" import time return time.time() def uuid(self) -> str: """生成UUID""" import uuid return str(uuid.uuid4()) 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_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): """注册并运行设备处理器 注册完成后立即: 1. 订阅指定的MQTT topics 2. 开始监听消息 3. 消息到达时自动调用处理函数 4. 自动转发处理结果 5. 支持设备品牌特定的协议编码/解码 使用方式: # 基础用法 VWED.device.register_and_run( device_id="agv_001", device_type="vehicle", listen_topics=["factory/agv/command"], forward_topics=["factory/agv/execute"], handler=agv_processor, description="AGV命令处理器" ) # 支持品牌特定协议 VWED.device.register_and_run( device_id="huarui_agv_001", device_type="vehicle", device_brand="huarui", # 华睿品牌 listen_topics=["huarui/agv/status"], forward_topics=["huarui/agv/command"], handler=huarui_agv_processor, auto_encode=True, # 自动进行华睿协议编码/解码 description="华睿AGV处理器" ) # 自定义协议 VWED.device.register_and_run( device_id="custom_device_001", device_type="custom", protocol_key="my_custom_protocol", # 使用预注册的自定义协议 listen_topics=["custom/device/data"], forward_topics=["custom/device/control"], handler=custom_device_processor, description="自定义设备处理器" ) Args: device_id: 设备唯一标识 device_type: 设备类型 (vehicle, door, caller, sensor等) listen_topics: 监听的MQTT主题列表 forward_topics: 转发的MQTT主题列表 handler: 消息处理函数 description: 设备描述信息 device_brand: 设备品牌 (huarui, seer, quicktron等) protocol_key: 自定义协议标识 auto_encode: 是否自动进行协议编码/解码 **kwargs: 其他配置参数 """ if handler is None: raise ValueError("handler参数不能为空") if not listen_topics: raise ValueError("listen_topics参数不能为空") # 确保device_type是字符串格式 if isinstance(device_type, DeviceType): device_type = device_type.value # 验证设备类型 valid_types = [dt.value for dt in DeviceType] if device_type not in valid_types: raise ValueError(f"device_type参数错误:'{device_type}' 不是有效的设备类型。支持的类型有:{', '.join(valid_types)}") # 设置默认转发topics if forward_topics is None: forward_topics = [] # 调用设备服务注册 self.device_service.register_and_run( device_id=device_id, device_type=device_type, listen_topics=listen_topics, forward_topics=forward_topics, handler=handler, script_id=self.script_id, description=description, device_brand=device_brand, protocol_key=protocol_key, auto_encode=auto_encode, **kwargs ) # 同时在注册中心记录 self.registry.register_device_handler( device_id=device_id, device_type=device_type, listen_topics=listen_topics, forward_topics=forward_topics, handler=handler, script_id=self.script_id, description=description, device_brand=device_brand, protocol_key=protocol_key, auto_encode=auto_encode, **kwargs ) brand_info = f" (品牌: {device_brand})" if device_brand else "" protocol_info = f" (协议: {protocol_key})" if protocol_key else "" self.logger.info(f"设备处理器已注册并运行: {device_id} ({device_type}{brand_info}{protocol_info})") return handler 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 class VWEDObject: """VWED统一对象 - 脚本中的全局VWED变量""" def __init__(self, script_id: str): self.script_id = script_id # 初始化各模块 self.api = VWEDApiModule(script_id) self.function = VWEDFunctionModule(script_id) self.event = VWEDEventModule(script_id) self.timer = VWEDTimerModule(script_id) self.log = VWEDLogModule(script_id) self.task = VWEDTaskModule(script_id) self.data = VWEDDataModule(script_id) self.util = VWEDUtilModule(script_id) self.device = VWEDDeviceModule(script_id) def get_script_id(self) -> str: """获取当前脚本ID""" return self.script_id def create_vwed_object(script_id: str) -> VWEDObject: """创建VWED对象实例""" return VWEDObject(script_id)