解码字节数组

Decode byte array

我想解码从 modbus 通信接收到的字节数组。这是表示字节数组的十六进制字符串:

01 11 0C 46 57 35 32 39 37 30 31 52 30 2E 35 FE 27

我想分成三部分:

  1. 01 11 0C
  2. 46 57 35 32 39 37 30 31 52 30 2E 35
  3. FE 27

为了从字节转换为十六进制,我使用了这个方法:

 #region ByteToHex
    /// <summary>
    /// method to convert a byte array into a hex string
    /// </summary>
    /// <param name="comByte">byte array to convert</param>
    /// <returns>a hex string</returns>
    public string ByteToHex(byte[] comByte)
    {
        //create a new StringBuilder object
        StringBuilder builder = new StringBuilder(comByte.Length * 3);
        //loop through each byte in the array
        foreach (byte data in comByte)
            //convert the byte to a string and add to the stringbuilder
            builder.Append(Convert.ToString(data, 16).PadLeft(2, '0').PadRight(3, ' '));
        //return the converted value
        return builder.ToString().ToUpper();
    }

如何将 returned 字符串从该方法拆分为 return: 前 3 个字节是:

  1. 服务器编号
  2. 函数代码
  3. 字节数

接下来的N个字节(见3.)是载荷;最后 2 个是 CRC16。

我需要找到负载;它是一个字符串。

string str = ByteToHex(comByte);
string partOne = str.Substring(0, 8);
string partTwo = str.Substring(9, 35);
string partThree = str.Substring(45, 5);

部分答案:

如果返回的字节表示结构化数据,您应该将所有字节转换为字符串。而是根据字节的含义转换字节:

int serverId = Convert.ToInt32(byte[0]);
int functionCode = Convert.ToInt32(byte[1]);
int byteCount = Convert.ToInt32(byte[2]);

一旦你知道了数据部分的长度,你就可以解码这个数据:

for(int i = 0; i < byteCount; i++)
{
    // do something with byte[3+i]
}

首先更改现有 ByteToHex 方法的签名,使输入参数为 IEnumerable<byte> 而不是 byte[]

然后使用:

var str1 = ByteToHex(new ArraySegment<byte>(yourBytes, 0, 3));
var str2 = ByteToHex(new ArraySegment<byte>(yourBytes, 3, 12));
var str3 = ByteToHex(new ArraySegment<byte>(yourBytes, 15, 2));

自 .NET 4.5(Visual Studio 2012)起可以使用 ArraySegment<>


PS!可以简化方法的整个主体:

public static string ByteToHex(IEnumerable<byte> comByte)
{
  return string.Join(" ", comByte.Select(b => b.ToString("X2")));
}

这里的大写 X 表示十六进制格式,大写 "digits" A 到 F, 2 表示用零填充长度为 2。

如果要拆分字节数组,请直接在数组上进行 - 不要事先转换为字符串!生成的代码将更容易理解。

之后您仍然可以将它的一部分转换为十六进制字符串,或者在您的情况下转换为带有编码而不是十六进制的文本字符串。

对于子数组,我有这个小扩展:

// Returns the SubArray starting from index and covering the amount of length items.
public static T[] SubArray<T>(this T[] data, int index, int length)
{
    T[] result = new T[length];
    System.Array.Copy(data, index, result, 0, length);
    return result;
}

正如另一个答案中提到的那样,自 .NET 4.5 以来,您也可以改用新的 ArraySegment

有了这个,你可以像这样简单地拆分:

const int HEADER_LENGTH = 3; //1 Byte ServerId, 1 Byte Function Code, 1 Byte ByteCount
const int CRC_LENGTH = 2;

var bytes = new byte[]{0x01, 0x11,  0x0C, 0x46, 0x57, 0x35, 0x32, 0x39, 0x37, 0x30, 0x31, 0x52, 0x30, 0x2E, 0x35, 0xFE, 0x27};
int payloadLength = bytes.Count() - HEADER_LENGTH - CRC_LENGTH;

//.NET 4.5 solution
var textbytes = new ArraySegment<byte>(bytes, HEADER_LENGTH, payloadLength).ToArray();

//pre 4.5 solution with extension
var textbytes = bytes.SubArray(HEADER_LENGTH, payloadLength);

var text = Encoding.ASCII.GetString(textbytes); //FW529701R0.5

将输入转为十六进制字符串意义不大。您需要解码字节包含的信息。他们持有的内容完全取决于您只是解释的文档。我会试一试:

public class ModbusRequest
{
    public byte ServerId { get; set; }
    public byte FunctionCode { get; set; }
    public string Payload { get; set; }
}

public ModbusRequest DecodeMessage(byte[] message)
{
    var result = new ModbusRequest();

    // Simply copy bytes 0 and 1 into the destination structure.
    result.ServerId = message[0];
    result.FunctionCode = message[1];

    byte stringLength = message[2];

    // Assuming ASCII encoding, see docs.
    result.Payload = Encoding.ASCII.GetString(message, 3, stringLength);

    // Get the CRC bytes.
    byte[] crc = new byte[2];
    Buffer.BlockCopy(message, 4 + stringLength, crc, 0, 2);

    // TODO: verify CRC.

    return result;

}

要验证 CRC,找出正在使用的格式。我建议使用 classless-hasher 来实现各种 CRC 变体。