欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-05-18 10:33:34
1,博图数据块的数据排列原则: 数据对齐算法: 将当前地址对齐到整数:numBytes = (int)Math.Ceiling(numBytes); 将当前地址对齐到偶整数: numBytes = Math.Ceiling(numBytes); if ((numBytes / 2 - Math.Fl... ......

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;
                        else
                            value = false;
  • 赋值
     bytepos = (int)math.floor(numbytes);
                            bitpos = (int)((numbytes - (double)bytepos) / 0.125);
                            if ((bool)obj)
                                bytes[bytepos] |= (byte)math.pow(2, bitpos);            // is true
                            else
                                bytes[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.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
   recipe1.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_struct
                        item.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 macinfos

                             where 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地址,

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