1,博图数据块的数据排列原则:

数据对齐算法:

  • 将当前地址对齐到整数:
numBytes = (int)Math.Ceiling(numBytes);
  • 将当前地址对齐到偶整数:
 numBytes = Math.Ceiling(numBytes);if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)numBytes++;  

2,数据对齐的原则,如果当前的数据类型是:

  1. bool,则使用当前地址.
  2. byte,则使用对齐方式1
  3. 其他,则使用对齐方式2,即偶数对齐的方法.

3,如何从地址和bool进行设定和取值,假设我们有一个byte[]数组代表整个DB,和一个浮点数代表bool的地址?

  • 取值:
int bytePos = (int)Math.Floor(numBytes);int bitPos = (int)((numBytes - (double)bytePos) / 0.125);if ((bytes[bytePos] & (int)Math.Pow(2, bitPos)) != 0)value = true;elsevalue = false;

赋值

 bytePos = (int)Math.Floor(numBytes);bitPos = (int)((numBytes - (double)bytePos) / 0.125);if ((bool)obj)bytes[bytePos] |= (byte)Math.Pow(2, bitPos);            // is trueelsebytes[bytePos] &= (byte)(~(byte)Math.Pow(2, bitPos));   // is false

思路:获取当前bit所在的字节地址.然后使用  (注意位是从0开始的.位0..位15..位31.)

t=t | 1<<(bitPos)来进行置位单个位. (掩码置位,使用|,所有为1的位进行置位)

