Files
petwash/MODBUS_QUICK_START.md
GukSang.Jin 9c66b6cd82
2026-03-03 16:55:02 +08:00

421 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`