C#应用 - 实现SECS协议

前言

本文部分实现了HSMS,SECS-II协议。只关注协议核心部分,用尽量少的代码写一个简化版的SECS端点,能够与第三方SECS工具进行通信。

1,SECS标准介绍

简单介绍一下SECS标准,此处我们只关注HSMS和SECS-II(其中E5的意思是SEMI标准里编号为E5的标准)。

  • HSMS(High-Speed SECS Message Services):属于传输层,在TCP/IP协议基础上做封装
  • SECS-II(SEMI Equipment Communications Standard II):属于应用层,在HSMS协议基础上做封装。

本文重点在于协议的代码实现,介绍部分只是列出了协议的关键部分,如果你对协议并不熟悉,建议参考官方文档。

2,HSMS协议

2.1 基本概念

HSMS连接分4种状态:

  • NOT CONNECTED:还没有建立任何TCP连接
  • CONNECTED:已经建立了TCP连接。此状态有两个子状态,NOT SELECTED 和 SELECTED
  • NOT SELECTED:还没有建立HSMS会话
  • SELECTED:建立了HSMS会话,此状态下可以正式通信,交换数据消息(Data Message)

HSMS分两种模式:

  • Passive:被动模式,内部封装TcpListener,监听端口
  • Active:主动模式,内部封装TcpClient,主动发起连接

HSMS分两种消息:

  • Control Message:控制消息,用于管理TCP/IP连接上的会话状态,总共有8种
    • Select.req / Select.rsp:建立HSMS会话
    • Linktest.req / Linktest.rsp:检测会话存活,作用类似心跳
    • Deselect.req / Deselect.rsp / Reject.req / Separate.req:简化版不管这些
  • Data Message:数据消息,携带实际业务数据,属于SECS-II的范畴

HSMS消息顺序:

  • Primary message:主动发送的消息
  • Reply message :对Primary message的回复,不一定需要

Passive模式状态转移过程:

  • 从NOT CONNECTED到CONNECTED:监听端口,有Active端(TcpClient)连接到此端口
  • 从CONNECTED到SELECTED:收到Active端(TcpClient)发送的Select.req,给Active端(TcpClient)回复Select.rsp

Active模式状态转移过程:

  • 从NOT CONNECTED到CONNECTED:连接到Passive端(TcpListener)
  • 从CONNECTED到SELECTED:向Passive端(TcpListener)发送Select.req,收到Passive端(TcpListener)回复的Select.rsp

超时时间(所有超时时间简化版都不管):

  • T3:Reply Timeout,数据消息回复超时,等待数据消息回复的最长时间
  • T5:Connect Separation Time,连接超时,两个连接请求之间的最小时间间隔
  • T6:Control Transactions Timeout,控制消息超时,等待控制消息回复的最长时间
  • T7:Not Selected Timeout,未选择超时,CONNECTED后没有转移到SELECTED的超时时间

2.2 协议格式

  • 消息整体格式如下表
字节数 描述
4 bytes Message Length,表示Message Header+Message Text的字节数
10 bytes Message Header
N bytes Message Text,格式由消息报头的 PType 字段进一步指定
  • Message Header格式如下表
字节序号 描述
0-1 Session ID (Device ID),用来区分会话,回复消息时保持一致就行了(Linktest.req和Linktest.rsp是特殊的,必须为0xFFFF)
2 Header Byte 2,对于控制消息,表示状态码。对于数据消息,表示SECS Stream和是否需要回复(后面SECS-II再介绍)
3 Header Byte 3,对于控制消息,表示状态码。对于数据消息,表示SECS Function(后面SECS-II再介绍)
4 PType,一般为0,表示应用层为SECS-II
5 SType,表示消息类型,0=数据消息,1-9=控制消息(1=Select.req 2=Select.rsp 5=Linktest.req 6=Linktest.rsp)
6-9 System Bytes,用来区分消息,回复消息时保持一致就行了

2.2.1 Control Message

基于上述协议格式,可以写出Select.req和Select.rsp的具体格式,Linktest.req和Linktest.rsp也是类似就不列举了。

