| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837 |
- 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<ModbusRegisterMapping> _mappings;
- private CancellationTokenSource _cts;
- private Task _syncTask;
-
- // 缓存上一次的写入值,用于检测变化(使用数值比较避免字符串格式不一致问题)
- private Dictionary<int, double> _lastWriteValues = new Dictionary<int, double>();
-
- // 线程安全锁
- 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<string> _connectedClients = new HashSet<string>();
- private int _totalRequests = 0;
- private DateTime _lastRequestTime = DateTime.MinValue;
- private Task _connectionMonitorTask;
-
- public event Action<string> OnLog;
- public event Action<int, int, DateTime> OnClientStatusChanged; // (客户端数, 请求数, 最后请求时间)
- public bool IsRunning { get; private set; }
- public int ConnectedClientCount => _connectedClients.Count;
- public int TotalRequests => _totalRequests;
- public DateTime LastRequestTime => _lastRequestTime;
-
- /// <summary>
- /// 仿真模式:当设备未连接时,也会生成并记录命令(不实际发送)
- /// </summary>
- public bool SimulationMode { get; set; } = false;
-
- /// <summary>
- /// 轮询是否已暂停
- /// 暂停轮询后,Modbus 服务将不再向设备发送 SCPI 命令
- /// 用于保持设备在本地模式
- /// </summary>
- public bool IsPollingSuspended => _isPollingSuspended;
-
- /// <summary>
- /// 暂停设备轮询
- /// 暂停后 Modbus 服务不再向设备发送 SCPI 命令,设备可保持本地模式
- /// </summary>
- public void SuspendPolling()
- {
- _isPollingSuspended = true;
- Log("⚠ 设备轮询已暂停 - 设备将保持当前模式");
- }
-
- /// <summary>
- /// 恢复设备轮询
- /// 恢复后 Modbus 服务将继续向设备发送 SCPI 命令
- /// ⚠ 注意:这会使 APS7100 自动切换到远程模式
- /// </summary>
- public void ResumePolling()
- {
- _isPollingSuspended = false;
- Log("✓ 设备轮询已恢复");
- }
-
- /// <summary>
- /// 当前心跳计数值
- /// </summary>
- public ushort HeartbeatValue => _heartbeatCounter;
-
- /// <summary>
- /// 当前设备状态 (Bit0=APS7100, Bit1=PSW250)
- /// </summary>
- public (bool APS7100, bool PSW250) DeviceStatus => (_isAPS7100Connected, _isPSW250Connected);
- public ModbusTcpServerService(IPowerSupplyController controllerAPS7100, IPowerSupplyController controllerPSW250)
- {
- _controllerAPS7100 = controllerAPS7100;
- _controllerPSW250 = controllerPSW250;
- }
- /// <summary>
- /// 设置心跳寄存器地址
- /// </summary>
- public void SetHeartbeatAddresses(ushort heartbeatAddress, ushort deviceStatusAddress)
- {
- _heartbeatRegisterAddress = heartbeatAddress;
- _deviceStatusRegisterAddress = deviceStatusAddress;
- }
- /// <summary>
- /// 更新设备连接状态 (由主窗体调用)
- /// </summary>
- public void UpdateDeviceStatus(bool aps7100Connected, bool psw250Connected)
- {
- _isAPS7100Connected = aps7100Connected;
- _isPSW250Connected = psw250Connected;
- }
- public void UpdateControllers(IPowerSupplyController controllerAPS7100, IPowerSupplyController controllerPSW250)
- {
- lock (_controllerLock)
- {
- _controllerAPS7100 = controllerAPS7100;
- _controllerPSW250 = controllerPSW250;
- }
- }
-
- /// <summary>
- /// 根据设备目标获取对应的控制器
- /// </summary>
- 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;
- }
- /// <summary>
- /// 启动 Modbus TCP 服务器(绑定所有接口)
- /// </summary>
- public void Start(int port, List<ModbusRegisterMapping> mappings)
- {
- Start("0.0.0.0", port, mappings);
- }
- /// <summary>
- /// 启动 Modbus TCP 服务器(绑定指定IP)
- /// </summary>
- /// <param name="bindIpAddress">绑定的IP地址,0.0.0.0表示所有接口</param>
- /// <param name="port">端口号</param>
- /// <param name="mappings">寄存器映射配置</param>
- public void Start(string bindIpAddress, int port, List<ModbusRegisterMapping> 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);
- }
- }
- /// <summary>
- /// 监控客户端连接
- /// </summary>
- 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 { }
- }
- }
- /// <summary>
- /// 获取当前活动的 TCP 连接
- /// </summary>
- private HashSet<string> GetActiveTcpConnections()
- {
- var connections = new HashSet<string>();
- 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;
- }
- /// <summary>
- /// 记录请求(供外部调用或内部调用)
- /// </summary>
- 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;
- }
- }
- }
- /// <summary>
- /// 更新心跳寄存器 (每次调用+1,溢出后从0重新开始)
- /// PLC可以通过监测此值是否变化来判断APP是否正常运行
- /// </summary>
- private void UpdateHeartbeat()
- {
- lock (_dataStoreLock)
- {
- if (_slave == null) return;
-
- try
- {
- _heartbeatCounter++;
- var dataStore = _slave.DataStore;
- dataStore.HoldingRegisters.WritePoints(_heartbeatRegisterAddress, new[] { _heartbeatCounter });
- }
- catch { }
- }
- }
- /// <summary>
- /// 更新设备状态寄存器
- /// Bit 0: APS7100 连接状态 (1=已连接, 0=未连接)
- /// Bit 1: PSW250 连接状态 (1=已连接, 0=未连接)
- /// Bit 2-15: 预留
- /// </summary>
- 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)
- {
- // 忽略写入错误
- }
- }
- }
- /// <summary>
- /// 写入命令执行确认值(只写一次)
- /// 成功:写入触发值;失败:写入 -1
- /// </summary>
- 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 公共读取寄存器方法
- /// <summary>
- /// 读取指定地址的 Holding 寄存器值(单个 UInt16)
- /// </summary>
- 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; }
- }
- }
- /// <summary>
- /// 读取指定地址的多个 Holding 寄存器值
- /// </summary>
- 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; }
- }
- }
- /// <summary>
- /// 读取指定地址的 Float 值(占用 2 个寄存器)
- /// </summary>
- 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; }
- }
- }
- /// <summary>
- /// 获取当前配置的映射列表
- /// </summary>
- public List<ModbusRegisterMapping> GetMappings()
- {
- return _mappings ?? new List<ModbusRegisterMapping>();
- }
- /// <summary>
- /// 读取所有配置的寄存器值,返回地址和值的字典
- /// </summary>
- public Dictionary<int, (string RawValue, string Description, string DataType)> ReadAllConfiguredRegisters()
- {
- var result = new Dictionary<int, (string RawValue, string Description, string DataType)>();
-
- 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();
- }
- }
- }
|