11 KiB
11 KiB
Modbus TCP 快速入门指南
1. 配置设备连接
在 PetWashControl/Services/ConfigurationService.cs 中修改设备连接参数:
// 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 设备:
// MainViewModel.InitializeAsync() 中自动执行
var modbusConnected = await _modbusService.ConnectAsync();
连接状态会显示在界面上:
- ✅ "系统就绪,设备已连接" - 连接成功
- ⚠️ "系统就绪,设备未连接" - 连接失败
3. 使用 Modbus 服务
在 MainViewModel 中访问服务
// _modbusService 已在构造函数中初始化
private readonly ModbusService _modbusService;
常用操作示例
读取设备状态
try
{
// 读取设备状态寄存器(地址0)
var status = await _modbusService.ReadHoldingRegistersAsync(0, 1);
_logger.LogInfo($"设备状态: {status[0]}");
}
catch (Exception ex)
{
_logger.LogError("读取设备状态失败", ex);
}
控制设备门
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);
}
读取温度传感器
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);
}
启动洗护流程
try
{
// 写入控制寄存器(地址200-201)
ushort[] controlData = new ushort[]
{
1, // 启动命令
30 // 运行时长(分钟)
};
await _modbusService.WriteMultipleRegistersAsync(200, controlData);
_logger.LogInfo("洗护流程已启动");
}
catch (Exception ex)
{
_logger.LogError("启动洗护失败", ex);
}
读取液位传感器
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 方法:
[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 方法:
[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 方法,添加实时数据读取:
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 连接状态:
// 连接状态变化时自动触发
private void OnModbusConnectionStatusChanged(bool isConnected)
{
Application.Current.Dispatcher.Invoke(() =>
{
IsModbusConnected = isConnected;
if (isConnected)
{
_logger.LogInfo("Modbus TCP 设备已连接");
StatusMessage = "系统就绪,设备已连接";
}
else
{
_logger.LogWarning("Modbus TCP 设备连接断开");
StatusMessage = "警告:设备连接断开";
}
});
}
在界面中绑定连接状态:
<!-- 在 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. 错误处理最佳实践
// 始终使用 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. 测试步骤
测试连接
- 启动应用程序
- 查看日志输出:
[INFO] 正在连接 Modbus TCP 设备: 192.168.1.10:502 [INFO] Modbus TCP 连接成功: 192.168.1.10:502 - 界面显示 "系统就绪,设备已连接"
测试读取
// 在 InitializeAsync 后添加测试代码
var testData = await _modbusService.ReadHoldingRegistersAsync(0, 1);
_logger.LogInfo($"测试读取成功,值: {testData[0]}");
测试写入
// 测试写入寄存器
await _modbusService.WriteSingleRegisterAsync(0, 100);
var verify = await _modbusService.ReadHoldingRegistersAsync(0, 1);
_logger.LogInfo($"测试写入成功,验证值: {verify[0]}");
8. 常见问题
Q: 连接失败怎么办?
A: 检查以下几点:
- 设备 IP 地址是否正确
- 网络是否连通(ping 测试)
- 设备是否启用 Modbus TCP 服务
- 防火墙是否阻止 502 端口
Q: 读写操作失败?
A: 确认:
- 寄存器地址是否正确
- 从站 ID 是否匹配
- 数据格式是否符合设备要求
- 查看详细错误日志
Q: 如何调试 Modbus 通信?
A:
- 查看日志文件中的详细信息
- 使用 Modbus 测试工具(如 Modbus Poll)验证设备
- 使用网络抓包工具(如 Wireshark)分析通信数据
9. 下一步
- 根据实际设备的 Modbus 地址映射表修改代码
- 实现完整的设备控制逻辑
- 添加更多的错误处理和用户提示
- 进行完整的集成测试
详细文档请参考:MODBUS_INTEGRATION.md