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,數據對齊的原則,如果當前的數據類型是:
- bool,則使用當前地址.
- byte,則使用對齊方式1
- 其他,則使用對齊方式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,運行小軟體.結束.
有喜歡工控軟體開發的多多溝通交流.