問題現場
RF / Wi-Fi 驗證裡有一段很瑣碎的工作:盯著某顆 AP(無線基地台)還在不在、訊號好不好、有沒有斷線。原本的做法是工程師手動敲 netsh wlan show networks,再用肉眼從那一大段純文字裡撈重點——偶爾做還行,天天盯就很耗人。
敲指令本身不難。真正卡住的是它什麼都撐不起來——三個地方都會痛:
- 沒有歷史:每次只有當下快照,看完就沒了;想回看「這顆 AP 過去兩小時的訊號趨勢」根本無從查起。
- 沒有告警:目標 AP 掉線,得有人剛好盯著畫面才會發現——而盯著一個大半時間都沒事的畫面,本身就很折磨人。
- 無法被接手:流程綁在某個人的操作習慣上,換人、或想交給程式 / AI 跑,都得從頭再摸一遍。
作者備註: 真正的痛點從來不是「指令難打」,是這套流程鬆散到沒有形狀——沒形狀就工具化不了,更別說安全地交給 AI。
最小技術背景
讀這個案子,先認得四個詞就夠,其餘用到再說:
- AP / BSSID:AP 是一台無線基地台;BSSID 是它的硬體識別碼。
netsh:Windows 內建查無線網路狀態的指令。它的輸出是一大段給人讀的純文字——給人看剛好,給程式吃就很痛苦。- SSE(Server-Sent Events):伺服器單向、持續把即時資料推到瀏覽器的機制;用來做不需重整就會更新的儀表板。
- MCP(Model Context Protocol):讓 AI 助理以固定協定呼叫外部工具與資料的介面層。這裡用它當 AI 和本系統之間的「薄轉接層」。
系統架構與資料流
整條設計就是把「人工讀文字」拆成一條看得到、測得動的資料鏈。最要緊的一點:AI 不直接碰 scanner,也不直接碰狀態——它只能從 /api/v1 這個窄窗口、再經 MCP 薄轉接層進來,繞不過去。
flowchart LR IF["WLAN 介面"] --> SC["scanner|定時觸發 netsh"] SC --> PA["netsh_parser|文字→結構化"] PA --> DS["data_store|近期歷史"] DS --> AL["alert_engine|比對門檻"] AL --> WEB["Flask|dashboard + REST"] WEB -->|SSE| BR["瀏覽器"] WEB --> API["/api/v1|給 AI 的窄窗口"] API --> MCP["MCP thin wrapper|不持狀態"] MCP --> AIA["AI 助理"]
關鍵設計決策(含取捨)
| 決策 | 選了什麼 | 否決的替代 | 為什麼 |
|---|---|---|---|
| AI 介接層 | MCP 只做 thin wrapper、不持狀態 | 讓 MCP 自己快取一份狀態 | 否決:等於多養一份「真相」,兩邊一不同步就查不清誰才對 |
| API 版本 | 新增 /api/v1 給 AI、保留 legacy /api/* | 直接改寫舊 API | 否決:舊的人工操作會被我一起弄壞,代價太大 |
| 寫入安全 | dry-run → audit → confirm → persist,且「暫時調」與「永久寫」分離 | AI 請求直接套用 | 否決:AI 一手滑就回不去,等於把整台機器的鑰匙交出去 |
| 設定管理 | runtime config 收斂成單一 thread-safe 物件 | 散落各處的全域變數 | 否決:沒有單一入口,「誰在何時改了什麼」根本追不到 |
代表性技術證據
解析:非結構化文字 → 結構化資料
輸入(示意,已用合成值;非真實 SSID/BSSID):
SSID 1 : LAB-AP-01
BSSID 1 : aa:bb:cc:00:00:01
Signal : 78%
Channel : 36
解析後輸出:
{ "ssid": "LAB-AP-01", "bssid": "aa:bb:cc:00:00:01", "signal_pct": 78, "channel": 36, "band": "5GHz" }
把解析跟真實 OS 呼叫拆開,「文字 → 結構」這段才能單獨測——不必為了測試,真的生出一台會斷線的 AP 來配合。
受控寫入流程
AI 的每個寫入請求都走這條「先預演、要確認、可還原」的路徑:
flowchart TD
R["AI 寫入請求"] --> PLAN["build_plan:算出會改什麼"]
PLAN --> AUDIT["寫稽核 log"]
AUDIT --> DRY{"dry-run?"}
DRY -->|是| OUT["只回報,不動作"]
DRY -->|否| CONF{"通過確認?"}
CONF -->|否| STOP["停止"]
CONF -->|是| SNAP["取 snapshot 還原點"]
SNAP --> APPLY["apply|runtime 或 persist"]
APPLY --> OK["成功:記錄 applied"]
APPLY --> ERR["失敗:restore 還原"]
對應的關鍵實作(pseudo-code,示意):
def apply_change(req, mode):
plan = build_plan(req) # 1. 先算清楚這次到底會動到什麼
audit.log("plan", plan) # 2. 不管哪種模式,先把帳記下來再說
if mode == "dry_run":
return plan # 預演:只回報,絕不真的動手
require_confirmation(plan) # 3. 沒過確認,到這就停
snapshot = state.snapshot() # 4. 動手前先存個檔,等下好反悔
try:
state.apply(plan, persist=req.persist) # 看是暫時調一下、還是要寫死
except Exception:
state.restore(snapshot) # 5. 出事就立刻復原,當沒發生過
raise
audit.log("applied", plan)
這段就是「讓 AI 動手」的信任邊界:先預演、留稽核、要確認、可還原。
踩過的坑
| 欄位 | 內容 |
|---|---|
| 症狀 | demo 長時間執行後,CPU 緩升、SSE 連線數不收斂 |
| 初判(猜錯) | 第一反應是 dashboard 前端在漏記憶體 |
| 驗證 | 觀察後端,發現 WLAN 介面短暫不可用時 scan loop 沒退避;瀏覽器關閉後 SSE generator 沒結束 |
| 根因 | scan loop 缺 backoff;SSE 缺 client-disconnect 處理 |
| 修法 | 介面錯誤時指數退避;SSE 加心跳與斷線偵測,client 離開即結束 generator |
| 預防 | 新增「介面不可用」與「client 中途斷線」兩個 regression 情境 |
驗證方式
- 解析:以固定
netsh樣本對期望結構化輸出做 table-driven 單元測試。 - AI 寫入路徑:盯三件事——dry-run 真的沒動到狀態、confirm 後才改、丟例外時會乖乖 restore。
- 資源:手動在多分頁反覆開開關關,盯著 SSE 連線數有沒有乖乖回到 0。
- 相容性(驗收標準):legacy API 行為不變;
/api/v1只能在受控邊界內操作。
可複製 SOP:讓 AI 安全操作一個既有系統
適用情境:你要讓 AI 或自動化,接手一個「會改變狀態」的系統——改設定、寫資料、觸發動作這類,做錯了會留下痕跡的。
不適用情境:純唯讀查詢、且不觸發任何副作用時,可省略 confirm 與 snapshot。
前置條件(做之前要先具備)
- 稽核機制(audit log):能記下「誰、何時、做了什麼、結果如何」。
- 可回復點:snapshot、備份或交易,能把狀態還原到動作之前。
- 與「人用介面」分開、且版本獨立的程式介面(API)。
步驟
- 開窄窗口:給 AI 開一道獨立、有版本的門(像
/api/v1),只露出非開不可的操作,別讓它跟人用的 legacy 介面擠同一個。 - 先預演:任何寫入都先空跑一次 dry-run,把「會改什麼」攤出來看,先別真的動。
- 要確認:dry-run 的結果得過一關(人工或上層規則點頭),才準進 apply。
- 分級寫入:講清楚這是「暫時調一下(runtime-only)」還是「寫死(persist)」,預設挑影響最小的那個。
- 取還原點:apply 之前先 snapshot,給自己留條退路。
- 可還原地套用:apply 失敗就 restore,成功才記一筆 applied。
- 全程留痕:plan、confirm、apply、restore,每一步都寫進稽核。
停損點
- dry-run 結果與預期不符 → 停,不要 apply。
- 找不到可回復點(無法 snapshot / 備份)→ 停,先補上再做。
驗收標準
- 既有人工操作行為零變動(legacy 行為相容)。
- 任一 AI 寫入都查得到、且可還原。
- dry-run 不改到任何狀態(以測試證明)。
回滾:以 apply 前的 snapshot 為還原點;必要時停用 /api/v1 而不影響人工操作。
可複製到:議題追蹤匯入、Wi-Fi 摘要回寫,任何「AI 會寫資料」的場景。
決策回放(三個月後回讀用)
- 背景:把現場 RF 監控從「手動敲 netsh、肉眼讀」工程化,順手替之後的 AI 接手留好空間。
- 限制:不能改 legacy route 行為;不可外流真實 SSID / BSSID / IP;Windows 平台特定行為要可測。
- 考慮過但放棄:本來想讓 MCP 自己存一份狀態快取,想想還是算了——兩份狀態遲早會打架。
- 證據:
netsh_parser測試、/api/v1受控寫入路徑、audit log。 - 未解:哪些 dashboard 截圖遮一遮可以公開;MCP 要不要真的上 production。
作者備註: 這案子真正的價值不是「會掃 Wi-Fi」——那段
netsh早就會了——是把「讓 AI 動手」變成我敢按下去、出事也收得回來的風險。換個工具我還是先把這套邊界搭好、再談功能;順序反過來,遲早會還。