#!/usr/bin/env python # -*- coding: utf-8 -*- """ Modbus设备操作处理器模块 提供与Modbus设备操作相关的各种处理器 """ import logging import json import asyncio import uuid from typing import Dict, Any, Optional from pymodbus.client import ModbusTcpClient from pymodbus.exceptions import ConnectionException, ModbusException from services.execution.task_context import TaskContext from .base import BlockHandler, register_handler from config.settings import settings from sqlalchemy import select from data.models.modbusconfig import VWEDModbusConfig from data.session import get_async_session from utils.logger import get_logger from .model.block_name import ModbusBlockName # 获取日志记录器 logger = get_logger("services.execution.handlers.modbus_equipment") # 模拟模式下的延迟时间(秒) MOCK_DELAY = 0.5 async def get_modbus_config(instance_name: str, address_number: Optional[int] = None) -> Optional[Dict[str, Any]]: """ 从数据库获取Modbus配置信息 Args: instance_name: Modbus实例名称 address_number: 地址编号(可选) Returns: Optional[Dict[str, Any]]: Modbus配置信息,如果未找到则返回None """ try: async with get_async_session() as session: # 构建查询条件 query = select(VWEDModbusConfig).where(VWEDModbusConfig.name == instance_name) # 如果指定了地址编号,添加条件 if address_number.strip() and address_number is not None : query = query.where(VWEDModbusConfig.address_number == address_number) # 执行查询 result = await session.execute(query) config = result.scalars().first() if not config: logger.warning(f"未找到Modbus配置: {instance_name}" + (f", 地址编号: {address_number}" if address_number is not None else "")) return None # 将ORM对象转换为字典 config_dict = { "id": config.id, "name": config.name, "ip": config.ip, "port": config.port, "slave_id": config.slave_id, "address_type": config.address_type, "address_number": config.address_number, "task_id": config.task_id, "target_value": config.target_value, "remark": config.remark, "status": config.status } return config_dict except Exception as e: logger.error(f"获取Modbus配置出错: {str(e)}") return None async def read_modbus_value(config: Dict[str, Any]) -> Dict[str, Any]: """ 读取Modbus值 Args: config: Modbus配置信息 Returns: Dict[str, Any]: 读取结果,包含success和value字段 """ # 检查是否启用测试模式 if settings.MODBUS_MOCK_MODE: logger.info(f"[测试模式] 模拟读取Modbus值: {config['name']}, 地址: {config['address_number']}") # 模拟读取延迟 await asyncio.sleep(MOCK_DELAY) # 构造测试模式下的响应 mock_value = (config.get('address_number', 0) + uuid.uuid4().int % 100) % 1000 return { "success": True, "value": mock_value, "message": f"[测试模式] 成功读取Modbus值: {mock_value}" } try: # 创建Modbus TCP客户端 client = ModbusTcpClient( host=config['ip'], port=config['port'], timeout=settings.MODBUS_TIMEOUT ) # 连接到Modbus服务器 if not client.connect(): return { "success": False, "message": f"无法连接到Modbus服务器: {config['ip']}:{config['port']}" } # 根据地址类型读取不同的寄存器 address_type = config['address_type'] address = config['address_number'] slave_id = config['slave_id'] value = None if address_type == 'coil': # 读取线圈状态 result = client.read_coils(address, 1, unit=slave_id) if not result.isError(): value = 1 if result.bits[0] else 0 elif address_type == 'discrete_input': # 读取离散输入 result = client.read_discrete_inputs(address, 1, unit=slave_id) if not result.isError(): value = 1 if result.bits[0] else 0 elif address_type == 'holding_register': # 读取保持寄存器 result = client.read_holding_registers(address, 1, unit=slave_id) if not result.isError(): value = result.registers[0] elif address_type == 'input_register': # 读取输入寄存器 result = client.read_input_registers(address, 1, unit=slave_id) if not result.isError(): value = result.registers[0] else: # 未知的地址类型 client.close() return { "success": False, "message": f"不支持的地址类型: {address_type}" } # 关闭连接 client.close() # 检查读取结果 if value is None: return { "success": False, "message": f"读取Modbus值失败: {result if 'result' in locals() else '未知错误'}" } return { "success": True, "value": value, "message": f"成功读取Modbus值: {value}" } except ConnectionException as e: logger.error(f"Modbus连接异常: {str(e)}") return { "success": False, "message": f"Modbus连接异常: {str(e)}" } except ModbusException as e: logger.error(f"Modbus操作异常: {str(e)}") return { "success": False, "message": f"Modbus操作异常: {str(e)}" } except Exception as e: logger.error(f"读取Modbus值异常: {str(e)}") return { "success": False, "message": f"读取Modbus值异常: {str(e)}" } async def write_modbus_value(config: Dict[str, Any], value: int) -> Dict[str, Any]: """ 写入Modbus值 Args: config: Modbus配置信息 value: 要写入的值 Returns: Dict[str, Any]: 写入结果,包含success字段 """ # 检查是否启用测试模式 if settings.MODBUS_MOCK_MODE: logger.info(f"[测试模式] 模拟写入Modbus值: {config['name']}, 地址: {config['address_number']}, 值: {value}") # 模拟写入延迟 await asyncio.sleep(MOCK_DELAY) # 构造测试模式下的响应 return { "success": True, "message": f"[测试模式] 成功写入Modbus值: {value}" } try: # 创建Modbus TCP客户端 client = ModbusTcpClient( host=config['ip'], port=config['port'], timeout=settings.MODBUS_TIMEOUT ) # 连接到Modbus服务器 if not client.connect(): return { "success": False, "message": f"无法连接到Modbus服务器: {config['ip']}:{config['port']}" } # 根据地址类型写入不同的寄存器 address_type = config['address_type'] address = config['address_number'] slave_id = config['slave_id'] result = None if address_type == 'coil': # 写入线圈状态 result = client.write_coil(address, value == 1, unit=slave_id) elif address_type == 'holding_register': # 写入保持寄存器 result = client.write_register(address, value, unit=slave_id) else: # 不支持写入离散输入和输入寄存器 client.close() return { "success": False, "message": f"不支持向地址类型 {address_type} 写入值" } # 关闭连接 client.close() # 检查写入结果 if result and not result.isError(): return { "success": True, "message": f"成功写入Modbus值: {value}" } else: return { "success": False, "message": f"写入Modbus值失败: {result if result else '未知错误'}" } except ConnectionException as e: logger.error(f"Modbus连接异常: {str(e)}") return { "success": False, "message": f"Modbus连接异常: {str(e)}" } except ModbusException as e: logger.error(f"Modbus操作异常: {str(e)}") return { "success": False, "message": f"Modbus操作异常: {str(e)}" } except Exception as e: logger.error(f"写入Modbus值异常: {str(e)}") return { "success": False, "message": f"写入Modbus值异常: {str(e)}" } class ModbusBlockHandler(BlockHandler): """Modbus设备操作处理器基类""" async def _get_modbus_config(self, instance_name: str, address_number: Optional[int] = None) -> Optional[Dict[str, Any]]: """获取Modbus配置信息""" return await get_modbus_config(instance_name, address_number) async def _read_modbus_value(self, config: Dict[str, Any]) -> Dict[str, Any]: """读取Modbus值""" return await read_modbus_value(config) async def _write_modbus_value(self, config: Dict[str, Any], value: int) -> Dict[str, Any]: """写入Modbus值""" return await write_modbus_value(config, value) # 通用读取Modbus值处理器 @register_handler(ModbusBlockName.COMMON_READ_NAME) class ModbusCommonReadNameBlockHandler(ModbusBlockHandler): """通用读取Modbus值(Name)处理器""" async def execute( self, block: Dict[str, Any], input_params: Dict[str, Any], context: TaskContext ) -> Dict[str, Any]: """执行通用读取Modbus值操作""" try: # 获取关键参数 instance_name = input_params.get("instanceName") address = input_params.get("address") remark = input_params.get("remark", "") # 参数检查 if not instance_name: result = { "success": False, "message": "Modbus实例名称不能为空" } await self._record_task_log(block, result, context) return result # 获取Modbus配置信息 config = await self._get_modbus_config(instance_name, address) if not config: result = { "success": False, "message": f"未找到Modbus配置: {instance_name}" + (f", 地址编号: {address}" if address is not None else "") } await self._record_task_log(block, result, context) return result # 重试标志 retry = True retry_count = 0 while retry: # 读取Modbus值 read_result = await self._read_modbus_value(config) if read_result.get("success", False): # 读取成功,设置上下文变量 modbus_value = read_result.get("value") context.set_variable("modbusValue", str(modbus_value)) context.set_block_output(block.get("name"), {"modbusValue": str(modbus_value)}) # 构建成功消息 remark_info = f", 说明: {remark}" if remark else "" address_info = f", 地址: {config['address_number']}" if 'address_number' in config else "" result = { "success": True, "data": { "modbusValue": str(modbus_value) }, "message": f"成功读取Modbus值: 实例: {instance_name}{address_info}, 值: {modbus_value}{remark_info}" } await self._record_task_log(block, result, context) return result else: # 读取失败,重试 retry_count += 1 logger.warning(f"读取Modbus值失败,准备第 {retry_count} 次重试: {read_result.get('message')}") # 等待一段时间再重试 await asyncio.sleep(settings.MODBUS_RETRY_INTERVAL) except Exception as e: result = { "success": False, "message": f"执行通用读取Modbus值异常: {str(e)}" } await self._record_task_log(block, result, context) return result # 通用写入Modbus值处理器 @register_handler(ModbusBlockName.COMMON_WRITE_NAME) class ModbusCommonWriteNameBlockHandler(ModbusBlockHandler): """通用写入Modbus值(Name)处理器""" async def execute( self, block: Dict[str, Any], input_params: Dict[str, Any], context: TaskContext ) -> Dict[str, Any]: """执行通用写入Modbus值操作""" try: # 获取关键参数 instance_name = input_params.get("instanceName") new_value = input_params.get("newValue") address = input_params.get("address") remark = input_params.get("remark", "") # 参数检查 if not instance_name: result = { "success": False, "message": "Modbus实例名称不能为空" } await self._record_task_log(block, result, context) return result if new_value is None: result = { "success": False, "message": "写入值不能为空" } await self._record_task_log(block, result, context) return result # 确保写入值是整数类型 try: new_value = int(new_value) except (ValueError, TypeError): result = { "success": False, "message": f"写入值必须是整数类型,当前值: {new_value}" } await self._record_task_log(block, result, context) return result # 获取Modbus配置信息 config = await self._get_modbus_config(instance_name, address) if not config: result = { "success": False, "message": f"未找到Modbus配置: {instance_name}" + (f", 地址编号: {address}" if address is not None else "") } await self._record_task_log(block, result, context) return result # 重试标志 retry = True retry_count = 0 while retry: # 写入Modbus值 write_result = await self._write_modbus_value(config, new_value) if write_result.get("success", False): # 写入成功 # 构建成功消息 remark_info = f", 说明: {remark}" if remark else "" address_info = f", 地址: {config['address_number']}" if 'address_number' in config else "" result = { "success": True, "message": f"成功写入Modbus值: 实例: {instance_name}{address_info}, 值: {new_value}{remark_info}" } await self._record_task_log(block, result, context) return result else: # 写入失败,重试 retry_count += 1 logger.warning(f"写入Modbus值失败,准备第 {retry_count} 次重试: {write_result.get('message')}") # 等待一段时间再重试 await asyncio.sleep(settings.MODBUS_RETRY_INTERVAL) except Exception as e: result = { "success": False, "message": f"执行通用写入Modbus值异常: {str(e)}" } await self._record_task_log(block, result, context) return result # 通用等待Modbus值处理器 @register_handler(ModbusBlockName.COMMON_WAIT_NAME) class ModbusCommonWaitNameBlockHandler(ModbusBlockHandler): """通用等待Modbus值(Name)处理器""" async def execute( self, block: Dict[str, Any], input_params: Dict[str, Any], context: TaskContext ) -> Dict[str, Any]: """执行通用等待Modbus值操作""" try: # 获取关键参数 instance_name = input_params.get("instanceName") target_value = input_params.get("targetValue") address = input_params.get("address") remark = input_params.get("remark", "") # 参数检查 if not instance_name: result = { "success": False, "message": "Modbus实例名称不能为空" } await self._record_task_log(block, result, context) return result if target_value is None: result = { "success": False, "message": "目标值不能为空" } await self._record_task_log(block, result, context) return result # 确保目标值是整数类型 try: target_value = int(target_value) except (ValueError, TypeError): result = { "success": False, "message": f"目标值必须是整数类型,当前值: {target_value}" } await self._record_task_log(block, result, context) return result # 获取Modbus配置信息 config = await self._get_modbus_config(instance_name, address) if not config: result = { "success": False, "message": f"未找到Modbus配置: {instance_name}" + (f", 地址编号: {address}" if address is not None else "") } await self._record_task_log(block, result, context) return result # 轮询标志 polling = True poll_count = 0 # 记录开始等待的日志 start_log = { "success": True, "message": f"开始等待Modbus值达到目标值: 实例: {instance_name}, 目标值: {target_value}" } await self._record_task_log(block, start_log, context) while polling: # 读取Modbus值 read_result = await self._read_modbus_value(config) if read_result.get("success", False): # 读取成功,检查值是否达到目标 current_value = read_result.get("value") if int(current_value) == target_value: # 达到目标值 remark_info = f", 说明: {remark}" if remark else "" address_info = f", 地址: {config['address_number']}" if 'address_number' in config else "" result = { "success": True, "message": f"Modbus值已达到目标值: 实例: {instance_name}{address_info}, 值: {current_value}{remark_info}" } await self._record_task_log(block, result, context) return result else: # 未达到目标值,继续轮询 poll_count += 1 if poll_count % 10 == 0: # 每10次轮询记录一次日志 logger.info(f"等待Modbus值,当前值: {current_value}, 目标值: {target_value}, 已轮询 {poll_count} 次") # 等待一段时间再检查 await asyncio.sleep(settings.MODBUS_POLL_INTERVAL) else: # 读取失败,记录错误但继续轮询 poll_count += 1 logger.warning(f"读取Modbus值失败,继续轮询第 {poll_count} 次: {read_result.get('message')}") # 等待一段时间再重试 await asyncio.sleep(settings.MODBUS_RETRY_INTERVAL) except Exception as e: result = { "success": False, "message": f"执行通用等待Modbus值异常: {str(e)}" } await self._record_task_log(block, result, context) return result