#!/usr/bin/env python # -*- coding: utf-8 -*- """ Modbus TCP 模拟服务器 用于测试在线脚本中的Modbus通信功能 """ import time import threading from pymodbus.server.startstop import StartTcpServer from pymodbus.device import ModbusDeviceIdentification from pymodbus.datastore import ModbusSequentialDataBlock, ModbusSlaveContext, ModbusServerContext from pymodbus.datastore.context import ModbusServerContext import logging # 配置日志 logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) class ModbusTestServer: """Modbus TCP 测试服务器""" def __init__(self, host='localhost', port=5020): self.host = host self.port = port self.server_thread = None self.running = False # 创建数据存储 # 参数:(起始地址, 初始值列表) # 线圈 (0x区域) - 可读写的离散值 coils = ModbusSequentialDataBlock(0, [False] * 100) # 离散输入 (1x区域) - 只读的离散值 discrete_inputs = ModbusSequentialDataBlock(0, [True, False, True, False] * 25) # 输入寄存器 (3x区域) - 只读的16位值 input_registers = ModbusSequentialDataBlock(0, [100, 200, 300, 400] * 25) # 保持寄存器 (4x区域) - 可读写的16位值 holding_registers = ModbusSequentialDataBlock(0, [1000, 2000, 3000, 4000] * 25) # 创建从站上下文 slave_context = ModbusSlaveContext( di=discrete_inputs, # 离散输入 co=coils, # 线圈 hr=holding_registers, # 保持寄存器 ir=input_registers # 输入寄存器 ) # 创建服务器上下文(支持从站ID 1-10) self.context = ModbusServerContext(slaves={ 1: slave_context, # 从站ID 1 2: slave_context, # 从站ID 2 (共享同一个上下文,简化测试) }, single=False) # 设备信息 self.identity = ModbusDeviceIdentification() self.identity.VendorName = 'VWED Test' self.identity.ProductCode = 'VWED-MODBUS-SIM' self.identity.VendorUrl = 'https://github.com/vwed' self.identity.ProductName = 'VWED Modbus TCP Simulator' self.identity.ModelName = 'Test Server v1.0' self.identity.MajorMinorRevision = '1.0.0' # 启动数据更新线程 self.update_thread = None def start_server(self): """启动Modbus服务器""" if self.running: logger.warning("服务器已经在运行") return logger.info(f"启动Modbus TCP服务器: {self.host}:{self.port}") self.running = True # 启动数据更新线程 self.update_thread = threading.Thread(target=self._update_data_loop, daemon=True) self.update_thread.start() # 启动服务器(在单独线程中) self.server_thread = threading.Thread( target=self._run_server, daemon=True ) self.server_thread.start() logger.info("Modbus TCP服务器启动成功") def _run_server(self): """运行服务器""" try: StartTcpServer( context=self.context, identity=self.identity, address=(self.host, self.port) ) except Exception as e: logger.error(f"服务器运行错误: {e}") self.running = False def _update_data_loop(self): """更新测试数据的循环""" counter = 0 while self.running: try: # 更新输入寄存器数据(模拟传感器读数) for slave_id in [1, 2]: slave_context = self.context[slave_id] # 更新输入寄存器(模拟变化的传感器数据) new_values = [ 100 + counter % 100, # 地址0: 100-199循环 200 + (counter * 2) % 200, # 地址1: 200-399循环 300 + (counter * 3) % 300, # 地址2: 300-599循环 400 + (counter * 4) % 400, # 地址3: 400-799循环 ] for i, value in enumerate(new_values): slave_context.setValues(3, i, [value]) # 3 = 输入寄存器 # 更新离散输入(模拟开关状态) discrete_values = [ counter % 2 == 0, # 地址0: 每秒切换 counter % 4 < 2, # 地址1: 每2秒切换 counter % 6 < 3, # 地址2: 每3秒切换 counter % 8 < 4, # 地址3: 每4秒切换 ] for i, value in enumerate(discrete_values): slave_context.setValues(2, i, [value]) # 2 = 离散输入 counter += 1 time.sleep(1) # 每秒更新一次 except Exception as e: logger.error(f"数据更新错误: {e}") time.sleep(1) def stop_server(self): """停止服务器""" logger.info("正在停止Modbus TCP服务器...") self.running = False def get_server_info(self): """获取服务器信息""" return { 'host': self.host, 'port': self.port, 'running': self.running, 'slaves': list(self.context.slaves()) if hasattr(self.context, 'slaves') and callable(self.context.slaves) else [1, 2], } def set_coil_value(self, slave_id, address, value): """设置线圈值(用于测试)""" try: slave_context = self.context[slave_id] slave_context.setValues(1, address, [bool(value)]) # 1 = 线圈 logger.info(f"设置线圈 slave={slave_id}, addr={address}, value={value}") except Exception as e: logger.error(f"设置线圈失败: {e}") def set_holding_register_value(self, slave_id, address, value): """设置保持寄存器值(用于测试)""" try: slave_context = self.context[slave_id] slave_context.setValues(4, address, [int(value)]) # 4 = 保持寄存器 logger.info(f"设置保持寄存器 slave={slave_id}, addr={address}, value={value}") except Exception as e: logger.error(f"设置保持寄存器失败: {e}") def get_all_values(self, slave_id=1): """获取所有值(用于调试)""" try: slave_context = self.context[slave_id] # 读取各种类型的数据 coils = slave_context.getValues(1, 0, 10) # 线圈 discrete = slave_context.getValues(2, 0, 10) # 离散输入 input_regs = slave_context.getValues(3, 0, 10) # 输入寄存器 holding_regs = slave_context.getValues(4, 0, 10) # 保持寄存器 return { 'coils': coils, 'discrete_inputs': discrete, 'input_registers': input_regs, 'holding_registers': holding_regs } except Exception as e: logger.error(f"获取数据失败: {e}") return None def main(): """主函数 - 启动测试服务器""" print("=" * 60) print("VWED Modbus TCP 测试服务器") print("=" * 60) # 创建并启动服务器 server = ModbusTestServer(host='localhost', port=5020) server.start_server() print(f"服务器信息: {server.get_server_info()}") print("\n测试数据布局:") print("- 线圈 (0x区域): 地址0-99, 初始值为False") print("- 离散输入 (1x区域): 地址0-99, 动态变化的True/False") print("- 输入寄存器 (3x区域): 地址0-99, 动态变化的数值") print("- 保持寄存器 (4x区域): 地址0-99, 初始值1000,2000,3000,4000...") print("\n支持的从站ID: 1, 2") print(f"\n连接地址: {server.host}:{server.port}") print("\n测试示例:") print("python -c \"") print("from services.online_script.built_in_modules.modbus_module import VWEDModbusModule") print("m = VWEDModbusModule('test')") print("print(m.read_holding_register('localhost', 5020, 1, 0, 2))") print("\"") try: while True: time.sleep(5) # 显示当前数据状态 values = server.get_all_values(1) if values: print(f"\n[{time.strftime('%H:%M:%S')}] 从站1当前数据:") print(f" 输入寄存器[0-3]: {values['input_registers'][:4]}") print(f" 离散输入[0-3]: {values['discrete_inputs'][:4]}") print(f" 保持寄存器[0-3]: {values['holding_registers'][:4]}") except KeyboardInterrupt: print("\n\n正在关闭服务器...") server.stop_server() print("服务器已关闭") if __name__ == "__main__": main()