把地震速報接到自家警報器:WebSocket 斷線與重送實測

这篇内容暂时只有 繁體中文 版本,以下是原文。

去年幫一棟六層舊公寓的管委會做一個小東西:把氣象署的地震速報接到地下室的蜂鳴器跟LINE群。環境是 Raspberry Pi 3、Raspbian 11、Python 3.9,外網走社區的第四台網路,偶爾會瞬斷。資料量不大,一天幾十則,但每一則都要求十秒內送到。

做法很土。先用 WebSocket 訂閱速報頻道,收到 JSON 後只取需要的欄位,寫進一個 SQLite 佇列,再由另一支程式讀取、觸發警報。重點在斷線處理,我沒有用現成的重連套件,自己寫了迴圈:

import json, time, websocket

def run(url, on_event):
    backoff = 1
    while True:
        try:
            ws = websocket.create_connection(url, timeout=15)
            backoff = 1
            while True:
                raw = ws.recv()
                if raw:
                    on_event(json.loads(raw))
        except Exception as e:
            log.warning("ws down: %s", e)
            time.sleep(backoff)
            backoff = min(backoff * 2, 60)

寫入佇列前先算一個事件雜湊(時間、規模、位置),重複就丟掉。這樣斷線重連時,對方補送的舊資料不會讓蜂鳴器半夜亂叫。

為什麼要自己管重連?因為現成套件預設會無限快速重試,遇到社區網路那種「連得上但沒資料」的半死狀態,它不會報錯,只會安靜地掛著。我加了一個 watchdog:超過 90 秒沒收到任何訊息就主動關掉 socket 重連。代價是每天會多幾次無謂的握手,但換來的是不會漏掉真正的速報。

會失效的地方很明確。第一,Pi 的系統時間如果沒校時,事件雜湊會全部對不上,重送就擋不住;我後來加了開機強制 ntpd 同步。第二,地下室收不到手機訊號,LINE 通知只能靠有線網路,網路一斷,本地蜂鳴器還是會響,但群組訊息就補不回來。第三,這種做法只適合「通知」,不適合「判斷」——我曾經拿它去比對兩家速報的規模,結果兩邊演算法不同,同一個地震差到 0.4,後來就放棄了。

還有一個坑:WebSocket 連線閒置太久會被中間的 NAT 踢掉,我原本以為設 timeout 就夠,結果是連線層活著、應用層早就沒資料。現在改成每 30 秒送一次 ping,沒 pong 就重連。

你們那邊如果也是用長連線接速報,斷線後的補送是怎麼去重的?我目前只用時間加座標的雜湊,遇到同一地震更新規模就擋不住,還在想更穩的鍵。

0
赞同度 100% · 0 条评论 · 2 阅读

0 条评论

支持 Markdown · 回复会实时推送给对方

还没有评论,写下第一条。