我有一个带有几个命令的串行通信协议,所有命令都在文本文件中指定。我有一个提供基本通信方法的库,但我想添加解析而不必硬编码指定数据包格式的所有结构。根据我的发现,这可能是使用XML或JSON,但我不知道从哪里开始。
我的文本文件中指定的命令格式示例如下:
[255]
Name = Get Relay State
Param1 = Relay Number, uint8_t
Result1 = Relay State, uint8_t
因此,字节数组中此命令获取中继1的请求将为FF 01
,其响应类似于FF 00
(表示中继已打开)。
这是一个高度简化的例子,因为有数百个这些命令,每个命令都有许多输入和输出。
我是否可以通过某种方式提取值,字段名称和命令名称,而无需为每个命令中的每个输入和输出明确声明struct
?到目前为止,我已经完成了几个,但即使使用代码生成工具,协议的更改也不灵活。
答案 0 :(得分:1)
您可以使用我编写的类来解析您定义的串行协议。我还没有测试过这段代码,所以可能需要进行一些调整才能让它运行起来。这基于我在下面定义的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; }
}