当前位置: 移动技术网 > IT编程>开发语言>.net > 解析博图数据块(昆仑通态触摸屏自动命名)

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

2020年03月09日  | 移动技术网IT编程  | 我要评论

火车鸣笛,黎姿早年禁片流出,爱划算

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

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

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网