Raspberry Shake で自作する震度計。やっと動いた話
- ⏱ 約7分で読めます - 👁 … viewsずっとやりたかったことが、ようやく形になりました。
Raspberry Shake という地震計を持っています。RS4D という機種で、加速度計と速度計が一体になったやつです。2017年に買ってから9年近く経つのに、「データは取れてる、でも震度はわからない」という中途半端な状態がずっと続いていました。
気象庁の計測震度というのは、3成分(上下・南北・東西)の加速度を受け取ってJMAフィルタを掛け、0.3秒以上継続する最大値から I = 2×log₁₀(a) + 0.94 という式で計算します。式だけ見ると単純なんですが、フィルタの仕様が独特で、最初に実装したときはまったく合いませんでした。
最初のバージョンは何が駄目だったか
半年ほど前に一度、似たようなことを試みました。感度値の掛け方が逆だったり、STA/LTAのバッファ管理がおかしかったりで、震度がまったく現実と合わない状態でした。「発火はする。でも数値がおかしい」という、デバッグがしんどい状況です。
結局そのまま放置していました。
今回、Claude Code と一緒に一から作り直すことにしました。
作ったもの
ターミナルで動く TUI(テキストUI)ダッシュボードです。最近はGUIよりもTUIがマイブームなので、今回は rich というライブラリで作りました。ターミナル上で軽く動いて、SSH越しでも見られるのがいいところです。こんなものが画面に出ます。

- リアルタイムの計測震度と加速度(gal)
- Z/N/Eの3成分波形スパークライン
- STA/LTAのバー表示と直近5分の推移グラフ
- I値の推移グラフ
- トリガ履歴(地震を検出した時刻・震度の一覧)
- P2P地震情報から引っ張ってきた全国の地震情報(WebSocket接続でリアルタイム)
- EEWの参考表示(P2P経由なので無保証ですが、一応出ます)
受信スレッド・計算スレッド・描画の3層構成で、パケットの取りこぼしなく処理できるようにしました。
あと、地震を検出したときにVoiceVoxで音声アラートが出ます。No.7のアナウンス体で、震度によって「揺れを検出。」「注意!地震です。」「緊急警報!非常に強い地震です。」と変わります。VoiceVoxが起動していなければmacOSの say コマンドにフォールバックします。
一番悩んだのは「確定I値の記録タイミング」
STA/LTAがトリガした瞬間に計測震度を記録すると、値が低すぎる問題がありました。
なぜかというと、計測震度の計算には一定の窓長(デフォルト90秒)のデータが必要で、トリガした直後はその窓にまだ静穏期間のデータが大量に残っています。だから「震度2だけど本当は3」みたいなことになります。
対策として pending_event という仕組みを入れました。トリガが発火したら時刻だけ記録しておいて、窓長が経過してから改めて確定I値をトリガ履歴に書きます。これで 15:18:35 3.00 震度3 というように正しい値が残るようになりました。
シミュレーターで確認できたときは、正直ちょっと声が出ました。
シミュレーターを作ったのが大きかった
実機でテストを繰り返すのは限界があります。地震は来ないし、Raspberry Shakeを無理に揺らすのも変な話なので。
なので simulate_udp.py というスクリプトを作りました。目標とする計測震度を指定すると、それに対応した合成正弦波をUDPパケットとして吐き出すやつです。震度0から7まで任意に設定できて、TUIに流し込んで動作確認できます。
これがあったから、「震度3のシミュレーションを流したら、90秒後に3.00が記録される」という確認ができました。実機がなくても開発を進められるのは思った以上に大きかったです。
JMAフィルタの実装が正しいか不安だったので検証スクリプトも書いた
verify_filter.py という5項目のテストスクリプトを作って、フィルタ特性・DC除去・逆算精度・0.3秒閾値の動作・震度階級変換の境界値をチェックできるようにしました。全部パスしているのを確認したときは、さすがに安心しました。
実は気象庁の公式サイトにJMAフィルタの仕様が公開されているんですが、数式の解釈で迷う部分があります。ここはClaude Codeと一緒に論文レベルで整理しながら実装できたのが効きました。
P2P地震情報はWebSocketに切り替えた
最初は60秒ごとにHTTPポーリングしていたんですが、「WebSocketのほうがよくない?」となって切り替えました。wss://api.p2pquake.net/v2/ws に常時接続して、code=551(地震情報)とcode=556(EEW)をリアルタイムで受け取ります。
EEWについては、P2P経由なので「参考情報です・無保証です」という表示にしています。気象庁の正式なEEWとは違います。でも、「なんか大きそうだ」という最初の気づきにはなります。
接続が切れたら5秒後に自動再接続します。websocket-client が入っていなければHTTPポーリングにフォールバックします。
起動がめんどくさかったので start.sh を作った
VoiceVoxを手動で立ち上げてからTUIを起動する、という手順が毎回面倒だったので、まとめました。
./start.sh --station R38DC
これだけで、VoiceVoxが起動していなければ自動で立ち上げて、起動を待って、TUIを起動します。websocket-client が入っていなければそのままインストールもします。地味ですが、こういうのが日常使いには一番大事だと思っています。
やっと「使える」ものになった
Raspberry Shakeを買ったのは2017年で、「いつかちゃんと震度計として使いたい」と9年近くずっと思っていました。
前回のバージョンは「動く」とは言えない状態だったので、今回の完成は素直に嬉しかったです。実際に5月22日の栃木の地震がリストに出たとき、ちゃんと動いてるな、と思いました。
ブラウザからも見られるようにした
TUIは手元のターミナルでしか見られません。「外出先や別の部屋のスマホからも確認したい」という気持ちが出てきたので、同じデータをブラウザで表示するWeb版も作りました。

サーバーは FastAPI + uvicorn で、WebSocketを使って1秒ごとにブラウザへデータをプッシュします。TUI版のコアロジック(SharedState・recv_loop・compute_loop)をそのままimportして使っているので、計算部分は完全に同じです。
画面の構成はこんな感じです。
- 左カラム:現地の計測震度(大きな数字)、I値、加速度(gal)、STA/LTAバー、トリガ履歴
- 中央カラム:日本地図(Leaflet.js)+推移グラフ(Chart.js)
- 右カラム:P2P地震情報の最新リスト
地図が特に気に入っています。P2P地震情報で取得した各地の震度を市区町村単位で色分けして塗り、震源に×印のマーカーを立てます。マーカーのサイズはマグニチュードに比例させました。震源をクリックするとその周辺にズームして、市区町村ごとの震度が色で分かります。
トリガ履歴の行をクリックすると、時刻が近いP2P地震情報と自動的に照合して同じズーム表示が出るようにもしてあります。「この揺れは何の地震だったのか」がすぐわかります。
バックグラウンドタブに移動したときはWebSocket接続を自動で切断し、前面に戻ったときに再接続する仕組みも入れました。タブを放置したあとに戻ってきたときに、溜まったメッセージが一気に流れて画面が高速スクロールする問題がこれで解消されました。
起動は TUI と同様で、ポートを指定するだけです。
.venv/bin/python3 jma_intensity_web.py --station R38DC --web-port 8080
TUIと同時に起動する場合はRaspberry ShakeのDATACAST設定で送信先を2エントリ登録し、それぞれ別のUDPポートを割り当てると競合しません。
コードはGitHubに置いてあります。Masakai/earthQuake
Raspberry Shakeを持っていて、同じようなことをやりたい人の参考になれば。