zoukankan      html  css  js  c++  java
  • 解析博图数据块(昆仑通态触摸屏自动命名)

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

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

  • 相关阅读:
    [NOI2019]回家路线(最短路,斜率优化)
    LOJ6686 Stupid GCD(数论,欧拉函数,杜教筛)
    Codeforces Global Round 4 题解
    CF908G New Year and Original Order(DP,数位 DP)
    [BJOI2019]光线(DP)
    CF1194F Crossword Expert(数论,组合数学)
    SPOJ31428 FIBONOMIAL(斐波那契数列)
    Codeforces Round 573 (Div.1) 题解
    [THUPC2018]弗雷兹的玩具商店(线段树,背包)
    数学基础
  • 原文地址:https://www.cnblogs.com/frogkiller/p/12374010.html
Copyright © 2011-2022 走看看