在编写上位机软件时,需要经常处理命令拼接与其他设备进行通信,通常对不同的命令封装成不同的方法,扩展稍许麻烦。

本次拟以特性方式实现,以兼顾维护性与扩展性。


思想:

一种命令对应一个类,其类中的各个属性对应各个命令段,通过特性的方式,实现其在这包数据命令中的位置、大端或小端及其转换为对应的目标类型;

然后通过反射对其进行拼包,从而得到一包完整数据。

场景:

将一个轴移动到对应的X,Y,Z位置,为了演示,对其共用一个速度

这个移动到指定位置的命令假设按以下顺序构成(为了展示,草率的命令结构):

序号 1 2 3 4 5 6 7 8 9
字节 2 u32 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
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  特性即可

C# 使用特性的方式封装报文的更多相关文章

  1. PHP 开发 APP 接口 学习笔记与总结 - XML 方式封装通信接口

    1.PHP 生成 XML 数据 ① 拼接字符串 ② 使用系统类(DomDocument,XMLWriter,SimpleXML) 例1 使用 PHP 系统类中的 DomDocument 类: < ...

  2. PHP 开发 APP 接口 学习笔记与总结 - JSON 方式封装通信接口

    1.通信数据的标准格式 ( JSON ),包括: code:状态码(200,400等) message:提示信息(例如:数据返回成功.邮箱格式错误等) data:返回数据 2.JSON 方式封装通信接 ...

  3. js面向对象学习笔记(二):工厂方式:封装函数

    //工厂方式:封装函数function test(name) { var obj = new Object(); obj.name = name; obj.sayName = function () ...

  4. xml方式封装数据方法

    1.xml方式封装数据方法 2.demo <?php xml方式封装数据方法 /** * [xmlEncode description] * @param [type] $code [descr ...

  5. xml方式封装通信数据方法

    xml方式封装通信数据方法 public static function xmlToEncode($data) { $xml = ""; foreach($data as $key ...

  6. 用DLL方式封装MDI子窗体

    用DLL方式封装MDI子窗体是一种常用的软件研发技术,他的长处: 研发人员能够负责某一个模块的编写包括(界面+逻辑),能够互不干扰,模块研发完成后,主程式统一调用. 易于程式升级,当程式升级时,不用编 ...

  7. 用Typescript 的方式封装Vue3的表单绑定,支持防抖等功能。

    Vue3 的父子组件传值.绑定表单数据.UI库的二次封装.防抖等,想来大家都很熟悉了,本篇介绍一种使用 Typescript 的方式进行统一的封装的方法. 基础使用方法 Vue3对于表单的绑定提供了一 ...

  8. App接口中json方式封装通信接口

    封装json通信接口的类 <?php class Response{ /** * 按json方式输出通信数据 * @param integer $code状态码 * @param string ...

  9. PHP 开发 APP 接口 学习笔记与总结 - JSON 结合 XML 方式封装通信接口

    要求: 1.在一个类中封装多种数据通信方法(JSON,XML),并且只通过一个入口选择需要的数据通信格式 2.客户端开发工程师可以自行选择数据传输格式(GET 方式) response.php < ...

  10. okhttp 常用使用方式 封装 演示

    工具介绍 使用: AndroidStudio:[compile 'com.squareup.okhttp3:okhttp:3.4.2']和[compile 'com.zhy:okhttputils:2 ...

随机推荐

  1. MYSQL8.0-JSON函数简单示例-JSON_EXTRACT|JSON_VALUE|JSON_TABLE

    JSON类型在日常应用开发中,用得很少,个人通常用于存储常常变化的配置参数. 它适用于什么业务场景,不好说.就好像许多年前读到的一篇文章,说有个国外公司利用ORACLE的CLOB/BLOB管理一些信息 ...

  2. EMQX配置ssl/tls双向认证+SpringBoot项目整合MQTT_真实业务实践

    一.使用docker搭建Emqx 1.拉取emqx镜像 docker pull emqx/emqx:5.7 2.运行 docker run -d --name emqx emqx/emqx:5.7 3 ...

  3. Linux 内核:设备驱动模型 学习总结

    背景 其实之前就转载过别人针对Linux的设备驱动模型(Linux Device Driver Model,LDDM)的文章,但是受限于自身的能力,因此花了点时间重新学习了一下. 前人写的文章很好,我 ...

  4. 一文带你深入理解SpringMVC的执行原理

    今天大致来看一下Spring MVC的执行流程是什么样的 执行流程:也就是一个请求是怎么到我们Controller的,返回值是怎么给客户端的 本文分析的问题: 文件上传的请求是怎么处理的 跨域是怎么处 ...

  5. java 编程思想--个人总结

    从应用开始思考----思考解题思路--将思路分解成一步一步的步骤-----根据每一步的步骤思考如何用代码实现-- -- 不要心急,可以一块一块来完成-- 最后再思考如何用代码实现每两块之间的连接--- ...

  6. JVM(Java虚拟机) 整理(一):基础理论

    JVM整体结构 本文主要说的是HotSpot虚拟机, JVM 全称是 Java Virtual Machine,中文译名:Java虚拟机 简化一下: Java字节码文件 Class文件本质上是一个以8 ...

  7. 劫持TLS绕过canary && 堆和栈的灵活转换

    引入:什么是TLScanary? TLScanary 是一种在 Pwn(主要是二进制漏洞利用)中常见的技术,专门用于处理 TLS 保护的二进制文件.在安全竞赛(例如 CTF)和漏洞利用场景中,攻击者需 ...

  8. Solo开发者社区-H5-Dooring, 开箱即用的零代码搭建平台

    Dooring-Saas 是一款功能强大,高可扩展的零代码解决方案,致力于提供一套简单方便.专业可靠.无限可能的页面可视化搭建最佳实践.(Solo社区 投稿) 功能特点 可扩展, Dooring 实现 ...

  9. 解决方案 | MiKTex SSL connect error code 35

    可能是:你的网络屏蔽了需要连接的网站,更换手机流量热点即可解决

  10. 在windows双系统中,nginx配置虚拟域名

    比如在ubuntu系统中,nginx配置了域名www.abc.com, 那么需要在终端 sudo vim /etc/hosts文件中配置域名,如下: 127.0.0.1 www.abc.com 即可访 ...