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 itemInfos, string preName, ElementItem item)

{

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 list;

while (!tream)

static void GetTypeFromFile(Dictionary

List> Dict, string path, out ElementItem

{

string line = ne();//读取一行数据进行判断

switch (GetReaderLineTYPE(line))//通过解析函数解析这一行的数据是什么类型.

{

case "TYPE"://如果是类型 对应 db文件里面 TYPE xxxx 的格式

list = new List();//则创建一个列表准备容纳该TYPE的ELementItem,也就是元素集.

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 tp;

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 list, string type_name,

Dictionary> Dict)

tp = GetElementInfo(line);

= 1;

(dexOf("Struct"));//由于Struct的存在,将该元素类型从....Struct

//替换为 .... elementName_Struct

= + type_name + "_" + + "_"

+ "Struct";//

string structType = type_name + "_" + + "_" +

"Struct";//该名称为其新的类别.

(item);//首先将该元素加入列表.

List sub = new List();//将该子类别(我们自定义的类别,添加到字典中.

GetElementsFromReader(reader, sub, structType, Dict);

Dict[structType] = sub;

break;

case "END_STRUCT"://当接受到这个时,表明一个Type或者一个DataBlock的解析工作结束了,返回上层对象.

return;

default:

break;

}

}

}

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

private static Tuple GetElementInfo(string

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(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 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也就是展开DB块信息.

public static void DeserializeItem(ref double numBytes,

List itemInfos, string preName, ElementItem item)

{

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> Dict, Dictionary

List> DataBlocks)

{

DirectoryInfo dir = new DirectoryInfo(path);

FileInfo[] files = es("*.db");

List blocks = new 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 itemInfos = new 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().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[]

效信息字符串数组.

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地址和我的邮箱地址,

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


本文标签: 进行 类型 元素 信息 文件