VWED_server/services/online_script/script_vwed_objects.py

868 lines
31 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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)