Files
Sleep-Multi-functionality/Window8.xaml.cs
2026-05-08 19:43:08 +08:00

464 lines
17 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using Modbus.Device;
using Modbus;
using OxyPlot;
using OxyPlot.Axes;
using OxyPlot.Legends;
using OxyPlot.Series;
using System.Configuration;
using System.Net.Sockets;
using System.Windows.Interop;
using ;
namespace LineChartDemo
{
public partial class MainWindow : Window
{
private readonly string _lang = ConfigurationManager.AppSettings["Language"] ?? "zh-CN";
#region
private PlotModel _plotModel;
private LineSeries _exhaleSeries;
private LineSeries _inhaleSeries;
private List<DataPoint> _exhaleData = new List<DataPoint>();
private List<DataPoint> _inhaleData = new List<DataPoint>();
private DispatcherTimer _dataTimer;
private double _timeCounter = 0;
private const int MAX_DATA_POINTS = 30; // 最多显示30个数据点
private Function ma;
private DataChange c = new DataChange();
// Modbus通信
private TcpClient _tcpClient;
private IModbusMaster _modbusMaster;
// 坐标轴引用
private LinearAxis _xAxis;
private LinearAxis _yAxis;
// 性能优化相关
private bool _isResizing = false;
private readonly object _lockObj = new object();
private CancellationTokenSource _resizeTokenSource;
#endregion
#region Modbus相关配置
private readonly ushort _OutBreathAddress = 0x1398; // D5016
private readonly ushort _InBreathAddress = 0x1396; // D5014
#endregion
public MainWindow()
{
InitializeComponent();
RenderOptions.ProcessRenderMode = RenderMode.Default;
InitializeModbusTcp();
InitializePlot();
InitializeEvents();
}
#region
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
base.OnRenderSizeChanged(sizeInfo);
if (!_isResizing)
{
_isResizing = true;
_dataTimer?.Stop();
}
_resizeTokenSource?.Cancel();
_resizeTokenSource = new CancellationTokenSource();
var token = _resizeTokenSource.Token;
Task.Delay(50, token).ContinueWith(_ =>
{
Dispatcher.Invoke(() =>
{
if (!token.IsCancellationRequested)
{
_plotModel?.InvalidatePlot(true);
_isResizing = false;
if (StopBtn.IsEnabled)
{
_dataTimer?.Start();
}
}
});
}, token);
}
#endregion
#region Modbus初始化
private void InitializeModbusTcp()
{
try
{
string plcIp = ConfigurationManager.AppSettings["PLC2_IP"];
int plcPort = int.Parse(ConfigurationManager.AppSettings["PLC2_Port"] ?? "502");
_tcpClient = new TcpClient();
var connectResult = _tcpClient.BeginConnect(plcIp, plcPort, null, null);
bool success = connectResult.AsyncWaitHandle.WaitOne(3000);
if (!success || !_tcpClient.Connected)
throw new Exception(_lang == "en-US" ? "PLC connect timeout" : "连接PLC超时请检查IP和端口");
_modbusMaster = ModbusIpMaster.CreateIp(_tcpClient);
_modbusMaster.Transport.ReadTimeout = 1000;
_modbusMaster.Transport.WriteTimeout = 1000;
ma = new Function(_modbusMaster);
StatusText.Text = _lang == "en-US" ? "Status: Modbus connected" : "状态Modbus连接成功";
StatusText.Foreground = new SolidColorBrush(Color.FromRgb(76, 175, 80));
}
catch (Exception ex)
{
ShowError(_lang == "en-US" ? $"Modbus init failed: {ex.Message}" : $"Modbus初始化失败: {ex.Message}");
StatusText.Text = _lang == "en-US" ? "Status: Connect failed" : "状态:连接失败";
StatusText.Foreground = new SolidColorBrush(Color.FromRgb(239, 83, 80));
}
}
private void ShowError(string message) => MessageBox.Show(message, "错误", MessageBoxButton.OK, MessageBoxImage.Error);
#endregion
#region
private void InitializePlot()
{
_plotModel = new PlotModel
{
Title = _lang == "en-US"
? "Flow Monitoring (Latest 30 Points)"
: "呼气流量与吸气流量监测最新30个数据",
TitleFontSize = 18,
TitleFontWeight = OxyPlot.FontWeights.Bold,
Background = OxyColors.White,
PlotAreaBackground = OxyColor.FromRgb(250, 252, 255),
};
var legend = new Legend
{
LegendPlacement = LegendPlacement.Outside,
LegendPosition = LegendPosition.TopRight,
FontSize = 14
};
_plotModel.Legends.Add(legend);
_exhaleSeries = new LineSeries
{
Title = _lang == "en-US" ? "Exhale Flow (L/min)" : "呼气流量 (L/min)",
Color = OxyColor.FromRgb(239, 83, 80),
StrokeThickness = 2,
MarkerType = MarkerType.None,
MarkerSize = 4
};
_inhaleSeries = new LineSeries
{
Title = _lang == "en-US" ? "Inhale Flow (L/min)" : "吸气流量 (L/min)",
Color = OxyColor.FromRgb(66, 165, 245),
StrokeThickness = 2,
MarkerType = MarkerType.None,
MarkerSize = 4
};
_plotModel.Series.Add(_exhaleSeries);
_plotModel.Series.Add(_inhaleSeries);
_xAxis = new LinearAxis
{
Position = AxisPosition.Bottom,
Title = _lang == "en-US" ? "Time (s)" : "时间 (秒)",
TitleFontSize = 14,
AxislineColor = OxyColors.LightGray,
MajorGridlineColor = OxyColor.FromRgb(220, 231, 243),
MajorGridlineStyle = LineStyle.Solid,
MinorGridlineStyle = LineStyle.None,
FontSize = 12,
Minimum = 0
};
_yAxis = new LinearAxis
{
Position = AxisPosition.Left,
Title = _lang == "en-US" ? "Flow (L/min)" : "流量 (L/min)",
TitleFontSize = 14,
AxislineColor = OxyColors.LightGray,
MajorGridlineColor = OxyColor.FromRgb(220, 231, 243),
MajorGridlineStyle = LineStyle.Solid,
MinorGridlineStyle = LineStyle.None,
FontSize = 12,
MajorStep = 5
};
_plotModel.Axes.Add(_xAxis);
_plotModel.Axes.Add(_yAxis);
PlotView.Model = _plotModel;
}
#endregion
#region
private void InitializeEvents()
{
_dataTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(500) };
_dataTimer.Tick += async (s, e) => await UpdateData();
StartBtn.Click += (s, e) =>
{
_dataTimer.Start();
_ = UpdateData();
StartBtn.IsEnabled = false;
StopBtn.IsEnabled = true;
StatusText.Text = _lang == "en-US" ? "Status: Monitoring" : "状态:正在监测";
StatusText.Foreground = new SolidColorBrush(Color.FromRgb(76, 175, 80));
_exhaleSeries.MarkerType = MarkerType.Circle;
_inhaleSeries.MarkerType = MarkerType.Circle;
};
StopBtn.Click += (s, e) =>
{
_dataTimer.Stop();
StartBtn.IsEnabled = true;
StopBtn.IsEnabled = false;
StatusText.Text = _lang == "en-US" ? "Status: Stopped" : "状态:已停止";
StatusText.Foreground = new SolidColorBrush(Color.FromRgb(156, 39, 176));
};
ClearBtn.Click += (s, e) =>
{
lock (_lockObj)
{
_exhaleData.Clear();
_inhaleData.Clear();
_exhaleSeries.Points.Clear();
_inhaleSeries.Points.Clear();
_timeCounter = 0;
AdjustYAxisRange();
AdjustXAxisRange();
_plotModel.InvalidatePlot(true);
}
};
}
#endregion
#region 20
private async Task UpdateData()
{
if (_isResizing) return;
if (!IsModbusConnected())
{
StatusText.Text = "状态Modbus已断开";
StatusText.Foreground = new SolidColorBrush(Color.FromRgb(239, 83, 80));
return;
}
try
{
var (exhaleValue, inhaleValue) = await Task.Run(() => ReadModbusData(),
CancellationToken.None);
lock (_lockObj)
{
// 添加新数据点
var exhalePoint = new DataPoint(_timeCounter, exhaleValue);
var inhalePoint = new DataPoint(_timeCounter, inhaleValue);
_exhaleData.Add(exhalePoint);
_inhaleData.Add(inhalePoint);
// 关键只保留最新的30个数据点
if (_exhaleData.Count > MAX_DATA_POINTS)
{
// 移除最旧的数据点超出20个的部分
int removeCount = _exhaleData.Count - MAX_DATA_POINTS;
_exhaleData.RemoveRange(0, removeCount);
_inhaleData.RemoveRange(0, removeCount);
}
_timeCounter += 0.5;
// 更新折线数据
_exhaleSeries.Points.Clear();
_inhaleSeries.Points.Clear();
_exhaleData.ForEach(p => _exhaleSeries.Points.Add(p));
_inhaleData.ForEach(p => _inhaleSeries.Points.Add(p));
// 调整坐标轴范围基于当前20个数据
AdjustYAxisRange();
AdjustXAxisRange();
}
_plotModel.InvalidatePlot(false);
}
catch (Exception ex)
{
StatusText.Text = $"状态:数据错误 - {ex.Message}";
StatusText.Foreground = new SolidColorBrush(Color.FromRgb(239, 83, 80));
}
}
private (float exhale, float inhale) ReadModbusData()
{
try
{
ushort[] exhalationData = _modbusMaster?.ReadHoldingRegisters(0x01, _OutBreathAddress, 2);
ushort[] inhalationData = _modbusMaster?.ReadHoldingRegisters(0x01, _InBreathAddress, 2);
float exhaleValue = c.UshortToFloat(exhalationData[1], exhalationData[0]);
float inhaleValue = c.UshortToFloat(inhalationData[1], inhalationData[0]);
return ((float)Math.Round(exhaleValue, 4), (float)Math.Round(inhaleValue, 4));
}
catch (Exception ex)
{
throw new Exception($"Modbus读取错误{ex.Message}");
}
}
private void AdjustYAxisRange()
{
var allYValues = new List<double>();
allYValues.AddRange(_exhaleData.Select(p => p.Y));
allYValues.AddRange(_inhaleData.Select(p => p.Y));
if (!allYValues.Any())
{
// 无数据时默认范围(预留一定空间,避免后续数据突然增大时轴范围不足)
_yAxis.Minimum = 0;
_yAxis.Maximum = 10; // 初始值设大一些,应对可能的大数值
return;
}
double currentMin = allYValues.Min();
double currentMax = allYValues.Max();
// 计算当前数据的波动范围
double dataRange = currentMax - currentMin;
// 1. 底部留固定余量(避免数据贴底)
double bottomPadding = Math.Max(0.5, dataRange * 0.1); // 取两者较大值确保至少0.5
double newMin = currentMin - bottomPadding;
// 若数据全为正数Y轴从0开始更合理可选根据业务场景
if (newMin < 0 && currentMin >= 0)
newMin = 0;
// 2. 顶部留动态余量(随数据增大自动扩展)
double topPadding = Math.Max(1, dataRange * 0.2); // 顶部余量稍大,应对突发大值
double newMax = currentMax + topPadding;
// 3. 确保Y轴范围不会“收缩”避免数据忽大忽小时轴频繁跳动
// 仅当新范围的上限大于当前轴上限时,才更新上限
if (newMax > _yAxis.Maximum)
{
_yAxis.Maximum = newMax;
}
// 下限可根据当前数据动态收缩但不小于0若数据全为正
_yAxis.Minimum = newMin;
// 强制设置轴的“弹性”允许数据超出当前范围时自动扩展OxyPlot特性
_yAxis.IsZoomEnabled = false; // 禁止用户手动缩放(避免干扰自动范围)
}
private void AdjustXAxisRange()
{
if (_exhaleData.Count == 0 && _inhaleData.Count == 0)
{
_xAxis.Maximum = 1;
return;
}
// X轴范围基于最新20个数据的时间范围
double maxX = Math.Max(
_exhaleData.DefaultIfEmpty().Max(p => p.X),
_inhaleData.DefaultIfEmpty().Max(p => p.X)
);
// 当数据满20个时X轴范围固定为这20个数据的时间跨度
if (_exhaleData.Count >= MAX_DATA_POINTS)
{
double minX = _exhaleData.Min(p => p.X);
_xAxis.Minimum = minX;
_xAxis.Maximum = maxX + 0.5; // 右侧留一点余量
}
else
{
_xAxis.Minimum = 0;
_xAxis.Maximum = maxX < 1 ? 1 : maxX * 1.1;
}
}
private bool IsModbusConnected()
{
return _tcpClient != null && _tcpClient.Connected && _modbusMaster != null;
}
#endregion
#region
private void Window_Loaded(object sender, RoutedEventArgs e)
{
try
{
string imagePath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources/sleep2.jpg");
if (System.IO.File.Exists(imagePath))
{
var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.UriSource = new Uri(imagePath, UriKind.Absolute);
bitmap.DecodePixelWidth = (int)this.Width;
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.EndInit();
Background = new ImageBrush
{
ImageSource = bitmap,
Stretch = Stretch.UniformToFill,
//CacheOption = BitmapCacheOption.OnLoad
};
}
else
{
Console.WriteLine($"背景图片不存在: {imagePath}");
}
// 获取屏幕工作区(排除任务栏)
var workingArea = SystemParameters.WorkArea;
// 计算居中位置
this.Left = workingArea.Left + (workingArea.Width - this.Width) / 2;
this.Top = workingArea.Top + (workingArea.Height - this.Height) / 2;
}
catch (Exception ex)
{
Console.WriteLine($"加载背景失败: {ex.Message}");
Background = Brushes.White;
}
}
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
_dataTimer?.Stop();
_tcpClient?.Close();
_resizeTokenSource?.Dispose();
}
#endregion
}
}