The first day in siemens

作者在 Siemens 的 CT 部门开始了实习生涯,首日主要进行了软件安装,并学习了 C# 和 SQL Server 的应用实例,特别是 CredentialsManager 项目。该项目清晰地展示了如何使用 C# 连接并操作数据库。
rel="File-List" href="file:///C:%5CUsers%5Cpebble%5CAppData%5CLocal%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_filelist.xml"> rel="Edit-Time-Data" href="file:///C:%5CUsers%5Cpebble%5CAppData%5CLocal%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_editdata.mso"> rel="themeData" href="file:///C:%5CUsers%5Cpebble%5CAppData%5CLocal%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_themedata.thmx"> rel="colorSchemeMapping" href="file:///C:%5CUsers%5Cpebble%5CAppData%5CLocal%5CTemp%5Cmsohtmlclip1%5C01%5Cclip_colorschememapping.xml">

This is the first day in CT of siemens. In the morning I spend all the time installing the software, including win xp and vs2008. I found that the cd of windows is not pirate and totally English.

Then in the afternoon, mentor give me some examples about c# and sql server. The most important one is CredentialsManager. It is the Ex provided by MS. The structure is very clear. It pack the functions of some other services like provider to connect the DB in the lower layer and could change the table in the DB responding to the change of  the front.  It supplies some interface for other developers included in the interfaces file of the project.

 

In my opinion, it is absolutely a project about C# and sql server. The point is how C# can use the DB and make some changes on it. Maybe it is very similar with the  way of  the  connection of JDBC which is java and sql server.

 

The work In the next few days

In the next, I think I donnot need to read all the source code of the CredentialsManager. And it is really not the opinion of my mentor. What I need is to carry out a small and simple application which apply C# and sql server. The application should include a windows form..

 

The Structure of the whole project:

There are four parts in the solution explorer. It is based on b/s. The windows form of the front and the procedure of the background is very important for me .The windows form is quiet easy to understand. So the most time I spend on is the part of how C# connect and operate database.

Implement IRoleManager,IApplicationManager,IPasswordManager,IMembershipManager,IUserManager

 

AspNetSqlProviderService

 

 

 

Inf IM

 

 

 

 

 

InF IAM

 

