wpf 511 封装通信类 半导体协议:SECS

封装SQL

csharp 复制代码
cmd.Parameters.AddWithValue

给 SQL 里的参数赋值

就是把:

时间、压力、流量这些具体数值,塞进 SQL 语句里。

大白话:

把值传进去,填坑。

csharp 复制代码
cmd.ExecuteNonQuery()

执行 SQL 语句

执行 建表 / 插入数据 / 更新数据。

大白话:

真正动手把数据存进数据库。

csharp 复制代码
  var close=connType.GetMethod("Close");
  close.Invoke(conn, null);

通过反射拿到 Close 方法,然后调用它,把数据库连接关掉。

connType.GetMethod("Close")

找到数据库连接里的 关闭方法

close.Invoke(conn, null)

执行这个关闭方法,把连接关掉,释放资源

csharp 复制代码
 try
 {
     if(!File.Exists(_csvPath))
     {
         File.WriteAllText(_csvPath, "Timestamp,Pressure,Flow,TimeValue,Reg4Value\n",Encoding.UTF8);
     }
     var line= $"{DateTime.Now:O},{data.PressureValue},{data.Flow},{data.Time},{data.Reg4Value}\n";
     File.AppendAllText(_csvPath, line, Encoding.UTF8);
 }

核心

这是往 CSV 文件里存数据,没有文件就先创建表头,有就直接追加一行数据。

csharp 复制代码
if (!File.Exists(_csvPath))
//判断 CSV 文件有没有,没有就新建一个
File.WriteAllText(...)
//写表头:时间、压力、流量、时间值、寄存器值
//就是第一行标题。
var line = $"...";
//拼一行数据:当前时间 + 压力 + 流量 + 设备数据
File.AppendAllText(...)
//把这一行追加写到文件里
//不会覆盖,只会往下加。

面试只背这一句(满分)

这段代码是将设备数据保存到 CSV 文件,没有文件就创建表头,有文件就直接追加数据。

完美!就这么简单!

csharp 复制代码
_dbPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, dbFile);

拼接文件路径

封装通信类

private readonly object _lockObj = new object(); // 关键:串口锁

csharp 复制代码
// 定义锁(全局唯一)
private readonly object _lockObj = new object(); 

public void Disconnect()
{
    // 加锁:此时就算定时器线程正在读串口,也得等这个断连操作做完
    lock(_lockObj)
    {
        _cts?.Cancel();
        _serialPort?.Close(); // 关串口时,绝对不会有其他线程在读串口了
        // ...其他释放操作
    }
}

// 读数据的方法也要加锁
private async Task ReadSerialData()
{
    lock(_lockObj)
    {
        // 读串口数据:此时断连操作也进不来,不会中途关串口
        var data = _serialPort.ReadExisting();
    }
}
csharp 复制代码
public bool ConnectTcp(string host, int port)
 {
     //throw new NotImplementedException();
     try
     {
         Disconnect();
         _cts = new CancellationTokenSource();
         //创建 CancellationTokenSource 用于控制后台异步数据读取任务的取消,保证断开连接时能安全停止任务。
         _tcpClient = new TcpClient();
         _tcpClient.Connect(host, port);
         //创建 TCP 客户端,去连接设备。
         
         LastError = null;
         _master = ModbusIpMaster.CreateIp(_tcpClient);
         //创建 Modbus 通讯工具,用来和设备收发数据。
         
         _master.Transport.Retries = 1;
         _master.Transport.WaitToRetryMilliseconds = 50;
         //设置重试次数和超时时间,通讯更稳定。
         _readTimer.Start();
         return true;
     }catch(Exception ex)
     {
         LastError = ex.ToString();
         OnError?.Invoke($"TCP连接失败:{ex.Message}");
         return false;
         //连接失败 → 记录错误,发出错误通知,返回 false。
     }
 }
