using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; using APS7100TestTool.Controllers; using APS7100TestTool.Models; using NModbus; namespace APS7100TestTool.Services { public class ModbusTcpServerService : IDisposable { private TcpListener _tcpListener; private IModbusSlaveNetwork _modbusNetwork; private IModbusSlave _slave; private IPowerSupplyController _controllerAPS7100; // APS7100 控制器 private IPowerSupplyController _controllerPSW250; // PSW250 控制器 private List _mappings; private CancellationTokenSource _cts; private Task _syncTask; // 缓存上一次的写入值,用于检测变化(使用数值比较避免字符串格式不一致问题) private Dictionary _lastWriteValues = new Dictionary(); // 线程安全锁 private readonly object _controllerLock = new object(); private readonly object _dataStoreLock = new object(); private volatile bool _isStopping = false; // 暂停轮询标志(用于保持设备在本地模式) private volatile bool _isPollingSuspended = false; // 心跳相关 private ushort _heartbeatCounter = 0; private ushort _heartbeatRegisterAddress = 1; // 心跳寄存器地址(默认1,避免地址0的兼容性问题) private ushort _deviceStatusRegisterAddress = 2; // 设备状态寄存器地址 // 设备连接状态 (由外部更新) private bool _isAPS7100Connected = false; private bool _isPSW250Connected = false; // 客户端连接跟踪 private readonly object _clientLock = new object(); private HashSet _connectedClients = new HashSet(); private int _totalRequests = 0; private DateTime _lastRequestTime = DateTime.MinValue; private Task _connectionMonitorTask; public event Action OnLog; public event Action OnClientStatusChanged; // (客户端数, 请求数, 最后请求时间) public bool IsRunning { get; private set; } public int ConnectedClientCount => _connectedClients.Count; public int TotalRequests => _totalRequests; public DateTime LastRequestTime => _lastRequestTime; /// /// 仿真模式:当设备未连接时,也会生成并记录命令(不实际发送) /// public bool SimulationMode { get; set; } = false; /// /// 轮询是否已暂停 /// 暂停轮询后,Modbus 服务将不再向设备发送 SCPI 命令 /// 用于保持设备在本地模式 /// public bool IsPollingSuspended => _isPollingSuspended; /// /// 暂停设备轮询 /// 暂停后 Modbus 服务不再向设备发送 SCPI 命令,设备可保持本地模式 /// public void SuspendPolling() { _isPollingSuspended = true; Log("⚠ 设备轮询已暂停 - 设备将保持当前模式"); } /// /// 恢复设备轮询 /// 恢复后 Modbus 服务将继续向设备发送 SCPI 命令 /// ⚠ 注意:这会使 APS7100 自动切换到远程模式 /// public void ResumePolling() { _isPollingSuspended = false; Log("✓ 设备轮询已恢复"); } /// /// 当前心跳计数值 /// public ushort HeartbeatValue => _heartbeatCounter; /// /// 当前设备状态 (Bit0=APS7100, Bit1=PSW250) /// public (bool APS7100, bool PSW250) DeviceStatus => (_isAPS7100Connected, _isPSW250Connected); public ModbusTcpServerService(IPowerSupplyController controllerAPS7100, IPowerSupplyController controllerPSW250) { _controllerAPS7100 = controllerAPS7100; _controllerPSW250 = controllerPSW250; } /// /// 设置心跳寄存器地址 /// public void SetHeartbeatAddresses(ushort heartbeatAddress, ushort deviceStatusAddress) { _heartbeatRegisterAddress = heartbeatAddress; _deviceStatusRegisterAddress = deviceStatusAddress; } /// /// 更新设备连接状态 (由主窗体调用) /// public void UpdateDeviceStatus(bool aps7100Connected, bool psw250Connected) { _isAPS7100Connected = aps7100Connected; _isPSW250Connected = psw250Connected; } public void UpdateControllers(IPowerSupplyController controllerAPS7100, IPowerSupplyController controllerPSW250) { lock (_controllerLock) { _controllerAPS7100 = controllerAPS7100; _controllerPSW250 = controllerPSW250; } } /// /// 根据设备目标获取对应的控制器 /// private IPowerSupplyController GetController(string deviceTarget) { if (string.IsNullOrEmpty(deviceTarget) || deviceTarget.Equals("Universal", StringComparison.OrdinalIgnoreCase)) { // Universal: 优先使用 APS7100,如果没有则用 PSW250 return _controllerAPS7100 ?? _controllerPSW250; } if (deviceTarget.Equals("APS7100", StringComparison.OrdinalIgnoreCase)) { return _controllerAPS7100; } if (deviceTarget.Equals("PSW250", StringComparison.OrdinalIgnoreCase)) { return _controllerPSW250; } return null; } /// /// 启动 Modbus TCP 服务器(绑定所有接口) /// public void Start(int port, List mappings) { Start("0.0.0.0", port, mappings); } /// /// 启动 Modbus TCP 服务器(绑定指定IP) /// /// 绑定的IP地址,0.0.0.0表示所有接口 /// 端口号 /// 寄存器映射配置 public void Start(string bindIpAddress, int port, List mappings) { if (IsRunning) return; _mappings = mappings; try { // 解析绑定IP地址 IPAddress bindAddress; if (string.IsNullOrEmpty(bindIpAddress) || bindIpAddress == "0.0.0.0") { bindAddress = IPAddress.Any; } else { if (!IPAddress.TryParse(bindIpAddress, out bindAddress)) { throw new Exception($"无效的IP地址: {bindIpAddress}"); } } _tcpListener = new TcpListener(bindAddress, port); _tcpListener.Start(); var factory = new ModbusFactory(); _modbusNetwork = factory.CreateSlaveNetwork(_tcpListener); _slave = factory.CreateSlave(1); // Slave ID 1 _modbusNetwork.AddSlave(_slave); _modbusNetwork.ListenAsync(); _cts = new CancellationTokenSource(); _syncTask = Task.Run(() => SyncLoop(_cts.Token)); // 启动连接监控任务 _connectionMonitorTask = Task.Run(() => MonitorConnections(_cts.Token)); IsRunning = true; _totalRequests = 0; _lastRequestTime = DateTime.MinValue; lock (_clientLock) { _connectedClients.Clear(); } string bindInfo = bindAddress.Equals(IPAddress.Any) ? "所有接口" : bindIpAddress; Log($"Modbus TCP 服务器已启动,绑定: {bindInfo}:{port}"); } catch (Exception ex) { IsRunning = false; throw new Exception($"启动 Modbus 服务失败: {ex.Message}", ex); } } /// /// 监控客户端连接 /// private async Task MonitorConnections(CancellationToken token) { int lastClientCount = 0; int lastRequestCount = 0; while (!token.IsCancellationRequested && !_isStopping) { try { // 检查活动的 TCP 连接 var activeConnections = GetActiveTcpConnections(); lock (_clientLock) { _connectedClients = activeConnections; } int currentClientCount = _connectedClients.Count; int currentRequestCount = _totalRequests; // 如果有变化,触发事件 if (currentClientCount != lastClientCount || currentRequestCount != lastRequestCount) { lastClientCount = currentClientCount; lastRequestCount = currentRequestCount; OnClientStatusChanged?.Invoke(currentClientCount, currentRequestCount, _lastRequestTime); } await Task.Delay(500, token); } catch (TaskCanceledException) { break; } catch { } } } /// /// 获取当前活动的 TCP 连接 /// private HashSet GetActiveTcpConnections() { var connections = new HashSet(); try { var properties = System.Net.NetworkInformation.IPGlobalProperties.GetIPGlobalProperties(); var tcpConnections = properties.GetActiveTcpConnections(); var localEndpoint = _tcpListener?.LocalEndpoint as IPEndPoint; if (localEndpoint != null) { foreach (var conn in tcpConnections) { // 检查是否是连接到我们服务器端口的客户端 if (conn.LocalEndPoint.Port == localEndpoint.Port && conn.State == System.Net.NetworkInformation.TcpState.Established) { connections.Add(conn.RemoteEndPoint.ToString()); } } } } catch { } return connections; } /// /// 记录请求(供外部调用或内部调用) /// public void RecordRequest() { Interlocked.Increment(ref _totalRequests); _lastRequestTime = DateTime.Now; } public void Stop() { if (!IsRunning) return; _isStopping = true; _cts?.Cancel(); // 等待同步任务结束 try { if (_syncTask != null && !_syncTask.IsCompleted) { _syncTask.Wait(2000); // 最多等待 2 秒 } } catch { /* 忽略取消异常 */ } try { _tcpListener?.Stop(); } catch { } try { _modbusNetwork?.Dispose(); } catch { } IsRunning = false; _isStopping = false; Log("Modbus TCP 服务器已停止"); } private async Task SyncLoop(CancellationToken token) { while (!token.IsCancellationRequested && !_isStopping) { // 更新心跳和设备状态(这些不涉及设备通讯,始终执行) UpdateHeartbeat(); UpdateDeviceStatusRegister(); // 如果轮询已暂停,跳过设备通讯操作 // 这样可以保持设备在本地模式 if (!_isPollingSuspended) { try { // 处理写入命令(根据配置的设备目标分发到对应设备) ProcessWriteCommands(); // 处理读取状态(根据配置的设备目标从对应设备读取) ProcessReadStatus(); } catch (Exception ex) { Log($"同步错误: {ex.Message}"); } } try { await Task.Delay(200, token); } catch (TaskCanceledException) { break; } } } /// /// 更新心跳寄存器 (每次调用+1,溢出后从0重新开始) /// PLC可以通过监测此值是否变化来判断APP是否正常运行 /// private void UpdateHeartbeat() { lock (_dataStoreLock) { if (_slave == null) return; try { _heartbeatCounter++; var dataStore = _slave.DataStore; dataStore.HoldingRegisters.WritePoints(_heartbeatRegisterAddress, new[] { _heartbeatCounter }); } catch { } } } /// /// 更新设备状态寄存器 /// Bit 0: APS7100 连接状态 (1=已连接, 0=未连接) /// Bit 1: PSW250 连接状态 (1=已连接, 0=未连接) /// Bit 2-15: 预留 /// private void UpdateDeviceStatusRegister() { lock (_dataStoreLock) { if (_slave == null) return; try { ushort status = 0; if (_isAPS7100Connected) status |= 0x0001; // Bit 0 if (_isPSW250Connected) status |= 0x0002; // Bit 1 var dataStore = _slave.DataStore; dataStore.HoldingRegisters.WritePoints(_deviceStatusRegisterAddress, new[] { status }); } catch { } } } private void ProcessWriteCommands() { if (_mappings == null) return; // 浮点数比较的容差值(用于判断值是否变化、是否为零、是否匹配触发值) const double VALUE_TOLERANCE = 0.0001; var writeMappings = _mappings.Where(m => m.GetOpType() == ModbusOperationType.WriteCommand).ToList(); // 按地址分组,先读取所有地址的当前值 var addressGroups = writeMappings.GroupBy(m => m.Address); foreach (var group in addressGroups) { if (_isStopping) return; int address = group.Key; string currentValueStr = ReadFromDataStore(group.First()); double currentValue = 0; try { currentValue = double.Parse(currentValueStr); } catch { continue; } // 检测该地址的值是否变化(使用数值比较,避免字符串格式不一致问题) bool addressValueChanged = false; if (!_lastWriteValues.ContainsKey(address)) { _lastWriteValues[address] = currentValue; // 首次运行也检测触发(如果当前值正好等于某个触发值) addressValueChanged = true; } else if (Math.Abs(_lastWriteValues[address] - currentValue) > VALUE_TOLERANCE) { // 数值变化超过容差,认为值变化了 addressValueChanged = true; _lastWriteValues[address] = currentValue; } if (!addressValueChanged) continue; // 值变化了,检查该地址下的所有配置,找到匹配的触发值 // 特殊处理:当值变为0时,将所有配置了确认地址的映射的确认地址也设置为0(复位) if (Math.Abs(currentValue) < VALUE_TOLERANCE) { foreach (var map in group) { if (map.ResponseAddress.HasValue && map.ResponseAddress.Value > 0) { WriteResponseValue(map, 0); Log($"[复位] 地址:{address} 值为0 -> 确认地址:{map.ResponseAddress.Value} <- 0"); } } continue; // 值为0时不执行任何命令,仅复位确认地址 } foreach (var map in group) { if (_isStopping) return; // 根据配置的设备目标获取对应的控制器 var controller = GetController(map.DeviceTarget); bool isSimulation = (controller == null && SimulationMode); // 非仿真模式下,设备未连接则跳过 if (controller == null && !SimulationMode) continue; // 检查 TriggerValue 是否匹配 if (map.TriggerValue.HasValue) { // 触发模式:只有当前值等于触发值时才执行 if (Math.Abs(currentValue - map.TriggerValue.Value) > VALUE_TOLERANCE) { continue; // 不匹配,跳过 } } // 没有 TriggerValue 的是数值同步模式,任何变化都执行 try { string cmd; if (map.TriggerValue.HasValue) { // Trigger 模式 if (!map.ScpiCommand.Contains("{0}")) { cmd = map.ScpiCommand; } else { // 命令包含 {0},需要从数据地址读取值 double commandValue; if (map.DataAddress.HasValue && map.DataAddress.Value > 0) { var dataAddressType = map.GetDataAddressType(); var dummyMap = new ModbusRegisterMapping { Address = map.DataAddress.Value, DataType = dataAddressType.ToString(), }; string dataValStr = ReadFromDataStore(dummyMap); if (double.TryParse(dataValStr, out double dVal)) { commandValue = dVal; } else { commandValue = 0; } } else { commandValue = currentValue; } double physicalValue = commandValue * map.ScaleFactor; cmd = string.Format(CultureInfo.InvariantCulture, map.ScpiCommand, physicalValue); } } else { // 数值同步模式 double physicalValue = currentValue * map.ScaleFactor; cmd = string.Format(CultureInfo.InvariantCulture, map.ScpiCommand, physicalValue); } // 仿真模式:只记录命令,不实际发送 if (isSimulation) { RecordRequest(); Log($"[仿真->{map.DeviceTarget}] 地址:{map.Address} 触发值:{map.TriggerValue?.ToString() ?? "无"} 当前值:{currentValue} -> 命令:{cmd}"); // 写入确认值(仿真模式也写入,方便测试) WriteResponseValue(map, (short)currentValue); } else { RecordRequest(); controller.SendCustomCommand(cmd); Log($"[Modbus->{map.DeviceTarget}] 地址:{map.Address} 触发值:{map.TriggerValue?.ToString() ?? "无"} 当前值:{currentValue} -> 发送:{cmd}"); // 执行成功,写入确认值(触发值) WriteResponseValue(map, (short)currentValue); } } catch (Exception ex) { Log($"执行命令失败 (Addr {map.Address}, {map.DeviceTarget}): {ex.Message}"); // 执行失败,写入 -1 WriteResponseValue(map, -1); } } } } private void ProcessReadStatus() { if (_mappings == null) return; var readMappings = _mappings.Where(m => m.GetOpType() == ModbusOperationType.ReadStatus); foreach (var map in readMappings) { // 检查是否正在停止 if (_isStopping) return; // 根据配置的设备目标获取对应的控制器 var controller = GetController(map.DeviceTarget); if (controller == null) continue; // 对应设备未连接,跳过 try { string response = controller.SendCustomQuery(map.ScpiCommand); if (double.TryParse(response, out double val)) { double regValueDouble = val / map.ScaleFactor; WriteToDataStore(map, regValueDouble); } } catch { // 忽略读取错误 } } } private string ReadFromDataStore(ModbusRegisterMapping map) { lock (_dataStoreLock) { if (_slave == null) return "0"; try { var dataStore = _slave.DataStore; ushort[] registers; if (map.GetDataType() == ModbusDataType.Float) { registers = dataStore.HoldingRegisters.ReadPoints((ushort)map.Address, 2); float f = GetFloatFromRegisters(registers); return f.ToString(); } else { registers = dataStore.HoldingRegisters.ReadPoints((ushort)map.Address, 1); if (map.GetDataType() == ModbusDataType.Int16) return ((short)registers[0]).ToString(); else return registers[0].ToString(); } } catch (Exception) { return "0"; } } } private void WriteToDataStore(ModbusRegisterMapping map, double value) { lock (_dataStoreLock) { if (_slave == null) return; try { var dataStore = _slave.DataStore; var collection = map.GetRegType() == ModbusRegisterType.Input ? dataStore.InputRegisters : dataStore.HoldingRegisters; if (map.GetDataType() == ModbusDataType.Float) { ushort[] regs = GetRegistersFromFloat((float)value); collection.WritePoints((ushort)map.Address, regs); } else { ushort val; if (map.GetDataType() == ModbusDataType.Int16) val = (ushort)((short)value); else val = (ushort)value; collection.WritePoints((ushort)map.Address, new[] { val }); } } catch (Exception) { // 忽略写入错误 } } } /// /// 写入命令执行确认值(只写一次) /// 成功:写入触发值;失败:写入 -1 /// private void WriteResponseValue(ModbusRegisterMapping map, short value) { if (!map.ResponseAddress.HasValue || map.ResponseAddress.Value <= 0) return; // 未配置确认地址,跳过 lock (_dataStoreLock) { if (_slave == null) return; try { var dataStore = _slave.DataStore; dataStore.HoldingRegisters.WritePoints((ushort)map.ResponseAddress.Value, new[] { (ushort)value }); if (value >= 0) Log($"[确认] 地址:{map.ResponseAddress.Value} <- {value} (成功)"); else Log($"[确认] 地址:{map.ResponseAddress.Value} <- {value} (失败)"); } catch (Exception ex) { Log($"写入确认值失败: {ex.Message}"); } } } private float GetFloatFromRegisters(ushort[] regs) { if (regs.Length < 2) return 0; byte[] bytes = new byte[4]; // 假设 AB CD 顺序 byte[] high = BitConverter.GetBytes(regs[0]); // AB ? No, Modbus register is 16-bit // 通常 Modbus Float: // Reg1: High Word, Reg2: Low Word (Big Endian Modbus) // 或者 Reg1: Low Word, Reg2: High Word (Little Endian Modbus) // 这是一个常见的坑。这里我们使用最简单的 Little Endian Words 组合 Buffer.BlockCopy(regs, 0, bytes, 0, 4); return BitConverter.ToSingle(bytes, 0); } private ushort[] GetRegistersFromFloat(float value) { byte[] bytes = BitConverter.GetBytes(value); ushort[] regs = new ushort[2]; Buffer.BlockCopy(bytes, 0, regs, 0, 4); return regs; } private void Log(string msg) { OnLog?.Invoke($"[Modbus] {msg}"); } #region 公共读取寄存器方法 /// /// 读取指定地址的 Holding 寄存器值(单个 UInt16) /// public ushort? ReadHoldingRegister(ushort address) { lock (_dataStoreLock) { if (_slave == null) return null; try { var registers = _slave.DataStore.HoldingRegisters.ReadPoints(address, 1); return registers[0]; } catch { return null; } } } /// /// 读取指定地址的多个 Holding 寄存器值 /// public ushort[] ReadHoldingRegisters(ushort startAddress, ushort count) { lock (_dataStoreLock) { if (_slave == null) return null; try { return _slave.DataStore.HoldingRegisters.ReadPoints(startAddress, count); } catch { return null; } } } /// /// 读取指定地址的 Float 值(占用 2 个寄存器) /// public float? ReadHoldingFloat(ushort address) { lock (_dataStoreLock) { if (_slave == null) return null; try { var registers = _slave.DataStore.HoldingRegisters.ReadPoints(address, 2); return GetFloatFromRegisters(registers); } catch { return null; } } } /// /// 获取当前配置的映射列表 /// public List GetMappings() { return _mappings ?? new List(); } /// /// 读取所有配置的寄存器值,返回地址和值的字典 /// public Dictionary ReadAllConfiguredRegisters() { var result = new Dictionary(); if (_mappings == null || _slave == null) return result; lock (_dataStoreLock) { foreach (var map in _mappings) { try { string value = ""; if (map.GetDataType() == ModbusDataType.Float) { var registers = _slave.DataStore.HoldingRegisters.ReadPoints((ushort)map.Address, 2); float f = GetFloatFromRegisters(registers); value = f.ToString("F4"); } else { var registers = _slave.DataStore.HoldingRegisters.ReadPoints((ushort)map.Address, 1); if (map.GetDataType() == ModbusDataType.Int16) value = ((short)registers[0]).ToString(); else value = registers[0].ToString(); } result[map.Address] = (value, map.Description ?? "", map.DataType ?? "Int16"); } catch { result[map.Address] = ("读取失败", map.Description ?? "", map.DataType ?? "Int16"); } } } return result; } #endregion public void Dispose() { Stop(); } } }