using HslCommunication; using HslCommunication.Profinet.Melsec; using MySqlConnector; using System; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Windows.Forms; using System.Timers; using System.Data.SqlClient; using NLog; using Timer = System.Windows.Forms.Timer; using System.Collections.Concurrent; using System.Threading.Tasks; using System.Collections.Generic; using System.Reflection; namespace OvenData { public partial class Form1 : Form { // PLC连接管理优化 - 改为长连接模式 private MelsecMcNet plc; private readonly object plcLock = new object(); private const int MaxConnectionRetries = 100000; private const int ConnectionRetryDelay = 20000; // 10秒 private bool isPlcConnecting = false; private DateTime lastHeartbeatTime = DateTime.MinValue; private const int HeartbeatInterval = 30000; // 30秒心跳包 private DateTime lastSuccessfulConnectionTime = DateTime.MinValue; private int consecutiveFailedConnections = 0; private int totalFailedConnections = 0; private int totalSuccessfulConnections = 0; private int currentConnectionRetryDelay = ConnectionRetryDelay; private bool isFirstConnectionAttempt = true; private bool isHeartbeatActive = false; // 新增:心跳活动状态 // 数据库连接优化 private string connectionString; private ConcurrentQueue<DbCommandData> dbCommandQueue = new ConcurrentQueue<DbCommandData>(); private const int MaxQueueSize = 1000; // 限制队列最大长度 private System.Timers.Timer dbBatchTimer; // 定时器优化 private System.Timers.Timer dataCollectionTimer; private System.Timers.Timer connectionCheckTimer; private System.Timers.Timer heartbeatTimer; private DateTime nextTempReadTime = DateTime.MinValue; private DateTime nextUnitReadTime = DateTime.MinValue; private bool isPLCConnected = false; // UI组件 private Label statusLabel; private Timer uiUpdateTimer; private Label connectionStatusLabel; private Label lastConnectionLabel; private Label retryDelayLabel; private Label dataCollectionStatusLabel; private Label lastDataCollectionLabel; // 日志 private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); // 系统托盘 private NotifyIcon trayIcon; private ContextMenuStrip trayMenu; // 报警状态跟踪 private Dictionary<string, bool> alarmStatusDict = new Dictionary<string, bool>(); private System.Timers.Timer alarmCheckTimer; // 数据采集标记 private DateTime lastTempCollectionTime = DateTime.MinValue; private DateTime lastUnitCollectionTime = DateTime.MinValue; private readonly object collectionLock = new object(); // 用于跟踪上次采集是否成功 private bool lastTemperatureCollectionSucceeded = true; private bool lastUnitDataCollectionSucceeded = true; // 重试计数 private int temperatureRetryCount = 0; private int unitDataRetryCount = 0; private const int MaxRetries = 3; // 最大重试次数 private const int RetryDelayBase = 20000; // 基础重试延迟(毫秒) // 程序运行状态 private bool isShuttingDown = false; private DateTime lastSuccessfulDataCollectionTime = DateTime.MinValue; private int totalDataCollections = 0; private int successfulDataCollections = 0; // 采集时间间隔定义 private static readonly TimeSpan TemperatureCollectionInterval = TimeSpan.FromMinutes(5); private static readonly TimeSpan UnitDataCollectionInterval = TimeSpan.FromHours(1); public Form1() { InitializeComponent(); InitializeComponents(); InitializeTimers(); InitializeTrayIcon(); InitializeAlarmStatus(); // 注册窗体事件 this.Resize += Form1_Resize; this.FormClosing += Form1_FormClosing; // 启动时立即尝试连接 Task.Run(async () => await CheckConnectionAsync()); // 记录程序启动 Logger.Info("低温烤箱数据采集系统已启动"); } private void InitializeAlarmStatus() { // 初始化常见报警点位的状态跟踪 for (int i = 0; i < 6; i++) { alarmStatusDict[$"D7500.{i}"] = false; // 仓高温 alarmStatusDict[$"D7500.{i + 16}"] = false; // 仓低温 alarmStatusDict[$"D7500.{i + 32}"] = false; // 仓温度异常 alarmStatusDict[$"D7500.{i + 48}"] = false; // 仓超温保护 alarmStatusDict[$"D7500.{i + 64}"] = false; // 仓风压低 alarmStatusDict[$"D7513.{i}"] = false; // 仓循环风机故障 alarmStatusDict[$"D7513.{i + 24}"] = false; // 仓发热体故障 alarmStatusDict[$"D7513.{i + 48}"] = false; // 仓跳闸 } // 其他报警点位 alarmStatusDict["D7521.0"] = false; // 下位要板超时 alarmStatusDict["D7521.1"] = false; // 定位出错 alarmStatusDict["D7521.2"] = false; // 前门上升报警 } private void InitializeTrayIcon() { // 创建托盘菜单 trayMenu = new ContextMenuStrip(); // 创建菜单项 var showMenuItem = new ToolStripMenuItem("显示窗口"); showMenuItem.Click += (s, e) => { this.Show(); this.WindowState = FormWindowState.Normal; this.Activate(); }; var exitMenuItem = new ToolStripMenuItem("退出程序"); exitMenuItem.Click += (s, e) => { isShuttingDown = true; Application.Exit(); }; // 添加菜单项到托盘菜单 trayMenu.Items.Add(showMenuItem); trayMenu.Items.Add(exitMenuItem); // 创建托盘图标 trayIcon = new NotifyIcon { Text = "低温烤箱数据采集", Icon = GetAppIcon(), ContextMenuStrip = trayMenu, Visible = true }; // 双击托盘图标显示窗口 trayIcon.DoubleClick += (s, e) => { this.Show(); this.WindowState = FormWindowState.Normal; this.Activate(); }; } // 获取应用程序图标 private Icon GetAppIcon() { try { return Properties.Resources.kaoxiang1; } catch (Exception ex) { Logger.Warn(ex, "无法加载自定义图标,使用系统默认图标"); return SystemIcons.Information; } } private void InitializeComponents() { // 初始化PLC连接 plc = new MelsecMcNet("192.168.38.174", 8001) { ConnectTimeOut = 10000, ReceiveTimeOut = 10000 }; // 数据库连接字符串 connectionString = new MySqlConnectionStringBuilder { Server = "192.168.238.247", Database = "Oven_Data", UserID = "root", Password = "RelianceERP2013&&", Pooling = true, MinimumPoolSize = 1, MaximumPoolSize = 10, ConnectionTimeout = 15, ConnectionLifeTime = 300, Keepalive = 30, AllowUserVariables = true }.ToString(); // 状态标签 statusLabel = new Label { Text = "PLC连接状态: 初始化中...", AutoSize = true, Location = new Point(12, 12), Font = new Font("Microsoft YaHei", 10F, FontStyle.Bold), ForeColor = Color.Blue }; this.Controls.Add(statusLabel); // 添加队列状态标签 Label queueStatusLabel = new Label { Name = "queueStatusLabel", Text = "数据库队列: 0条", AutoSize = true, Location = new Point(12, 40), Font = new Font("Microsoft YaHei", 9F), ForeColor = Color.Gray }; this.Controls.Add(queueStatusLabel); // 添加连接状态标签 connectionStatusLabel = new Label { Name = "connectionStatusLabel", Text = "连接状态: 等待连接", AutoSize = true, Location = new Point(12, 70), Font = new Font("Microsoft YaHei", 9F), ForeColor = Color.Gray }; this.Controls.Add(connectionStatusLabel); // 添加最后连接时间标签 lastConnectionLabel = new Label { Name = "lastConnectionLabel", Text = "最后成功连接: 从未", AutoSize = true, Location = new Point(12, 100), Font = new Font("Microsoft YaHei", 9F), ForeColor = Color.Gray }; this.Controls.Add(lastConnectionLabel); // 添加重试延迟标签 retryDelayLabel = new Label { Name = "retryDelayLabel", Text = "重试延迟: 5秒", AutoSize = true, Location = new Point(12, 130), Font = new Font("Microsoft YaHei", 9F), ForeColor = Color.Gray }; this.Controls.Add(retryDelayLabel); // 添加数据采集状态标签 dataCollectionStatusLabel = new Label { Name = "dataCollectionStatusLabel", Text = "数据采集: 等待中", AutoSize = true, Location = new Point(12, 160), Font = new Font("Microsoft YaHei", 9F), ForeColor = Color.Gray }; this.Controls.Add(dataCollectionStatusLabel); // 添加最后数据采集时间标签 lastDataCollectionLabel = new Label { Name = "lastDataCollectionLabel", Text = "最后采集: 从未", AutoSize = true, Location = new Point(12, 190), Font = new Font("Microsoft YaHei", 9F), ForeColor = Color.Gray }; this.Controls.Add(lastDataCollectionLabel); // 添加系统状态标签 Label systemStatusLabel = new Label { Name = "systemStatusLabel", Text = "系统状态: 正常运行", AutoSize = true, Location = new Point(12, 220), Font = new Font("Microsoft YaHei", 9F), ForeColor = Color.Gray }; this.Controls.Add(systemStatusLabel); } private void InitializeTimers() { // UI更新定时器 uiUpdateTimer = new Timer { Interval = 1000 }; uiUpdateTimer.Tick += UiUpdateTimer_Tick; uiUpdateTimer.Start(); // 连接检查定时器 connectionCheckTimer = new System.Timers.Timer(5000) { AutoReset = true }; connectionCheckTimer.Elapsed += async (s, e) => await CheckConnectionAsync(); connectionCheckTimer.Start(); // 心跳包定时器 heartbeatTimer = new System.Timers.Timer(HeartbeatInterval) { AutoReset = true }; heartbeatTimer.Elapsed += async (s, e) => await SendHeartbeatAsync(); heartbeatTimer.Start(); // 报警检查定时器 alarmCheckTimer = new System.Timers.Timer(1000) { AutoReset = true }; alarmCheckTimer.Elapsed += async (s, e) => await CheckAndSaveAlarmDataAsync(); alarmCheckTimer.Start(); // 数据采集定时器 - 改为一次性触发 dataCollectionTimer = new System.Timers.Timer { AutoReset = false, // 只触发一次 Enabled = false // 初始禁用 }; dataCollectionTimer.Elapsed += async (s, e) => await CollectDataAsync(); // 数据库批量处理定时器 dbBatchTimer = new System.Timers.Timer(2000) { AutoReset = true }; dbBatchTimer.Elapsed += async (s, e) => await ProcessDbQueueAsync(); dbBatchTimer.Start(); // 初始化下一次采集时间 InitializeNextCollectionTimes(); // 启动精确的定时器调度 ScheduleNextDataCollection(); } private void InitializeNextCollectionTimes() { DateTime now = DateTime.Now; // 计算温度采集时间点(从当前时间开始的5分钟间隔) nextTempReadTime = CalculateNextTempTime(now); // 计算单位数据采集时间点(从当前时间开始的下一个整点) nextUnitReadTime = CalculateNextUnitTime(now); Logger.Info($"初始化采集时间: 温度采集 {nextTempReadTime}, 单位数据采集 {nextUnitReadTime}"); } private DateTime CalculateNextTempTime(DateTime now) { // 计算从当前时间开始的下一个5分钟点 int minutes = now.Minute; int nextMinute = ((minutes / 5) + 1) * 5; if (nextMinute >= 60) { // 下一个小时的0分钟 return new DateTime(now.Year, now.Month, now.Day, now.Hour + 1, 0, 0); } else { return new DateTime(now.Year, now.Month, now.Day, now.Hour, nextMinute, 0); } } private DateTime CalculateNextUnitTime(DateTime now) { // 计算从当前时间开始的下一个整点 if (now.Minute > 0 || now.Second > 0 || now.Millisecond > 0) { return new DateTime(now.Year, now.Month, now.Day, now.Hour, 0, 0).AddHours(1); } else { return now; } } private DateTime CalculateExpectedLastCollectionTime(DateTime now, TimeSpan interval) { // 计算理论上的上次采集时间点 long ticksSinceEpoch = now.Ticks; long intervalTicks = interval.Ticks; long remainder = ticksSinceEpoch % intervalTicks; return new DateTime(ticksSinceEpoch - remainder); } private async Task CheckConnectionAsync() { if (isPlcConnecting || isShuttingDown) return; try { isPlcConnecting = true; bool connected = await Task.Run(() => { lock (plcLock) { if (!isPLCConnected) { // 计算指数退避重试延迟(增加最大延迟到5分钟) if (consecutiveFailedConnections > 0) { currentConnectionRetryDelay = Math.Min( ConnectionRetryDelay * (int)Math.Pow(2, Math.Min(consecutiveFailedConnections, 8)), // 最大延迟约5分钟 300000); } else { currentConnectionRetryDelay = ConnectionRetryDelay; } Logger.Info($"尝试连接PLC (第{consecutiveFailedConnections + 1}次尝试, 延迟: {currentConnectionRetryDelay / 1000}秒)..."); UpdateConnectionStatusLabel($"正在连接PLC (第{consecutiveFailedConnections + 1}次尝试)..."); UpdateRetryDelayLabel($"{currentConnectionRetryDelay / 1000}秒"); // 等待指定延迟 if (!isFirstConnectionAttempt && consecutiveFailedConnections > 0) { System.Threading.Thread.Sleep(currentConnectionRetryDelay); } isFirstConnectionAttempt = false; var result = plc.ConnectServer(); if (result.IsSuccess) { // 连接成功,重置所有状态 ResetAllStates(); Logger.Info($"PLC连接成功! 这是第{totalSuccessfulConnections}次成功连接"); return true; } consecutiveFailedConnections++; totalFailedConnections++; Logger.Warn($"PLC连接失败: {result.Message}"); UpdateConnectionStatusLabel($"连接失败: {result.Message}"); UpdateLastConnectionLabel("从未"); return false; } // 已连接状态下检查心跳 if ((DateTime.Now - lastHeartbeatTime).TotalMilliseconds > HeartbeatInterval * 2) { Logger.Warn("心跳超时,尝试重连PLC..."); UpdateConnectionStatusLabel("心跳超时,正在重连..."); plc.ConnectClose(); var result = plc.ConnectServer(); if (result.IsSuccess) { ResetAllStates(); Logger.Info("PLC重连成功"); return true; } Logger.Warn($"PLC重连失败: {result.Message}"); UpdateConnectionStatusLabel($"重连失败: {result.Message}"); isPLCConnected = false; return false; } return true; } }); isPLCConnected = connected; } catch (Exception ex) { Logger.Error(ex, "PLC连接检查失败"); isPLCConnected = false; } finally { isPlcConnecting = false; } } private void ResetAllStates() { // 重置连接相关状态 consecutiveFailedConnections = 0; currentConnectionRetryDelay = ConnectionRetryDelay; lastSuccessfulConnectionTime = DateTime.Now; isHeartbeatActive = true; lastHeartbeatTime = DateTime.Now; isPLCConnected = true; // 重置采集相关状态 ResetCollectionState(); // 记录连接成功 totalSuccessfulConnections++; } private void ResetCollectionState() { lock (collectionLock) { // 重置采集时间和重试计数 lastTempCollectionTime = DateTime.MinValue; lastUnitCollectionTime = DateTime.MinValue; temperatureRetryCount = 0; unitDataRetryCount = 0; lastTemperatureCollectionSucceeded = true; lastUnitDataCollectionSucceeded = true; // 重新计算采集时间点(关键修复) ResetCollectionTimes(); // 立即调度下一次采集 ScheduleNextDataCollection(); Logger.Info("数据采集状态已重置"); } } private void ResetCollectionTimes() { DateTime now = DateTime.Now; // 温度采集时间点:从当前时间开始计算下一个5分钟间隔 nextTempReadTime = CalculateNextTempTime(now); // 单位数据采集时间点:从当前时间开始计算下一个整点 nextUnitReadTime = CalculateNextUnitTime(now); Logger.Info($"重置采集时间: 温度采集 {nextTempReadTime}, 单位数据采集 {nextUnitReadTime}"); } private void UpdateConnectionStatusLabel(string status) { if (connectionStatusLabel.InvokeRequired) { connectionStatusLabel.BeginInvoke(new Action(() => UpdateConnectionStatusLabel(status))); return; } connectionStatusLabel.Text = $"连接状态: {status}"; connectionStatusLabel.ForeColor = isPLCConnected ? Color.Green : Color.Red; } private void UpdateLastConnectionLabel(string timeText) { if (lastConnectionLabel.InvokeRequired) { lastConnectionLabel.BeginInvoke(new Action(() => UpdateLastConnectionLabel(timeText))); return; } if (lastSuccessfulConnectionTime != DateTime.MinValue) { lastConnectionLabel.Text = $"最后成功连接: {lastSuccessfulConnectionTime:yyyy-MM-dd HH:mm:ss}"; } else { lastConnectionLabel.Text = $"最后成功连接: {timeText}"; } } private void UpdateRetryDelayLabel(string delayText) { if (retryDelayLabel.InvokeRequired) { retryDelayLabel.BeginInvoke(new Action(() => UpdateRetryDelayLabel(delayText))); return; } retryDelayLabel.Text = $"重试延迟: {delayText}"; } private void UpdateDataCollectionStatusLabel(string status) { if (dataCollectionStatusLabel.InvokeRequired) { dataCollectionStatusLabel.BeginInvoke(new Action(() => UpdateDataCollectionStatusLabel(status))); return; } dataCollectionStatusLabel.Text = $"数据采集: {status}"; dataCollectionStatusLabel.ForeColor = lastTemperatureCollectionSucceeded && lastUnitDataCollectionSucceeded ? Color.Green : Color.Orange; } private void UpdateLastDataCollectionLabel(string timeText) { if (lastDataCollectionLabel.InvokeRequired) { lastDataCollectionLabel.BeginInvoke(new Action(() => UpdateLastDataCollectionLabel(timeText))); return; } if (lastSuccessfulDataCollectionTime != DateTime.MinValue) { lastDataCollectionLabel.Text = $"最后采集: {lastSuccessfulDataCollectionTime:yyyy-MM-dd HH:mm:ss}"; } else { lastDataCollectionLabel.Text = $"最后采集: {timeText}"; } } private async Task SendHeartbeatAsync() { if (!isPLCConnected || isShuttingDown || !isHeartbeatActive) return; try { await Task.Run(() => { lock (plcLock) { // 读取一个固定点位作为心跳检测 var result = plc.ReadBool("M8000"); if (result.IsSuccess) { lastHeartbeatTime = DateTime.Now; Logger.Debug("心跳包发送成功"); } else { Logger.Warn($"心跳包发送失败: {result.Message}"); isPLCConnected = false; isHeartbeatActive = false; UpdateConnectionStatusLabel("心跳检测失败"); } } }); } catch (Exception ex) { Logger.Error(ex, "心跳包发送异常"); isPLCConnected = false; isHeartbeatActive = false; UpdateConnectionStatusLabel("心跳检测异常"); } } private async Task CollectDataAsync() { if (isShuttingDown) return; if (!isPLCConnected) { // 重新调度下一次采集 ScheduleNextDataCollection(); return; } DateTime now = DateTime.Now; bool temperatureSucceeded = false; bool unitDataSucceeded = false; bool anyCollectionAttempted = false; bool allCollectionsSucceeded = true; try { UpdateDataCollectionStatusLabel("正在采集..."); // 温度数据采集 if (ShouldCollectTemperature(now)) { anyCollectionAttempted = true; temperatureSucceeded = await ReadAndSaveTemperatureDataAsync(); if (temperatureSucceeded) { lock (collectionLock) { lastTempCollectionTime = now; temperatureRetryCount = 0; // 重置重试计数 Logger.Info($"温度数据采集完成: {now}"); // 更新下一次温度采集时间 nextTempReadTime = CalculateNextTempTime(now); Logger.Info($"下一次温度采集时间更新为: {nextTempReadTime}"); } } else { temperatureRetryCount++; Logger.Warn($"温度数据采集失败 (重试{temperatureRetryCount}/{MaxRetries}): {now}"); allCollectionsSucceeded = false; } } // 其他数据采集 if (ShouldCollectUnitData(now)) { anyCollectionAttempted = true; unitDataSucceeded = await ReadAndSaveOtherUnitDataAsync(); if (unitDataSucceeded) { lock (collectionLock) { lastUnitCollectionTime = now; unitDataRetryCount = 0; // 重置重试计数 Logger.Info($"单位数据采集完成: {now}"); // 更新下一次单位数据采集时间 nextUnitReadTime = CalculateNextUnitTime(now); Logger.Info($"下一次单位数据采集时间更新为: {nextUnitReadTime}"); } } else { unitDataRetryCount++; Logger.Warn($"单位数据采集失败 (重试{unitDataRetryCount}/{MaxRetries}): {now}"); allCollectionsSucceeded = false; } } // 记录本次采集结果 lastTemperatureCollectionSucceeded = temperatureSucceeded; lastUnitDataCollectionSucceeded = unitDataSucceeded; // 更新成功采集时间 if (anyCollectionAttempted && allCollectionsSucceeded) { lastSuccessfulDataCollectionTime = now; successfulDataCollections++; } totalDataCollections++; } catch (Exception ex) { Logger.Error(ex, "数据采集失败"); allCollectionsSucceeded = false; } finally { // 根据采集结果动态调整定时器 ScheduleNextDataCollection(temperatureSucceeded, unitDataSucceeded); // 更新UI状态 UpdateDataCollectionStatusLabel(allCollectionsSucceeded ? "成功" : "部分失败"); UpdateLastDataCollectionLabel(lastSuccessfulDataCollectionTime == DateTime.MinValue ? "从未" : lastSuccessfulDataCollectionTime.ToString("yyyy-MM-dd HH:mm:ss")); } } // 判断是否应该采集温度数据 private bool ShouldCollectTemperature(DateTime now) { lock (collectionLock) { // 检查是否到达下一次采集时间 if (now < nextTempReadTime) return false; // 计算理论上的上次采集时间 DateTime expectedLastCollection = CalculateExpectedLastCollectionTime(now, TemperatureCollectionInterval); // 检查是否已经在当前时间间隔内采集过 if (lastTempCollectionTime > expectedLastCollection) { Logger.Debug($"跳过温度数据采集: 上次采集时间 {lastTempCollectionTime}, 当前时间 {now}"); return false; } return true; } } // 判断是否应该采集单位数据 private bool ShouldCollectUnitData(DateTime now) { lock (collectionLock) { // 检查是否到达下一次采集时间 if (now < nextUnitReadTime) return false; // 计算理论上的上次采集时间 DateTime expectedLastCollection = CalculateExpectedLastCollectionTime(now, UnitDataCollectionInterval); // 检查是否已经在当前时间间隔内采集过 if (lastUnitCollectionTime > expectedLastCollection) { Logger.Debug($"跳过单位数据采集: 上次采集时间 {lastUnitCollectionTime}, 当前时间 {now}"); return false; } return true; } } private async Task<bool> ReadAndSaveTemperatureDataAsync() { if (isShuttingDown) return false; try { // 批量读取温度数据 var readTasks = new[] { ReadPlcDataAsync<short>("D7007", 6), // 当前温度 ReadPlcDataAsync<short>("D7102", 6) // 目标温度 }; await Task.WhenAll(readTasks); var currentTemps = readTasks[0].Result; var targetTemps = readTasks[1].Result; // 批量添加到数据库队列 for (int i = 0; i < 6; i++) { if (currentTemps != null && i < currentTemps.Length) { double value = currentTemps[i] / 10.0; EnqueueDbCommand( "INSERT INTO temperature_data (variable_name, address, value, unit, timestamp) VALUES (@var, @addr, @val, @unit, NOW())", new { var = $"{i + 1}仓温度当前值", addr = $"D700{i + 7}", val = value, unit = "℃" }); } if (targetTemps != null && i < targetTemps.Length) { double value = targetTemps[i] / 10.0; EnqueueDbCommand( "INSERT INTO temperature_data (variable_name, address, value, unit, timestamp) VALUES (@var, @addr, @val, @unit, NOW())", new { var = $"{i + 1}仓温度目标值", addr = $"D710{i + 2}", val = value, unit = "℃" }); } } Logger.Info("温度数据采集完成"); return true; } catch (Exception ex) { Logger.Error(ex, "温度数据采集失败"); return false; } } private async Task<bool> ReadAndSaveOtherUnitDataAsync() { if (isShuttingDown) return false; try { // 使用并行读取提高效率 var tasks = new[] { ReadAndSaveUnitDataAsync("D7202", "累计待机时间", "H", 0.1), ReadAndSaveUnitDataAsync("D7206", "累计运行时间", "H", 0.1), ReadAndSaveUnitDataAsync("D7210", "累计故障时间", "H", 0.1), ReadAndSaveUnitDataAsync("D7214", "累计维修时间", "H", 0.1), ReadAndSaveUnitDataAsync("D7218", "累计保养时间", "H", 0.1), ReadAndSaveUnitDataAsync("D7222", "累计产量", "片", 1), ReadAndSaveUnitDataAsync("D7226", "累计用电", "KWH", 1) }; await Task.WhenAll(tasks); Logger.Info("单位数据采集完成"); return true; } catch (Exception ex) { Logger.Error(ex, "单位数据采集失败"); return false; } } private async Task ReadAndSaveUnitDataAsync(string address, string name, string unit, double factor) { if (isShuttingDown) return; var value = await ReadPlcDataAsync<int>(address, 1); if (value != null && value.Length > 0) { double actualValue = value[0] * factor; EnqueueDbCommand( "INSERT INTO other_data (variable_name, address, value, unit, timestamp) VALUES (@var, @addr, @val, @unit, NOW())", new { var = name, addr = address, val = actualValue, unit = unit }); } } private async Task CheckAndSaveAlarmDataAsync() { if (!isPLCConnected || isShuttingDown) return; try { // 并行读取报警区域 var alarmTasks = new[] { ProcessAlarmRegionAsync("D7500.0", 48, new[] { ("仓高温", 0, 6), ("仓低温", 16, 6), ("仓温度异常", 32, 6), ("仓超温保护", 48, 6), ("仓风压低", 64, 6) }), ProcessAlarmRegionAsync("D7513.0", 48, new[] { ("仓循环风机故障", 0, 6), ("仓发热体故障", 24, 6), ("仓跳闸", 48, 6) }), ProcessAlarmRegionAsync("D7521.0", 16, new[] { ("下位要板超时", 0, 1), ("定位出错", 1, 1), ("前门上升报警", 2, 1) }) }; await Task.WhenAll(alarmTasks); } catch (Exception ex) { Logger.Error(ex, "报警信息检查失败"); throw; } } private async Task ProcessAlarmRegionAsync(string startAddress, ushort bitCount, (string, int, int)[] alarmDefinitions) { if (isShuttingDown) return; var alarms = await ReadPlcDataAsync<bool>(startAddress, bitCount); if (alarms == null) return; foreach (var (baseName, startBit, count) in alarmDefinitions) { for (int i = 0; i < count; i++) { int index = startBit + i; if (index < alarms.Length) { string alarmName = count > 1 ? $"{i + 1}{baseName}" : baseName; string fullAddress = $"{startAddress.Substring(0, startAddress.Length - 1)}{index}"; // 检查报警状态变化 bool currentStatus = alarms[index]; bool previousStatus; lock (alarmStatusDict) { alarmStatusDict.TryGetValue(fullAddress, out previousStatus); // 状态变化时记录事件 if (currentStatus != previousStatus) { alarmStatusDict[fullAddress] = currentStatus; if (currentStatus) { // 报警触发 EnqueueDbCommand( "INSERT INTO alarm_data (alarm_name, address, status, timestamp) VALUES (@name, @addr, 1, NOW())", new { name = alarmName, addr = fullAddress }); Logger.Warn($"报警触发: {alarmName} ({fullAddress})"); // 显示托盘通知 ShowAlarmNotification(alarmName); } else { // 报警恢复 EnqueueDbCommand( "INSERT INTO alarm_data (alarm_name, address, status, timestamp) VALUES (@name, @addr, 0, NOW())", new { name = alarmName, addr = fullAddress }); Logger.Info($"报警恢复: {alarmName} ({fullAddress})"); } } } } } } } private void ShowAlarmNotification(string alarmName) { if (trayIcon != null) { trayIcon.ShowBalloonTip( 5000, "低温烤箱报警", $"报警触发: {alarmName}", ToolTipIcon.Warning); } } private async Task<T[]> ReadPlcDataAsync<T>(string address, int length) where T : struct { if (isShuttingDown) return null; int retries = 0; OperateResult<T[]> result = null; while (retries < MaxConnectionRetries) { try { lock (plcLock) { // 确保连接已建立 if (!isPLCConnected) { var connectResult = plc.ConnectServer(); if (!connectResult.IsSuccess) { Logger.Error($"PLC连接失败: {connectResult.Message}"); throw new Exception($"PLC连接失败: {connectResult.Message}"); } isPLCConnected = true; isHeartbeatActive = true; lastHeartbeatTime = DateTime.Now; } // 根据数据类型选择不同的读取方法 if (typeof(T) == typeof(bool)) { // 处理位地址读取 if (address.Contains(".")) { string[] parts = address.Split('.'); string baseAddress = parts[0]; int bitOffset = int.Parse(parts[1]); // 计算需要读取的字节数 int bytesToRead = (bitOffset + length + 7) / 8; var byteResult = plc.Read(baseAddress, (ushort)bytesToRead); if (byteResult.IsSuccess) { bool[] bits = new bool[length]; for (int i = 0; i < length; i++) { int byteIndex = (bitOffset + i) / 8; int bitIndex = (bitOffset + i) % 8; bits[i] = (byteResult.Content[byteIndex] & (1 << bitIndex)) != 0; } return bits as dynamic; } } else { result = plc.ReadBool(address, (ushort)length) as OperateResult<T[]>; } } else if (typeof(T) == typeof(short)) { result = plc.ReadInt16(address, (ushort)length) as OperateResult<T[]>; } else if (typeof(T) == typeof(int)) { // 支持批量读取多个int if (length > 1) { result = plc.ReadInt32(address, (ushort)length) as OperateResult<T[]>; } else { var singleResult = plc.ReadInt32(address); if (singleResult.IsSuccess) { return new[] { singleResult.Content } as dynamic; } } } else if (typeof(T) == typeof(float)) { result = plc.ReadFloat(address, (ushort)length) as OperateResult<T[]>; } if (result != null && result.IsSuccess) { lastHeartbeatTime = DateTime.Now; return result.Content; } Logger.Warn($"PLC数据读取失败: 地址 {address}, 错误: {result?.Message}"); } } catch (Exception ex) { retries++; if (retries >= MaxConnectionRetries) { Logger.Error(ex, $"PLC数据读取失败: 地址 {address}, 已重试 {retries} 次"); isPLCConnected = false; isHeartbeatActive = false; throw; } Logger.Warn($"PLC数据读取重试 ({retries}/{MaxConnectionRetries}): {ex.Message}"); await Task.Delay(ConnectionRetryDelay); } } return null; } private void EnqueueDbCommand(string sql, object parameters) { if (isShuttingDown) return; // 限制队列大小,防止内存溢出 if (dbCommandQueue.Count >= MaxQueueSize) { Logger.Warn("数据库队列已满,丢弃命令: " + sql); return; } // 验证参数类型 if (parameters == null) { Logger.Error("数据库命令参数为空: " + sql); return; } // 检查参数属性与SQL参数是否匹配 var paramType = parameters.GetType(); var sqlParams = sql.Split('@').Skip(1).Select(p => p.Split(' ', ',', ')', ';').First()).ToList(); foreach (var sqlParam in sqlParams) { if (paramType.GetProperty(sqlParam) == null) { Logger.Warn($"SQL参数与对象属性不匹配: @{sqlParam} 在 {paramType.Name} 中未找到"); } } dbCommandQueue.Enqueue(new DbCommandData { Sql = sql, Parameters = parameters }); } private async Task ProcessDbQueueAsync() { if (isShuttingDown || dbCommandQueue.IsEmpty) return; var batch = new List<DbCommandData>(); int dequeueCount = Math.Min(100, dbCommandQueue.Count); for (int i = 0; i < dequeueCount; i++) { if (dbCommandQueue.TryDequeue(out var cmd)) { batch.Add(cmd); } } if (batch.Count == 0) return; try { using (var conn = new MySqlConnection(connectionString)) { await conn.OpenAsync(); using (var transaction = await conn.BeginTransactionAsync()) { try { foreach (var cmd in batch) { using (var dbCmd = new MySqlCommand(cmd.Sql, conn, transaction)) { // 使用强类型参数 foreach (var prop in cmd.Parameters.GetType().GetProperties()) { object value = prop.GetValue(cmd.Parameters); dbCmd.Parameters.AddWithValue($"@{prop.Name}", value ?? DBNull.Value); } await dbCmd.ExecuteNonQueryAsync(); } } await transaction.CommitAsync(); Logger.Debug($"成功批量写入数据库: {batch.Count} 条记录"); } catch (Exception ex) { Logger.Error(ex, "数据库事务执行失败,回滚操作"); await transaction.RollbackAsync(); throw; } } } } catch (Exception ex) { Logger.Error(ex, "数据库批量操作失败"); // 将失败的命令重新加入队列 foreach (var cmd in batch) { dbCommandQueue.Enqueue(cmd); } } } private void UiUpdateTimer_Tick(object sender, EventArgs e) { UpdateStatusLabel(); UpdateQueueStatusLabel(); UpdateLastConnectionLabel(""); UpdateLastDataCollectionLabel(""); // 更新系统状态 var systemStatusLabel = Controls.Find("systemStatusLabel", true).FirstOrDefault() as Label; if (systemStatusLabel != null) { if (systemStatusLabel.InvokeRequired) { systemStatusLabel.BeginInvoke(new Action(() => { string status = $"系统状态: 正常运行 (连接: {(isPLCConnected ? "已连接" : "未连接")}, 心跳: {(isHeartbeatActive ? "活跃" : "未激活")})"; systemStatusLabel.Text = status; systemStatusLabel.ForeColor = isPLCConnected ? Color.Green : Color.Red; })); } } } private void UpdateStatusLabel() { if (statusLabel.InvokeRequired) { statusLabel.BeginInvoke(new Action(UpdateStatusLabel)); return; } statusLabel.Text = isPLCConnected ? "PLC连接状态: 已连接 (正常采集数据)" : "PLC连接状态: 未连接 (等待设备开启)"; statusLabel.ForeColor = isPLCConnected ? Color.Green : Color.Red; if (trayIcon != null) { trayIcon.Text = isPLCConnected ? "低温烤箱数据采集程序 - 已连接" : "低温烤箱数据采集程序 - 未连接"; trayIcon.Icon = isPLCConnected ? GetAppIcon() : SystemIcons.Warning; } } private void UpdateQueueStatusLabel() { var queueLabel = Controls.Find("queueStatusLabel", true).FirstOrDefault() as Label; if (queueLabel == null) return; if (queueLabel.InvokeRequired) { queueLabel.BeginInvoke(new Action(UpdateQueueStatusLabel)); return; } int queueCount = dbCommandQueue.Count; queueLabel.Text = $"数据库队列: {queueCount}条"; // 根据队列长度设置不同的颜色 if (queueCount > 500) { queueLabel.ForeColor = Color.Red; } else if (queueCount > 200) { queueLabel.ForeColor = Color.Orange; } else { queueLabel.ForeColor = Color.Gray; } } private void ScheduleNextDataCollection(bool? temperatureSuccess = null, bool? unitDataSuccess = null) { if (isShuttingDown) return; DateTime now = DateTime.Now; TimeSpan delay = TimeSpan.Zero; // 如果温度采集失败且在重试次数内 if (temperatureSuccess.HasValue && !temperatureSuccess.Value && temperatureRetryCount < MaxRetries) { int retryDelay = RetryDelayBase * (int)Math.Pow(2, temperatureRetryCount); delay = TimeSpan.FromMilliseconds(retryDelay); Logger.Info($"温度数据采集失败,{retryDelay / 1000}秒后重试 (第{temperatureRetryCount + 1}/{MaxRetries}次)"); } // 如果单位数据采集失败且在重试次数内 else if (unitDataSuccess.HasValue && !unitDataSuccess.Value && unitDataRetryCount < MaxRetries) { int retryDelay = RetryDelayBase * (int)Math.Pow(2, unitDataRetryCount); delay = TimeSpan.FromMilliseconds(retryDelay); Logger.Info($"单位数据采集失败,{retryDelay / 1000}秒后重试 (第{unitDataRetryCount + 1}/{MaxRetries}次)"); } else { // 正常情况下,计算下次采集时间 DateTime nextCollectionTime = DateTime.MaxValue; // 取温度和单位数据采集时间的最小值 if (nextTempReadTime > now) { nextCollectionTime = nextTempReadTime; } if (nextUnitReadTime > now && nextUnitReadTime < nextCollectionTime) { nextCollectionTime = nextUnitReadTime; } delay = nextCollectionTime - now; // 关键修复:如果下次采集时间超过1小时,说明时间计算可能有问题,重新计算 if (delay.TotalHours > 1) { Logger.Warn($"检测到过长的采集延迟: {delay.TotalHours}小时,重新计算采集时间"); ResetCollectionTimes(); ScheduleNextDataCollection(); return; } // 确保延迟为正 if (delay.TotalMilliseconds < 0) { // 检查是否有应该采集但未采集的数据 bool shouldCollectTemp = ShouldCollectTemperature(now); bool shouldCollectUnit = ShouldCollectUnitData(now); if (shouldCollectTemp || shouldCollectUnit) { Logger.Info("检测到需要立即采集的数据,触发采集"); delay = TimeSpan.Zero; } else { // 重新计算采集时间 Logger.Info("检测到数据采集时间计算错误,重新计算采集时间"); ResetCollectionTimes(); ScheduleNextDataCollection(); return; } } } // 设置定时器 dataCollectionTimer.Interval = Math.Max(1000, delay.TotalMilliseconds); // 最小延迟1秒 dataCollectionTimer.Enabled = true; Logger.Info($"下次数据采集时间: {DateTime.Now.Add(delay):yyyy-MM-dd HH:mm:ss} (约{delay.TotalSeconds:F2}秒后)"); } private void Form1_Resize(object sender, EventArgs e) { if (WindowState == FormWindowState.Minimized) { this.Hide(); //trayIcon.ShowBalloonTip(5000, "低温烤箱数据采集", "程序已最小化到系统托盘", ToolTipIcon.Info); } } private void Form1_FormClosing(object sender, FormClosingEventArgs e) { if (e.CloseReason == CloseReason.UserClosing) { // 阻止默认关闭行为 e.Cancel = true; this.Hide(); //trayIcon.ShowBalloonTip(5000, "低温烤箱数据采集", "程序已最小化到系统托盘", ToolTipIcon.Info); return; } // 其他关闭原因(如应用程序退出) isShuttingDown = true; // 停止所有定时器 if (dataCollectionTimer != null) dataCollectionTimer.Stop(); if (connectionCheckTimer != null) connectionCheckTimer.Stop(); if (heartbeatTimer != null) heartbeatTimer.Stop(); if (alarmCheckTimer != null) alarmCheckTimer.Stop(); if (dbBatchTimer != null) dbBatchTimer.Stop(); if (uiUpdateTimer != null) uiUpdateTimer.Stop(); // 关闭PLC连接 try { if (plc != null && isPLCConnected) { plc.ConnectClose(); Logger.Info("PLC连接已关闭"); } } catch (Exception ex) { Logger.Error(ex, "关闭PLC连接时出错"); } // 处理剩余的数据库命令 try { if (dbCommandQueue.Count > 0) { Logger.Info($"正在处理剩余的 {dbCommandQueue.Count} 条数据库命令..."); ProcessDbQueueAsync().Wait(); Logger.Info("所有数据库命令已处理完毕"); } } catch (Exception ex) { Logger.Error(ex, "处理剩余数据库命令时出错"); } // 隐藏托盘图标 if (trayIcon != null) { trayIcon.Visible = false; } Logger.Info("低温烤箱数据采集系统已关闭"); } } // 数据库命令数据类 public class DbCommandData { public string Sql { get; set; } public object Parameters { get; set; } } }实现重连后继续读取数据
06-21
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值