将字节数组解析为XML

本文关键字:XML 数组 字节 字节数 | 更新日期: 2023-09-27 18:17:25

我有一个带有几个命令的串行通信协议,所有这些命令都在一个文本文件中指定。我有一个提供基本通信方法的库,但是我想添加解析,而不必硬编码指定数据包格式的所有结构。从我所发现的,这可能是使用XML或JSON,但我不知道从哪里开始。

在我的文本文件中指定的命令格式的一个例子是:

[255]
Name = Get Relay State
Param1 = Relay Number, uint8_t
Result1 = Relay State, uint8_t

因此,该命令请求获得中继1的字节数组将是FF 01,其响应将是FF 00(表示中继打开)。

这是一个高度简化的例子,因为有数百个这样的命令,每个命令都有许多输入和输出。

是否有一些方法我可以提取值,字段名和命令名,而不显式地声明每个命令中的每个输入和输出的struct ?到目前为止,我已经这样做了几个,但即使使用代码生成工具,它也不能灵活地更改协议。

将字节数组解析为XML

您可以使用我编写的类来解析您定义的串行协议。我还没有测试过这段代码,所以可能需要进行一些调整才能使其运行。这是基于我在下面定义的XML模式:

<?xml version="1.0" encoding="utf-8" ?>
<protocol>
  <!-- Get Relay State (255) -->
  <command name="GetRelayState" value="255">
    <parameters>
      <parameter name="RelayNumber" type="Byte"/>
    </parameters>
    <results>
      <result name="RelayState" type="Byte"/>
    </results>
  </command>
  <!-- Get Voltage (254) -->
  <command name="GetVoltage" value="254">
    <parameters>
      <parameter name="CircuitNumber" type="Short"/>
    </parameters>
    <results>
      <result name="Voltage" type="Single"/>
      <result name="MaxVoltage" type="Single"/>
      <result name="MinVoltage" type="Single"/>
    </results>
  </command>
</protocol>
下面是c#代码
public class ProtocolManager
{
    public event Action<Command> ResponseReceived;
    // This will read the XML definition of your protocol into a Protocol object
    public Protocol BuildProtocol()
    {
        var protocol = new Protocol
        {
            Commands = new List<Command>()
        };
        var xmlFile = new XmlDocument();
        xmlFile.Load(
            Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Protocol.xml"));
        var protocolElement = xmlFile["protocol"];
        foreach (XmlNode command in protocolElement.ChildNodes)
        {
            // Load the command definition
            var comm =
                new Command
                {
                    Name = command.Attributes["name"].Value,
                    Value = int.Parse(command.Attributes["value"].Value),
                    Parameters = new List<Parameter>(),
                    Results = new List<Result>()
                };
            // Load the list of parameters
            foreach (XmlNode p in command.SelectSingleNode("parameters").ChildNodes)
            {
                comm.Parameters.Add(
                    new Parameter
                    {
                        Name = p.Attributes["name"].Value,
                        ParamType = Type.GetType(p.Attributes["type"].Value)
                    });
            }
            // Load the list of expected results
            foreach (XmlNode p in command.SelectSingleNode("results").ChildNodes)
            {
                comm.Parameters.Add(
                    new Parameter
                    {
                        Name = p.Attributes["name"].Value,
                        ParamType = Type.GetType(p.Attributes["type"].Value)
                    });
            }
            protocol.Commands.Add(comm);
        }
        // You should now have a complete protocol which you can bind to UI controls
        // such as dropdown lists, and be able to parse
        return protocol;
    }
    // This will test the incoming stream for responses (packets)
    public void TestProtocol(SerialPort port, Protocol p)
    {
        int bytesRead = 0;
        var buffer = new byte[1024];
        do
        {
            Thread.Sleep(50); // Give the port 50ms to receive a full response
            bytesRead = port.Read(buffer, 0, buffer.Length);
            // For the sake of simplicity assume we read the whole packet in a single read and we don't 
            // need to look for message encapsulation (STX, ETX).
            var commandValue = buffer[0];
            var command = p.Commands.SingleOrDefault(c => c.Value == commandValue);
            if (command == null)
            {
                throw new NotSupportedException(
                    String.Format("Unknown command received {0}", commandValue));
            }
            // Read result parameters
            int index = 1;
            foreach (var r in command.Results)
            {
                // Here you need to implement every data type you are expecting to receive
                // I have done 2
                switch (r.ResultType.Name)
                {
                    case "Byte":
                        r.Value = buffer[index];
                        index++; // Reading only a single byte
                        break;
                    case "Short":
                        r.Value = (float)(buffer[index] >> 8 | buffer[index]);
                        index += 2; // Reading a 16bit short is 2 bytes
                        break;
                    default:
                        throw new NotSupportedException(
                            String.Format("Unknown response type {0}", r.ResultType.Name));
                }
            }
            // Notify listening client that we received a message response
            // and it will contain the response parameters with values
            var evt = ResponseReceived;
            if (evt != null)
            {
                evt(command);
            }
        } while (bytesRead > 0);
        // End of read / wait for next command to be sent
    }
}
// Represents your serial protocol as a list of support commands 
// with request/response parameters
public  class Protocol
{
    public List<Command> Commands { get; set; }
    // Used to data-bind to comboboxes etc.
    public List<string> GetCommandNames()
    {
        return Commands.Select(c => c.Name).OrderBy(c => c).ToList();
    }
}
// A single command with a name, value and list of request/response parameters
public class Command
{
    public string Name { get; set; }
    public int Value { get; set; }
    public List<Parameter> Parameters { get; set; }
    public List<Result> Results { get; set; }
}
public class Parameter
{
    public string Name { get; set; }
    public Type ParamType { get; set; }
    public object Value { get; set; }
}
public class Result
{
    public string Name { get; set; }
    public Type ResultType { get; set; }
    public object Value { get; set; }
}