解析博图数据块(昆仑通态触摸屏自动命名)
1,博图数据块的数据排列原则:
数据对齐算法:
- 将当前地址对齐到整数:
numBytes = (int)Math.Ceiling(numBytes);
- 将当前地址对齐到偶整数:
numBytes = Math.Ceiling(numBytes);if ((numBytes / 2 - Math.Floor(numBytes / 2.0)) > 0)numBytes++;
2,数据对齐的原则,如果当前的数据类型是:
- bool,则使用当前地址.
- byte,则使用对齐方式1
- 其他,则使用对齐方式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地址和我的邮箱地址,
有喜欢工控软件开发的多多沟通交流.
解析博图数据块(昆仑通态触摸屏自动命名)相关推荐
- 博途数据类型wstring怎么用_解析博图数据块(昆仑通态触摸屏自动命名)
1,博图数据块的数据排列原则: 数据对齐算法:将当前地址对齐到整数: numBytes = (int)Math.Ceiling(numBytes);将当前地址对齐到偶整数: numBytes = Ma ...
- MCGS昆仑通态触摸屏导入博途自定义数据类型和DB块变量的具体方法演示
MCGS昆仑通态触摸屏导入博途自定义数据类型和DB块变量的具体方法演示 如下图所示,在博途中新建项目后,添加自己所需的数据类型,然后选中该数据类型,右击选择"从块生成源"-&quo ...
- 西门子PLC1200伺服库卡机器人12工位博图程序例程,组态采用昆仑通态触摸屏
西门子PLC1200伺服库卡机器人12工位博图程序例程,组态采用昆仑通态触摸屏,详细中文注释,PDF电路图参考,设备操作说明,物料BOM ,PLC和一台库卡机器人profinet通讯 PTO模式控制松 ...
- 松下FPXH自动螺丝机程序 昆仑通态触摸屏控触摸,松下FPXH数据表定位模式
松下FPXH自动螺丝机程序 昆仑通态触摸屏控触摸,松 下FPXH数据表定位模式,写法新颖,程序严谨,思路清晰,通俗易懂,程序一万多步,但分块编写,可读性非常强,带配方,省去更改产品再去修改程序的麻烦. ...
- 松下FPXH自动螺丝机程序 昆仑通态触摸屏控触摸,松 下FPXH数据表定位模式,写法新颖
松下FPXH自动螺丝机程序 昆仑通态触摸屏控触摸,松 下FPXH数据表定位模式,写法新颖,程序严谨,思路清晰,通俗易懂,程序一万多步,但分块编写,可读性非常强,带配方,省去更改产品再去修改程序的麻烦. ...
- 空压机数据采集系统,使用昆仑通态触摸屏制作,具备完善的数据采集和历史记录保存功能
空压机数据采集系统,使用昆仑通态触摸屏制作,具备完善的数据采集和历史记录保存功能,异常信息记录,系统运行稳定可靠,程序 ID:66500617212287592猫猫工控
- Modbus TCP转Profinet网关连接昆仑通态触摸屏案例
本案例是模拟将Modbus TCP 设备数据接入到西门子PROFINET 网络中,使用设备为西门子 S7-15000 型 PLC,Modbus TCP转Profinet网关,Modbus TCP从站设 ...
- Profinet转ModbusTCP网关连接昆仑通态触摸屏配置案例
本案例是模拟将Modbus TCP 设备数据接入到西门子PROFINET 网络中. 使用设备为西门子 S7-1500 型 PLC, Profinet转ModbusTCP网关.MODBUS 从站昆仑通态 ...
- 昆仑通态9针通讯口定义_MCGS昆仑通态触摸屏常见问题(4)
写在前面的话: 现在很多搞工控的都会接触到各种各样的触摸屏.个人认为MCGS昆仑通态触摸屏,无论是从组态过程以及通讯方式上都是比较容易上手的.但是,对于新手来说,也会遇到形形色色的问题.所以小编接下来 ...
- 昆仑通态复制的程序可以用吗_MCGS昆仑通态触摸屏实时报警
大家好,我是小江,在这里分享一些我工作中遇到的一些问题,有的是我写的程序,有的是看书一些心得,分享到这上面,如果有不妥的地方,希望见谅,能看得过去,就看看,看不过去的话,就当啥也没用,今天分享一些触摸 ...
最新文章
- LiFi会将大数据和物联网带到新高度吗?
- typescript_如何掌握高级TypeScript模式
- Kava下一阶段Kava 5主网将于3月4日上线
- mysql默认端口号_什么是MySQL默认端口号?
- python安装win32com模块
- weblogic 安装部署
- kd树 python实现_python K近邻算法的kd树实现
- 在编辑word时,如何查找和替换空格符呢?
- NVIDIA GPU Compute Capability解释
- Cadence导出Excel格式BOM表
- html5地图定位高德,JS使用高德地图定位
- 服务器系统盘是否需要阵列,服务器硬盘必须设置阵列吗
- 《筑墙——只需一点点安全常识就能阻止网络犯罪》
- qt4.8.7 2016年4月18日 error reading collection file qthelpcollection cannot load sql
- 2017男宝宝起名大全来了,快来给你的孩子挑个好名字吧
- flash 图表(XML动态获取数据)
- 数据中台稳定性的“四高” | StartDT Tech Lab 18
- Android入门教程(八)
- SCAPE、BlendSCAPE、SMPL、SMPL-H、SMPL-X、STAR等都是什么?请分别仔细介绍一下
- 《大数据时代:生活、工作与思维的大变革》读书笔记3(完)