字段 值(16#) 说明
Message Length 00 00 00 0A 固定10 bytes,Header(10 bytes)+Text(0 bytes)=10 bytes
Session ID XX XX 发送方自定义,比如00 01
Header Byte 2 00 固定为0
Header Byte 3 00 固定为0
PType 00 固定为0
SType 01 1=Select.req
System Bytes XX XX XX XX 发送方自定义,比如00 00 00 01
字段 值(16#) 说明
Message Length 00 00 00 0A 固定10 bytes,Header(10 bytes)+Text(0 bytes)=10 bytes
Session ID XX XX 必须与Select.req的Session ID相同
Header Byte 2 00 固定为0
Header Byte 3 00 状态,0=成功 其他=失败
PType 00 固定为0
SType 02 2=Select.rsp
System Bytes XX XX XX XX 必须与Select.req的System Bytes相同

2.2.2 Data Message

Data Message的Message Text解析属于SECS-II部分暂时不管,此处只涉及HSMS相关部分。以S1F1(Stream=1 Function=1)为例

字段 值(16#) 说明
Message Length 00 00 00 XX Header(10 bytes)+Text(N bytes)=10+N bytes
Session ID XX XX 发送方自定义,比如00 01
Header Byte 2 81 bit7表示W-bit=true此消息需要回复,bit6-bit0表示SECS Stream=1
Header Byte 3 01 SECS Function=1
PType 00 固定为0
SType 02 0=Data Message
System Bytes XX XX XX XX 发送方自定义,比如00 00 00 01
Message Text Unkwon N bytes

2.3 协议实现

用2个类实现HSMS协议,class HSMSMessage和class HSMSConnection

2.3.1 HSMSMessage类

此类定义了HSMS消息本身

namespace 简单SECS
{
    public class HSMSMessage
    {
        // 实际数据由Length(4byte)+Header(10byte)+Data(Nbyte)组成
        public byte[] Header { get; } = new byte[10]; // 10-byte HSMS header
        public byte[] Data { get; set; }

        // Structured accessors
        public int SessionId => (Header[0] << 8) | Header[1];
        public HSMSSessionType SType => (HSMSSessionType)Header[5];
        public int SystemBytes => (Header[6] << 24) | (Header[7] << 16) | (Header[8] << 8) | Header[9];

        // Data message properties
        public bool ReplyExpected => (Header[2] & 0x80) != 0;
        public byte Stream => (byte)(Header[2] & 0x7F);
        public byte Function => Header[3];

        // Control Message properties
        public byte StatusCode => Header[3]; // 用于Select.rsp等

        // 从字节流解析(包含4字节长度头)
        public static HSMSMessage FromBytes(byte[] data)
        {
            if (data.Length < 14) // 4-byte length + 10-byte header
                throw new ArgumentException("Invalid HSMS message");

            var message = new HSMSMessage();

            // Verify length
            int declaredLength = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3];
            if (declaredLength + 4 != data.Length)
                throw new ArgumentException("Length mismatch");

            // Copy header (skip 4-byte length)
            Array.Copy(data, 4, message.Header, 0, 10);

            // Copy data if present
            if (declaredLength > 10)
            {
                message.Data = new byte[declaredLength - 10];
                Array.Copy(data, 14, message.Data, 0, declaredLength - 10);
            }

            return message;
        }

        // 生成字节流(包含4字节长度头)
        public byte[] ToBytes()
        {
            // 4 byte + 10 byte + N byte
            int dataLength = Data?.Length ?? 0;
            int totalLength = 10 + dataLength;
            byte[] result = new byte[4 + totalLength]; // 4-byte length + message

            // Write length (big-endian)
            result[0] = (byte)((totalLength >> 24) & 0xFF);
            result[1] = (byte)((totalLength >> 16) & 0xFF);
            result[2] = (byte)((totalLength >> 8) & 0xFF);
            result[3] = (byte)(totalLength & 0xFF);

            // Write header
            Array.Copy(Header, 0, result, 4, 10);

            // Write data
            if (dataLength > 0)
            {
                Array.Copy(Data, 0, result, 14, dataLength);
            }

            return result;
        }

        public static HSMSMessage CreateSelectReq()
        {
            HSMSMessage msg = new HSMSMessage();
            msg.Header[0] = 0x00;  // 0-1是SessionID
            msg.Header[1] = 0x00;
            msg.Header[2] = 0x00;  // Header Byte 2,必须为0
            msg.Header[3] = 0x00;  // Header Byte 3,必须为0
            msg.Header[4] = 0x00;  // PType,固定为0(SECS-II编码)
            msg.Header[5] = (byte)HSMSSessionType.SelectReq;  // SType
            msg.Header[6] = 0x00; // 6-9是SystemBytes
            msg.Header[7] = 0x00;
            msg.Header[8] = 0x00;
            msg.Header[9] = 0x01;
            return msg;
        }
        public static HSMSMessage CreateLinktestReq()
        {
            HSMSMessage msg = new HSMSMessage();
            msg.Header[0] = 0xFF;  // 0-1是SessionID=0xFFFF
            msg.Header[1] = 0xFF;
            msg.Header[2] = 0x00;  // Header Byte 2,必须为0
            msg.Header[3] = 0x00;  // Header Byte 3,必须为0
            msg.Header[4] = 0x00;  // PType,固定为0(SECS-II编码)
            msg.Header[5] = (byte)HSMSSessionType.LinktestReq;  // SType
            msg.Header[6] = 0x00; // 6-9是SystemBytes
            msg.Header[7] = 0x00;
            msg.Header[8] = 0x00;
            msg.Header[9] = 0x01;
            return msg;
        }
        public static HSMSMessage CreateSelectRsp(HSMSMessage hsmsMessage)
        {
            HSMSMessage msg = new HSMSMessage();
            Array.Copy(hsmsMessage.Header, 0, msg.Header, 0, 10);
            msg.Header[3] = 0x00;  // Header Byte 3,0表示成功
            msg.Header[5] = (byte)HSMSSessionType.SelectRsp;  // SType
            return msg;
        }
        public static HSMSMessage CreateLinktestRsp(HSMSMessage hsmsMessage)
        {
            HSMSMessage msg = new HSMSMessage();
            Array.Copy(hsmsMessage.Header, 0, msg.Header, 0, 10);
            msg.Header[5] = (byte)HSMSSessionType.LinktestRsp;  // SType
            return msg;
        }

        public override string ToString()
        {
            switch (SType)
            {
                case HSMSSessionType.DataMessage:
                    return $"HSMS Message S{Stream}F{Function} SystemBytes: {SystemBytes} Reply Expected: {ReplyExpected}";
                default:
                    return $"{SType} (Session:{SessionId})";
            }
        }
    }

    public enum HSMSSessionType : byte
    {
        DataMessage = 0,
        SelectReq = 1,
        SelectRsp = 2,
        DeselectReq = 3,
        DeselectRsp = 4,
        LinktestReq = 5,
        LinktestRsp = 6,
        RejectReq = 7,
        SeparateReq = 9
    }

2.3.2 HSMSConnection类

此类定义了模式,连接,消息收发,状态转移等

namespace 简单SECS
{
    public enum ConnectionMode { Passive, Active }
    public enum ConnectionState { NotConnected, Connecting, Connected, Selected }

    public class HSMSConnection
    {
        private TcpClient _tcpClient;
        private TcpListener _tcpListener;
        private NetworkStream _networkStream;
        private ConnectionMode _mode;
        private ConnectionState _state;
        private IPEndPoint _endPoint;
        private CancellationTokenSource _cts;

        // 添加缓冲区用于处理粘包
        private byte[] _receiveBuffer = new byte[4096];
        private int _bufferOffset = 0;

        public event EventHandler<HSMSMessage> ControlMessageSend;
        public event EventHandler<HSMSMessage> DataMessageReceived;
        public event EventHandler<ConnectionState> StateChanged;
        public event EventHandler<string> InternalMessage;

        public ConnectionState State
        {
            get => _state;
            set
            {
                if (_state != value)
                {
                    _state = value;
                    StateChanged?.Invoke(this, _state);
                }
            }
        }

        public HSMSConnection(IPEndPoint endPoint, ConnectionMode mode)
        {
            _endPoint = endPoint ?? throw new ArgumentNullException(nameof(endPoint));
            _mode = mode;
            _cts = new CancellationTokenSource();
            State = ConnectionState.NotConnected;
        }

        public async Task ConnectAsync()
        {
            try
            {
                if (_mode == ConnectionMode.Active)
                {
                    _tcpClient = new TcpClient();
                    State = ConnectionState.Connecting;
                    await _tcpClient.ConnectAsync(_endPoint.Address, _endPoint.Port);
                    _networkStream = _tcpClient.GetStream();
                    State = ConnectionState.Connected;
                    
                }
                else
                {
                    _tcpListener = new TcpListener(_endPoint);
                    _tcpListener.Start();
                    State = ConnectionState.Connecting;
                    _tcpClient = await _tcpListener.AcceptTcpClientAsync();
                    _networkStream = _tcpClient.GetStream();
                    State = ConnectionState.Connected;
                }
                StartReceiving();
            }
            catch(Exception ex)
            {
                InternalMessage?.Invoke(this, $"[{_mode}] Connect error: {ex.Message}");
            }
        }

        private void StartReceiving()
        {
            Task.Run(async () =>
            {
                while (!_cts.IsCancellationRequested && _tcpClient.Connected)
                {
                    try
                    {
                        // 读取数据到缓冲区
                        int bytesRead = await _networkStream.ReadAsync(_receiveBuffer, _bufferOffset, _receiveBuffer.Length - _bufferOffset, _cts.Token);
                        if (bytesRead > 0)
                        {
                            _bufferOffset += bytesRead;
                            ProcessBuffer();
                        }
                    }
                    catch (Exception ex)
                    {
                        InternalMessage?.Invoke(this, $"[{_mode}] Receive error: {ex.Message}");
                    }
                }
                Disconnect();
            }, _cts.Token);

            async void ProcessBuffer()
            {
                while (_bufferOffset >= 4) // 至少能读取长度头
                {
                    // 读取消息总长度(大端序)
                    int totalLength = (_receiveBuffer[0] << 24) | (_receiveBuffer[1] << 16) | (_receiveBuffer[2] << 8) | _receiveBuffer[3];

                    // 检查是否收到完整消息
                    if (_bufferOffset < totalLength + 4)
                    {
                        break; // 等待更多数据
                    }

                    // 提取完整消息(包含4字节长度头)
                    byte[] messageData = new byte[totalLength + 4];
                    Array.Copy(_receiveBuffer, 0, messageData, 0, totalLength + 4);

                    // 获取消息(跳过长度头)
                    HSMSMessage hsmsMsg = HSMSMessage.FromBytes(messageData);

                    // 移动缓冲区剩余数据
                    int remaining = _bufferOffset - (totalLength + 4);
                    if (remaining > 0)
                    {
                        Array.Copy(_receiveBuffer, totalLength + 4, _receiveBuffer, 0, remaining);
                    }
                    _bufferOffset = remaining;

                    // 处理消息
                    try
                    {
                        switch (hsmsMsg.SType)
                        {
                            case HSMSSessionType.SelectReq:
                                // HandleSelectReq
                                InternalMessage?.Invoke(this, $"[{_mode}] Received: {hsmsMsg}");
                                HSMSMessage reply = HSMSMessage.CreateSelectRsp(hsmsMsg);
                                await SendMessageAsync(reply);
                                InternalMessage?.Invoke(this, $"[{_mode}] Send: {reply}");
                                State = ConnectionState.Selected;
                                break;

                            case HSMSSessionType.SelectRsp:
                                // HandleSelectRsp
                                InternalMessage?.Invoke(this, $"[{_mode}] Received: {hsmsMsg}");
                                State = ConnectionState.Selected;
                                break;

                            case HSMSSessionType.LinktestReq:
                                // HandleLinktestReq
                                InternalMessage?.Invoke(this, $"[{_mode}] Received: {hsmsMsg}");
                                HSMSMessage reply1 = HSMSMessage.CreateLinktestRsp(hsmsMsg);
                                await SendMessageAsync(reply1);
                                InternalMessage?.Invoke(this, $"[{_mode}] Send: {reply1}");
                                break;

                            case HSMSSessionType.DataMessage:
                                // 交给SECS-II处理
                                DataMessageReceived?.Invoke(this, hsmsMsg);
                                break;
                        }
                    }
                    catch (Exception ex)
                    {
                        InternalMessage?.Invoke(this, $"[{_mode}] Error processing message: {ex}");
                    }
                }
            }
        }

        public async Task SendSelectReqAsync()
        {
            HSMSMessage selectReq = HSMSMessage.CreateSelectReq();
            ControlMessageSend?.Invoke(this, selectReq);
            await SendMessageAsync(selectReq);
        }

        public async Task SendMessageAsync(HSMSMessage message)
        {
            if (_networkStream == null || !_tcpClient.Connected)
            {
                InternalMessage?.Invoke(this, $"[{_mode}] Send Message Failed: Not connected");
                return;
            }
            try
            {
                byte[] data = message.ToBytes();
                if (_networkStream != null)
                {
                    await _networkStream.WriteAsync(data, 0, data.Length);
                }
            }
            catch (Exception ex)
            {
                InternalMessage?.Invoke(this, $"[{_mode}] Send Message error {ex}");
                Disconnect();
            }
        }

        public void Disconnect()
        {
            _cts.Cancel();
            _networkStream?.Close();
            _tcpClient?.Close();
            _tcpListener?.Stop();
            State = ConnectionState.NotConnected;
        }
    }
}

3,SECS-II协议

3.1 基本概念

消息组成

  • Stream:表示消息的主要类型
  • Function:表示消息的特定功能,Primary Message的Function为奇数,Reply Message的Function为对应的Primary Message的Function加1
  • Message Text:表示消息的内容,即Data Items,TVL(Type-Length-Value)格式

Data Item组成

  • Type(1 byte):bit7-bit2表示数据类型,bit1-bit0表示Length byte长度
  • Length(1-3 bytes):表示Value的长度,到底用几个byte由Type的bit1-bit0决定
  • Value:实际数据内容

数据类型

  • List:类似于C#的List<object>,可以包含一系列Data Item
  • Binary, Boolean, ASCII, I8, I1, I2, I4, F8, F4, U8, U1, U2, U4:类似于C#的基本数据类型byte[], bool, string, long, sbyte, short, int, double, float, ulong, byte, ushort, uint,可以单独存在,也可以被List包含

3.2 HSMS与SECS-II转换

以HSMS消息转换到SECS消息为例

3.2.1 Stream和Function

  1. 根据Message Header的Header Byte 2,bit6-bit0解析出Stream。比如S1F1 W的Header Byte 2为16#81,bit6-bit0为1,表示S1
  2. 根据Message Header的Header Byte 3,解析出Function。比如S1F1 W的Header Byte 3为16#01,表示F1

3.2.2 Data Items

数据类型定义如下图

  • 例子1,Message Text内容为41 0A 68 65 6C 6C 6F 00 73 65 63 73

    • Type(1 byte)为16#41,bit7-bit2为16#40,根据定义16#40对应ASCII。bit1-bit0为16#1,16#1表示Length byte长度为1,即用1个byte表示Length
    • Length(1 byte)为16#0A,表示Value的长度为10 bytes
    • Value(10 bytes)为68 65 6C 6C 6F 00 73 65 63 73,从ASCII码转换成字符串就是"hello_secs"
    • 此Text只有1段,可简写为A[10] “hello_secs”
  • 例子2,Message Text内容为01 02 A9 04 00 64 00 C8 01 02 41 04 32 30 30 31 41 04 32 30 30 32

    • Type(1 byte)为16#01,bit7-bit2为16#40,根据定义16#40对应List。bit1-bit0为16#1,16#1表示Length byte长度为1,即用1个byte表示Length
    • Length(1 byte)为16#02,表示Value的长度为2,即List有2个元素。注意List和基本数据类型不同,List只是包含Data Item,自身并没有Value部分
    • Text的第1段,可简写为L[2]
    • Type(1 byte)为16#A9,bit7-bit2为16#A8,根据定义16#A8对应U2。bit1-bit0为16#1,16#1表示Length byte长度为1,即用1个byte表示Length
    • Length(1 byte)为16#04,表示Value的长度为4 bytes
    • Value(4 bytes)为00 64 00 C8,转换成ushort就是100,200
    • Text的第2段,可简写为U2[2] 100 200
    • Type(1 byte)为16#01
    • Length(1 byte)为16#02
    • Text的第3段,可简写为L[2]
    • Type(1 byte)为16#41
    • Length(1 byte)为16#04
    • Value(4 bytes)为32 30 30 31,从ASCII码转换成字符串就是"2001"
    • Text的第4段,可简写为A[4] "2001"
    • Type(1 byte)为16#41
    • Length(1 byte)为16#04
    • Value(4 bytes)为32 30 30 32,从ASCII码转换成字符串就是"2002"
    • Text的第5段,可简写为A[4] "2002"

    综上,例子2的消息简写如下

    L[2]
      U2[2] 100 200
    ​  L[2]
    ​    A[4] “2001”
        A[4] “2002”

    用C#来类比如下,很清晰吧

    new List<object>() {new ushort[] {100, 200}, new List<object>() { "2001", "2002"}}
    

3.2.3 完整案例

这次以SECS消息转换到HSMS消息为例

  • SECS消息如下

    S1F3 W
    L[2]
    ​  U4[1] 4115
    ​  U4[1] 4116

  • 对应的HSMS消息

    字段 值(16#) 说明
    Message Length 00 00 00 18 Header(10 bytes)+Text(14 bytes)=24 bytes
    Session ID XX XX 发送方自定义
    Header Byte 2 81 bit7表示W-bit=true此消息需要回复,bit6-bit0表示SECS Stream=1
    Header Byte 3 03 SECS Function=3
    PType 00 固定为0
    SType 00 0=Data Message
    System Bytes XX XX XX XX 发送方自定义
    Message Text 01 02
    B1 04 00 00 10 13
    B1 04 00 00 10 14
    L[2]
    U4[1] 4115
    U4[1] 4116

3.3 协议实现

用3个类实现SECS-II协议,class SECSItem,class SECSMessage和class SECSEndpoint。用1个类做大小端转换,class ByteArrayConverter

3.3.1 SECSItem类

此类定义了所有数据类型,List和基本数据类型

namespace 简单SECS
{
    public abstract class SECSItem
    {
        public SECSItemFormat Format { get; protected set; }
        public abstract byte[] ToBytes();

        public static List<SECSItem> ParseItems(byte[] data, HSMSMessage hsmsMsg = null)
        {
            var items = new List<SECSItem>();
            int index = 0;

            while (index < data.Length)
            {
                byte formatByte = data[index++];
                SECSItemFormat format = (SECSItemFormat)(formatByte & 0xFC);
                int lengthBytes = formatByte & 0x03;  // 表示长度的字节数

                // List类型保存长度,其他类型保存内容
                if (format == SECSItemFormat.List)
                {
                    byte[] itemData = new byte[lengthBytes];
                    Array.Copy(data, index, itemData, 0, lengthBytes);
                    index += lengthBytes;
                    items.Add(CreateItem(format, itemData));
                }
                else
                {
                    // Read length
                    int length = 0;
                    for (int i = 0; i < lengthBytes; i++)
                    {
                        if (index >= data.Length)
                        {
                            throw new Exception("长度不对");
                        }
                        length = (length << 8) | data[index++];
                    }

                    // Read data
                    if (index + length > data.Length)
                    {
                        throw new Exception("越界");
                    }

                    byte[] itemData = new byte[length];
                    Array.Copy(data, index, itemData, 0, length);
                    index += length;

                    items.Add(CreateItem(format, itemData));
                }
            }
            return items;
        }
        private static SECSItem CreateItem(SECSItemFormat format, byte[] data)
        {
            switch (format)
            {
                case SECSItemFormat.List:
                    return new SECSListItem(data);
                case SECSItemFormat.ASCII:
                    return new SECSASCIIItem(data);
                case SECSItemFormat.Binary:
                    return new SECSBinaryItem(data);
                case SECSItemFormat.Boolean:
                    return new SECSBooleanItem(data);
                case SECSItemFormat.I1:
                    return new SECSI1Item(data);
                case SECSItemFormat.I2:
                    return new SECSI2Item(data);
                case SECSItemFormat.I4:
                    return new SECSI4Item(data);
                case SECSItemFormat.I8:
                    return new SECSI8Item(data);
                case SECSItemFormat.U1:
                    return new SECSU1Item(data);
                case SECSItemFormat.U2:
                    return new SECSU2Item(data);
                case SECSItemFormat.U4:
                    return new SECSU4Item(data);
                case SECSItemFormat.U8:
                    return new SECSU8Item(data);
                case SECSItemFormat.F4:
                    return new SECSF4Item(data);
                case SECSItemFormat.F8:
                    return new SECSF8Item(data);
                default:
                    throw new NotSupportedException($"Format {format} not supported");
            }
        }

        // 辅助方法
        protected int BytesToInt(byte[] value, bool changeEndian = true)
        {
            if (value == null)
            {
                throw new ArgumentNullException(nameof(value));
            }
            if (value.Length >= 4)
            {
                return BitConverter.ToInt32(value, 0);
            }
            // 检查最高位是否设置(是否为负数)
            bool isNegative = (value[0] & 0x80) != 0;
            byte[] paddedBytes = new byte[4];
            int startIndex = 4 - value.Length;
            // 填充字节(如果负数,填充 0xFF;否则填充 0x00)
            byte fillByte = isNegative ? (byte)0xFF : (byte)0x00;
            for (int i = 0; i < startIndex; i++)
            {
                paddedBytes[i] = fillByte;
            }
            Array.Copy(value, 0, paddedBytes, startIndex, value.Length);
            if (changeEndian)
            {
                Array.Reverse(paddedBytes);
            }
            int result = BitConverter.ToInt32(paddedBytes, 0);
            return result;
        }
        protected byte[] IntToBytes(int value, bool changeEndian = true)
        {
            byte[] bytes = BitConverter.GetBytes(value);
            if (changeEndian)
            {
                Array.Reverse(bytes);
            }
            // 找到第一个非零字节的位置
            int startIndex = 0;
            while (startIndex < bytes.Length && bytes[startIndex] == 0)
            {
                startIndex++;
            }
            // 如果全部是零,返回单个零字节
            if (startIndex == bytes.Length)
            {
                return new byte[] { 0 };
            }
            // 截取非零部分
            byte[] result = new byte[bytes.Length - startIndex];
            Array.Copy(bytes, startIndex, result, 0, result.Length);
            return result;
        }
    }

    public class SECSListItem : SECSItem
    {
        public int Length { get; set; }  // List类型保存长度,其他类型保存内容

        public SECSListItem(int length)
        {
            Format = SECSItemFormat.List;
            Length = length;
        }
        public SECSListItem(byte[] data)
        {
            Format = SECSItemFormat.List;
            Length = BytesToInt(data);
        }
        public override byte[] ToBytes()
        {
            byte[] data = IntToBytes(Length);

            var result = new List<byte>();
            result.Add((byte)((byte)Format | (data.Length & 0x03)));
            result.AddRange(data);

            return result.ToArray();
        }
        public override string ToString()
        {
            var sb = new StringBuilder();
            sb.Append($"L[{Length}]");
            return sb.ToString();
        }
    }

    public class SECSASCIIItem : SECSItem
    {
        public string Value { get; set; }

        public SECSASCIIItem(string value)
        {
            Format = SECSItemFormat.ASCII;
            Value = value;
        }

        public SECSASCIIItem(byte[] data)
        {
            Format = SECSItemFormat.ASCII;
            Value = Encoding.ASCII.GetString(data);
        }

        public override byte[] ToBytes()
        {
            byte[] data = Encoding.ASCII.GetBytes(Value);
            byte[] lengthBytes = IntToBytes(data.Length);

            List<byte> result = new List<byte>();
            result.Add((byte)((byte)Format | (lengthBytes.Length & 0x03)));
            result.AddRange(lengthBytes);
            result.AddRange(data);

            return result.ToArray();
        }

        public override string ToString()
        {
            return $"A[{Value.Length}]\"{Value}\"";
        }
    }

    public class SECSBinaryItem : SECSItem
    {
        public byte[] Value { get; set; }

        public SECSBinaryItem(byte[] value)
        {
            Format = SECSItemFormat.Binary;
            Value = value;
        }

        public override byte[] ToBytes()
        {
            byte[] lengthBytes = IntToBytes(Value.Length);

            var result = new List<byte>();
            result.Add((byte)((byte)Format | (lengthBytes.Length & 0x03)));
            result.AddRange(lengthBytes);
            result.AddRange(Value);

            return result.ToArray();
        }

        public override string ToString()
        {
            return $"B[{Value.Length}]{string.Join(",", Value)}";
        }
    }

    public class SECSBooleanItem : SECSItem
    {
        public bool[] Value { get; set; }

        public SECSBooleanItem(bool[] value)
        {
            Format = SECSItemFormat.Boolean;
            Value = value;
        }

        public SECSBooleanItem(byte[] data)
        {
            Format = SECSItemFormat.Boolean;
            Value = new bool[data.Length];
            for (int i = 0; i < data.Length; i++)
            {
                Value[i] = data[i] != 0;
            }
        }

        public override byte[] ToBytes()
        {
            byte[] data = new byte[Value.Length];
            for (int i = 0; i < Value.Length; i++)
            {
                data[i] = (byte)(Value[i] ? 1 : 0);
            }
            byte[] lengthBytes = IntToBytes(data.Length);

            var result = new List<byte>();
            result.Add((byte)((byte)Format | (lengthBytes.Length & 0x03)));
            result.AddRange(lengthBytes);
            result.AddRange(data);

            return result.ToArray();
        }

        public override string ToString()
        {
            return $"Bool[{Value.Length}]{string.Join(",", Value)}";
        }
    }

    public class SECSI1Item : SECSItem
    {
        public sbyte[] Value { get; set; }

        public SECSI1Item(sbyte[] value)
        {
            Format = SECSItemFormat.I1;
            Value = value;
        }

        public SECSI1Item(byte[] data)
        {
            Format = SECSItemFormat.I1;
            Value = ByteArrayConverter.FromByteArray<sbyte>(data, true);
        }

        public override byte[] ToBytes()
        {
            byte[] data = ByteArrayConverter.ToByteArray<sbyte>(Value, true);
            byte[] lengthBytes = IntToBytes(data.Length);

            var result = new List<byte>();
            result.Add((byte)((byte)Format | (lengthBytes.Length & 0x03)));
            result.AddRange(lengthBytes);
            result.AddRange(data);

            return result.ToArray();
        }

        public override string ToString()
        {
            return $"I1[{Value.Length}]{string.Join(",", Value)}";
        }
    }

    public class SECSI2Item : SECSItem
    {
        public short[] Value { get; set; }

        public SECSI2Item(short[] value)
        {
            Format = SECSItemFormat.I2;
            Value = value;
        }

        public SECSI2Item(byte[] data)
        {
            Format = SECSItemFormat.I2;
            Value = ByteArrayConverter.FromByteArray<short>(data, true);
        }

        public override byte[] ToBytes()
        {
            byte[] data = ByteArrayConverter.ToByteArray<short>(Value, true);
            byte[] lengthBytes = IntToBytes(data.Length);

            var result = new List<byte>();
            result.Add((byte)((byte)Format | (lengthBytes.Length & 0x03)));
            result.AddRange(lengthBytes);
            result.AddRange(data);

            return result.ToArray();
        }

        public override string ToString()
        {
            return $"I2[{Value.Length}]{string.Join(",", Value)}";
        }
    }

    public class SECSI4Item : SECSItem
    {
        public int[] Value { get; set; }

        public SECSI4Item(int[] value)
        {
            Format = SECSItemFormat.I4;
            Value = value;
        }

        public SECSI4Item(byte[] data)
        {
            Format = SECSItemFormat.I4;
            Value = ByteArrayConverter.FromByteArray<int>(data, true);
        }

        public override byte[] ToBytes()
        {
            byte[] data = ByteArrayConverter.ToByteArray<int>(Value, true);
            byte[] lengthBytes = IntToBytes(data.Length);

            var result = new List<byte>();
            result.Add((byte)((byte)Format | (lengthBytes.Length & 0x03)));
            result.AddRange(lengthBytes);
            result.AddRange(data);

            return result.ToArray();
        }

        public override string ToString()
        {
            return $"I4[{Value.Length}]{string.Join(",", Value)}";
        }
    }

    public class SECSI8Item : SECSItem
    {
        public long[] Value { get; set; }

        public SECSI8Item(long[] value)
        {
            Format = SECSItemFormat.I8;
            Value = value;
        }

        public SECSI8Item(byte[] data)
        {
            Format = SECSItemFormat.I8;
            Value = ByteArrayConverter.FromByteArray<long>(data, true);
        }

        public override byte[] ToBytes()
        {
            byte[] data = ByteArrayConverter.ToByteArray<long>(Value, true);
            byte[] lengthBytes = IntToBytes(data.Length);

            var result = new List<byte>();
            result.Add((byte)((byte)Format | (lengthBytes.Length & 0x03)));
            result.AddRange(lengthBytes);
            result.AddRange(data);

            return result.ToArray();
        }

        public override string ToString()
        {
            return $"I8[{Value.Length}]{string.Join(",", Value)}";
        }
    }

    public class SECSU1Item : SECSItem
    {
        public byte[] Value { get; set; }

        public SECSU1Item(byte[] value)
        {
            Format = SECSItemFormat.U1;
            Value = value;
        }

        public override byte[] ToBytes()
        {
            byte[] lengthBytes = IntToBytes(Value.Length);

            var result = new List<byte>();
            result.Add((byte)((byte)Format | (lengthBytes.Length & 0x03)));
            result.AddRange(lengthBytes);
            result.AddRange(Value);

            return result.ToArray();
        }

        public override string ToString()
        {
            return $"U1[{Value.Length}]{string.Join(",", Value)}";
        }
    }

    public class SECSU2Item : SECSItem
    {
        public ushort[] Value { get; set; }

        public SECSU2Item(ushort[] value)
        {
            Format = SECSItemFormat.U2;
            Value = value;
        }

        public SECSU2Item(byte[] data)
        {
            Format = SECSItemFormat.U2;
            Value = ByteArrayConverter.FromByteArray<ushort>(data, true);
        }

        public override byte[] ToBytes()
        {
            byte[] data = ByteArrayConverter.ToByteArray<ushort>(Value, true);
            byte[] lengthBytes = IntToBytes(data.Length);

            var result = new List<byte>();
            result.Add((byte)((byte)Format | (lengthBytes.Length & 0x03)));
            result.AddRange(lengthBytes);
            result.AddRange(data);

            return result.ToArray();
        }

        public override string ToString()
        {
            return $"U2[{Value.Length}]{string.Join(",", Value)}";
        }
    }

    public class SECSU4Item : SECSItem
    {
        public uint[] Value { get; set; }

        public SECSU4Item(uint[] value)
        {
            Format = SECSItemFormat.U4;
            Value = value;
        }

        public SECSU4Item(byte[] data)
        {
            Format = SECSItemFormat.U4;
            Value = ByteArrayConverter.FromByteArray<uint>(data, true);  // 00 00 03 E9 => 1001
        }

        public override byte[] ToBytes()
        {
            byte[] data = ByteArrayConverter.ToByteArray<uint>(Value, true);
            byte[] lengthBytes = IntToBytes(data.Length);

            var result = new List<byte>();
            result.Add((byte)((byte)Format | (lengthBytes.Length & 0x03)));
            result.AddRange(lengthBytes);
            result.AddRange(data);

            return result.ToArray();
        }

        public override string ToString()
        {
            return $"U4[{Value.Length}]{string.Join(",", Value)}";
        }
    }

    public class SECSU8Item : SECSItem
    {
        public ulong[] Value { get; set; }

        public SECSU8Item(ulong[] value)
        {
            Format = SECSItemFormat.U8;
            Value = value;
        }

        public SECSU8Item(byte[] data)
        {
            Format = SECSItemFormat.U8;
            Value = ByteArrayConverter.FromByteArray<ulong>(data, true);
        }

        public override byte[] ToBytes()
        {
            byte[] data = ByteArrayConverter.ToByteArray<ulong>(Value, true);
            byte[] lengthBytes = IntToBytes(data.Length);

            var result = new List<byte>();
            result.Add((byte)((byte)Format | (lengthBytes.Length & 0x03)));
            result.AddRange(lengthBytes);
            result.AddRange(data);

            return result.ToArray();
        }

        public override string ToString()
        {
            return $"U8[{Value.Length}]{string.Join(",", Value)}";
        }
    }

    public class SECSF4Item : SECSItem
    {
        public float[] Value { get; set; }

        public SECSF4Item(float[] value)
        {
            Format = SECSItemFormat.F4;
            Value = value;
        }

        public SECSF4Item(byte[] data)
        {
            Format = SECSItemFormat.F4;
            Value = ByteArrayConverter.FromByteArray<float>(data, true);
        }

        public override byte[] ToBytes()
        {
            byte[] data = ByteArrayConverter.ToByteArray<float>(Value, true);
            byte[] lengthBytes = IntToBytes(data.Length);

            var result = new List<byte>();
            result.Add((byte)((byte)Format | (lengthBytes.Length & 0x03)));
            result.AddRange(lengthBytes);
            result.AddRange(data);

            return result.ToArray();
        }

        public override string ToString()
        {
            return $"F4[{Value.Length}]{string.Join(",", Value)}";
        }
    }

    public class SECSF8Item : SECSItem
    {
        public double[] Value { get; set; }

        public SECSF8Item(double[] value)
        {
            Format = SECSItemFormat.F8;
            Value = value;
        }

        public SECSF8Item(byte[] data)
        {
            Format = SECSItemFormat.F8;
            Value = ByteArrayConverter.FromByteArray<double>(data, true);
        }

        public override byte[] ToBytes()
        {
            byte[] data = ByteArrayConverter.ToByteArray<double>(Value, true);
            byte[] lengthBytes = IntToBytes(data.Length);

            var result = new List<byte>();
            result.Add((byte)((byte)Format | (lengthBytes.Length & 0x03)));
            result.AddRange(lengthBytes);
            result.AddRange(data);

            return result.ToArray();
        }

        public override string ToString()
        {
            return $"F8[{Value.Length}]{string.Join(",", Value)}";
        }
    }

    public enum SECSItemFormat : byte
    {
        List = 0x00,
        Binary = 0x20,
        Boolean = 0x24,
        ASCII = 0x40,
        JIS8 = 0x42,
        I8 = 0x60,
        I1 = 0x64,
        I2 = 0x68,
        I4 = 0x70,
        F8 = 0x80,
        F4 = 0x90,
        U8 = 0xA0,
        U1 = 0xA4,
        U2 = 0xA8,
        U4 = 0xB0
    }
}

3.3.2 SECSMessage类

此类定义了SECS消息本身,内部封装了SECSItem

namespace 简单SECS
{
    public class SECSMessage
    {
        public byte Stream { get; set; }
        public byte Function { get; set; }
        public bool ReplyExpected { get; set; }
        public int SystemBytes { get; set; }
        public List<SECSItem> Items { get; set; } = new List<SECSItem>();

        public static SECSMessage FromHSMS(HSMSMessage hsmsMsg)
        {
            var secsMsg = new SECSMessage
            {
                Stream = hsmsMsg.Stream,
                Function = hsmsMsg.Function,
                ReplyExpected = hsmsMsg.ReplyExpected,
                SystemBytes = hsmsMsg.SystemBytes
            };
            if (hsmsMsg.Data != null && hsmsMsg.Data.Length > 2)
            {
                secsMsg.Items = SECSItem.ParseItems(hsmsMsg.Data, hsmsMsg);
            }
            return secsMsg;
        }

        public HSMSMessage ToHSMS()
        {
            HSMSMessage hsmsMsg = new HSMSMessage();

            // Header
            hsmsMsg.Header[2] = (byte)((ReplyExpected ? 0x80 : 0x00) | (Stream & 0x7F));
            hsmsMsg.Header[3] = Function;
            hsmsMsg.Header[5] = (byte)HSMSSessionType.DataMessage;

            // SystemBytes
            hsmsMsg.Header[6] = (byte)((SystemBytes >> 24) & 0xFF);
            hsmsMsg.Header[7] = (byte)((SystemBytes >> 16) & 0xFF);
            hsmsMsg.Header[8] = (byte)((SystemBytes >> 8) & 0xFF);
            hsmsMsg.Header[9] = (byte)(SystemBytes & 0xFF);

            // Serialize items
            if (Items.Count > 0)
            {
                List<byte> dataBytes = new List<byte>();
                foreach (SECSItem item in Items)
                {
                    dataBytes.AddRange(item.ToBytes());
                }
                hsmsMsg.Data = dataBytes.ToArray();
            }

            return hsmsMsg;
        }

        public SECSMessage CreateReply(byte function)
        {
            return new SECSMessage
            {
                Stream = Stream,
                Function = function,
                SystemBytes = SystemBytes,
                ReplyExpected = false
            };
        }

        public override string ToString()
        {
            var sb = new StringBuilder();
            sb.AppendLine($"SECS Message S{Stream}F{Function} SystemBytes: {SystemBytes} Reply Expected: {ReplyExpected}");
            List<int> LengthList = new List<int>();  // 用于计算缩进
            foreach (SECSItem item in Items)
            {
                sb.Append(' ', LengthList.Count * 2);
                sb.AppendLine($"{item}\n");
                
                if (LengthList.Count > 0 &&LengthList.Last() > 0)
                {
                    LengthList[LengthList.Count - 1] -= 1;
                }
                if (item is SECSListItem)
                {
                    LengthList.Add(((SECSListItem)item).Length);
                }
                while (LengthList.Count > 0 && LengthList[LengthList.Count - 1] <= 0)
                {
                    LengthList.RemoveAt(LengthList.Count - 1);
                }
            }
            return sb.ToString();
        }
    }
}

3.3.3 SECSEndpoint类

此类面向应用,定义了一系列事件,内部封装了HSMSConnection

namespace 简单SECS
{
    public class SECSEndpoint
    {
        private readonly HSMSConnection _connection;

        private ConnectionMode _mode;
        public ConnectionMode Mode
        {
            get => _mode;
            private set
            {
                _mode = value;
            }
        }
        public string Id { get; } = Guid.NewGuid().ToString("N").Substring(0, 8);
        public ConnectionState ConnectionState => _connection.State;

        public event EventHandler<HSMSMessage> ControlMessageSend;
        public event EventHandler<SECSMessage> DataMessageSend;
        public event EventHandler<SECSMessage> DataMessageReceived;
        public event EventHandler<ConnectionState> StateChanged;
        public event EventHandler<string> InternalMessage;

        public SECSEndpoint(IPEndPoint endPoint, ConnectionMode mode)
        {
            _mode = mode;
            _connection = new HSMSConnection(endPoint, mode);
            _connection.ControlMessageSend += OnControlMessageSend;
            _connection.DataMessageReceived += OnDataMessageReceived;
            _connection.StateChanged += OnStateChanged;
            _connection.InternalMessage += OnInternalMessage;
        }

        public async Task ConnectAsync()
        {
            await _connection.ConnectAsync();
            if (_connection.State == ConnectionState.Connected && Mode == ConnectionMode.Active)
            {
                await _connection.SendSelectReqAsync();
            }
        }

        public async Task SendMessageAsync(SECSMessage message)
        {
            DataMessageSend?.Invoke(this, message);
            await _connection.SendMessageAsync(message.ToHSMS());
        }

        public void Disconnect()
        {
            _connection.Disconnect();
        }

        private void OnControlMessageSend(object sender, HSMSMessage hsmsMsg)
        {
            ControlMessageSend?.Invoke(this, hsmsMsg);
        }
        private void OnDataMessageReceived(object sender, HSMSMessage hsmsMsg)
        {
            SECSMessage secsMessage = SECSMessage.FromHSMS(hsmsMsg);
            DataMessageReceived?.Invoke(this, secsMessage);
        }
        private void OnStateChanged(object sender, ConnectionState state)
        {
            StateChanged?.Invoke(this, state);
        }
        private void OnInternalMessage(object sender, string message)
        {
            InternalMessage?.Invoke(this, message);
        }
    }
}

3.3.4 ByteArrayConverter类

由于BitConverter的默认行为,定义了几个辅助方法

namespace 简单SECS
{
    // BitConverter.IsLittleEndian=true还不让改
    // 它认为系统是小端,网络是大端,所以BitConverter所有的方法都自动会做大小端转换,但我并不希望它转换,因此才有了ByteArrayConverter类
    public static class ByteArrayConverter
    {
        /// <summary>
        /// 将基本类型数组转换为byte数组,x86/x64是小端,网络传输是大端,小端就是低对低,大端就是低对高
        /// </summary>
        /// <typeparam name="T">支持的类型: sbyte, short, int, long, byte, ushort, uint, ulong, float, double</typeparam>
        /// <param name="array">源数组</param>
        /// <param name="changeEndian">改变字节序</param>
        public static byte[] ToByteArray<T>(this T[] array, bool changeEndian) where T : struct
        {
            if (array == null || array.Length == 0)
            {
                return Array.Empty<byte>();
            }
            int elementSize = GetElementSize<T>();
            byte[] result = new byte[array.Length * elementSize];
            for (int i = 0; i < array.Length; i++)
            {
                int offset = i * elementSize;
                switch (array[i])
                {
                    case sbyte sb: 
                        result[offset] = (byte)sb; 
                        break;
                    case byte b: 
                        result[offset] = b; 
                        break;
                    case short s: 
                        WriteBytes(BitConverter.GetBytes(s), result, offset, changeEndian); 
                        break;
                    case ushort us: 
                        WriteBytes(BitConverter.GetBytes(us), result, offset, changeEndian); 
                        break;
                    case int i32: 
                        WriteBytes(BitConverter.GetBytes(i32), result, offset, changeEndian);
                        break;
                    case uint ui32: 
                        WriteBytes(BitConverter.GetBytes(ui32), result, offset, changeEndian); 
                        break;
                    case long l64: 
                        WriteBytes(BitConverter.GetBytes(l64), result, offset, changeEndian); 
                        break;
                    case ulong ul64: 
                        WriteBytes(BitConverter.GetBytes(ul64), result, offset, changeEndian); 
                        break;
                    case float f: 
                        WriteBytes(BitConverter.GetBytes(f), result, offset, changeEndian); 
                        break;
                    case double d: 
                        WriteBytes(BitConverter.GetBytes(d), result, offset, changeEndian); 
                        break;
                    default: 
                        throw new NotSupportedException($"不支持类型{typeof(T)}");
                }
            }
            return result;
        }

        /// <summary>
        /// 将byte数组转换为基本类型数组,x86/x64是小端,网络传输是大端,小端就是低对低,大端就是低对高
        /// </summary>
        /// <typeparam name="T">目标类型</typeparam>
        /// <param name="bytes">源字节数组</param>
        /// <param name="changeEndian">改变字节序</param>
        public static T[] FromByteArray<T>(this byte[] bytes, bool changeEndian) where T : struct
        {
            if (bytes == null || bytes.Length == 0)
            {
                return Array.Empty<T>();
            }
            int elementSize = GetElementSize<T>();
            if (bytes.Length % elementSize != 0)
            {
                throw new ArgumentException($"byte数组长度必须是{elementSize}的整数倍");
            }
            int count = bytes.Length / elementSize;
            T[] result = new T[count];
            for (int i = 0; i < count; i++)
            {
                int offset = i * elementSize;
                if (typeof(T) == typeof(sbyte))
                {
                    result[i] = (T)(object)(sbyte)bytes[offset];
                }
                else if (typeof(T) == typeof(byte))
                {
                    result[i] = (T)(object)bytes[offset];
                }
                else
                {
                    byte[] temp = new byte[elementSize];
                    Array.Copy(bytes, offset, temp, 0, elementSize);
                    if (changeEndian)
                    {
                        Array.Reverse(temp);
                    }
                    result[i] = GetValueFromBytes<T>(temp);
                }
            }
            return result;
        }

        #region 辅助方法

        private static int GetElementSize<T>() where T : struct
        {
            if (typeof(T) == typeof(sbyte) || typeof(T) == typeof(byte)) 
                return 1;
            if (typeof(T) == typeof(short) || typeof(T) == typeof(ushort)) 
                return 2;
            if (typeof(T) == typeof(int) || typeof(T) == typeof(uint) || typeof(T) == typeof(float)) 
                return 4;
            if (typeof(T) == typeof(long) || typeof(T) == typeof(ulong) || typeof(T) == typeof(double)) 
                return 8;
            throw new NotSupportedException($"不支持类型{typeof(T)}");
        }

        private static void WriteBytes(byte[] source, byte[] destination, int offset, bool changeEndian)
        {
            if (changeEndian)
            {
                for (int i = 0; i < source.Length; i++)
                {
                    destination[offset + i] = source[source.Length - 1 - i];
                }
            }
            else
            {
                Array.Copy(source, 0, destination, offset, source.Length);
            }
        }

        private static T GetValueFromBytes<T>(byte[] bytes) where T : struct
        {
            if (typeof(T) == typeof(short)) 
                return (T)(object)BitConverter.ToInt16(bytes, 0);
            if (typeof(T) == typeof(ushort)) 
                return (T)(object)BitConverter.ToUInt16(bytes, 0);
            if (typeof(T) == typeof(int)) 
                return (T)(object)BitConverter.ToInt32(bytes, 0);
            if (typeof(T) == typeof(uint)) 
                return (T)(object)BitConverter.ToUInt32(bytes, 0);
            if (typeof(T) == typeof(long)) 
                return (T)(object)BitConverter.ToInt64(bytes, 0);
            if (typeof(T) == typeof(ulong)) 
                return (T)(object)BitConverter.ToUInt64(bytes, 0);
            if (typeof(T) == typeof(float)) 
                return (T)(object)BitConverter.ToSingle(bytes, 0);
            if (typeof(T) == typeof(double)) 
                return (T)(object)BitConverter.ToDouble(bytes, 0);
            throw new NotSupportedException($"不支持类型{typeof(T)}");
        }
        #endregion
    }
}

4,应用

4.1 Passive端

新建一个winform项目,引用简单SECS.dll,建立起Passive端。里面定义了一些收发方法,纯粹是为了测试,与实际业务数据不一样。

namespace SECSPassive端
{
    public partial class Form1 : Form
    {
        SECSEndpoint passive;
        private readonly Dictionary<int, Func<SECSMessage, Task>> _messageHandlers = new Dictionary<int, Func<SECSMessage, Task>>();
        public Form1()
        {
            InitializeComponent();
        }

        #region 控件方法
        private async void 连接_Click(object sender, EventArgs e)
        {
            SetTextbox("SECS Passive Simulator");
            IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 5555);
            passive = new SECSEndpoint(endPoint, ConnectionMode.Passive);
            InitializeMessageHandlers();
            passive.ControlMessageSend += Passive_ControlMessageSend;
            passive.DataMessageSend += Passive_DataMessageSend;
            passive.DataMessageReceived += Passive_DataMessageReceived;
            passive.StateChanged += Passive_StateChanged;
            passive.InternalMessage += Passive_InternalMessage;
            await passive.ConnectAsync();
        }

        private async void 发送_Click(object sender, EventArgs e)
        {
            string stream = Stream.Text;
            string function = Function.Text;
            MethodInfo info = this.GetType().GetMethod($"SendS{stream}F{function}");
            Task task = (Task)info?.Invoke(this, null);
            await task;
        }

        private void 断开_Click(object sender, EventArgs e)
        {
            passive.Disconnect();
        }
        #endregion

        private void InitializeMessageHandlers()
        {
            _messageHandlers[257] = HandleS1F1; // Are You There Request 1*256+1=257
            _messageHandlers[269] = HandleS1F13;  // Establish Communications Request

            _messageHandlers[525] = HandleS2F13; // Equipment Constant Request 
            _messageHandlers[527] = HandleS2F15; // New Equipment Constant Send
            _messageHandlers[529] = HandleS2F17; // Date and Time Request 2*256+17=529

            _messageHandlers[1547] = HandleS6F11; // Event Report Send 6*256+11=1547
        }

        private void Passive_ControlMessageSend(object sender, HSMSMessage hsmsMessage)
        {
            SetTextbox($"[{passive.Mode}] Send: {hsmsMessage}");
        }
        private void Passive_DataMessageSend(object sender, SECSMessage secsMessage)
        {
            SetTextbox($"[{passive.Mode}] Send: {secsMessage}");
        }
        private async void Passive_DataMessageReceived(object sender, SECSMessage secsMessage)
        {
            try
            {
                SetTextbox($"[{passive.Mode}] Received: {secsMessage}");
                int streamFunction = (secsMessage.Stream << 8) | secsMessage.Function;
                if (_messageHandlers.TryGetValue(streamFunction, out var handler))
                {
                    await handler(secsMessage);
                }
                else
                {
                    SetTextbox($"[{passive.Mode}] No handler for S{secsMessage.Stream}F{secsMessage.Function}");
                }
            }
            catch (Exception ex)
            {
                SetTextbox($"[{passive.Mode}] Error processing message: {ex}");
            }
        }
        private void Passive_StateChanged(object sender, ConnectionState state)
        {
            if (this.InvokeRequired)
            {
                if (state == ConnectionState.Connected || state == ConnectionState.Selected)
                {
                    Invoke(new Action(() =>
                    {
                        连接.Enabled = false;
                        断开.Enabled = true;
                    }));
                }
                else
                {
                    Invoke(new Action(() =>
                    {
                        连接.Enabled = true;
                        断开.Enabled = false;
                    }));
                }
            }
            else
            {
                if (state == ConnectionState.Connected || state == ConnectionState.Selected)
                {
                    连接.Enabled = false;
                    断开.Enabled = true;
                }
                else
                {
                    连接.Enabled = true;
                    断开.Enabled = false;
                }
            }
            SetTextbox($"[{passive.Mode}] Connection state changed: {state}");
        }
        private void Passive_InternalMessage(object sender, string message)
        {
            SetTextbox(message);
        }
        private void SetTextbox(string str)
        {
            if (!str.EndsWith("\r\n"))
            {
                str += "\r\n";
            }
            if (this.InvokeRequired)
            {
                Invoke(new Action(() => textBox1.AppendText(str)));
            }
            else
            {
                textBox1.AppendText(str);
            }
        }

        #region Handle
        private async Task HandleS1F1(SECSMessage msg)
        {
            SECSMessage reply = msg.CreateReply(2);
            reply.Items.Add(new SECSASCIIItem($"SECSEndpoint_{passive.Id}"));
            await passive.SendMessageAsync(reply);
        }
        private async Task HandleS1F13(SECSMessage msg)
        {
            SECSMessage reply = msg.CreateReply(14);
            reply.Items.Add(new SECSASCIIItem($"SECSEndpoint_{passive.Id}"));
            await passive.SendMessageAsync(reply);
        }
        private async Task HandleS2F13(SECSMessage msg)
        {
            // 处理Equipment Constant Request 
            SECSMessage reply = msg.CreateReply(14);
            if (msg.Items.Count <= 0)  // 返回所有Equipment Constant
            {
                reply.Items.Add(new SECSListItem(5));
                for (int i = 0; i < 5; i++)
                {
                    reply.Items.Add(new SECSU4Item(new uint[] { (uint)(100 + i) }));
                }
            }
            else  // 返回指定Equipment Constant
            {
                int length = ((SECSListItem)msg.Items[0]).Length;
                reply.Items.Add(new SECSListItem(length));
                for (int i = 1; i < msg.Items.Count; i++)
                {
                    uint[] ecId = ((SECSU4Item)msg.Items[i]).Value;
                    reply.Items.Add(new SECSU4Item(ecId.Select(item => item + 100).ToArray()));
                }
            }
            await passive.SendMessageAsync(reply);
        }
        private async Task HandleS2F15(SECSMessage msg)
        {
            SECSMessage reply = msg.CreateReply(16);
            await passive.SendMessageAsync(reply);
        }
        private async Task HandleS2F17(SECSMessage msg)
        {
            SECSMessage reply = msg.CreateReply(18);
            reply.Items.Add(new SECSASCIIItem(DateTime.Now.ToString("yyyyMMdd-HH:mm:ss")));
            await passive.SendMessageAsync(reply);
        }
        private async Task HandleS6F11(SECSMessage msg)
        {
            SECSMessage reply = msg.CreateReply(12);
            await passive.SendMessageAsync(reply);
        }
        #endregion

        #region Send
        public async Task SendS1F1()
        {
            var message = new SECSMessage
            {
                Stream = 1,
                Function = 1,
                ReplyExpected = true
            };
            await passive.SendMessageAsync(message);
        }
        public async Task SendS1F13()  //EstablishCommunication
        {
            var message = new SECSMessage
            {
                Stream = 1,
                Function = 13,
                ReplyExpected = true
            };
            await passive.SendMessageAsync(message);
        }
        public async Task SendS2F13(int ecId)  //RequestEquipmentConstant
        {
            var message = new SECSMessage
            {
                Stream = 2,
                Function = 13,
                ReplyExpected = true
            };

            message.Items.Add(new SECSI4Item(new int[] { ecId }));
            await passive.SendMessageAsync(message);
        }
        public async Task SendS2F15(int eventId)
        {
            var message = new SECSMessage
            {
                Stream = 2,
                Function = 15,
                ReplyExpected = true
            };

            List<SECSItem> list = new List<SECSItem>();
            list.Add(new SECSListItem(2));
            list.Add(new SECSI4Item(new int[] { eventId }));
            list.Add(new SECSASCIIItem("Enable"));

            message.Items.AddRange(list);
            await passive.SendMessageAsync(message);
        }
        public async Task SendS2F17()  //RequestDateTime
        {
            var message = new SECSMessage
            {
                Stream = 2,
                Function = 17,
                ReplyExpected = true
            };
            await passive.SendMessageAsync(message);
        }
        public async Task SendS6F11()
        {
            var message = new SECSMessage
            {
                Stream = 6,
                Function = 11,
                ReplyExpected = false
            };

            List<SECSItem> list = new List<SECSItem>();
            list.Add(new SECSListItem(2));
            list.Add(new SECSI4Item(new int[] { 1001 }));
            list.Add(new SECSListItem(2));
            list.Add(new SECSASCIIItem("SampleEvent"));
            list.Add(new SECSI2Item(new short[] { 123 }));

            message.Items.AddRange(list);
            await passive.SendMessageAsync(message);
        }
        public async Task SendS10F11()  //TEST
        {
            var message = new SECSMessage
            {
                Stream = 10,
                Function = 11,
                ReplyExpected = false
            };

            List<SECSItem> list = new List<SECSItem>();
            list.Add(new SECSListItem(2));
            list.Add(new SECSU2Item(new ushort[] { 100, 200 }));
            list.Add(new SECSListItem(2));
            list.Add(new SECSASCIIItem("2001"));
            list.Add(new SECSASCIIItem("2002"));

            message.Items.AddRange(list);
            await passive.SendMessageAsync(message);
        }
        #endregion
    }
}

4.2 Active端

新建一个winform项目,引用简单SECS.dll,建立起Passive端。与Passive端只有一行代码有区别,其余完全一致。

active = new SECSEndpoint(endPoint, ConnectionMode.Active);

至此代码部分都写完了,解决方案结构如下

4.3 第三方工具

市面上第三方工具很多,我选了两种来测试,一种是ITRI SECS Emulator,另一种是FASTSim。

4.3.1 ITRI SECS Emulator

下面让Emulator做Passive,简单SECS做Active,建立连接,然后互发几句测试消息。

  • 打开SECS Emulator,设置为Passive端,设置好HSMS的IP地址和端口

  • 读取准备好的SML文件,然后开启通信

  • 把Active端winform项目跑起来,点击连接,然后让Active发送S1F1

  • 让Passive发送复杂测试消息,实际业务中消息不是这样的,此处纯粹是为了测试

没毛病,符合预期。

4.3.2 FASTSim

下面让FASTSim做Active,简单SECS做Passive,建立连接,然后互发几句测试消息。

  • 把Passive端winform项目跑起来,点击连接

  • 打开FASTSim,设置为Active端,设置好HSMS的IP地址和端口

  • 建立通信,然后让Active和Passive相互发送S1F1

  • 让Passive发送复杂测试消息,实际业务中消息不是这样的,此处纯粹是为了测试

没毛病,符合预期。

posted @ 2025-04-30 19:44  tossorrow  阅读(1602)  评论(2)    收藏  举报