Files
kou_zhaoxielou_shandong/口罩泄露定制款/Modbus/Connect_TSI.cs

530 lines
17 KiB
C#
Raw Normal View History

2026-01-16 20:53:33 +08:00
using System;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Text;
2026-01-22 16:06:24 +08:00
using System.Threading;
2026-01-16 20:53:33 +08:00
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;
2026-01-22 16:06:24 +08:00
// 协议相关常量
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; } }
2026-01-16 20:53:33 +08:00
public Connect_TSI(string ComPort_TSI_Indoor, string ComPort_TSI_Outdoor)
{
try
{
2026-01-22 16:06:24 +08:00
// 初始化串口,使用协议规定的参数
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事件
2026-01-16 20:53:33 +08:00
sp_Indoor.DataReceived += new SerialDataReceivedEventHandler(Sp_Indoor_DataReceived);
sp_Outdoor.DataReceived += new SerialDataReceivedEventHandler(Sp_Outdoor_DataReceived);
2026-01-22 16:06:24 +08:00
sp_bendiPort.DataReceived += new SerialDataReceivedEventHandler(Sp_Bendi_DataReceived);
2026-01-16 20:53:33 +08:00
2026-01-22 16:06:24 +08:00
// 设置读取超时
sp_Indoor.ReadTimeout = 1000;
sp_Outdoor.ReadTimeout = 1000;
sp_bendiPort.ReadTimeout = 1000;
2026-01-16 20:53:33 +08:00
}
catch (Exception ex)
{
2026-01-22 16:06:24 +08:00
MessageBox.Show($"初始化串口失败: {ex.Message}");
2026-01-16 20:53:33 +08:00
}
}
2026-01-22 16:06:24 +08:00
/// <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); // 移除错误数据
}
}
// 室内数据接收处理
2026-01-16 20:53:33 +08:00
private void Sp_Indoor_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
2026-01-22 16:06:24 +08:00
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}");
}
2026-01-16 20:53:33 +08:00
}
2026-01-22 16:06:24 +08:00
// 室外数据接收处理
2026-01-16 20:53:33 +08:00
private void Sp_Outdoor_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
2026-01-22 16:06:24 +08:00
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}");
}
2026-01-16 20:53:33 +08:00
}
2026-01-22 16:06:24 +08:00
// 本底数据接收处理
private void Sp_Bendi_DataReceived(object sender, SerialDataReceivedEventArgs e)
2026-01-16 20:53:33 +08:00
{
2026-01-22 16:06:24 +08:00
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}");
}
2026-01-16 20:53:33 +08:00
}
2026-01-22 16:06:24 +08:00
/// <summary>
/// 启动数据采集
/// </summary>
2026-01-16 20:53:33 +08:00
public void Start_Indoor()
{
2026-01-22 16:06:24 +08:00
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;
}
}
});
}
2026-01-16 20:53:33 +08:00
}
2026-01-22 16:06:24 +08:00
2026-01-16 20:53:33 +08:00
public void Start_Outdoor()
{
2026-01-22 16:06:24 +08:00
if (sp_Outdoor != null && sp_Outdoor.IsOpen)
{
// 发送连接命令
SendCommand(sp_Outdoor, PC_CMD.PC_CMD_CONNECT);
Thread.Sleep(100); // 等待响应
2026-01-16 20:53:33 +08:00
2026-01-22 16:06:24 +08:00
// 开始定时获取数据
Task.Run(() =>
{
while (sp_Outdoor.IsOpen)
{
try
{
SendCommand(sp_Outdoor, PC_CMD.PC_CMD_GET_DATA);
Thread.Sleep(500); // 每500ms获取一次数据
}
catch
{
break;
}
}
});
}
}
2026-01-16 20:53:33 +08:00
public void Start_bendidoor()
{
2026-01-22 16:06:24 +08:00
if (sp_bendiPort != null && sp_bendiPort.IsOpen)
{
// 发送连接命令
SendCommand(sp_bendiPort, PC_CMD.PC_CMD_CONNECT);
Thread.Sleep(100); // 等待响应
2026-01-16 20:53:33 +08:00
2026-01-22 16:06:24 +08:00
// 开始定时获取数据
Task.Run(() =>
{
while (sp_bendiPort.IsOpen)
{
try
{
SendCommand(sp_bendiPort, PC_CMD.PC_CMD_GET_DATA);
Thread.Sleep(500); // 每500ms获取一次数据
}
catch
{
break;
}
}
});
}
}
2026-01-16 20:53:33 +08:00
2026-01-22 16:06:24 +08:00
/// <summary>
/// 停止数据采集
/// </summary>
2026-01-16 20:53:33 +08:00
public void Stop_Indoor()
{
2026-01-22 16:06:24 +08:00
if (sp_Indoor != null && sp_Indoor.IsOpen)
{
SendCommand(sp_Indoor, PC_CMD.PC_CMD_DISCONNECT);
Thread.Sleep(100);
}
2026-01-16 20:53:33 +08:00
}
2026-01-22 16:06:24 +08:00
2026-01-16 20:53:33 +08:00
public void Stop_Outdoor()
{
2026-01-22 16:06:24 +08:00
if (sp_Outdoor != null && sp_Outdoor.IsOpen)
{
SendCommand(sp_Outdoor, PC_CMD.PC_CMD_DISCONNECT);
Thread.Sleep(100);
}
2026-01-16 20:53:33 +08:00
}
2026-01-22 16:06:24 +08:00
2026-01-16 20:53:33 +08:00
public void Stop_bendidoor()
{
2026-01-22 16:06:24 +08:00
if (sp_bendiPort != null && sp_bendiPort.IsOpen)
{
SendCommand(sp_bendiPort, PC_CMD.PC_CMD_DISCONNECT);
Thread.Sleep(100);
}
2026-01-16 20:53:33 +08:00
}
2026-01-22 16:06:24 +08:00
/// <summary>
/// 连接串口
/// </summary>
2026-01-16 20:53:33 +08:00
public void Connect()
{
2026-01-22 16:06:24 +08:00
try
{
if (sp_Indoor != null && !sp_Indoor.IsOpen)
{
sp_Indoor.Open();
Console.WriteLine("室内光度计串口已打开");
}
2026-01-16 20:53:33 +08:00
2026-01-22 16:06:24 +08:00
if (sp_Outdoor != null && !sp_Outdoor.IsOpen)
{
sp_Outdoor.Open();
Console.WriteLine("室外光度计串口已打开");
}
2026-01-16 20:53:33 +08:00
2026-01-22 16:06:24 +08:00
if (sp_bendiPort != null && !sp_bendiPort.IsOpen)
{
sp_bendiPort.Open();
Console.WriteLine("本底光度计串口已打开");
}
}
catch (Exception ex)
{
MessageBox.Show($"打开串口失败: {ex.Message}");
}
2026-01-16 20:53:33 +08:00
}
2026-01-22 16:06:24 +08:00
/// <summary>
/// 断开串口连接
/// </summary>
2026-01-16 20:53:33 +08:00
public void Disconnect()
{
2026-01-22 16:06:24 +08:00
try
2026-01-16 20:53:33 +08:00
{
2026-01-22 16:06:24 +08:00
Stop_Indoor();
Stop_Outdoor();
Stop_bendidoor();
if (sp_Indoor != null && sp_Indoor.IsOpen)
2026-01-16 20:53:33 +08:00
{
sp_Indoor.Close();
}
2026-01-22 16:06:24 +08:00
if (sp_Outdoor != null && sp_Outdoor.IsOpen)
2026-01-16 20:53:33 +08:00
{
sp_Outdoor.Close();
}
2026-01-22 16:06:24 +08:00
if (sp_bendiPort != null && sp_bendiPort.IsOpen)
{
sp_bendiPort.Close();
}
Console.WriteLine("所有串口已关闭");
}
catch (Exception ex)
{
Console.WriteLine($"关闭串口失败: {ex.Message}");
2026-01-16 20:53:33 +08:00
}
}
}
2026-01-22 16:06:24 +08:00
}