csharp 复制代码
public bool Connect(string portName,int baudRate)
{
    lock (_lockObj)  // 加锁:防止多线程同时点"连接",避免串口冲突
    {
        // 1. 记录本次连接的串口名/波特率(方便后续重连用)
        _lastPortName= portName;
        _lastBaudRate= baudRate;

        // 2. 先断开旧连接(核心!避免重复连接导致串口被占)
        Disconnect();

        // 3. 重置取消令牌(给新的读取任务用)
        _cts=new CancellationTokenSource();

        try
        {
            // 4. 调用你之前的"智能重试串口连接"方法,连不上直接返回false
            if(!OpenSerial(portName, baudRate))
            {
                return false;
            }
            
            // 5. 创建Modbus RTU通讯对象(串口专用),配置重试参数
            _modbusMaster = ModbusSerialMaster.CreateRtu(_serialPort);
            _modbusMaster.Transport.Retries = 1;          // 读失败重试1次
            _modbusMaster.Transport.WaitToRetryMilliseconds = 50; // 重试间隔50毫秒

            // 6. 启动后台读取任务(_readTask就是这里创建的)
            StartReading();

            // 7. 全部成功,返回true
            return true;
        }
        catch (Exception ex)
        {
            // 8. 连接出错:记录错误、通知界面、AI分析故障
            LastError= ex.ToString();
            OnError?.Invoke($"连接串口发生异常:{ex.Message}");
            _ = AnalyzeExceptionAsync(ex, "Connect");

            // 9. 出错后兜底:断开所有可能的残留连接
            Disconnect();

            // 10. 返回false,告诉调用方连接失败
            return false;
        }
    }
}

_ = AnalyzeExceptionAsync(ex, "Connect");

_ = 只是为了消除编译器警告,不影响功能

csharp 复制代码
 public void Disconnect()
 {
     //throw new NotImplementedException();
     try
     {
         _readTimer?.Stop();
         //停止自动读取数据的定时器
         _cts?.Cancel();
         //安全取消后台异步任务(就是你刚才问的取消令牌)
         _master?.Dispose();
         _master = null;
         //释放 Modbus 通讯工具

         if (_tcpClient != null)
         {
             try { _tcpClient.Close(); } catch { }
             _tcpClient= null;
         }
         //关闭 TCP 连接,清空对象,彻底断开设备

     }
     catch { }
     //出错也不崩溃,保证安全
 }
csharp 复制代码
private void StopTimer()
{
    lock (_lockObj)  // 加个锁:防止多线程乱操作,避免报错
    {
        _readTimer?.Stop();   // 停止定时器 → 不再自动触发读取
        _cts?.Cancel();       // 取消令牌 → 让正在执行的异步读取立刻停止
    }
}
csharp 复制代码
public void Disconnect()
{
    lock(_lockObj)  // 加锁:防止多线程同时操作(比如一边断连、一边读数据),避免报错
    {
        try
        {
            _cts?.Cancel();               // 1. 取消令牌:让正在读数据的异步任务立刻停手
            _modbusMaster?.Dispose();     // 2. 释放Modbus通讯对象
            _modbusMaster= null;          // 3. 清空对象,避免后续误操作
            _serialPort?.Close();         // 4. 关闭串口连接(核心:断开和设备的物理连接)
            _serialPort?.Dispose();       // 5. 释放串口资源(操作系统层面回收)
            _serialPort= null;            // 6. 清空串口对象
        }
        catch { }  // 吞掉所有异常:断连时就算出错,也不影响程序运行
        finally
        {
            // 7. 等后台读取任务结束(最多等50毫秒)
            try { _readTask?.Wait(50); } catch { }
        }
    }
}
csharp 复制代码
public void Disconnect()
{
    lock (_lockObj)  // 1. 上锁!同一时间只能断开,防止线程打架
    {
        try
        {
            // 2. 工长喊停工:工人、监工全部停止干活!
            try { _cts?.Cancel(); } catch { }

            // 3. 【重点优化!】等工人收尾200ms,等监工收尾200ms
            // 给双方足够时间下班,不卡死、不残留
            try { if (_readTask != null) _readTask.Wait(200); } catch { }
            try { if (_heartbeatTask != null) _heartbeatTask.Wait(200); } catch { }

            // 4. 销毁工长,释放资源 → 工长彻底消失
            try { _cts?.Dispose(); } catch { }
            _cts = null;

            // 5. 销毁Modbus通信工具
            try { _modbusMaster?.Dispose(); } catch { }
            _modbusMaster = null;

            // 6. 关闭串口 → 销毁串口 → 彻底释放硬件资源
            try { _serialPort?.Close(); } catch { }
            try { _serialPort?.Dispose(); } catch { }
            _serialPort = null;
        }
        catch { }  // 所有异常都捕获,工业软件绝不崩溃
    }
    finally
    {
        // 7. 最后兜底:工人、监工直接置空,彻底清场
        _readTask = null;
        _heartbeatTask = null;
    }
}

