using System; using System.Collections.Generic; using System.Data; using System.Drawing; using System.Drawing.Drawing2D; using System.IO; using System.Linq; using System.Reflection; using System.Threading.Tasks; using System.Timers; using System.Windows.Forms; using MySql.Data.MySqlClient; using Newtonsoft.Json; using YiDa_WinForm.Config; using YiDa_WinForm.Model; using YiDa_WinForm.Service; using YiDa_WinForm.Utils; using NPOI.SS.UserModel; namespace YiDa_WinForm { public partial class MainForm : Form { /// /// 依赖注入 /// private readonly MqttClientService _mqttService; private readonly YiDaUploadService _uploadService; private readonly ButtonOperationService _buttonService; /// /// 日志文件相关路径 /// private readonly string _logDirectory = "log"; private string _currentLogFile; private DateTime _lastLogDate = DateTime.MinValue; /// /// 配方配置 /// private DataTable _formulas; // 原始配方表(含多行,未去重) private DataTable _dtMqttDic; // MQTT 参数字典表 private List _formulasListOne; // 设备1选择的配方(多行) private List _formulasListTwo; // 设备2选择的配方(多行) /// /// 定时任务 /// // 通用上传定时器 private System.Timers.Timer _timer; // 设备1 private System.Timers.Timer _scrapAlarmTimerOne; // 报废上传定时任务 private int _lastScrapIntervalOne = 60; // 报废间隔 private static bool _isScrapImageUploadedOne = false; // 报废图片是否上传 private static bool _isScrapImageUploadedInCycleOne = false; private System.Timers.Timer _rodAlarmTimerOne; // 连杆测试定时任务 private int _lastRodIntervalOne = 60; // 连杆间隔 private static bool _isRodImageUploadedOne = false; // 连杆图片是否上传 private static bool _isRodUploadedInCycleOne = false; private static bool _isScrapImageWorkNoticeOne = false; // 报废图片工作通知标记 private static bool _isRodImageWorkNoticeOne = false; // 连杆图片工作通知标记 // 设备2 private System.Timers.Timer _scrapAlarmTimerTwo; // 报废上传定时任务 private int _lastScrapIntervalTwo = 60; // 报废间隔 private static bool _isScrapImageUploadedTwo = false; // 报废图片是否上传 private static bool _isScrapImageUploadedInCycleTwo = false; private System.Timers.Timer _rodAlarmTimerTwo; // 连杆测试定时任务 private int _lastRodIntervalTwo = 60; // 连杆间隔 private static bool _isRodImageUploadedTwo = false; // 连杆图片是否上传 private static bool _isRodUploadedInCycleTwo = false; private static bool _isScrapImageWorkNoticeTwo = false; // 报废图片工作通知标记 private static bool _isRodImageWorkNoticeTwo = false; // 连杆图片工作通知标记 // 连杆工作时间配置 private System.Timers.Timer _morningRodAlarmTimerOne; // 早上检查点定时器(工作开始+30分钟) private System.Timers.Timer _eveningRodAlarmTimerOne; // 晚上检查点定时器(工作结束-30分钟) private System.Timers.Timer _morningRodAlarmTimerTwo; // 早上检查点定时器(工作开始+30分钟) private System.Timers.Timer _eveningRodAlarmTimerTwo; // 晚上检查点定时器(工作结束-30分钟) private bool _isMorningRodUploadedOne = false; // 早上标记(工作开始后30分钟前是否上传) private bool _isEveningRodUploadedOne = false; // 晚上标记(工作结束前30分钟前是否上传) private bool _isMorningRodUploadedTwo = false; // 早上标记(工作开始后30分钟前是否上传) private bool _isEveningRodUploadedTwo = false; // 晚上标记(工作结束前30分钟前是否上传) public static TimeSpan _workStartTimeOne = new TimeSpan(9, 0, 0); public static TimeSpan _workEndTimeOne = new TimeSpan(17, 30, 0); private TimeSpan _morningCheckTimeOne; // 早上检查节点(_workStartTime + 30分钟) private TimeSpan _eveningCheckTimeOne; // 晚上检查节点(_workEndTime + 30分钟) public static TimeSpan _workStartTimeTwo = new TimeSpan(9, 0, 0); public static TimeSpan _workEndTimeTwo = new TimeSpan(17, 30, 0); private TimeSpan _morningCheckTimeTwo; // 早上检查节点(_workStartTime + 30分钟) private TimeSpan _eveningCheckTimeTwo; // 晚上检查节点(_workEndTime + 30分钟) // ======================================== 初始化相关方法 ======================================== public MainForm() { // 初始化设计器方法 InitializeComponent(); // 订阅日志事件 LogHelper.OnLogGenerated += LogHelper_OnLogGenerated; // 初始化服务实例 _buttonService = new ButtonOperationService(); _mqttService = new MqttClientService(); _uploadService = new YiDaUploadService(); _buttonService.MessageReceived += (msg) => { LogHelper.AppendLog($"服务层消息:{msg}"); }; // 绑定回调事件 _mqttService.MessageReceived += OnMqttMessage; _uploadService.ScrapUploadStatusChangedOne += OnScrapUploadStatusChangedOne; _uploadService.ScrapUploadStatusChangedTwo += OnScrapUploadStatusChangedTwo; _uploadService.RodUploadStatusChangedOne += OnRodUploadStatusChangedOne; _uploadService.RodUploadStatusChangedTwo += OnRodUploadStatusChangedTwo; // UI初始化配置 buttonDisconnect.Enabled = false; // 初始化日志目录 InitializeLogDirectory(); // 初始化配方相关变量 _formulas = new DataTable(); _dtMqttDic = new DataTable(); _formulasListOne = new List(); _formulasListTwo = new List(); InitDailyRodDeviceOneCheckTimes(); InitDailyRodDeviceTwoCheckTimes(); } /// /// 初始化日志目录 /// private void InitializeLogDirectory() { if (!Directory.Exists(_logDirectory)) { Directory.CreateDirectory(_logDirectory); } UpdateLogFile(); } /// /// 更新当前日志文件(每日一个新文件) /// private void UpdateLogFile() { DateTime today = DateTime.Now.Date; if (today != _lastLogDate) { _lastLogDate = today; _currentLogFile = Path.Combine(_logDirectory, $"log_{today:yyyyMMdd}.txt"); } } /// /// 加载MainForm信息 /// private async void Form1_Load(object sender, EventArgs e) { try { MakePanelRound(panelLed); await InitFormulas(); // 移植:加载配方 InitMqttDic(); // 移植:加载MQTT字典 timer1.Enabled = true; // 初始化时,若下拉框未选择,禁用所有按钮 UpdateDevice1ButtonStates(); UpdateDevice2ButtonStates(); // 绑定下拉框的选择变化事件 comboBox1.SelectedIndexChanged += comboBox1_SelectedIndexChanged; comboBox2.SelectedIndexChanged += comboBox2_SelectedIndexChanged; } catch (Exception ex) { LogHelper.AppendLog($"初始化失败:{ex.Message}"); } } /// /// 将指定 Panel 变成圆形 /// private static void MakePanelRound(Panel panel) { int size = Math.Min(panel.Width, panel.Height); panel.Size = new Size(size, size); GraphicsPath path = new GraphicsPath(); path.AddEllipse(0, 0, panel.Width, panel.Height); panel.Region = new Region(path); } // ======================================== 配方核心方法 ======================================== /// /// 查询配方并去重 /// private async Task InitFormulas() { DataTable dt = await _buttonService.QueryFormulaAsync(); if (dt != null && dt.Rows.Count > 0) { // 新增列用于拼接 part_number|part_name if (!dt.Columns.Contains("part_number_name")) dt.Columns.Add("part_number_name", typeof(string)); foreach (DataRow row in dt.Rows) { row["part_number_name"] = row["part_number"].ToString() + "|" + row["part_name"].ToString(); } // 使用 LINQ 去重,按 part_number + part_name var uniqueParts = dt.AsEnumerable() .GroupBy(r => r["part_number_name"].ToString()) .Select(g => g.First()) .ToList(); // 新建 DataTable 用于绑定下拉框 DataTable dtUnique = dt.Clone(); // 保留原有列 foreach (var row in uniqueParts) { dtUnique.ImportRow(row); } // 新建空白行 DataRow drNew = dtUnique.NewRow(); drNew["id"] = -1; drNew["part_number_name"] = ""; // 空行值 dtUnique.Rows.InsertAt(drNew, 0); _formulas = dt; // 原始配方表(多行) bsPF.DataSource = dtUnique; bsPF2.DataSource = dtUnique; comboBox1.DisplayMember = "part_number_name"; // 显示“零件号|零件名” comboBox1.ValueMember = "part_number_name"; comboBox2.DisplayMember = "part_number_name"; comboBox2.ValueMember = "part_number_name"; // 自定义显示格式:显示“零件号 - 零件名” comboBox1.Format += (s, e) => { if (e.ListItem is DataRowView drv) { e.Value = string.IsNullOrEmpty(drv["part_number_name"].ToString()) ? "" : drv["part_number"].ToString() + " - " + drv["part_name"].ToString(); } }; comboBox2.Format += (s, e) => { if (e.ListItem is DataRowView drv) { e.Value = string.IsNullOrEmpty(drv["part_number_name"].ToString()) ? "" : drv["part_number"].ToString() + " - " + drv["part_name"].ToString(); } }; } } /// /// 初始化MQTT字典 /// private async void InitMqttDic() { DataTable dt = await _buttonService.InitMqttDic(); if (dt != null) { _dtMqttDic = dt; } } // ======================================== 按钮相关方法 ======================================== /// /// 网关连接按钮 /// private async void buttonConnect_Click(object sender, EventArgs e) { try { LogHelper.AppendLog("正在连接MQTT服务器..."); await _mqttService.MqttClientStartAsync(); LogHelper.AppendLog("连接成功!"); panelLed.BackColor = Color.Green; toolStripStatusLabel1.Text = "已连接"; buttonConnect.Enabled = false; buttonDisconnect.Enabled = true; } catch (Exception ex) { panelLed.BackColor = Color.Red; LogHelper.AppendLog($"连接失败:{ex.Message}"); toolStripStatusLabel1.Text = "连接失败"; } } /// /// 网关断开按钮 /// private async void buttonDisconnect_Click(object sender, EventArgs e) { try { await _mqttService.MqttClientStopAsync(); LogHelper.AppendLog("已断开连接"); panelLed.BackColor = Color.Red; toolStripStatusLabel1.Text = "未连接"; buttonConnect.Enabled = true; buttonDisconnect.Enabled = false; } catch (Exception ex) { LogHelper.AppendLog($"无法断开连接:{ex.Message}"); } } /// /// 配方导入按钮 /// private async void button1_Click(object sender, EventArgs e) { try { OpenFileDialog openFile = new OpenFileDialog(); openFile.Filter = "Excel文件|*.xlsx;*.xls"; if (openFile.ShowDialog() != DialogResult.OK) { return; } // 显示加载提示 this.Cursor = Cursors.WaitCursor; LogHelper.AppendLog("正在导入配方,请稍候..."); string filePath = openFile.FileName; // 优化:使用 Task.Run 异步执行,不阻塞UI线程 DataTable excelDt = await Task.Run(() => ExcelHelper.GetExcel(filePath)); if (excelDt == null || excelDt.Rows.Count == 0) { MessageBox.Show("Excel解析后无有效数据,不允许导入!"); LogHelper.AppendLog("Excel无有效数据,导入中止"); return; } // excel导入列 string[] requiredColumns = { "供应商代码", "供应商名称", "车型", "零件号", "零件名", "参数名" }; var missingColumns = requiredColumns.Where(col => !excelDt.Columns.Contains(col)).ToList(); if (missingColumns.Count > 0) { string msg = $"Excel缺少必要列:{string.Join("、", missingColumns)},导入失败"; MessageBox.Show(msg); LogHelper.AppendLog(msg); return; } // 异步执行批量插入 await _buttonService.SaveFormulaByExcel(excelDt); // 异步刷新配方 await InitFormulas(); MessageBox.Show("配方信息已导入!"); LogHelper.AppendLog($"配方导入成功:{Path.GetFileName(filePath)},共{excelDt.Rows.Count}行"); } catch (Exception ex) { LogHelper.AppendLog($"配方导入失败:{ex.Message}"); MessageBox.Show($"导入失败:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); } finally { // 恢复光标 this.Cursor = Cursors.Default; } } /// /// 上传宜搭按钮 /// private async void button2_Click(object sender, EventArgs e) { try { if (toolStripStatusLabel1.Text != "已连接") //如果 MQTT 没连接,就不允许上传。 { MessageBox.Show("请先连接MQTT网关!"); return; } if (this.comboBox1.Text.Length == 0 && this.comboBox2.Text.Length == 0) { //如果两个下拉框都还没数据,说明没导入过配方,直接中止。 MessageBox.Show("请先导入配方信息,再刷新!"); return; } string selection = null; // 判断是否选择有效下拉框 bool hasSelection1 = comboBox1.SelectedIndex > 0 && !string.IsNullOrEmpty(comboBox1.SelectedValue?.ToString()); bool hasSelection2 = comboBox2.SelectedIndex > 0 && !string.IsNullOrEmpty(comboBox2.SelectedValue?.ToString()); // 1. 重置静态上传标记(每次连接都重新开始判断) _isScrapImageUploadedOne = false; _isRodImageUploadedOne = false; _isScrapImageUploadedTwo = false; _isRodImageUploadedTwo = false; _isRodImageWorkNoticeOne = false; _isRodImageWorkNoticeTwo = false; _isScrapImageWorkNoticeOne = false; _isScrapImageWorkNoticeTwo = false; LogHelper.AppendLog("重置上传标记:连杆未上传、报废未上传"); string selectedContent1 = string.Empty; string selectedContent2 = string.Empty; // 获取选中项的DataRowView(增加判空) DataRowView drv1 = comboBox1.SelectedItem as DataRowView; // 先判断drv1是否为null,再访问字段 selectedContent1 = drv1 != null ? (drv1["part_name"]?.ToString()?.Trim() ?? string.Empty) : string.Empty; // 获取选中项的DataRowView(增加判空) DataRowView drv2 = comboBox2.SelectedItem as DataRowView; // 先判断drv2是否为null,再访问字段 selectedContent2 = drv2 != null ? (drv2["part_name"]?.ToString()?.Trim() ?? string.Empty) : string.Empty; if (hasSelection1 && hasSelection2) { selection = "3"; StartScrapOneAlarmTimers(); StartScrapTwoAlarmTimers(); if (selectedContent1.Contains("连杆2") && selectedContent2.Contains("连杆2")) { StartRodOneAlarmTimers(); StartDailyRodDeviceOneFixedTimers(); StartRodTwoAlarmTimers(); StartDailyRodDeviceTwoFixedTimers(); } } else if (hasSelection1) { selection = "1"; StartScrapOneAlarmTimers(); if (selectedContent1.Contains("连杆2")) { StartRodOneAlarmTimers(); StartDailyRodDeviceOneFixedTimers(); } } else if (hasSelection2) { selection = "2"; StartScrapTwoAlarmTimers(); if (selectedContent2.Contains("连杆2")) { StartRodTwoAlarmTimers(); StartDailyRodDeviceTwoFixedTimers(); } } else { MessageBox.Show("未选择数据!"); } this.button2.Enabled = false; // 设备1选择配方 string selected1 = this.comboBox1.SelectedValue?.ToString(); if (string.IsNullOrEmpty(selected1) || selected1 == "-1") { _formulasListOne = null; } else { string[] arr1 = selected1.Split('|'); if (arr1.Length == 2) { string partNumber1 = arr1[0]; string partName1 = arr1[1]; _formulasListOne = _formulas.AsEnumerable() .Where(r => r.Field("part_number") == partNumber1 && r.Field("part_name") == partName1) .ToList(); } else { _formulasListOne = null; } } // 设备2选择配方 string selected2 = this.comboBox2.SelectedValue?.ToString(); if (string.IsNullOrEmpty(selected2) || selected2 == "-1") { _formulasListTwo = null; } else { string[] arr2 = selected2.Split('|'); if (arr2.Length == 2) { string partNumber2 = arr2[0]; string partName2 = arr2[1]; _formulasListTwo = _formulas.AsEnumerable() .Where(r => r.Field("part_number") == partNumber2 && r.Field("part_name") == partName2) .ToList(); } else { _formulasListTwo = null; } } double seconds; if (!double.TryParse(txtLUploadPL.Text, out seconds) || seconds <= 0) { seconds = 10; //控制上传间隔,默认10秒。 } // 立即上传一次 await MqttYiDaUpload(selection, null); _timer = new System.Timers.Timer(seconds * 1000); _timer.Elapsed += async (s, evt) => { await MqttYiDaUpload(selection, null); }; _timer.AutoReset = true; // 是否重复 _timer.Enabled = true; // 开始计时 LogHelper.AppendLog($"自动上传已启动,间隔:{seconds}秒"); } catch (Exception ex) { this.button2.Enabled = true; LogHelper.AppendLog($"上传宜搭失败:{ex.Message}"); } } /// /// 中止上传按钮 /// private void button3_Click(object sender, EventArgs e) { button2.Enabled = true; if (_timer != null) { _timer.Enabled = false; _timer.Dispose(); _timer = null; LogHelper.AppendLog("已停止自动上传数据"); MessageBox.Show("已停止自动上传", "信息", MessageBoxButtons.OK, MessageBoxIcon.Information); } else { LogHelper.AppendLog("无运行中的上传定时器"); MessageBox.Show("当前无自动上传任务", "信息", MessageBoxButtons.OK, MessageBoxIcon.Information); } StopDeviceOneAlarmTimers(); StopDeviceTwoAlarmTimers(); StopDailyRodDeviceOneFixedTimers(); StopDailyRodDeviceTwoFixedTimers(); } /// /// 报废上传凭证按钮(设备1) /// private async void button4_Click(object sender, EventArgs e) { // 传入间隔值 using (var scrapForm = new ScrapUploadForm(_lastScrapIntervalOne, "1")) { DialogResult result = scrapForm.ShowDialog(this); if (result == DialogResult.OK) { // 1. 提取用户本次选择结果 int interval = scrapForm.ScrapTimedIntervalOne; _lastScrapIntervalOne = interval; // 2. 上传逻辑 bool isImageUploadSuccess = false; using (OpenFileDialog openFile = new OpenFileDialog()) { if (openFile.ShowDialog() == DialogResult.OK) { string filePath = openFile.FileName; if (!string.IsNullOrEmpty(filePath)) { isImageUploadSuccess = await _uploadService.UploadScrapDeviceOneAsync(filePath); LogHelper.AppendLog(isImageUploadSuccess ? "报废凭证图片已上传!" : "报废凭证图片上传失败!"); if (isImageUploadSuccess) { _buttonService.MergeAndSaveData("1", "device1"); MqttYiDaUpload("1", "device1"); // 上传成功:直接标记为已上传(避免后续定时报警) _isScrapImageUploadedOne = true; } } else { MessageBox.Show("不允许上传空文件!"); } } } } } } /// /// 报废上传凭证按钮(设备2) /// /// /// private async void button7_Click(object sender, EventArgs e) { // 传入间隔值 using (var scrapForm = new ScrapUploadForm(_lastScrapIntervalTwo, "2")) { DialogResult result = scrapForm.ShowDialog(this); if (result == DialogResult.OK) { // 1. 提取用户本次选择结果 int interval = scrapForm.ScrapTimedIntervalTwo; _lastScrapIntervalTwo = interval; // 2. 上传逻辑 bool isImageUploadSuccess = false; OpenFileDialog openFile = new OpenFileDialog(); if (openFile.ShowDialog() == DialogResult.OK) { string filePath = openFile.FileName; if (!string.IsNullOrEmpty(filePath)) { isImageUploadSuccess = await _uploadService.UploadScrapDeviceTwoAsync(filePath); LogHelper.AppendLog(isImageUploadSuccess ? "报废凭证图片已上传!" : "报废凭证图片上传失败!"); if (isImageUploadSuccess) { _buttonService.MergeAndSaveData("1", "device2"); MqttYiDaUpload("2", "device2"); // 上传成功:直接标记为已上传(避免后续定时报警) _isScrapImageUploadedTwo = true; } } else { MessageBox.Show("不允许上传空文件!"); } } } } } /// /// 连杆测试上传按钮(设备1) /// private async void button5_Click(object sender, EventArgs e) { if (!button5.Enabled) { StopDeviceOneAlarmTimers(); return; } using (var selectForm = new RodTestUploadForm(_lastRodIntervalOne, "1")) { DialogResult result = selectForm.ShowDialog(this); if (result == DialogResult.OK) { int interval = selectForm.RodTimedIntervalOne; _lastRodIntervalOne = interval; // 3. 上传逻辑:仅当勾选「模具上传」时,才弹出文件选择框(分离上传和报警逻辑) bool isImageUploadSuccess = false; OpenFileDialog openFile = new OpenFileDialog(); if (openFile.ShowDialog() == DialogResult.OK) { string filePath = openFile.FileName; if (!string.IsNullOrEmpty(filePath)) { isImageUploadSuccess = await _uploadService.UploadRodTestDeviceOneAsync(filePath); LogHelper.AppendLog(isImageUploadSuccess ? "连杆测试图片已上传!" : "连杆测试图片上传失败!"); if (isImageUploadSuccess) { _buttonService.MergeAndSaveData("2", "device1"); MqttYiDaUpload("2", "device1"); // 上传成功:直接标记为已上传(避免后续定时报警) _isRodImageUploadedOne = true; DateTime now = DateTime.Now; TimeSpan timeOfDay = now.TimeOfDay; if (timeOfDay > _workStartTimeOne && timeOfDay < _morningCheckTimeOne) { _isMorningRodUploadedOne = true; } if (timeOfDay > _workEndTimeOne || timeOfDay < _eveningCheckTimeOne) { _isEveningRodUploadedOne = true; } } } else { MessageBox.Show("不允许上传空文件!"); } } } } } /// /// 配置按钮(设备1) /// /// /// private async void button6_Click(object sender, EventArgs e) { try { if (!button6.Enabled) { StopDailyRodDeviceOneFixedTimers(); return; } using (var configForm = new WorkTimeConfigForm(_workStartTimeOne, _workEndTimeOne, "1")) { DialogResult result = configForm.ShowDialog(this); if (result == DialogResult.OK) { _workStartTimeOne = configForm.WorkStartTimeOne; _workEndTimeOne = configForm.WorkEndTimeOne; // ========== 重新初始化早晚连杆检查节点 ========== InitDailyRodDeviceOneCheckTimes(); // ========== 如果网关已连接,重启早晚固定检查定时器 ========== if (toolStripStatusLabel1.Text == "已连接") { StartDailyRodDeviceOneFixedTimers(); } LogHelper.AppendLog($"工作时间配置更新:{_workStartTimeOne:hh\\:mm} - {_workEndTimeOne:hh\\:mm}"); // 3. 上传逻辑:仅当勾选「模具上传」时,才弹出文件选择框(分离上传和报警逻辑) bool isImageUploadSuccess = true; OpenFileDialog openFile = new OpenFileDialog(); if (openFile.ShowDialog() == DialogResult.OK) { string filePath = openFile.FileName; if (!string.IsNullOrEmpty(filePath)) { isImageUploadSuccess = await _uploadService.UploadRodTestDeviceOneAsync(filePath); LogHelper.AppendLog(isImageUploadSuccess ? "连杆测试图片已上传!" : "连杆测试图片上传失败!"); if (isImageUploadSuccess) { _buttonService.MergeAndSaveData("2", "device1"); MqttYiDaUpload("2", "device1"); // 上传成功:直接标记为已上传(避免后续定时报警) _isRodImageUploadedOne = true; DateTime now = DateTime.Now; TimeSpan timeOfDay = now.TimeOfDay; if (timeOfDay > _workStartTimeOne && timeOfDay < _morningCheckTimeOne) { _isMorningRodUploadedOne = true; } if (timeOfDay > _workEndTimeOne || timeOfDay < _eveningCheckTimeOne) { _isEveningRodUploadedOne = true; } } } else { MessageBox.Show("不允许上传空文件!"); } } } } } catch (Exception ex) { LogHelper.AppendLog($"工作时间配置失败:{ex.Message}"); MessageBox.Show($"配置失败:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); } } /// /// 时间配置(设备2) /// /// /// private async void button9_Click(object sender, EventArgs e) { try { if (!button9.Enabled) { StopDailyRodDeviceTwoFixedTimers(); return; } using (var configForm = new WorkTimeConfigForm(_workStartTimeTwo, _workEndTimeTwo, "2")) { DialogResult result = configForm.ShowDialog(this); if (result == DialogResult.OK) { _workStartTimeTwo = configForm.WorkStartTimeTwo; _workEndTimeTwo = configForm.WorkEndTimeTwo; // ========== 重新初始化早晚连杆检查节点 ========== InitDailyRodDeviceTwoCheckTimes(); // ========== 如果网关已连接,重启早晚固定检查定时器 ========== if (toolStripStatusLabel1.Text == "已连接") { StartDailyRodDeviceTwoFixedTimers(); } LogHelper.AppendLog($"工作时间配置更新:{_workStartTimeTwo:hh\\:mm} - {_workEndTimeTwo:hh\\:mm}"); // 3. 上传逻辑:仅当勾选「模具上传」时,才弹出文件选择框(分离上传和报警逻辑) bool isImageUploadSuccess = true; OpenFileDialog openFile = new OpenFileDialog(); if (openFile.ShowDialog() == DialogResult.OK) { string filePath = openFile.FileName; if (!string.IsNullOrEmpty(filePath)) { isImageUploadSuccess = await _uploadService.UploadRodTestDeviceTwoAsync(filePath); LogHelper.AppendLog(isImageUploadSuccess ? "连杆测试图片已上传!" : "连杆测试图片上传失败!"); if (isImageUploadSuccess) { _buttonService.MergeAndSaveData("2", "device2"); MqttYiDaUpload("2", "device2"); // 上传成功:直接标记为已上传(避免后续定时报警) _isRodImageUploadedTwo = true; DateTime now = DateTime.Now; TimeSpan timeOfDay = now.TimeOfDay; if (timeOfDay > _workStartTimeTwo && timeOfDay < _morningCheckTimeTwo) { _isMorningRodUploadedTwo = true; } if (timeOfDay > _workEndTimeTwo || timeOfDay < _eveningCheckTimeTwo) { _isEveningRodUploadedTwo = true; } } } else { MessageBox.Show("不允许上传空文件!"); } } } } } catch (Exception ex) { LogHelper.AppendLog($"工作时间配置失败:{ex.Message}"); MessageBox.Show($"配置失败:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); } } /// /// 连杆测试上传按钮(设备2) /// /// /// private async void button8_Click(object sender, EventArgs e) { if (!button8.Enabled) { StopDeviceTwoAlarmTimers(); return; } using (var selectForm = new RodTestUploadForm(_lastRodIntervalTwo, "2")) { DialogResult result = selectForm.ShowDialog(this); if (result == DialogResult.OK) { int interval = selectForm.RodTimedIntervalTwo; _lastRodIntervalTwo = interval; // 3. 上传逻辑 bool isImageUploadSuccess = false; OpenFileDialog openFile = new OpenFileDialog(); if (openFile.ShowDialog() == DialogResult.OK) { string filePath = openFile.FileName; if (!string.IsNullOrEmpty(filePath)) { isImageUploadSuccess = await _uploadService.UploadRodTestDeviceTwoAsync(filePath); LogHelper.AppendLog(isImageUploadSuccess ? "连杆测试图片已上传!" : "连杆测试图片上传失败!"); if (isImageUploadSuccess) { _buttonService.MergeAndSaveData("2", "device2"); MqttYiDaUpload("2", "device2"); // 上传成功:直接标记为已上传(避免后续定时报警) _isRodImageUploadedTwo = true; DateTime now = DateTime.Now; TimeSpan timeOfDay = now.TimeOfDay; if (timeOfDay > _workStartTimeTwo && timeOfDay < _morningCheckTimeTwo) { _isMorningRodUploadedTwo = true; } if (timeOfDay > _workEndTimeTwo || timeOfDay < _eveningCheckTimeTwo) { _isEveningRodUploadedTwo = true; } } } else { MessageBox.Show("不允许上传空文件!"); } } } } } /// /// 关闭MainForm按钮 /// private async void MainFormClosing(object sender, FormClosingEventArgs e) { try { if (_timer != null) { _timer.Enabled = false; _timer.Dispose(); } StopDeviceOneAlarmTimers(); StopDeviceTwoAlarmTimers(); StopDailyRodDeviceOneFixedTimers(); StopDailyRodDeviceTwoFixedTimers(); await _mqttService.MqttClientStopAsync(); } catch (Exception ex) { LogHelper.AppendLog($"关闭窗口失败:{ex.Message}"); } } /// /// 刷新配方按钮 /// private async void btnRefresh_Click(object sender, EventArgs e) { try { await InitFormulas(); InitMqttDic(); MessageBox.Show("配方已刷新!", "成功", MessageBoxButtons.OK, MessageBoxIcon.Information); UpdateDevice1ButtonStates(); UpdateDevice2ButtonStates(); } catch (Exception ex) { LogHelper.AppendLog($"刷新配方失败:{ex.Message}"); MessageBox.Show($"刷新配方失败:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); } } /// /// 上传间隔输入框校验 /// private void txtLUploadPL_KeyPress(object sender, KeyPressEventArgs e) { if (e.KeyChar == (char)Keys.Back) { e.Handled = false; return; } if (char.IsDigit(e.KeyChar) || (e.KeyChar == '.' && !txtLUploadPL.Text.Contains("."))) { e.Handled = false; } else { e.Handled = true; } } /// /// 更新时间显示 /// private void timer1_Tick(object sender, EventArgs e) { label4.Text = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"); } // ======================================== 事件绑定 ======================================== // 设备1下拉框选择变化事件 private void comboBox1_SelectedIndexChanged(object sender, EventArgs e) { UpdateDevice1ButtonStates(); } // 设备2下拉框选择变化事件 private void comboBox2_SelectedIndexChanged(object sender, EventArgs e) { UpdateDevice2ButtonStates(); } // 更新设备1对应按钮的状态 private void UpdateDevice1ButtonStates() { // 判断下拉框是否有有效选择 bool hasSelection = comboBox1.SelectedIndex > 0 && !string.IsNullOrEmpty(comboBox1.SelectedValue?.ToString()); // 判断是否选中连杆2 string selectedContent = string.Empty; if (hasSelection) { // 获取选中项的DataRowView DataRowView drv = comboBox1.SelectedItem as DataRowView; // 取零件名(part_name)字段,这是你的配方表中实际存储"连杆2"的字段 selectedContent = drv["part_name"]?.ToString()?.Trim() ?? string.Empty; } // 判断是否包含"连杆2" button6.Enabled = hasSelection && selectedContent.Contains("连杆2"); button5.Enabled = hasSelection && selectedContent.Contains("连杆2"); button4.Enabled = hasSelection; // 报废上传凭证1 } // 更新设备2对应按钮的状态 private void UpdateDevice2ButtonStates() { bool hasSelection = comboBox2.SelectedIndex > 0 && !string.IsNullOrEmpty(comboBox2.SelectedValue?.ToString()); // 判断是否选中连杆2 string selectedContent = string.Empty; if (hasSelection) { // 获取选中项的DataRowView DataRowView drv = comboBox2.SelectedItem as DataRowView; // 取零件名(part_name)字段,这是你的配方表中实际存储"连杆2"的字段 selectedContent = drv["part_name"]?.ToString()?.Trim() ?? string.Empty; } // 判断是否包含"连杆2" button9.Enabled = hasSelection && selectedContent.Contains("连杆2"); button8.Enabled = hasSelection && selectedContent.Contains("连杆2"); button7.Enabled = hasSelection; // 报废上传凭证2 } // ======================================== 定时报警核心方法 ======================================== /// /// 启动报废定时任务(前30分钟上传) /// private void StartScrapOneAlarmTimers() { // 设备1 if (_scrapAlarmTimerOne != null) { _scrapAlarmTimerOne.Enabled = false; _scrapAlarmTimerOne.Dispose(); } int scrapIntervalMsOne = _lastScrapIntervalOne * 60 * 1000; // 分钟 → 毫秒 _scrapAlarmTimerOne = new System.Timers.Timer(scrapIntervalMsOne); _scrapAlarmTimerOne.AutoReset = false; // 关键:一次性执行,不循环 _scrapAlarmTimerOne.Elapsed += ScrapAlarmTimerOne_Elapsed; _scrapAlarmTimerOne.Enabled = true; // 启动定时 LogHelper.AppendLog($"设备1报废上传定时已启动:若{_lastScrapIntervalOne}分钟内未上传凭证,将触发钉钉报警"); } private void StartScrapTwoAlarmTimers() { // 设备2 if (_scrapAlarmTimerTwo != null) { _scrapAlarmTimerTwo.Enabled = false; _scrapAlarmTimerTwo.Dispose(); } int scrapIntervalMsTwo = _lastScrapIntervalTwo * 60 * 1000; // 分钟 → 毫秒 _scrapAlarmTimerTwo = new System.Timers.Timer(scrapIntervalMsTwo); _scrapAlarmTimerTwo.AutoReset = false; // 关键:一次性执行,不循环 _scrapAlarmTimerTwo.Elapsed += ScrapAlarmTimerTwo_Elapsed; _scrapAlarmTimerTwo.Enabled = true; // 启动定时 LogHelper.AppendLog($"设备1报废上传定时已启动:若{_lastScrapIntervalTwo}分钟内未上传凭证,将触发钉钉报警"); } /// /// 启动连杆定时任务(前30分钟上传) /// private void StartRodOneAlarmTimers() { // 设备1 if (_rodAlarmTimerOne != null) { _rodAlarmTimerOne.Enabled = false; _rodAlarmTimerOne.Dispose(); } int rodIntervalMsOne = _lastRodIntervalOne * 60 * 1000; // 分钟 → 毫秒 _rodAlarmTimerOne = new System.Timers.Timer(rodIntervalMsOne); _rodAlarmTimerOne.AutoReset = false; // 关键:一次性执行,不循环 _rodAlarmTimerOne.Elapsed += RodAlarmTimerOne_Elapsed; _rodAlarmTimerOne.Enabled = true; // 启动定时 LogHelper.AppendLog($"设备1连杆定时已启动:若{_lastRodIntervalOne}分钟内未上传凭证,将触发钉钉报警"); } private void StartRodTwoAlarmTimers() { // 设备2 if (_rodAlarmTimerTwo != null) { _rodAlarmTimerTwo.Enabled = false; _rodAlarmTimerTwo.Dispose(); } int scrapIntervalMsTwo = _lastRodIntervalTwo * 60 * 1000; // 分钟 → 毫秒 _rodAlarmTimerTwo = new System.Timers.Timer(scrapIntervalMsTwo); _rodAlarmTimerTwo.AutoReset = false; // 关键:一次性执行,不循环 _rodAlarmTimerTwo.Elapsed += RodAlarmTimerTwo_Elapsed; _rodAlarmTimerTwo.Enabled = true; // 启动定时 LogHelper.AppendLog($"设备2连杆上传定时已启动:若{_lastRodIntervalTwo}分钟内未上传凭证,将触发钉钉报警"); } /// /// 启动连杆定时任务(设备1早晚检查) /// private void StartDailyRodDeviceOneFixedTimers() { // 先停止并释放原有定时器(防止重复启动) StopDailyRodDeviceOneFixedTimers(); // 重置早晚标记 ResetDailyRodMarks(); DateTime now = DateTime.Now; DayOfWeek weekEnum = now.DayOfWeek; if (weekEnum.Equals(DayOfWeek.Monday)) { _morningCheckTimeOne = new TimeSpan(10, 30, 0); _workStartTimeOne = new TimeSpan(10, 0, 0); } // 1. 启动早上检查定时器(若目标时间未过) long morningDelayMs = CalculateDelayToTargetTime(_morningCheckTimeOne, "morning"); if (morningDelayMs > 0 && _morningCheckTimeOne > _workStartTimeOne) { _morningRodAlarmTimerOne = new System.Timers.Timer(morningDelayMs); _morningRodAlarmTimerOne.AutoReset = false; // 一次性执行,不循环 _morningRodAlarmTimerOne.Elapsed += MorningRodDeviceOneAlarmTimer_Elapsed; _morningRodAlarmTimerOne.Enabled = true; // 启动定时器 LogHelper.AppendLog( $"早上连杆检查定时器已启动:将在{_morningCheckTimeOne:hh\\:mm}触发"); } else { LogHelper.AppendLog("早上检查节点已过当天当前时间,跳过早上连杆检查定时器启动"); _isMorningRodUploadedOne = true; // 标记为已检查,避免重复判断 } // 2. 启动晚上检查定时器(若目标时间未过) long eveningDelayMs = CalculateDelayToTargetTime(_eveningCheckTimeOne, "evening"); if (eveningDelayMs > 0 && _eveningCheckTimeOne > _workEndTimeOne) { _eveningRodAlarmTimerOne = new System.Timers.Timer(eveningDelayMs); _eveningRodAlarmTimerOne.AutoReset = false; // 一次性执行,不循环 _eveningRodAlarmTimerOne.Elapsed += EveningRodDeviceOneAlarmTimer_Elapsed; _eveningRodAlarmTimerOne.Enabled = true; // 启动定时器 LogHelper.AppendLog( $"晚上连杆检查定时器已启动:将在{_eveningCheckTimeOne:hh\\:mm}触发"); } else { LogHelper.AppendLog("晚上检查节点已过当天当前时间,跳过晚上连杆检查定时器启动"); _isEveningRodUploadedOne = true; // 标记为已检查,避免重复判断 } } /// /// 启动连杆定时任务(设备2早晚检查) /// private void StartDailyRodDeviceTwoFixedTimers() { // 先停止并释放原有定时器(防止重复启动) StopDailyRodDeviceTwoFixedTimers(); // 重置早晚标记 ResetDailyRodMarks(); DateTime now = DateTime.Now; DayOfWeek weekEnum = now.DayOfWeek; if (weekEnum.Equals(DayOfWeek.Monday)) { _morningCheckTimeTwo = new TimeSpan(10, 30, 0); _workStartTimeTwo = new TimeSpan(10, 0, 0); } // 1. 启动早上检查定时器(若目标时间未过) long morningDelayMs = CalculateDelayToTargetTime(_morningCheckTimeTwo, "morning"); if (morningDelayMs > 0 && _morningCheckTimeTwo > _workStartTimeTwo) { _morningRodAlarmTimerTwo = new System.Timers.Timer(morningDelayMs); _morningRodAlarmTimerTwo.AutoReset = false; // 一次性执行,不循环 _morningRodAlarmTimerTwo.Elapsed += MorningRodDeviceTwoAlarmTimer_Elapsed; _morningRodAlarmTimerTwo.Enabled = true; // 启动定时器 LogHelper.AppendLog( $"早上连杆检查定时器已启动:将在{_morningCheckTimeTwo:hh\\:mm}触发"); } else { LogHelper.AppendLog("早上检查节点已过当天当前时间,跳过早上连杆检查定时器启动"); _isMorningRodUploadedTwo = true; // 标记为已检查,避免重复判断 } // 2. 启动晚上检查定时器(若目标时间未过) long eveningDelayMs = CalculateDelayToTargetTime(_eveningCheckTimeTwo, "evening"); if (eveningDelayMs > 0 && _eveningCheckTimeTwo > _workEndTimeTwo) { _eveningRodAlarmTimerTwo = new System.Timers.Timer(eveningDelayMs); _eveningRodAlarmTimerTwo.AutoReset = false; // 一次性执行,不循环 _eveningRodAlarmTimerTwo.Elapsed += EveningRodDeviceTwoAlarmTimer_Elapsed; _eveningRodAlarmTimerTwo.Enabled = true; // 启动定时器 LogHelper.AppendLog( $"晚上连杆检查定时器已启动:将在{_eveningCheckTimeTwo:hh\\:mm}触发"); } else { LogHelper.AppendLog("晚上检查节点已过当天当前时间,跳过晚上连杆检查定时器启动"); _isEveningRodUploadedTwo = true; // 标记为已检查,避免重复判断 } } /// /// 终止所有报警定时任务并重置标记 /// private void StopDeviceOneAlarmTimers() { // 终止报废定时 if (_scrapAlarmTimerOne != null) { _scrapAlarmTimerOne.Enabled = false; _scrapAlarmTimerOne.Dispose(); _scrapAlarmTimerOne = null; } if (_scrapAlarmTimerTwo != null) { _scrapAlarmTimerTwo.Enabled = false; _scrapAlarmTimerTwo.Dispose(); _scrapAlarmTimerTwo = null; } LogHelper.AppendLog("报废上传报警定时已终止"); // 重置静态上传标记 _isScrapImageUploadedOne = false; _isRodImageUploadedOne = false; } /// /// 终止所有报警定时任务并重置标记 /// private void StopDeviceTwoAlarmTimers() { // 终止报废定时 if (_rodAlarmTimerOne != null) { _rodAlarmTimerOne.Enabled = false; _rodAlarmTimerOne.Dispose(); _rodAlarmTimerOne = null; } if (_rodAlarmTimerTwo != null) { _rodAlarmTimerTwo.Enabled = false; _rodAlarmTimerTwo.Dispose(); _rodAlarmTimerTwo = null; } LogHelper.AppendLog("连杆上传报警定时已终止"); // 重置静态上传标记 _isScrapImageUploadedTwo = false; _isRodImageUploadedTwo = false; } /// /// 跳过报废报警(设备1) /// /// /// private void ScrapAlarmTimerOne_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { DateTime now = DateTime.Now; TimeSpan currentTime = now.TimeOfDay; // 使用静态标记 _isScrapImageUploaded 判断是否报警(核心:上传成功则不报警) if (!_isScrapImageUploadedOne) { string alarmContent = $"报废上传超时未上传凭证!
当前时间:{now:yyyy-MM-dd HH:mm:ss}"; this.Invoke(new Action(() => SendDingDingAlarm(alarmContent, false, "1"))); _isScrapImageWorkNoticeOne = true; } else { LogHelper.AppendLog($"报废上传{_lastScrapIntervalOne}分钟内已上传凭证,无需触发报警"); } // 3. 销毁定时器,重置标记 _scrapAlarmTimerOne?.Dispose(); _scrapAlarmTimerOne = null; _isScrapImageUploadedOne = false; } /// /// 跳过报废报警(设备2) /// /// /// private void ScrapAlarmTimerTwo_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { DateTime now = DateTime.Now; TimeSpan currentTime = now.TimeOfDay; // 使用静态标记 _isScrapImageUploaded 判断是否报警(核心:上传成功则不报警) if (!_isScrapImageUploadedTwo) { string alarmContent = $"报废上传超时未上传凭证!
当前时间:{now:yyyy-MM-dd HH:mm:ss}"; this.Invoke(new Action(() => SendDingDingAlarm(alarmContent, false, "2"))); _isScrapImageWorkNoticeTwo = true; } else { LogHelper.AppendLog($"报废上传{_lastScrapIntervalTwo}分钟内已上传凭证,无需触发报警"); } // 3. 销毁定时器,重置标记 _scrapAlarmTimerTwo?.Dispose(); _scrapAlarmTimerTwo = null; _isScrapImageUploadedTwo = false; } /// /// 跳过连杆报警(设备1) /// /// /// private void RodAlarmTimerOne_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { DateTime now = DateTime.Now; TimeSpan currentTime = now.TimeOfDay; // 2. 使用静态标记 _isRodImageUploaded 判断是否报警 if (!_isRodImageUploadedOne) { string alarmContent = $"连杆测试超时未上传图片!
当前时间:{now:yyyy-MM-dd HH:mm:ss}"; this.Invoke(new Action(() => SendDingDingAlarm(alarmContent, true, "1"))); _isRodImageWorkNoticeOne = true; } else { LogHelper.AppendLog($"连杆测试{_lastRodIntervalOne}分钟内已上传图片,无需触发报警"); } // 3. 销毁定时器,重置标记 _rodAlarmTimerOne?.Dispose(); _rodAlarmTimerOne = null; _isRodImageUploadedOne = false; } /// /// 跳过连杆报警(设备2) /// /// /// private void RodAlarmTimerTwo_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { DateTime now = DateTime.Now; TimeSpan currentTime = now.TimeOfDay; // 2. 使用静态标记 _isRodImageUploaded 判断是否报警 if (!_isRodImageUploadedTwo) { string alarmContent = $"连杆测试超时未上传图片!
当前时间:{now:yyyy-MM-dd HH:mm:ss}"; this.Invoke(new Action(() => SendDingDingAlarm(alarmContent, true, "2"))); _isRodImageWorkNoticeTwo = true; } else { LogHelper.AppendLog($"连杆测试{_lastRodIntervalTwo}分钟内已上传图片,无需触发报警"); } // 3. 销毁定时器,重置标记 _rodAlarmTimerTwo?.Dispose(); _rodAlarmTimerTwo = null; _isRodImageUploadedTwo = false; } /// /// 发送钉钉报警 /// /// /// private async void SendDingDingAlarm(string alarmContent, bool isRodAlarm, string deviceCode) { try { LogHelper.AppendLog($"【钉钉报警】{(isRodAlarm ? "连杆" : "报废")}:{alarmContent}"); await _uploadService.SendDingDingTextAlarmAsync(alarmContent, deviceCode, isRodAlarm); // if (deviceCode.Contains("1")) // { // _isScrapImageWorkNoticeOne = true; // } // else // { // _isScrapImageWorkNoticeTwo = true; // } // MessageBox.Show($"已发送钉钉{(isRodAlarm ? "连杆" : "报废")}报警:{alarmContent}", "报警提示", MessageBoxButtons.OK, // MessageBoxIcon.Warning); } catch (Exception ex) { LogHelper.AppendLog($"{(isRodAlarm ? "连杆" : "报废")}钉钉报警发送失败:{ex.Message}"); } } // ======================================== 上传状态回调方法(连杆+报废) ======================================== /// /// 报废上传状态回调(设备1) /// private void OnScrapUploadStatusChangedOne(object sender, ScrapUploadStatusEventArgs e) { if (this.InvokeRequired) { this.Invoke(new Action(OnScrapUploadStatusChangedOne), sender, e); return; } _isScrapImageUploadedInCycleOne = e.IsUploadSuccess; // 上传成功时更新静态标记 if (e.IsUploadSuccess) { _isScrapImageUploadedOne = true; LogHelper.AppendLog("设备1报废凭证上传成功"); } LogHelper.AppendLog($"报废上传状态:{(e.IsUploadSuccess ? "成功" : "失败")},消息:{e.Message}"); } /// /// 报废上传状态回调(设备2) /// private void OnScrapUploadStatusChangedTwo(object sender, ScrapUploadStatusEventArgs e) { if (this.InvokeRequired) { this.Invoke(new Action(OnScrapUploadStatusChangedOne), sender, e); return; } _isScrapImageUploadedInCycleTwo = e.IsUploadSuccess; // 上传成功时更新静态标记 if (e.IsUploadSuccess) { _isScrapImageUploadedTwo = true; LogHelper.AppendLog("设备2报废凭证上传成功"); } LogHelper.AppendLog($"报废上传状态:{(e.IsUploadSuccess ? "成功" : "失败")},消息:{e.Message}"); } /// /// 连杆上传状态回调(设备1) /// private void OnRodUploadStatusChangedOne(object sender, RodUploadStatusEventArgs e) { if (this.InvokeRequired) { this.Invoke(new Action(OnRodUploadStatusChangedOne), sender, e); return; } _isRodUploadedInCycleOne = e.IsUploadSuccess; // 上传成功时更新静态标记 if (e.IsUploadSuccess) { _isRodImageUploadedOne = true; LogHelper.AppendLog("连杆图片上传成功,标记为「已上传」,定时结束后将不触发报警"); // // ========== 保留:同步更新早晚连杆上传标记(未到节点时) ========== // TimeSpan currentTime = DateTime.Now.TimeOfDay; // // // 早上节点前上传,更新早上标记 // if (currentTime < _morningCheckTime && !_isMorningRodUploaded) // { // _isMorningRodUploaded = true; // LogHelper.AppendLog("连杆图片上传成功,同步标记:早上无需触发强制上传报警"); // } // // 晚上节点前上传,更新晚上标记 // else if (currentTime >= _morningCheckTime && currentTime < _eveningCheckTime && !_isEveningRodUploaded) // { // _isEveningRodUploaded = true; // LogHelper.AppendLog("连杆图片上传成功,同步标记:晚上无需触发强制上传报警"); // } } LogHelper.AppendLog($"连杆上传状态:{(e.IsUploadSuccess ? "成功" : "失败")},消息:{e.Message}"); } /// /// 连杆上传状态回调(设备2) /// private void OnRodUploadStatusChangedTwo(object sender, RodUploadStatusEventArgs e) { if (this.InvokeRequired) { this.Invoke(new Action(OnRodUploadStatusChangedTwo), sender, e); return; } _isRodUploadedInCycleTwo = e.IsUploadSuccess; // 上传成功时更新静态标记 if (e.IsUploadSuccess) { _isRodImageUploadedTwo = true; LogHelper.AppendLog("连杆图片上传成功,标记为「已上传」,定时结束后将不触发报警"); // // ========== 保留:同步更新早晚连杆上传标记(未到节点时) ========== // TimeSpan currentTime = DateTime.Now.TimeOfDay; // // // 早上节点前上传,更新早上标记 // if (currentTime < _morningCheckTime && !_isMorningRodUploaded) // { // _isMorningRodUploaded = true; // LogHelper.AppendLog("连杆图片上传成功,同步标记:早上无需触发强制上传报警"); // } // // 晚上节点前上传,更新晚上标记 // else if (currentTime >= _morningCheckTime && currentTime < _eveningCheckTime && !_isEveningRodUploaded) // { // _isEveningRodUploaded = true; // LogHelper.AppendLog("连杆图片上传成功,同步标记:晚上无需触发强制上传报警"); // } } LogHelper.AppendLog($"连杆上传状态:{(e.IsUploadSuccess ? "成功" : "失败")},消息:{e.Message}"); } /// /// 初始化早晚连杆检查节点时间(基于配置的工作时间) /// private void InitDailyRodDeviceOneCheckTimes() { // 计算早上检查节点:工作开始时间 + 30分钟 _morningCheckTimeOne = _workStartTimeOne.Add(TimeSpan.FromMinutes(2)); // 计算晚上检查节点:工作结束时间 + 30分钟 _eveningCheckTimeOne = _workEndTimeOne.Add(TimeSpan.FromMinutes(2)); // 校验:防止晚上检查节点早于早上检查节点(避免用户配置异常) if (_eveningCheckTimeOne <= _morningCheckTimeOne) { LogHelper.AppendLog("警告:工作时间配置异常,结束时间前30分钟早于开始时间后30分钟,早晚连杆检查功能将失效"); } } /// /// 初始化早晚连杆检查节点时间(设备2) /// private void InitDailyRodDeviceTwoCheckTimes() { // 计算早上检查节点:工作开始时间 + 2分钟 _morningCheckTimeTwo = _workStartTimeTwo.Add(TimeSpan.FromMinutes(2)); // 计算晚上检查节点:工作结束时间 + 2分钟 _eveningCheckTimeTwo = _workEndTimeTwo.Add(TimeSpan.FromMinutes(2)); // 校验:防止晚上检查节点早于早上检查节点(避免用户配置异常) if (_eveningCheckTimeTwo <= _morningCheckTimeTwo) { LogHelper.AppendLog("警告:工作时间配置异常,结束时间前30分钟早于开始时间后30分钟,早晚连杆检查功能将失效"); } } /// /// 计算当前时间到目标检查节点的毫秒数(用于设置定时器延迟) /// /// 目标检查节点(当天的时间点) /// 延迟毫秒数(若目标时间已过,返回-1) private long CalculateDelayToTargetTime(TimeSpan targetTime, string dayOrNight) { DateTime now = DateTime.Now; // 拼接当天的目标日期时间 DateTime targetDateTime = new DateTime(now.Year, now.Month, now.Day, targetTime.Hours, targetTime.Minutes, targetTime.Seconds); TimeSpan delay; if (targetDateTime <= now) { return -1; } // 计算延迟毫秒数 delay = targetDateTime - now; return (long)delay.TotalMilliseconds; } /// /// 停止设备1早晚连杆检查定时器 /// private void StopDailyRodDeviceOneFixedTimers() { // 仅停止设备1的定时器 if (_morningRodAlarmTimerOne != null) { _morningRodAlarmTimerOne.Enabled = false; _morningRodAlarmTimerOne.Dispose(); _morningRodAlarmTimerOne = null; } if (_eveningRodAlarmTimerOne != null) { _eveningRodAlarmTimerOne.Enabled = false; _eveningRodAlarmTimerOne.Dispose(); _eveningRodAlarmTimerOne = null; } LogHelper.AppendLog("设备1早晚连杆检查定时器已停止"); } /// /// 停止设备2早晚连杆检查定时器 /// private void StopDailyRodDeviceTwoFixedTimers() { // 仅停止设备2的定时器 if (_morningRodAlarmTimerTwo != null) { _morningRodAlarmTimerTwo.Enabled = false; _morningRodAlarmTimerTwo.Dispose(); _morningRodAlarmTimerTwo = null; } if (_eveningRodAlarmTimerTwo != null) { _eveningRodAlarmTimerTwo.Enabled = false; _eveningRodAlarmTimerTwo.Dispose(); _eveningRodAlarmTimerTwo = null; } LogHelper.AppendLog("设备2早晚连杆检查定时器已停止"); } /// /// 重置早晚连杆上传标记(每日启动定时器或网关断开时调用) /// private void ResetDailyRodMarks() { _isMorningRodUploadedOne = false; _isEveningRodUploadedOne = false; _isMorningRodUploadedTwo = false; _isEveningRodUploadedTwo = false; LogHelper.AppendLog("早晚连杆上传标记已重置:早上未上传、晚上未上传"); } /// /// 早上连杆检查定时器回调(工作开始+30分钟) /// private void MorningRodDeviceOneAlarmTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { // 1. 判断是否已上传,未上传则触发报警 if (!_isMorningRodUploadedOne) { DateTime now = DateTime.Now; string morningAlarmContent = $"【早上连杆强制上传报警】
当前时间{now:yyyy-MM-dd HH:mm:ss},
已超过工作开始后30分钟({_morningCheckTimeOne:hh\\:mm}),
未上传连杆测试图片!"; this.Invoke(new Action(() => SendDingDingAlarm(morningAlarmContent, true, "1"))); _isRodImageWorkNoticeOne = true; } else { LogHelper.AppendLog($"早上{_morningCheckTimeOne:hh\\:mm}:连杆图片已上传,无需触发强制上传报警"); } // 2. 销毁早上定时器,标记为已检查(避免重复触发) _morningRodAlarmTimerOne?.Dispose(); _morningRodAlarmTimerOne = null; _isMorningRodUploadedOne = true; } /// /// 晚上连杆检查定时器回调(工作结束+30分钟) /// private void EveningRodDeviceOneAlarmTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { // 1. 判断是否已上传,未上传则触发报警 if (!_isEveningRodUploadedOne) { DateTime now = DateTime.Now; string eveningAlarmContent = $"【晚上连杆强制上传报警】
当前时间{now:yyyy-MM-dd HH:mm:ss},
已超过工作结束前30分钟({_eveningCheckTimeOne:hh\\:mm}),
未上传连杆测试图片!"; this.Invoke(new Action(() => SendDingDingAlarm(eveningAlarmContent, true, "1"))); _isRodImageWorkNoticeOne = true; } else { LogHelper.AppendLog($"晚上{_eveningCheckTimeOne:hh\\:mm}:连杆图片已上传,无需触发强制上传报警"); } // 2. 销毁晚上定时器,标记为已检查(避免重复触发) _eveningRodAlarmTimerOne?.Dispose(); _eveningRodAlarmTimerOne = null; _isEveningRodUploadedOne = true; } /// /// 早上连杆检查定时器回调(工作开始+30分钟) /// private void MorningRodDeviceTwoAlarmTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { // 1. 判断是否已上传,未上传则触发报警 if (!_isMorningRodUploadedTwo) { DateTime now = DateTime.Now; string morningAlarmContent = $"【早上连杆强制上传报警】
当前时间{now:yyyy-MM-dd HH:mm:ss},
已超过工作开始后30分钟({_morningCheckTimeTwo:hh\\:mm}),
未上传连杆测试图片!"; this.Invoke(new Action(() => SendDingDingAlarm(morningAlarmContent, true, "2"))); _isRodImageWorkNoticeTwo = true; } else { LogHelper.AppendLog($"早上{_morningCheckTimeTwo:hh\\:mm}:连杆图片已上传,无需触发强制上传报警"); } // 2. 销毁早上定时器,标记为已检查(避免重复触发) _morningRodAlarmTimerTwo?.Dispose(); _morningRodAlarmTimerTwo = null; _isMorningRodUploadedTwo = true; } /// /// 晚上连杆检查定时器回调(工作结束+30分钟) /// private void EveningRodDeviceTwoAlarmTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { // 1. 判断是否已上传,未上传则触发报警 if (!_isEveningRodUploadedTwo) { DateTime now = DateTime.Now; string eveningAlarmContent = $"【晚上连杆强制上传报警】
当前时间{now:yyyy-MM-dd HH:mm:ss},
已超过工作结束前30分钟({_eveningCheckTimeTwo:hh\\:mm}),
未上传连杆测试图片!"; this.Invoke(new Action(() => SendDingDingAlarm(eveningAlarmContent, true, "2"))); _isRodImageWorkNoticeTwo = true; } else { LogHelper.AppendLog($"晚上{_eveningCheckTimeTwo:hh\\:mm}:连杆图片已上传,无需触发强制上传报警"); } // 2. 销毁晚上定时器,标记为已检查(避免重复触发) _eveningRodAlarmTimerTwo?.Dispose(); _eveningRodAlarmTimerTwo = null; _isEveningRodUploadedTwo = true; } // ======================================== 辅助方法 ======================================== private async void Timer_Elapsed(object sender, ElapsedEventArgs e) { // 适配移植的配方变量,刷新选中配方 string selected1 = this.comboBox1.SelectedValue?.ToString(); string selected2 = this.comboBox2.SelectedValue?.ToString(); RefreshSelectedFormula(selected1, selected2); // await MqttYiDaUpload(); } /// /// 刷新选中配方(辅助方法) /// private void RefreshSelectedFormula(string selected1, string selected2) { if (string.IsNullOrEmpty(selected1) || selected1 == "-1") { _formulasListOne = null; return; } string[] arr1 = selected1.Split('|'); if (arr1.Length == 2) { string partNumber1 = arr1[0]; string partName1 = arr1[1]; _formulasListOne = _formulas.AsEnumerable() .Where(r => r.Field("part_number") == partNumber1 && r.Field("part_name") == partName1) .ToList(); } if (string.IsNullOrEmpty(selected2) || selected2 == "-1") { _formulasListTwo = null; return; } string[] arr2 = selected2.Split('|'); if (arr2.Length == 2) { string partNumber2 = arr2[0]; string partName2 = arr2[1]; _formulasListTwo = _formulas.AsEnumerable() .Where(r => r.Field("part_number") == partNumber2 && r.Field("part_name") == partName2) .ToList(); } } // ======================================== 核心上传方法 ======================================== private async Task MqttYiDaUpload(string selection, string device) { try { DataTable dtMqtt = await _buttonService.GetLatestMqttDataAsync(selection, device); if (dtMqtt == null || dtMqtt.Rows.Count == 0) { LogHelper.AppendLog("未查询到MQTT设备数据,跳过本次上传"); return; } LogHelper.AppendLog($"本次读取到{dtMqtt.Rows.Count}条MQTT设备数据"); foreach (DataRow dr in dtMqtt.Rows) { string deviceCode = dr["device_code"].ToString(); LogHelper.AppendLog($"开始处理设备[{deviceCode}]数据"); // ======================================== 使用移植的配方列表变量 ======================================== List formulaList = null; string site = null; if (string.Equals(deviceCode, "device1", StringComparison.Ordinal)) { formulaList = _formulasListOne; site = "1号设备"; } else if (string.Equals(deviceCode, "device2", StringComparison.Ordinal)) { formulaList = _formulasListTwo; site = "2号设备"; } if (formulaList == null || formulaList.Count == 0) { LogHelper.AppendLog($"设备[{deviceCode}]未选择配方,跳过"); continue; } string strMessage = dr["receive_data"].ToString(); if (string.IsNullOrEmpty(strMessage)) { LogHelper.AppendLog($"设备[{deviceCode}]无有效receive_data,跳过"); continue; } SQLDataModel data = JsonConvert.DeserializeObject(strMessage); if (data == null || data.@params == null) { LogHelper.AppendLog($"设备[{deviceCode}]反序列化后无params数据,跳过"); continue; } Type paramsType = data.@params.GetType(); PropertyInfo[] properties = paramsType.GetProperties(BindingFlags.Public | BindingFlags.Instance); List mqttLists = new List(); List yidaLists = new List(); foreach (PropertyInfo prop in properties) { string paramCode = prop.Name; object value = prop.GetValue(data.@params); if (value == null || string.IsNullOrWhiteSpace(value.ToString())) { continue; } // ======================================== 使用移植的MQTT字典进行参数映射 ======================================== DataRow[] drs = _dtMqttDic.Select($"param_code = '" + paramCode + "'"); string paramName = paramCode; if (drs != null && drs.Length > 0) { paramName = drs[0]["param_name"].ToString(); } DataRow drMatch = formulaList.FirstOrDefault(r => r.Field("parameter_name") == paramName); if (drMatch == null) { LogHelper.AppendLog($"设备[{deviceCode}]配方中未找到参数[{paramName}],跳过"); continue; } YiDaModel yidaNew = BuildYiDaModel(drMatch, paramName, value, site); MqttModel mqttNew = BuildMqttModel(drMatch, paramName, value, site); mqttLists.Add(mqttNew); yidaLists.Add(yidaNew); } int len = Math.Min(mqttLists.Count, yidaLists.Count); if (len > 0) { List mqttLists1 = mqttLists.Take(len).ToList(); List yidaLists1 = yidaLists.Take(len).ToList(); string token = _uploadService.GetDingDingToken(); if (string.IsNullOrEmpty(token)) { LogHelper.AppendLog("获取 token 失败,请检查 AppKey/AppSecret"); this.Invoke(new Action(() => { MessageBox.Show("获取 token 失败,请检查 AppKey/AppSecret", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); })); return; } this.Invoke(new Action(() => { _uploadService.UploadDatabaseDataToYiDaWithLogging(token, yidaLists1, mqttLists1, this); })); LogHelper.AppendLog($"设备[{deviceCode}]成功上传{len}条数据到宜搭"); } else { LogHelper.AppendLog($"设备[{deviceCode}]无有效参数数据可上传"); } } } catch (Exception ex) { LogHelper.AppendLog($"MqttYiDaUpload执行失败:{ex.Message}"); this.Invoke(new Action(() => { MessageBox.Show($"上传失败:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); })); } } private MqttModel BuildMqttModel(DataRow drMatch, string paramName, object value, string site) { MqttModel mqttNew = new MqttModel(); mqttNew.SupplierCode = drMatch["supplier_code"].ToString(); mqttNew.SupplierName = drMatch["supplier_name"].ToString(); mqttNew.VehicleModel = drMatch["vehicle_model"].ToString(); mqttNew.PartNumber = drMatch["part_number"].ToString(); mqttNew.PartName = drMatch["part_name"].ToString(); mqttNew.ParameterName = paramName; mqttNew.ParameterValue = value.ToString(); mqttNew.ToleranceLower = drMatch["tolerance_lower"].ToString(); mqttNew.ToleranceUpper = drMatch["tolerance_upper"].ToString(); mqttNew.LeaderPart = drMatch["leader_part"].ToString(); mqttNew.WorkStation = site; mqttNew.LeaderOutProtection = drMatch["leader_out_protection"].ToString(); mqttNew.IsQualification = JudgeQualification(paramName, value, drMatch, site); return mqttNew; } private YiDaModel BuildYiDaModel(DataRow drMatch, string paramName, object value, string site) { YiDaModel yidaNew = new YiDaModel(); yidaNew.textField_mha98neu = drMatch["supplier_code"].ToString(); yidaNew.textField_mha98nev = drMatch["supplier_name"].ToString(); yidaNew.textField_mha98new = drMatch["vehicle_model"].ToString(); yidaNew.textField_mha98nex = drMatch["part_number"].ToString(); yidaNew.textField_mha98ney = drMatch["part_name"].ToString(); yidaNew.textField_mha98nf1 = paramName; yidaNew.textField_mhx44i2i = value.ToString(); yidaNew.textField_mhx44i2j = drMatch["tolerance_lower"].ToString(); yidaNew.textField_mhx44i2k = drMatch["tolerance_upper"].ToString(); yidaNew.textField_mha98nf7 = drMatch["leader_part"].ToString(); yidaNew.textField_mha98nf0 = site; yidaNew.textField_mha98nfh = drMatch["leader_out_protection"].ToString(); yidaNew.textField_mhlvt8ht = Convert.ToInt64((DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)) .TotalMilliseconds); yidaNew.textField_mha98nf5 = JudgeQualification(paramName, value, drMatch, site); return yidaNew; } private string JudgeQualification(string paramName, object value, DataRow drMatch, string site) { if (paramName == "报警信息") { return string.Equals(value?.ToString(), "无错误", StringComparison.Ordinal) ? "合格" : "不合格"; } else if (paramName == "报废图片" || paramName == "连杆测试") { bool isNoticeTriggered = false; if (paramName == "报废图片") { isNoticeTriggered = (site == "1号设备" && _isScrapImageWorkNoticeOne) || (site == "2号设备" && _isScrapImageWorkNoticeTwo); } else if (paramName == "连杆测试") { isNoticeTriggered = (site == "1号设备" && _isRodImageWorkNoticeOne) || (site == "2号设备" && _isRodImageWorkNoticeTwo); } if (isNoticeTriggered) { // 重置对应设备的标记 if (paramName == "报废图片") { if (site == "1号设备") _isScrapImageWorkNoticeOne = false; if (site == "2号设备") _isScrapImageWorkNoticeTwo = false; } else if (paramName == "连杆测试") { if (site == "1号设备") _isRodImageWorkNoticeOne = false; if (site == "2号设备") _isRodImageWorkNoticeTwo = false; } return "不合格"; } else { return "合格"; } } else if (paramName == "开模总数实时" || paramName == "托模次数") { return "合格"; } else { if (float.TryParse(value?.ToString(), out float paramValue) && float.TryParse(drMatch["tolerance_lower"].ToString(), out float toleranceLower) && float.TryParse(drMatch["tolerance_upper"].ToString(), out float toleranceUpper)) { return (paramValue >= toleranceLower && paramValue <= toleranceUpper) ? "合格" : "不合格"; } else { LogHelper.AppendLog( $"数值转换失败:参数名={paramName},参数值={value},下公差={drMatch["tolerance_lower"]},上公差={drMatch["tolerance_upper"]}"); return "不合格"; } } } // ======================================== 日志相关方法 ======================================== private void OnMqttMessage(string msg) { if (this.InvokeRequired) { this.Invoke(new Action(OnMqttMessage), msg); return; } LogHelper.AppendLog($"收到消息: {msg}"); } private void LogHelper_OnLogGenerated(string logContent) { Action appendAction = () => AppendLogToTextBox(logContent); if (textBoxLog.InvokeRequired) { textBoxLog.BeginInvoke(appendAction); // 改用BeginInvoke避免阻塞 } else { appendAction(); } } // 定义日志最大行数 private const int MAX_LOG_LINES = 100; /// /// 日志到WinForm弹窗 /// /// private void AppendLogToTextBox(string logContent) { // 解决跨线程访问控件的问题(WinForm必备) if (textBoxLog.InvokeRequired) { textBoxLog.Invoke(new Action(AppendLogToTextBox), logContent); return; } // 1. 拆分现有日志为行列表(修复原代码空行问题) List logLines = textBoxLog.Text .Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries) .ToList(); // 2. 添加新日志行(原代码错误地添加了logContent而非newLog,这里统一处理) logLines.Add(logContent); // 3. 如果超过最大行数,删除最旧的行(核心逻辑优化) if (logLines.Count > MAX_LOG_LINES) { // 移除超出部分的最旧日志(从列表头部删除) logLines.RemoveRange(0, logLines.Count - MAX_LOG_LINES); } // 4. 重新拼接日志内容(避免末尾多余空行) textBoxLog.Text = string.Join("\r\n", logLines); // 5. 保持滚动到底部 textBoxLog.SelectionStart = textBoxLog.Text.Length; textBoxLog.ScrollToCaret(); } // ======================================== 未实现方法 ======================================== private void txtLUploadPL_TextChanged(object sender, EventArgs e) { } private void panel1_Paint_1(object sender, PaintEventArgs e) { } private void label6_Click(object sender, EventArgs e) { } private void label8_Click(object sender, EventArgs e) { } private void panel3_Paint(object sender, PaintEventArgs e) { } private void toolStripStatusLabel1_Click(object sender, EventArgs e) { } } }