This commit is contained in:
xyy
2026-01-22 16:06:24 +08:00
parent b0d77b4292
commit e88d0aed75
6 changed files with 864 additions and 501 deletions

View File

@@ -3,6 +3,7 @@ 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;
@@ -13,115 +14,517 @@ namespace 口罩泄露定制款.Modbus
private SerialPort sp_Indoor;
private SerialPort sp_Outdoor;
private SerialPort sp_bendiPort;
static string _indoor_data = "0.00";
static string _outdoor_data = "0.00";
static string sp_bendi = "0.00";
public string Indoor_Data { get { return _indoor_data; } set { _indoor_data = value; } }
public string Outdoor_Data { get { return _outdoor_data; } set { _outdoor_data = value; } }
public string sp_bendiData { get { return sp_bendi; } set { sp_bendi = value; } }
// 协议相关常量
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, 9600, Parity.None, 8, StopBits.One);
// 订阅DataReceived事件可选用于接收数据
sp_Indoor.DataReceived += new SerialDataReceivedEventHandler(Sp_Indoor_DataReceived);
sp_Outdoor = new SerialPort(ComPort_TSI_Outdoor, 9600, Parity.None, 8, StopBits.One);
sp_Outdoor.DataReceived += new SerialDataReceivedEventHandler(Sp_Outdoor_DataReceived);
// 初始化串口,使用协议规定的参数
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
sp_bendiPort = new SerialPort(ComPort_TSI_Outdoor, 9600, Parity.None, 8, StopBits.One);
sp_bendiPort.DataReceived += new SerialDataReceivedEventHandler(Sp_bendi_DataReceived);
// 订阅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);
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)
{
Indoor_Data = sp_Indoor.ReadExisting().Trim();
sp_bendiData = Indoor_Data;
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)
{
Outdoor_Data = sp_Outdoor.ReadExisting().Trim();
}
private void Sp_bendi_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
sp_bendiData = sp_bendiPort.ReadExisting().Trim();
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_Outdoor.IsOpen)
sp_Indoor.WriteLine("X");
}
public void Start_Outdoor()
{
if (sp_Outdoor.IsOpen)
sp_Outdoor.WriteLine("X");
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.IsOpen)
sp_bendiPort.WriteLine("X");
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_Outdoor.IsOpen)
sp_Indoor.WriteLine("Z");
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.IsOpen)
sp_Outdoor.WriteLine("Z");
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.IsOpen)
sp_bendiPort.WriteLine("Z");
if (sp_bendiPort != null && sp_bendiPort.IsOpen)
{
SendCommand(sp_bendiPort, PC_CMD.PC_CMD_DISCONNECT);
Thread.Sleep(100);
}
}
/// <summary>
/// 连接串口
/// </summary>
public void Connect()
{
sp_Indoor.Open();
sp_Outdoor.Open();
//sp_bendiPort.Open();
//if (sp_Indoor != null && sp_Outdoor != null)
//{
// if (!sp_Indoor.IsOpen)
// {
// sp_Indoor.Open();
// }
// if (!sp_Outdoor.IsOpen)
// {
// sp_Outdoor.Open();
// }
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()
{
if (sp_Indoor != null && sp_Outdoor != null)
try
{
if (sp_Indoor.IsOpen)
Stop_Indoor();
Stop_Outdoor();
Stop_bendidoor();
if (sp_Indoor != null && sp_Indoor.IsOpen)
{
sp_Indoor.Close();
}
if (sp_Outdoor.IsOpen)
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}");
}
}
}
}
}