Files
petwash/PetWashControl/Services/ModbusService.cs
2026-02-27 11:20:42 +08:00

397 lines
11 KiB
C#

using NModbus;
using System.Net.Sockets;
namespace PetWashControl.Services;
/// <summary>
/// Modbus TCP 服务,用于与设备进行通信
/// </summary>
public class ModbusService : IDisposable
{
private readonly ConfigurationService _config;
private readonly LogService _logger;
private TcpClient? _tcpClient;
private IModbusMaster? _modbusMaster;
private bool _isConnected;
private readonly object _lockObject = new();
private System.Timers.Timer? _heartbeatTimer;
public bool IsConnected
{
get
{
lock (_lockObject)
{
return _isConnected && _tcpClient?.Connected == true;
}
}
}
public event Action<bool>? ConnectionStatusChanged;
public ModbusService(ConfigurationService config, LogService logger)
{
_config = config;
_logger = logger;
}
/// <summary>
/// 连接到 Modbus TCP 设备
/// </summary>
public async Task<bool> ConnectAsync()
{
try
{
lock (_lockObject)
{
if (_isConnected && _tcpClient?.Connected == true)
{
_logger.LogInfo("Modbus TCP 已经连接");
return true;
}
}
_logger.LogInfo($"正在连接 Modbus TCP 设备: {_config.ModbusIpAddress}:{_config.ModbusPort}");
// 创建 TCP 客户端
var tcpClient = new TcpClient
{
SendTimeout = _config.ModbusReadTimeoutMs,
ReceiveTimeout = _config.ModbusReadTimeoutMs
};
// 异步连接,带超时控制
var connectTask = tcpClient.ConnectAsync(_config.ModbusIpAddress, _config.ModbusPort);
var timeoutTask = Task.Delay(_config.ModbusConnectTimeoutMs);
var completedTask = await Task.WhenAny(connectTask, timeoutTask);
if (completedTask == timeoutTask)
{
tcpClient.Close();
throw new TimeoutException($"连接超时 ({_config.ModbusConnectTimeoutMs}ms)");
}
if (!tcpClient.Connected)
{
throw new Exception("TCP 连接失败");
}
// 创建 Modbus Master
var factory = new ModbusFactory();
var modbusMaster = factory.CreateMaster(tcpClient);
// 测试连接 - 读取保持寄存器地址 0
try
{
await Task.Run(() => modbusMaster.ReadHoldingRegisters(_config.ModbusSlaveId, 0, 1));
_logger.LogInfo("Modbus 设备响应正常");
}
catch (Exception ex)
{
_logger.LogWarning($"Modbus 设备测试读取失败: {ex.Message},但连接已建立");
}
lock (_lockObject)
{
_tcpClient = tcpClient;
_modbusMaster = modbusMaster;
_isConnected = true;
}
_logger.LogInfo($"Modbus TCP 连接成功: {_config.ModbusIpAddress}:{_config.ModbusPort}");
// 启动心跳检测
StartHeartbeat();
ConnectionStatusChanged?.Invoke(true);
return true;
}
catch (Exception ex)
{
_logger.LogError($"Modbus TCP 连接失败", ex);
lock (_lockObject)
{
_isConnected = false;
}
ConnectionStatusChanged?.Invoke(false);
return false;
}
}
/// <summary>
/// 断开连接
/// </summary>
public void Disconnect()
{
try
{
StopHeartbeat();
lock (_lockObject)
{
if (_tcpClient != null)
{
_tcpClient.Close();
_tcpClient.Dispose();
_tcpClient = null;
}
_modbusMaster?.Dispose();
_modbusMaster = null;
_isConnected = false;
}
_logger.LogInfo("Modbus TCP 已断开连接");
ConnectionStatusChanged?.Invoke(false);
}
catch (Exception ex)
{
_logger.LogError("断开 Modbus TCP 连接时出错", ex);
}
}
/// <summary>
/// 读取保持寄存器
/// </summary>
public async Task<ushort[]> ReadHoldingRegistersAsync(ushort startAddress, ushort numberOfPoints)
{
EnsureConnected();
try
{
var result = await Task.Run(() =>
_modbusMaster!.ReadHoldingRegisters(_config.ModbusSlaveId, startAddress, numberOfPoints));
_logger.LogInfo($"读取保持寄存器成功: 地址={startAddress}, 数量={numberOfPoints}");
return result;
}
catch (Exception ex)
{
_logger.LogError($"读取保持寄存器失败: 地址={startAddress}", ex);
await HandleConnectionError();
throw;
}
}
/// <summary>
/// 读取输入寄存器
/// </summary>
public async Task<ushort[]> ReadInputRegistersAsync(ushort startAddress, ushort numberOfPoints)
{
EnsureConnected();
try
{
var result = await Task.Run(() =>
_modbusMaster!.ReadInputRegisters(_config.ModbusSlaveId, startAddress, numberOfPoints));
_logger.LogInfo($"读取输入寄存器成功: 地址={startAddress}, 数量={numberOfPoints}");
return result;
}
catch (Exception ex)
{
_logger.LogError($"读取输入寄存器失败: 地址={startAddress}", ex);
await HandleConnectionError();
throw;
}
}
/// <summary>
/// 写入单个保持寄存器
/// </summary>
public async Task WriteSingleRegisterAsync(ushort registerAddress, ushort value)
{
EnsureConnected();
try
{
await Task.Run(() =>
_modbusMaster!.WriteSingleRegister(_config.ModbusSlaveId, registerAddress, value));
_logger.LogInfo($"写入单个寄存器成功: 地址={registerAddress}, 值={value}");
}
catch (Exception ex)
{
_logger.LogError($"写入单个寄存器失败: 地址={registerAddress}", ex);
await HandleConnectionError();
throw;
}
}
/// <summary>
/// 写入多个保持寄存器
/// </summary>
public async Task WriteMultipleRegistersAsync(ushort startAddress, ushort[] data)
{
EnsureConnected();
try
{
await Task.Run(() =>
_modbusMaster!.WriteMultipleRegisters(_config.ModbusSlaveId, startAddress, data));
_logger.LogInfo($"写入多个寄存器成功: 地址={startAddress}, 数量={data.Length}");
}
catch (Exception ex)
{
_logger.LogError($"写入多个寄存器失败: 地址={startAddress}", ex);
await HandleConnectionError();
throw;
}
}
/// <summary>
/// 读取线圈状态
/// </summary>
public async Task<bool[]> ReadCoilsAsync(ushort startAddress, ushort numberOfPoints)
{
EnsureConnected();
try
{
var result = await Task.Run(() =>
_modbusMaster!.ReadCoils(_config.ModbusSlaveId, startAddress, numberOfPoints));
_logger.LogInfo($"读取线圈成功: 地址={startAddress}, 数量={numberOfPoints}");
return result;
}
catch (Exception ex)
{
_logger.LogError($"读取线圈失败: 地址={startAddress}", ex);
await HandleConnectionError();
throw;
}
}
/// <summary>
/// 写入单个线圈
/// </summary>
public async Task WriteSingleCoilAsync(ushort coilAddress, bool value)
{
EnsureConnected();
try
{
await Task.Run(() =>
_modbusMaster!.WriteSingleCoil(_config.ModbusSlaveId, coilAddress, value));
_logger.LogInfo($"写入单个线圈成功: 地址={coilAddress}, 值={value}");
}
catch (Exception ex)
{
_logger.LogError($"写入单个线圈失败: 地址={coilAddress}", ex);
await HandleConnectionError();
throw;
}
}
/// <summary>
/// 写入多个线圈
/// </summary>
public async Task WriteMultipleCoilsAsync(ushort startAddress, bool[] data)
{
EnsureConnected();
try
{
await Task.Run(() =>
_modbusMaster!.WriteMultipleCoils(_config.ModbusSlaveId, startAddress, data));
_logger.LogInfo($"写入多个线圈成功: 地址={startAddress}, 数量={data.Length}");
}
catch (Exception ex)
{
_logger.LogError($"写入多个线圈失败: 地址={startAddress}", ex);
await HandleConnectionError();
throw;
}
}
/// <summary>
/// 启动心跳检测
/// </summary>
private void StartHeartbeat()
{
StopHeartbeat();
_heartbeatTimer = new System.Timers.Timer(10000); // 每10秒检测一次
_heartbeatTimer.Elapsed += async (s, e) =>
{
try
{
if (!IsConnected)
{
_logger.LogWarning("心跳检测: 连接已断开,尝试重连...");
await ConnectAsync();
}
else
{
// 尝试读取一个寄存器来验证连接
await ReadHoldingRegistersAsync(0, 1);
}
}
catch (Exception ex)
{
_logger.LogError("心跳检测失败", ex);
await HandleConnectionError();
}
};
_heartbeatTimer.Start();
_logger.LogInfo("Modbus 心跳检测已启动");
}
/// <summary>
/// 停止心跳检测
/// </summary>
private void StopHeartbeat()
{
if (_heartbeatTimer != null)
{
_heartbeatTimer.Stop();
_heartbeatTimer.Dispose();
_heartbeatTimer = null;
_logger.LogInfo("Modbus 心跳检测已停止");
}
}
/// <summary>
/// 处理连接错误
/// </summary>
private async Task HandleConnectionError()
{
lock (_lockObject)
{
if (_isConnected)
{
_isConnected = false;
ConnectionStatusChanged?.Invoke(false);
}
}
_logger.LogWarning("检测到连接错误,尝试重新连接...");
// 尝试重新连接
await Task.Delay(2000);
await ConnectAsync();
}
/// <summary>
/// 确保已连接
/// </summary>
private void EnsureConnected()
{
if (!IsConnected)
{
throw new InvalidOperationException("Modbus TCP 未连接,请先调用 ConnectAsync()");
}
}
public void Dispose()
{
Disconnect();
}
}