1131 lines
42 KiB
C#
1131 lines
42 KiB
C#
using CommunityToolkit.Mvvm.ComponentModel;
|
||
using CommunityToolkit.Mvvm.Input;
|
||
using PetWashControl.Models;
|
||
using PetWashControl.Services;
|
||
using System.Collections.ObjectModel;
|
||
using System.Text.Json;
|
||
using System.Windows;
|
||
|
||
namespace PetWashControl.ViewModels;
|
||
|
||
public partial class WashStep : ObservableObject
|
||
{
|
||
[ObservableProperty]
|
||
private string _name = "";
|
||
|
||
[ObservableProperty]
|
||
private string _status = "等待中";
|
||
|
||
[ObservableProperty]
|
||
private bool _isActive;
|
||
}
|
||
|
||
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;
|
||
|
||
public event Action<string>? ViewChanged;
|
||
|
||
[ObservableProperty]
|
||
private ObservableCollection<Package> _packages = new();
|
||
|
||
[ObservableProperty]
|
||
private Package? _selectedPackage;
|
||
|
||
[ObservableProperty]
|
||
private Order? _currentOrder;
|
||
|
||
[ObservableProperty]
|
||
private string _statusMessage = "系统就绪";
|
||
|
||
[ObservableProperty]
|
||
private bool _isDoorOpen;
|
||
|
||
[ObservableProperty]
|
||
private bool _isWashing;
|
||
|
||
[ObservableProperty]
|
||
private bool _isConnected;
|
||
|
||
[ObservableProperty]
|
||
private bool _isModbusConnected;
|
||
|
||
[ObservableProperty]
|
||
private string _currentView = "Idle";
|
||
|
||
private string _previousView = "Idle"; // 保存进入设置前的视图
|
||
|
||
[ObservableProperty]
|
||
private int _washProgress;
|
||
|
||
[ObservableProperty]
|
||
private string _currentStep = "";
|
||
|
||
[ObservableProperty]
|
||
private string _remainingTime = "00:00";
|
||
|
||
[ObservableProperty]
|
||
private ObservableCollection<WashStep> _washSteps = new();
|
||
|
||
[ObservableProperty]
|
||
private double _waterTemperature = 40.0;
|
||
|
||
[ObservableProperty]
|
||
private double _roomTemperature = 25.0;
|
||
|
||
[ObservableProperty]
|
||
private string _currentCarouselImage = "/Images/dog.png";
|
||
|
||
[ObservableProperty]
|
||
private string _deviceNumber = "设备编号: PW-001";
|
||
|
||
[ObservableProperty]
|
||
private string _currentDateTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
||
|
||
[ObservableProperty]
|
||
private string _currentDayOfWeek = DateTime.Now.ToString("dddd", new System.Globalization.CultureInfo("zh-CN"));
|
||
|
||
[ObservableProperty]
|
||
private bool _isInstructionDialogOpen;
|
||
|
||
[ObservableProperty]
|
||
private bool _isCustomerServiceDialogOpen;
|
||
|
||
[ObservableProperty]
|
||
private int _firstSprayWaterTime = 2;
|
||
|
||
[ObservableProperty]
|
||
private int _afterShampoo1SprayTime = 2;
|
||
|
||
[ObservableProperty]
|
||
private int _afterShampoo2SprayTime = 2;
|
||
|
||
[ObservableProperty]
|
||
private int _afterShampoo3SprayTime = 2;
|
||
|
||
[ObservableProperty]
|
||
private int _sprayShampoo1Time = 1;
|
||
|
||
[ObservableProperty]
|
||
private int _sprayShampoo2Time = 1;
|
||
|
||
[ObservableProperty]
|
||
private int _sprayShampoo3Time = 1;
|
||
|
||
[ObservableProperty]
|
||
private int _coldAirTime = 2;
|
||
|
||
[ObservableProperty]
|
||
private int _hotAirTime = 5;
|
||
|
||
[ObservableProperty]
|
||
private int _uvSterilizationTime = 3;
|
||
|
||
[ObservableProperty]
|
||
private bool _isCleaningDialogOpen;
|
||
|
||
[ObservableProperty]
|
||
private string _cleaningMessage = "正在清理笼子...";
|
||
|
||
[ObservableProperty]
|
||
private int _cleaningProgress;
|
||
|
||
[ObservableProperty]
|
||
private int _shampoo1Level = 80;
|
||
|
||
[ObservableProperty]
|
||
private int _shampoo2Level = 75;
|
||
|
||
[ObservableProperty]
|
||
private int _shampoo3Level = 70;
|
||
|
||
private readonly System.Timers.Timer _carouselTimer;
|
||
private readonly System.Timers.Timer _clockTimer;
|
||
private readonly System.Timers.Timer _liquidLevelTimer;
|
||
private readonly string[] _carouselImages = { "/Images/dog.png", "/Images/dog1.png", "/Images/dog2.png" };
|
||
private int _currentImageIndex = 0;
|
||
|
||
public MainViewModel()
|
||
{
|
||
_config = new ConfigurationService();
|
||
_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;
|
||
AfterShampoo1SprayTime = _config.AfterShampoo1SprayTime;
|
||
AfterShampoo2SprayTime = _config.AfterShampoo2SprayTime;
|
||
AfterShampoo3SprayTime = _config.AfterShampoo3SprayTime;
|
||
SprayShampoo1Time = _config.SprayShampoo1Time;
|
||
SprayShampoo2Time = _config.SprayShampoo2Time;
|
||
SprayShampoo3Time = _config.SprayShampoo3Time;
|
||
ColdAirTime = _config.ColdAirTime;
|
||
HotAirTime = _config.HotAirTime;
|
||
UvSterilizationTime = _config.UvSterilizationTime;
|
||
|
||
// 初始化洗护步骤
|
||
InitializeWashSteps();
|
||
|
||
// 初始化图片轮播定时器(每5秒切换)
|
||
_carouselTimer = new System.Timers.Timer(5000);
|
||
_carouselTimer.Elapsed += (s, e) =>
|
||
{
|
||
Application.Current.Dispatcher.Invoke(() =>
|
||
{
|
||
_currentImageIndex = (_currentImageIndex + 1) % _carouselImages.Length;
|
||
CurrentCarouselImage = _carouselImages[_currentImageIndex];
|
||
});
|
||
};
|
||
_carouselTimer.Start();
|
||
|
||
// 初始化时钟定时器(每秒更新)
|
||
_clockTimer = new System.Timers.Timer(1000);
|
||
_clockTimer.Elapsed += (s, e) =>
|
||
{
|
||
Application.Current.Dispatcher.Invoke(() =>
|
||
{
|
||
var now = DateTime.Now;
|
||
CurrentDateTime = now.ToString("yyyy-MM-dd HH:mm:ss");
|
||
CurrentDayOfWeek = now.ToString("dddd", new System.Globalization.CultureInfo("zh-CN"));
|
||
});
|
||
};
|
||
_clockTimer.Start();
|
||
|
||
// 初始化液位监测定时器(每30秒更新一次)
|
||
_liquidLevelTimer = new System.Timers.Timer(30000);
|
||
_liquidLevelTimer.Elapsed += async (s, e) =>
|
||
{
|
||
if (IsModbusConnected)
|
||
{
|
||
try
|
||
{
|
||
await LoadLiquidLevelsAsync();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError("定时更新液位失败", ex);
|
||
}
|
||
}
|
||
};
|
||
_liquidLevelTimer.Start();
|
||
}
|
||
|
||
private void InitializeWashSteps()
|
||
{
|
||
WashSteps.Add(new WashStep { Name = "第一次冲水", Status = "等待中" });
|
||
WashSteps.Add(new WashStep { Name = "沐浴露喷洒", Status = "等待中" });
|
||
WashSteps.Add(new WashStep { Name = "第二次冲水", Status = "等待中" });
|
||
WashSteps.Add(new WashStep { Name = "香波喷洒", Status = "等待中" });
|
||
WashSteps.Add(new WashStep { Name = "第三次冲水", Status = "等待中" });
|
||
WashSteps.Add(new WashStep { Name = "护理液喷洒", Status = "等待中" });
|
||
WashSteps.Add(new WashStep { Name = "第四次冲水", Status = "等待中" });
|
||
WashSteps.Add(new WashStep { Name = "热风吹毛", Status = "等待中" });
|
||
WashSteps.Add(new WashStep { Name = "冷热风混合", Status = "等待中" });
|
||
// 紫外线杀菌不在洗护流程中,在流程完成后独立执行
|
||
}
|
||
|
||
public async Task InitializeAsync()
|
||
{
|
||
try
|
||
{
|
||
_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 设备连接成功");
|
||
|
||
// 从设备读取初始配置参数
|
||
await LoadDeviceParametersAsync();
|
||
}
|
||
else
|
||
{
|
||
_logger.LogWarning("Modbus TCP 设备连接失败,使用默认配置");
|
||
LoadDefaultParameters();
|
||
}
|
||
|
||
// 连接 MQTT
|
||
await _mqttService.ConnectAsync();
|
||
IsConnected = _mqttService.IsConnected;
|
||
_logger.LogInfo("MQTT连接成功");
|
||
|
||
await LoadPackagesAsync();
|
||
|
||
if (IsModbusConnected)
|
||
{
|
||
StatusMessage = "系统就绪,设备已连接";
|
||
}
|
||
else
|
||
{
|
||
StatusMessage = "系统就绪,设备未连接";
|
||
}
|
||
|
||
_logger.LogInfo("系统初始化完成");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError("初始化失败", ex);
|
||
StatusMessage = $"初始化失败: {ex.Message}";
|
||
MessageBox.Show($"系统初始化失败,请检查后端服务和设备连接。\n\n错误: {ex.Message}",
|
||
"错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从 Modbus 设备读取系统参数
|
||
/// </summary>
|
||
private async Task LoadDeviceParametersAsync()
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInfo("正在从设备读取系统参数...");
|
||
|
||
// 读取时间参数(D404-D424,共22个寄存器,11个浮点数)
|
||
// 每个浮点数占用2个寄存器
|
||
var timeParams = await _modbusService.ReadHoldingRegistersAsync(404, 22);
|
||
|
||
// 使用 ConvertRegistersToFloat 转换每个参数(CDAB字节序)
|
||
FirstSprayWaterTime = (int)_modbusService.ConvertRegistersToFloat(timeParams, 0, FloatByteOrder.CDAB); // D404-D405
|
||
AfterShampoo1SprayTime = (int)_modbusService.ConvertRegistersToFloat(timeParams, 2, FloatByteOrder.CDAB); // D406-D407
|
||
AfterShampoo2SprayTime = (int)_modbusService.ConvertRegistersToFloat(timeParams, 4, FloatByteOrder.CDAB); // D408-D409
|
||
AfterShampoo3SprayTime = (int)_modbusService.ConvertRegistersToFloat(timeParams, 6, FloatByteOrder.CDAB); // D410-D411
|
||
// D412-D413 清洗笼子喷水(保留,索引8)
|
||
SprayShampoo1Time = (int)_modbusService.ConvertRegistersToFloat(timeParams, 10, FloatByteOrder.CDAB); // D414-D415
|
||
SprayShampoo2Time = (int)_modbusService.ConvertRegistersToFloat(timeParams, 12, FloatByteOrder.CDAB); // D416-D417
|
||
SprayShampoo3Time = (int)_modbusService.ConvertRegistersToFloat(timeParams, 14, FloatByteOrder.CDAB); // D418-D419
|
||
HotAirTime = (int)_modbusService.ConvertRegistersToFloat(timeParams, 16, FloatByteOrder.CDAB); // D420-D421
|
||
ColdAirTime = (int)_modbusService.ConvertRegistersToFloat(timeParams, 18, FloatByteOrder.CDAB); // D422-D423
|
||
UvSterilizationTime = (int)_modbusService.ConvertRegistersToFloat(timeParams, 20, FloatByteOrder.CDAB); // D424-D425
|
||
|
||
_logger.LogInfo($"时间参数读取成功 - 首次喷水:{FirstSprayWaterTime}min, 沐浴1后:{AfterShampoo1SprayTime}min, " +
|
||
$"沐浴2后:{AfterShampoo2SprayTime}min, 沐浴3后:{AfterShampoo3SprayTime}min, " +
|
||
$"喷沐浴露1:{SprayShampoo1Time}min, 喷沐浴露2:{SprayShampoo2Time}min, 喷沐浴露3:{SprayShampoo3Time}min, " +
|
||
$"热风:{HotAirTime}min, 冷风:{ColdAirTime}min, 紫外线:{UvSterilizationTime}min");
|
||
|
||
// 读取液位参数
|
||
await LoadLiquidLevelsAsync();
|
||
|
||
StatusMessage = "设备参数加载成功";
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError("读取设备参数失败,使用默认配置", ex);
|
||
LoadDefaultParameters();
|
||
StatusMessage = "设备参数读取失败,使用默认配置";
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 读取液位传感器数据
|
||
/// </summary>
|
||
private async Task LoadLiquidLevelsAsync()
|
||
{
|
||
try
|
||
{
|
||
// 读取1号液位 (D1280)
|
||
var level1 = await _modbusService.ReadHoldingRegistersAsync(1280, 1);
|
||
Shampoo1Level = level1[0];
|
||
|
||
// 读取2号液位 (D1330)
|
||
var level2 = await _modbusService.ReadHoldingRegistersAsync(1330, 1);
|
||
Shampoo2Level = level2[0];
|
||
|
||
// 读取3号液位 (D1380)
|
||
var level3 = await _modbusService.ReadHoldingRegistersAsync(1380, 1);
|
||
Shampoo3Level = level3[0];
|
||
|
||
_logger.LogInfo($"液位读取成功 - 1号:{Shampoo1Level}%, 2号:{Shampoo2Level}%, 3号:{Shampoo3Level}%");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError("读取液位失败", ex);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 加载默认参数
|
||
/// </summary>
|
||
private void LoadDefaultParameters()
|
||
{
|
||
FirstSprayWaterTime = _config.FirstSprayWaterTime;
|
||
AfterShampoo1SprayTime = _config.AfterShampoo1SprayTime;
|
||
AfterShampoo2SprayTime = _config.AfterShampoo2SprayTime;
|
||
AfterShampoo3SprayTime = _config.AfterShampoo3SprayTime;
|
||
SprayShampoo1Time = _config.SprayShampoo1Time;
|
||
SprayShampoo2Time = _config.SprayShampoo2Time;
|
||
SprayShampoo3Time = _config.SprayShampoo3Time;
|
||
ColdAirTime = _config.ColdAirTime;
|
||
HotAirTime = _config.HotAirTime;
|
||
UvSterilizationTime = _config.UvSterilizationTime;
|
||
|
||
_logger.LogInfo("已加载默认参数配置");
|
||
}
|
||
|
||
[RelayCommand]
|
||
private async Task LoadPackagesAsync()
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInfo("加载套餐列表...");
|
||
var packages = await _apiService.GetPackagesAsync();
|
||
Packages.Clear();
|
||
foreach (var package in packages)
|
||
{
|
||
Packages.Add(package);
|
||
}
|
||
_logger.LogInfo($"成功加载 {packages.Count} 个套餐");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError("加载套餐失败", ex);
|
||
StatusMessage = $"加载套餐失败: {ex.Message}";
|
||
}
|
||
}
|
||
|
||
[RelayCommand]
|
||
private void ShowPayment()
|
||
{
|
||
CurrentView = "Payment";
|
||
ViewChanged?.Invoke("Payment");
|
||
StatusMessage = "请选择套餐";
|
||
_logger.LogInfo("切换到支付界面");
|
||
}
|
||
|
||
[RelayCommand]
|
||
private void ShowSettings()
|
||
{
|
||
// 保存当前视图状态
|
||
_previousView = CurrentView;
|
||
|
||
CurrentView = "Settings";
|
||
ViewChanged?.Invoke("Settings");
|
||
StatusMessage = "系统设置";
|
||
_logger.LogInfo($"切换到设置界面,之前的视图: {_previousView}");
|
||
}
|
||
|
||
[RelayCommand]
|
||
private void ShowInstruction()
|
||
{
|
||
IsInstructionDialogOpen = true;
|
||
_logger.LogInfo("显示使用说明");
|
||
}
|
||
|
||
[RelayCommand]
|
||
private void CloseInstruction()
|
||
{
|
||
IsInstructionDialogOpen = false;
|
||
_logger.LogInfo("关闭使用说明");
|
||
}
|
||
|
||
[RelayCommand]
|
||
private void ShowCustomerService()
|
||
{
|
||
IsCustomerServiceDialogOpen = true;
|
||
_logger.LogInfo("显示联系客服");
|
||
}
|
||
|
||
[RelayCommand]
|
||
private void CloseCustomerService()
|
||
{
|
||
IsCustomerServiceDialogOpen = false;
|
||
_logger.LogInfo("关闭联系客服");
|
||
}
|
||
|
||
[RelayCommand]
|
||
private async Task SaveSettingsAsync()
|
||
{
|
||
try
|
||
{
|
||
StatusMessage = "正在保存参数...";
|
||
|
||
// 更新本地配置
|
||
_config.FirstSprayWaterTime = FirstSprayWaterTime;
|
||
_config.AfterShampoo1SprayTime = AfterShampoo1SprayTime;
|
||
_config.AfterShampoo2SprayTime = AfterShampoo2SprayTime;
|
||
_config.AfterShampoo3SprayTime = AfterShampoo3SprayTime;
|
||
_config.SprayShampoo1Time = SprayShampoo1Time;
|
||
_config.SprayShampoo2Time = SprayShampoo2Time;
|
||
_config.SprayShampoo3Time = SprayShampoo3Time;
|
||
_config.ColdAirTime = ColdAirTime;
|
||
_config.HotAirTime = HotAirTime;
|
||
_config.UvSterilizationTime = UvSterilizationTime;
|
||
|
||
_logger.LogInfo($"本地参数已更新 - 首次喷水:{FirstSprayWaterTime}min, 沐浴1后:{AfterShampoo1SprayTime}min, " +
|
||
$"沐浴2后:{AfterShampoo2SprayTime}min, 沐浴3后:{AfterShampoo3SprayTime}min, " +
|
||
$"喷沐浴露1:{SprayShampoo1Time}min, 喷沐浴露2:{SprayShampoo2Time}min, 喷沐浴露3:{SprayShampoo3Time}min, " +
|
||
$"冷风:{ColdAirTime}min, 热风:{HotAirTime}min, 紫外线:{UvSterilizationTime}min");
|
||
|
||
// 如果 Modbus 已连接,写入设备
|
||
if (IsModbusConnected)
|
||
{
|
||
await SaveParametersToDeviceAsync();
|
||
MessageBox.Show("系统参数已保存到设备!", "成功", MessageBoxButton.OK, MessageBoxImage.Information);
|
||
StatusMessage = "参数保存成功";
|
||
}
|
||
else
|
||
{
|
||
MessageBox.Show("系统参数已保存到本地配置!\n\n注意:设备未连接,参数未写入设备。",
|
||
"保存成功", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||
StatusMessage = "参数已保存(仅本地)";
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError("保存系统参数失败", ex);
|
||
StatusMessage = "参数保存失败";
|
||
MessageBox.Show($"保存失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将参数保存到 Modbus 设备
|
||
/// </summary>
|
||
private async Task SaveParametersToDeviceAsync()
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInfo("正在将参数写入设备...");
|
||
|
||
// 准备时间参数数据(D404-D425,共22个寄存器,11个浮点数)
|
||
// 每个浮点数占用2个寄存器
|
||
List<ushort> allRegisters = new List<ushort>();
|
||
|
||
// 使用 ConvertFloatToRegisters 转换每个参数(CDAB字节序)
|
||
allRegisters.AddRange(_modbusService.ConvertFloatToRegisters(FirstSprayWaterTime, FloatByteOrder.CDAB)); // D404-D405
|
||
allRegisters.AddRange(_modbusService.ConvertFloatToRegisters(AfterShampoo1SprayTime, FloatByteOrder.CDAB)); // D406-D407
|
||
allRegisters.AddRange(_modbusService.ConvertFloatToRegisters(AfterShampoo2SprayTime, FloatByteOrder.CDAB)); // D408-D409
|
||
allRegisters.AddRange(_modbusService.ConvertFloatToRegisters(AfterShampoo3SprayTime, FloatByteOrder.CDAB)); // D410-D411
|
||
allRegisters.AddRange(_modbusService.ConvertFloatToRegisters(0, FloatByteOrder.CDAB)); // D412-D413 清洗笼子喷水(保留)
|
||
allRegisters.AddRange(_modbusService.ConvertFloatToRegisters(SprayShampoo1Time, FloatByteOrder.CDAB)); // D414-D415
|
||
allRegisters.AddRange(_modbusService.ConvertFloatToRegisters(SprayShampoo2Time, FloatByteOrder.CDAB)); // D416-D417
|
||
allRegisters.AddRange(_modbusService.ConvertFloatToRegisters(SprayShampoo3Time, FloatByteOrder.CDAB)); // D418-D419
|
||
allRegisters.AddRange(_modbusService.ConvertFloatToRegisters(HotAirTime, FloatByteOrder.CDAB)); // D420-D421
|
||
allRegisters.AddRange(_modbusService.ConvertFloatToRegisters(ColdAirTime, FloatByteOrder.CDAB)); // D422-D423
|
||
allRegisters.AddRange(_modbusService.ConvertFloatToRegisters(UvSterilizationTime, FloatByteOrder.CDAB)); // D424-D425
|
||
|
||
// 写入设备(22个寄存器)
|
||
await _modbusService.WriteMultipleRegistersAsync(404, allRegisters.ToArray());
|
||
|
||
_logger.LogInfo("参数已成功写入设备");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError("写入设备参数失败", ex);
|
||
throw new Exception($"写入设备失败: {ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
// 参数调整命令
|
||
[RelayCommand]
|
||
private void IncreaseFirstSprayWater() => FirstSprayWaterTime = Math.Min(FirstSprayWaterTime + 1, 60);
|
||
|
||
[RelayCommand]
|
||
private void DecreaseFirstSprayWater() => FirstSprayWaterTime = Math.Max(FirstSprayWaterTime - 1, 0);
|
||
|
||
[RelayCommand]
|
||
private void IncreaseSprayShampoo1() => SprayShampoo1Time = Math.Min(SprayShampoo1Time + 1, 60);
|
||
|
||
[RelayCommand]
|
||
private void DecreaseSprayShampoo1() => SprayShampoo1Time = Math.Max(SprayShampoo1Time - 1, 0);
|
||
|
||
[RelayCommand]
|
||
private void IncreaseAfterShampoo1Spray() => AfterShampoo1SprayTime = Math.Min(AfterShampoo1SprayTime + 1, 60);
|
||
|
||
[RelayCommand]
|
||
private void DecreaseAfterShampoo1Spray() => AfterShampoo1SprayTime = Math.Max(AfterShampoo1SprayTime - 1, 0);
|
||
|
||
[RelayCommand]
|
||
private void IncreaseSprayShampoo2() => SprayShampoo2Time = Math.Min(SprayShampoo2Time + 1, 60);
|
||
|
||
[RelayCommand]
|
||
private void DecreaseSprayShampoo2() => SprayShampoo2Time = Math.Max(SprayShampoo2Time - 1, 0);
|
||
|
||
[RelayCommand]
|
||
private void IncreaseAfterShampoo2Spray() => AfterShampoo2SprayTime = Math.Min(AfterShampoo2SprayTime + 1, 60);
|
||
|
||
[RelayCommand]
|
||
private void DecreaseAfterShampoo2Spray() => AfterShampoo2SprayTime = Math.Max(AfterShampoo2SprayTime - 1, 0);
|
||
|
||
[RelayCommand]
|
||
private void IncreaseSprayShampoo3() => SprayShampoo3Time = Math.Min(SprayShampoo3Time + 1, 60);
|
||
|
||
[RelayCommand]
|
||
private void DecreaseSprayShampoo3() => SprayShampoo3Time = Math.Max(SprayShampoo3Time - 1, 0);
|
||
|
||
[RelayCommand]
|
||
private void IncreaseAfterShampoo3Spray() => AfterShampoo3SprayTime = Math.Min(AfterShampoo3SprayTime + 1, 60);
|
||
|
||
[RelayCommand]
|
||
private void DecreaseAfterShampoo3Spray() => AfterShampoo3SprayTime = Math.Max(AfterShampoo3SprayTime - 1, 0);
|
||
|
||
[RelayCommand]
|
||
private void IncreaseColdAir() => ColdAirTime = Math.Min(ColdAirTime + 1, 60);
|
||
|
||
[RelayCommand]
|
||
private void DecreaseColdAir() => ColdAirTime = Math.Max(ColdAirTime - 1, 0);
|
||
|
||
[RelayCommand]
|
||
private void IncreaseHotAir() => HotAirTime = Math.Min(HotAirTime + 1, 60);
|
||
|
||
[RelayCommand]
|
||
private void DecreaseHotAir() => HotAirTime = Math.Max(HotAirTime - 1, 0);
|
||
|
||
[RelayCommand]
|
||
private void IncreaseUvSterilization() => UvSterilizationTime = Math.Min(UvSterilizationTime + 1, 60);
|
||
|
||
[RelayCommand]
|
||
private void DecreaseUvSterilization() => UvSterilizationTime = Math.Max(UvSterilizationTime - 1, 0);
|
||
|
||
[RelayCommand]
|
||
private void BackToIdle()
|
||
{
|
||
// 如果从设置页面返回,且之前不是待机界面,则返回到之前的视图
|
||
if (CurrentView == "Settings" && _previousView != "Idle" && _previousView != "Settings")
|
||
{
|
||
CurrentView = _previousView;
|
||
ViewChanged?.Invoke(_previousView);
|
||
|
||
// 根据返回的视图设置相应的状态消息
|
||
switch (_previousView)
|
||
{
|
||
case "Washing":
|
||
StatusMessage = "洗护进行中...";
|
||
_logger.LogInfo("从设置返回到洗护界面");
|
||
break;
|
||
case "Payment":
|
||
StatusMessage = "请选择套餐";
|
||
_logger.LogInfo("从设置返回到支付界面");
|
||
break;
|
||
case "QRCode":
|
||
StatusMessage = $"请扫码支付 ¥{SelectedPackage?.Price}";
|
||
_logger.LogInfo("从设置返回到二维码支付界面");
|
||
break;
|
||
default:
|
||
StatusMessage = "系统就绪,请点击开始";
|
||
_logger.LogInfo("从设置返回到待机界面");
|
||
break;
|
||
}
|
||
|
||
// 重置之前的视图状态
|
||
_previousView = "Idle";
|
||
return;
|
||
}
|
||
|
||
// 默认返回到待机界面
|
||
CurrentView = "Idle";
|
||
ViewChanged?.Invoke("Idle");
|
||
StatusMessage = "系统就绪,请点击开始";
|
||
SelectedPackage = null;
|
||
CurrentOrder = null;
|
||
IsDoorOpen = false;
|
||
|
||
// 重置洗护步骤状态
|
||
foreach (var step in WashSteps)
|
||
{
|
||
step.Status = "等待中";
|
||
step.IsActive = false;
|
||
}
|
||
|
||
_previousView = "Idle";
|
||
_logger.LogInfo("返回待机界面");
|
||
}
|
||
|
||
[RelayCommand]
|
||
private async Task SelectPackage(Package package)
|
||
{
|
||
if (package == null) return;
|
||
|
||
SelectedPackage = package;
|
||
_logger.LogInfo($"选择套餐: {package.Name}");
|
||
|
||
try
|
||
{
|
||
// 创建订单
|
||
CurrentOrder = await _apiService.CreateOrderAsync(package.Id);
|
||
StatusMessage = $"订单创建成功,请扫码支付 ¥{package.Price}";
|
||
_logger.LogInfo($"订单创建成功,订单ID: {CurrentOrder?.Id}");
|
||
|
||
// 切换到二维码支付界面
|
||
CurrentView = "QRCode";
|
||
ViewChanged?.Invoke("QRCode");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError("创建订单失败", ex);
|
||
StatusMessage = $"创建订单失败: {ex.Message}";
|
||
MessageBox.Show($"创建订单失败\n\n{ex.Message}", "错误",
|
||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||
}
|
||
}
|
||
|
||
[RelayCommand]
|
||
private void CancelPayment()
|
||
{
|
||
_logger.LogInfo("取消支付");
|
||
CurrentOrder = null;
|
||
SelectedPackage = null;
|
||
CurrentView = "Payment";
|
||
ViewChanged?.Invoke("Payment");
|
||
StatusMessage = "已取消支付,请重新选择套餐";
|
||
}
|
||
|
||
[RelayCommand]
|
||
private async Task CreateOrderAsync()
|
||
{
|
||
if (SelectedPackage == null)
|
||
{
|
||
MessageBox.Show("请先选择套餐", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
_logger.LogInfo($"创建订单,套餐ID: {SelectedPackage.Id}");
|
||
CurrentOrder = await _apiService.CreateOrderAsync(SelectedPackage.Id);
|
||
StatusMessage = $"订单创建成功,订单号: {CurrentOrder?.Id},请支付 ¥{SelectedPackage.Price}";
|
||
_logger.LogInfo($"订单创建成功,订单ID: {CurrentOrder?.Id}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError("创建订单失败", ex);
|
||
StatusMessage = $"创建订单失败: {ex.Message}";
|
||
MessageBox.Show($"创建订单失败\n\n{ex.Message}", "错误",
|
||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||
}
|
||
}
|
||
|
||
[RelayCommand]
|
||
private async Task SimulatePaymentAsync()
|
||
{
|
||
if (CurrentOrder == null)
|
||
{
|
||
MessageBox.Show("请先创建订单", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
_logger.LogInfo($"模拟支付,订单ID: {CurrentOrder.Id}");
|
||
|
||
// 模拟支付处理延迟
|
||
StatusMessage = "正在处理支付...";
|
||
await Task.Delay(1500);
|
||
|
||
// 确认支付
|
||
CurrentOrder = await _apiService.ConfirmPaymentAsync(CurrentOrder.Id);
|
||
StatusMessage = "支付成功!设备门已打开,请将宠物放入";
|
||
IsDoorOpen = true;
|
||
_logger.LogInfo("支付成功,门已打开");
|
||
|
||
// 显示支付成功提示
|
||
MessageBox.Show("支付成功!\n\n设备门已自动打开\n请将宠物放入设备后关闭门开始洗护",
|
||
"支付成功", MessageBoxButton.OK, MessageBoxImage.Information);
|
||
|
||
// 返回待机界面,显示关门按钮
|
||
CurrentView = "Idle";
|
||
ViewChanged?.Invoke("Idle");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError("支付失败", ex);
|
||
StatusMessage = $"支付失败: {ex.Message}";
|
||
MessageBox.Show($"支付失败\n\n{ex.Message}", "错误",
|
||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||
}
|
||
}
|
||
|
||
[RelayCommand]
|
||
private async Task CloseDoorAsync()
|
||
{
|
||
if (CurrentOrder == null || !IsDoorOpen)
|
||
{
|
||
MessageBox.Show("门未打开", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
_logger.LogInfo($"关门,订单ID: {CurrentOrder.Id}");
|
||
|
||
// 通过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 = "门已关闭,清洗即将开始...";
|
||
_logger.LogInfo("门已关闭,等待清洗开始");
|
||
|
||
// 切换到洗护界面
|
||
CurrentView = "Washing";
|
||
ViewChanged?.Invoke("Washing");
|
||
IsWashing = true;
|
||
CurrentStep = "第一次冲水";
|
||
WashProgress = 0;
|
||
|
||
// 开始模拟洗护流程
|
||
_ = SimulateWashingProcessAsync();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError("关门失败", ex);
|
||
StatusMessage = $"关门失败: {ex.Message}";
|
||
MessageBox.Show($"关门失败\n\n{ex.Message}", "错误",
|
||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||
}
|
||
}
|
||
|
||
private async Task SimulateWashingProcessAsync()
|
||
{
|
||
try
|
||
{
|
||
// 将分钟转换为秒
|
||
var steps = new[]
|
||
{
|
||
("第一次冲水", FirstSprayWaterTime * 60),
|
||
("沐浴露喷洒", SprayShampoo1Time * 60),
|
||
("第二次冲水", AfterShampoo1SprayTime * 60),
|
||
("香波喷洒", SprayShampoo2Time * 60),
|
||
("第三次冲水", AfterShampoo2SprayTime * 60),
|
||
("护理液喷洒", SprayShampoo3Time * 60),
|
||
("第四次冲水", AfterShampoo3SprayTime * 60),
|
||
("热风吹毛", HotAirTime * 60),
|
||
("冷热风混合", ColdAirTime * 60)
|
||
// 紫外线杀菌不在洗护流程中
|
||
};
|
||
|
||
int totalDuration = steps.Sum(s => s.Item2);
|
||
int elapsed = 0;
|
||
|
||
for (int stepIndex = 0; stepIndex < steps.Length; stepIndex++)
|
||
{
|
||
var (stepName, duration) = steps[stepIndex];
|
||
CurrentStep = stepName;
|
||
_logger.LogInfo($"开始步骤: {stepName}, 时长: {duration / 60}分钟");
|
||
|
||
// 更新步骤状态
|
||
Application.Current.Dispatcher.Invoke(() =>
|
||
{
|
||
// 标记之前的步骤为已完成
|
||
for (int i = 0; i < stepIndex; i++)
|
||
{
|
||
WashSteps[i].Status = "已完成";
|
||
WashSteps[i].IsActive = false;
|
||
}
|
||
|
||
// 标记当前步骤为进行中
|
||
WashSteps[stepIndex].Status = "进行中";
|
||
WashSteps[stepIndex].IsActive = true;
|
||
|
||
// 标记后续步骤为等待中
|
||
for (int i = stepIndex + 1; i < WashSteps.Count; i++)
|
||
{
|
||
WashSteps[i].Status = "等待中";
|
||
WashSteps[i].IsActive = false;
|
||
}
|
||
});
|
||
|
||
for (int i = 0; i <= duration; i++)
|
||
{
|
||
if (!IsWashing) return; // 如果被停止,退出
|
||
|
||
elapsed++;
|
||
WashProgress = (int)((double)elapsed / totalDuration * 100);
|
||
|
||
int remaining = totalDuration - elapsed;
|
||
int minutes = remaining / 60;
|
||
int seconds = remaining % 60;
|
||
RemainingTime = $"{minutes:D2}:{seconds:D2}";
|
||
|
||
// 模拟温度变化
|
||
UpdateTemperatures(stepName, i, duration);
|
||
|
||
await Task.Delay(100); // 加速模拟,实际应为1000ms
|
||
}
|
||
}
|
||
|
||
// 标记所有步骤为已完成
|
||
Application.Current.Dispatcher.Invoke(() =>
|
||
{
|
||
foreach (var step in WashSteps)
|
||
{
|
||
step.Status = "已完成";
|
||
step.IsActive = false;
|
||
}
|
||
});
|
||
|
||
// 洗护流程完成,自动开门
|
||
_logger.LogInfo("洗护流程完成,自动开门");
|
||
IsDoorOpen = true;
|
||
StatusMessage = "洗护完成,请取走宠物";
|
||
CurrentStep = "请取走宠物";
|
||
|
||
// 等待门关闭(模拟宠物取走后关门)
|
||
await WaitForDoorCloseAsync();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError("洗护流程失败", ex);
|
||
StatusMessage = $"洗护流程失败: {ex.Message}";
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 等待门关闭(宠物取走后)
|
||
/// </summary>
|
||
private async Task WaitForDoorCloseAsync()
|
||
{
|
||
_logger.LogInfo("等待门关闭...");
|
||
|
||
// 在实际生产环境中,这里应该监听门传感器信号
|
||
// 这里模拟等待10秒后自动关门
|
||
await Task.Delay(10000);
|
||
|
||
IsDoorOpen = false;
|
||
_logger.LogInfo("门已关闭,开始清理和杀菌流程");
|
||
|
||
// 门关闭后,开始清理和杀菌流程
|
||
await StartCleaningAndSterilizationAsync();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清理和紫外线杀菌流程(独立于洗护流程)
|
||
/// 关门后冲水2分钟清理笼子内多余狗毛,同时紫外线灯开启杀菌
|
||
/// </summary>
|
||
private async Task StartCleaningAndSterilizationAsync()
|
||
{
|
||
try
|
||
{
|
||
// 显示清理弹窗(不可手动关闭)
|
||
IsCleaningDialogOpen = true;
|
||
CleaningProgress = 0;
|
||
|
||
_logger.LogInfo("开始清理和杀菌流程");
|
||
|
||
// 冲水2分钟 + 紫外线杀菌时间
|
||
int flushDuration = 2 * 60; // 2分钟冲水
|
||
int uvDuration = UvSterilizationTime * 60; // 紫外线杀菌时间
|
||
int totalDuration = flushDuration + uvDuration;
|
||
|
||
// 第一阶段:冲水2分钟清理狗毛,同时开启紫外线灯
|
||
_logger.LogInfo("开始冲水清理狗毛,同时开启紫外线灯");
|
||
CleaningMessage = "正在冲水清理笼子...";
|
||
|
||
for (int i = 0; i <= flushDuration; i++)
|
||
{
|
||
CleaningProgress = (int)((double)i / totalDuration * 100);
|
||
await Task.Delay(100);
|
||
}
|
||
|
||
// 第二阶段:继续紫外线杀菌
|
||
_logger.LogInfo("冲水完成,继续紫外线杀菌");
|
||
CleaningMessage = "正在紫外线杀菌...";
|
||
|
||
for (int i = flushDuration; i <= totalDuration; i++)
|
||
{
|
||
CleaningProgress = (int)((double)i / totalDuration * 100);
|
||
await Task.Delay(100);
|
||
}
|
||
|
||
CleaningProgress = 100;
|
||
CleaningMessage = "清理完成!";
|
||
await Task.Delay(1000);
|
||
|
||
// 关闭清理弹窗
|
||
IsCleaningDialogOpen = false;
|
||
|
||
_logger.LogInfo("清理和杀菌流程完成");
|
||
|
||
// 全部完成,返回待机界面
|
||
await CompleteWashingAsync();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError("清理和杀菌流程失败", ex);
|
||
StatusMessage = $"清理失败: {ex.Message}";
|
||
IsCleaningDialogOpen = false;
|
||
}
|
||
}
|
||
|
||
private async Task CompleteWashingAsync()
|
||
{
|
||
if (CurrentOrder == null) return;
|
||
|
||
try
|
||
{
|
||
_logger.LogInfo($"清洗完成,订单ID: {CurrentOrder.Id}");
|
||
|
||
// 发送清洗完成状态
|
||
await _mqttService.PublishAsync("device/status", new
|
||
{
|
||
status = "completed",
|
||
orderId = CurrentOrder.Id,
|
||
timestamp = DateTime.Now
|
||
});
|
||
|
||
CurrentOrder = await _apiService.UpdateOrderStatusAsync(CurrentOrder.Id, OrderStatus.Completed);
|
||
|
||
IsWashing = false;
|
||
IsDoorOpen = true;
|
||
StatusMessage = "清洗完成!请取出宠物";
|
||
|
||
MessageBox.Show("清洗完成!\n\n感谢使用无人自动洗宠机", "完成",
|
||
MessageBoxButton.OK, MessageBoxImage.Information);
|
||
|
||
_logger.LogInfo("订单流程完成");
|
||
|
||
// 返回待机界面
|
||
CurrentOrder = null;
|
||
SelectedPackage = null;
|
||
BackToIdle();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError("完成流程失败", ex);
|
||
StatusMessage = $"完成流程失败: {ex.Message}";
|
||
}
|
||
}
|
||
|
||
private void OnMqttMessageReceived(string topic, string payload)
|
||
{
|
||
Application.Current.Dispatcher.Invoke(() =>
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInfo($"收到MQTT消息 - Topic: {topic}, Payload: {payload}");
|
||
var message = JsonSerializer.Deserialize<JsonElement>(payload);
|
||
|
||
if (topic == "device/command")
|
||
{
|
||
var command = message.GetProperty("command").GetString();
|
||
|
||
if (command == "open_door")
|
||
{
|
||
IsDoorOpen = true;
|
||
StatusMessage = "设备门已打开,请将宠物放入后点击关门";
|
||
_logger.LogInfo("收到开门指令");
|
||
}
|
||
else if (command == "start_wash")
|
||
{
|
||
IsWashing = true;
|
||
var duration = message.GetProperty("durationMinutes").GetInt32();
|
||
StatusMessage = $"清洗已开始,预计 {duration} 分钟完成";
|
||
_logger.LogInfo($"收到开始清洗指令,时长: {duration}分钟");
|
||
}
|
||
}
|
||
else if (topic == "device/status")
|
||
{
|
||
var status = message.GetProperty("status").GetString();
|
||
StatusMessage = $"设备状态: {status}";
|
||
_logger.LogInfo($"设备状态更新: {status}");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError("处理MQTT消息失败", ex);
|
||
StatusMessage = $"处理消息失败: {ex.Message}";
|
||
}
|
||
});
|
||
}
|
||
|
||
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)
|
||
{
|
||
// 根据不同步骤模拟温度变化
|
||
var random = new Random();
|
||
double progress = (double)currentTime / totalTime;
|
||
|
||
switch (stepName)
|
||
{
|
||
case "第一次冲水":
|
||
case "第二次冲水":
|
||
case "第三次冲水":
|
||
case "第四次冲水":
|
||
// 冲水阶段:水温在38-42度之间波动
|
||
WaterTemperature = 40.0 + (random.NextDouble() - 0.5) * 4;
|
||
RoomTemperature = 25.0 + (random.NextDouble() - 0.5) * 2;
|
||
break;
|
||
|
||
case "沐浴露喷洒":
|
||
case "香波喷洒":
|
||
case "护理液喷洒":
|
||
// 喷洒阶段:水温略低
|
||
WaterTemperature = 38.0 + (random.NextDouble() - 0.5) * 2;
|
||
RoomTemperature = 25.0 + (random.NextDouble() - 0.5) * 2;
|
||
break;
|
||
|
||
case "热风吹毛":
|
||
// 热风阶段:水温降低,室温升高
|
||
WaterTemperature = 35.0 - progress * 10 + (random.NextDouble() - 0.5) * 2;
|
||
RoomTemperature = 25.0 + progress * 8 + (random.NextDouble() - 0.5) * 2;
|
||
break;
|
||
|
||
case "冷热风混合":
|
||
// 混合风阶段:温度逐渐降低
|
||
WaterTemperature = 25.0 + (random.NextDouble() - 0.5) * 2;
|
||
RoomTemperature = 30.0 - progress * 5 + (random.NextDouble() - 0.5) * 2;
|
||
break;
|
||
|
||
case "紫外线杀菌":
|
||
// 紫外线杀菌阶段:温度稳定
|
||
WaterTemperature = 25.0 + (random.NextDouble() - 0.5) * 1;
|
||
RoomTemperature = 26.0 + (random.NextDouble() - 0.5) * 1;
|
||
break;
|
||
|
||
default:
|
||
// 默认温度
|
||
WaterTemperature = 40.0 + (random.NextDouble() - 0.5) * 2;
|
||
RoomTemperature = 25.0 + (random.NextDouble() - 0.5) * 2;
|
||
break;
|
||
}
|
||
|
||
// 确保温度在合理范围内
|
||
WaterTemperature = Math.Max(20, Math.Min(45, WaterTemperature));
|
||
RoomTemperature = Math.Max(20, Math.Min(35, RoomTemperature));
|
||
}
|
||
}
|
||
|