添加项目文件。
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