4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

"親フラ"回避!Discordと連動する物理ON AIRランプ

Last updated at Posted at 2025-12-23

【Seeed XIAO ESP32C3 + Rust】DiscordのVC参加を知らせる「ON AIRランプ」を作った話

この記事は身の回りの困りごとを楽しく解決! by Works Human Intelligence Advent Calendar 2025の何日目かの記事です。
テーマが「身の周りに起きた困りごとを技術で楽しく解決した話」ということで、私はずばり"親フラ"に困っていまして、DiscordのVCに参加すると点灯する「ON AIRランプ」の話を紹介します。
私は友人とよくゲームをするのでDiscordのVCを頻繁に利用します。
しかし、PC内のステータスは、同じ家に住む家族には伝わっていません。
それを視覚的に伝える「ON AIR」ランプがこちらです↓

onair_on.jpg

システム構成・環境

今回の構成は、PC側でサーバーを立て、マイコン(XIAO ESP32C3)から取りに行く「ポーリング方式」を採用しました。

  1. Server (PC側 / Rust):
    * Discord Bot (serenity): 私のVC参加ステータスを常時監視します。 * Web Server (axum): 現在のステータス(on または off)を返すAPIを提供します。
    * これらを1つのRustバイナリ内で非同期実行(tokio)し、状態をメモリ上で共有しています。
  2. Client (デバイス側 / C++, PlatformIO):
    * Seeed Studio XIAO ESP32C3: 自宅のWi-Fiに接続します。 * 定期的にPCのWebサーバーへHTTPリクエスト(GET /status)を送り、状態を確認します。
    * ステータスに応じてGPIOピン(D0)を制御し、LEDやリレーをON/OFFします。

使用マイコンはこれ↓

1. 実装

ソースコード全体はこちらにあります。

サーバーサイド (Rust)

サーバー側は、Rustのエコシステムを活用してシンプルかつ堅牢に実装しました。
main.rs では、Discord Bot用の serenity と、Webサーバー用の axumtokio::select! で同時に動かしています。
VCの入退出イベントを検知すると、共有しているステート(SharedStatus)を書き換えます。

// rust-server/src/main.rs より抜粋
async fn voice_state_update(&self, ctx: Context, old: Option<VoiceState>, new: VoiceState) {
    // ... (中略) ...
    // 参加/退出の判定
    let is_joining = old.as_ref().and_then(|o| o.channel_id).is_none() && new.channel_id.is_some();
    let is_leaving = old.as_ref().and_then(|o| o.channel_id).is_some() && new.channel_id.is_none();

    // ステータス更新処理
    // ...
}

Webサーバー側は、その共有ステートを読み取ってJSONで返すだけです。

// rust-server/src/main.rs より抜粋
async fn get_status_handler(State(state): State<AppState>) -> Json<VoiceStatusResponse> {
    let status_str = state.read().await.status.clone();
    Json(VoiceStatusResponse { status: status_str })
}

クライアントサイド (C++ / Arduino)

クライアントは以前のM5Stackから、指先サイズのマイコン Seeed Studio XIAO ESP32C3 に変更しました。
コードはC++ (Arduino Framework) で、HTTPClient を使ってサーバーをポーリングする単純なループです。

// esp32c3-client/src/main.cpp より抜粋
void loop() {
  if (WiFi.status() == WL_CONNECTED) {
    HTTPClient http;
    http.begin(serverUrl); // RustサーバーのURL
    int httpCode = http.GET();

    if (httpCode == HTTP_CODE_OK) {
      String payload = http.getString();
      // JSONパースして "on" なら LED点灯
      // ...
    }
    http.end();
  }
  delay(3000); // 3秒ごとにチェック
}

開発環境は VSCode + PlatformIO を使用しており、Wi-Fi設定などは secret.ini に分離して管理できるようにしています。

3. ハードウェア

今回は物理的な「ライトの制御」に特化するため、画面のないXIAO ESP32C3を採用しました。非常に小型なので、筐体の中に隠すのが容易です。

  • Seeed Studio XIAO ESP32C3
  • LED または リレーモジュール (100Vのライトを制御する場合)
    配線はシンプルに、制御したいデバイスを D0ピン に接続するだけです。
    筐体は、3Dプリンタで印刷したものを使用しています。データ自体はThingiverseというフリー3Dデータ配布サイトから拾いました。
    IMG_4210_Original.jpeg

まとめ

私がDiscordのVCに参加するだけで、自動的にランプが点灯するシステムを Rust と XIAO ESP32C3 で再構築しました。
「今、話しかけないで」と口で言う必要がなくなり、家族との無用な衝突を避けられるという実利的なメリットはそのままに、PythonからRustへ、M5StackからXIAOへと技術スタックを刷新することで、作っていて最高に楽しい 体験が得られました。

身の回りの小さな「困りごと」は、新しい技術を試す絶好のチャンスだと改めて感じました。

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?