National Central University
Uedu Main Site
Explore Uedu
Student Console
Register as Member/Login
Research Informed Consent Center
Survey Center
Educational Big Data Challenge
Claim or create a course
Try an AI Scenario Task
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

3

Local AI

--

Uedu Code

--

中央大學 AQI 72 24°C PM2.5 13
AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

Unit 12

用 Python 監控 PLC

輪詢、記錄、告警與趨勢
Interaction 30 minute(s)

Learning Objectives

  • 寫出固定週期的輪詢迴圈,讀取 PLC 的資料並換算成工程單位
  • 用 csv 模組把每一次讀取記錄下來,再讀回檔案做簡單的統計
  • 設計有遲滯的告警:只在狀態改變時通知,並用線圈驅動警報
  • 用 try/except 處理通訊錯誤,讓監控程式不會因為一次失敗就停止

讓電腦替你看著儀表

PLC 負責控制;但「過去一週水箱的液位怎麼變化」「半夜液位太高有沒有人知道」這些問題,PLC 自己不太會回答。 監控軟體(SCADA、HMI)做的事情,用幾十行 Python 就能做出最基本的版本:

  1. 輪詢:每隔固定時間讀一次 PLC 的資料。
  2. 記錄:把每一次讀到的值寫進檔案或資料庫。
  3. 告警:數值超過門檻時通知人。

這一課的 PLC 自己用階梯圖控制水箱:低於 40% 開泵浦、高於 90% 關泵浦。Python 只看、只記錄、只在必要時響警報, 不改變控制邏輯。這是監控程式的基本分工:控制留在 PLC,監控程式出問題時,設備仍照常運轉。

輪詢週期要多久

考量 說明
製程變化多快 液位、溫度變化慢,1~10 秒一次通常足夠;壓力突波要更快
設備的負擔 每一次請求都佔用 PLC 的通訊資源;很多 client 一起高頻輪詢,可能拖慢 PLC
資料量 每秒一筆,一天就有 86400 筆;長期保存時常改成記錄平均值或只記錄變化

逐步看:每秒讀一次液位

看水箱的動畫與即時階梯圖:PLC 自己在控制泵浦,Python 每秒讀一次 IW0。time.sleep(1) 決定輪詢週期,time.monotonic() 用來計算經過的秒數。

記錄成 CSV

CSV(逗號分隔值)是最通用的記錄格式:試算表、pandas、資料庫都讀得懂。Python 內建的 csv 模組會處理逗號、引號這些細節:

import csv
with open("levels.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.writer(f)
    writer.writerow(["seconds", "level"])     # 第一列放欄位名稱
    writer.writerow([0, 50.1])
  • newline="":讓 csv 模組自己處理換行,避免在 Windows 上每列之間多出空白列。
  • encoding="utf-8":欄位裡有中文時不會變成亂碼。
  • 讀回來用 csv.DictReader:每一列變成一個字典,欄位名稱當鍵;讀到的值都是字串,計算前要轉成數字。
  • 長時間記錄時,每一列要有時間戳記;實務上記錄 UTC 時間或 ISO 8601 格式(例如 datetime.now(timezone.utc).isoformat()),顯示時再轉成當地時間。

逐步看:寫入 CSV,再讀回來統計

前半段一邊輪詢一邊寫入,後半段把檔案讀回來。注意讀回來的 r["level"] 是字串,要用 float() 轉換才能算最大值與平均。

告警:只在狀態改變時通知

最簡單的告警是「超過 85% 就印出警告」。但輪詢每秒一次,液位停在 86% 的那幾分鐘,就會連續收到幾百則一樣的警告, 真正重要的訊息反而被淹沒。好的告警要做到:

  1. 記住狀態:用一個變數 alarm 記住目前是不是在告警中。
  2. 只在改變時通知:從「正常」變「告警」時通知一次,從「告警」變「正常」時再通知一次。
  3. 遲滯(死區):超過 85% 才告警,降到 80% 以下才解除。否則液位在 85% 上下抖動時,告警會一直開開關關(和第 7 課的泵浦控制同樣的道理)。

通訊錯誤也要處理:網路偶爾斷一下,read_input_registers 就會丟出例外。如果沒接住,整個監控程式就停了, 之後真的發生高液位也沒人知道。用 try/except 接住 ModbusException(pymodbus 各種錯誤的共同父類別),記下次數,下一輪再試; 連續失敗太多次時,「通訊中斷」本身也應該是一個告警。

逐步看:有遲滯的告警與警報喇叭

看 alarm 變數與畫面上的警報喇叭:液位超過 85% 時只印一次「警報開始」並打開喇叭(Q0.2);之後液位繼續變化也不再重複印,直到降到 80% 以下才解除。

互動練習:寫一個水箱監控程式

PLC 在 192.168.0.10,自己控制水箱(低於 40% 開泵浦、高於 90% 關泵浦),液位從 50% 開始上升。 警報喇叭接在 Q0.2(線圈 2),階梯圖沒有控制它。把起始程式擴充成監控程式,依序完成任務:

  1. 直接執行起始程式,讀到一次液位。
  2. 改成每秒讀一次、共 15 次,每次印出一行,包含液位的百分比。
  3. 每次讀取寫一列到 levels.csv;結束後讀回檔案,印出「最高液位 xx.x %」。
  4. 超過 85% 時打開警報喇叭並印出一次「警報開始」,降到 80% 以下才關閉喇叭;程式結束時喇叭要是關的。
  5. 回答一個問題。

可以參考上面三個逐步動畫的寫法。

小測驗:輪詢與記錄

1.

每 0.5 秒讀一次、連續記錄一天(24 小時),會有多少筆資料?

2.

下面的程式會印出什麼?

import csv, io
f = io.StringIO("seconds,level\n0,50.5\n1,62.0\n")
rows = list(csv.DictReader(f))
print(rows[1]["level"] + "%")
3.

用 open(..., "w") 寫 CSV 時,為什麼要加 newline=""?

第 1 次作答

小測驗:告警

1.

下面的告警邏輯會印出幾次「告警」?

alarm = False
for level in [84, 86, 85, 87, 82, 79, 86]:
    if not alarm and level > 85:
        alarm = True
        print("告警")
    elif alarm and level < 80:
        alarm = False
print("結束")
2.

告警為什麼要用兩個門檻(85% 告警、80% 解除)?

第 1 次作答

重點整理

  • 監控程式的三件事:輪詢(固定週期讀取)、記錄(寫入 CSV 或資料庫)、告警(超過門檻通知)。
  • 控制留在 PLC,監控程式只讀取、記錄、通知;監控程式出問題時,設備照常運轉。
  • 輪詢週期依製程的變化速度與設備的負擔決定;time.monotonic() 適合計算經過的時間。
  • CSV 用 csv 模組寫,開檔加 newline="" 與 encoding="utf-8";讀回來的值是字串,要轉換才能計算。
  • 告警要記住狀態、只在改變時通知,並用兩個門檻避免抖動。
  • 用 try/except ModbusException 接住通訊錯誤並計數;持續失敗本身也是告警。
  • 若記錄的資料涉及人員(例如誰在什麼時間操作),保存與使用要依單位的規定與研究倫理。

下一課換個角色:用 Python 寫一個 Modbus 從站,模擬一台設備,讓別的程式來讀你。

這一課完成了!
Next lesson:用 Python 做 Modbus 從站
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。