admin 管理员组文章数量: 887053
2023年12月24日发(作者:表单大师最大购买量)
解析博图数据块(昆仑通态触摸屏自动命名)
1,博图数据块的数据排列原则:
数据对齐算法:
•
将当前地址对齐到整数:
numBytes = (int)g(numBytes);
•
将当前地址对齐到偶整数:
numBytes = g(numBytes);
if ((numBytes / 2 - (numBytes / 2.0)) > 0)
numBytes++;
2,数据对齐的原则,如果当前的数据类型是:
1. bool,则使用当前地址.
2. byte,则使用对齐方式1
3. 其他,则使用对齐方式2,即偶数对齐的方法.
3,如何从地址和bool进行设定和取值,假设我们有一个byte[]数组代表整个DB,和一个浮点数代表bool的地址?
•
取值:
int bytePos = (int)(numBytes);
int bitPos = (int)((numBytes - (double)bytePos) / 0.125);
if ((bytes[bytePos] & (int)(2, bitPos)) != 0)
value = true;
else
value = false;
•
赋值
bytePos = (int)(numBytes);
bitPos = (int)((numBytes - (double)bytePos) / 0.125);
if ((bool)obj)
bytes[bytePos] |= (byte)(2, bitPos); // is true
else
bytes[bytePos] &= (byte)(~(byte)(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
{
var PreName = (OrEmpty(preName)) ? "" :
preName + "_";
var Info = new ItemInfo() { Name = PreName + ,
Type = };
switch (eInfo())
{
case pe:
switch ()
{
case "Bool":
= ParseAddr(numBytes, item);
numBytes += 0.125;
break;
case "Char":
case "Byte":
numBytes = g(numBytes);
= ParseAddr(numBytes, item);
numBytes++;
;
break;
case "Int":
case "UInt":
case "Word":
numBytes = g(numBytes);
if ((numBytes / 2 - (numBytes / 2.0)) > 0)
numBytes++;
= ParseAddr(numBytes, item);
numBytes += 2;
;
break;
case "DInt":
case "UDInt":
case "DWord":
case "Time":
case "Real":
numBytes = g(numBytes);
if ((numBytes / 2 - (numBytes / 2.0)) > 0)
numBytes++;
= ParseAddr(numBytes, item);
numBytes += 4;
;
break;
default:
break;
}
(Info);
break;
case :
numBytes = g(numBytes);
if ((numBytes / 2 - (numBytes / 2.0)) > 0)
numBytes++;
//----------
= ParseAddr(numBytes, item);
numBytes += ingLength();
(Info);
break;
case :
//------------原程序的可能是个漏洞.
numBytes = g(numBytes);
if ((numBytes / 2 - (numBytes / 2.0)) > 0)
numBytes++;
//-------------
var elementType = mentType();
for (var i = 0; i < ayLength(); i++)
{
var element = new ElementItem() { Name = +
$"[{i}]", Type = elementType };
DeserializeItem(ref numBytes, itemInfos, PreName, element);
}
break;
case :
numBytes = g(numBytes);
if ((numBytes / 2 - (numBytes / 2.0)) > 0)
numBytes++;
//格式
foreach (var element in mentItems(Dict))
{
DeserializeItem(ref numBytes, itemInfos, PreName +
, element);
}
break;
default:
throw new ArgumentException("do not find type");
}
}
思路: 如果元素的类型是基本类型:比如 bool,等等,则直接生成一条ItemInfo记录
如果元素的类型是数组,则获取数组的长度和获取数组的元素类型,然后进行迭代解析.
如果元素是UDT类型,也就是自定义的类型.那么就迭代获取元素,并且迭代解析.
----------------------
所有的这一切,源于一个Dictionary
比如有以下的一个DB导出文件;
则可以看到其有 "step"类型
元素有pe为
还有 "Recipe"类型
还有 "test1"类型
还有一个DataBlock,就是这个数据库,也将它当作一个类型,其type就是"数据块_1" ,其元素就是
名称 类型
Recipe1 Recipe(这个可以在上面找到)
关键是其内部的Struct结构,对于这个结构,我们自定义生成其类型
为 Name_Struct.
TYPE "step"
VERSION : 0.1
STRUCT
element1 : 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_TYPE
TYPE "Recipe"
VERSION : 0.1
STRUCT
"Name" : String[20];
steps : Array[0..29] of "step";
END_STRUCT;
END_TYPE
TYPE "test1"
VERSION : 0.1
STRUCT
b : Bool;
END_STRUCT;
END_TYPE
DATA_BLOCK "数据块_1"
{ S7_Optimized_Access := 'FALSE' }
VERSION : 0.1
NON_RETAIN
STRUCT
Recipe1 : "Recipe";
AAA : "Recipe";
aa : String;
adef : Struct
a : Bool;
b : Bool;
c : Int;
bool1 : Bool;
bool2 : Bool;
ffff : Struct
ttt : 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 Struct
aef : Struct
Static_1 : Bool;
Static_2 : Bool;
END_STRUCT;
eef : Bool;
affe : Bool;
END_STRUCT;
END_STRUCT;
END_STRUCT;
BEGIN
[29].element14 := FALSE;
END_DATA_BLOCK
3,从db文件中读取类信息.
//从db文件中读取类信息,并且放入到Dict之中,同时填充MainBlock信息,如果有的话.
public
MainBlock)
{
MainBlock = new ElementItem();//生成Block对象.
using (Stream st = new FileStream(path, ))//读取文本文件DB块中的数据.
{
StreamReader reader = new StreamReader(st);
List
while (!tream)
static void GetTypeFromFile(Dictionary List { string line = ne();//读取一行数据进行判断 switch (GetReaderLineTYPE(line))//通过解析函数解析这一行的数据是什么类型. { case "TYPE"://如果是类型 对应 db文件里面 TYPE xxxx 的格式 list = new List string tn = GetReaderLineName(line);//解析type行的名称.也就是该type的名称. GetElementsFromReader(reader, list, tn, Dict);//然后调用函数进行将元素放入列表,最后哪个Dictionary参数用于接受内联Struct类型. Dict[tn] = list;//将该类型在字典中生成名值对,也就是Type名称,List break; case "DATA_BLOCK": MainBlock = new ElementItem(); string bn = GetReaderLineName(line); = bn; = bn; list = new List GetElementsFromReader(reader, list, bn, Dict);//如果是DB块,则填充Main Block(备用),剩下的根上面一样). Dict[bn] = list; break; default: break; } } } } 4, 辅助的读取迭代函数,实现的关键... public static void GetElementsFromReader(StreamReader reader, { ElementItem item; Tuple while (!tream) { string line = ne();//首先,其必须是一个解析Type或者DataBlock的过程,因为只有这两处调用它. switch (GetReaderLineTYPE(line))//解析每行的类别. { case "ELEMENT"://当解析到该行是元素的时候.就将该行加入到列表中. item = new ElementItem(); tp = GetElementInfo(line); = 1; = 2; (item); break; case "StructELEMENT"://当解析到该行是Struct时,也就是元素类型时Struct的时候, item = new ElementItem(); List Dictionary tp = GetElementInfo(line); = 1; (dexOf("Struct"));//由于Struct的存在,将该元素类型从....Struct //替换为 .... elementName_Struct = + type_name + "_" + + "_" + "Struct";// string structType = type_name + "_" + + "_" + "Struct";//该名称为其新的类别. (item);//首先将该元素加入列表. List GetElementsFromReader(reader, sub, structType, Dict); Dict[structType] = sub; break; case "END_STRUCT"://当接受到这个时,表明一个Type或者一个DataBlock的解析工作结束了,返回上层对象. return; default: break; } } } 5,工具函数1,用于帮助判断DB文件每行的信息. private static Tuple line) { if (!GetReaderLineTYPE(line).Contains("ELEMENT")) throw new Exception("this line is not element " + line); = Array int pos = f(" : "); string Name = (pos).Trim(' ', ';'); var t = f(' '); if (t > 0) Name = (t); string Type = ing(pos + 3).Trim(' ', ';'); if (ns(" :=")) Type = (f(" :=")); return new Tuple } private static string GetReaderLineName(string line) { if ((GetReaderLineTYPE(line) != "TYPE") && (GetReaderLineTYPE(line) != "DATA_BLOCK")) throw new Exception("not read name of " + line); return ing(f(' ')).Trim(' '); } private static string GetReaderLineTYPE(string line) { //ine(line); if (ns("TYPE ")) return "TYPE"; if (ns("DATA_BLOCK ")) return "DATA_BLOCK"; if (ns("END_STRUCT;")) return "END_STRUCT"; if ((' ') == "STRUCT") return "STRUCT"; if (th("Struct")) return "StructELEMENT"; if ((th(";"))) return "ELEMENT"; return null; } 6,从之前生成的字典和MainDataBlock中生成 List public static void DeserializeItem(ref double numBytes, List { var PreName = (OrEmpty(preName)) ? "" : preName + "_";//如果前导名称不为空,则添加到展开的名称上去. //这里是个bug,最后将这行放到string生成,或者BaseType生成上,因为会出现多次_ var Info = new ItemInfo() { Name = PreName + , Type = }; switch (eInfo())//解析这个元素的类别 { case pe://基类直接添加. switch () { case "Bool": = ParseAddr(numBytes, item); numBytes += 0.125; break; case "Char": case "Byte": numBytes = g(numBytes); = ParseAddr(numBytes, item); numBytes++; ; break; case "Int": case "UInt": case "Word": numBytes = g(numBytes); if ((numBytes / 2 - (numBytes / 2.0)) > 0) numBytes++; = ParseAddr(numBytes, item); numBytes += 2; ; break; case "DInt": case "UDInt": case "DWord": case "Time": case "Real": numBytes = g(numBytes); if ((numBytes / 2 - (numBytes / 2.0)) > 0) numBytes++; = ParseAddr(numBytes, item); numBytes += 4; ; break; default: break; } (Info); break; case ://string类型.直接添加 numBytes = g(numBytes); if ((numBytes / 2 - (numBytes / 2.0)) > 0) numBytes++; //---------- = ParseAddr(numBytes, item); numBytes += ingLength();//如果是string,则加256,否则加 xxx+2; (Info); break; case ://数组则进行分解,再迭代. //------------原程序的可能是个漏洞. numBytes = g(numBytes); if ((numBytes / 2 - (numBytes / 2.0)) > 0) numBytes++; //------------- var elementType = mentType(); for (var i = 0; i < ayLength(); i++) { var element = new ElementItem() { Name = + $"[{i}]", Type = elementType }; DeserializeItem(ref numBytes, itemInfos, PreName, element); } break; case ://PLC类型,进行分解后迭代. numBytes = g(numBytes); if ((numBytes / 2 - (numBytes / 2.0)) > 0) numBytes++; //格式 foreach (var element in mentItems(Dict)) { DeserializeItem(ref numBytes, itemInfos, PreName , 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 List { DirectoryInfo dir = new DirectoryInfo(path); FileInfo[] files = es("*.db"); List foreach (var file in files)//将每个文件的db块加入到 db数组中,再将解析的TYPE都加入到字典中. { ElementItem MainBlock; GetTypeFromFile(Dict, me, out MainBlock); if (MainBlock != null) { = (f('.')); (MainBlock); } } foreach (var block in blocks)//然后迭代解析每个DB块的信息,将求加入到第二个字典中. { double numBytes = 0.0; List DeserializeItem(ref numBytes, itemInfos, "", block); DataBlocks[] = itemInfos; } } 8,使用CSVHELPER类从昆仑通态导出的文件中来读取信息 public { //用csvhelper类读取数据:注意,必须用这个方法读取,否则是乱码! using { using { //读取4行无效信息............. (); infos[0] = ld(0); (); (var csv = new CsvReader(reader, antCulture)) (var reader = new StreamReader(path, oding("GB2312"))) static MacInfos[] GetCSVInfosFromFile(string path,string[] infos) infos[1] = ld(0); (); infos[2] = ld(0); (); infos[3] = ld(0); //读取所有的数据放入一个数组中.标准用法. var records = ords 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[] 效信息字符串数组. foreach(var key in )//轮询每个DB块. macInfos = GetCSVInfosFromFile(csvpath,strinfos);//获取MacInfos数组和无 { var infos = (from info in macInfos where select info).ToArray();//将找到的Macinfo中再去查找对应的DB块的Macinfos[]数组. WriteDBToMacInfos(key, infos);//然后将对应的db块的信息,(找到其中的元素的名称,一一写入对应infos的变量名之中. } //填充完所有的Macinfos数组后,将这个数组反写回CSV文件. using (var writer = new StreamWriter(new FileStream(csvpath, ), oding("GB2312"))) using { //将原来的无效信息反填充回去. ield(strinfos[0]); cord();//转移到下一行.去读写数据. ield(strinfos[1]); cord(); ield(strinfos[2]); cord(); ield(strinfos[3]); cord(); (var csv = new CsvWriter(writer, antCulture)) GetDBFromMacInfo(info) == (f('_')) //然后填充数组. ecords(macInfos); } } 10,结论: 1,在使用的时候,首先导出DB块的块生成源, 2,然后将所有的*.db文件们放入c:macfile文件夹之中. 3,使用昆仑通态软件导入标签功能,导入db块和导入utc块,将变量导入到软件中,这个时候,变量名一栏是空的. 4,使用导出设备信息,将导出一个csv文件. 5,运行小软件.结束. 附上Git地址和我的邮箱地址, 有喜欢工控软件开发的多多沟通交流.
版权声明:本文标题:解析博图数据块(昆仑通态触摸屏自动命名) 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.freenas.com.cn/free/1703399287h449563.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论