using Modbus.Device; using System; using System.Collections.Generic; using System.Configuration; using System.IO.Ports; using System.Linq; using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using System.Windows.Threading; using 自救器呼吸器综合检验仪.Data; namespace 自救器呼吸器综合检验仪 { /// /// MainWindow.xaml 的交互逻辑 /// public partial class MainWindow : Window { private MainWindow _mainWindow; private MainWindow2 _mainWindow2; private MainWindow3 _mainWindow3; private MainWindow4 _mainWindow4; private ParameterSettingsWindow _mainWindow5; private ReportWindow _reportWindow; private bool _wasRunning = false; // 上次检验的运行状态 private bool _recordAdded = false; // 本轮测试是否已插入记录 private DateTime _testStartTime; // 测试开始时间 int retryCount = 0; // 连接失败重试次数 private TcpClient _tcpClient => ModbusResourceManager.Instance.TcpClient; private IModbusMaster _modbusMaster => ModbusResourceManager.Instance.ModbusMaster; private const byte SlaveId = 1; // PLC从站地址 private const ushort Address262 = 300; private bool _isManualInput = false; // 手动输入标记:true=输入中(暂停读取),false=正常读取 private readonly DispatcherTimer _readTimer; Function ma; DataChange c = new DataChange(); public MainWindow() { InitializeComponent(); _readTimer = InitDispatcherTimer(); //InitializeModbusTcp(); BindInputFocusEvents(); } private void BindInputFocusEvents() { // 地址400输入框(count) settingtime.GotFocus += (s, e) => _isManualInput = true; // 获得焦点→暂停读取 settingtime.LostFocus += (s, e) => _isManualInput = false; // 失去焦点→恢复读取 } private DispatcherTimer InitDispatcherTimer() { var timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(500) }; timer.Tick += async (s, e) => { if (!_isManualInput && _modbusMaster != null) { try { await ReadAllPlcDataAsync(); } catch { } } }; return timer; } private async System.Threading.Tasks.Task ReadAllPlcDataAsync() { try { // 检查连接状态 if (_tcpClient == null || !_tcpClient.Connected || _modbusMaster == null) { return; } // 仅在非手动输入时读取设置地址(避免覆盖输入) if (!_isManualInput) { await ReadAddr262DataAsync(); await System.Threading.Tasks.Task.Delay(50); await ReadAddr404DataAsync(); await System.Threading.Tasks.Task.Delay(50); await ReadAddr406DataAsync(); await System.Threading.Tasks.Task.Delay(50); await ReadAddr130DataAsync(); } await ReadAddrStatusDataAsync(); } catch (Exception ex) { retryCount++; if (retryCount > 3) { ShowErrorMsg($"读取PLC数据失败:{ex.Message}"); retryCount = 0; return; } await System.Threading.Tasks.Task.Delay(500); await ReadAllPlcDataAsync(); } } private async System.Threading.Tasks.Task ReadAddr262DataAsync() { try { ushort[] registers = await System.Threading.Tasks.Task.Run(() => _modbusMaster.ReadHoldingRegisters(1, Address262, 2) ); if (registers != null) { int addr406Value = registers[0]; Dispatcher.Invoke(() => settingtime.Text = addr406Value.ToString("F0")); } } catch (Exception ex) { //ShowErrorMsg($"读取地址406失败:{ex.Message}"); } } private async System.Threading.Tasks.Task ReadAddr404DataAsync() { try { ushort[] registers = await System.Threading.Tasks.Task.Run(() => _modbusMaster.ReadHoldingRegisters(1, 1330, 2) ); if (registers != null) { float addr406Value = c.UshortToFloat(registers[1], registers[0]); Dispatcher.Invoke(() => min.Text = addr406Value.ToString("F2")); } } catch (Exception ex) { //ShowErrorMsg($"读取地址406失败:{ex.Message}"); } } private async System.Threading.Tasks.Task ReadAddr406DataAsync() { try { ushort[] registers = await System.Threading.Tasks.Task.Run(() => _modbusMaster.ReadHoldingRegisters(1, 72, 1) ); if (registers != null) { int addr406Value = registers[0]; Dispatcher.Invoke(() => second.Text = addr406Value.ToString("F0")); } } catch (Exception ex) { //ShowErrorMsg($"读取地址406失败:{ex.Message}"); } } private async System.Threading.Tasks.Task ReadAddr130DataAsync() { try { ushort[] registers = await System.Threading.Tasks.Task.Run(() => _modbusMaster?.ReadHoldingRegisters(1, 76, 2) ); if (registers != null) { double addr406Value = c.UshortToFloat(registers[1], registers[0]); addr406Value = Math.Truncate(addr406Value * 100) / 100.0; Dispatcher.Invoke(() => { speed.Text = addr406Value.ToString("F2"); }); } } catch (Exception ex) { //ShowErrorMsg($"读取地址406失败:{ex.Message}"); } } bool isReset = false; // 地址400写入(16位整数) private async void BtnWrite400_Click(object sender, RoutedEventArgs e) { _modbusMaster.WriteSingleCoil(1, 75, true); isReset = true; } private void ShowErrorMsg(string msg) { Dispatcher.Invoke(() => { MessageBox.Show(msg, "错误", MessageBoxButton.OK, MessageBoxImage.Error); }); } private void Button_Click(object sender, RoutedEventArgs e) { //ma.BtnClickFunctionForNew(Function.ButtonType.切换型, 170); OpenQuantitativeSupplyWindow2(); // SwitchWindow(ref _reportWindow, () => new ReportWindow()); } private void Button_Click_1(object sender, RoutedEventArgs e) { ma.BtnClickFunctionForNew(Function.ButtonType.切换型, 70); } private void Button_Click_2(object sender, RoutedEventArgs e) { //ma.BtnClickFunctionForNew(Function.ButtonType.复归型, 73); ma.BtnClickFunctionForNew(Function.ButtonType.复归型, 73); } private void Button_Click_3(object sender, RoutedEventArgs e) { ma.BtnClickFunctionForNew(Function.ButtonType.复归型, 74); } private void Button_Click_4(object sender, RoutedEventArgs e) { SwitchWindow(ref _mainWindow, () => new MainWindow()); } private void Button_Click_5(object sender, RoutedEventArgs e) { SwitchWindow(ref _mainWindow2, () => new MainWindow2()); } private void Button_Click_6(object sender, RoutedEventArgs e) { SwitchWindow(ref _mainWindow3, () => new MainWindow3()); } private void Button_Click_7(object sender, RoutedEventArgs e) { SwitchWindow(ref _mainWindow4, () => new MainWindow4()); } private void Window_Loaded(object sender, RoutedEventArgs e) { string plcIp = "192.168.1.10"; //string plcIp = "127.0.0.1"; bool initSuccess = Data.ModbusResourceManager.Instance.Init(plcIp, 502); if (!initSuccess) { MessageBox.Show("连接Modbus服务器失败!", "错误"); this.Close(); return; } // 检查连接状态 if (_tcpClient == null || !_tcpClient.Connected) { MessageBox.Show("Modbus连接异常!", "错误"); this.Close(); return; } ma = new Function(_modbusMaster); _modbusMaster.WriteSingleCoil(1, 61, true); System.Threading.Tasks.Task.Delay(50); _modbusMaster.WriteSingleCoil(1, 62, false); System.Threading.Tasks.Task.Delay(50); _modbusMaster.WriteSingleCoil(1, 63, false); System.Threading.Tasks.Task.Delay(50); _modbusMaster.WriteSingleCoil(1, 64, false); System.Threading.Tasks.Task.Delay(50); _modbusMaster.WriteSingleCoil(1, 75, true); _readTimer.Start(); pressureDiff2.Text = ConfigurationManager.AppSettings["No"]?.ToString(); string imagePath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources/sleep2.jpg"); ImageBrush brush = new ImageBrush(); brush.ImageSource = new BitmapImage(new Uri(imagePath, UriKind.Absolute)); this.Background = brush; } private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) { // 停止定时器 _readTimer?.Stop(); // 停止ViewModel的定时器 if (DataContext is MainViewModel viewModel) { viewModel.StopTimer(); } // 释放Modbus资源 ModbusResourceManager.Instance?.Dispose(); // 确保应用程序完全退出 Application.Current.Shutdown(); } private void Window_Closed(object sender, EventArgs e) { // 清理其他窗口实例 //_mainWindow?.Close(); _mainWindow2?.Close(); _mainWindow3?.Close(); _mainWindow4?.Close(); _mainWindow5?.Close(); _reportWindow?.Close(); } private void BtnWrite401_Click(object sender, RoutedEventArgs e) { if (!short.TryParse(settingtime.Text.Trim(), out short writeValue) || writeValue < short.MinValue || writeValue > short.MaxValue) { ShowErrorMsg($"需输入整数(范围:{short.MinValue} ~ {short.MaxValue})"); settingtime.Focus(); return; } try { //await System.Threading.Tasks.Task.Run(() => // _modbusMaster.WriteSingleRegister(SlaveId, Address262, (ushort)writeValue) //); ma.WriteToPLCForNew(settingtime.Text.Trim(), 300, Function.DataType.整形); System.Threading.Tasks.Task.Delay(50); _isManualInput = false; // 写入后恢复读取 //await ReadAddr400DataAsync(); // 刷新显示(确认写入成功) } catch (Exception ex) { ShowErrorMsg($"地址400写入失败:{ex.Message}"); } } private void SwitchWindow(ref T windowInstance, Func createFunc) where T : Window, new() { // 停止当前窗口的定时器(不释放资源) _readTimer?.Stop(); // 检查资源是否可用(添加重连机制) if (_tcpClient == null || !_tcpClient.Connected || _modbusMaster == null) { if (!TryReconnect()) { MessageBox.Show("TCP连接已断开,请重新连接!", "提示"); return; } } // 创建一个简易的加载覆盖窗口,作为状态栏/遮罩,避免白屏 Window overlay = null; try { overlay = new Window { Owner = this, WindowStyle = WindowStyle.None, AllowsTransparency = true, // 取消遮罩背景色,使用完全透明背景以避免暗色遮罩 Background = Brushes.Transparent, ShowInTaskbar = false, ResizeMode = ResizeMode.NoResize, Width = this.ActualWidth, Height = this.ActualHeight, Left = this.Left, Top = this.Top, ShowActivated = false, Topmost = true }; // 内容:底部居中的状态文本(不遮挡背景) var grid = new Grid(); var tb = new TextBlock { Text = "正在加载,请稍候...", Foreground = Brushes.Black, Background = Brushes.Transparent, HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Bottom, Margin = new Thickness(0, 0, 0, 20), FontSize = 16 }; grid.Children.Add(tb); overlay.Content = grid; // 显示遮罩 overlay.Show(); void ShowTargetWindow(T target) { // 如果目标已可见,直接激活并隐藏当前 if (target.IsVisible) { target.Activate(); this.Hide(); overlay?.Close(); return; } target.Opacity = 0; void OnContentRendered(object s, EventArgs e) { target.ContentRendered -= OnContentRendered; var anim = new DoubleAnimation(0, 1, TimeSpan.FromMilliseconds(200)); target.BeginAnimation(Window.OpacityProperty, anim); this.Hide(); target.Activate(); overlay?.Close(); } target.ContentRendered += OnContentRendered; target.Show(); } // 创建或复用窗口实例 if (windowInstance == null) { windowInstance = createFunc(); windowInstance.Closed += (s, args) => { _readTimer?.Start(); this.Activate(); }; ShowTargetWindow(windowInstance); } else { ShowTargetWindow(windowInstance); } } catch (Exception ex) { overlay?.Close(); ShowErrorMsg($"切换窗口失败:{ex.Message}"); } } // 添加重连方法 private bool TryReconnect() { try { string plcIp = "192.168.1.10"; bool initSuccess = Data.ModbusResourceManager.Instance.Init(plcIp, 502); if (initSuccess) { ma = new Function(_modbusMaster); return true; } } catch (Exception ex) { ShowErrorMsg($"重新连接失败:{ex.Message}"); } return false; } private void OpenQuantitativeSupplyWindow() { try { SwitchWindow(ref _mainWindow5, () => new ParameterSettingsWindow()); } catch (Exception ex) { MessageBox.Show($"打开定量供应检验窗口时出错:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } } private void btn_setting_Click(object sender, RoutedEventArgs e) { OpenQuantitativeSupplyWindow(); } private async System.Threading.Tasks.Task ReadAddrStatusDataAsync() { try { bool[] result = _modbusMaster?.ReadCoils(0x01, 71, 1); bool isTestRunning = result != null && result.Length > 0 && result[0]; Dispatcher.Invoke(() => { if (isTestRunning) { status.Text = "正在运行"; statusPanel.Background = Brushes.Green; if (!_wasRunning) { _testStartTime = DateTime.Now; _recordAdded = false; // 测试刚开始,允许插入记录 } } else { status.Text = "空闲"; statusPanel.Background = Brushes.Blue; // 状态从“运行”切换到“空闲”,且还没插入记录 if (_wasRunning && !_recordAdded) { AddTestRecord(); // 插入记录 _recordAdded = true; // 标记已插入 } } _wasRunning = isTestRunning; // 更新上一次状态 }); } catch (Exception ex) { ShowErrorMsg($"读取运行状态失败:{ex.Message}"); } } private void OpenQuantitativeSupplyWindow2() { try { SwitchWindow(ref _reportWindow, () => new ReportWindow()); } catch (Exception ex) { MessageBox.Show($"打开定量供应检验窗口时出错:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); } } private void AddTestRecord() { try { // 计算测试时长 TimeSpan duration = DateTime.Now - _testStartTime; string durationStr = $"{duration.Minutes:00}:{duration.Seconds:00}"; // 获取当前流量值 double currentFlowRate = 0; if (!string.IsNullOrEmpty(speed.Text) && double.TryParse(speed.Text, out double flow)) { currentFlowRate = flow; } // 获取当前时间 string currentTime = DateTime.Now.ToString("HH:mm"); string currentDate = DateTime.Now.ToString("yy/MM/dd"); // 添加到报表窗口(如果存在或创建) Dispatcher.Invoke(() => { // 如果窗口不存在,先创建但不显示 if (_reportWindow == null) { _reportWindow = new ReportWindow(); } double temprate = 0; if (!isReset) { temprate = currentFlowRate; currentFlowRate = 0; } else { } string No = ConfigurationManager.AppSettings["No"]?.ToString(); // 添加记录到窗口(无论窗口是否显示) _reportWindow.AddRecord(currentTime, currentDate, currentFlowRate, durationStr, temprate, No); }); } catch (Exception ex) { ShowErrorMsg($"记录测试数据失败:{ex.Message}"); } } private async void BtnWrite400_Click1(object sender, RoutedEventArgs e) { _modbusMaster.WriteSingleCoil(1, 75, false); isReset = false; } //private void pressureDiff2_GotFocus(object sender, RoutedEventArgs e) //{ //} private void pressureDiff2_GotFocus(object sender, RoutedEventArgs e) { SaveAppSetting("No", pressureDiff2.Text); } /// /// 保存 AppSettings 配置到配置文件 /// /// 配置键名 /// 配置值 /// 是否保存成功 public bool SaveAppSetting(string key, string value) { try { // 1. 获取当前程序的配置文件路径(编译后的 exe.config) string configPath = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile; // 2. 加载配置文件(ExeConfigurationFileMap 指定配置路径) ExeConfigurationFileMap configFileMap = new ExeConfigurationFileMap { ExeConfigFilename = configPath }; Configuration config = ConfigurationManager.OpenMappedExeConfiguration(configFileMap, ConfigurationUserLevel.None); // 3. 处理 appSettings 节点 if (config.AppSettings.Settings[key] == null) { // 键不存在 → 新增配置 config.AppSettings.Settings.Add(key, value); } else { // 键已存在 → 更新配置 config.AppSettings.Settings[key].Value = value; } // 4. 保存配置(重要!否则修改不生效) config.Save(ConfigurationSaveMode.Modified); // 5. 刷新 ConfigurationManager 缓存(可选,让后续读取能立即获取新值) ConfigurationManager.RefreshSection("appSettings"); return true; } catch (Exception ex) { // 捕获异常(如权限不足、文件被占用等) MessageBox.Show($"保存配置失败:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error); return false; } } private void settingtime_TextChanged(object sender, TextChangedEventArgs e) { } private void Button_Click_8(object sender, RoutedEventArgs e) { } } public class TestRecord { public int Id { get; set; } public string Time { get; set; }//时间 public string Date { get; set; }//日期 public double FlowRate { get; set; } // 供氧流量L/min public string Duration { get; set; } // 测试时长 public DateTime CreateTime { get; set; } public string HeadName { get; set; } // 表头名 public double FlowRate2 { get; set; } // 供氧流量L/min public double FlowRate3 { get; set; } // 供氧流量L/min public string No { get; set; } } }