Files
petwash/MODBUS_QUICK_START.md

421 lines
11 KiB
Markdown
Raw Normal View History

2026-03-03 16:55:02 +08:00
# Modbus TCP 快速入门指南
## 1. 配置设备连接
`PetWashControl/Services/ConfigurationService.cs` 中修改设备连接参数:
```csharp
// Modbus TCP 配置
public string ModbusIpAddress { get; set; } = "192.168.1.10"; // 修改为实际设备IP
public int ModbusPort { get; set; } = 502; // 默认502端口
public byte ModbusSlaveId { get; set; } = 1; // 从站ID根据设备配置
```
## 2. 系统自动连接
系统启动时会自动连接 Modbus TCP 设备:
```csharp
// MainViewModel.InitializeAsync() 中自动执行
var modbusConnected = await _modbusService.ConnectAsync();
```
连接状态会显示在界面上:
- ✅ "系统就绪,设备已连接" - 连接成功
- ⚠️ "系统就绪,设备未连接" - 连接失败
## 3. 使用 Modbus 服务
### 在 MainViewModel 中访问服务
```csharp
// _modbusService 已在构造函数中初始化
private readonly ModbusService _modbusService;
```
### 常用操作示例
#### 读取设备状态
```csharp
try
{
// 读取设备状态寄存器地址0
var status = await _modbusService.ReadHoldingRegistersAsync(0, 1);
_logger.LogInfo($"设备状态: {status[0]}");
}
catch (Exception ex)
{
_logger.LogError("读取设备状态失败", ex);
}
```
#### 控制设备门
```csharp
try
{
// 打开门线圈地址0
await _modbusService.WriteSingleCoilAsync(0, true);
IsDoorOpen = true;
_logger.LogInfo("设备门已打开");
// 关闭门
await _modbusService.WriteSingleCoilAsync(0, false);
IsDoorOpen = false;
_logger.LogInfo("设备门已关闭");
}
catch (Exception ex)
{
_logger.LogError("控制门失败", ex);
}
```
#### 读取温度传感器
```csharp
try
{
// 读取温度传感器输入寄存器100-101
var temps = await _modbusService.ReadInputRegistersAsync(100, 2);
WaterTemperature = temps[0] / 10.0; // 假设数据需要除以10
RoomTemperature = temps[1] / 10.0;
_logger.LogInfo($"水温: {WaterTemperature}°C, 室温: {RoomTemperature}°C");
}
catch (Exception ex)
{
_logger.LogError("读取温度失败", ex);
}
```
#### 启动洗护流程
```csharp
try
{
// 写入控制寄存器地址200-201
ushort[] controlData = new ushort[]
{
1, // 启动命令
30 // 运行时长(分钟)
};
await _modbusService.WriteMultipleRegistersAsync(200, controlData);
_logger.LogInfo("洗护流程已启动");
}
catch (Exception ex)
{
_logger.LogError("启动洗护失败", ex);
}
```
#### 读取液位传感器
```csharp
try
{
// 读取三个液位传感器输入寄存器300-302
var levels = await _modbusService.ReadInputRegistersAsync(300, 3);
Shampoo1Level = levels[0];
Shampoo2Level = levels[1];
Shampoo3Level = levels[2];
_logger.LogInfo($"液位: {Shampoo1Level}%, {Shampoo2Level}%, {Shampoo3Level}%");
}
catch (Exception ex)
{
_logger.LogError("读取液位失败", ex);
}
```
## 4. 集成到现有流程
### 在支付成功后打开门
修改 `SimulatePaymentAsync` 方法:
```csharp
[RelayCommand]
private async Task SimulatePaymentAsync()
{
if (CurrentOrder == null) return;
try
{
StatusMessage = "正在处理支付...";
await Task.Delay(1500);
// 确认支付
CurrentOrder = await _apiService.ConfirmPaymentAsync(CurrentOrder.Id);
// 通过 Modbus 打开设备门
if (IsModbusConnected)
{
await _modbusService.WriteSingleCoilAsync(0, true);
_logger.LogInfo("通过 Modbus 打开设备门");
}
IsDoorOpen = true;
StatusMessage = "支付成功!设备门已打开,请将宠物放入";
MessageBox.Show("支付成功!\n\n设备门已自动打开\n请将宠物放入设备后关闭门开始洗护",
"支付成功", MessageBoxButton.OK, MessageBoxImage.Information);
CurrentView = "Idle";
ViewChanged?.Invoke("Idle");
}
catch (Exception ex)
{
_logger.LogError("支付失败", ex);
StatusMessage = $"支付失败: {ex.Message}";
}
}
```
### 在关门时启动洗护
修改 `CloseDoorAsync` 方法:
```csharp
[RelayCommand]
private async Task CloseDoorAsync()
{
if (CurrentOrder == null || !IsDoorOpen) return;
try
{
_logger.LogInfo($"关门订单ID: {CurrentOrder.Id}");
// 通过 Modbus 关闭设备门
if (IsModbusConnected)
{
await _modbusService.WriteSingleCoilAsync(0, false);
_logger.LogInfo("通过 Modbus 关闭设备门");
}
// 通过MQTT发送关门状态
await _mqttService.PublishAsync("device/status", new
{
status = "door_closed",
orderId = CurrentOrder.Id,
timestamp = DateTime.Now
});
CurrentOrder = await _apiService.UpdateOrderStatusAsync(CurrentOrder.Id, OrderStatus.DoorClosed);
IsDoorOpen = false;
StatusMessage = "门已关闭,清洗即将开始...";
// 通过 Modbus 启动洗护流程
if (IsModbusConnected)
{
int totalMinutes = CalculateTotalWashTime();
ushort[] controlData = new ushort[] { 1, (ushort)totalMinutes };
await _modbusService.WriteMultipleRegistersAsync(200, controlData);
_logger.LogInfo($"通过 Modbus 启动洗护流程,时长: {totalMinutes}分钟");
}
CurrentView = "Washing";
ViewChanged?.Invoke("Washing");
IsWashing = true;
_ = SimulateWashingProcessAsync();
}
catch (Exception ex)
{
_logger.LogError("关门失败", ex);
StatusMessage = $"关门失败: {ex.Message}";
}
}
private int CalculateTotalWashTime()
{
return FirstSprayWaterTime + SprayShampoo1Time + AfterShampoo1SprayTime +
SprayShampoo2Time + AfterShampoo2SprayTime + SprayShampoo3Time +
AfterShampoo3SprayTime + HotAirTime + ColdAirTime + UvSterilizationTime;
}
```
### 在洗护过程中读取实时数据
修改 `SimulateWashingProcessAsync` 方法,添加实时数据读取:
```csharp
private async Task SimulateWashingProcessAsync()
{
try
{
var steps = new[]
{
("第一次冲水", FirstSprayWaterTime * 60),
// ... 其他步骤
};
for (int stepIndex = 0; stepIndex < steps.Length; stepIndex++)
{
var (stepName, duration) = steps[stepIndex];
CurrentStep = stepName;
for (int i = 0; i <= duration; i++)
{
if (!IsWashing) return;
// 从 Modbus 读取实时温度
if (IsModbusConnected && i % 10 == 0) // 每10秒读取一次
{
try
{
var temps = await _modbusService.ReadInputRegistersAsync(100, 2);
WaterTemperature = temps[0] / 10.0;
RoomTemperature = temps[1] / 10.0;
}
catch (Exception ex)
{
_logger.LogError("读取实时温度失败", ex);
// 失败时使用模拟数据
UpdateTemperatures(stepName, i, duration);
}
}
else
{
UpdateTemperatures(stepName, i, duration);
}
await Task.Delay(100);
}
}
await CompleteWashingAsync();
}
catch (Exception ex)
{
_logger.LogError("洗护流程失败", ex);
}
}
```
## 5. 监控连接状态
系统会自动监控 Modbus 连接状态:
```csharp
// 连接状态变化时自动触发
private void OnModbusConnectionStatusChanged(bool isConnected)
{
Application.Current.Dispatcher.Invoke(() =>
{
IsModbusConnected = isConnected;
if (isConnected)
{
_logger.LogInfo("Modbus TCP 设备已连接");
StatusMessage = "系统就绪,设备已连接";
}
else
{
_logger.LogWarning("Modbus TCP 设备连接断开");
StatusMessage = "警告:设备连接断开";
}
});
}
```
在界面中绑定连接状态:
```xml
<!-- 在 MainWindow.xaml 中添加连接状态指示器 -->
<StackPanel Orientation="Horizontal">
<Ellipse Width="10" Height="10"
Fill="{Binding IsModbusConnected, Converter={StaticResource BoolToColorConverter}}" />
<TextBlock Text="{Binding IsModbusConnected, Converter={StaticResource BoolToStatusConverter}}"
Margin="5,0,0,0" />
</StackPanel>
```
## 6. 错误处理最佳实践
```csharp
// 始终使用 try-catch 包装 Modbus 操作
try
{
// 检查连接状态
if (!IsModbusConnected)
{
_logger.LogWarning("Modbus 未连接,跳过设备控制");
// 使用备用方案或提示用户
return;
}
// 执行 Modbus 操作
await _modbusService.WriteSingleCoilAsync(0, true);
// 记录成功日志
_logger.LogInfo("Modbus 操作成功");
}
catch (TimeoutException ex)
{
_logger.LogError("Modbus 操作超时", ex);
StatusMessage = "设备响应超时,请检查连接";
}
catch (InvalidOperationException ex)
{
_logger.LogError("Modbus 未连接", ex);
StatusMessage = "设备未连接";
}
catch (Exception ex)
{
_logger.LogError("Modbus 操作失败", ex);
StatusMessage = $"设备控制失败: {ex.Message}";
}
```
## 7. 测试步骤
### 测试连接
1. 启动应用程序
2. 查看日志输出:
```
[INFO] 正在连接 Modbus TCP 设备: 192.168.1.10:502
[INFO] Modbus TCP 连接成功: 192.168.1.10:502
```
3. 界面显示 "系统就绪,设备已连接"
### 测试读取
```csharp
// 在 InitializeAsync 后添加测试代码
var testData = await _modbusService.ReadHoldingRegistersAsync(0, 1);
_logger.LogInfo($"测试读取成功,值: {testData[0]}");
```
### 测试写入
```csharp
// 测试写入寄存器
await _modbusService.WriteSingleRegisterAsync(0, 100);
var verify = await _modbusService.ReadHoldingRegistersAsync(0, 1);
_logger.LogInfo($"测试写入成功,验证值: {verify[0]}");
```
## 8. 常见问题
### Q: 连接失败怎么办?
A: 检查以下几点:
1. 设备 IP 地址是否正确
2. 网络是否连通ping 测试)
3. 设备是否启用 Modbus TCP 服务
4. 防火墙是否阻止 502 端口
### Q: 读写操作失败?
A: 确认:
1. 寄存器地址是否正确
2. 从站 ID 是否匹配
3. 数据格式是否符合设备要求
4. 查看详细错误日志
### Q: 如何调试 Modbus 通信?
A:
1. 查看日志文件中的详细信息
2. 使用 Modbus 测试工具(如 Modbus Poll验证设备
3. 使用网络抓包工具(如 Wireshark分析通信数据
## 9. 下一步
- 根据实际设备的 Modbus 地址映射表修改代码
- 实现完整的设备控制逻辑
- 添加更多的错误处理和用户提示
- 进行完整的集成测试
详细文档请参考:`MODBUS_INTEGRATION.md`