Files
FootwearTest/FootwearTest/ViewModels/MethodAViewModel.cs
2026-05-30 15:37:45 +08:00

370 lines
13 KiB
C#
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.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Input;
using FootwearTest.Models;
using FootwearTest.Services;
using Microsoft.Extensions.Logging;
namespace FootwearTest.ViewModels;
public partial class MethodAViewModel : ViewModelBase
{
private const int MethodAMinimumSampleCount = 30;
private readonly IDeviceClient _deviceClient;
private readonly DeviceSettings _settings;
private readonly TestFormulaService _formulaService;
private readonly TestRunRepository _repository;
private readonly ILogger<MethodAViewModel> _logger;
public MethodAViewModel(
IDeviceClient deviceClient,
DeviceSettings settings,
TestFormulaService formulaService,
TestRunRepository repository,
ILogger<MethodAViewModel> logger)
{
_deviceClient = deviceClient;
_settings = settings;
_formulaService = formulaService;
_repository = repository;
_logger = logger;
MeasureSkinResistanceCommand = new AsyncRelayCommand(MeasureSkinResistanceAsync);
RunWholeShoeCommand = new AsyncRelayCommand(RunWholeShoeAsync);
StopCommand = new AsyncRelayCommand(StopAsync);
}
public ObservableCollection<MethodASampleRecord> Samples { get; } = [];
public IAsyncRelayCommand MeasureSkinResistanceCommand { get; }
public IAsyncRelayCommand RunWholeShoeCommand { get; }
public IAsyncRelayCommand StopCommand { get; }
private string _sampleDescription = "整鞋样品 1# / 2#";
private string _statusText = "待机";
private bool _isRunning;
private double _skinMoistureResistance = 6.0;
private double _averageMoistureResistance;
private double _averageThermalResistance;
private double _moistureCvPercent;
private double _thermalCvPercent;
private string _resultText = "尚未开始试验";
public string SampleDescription
{
get => _sampleDescription;
set => SetProperty(ref _sampleDescription, value);
}
public string StatusText
{
get => _statusText;
set => SetProperty(ref _statusText, value);
}
public bool IsRunning
{
get => _isRunning;
set => SetProperty(ref _isRunning, value);
}
public double SkinMoistureResistance
{
get => _skinMoistureResistance;
set => SetProperty(ref _skinMoistureResistance, value);
}
public double AverageMoistureResistance
{
get => _averageMoistureResistance;
set => SetProperty(ref _averageMoistureResistance, value);
}
public double AverageThermalResistance
{
get => _averageThermalResistance;
set => SetProperty(ref _averageThermalResistance, value);
}
public double MoistureCvPercent
{
get => _moistureCvPercent;
set => SetProperty(ref _moistureCvPercent, value);
}
public double ThermalCvPercent
{
get => _thermalCvPercent;
set => SetProperty(ref _thermalCvPercent, value);
}
public string ResultText
{
get => _resultText;
set => SetProperty(ref _resultText, value);
}
private async Task MeasureSkinResistanceAsync()
{
if (IsRunning)
{
return;
}
try
{
_logger.LogInformation("Method A skin moisture resistance started. SampleDescription={SampleDescription}", SampleDescription);
await EnsureConnectedAsync();
IsRunning = true;
StatusText = "等待假脚/环境稳定";
Samples.Clear();
await _deviceClient.SetOutputsAsync(true, true, true);
await WaitForMethodAStabilityAsync();
if (!IsRunning)
{
StatusText = "已停止";
return;
}
StatusText = "皮肤湿阻测定中";
while (IsRunning)
{
await Task.Delay(GetMethodASamplingInterval());
if (!IsRunning)
{
break;
}
var snapshot = await _deviceClient.ReadSnapshotAsync();
var sample = _formulaService.CalculateSkinMoistureResistanceSample(Samples.Count + 1, snapshot, _settings.FootAreaSquareMeters);
Samples.Add(sample);
SkinMoistureResistance = Round(_formulaService.Average(Samples.Select(item => item.MoistureResistance)), 3);
MoistureCvPercent = Round(_formulaService.CoefficientOfVariationPercent(Samples.Select(item => item.MoistureResistance)), 2);
ResultText = $"皮肤湿阻: 已采集 {Samples.Count} 次Res {SkinMoistureResistance:F3} Pa·m²/WCV {MoistureCvPercent:F2}%";
if (HasReachedVariationCoefficient(Samples.Select(item => item.MoistureResistance)))
{
break;
}
}
if (!IsRunning)
{
StatusText = "已停止";
return;
}
await _deviceClient.SetOutputsAsync(false, true, false);
ThermalCvPercent = 0;
AverageMoistureResistance = 0;
AverageThermalResistance = 0;
ResultText = $"皮肤湿阻: Res {SkinMoistureResistance:F3} Pa·m²/WCV {MoistureCvPercent:F2}%";
var id = await SaveRunAsync("方法 A - 皮肤湿阻", true);
StatusText = "皮肤湿阻测定完成";
_logger.LogInformation("Method A skin moisture resistance completed. RecordId={RecordId}, Samples={Samples}, Result={ResultText}", id, Samples.Count, ResultText);
}
catch (Exception ex)
{
StatusText = $"皮肤湿阻测定失败: {ex.Message}";
_logger.LogError(ex, "Method A skin moisture resistance failed. SampleDescription={SampleDescription}", SampleDescription);
await StopOutputsAfterFailureAsync("方法 A 皮肤湿阻失败后关闭输出");
}
finally
{
IsRunning = false;
}
}
private async Task RunWholeShoeAsync()
{
if (IsRunning)
{
return;
}
try
{
_logger.LogInformation("Method A whole shoe test started. SampleDescription={SampleDescription}", SampleDescription);
await EnsureConnectedAsync();
IsRunning = true;
StatusText = "等待假脚/环境稳定";
Samples.Clear();
await _deviceClient.SetOutputsAsync(true, true, true);
await WaitForMethodAStabilityAsync();
if (!IsRunning)
{
StatusText = "已停止";
return;
}
StatusText = "整鞋热阻/湿阻测定中";
while (IsRunning)
{
await Task.Delay(GetMethodASamplingInterval());
if (!IsRunning)
{
break;
}
var snapshot = await _deviceClient.ReadSnapshotAsync();
Samples.Add(_formulaService.CalculateMethodASample(Samples.Count + 1, snapshot, _settings.FootAreaSquareMeters, SkinMoistureResistance));
UpdateMethodAResult("整鞋热阻/湿阻");
if (HasReachedWholeShoeVariationCoefficient())
{
break;
}
}
if (!IsRunning)
{
StatusText = "已停止";
return;
}
await _deviceClient.SetOutputsAsync(false, true, false);
var passed =
_formulaService.PassesTenPercentDeviation(Samples.Select(sample => sample.MoistureResistance)) &&
_formulaService.PassesTenPercentDeviation(Samples.Select(sample => sample.ThermalResistance));
UpdateMethodAResult("整鞋热阻/湿阻");
var id = await SaveRunAsync("方法 A - 整鞋热阻/湿阻", passed);
StatusText = passed ? "试验完成" : "结果偏差超过 ±10%,建议重测";
_logger.LogInformation("Method A whole shoe test completed. RecordId={RecordId}, Passed={Passed}, Samples={Samples}, Result={ResultText}", id, passed, Samples.Count, ResultText);
}
catch (Exception ex)
{
StatusText = $"整鞋热阻/湿阻测定失败: {ex.Message}";
_logger.LogError(ex, "Method A whole shoe test failed. SampleDescription={SampleDescription}", SampleDescription);
await StopOutputsAfterFailureAsync("方法 A 整鞋试验失败后关闭输出");
}
finally
{
IsRunning = false;
}
}
private async Task StopAsync()
{
try
{
_logger.LogWarning("Operator stopped Method A test");
await _deviceClient.SetOutputsAsync(false, false, false);
IsRunning = false;
StatusText = "已停止";
}
catch (Exception ex)
{
StatusText = $"停止失败: {ex.Message}";
_logger.LogError(ex, "Method A stop command failed");
}
}
private TimeSpan GetMethodASamplingInterval()
{
return _settings.UseSimulator ? TimeSpan.FromMilliseconds(100) : TimeSpan.FromMinutes(1);
}
private TimeSpan GetMethodAStabilityPollInterval()
{
return _settings.UseSimulator ? TimeSpan.FromMilliseconds(100) : TimeSpan.FromSeconds(5);
}
private async Task WaitForMethodAStabilityAsync()
{
while (IsRunning)
{
var snapshot = await _deviceClient.ReadSnapshotAsync();
if (IsMethodAStable(snapshot))
{
return;
}
StatusText = $"等待稳定: 假脚 {snapshot.FootTemperatureC:F1} ℃,环境 {snapshot.EnvironmentTemperatureC:F1} ℃ / {snapshot.EnvironmentHumidityPercent:F0}%";
await Task.Delay(GetMethodAStabilityPollInterval());
}
}
private bool IsMethodAStable(DeviceSnapshot snapshot)
{
return Math.Abs(snapshot.FootTemperatureC - _settings.MethodATargetTemperatureC) <= 0.3 &&
Math.Abs(snapshot.EnvironmentTemperatureC - _settings.EnvironmentTemperatureC) <= 2.0 &&
Math.Abs(snapshot.EnvironmentHumidityPercent - _settings.EnvironmentHumidityPercent) <= 5.0;
}
private bool HasReachedVariationCoefficient(IEnumerable<double> values)
{
var list = values.ToArray();
return list.Length >= MethodAMinimumSampleCount &&
_formulaService.CoefficientOfVariationPercent(list) <= _settings.CoefficientOfVariationLimitPercent;
}
private bool HasReachedWholeShoeVariationCoefficient()
{
return Samples.Count >= MethodAMinimumSampleCount &&
HasReachedVariationCoefficient(Samples.Select(sample => sample.MoistureResistance)) &&
HasReachedVariationCoefficient(Samples.Select(sample => sample.ThermalResistance));
}
private async Task EnsureConnectedAsync()
{
if (!_deviceClient.IsConnected)
{
await _deviceClient.ConnectAsync();
}
}
private void UpdateMethodAResult(string stage)
{
AverageMoistureResistance = Round(_formulaService.Average(Samples.Select(sample => sample.MoistureResistance)), 3);
AverageThermalResistance = Round(_formulaService.Average(Samples.Select(sample => sample.ThermalResistance)), 4);
MoistureCvPercent = Round(_formulaService.CoefficientOfVariationPercent(Samples.Select(sample => sample.MoistureResistance)), 2);
ThermalCvPercent = Round(_formulaService.CoefficientOfVariationPercent(Samples.Select(sample => sample.ThermalResistance)), 2);
ResultText = $"{stage}: 湿阻 {AverageMoistureResistance:F3} Pa·m²/W热阻 {AverageThermalResistance:F4} m²·℃/WCV {ThermalCvPercent:F2}%";
}
private static double Round(double value, int digits)
{
return Math.Round(value, digits, MidpointRounding.AwayFromZero);
}
private async Task StopOutputsAfterFailureAsync(string reason)
{
try
{
await _deviceClient.SetOutputsAsync(false, false, false);
_logger.LogWarning("Outputs forced off after Method A failure. Reason={Reason}", reason);
}
catch (Exception stopEx)
{
_logger.LogError(stopEx, "Failed to force outputs off after Method A failure. Reason={Reason}", reason);
}
}
private async Task<long> SaveRunAsync(string method, bool isValid)
{
var result = new MethodAResult(
method,
SkinMoistureResistance,
AverageMoistureResistance,
AverageThermalResistance,
MoistureCvPercent,
ThermalCvPercent,
isValid);
var data = JsonSerializer.Serialize(new { Result = result, Samples }, new JsonSerializerOptions { WriteIndented = true });
return await _repository.SaveAsync(new TestRunRecord(
0,
method,
SampleDescription,
$"假脚 {_settings.MethodATargetTemperatureC:F1} ℃;环境 {_settings.EnvironmentTemperatureC:F1} ℃ / {_settings.EnvironmentHumidityPercent:F0}%;风速 {_settings.MethodAAirSpeedMetersPerSecond:F2} m/s",
ResultText,
data,
isValid,
DateTime.Now));
}
}