工業 4.0 最早從 2011 年德國的漢諾瓦工業博覽會提出,台灣在 2014 年也提出生產力 4.0 發展方案 ,不管是哪個 4.0 都會出現 IoT 物聯網的概念,然而大多數現行的機台上,即便有感測器去蒐集資料,但往往只是單純的做 Log 儲存成檔案 ,供後續有心人接續利用,離我們自動化蒐集感測資料,連網上傳資料進行分析作業,有一段不知如何跨越的距離,但其實我們只要做到持續監看 Log 的變化,並轉交由分析系統,傳統的機台也是可以沾點工業 4.0 的光,.NET 提供的 FileSystemWatcher 類別正是讓傳統機台發光的黑魔法。
FileSystemWatcher 基本用法 要做到監看檔案系統的變化,可以透過 System.IO 這個命名空間之下的 FileSystemWatcher 物件來幫我們簡單達成。
FileSystemWatcher 物件使用上有幾個比較重要的屬性:
FileSystemWatcher 屬性
說明
Path
設定要監看的資料夾
NotifyFilter
設定要監看的變更類型
Filter
設定要監看的檔案類型
IncludeSubdirectories
是否監看子目錄
EnableRaisingEvents
開始或停止監看
其中 NotifyFilter 這個屬性又可以設定監看以下這幾種變更類型:
NotifyFilter 選項值
說明
FileName
檔案名稱的變更
DirectoryName
資料夾名稱的變更
Attributes
檔案或資料夾的屬性變更
Size
檔案或資料夾的尺寸變更
LastWrite
檔案或資料夾的最後修改時間變更
LastAccess
檔案或資料夾的存取時間變更
CreationTime
檔案或資料夾的建立時間變更
Security
檔案或資料夾的安全性變更
FileSystemWatcher 物件還有以下四種重要的監看事件,作為驅動我們處理邏輯的觸發:
FileSystemWatcher 事件
說明
Created
指定的路徑中,發生檔案或資料夾新增的事件
Changed
指定的路徑中,發生檔案或資料夾修改的事件
Deleted
指定的路徑中,發生檔案或資料夾刪除的事件
Renamed
指定的路徑中,發生檔案或資料夾重新命名的事件
Error
發生 FileSystemWatcher 無法監看或內部緩存異常的事件
知道上述的設定用法後,要寫一隻監看檔案系統變化的程式碼其實就呼之欲出了,直接用程式碼來說明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public static void Run (){ var watcher = new FileSystemWatcher { Path = "C:\MonitoringFolder" , NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName, Filter = "*.CSV" , IncludeSubdirectories = false , EnableRaisingEvents = true }; watcher.Changed += new FileSystemEventHandler(OnChanged); watcher.Created += new FileSystemEventHandler(OnChanged); watcher.Deleted += new FileSystemEventHandler(OnChanged); watcher.Renamed += new RenamedEventHandler(OnRenamed); Console.ReadLine(); } private static void OnChanged (object source, FileSystemEventArgs e ){ Console.WriteLine("檔案變更: " + e.FullPath + " " + e.ChangeType); } private static void OnRenamed (object source, RenamedEventArgs e ){ Console.WriteLine("檔名變更: {0} 重新命名為 {1}" , e.OldFullPath, e.FullPath); }
模擬持續寫入資料 接著我們來模擬一個 IoT 情境,假設我們有一個機台約 1 秒的時間間隔會產生一筆資料,並將資料持續寫進檔案,因此我們先寫一隻作為模擬機台感測到資料,並將資料寫入 Log 中的程式。
這裡假設欄位結構會像這樣:序號,日期,數值1,數值2,數值3。
1 2 3 4 5 6 7 8 9 10 11 12 13 private static void AppendTextSimulator (string filePah ){ var rand = new Random(); var date = DateTime.Now; for (var i = 0 ; i < 1000 ; i++) { Thread.Sleep(rand.Next(1000 , 1200 )); using (var sw = File.AppendText(filePah)) { sw.WriteLine($@"{i} ,{date.ToShortDateString()} ,{rand.Next(1 , 10 )} ,{rand.Next(50 , 100 )} ,{(float )rand.Next(1 , 10 ) / 10 } " ); } } }
上面的程式執行如下:
所產生的資料會長得像這樣:
1 2 3 4 0,7/2/2018,8,79,0.1 1,7/2/2018,9,87,0.3 2,7/2/2018,8,94,0.4 3,7/2/2018,5,63,0.8
完整程式碼請參考 poychang/Demo-FileSystemWatcher 中的 ContinuousWriteFileApp 專案。
監看檔案變化程式 這裡自己建立了一個 Watcher 類別,並在建構式中設定好要監看的相關屬性,參考以下程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 public class Watcher { private readonly FileSystemWatcher _watch; public Watcher (string watchFolder ) { _watch = new FileSystemWatcher { Path = watchFolder, NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName, Filter = "*.CSV" , IncludeSubdirectories = false , EnableRaisingEvents = true }; } public Watcher WatchCreated () { _watch.Created += new FileSystemEventHandler(_Watch_Created); return this ; } public Watcher WatchChanged () { _watch.Changed += new FileSystemEventHandler(_Watch_Changed); return this ; } public Watcher WatchRenamed () { _watch.Renamed += new RenamedEventHandler(_Watch_Renamed); return this ; } public Watcher WatchDeleted () { _watch.Deleted += new FileSystemEventHandler(_Watch_Deleted); return this ; } private static void _Watch_Created(object sender, FileSystemEventArgs e) { var sb = new StringBuilder(); var dirInfo = new DirectoryInfo(e.FullPath); sb.AppendLine($"新建檔案於:{dirInfo.FullName.Replace(dirInfo.Name, "" )} " ); sb.AppendLine($"新建檔案名稱:{dirInfo.Name} " ); sb.AppendLine($"建立時間:{dirInfo.CreationTime} " ); sb.AppendLine($"目錄下共有:{dirInfo.Parent?.GetFiles().Length} 檔案" ); sb.AppendLine($"目錄下共有:{dirInfo.Parent?.GetDirectories().Length} 資料夾" ); Console.WriteLine(sb.ToString()); } private static void _Watch_Changed(object sender, FileSystemEventArgs e) { var sb = new StringBuilder(); var dirInfo = new DirectoryInfo(e.FullPath); sb.AppendLine($"被異動的檔名為:{e.Name} " ); sb.AppendLine($"檔案所在位址為:{e.FullPath.Replace(e.Name, "" )} " ); sb.AppendLine($"異動內容時間為:{dirInfo.LastWriteTime} " ); sb.AppendLine($"最後一筆內容:{File.ReadLines(e.FullPath).Last()} " ); Console.WriteLine(sb.ToString()); } private static void _Watch_Renamed(object sender, RenamedEventArgs e) { var sb = new StringBuilder(); var fileInfo = new FileInfo(e.FullPath); sb.AppendLine($"檔名更新前:{e.OldName} " ); sb.AppendLine($"檔名更新後:{e.Name} " ); sb.AppendLine($"檔名更新前路徑:{e.OldFullPath} " ); sb.AppendLine($"檔名更新後路徑:{e.FullPath} " ); sb.AppendLine($"建立時間:{fileInfo.LastAccessTime} " ); Console.WriteLine(sb.ToString()); } private static void _Watch_Deleted(object sender, FileSystemEventArgs e) { var sb = new StringBuilder(); sb.AppendLine($"被刪除的檔名為:{e.Name} " ); sb.AppendLine($"檔案所在位址為:{e.FullPath.Replace(e.Name, "" )} " ); sb.AppendLine($"刪除時間:{DateTime.Now} " ); Console.WriteLine(sb.ToString()); } }
其實主要的程式邏輯和第一段的基本用法沒有甚麼不同,只是封裝成一個自訂的類別,然後加入了鏈接設定的技巧,這鏈接的處理方式,讓我在使用次類別時,能更簡單的調用想要監看的事件:
1 2 3 4 5 6 7 8 9 10 11 12 13 static void Main (string [] args ){ var monitoringPath = @"C:\TestFileWatcher\" ; Console.WriteLine("Monitoring file changed in target folder." ); Console.WriteLine($"Target Folder: {monitoringPath} " ); new Watcher(monitoringPath) .WatchCreated() .WatchChanged(); Console.ReadLine(); }
完整程式碼請參考 poychang/Demo-FileSystemWatcher 中的 FileSystemWatcherConsoleApp 專案。
最後可以先執行 ContinuousWriteFileApp 專案,模擬持續寫入資料到指定檔案,再執行 FileSystemWatcherConsoleApp 專案,監看檔案的變化,並只輸出檔案中最後一筆紀錄。
透過這樣的模擬情境,對於早就有感測器,也早就會吐出相關的資料出來的老舊機台,我們只要持續監控 Log 的變化,回拋分析系統,老機台也可以 4.0 唷。
本篇完整範例程式碼請參考 poychang/Demo-FileSystemWatcher 。
參考資料: