feat: add TCP
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
@@ -12,6 +12,7 @@
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.1" />
|
||||
<PackageReference Include="MQTTnet" Version="4.3.7.1207" />
|
||||
<PackageReference Include="NModbus" Version="3.0.81" />
|
||||
<PackageReference Include="System.Net.Http.Json" Version="8.0.1" />
|
||||
<PackageReference Include="System.Text.Json" Version="8.0.5" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -7,6 +7,13 @@ public class ConfigurationService
|
||||
public int MqttBrokerPort { get; set; } = 1883;
|
||||
public string MqttClientId { get; set; } = "PetWashControl";
|
||||
|
||||
// Modbus TCP 配置
|
||||
public string ModbusIpAddress { get; set; } = "192.168.1.10";
|
||||
public int ModbusPort { get; set; } = 502;
|
||||
public byte ModbusSlaveId { get; set; } = 1;
|
||||
public int ModbusConnectTimeoutMs { get; set; } = 5000;
|
||||
public int ModbusReadTimeoutMs { get; set; } = 3000;
|
||||
|
||||
public int PaymentCheckIntervalSeconds { get; set; } = 2;
|
||||
public int WashSimulationSeconds { get; set; } = 10;
|
||||
|
||||
|
||||
396
PetWashControl/Services/ModbusService.cs
Normal file
396
PetWashControl/Services/ModbusService.cs
Normal file
@@ -0,0 +1,396 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ public partial class MainViewModel : ObservableObject
|
||||
{
|
||||
private readonly ApiService _apiService;
|
||||
private readonly MqttClientService _mqttService;
|
||||
private readonly ModbusService _modbusService;
|
||||
private readonly ConfigurationService _config;
|
||||
private readonly LogService _logger;
|
||||
|
||||
@@ -50,6 +51,9 @@ public partial class MainViewModel : ObservableObject
|
||||
[ObservableProperty]
|
||||
private bool _isConnected;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isModbusConnected;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _currentView = "Idle";
|
||||
|
||||
@@ -141,7 +145,10 @@ public partial class MainViewModel : ObservableObject
|
||||
_logger = new LogService();
|
||||
_apiService = new ApiService(_config);
|
||||
_mqttService = new MqttClientService(_config);
|
||||
_modbusService = new ModbusService(_config, _logger);
|
||||
|
||||
_mqttService.MessageReceived += OnMqttMessageReceived;
|
||||
_modbusService.ConnectionStatusChanged += OnModbusConnectionStatusChanged;
|
||||
|
||||
// 从配置加载时间参数
|
||||
FirstSprayWaterTime = _config.FirstSprayWaterTime;
|
||||
@@ -204,19 +211,43 @@ public partial class MainViewModel : ObservableObject
|
||||
{
|
||||
_logger.LogInfo("开始初始化系统...");
|
||||
|
||||
// 连接 Modbus TCP 设备
|
||||
_logger.LogInfo($"正在连接 Modbus TCP 设备: {_config.ModbusIpAddress}:{_config.ModbusPort}");
|
||||
var modbusConnected = await _modbusService.ConnectAsync();
|
||||
IsModbusConnected = modbusConnected;
|
||||
|
||||
if (modbusConnected)
|
||||
{
|
||||
_logger.LogInfo("Modbus TCP 设备连接成功");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("Modbus TCP 设备连接失败,系统将继续运行但设备控制功能可能不可用");
|
||||
}
|
||||
|
||||
// 连接 MQTT
|
||||
await _mqttService.ConnectAsync();
|
||||
IsConnected = _mqttService.IsConnected;
|
||||
_logger.LogInfo("MQTT连接成功");
|
||||
|
||||
await LoadPackagesAsync();
|
||||
StatusMessage = "系统就绪,请点击开始";
|
||||
|
||||
if (IsModbusConnected)
|
||||
{
|
||||
StatusMessage = "系统就绪,设备已连接";
|
||||
}
|
||||
else
|
||||
{
|
||||
StatusMessage = "系统就绪,设备未连接";
|
||||
}
|
||||
|
||||
_logger.LogInfo("系统初始化完成");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError("初始化失败", ex);
|
||||
StatusMessage = $"初始化失败: {ex.Message}";
|
||||
MessageBox.Show($"系统初始化失败,请检查后端服务是否启动。\n\n错误: {ex.Message}",
|
||||
MessageBox.Show($"系统初始化失败,请检查后端服务和设备连接。\n\n错误: {ex.Message}",
|
||||
"错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
@@ -755,6 +786,31 @@ public partial class MainViewModel : ObservableObject
|
||||
});
|
||||
}
|
||||
|
||||
private void OnModbusConnectionStatusChanged(bool isConnected)
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
IsModbusConnected = isConnected;
|
||||
|
||||
if (isConnected)
|
||||
{
|
||||
_logger.LogInfo("Modbus TCP 设备已连接");
|
||||
if (StatusMessage.Contains("设备未连接"))
|
||||
{
|
||||
StatusMessage = "系统就绪,设备已连接";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("Modbus TCP 设备连接断开");
|
||||
if (!StatusMessage.Contains("失败") && !StatusMessage.Contains("错误"))
|
||||
{
|
||||
StatusMessage = "警告:设备连接断开";
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void UpdateTemperatures(string stepName, int currentTime, int totalTime)
|
||||
{
|
||||
// 根据不同步骤模拟温度变化
|
||||
|
||||
Reference in New Issue
Block a user