2026-01-30 10:10:14 +08:00

1444 lines
60 KiB
C#
Raw 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.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 System.Timers.Timer _timer;
// 日志文件相关路径
private readonly string _logDirectory = "log";
private string _currentLogFile;
private DateTime _lastLogDate = DateTime.MinValue;
// 工作时间配置
private TimeSpan _workStartTime = new TimeSpan(9, 0, 0);
private TimeSpan _workEndTime = new TimeSpan(23, 30, 0);
// 连杆测试定时报警相关
private System.Timers.Timer _rodAlarmTimer;
private bool _isImageUploadedInCycle = false;
// 报废上传定时报警相关
private System.Timers.Timer _scrapAlarmTimer;
private bool _isScrapImageUploadedInCycle = false;
// 配方
private DataTable _formulas; // 原始配方表(含多行,未去重)
private DataTable _dtMqttDic; // MQTT 参数字典表
private List<DataRow> _formulasList1; // 设备1选择的配方多行
private List<DataRow> _formulasList2; // 设备2选择的配方多行
// 生产相关标记
private static bool _isProduction = false;
private static bool _isRodImageUploaded = false; // 连杆图片是否上传
private static bool _isScrapImageUploaded = false; // 报废图片是否上传
// 报废间隔
private int _lastScrapInterval = 60; // 关键:这个变量在主窗体生命周期内不会销毁,能保留值
// 连杆间隔
private int _lastStrengthInterval = 60;
// 持久化保存两个弹窗的上次勾选状态(核心:再次打开弹窗保留用户选择)
private bool _lastScrapUploadSelected = true; // 报废:上次是否勾选「上传凭证」
private bool _lastScrapTimedAlarmSelected = true; // 报废:上次是否勾选「定时报警」
private bool _lastRodUploadSelected = true; // 连杆:上次是否勾选「模具上传」
private bool _lastRodTimedAlarmSelected = true; // 连杆:上次是否勾选「定时报警」
// 早晚固定时段连杆上传报警相关
private System.Timers.Timer _morningRodAlarmTimer; // 早上检查点定时器(工作开始+30分钟
private System.Timers.Timer _eveningRodAlarmTimer; // 晚上检查点定时器(工作结束-30分钟
private bool _isMorningRodUploaded = false; // 早上标记工作开始后30分钟前是否上传
private bool _isEveningRodUploaded = false; // 晚上标记工作结束前30分钟前是否上传
private TimeSpan _morningCheckTime; // 早上检查节点_workStartTime + 30分钟
private TimeSpan _eveningCheckTime; // 晚上检查节点_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.RodUploadStatusChanged += OnRodUploadStatusChanged;
_uploadService.ScrapUploadStatusChanged += OnScrapUploadStatusChanged;
// UI初始化配置
buttonDisconnect.Enabled = false;
// 初始化日志目录
InitializeLogDirectory();
// 初始化配方相关变量
_formulas = new DataTable();
_dtMqttDic = new DataTable();
_formulasList1 = new List<DataRow>();
_formulasList2 = new List<DataRow>();
InitDailyRodCheckTimes();
}
/// <summary>
/// 初始化日志目录
/// </summary>
private void InitializeLogDirectory()
{
if (!Directory.Exists(_logDirectory))
{
Directory.CreateDirectory(_logDirectory);
}
UpdateLogFile();
}
/// <summary>
/// 更新当前日志文件(每日一个新文件)
/// </summary>
private void UpdateLogFile()
{
DateTime today = DateTime.Now.Date;
if (today != _lastLogDate)
{
_lastLogDate = today;
_currentLogFile = Path.Combine(_logDirectory, $"log_{today:yyyyMMdd}.txt");
}
}
/// <summary>
/// 加载MainForm信息
/// </summary>
private async void Form1_Load(object sender, EventArgs e)
{
try
{
MakePanelRound(panelLed);
await InitFormulas(); // 移植:加载配方
InitMqttDic(); // 移植加载MQTT字典
timer1.Enabled = true;
}
catch (Exception ex)
{
LogHelper.AppendLog($"初始化失败:{ex.Message}");
}
}
/// <summary>
/// 将指定 Panel 变成圆形
/// </summary>
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);
}
// ======================================== 配方核心方法 ========================================
/// <summary>
/// 查询配方并去重
/// </summary>
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();
}
};
}
}
/// <summary>
/// 移植初始化MQTT字典第二份 Form1 的 InitMqttDic() 完整迁移)
/// </summary>
private async void InitMqttDic()
{
DataTable dt = await _buttonService.InitMqttDic(); // 适配第一份的 _buttonService替换第二份的 _mqttService
if (dt != null)
{
_dtMqttDic = dt;
}
}
// ======================================== 按钮相关方法 ========================================
/// <summary>
/// 网关连接按钮
/// </summary>
private async void buttonConnect_Click(object sender, EventArgs e)
{
try
{
LogHelper.AppendLog("正在连接MQTT服务器...");
await _mqttService.MqttClientStartAsync();
LogHelper.AppendLog("连接成功!");
panelLed.BackColor = Color.Green;
toolStripStatusLabel3.Text = "已连接";
buttonConnect.Enabled = false;
buttonDisconnect.Enabled = true;
// 1. 重置静态上传标记(每次连接都重新开始判断)
_isRodImageUploaded = false;
_isScrapImageUploaded = false;
LogHelper.AppendLog("重置上传标记:连杆未上传、报废未上传");
// 2. 启动一次性定时任务(复用用户保存的间隔值)
StartOneTimeAlarmTimers();
StartDailyRodFixedTimers();
}
catch (Exception ex)
{
panelLed.BackColor = Color.Red;
LogHelper.AppendLog($"连接失败:{ex.Message}");
toolStripStatusLabel3.Text = "连接失败";
}
}
/// <summary>
/// 网关断开按钮
/// </summary>
private async void buttonDisconnect_Click(object sender, EventArgs e)
{
try
{
await _mqttService.MqttClientStopAsync();
LogHelper.AppendLog("已断开连接");
panelLed.BackColor = Color.Red;
toolStripStatusLabel3.Text = "未连接";
buttonConnect.Enabled = true;
buttonDisconnect.Enabled = false;
StopAllAlarmTimers();
StopDailyRodFixedTimers();
}
catch (Exception ex)
{
LogHelper.AppendLog($"无法断开连接:{ex.Message}");
}
}
/// <summary>
/// 配方导入按钮
/// </summary>
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;
}
}
/// <summary>
/// 上传宜搭按钮(第一份原有,仅替换配方筛选逻辑为第二份的多行筛选)
/// </summary>
private async void button2_Click(object sender, EventArgs e)
{
try
{
if (toolStripStatusLabel3.Text != "已连接") //如果 MQTT 没连接,就不允许上传。
{
MessageBox.Show("请先连接MQTT网关");
return;
}
if (this.comboBox1.Text.Length == 0 && this.comboBox2.Text.Length == 0)
{
//如果两个下拉框都还没数据,说明没导入过配方,直接中止。
MessageBox.Show("请先导入配方信息,再刷新!");
return;
}
this.button2.Enabled = false;
// 设备1选择配方
string selected1 = this.comboBox1.SelectedValue?.ToString();
if (string.IsNullOrEmpty(selected1) || selected1 == "-1")
{
_formulasList1 = null;
}
else
{
string[] arr1 = selected1.Split('|');
if (arr1.Length == 2)
{
string partNumber1 = arr1[0];
string partName1 = arr1[1];
_formulasList1 = _formulas.AsEnumerable()
.Where(r => r.Field<string>("part_number") == partNumber1
&& r.Field<string>("part_name") == partName1)
.ToList();
}
else
{
_formulasList1 = null;
}
}
// 设备2选择配方
string selected2 = this.comboBox2.SelectedValue?.ToString();
if (string.IsNullOrEmpty(selected2) || selected2 == "-1")
{
_formulasList2 = null;
}
else
{
string[] arr2 = selected2.Split('|');
if (arr2.Length == 2)
{
string partNumber2 = arr2[0];
string partName2 = arr2[1];
_formulasList2 = _formulas.AsEnumerable()
.Where(r => r.Field<string>("part_number") == partNumber2
&& r.Field<string>("part_name") == partName2)
.ToList();
}
else
{
_formulasList2 = null;
}
}
double seconds;
if (!double.TryParse(txtLUploadPL.Text, out seconds) || seconds <= 0)
{
seconds = 10; //控制上传间隔默认10秒。
}
// 立即上传一次
await MqttYiDaUpload();
_timer = new System.Timers.Timer(seconds * 1000);
_timer.Elapsed += async (s, evt) => { await MqttYiDaUpload(); };
_timer.AutoReset = true; // 是否重复
_timer.Enabled = true; // 开始计时
LogHelper.AppendLog($"自动上传已启动,间隔:{seconds}秒");
}
catch (Exception ex)
{
this.button2.Enabled = true;
LogHelper.AppendLog($"上传宜搭失败:{ex.Message}");
}
}
/// <summary>
/// 中止上传按钮
/// </summary>
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);
}
}
/// <summary>
/// 报废上传凭证按钮
/// </summary>
private async void button4_Click(object sender, EventArgs e)
{
// 传入上次的勾选状态+间隔值(核心:持久化用户选择)
using (var scrapForm = new ScrapUploadForm(_lastScrapUploadSelected, _lastScrapTimedAlarmSelected,
_lastScrapInterval))
{
DialogResult result = scrapForm.ShowDialog(this);
if (result == DialogResult.OK)
{
// 1. 提取用户本次选择结果
bool isScrapUpload = scrapForm.IsScrapUploadSelected;
bool isScrapTimedAlarm = scrapForm.IsScrapTimedAlarmSelected;
int interval = scrapForm.ScrapTimedInterval;
// 2. 持久化保存用户选择(核心:下次打开弹窗保留本次状态)
_lastScrapUploadSelected = isScrapUpload;
_lastScrapTimedAlarmSelected = isScrapTimedAlarm;
_lastScrapInterval = interval;
// 3. 上传逻辑:仅当勾选「上传凭证」时,才弹出文件选择框(分离上传和报警逻辑)
bool isImageUploadSuccess = false;
if (isScrapUpload)
{
OpenFileDialog openFile = new OpenFileDialog();
if (openFile.ShowDialog() == DialogResult.OK)
{
string filePath = openFile.FileName;
if (!string.IsNullOrEmpty(filePath))
{
isImageUploadSuccess = await _uploadService.UploadScrapImageInternalAsync(filePath);
LogHelper.AppendLog(isImageUploadSuccess ? "报废凭证图片已上传!" : "报废凭证图片上传失败!");
if (isImageUploadSuccess)
{
_buttonService.MergeAndSaveData("1");
// 上传成功:直接标记为已上传(避免后续定时报警)
_isScrapImageUploaded = true;
}
}
else
{
MessageBox.Show("不允许上传空文件!");
}
}
}
// 4. 仅日志提示(报警逻辑已移到网关连接时,此处不启动定时)
LogHelper.AppendLog(
$"选择结果:报废凭证上传={isScrapUpload}(上传结果={isImageUploadSuccess}),定时上传报警={isScrapTimedAlarm},时间间隔={interval}分钟");
if (isScrapTimedAlarm)
{
MessageBox.Show("报废定时报警已启用连接MQTT网关后将自动启动定时任务", "提示", MessageBoxButtons.OK,
MessageBoxIcon.Information);
}
else
{
MessageBox.Show("报废定时报警已禁用连接MQTT网关后将不启动该定时任务", "提示", MessageBoxButtons.OK,
MessageBoxIcon.Information);
}
}
}
}
/// <summary>
/// 连杆测试上传按钮
/// </summary>
private async void button5_Click(object sender, EventArgs e)
{
// 传入上次的勾选状态+间隔值(核心:持久化用户选择)
using (var selectForm = new StrengthTestUploadForm(_lastRodUploadSelected, _lastRodTimedAlarmSelected,
_lastStrengthInterval))
{
DialogResult result = selectForm.ShowDialog(this);
if (result == DialogResult.OK)
{
// 1. 提取用户本次选择结果
bool isMoldUpload = selectForm.IsMoldProductionSelected;
bool isTimedAlarm = selectForm.IsTimedAlarmSelected;
int interval = selectForm.TimedInterval;
// 2. 持久化保存用户选择(核心:下次打开弹窗保留本次状态)
_lastRodUploadSelected = isMoldUpload;
_lastRodTimedAlarmSelected = isTimedAlarm;
_lastStrengthInterval = interval;
// 3. 上传逻辑:仅当勾选「模具上传」时,才弹出文件选择框(分离上传和报警逻辑)
bool isImageUploadSuccess = false;
if (isMoldUpload)
{
OpenFileDialog openFile = new OpenFileDialog();
if (openFile.ShowDialog() == DialogResult.OK)
{
string filePath = openFile.FileName;
if (!string.IsNullOrEmpty(filePath))
{
isImageUploadSuccess = await _uploadService.UploadRodTestImageInternalAsync(filePath);
LogHelper.AppendLog(isImageUploadSuccess ? "连杆测试图片已上传!" : "连杆测试图片上传失败!");
if (isImageUploadSuccess)
{
_buttonService.MergeAndSaveData("2");
// 上传成功:直接标记为已上传(避免后续定时报警)
_isRodImageUploaded = true;
}
}
else
{
MessageBox.Show("不允许上传空文件!");
}
}
}
// 4. 仅日志提示(报警逻辑已移到网关连接时,此处不启动定时)
LogHelper.AppendLog(
$"选择结果:模具投产上传={isMoldUpload}(上传结果={isImageUploadSuccess}),定时上传报警={isTimedAlarm},时间间隔={interval}分钟");
if (isTimedAlarm)
{
MessageBox.Show("连杆定时报警已启用连接MQTT网关后将自动启动定时任务", "提示", MessageBoxButtons.OK,
MessageBoxIcon.Information);
}
else
{
MessageBox.Show("连杆定时报警已禁用连接MQTT网关后将不启动该定时任务", "提示", MessageBoxButtons.OK,
MessageBoxIcon.Information);
}
}
}
}
/// <summary>
/// 配置按钮
/// </summary>
private void button6_Click(object sender, EventArgs e)
{
try
{
using (var configForm = new WorkTimeConfigForm(_workStartTime, _workEndTime))
{
DialogResult result = configForm.ShowDialog(this);
if (result == DialogResult.OK)
{
_workStartTime = configForm.WorkStartTime;
_workEndTime = configForm.WorkEndTime;
// ========== 保留:重新初始化早晚连杆检查节点 ==========
InitDailyRodCheckTimes();
// ========== 修改:如果网关已连接,重启早晚固定检查定时器 ==========
if (toolStripStatusLabel3.Text == "已连接")
{
StartDailyRodFixedTimers();
}
LogHelper.AppendLog($"工作时间配置更新:{_workStartTime:hh\\:mm} - {_workEndTime:hh\\:mm}");
MessageBox.Show("工作时间配置成功!", "成功", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
}
catch (Exception ex)
{
LogHelper.AppendLog($"工作时间配置失败:{ex.Message}");
MessageBox.Show($"配置失败:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
/// <summary>
/// 关闭MainForm按钮
/// </summary>
private async void MainFormClosing(object sender, FormClosingEventArgs e)
{
try
{
if (_timer != null)
{
_timer.Enabled = false;
_timer.Dispose();
}
if (_rodAlarmTimer != null)
{
_rodAlarmTimer.Enabled = false;
_rodAlarmTimer.Dispose();
}
if (_scrapAlarmTimer != null)
{
_scrapAlarmTimer.Enabled = false;
_scrapAlarmTimer.Dispose();
}
StopDailyRodFixedTimers();
await _mqttService.MqttClientStopAsync();
}
catch (Exception ex)
{
LogHelper.AppendLog($"关闭窗口失败:{ex.Message}");
}
}
/// <summary>
/// 刷新配方按钮
/// </summary>
private async void btnRefresh_Click(object sender, EventArgs e)
{
try
{
await InitFormulas();
InitMqttDic();
MessageBox.Show("配方已刷新!", "成功", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{
LogHelper.AppendLog($"刷新配方失败:{ex.Message}");
MessageBox.Show($"刷新配方失败:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
/// <summary>
/// 上传间隔输入框校验
/// </summary>
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;
}
}
/// <summary>
/// 更新时间显示
/// </summary>
private void timer1_Tick(object sender, EventArgs e)
{
label4.Text = DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss");
}
// ======================================== 定时报警核心方法 ========================================
/// <summary>
/// 新增:启动连杆+报废的一次性定时任务(仅当用户勾选了对应报警时才启动)
/// </summary>
private void StartOneTimeAlarmTimers()
{
// 1. 连杆测试一次性定时(仅当用户勾选了连杆报警时启动)
if (_lastRodTimedAlarmSelected)
{
if (_rodAlarmTimer != null)
{
_rodAlarmTimer.Enabled = false;
_rodAlarmTimer.Dispose();
}
int rodIntervalMs = _lastStrengthInterval * 60 * 1000; // 分钟 → 毫秒
_rodAlarmTimer = new System.Timers.Timer(rodIntervalMs);
_rodAlarmTimer.AutoReset = false; // 关键:一次性执行,不循环
_rodAlarmTimer.Elapsed += RodAlarmTimer_Elapsed;
_rodAlarmTimer.Enabled = true; // 启动定时
LogHelper.AppendLog($"连杆测试一次性报警定时已启动:若{_lastStrengthInterval}分钟内未上传图片,将触发钉钉报警");
}
else
{
LogHelper.AppendLog("用户未勾选连杆定时报警,跳过连杆定时任务启动");
}
// 2. 报废上传一次性定时(仅当用户勾选了报废报警时启动)
if (_lastScrapTimedAlarmSelected)
{
if (_scrapAlarmTimer != null)
{
_scrapAlarmTimer.Enabled = false;
_scrapAlarmTimer.Dispose();
}
int scrapIntervalMs = _lastScrapInterval * 60 * 1000; // 分钟 → 毫秒
_scrapAlarmTimer = new System.Timers.Timer(scrapIntervalMs);
_scrapAlarmTimer.AutoReset = false; // 关键:一次性执行,不循环
_scrapAlarmTimer.Elapsed += ScrapAlarmTimer_Elapsed;
_scrapAlarmTimer.Enabled = true; // 启动定时
LogHelper.AppendLog($"报废上传一次性报警定时已启动:若{_lastScrapInterval}分钟内未上传凭证,将触发钉钉报警");
}
else
{
LogHelper.AppendLog("用户未勾选报废定时报警,跳过报废定时任务启动");
}
}
/// <summary>
/// 终止所有报警定时任务并重置标记
/// </summary>
private void StopAllAlarmTimers()
{
// 1. 终止连杆定时
if (_rodAlarmTimer != null)
{
_rodAlarmTimer.Enabled = false;
_rodAlarmTimer.Dispose();
_rodAlarmTimer = null;
LogHelper.AppendLog("连杆测试报警定时已终止(网关断开)");
}
// 2. 终止报废定时
if (_scrapAlarmTimer != null)
{
_scrapAlarmTimer.Enabled = false;
_scrapAlarmTimer.Dispose();
_scrapAlarmTimer = null;
LogHelper.AppendLog("报废上传报警定时已终止(网关断开)");
}
// 3. 重置静态上传标记
_isRodImageUploaded = false;
_isScrapImageUploaded = false;
}
private void RodAlarmTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
DateTime now = DateTime.Now;
TimeSpan currentTime = now.TimeOfDay;
// 1. 工作时间判断
if (currentTime < _workStartTime || currentTime > _workEndTime)
{
LogHelper.AppendLog($"当前时间{currentTime:hh\\:mm}不在工作时间内,跳过本次连杆报警判断");
_rodAlarmTimer?.Dispose();
_rodAlarmTimer = null;
return;
}
// ========== 保留:早晚节点标记判断(已上传则跳过原有报警) ==========
if (_isMorningRodUploaded || _isEveningRodUploaded)
{
LogHelper.AppendLog("连杆图片已在早晚强制时段内上传,跳过本次原有定时报警");
_rodAlarmTimer?.Dispose();
_rodAlarmTimer = null;
_isRodImageUploaded = false;
return;
}
// 2. 使用静态标记 _isRodImageUploaded 判断是否报警
if (!_isRodImageUploaded)
{
string alarmContent = $"连杆测试超时未上传图片!<br>当前时间:{now:yyyy-MM-dd HH:mm:ss}";
this.Invoke(new Action(() => SendDingDingAlarm(alarmContent, true)));
}
else
{
LogHelper.AppendLog($"连杆测试{_lastStrengthInterval}分钟内已上传图片,无需触发报警");
}
// 3. 销毁定时器,重置标记
_rodAlarmTimer?.Dispose();
_rodAlarmTimer = null;
_isRodImageUploaded = false;
}
private void ScrapAlarmTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
DateTime now = DateTime.Now;
TimeSpan currentTime = now.TimeOfDay;
// 1. 工作时间判断
if (currentTime < _workStartTime || currentTime > _workEndTime)
{
LogHelper.AppendLog($"当前时间{currentTime:hh\\:mm}不在工作时间内,跳过本次报废报警判断");
// 销毁定时器
_scrapAlarmTimer?.Dispose();
_scrapAlarmTimer = null;
return;
}
// 2. 使用静态标记 _isScrapImageUploaded 判断是否报警(核心:上传成功则不报警)
if (!_isScrapImageUploaded)
{
string alarmContent = $"报废上传超时未上传凭证!<br>当前时间:{now:yyyy-MM-dd HH:mm:ss}";
this.Invoke(new Action(() => SendDingDingAlarm(alarmContent, false)));
}
else
{
LogHelper.AppendLog($"报废上传{_lastScrapInterval}分钟内已上传凭证,无需触发报警");
}
// 3. 销毁定时器,重置标记
_scrapAlarmTimer?.Dispose();
_scrapAlarmTimer = null;
_isScrapImageUploaded = false;
}
private async void SendDingDingAlarm(string alarmContent, bool isRodAlarm)
{
try
{
LogHelper.AppendLog($"【钉钉报警】{(isRodAlarm ? "" : "")}{alarmContent}");
await _uploadService.SendDingDingTextAlarmAsync(alarmContent, isRodAlarm);
// MessageBox.Show($"已发送钉钉{(isRodAlarm ? "连杆" : "报废")}报警:{alarmContent}", "报警提示", MessageBoxButtons.OK,
// MessageBoxIcon.Warning);
}
catch (Exception ex)
{
LogHelper.AppendLog($"{(isRodAlarm ? "" : "")}钉钉报警发送失败:{ex.Message}");
}
}
// ======================================== 上传状态回调方法(连杆+报废) ========================================
/// <summary>
/// 连杆上传状态回调
/// </summary>
private void OnRodUploadStatusChanged(object sender, RodUploadStatusEventArgs e)
{
if (this.InvokeRequired)
{
this.Invoke(new Action<object, RodUploadStatusEventArgs>(OnRodUploadStatusChanged), sender, e);
return;
}
_isImageUploadedInCycle = e.IsUploadSuccess;
// 上传成功时更新静态标记
if (e.IsUploadSuccess)
{
_isRodImageUploaded = 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}");
}
/// <summary>
/// 报废上传状态回调
/// </summary>
private void OnScrapUploadStatusChanged(object sender, ScrapUploadStatusEventArgs e)
{
if (this.InvokeRequired)
{
this.Invoke(new Action<object, ScrapUploadStatusEventArgs>(OnScrapUploadStatusChanged), sender, e);
return;
}
_isScrapImageUploadedInCycle = e.IsUploadSuccess;
// 上传成功时更新静态标记
if (e.IsUploadSuccess)
{
_isScrapImageUploaded = true;
LogHelper.AppendLog("报废凭证上传成功,标记为「已上传」,定时结束后将不触发报警");
}
LogHelper.AppendLog($"报废上传状态:{(e.IsUploadSuccess ? "" : "")},消息:{e.Message}");
}
/// <summary>
/// 初始化早晚连杆检查节点时间(基于配置的工作时间)
/// </summary>
private void InitDailyRodCheckTimes()
{
// 计算早上检查节点:工作开始时间 + 30分钟
_morningCheckTime = _workStartTime.Add(TimeSpan.FromMinutes(30));
// 计算晚上检查节点:工作结束时间 - 30分钟
_eveningCheckTime = _workEndTime.Subtract(TimeSpan.FromMinutes(30));
// 校验:防止晚上检查节点早于早上检查节点(避免用户配置异常)
if (_eveningCheckTime <= _morningCheckTime)
{
LogHelper.AppendLog("警告工作时间配置异常结束时间前30分钟早于开始时间后30分钟早晚连杆检查功能将失效");
}
LogHelper.AppendLog($"早晚连杆检查节点已初始化:早上{_morningCheckTime:hh\\:mm},晚上{_eveningCheckTime:hh\\:mm}");
}
/// <summary>
/// 计算当前时间到目标检查节点的毫秒数(用于设置定时器延迟)
/// </summary>
/// <param name="targetTime">目标检查节点(当天的时间点)</param>
/// <returns>延迟毫秒数(若目标时间已过,返回-1</returns>
private long CalculateDelayToTargetTime(TimeSpan targetTime)
{
DateTime now = DateTime.Now;
// 拼接当天的目标日期时间
DateTime targetDateTime = new DateTime(now.Year, now.Month, now.Day,
targetTime.Hours, targetTime.Minutes, targetTime.Seconds);
// 若目标时间已过当天当前时间,返回-1无需启动当天定时器
if (targetDateTime <= now)
{
return -1;
}
// 计算延迟毫秒数
TimeSpan delay = targetDateTime - now;
return (long)delay.TotalMilliseconds;
}
/// <summary>
/// 启动早晚连杆检查定时器(一次性,到点触发)
/// </summary>
private void StartDailyRodFixedTimers()
{
// 先停止并释放原有定时器(防止重复启动)
StopDailyRodFixedTimers();
// 重置早晚标记
ResetDailyRodMarks();
// 1. 启动早上检查定时器(若目标时间未过)
long morningDelayMs = CalculateDelayToTargetTime(_morningCheckTime);
if (morningDelayMs > 0 && _morningCheckTime > _workStartTime)
{
_morningRodAlarmTimer = new System.Timers.Timer(morningDelayMs);
_morningRodAlarmTimer.AutoReset = false; // 一次性执行,不循环
_morningRodAlarmTimer.Elapsed += MorningRodAlarmTimer_Elapsed;
_morningRodAlarmTimer.Enabled = true; // 启动定时器
LogHelper.AppendLog(
$"早上连杆检查定时器已启动:将在{_morningCheckTime:hh\\:mm}触发");
}
else
{
LogHelper.AppendLog("早上检查节点已过当天当前时间,跳过早上连杆检查定时器启动");
_isMorningRodUploaded = true; // 标记为已检查,避免重复判断
}
// 2. 启动晚上检查定时器(若目标时间未过)
long eveningDelayMs = CalculateDelayToTargetTime(_eveningCheckTime);
if (eveningDelayMs > 0 && _eveningCheckTime < _workEndTime)
{
_eveningRodAlarmTimer = new System.Timers.Timer(eveningDelayMs);
_eveningRodAlarmTimer.AutoReset = false; // 一次性执行,不循环
_eveningRodAlarmTimer.Elapsed += EveningRodAlarmTimer_Elapsed;
_eveningRodAlarmTimer.Enabled = true; // 启动定时器
LogHelper.AppendLog(
$"晚上连杆检查定时器已启动:将在{_eveningCheckTime:hh\\:mm}触发");
}
else
{
LogHelper.AppendLog("晚上检查节点已过当天当前时间,跳过晚上连杆检查定时器启动");
_isEveningRodUploaded = true; // 标记为已检查,避免重复判断
}
}
/// <summary>
/// 停止早晚连杆检查定时器并重置标记
/// </summary>
private void StopDailyRodFixedTimers()
{
// 停止早上定时器
if (_morningRodAlarmTimer != null)
{
_morningRodAlarmTimer.Enabled = false;
_morningRodAlarmTimer.Dispose();
_morningRodAlarmTimer = null;
}
// 停止晚上定时器
if (_eveningRodAlarmTimer != null)
{
_eveningRodAlarmTimer.Enabled = false;
_eveningRodAlarmTimer.Dispose();
_eveningRodAlarmTimer = null;
}
LogHelper.AppendLog("早晚连杆检查定时器已停止");
}
/// <summary>
/// 重置早晚连杆上传标记(每日启动定时器或网关断开时调用)
/// </summary>
private void ResetDailyRodMarks()
{
_isMorningRodUploaded = false;
_isEveningRodUploaded = false;
LogHelper.AppendLog("早晚连杆上传标记已重置:早上未上传、晚上未上传");
}
/// <summary>
/// 早上连杆检查定时器回调(工作开始+30分钟
/// </summary>
private void MorningRodAlarmTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
// 1. 判断是否已上传,未上传则触发报警
if (!_isMorningRodUploaded)
{
DateTime now = DateTime.Now;
string morningAlarmContent = $"【早上连杆强制上传报警】<br>当前时间{now:yyyy-MM-dd HH:mm:ss}<br>已超过工作开始后30分钟{_morningCheckTime:hh\\:mm}<br>未上传连杆测试图片!";
this.Invoke(new Action(() => SendDingDingAlarm(morningAlarmContent, true)));
}
else
{
LogHelper.AppendLog($"早上{_morningCheckTime:hh\\:mm}:连杆图片已上传,无需触发强制上传报警");
}
// 2. 销毁早上定时器,标记为已检查(避免重复触发)
_morningRodAlarmTimer?.Dispose();
_morningRodAlarmTimer = null;
_isMorningRodUploaded = true;
}
/// <summary>
/// 晚上连杆检查定时器回调(工作结束-30分钟
/// </summary>
private void EveningRodAlarmTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
// 1. 判断是否已上传,未上传则触发报警
if (!_isEveningRodUploaded)
{
DateTime now = DateTime.Now;
string eveningAlarmContent = $"【晚上连杆强制上传报警】<br>当前时间{now:yyyy-MM-dd HH:mm:ss}<br>已超过工作结束前30分钟{_eveningCheckTime:hh\\:mm}<br>未上传连杆测试图片!";
this.Invoke(new Action(() => SendDingDingAlarm(eveningAlarmContent, true)));
}
else
{
LogHelper.AppendLog($"晚上{_eveningCheckTime:hh\\:mm}:连杆图片已上传,无需触发强制上传报警");
}
// 2. 销毁晚上定时器,标记为已检查(避免重复触发)
_eveningRodAlarmTimer?.Dispose();
_eveningRodAlarmTimer = null;
_isEveningRodUploaded = 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();
}
/// <summary>
/// 适配移植:刷新选中配方(辅助方法)
/// </summary>
private void RefreshSelectedFormula(string selected1, string selected2)
{
if (string.IsNullOrEmpty(selected1) || selected1 == "-1")
{
_formulasList1 = null;
return;
}
string[] arr1 = selected1.Split('|');
if (arr1.Length == 2)
{
string partNumber1 = arr1[0];
string partName1 = arr1[1];
_formulasList1 = _formulas.AsEnumerable()
.Where(r => r.Field<string>("part_number") == partNumber1
&& r.Field<string>("part_name") == partName1)
.ToList();
}
if (string.IsNullOrEmpty(selected2) || selected2 == "-1")
{
_formulasList2 = null;
return;
}
string[] arr2 = selected2.Split('|');
if (arr2.Length == 2)
{
string partNumber2 = arr2[0];
string partName2 = arr2[1];
_formulasList2 = _formulas.AsEnumerable()
.Where(r => r.Field<string>("part_number") == partNumber2
&& r.Field<string>("part_name") == partName2)
.ToList();
}
}
// ======================================== 核心上传方法 ========================================
private async Task MqttYiDaUpload()
{
try
{
DataTable dtMqtt = await _buttonService.GetLatestMqttDataAsync();
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<DataRow> formulaList = null;
string site = null;
if (string.Equals(deviceCode, "device1", StringComparison.Ordinal))
{
formulaList = _formulasList1;
site = "1号设备";
}
else if (string.Equals(deviceCode, "device2", StringComparison.Ordinal))
{
formulaList = _formulasList2;
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<SQLDataModel>(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<MqttModel> mqttLists = new List<MqttModel>();
List<YiDaModel> yidaLists = new List<YiDaModel>();
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<string>("parameter_name") == paramName);
if (drMatch == null)
{
LogHelper.AppendLog($"设备[{deviceCode}]配方中未找到参数[{paramName}],跳过");
continue;
}
MqttModel mqttNew = BuildMqttModel(drMatch, paramName, value, site);
YiDaModel yidaNew = BuildYiDaModel(drMatch, paramName, value, site);
mqttLists.Add(mqttNew);
yidaLists.Add(yidaNew);
}
int len = Math.Min(mqttLists.Count, yidaLists.Count);
if (len > 0)
{
List<MqttModel> mqttLists1 = mqttLists.Take(len).ToList();
List<YiDaModel> 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, mqttLists, 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);
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);
return yidaNew;
}
private string JudgeQualification(string paramName, object value, DataRow drMatch)
{
if (paramName == "报警信息")
{
return string.Equals(value?.ToString(), "无错误", StringComparison.Ordinal) ? "合格" : "不合格";
}
else if (paramName == "开模总数实时" || paramName == "托模次数" || 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<string>(OnMqttMessage), msg);
return;
}
LogHelper.AppendLog($"收到消息: {msg}");
}
private void LogHelper_OnLogGenerated(string logContent)
{
if (textBoxLog.InvokeRequired)
{
textBoxLog.Invoke(new Action(() => { AppendLogToTextBox(logContent); }));
}
else
{
AppendLogToTextBox(logContent);
}
}
private void AppendLogToTextBox(string logContent)
{
textBoxLog.Text += $"{logContent}\r\n";
textBoxLog.SelectionStart = textBoxLog.Text.Length;
textBoxLog.ScrollToCaret();
}
// ======================================== 未实现方法 ========================================
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
}
private void button5_Click_1(object sender, EventArgs e)
{
}
private void txtLUploadPL_TextChanged(object sender, EventArgs e)
{
}
private void panel1_Paint(object sender, PaintEventArgs e)
{
}
private void panel1_Paint_1(object sender, PaintEventArgs e)
{
}
private void label6_Click(object sender, EventArgs e)
{
}
private void toolStripStatusLabel3_Click(object sender, EventArgs e)
{
}
}
}