在自動化設備的程式中,一定會有參數檔案的運用,通常分為以下兩種:
系統參數(System Data) : 指打開程式後,程式要恢復的狀態(ex : 上次程式關閉前選擇的工單、語系…)。
工單參數(Recipe Data) : 指某一類產品的加工(製程)參數,藉著選擇不同工單,讓設備採用不同的製程參數加工。
實作在系統中的流程如下圖,比較需要注意有以下幾點:
1. System data的檔案路徑是唯一的,通常會在程式定好一個檔案路徑。
2. 注意參數初始值的問題。 Ex: 數值型別預設值是0,新增recipe時,會得到一堆數值為0的欄位。
3. 在System data中定義一個string參數 ,儲存當前recipe data的file path。(*1)
4. 文字型別轉型為數值型別時的例外處理與參數設定頁面的控制項防呆。
(*1) 採用Recipe data 的file path是因為recipe system基於windows 的file system,
如果有另外寫自己的recipe system, 也有可能是recipe的index,
不過既然是windows base的系統,採用檔案路徑會比較直覺。
說明完程式架構,來看看參數檔的樣子,最常見的格式就是ini檔,格式如下:
通常會用Win32API (WritePrivateProfileString、GetPrivateProfileString)去讀,細節就不實作了,最後大概都會寫成以下的func。
//取得值:
string GetKeyValue(string section, string key)//設定值:
void SetKeyValue(string section, string key, string value)
使用方式如下:
//初始化 IniFile ini = new IniFile(@"C:\Data.ini"); //取值 string val = ini.GetKeyVaule("Section2","key1"); //val = value1 //寫值 ini.SetKeyValue("Section2","key2","NewValue"); // ini 的section2的key2會變成 NewValue
實作一開始的流程圖,在win form的程式中大概會長以下這樣。(為求簡潔先省略轉型例外處理,另外本文主旨是參數檔案架構,所以並沒有實作Recipe system,一般會另外寫程式來增刪改查Recipe清單,日後會在寫一篇文章介紹Recipe system)
public partial class Form1 : Form { private string systemDataFilePath; private IniSystem iniSystem; public SystemData SystemData { get; set; } public RecipeData RecipeData { get; set; } public Form1() { InitializeComponent(); } private void Form1_Load(object sender , EventArgs e) { systemDataFilePath = @"C:\System\System.ini"; SystemData = new SystemData(); RecipeData = new RecipeData(); loadIniFile(); } private void Form1_FormClosing(object sender , FormClosingEventArgs e) { saveIniFile(); } private void saveIniFile() { iniSystem = new IniSystem(SystemData.RecipeFilePath); iniSystem.SetKeyValue("Recipe" , "Speed" , RecipeData.Speed.ToString()); iniSystem.SetKeyValue("Recipe" , "Acc" , RecipeData.Acc.ToString()); iniSystem.SetKeyValue("Recipe" , "Dec" , RecipeData.Dec.ToString()); iniSystem = new IniSystem(systemDataFilePath); iniSystem.SetKeyValue("System" , "RecipeFilePath" , RecipeData.Speed.ToString()); iniSystem.SetKeyValue("System" , "Data1" , RecipeData.Acc.ToString()); } private void loadIniFile() { if (File.Exists(systemDataFilePath)) { //讀取系統參數 iniSystem = new IniSystem(systemDataFilePath); SystemData.Language = iniSystem.GetKeyValue("System" , "Language"); SystemData.RecipeFilePath = iniSystem.GetKeyValue("System" , "RecipeFilePath"); if (File.Exists(systemDataFilePath)) { //讀取工單參數 iniSystem = new IniSystem(SystemData.RecipeFilePath); RecipeData.Speed = Convert.ToDouble(iniSystem.GetKeyValue("Recipe" , "Speed")); RecipeData.Acc = Convert.ToDouble(iniSystem.GetKeyValue("Recipe" , "Acc")); RecipeData.Dec = Convert.ToDouble(iniSystem.GetKeyValue("Recipe" , "Dec")); } else { //create recipe data file with default vaule } } else { //create system data file with default vaule } } private void recipeChange(string path) { SystemData.RecipeFilePath = path; } private void refreshControls() { //通常會在winform上建立一些控制項去改變recipe or system data的數值 //為了文件簡潔先省略實作 } }
上面程式簡單實作了文章開頭的架構圖,看起來似乎沒有什麼問題,其實魔鬼藏在細節,想當初剛接手設備時也是身受其害,接著就來說說缺點:
1. 新增或刪除參數時,必須逐一修改loadIniFile()、saveIniFile()中對應的程式碼。
2. 如果參數非字串型別,從ini file讀回時,必須一一處理轉型問題
3. 新增或刪除參數時,必須修改在Winform參數頁面的layout對應。
4. 如果參數是自訂型別,必須把實體內層參數提取出來。
5. 無法簡單的處理集合資料,必須一筆一筆資料讀寫。
6. 參數讀寫與控制高度耦合在Winform之中。
第1-3點非常擾人,當設備是處於測試階段時,參數通常都會變來變去,所以要一直去改這些程式碼,只要一個地方沒改好就沒辦法馬上測試,當設備是跟旁人協作時,旁人這時就會發起注視攻擊,一邊問『還要多久?』,菜鳥內心應該感到無限
第3點是當原有的參數設定畫面放不下新的控制項時,若要求介面美觀,必須得一個一個元件去修改layout,這也相當耗費時間,通常都會先隨便拉個介面應急,採取迴避大法說:『這個我要回去改一下』,然後回辦公室邊吃香蕉邊改介面。
第4 5點不一定會遇到,但既然是物件導向的語言,自訂class是很常有的,如果想要以自訂class作為參數型別,在撰寫讀寫部份程式碼時,過程冗長又容易出錯,第5點同4。
第6點是前五點的結論,在簡單的小程式中做上述操作不會太難,吃根香蕉認真地做個碼猴,施展Ctrl+C – V大法,再稍微修修剪剪一下很快就可以完成XD。但…我想簡單的小程式這種東西應該不會存在自動化設備中…
提出這麼多缺點
1. 讀取\寫入檔案一定要這樣一個一個刷物件裡的變數嗎?
2. 基本型別的參數不能自己轉型成對應的型別嗎?
3. 修改參數的介面一定得一個一個拉嗎?
終於到本文的末段了,好不容易才引出這三道題目,那要怎麼解決這些問題呢?查了一下發現序列化可以簡單的解決上述1和2的問題,參考黑大的文章,從下圖可得知,只要把黑大範例中的bigList換成參數實體SystemData、RecipeData就可以依樣畫葫蘆,整個instance序列化與反序列化並且存讀檔,完美避開要刷參數與參數轉型問題。
那第三個問題又要怎麼解決呢?每台設備的參數通常都不會一樣,根本無法統一成一個介面,最後也是避免不了對UI修修改改,其實visual studio已經告訴我們答案,參考下圖,很眼熟吧!這是在VS中控制項的屬性頁,每當你選到不同控制項時,畫面就會變成對應的變數,其實這就是Property Grid,如果我們以Property Grid作為設定參數的頁面,那就算變更參數也不需要再去維護介面了。
這邊先小結一下,所以只要透過序列化加上以PropertyGrid作為參數設定頁面,就可以避免採用Ini與不斷修改參數設定頁面的種種不便,