工業 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 無法監看或內部緩存異常的事件 |
知道上述的設定用法後,要寫一隻監看檔案系統變化的程式碼其實就呼之欲出了,直接用程式碼來說明:
public static void Run()
{
// 建立一個 FileSystemWatcher 實體,並設定相關屬性
var watcher = new FileSystemWatcher
{
// 設定要監看的資料夾
Path = "C:\MonitoringFolder",
// 設定要監看的變更類型,這裡設定監看最後修改時間與修改檔名的變更事件
NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName,
// 設定要監看的檔案類型
Filter = "*.CSV",
// 設定是否監看子資料夾
IncludeSubdirectories = false,
// 設定是否啟動元件,必須要設定為 true,否則監看事件是不會被觸發
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
。
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}");
}
}
}
上面的程式執行如下:
所產生的資料會長得像這樣:
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
類別,並在建構式中設定好要監看的相關屬性,參考以下程式碼:
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,
// 設定是否啟動元件,必須要設定為 true,否則事件是不會被觸發
EnableRaisingEvents = true
};
}
/// <summary>
/// 設定監看新增檔案的觸發事件
/// </summary>
public Watcher WatchCreated()
{
_watch.Created += new FileSystemEventHandler(_Watch_Created);
return this;
}
/// <summary>
/// 設定監看修改檔案的觸發事件
/// </summary>
public Watcher WatchChanged()
{
_watch.Changed += new FileSystemEventHandler(_Watch_Changed);
return this;
}
/// <summary>
/// 設定監看重新命名的觸發事件
/// </summary>
public Watcher WatchRenamed()
{
_watch.Renamed += new RenamedEventHandler(_Watch_Renamed);
return this;
}
/// <summary>
/// 設定監看刪除檔案的觸發事件
/// </summary>
public Watcher WatchDeleted()
{
_watch.Deleted += new FileSystemEventHandler(_Watch_Deleted);
return this;
}
/// <summary>
/// 當所監看的資料夾有建立文字檔時觸發
/// </summary>
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());
}
/// <summary>
/// 當所監看的資料夾有文字檔檔案內容有異動時觸發
/// </summary>
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());
}
/// <summary>
/// 當所監看的資料夾有文字檔檔案重新命名時觸發
/// </summary>
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());
}
/// <summary>
/// 當所監看的資料夾有文字檔檔案有被刪除時觸發
/// </summary>
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());
}
}
其實主要的程式邏輯和第一段的基本用法沒有甚麼不同,只是封裝成一個自訂的類別,然後加入了鏈接設定的技巧,這鏈接的處理方式,讓我在使用次類別時,能更簡單的調用想要監看的事件:
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。
參考資料: