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 Newtonsoft.Json;
using YiDa_WinForm.Business;
using YiDa_WinForm.Config;
using YiDa_WinForm.Model;
using YiDa_WinForm.Service;
using YiDa_WinForm.Service.Mqtt;
using YiDa_WinForm.Utils;
namespace YiDa_WinForm
{
public partial class MainForm : Form
{
// 依赖注入
private readonly MqttClientService _mqttService;
private readonly YiDaUploadService _uploadService;
private readonly ButtonOperationService _buttonService;
private readonly FormulaBusinessService _formulaBusinessService;
// 通用上传定时器
private System.Timers.Timer _timer;
// 日志文件相关路径
private readonly string _logDirectory = "log";
private string _currentLogFile;
private DateTime _lastLogDate = DateTime.MinValue;
// 工作时间配置(默认 9:00 - 23:30)
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;
// ======================================== 初始化相关方法 ========================================
public MainForm()
{
// 初始化设计器方法
InitializeComponent();
// 订阅日志事件
LogHelper.OnLogGenerated += LogHelper_OnLogGenerated;
// 初始化服务实例
_buttonService = new ButtonOperationService();
_mqttService = new MqttClientService();
_uploadService = new YiDaUploadService();
_formulaBusinessService = new FormulaBusinessService(_buttonService);
// 绑定回调事件(连杆+报废)
_mqttService.MessageReceived += OnMqttMessage;
_uploadService.RodUploadStatusChanged += OnRodUploadStatusChanged;
_uploadService.ScrapUploadStatusChanged += OnScrapUploadStatusChanged;
// UI初始化配置
buttonDisconnect.Enabled = false;
// 初始化日志目录
InitializeLogDirectory();
}
///
/// 初始化日志目录
///
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 LoadFormula();
await LoadMqttDic();
timer1.Enabled = true;
}
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);
}
///
/// 加载配方到UI下拉框
///
private async Task LoadFormula()
{
await _formulaBusinessService.LoadFormulaAsync();
// 绑定下拉框数据源
bsPF.DataSource = _formulaBusinessService.UniqueFormula;
bsPF2.DataSource = _formulaBusinessService.UniqueFormula;
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"]} - {drv["part_name"]}";
}
};
comboBox2.Format += (s, e) =>
{
if (e.ListItem is DataRowView drv)
{
e.Value = string.IsNullOrEmpty(drv["part_number_name"].ToString())
? ""
: $"{drv["part_number"]} - {drv["part_name"]}";
}
};
}
///
/// 加载MQTT字典到UI
///
private async Task LoadMqttDic()
{
await _formulaBusinessService.LoadMqttDicAsync();
}
// ======================================== 按钮相关方法 ========================================
///
/// 网关连接按钮
///
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)
{
string filePath = openFile.FileName;
DataTable excelDt = ExcelHelper.GetExcelToDataTable(filePath);
if (excelDt != null && excelDt.Rows.Count > 0)
{
await _buttonService.SaveFormulaByExcel(excelDt);
await LoadFormula();
MessageBox.Show("配方信息已导入!", "成功", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
else
{
MessageBox.Show("不允许导入空表!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
}
}
catch (Exception ex)
{
LogHelper.AppendLog($"配方导入失败:{ex.Message}");
MessageBox.Show($"配方导入失败:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
///
/// 上传宜搭按钮
///
private async void button2_Click(object sender, EventArgs e)
{
try
{
if (toolStripStatusLabel1.Text != "已连接")
{
MessageBox.Show("请先连接MQTT网关!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
if (string.IsNullOrEmpty(comboBox1.Text) && string.IsNullOrEmpty(comboBox2.Text))
{
MessageBox.Show("请先导入配方信息并选择配方!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
button2.Enabled = false;
if (_timer != null)
{
_timer.Enabled = false;
_timer.Dispose();
}
RefreshSelectedFormulaToService();
double seconds = 10;
if (double.TryParse(txtLUploadPL.Text, out double inputSeconds) && inputSeconds > 0)
{
seconds = inputSeconds;
}
await MqttYiDaUpload();
_timer = new System.Timers.Timer(seconds * 1000);
_timer.Elapsed += Timer_Elapsed;
_timer.AutoReset = true;
_timer.Enabled = true;
LogHelper.AppendLog($"自动上传已启动,间隔:{seconds}秒");
MessageBox.Show($"自动上传已启动,间隔:{seconds}秒", "成功", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{
button2.Enabled = true;
LogHelper.AppendLog($"上传宜搭失败:{ex.Message}");
MessageBox.Show($"上传宜搭失败:{ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
///
/// 中止上传按钮
///
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);
}
}
///
/// 报废上传凭证按钮(完整逻辑:定时报警 + 全不选中关闭定时器)
///
private async void button4_Click(object sender, EventArgs e)
{
using (var scrapForm = new ScrapUploadForm())
{
DialogResult result = scrapForm.ShowDialog(this);
if (result == DialogResult.OK)
{
bool isScrapUpload = scrapForm.IsScrapUploadSelected;
bool isScrapTimedAlarm = scrapForm.IsScrapTimedAlarmSelected;
int interval = scrapForm.ScrapTimedInterval;
// ========== 核心逻辑:全不选中时销毁报废定时器 ==========
if (!isScrapUpload && !isScrapTimedAlarm)
{
if (_scrapAlarmTimer != null)
{
_scrapAlarmTimer.Enabled = false;
_scrapAlarmTimer.Dispose();
_scrapAlarmTimer = null;
LogHelper.AppendLog("未勾选任何选框,已销毁报废上传定时报警定时器");
MessageBox.Show("未勾选任何选框,已关闭报废上传定时报警", "信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
else
{
LogHelper.AppendLog("未勾选任何选框,当前无运行中的报废上传定时报警定时器");
MessageBox.Show("未勾选任何选框,当前无运行中的定时报警任务", "信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
return;
}
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);
MessageBox.Show(isImageUploadSuccess ? "报废凭证图片已上传!" : "报废凭证图片上传失败!");
}
else
{
MessageBox.Show("不允许上传空文件!");
}
}
}
if (isScrapTimedAlarm)
{
LogHelper.AppendLog($"勾选了报废定时上传报警,准备启动定时任务(间隔{interval}分钟)");
TimeSpan currentTime = DateTime.Now.TimeOfDay;
if (currentTime < _workStartTime || currentTime > _workEndTime)
{
this.Invoke(new Action(() =>
{
MessageBox.Show(
$"当前时间不在工作时间({_workStartTime:hh\\:mm}-{_workEndTime:hh\\:mm})内,定时任务将在工作时间内自动生效",
"提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
}));
}
StartScrapAlarmTimer(interval);
}
LogHelper.AppendLog(
$"选择结果:报废凭证上传={isScrapUpload}(上传结果={isImageUploadSuccess}),定时上传报警={isScrapTimedAlarm},时间间隔={interval}分钟");
if (!isScrapUpload && isScrapTimedAlarm)
{
MessageBox.Show("定时报警任务已启动,将在工作时间内按配置间隔执行判断", "成功", MessageBoxButtons.OK,
MessageBoxIcon.Information);
}
}
}
}
///
/// 连杆测试上传按钮(完整逻辑:定时报警 + 全不选中关闭定时器)
///
private async void button5_Click(object sender, EventArgs e)
{
using (var selectForm = new StrengthTestUploadForm())
{
DialogResult result = selectForm.ShowDialog(this);
if (result == DialogResult.OK)
{
bool isMoldUpload = selectForm.IsMoldProductionSelected;
bool isTimedAlarm = selectForm.IsTimedAlarmSelected;
int interval = selectForm.TimedInterval;
// ========== 核心逻辑:全不选中时销毁连杆定时器 ==========
if (!isMoldUpload && !isTimedAlarm)
{
if (_rodAlarmTimer != null)
{
_rodAlarmTimer.Enabled = false;
_rodAlarmTimer.Dispose();
_rodAlarmTimer = null;
LogHelper.AppendLog("未勾选任何选框,已销毁连杆测试定时报警定时器");
MessageBox.Show("未勾选任何选框,已关闭连杆测试定时报警", "信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
else
{
LogHelper.AppendLog("未勾选任何选框,当前无运行中的连杆测试定时报警定时器");
MessageBox.Show("未勾选任何选框,当前无运行中的定时报警任务", "信息", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
return;
}
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);
MessageBox.Show(isImageUploadSuccess ? "连杆测试图片已上传!" : "连杆测试图片上传失败!");
}
else
{
MessageBox.Show("不允许上传空文件!");
}
}
}
if (isTimedAlarm)
{
LogHelper.AppendLog($"勾选了定时上传报警,准备启动定时任务(间隔{interval}分钟)");
TimeSpan currentTime = DateTime.Now.TimeOfDay;
if (currentTime < _workStartTime || currentTime > _workEndTime)
{
this.Invoke(new Action(() =>
{
MessageBox.Show(
$"当前时间不在工作时间({_workStartTime:hh\\:mm}-{_workEndTime:hh\\:mm})内,定时任务将在工作时间内自动生效",
"提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
}));
}
StartRodAlarmTimer(interval);
}
LogHelper.AppendLog(
$"选择结果:模具投产上传={isMoldUpload}(上传结果={isImageUploadSuccess}),定时上传报警={isTimedAlarm},时间间隔={interval}分钟");
if (!isMoldUpload && isTimedAlarm)
{
MessageBox.Show("定时报警任务已启动,将在工作时间内按配置间隔执行判断", "成功", MessageBoxButtons.OK,
MessageBoxIcon.Information);
}
}
}
}
///
/// 配置按钮
///
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;
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);
}
}
///
/// 关闭MainForm按钮
///
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();
}
await _mqttService.MqttClientStopAsync();
}
catch (Exception ex)
{
LogHelper.AppendLog($"关闭窗口失败:{ex.Message}");
}
}
///
/// 刷新配方按钮
///
private async void btnRefresh_Click(object sender, EventArgs e)
{
try
{
await LoadFormula();
await LoadMqttDic();
MessageBox.Show("配方已刷新!", "成功", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
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");
}
// ======================================== 定时报警核心方法(连杆+报废) ========================================
///
/// 启动连杆测试定时报警任务
///
private void StartRodAlarmTimer(int intervalMinutes)
{
if (_rodAlarmTimer != null)
{
_rodAlarmTimer.Enabled = false;
_rodAlarmTimer.Dispose();
}
_rodAlarmTimer = new System.Timers.Timer(intervalMinutes * 60 * 1000);
_rodAlarmTimer.Elapsed += RodAlarmTimer_Elapsed;
_rodAlarmTimer.AutoReset = true;
_rodAlarmTimer.Enabled = true;
_isImageUploadedInCycle = false;
LogHelper.AppendLog(
$"连杆测试定时报警已启动:间隔{intervalMinutes}分钟,工作时间{_workStartTime:hh\\:mm}-{_workEndTime:hh\\:mm}");
}
///
/// 启动报废上传定时报警任务
///
private void StartScrapAlarmTimer(int intervalMinutes)
{
if (_scrapAlarmTimer != null)
{
_scrapAlarmTimer.Enabled = false;
_scrapAlarmTimer.Dispose();
}
_scrapAlarmTimer = new System.Timers.Timer(intervalMinutes * 60 * 1000);
_scrapAlarmTimer.Elapsed += ScrapAlarmTimer_Elapsed;
_scrapAlarmTimer.AutoReset = true;
_scrapAlarmTimer.Enabled = true;
_isScrapImageUploadedInCycle = false;
LogHelper.AppendLog(
$"报废上传定时报警已启动:间隔{intervalMinutes}分钟,工作时间{_workStartTime:hh\\:mm}-{_workEndTime:hh\\:mm}");
}
///
/// 连杆报警定时器触发事件
///
private void RodAlarmTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
DateTime now = DateTime.Now;
TimeSpan currentTime = now.TimeOfDay;
if (currentTime < _workStartTime || currentTime > _workEndTime)
{
LogHelper.AppendLog($"当前时间{currentTime:hh\\:mm}不在工作时间内,跳过本次连杆报警判断");
_isImageUploadedInCycle = false;
return;
}
if (!_isImageUploadedInCycle)
{
string alarmContent =
$"连杆测试超时未上传图片!当前时间:{now:yyyy-MM-dd HH:mm:ss},配置间隔:{_rodAlarmTimer.Interval / 60000}分钟";
this.Invoke(new Action(() => SendDingDingAlarm(alarmContent, true)));
}
else
{
LogHelper.AppendLog($"连杆测试计时周期内已上传图片,无需报警,重置标记");
}
_isImageUploadedInCycle = false;
}
///
/// 报废报警定时器触发事件
///
private void ScrapAlarmTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
DateTime now = DateTime.Now;
TimeSpan currentTime = now.TimeOfDay;
if (currentTime < _workStartTime || currentTime > _workEndTime)
{
LogHelper.AppendLog($"当前时间{currentTime:hh\\:mm}不在工作时间内,跳过本次报废报警判断");
_isScrapImageUploadedInCycle = false;
return;
}
if (!_isScrapImageUploadedInCycle)
{
string alarmContent =
$"报废上传超时未上传凭证!当前时间:{now:yyyy-MM-dd HH:mm:ss},配置间隔:{_scrapAlarmTimer.Interval / 60000}分钟";
this.Invoke(new Action(() => SendDingDingAlarm(alarmContent, false)));
}
else
{
LogHelper.AppendLog($"报废上传计时周期内已上传凭证,无需报警,重置标记");
}
_isScrapImageUploadedInCycle = 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}");
}
}
// ======================================== 上传状态回调方法(连杆+报废) ========================================
///
/// 处理连杆上传状态回调
///
private void OnRodUploadStatusChanged(object sender, RodUploadStatusEventArgs e)
{
if (this.InvokeRequired)
{
this.Invoke(new Action