_readTask

_readTask 是你程序里后台读取串口 / Modbus 数据的异步任务对象,简单说就是 "正在干活的读取线程",Wait(50) 就是等这个 "干活线程" 最多 50 毫秒,让它收尾完再彻底断连。

csharp 复制代码
 private bool OpenSerial(string portName, int baudRate)
 {
     //throw new NotImplementedException();
     int attempt = 0;
     Exception lastEx= null;
     while (attempt < Math.Max(1, ReconnectMaxAttempts))
     {
         try
         {
             _serialPort = new SerialPort(portName, baudRate, Parity.None, 8, StopBits.One)
             {
                 ReadTimeout = 1000,
                 WriteTimeout = 1000,
                 Handshake = Handshake.None
             };
             _serialPort.Open();
             return true;
         }
         catch(Exception ex)
         {
             lastEx = ex;
             attempt++;
             int delay=ReconnectBaseDelayMs*(int)Math.Pow(2, attempt-1);
             try { Thread.Sleep(Math.Min(delay, 30000)); } catch { }
         }
         }
     if (lastEx != null) LastError = lastEx.ToString();
     return false;
     }
        

智能重试器

csharp 复制代码
private bool OpenSerial(string portName, int baudRate)
{
    int attempt = 0;          // 记录重试次数(从0开始)
    Exception lastEx= null;   // 记录最后一次失败的错误原因

    // 循环重试:最多试 ReconnectMaxAttempts 次(最少试1次)
    while (attempt < Math.Max(1, ReconnectMaxAttempts))
    {
        try
        {
            // 1. 创建串口对象,配置参数(和设备匹配:无校验、8位数据位、1位停止位)
            _serialPort = new SerialPort(portName, baudRate, Parity.None, 8, StopBits.One)
            {
                ReadTimeout = 1000,  // 读串口超时1秒
                WriteTimeout = 1000, // 写串口超时1秒
                Handshake = Handshake.None // 无握手协议(串口通用配置)
            };
            // 2. 打开串口
            _serialPort.Open();
            // 3. 成功:直接返回true
            return true;
        }
        catch(Exception ex)
        {
            // 失败:记录错误、重试次数+1
            lastEx = ex;
            attempt++;

            // 核心:指数退避延迟(重试间隔越来越久,避免频繁重试占资源)
            // 第1次重试等 ReconnectBaseDelayMs 毫秒,第2次等 2倍,第3次等4倍...
            int delay=ReconnectBaseDelayMs*(int)Math.Pow(2, attempt-1);
            
            // 延迟重试(最多等30秒,避免无限等)
            try { Thread.Sleep(Math.Min(delay, 30000)); } catch { }
        }
    }

    // 所有重试都失败:记录最后一次错误,返回false
    if (lastEx != null) LastError = lastEx.ToString();
    return false;
}
        

心跳检测

