与下位机或设备的通信解析优化的一点功能:T4+动态编译

原文:与下位机或设备的通信解析优化的一点功能:T4+动态编译

    去年接触的一个项目中,需要通过TCP与设备进行对接的,传的是Modbus协议的数据,然后后台需要可以动态配置协议解析的方式,即寄存器的解析方式,,配置信息有:Key,数据Index,源数据类型,数据库列类型,数据排列方式

一开始使用的方式是,从数据库读取出协议的配置,然后在接收到数据的时候,循环每个配置项根据配置-----解析数据------转换类型----存临时列表,,后来改进了一下,配置项存缓存,,数据库修改的时候,同时更新缓存。。

但还是慢了一点,因为需一个配置大概是20-30个数据项,每一条数据都要for循环20-30次 ,再加上还有N个根据配置的数据类型去做转换的if判断,这么一套下来,也很耗时间,但待解析的数据量大的情况下,,相对也很耗资源。。。

最后的觉得方案是:利用T4生成C#的class源码+运行时编译成类,数据直接扔class里直接解析出结果,不需要循环,也不需要if判断,因为在t4生成源码的时候,已经根据配置处理完了,因此节省了很多的时间。

不过由于T4模板的IDE支持的很不好,不过好在运行时T4模板在IDE内生成出来的类是partial的,因此,可以把大部分的代码,放在外部的C#文件里。先来看数据项的配置信息:

 1  public class DataItem
 2         {
 3             /// <summary>
 4             ///  数据项ID
 5             /// </summary>
 6             public ObjectId DataItemID { set; get; }
 7
 8             /// <summary>
 9             /// 偏移量
10             /// </summary>
11             public int Pos { set; get; }
12
13             /// <summary>
14             /// 大小
15             /// </summary>
16             public int Size { set; get; }
17
18             public int BitIndex { set; get; }
19
20             /// <summary>
21             /// 数据项数据库储存类型
22             /// </summary>
23             public DbDataTypeEnum DbType { set; get; }
24
25             /// <summary>
26             /// 数据项协议源字节数组中的数据类型
27             /// </summary>
28             public DataTypeEnum SourceType { set; get; }
29
30             /// <summary>
31             /// 计算因子
32             /// </summary>
33             public decimal Factor { set; get; }
34
35             public string Key { set; get; }
36         }
37
38     /// <summary>
39     /// 对应的数据库字段类型
40     /// </summary>
41     public enum DbDataTypeEnum
42     {
43         Int32 = 0,
44
45         Int64 = 1,
46
47         Double = 2,
48
49         DateTime = 3,
50
51         Decimal = 4,
52
53         Boolean = 5
54     }
55
56     public enum DataTypeEnum
57     {
58         Int = 0,
59
60         Short = 1,
61
62         Datetime = 3,
63
64         Long = 5,
65
66         Decimal = 6,
67
68         UInt = 7,
69
70         Byte = 8,
71
72         Boolean = 9,
73
74         Bit = 10,
75
76         UShort = 11,
77
78         UByte = 12
79     }

这里为什么要区分源数据和数据库数据类型呢?主要是因为设备一般是int,short,double,float等类型,但是,对应到数据库,有时候比如说使用mongodb,之类的数据库,不一定有完全匹配的,因此需要区分两种数据项,

再来就是T4的模板  ProtocolExecuteTemplate.tt:

 1 <#@ template language="C#" #>
 2 <#@ assembly name="System.Core" #>
 3 <#@ assembly name="Kugar.Core.NetCore" #>
 4 <#@ assembly name="Kugar.Device.Service.BLL" #>
 5 <#@ assembly name="Kugar.Device.Service.Data" #>
 6 <#@ assembly name="MongoDB.Bson" #>
 7
 8 <#@ import namespace="System.Linq" #>
 9 <#@ import namespace="System.Text" #>
