Files
kou_zhaoxielou_shandong/口罩泄露定制款/Modbus/Connect_TSI.cs
2026-01-22 16:06:24 +08:00

530 lines
17 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<byte> indoorBuffer = new List<byte>();
private List<byte> outdoorBuffer = new List<byte>();
private List<byte> bendiBuffer = new List<byte>();
// 元素索引
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}");
}
}
/// <summary>
/// 发送命令帧
/// </summary>
/// <param name="port">串口对象</param>
/// <param name="command">命令字节</param>
/// <param name="data">数据域(可选)</param>
private void SendCommand(SerialPort port, PC_CMD command, byte[] data = null)
{
try
{
if (port == null || !port.IsOpen)
return;
List<byte> frame = new List<byte>();
// 帧头
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}");
}
}
/// <summary>
/// 解析接收到的数据帧
/// </summary>
private void ParseFrame(List<byte> 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}");
}
}
/// <summary>
/// 启动数据采集
/// </summary>
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;
}
}
});
}
}
/// <summary>
/// 停止数据采集
/// </summary>
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);
}
}
/// <summary>
/// 连接串口
/// </summary>
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}");
}
}
/// <summary>
/// 断开串口连接
/// </summary>
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}");
}
}
}
}