using System; using System.Collections.Generic; using System.IO.Ports; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace 口罩泄露定制款.Modbus { public class Connect_TSI { private SerialPort sp_Indoor; private SerialPort sp_Outdoor; private SerialPort sp_bendiPort; // 协议相关常量 private const byte FRAME_HEADER = 0x15; private const byte FRAME_TAIL = 0x16; private const int BAUD_RATE = 115200; private const int DATA_BITS = 8; private const Parity PARITY = Parity.None; private const StopBits STOP_BITS = StopBits.One; // 命令枚举 private enum PC_CMD : byte { PC_CMD_RESPOND = 0x00, PC_CMD_CONNECT, PC_CMD_DISCONNECT, PC_CMD_GET_DATA, PC_CMD_GET_GENERAL, PC_CMD_SET_GENERAL, PC_CMD_RESET, PC_CMD_GET_MAINTAIN, PC_CMD_SET_MAINTAIN, PC_CMD_GET_MEAS_SETTING, PC_CMD_SET_MEAS_SETTING, PC_CMD_CTRL_FIRE, PC_CMD_DONGLE_CONNECT, PC_CMD_GET_SELECTED_CURVE_PART1, PC_CMD_GET_SELECTED_CURVE_PART2, PC_CMD_GET_SELECTED_CURVE_PART3, PC_CMD_GET_RECORD_NUM, PC_CMD_GET_RECORD } // 响应结果枚举 private enum RESPOND_RESULT : byte { RESPOND_RESULT_SUCCESS = 0x00, RESPOND_RESULT_FAIL, RESPOND_RESULT_BUSY } // 数据结构 private struct PC_DATA { public byte real_param_sta; // 每个元素的真实使能情况,每1bit对应一个元素 public byte disp_param_sta; // 每个元素的显示使能情况,每1bit对应一个元素 public byte unit; // 浓度单位 public byte is_fire; // 是否已经点着火了 public ushort[] energys; // 每一个元素的能量值 } // 当前数据 private float _indoor_data = 0.0f; private float _outdoor_data = 0.0f; private float _bendi_data = 0.0f; // 接收缓冲区 private List indoorBuffer = new List(); private List outdoorBuffer = new List(); private List bendiBuffer = new List(); // 元素索引 private enum ELEMENT_INDEX : int { PARAM_NA = 0, PARAM_K, PARAM_LI, PARAM_CA, PARAM_BA, PARAM_MAX_NUM = 5 } public float Indoor_Data { get { return _indoor_data; } } public float Outdoor_Data { get { return _outdoor_data; } } public float Bend_Data { get { return _bendi_data; } } public Connect_TSI(string ComPort_TSI_Indoor, string ComPort_TSI_Outdoor) { try { // 初始化串口,使用协议规定的参数 sp_Indoor = new SerialPort(ComPort_TSI_Indoor, BAUD_RATE, PARITY, DATA_BITS, STOP_BITS); sp_Outdoor = new SerialPort(ComPort_TSI_Outdoor, BAUD_RATE, PARITY, DATA_BITS, STOP_BITS); sp_bendiPort = new SerialPort("COM8", BAUD_RATE, PARITY, DATA_BITS, STOP_BITS); // 假设本底使用COM8 // 订阅DataReceived事件 sp_Indoor.DataReceived += new SerialDataReceivedEventHandler(Sp_Indoor_DataReceived); sp_Outdoor.DataReceived += new SerialDataReceivedEventHandler(Sp_Outdoor_DataReceived); sp_bendiPort.DataReceived += new SerialDataReceivedEventHandler(Sp_Bendi_DataReceived); // 设置读取超时 sp_Indoor.ReadTimeout = 1000; sp_Outdoor.ReadTimeout = 1000; sp_bendiPort.ReadTimeout = 1000; } catch (Exception ex) { MessageBox.Show($"初始化串口失败: {ex.Message}"); } } /// /// 发送命令帧 /// /// 串口对象 /// 命令字节 /// 数据域(可选) private void SendCommand(SerialPort port, PC_CMD command, byte[] data = null) { try { if (port == null || !port.IsOpen) return; List frame = new List(); // 帧头 frame.Add(FRAME_HEADER); // 数据域长度 byte dataLength = (byte)((data != null) ? data.Length : 0); frame.Add(dataLength); // 命令字节(数据域的第一个字节) frame.Add((byte)command); // 如果有额外的数据,添加到数据域 if (data != null && data.Length > 0) { frame.AddRange(data); } // 计算校验和(对数据域的所有字节累加求和,取低8位) byte checksum = 0; for (int i = 2; i < frame.Count; i++) // 从命令字节开始计算 { checksum += frame[i]; } frame.Add(checksum); // 帧尾 frame.Add(FRAME_TAIL); // 发送帧 port.Write(frame.ToArray(), 0, frame.Count); } catch (Exception ex) { Console.WriteLine($"发送命令失败: {ex.Message}"); } } /// /// 解析接收到的数据帧 /// private void ParseFrame(List buffer, ref float dataStore, string portName) { try { // 查找帧头 int headerIndex = buffer.IndexOf(FRAME_HEADER); if (headerIndex < 0) return; // 移除帧头之前的数据 if (headerIndex > 0) { buffer.RemoveRange(0, headerIndex); } // 检查是否有足够的数据读取长度字段 if (buffer.Count < 2) return; byte dataLength = buffer[1]; // 检查是否收到完整帧(帧头1 + 长度1 + 数据域 + 校验和1 + 帧尾1) int frameLength = 2 + dataLength + 2; if (buffer.Count < frameLength) return; // 验证帧尾 if (buffer[frameLength - 1] != FRAME_TAIL) { // 帧尾错误,移除帧头继续查找 buffer.RemoveAt(0); return; } // 提取完整帧 byte[] frame = buffer.GetRange(0, frameLength).ToArray(); // 验证校验和 byte checksum = 0; for (int i = 2; i < 2 + dataLength; i++) { checksum += frame[i]; } if (checksum != frame[frameLength - 2]) { // 校验和错误 buffer.RemoveRange(0, frameLength); return; } // 解析响应 if (frame[2] == (byte)PC_CMD.PC_CMD_RESPOND) // 响应命令 { byte to_cmd = frame[3]; byte result = frame[4]; byte dat_len = frame[5]; if (result == (byte)RESPOND_RESULT.RESPOND_RESULT_SUCCESS) { if (to_cmd == (byte)PC_CMD.PC_CMD_GET_DATA && dat_len >= 2 + 5 * 2) // 至少包含header + 5个元素的能量值 { // 解析PC_DATA结构 PC_DATA pcData = new PC_DATA(); pcData.real_param_sta = frame[6]; pcData.disp_param_sta = frame[7]; pcData.unit = frame[8]; pcData.is_fire = frame[9]; // 读取5个元素的能量值(每个2字节) pcData.energys = new ushort[5]; int offset = 10; for (int i = 0; i < 5; i++) { pcData.energys[i] = (ushort)(frame[offset] + (frame[offset + 1] << 8)); offset += 2; } // 这里我们取第一个元素(PARAM_NA)的能量值作为浓度值 // 根据实际需求,可以选择不同的元素 float energyValue = pcData.energys[(int)ELEMENT_INDEX.PARAM_NA]; // 转换为浓度值(这里需要根据实际校准曲线转换,暂时直接使用能量值) // 实际应用中需要调用校准曲线进行转换 dataStore = energyValue; Console.WriteLine($"{portName} 收到数据: 能量值={energyValue}, 浓度={dataStore:F2}"); } } } // 移除已处理的帧 buffer.RemoveRange(0, frameLength); } catch (Exception ex) { Console.WriteLine($"解析数据帧失败: {ex.Message}"); if (buffer.Count > 0) buffer.RemoveAt(0); // 移除错误数据 } } // 室内数据接收处理 private void Sp_Indoor_DataReceived(object sender, SerialDataReceivedEventArgs e) { try { if (sp_Indoor == null || !sp_Indoor.IsOpen) return; int bytesToRead = sp_Indoor.BytesToRead; if (bytesToRead <= 0) return; byte[] buffer = new byte[bytesToRead]; sp_Indoor.Read(buffer, 0, bytesToRead); indoorBuffer.AddRange(buffer); // 尝试解析帧 ParseFrame(indoorBuffer, ref _indoor_data, "室内光度计"); } catch (Exception ex) { Console.WriteLine($"室内数据接收异常: {ex.Message}"); } } // 室外数据接收处理 private void Sp_Outdoor_DataReceived(object sender, SerialDataReceivedEventArgs e) { try { if (sp_Outdoor == null || !sp_Outdoor.IsOpen) return; int bytesToRead = sp_Outdoor.BytesToRead; if (bytesToRead <= 0) return; byte[] buffer = new byte[bytesToRead]; sp_Outdoor.Read(buffer, 0, bytesToRead); outdoorBuffer.AddRange(buffer); // 尝试解析帧 ParseFrame(outdoorBuffer, ref _outdoor_data, "室外光度计"); } catch (Exception ex) { Console.WriteLine($"室外数据接收异常: {ex.Message}"); } } // 本底数据接收处理 private void Sp_Bendi_DataReceived(object sender, SerialDataReceivedEventArgs e) { try { if (sp_bendiPort == null || !sp_bendiPort.IsOpen) return; int bytesToRead = sp_bendiPort.BytesToRead; if (bytesToRead <= 0) return; byte[] buffer = new byte[bytesToRead]; sp_bendiPort.Read(buffer, 0, bytesToRead); bendiBuffer.AddRange(buffer); // 尝试解析帧 ParseFrame(bendiBuffer, ref _bendi_data, "本底光度计"); } catch (Exception ex) { Console.WriteLine($"本底数据接收异常: {ex.Message}"); } } /// /// 启动数据采集 /// public void Start_Indoor() { if (sp_Indoor != null && sp_Indoor.IsOpen) { // 发送连接命令 SendCommand(sp_Indoor, PC_CMD.PC_CMD_CONNECT); Thread.Sleep(100); // 等待响应 // 开始定时获取数据 Task.Run(() => { while (sp_Indoor.IsOpen) { try { SendCommand(sp_Indoor, PC_CMD.PC_CMD_GET_DATA); Thread.Sleep(500); // 每500ms获取一次数据 } catch { break; } } }); } } public void Start_Outdoor() { if (sp_Outdoor != null && sp_Outdoor.IsOpen) { // 发送连接命令 SendCommand(sp_Outdoor, PC_CMD.PC_CMD_CONNECT); Thread.Sleep(100); // 等待响应 // 开始定时获取数据 Task.Run(() => { while (sp_Outdoor.IsOpen) { try { SendCommand(sp_Outdoor, PC_CMD.PC_CMD_GET_DATA); Thread.Sleep(500); // 每500ms获取一次数据 } catch { break; } } }); } } public void Start_bendidoor() { if (sp_bendiPort != null && sp_bendiPort.IsOpen) { // 发送连接命令 SendCommand(sp_bendiPort, PC_CMD.PC_CMD_CONNECT); Thread.Sleep(100); // 等待响应 // 开始定时获取数据 Task.Run(() => { while (sp_bendiPort.IsOpen) { try { SendCommand(sp_bendiPort, PC_CMD.PC_CMD_GET_DATA); Thread.Sleep(500); // 每500ms获取一次数据 } catch { break; } } }); } } /// /// 停止数据采集 /// public void Stop_Indoor() { if (sp_Indoor != null && sp_Indoor.IsOpen) { SendCommand(sp_Indoor, PC_CMD.PC_CMD_DISCONNECT); Thread.Sleep(100); } } public void Stop_Outdoor() { if (sp_Outdoor != null && sp_Outdoor.IsOpen) { SendCommand(sp_Outdoor, PC_CMD.PC_CMD_DISCONNECT); Thread.Sleep(100); } } public void Stop_bendidoor() { if (sp_bendiPort != null && sp_bendiPort.IsOpen) { SendCommand(sp_bendiPort, PC_CMD.PC_CMD_DISCONNECT); Thread.Sleep(100); } } /// /// 连接串口 /// public void Connect() { try { if (sp_Indoor != null && !sp_Indoor.IsOpen) { sp_Indoor.Open(); Console.WriteLine("室内光度计串口已打开"); } if (sp_Outdoor != null && !sp_Outdoor.IsOpen) { sp_Outdoor.Open(); Console.WriteLine("室外光度计串口已打开"); } if (sp_bendiPort != null && !sp_bendiPort.IsOpen) { sp_bendiPort.Open(); Console.WriteLine("本底光度计串口已打开"); } } catch (Exception ex) { MessageBox.Show($"打开串口失败: {ex.Message}"); } } /// /// 断开串口连接 /// public void Disconnect() { try { Stop_Indoor(); Stop_Outdoor(); Stop_bendidoor(); if (sp_Indoor != null && sp_Indoor.IsOpen) { sp_Indoor.Close(); } if (sp_Outdoor != null && sp_Outdoor.IsOpen) { sp_Outdoor.Close(); } if (sp_bendiPort != null && sp_bendiPort.IsOpen) { sp_bendiPort.Close(); } Console.WriteLine("所有串口已关闭"); } catch (Exception ex) { Console.WriteLine($"关闭串口失败: {ex.Message}"); } } } }