10 <#@ import namespace="System.Collections.Generic" #>
11 <#@ import namespace="Kugar.Core.BaseStruct" #>
12 <#@ import namespace="MongoDB.Bson" #>
13
14 using System;
15 using System.Text;
16 using Kugar.Core.BaseStruct;
17 using Kugar.Core.ExtMethod;
18 using Kugar.Core.Log;
19 using Kugar.Device.Service.Data.DTO;
20 using Kugar.Device.Service.Data.Enums;
21 using MongoDB.Bson;
22
23 namespace Kugar.Device.Service.BLL
24 {
25     <#
26         var className="ProtocolExecutor_" + Protocol.Version.Replace(‘.‘,‘_‘) + "_" + this.GetNextClasID();
27  #>
28
29
30     public class <#=className #>:IProtocolExecutor
31     {
32         private string _version="";
33         private ObjectId _protocolID;
34         private readonly DateTime _baseDt=TimeZone.CurrentTimeZone.ToLocalTime(new System.DateTime(1970, 1, 1));
35
36         public <#=className #> (ObjectId protocolID, string version)
37         {
38             _version=version;
39             _protocolID=protocolID;
40         }
41
42         public ObjectId ProtocolID {get{return _protocolID;}}
43
44         public BsonDocument Execute(byte[] data, int startIndex)
45         {
46             BsonDocument bson=new BsonDocument();
47             <#
48                 foreach(var item in Protocol.Items){ #>
49             bson["<#=item.Key #>"]= <#= DecodeConfig(item,0) #>;
50 <#
51                 }
52  #>
53
54             return bson;
55
56         }
57     }
58 }

在直接在循环里输出解析后的语句,并且生成的类名记得后面加多一个随机数。。。。

然后再加一个ProtocolExecuteTemplate.Part.cs的部分类,补全T4模板的功能,因为在T4里IDE支持的不好,,写代码确实难受,,没直接写C#舒服:

  1 public partial class ProtocolExecuteTemplate
  2     {
  3         private static int _classID = 0;
  4
  5         public ProtocolExecuteTemplate(DTO_ProtocolDataItem protocol)
  6         {
  7             Protocol = protocol;
  8
  9         }
 10
 11
 12
 13         public DTO_ProtocolDataItem Protocol { set; get; }
 14
 15         public string DecodeConfig(DTO_ProtocolDataItem.DataItem item,int startIndex)
 16         {
 17             var str = "";
 18
 19             switch (item.SourceType)
 20             {
 21                 case DataTypeEnum.Int:
 22                     str = $"BitConverter.ToInt32(data,startIndex + {startIndex + item.Pos})";
 23                     break;
 24                 case DataTypeEnum.UInt:
 25                     str = $"BitConverter.ToUInt32(data,startIndex + {startIndex + item.Pos})";
 26                     break;
 27                 case DataTypeEnum.Short:
 28                     str = $"BitConverter.ToInt16(data,startIndex + {startIndex + item.Pos})";
 29                     break;
 30                 case DataTypeEnum.Long:
 31                     str= $"BitConverter.ToInt64(data,startIndex + {startIndex + item.Pos})";
 32                     break;
 33                 case DataTypeEnum.Byte:
 34                 case DataTypeEnum.UByte:
 35                 case DataTypeEnum.Bit:
 36                     str = $"data[startIndex + {startIndex + item.Pos}]";
 37                     break;
 38                 case DataTypeEnum.UShort:
 39                     str = $"BitConverter.ToUInt16(data,startIndex+{startIndex + item.Pos})";
 40                     break;
 41                 default:
 42                     throw new ArgumentOutOfRangeException();
 43             }
 44
 45             if (item.SourceType==DataTypeEnum.Bit)
 46             {
 47                 return byteToBit(str, item.BitIndex);
 48             }
 49             else
 50             {
 51                 return valueTODBType(str, item.Factor, item.DbType);
 52             }
 53         }
 54
 55         private string valueTODBType(string sourceValue, decimal factor, DbDataTypeEnum dbType)
 56         {
 57             switch (dbType)
 58             {
 59                 case DbDataTypeEnum.Int32:
 60                     return $" new BsonInt32({(factor > 0 ? $"(int)({factor}{getDecimalShortChar(factor)} * {sourceValue})" : sourceValue)})";
 61                 case DbDataTypeEnum.Int64:
 62                     return $" new BsonInt64({(factor > 0 ? $"(long)({factor}{getDecimalShortChar(factor)} * {sourceValue})" : sourceValue)})";
 63                 case DbDataTypeEnum.Double:
 64                     return $"new BsonDouble({(factor > 0 ? $"(double)({factor}{getDecimalShortChar(factor)} * {sourceValue})" : $"(double){sourceValue}")})";
 65                 case DbDataTypeEnum.DateTime:
 66                     return $"new BsonDateTime(_baseDt.AddSeconds({sourceValue}))";
 67                 case DbDataTypeEnum.Decimal:
 68                     return $"new Decimal128({(factor > 0 ? $"(decimal)({factor}{getDecimalShortChar(factor)}  * {sourceValue})" : sourceValue)})";
 69                 default:
 70                     throw new ArgumentOutOfRangeException(nameof(dbType), dbType, null);
 71             }
 72         }
 73
 74         private string byteToBit(string data, int index)
 75         {
 76             switch (index)
 77             {
 78                 case 0:
 79                 {
 80
 81                     return $"(({data} & 1) ==1)";//(data & 1) == 1;
 82                 }
 83                 case 1:
 84                 {
 85                     return $"(({data} & 2) ==1)";// (data & 2) == 2;
 86                     }
 87                 case 2:
 88                 {
 89                     return $"(({data} & 4) ==1)";//(data & 4) == 4;
 90                     }
 91                 case 3:
 92                 {
 93                     return $"(({data} & 8) ==1)";//(data & 8) == 8;
 94                     }
 95                 case 4:
 96                 {
 97                     return $"(({data} & 16) ==1)";//(data & 16) == 16;
 98                     }
 99                 case 5:
100                 {
101                     return $"(({data} & 32) ==1)";//(data & 32) == 32;
102                     }
103                 case 6:
104                 {
105                     return $"(({data} & 64) ==1)";//(data & 64) == 64;
106                     }
107                 case 7:
108                 {
109                     return $"(({data} & 128) ==1)";//(data & 128) == 128;
110                     }
111                 default:
112                     throw new ArgumentOutOfRangeException(nameof(index));
113             }
114
115             return $"(({data} & {index + 1}) ==1)";
116         }
117
118         /// <summary>
119         /// 用于判断传入的fator是否需要使用deciaml进行运算,如果有小数点的,则是否decimal缩写m,,如果没有小数点,则使用普通的int类型
120         /// </summary>
121         /// <param name="value"></param>
122         /// <returns></returns>
123         private string getDecimalShortChar(decimal value)
124         {
125             return (value % 1) == 0 ? "" : "m";
126         }
127
128         public int GetNextClasID()
129         {
130             return Interlocked.Increment(ref _classID);
131         }
132     }

