← 回作品列表

RF 監控協作框架

AI-Operable RF Monitoring Harness

將現場 Wi-Fi / AP 掃描從人工 netsh 操作,工程化為可視化儀表板、版本化 REST API,與一個受控的 AI 操作介面(dry-run → audit → confirm → persist)。

rf RFWi-FiFlaskREST APIMCPAI Harness 原始碼:私有

問題現場

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)。

步驟

  1. 開窄窗口:給 AI 開一道獨立、有版本的門(像 /api/v1),只露出非開不可的操作,別讓它跟人用的 legacy 介面擠同一個。
  2. 先預演:任何寫入都先空跑一次 dry-run,把「會改什麼」攤出來看,先別真的動。
  3. 要確認:dry-run 的結果得過一關(人工或上層規則點頭),才準進 apply。
  4. 分級寫入:講清楚這是「暫時調一下(runtime-only)」還是「寫死(persist)」,預設挑影響最小的那個。
  5. 取還原點:apply 之前先 snapshot,給自己留條退路。
  6. 可還原地套用:apply 失敗就 restore,成功才記一筆 applied。
  7. 全程留痕: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 動手」變成我敢按下去、出事也收得回來的風險。換個工具我還是先把這套邊界搭好、再談功能;順序反過來,遲早會還。