添加项目文件。
This commit is contained in:
121
App.xaml
Normal file
121
App.xaml
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
<Application x:Class="HME_MoistureLossMeter.App"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="clr-namespace:HME_MoistureLossMeter"
|
||||||
|
xmlns:converters="clr-namespace:HME_MoistureLossMeter.Converters"
|
||||||
|
xmlns:vm="clr-namespace:HME_MoistureLossMeter.ViewModels"
|
||||||
|
xmlns:views="clr-namespace:HME_MoistureLossMeter.Views"
|
||||||
|
|
||||||
|
>
|
||||||
|
<Application.Resources>
|
||||||
|
<ResourceDictionary>
|
||||||
|
<!-- 颜色资源 -->
|
||||||
|
<Color x:Key="PrimaryColor">#2C3E50</Color>
|
||||||
|
<Color x:Key="AccentColor">#3498DB</Color>
|
||||||
|
<Color x:Key="SuccessColor">#27AE60</Color>
|
||||||
|
<Color x:Key="WarningColor">#F39C12</Color>
|
||||||
|
<Color x:Key="DangerColor">#E74C3C</Color>
|
||||||
|
<Color x:Key="BackgroundColor">#ECF0F1</Color>
|
||||||
|
<Color x:Key="PanelBackgroundColor">#FFFFFF</Color>
|
||||||
|
|
||||||
|
<!-- 画刷 -->
|
||||||
|
<SolidColorBrush x:Key="PrimaryBrush" Color="{StaticResource PrimaryColor}"/>
|
||||||
|
<SolidColorBrush x:Key="AccentBrush" Color="{StaticResource AccentColor}"/>
|
||||||
|
<SolidColorBrush x:Key="SuccessBrush" Color="{StaticResource SuccessColor}"/>
|
||||||
|
<SolidColorBrush x:Key="WarningBrush" Color="{StaticResource WarningColor}"/>
|
||||||
|
<SolidColorBrush x:Key="DangerBrush" Color="{StaticResource DangerColor}"/>
|
||||||
|
<SolidColorBrush x:Key="BackgroundBrush" Color="{StaticResource BackgroundColor}"/>
|
||||||
|
<SolidColorBrush x:Key="PanelBackgroundBrush" Color="{StaticResource PanelBackgroundColor}"/>
|
||||||
|
|
||||||
|
<!-- 转换器 -->
|
||||||
|
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
|
||||||
|
|
||||||
|
<!-- 样式 -->
|
||||||
|
<Style TargetType="Button">
|
||||||
|
<Setter Property="FontSize" Value="14"/>
|
||||||
|
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||||
|
<Setter Property="Padding" Value="12,6"/>
|
||||||
|
<Setter Property="Margin" Value="4"/>
|
||||||
|
<Setter Property="Cursor" Value="Hand"/>
|
||||||
|
<Setter Property="Background" Value="{StaticResource AccentBrush}"/>
|
||||||
|
<Setter Property="Foreground" Value="White"/>
|
||||||
|
<Setter Property="BorderThickness" Value="0"/>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="Button">
|
||||||
|
<Border Background="{TemplateBinding Background}"
|
||||||
|
BorderBrush="{TemplateBinding BorderBrush}"
|
||||||
|
BorderThickness="{TemplateBinding BorderThickness}"
|
||||||
|
CornerRadius="4">
|
||||||
|
<ContentPresenter HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Margin="{TemplateBinding Padding}"/>
|
||||||
|
</Border>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter Property="Background" Value="#2980B9"/>
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsPressed" Value="True">
|
||||||
|
<Setter Property="Background" Value="#1A5276"/>
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsEnabled" Value="False">
|
||||||
|
<Setter Property="Background" Value="#BDC3C7"/>
|
||||||
|
<Setter Property="Foreground" Value="#7F8C8D"/>
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="TextBox">
|
||||||
|
<Setter Property="FontSize" Value="14"/>
|
||||||
|
<Setter Property="Padding" Value="4,2"/>
|
||||||
|
<Setter Property="BorderBrush" Value="#BDC3C7"/>
|
||||||
|
<Setter Property="BorderThickness" Value="1"/>
|
||||||
|
<Setter Property="Background" Value="{StaticResource PanelBackgroundBrush}"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="Label">
|
||||||
|
<Setter Property="FontSize" Value="14"/>
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource PrimaryBrush}"/>
|
||||||
|
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="TextBlock" x:Key="DisplayValueStyle">
|
||||||
|
<Setter Property="FontSize" Value="18"/>
|
||||||
|
<Setter Property="FontWeight" Value="Bold"/>
|
||||||
|
<Setter Property="Foreground" Value="{StaticResource AccentBrush}"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="TextBlock" x:Key="DisplayLabelStyle">
|
||||||
|
<Setter Property="FontSize" Value="12"/>
|
||||||
|
<Setter Property="Foreground" Value="#7F8C8D"/>
|
||||||
|
<Setter Property="TextWrapping" Value="Wrap"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="Border" x:Key="PanelBorderStyle">
|
||||||
|
<Setter Property="Background" Value="{StaticResource PanelBackgroundBrush}"/>
|
||||||
|
<Setter Property="BorderBrush" Value="#E0E0E0"/>
|
||||||
|
<Setter Property="BorderThickness" Value="1"/>
|
||||||
|
<Setter Property="CornerRadius" Value="4"/>
|
||||||
|
<Setter Property="Padding" Value="8"/>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<DataTemplate DataType="{x:Type vm:TestViewModel}">
|
||||||
|
<views:TestView />
|
||||||
|
</DataTemplate>
|
||||||
|
<DataTemplate DataType="{x:Type vm:ManualControlViewModel}">
|
||||||
|
<views:ManualControlView />
|
||||||
|
</DataTemplate>
|
||||||
|
<DataTemplate DataType="{x:Type vm:RecordViewModel}">
|
||||||
|
<views:RecordView />
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
|
</ResourceDictionary>
|
||||||
|
|
||||||
|
|
||||||
|
</Application.Resources>
|
||||||
|
</Application>
|
||||||
94
App.xaml.cs
Normal file
94
App.xaml.cs
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
using HME_MoistureLossMeter.Models;
|
||||||
|
using HME_MoistureLossMeter.Services;
|
||||||
|
using HME_MoistureLossMeter.ViewModels;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Serilog;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace HME_MoistureLossMeter
|
||||||
|
{
|
||||||
|
public partial class App : Application
|
||||||
|
{
|
||||||
|
private IHost _host;
|
||||||
|
public static IServiceProvider ServiceProvider { get; private set; }
|
||||||
|
|
||||||
|
protected override void OnStartup(StartupEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnStartup(e);
|
||||||
|
|
||||||
|
// 配置日志
|
||||||
|
Log.Logger = new LoggerConfiguration()
|
||||||
|
.MinimumLevel.Information()
|
||||||
|
.WriteTo.File(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logs", "log-.txt"),
|
||||||
|
rollingInterval: RollingInterval.Day,
|
||||||
|
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}] {Message:lj}{NewLine}{Exception}")
|
||||||
|
.CreateLogger();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_host = Host.CreateDefaultBuilder(e.Args)
|
||||||
|
.ConfigureServices((context, services) =>
|
||||||
|
{
|
||||||
|
// 配置
|
||||||
|
var plcConfig = context.Configuration.GetSection("PlcConfiguration").Get<PlcConfiguration>();
|
||||||
|
var mesConfig = context.Configuration.GetSection("MesConfiguration").Get<MesConfiguration>();
|
||||||
|
var deviceConfig = context.Configuration.GetSection("DeviceConfiguration").Get<DeviceConfiguration>();
|
||||||
|
|
||||||
|
services.AddSingleton(plcConfig);
|
||||||
|
services.AddSingleton(mesConfig);
|
||||||
|
services.AddSingleton(deviceConfig);
|
||||||
|
|
||||||
|
// 服务
|
||||||
|
services.AddSingleton<IPlcService, ModbusTcpPlcService>();
|
||||||
|
services.AddSingleton<IMesService, MesHttpService>();
|
||||||
|
|
||||||
|
// ViewModels
|
||||||
|
services.AddSingleton<MainViewModel>();
|
||||||
|
services.AddSingleton<TestViewModel>();
|
||||||
|
services.AddSingleton<ManualControlViewModel>();
|
||||||
|
services.AddSingleton<RecordViewModel>();
|
||||||
|
services.AddSingleton<ReportViewModel>();
|
||||||
|
|
||||||
|
|
||||||
|
// HttpClient
|
||||||
|
services.AddHttpClient<IMesService, MesHttpService>(client =>
|
||||||
|
{
|
||||||
|
client.BaseAddress = new Uri(mesConfig.BaseUrl);
|
||||||
|
client.Timeout = TimeSpan.FromSeconds(mesConfig.TimeoutSeconds);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.UseSerilog()
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
ServiceProvider = _host.Services;
|
||||||
|
|
||||||
|
// 初始化并启动服务
|
||||||
|
var plcService = ServiceProvider.GetRequiredService<IPlcService>();
|
||||||
|
var mesService = ServiceProvider.GetRequiredService<IMesService>();
|
||||||
|
|
||||||
|
// 创建主窗口
|
||||||
|
var mainWindow = new MainWindow();
|
||||||
|
mainWindow.DataContext = ServiceProvider.GetRequiredService<MainViewModel>();
|
||||||
|
mainWindow.Show();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Fatal(ex, "应用程序启动失败");
|
||||||
|
MessageBox.Show($"启动失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
|
Shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnExit(ExitEventArgs e)
|
||||||
|
{
|
||||||
|
Log.Information("应用程序关闭");
|
||||||
|
Log.CloseAndFlush();
|
||||||
|
_host?.Dispose();
|
||||||
|
base.OnExit(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
10
AssemblyInfo.cs
Normal file
10
AssemblyInfo.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
[assembly: ThemeInfo(
|
||||||
|
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||||
|
//(used if a resource is not found in the page,
|
||||||
|
// or application resource dictionaries)
|
||||||
|
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||||
|
//(used if a resource is not found in the page,
|
||||||
|
// app, or any theme specific resource dictionaries)
|
||||||
|
)]
|
||||||
16
Converters/BoolToColorConverter.cs
Normal file
16
Converters/BoolToColorConverter.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using System.Windows.Media;
|
||||||
|
|
||||||
|
namespace HME_MoistureLossMeter.Converters
|
||||||
|
{
|
||||||
|
public class BoolToColorConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
return (value is bool b && b) ? Brushes.Green : Brushes.Red;
|
||||||
|
}
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
15
Converters/BoolToStringConverter.cs
Normal file
15
Converters/BoolToStringConverter.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Windows.Data;
|
||||||
|
|
||||||
|
namespace HME_MoistureLossMeter.Converters
|
||||||
|
{
|
||||||
|
public class BoolToStringConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
return (value is bool b && b) ? "运行中" : "已停止";
|
||||||
|
}
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
39
Converters/BoolToVisibilityConverter.cs
Normal file
39
Converters/BoolToVisibilityConverter.cs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using System.Windows.Media;
|
||||||
|
|
||||||
|
namespace HME_MoistureLossMeter.Converters
|
||||||
|
{
|
||||||
|
public class BoolToVisibilityConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if (value is bool boolValue)
|
||||||
|
{
|
||||||
|
if (parameter is string param)
|
||||||
|
{
|
||||||
|
// 用于颜色转换
|
||||||
|
if (param.Contains("Brush") && targetType == typeof(Brush))
|
||||||
|
{
|
||||||
|
var app = Application.Current;
|
||||||
|
return boolValue ? app.FindResource(param) : app.FindResource("PanelBackgroundBrush");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文本转换
|
||||||
|
return boolValue ? param : "";
|
||||||
|
}
|
||||||
|
return boolValue ? Visibility.Visible : Visibility.Collapsed;
|
||||||
|
}
|
||||||
|
return Visibility.Collapsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if (value is Visibility visibility)
|
||||||
|
return visibility == Visibility.Visible;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
Converters/InverseBoolConverter.cs
Normal file
20
Converters/InverseBoolConverter.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Windows.Data;
|
||||||
|
|
||||||
|
namespace HME_MoistureLossMeter.Converters
|
||||||
|
{
|
||||||
|
public class InverseBoolConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if (value is bool b) return !b;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if (value is bool b) return !b;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
132
Models/DeviceDataModel.cs
Normal file
132
Models/DeviceDataModel.cs
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace HME_MoistureLossMeter.Models
|
||||||
|
{
|
||||||
|
public class DeviceDataModel
|
||||||
|
{
|
||||||
|
// 实时数据
|
||||||
|
public float WaterTemp { get; set; } // 水浴温度 D1118
|
||||||
|
public float ChamberTemp { get; set; } // 箱体温度 D1168
|
||||||
|
public float Weight { get; set; } // 重量 D1268
|
||||||
|
public float AirVolume { get; set; } // 空气体积 D4110
|
||||||
|
public float OutletFlow { get; set; } // 出口流量 D1218
|
||||||
|
public float TidalVolume { get; set; } // 潮气量 D300
|
||||||
|
public float Frequency { get; set; } // 频率 D210
|
||||||
|
public int BreathCount { get; set; } // 呼吸次数 D3000
|
||||||
|
public float DryAirFlow { get; set; } // 干燥空气量 D3700
|
||||||
|
|
||||||
|
// 设置参数
|
||||||
|
public int PresetHour { get; set; } // 设定测试时 D1681
|
||||||
|
public int PresetMinute { get; set; } // 设定测试分 D1680
|
||||||
|
public int PresetSecond { get; set; } // 显示时间秒 D3002
|
||||||
|
public int DisplayHour { get; set; } // 显示时 D3004
|
||||||
|
public int DisplayMinute { get; set; } // 显示分 D3003
|
||||||
|
public int DisplaySecond { get; set; } // 显示秒 D3002
|
||||||
|
|
||||||
|
public float ManualSpeed { get; set; } // 手动速度 D218
|
||||||
|
public float TidalCoeff { get; set; } // 潮气量系数 D302
|
||||||
|
public float FreqCoeff { get; set; } // 频率系数 D282
|
||||||
|
|
||||||
|
public int OperatorId { get; set; } // 操作员编号 D3200
|
||||||
|
public string BatchNo { get; set; } = ""; // 生产批号 D3202
|
||||||
|
public int ExperimentId { get; set; } // 实验编号 D3204
|
||||||
|
|
||||||
|
// 测试结果
|
||||||
|
public float InitialMass { get; set; } // 初始质量 D206
|
||||||
|
public float FinalMass { get; set; } // 测后质量 D208
|
||||||
|
public float MoistureLoss { get; set; } // 水分损失 D4106
|
||||||
|
|
||||||
|
// 状态
|
||||||
|
public bool IsTesting { get; set; }
|
||||||
|
public bool IsFault { get; set; }
|
||||||
|
public bool IsHeating { get; set; }
|
||||||
|
public bool IsInhale { get; set; }
|
||||||
|
public bool IsExhale { get; set; }
|
||||||
|
public bool IsRising { get; set; }
|
||||||
|
public bool IsFalling { get; set; }
|
||||||
|
public bool IsZeroing { get; set; }
|
||||||
|
public DateTime Timestamp { get; set; } = DateTime.Now;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TestResultModel
|
||||||
|
{
|
||||||
|
public string DeviceId { get; set; } = "";
|
||||||
|
public string TestFinishTime { get; set; } = "";
|
||||||
|
public string BatchNo { get; set; } = "";
|
||||||
|
public int OperatorId { get; set; }
|
||||||
|
public int? ExperimentId { get; set; }
|
||||||
|
public float InitialMass { get; set; }
|
||||||
|
public float FinalMass { get; set; }
|
||||||
|
public float MoistureLoss { get; set; }
|
||||||
|
public TestConditions TestConditions { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TestConditions
|
||||||
|
{
|
||||||
|
public float? AvgWaterTemp { get; set; }
|
||||||
|
public float? AvgChamberTemp { get; set; }
|
||||||
|
public float? AvgTidalVolume { get; set; }
|
||||||
|
public float? AvgFrequency { get; set; }
|
||||||
|
public float? AvgAirVolume { get; set; }
|
||||||
|
public float? AvgOutletFlow { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class HistoryRecordModel
|
||||||
|
{
|
||||||
|
public int? RecordId { get; set; }
|
||||||
|
public string TestTime { get; set; } = "";
|
||||||
|
public int OperatorId { get; set; }
|
||||||
|
public string BatchNo { get; set; } = "";
|
||||||
|
public int? ExperimentId { get; set; }
|
||||||
|
public float InitialMass { get; set; }
|
||||||
|
public float FinalMass { get; set; }
|
||||||
|
public float MoistureLoss { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class HistoryDataModel
|
||||||
|
{
|
||||||
|
public string DeviceId { get; set; } = "";
|
||||||
|
public List<HistoryRecordModel> Records { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RealtimeDataModel
|
||||||
|
{
|
||||||
|
public string DeviceId { get; set; } = "";
|
||||||
|
public string Timestamp { get; set; } = "";
|
||||||
|
public RealtimeData Realtime { get; set; } = new();
|
||||||
|
public SettingsData Settings { get; set; } = new();
|
||||||
|
public StatusData Status { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RealtimeData
|
||||||
|
{
|
||||||
|
public float WaterTemp { get; set; }
|
||||||
|
public float ChamberTemp { get; set; }
|
||||||
|
public float Weight { get; set; }
|
||||||
|
public float AirVolume { get; set; }
|
||||||
|
public float OutletFlow { get; set; }
|
||||||
|
public float TidalVolume { get; set; }
|
||||||
|
public float Frequency { get; set; }
|
||||||
|
public int BreathRate { get; set; }
|
||||||
|
public float DryAirFlow { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SettingsData
|
||||||
|
{
|
||||||
|
public int? PresetHour { get; set; }
|
||||||
|
public int? PresetMinute { get; set; }
|
||||||
|
public int? PresetSecond { get; set; }
|
||||||
|
public float? ManualSpeed { get; set; }
|
||||||
|
public float? TidalCoeff { get; set; }
|
||||||
|
public float? FreqCoeff { get; set; }
|
||||||
|
public int? OperatorId { get; set; }
|
||||||
|
public string? BatchNo { get; set; }
|
||||||
|
public int? ExperimentId { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class StatusData
|
||||||
|
{
|
||||||
|
public bool IsTesting { get; set; }
|
||||||
|
public bool IsFault { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
30
Models/PlcConfiguration.cs
Normal file
30
Models/PlcConfiguration.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
namespace HME_MoistureLossMeter.Models
|
||||||
|
{
|
||||||
|
public class PlcConfiguration
|
||||||
|
{
|
||||||
|
public string IpAddress { get; set; } = "192.168.1.10";
|
||||||
|
public int Port { get; set; } = 502;
|
||||||
|
public byte SlaveId { get; set; } = 1;
|
||||||
|
public ushort PressureRegister { get; set; } = 1000;
|
||||||
|
public ushort WetFlowRegister { get; set; } = 1002;
|
||||||
|
public ushort WetFlowRegister2 { get; set; } = 1004;
|
||||||
|
public ushort WetFlowRegister3 { get; set; } = 1006;
|
||||||
|
public ushort PressureRegisterStation1 { get; set; } = 1008;
|
||||||
|
public ushort PressureRegisterStation2 { get; set; } = 1010;
|
||||||
|
public ushort PressureRegisterStation3 { get; set; } = 1012;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MesConfiguration
|
||||||
|
{
|
||||||
|
public string BaseUrl { get; set; } = "http://localhost:8080";
|
||||||
|
public string ApiKey { get; set; } = "";
|
||||||
|
public int TimeoutSeconds { get; set; } = 30;
|
||||||
|
public int RetryCount { get; set; } = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DeviceConfiguration
|
||||||
|
{
|
||||||
|
public string DeviceId { get; set; } = "HME-001";
|
||||||
|
public int UpdateIntervalMs { get; set; } = 1000;
|
||||||
|
}
|
||||||
|
}
|
||||||
12
Services/IMesService.cs
Normal file
12
Services/IMesService.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using HME_MoistureLossMeter.Models;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace HME_MoistureLossMeter.Services
|
||||||
|
{
|
||||||
|
public interface IMesService
|
||||||
|
{
|
||||||
|
Task<bool> SendRealtimeDataAsync(RealtimeDataModel data);
|
||||||
|
Task<bool> SendTestResultAsync(TestResultModel result);
|
||||||
|
Task<bool> SendHistoryDataAsync(HistoryDataModel history);
|
||||||
|
}
|
||||||
|
}
|
||||||
23
Services/IPlcService.cs
Normal file
23
Services/IPlcService.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace HME_MoistureLossMeter.Services
|
||||||
|
{
|
||||||
|
public interface IPlcService
|
||||||
|
{
|
||||||
|
Task EnsureConnectedAsync(int retryCount = 3);
|
||||||
|
Task<float> ReadFloatAsync(ushort startAddress);
|
||||||
|
Task<float> ReadPressureAsync();
|
||||||
|
Task<float> ReadWetFlowAsync(int stationId);
|
||||||
|
Task<float> ReadPressureAsync(int stationId);
|
||||||
|
Task<bool> ReadCoilAsync(ushort coilAddress);
|
||||||
|
Task<ushort[]> ReadHoldingRegistersAsync(ushort startAddress, ushort count);
|
||||||
|
Task WriteCoilAsync(ushort coilAddress, bool value);
|
||||||
|
Task WriteRegisterAsync(ushort registerAddress, ushort value);
|
||||||
|
Task WriteSingleRegisterAsync(ushort registerAddress, ushort value);
|
||||||
|
Task WriteMultipleRegistersAsync(ushort registerAddress, float value);
|
||||||
|
Task<int> ReadInt32Async(ushort startAddress);
|
||||||
|
Task WriteInt32Async(ushort startAddress, int value);
|
||||||
|
bool IsConnected { get; }
|
||||||
|
void Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
111
Services/MesHttpService.cs
Normal file
111
Services/MesHttpService.cs
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
using HME_MoistureLossMeter.Models;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using System;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
|
namespace HME_MoistureLossMeter.Services
|
||||||
|
{
|
||||||
|
public class MesHttpService : IMesService
|
||||||
|
{
|
||||||
|
private readonly HttpClient _httpClient;
|
||||||
|
private readonly MesConfiguration _config;
|
||||||
|
|
||||||
|
public MesHttpService(HttpClient httpClient, MesConfiguration config)
|
||||||
|
{
|
||||||
|
_httpClient = httpClient;
|
||||||
|
_config = config;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(_config.ApiKey))
|
||||||
|
{
|
||||||
|
_httpClient.DefaultRequestHeaders.Add("X-API-Key", _config.ApiKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> SendRealtimeDataAsync(RealtimeDataModel data)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var json = JsonConvert.SerializeObject(data, new JsonSerializerSettings
|
||||||
|
{
|
||||||
|
NullValueHandling = NullValueHandling.Ignore
|
||||||
|
});
|
||||||
|
var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||||
|
|
||||||
|
var response = await _httpClient.PostAsync("/api/hme/realtime", content);
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
Log.Warning("发送实时数据失败: {StatusCode}, {Reason}",
|
||||||
|
response.StatusCode, response.ReasonPhrase);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "发送实时数据异常");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> SendTestResultAsync(TestResultModel result)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var json = JsonConvert.SerializeObject(result, new JsonSerializerSettings
|
||||||
|
{
|
||||||
|
NullValueHandling = NullValueHandling.Ignore
|
||||||
|
});
|
||||||
|
var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||||
|
|
||||||
|
var response = await _httpClient.PostAsync("/api/hme/test-result", content);
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
Log.Warning("发送测试结果失败: {StatusCode}, {Reason}",
|
||||||
|
response.StatusCode, response.ReasonPhrase);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "发送测试结果异常");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> SendHistoryDataAsync(HistoryDataModel history)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var json = JsonConvert.SerializeObject(history, new JsonSerializerSettings
|
||||||
|
{
|
||||||
|
NullValueHandling = NullValueHandling.Ignore
|
||||||
|
});
|
||||||
|
var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||||
|
|
||||||
|
var response = await _httpClient.PostAsync("/api/hme/history", content);
|
||||||
|
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
Log.Warning("发送历史数据失败: {StatusCode}, {Reason}",
|
||||||
|
response.StatusCode, response.ReasonPhrase);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "发送历史数据异常");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
232
Services/ModbusTcpPlcService.cs
Normal file
232
Services/ModbusTcpPlcService.cs
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
using HME_MoistureLossMeter.Models;
|
||||||
|
using Modbus.Device;
|
||||||
|
using System;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace HME_MoistureLossMeter.Services
|
||||||
|
{
|
||||||
|
public class ModbusTcpPlcService : IPlcService, IDisposable
|
||||||
|
{
|
||||||
|
// 寄存器地址常量 - 测试界面
|
||||||
|
public const ushort ADDR_WATER_TEMP = 1118; // 水浴温度
|
||||||
|
public const ushort ADDR_CHAMBER_TEMP = 1168; // 箱体温度
|
||||||
|
public const ushort ADDR_WEIGHT = 1268; // 重量
|
||||||
|
public const ushort ADDR_AIR_VOLUME = 4110; // 空气体积
|
||||||
|
public const ushort ADDR_OUTLET_FLOW = 1218; // 出口流量
|
||||||
|
public const ushort ADDR_PRESET_HOUR = 1681; // 设定测试时
|
||||||
|
public const ushort ADDR_PRESET_MINUTE = 1680; // 设定测试分
|
||||||
|
public const ushort ADDR_DISPLAY_HOUR = 3004; // 显示时
|
||||||
|
public const ushort ADDR_DISPLAY_MINUTE = 3003; // 显示分
|
||||||
|
public const ushort ADDR_DISPLAY_SECOND = 3002; // 显示秒
|
||||||
|
public const ushort ADDR_INITIAL_MASS = 206; // 初始质量
|
||||||
|
public const ushort ADDR_FINAL_MASS = 208; // 测后质量
|
||||||
|
public const ushort ADDR_MOISTURE_LOSS = 4106; // 水分损失
|
||||||
|
public const ushort ADDR_BATCH_NO = 3202; // 生产批号
|
||||||
|
public const ushort ADDR_OPERATOR_ID = 3200; // 操作员编号
|
||||||
|
public const ushort ADDR_EXPERIMENT_ID = 3204; // 实验编号
|
||||||
|
public const ushort ADDR_TIDAL_VOLUME = 300; // 潮气量
|
||||||
|
public const ushort ADDR_FREQUENCY = 210; // 频率
|
||||||
|
public const ushort ADDR_BREATH_COUNT = 3000; // 呼吸次数
|
||||||
|
public const ushort ADDR_DRY_AIR_FLOW = 3700; // 通入干燥空气量
|
||||||
|
|
||||||
|
// 线圈地址 - 测试界面
|
||||||
|
public const ushort COIL_RESET = 3; // 复位 M3
|
||||||
|
public const ushort COIL_TEST = 5; // 测试 M5
|
||||||
|
public const ushort COIL_STOP = 8; // 停止 M8
|
||||||
|
public const ushort COIL_CLEAR = 41; // 清零 M41
|
||||||
|
public const ushort COIL_HEAT = 300; // 加热 M300
|
||||||
|
public const ushort COIL_P1_RECORD = 91; // P1记录 M91
|
||||||
|
public const ushort COIL_P2_RECORD = 92; // P2记录 M92
|
||||||
|
public const ushort COIL_PRINT = 15; // 打印 M15
|
||||||
|
public const ushort COIL_EXHALE = 51; // 呼气 M51
|
||||||
|
public const ushort COIL_INHALE = 55; // 吸气 M55
|
||||||
|
public const ushort COIL_DOWN = 46; // 下降 M46
|
||||||
|
public const ushort COIL_UP = 47; // 上升 M47
|
||||||
|
|
||||||
|
// 寄存器地址 - 手动界面
|
||||||
|
public const ushort ADDR_MANUAL_SPEED = 218; // 手动速度 D218
|
||||||
|
public const ushort ADDR_TIDAL_COEFF = 302; // 潮气量系数 D302
|
||||||
|
public const ushort ADDR_FREQ_COEFF = 282; // 频率系数 D282
|
||||||
|
|
||||||
|
// 线圈地址 - 手动界面
|
||||||
|
public const ushort COIL_LEFT = 16; // 左 M16
|
||||||
|
public const ushort COIL_RIGHT = 17; // 右 M17
|
||||||
|
public const ushort COIL_MANUAL_INHALE = 19; // 手动吸 M19
|
||||||
|
public const ushort COIL_MANUAL_EXHALE = 18; // 手动呼 M18
|
||||||
|
public const ushort COIL_ZERO = 40; // 校零 M40
|
||||||
|
|
||||||
|
private readonly PlcConfiguration _config;
|
||||||
|
private TcpClient _tcpClient;
|
||||||
|
private IModbusMaster _master;
|
||||||
|
|
||||||
|
public ModbusTcpPlcService(PlcConfiguration config)
|
||||||
|
{
|
||||||
|
_config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsConnected => _tcpClient != null && _tcpClient.Connected;
|
||||||
|
|
||||||
|
public async Task EnsureConnectedAsync(int retryCount = 3)
|
||||||
|
{
|
||||||
|
if (_tcpClient != null && _tcpClient.Connected)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (int i = 0; i < retryCount; i++)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_tcpClient?.Close();
|
||||||
|
_tcpClient = new TcpClient();
|
||||||
|
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3));
|
||||||
|
await _tcpClient.ConnectAsync(_config.IpAddress, _config.Port);
|
||||||
|
_master = ModbusIpMaster.CreateIp(_tcpClient);
|
||||||
|
_master.Transport.ReadTimeout = 3000;
|
||||||
|
_master.Transport.WriteTimeout = 3000;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (i < retryCount - 1)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"连接失败,500ms 后重试... {ex.Message}");
|
||||||
|
await Task.Delay(500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Exception($"无法连接到 PLC ({_config.IpAddress}:{_config.Port}),请检查网络和 PLC 状态。");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<float> ReadFloatAsync(ushort startAddress)
|
||||||
|
{
|
||||||
|
await EnsureConnectedAsync();
|
||||||
|
var registers = await ReadHoldingRegistersAsync(startAddress, 2);
|
||||||
|
return UshortToFloat(registers[1], registers[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<int> ReadInt32Async(ushort startAddress)
|
||||||
|
{
|
||||||
|
await EnsureConnectedAsync();
|
||||||
|
var regs = await ReadHoldingRegistersAsync(startAddress, 2);
|
||||||
|
return regs[1] << 16 | regs[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<float> ReadPressureAsync() =>
|
||||||
|
await ReadFloatAsync(_config.PressureRegister);
|
||||||
|
|
||||||
|
public async Task<float> ReadWetFlowAsync(int stationId)
|
||||||
|
{
|
||||||
|
ushort startAddress = stationId switch
|
||||||
|
{
|
||||||
|
1 => _config.WetFlowRegister,
|
||||||
|
2 => _config.WetFlowRegister2,
|
||||||
|
3 => _config.WetFlowRegister3,
|
||||||
|
_ => _config.WetFlowRegister
|
||||||
|
};
|
||||||
|
return await ReadFloatAsync(startAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<float> ReadPressureAsync(int stationId)
|
||||||
|
{
|
||||||
|
ushort startAddress = stationId switch
|
||||||
|
{
|
||||||
|
1 => _config.PressureRegisterStation1,
|
||||||
|
2 => _config.PressureRegisterStation2,
|
||||||
|
3 => _config.PressureRegisterStation3,
|
||||||
|
_ => _config.PressureRegisterStation1
|
||||||
|
};
|
||||||
|
return await ReadFloatAsync(startAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> ReadCoilAsync(ushort coilAddress)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await EnsureConnectedAsync();
|
||||||
|
bool[] result = await _master.ReadCoilsAsync(_config.SlaveId, coilAddress, 1);
|
||||||
|
return result[0];
|
||||||
|
}
|
||||||
|
catch { return false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ushort[]> ReadHoldingRegistersAsync(ushort startAddress, ushort count)
|
||||||
|
{
|
||||||
|
await EnsureConnectedAsync();
|
||||||
|
return await _master.ReadHoldingRegistersAsync(_config.SlaveId, startAddress, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task WriteCoilAsync(ushort coilAddress, bool value)
|
||||||
|
{
|
||||||
|
await EnsureConnectedAsync();
|
||||||
|
await _master.WriteSingleCoilAsync(_config.SlaveId, coilAddress, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task WriteRegisterAsync(ushort registerAddress, ushort value)
|
||||||
|
{
|
||||||
|
await EnsureConnectedAsync();
|
||||||
|
await Task.Delay(50);
|
||||||
|
await _master.WriteSingleRegisterAsync(_config.SlaveId, registerAddress, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task WriteSingleRegisterAsync(ushort registerAddress, ushort value)
|
||||||
|
{
|
||||||
|
await EnsureConnectedAsync();
|
||||||
|
await Task.Delay(50);
|
||||||
|
await _master.WriteSingleRegisterAsync(_config.SlaveId, registerAddress, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task WriteMultipleRegistersAsync(ushort registerAddress, float value)
|
||||||
|
{
|
||||||
|
await EnsureConnectedAsync();
|
||||||
|
await Task.Delay(50);
|
||||||
|
await _master.WriteMultipleRegistersAsync(_config.SlaveId, registerAddress, SplitFloatToUShortArray(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task WriteInt32Async(ushort startAddress, int value)
|
||||||
|
{
|
||||||
|
await EnsureConnectedAsync();
|
||||||
|
ushort[] registers = ConvertIntToRegisters(value);
|
||||||
|
await _master.WriteMultipleRegistersAsync(_config.SlaveId, startAddress, registers);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ushort[] SplitFloatToUShortArray(float value)
|
||||||
|
{
|
||||||
|
byte[] floatBytes = BitConverter.GetBytes(value);
|
||||||
|
ushort[] ushortArray = new ushort[floatBytes.Length / 2];
|
||||||
|
|
||||||
|
for (int i = 0, j = 0; i < floatBytes.Length; i += 2, j++)
|
||||||
|
{
|
||||||
|
ushortArray[j] = BitConverter.ToUInt16(floatBytes, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ushortArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
private float UshortToFloat(ushort p1, ushort p2)
|
||||||
|
{
|
||||||
|
int intSign, intSignRest, intExponent, intExponentRest;
|
||||||
|
float faResult, faDigit;
|
||||||
|
intSign = p1 / 32768;
|
||||||
|
intSignRest = p1 % 32768;
|
||||||
|
intExponent = intSignRest / 128;
|
||||||
|
intExponentRest = intSignRest % 128;
|
||||||
|
faDigit = (float)(intExponentRest * 65536 + p2) / 8388608;
|
||||||
|
faResult = (float)Math.Pow(-1, intSign) * (float)Math.Pow(2, intExponent - 127) * (faDigit + 1);
|
||||||
|
return faResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ushort[] ConvertIntToRegisters(int value)
|
||||||
|
{
|
||||||
|
byte[] bytes = BitConverter.GetBytes(value);
|
||||||
|
ushort[] registers = new ushort[2];
|
||||||
|
registers[0] = BitConverter.ToUInt16(bytes, 0);
|
||||||
|
registers[1] = BitConverter.ToUInt16(bytes, 2);
|
||||||
|
return registers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_master?.Dispose();
|
||||||
|
_tcpClient?.Close();
|
||||||
|
_tcpClient?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
92
ViewModels/MainViewModel.cs
Normal file
92
ViewModels/MainViewModel.cs
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using System;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace HME_MoistureLossMeter.ViewModels
|
||||||
|
{
|
||||||
|
public partial class MainViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
private readonly IServiceProvider _serviceProvider;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private object _currentView;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _currentViewName = "测试画面";
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _isConnected;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _connectionStatus = "未连接";
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _isTesting;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private DateTime _currentTime = DateTime.Now;
|
||||||
|
|
||||||
|
public TestViewModel TestViewModel { get; }
|
||||||
|
public ManualControlViewModel ManualControlViewModel { get; }
|
||||||
|
public RecordViewModel RecordViewModel { get; }
|
||||||
|
public ReportViewModel ReportViewModel { get; }
|
||||||
|
|
||||||
|
public MainViewModel(IServiceProvider serviceProvider,
|
||||||
|
TestViewModel testViewModel,
|
||||||
|
ManualControlViewModel manualControlViewModel,
|
||||||
|
RecordViewModel recordViewModel,
|
||||||
|
ReportViewModel reportViewModel)
|
||||||
|
{
|
||||||
|
_serviceProvider = serviceProvider;
|
||||||
|
TestViewModel = testViewModel;
|
||||||
|
ManualControlViewModel = manualControlViewModel;
|
||||||
|
RecordViewModel = recordViewModel;
|
||||||
|
ReportViewModel = reportViewModel;
|
||||||
|
|
||||||
|
CurrentView = TestViewModel;
|
||||||
|
CurrentViewName = "测试画面";
|
||||||
|
|
||||||
|
var timer = new System.Timers.Timer(1000);
|
||||||
|
timer.Elapsed += (s, e) => CurrentTime = DateTime.Now;
|
||||||
|
timer.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void NavigateToTest()
|
||||||
|
{
|
||||||
|
CurrentView = TestViewModel;
|
||||||
|
CurrentViewName = "测试画面";
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void NavigateToManual()
|
||||||
|
{
|
||||||
|
CurrentView = ManualControlViewModel;
|
||||||
|
CurrentViewName = "手动控制";
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void NavigateToRecord()
|
||||||
|
{
|
||||||
|
CurrentView = RecordViewModel;
|
||||||
|
CurrentViewName = "记录画面";
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void NavigateToReport()
|
||||||
|
{
|
||||||
|
CurrentView = ReportViewModel;
|
||||||
|
CurrentViewName = "报表";
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void ExitApplication()
|
||||||
|
{
|
||||||
|
var result = MessageBox.Show("确定要退出程序吗?", "确认退出",
|
||||||
|
MessageBoxButton.YesNo, MessageBoxImage.Question);
|
||||||
|
if (result == MessageBoxResult.Yes)
|
||||||
|
Application.Current.Shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
176
ViewModels/ManualControlViewModel.cs
Normal file
176
ViewModels/ManualControlViewModel.cs
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using HME_MoistureLossMeter.Services;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
|
namespace HME_MoistureLossMeter.ViewModels
|
||||||
|
{
|
||||||
|
public partial class ManualControlViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
private readonly IPlcService _plcService;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private float _manualSpeed;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private float _tidalCoeff;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private float _freqCoeff;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _isConnected;
|
||||||
|
|
||||||
|
public ManualControlViewModel(IPlcService plcService)
|
||||||
|
{
|
||||||
|
_plcService = plcService;
|
||||||
|
|
||||||
|
// 启动时读取参数
|
||||||
|
Task.Run(async () => await ReadParameters());
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ReadParameters()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!_plcService.IsConnected)
|
||||||
|
await _plcService.EnsureConnectedAsync();
|
||||||
|
|
||||||
|
ManualSpeed = await _plcService.ReadFloatAsync(ModbusTcpPlcService.ADDR_MANUAL_SPEED);
|
||||||
|
TidalCoeff = await _plcService.ReadFloatAsync(ModbusTcpPlcService.ADDR_TIDAL_COEFF);
|
||||||
|
FreqCoeff = await _plcService.ReadFloatAsync(ModbusTcpPlcService.ADDR_FREQ_COEFF);
|
||||||
|
IsConnected = true;
|
||||||
|
StatusMessage = "参数读取成功";
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
IsConnected = false;
|
||||||
|
StatusMessage = $"读取参数失败: {ex.Message}";
|
||||||
|
Log.Error(ex, "读取手动参数失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task SaveParameters()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IsBusy = true;
|
||||||
|
await _plcService.WriteMultipleRegistersAsync(ModbusTcpPlcService.ADDR_MANUAL_SPEED, ManualSpeed);
|
||||||
|
await _plcService.WriteMultipleRegistersAsync(ModbusTcpPlcService.ADDR_TIDAL_COEFF, TidalCoeff);
|
||||||
|
await _plcService.WriteMultipleRegistersAsync(ModbusTcpPlcService.ADDR_FREQ_COEFF, FreqCoeff);
|
||||||
|
StatusMessage = "参数保存成功";
|
||||||
|
Log.Information("手动参数已保存");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
StatusMessage = $"保存参数失败: {ex.Message}";
|
||||||
|
Log.Error(ex, "保存手动参数失败");
|
||||||
|
MessageBox.Show($"保存参数失败: {ex.Message}", "错误",
|
||||||
|
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IsBusy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task ManualInhale()
|
||||||
|
{
|
||||||
|
await ExecuteCoilCommand(ModbusTcpPlcService.COIL_MANUAL_INHALE, "手动吸气");
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task ManualExhale()
|
||||||
|
{
|
||||||
|
await ExecuteCoilCommand(ModbusTcpPlcService.COIL_MANUAL_EXHALE, "手动呼气");
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task MoveLeft()
|
||||||
|
{
|
||||||
|
await ExecuteCoilCommand(ModbusTcpPlcService.COIL_LEFT, "左移");
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task MoveRight()
|
||||||
|
{
|
||||||
|
await ExecuteCoilCommand(ModbusTcpPlcService.COIL_RIGHT, "右移");
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task MoveUp()
|
||||||
|
{
|
||||||
|
await ExecuteCoilCommand(ModbusTcpPlcService.COIL_UP, "上升");
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task MoveDown()
|
||||||
|
{
|
||||||
|
await ExecuteCoilCommand(ModbusTcpPlcService.COIL_DOWN, "下降");
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task ZeroCalibration()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IsBusy = true;
|
||||||
|
await _plcService.WriteCoilAsync(ModbusTcpPlcService.COIL_ZERO, true);
|
||||||
|
StatusMessage = "校零已触发";
|
||||||
|
Log.Information("校零触发");
|
||||||
|
|
||||||
|
// 等待完成后自动复位
|
||||||
|
await Task.Delay(2000);
|
||||||
|
await _plcService.WriteCoilAsync(ModbusTcpPlcService.COIL_ZERO, false);
|
||||||
|
StatusMessage = "校零完成";
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
StatusMessage = $"校零失败: {ex.Message}";
|
||||||
|
Log.Error(ex, "校零失败");
|
||||||
|
MessageBox.Show($"校零失败: {ex.Message}", "错误",
|
||||||
|
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IsBusy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ExecuteCoilCommand(ushort coilAddress, string commandName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IsBusy = true;
|
||||||
|
await _plcService.WriteCoilAsync(coilAddress, true);
|
||||||
|
StatusMessage = $"{commandName}已执行";
|
||||||
|
Log.Information("{Command}执行", commandName);
|
||||||
|
|
||||||
|
// 短暂延迟后自动复位
|
||||||
|
await Task.Delay(200);
|
||||||
|
await _plcService.WriteCoilAsync(coilAddress, false);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
StatusMessage = $"{commandName}失败: {ex.Message}";
|
||||||
|
Log.Error(ex, "{Command}失败", commandName);
|
||||||
|
MessageBox.Show($"{commandName}失败: {ex.Message}", "错误",
|
||||||
|
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IsBusy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task RefreshParameters()
|
||||||
|
{
|
||||||
|
await ReadParameters();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
141
ViewModels/RecordViewModel.cs
Normal file
141
ViewModels/RecordViewModel.cs
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using HME_MoistureLossMeter.Models;
|
||||||
|
using HME_MoistureLossMeter.Services;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using Serilog;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace HME_MoistureLossMeter.ViewModels
|
||||||
|
{
|
||||||
|
public partial class RecordViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
private readonly IMesService _mesService;
|
||||||
|
private readonly DeviceConfiguration _deviceConfig;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private ObservableCollection<HistoryRecordModel> _records = new();
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private HistoryRecordModel _selectedRecord;
|
||||||
|
|
||||||
|
public RecordViewModel(IMesService mesService, DeviceConfiguration deviceConfig)
|
||||||
|
{
|
||||||
|
_mesService = mesService;
|
||||||
|
_deviceConfig = deviceConfig;
|
||||||
|
|
||||||
|
// 加载测试数据
|
||||||
|
LoadTestRecords();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadTestRecords()
|
||||||
|
{
|
||||||
|
// 从本地存储或数据库加载历史记录
|
||||||
|
// 这里使用示例数据,实际应从数据库读取
|
||||||
|
Records.Add(new HistoryRecordModel
|
||||||
|
{
|
||||||
|
RecordId = 1,
|
||||||
|
TestTime = DateTime.Now.AddHours(-1).ToString("yyyy-MM-dd HH:mm:ss"),
|
||||||
|
OperatorId = 1001,
|
||||||
|
BatchNo = "BATCH-2024-001",
|
||||||
|
ExperimentId = 1,
|
||||||
|
InitialMass = 50.5f,
|
||||||
|
FinalMass = 48.2f,
|
||||||
|
MoistureLoss = 2.3f
|
||||||
|
});
|
||||||
|
|
||||||
|
Records.Add(new HistoryRecordModel
|
||||||
|
{
|
||||||
|
RecordId = 2,
|
||||||
|
TestTime = DateTime.Now.AddHours(-2).ToString("yyyy-MM-dd HH:mm:ss"),
|
||||||
|
OperatorId = 1002,
|
||||||
|
BatchNo = "BATCH-2024-002",
|
||||||
|
ExperimentId = 2,
|
||||||
|
InitialMass = 45.8f,
|
||||||
|
FinalMass = 44.1f,
|
||||||
|
MoistureLoss = 1.7f
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task SyncToMes()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IsBusy = true;
|
||||||
|
StatusMessage = "正在同步历史数据到MES...";
|
||||||
|
|
||||||
|
var historyData = new HistoryDataModel
|
||||||
|
{
|
||||||
|
DeviceId = _deviceConfig.DeviceId,
|
||||||
|
Records = Records.ToList()
|
||||||
|
};
|
||||||
|
|
||||||
|
bool success = await _mesService.SendHistoryDataAsync(historyData);
|
||||||
|
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
StatusMessage = "历史数据同步成功";
|
||||||
|
Log.Information("历史数据同步到MES成功");
|
||||||
|
MessageBox.Show("历史数据同步成功", "提示",
|
||||||
|
MessageBoxButton.OK, MessageBoxImage.Information);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
StatusMessage = "历史数据同步失败";
|
||||||
|
MessageBox.Show("历史数据同步失败,请检查网络连接", "错误",
|
||||||
|
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
StatusMessage = $"同步失败: {ex.Message}";
|
||||||
|
Log.Error(ex, "同步历史数据到MES失败");
|
||||||
|
MessageBox.Show($"同步失败: {ex.Message}", "错误",
|
||||||
|
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IsBusy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void ClearRecords()
|
||||||
|
{
|
||||||
|
var result = MessageBox.Show("确定要清除所有历史记录吗?", "确认清除",
|
||||||
|
MessageBoxButton.YesNo, MessageBoxImage.Warning);
|
||||||
|
|
||||||
|
if (result == MessageBoxResult.Yes)
|
||||||
|
{
|
||||||
|
Records.Clear();
|
||||||
|
StatusMessage = "历史记录已清除";
|
||||||
|
Log.Information("历史记录已清除");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void DeleteSelectedRecord()
|
||||||
|
{
|
||||||
|
if (SelectedRecord == null)
|
||||||
|
{
|
||||||
|
MessageBox.Show("请先选择要删除的记录", "提示",
|
||||||
|
MessageBoxButton.OK, MessageBoxImage.Information);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = MessageBox.Show($"确定要删除记录 #{SelectedRecord.RecordId} 吗?", "确认删除",
|
||||||
|
MessageBoxButton.YesNo, MessageBoxImage.Warning);
|
||||||
|
|
||||||
|
if (result == MessageBoxResult.Yes)
|
||||||
|
{
|
||||||
|
Records.Remove(SelectedRecord);
|
||||||
|
SelectedRecord = null;
|
||||||
|
StatusMessage = "记录已删除";
|
||||||
|
Log.Information("记录已删除");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
ViewModels/ReportViewModel.cs
Normal file
12
ViewModels/ReportViewModel.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
|
||||||
|
namespace HME_MoistureLossMeter.ViewModels
|
||||||
|
{
|
||||||
|
public partial class ReportViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
public ReportViewModel()
|
||||||
|
{
|
||||||
|
StatusMessage = "报表视图(待扩展)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
573
ViewModels/TestViewModel.cs
Normal file
573
ViewModels/TestViewModel.cs
Normal file
@@ -0,0 +1,573 @@
|
|||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using HME_MoistureLossMeter.Models;
|
||||||
|
using HME_MoistureLossMeter.Services;
|
||||||
|
using Serilog;
|
||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Data;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Media.Media3D;
|
||||||
|
|
||||||
|
namespace HME_MoistureLossMeter.ViewModels
|
||||||
|
{
|
||||||
|
public partial class TestViewModel : ViewModelBase
|
||||||
|
{
|
||||||
|
private readonly IPlcService _plcService;
|
||||||
|
private readonly IMesService _mesService;
|
||||||
|
private readonly DeviceConfiguration _deviceConfig;
|
||||||
|
private CancellationTokenSource _cts;
|
||||||
|
private bool _isDataLoopRunning;
|
||||||
|
private float _totalWaterTemp;
|
||||||
|
private float _totalChamberTemp;
|
||||||
|
private float _totalTidalVolume;
|
||||||
|
private float _totalFrequency;
|
||||||
|
private float _totalAirVolume;
|
||||||
|
private float _totalOutletFlow;
|
||||||
|
private int _dataSampleCount;
|
||||||
|
|
||||||
|
// 实时数据属性
|
||||||
|
[ObservableProperty]
|
||||||
|
private float _waterTemp;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private float _chamberTemp;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private float _weight;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private float _airVolume;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private float _outletFlow;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private int _presetHour;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private int _presetMinute;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private int _displayHour;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private int _displayMinute;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private int _displaySecond;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private float _initialMass;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private float _finalMass;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private float _moistureLoss;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _batchNo = "";
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private int _operatorId;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private int _experimentId;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private float _tidalVolume;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private float _frequency;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private int _breathCount;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private float _dryAirFlow;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _isTesting;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _isHeating;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _isInhale;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _isExhale;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _isRising;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _isFalling;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _isConnected;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private string _connectionStatus = "未连接";
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private DateTime _currentTime = DateTime.Now;
|
||||||
|
|
||||||
|
public ObservableCollection<HistoryRecordModel> TestRecords { get; } = new();
|
||||||
|
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task MoveUp() => await ExecuteCoilCommand(ModbusTcpPlcService.COIL_UP, "上升");
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task MoveDown() => await ExecuteCoilCommand(ModbusTcpPlcService.COIL_DOWN, "下降");
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task ManualInhale() => await ExecuteCoilCommand(ModbusTcpPlcService.COIL_MANUAL_INHALE, "手动吸气");
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task ManualExhale() => await ExecuteCoilCommand(ModbusTcpPlcService.COIL_MANUAL_EXHALE, "手动呼气");
|
||||||
|
|
||||||
|
// 辅助方法
|
||||||
|
private async Task ExecuteCoilCommand(ushort coilAddress, string commandName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IsBusy = true;
|
||||||
|
await _plcService.WriteCoilAsync(coilAddress, true);
|
||||||
|
StatusMessage = $"{commandName}已执行";
|
||||||
|
await Task.Delay(200);
|
||||||
|
await _plcService.WriteCoilAsync(coilAddress, false);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
StatusMessage = $"{commandName}失败: {ex.Message}";
|
||||||
|
Log.Error(ex, "{Command}失败", commandName);
|
||||||
|
MessageBox.Show($"{commandName}失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IsBusy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public TestViewModel(IPlcService plcService, IMesService mesService, DeviceConfiguration deviceConfig)
|
||||||
|
{
|
||||||
|
_plcService = plcService;
|
||||||
|
_mesService = mesService;
|
||||||
|
_deviceConfig = deviceConfig;
|
||||||
|
|
||||||
|
// 初始化定时更新
|
||||||
|
var timer = new System.Timers.Timer(1000);
|
||||||
|
timer.Elapsed += (s, e) => CurrentTime = DateTime.Now;
|
||||||
|
timer.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
partial void OnIsTestingChanged(bool value)
|
||||||
|
{
|
||||||
|
if (value)
|
||||||
|
{
|
||||||
|
// 开始测试时重置数据
|
||||||
|
_dataSampleCount = 0;
|
||||||
|
_totalWaterTemp = 0;
|
||||||
|
_totalChamberTemp = 0;
|
||||||
|
_totalTidalVolume = 0;
|
||||||
|
_totalFrequency = 0;
|
||||||
|
_totalAirVolume = 0;
|
||||||
|
_totalOutletFlow = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task Connect()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IsBusy = true;
|
||||||
|
StatusMessage = "正在连接PLC...";
|
||||||
|
await _plcService.EnsureConnectedAsync();
|
||||||
|
IsConnected = true;
|
||||||
|
ConnectionStatus = "已连接";
|
||||||
|
StatusMessage = "PLC连接成功";
|
||||||
|
Log.Information("PLC连接成功");
|
||||||
|
|
||||||
|
// 启动数据循环
|
||||||
|
StartDataLoop();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
IsConnected = false;
|
||||||
|
ConnectionStatus = "连接失败";
|
||||||
|
StatusMessage = $"连接失败: {ex.Message}";
|
||||||
|
Log.Error(ex, "PLC连接失败");
|
||||||
|
MessageBox.Show($"PLC连接失败: {ex.Message}", "错误",
|
||||||
|
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IsBusy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void Disconnect()
|
||||||
|
{
|
||||||
|
StopDataLoop();
|
||||||
|
IsConnected = false;
|
||||||
|
ConnectionStatus = "已断开";
|
||||||
|
StatusMessage = "已断开PLC连接";
|
||||||
|
Log.Information("PLC连接已断开");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartDataLoop()
|
||||||
|
{
|
||||||
|
if (_isDataLoopRunning) return;
|
||||||
|
|
||||||
|
_cts = new CancellationTokenSource();
|
||||||
|
_isDataLoopRunning = true;
|
||||||
|
|
||||||
|
Task.Run(async () =>
|
||||||
|
{
|
||||||
|
while (!_cts.Token.IsCancellationRequested && IsConnected)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await ReadAllData();
|
||||||
|
await Task.Delay(_deviceConfig.UpdateIntervalMs, _cts.Token);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "读取数据异常");
|
||||||
|
StatusMessage = $"读取数据异常: {ex.Message}";
|
||||||
|
await Task.Delay(2000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, _cts.Token);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StopDataLoop()
|
||||||
|
{
|
||||||
|
_isDataLoopRunning = false;
|
||||||
|
_cts?.Cancel();
|
||||||
|
_cts?.Dispose();
|
||||||
|
_cts = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ReadAllData()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 读取浮点数数据
|
||||||
|
WaterTemp = await _plcService.ReadFloatAsync(ModbusTcpPlcService.ADDR_WATER_TEMP);
|
||||||
|
ChamberTemp = await _plcService.ReadFloatAsync(ModbusTcpPlcService.ADDR_CHAMBER_TEMP);
|
||||||
|
Weight = await _plcService.ReadFloatAsync(ModbusTcpPlcService.ADDR_WEIGHT);
|
||||||
|
AirVolume = await _plcService.ReadFloatAsync(ModbusTcpPlcService.ADDR_AIR_VOLUME);
|
||||||
|
OutletFlow = await _plcService.ReadFloatAsync(ModbusTcpPlcService.ADDR_OUTLET_FLOW);
|
||||||
|
TidalVolume = await _plcService.ReadFloatAsync(ModbusTcpPlcService.ADDR_TIDAL_VOLUME);
|
||||||
|
Frequency = await _plcService.ReadFloatAsync(ModbusTcpPlcService.ADDR_FREQUENCY);
|
||||||
|
DryAirFlow = await _plcService.ReadFloatAsync(ModbusTcpPlcService.ADDR_DRY_AIR_FLOW);
|
||||||
|
InitialMass = await _plcService.ReadFloatAsync(ModbusTcpPlcService.ADDR_INITIAL_MASS);
|
||||||
|
FinalMass = await _plcService.ReadFloatAsync(ModbusTcpPlcService.ADDR_FINAL_MASS);
|
||||||
|
MoistureLoss = await _plcService.ReadFloatAsync(ModbusTcpPlcService.ADDR_MOISTURE_LOSS);
|
||||||
|
|
||||||
|
// 读取整数数据
|
||||||
|
PresetHour = await _plcService.ReadInt32Async(ModbusTcpPlcService.ADDR_PRESET_HOUR);
|
||||||
|
PresetMinute = await _plcService.ReadInt32Async(ModbusTcpPlcService.ADDR_PRESET_MINUTE);
|
||||||
|
DisplayHour = await _plcService.ReadInt32Async(ModbusTcpPlcService.ADDR_DISPLAY_HOUR);
|
||||||
|
DisplayMinute = await _plcService.ReadInt32Async(ModbusTcpPlcService.ADDR_DISPLAY_MINUTE);
|
||||||
|
DisplaySecond = await _plcService.ReadInt32Async(ModbusTcpPlcService.ADDR_DISPLAY_SECOND);
|
||||||
|
BreathCount = await _plcService.ReadInt32Async(ModbusTcpPlcService.ADDR_BREATH_COUNT);
|
||||||
|
OperatorId = await _plcService.ReadInt32Async(ModbusTcpPlcService.ADDR_OPERATOR_ID);
|
||||||
|
ExperimentId = await _plcService.ReadInt32Async(ModbusTcpPlcService.ADDR_EXPERIMENT_ID);
|
||||||
|
|
||||||
|
// 读取字符串(批号)
|
||||||
|
var batchRegs = await _plcService.ReadHoldingRegistersAsync(ModbusTcpPlcService.ADDR_BATCH_NO, 10);
|
||||||
|
BatchNo = RegistersToString(batchRegs);
|
||||||
|
|
||||||
|
// 读取线圈状态
|
||||||
|
IsTesting = await _plcService.ReadCoilAsync(ModbusTcpPlcService.COIL_TEST);
|
||||||
|
IsHeating = await _plcService.ReadCoilAsync(ModbusTcpPlcService.COIL_HEAT);
|
||||||
|
IsInhale = await _plcService.ReadCoilAsync(ModbusTcpPlcService.COIL_INHALE);
|
||||||
|
IsExhale = await _plcService.ReadCoilAsync(ModbusTcpPlcService.COIL_EXHALE);
|
||||||
|
IsRising = await _plcService.ReadCoilAsync(ModbusTcpPlcService.COIL_UP);
|
||||||
|
IsFalling = await _plcService.ReadCoilAsync(ModbusTcpPlcService.COIL_DOWN);
|
||||||
|
|
||||||
|
// 如果正在测试,收集数据用于平均值计算
|
||||||
|
if (IsTesting)
|
||||||
|
{
|
||||||
|
_dataSampleCount++;
|
||||||
|
_totalWaterTemp += WaterTemp;
|
||||||
|
_totalChamberTemp += ChamberTemp;
|
||||||
|
_totalTidalVolume += TidalVolume;
|
||||||
|
_totalFrequency += Frequency;
|
||||||
|
_totalAirVolume += AirVolume;
|
||||||
|
_totalOutletFlow += OutletFlow;
|
||||||
|
|
||||||
|
// 发送实时数据到MES
|
||||||
|
await SendRealtimeDataToMes();
|
||||||
|
}
|
||||||
|
|
||||||
|
StatusMessage = "数据读取成功";
|
||||||
|
IsConnected = true;
|
||||||
|
ConnectionStatus = "已连接";
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
IsConnected = false;
|
||||||
|
ConnectionStatus = "通信异常";
|
||||||
|
Log.Error(ex, "读取数据失败");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string RegistersToString(ushort[] registers)
|
||||||
|
{
|
||||||
|
var bytes = new byte[registers.Length * 2];
|
||||||
|
for (int i = 0; i < registers.Length; i++)
|
||||||
|
{
|
||||||
|
var regBytes = BitConverter.GetBytes(registers[i]);
|
||||||
|
bytes[i * 2] = regBytes[0];
|
||||||
|
bytes[i * 2 + 1] = regBytes[1];
|
||||||
|
}
|
||||||
|
return System.Text.Encoding.ASCII.GetString(bytes).TrimEnd('\0');
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SendRealtimeDataToMes()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var data = new RealtimeDataModel
|
||||||
|
{
|
||||||
|
DeviceId = _deviceConfig.DeviceId,
|
||||||
|
Timestamp = DateTime.Now.ToString("o"),
|
||||||
|
Realtime = new RealtimeData
|
||||||
|
{
|
||||||
|
WaterTemp = WaterTemp,
|
||||||
|
ChamberTemp = ChamberTemp,
|
||||||
|
Weight = Weight,
|
||||||
|
AirVolume = AirVolume,
|
||||||
|
OutletFlow = OutletFlow,
|
||||||
|
TidalVolume = TidalVolume,
|
||||||
|
Frequency = Frequency,
|
||||||
|
BreathRate = BreathCount,
|
||||||
|
DryAirFlow = DryAirFlow
|
||||||
|
},
|
||||||
|
Settings = new SettingsData
|
||||||
|
{
|
||||||
|
PresetHour = PresetHour,
|
||||||
|
PresetMinute = PresetMinute,
|
||||||
|
PresetSecond = DisplaySecond,
|
||||||
|
OperatorId = OperatorId,
|
||||||
|
BatchNo = BatchNo,
|
||||||
|
ExperimentId = ExperimentId
|
||||||
|
},
|
||||||
|
Status = new StatusData
|
||||||
|
{
|
||||||
|
IsTesting = IsTesting,
|
||||||
|
IsFault = false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
await _mesService.SendRealtimeDataAsync(data);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "发送实时数据到MES失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试控制命令
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task StartTest()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IsBusy = true;
|
||||||
|
await _plcService.WriteCoilAsync(ModbusTcpPlcService.COIL_TEST, true);
|
||||||
|
StatusMessage = "测试已启动";
|
||||||
|
Log.Information("测试启动");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "启动测试失败");
|
||||||
|
MessageBox.Show($"启动测试失败: {ex.Message}", "错误",
|
||||||
|
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IsBusy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task StopTest()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IsBusy = true;
|
||||||
|
await _plcService.WriteCoilAsync(ModbusTcpPlcService.COIL_STOP, true);
|
||||||
|
StatusMessage = "测试已停止";
|
||||||
|
Log.Information("测试停止");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "停止测试失败");
|
||||||
|
MessageBox.Show($"停止测试失败: {ex.Message}", "错误",
|
||||||
|
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IsBusy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task Reset()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IsBusy = true;
|
||||||
|
await _plcService.WriteCoilAsync(ModbusTcpPlcService.COIL_RESET, true);
|
||||||
|
StatusMessage = "已复位";
|
||||||
|
Log.Information("设备复位");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "复位失败");
|
||||||
|
MessageBox.Show($"复位失败: {ex.Message}", "错误",
|
||||||
|
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IsBusy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task Clear()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IsBusy = true;
|
||||||
|
await _plcService.WriteCoilAsync(ModbusTcpPlcService.COIL_CLEAR, true);
|
||||||
|
StatusMessage = "已清零";
|
||||||
|
Log.Information("数据清零");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "清零失败");
|
||||||
|
MessageBox.Show($"清零失败: {ex.Message}", "错误",
|
||||||
|
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IsBusy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task ToggleHeat()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IsBusy = true;
|
||||||
|
bool currentState = await _plcService.ReadCoilAsync(ModbusTcpPlcService.COIL_HEAT);
|
||||||
|
await _plcService.WriteCoilAsync(ModbusTcpPlcService.COIL_HEAT, !currentState);
|
||||||
|
StatusMessage = currentState ? "加热关闭" : "加热开启";
|
||||||
|
Log.Information("加热切换: {State}", !currentState);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "切换加热失败");
|
||||||
|
MessageBox.Show($"切换加热失败: {ex.Message}", "错误",
|
||||||
|
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IsBusy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task RecordP1()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IsBusy = true;
|
||||||
|
await _plcService.WriteCoilAsync(ModbusTcpPlcService.COIL_P1_RECORD, true);
|
||||||
|
StatusMessage = "P1记录已触发";
|
||||||
|
Log.Information("P1记录触发");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "P1记录失败");
|
||||||
|
MessageBox.Show($"P1记录失败: {ex.Message}", "错误",
|
||||||
|
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IsBusy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task RecordP2()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IsBusy = true;
|
||||||
|
await _plcService.WriteCoilAsync(ModbusTcpPlcService.COIL_P2_RECORD, true);
|
||||||
|
StatusMessage = "P2记录已触发";
|
||||||
|
Log.Information("P2记录触发");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "P2记录失败");
|
||||||
|
MessageBox.Show($"P2记录失败: {ex.Message}", "错误",
|
||||||
|
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IsBusy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task Print()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IsBusy = true;
|
||||||
|
await _plcService.WriteCoilAsync(ModbusTcpPlcService.COIL_PRINT, true);
|
||||||
|
StatusMessage = "打印已触发";
|
||||||
|
Log.Information("打印触发");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "打印失败");
|
||||||
|
MessageBox.Show($"打印失败: {ex.Message}", "错误",
|
||||||
|
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IsBusy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
StopDataLoop();
|
||||||
|
_plcService?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
21
ViewModels/ViewModelBase.cs
Normal file
21
ViewModels/ViewModelBase.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
|
||||||
|
namespace HME_MoistureLossMeter.ViewModels
|
||||||
|
{
|
||||||
|
public abstract class ViewModelBase : ObservableObject
|
||||||
|
{
|
||||||
|
private bool _isBusy;
|
||||||
|
public bool IsBusy
|
||||||
|
{
|
||||||
|
get => _isBusy;
|
||||||
|
set => SetProperty(ref _isBusy, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _statusMessage = "就绪";
|
||||||
|
public string StatusMessage
|
||||||
|
{
|
||||||
|
get => _statusMessage;
|
||||||
|
set => SetProperty(ref _statusMessage, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
119
Views/MainWindow.xaml
Normal file
119
Views/MainWindow.xaml
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
<Window x:Class="HME_MoistureLossMeter.MainWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:views="clr-namespace:HME_MoistureLossMeter.Views"
|
||||||
|
xmlns:converters="clr-namespace:HME_MoistureLossMeter.Converters"
|
||||||
|
Title="HME水分损失测量仪"
|
||||||
|
Width="1024" Height="768"
|
||||||
|
WindowStartupLocation="CenterScreen"
|
||||||
|
Background="#F0F2F5">
|
||||||
|
<Window.Resources>
|
||||||
|
<Style TargetType="Button">
|
||||||
|
<Setter Property="Height" Value="40"/>
|
||||||
|
<Setter Property="Margin" Value="5"/>
|
||||||
|
<Setter Property="FontSize" Value="14"/>
|
||||||
|
<Setter Property="FontWeight" Value="Bold"/>
|
||||||
|
<Setter Property="Background" Value="#2C7DA0"/>
|
||||||
|
<Setter Property="Foreground" Value="White"/>
|
||||||
|
<Setter Property="BorderBrush" Value="#1F5E7A"/>
|
||||||
|
<Setter Property="BorderThickness" Value="1"/>
|
||||||
|
<Setter Property="Cursor" Value="Hand"/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<Trigger Property="IsEnabled" Value="False">
|
||||||
|
<Setter Property="Background" Value="#CCCCCC"/>
|
||||||
|
<Setter Property="Foreground" Value="#666666"/>
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter Property="Background" Value="#1F5E7A"/>
|
||||||
|
</Trigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
<Style TargetType="TextBox">
|
||||||
|
<Setter Property="Height" Value="35"/>
|
||||||
|
<Setter Property="FontSize" Value="14"/>
|
||||||
|
<Setter Property="BorderBrush" Value="#CCCCCC"/>
|
||||||
|
<Setter Property="BorderThickness" Value="1"/>
|
||||||
|
<Setter Property="Padding" Value="5"/>
|
||||||
|
<Setter Property="VerticalContentAlignment" Value="Center"/>
|
||||||
|
</Style>
|
||||||
|
<Style TargetType="TextBlock">
|
||||||
|
<Setter Property="FontSize" Value="14"/>
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||||
|
</Style>
|
||||||
|
<Style TargetType="GroupBox">
|
||||||
|
<Setter Property="FontWeight" Value="Bold"/>
|
||||||
|
<Setter Property="FontSize" Value="14"/>
|
||||||
|
<Setter Property="Margin" Value="0,5"/>
|
||||||
|
</Style>
|
||||||
|
<Style TargetType="Border" x:Key="CardBorder">
|
||||||
|
<Setter Property="Background" Value="White"/>
|
||||||
|
<Setter Property="CornerRadius" Value="8"/>
|
||||||
|
<Setter Property="Padding" Value="10"/>
|
||||||
|
<Setter Property="BorderBrush" Value="#DDDDDD"/>
|
||||||
|
<Setter Property="BorderThickness" Value="1"/>
|
||||||
|
</Style>
|
||||||
|
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
|
||||||
|
<converters:BoolToColorConverter x:Key="BoolToColorConverter"/>
|
||||||
|
<converters:BoolToStringConverter x:Key="BoolToStringConverter"/>
|
||||||
|
<converters:InverseBoolConverter x:Key="InverseBoolConverter"/>
|
||||||
|
</Window.Resources>
|
||||||
|
|
||||||
|
<Grid Margin="15">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<!-- 顶部状态栏 -->
|
||||||
|
<Border Grid.Row="0" Background="#2C3E50" CornerRadius="5" Padding="10" Margin="0,0,0,10">
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<TextBlock Text="状态:" Foreground="White" FontSize="14" VerticalAlignment="Center"/>
|
||||||
|
<TextBlock Text="{Binding ConnectionStatus}"
|
||||||
|
Foreground="{Binding IsConnected, Converter={StaticResource BoolToColorConverter}}"
|
||||||
|
FontSize="14" FontWeight="Bold" Margin="5,0,20,0"/>
|
||||||
|
<Separator Background="White" Width="1" Margin="5,0"/>
|
||||||
|
<TextBlock Text="当前视图:" Foreground="White" FontSize="14" Margin="15,0,0,0"/>
|
||||||
|
<TextBlock Text="{Binding CurrentViewName}" Foreground="White" FontSize="14" FontWeight="Bold" Margin="5,0,20,0"/>
|
||||||
|
<Separator Background="White" Width="1" Margin="5,0"/>
|
||||||
|
<TextBlock Text="测试状态:" Foreground="White" FontSize="14" Margin="15,0,0,0"/>
|
||||||
|
<TextBlock Text="{Binding IsTesting, Converter={StaticResource BoolToStringConverter}}"
|
||||||
|
Foreground="White" FontSize="14" FontWeight="Bold" Margin="5,0,0,0"/>
|
||||||
|
</StackPanel>
|
||||||
|
<TextBlock Grid.Column="1" Text="{Binding CurrentTime, StringFormat='{}{0:yyyy-MM-dd HH:mm:ss}'}"
|
||||||
|
Foreground="White" FontSize="14" VerticalAlignment="Center"/>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- 主内容 -->
|
||||||
|
<Grid Grid.Row="1">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="180"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<!-- 导航 -->
|
||||||
|
<Border Grid.Column="0" Style="{StaticResource CardBorder}" Margin="0,0,10,0">
|
||||||
|
<StackPanel Margin="5">
|
||||||
|
<Button Content="测试画面" Command="{Binding NavigateToTestCommand}" Height="45" Margin="0,5"/>
|
||||||
|
<Button Content="手动控制" Command="{Binding NavigateToManualCommand}" Height="45" Margin="0,5"/>
|
||||||
|
<Button Content="记录画面" Command="{Binding NavigateToRecordCommand}" Height="45" Margin="0,5"/>
|
||||||
|
<Button Content="报表" Command="{Binding NavigateToReportCommand}" Height="45" Margin="0,5"/>
|
||||||
|
<Separator Margin="0,15"/>
|
||||||
|
<Button Content="退出" Command="{Binding ExitApplicationCommand}" Height="45" Margin="0,5" Background="#E74C3C"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
<!-- 视图 -->
|
||||||
|
<ContentControl Grid.Column="1" Content="{Binding CurrentView}"/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- 底部状态 -->
|
||||||
|
<Border Grid.Row="2" Background="#2C3E50" CornerRadius="5" Padding="5" Margin="0,10,0,0">
|
||||||
|
<TextBlock Text="{Binding StatusMessage}" Foreground="White" FontSize="12" HorizontalAlignment="Center"/>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
12
Views/MainWindow.xaml.cs
Normal file
12
Views/MainWindow.xaml.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace HME_MoistureLossMeter
|
||||||
|
{
|
||||||
|
public partial class MainWindow : Window
|
||||||
|
{
|
||||||
|
public MainWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
200
Views/ManualControlView.xaml
Normal file
200
Views/ManualControlView.xaml
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
<UserControl x:Class="HME_MoistureLossMeter.Views.ManualControlView"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="700" d:DesignWidth="1024">
|
||||||
|
|
||||||
|
<Grid Background="{StaticResource BackgroundBrush}" Margin="10">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<!-- 左侧:参数设置 -->
|
||||||
|
<Border Grid.Row="0" Grid.RowSpan="2" Grid.Column="0"
|
||||||
|
Style="{StaticResource PanelBorderStyle}"
|
||||||
|
Margin="0,0,10,0">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<TextBlock Text="参数设置"
|
||||||
|
FontSize="16"
|
||||||
|
FontWeight="Bold"
|
||||||
|
Foreground="{StaticResource PrimaryBrush}"
|
||||||
|
Margin="0,0,0,15"/>
|
||||||
|
|
||||||
|
<Grid Grid.Row="1">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="120"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="Auto"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="0" Grid.Column="0"
|
||||||
|
Text="手动速度"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
<TextBox Grid.Row="0" Grid.Column="1"
|
||||||
|
Text="{Binding ManualSpeed, StringFormat='{}{0:F2}'}"
|
||||||
|
Margin="5,0,5,0"/>
|
||||||
|
<TextBlock Grid.Row="0" Grid.Column="2"
|
||||||
|
Text="mm/MIN"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Foreground="#7F8C8D"/>
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="1" Grid.Column="0"
|
||||||
|
Text="潮气量系数"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
<TextBox Grid.Row="1" Grid.Column="1"
|
||||||
|
Text="{Binding TidalCoeff, StringFormat='{}{0:F2}'}"
|
||||||
|
Margin="5,0,5,0"/>
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="2" Grid.Column="0"
|
||||||
|
Text="频率系数"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
<TextBox Grid.Row="2" Grid.Column="1"
|
||||||
|
Text="{Binding FreqCoeff, StringFormat='{}{0:F2}'}"
|
||||||
|
Margin="5,0,5,0"/>
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="3"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Margin="0,10,0,0">
|
||||||
|
<Button Content="保存参数"
|
||||||
|
Command="{Binding SaveParametersCommand}"
|
||||||
|
Width="100" Height="35"
|
||||||
|
Background="{StaticResource SuccessBrush}"/>
|
||||||
|
<Button Content="刷新"
|
||||||
|
Command="{Binding RefreshParametersCommand}"
|
||||||
|
Width="80" Height="35"
|
||||||
|
Margin="10,0,0,0"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- 右侧:手动控制 -->
|
||||||
|
<Border Grid.Row="0" Grid.RowSpan="2" Grid.Column="1"
|
||||||
|
Style="{StaticResource PanelBorderStyle}"
|
||||||
|
Margin="10,0,0,0">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<TextBlock Text="手动控制"
|
||||||
|
FontSize="16"
|
||||||
|
FontWeight="Bold"
|
||||||
|
Foreground="{StaticResource PrimaryBrush}"
|
||||||
|
Margin="0,0,0,15"/>
|
||||||
|
|
||||||
|
<!-- 控制按钮 -->
|
||||||
|
<Grid Grid.Row="1">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<!-- 方向控制 -->
|
||||||
|
<Button Grid.Row="0" Grid.Column="1"
|
||||||
|
Content="▲ 上升"
|
||||||
|
Command="{Binding MoveUpCommand}"
|
||||||
|
Height="40"
|
||||||
|
Background="{StaticResource WarningBrush}"
|
||||||
|
Margin="5"/>
|
||||||
|
|
||||||
|
<Button Grid.Row="1" Grid.Column="0"
|
||||||
|
Content="◄ 左"
|
||||||
|
Command="{Binding MoveLeftCommand}"
|
||||||
|
Height="40"
|
||||||
|
Margin="5"/>
|
||||||
|
|
||||||
|
<Button Grid.Row="1" Grid.Column="1"
|
||||||
|
Content="校零"
|
||||||
|
Command="{Binding ZeroCalibrationCommand}"
|
||||||
|
Height="40"
|
||||||
|
Background="{StaticResource AccentBrush}"
|
||||||
|
Margin="5"/>
|
||||||
|
|
||||||
|
<Button Grid.Row="1" Grid.Column="2"
|
||||||
|
Content="右 ►"
|
||||||
|
Command="{Binding MoveRightCommand}"
|
||||||
|
Height="40"
|
||||||
|
Margin="5"/>
|
||||||
|
|
||||||
|
<Button Grid.Row="2" Grid.Column="1"
|
||||||
|
Content="▼ 下降"
|
||||||
|
Command="{Binding MoveDownCommand}"
|
||||||
|
Height="40"
|
||||||
|
Background="{StaticResource WarningBrush}"
|
||||||
|
Margin="5"/>
|
||||||
|
|
||||||
|
<!-- 呼吸控制 -->
|
||||||
|
<Button Grid.Row="3" Grid.Column="0"
|
||||||
|
Content="手动呼"
|
||||||
|
Command="{Binding ManualExhaleCommand}"
|
||||||
|
Height="40"
|
||||||
|
Background="{StaticResource DangerBrush}"
|
||||||
|
Margin="5"/>
|
||||||
|
|
||||||
|
<Button Grid.Row="3" Grid.Column="1"
|
||||||
|
Content="停止"
|
||||||
|
Command="{Binding StopTestCommand}"
|
||||||
|
Height="40"
|
||||||
|
Background="{StaticResource DangerBrush}"
|
||||||
|
Margin="5"/>
|
||||||
|
|
||||||
|
<Button Grid.Row="3" Grid.Column="2"
|
||||||
|
Content="手动吸"
|
||||||
|
Command="{Binding ManualInhaleCommand}"
|
||||||
|
Height="40"
|
||||||
|
Background="{StaticResource SuccessBrush}"
|
||||||
|
Margin="5"/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- 状态显示 -->
|
||||||
|
<Border Grid.Row="2" Background="#F8F9FA" CornerRadius="4" Padding="5" Margin="0,10,0,0">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<TextBlock Text="PLC状态: " FontWeight="Bold" FontSize="12"/>
|
||||||
|
<TextBlock Text="{Binding ConnectionStatus}"
|
||||||
|
Foreground="{Binding IsConnected, Converter={StaticResource BoolToVisibilityConverter},
|
||||||
|
ConverterParameter=SuccessBrush, FallbackValue={StaticResource DangerBrush}}"
|
||||||
|
FontWeight="Bold"/>
|
||||||
|
<TextBlock Text=" | 手动速度: " FontWeight="Bold" FontSize="12" Margin="15,0,0,0"/>
|
||||||
|
<TextBlock Text="{Binding ManualSpeed, StringFormat='{}{0:F2} mm/MIN'}" FontSize="12"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
12
Views/ManualControlView.xaml.cs
Normal file
12
Views/ManualControlView.xaml.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using System.Windows.Controls;
|
||||||
|
|
||||||
|
namespace HME_MoistureLossMeter.Views
|
||||||
|
{
|
||||||
|
public partial class ManualControlView : UserControl
|
||||||
|
{
|
||||||
|
public ManualControlView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
95
Views/RecordView.xaml
Normal file
95
Views/RecordView.xaml
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
<UserControl x:Class="HME_MoistureLossMeter.Views.RecordView"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="700" d:DesignWidth="1024">
|
||||||
|
|
||||||
|
<Grid Background="{StaticResource BackgroundBrush}" Margin="10">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<!-- 标题 -->
|
||||||
|
<TextBlock Grid.Row="0"
|
||||||
|
Text="历史记录"
|
||||||
|
FontSize="18"
|
||||||
|
FontWeight="Bold"
|
||||||
|
Foreground="{StaticResource PrimaryBrush}"
|
||||||
|
Margin="0,0,0,10"/>
|
||||||
|
|
||||||
|
<!-- 数据表格 -->
|
||||||
|
<Border Grid.Row="1"
|
||||||
|
Style="{StaticResource PanelBorderStyle}">
|
||||||
|
<DataGrid ItemsSource="{Binding Records}"
|
||||||
|
SelectedItem="{Binding SelectedRecord}"
|
||||||
|
AutoGenerateColumns="False"
|
||||||
|
CanUserAddRows="False"
|
||||||
|
CanUserDeleteRows="False"
|
||||||
|
IsReadOnly="True"
|
||||||
|
HeadersVisibility="Column"
|
||||||
|
RowHeight="35"
|
||||||
|
FontSize="13"
|
||||||
|
AlternatingRowBackground="#F8F9FA"
|
||||||
|
GridLinesVisibility="Horizontal"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Stretch">
|
||||||
|
|
||||||
|
<DataGrid.Resources>
|
||||||
|
<Style TargetType="DataGridColumnHeader">
|
||||||
|
<Setter Property="Background" Value="{StaticResource PrimaryBrush}"/>
|
||||||
|
<Setter Property="Foreground" Value="White"/>
|
||||||
|
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||||
|
<Setter Property="Height" Value="35"/>
|
||||||
|
</Style>
|
||||||
|
</DataGrid.Resources>
|
||||||
|
|
||||||
|
<DataGrid.Columns>
|
||||||
|
<DataGridTextColumn Header="编号" Binding="{Binding RecordId}" Width="80"/>
|
||||||
|
<DataGridTextColumn Header="时间" Binding="{Binding TestTime}" Width="180"/>
|
||||||
|
<DataGridTextColumn Header="操作员编号" Binding="{Binding OperatorId}" Width="120"/>
|
||||||
|
<DataGridTextColumn Header="生产批号" Binding="{Binding BatchNo}" Width="150"/>
|
||||||
|
<DataGridTextColumn Header="实验编号" Binding="{Binding ExperimentId}" Width="100"/>
|
||||||
|
<DataGridTextColumn Header="初始质量(g)" Binding="{Binding InitialMass, StringFormat='{}{0:F2}'}" Width="120"/>
|
||||||
|
<DataGridTextColumn Header="测后质量(g)" Binding="{Binding FinalMass, StringFormat='{}{0:F2}'}" Width="120"/>
|
||||||
|
<DataGridTextColumn Header="水分损失(mg/L)" Binding="{Binding MoistureLoss, StringFormat='{}{0:F2}'}" Width="140">
|
||||||
|
<DataGridTextColumn.ElementStyle>
|
||||||
|
<Style>
|
||||||
|
<Setter Property="TextBlock.Foreground" Value="{StaticResource DangerBrush}"/>
|
||||||
|
<Setter Property="TextBlock.FontWeight" Value="Bold"/>
|
||||||
|
</Style>
|
||||||
|
</DataGridTextColumn.ElementStyle>
|
||||||
|
</DataGridTextColumn>
|
||||||
|
</DataGrid.Columns>
|
||||||
|
</DataGrid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- 底部操作按钮 -->
|
||||||
|
<WrapPanel Grid.Row="2"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Margin="0,10,0,0">
|
||||||
|
<Button Content="同步到MES"
|
||||||
|
Command="{Binding SyncToMesCommand}"
|
||||||
|
Width="120" Height="35"
|
||||||
|
Background="{StaticResource SuccessBrush}"/>
|
||||||
|
<Button Content="删除选中"
|
||||||
|
Command="{Binding DeleteSelectedRecordCommand}"
|
||||||
|
Width="120" Height="35"
|
||||||
|
Background="{StaticResource DangerBrush}"
|
||||||
|
Margin="10,0,0,0"/>
|
||||||
|
<Button Content="清除全部"
|
||||||
|
Command="{Binding ClearRecordsCommand}"
|
||||||
|
Width="120" Height="35"
|
||||||
|
Background="{StaticResource WarningBrush}"
|
||||||
|
Margin="10,0,0,0"/>
|
||||||
|
<TextBlock Text="提示:长按可清除记录"
|
||||||
|
Foreground="#7F8C8D"
|
||||||
|
FontSize="12"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Margin="20,0,0,0"/>
|
||||||
|
</WrapPanel>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
12
Views/RecordView.xaml.cs
Normal file
12
Views/RecordView.xaml.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using System.Windows.Controls;
|
||||||
|
|
||||||
|
namespace HME_MoistureLossMeter.Views
|
||||||
|
{
|
||||||
|
public partial class RecordView : UserControl
|
||||||
|
{
|
||||||
|
public RecordView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
Views/ReportView.xaml
Normal file
7
Views/ReportView.xaml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<UserControl x:Class="HME_MoistureLossMeter.Views.ReportView"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
|
<Grid Background="White">
|
||||||
|
<TextBlock Text="报表功能开发中..." FontSize="24" Foreground="Gray" HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
9
Views/ReportView.xaml.cs
Normal file
9
Views/ReportView.xaml.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
using System.Windows.Controls;
|
||||||
|
|
||||||
|
namespace HME_MoistureLossMeter.Views
|
||||||
|
{
|
||||||
|
public partial class ReportView : UserControl
|
||||||
|
{
|
||||||
|
public ReportView() => InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
299
Views/TestView.xaml
Normal file
299
Views/TestView.xaml
Normal file
@@ -0,0 +1,299 @@
|
|||||||
|
<UserControl x:Class="HME_MoistureLossMeter.Views.TestView"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="700" d:DesignWidth="1024">
|
||||||
|
|
||||||
|
<Grid Background="{StaticResource BackgroundBrush}" Margin="10">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="2*"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<!-- 左侧:主显示区域 -->
|
||||||
|
<Border Grid.Row="0" Grid.RowSpan="2" Grid.Column="0"
|
||||||
|
Style="{StaticResource PanelBorderStyle}"
|
||||||
|
Margin="0,0,10,0">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<!-- 标题 -->
|
||||||
|
<TextBlock Text="实时数据"
|
||||||
|
FontSize="16"
|
||||||
|
FontWeight="Bold"
|
||||||
|
Foreground="{StaticResource PrimaryBrush}"
|
||||||
|
Margin="0,0,0,10"/>
|
||||||
|
|
||||||
|
<!-- 数据显示网格 -->
|
||||||
|
<Grid Grid.Row="1">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
<ColumnDefinition Width="*"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<!-- 第一列 -->
|
||||||
|
<StackPanel Grid.Row="0" Grid.Column="0" Margin="5">
|
||||||
|
<TextBlock Text="水浴温度" Style="{StaticResource DisplayLabelStyle}"/>
|
||||||
|
<TextBlock Text="{Binding WaterTemp, StringFormat='{}{0:F2} ℃'}"
|
||||||
|
Style="{StaticResource DisplayValueStyle}"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="1" Grid.Column="0" Margin="5">
|
||||||
|
<TextBlock Text="箱体温度" Style="{StaticResource DisplayLabelStyle}"/>
|
||||||
|
<TextBlock Text="{Binding ChamberTemp, StringFormat='{}{0:F2} ℃'}"
|
||||||
|
Style="{StaticResource DisplayValueStyle}"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="2" Grid.Column="0" Margin="5">
|
||||||
|
<TextBlock Text="重量" Style="{StaticResource DisplayLabelStyle}"/>
|
||||||
|
<TextBlock Text="{Binding Weight, StringFormat='{}{0:F2} g'}"
|
||||||
|
Style="{StaticResource DisplayValueStyle}"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="3" Grid.Column="0" Margin="5">
|
||||||
|
<TextBlock Text="空气体积" Style="{StaticResource DisplayLabelStyle}"/>
|
||||||
|
<TextBlock Text="{Binding AirVolume, StringFormat='{}{0:F2} L'}"
|
||||||
|
Style="{StaticResource DisplayValueStyle}"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="4" Grid.Column="0" Margin="5">
|
||||||
|
<TextBlock Text="出口流量" Style="{StaticResource DisplayLabelStyle}"/>
|
||||||
|
<TextBlock Text="{Binding OutletFlow, StringFormat='{}{0:F2} L/min'}"
|
||||||
|
Style="{StaticResource DisplayValueStyle}"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="5" Grid.Column="0" Margin="5">
|
||||||
|
<TextBlock Text="潮气量" Style="{StaticResource DisplayLabelStyle}"/>
|
||||||
|
<TextBlock Text="{Binding TidalVolume, StringFormat='{}{0:F1} ml'}"
|
||||||
|
Style="{StaticResource DisplayValueStyle}"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="6" Grid.Column="0" Margin="5">
|
||||||
|
<TextBlock Text="频率" Style="{StaticResource DisplayLabelStyle}"/>
|
||||||
|
<TextBlock Text="{Binding Frequency, StringFormat='{}{0:F2} 次/min'}"
|
||||||
|
Style="{StaticResource DisplayValueStyle}"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="7" Grid.Column="0" Margin="5">
|
||||||
|
<TextBlock Text="呼吸次数" Style="{StaticResource DisplayLabelStyle}"/>
|
||||||
|
<TextBlock Text="{Binding BreathCount}"
|
||||||
|
Style="{StaticResource DisplayValueStyle}"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- 第二列 -->
|
||||||
|
<StackPanel Grid.Row="0" Grid.Column="1" Margin="5">
|
||||||
|
<TextBlock Text="设定测试时间" Style="{StaticResource DisplayLabelStyle}"/>
|
||||||
|
<TextBlock Text="{Binding PresetHour, StringFormat='{}{0}H'}"
|
||||||
|
Style="{StaticResource DisplayValueStyle}"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="1" Grid.Column="1" Margin="5">
|
||||||
|
<TextBlock Text="当前时间" Style="{StaticResource DisplayLabelStyle}"/>
|
||||||
|
<TextBlock Text="{Binding CurrentTime, StringFormat='{}{0:HH:mm:ss}'}"
|
||||||
|
Style="{StaticResource DisplayValueStyle}"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="2" Grid.Column="1" Margin="5">
|
||||||
|
<TextBlock Text="初始质量" Style="{StaticResource DisplayLabelStyle}"/>
|
||||||
|
<TextBlock Text="{Binding InitialMass, StringFormat='{}{0:F2} g'}"
|
||||||
|
Style="{StaticResource DisplayValueStyle}"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="3" Grid.Column="1" Margin="5">
|
||||||
|
<TextBlock Text="测后质量" Style="{StaticResource DisplayLabelStyle}"/>
|
||||||
|
<TextBlock Text="{Binding FinalMass, StringFormat='{}{0:F2} g'}"
|
||||||
|
Style="{StaticResource DisplayValueStyle}"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="4" Grid.Column="1" Margin="5">
|
||||||
|
<TextBlock Text="水分损失" Style="{StaticResource DisplayLabelStyle}"/>
|
||||||
|
<TextBlock Text="{Binding MoistureLoss, StringFormat='{}{0:F2} mg/L'}"
|
||||||
|
Style="{StaticResource DisplayValueStyle}"
|
||||||
|
Foreground="{StaticResource DangerBrush}"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="5" Grid.Column="1" Margin="5">
|
||||||
|
<TextBlock Text="干燥空气量" Style="{StaticResource DisplayLabelStyle}"/>
|
||||||
|
<TextBlock Text="{Binding DryAirFlow, StringFormat='{}{0:F2} L/min'}"
|
||||||
|
Style="{StaticResource DisplayValueStyle}"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="6" Grid.Column="1" Margin="5">
|
||||||
|
<TextBlock Text="测试状态" Style="{StaticResource DisplayLabelStyle}"/>
|
||||||
|
<TextBlock Text="{Binding IsTesting, Converter={StaticResource BoolToVisibilityConverter},
|
||||||
|
FallbackValue=未测试}"
|
||||||
|
Style="{StaticResource DisplayValueStyle}"
|
||||||
|
Foreground="{Binding IsTesting, Converter={StaticResource BoolToVisibilityConverter},
|
||||||
|
ConverterParameter=SuccessBrush, FallbackValue={StaticResource WarningBrush}}"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- 第三列:状态指示灯 -->
|
||||||
|
<StackPanel Grid.Row="0" Grid.Column="2" Margin="5">
|
||||||
|
<TextBlock Text="状态指示灯" Style="{StaticResource DisplayLabelStyle}"/>
|
||||||
|
<WrapPanel Margin="0,5,0,0">
|
||||||
|
<Border Background="{Binding IsHeating, Converter={StaticResource BoolToVisibilityConverter},
|
||||||
|
ConverterParameter=WarningBrush, FallbackValue=#95A5A6}"
|
||||||
|
Width="20" Height="20" CornerRadius="10" Margin="2"/>
|
||||||
|
<TextBlock Text="加热" FontSize="11" VerticalAlignment="Center" Margin="2,0,10,0"/>
|
||||||
|
|
||||||
|
<Border Background="{Binding IsInhale, Converter={StaticResource BoolToVisibilityConverter},
|
||||||
|
ConverterParameter=SuccessBrush, FallbackValue=#95A5A6}"
|
||||||
|
Width="20" Height="20" CornerRadius="10" Margin="2"/>
|
||||||
|
<TextBlock Text="吸气" FontSize="11" VerticalAlignment="Center" Margin="2,0,10,0"/>
|
||||||
|
|
||||||
|
<Border Background="{Binding IsExhale, Converter={StaticResource BoolToVisibilityConverter},
|
||||||
|
ConverterParameter=DangerBrush, FallbackValue=#95A5A6}"
|
||||||
|
Width="20" Height="20" CornerRadius="10" Margin="2"/>
|
||||||
|
<TextBlock Text="呼气" FontSize="11" VerticalAlignment="Center" Margin="2,0,10,0"/>
|
||||||
|
</WrapPanel>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="1" Grid.Column="2" Margin="5">
|
||||||
|
<TextBlock Text="PLC状态" Style="{StaticResource DisplayLabelStyle}"/>
|
||||||
|
<TextBlock Text="{Binding ConnectionStatus}"
|
||||||
|
Style="{StaticResource DisplayValueStyle}"
|
||||||
|
Foreground="{Binding IsConnected, Converter={StaticResource BoolToVisibilityConverter},
|
||||||
|
ConverterParameter=SuccessBrush, FallbackValue={StaticResource DangerBrush}}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- 底部信息 -->
|
||||||
|
<Border Grid.Row="2" Background="#F8F9FA" CornerRadius="4" Padding="5" Margin="0,10,0,0">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<TextBlock Text="实验条件: " FontWeight="Bold" FontSize="12"/>
|
||||||
|
<TextBlock Text="{Binding CurrentTime, StringFormat='{}{0:MM/dd/yy ddd HH:mm:ss}'}" FontSize="12"/>
|
||||||
|
<TextBlock Text=" | 操作员: " FontWeight="Bold" FontSize="12" Margin="10,0,0,0"/>
|
||||||
|
<TextBlock Text="{Binding OperatorId}" FontSize="12"/>
|
||||||
|
<TextBlock Text=" | 批号: " FontWeight="Bold" FontSize="12" Margin="10,0,0,0"/>
|
||||||
|
<TextBlock Text="{Binding BatchNo}" FontSize="12"/>
|
||||||
|
<TextBlock Text=" | 实验编号: " FontWeight="Bold" FontSize="12" Margin="10,0,0,0"/>
|
||||||
|
<TextBlock Text="{Binding ExperimentId}" FontSize="12"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- 右侧:控制区域 -->
|
||||||
|
<Border Grid.Row="0" Grid.Column="1"
|
||||||
|
Style="{StaticResource PanelBorderStyle}"
|
||||||
|
Margin="10,0,0,0">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/>
|
||||||
|
<RowDefinition Height="*"/>
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<TextBlock Text="测试控制"
|
||||||
|
FontSize="16"
|
||||||
|
FontWeight="Bold"
|
||||||
|
Foreground="{StaticResource PrimaryBrush}"
|
||||||
|
Margin="0,0,0,10"/>
|
||||||
|
|
||||||
|
<WrapPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,10,0,0">
|
||||||
|
<Button Content="复位"
|
||||||
|
Command="{Binding ResetCommand}"
|
||||||
|
Width="80" Height="40"
|
||||||
|
Background="{StaticResource WarningBrush}"/>
|
||||||
|
|
||||||
|
<Button Content="测试"
|
||||||
|
Command="{Binding StartTestCommand}"
|
||||||
|
Width="80" Height="40"
|
||||||
|
Background="{StaticResource SuccessBrush}"/>
|
||||||
|
|
||||||
|
<Button Content="停止"
|
||||||
|
Command="{Binding StopTestCommand}"
|
||||||
|
Width="80" Height="40"
|
||||||
|
Background="{StaticResource DangerBrush}"/>
|
||||||
|
|
||||||
|
<Button Content="P1记录"
|
||||||
|
Command="{Binding RecordP1Command}"
|
||||||
|
Width="80" Height="40"/>
|
||||||
|
|
||||||
|
<Button Content="P2记录"
|
||||||
|
Command="{Binding RecordP2Command}"
|
||||||
|
Width="80" Height="40"/>
|
||||||
|
|
||||||
|
<Button Content="打印"
|
||||||
|
Command="{Binding PrintCommand}"
|
||||||
|
Width="80" Height="40"/>
|
||||||
|
|
||||||
|
<Button Content="加热"
|
||||||
|
Command="{Binding ToggleHeatCommand}"
|
||||||
|
Width="80" Height="40"
|
||||||
|
Background="{Binding IsHeating, Converter={StaticResource BoolToVisibilityConverter},
|
||||||
|
ConverterParameter=DangerBrush, FallbackValue={StaticResource AccentBrush}}"/>
|
||||||
|
|
||||||
|
<Button Content="呼气"
|
||||||
|
Command="{Binding ManualExhaleCommand}"
|
||||||
|
Width="80" Height="40"
|
||||||
|
Background="{StaticResource DangerBrush}"/>
|
||||||
|
|
||||||
|
<Button Content="吸气"
|
||||||
|
Command="{Binding ManualInhaleCommand}"
|
||||||
|
Width="80" Height="40"
|
||||||
|
Background="{StaticResource SuccessBrush}"/>
|
||||||
|
|
||||||
|
<Button Content="下降"
|
||||||
|
Command="{Binding MoveDownCommand}"
|
||||||
|
Width="80" Height="40"
|
||||||
|
Background="{StaticResource WarningBrush}"/>
|
||||||
|
|
||||||
|
<Button Content="上升"
|
||||||
|
Command="{Binding MoveUpCommand}"
|
||||||
|
Width="80" Height="40"
|
||||||
|
Background="{StaticResource WarningBrush}"/>
|
||||||
|
|
||||||
|
<Button Content="清零"
|
||||||
|
Command="{Binding ClearCommand}"
|
||||||
|
Width="80" Height="40"/>
|
||||||
|
</WrapPanel>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- 连接控制 -->
|
||||||
|
<Border Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2"
|
||||||
|
Style="{StaticResource PanelBorderStyle}"
|
||||||
|
Margin="0,10,0,0">
|
||||||
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||||
|
<Button Content="连接PLC"
|
||||||
|
Command="{Binding ConnectCommand}"
|
||||||
|
Width="100" Height="35"
|
||||||
|
Background="{StaticResource SuccessBrush}"/>
|
||||||
|
<Button Content="断开PLC"
|
||||||
|
Command="{Binding DisconnectCommand}"
|
||||||
|
Width="100" Height="35"
|
||||||
|
Background="{StaticResource DangerBrush}"
|
||||||
|
Margin="10,0,0,0"/>
|
||||||
|
<TextBlock Text="状态:" FontWeight="Bold" Margin="20,0,5,0" VerticalAlignment="Center"/>
|
||||||
|
<TextBlock Text="{Binding ConnectionStatus}"
|
||||||
|
Foreground="{Binding IsConnected, Converter={StaticResource BoolToVisibilityConverter},
|
||||||
|
ConverterParameter=SuccessBrush, FallbackValue={StaticResource DangerBrush}}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
FontWeight="Bold"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
12
Views/TestView.xaml.cs
Normal file
12
Views/TestView.xaml.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using System.Windows.Controls;
|
||||||
|
|
||||||
|
namespace HME_MoistureLossMeter.Views
|
||||||
|
{
|
||||||
|
public partial class TestView : UserControl
|
||||||
|
{
|
||||||
|
public TestView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
24
appsettings.json
Normal file
24
appsettings.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"PlcConfiguration": {
|
||||||
|
"IpAddress": "192.168.1.100",
|
||||||
|
"Port": 502,
|
||||||
|
"SlaveId": 1,
|
||||||
|
"PressureRegister": 1000,
|
||||||
|
"WetFlowRegister": 1002,
|
||||||
|
"WetFlowRegister2": 1004,
|
||||||
|
"WetFlowRegister3": 1006,
|
||||||
|
"PressureRegisterStation1": 1008,
|
||||||
|
"PressureRegisterStation2": 1010,
|
||||||
|
"PressureRegisterStation3": 1012
|
||||||
|
},
|
||||||
|
"MesConfiguration": {
|
||||||
|
"BaseUrl": "http://192.168.1.200:8080",
|
||||||
|
"ApiKey": "your-api-key",
|
||||||
|
"TimeoutSeconds": 30,
|
||||||
|
"RetryCount": 3
|
||||||
|
},
|
||||||
|
"DeviceConfiguration": {
|
||||||
|
"DeviceId": "HME-001",
|
||||||
|
"UpdateIntervalMs": 1000
|
||||||
|
}
|
||||||
|
}
|
||||||
36
新版HME水分损失测量仪 _天平.csproj
Normal file
36
新版HME水分损失测量仪 _天平.csproj
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<TargetFramework>net8.0-windows</TargetFramework>
|
||||||
|
<RootNamespace>新版HME水分损失测量仪__天平</RootNamespace>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<UseWPF>true</UseWPF>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Helpers\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.2" />
|
||||||
|
<PackageReference Include="LiveCharts.Wpf" Version="0.9.7" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.9" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.9" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.9" />
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||||
|
<PackageReference Include="NModbus4.Core" Version="1.0.2" />
|
||||||
|
<PackageReference Include="Serilog" Version="4.3.1" />
|
||||||
|
<PackageReference Include="Serilog.Extensions.Hosting" Version="10.0.0" />
|
||||||
|
<PackageReference Include="Serilog.Extensions.Logging" Version="10.0.0" />
|
||||||
|
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="appsettings.json">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
3
新版HME水分损失测量仪 _天平.slnx
Normal file
3
新版HME水分损失测量仪 _天平.slnx
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<Solution>
|
||||||
|
<Project Path="新版HME水分损失测量仪 _天平.csproj" />
|
||||||
|
</Solution>
|
||||||
Reference in New Issue
Block a user