这样,在运行时,即可直接生成可用于解析的类了,而且也不需要for循环判断,生成出来的类如:

 1     public class ProtocolExecutor_1_1_000
 2     {
 3         public BsonDocument Execute(byte[] data, int startIndex)
 4         {
 5             BsonDocument bson = new BsonDocument();
 6
 7             bson["项目名1"] = new BsonInt32(BitConverter.ToInt32(data, startIndex + 偏移量));
 8             bson["项目名2"] = new BsonInt32(BitConverter.ToInt32(data, startIndex + 偏移量));
 9               。。。。。。。 //其他数据项
10
11             return bson;
12         }
13     }

到这一步,就可以根绝配置项生成出对应的C#代码了,剩下的就是动态编译的事情了、将该代码编译出运行时Type,然后传入数据----解析出数据

原文地址:https://www.cnblogs.com/lonelyxmas/p/12345308.html

时间: 2024-07-28 14:59:30

与下位机或设备的通信解析优化的一点功能:T4+动态编译的相关文章

与下位机或设备的通信解析优化的一点功能(续补):动态编译

原文:与下位机或设备的通信解析优化的一点功能(续补):动态编译 继上一篇<与下位机或设备的通信解析优化的一点功能:T4+动态编译>  ,现在已经生成出解析用的类的C#源码了,接下来,就轮到动态编译生成Type了. 在实现上,.net framework和.net core上,有些不同: .Net Framework的: 1 var transfer = ""; //解析后的C#源码字符串 2 3 ICodeCompiler comp = new CSharpCodeProv

12.PMAC上位机-中断通信(下位机通知上位机)

