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

11 KiB
Raw Blame History

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. 测试步骤

测试连接

  1. 启动应用程序
  2. 查看日志输出:
    [INFO] 正在连接 Modbus TCP 设备: 192.168.1.10:502
    [INFO] Modbus TCP 连接成功: 192.168.1.10:502
    
  3. 界面显示 "系统就绪,设备已连接"

测试读取

// 在 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: 检查以下几点:

  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