326 lines
13 KiB
Python
326 lines
13 KiB
Python
|
#!/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
|