社会課題克服案:ESP32 + 加速度センサー:常時監視 → 異常時FFT → サーバ送信(実装設計まとめ)

狙い:FFTを常時回さず、軽い異常検知でトリガして FFT を必要時だけ実行し、イベントをサーバへ送る。
はじめに:社会課題と「こんなのあったらいいな」
監視したい場所ほど、人手が足りません。見回りや点検が必要な場所は増える一方なのに、現場の人数は減り、移動時間もコストも重くなる。 しかも災害や強風・豪雨の後は「どこから見に行く?」が悩みのタネになります。
社会課題(現場で起きていること)
- 人手不足で「定期点検の頻度を上げられない」。
- 異常が起きたあとに駆けつけるが、どこが危ないのか分からないので優先順位がつけづらい。
- 「たぶん大丈夫」と「やっぱり危ない」の間に、判断の根拠が薄い。
- 監視システムを入れても、誤検知が多いと運用が破綻する(鳴りすぎる警報は嫌われる)。
こんなのあったらいいな(理想の姿)
平常時は静かに見守り、異常の兆しだけを拾って、「何が起きたか」を説明できる形で記録してくれる。 そして必要な情報だけを自動でサーバに送って、あとから比較・分析できる――。
要望を一言にすると:
「見回りに行く前に、行くべき場所と“根拠”が分かる仕組みがほしい。」
「見回りに行く前に、行くべき場所と“根拠”が分かる仕組みがほしい。」
だったら、こんなの考えてみた
そこで、ESP32 + 加速度センサーで常時監視しつつ、 平常時は軽い指標で監視し、怪しい瞬間だけFFTで“中身”を出す構成を考えました。 さらに、その結果をイベントとしてサーバに送れば、現場の判断と説明が一気に楽になります。
1. 目的と方針(結論)
- 加速度センサーをESP32に接続して常時監視する。
- 「おかしい」状態になった瞬間だけFFTを実行し、周波数特徴(ピークHz・帯域エネルギーなど)を算出する。
- 算出した特徴量(必要なら生波形の一部)を特定サーバへ送信する。
なぜFFTは常時やらない?
- FFTは重い(CPU・電力・通信・誤検知のコストが増える)。
- 「異常の入口」はFFTより軽い指標(RMS/分散/ピーク/継続時間)で十分検知できる。
- FFTは「原因の説明」「分類」「レポート」に強い。トリガ後に回すのが合理的。
推奨サンプリング
構造物/地震:100〜400Hz / 機械振動:〜1kHz(目的次第)
推奨FFT点数
256 or 512(分解能=Fs/N)
リングバッファ
常に最新10秒分程度(イベント前後を切り出す)
送信
異常時のみ HTTP POST(またはMQTT)
3. アーキテクチャ(壊れにくい構成)
[Sampler] 等間隔サンプリング(最重要)
↓ (a(t) をリングへ)
[Detector] 軽量異常判定(RMS/分散/ピーク/継続時間)
↓ (閾値超えでイベント発火)
[FFT Worker] イベント時のみFFT(ピークHz/帯域エネルギー等)
↓
[Uploader] HTTP POST / MQTT 送信(失敗時リトライ / キュー)
タスク分割(ESP32の勝ち筋)
- Sampler:タイマー割り込み or 高優先度タスク(等間隔を守る)
- Detector:短窓で指標を更新(軽い)
- FFT Worker:トリガ時にリングバッファから切り出してFFT
- Uploader:通信は別スレッド。サンプル取得を邪魔しない
リングバッファ(常時保存)
- 例:Fs=200Hz、10秒 → 2000点保持
- イベント時:直前2秒 + 直後2秒 などを切り出す(「何が起きたか」説明に効く)
5. FFTで出すべき「使える」特徴量
FFTの生スペクトル全部送ると通信が太る。まずは特徴量だけで十分。
推奨(最低限)
- peak_hz:最大ピーク周波数
- peak_mag:ピーク振幅
- band_energy:帯域別エネルギー(例:0–5Hz / 5–15Hz / 15–30Hz)
- rms:イベント窓のRMS
- duration_ms:異常継続時間
FFT前処理(必須)
- DC除去:平均を引く(0Hzの山を潰す)
- 窓関数:Hamming/Hann(漏れを減らす)
- FsとNは固定して、サーバ側で比較可能にする
周波数分解能の目安
freq_resolution = Fs / N
例:
Fs=200Hz, N=512 → 0.39Hz刻み
Fs=400Hz, N=512 → 0.78Hz刻み
7. 実装の要点(落とし穴だけ先に潰す)
等間隔サンプリングが命
- NG:loopでセンサー読み → 時間がブレる(FFTが嘘になる)
- OK:タイマー割り込み or 高優先度タスクで周期固定
通信でサンプリングを止めない
- Wi-Fi/HTTPは遅延が出る。Samplerと同じスレッドでやるとデータが欠ける。
- Uploaderは別タスク。送信キューを持つ。
再送(現場で必須)
- 送信失敗時はキューに残す
- 堅くやるならSPIFFS/Flashに一時退避(電源断でも残す)
時刻
- NTPで時刻合わせ(イベントの突き合わせができる)
付録:イベント処理フロー(擬似コード)
// Sampler: Fs固定で加速度取得 → ringへ
a = accel_to_motion_component(x,y,z) // |a|-1g 等(後で改善可能)
ring.push(a)
detector.feed(a)
// Detector: 短窓RMS + 継続時間
rms = detector.rms_0_5s()
if (rms > baseline_rms * TH && over_ms >= HOLD_MS) {
event_id = new_id()
fftQueue.push({event_id, now_ms})
}
// FFT Worker: ringから直近N点+前後を切り出し
buf = ring.slice(last N points or pre/post)
buf -= mean(buf)
window(buf)
mag = FFT(buf)
features = extract_peak_and_band_energy(mag)
// Uploader: JSON送信(失敗時リトライ)
payload = build_payload(device_id, ts, features, ...)
post_json(SERVER_URL, payload)
