一、C# WinForm 基恩士扫码枪 串口通信 SQLite Log4Net1.1 技术架构概览模块技术选型作用UI框架WinForms (.NET Framework 4.7)传统工业界面快速开发硬件通信SerialPort 基恩士SDK扫码枪数据采集数据存储SQLite System.Data.SQLite轻量级本地数据库日志系统Log4Net运行日志与故障追踪数据导出NPOI/EPPlusExcel报表生成1.2 基恩士扫码枪SDK集成基恩士(Keyence)扫码枪通常提供两种集成方式方式AUSB HID键盘模式免开发// 窗体级别键盘钩子捕获扫码数据 public partial class ScanForm : Form { private StringBuilder _scanBuffer new StringBuilder(); private DateTime _lastKeyTime; private const int SCAN_THRESHOLD_MS 50; // 扫码枪输入间隔50ms public ScanForm() { InitializeComponent(); this.KeyPreview true; // 捕获全局键盘事件 } protected override void OnKeyPress(KeyPressEventArgs e) { var now DateTime.Now; // 判断是否为扫码枪快速输入人工输入间隔100ms if ((now - _lastKeyTime).TotalMilliseconds 100 _scanBuffer.Length 0) { _scanBuffer.Clear(); } _lastKeyTime now; if (e.KeyChar (char)Keys.Enter) { string barcode _scanBuffer.ToString().Trim(); _scanBuffer.Clear(); ProcessBarcode(barcode); // 处理条码 } else { _scanBuffer.Append(e.KeyChar); } base.OnKeyPress(e); } }方式BSerialPort串口通信模式推荐工业场景using System.IO.Ports; public class KeyenceScanner { private SerialPort _serialPort; private ILog _logger LogManager.GetLogger(typeof(KeyenceScanner)); public event Actionstring OnBarcodeScanned; public bool Connect(string portName, int baudRate 9600) { try { _serialPort new SerialPort { PortName portName, BaudRate baudRate, DataBits 8, StopBits StopBits.One, Parity Parity.None, ReadTimeout 500, WriteTimeout 500 }; _serialPort.DataReceived SerialPort_DataReceived; _serialPort.Open(); _logger.Info($扫码枪已连接: {portName}); return true; } catch (Exception ex) { _logger.Error($扫码枪连接失败: {ex.Message}); return false; } } private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e) { try { string data _serialPort.ReadExisting().Trim(); if (!string.IsNullOrEmpty(data)) { // 跨线程更新UI OnBarcodeScanned?.Invoke(data); _logger.Debug($扫描到条码: {data}); } } catch (Exception ex) { _logger.Error($数据接收异常: {ex.Message}); } } // 发送指令控制扫码枪需参考基恩士指令集 public void TriggerScan() { // 示例发送触发扫描指令 _serialPort.WriteLine(LON); // 开启激光 Thread.Sleep(100); _serialPort.WriteLine(LOFF); // 关闭激光 } }1.3 SQLite数据库操作封装using System.Data.SQLite; public class SQLiteHelper : IDisposable { private SQLiteConnection _connection; private readonly string _connectionString; private static readonly object _lock new object(); public SQLiteHelper(string dbPath) { _connectionString $Data Source{dbPath};Version3;PoolingTrue;Max Pool Size100;; InitializeDatabase(); } private void InitializeDatabase() { if (!File.Exists(_connectionString.Split()[1].Split(;)[0])) { SQLiteConnection.CreateFile(_connectionString.Split()[1].Split(;)[0]); } _connection new SQLiteConnection(_connectionString); _connection.Open(); // 创建生产数据表 ExecuteNonQuery( CREATE TABLE IF NOT EXISTS ProductionData ( Id INTEGER PRIMARY KEY AUTOINCREMENT, Barcode TEXT NOT NULL, ScanTime DATETIME DEFAULT CURRENT_TIMESTAMP, Result TEXT, Operator TEXT )); } public void InsertScanRecord(string barcode, string result, string operatorName) { lock (_lock) // 多线程安全 { using (var cmd new SQLiteCommand( INSERT INTO ProductionData (Barcode, Result, Operator) VALUES (barcode, result, operator), _connection)) { cmd.Parameters.AddWithValue(barcode, barcode); cmd.Parameters.AddWithValue(result, result); cmd.Parameters.AddWithValue(operator, operatorName); cmd.ExecuteNonQuery(); } } } public DataTable QueryByDateRange(DateTime start, DateTime end) { using (var cmd new SQLiteCommand( SELECT * FROM ProductionData WHERE ScanTime BETWEEN start AND end ORDER BY ScanTime DESC, _connection)) { cmd.Parameters.AddWithValue(start, start); cmd.Parameters.AddWithValue(end, end); var adapter new SQLiteDataAdapter(cmd); var dt new DataTable(); adapter.Fill(dt); return dt; } } public void Dispose() _connection?.Dispose(); }1.4 Log4Net配置工业级!-- log4net.config -- log4net !-- 调试日志按日期滚动 -- appender nameDebugAppender typelog4net.Appender.RollingFileAppender file valueLogs\Debug\ / datePattern valueyyyy-MM-dd.log / appendToFile valuetrue / rollingStyle valueDate / staticLogFileName valuefalse / layout typelog4net.Layout.PatternLayout conversionPattern value%date [%thread] %-5level %logger - %message%newline / /layout /appender !-- 错误日志按大小滚动保留10个备份 -- appender nameErrorAppender typelog4net.Appender.RollingFileAppender file valueLogs\Error\error.log / appendToFile valuetrue / rollingStyle valueSize / maxSizeRollBackups value10 / maximumFileSize value5MB / filter typelog4net.Filter.LevelRangeFilter levelMin valueERROR / levelMax valueFATAL / /filter layout typelog4net.Layout.PatternLayout conversionPattern value%date [%thread] %-5level %logger - %message%newline%exception / /layout /appender !-- 扫码数据专用日志 -- appender nameScanAppender typelog4net.Appender.RollingFileAppender file valueLogs\Scan\scan.log / datePattern valueyyyy-MM-dd.log / appendToFile valuetrue / layout typelog4net.Layout.PatternLayout conversionPattern value%date,%message%newline / /layout /appender logger nameScanLogger additivityfalse level valueINFO / appender-ref refScanAppender / /logger root level valueDEBUG / appender-ref refDebugAppender / appender-ref refErrorAppender / /root /log4net二、C# WinForm 西门子S7-1200 PLC Modbus TCP LabVIEW辅助2.1 通信协议选择对比协议适用场景性能复杂度S7 Protocol纯西门子环境高原生优化中Modbus TCP多品牌混合中通用性强低Profinet实时性要求极高极高高2.2 Modbus TCP通信实现NModbus4using Modbus.Device; // NModbus4 NuGet包 public class ModbusPLCClient { private TcpClient _tcpClient; private IModbusMaster _master; private readonly string _ip; private readonly int _port; private readonly byte _slaveId; private Timer _heartbeatTimer; public bool IsConnected _tcpClient?.Connected ?? false; public event Actionbool OnConnectionStatusChanged; public ModbusPLCClient(string ip, int port 502, byte slaveId 1) { _ip ip; _port port; _slaveId slaveId; } public async Taskbool ConnectAsync() { try { _tcpClient new TcpClient(); await _tcpClient.ConnectAsync(_ip, _port); _master ModbusIpMaster.CreateIp(_tcpClient); StartHeartbeat(); OnConnectionStatusChanged?.Invoke(true); return true; } catch (Exception ex) { Log.Error($PLC连接失败: {ex.Message}); return false; } } // 读取保持寄存器对应PLC的DB块映射 public ushort[] ReadHoldingRegisters(ushort startAddress, ushort count) { if (!IsConnected) throw new InvalidOperationException(PLC未连接); return _master.ReadHoldingRegisters(_slaveId, startAddress, count); } // 写入线圈控制输出点 public void WriteCoil(ushort coilAddress, bool value) { if (!IsConnected) throw new InvalidOperationException(PLC未连接); _master.WriteSingleCoil(_slaveId, coilAddress, value); } // 心跳检测每3秒读取一次系统状态字 private void StartHeartbeat() { _heartbeatTimer new Timer(async _ { try { await Task.Run(() ReadHoldingRegisters(0, 1)); } catch { OnConnectionStatusChanged?.Invoke(false); await ReconnectAsync(); } }, null, 3000, 3000); } private async Task ReconnectAsync() { // 指数退避重连策略 int delay 1000; while (!IsConnected) { await Task.Delay(delay); if (await ConnectAsync()) break; delay Math.Min(delay * 2, 30000); // 最大30秒 } } }2.3 LabVIEW辅助调试技巧在开发阶段使用LabVIEW进行协议验证Modbus TCP测试使用LabVIEW的Modbus库快速验证寄存器地址映射数据监控通过LabVIEW前面板实时观察PLC数据变化报文分析利用LabVIEW捕获TCP报文对比C#程序发送的报文格式LabVIEW与C#混合调试流程TIA Portal配置PLC → LabVIEW验证通信 → C#实现业务逻辑 → 对比数据一致性2.4 Excel导出组件NPOIusing NPOI.XSSF.UserModel; using System.Data; public class ExcelExporter { public void ExportProductionData(DataTable data, string filePath) { IWorkbook workbook new XSSFWorkbook(); ISheet sheet workbook.CreateSheet(生产数据); // 创建表头 IRow headerRow sheet.CreateRow(0); for (int i 0; i data.Columns.Count; i) { headerRow.CreateCell(i).SetCellValue(data.Columns[i].ColumnName); } // 填充数据 for (int i 0; i data.Rows.Count; i) { IRow row sheet.CreateRow(i 1); for (int j 0; j data.Columns.Count; j) { row.CreateCell(j).SetCellValue(data.Rows[i][j].ToString()); } } // 自动列宽 for (int i 0; i data.Columns.Count; i) { sheet.AutoSizeColumn(i); } using (var fs new FileStream(filePath, FileMode.Create)) { workbook.Write(fs); } } }三、C# WPF MVVMLight Modbus TCP HTTPS S7NetPlus3.1 MVVMLight框架核心架构View (XAML) ↓ DataContext ViewModel (继承ViewModelBase) ↓ 命令(ICommand) 属性(INotifyPropertyChanged) Model (数据实体) ↓ Service (PLC通信服务/HTTP服务)3.2 完整ViewModel示例using GalaSoft.MvvmLight; using GalaSoft.MvvmLight.Command; using S7.Net; // S7NetPlus库 public class MainViewModel : ViewModelBase { private readonly PlcService _plcService; private readonly HttpService _httpService; // 绑定属性 private bool _isRunning; public bool IsRunning { get _isRunning; set Set(ref _isRunning, value); } private float _temperature; public float Temperature { get _temperature; set Set(ref _temperature, value); } // 命令 public RelayCommand StartCommand { get; private set; } public RelayCommand StopCommand { get; private set; } public RelayCommand UploadDataCommand { get; private set; } public MainViewModel() { _plcService new PlcService(192.168.0.1, CpuType.S71200); _httpService new HttpService(https://api.factory.com); InitializeCommands(); StartPolling(); } private void InitializeCommands() { StartCommand new RelayCommand(async () { await _plcService.WriteBit(DB1.DBX0.0, true); // 启动设备 IsRunning true; }); StopCommand new RelayCommand(async () { await _plcService.WriteBit(DB1.DBX0.0, false); // 停止设备 IsRunning false; }); UploadDataCommand new RelayCommand(async () { var data new { Temperature Temperature, Timestamp DateTime.Now }; await _httpService.PostAsync(/api/production, data); }); } // 定时轮询PLC数据 private void StartPolling() { Task.Run(async () { while (true) { try { Temperature await _plcService.ReadReal(DB1.DBD2); await Task.Delay(500); // 500ms刷新周期 } catch (Exception ex) { // 记录日志继续轮询 Log.Error(ex); } } }); } }3.3 S7NetPlus高级封装using S7.Net; public class PlcService { private Plc _plc; private readonly string _ip; private readonly CpuType _cpuType; private readonly object _lockObj new object(); public bool IsConnected _plc?.IsConnected ?? false; public PlcService(string ip, CpuType cpuType) { _ip ip; _cpuType cpuType; } public async Taskbool ConnectAsync() { try { _plc new Plc(_cpuType, _ip, 0, 1); // Rack0, Slot1(S7-1200) await Task.Run(() _plc.Open()); return true; } catch (Exception ex) { Log.Error($PLC连接失败: {ex.Message}); return false; } } // 读取Real类型浮点数 public async Taskfloat ReadReal(string address) { lock (_lockObj) // 线程安全 { var result _plc.Read(address); return Convert.ToSingle(result); } } // 读取Int类型 public async Taskshort ReadInt(string address) { lock (_lockObj) { return (short)_plc.Read(address); } } // 写入布尔值如DB1.DBX0.0 public async Task WriteBit(string address, bool value) { lock (_lockObj) { _plc.WriteBit(address, value); } } // 批量读取优化减少通信次数 public async Taskbyte[] ReadBytes(int dbNumber, int startByte, int count) { lock (_lockObj) { return _plc.ReadBytes(DataType.DataBlock, dbNumber, startByte, count); } } }3.4 HTTPS通信服务using System.Net.Http; using System.Net.Http.Json; public class HttpService { private readonly HttpClient _httpClient; public HttpService(string baseUrl) { _httpClient new HttpClient { BaseAddress new Uri(baseUrl), Timeout TimeSpan.FromSeconds(30) }; // 配置HTTPS证书验证开发环境可忽略 var handler new HttpClientHandler { ServerCertificateCustomValidationCallback (message, cert, chain, errors) true }; } public async TaskT GetAsyncT(string url) { var response await _httpClient.GetAsync(url); response.EnsureSuccessStatusCode(); return await response.Content.ReadFromJsonAsyncT(); } public async Task PostAsyncT(string url, T data) { var response await _httpClient.PostAsJsonAsync(url, data); response.EnsureSuccessStatusCode(); } }四、C# .NET Core MySQL Web API4.1 技术架构工业物联网方向┌─────────────────────────────────────────┐ │ ASP.NET Core Web API │ │ ┌──────────┐ ┌──────────┐ ┌─────────┐ │ │ │ Controllers│ │ Services │ │ Repos │ │ │ └──────────┘ └──────────┘ └─────────┘ │ │ ┌──────────┐ ┌──────────┐ │ │ │ JWT Auth │ │ EF Core │ │ │ └──────────┘ └──────────┘ │ └─────────────────────────────────────────┘ │ ┌─────────────────────────────────────────┐ │ MySQL Database │ │ (生产数据/设备状态/用户权限) │ └─────────────────────────────────────────┘4.2 完整Web API项目结构IndustrialApi/ ├── Controllers/ │ ├── ProductionController.cs # 生产数据API │ └── DeviceController.cs # 设备管理API ├── Services/ │ ├── IPlcCommunicationService.cs │ └── PlcCommunicationService.cs ├── Repositories/ │ ├── IProductionRepository.cs │ └── ProductionRepository.cs ├── Models/ │ ├── Entities/ # 数据库实体 │ └── DTOs/ # 数据传输对象 ├── Infrastructure/ │ ├── DatabaseContext.cs # DbContext │ └── JwtSettings.cs # JWT配置 └── Program.cs4.3 EF Core MySQL配置// Program.cs using Microsoft.EntityFrameworkCore; using Pomelo.EntityFrameworkCore.MySql.Infrastructure; var builder WebApplication.CreateBuilder(args); // 配置MySQL连接 builder.Services.AddDbContextIndustrialDbContext(options options.UseMySql( builder.Configuration.GetConnectionString(DefaultConnection), new MySqlServerVersion(new Version(8, 0, 21)), mySqlOptions mySqlOptions.EnableRetryOnFailure() )); // 添加JWT认证 builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options { options.TokenValidationParameters new TokenValidationParameters { ValidateIssuer true, ValidateAudience true, ValidateLifetime true, ValidateIssuerSigningKey true, ValidIssuer builder.Configuration[Jwt:Issuer], ValidAudience builder.Configuration[Jwt:Audience], IssuerSigningKey new SymmetricSecurityKey( Encoding.UTF8.GetBytes(builder.Configuration[Jwt:Key])) }; }); // 注册服务 builder.Services.AddScopedIPlcService, PlcService(); builder.Services.AddScopedIProductionRepository, ProductionRepository(); var app builder.Build(); app.Run();4.4 生产数据Controller示例[ApiController] [Route(api/[controller])] [Authorize] // JWT认证 public class ProductionController : ControllerBase { private readonly IProductionRepository _repository; private readonly IPlcService _plcService; [HttpGet(realtime)] public async TaskActionResultProductionDataDto GetRealtimeData() { // 从PLC读取实时数据 var data await _plcService.GetRealtimeDataAsync(); return Ok(data); } [HttpPost(batch)] public async TaskIActionResult UploadBatchData([FromBody] ListProductionDataDto data) { await _repository.InsertBatchAsync(data); return Ok(new { Count data.Count, Message 数据上传成功 }); } [HttpGet(history)] public async TaskActionResultPagedResultProductionDataDto GetHistory( [FromQuery] DateTime start, [FromQuery] DateTime end, [FromQuery] int page 1, [FromQuery] int pageSize 50) { var result await _repository.GetPagedAsync(start, end, page, pageSize); return Ok(result); } }五、关键技术对比与选型建议5.1 技术栈演进路线阶段技术组合适用场景传统工业WinForm SerialPort SQLite单机版产线终端设备联网WinForm Modbus TCP S7多PLC集中监控现代上位机WPF MVVM S7NetPlus复杂交互、数据可视化工业物联网.NET Core Web API MySQL云端数据汇总、远程监控5.2 性能优化要点PLC通信优化使用批量读取代替单次读取减少网络往返心跳间隔建议3-5秒避免PLC CPU过载采用指数退避重连策略1s→2s→4s→30s上限数据库优化SQLite使用连接池PoolingTrueMySQL启用失败重试EnableRetryOnFailure大数据量采用分表或归档策略UI响应优化所有IO操作必须异步async/await使用IProgressT报告进度到UI线程数据绑定使用ObservableCollectionINotifyPropertyChanged
[特殊字符] 工业上位机开发技术栈完整笔记
一、C# WinForm 基恩士扫码枪 串口通信 SQLite Log4Net1.1 技术架构概览模块技术选型作用UI框架WinForms (.NET Framework 4.7)传统工业界面快速开发硬件通信SerialPort 基恩士SDK扫码枪数据采集数据存储SQLite System.Data.SQLite轻量级本地数据库日志系统Log4Net运行日志与故障追踪数据导出NPOI/EPPlusExcel报表生成1.2 基恩士扫码枪SDK集成基恩士(Keyence)扫码枪通常提供两种集成方式方式AUSB HID键盘模式免开发// 窗体级别键盘钩子捕获扫码数据 public partial class ScanForm : Form { private StringBuilder _scanBuffer new StringBuilder(); private DateTime _lastKeyTime; private const int SCAN_THRESHOLD_MS 50; // 扫码枪输入间隔50ms public ScanForm() { InitializeComponent(); this.KeyPreview true; // 捕获全局键盘事件 } protected override void OnKeyPress(KeyPressEventArgs e) { var now DateTime.Now; // 判断是否为扫码枪快速输入人工输入间隔100ms if ((now - _lastKeyTime).TotalMilliseconds 100 _scanBuffer.Length 0) { _scanBuffer.Clear(); } _lastKeyTime now; if (e.KeyChar (char)Keys.Enter) { string barcode _scanBuffer.ToString().Trim(); _scanBuffer.Clear(); ProcessBarcode(barcode); // 处理条码 } else { _scanBuffer.Append(e.KeyChar); } base.OnKeyPress(e); } }方式BSerialPort串口通信模式推荐工业场景using System.IO.Ports; public class KeyenceScanner { private SerialPort _serialPort; private ILog _logger LogManager.GetLogger(typeof(KeyenceScanner)); public event Actionstring OnBarcodeScanned; public bool Connect(string portName, int baudRate 9600) { try { _serialPort new SerialPort { PortName portName, BaudRate baudRate, DataBits 8, StopBits StopBits.One, Parity Parity.None, ReadTimeout 500, WriteTimeout 500 }; _serialPort.DataReceived SerialPort_DataReceived; _serialPort.Open(); _logger.Info($扫码枪已连接: {portName}); return true; } catch (Exception ex) { _logger.Error($扫码枪连接失败: {ex.Message}); return false; } } private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e) { try { string data _serialPort.ReadExisting().Trim(); if (!string.IsNullOrEmpty(data)) { // 跨线程更新UI OnBarcodeScanned?.Invoke(data); _logger.Debug($扫描到条码: {data}); } } catch (Exception ex) { _logger.Error($数据接收异常: {ex.Message}); } } // 发送指令控制扫码枪需参考基恩士指令集 public void TriggerScan() { // 示例发送触发扫描指令 _serialPort.WriteLine(LON); // 开启激光 Thread.Sleep(100); _serialPort.WriteLine(LOFF); // 关闭激光 } }1.3 SQLite数据库操作封装using System.Data.SQLite; public class SQLiteHelper : IDisposable { private SQLiteConnection _connection; private readonly string _connectionString; private static readonly object _lock new object(); public SQLiteHelper(string dbPath) { _connectionString $Data Source{dbPath};Version3;PoolingTrue;Max Pool Size100;; InitializeDatabase(); } private void InitializeDatabase() { if (!File.Exists(_connectionString.Split()[1].Split(;)[0])) { SQLiteConnection.CreateFile(_connectionString.Split()[1].Split(;)[0]); } _connection new SQLiteConnection(_connectionString); _connection.Open(); // 创建生产数据表 ExecuteNonQuery( CREATE TABLE IF NOT EXISTS ProductionData ( Id INTEGER PRIMARY KEY AUTOINCREMENT, Barcode TEXT NOT NULL, ScanTime DATETIME DEFAULT CURRENT_TIMESTAMP, Result TEXT, Operator TEXT )); } public void InsertScanRecord(string barcode, string result, string operatorName) { lock (_lock) // 多线程安全 { using (var cmd new SQLiteCommand( INSERT INTO ProductionData (Barcode, Result, Operator) VALUES (barcode, result, operator), _connection)) { cmd.Parameters.AddWithValue(barcode, barcode); cmd.Parameters.AddWithValue(result, result); cmd.Parameters.AddWithValue(operator, operatorName); cmd.ExecuteNonQuery(); } } } public DataTable QueryByDateRange(DateTime start, DateTime end) { using (var cmd new SQLiteCommand( SELECT * FROM ProductionData WHERE ScanTime BETWEEN start AND end ORDER BY ScanTime DESC, _connection)) { cmd.Parameters.AddWithValue(start, start); cmd.Parameters.AddWithValue(end, end); var adapter new SQLiteDataAdapter(cmd); var dt new DataTable(); adapter.Fill(dt); return dt; } } public void Dispose() _connection?.Dispose(); }1.4 Log4Net配置工业级!-- log4net.config -- log4net !-- 调试日志按日期滚动 -- appender nameDebugAppender typelog4net.Appender.RollingFileAppender file valueLogs\Debug\ / datePattern valueyyyy-MM-dd.log / appendToFile valuetrue / rollingStyle valueDate / staticLogFileName valuefalse / layout typelog4net.Layout.PatternLayout conversionPattern value%date [%thread] %-5level %logger - %message%newline / /layout /appender !-- 错误日志按大小滚动保留10个备份 -- appender nameErrorAppender typelog4net.Appender.RollingFileAppender file valueLogs\Error\error.log / appendToFile valuetrue / rollingStyle valueSize / maxSizeRollBackups value10 / maximumFileSize value5MB / filter typelog4net.Filter.LevelRangeFilter levelMin valueERROR / levelMax valueFATAL / /filter layout typelog4net.Layout.PatternLayout conversionPattern value%date [%thread] %-5level %logger - %message%newline%exception / /layout /appender !-- 扫码数据专用日志 -- appender nameScanAppender typelog4net.Appender.RollingFileAppender file valueLogs\Scan\scan.log / datePattern valueyyyy-MM-dd.log / appendToFile valuetrue / layout typelog4net.Layout.PatternLayout conversionPattern value%date,%message%newline / /layout /appender logger nameScanLogger additivityfalse level valueINFO / appender-ref refScanAppender / /logger root level valueDEBUG / appender-ref refDebugAppender / appender-ref refErrorAppender / /root /log4net二、C# WinForm 西门子S7-1200 PLC Modbus TCP LabVIEW辅助2.1 通信协议选择对比协议适用场景性能复杂度S7 Protocol纯西门子环境高原生优化中Modbus TCP多品牌混合中通用性强低Profinet实时性要求极高极高高2.2 Modbus TCP通信实现NModbus4using Modbus.Device; // NModbus4 NuGet包 public class ModbusPLCClient { private TcpClient _tcpClient; private IModbusMaster _master; private readonly string _ip; private readonly int _port; private readonly byte _slaveId; private Timer _heartbeatTimer; public bool IsConnected _tcpClient?.Connected ?? false; public event Actionbool OnConnectionStatusChanged; public ModbusPLCClient(string ip, int port 502, byte slaveId 1) { _ip ip; _port port; _slaveId slaveId; } public async Taskbool ConnectAsync() { try { _tcpClient new TcpClient(); await _tcpClient.ConnectAsync(_ip, _port); _master ModbusIpMaster.CreateIp(_tcpClient); StartHeartbeat(); OnConnectionStatusChanged?.Invoke(true); return true; } catch (Exception ex) { Log.Error($PLC连接失败: {ex.Message}); return false; } } // 读取保持寄存器对应PLC的DB块映射 public ushort[] ReadHoldingRegisters(ushort startAddress, ushort count) { if (!IsConnected) throw new InvalidOperationException(PLC未连接); return _master.ReadHoldingRegisters(_slaveId, startAddress, count); } // 写入线圈控制输出点 public void WriteCoil(ushort coilAddress, bool value) { if (!IsConnected) throw new InvalidOperationException(PLC未连接); _master.WriteSingleCoil(_slaveId, coilAddress, value); } // 心跳检测每3秒读取一次系统状态字 private void StartHeartbeat() { _heartbeatTimer new Timer(async _ { try { await Task.Run(() ReadHoldingRegisters(0, 1)); } catch { OnConnectionStatusChanged?.Invoke(false); await ReconnectAsync(); } }, null, 3000, 3000); } private async Task ReconnectAsync() { // 指数退避重连策略 int delay 1000; while (!IsConnected) { await Task.Delay(delay); if (await ConnectAsync()) break; delay Math.Min(delay * 2, 30000); // 最大30秒 } } }2.3 LabVIEW辅助调试技巧在开发阶段使用LabVIEW进行协议验证Modbus TCP测试使用LabVIEW的Modbus库快速验证寄存器地址映射数据监控通过LabVIEW前面板实时观察PLC数据变化报文分析利用LabVIEW捕获TCP报文对比C#程序发送的报文格式LabVIEW与C#混合调试流程TIA Portal配置PLC → LabVIEW验证通信 → C#实现业务逻辑 → 对比数据一致性2.4 Excel导出组件NPOIusing NPOI.XSSF.UserModel; using System.Data; public class ExcelExporter { public void ExportProductionData(DataTable data, string filePath) { IWorkbook workbook new XSSFWorkbook(); ISheet sheet workbook.CreateSheet(生产数据); // 创建表头 IRow headerRow sheet.CreateRow(0); for (int i 0; i data.Columns.Count; i) { headerRow.CreateCell(i).SetCellValue(data.Columns[i].ColumnName); } // 填充数据 for (int i 0; i data.Rows.Count; i) { IRow row sheet.CreateRow(i 1); for (int j 0; j data.Columns.Count; j) { row.CreateCell(j).SetCellValue(data.Rows[i][j].ToString()); } } // 自动列宽 for (int i 0; i data.Columns.Count; i) { sheet.AutoSizeColumn(i); } using (var fs new FileStream(filePath, FileMode.Create)) { workbook.Write(fs); } } }三、C# WPF MVVMLight Modbus TCP HTTPS S7NetPlus3.1 MVVMLight框架核心架构View (XAML) ↓ DataContext ViewModel (继承ViewModelBase) ↓ 命令(ICommand) 属性(INotifyPropertyChanged) Model (数据实体) ↓ Service (PLC通信服务/HTTP服务)3.2 完整ViewModel示例using GalaSoft.MvvmLight; using GalaSoft.MvvmLight.Command; using S7.Net; // S7NetPlus库 public class MainViewModel : ViewModelBase { private readonly PlcService _plcService; private readonly HttpService _httpService; // 绑定属性 private bool _isRunning; public bool IsRunning { get _isRunning; set Set(ref _isRunning, value); } private float _temperature; public float Temperature { get _temperature; set Set(ref _temperature, value); } // 命令 public RelayCommand StartCommand { get; private set; } public RelayCommand StopCommand { get; private set; } public RelayCommand UploadDataCommand { get; private set; } public MainViewModel() { _plcService new PlcService(192.168.0.1, CpuType.S71200); _httpService new HttpService(https://api.factory.com); InitializeCommands(); StartPolling(); } private void InitializeCommands() { StartCommand new RelayCommand(async () { await _plcService.WriteBit(DB1.DBX0.0, true); // 启动设备 IsRunning true; }); StopCommand new RelayCommand(async () { await _plcService.WriteBit(DB1.DBX0.0, false); // 停止设备 IsRunning false; }); UploadDataCommand new RelayCommand(async () { var data new { Temperature Temperature, Timestamp DateTime.Now }; await _httpService.PostAsync(/api/production, data); }); } // 定时轮询PLC数据 private void StartPolling() { Task.Run(async () { while (true) { try { Temperature await _plcService.ReadReal(DB1.DBD2); await Task.Delay(500); // 500ms刷新周期 } catch (Exception ex) { // 记录日志继续轮询 Log.Error(ex); } } }); } }3.3 S7NetPlus高级封装using S7.Net; public class PlcService { private Plc _plc; private readonly string _ip; private readonly CpuType _cpuType; private readonly object _lockObj new object(); public bool IsConnected _plc?.IsConnected ?? false; public PlcService(string ip, CpuType cpuType) { _ip ip; _cpuType cpuType; } public async Taskbool ConnectAsync() { try { _plc new Plc(_cpuType, _ip, 0, 1); // Rack0, Slot1(S7-1200) await Task.Run(() _plc.Open()); return true; } catch (Exception ex) { Log.Error($PLC连接失败: {ex.Message}); return false; } } // 读取Real类型浮点数 public async Taskfloat ReadReal(string address) { lock (_lockObj) // 线程安全 { var result _plc.Read(address); return Convert.ToSingle(result); } } // 读取Int类型 public async Taskshort ReadInt(string address) { lock (_lockObj) { return (short)_plc.Read(address); } } // 写入布尔值如DB1.DBX0.0 public async Task WriteBit(string address, bool value) { lock (_lockObj) { _plc.WriteBit(address, value); } } // 批量读取优化减少通信次数 public async Taskbyte[] ReadBytes(int dbNumber, int startByte, int count) { lock (_lockObj) { return _plc.ReadBytes(DataType.DataBlock, dbNumber, startByte, count); } } }3.4 HTTPS通信服务using System.Net.Http; using System.Net.Http.Json; public class HttpService { private readonly HttpClient _httpClient; public HttpService(string baseUrl) { _httpClient new HttpClient { BaseAddress new Uri(baseUrl), Timeout TimeSpan.FromSeconds(30) }; // 配置HTTPS证书验证开发环境可忽略 var handler new HttpClientHandler { ServerCertificateCustomValidationCallback (message, cert, chain, errors) true }; } public async TaskT GetAsyncT(string url) { var response await _httpClient.GetAsync(url); response.EnsureSuccessStatusCode(); return await response.Content.ReadFromJsonAsyncT(); } public async Task PostAsyncT(string url, T data) { var response await _httpClient.PostAsJsonAsync(url, data); response.EnsureSuccessStatusCode(); } }四、C# .NET Core MySQL Web API4.1 技术架构工业物联网方向┌─────────────────────────────────────────┐ │ ASP.NET Core Web API │ │ ┌──────────┐ ┌──────────┐ ┌─────────┐ │ │ │ Controllers│ │ Services │ │ Repos │ │ │ └──────────┘ └──────────┘ └─────────┘ │ │ ┌──────────┐ ┌──────────┐ │ │ │ JWT Auth │ │ EF Core │ │ │ └──────────┘ └──────────┘ │ └─────────────────────────────────────────┘ │ ┌─────────────────────────────────────────┐ │ MySQL Database │ │ (生产数据/设备状态/用户权限) │ └─────────────────────────────────────────┘4.2 完整Web API项目结构IndustrialApi/ ├── Controllers/ │ ├── ProductionController.cs # 生产数据API │ └── DeviceController.cs # 设备管理API ├── Services/ │ ├── IPlcCommunicationService.cs │ └── PlcCommunicationService.cs ├── Repositories/ │ ├── IProductionRepository.cs │ └── ProductionRepository.cs ├── Models/ │ ├── Entities/ # 数据库实体 │ └── DTOs/ # 数据传输对象 ├── Infrastructure/ │ ├── DatabaseContext.cs # DbContext │ └── JwtSettings.cs # JWT配置 └── Program.cs4.3 EF Core MySQL配置// Program.cs using Microsoft.EntityFrameworkCore; using Pomelo.EntityFrameworkCore.MySql.Infrastructure; var builder WebApplication.CreateBuilder(args); // 配置MySQL连接 builder.Services.AddDbContextIndustrialDbContext(options options.UseMySql( builder.Configuration.GetConnectionString(DefaultConnection), new MySqlServerVersion(new Version(8, 0, 21)), mySqlOptions mySqlOptions.EnableRetryOnFailure() )); // 添加JWT认证 builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options { options.TokenValidationParameters new TokenValidationParameters { ValidateIssuer true, ValidateAudience true, ValidateLifetime true, ValidateIssuerSigningKey true, ValidIssuer builder.Configuration[Jwt:Issuer], ValidAudience builder.Configuration[Jwt:Audience], IssuerSigningKey new SymmetricSecurityKey( Encoding.UTF8.GetBytes(builder.Configuration[Jwt:Key])) }; }); // 注册服务 builder.Services.AddScopedIPlcService, PlcService(); builder.Services.AddScopedIProductionRepository, ProductionRepository(); var app builder.Build(); app.Run();4.4 生产数据Controller示例[ApiController] [Route(api/[controller])] [Authorize] // JWT认证 public class ProductionController : ControllerBase { private readonly IProductionRepository _repository; private readonly IPlcService _plcService; [HttpGet(realtime)] public async TaskActionResultProductionDataDto GetRealtimeData() { // 从PLC读取实时数据 var data await _plcService.GetRealtimeDataAsync(); return Ok(data); } [HttpPost(batch)] public async TaskIActionResult UploadBatchData([FromBody] ListProductionDataDto data) { await _repository.InsertBatchAsync(data); return Ok(new { Count data.Count, Message 数据上传成功 }); } [HttpGet(history)] public async TaskActionResultPagedResultProductionDataDto GetHistory( [FromQuery] DateTime start, [FromQuery] DateTime end, [FromQuery] int page 1, [FromQuery] int pageSize 50) { var result await _repository.GetPagedAsync(start, end, page, pageSize); return Ok(result); } }五、关键技术对比与选型建议5.1 技术栈演进路线阶段技术组合适用场景传统工业WinForm SerialPort SQLite单机版产线终端设备联网WinForm Modbus TCP S7多PLC集中监控现代上位机WPF MVVM S7NetPlus复杂交互、数据可视化工业物联网.NET Core Web API MySQL云端数据汇总、远程监控5.2 性能优化要点PLC通信优化使用批量读取代替单次读取减少网络往返心跳间隔建议3-5秒避免PLC CPU过载采用指数退避重连策略1s→2s→4s→30s上限数据库优化SQLite使用连接池PoolingTrueMySQL启用失败重试EnableRetryOnFailure大数据量采用分表或归档策略UI响应优化所有IO操作必须异步async/await使用IProgressT报告进度到UI线程数据绑定使用ObservableCollectionINotifyPropertyChanged