之前说过,上位机控制硬件都是通过发送指令进行的,这是单向的.那么反过来,下位机硬件给上位机发送指令怎么才能实现呢?对于PMAC来说,最常见的情况就是,当PMAC一次运动完成时通知当前PC程序运动已完成. 1.下位机通知上位机原理 如下图所示, PC给PMAC发送指令控制它做什么,当运动程序完成后中断通知上位机,在中断的同时发送一个标识P100表明完成的程序号,这个标明到底是1号轴运动完,还是是2号轴运动完.在PC端有一个中断回调函数,下位机发送中断后,会自动跳转到该函数中,中断函数接受中断,根据

局域网控制系统-下位机-单片机

1 /*----------------------------------- 2 多功能下位机 3 STC89C52RC 11.0592MHz 4 5 -----------------------------------*/ 6 #include<reg52.h> 7 #include<intrins.h> 8 9 char code huanhang[3]={0x0d,0x0a,0}; // "\r\n" 10 //-----------------普通输

ios申请真机调试( xcode 5)详细解析(转载)

目录[-] 第一步:申请"开发证书" 第二步:添加App IDs 第三步:申请设备 第四步:申请描述文件 第一步:申请"开发证书" 进入苹果开发者99美元账号: 选择:Certificates, Identifiers & Profiles 关于 Certificates,Identifiers,Profiles 的介绍请看 "Certificates, Identifiers ,Profiles" 选择:Certificates 选择:

下位机单片机c语言发送数据到串口,上位机pc机java语言获取端口数据

环境: Windows7 64b,jdk64b,myeclipse8.5,rxtx开发包,STC,keil,格西烽火,51单片机,rs232USB转串口线. 下位机c代码 #include <reg51.h> #include <string.h> #define INBUF_LEN 7   //数据长度 unsigned char inbuf1[INBUF_LEN]; unsigned char checksum,count3,count=0; bit           rea

MSP430G2333下位机乘法运算需要注意的一个问题

背景: 最近负责为主板管理电源的电源管理模块编写软体,使用的MCU为MSP430G2333.功能上很简单,即通过板子上的硬件拨码设定,或者通过IIC与主板通信,由主板的BIOS决定开机及关机的延时供电时间. 正文: 所有功能均按预期实现,但有一个bug在最后蹦了出来,即在延时关机设定时,明明设定为15.18.48个小时,却每次在9个多小时的时候,电源被执行关闭动作.9个多小时,很有意思的数字.不用深究,问题直接锁定在时间比对函数内. 1 // when the current time less

上位机 下位机

上位机是指:人可以直接发出操控命令的计算机,一般是PC,屏幕上显示各种信号变化(液压,水位,温度等).下位机是直接控制设备获取设备状况的的计算机,一般是PLC/单片机之类的.上位机发出的命令首先给下位机,下位机再根据此命令解释成相应时序信号直接控制相应设备.下位机不时读取设备状态数据(一般模拟量),转化成数字信号反馈给上位机.简言之如此,真实情况千差万别不离其宗.上下位机都需要编程,都有专门的开发系统. 在概念上 控制者和提供服务者是上位机 被控制者和被服务者是下位机 也可以理解为主机和从机的关

4.PMAC下位机-工具安装和使用

PMAC应该看成一台独立的电脑,它有自己的实时操作系统,为了更方便简单的使用,PMAC有自己的一套操作语言来对PMAC的输入输出信号进行控制(IO.电机等信号).和大多数的嵌入式开发一样,PMAC提供一套PC工具来方便查看PMAC控制卡的各种状态变量和编写.调试PMAC程序. 本文介绍PMAC配套工具PEWin32(这里推荐使用PEWin32 PRO,不推荐使用PEWin32 PRO 2,这个新版本有很多bug且不稳定),下一节介绍下位机相关知识,再下一节讲PMAC的下位机编程,这些都是在pew

C# 连蒙带骗不知所以然的搞定USB下位机读写

原文:C# 连蒙带骗不知所以然的搞定USB下位机读写 公司用了一台发卡机,usb接口,半双工,给了个dll,不支持线程操作,使得UI线程老卡. 懊恼了,想自己直接通过usb读写,各种百度,然后是无数的坑,最终搞定. 现将各种坑和我自己的某些猜想记录一下,也供各位参考. 一.常量定义 private const short INVALID_HANDLE_VALUE = -1; private const uint GENERIC_READ = 0x80000000; private const u