csharp 复制代码
private void StartReading()
{
    // 加锁保证线程安全,避免多线程同时启动任务
    lock (_lockObj)
    {
        // 🔴 核心修复1:防重复启动(原逻辑写反,导致任务永远启动不了)
        // 已有读取任务在运行 → 直接返回,不重复创建
        if (_readTask != null && !_readTask.IsCompleted && !_readTask.IsCanceled && !_readTask.IsFaulted)
        {
            return;
        }

        // 🔴 核心修复2:先清理旧令牌/任务,再创建新的(避免残留取消信号)
        // 取消旧的令牌(如果有)
        _cts?.Cancel();
        // 释放旧令牌资源
        _cts?.Dispose();
        // 创建新的取消令牌(给新任务用)
        _cts = new CancellationTokenSource();

        // 启动Modbus数据读取循环(后台线程,不卡主线程)
        _readTask = Task.Run(() => ReadLoopAsync(_cts.Token));

        // 🔴 优化:完善心跳任务启动逻辑(覆盖任务完成/异常场景)
        // 开启心跳 + 心跳任务未运行 → 启动心跳循环
        if (HeartbeatEnabled && (_heartbeatTask == null || _heartbeatTask.IsCompleted || _heartbeatTask.IsCanceled || _heartbeatTask.IsFaulted))
        {
            // 先取消旧心跳任务(如果有)
            _heartbeatTask?.Wait(50); // 等旧任务收尾(最多50ms)
            // 启动新的心跳循环
            _heartbeatTask = Task.Run(() => HeartbeatLoopAsync(_cts.Token));
        }
    }
}

半导体协议:secs

SECS 是半导体设备的 "Modbus",但更复杂、更专业,专门解决半导体产线设备与 MES/CIM 系统的标准化通信问题。

一、核心定义与架构

SECS:Semiconductor Equipment Communication Standard(半导体设备通信标准),由SEMI制定

完整名称:SECS/GEM(Generic Equipment Model,通用设备模型),包含两大核心:

SECS-I/HSMS:传输层(串口 / 以太网),类似 Modbus 的 RTU/TCP

SECS-II:应用层(消息格式),定义数据如何组织

GEM:设备行为规范(状态机、事件报告、远程控制等),类似设备的 "操作手册
一句话:Modbus 是 "遥控器",SECS 是 "智能中控系统",专为半导体高精度、高自动化场景设计。

三、核心概念(面试必背)

SxFy 消息标识:

S=Stream(流号),F=Function(功能号),如S1F1(设备身份请求)、S1F14(身份响应)、S2F1(数据采集)

GEM 状态机:

通信状态:Not Communicating → Communicating → Selected

控制状态:Local → Remote → Online(只有Online Remote才能远程启停、下发配方)

事件报告机制:

设备主动上报关键状态(如报警、加工完成),上位机订阅即可接收,无需轮询

相关推荐
lingxiao168881 小时前
WPF数据采集和监控(Industrial)
wpf
雨浓YN2 小时前
GKTGD 工业监控系统-02MySQL 数据库技术文档(类库:NET8_SQLData)
数据库·wpf
雨浓YN3 小时前
GKTGD 工业监控系统-03SQLite 数据库技术文档(类库:NET8_SQLData)
数据库·wpf
deokoo3 小时前
.NET WPF 工程离线迁移完整指南:告别“包降级”与assets文件缺失
wpf
雨浓YN4 小时前
GKTGD 工业监控系统-04MySQL 与 SQLite 数据库对比(类库:NET8_SQLData)
数据库·sqlite·wpf
Bofu-18 小时前
【内存测试】06-WPF 读取 SMBIOS 实现内存规格自动检测
wpf·p/invoke·windows api·smbios·内存检测·dimm·硬件信息读取
Bofu-21 小时前
【Storage存储测试】07-WPF 通过 WMI + NVMe SMART 实现 SSD 规格自动验证
wpf·nvme·wmi·smart·ssd检测
Bofu-1 天前
【键盘测试】05-WPF 可视化键盘布局配置 + 全局钩子按键检测实战
wpf·键盘测试·全局键盘钩子·scancode·组合键检测
bugcome_com1 天前
WPF 路径动画完全指南:自绘制控件实战
wpf