在編寫上位機軟體時,需要經常處理命令拼接與其他裝置進行通訊,通常對不同的命令封裝成不同的方法,擴充套件稍許麻煩。
本次擬以特性方式實現,以兼顧維護性與擴充套件性。
思想:
一種命令對應一個類,其類中的各個屬性對應各個命令段,透過特性的方式,實現其在這包資料命令中的位置、大端或小端及其轉換為對應的目標型別;
然後透過反射對其進行拼包,從而得到一包完整資料。
場景:
將一個軸移動到對應的X,Y,Z位置,為了演示,對其共用一個速度
這個移動到指定位置的命令假設按以下順序構成(為了展示,草率的命令結構):
序號 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
位元組 | 2 | s32 | u16 | u16 | u32 | s32 | s32 | s32 | 2 |
說明 | 包頭 | 步驟號(ID) | 功能碼 | 軸 | 速度 | X位置 | Y位置 | Z位置 | 包尾 |
實現:
建立特性 CmdPropertyAttribute
1 [AttributeUsage(AttributeTargets.Property)]
2 internal class CmdPropertyAttribute : Attribute
3 {
4 public Type? TargetType { get; set; }
5
6 public int Number { get; set; }
7
8 public bool IsReverse { get; set; }
9
10 public CmdPropertyAttribute(int number)
11 {
12 Number = number;
13 }
14
15 public CmdPropertyAttribute(int number, Type targetType)
16 {
17 Number = number;
18 TargetType = targetType;
19 }
20
21 public CmdPropertyAttribute(int number, bool isReverse)
22 {
23 Number = number;
24 IsReverse = isReverse;
25 }
26
27 public CmdPropertyAttribute(int number, Type targetType, bool isReverse)
28 {
29 Number = number;
30 IsReverse = isReverse;
31 TargetType = targetType;
32 }
33 }
引數類,每一種命令對應一個引數類,它們繼承於引數基類
建立引數基類 ParamBase ,每種資料都是步驟號處於第一位,特把其放入到基類中
1 public class ParamBase
2 {
3 [CmdProperty(0, true)]
4 public int StepNum { get; set; }
5 }
建立軸列舉 Axis
1 public enum Axis : ushort
2 {
3 Axis_1 = 1,
4
5 Axis_2 = 2,
6 }
建立功能碼列舉 FunctionCode
1 public enum FunctionCode
2 {
3 Move = 1
4 }
建立移動類 MoveParam ,為了更好展示高低位轉換,特對Speed屬性進行反轉
1 public class MoveParam : ParamBase
2 {
3 [CmdProperty(1, typeof(ushort))]
4 public FunctionCode Function { get; init; }
5
6 [CmdProperty(2, typeof(ushort))]
7 public Axis Axis { get; set; }
8
9 [CmdProperty(3, true)]
10 public uint Speed { get; set; }
11
12 [CmdProperty(4)]
13 public int XPoint { get; set; }
14
15 [CmdProperty(5)]
16 public int YPoint { get; set; }
17
18 [CmdProperty(6)]
19 public int ZPoint { get; set; }
20
21 public MoveParam()
22 {
23 Function = FunctionCode.Move;
24 }
25
26 public MoveParam(int stepNum, Axis axis, uint speed, int xPoint, int yPoint, int zPoint)
27 {
28 Function = FunctionCode.Move;
29 StepNum = stepNum;
30 Axis = axis;
31 Speed = speed;
32 XPoint = xPoint;
33 YPoint = yPoint;
34 ZPoint = zPoint;
35 }
36 }
對引數物件進行反射解析,生成對應的資料命令集合
建立擴充套件類 ParamBaseExtensions
1 public static class ParamBaseExtensions
2 {
3 public static byte[] ToCmd(this ParamBase param)
4 {
5 var properties = param.GetType().GetProperties()
6 .Where(x => x.IsDefined(typeof(CmdPropertyAttribute), false))
7 .OrderBy(x => ((CmdPropertyAttribute)x.GetCustomAttribute(typeof(CmdPropertyAttribute))).Number);
8
9 List<byte> result = new();
10
11 foreach (var item in properties)
12 {
13 var cmdAttribute = item.GetCustomAttribute(typeof(CmdPropertyAttribute)) as CmdPropertyAttribute;
14
15 var value = item.GetValue(param);
16
17 if (cmdAttribute.TargetType is not null)
18 {
19 value = Convert.ChangeType(value, cmdAttribute.TargetType);
20 }
21
22 var propertyBytes = value.ToBytes();
23
24 if (cmdAttribute.IsReverse)
25 propertyBytes = propertyBytes.Reverse().ToArray();
26
27 result.AddRange(propertyBytes);
28 }
29
30 return result.ToArray();
31 }
32
33
34 private static byte[] ToBytes(this object obj)
35 {
36 return obj switch
37 {
38 short s => BitConverter.GetBytes(s),
39 ushort s => BitConverter.GetBytes(s),
40 int s => BitConverter.GetBytes(s),
41 uint s => BitConverter.GetBytes(s),
42 float s => BitConverter.GetBytes(s),
43 double s => BitConverter.GetBytes(s),
44 byte s => [s],
45 _ => throw new NotImplementedException(),
46 };
47 }
48 }
將資料命令與包頭,包尾拼接,從而組合成一包完整資料
建立類 CmdHelper
1 public class CmdHelper
2 {
3 private byte[] GetHeads()
4 {
5 return [0x0B, 0x0F];
6 }
7
8
9 private byte[] GetTails()
10 {
11 return [0x0C, 0x0A];
12 }
13
14 public byte[] BuilderCmd(ParamBase param)
15 {
16 return
17 [
18 .. GetHeads(),
19 .. param.ToCmd(),
20 .. GetTails(),
21 ];
22 }
23 }
呼叫:
1 var cmdHelper = new CmdHelper();
2 var param = new MoveParam()
3 {
4 XPoint = 14,
5 YPoint = 14,
6 ZPoint = 14,
7 Axis = Enums.Axis.Axis_1,
8 Speed = 20,
9 StepNum = 1
10 };
11 var byteArr = cmdHelper.BuilderCmd(param);
12
13 foreach (var item in byteArr)
14 {
15 Console.Write(item.ToString("X2") + " ");
16 }
最後的列印結果為:
0B 0F 00 00 00 01 00 00 01 00 00 00 00 14 0E 00 00 00 0E 00 00 00 0E 00 00 00 0C 0A
如果後續在寫其他命令,只需繼承於 ParamBase 類,在對應的屬性上使用 CmdProperty 特性即可