868 lines
31 KiB
Python
868 lines
31 KiB
Python
#!/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) |