t=t &(~1<<(bitPos)来进行复位单个位,(掩码复位,使用 &(~掩码),则所有位1的掩码进行复位)

t=t^(1<<(bitPos) 来进行异或运算(,掩码的1所在的位进行取反操作.)

t=t&(1<<(bitPos))来判断某个位是否为1或为0.

2,迭代解析

numbytes: 迭代的plc地址

itemInfos:迭代的信息存储的列表

preName:迭代的名称.

ElementItem:用于解析的元素.

public static void DeserializeItem(ref double numBytes, List<ItemInfo> itemInfos, string preName, ElementItem item){var PreName = (string.IsNullOrEmpty(preName)) ? "" : preName + "_";var Info = new ItemInfo() { Name = PreName + item.Name, Type = item.Type };switch (item.GetTypeInfo()){case PlcParamType.BaseType:switch (item.Type){case "Bool":Info.Addr = ParseAddr(numBytes, item);numBytes += 0.125;break;case "Char":case "Byte":numBytes = Math.Ceiling(numBytes);Info.Addr = ParseAddr(numBytes, item);numBytes++;;break;case "Int":case "UInt":case "Word":numBytes = Math.Ceiling(numBytes);if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)numBytes++;Info.Addr = ParseAddr(numBytes, item);numBytes += 2;;break;case "DInt":case "UDInt":case "DWord":case "Time":case "Real":numBytes = Math.Ceiling(numBytes);if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)numBytes++;Info.Addr = ParseAddr(numBytes, item);numBytes += 4;;break;default:break;}itemInfos.Add(Info);break;case PlcParamType.String:numBytes = Math.Ceiling(numBytes);if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)numBytes++;//----------Info.Addr = ParseAddr(numBytes, item);numBytes += item.GetStringLength();itemInfos.Add(Info);break;case PlcParamType.Array://------------原程序的可能是个漏洞.numBytes = Math.Ceiling(numBytes);if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)numBytes++;//-------------var elementType = item.GetElementType();for (var i = 0; i < item.GetArrayLength(); i++){var element = new ElementItem() { Name = item.Name + $"[{i}]", Type = elementType };DeserializeItem(ref numBytes, itemInfos, PreName, element);}break;case PlcParamType.UDT:numBytes = Math.Ceiling(numBytes);if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)numBytes++;//格式foreach (var element in item.GetElementItems(Dict)){DeserializeItem(ref numBytes, itemInfos, PreName + item.Name, element);}break;default:throw new ArgumentException("do not find type");}}

思路: 如果元素的类型是基本类型:比如 bool,int,time...等等,则直接生成一条ItemInfo记录

如果元素的类型是数组,则获取数组的长度和获取数组的元素类型,然后进行迭代解析.

如果元素是UDT类型,也就是自定义的类型.那么就迭代获取元素,并且迭代解析.

----------------------

所有的这一切,源于一个Dictionary<string,List<ElementItem>>对象.这个对象存储了自定义的类别的名称和其子元素

比如有以下的一个DB导出文件;

则可以看到其有 "step"类型

元素有element1.....type为 int...

还有 "Recipe"类型

还有 "test1"类型

还有一个DataBlock,就是这个数据库,也将它当作一个类型,其type就是"数据块_1" ,其元素就是

名称             类型

Recipe1         Recipe(这个可以在上面找到)

关键是其内部的Struct结构,对于这个结构,我们自定义生成其类型 为 Name_Struct.

TYPE "step"
VERSION : 0.1STRUCTelement1 : Int;element2 : Int;element3 : Int;element4 : Int;element5 : Int;element6 : Int;element7 : Real;element8 : Real;element9 : Real;element10 : Real;element11 : Real;element12 : Real;element13 : Real;element14 : Bool;element15 : Bool;element16 : Int;END_STRUCT;END_TYPETYPE "Recipe"
VERSION : 0.1STRUCT"Name" : String[20];steps : Array[0..29] of "step";END_STRUCT;END_TYPETYPE "test1"
VERSION : 0.1STRUCTb : Bool;END_STRUCT;END_TYPEDATA_BLOCK "数据块_1"
{ S7_Optimized_Access := 'FALSE' }
VERSION : 0.1
NON_RETAINSTRUCTRecipe1 : "Recipe";AAA : "Recipe";aa : String;adef : Structa : Bool;b : Bool;c : Int;bool1 : Bool;bool2 : Bool;ffff : Structttt : Bool;aaa : Bool;fff : Bool;eefg : Bool;END_STRUCT;afe : Bool;aaaaaaa : "test1";x1 : "test1";x2 : "test1";x3 : Array[0..1] of Byte;abcef : Array[0..10] of Structaef : StructStatic_1 : Bool;Static_2 : Bool;END_STRUCT;eef : Bool;affe : Bool;END_STRUCT;END_STRUCT;END_STRUCT;BEGINRecipe1.steps[29].element14 := FALSE;END_DATA_BLOCK

3,从db文件中读取类信息.

//从db文件中读取类信息,并且放入到Dict之中,同时填充MainBlock信息,如果有的话.public static void GetTypeFromFile(Dictionary<string, List<ElementItem>> Dict, string path, out ElementItem MainBlock){MainBlock = new ElementItem();//生成Block对象.using (Stream st = new FileStream(path, FileMode.Open))//读取文本文件DB块中的数据.{StreamReader reader = new StreamReader(st);List<ElementItem> list;while (!reader.EndOfStream){string line = reader.ReadLine();//读取一行数据进行判断switch (GetReaderLineTYPE(line))//通过解析函数解析这一行的数据是什么类型.{case "TYPE"://如果是类型 对应 db文件里面 TYPE xxxx 的格式list = new List<ElementItem>();//则创建一个列表准备容纳该TYPE的ELementItem,也就是元素集.string tn = GetReaderLineName(line);//解析type行的名称.也就是该type的名称.GetElementsFromReader(reader, list, tn, Dict);//然后调用函数进行将元素放入列表,最后哪个Dictionary参数用于接受内联Struct类型.Dict[tn] = list;//将该类型在字典中生成名值对,也就是Type名称,List<elementItem>作为值.break;case "DATA_BLOCK":MainBlock = new ElementItem();string bn = GetReaderLineName(line);MainBlock.Name = bn;MainBlock.Type = bn;list = new List<ElementItem>();GetElementsFromReader(reader, list, bn, Dict);//如果是DB块,则填充Main Block(备用),剩下的根上面一样).Dict[bn] = list;break;default:break;}}}}

4, 辅助的读取迭代函数,实现的关键...

  public static void GetElementsFromReader(StreamReader reader, List<ElementItem> list, string type_name, Dictionary<string, List<ElementItem>> Dict){ElementItem item;Tuple<string, string> tp;while (!reader.EndOfStream){string line = reader.ReadLine();//首先,其必须是一个解析Type或者DataBlock的过程,因为只有这两处调用它.switch (GetReaderLineTYPE(line))//解析每行的类别.{case "ELEMENT"://当解析到该行是元素的时候.就将该行加入到列表中.item = new ElementItem();tp = GetElementInfo(line);item.Name = tp.Item1;item.Type = tp.Item2;list.Add(item);break;case "StructELEMENT"://当解析到该行是Struct时,也就是元素类型时Struct的时候,item = new ElementItem();tp = GetElementInfo(line);item.Name = tp.Item1;item.Type = tp.Item2.Remove(tp.Item2.LastIndexOf("Struct"));//由于Array Struct的存在,将该元素类型从....Struct //替换为 .... elementName_Structitem.Type = item.Type + type_name + "_" + item.Name + "_" + "Struct";//string structType = type_name + "_" + item.Name + "_" + "Struct";//该名称为其新的类别.list.Add(item);//首先将该元素加入列表.List<ElementItem> sub = new List<ElementItem>();//将该子类别(我们自定义的类别,添加到字典中.GetElementsFromReader(reader, sub, structType, Dict);Dict[structType] = sub;break;case "END_STRUCT"://当接受到这个时,表明一个Type或者一个DataBlock的解析工作结束了,返回上层对象.return;default:break;}}}

5,工具函数1,用于帮助判断DB文件每行的信息.

 private static Tuple<string, string> GetElementInfo(string line){if (!GetReaderLineTYPE(line).Contains("ELEMENT")) throw new Exception("this line is not element " + line);int pos = line.IndexOf(" : ");string Name = line.Remove(pos).Trim(' ', ';');var t = Name.IndexOf(' ');if (t > 0)Name = Name.Remove(t);string Type = line.Substring(pos + 3).Trim(' ', ';');if (Type.Contains(" :=")) Type = Type.Remove(Type.IndexOf(" :="));return new Tuple<string, string>(Name, Type);}private static string GetReaderLineName(string line){if ((GetReaderLineTYPE(line) != "TYPE") && (GetReaderLineTYPE(line) != "DATA_BLOCK")) throw new Exception("not read name of " + line);return line.Substring(line.IndexOf(' ')).Trim(' ');}private static string GetReaderLineTYPE(string line){//Console.WriteLine(line);if (line.Contains("TYPE ")) return "TYPE";if (line.Contains("DATA_BLOCK ")) return "DATA_BLOCK";if (line.Contains("END_STRUCT;")) return "END_STRUCT";if (line.Trim(' ') == "STRUCT") return "STRUCT";if (line.EndsWith("Struct")) return "StructELEMENT";if ((line.EndsWith(";"))) return "ELEMENT";return null;}

6,从之前生成的字典和MainDataBlock中生成 List<Item Infos>也就是展开DB块信息.

public static void DeserializeItem(ref double numBytes, List<ItemInfo> itemInfos, string preName, ElementItem item){var PreName = (string.IsNullOrEmpty(preName)) ? "" : preName + "_";//如果前导名称不为空,则添加到展开的名称上去.//这里是个bug,最后将这行放到string生成,或者BaseType生成上,因为会出现多次_var Info = new ItemInfo() { Name = PreName + item.Name, Type = item.Type };switch (item.GetTypeInfo())//解析这个元素的类别{case PlcParamType.BaseType://基类直接添加.switch (item.Type){case "Bool":Info.Addr = ParseAddr(numBytes, item);numBytes += 0.125;break;case "Char":case "Byte":numBytes = Math.Ceiling(numBytes);Info.Addr = ParseAddr(numBytes, item);numBytes++;;break;case "Int":case "UInt":case "Word":numBytes = Math.Ceiling(numBytes);if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)numBytes++;Info.Addr = ParseAddr(numBytes, item);numBytes += 2;;break;case "DInt":case "UDInt":case "DWord":case "Time":case "Real":numBytes = Math.Ceiling(numBytes);if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)numBytes++;Info.Addr = ParseAddr(numBytes, item);numBytes += 4;;break;default:break;}itemInfos.Add(Info);break;case PlcParamType.String://string类型.直接添加numBytes = Math.Ceiling(numBytes);if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)numBytes++;//----------Info.Addr = ParseAddr(numBytes, item);numBytes += item.GetStringLength();//如果是string,则加256,否则加 xxx+2;itemInfos.Add(Info);break;case PlcParamType.Array://数组则进行分解,再迭代.//------------原程序的可能是个漏洞.numBytes = Math.Ceiling(numBytes);if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)numBytes++;//-------------var elementType = item.GetElementType();for (var i = 0; i < item.GetArrayLength(); i++){var element = new ElementItem() { Name = item.Name + $"[{i}]", Type = elementType };DeserializeItem(ref numBytes, itemInfos, PreName, element);}break;case PlcParamType.UDT://PLC类型,进行分解后迭代.numBytes = Math.Ceiling(numBytes);if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)numBytes++;//格式foreach (var element in item.GetElementItems(Dict)){DeserializeItem(ref numBytes, itemInfos, PreName + item.Name, element);}break;default:throw new ArgumentException("do not find type");}}

注意,每条信息组成:(s

public class ItemInfo{public ItemInfo(){}public string Name { get; set; }//element的名称public string Type { get; set; }//element的类别(基类别,比如字符串,byte,bool之类.public object Addr { get; internal set; }//地址;比如dbx0.0之类.
//综合就是给出  PLC变量名  PLC变量类型   PLC变量地址 的一个列表.}

7,功能扩展,支持多个db文件的解析操作

 // MainFunction to read message from dbs.//迭代解析 多个db文件.public static void GetDBInfosFromFiles(string path, Dictionary<string, List<ElementItem>> Dict, Dictionary<string, List<ItemInfo>> DataBlocks){DirectoryInfo dir = new DirectoryInfo(path);FileInfo[] files = dir.GetFiles("*.db");List<ElementItem> blocks = new List<ElementItem>();foreach (var file in files)//将每个文件的db块加入到 db数组中,再将解析的TYPE都加入到字典中.{ElementItem MainBlock;GetTypeFromFile(Dict, file.FullName, out MainBlock);if (MainBlock != null){MainBlock.Name = file.Name.Remove(file.Name.IndexOf('.'));blocks.Add(MainBlock);}}foreach (var block in blocks)//然后迭代解析每个DB块的信息,将求加入到第二个字典中.{double numBytes = 0.0;List<ItemInfo> itemInfos = new List<ItemInfo>();DeserializeItem(ref numBytes, itemInfos, "", block);DataBlocks[block.Name] = itemInfos;}}

8,使用CSVHELPER类从昆仑通态导出的文件中来读取信息

public static MacInfos[] GetCSVInfosFromFile(string path,string[] infos){//用csvhelper类读取数据:注意,必须用这个方法读取,否则是乱码!using (var reader = new StreamReader(path, Encoding.GetEncoding("GB2312"))){using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture)){//读取4行无效信息.............csv.Read();infos[0] = csv.GetField(0);csv.Read();infos[1] = csv.GetField(0);csv.Read();infos[2] = csv.GetField(0);csv.Read();infos[3] = csv.GetField(0);//读取所有的数据放入一个数组中.标准用法.var records = csv.GetRecords<MacInfos>().ToArray();return records;}}}

9,将CSV文件的变量名进行填充,即将 Dict之中的变量名填充到 CSV的变量名之中.

  //用于将填充完的信息数组反写回csv文件.public static void WriteIntoCSVOfMAC(string path){string csvpath = FindFiles(path, "*.csv").First();//找到csv文件.string[] strinfos = new string[4];//填充无效信息的4行MacInfos[] macInfos = GetCSVInfosFromFile(csvpath,strinfos);//获取MacInfos数组和无效信息字符串数组.foreach(var key in DBInfos.Keys)//轮询每个DB块.{var infos = (from info in macInfoswhere GetDBFromMacInfo(info) == key.Remove(key.IndexOf('_'))select info).ToArray();//将找到的Macinfo中再去查找对应的DB块的Macinfos[]数组.WriteDBToMacInfos(key, infos);//然后将对应的db块的信息,(找到其中的元素的名称,一一写入对应infos的变量名之中.}//填充完所有的Macinfos数组后,将这个数组反写回CSV文件.using (var writer = new StreamWriter(new FileStream(csvpath, FileMode.Open), Encoding.GetEncoding("GB2312")))using (var csv = new CsvWriter(writer, CultureInfo.InvariantCulture)){//将原来的无效信息反填充回去.csv.WriteField(strinfos[0]);csv.NextRecord();//转移到下一行.去读写数据.csv.WriteField(strinfos[1]);csv.NextRecord();csv.WriteField(strinfos[2]);csv.NextRecord();csv.WriteField(strinfos[3]);csv.NextRecord();//然后填充数组.csv.WriteRecords(macInfos);}}

10,结论:

1,在使用的时候,首先导出DB块的块生成源,

2,然后将所有的*.db文件们放入c:\macfile文件夹之中.

3,使用昆仑通态软件导入标签功能,导入db块和导入utc块,将变量导入到软件中,这个时候,变量名一栏是空的.

4,使用导出设备信息,将导出一个csv文件.

5,运行小软件.结束.

附上Git地址和我的邮箱地址,

有喜欢工控软件开发的多多沟通交流.

解析博图数据块(昆仑通态触摸屏自动命名)相关推荐

  1. 博途数据类型wstring怎么用_解析博图数据块(昆仑通态触摸屏自动命名)

    1,博图数据块的数据排列原则: 数据对齐算法:将当前地址对齐到整数: numBytes = (int)Math.Ceiling(numBytes);将当前地址对齐到偶整数: numBytes = Ma ...

  2. MCGS昆仑通态触摸屏导入博途自定义数据类型和DB块变量的具体方法演示

    MCGS昆仑通态触摸屏导入博途自定义数据类型和DB块变量的具体方法演示 如下图所示,在博途中新建项目后,添加自己所需的数据类型,然后选中该数据类型,右击选择"从块生成源"-&quo ...

  3. 西门子PLC1200伺服库卡机器人12工位博图程序例程,组态采用昆仑通态触摸屏

    西门子PLC1200伺服库卡机器人12工位博图程序例程,组态采用昆仑通态触摸屏,详细中文注释,PDF电路图参考,设备操作说明,物料BOM ,PLC和一台库卡机器人profinet通讯 PTO模式控制松 ...

  4. 松下FPXH自动螺丝机程序 昆仑通态触摸屏控触摸,松下FPXH数据表定位模式

    松下FPXH自动螺丝机程序 昆仑通态触摸屏控触摸,松 下FPXH数据表定位模式,写法新颖,程序严谨,思路清晰,通俗易懂,程序一万多步,但分块编写,可读性非常强,带配方,省去更改产品再去修改程序的麻烦. ...

  5. 松下FPXH自动螺丝机程序 昆仑通态触摸屏控触摸,松 下FPXH数据表定位模式,写法新颖

    松下FPXH自动螺丝机程序 昆仑通态触摸屏控触摸,松 下FPXH数据表定位模式,写法新颖,程序严谨,思路清晰,通俗易懂,程序一万多步,但分块编写,可读性非常强,带配方,省去更改产品再去修改程序的麻烦. ...

  6. 空压机数据采集系统,使用昆仑通态触摸屏制作,具备完善的数据采集和历史记录保存功能

    空压机数据采集系统,使用昆仑通态触摸屏制作,具备完善的数据采集和历史记录保存功能,异常信息记录,系统运行稳定可靠,程序 ID:66500617212287592猫猫工控

  7. Modbus TCP转Profinet网关连接昆仑通态触摸屏案例

    本案例是模拟将Modbus TCP 设备数据接入到西门子PROFINET 网络中,使用设备为西门子 S7-15000 型 PLC,Modbus TCP转Profinet网关,Modbus TCP从站设 ...

  8. Profinet转ModbusTCP网关连接昆仑通态触摸屏配置案例

    本案例是模拟将Modbus TCP 设备数据接入到西门子PROFINET 网络中. 使用设备为西门子 S7-1500 型 PLC, Profinet转ModbusTCP网关.MODBUS 从站昆仑通态 ...

  9. 昆仑通态9针通讯口定义_MCGS昆仑通态触摸屏常见问题(4)

    写在前面的话: 现在很多搞工控的都会接触到各种各样的触摸屏.个人认为MCGS昆仑通态触摸屏,无论是从组态过程以及通讯方式上都是比较容易上手的.但是,对于新手来说,也会遇到形形色色的问题.所以小编接下来 ...

  10. 昆仑通态复制的程序可以用吗_MCGS昆仑通态触摸屏实时报警

    大家好,我是小江,在这里分享一些我工作中遇到的一些问题,有的是我写的程序,有的是看书一些心得,分享到这上面,如果有不妥的地方,希望见谅,能看得过去,就看看,看不过去的话,就当啥也没用,今天分享一些触摸 ...

最新文章

  1. LiFi会将大数据和物联网带到新高度吗?
  2. typescript_如何掌握高级TypeScript模式
  3. Kava下一阶段Kava 5主网将于3月4日上线
  4. mysql默认端口号_什么是MySQL默认端口号?
  5. python安装win32com模块
  6. weblogic 安装部署
  7. kd树 python实现_python K近邻算法的kd树实现
  8. 在编辑word时,如何查找和替换空格符呢?
  9. NVIDIA GPU Compute Capability解释
  10. Cadence导出Excel格式BOM表
  11. html5地图定位高德,JS使用高德地图定位
  12. 服务器系统盘是否需要阵列,服务器硬盘必须设置阵列吗
  13. 《筑墙——只需一点点安全常识就能阻止网络犯罪》
  14. qt4.8.7 2016年4月18日 error reading collection file qthelpcollection cannot load sql
  15. 2017男宝宝起名大全来了,快来给你的孩子挑个好名字吧
  16. flash 图表(XML动态获取数据)
  17. 数据中台稳定性的“四高” | StartDT Tech Lab 18
  18. Android入门教程(八)
  19. SCAPE、BlendSCAPE、SMPL、SMPL-H、SMPL-X、STAR等都是什么?请分别仔细介绍一下
  20. 《大数据时代:生活、工作与思维的大变革》读书笔记3(完)

热门文章

  1. 明解c语言答案第八章,明解C语言 入门篇 第八章答案
  2. Go语言躲坑经验总结
  3. 宝塔php爬虫无头浏览器,爬虫利器selenium和无头浏览器的使用
  4. Mybatis源码解析!!! 附中午注释源码
  5. C# 填充Excel
  6. excel表自动向下填充
  7. STM32f1之光敏电阻传感器实验
  8. glide加载gif图不显示动画_Glide 加载gif的一些个人总结
  9. 简析通达信股票接口测试过程
  10. 新媒体素材采集工具,帮你采集新媒体素材,提高效率