2025-09-25 10:52:52 +08:00
|
|
|
|
#!/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 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]
|
2025-10-01 15:20:55 +08:00
|
|
|
|
|