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

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

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

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

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

  public class DataItem
{
/// <summary>
/// 数据项ID
/// </summary>
public ObjectId DataItemID { set; get; } /// <summary>
/// 偏移量
/// </summary>
public int Pos { set; get; } /// <summary>
/// 大小
/// </summary>
public int Size { set; get; } public int BitIndex { set; get; } /// <summary>
/// 数据项数据库储存类型
/// </summary>
public DbDataTypeEnum DbType { set; get; } /// <summary>
/// 数据项协议源字节数组中的数据类型
/// </summary>
public DataTypeEnum SourceType { set; get; } /// <summary>
/// 计算因子
/// </summary>
public decimal Factor { set; get; } public string Key { set; get; }
} /// <summary>
/// 对应的数据库字段类型
/// </summary>
public enum DbDataTypeEnum
{
Int32 = , Int64 = , Double = , DateTime = , Decimal = , Boolean =
} public enum DataTypeEnum
{
Int = , Short = , Datetime = , Long = , Decimal = , UInt = , Byte = , Boolean = , Bit = , UShort = , UByte =
}

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

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

 <#@ template language="C#" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="Kugar.Core.NetCore" #>
<#@ assembly name="Kugar.Device.Service.BLL" #>
<#@ assembly name="Kugar.Device.Service.Data" #>
<#@ assembly name="MongoDB.Bson" #> <#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="Kugar.Core.BaseStruct" #>
<#@ import namespace="MongoDB.Bson" #> using System;
using System.Text;
using Kugar.Core.BaseStruct;
using Kugar.Core.ExtMethod;
using Kugar.Core.Log;
using Kugar.Device.Service.Data.DTO;
using Kugar.Device.Service.Data.Enums;
using MongoDB.Bson; namespace Kugar.Device.Service.BLL
{
<#
var className="ProtocolExecutor_" + Protocol.Version.Replace('.','_') + "_" + this.GetNextClasID();
#> public class <#=className #>:IProtocolExecutor
{
private string _version="";
private ObjectId _protocolID;
private readonly DateTime _baseDt=TimeZone.CurrentTimeZone.ToLocalTime(new System.DateTime(, , )); public <#=className #> (ObjectId protocolID, string version)
{
_version=version;
_protocolID=protocolID;
} public ObjectId ProtocolID {get{return _protocolID;}} public BsonDocument Execute(byte[] data, int startIndex)
{
BsonDocument bson=new BsonDocument();
<#
foreach(var item in Protocol.Items){ #>
bson["<#=item.Key #>"]= <#= DecodeConfig(item,) #>;
<#
}
#> return bson; }
}
}

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

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

 public partial class ProtocolExecuteTemplate
{
private static int _classID = ; public ProtocolExecuteTemplate(DTO_ProtocolDataItem protocol)
{
Protocol = protocol; } public DTO_ProtocolDataItem Protocol { set; get; } public string DecodeConfig(DTO_ProtocolDataItem.DataItem item,int startIndex)
{
var str = ""; switch (item.SourceType)
{
case DataTypeEnum.Int:
str = $"BitConverter.ToInt32(data,startIndex + {startIndex + item.Pos})";
break;
case DataTypeEnum.UInt:
str = $"BitConverter.ToUInt32(data,startIndex + {startIndex + item.Pos})";
break;
case DataTypeEnum.Short:
str = $"BitConverter.ToInt16(data,startIndex + {startIndex + item.Pos})";
break;
case DataTypeEnum.Long:
str= $"BitConverter.ToInt64(data,startIndex + {startIndex + item.Pos})";
break;
case DataTypeEnum.Byte:
case DataTypeEnum.UByte:
case DataTypeEnum.Bit:
str = $"data[startIndex + {startIndex + item.Pos}]";
break;
case DataTypeEnum.UShort:
str = $"BitConverter.ToUInt16(data,startIndex+{startIndex + item.Pos})";
break;
default:
throw new ArgumentOutOfRangeException();
} if (item.SourceType==DataTypeEnum.Bit)
{
return byteToBit(str, item.BitIndex);
}
else
{
return valueTODBType(str, item.Factor, item.DbType);
}
} private string valueTODBType(string sourceValue, decimal factor, DbDataTypeEnum dbType)
{
switch (dbType)
{
case DbDataTypeEnum.Int32:
return $" new BsonInt32({(factor > 0 ? $"(int)({factor}{getDecimalShortChar(factor)} * {sourceValue})" : sourceValue)})";
case DbDataTypeEnum.Int64:
return $" new BsonInt64({(factor > 0 ? $"(long)({factor}{getDecimalShortChar(factor)} * {sourceValue})" : sourceValue)})";
case DbDataTypeEnum.Double:
return $"new BsonDouble({(factor > 0 ? $"(double)({factor}{getDecimalShortChar(factor)} * {sourceValue})" : $"(double){sourceValue}")})";
case DbDataTypeEnum.DateTime:
return $"new BsonDateTime(_baseDt.AddSeconds({sourceValue}))";
case DbDataTypeEnum.Decimal:
return $"new Decimal128({(factor > 0 ? $"(decimal)({factor}{getDecimalShortChar(factor)} * {sourceValue})" : sourceValue)})";
default:
throw new ArgumentOutOfRangeException(nameof(dbType), dbType, null);
}
} private string byteToBit(string data, int index)
{
switch (index)
{
case :
{ return $"(({data} & 1) ==1)";//(data & 1) == 1;
}
case :
{
return $"(({data} & 2) ==1)";// (data & 2) == 2;
}
case :
{
return $"(({data} & 4) ==1)";//(data & 4) == 4;
}
case :
{
return $"(({data} & 8) ==1)";//(data & 8) == 8;
}
case :
{
return $"(({data} & 16) ==1)";//(data & 16) == 16;
}
case :
{
return $"(({data} & 32) ==1)";//(data & 32) == 32;
}
case :
{
return $"(({data} & 64) ==1)";//(data & 64) == 64;
}
case :
{
return $"(({data} & 128) ==1)";//(data & 128) == 128;
}
default:
throw new ArgumentOutOfRangeException(nameof(index));
} return $"(({data} & {index + 1}) ==1)";
} /// <summary>
/// 用于判断传入的fator是否需要使用deciaml进行运算,如果有小数点的,则是否decimal缩写m,,如果没有小数点,则使用普通的int类型
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
private string getDecimalShortChar(decimal value)
{
return (value % ) == ? "" : "m";
} public int GetNextClasID()
{
return Interlocked.Increment(ref _classID);
}
}

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

     public class ProtocolExecutor_1_1_000
{
public BsonDocument Execute(byte[] data, int startIndex)
{
BsonDocument bson = new BsonDocument(); bson["项目名1"] = new BsonInt32(BitConverter.ToInt32(data, startIndex + 偏移量));
bson["项目名2"] = new BsonInt32(BitConverter.ToInt32(data, startIndex + 偏移量));
。。。。。。。 //其他数据项 return bson;
}
}

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

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

  1. STM32 使用Cubemx 建一个USB(HID)设备下位机,实现数据收发

    这里我主要说一下如何做一个USB下位机,这里主要分3部分:1.建立工程:2.添加报文描述符:3.数据的传输.这里就不讲USB的理论知识了,有想要了解的自行百度一下就可以了. 建立工程:工程建立参考:h ...

  2. 转载 STM32 使用Cubemx 建一个USB(HID)设备下位机,实现数据收发

    STM32 使用Cubemx 建一个USB(HID)设备下位机,实现数据收发  本文转载自 https://www.cnblogs.com/xingboy/p/9913963.html 这里我主要说一 ...

  3. C#使用struct直接转换下位机数据

    编写上位机与下位机通信的时候,涉及到协议的转换,比较多会使用到二进制.传统的方法,是将数据整体获取到byte数组中,然后逐字节对数据进行解析.这样操作工作量比较大,对于较长数据段更容易计算位置出错. ...

  4. C#做上位机软件——绘图并传输给下位机

    拿到任务之后首先分成了几个部分: 1.绘图.学习了GDI+ 2.图片保存. 3.将图片转换成byte[].由于使用Socket通信,只能传输byte[]数据,所以这一步是向下位机传输的关键. 相应地, ...

  5. 下位机多个".c, .h"文件的相互包含及排版

    一.背景: 自从接触单片机编程以来,由于工作上的需要,不可避免的时常会接手别人的代码,但常常由于上一位同事的编码随意性有点大,导致可读性非常的差,有时候不得不完全舍弃原有代码,推倒重来,无形中增加了工 ...

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

    背景: 最近负责为主板管理电源的电源管理模块编写软体,使用的MCU为MSP430G2333.功能上很简单,即通过板子上的硬件拨码设定,或者通过IIC与主板通信,由主板的BIOS决定开机及关机的延时供电 ...

  7. vb配置下位机CAN寄存器小结

    2011-12-14 18:44:32 效果图 1,完成设计(由于没有eeprom等存储设备,所以每次上电后需要通过串口配置某些寄存器).在设计中,列出技术评估难度,并进行尝试,参看<我的设计& ...

  8. C# WPF上位机实现和下位机TCP通讯

    下位机使用北京大华程控电源DH1766-1,上位机使用WPF.实现了电压电流实时采集,曲线显示.上午在公司调试成功,手头没有程控电源,使用TCP服务端模拟.昨天写的TCP服务端正好排上用场. 界面如下 ...

  9. linux设备驱动程序-i2c(2)-adapter和设备树的解析

    linux设备驱动程序-i2c(2)-adapter和设备树的解析 (注: 基于beagle bone green开发板,linux4.14内核版本) 在本系列linux内核i2c框架的前两篇,分别讲 ...

随机推荐

  1. Kubernetes之canal的网络策略(NetworkPolicy)

    安装要求: 1.我们这里安装的是3.3的版本.kubernetes的要求: 支持的版本 1.10 1.11 1.12 2.CNI插件需要启用,Calico安装为CNI插件.必须通过传递--networ ...

  2. Java 集合系列之二:List基本操作

    1. Java List 1. Java List重要观点 Java List接口是Java Collections Framework的成员. List允许您添加重复元素. List允许您拥有'nu ...

  3. mongodb的部署记录

    操作系统redhat6.4,采用网络yum源的方式进行安装 一.linux下安装mongodb 1.配置yum源 [root@localhost ~]#vim /etc/yum.repos.d/mon ...

  4. localStorage sessionStorage cookie indexedDB

    目录: localStorage sessionStorage cookie indexedDB localStorage localStorage存储的数据能在跨浏览器会话保留 数据可以长期保留,关 ...

  5. 视频显著性检测-----Predicting Video Saliency using Object-to-Motion CNN and Two-layer Convolutional LSTM

    帧内显著性检测: 将卷积网络的多层特征进行组合通过unsampling 得到粗显著性预测: 帧间显著性检测: (粗检测结果+新卷积网络的特征图,最后+之前卷积网络的卷积特征输入到LSTM中)进行预测. ...

  6. 路径规划算法之Bellman-Ford算法

    最近由于工作需要一直在研究Bellman-Ford算法,这也是我第一次用C++编写代码. 1.Bellman-Ford算法总结 (1)Bellman-Ford算法计算从源点(起始点)到任意一点的最短路 ...

  7. UE4命令行使用,解释

    命令行在外部 从命令行运行编辑项目 1 导航到您的[LauncherInstall][VersionNumber]\Engine\Binaries\Win64 目录中. 2 右键单击上 UE4Edit ...

  8. Python:匿名函数lambda的函数用法和排序用法

    一.介绍: Lambda函数,是一个匿名函数,创建语法: lambda parameters:express parameters:可选,如果提供,通常是逗号分隔的变量表达式形式,即位置参数. exp ...

  9. 记事本:js简介

    引用js和css很类似,大致有三种方式: 第一种: 在行内引用js, <div onclick="alert(111);"> </div> 第二种: 在行外 ...

  10. 【转】让EntityManager的Query返回Map对象

    在JPA 2.0中我们可以使用entityManager.createNativeQuery()来执行原生的SQL语句.但当我们查询结果没有对应实体类时,需使用entityManager.create ...