326 lines
13 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.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