![[小ネタ] Google Meetでマイクかカメラがオンの間だけ赤く光るランプをESP32で作ってみた](https://images.ctfassets.net/ct0aopd36mqt/1dD7b8HkT2sbiJzUIewMTD/e5cdc6f33c4fdd9d798f11a4564612ff/eyecatch_developersio_darktone_1200x630.jpg?w=3840&fm=webp)
[小ネタ] Google Meetでマイクかカメラがオンの間だけ赤く光るランプをESP32で作ってみた
こんにちは、製造ビジネステクノロジー部のはすとです。
私の自宅ではデスクがリビングにあり、会議中に家族が後ろを通るとカメラに映ってしまいます。そのため家族には、遠くからカメラがオンかどうかを確かめてから、恐る恐る通ってもらっていました。
そこで、Google Meetでマイクかカメラがオンの間は赤、両方オフなら緑に光るランプを、ESP32のスターターキットで作ってみました。このキットには電子工作でよく使う部品がひととおり入っているため、これからESP32を始める方にはおすすめです。
ESP32の開発環境の準備やLチカは、あかいけさんが書いてくださった、以下記事が詳しいため本記事では省きます。
作ったもの
Meetでマイクとカメラのオンとオフを切り替えると、ランプが赤と緑に変わります。

| 表示 | 条件 |
|---|---|
| 赤 | カメラオン or マイクオン |
| 緑 | カメラ & マイク がオフ |
| 消灯 | 状態が分からない |
マイクがオンなら、話してなくても赤にしました。マイクがオンの時は話しかけてほしくないためです。なので、緑なら通っても良いし、声をかけても大丈夫という状態です。
全体の流れは以下です。
Meetのタブ(ボタンの表示を読む)──> Chrome拡張(USB、Web Serial)──> ESP32 ──> RGB LED(赤と緑を使用)
Chrome拡張がMeetの画面からマイクとカメラの状態を読み取り、0.5秒ごとに1文字をUSB経由でESP32に送ります。
使ったもの
- ELEGOO ESP-32 Super Starter Kit(ESP32の基板。USB変換チップはCP2102)
- 4本足のRGB LEDを1つ、1kΩの抵抗を2本
- PlatformIO(ESP32への書き込みに使用)
- Chrome 拡張機能
最初、抵抗は 220Ω にしてましたが、LEDが眩しかったため 1kΩ にしました。
配線は以下のとおりです。

- GPIO25 → 1kΩ → RGB LEDのRの足
- GPIO26 → 1kΩ → RGB LEDのGの足
- GND → 一番長い足
私の手元のRGB LEDでは、足は平らな側からR、共通、G、Bの順に並んでいました。

※ 足の並びはRGB LEDによって違うことがあるため、つなぐ前に確かめてください。
実際にブレッドボードに組むと、以下のようになります。

ESP32側は受けた1文字でLEDを切り替える
ESP32はUSBシリアルで受けた1文字に応じてLEDを点けるだけです。R で赤、G で緑、X で消灯にしています。
#include <Arduino.h>
constexpr int red = 25, green = 26;
unsigned long last = 0;
void show(char state) {
digitalWrite(green, LOW);
digitalWrite(red, state == 'R');
digitalWrite(green, state == 'G');
}
void setup() {
pinMode(red, OUTPUT);
pinMode(green, OUTPUT);
show('X');
Serial.begin(115200);
}
void loop() {
while (Serial.available()) {
char c = Serial.read();
show(c);
last = millis();
}
// 約5秒指示がなければ消灯する
if (millis() - last > 5000) show('X');
delay(10);
}
Chromeを閉じたりUSBの通信が止まったりしたときに、最後の色が点いたままにならないよう、約5秒指示がなければ消灯します。
platformio.ini は以下のとおりです。
[env:esp32]
platform = platformio/espressif32@6.12.0
framework = arduino
board = esp32dev
upload_speed = 115200
upload_port = /dev/cu.usbserial-0001
Chrome拡張側はMeetのボタンの表示から状態を読む
マイクとカメラの状態は、Meetの画面にあるボタンの表示から読み取ることにしました。音声や映像は取得していません。
ボタンに「マイクをオン」と出ていると、マイクがオンだと読みそうになりますが、実際には、ボタンの表示は押したときの操作を表しているため、「マイクをオン」と出ているときはマイクがオフです。
なので、判定は以下のようにしています。
const mic = control(labels, /^(turn off microphone|mute microphone|マイクを(オフ|ミュート)(にする)?)/i,
/^(turn on microphone|unmute microphone|マイクをオン|マイクのミュートを解除)/i);
const cam = control(labels, /^(turn off camera|カメラをオフ)/i, /^(turn on camera|カメラをオン)/i);
// どちらかがオンなら赤、両方オフなら緑、分からなければ消灯
return mic === true || cam === true ? 'R' : mic === false && cam === false ? 'G' : 'X';
control() は、1つ目の正規表現に合う表示があればオン、2つ目に合う表示があればオフを返す関数です。表示が見つからないときや、オンとオフの表示が混ざっているときは「分からない」として扱います。
この判定を、拡張の画面から chrome.scripting.executeScript() でMeetの全タブに0.5秒ごとに実行し、結果をWeb Serialで送ります。
const tabs = await chrome.tabs.query({url:'https://meet.google.com/*'});
// 各タブの判定(省略)
const state = states.includes('R') ? 'R' : states.every(s => s === 'G') ? 'G' : 'X';
await writer.write(new TextEncoder().encode(state));
Meetのタブが複数あるときは、1つでも赤なら赤にしています。
拡張の manifest.json で求めている権限は、meet.google.com へのアクセスと scripting だけです。
{
"manifest_version": 3,
"name": "Meet 会議ランプ",
"version": "1.0.0",
"action": {
"default_title": "会議ランプのUSB接続"
},
"background": {
"service_worker": "open.js"
},
"host_permissions": [
"https://meet.google.com/*"
],
"permissions": [
"scripting"
]
}
拡張機能の使い方
拡張のアイコンを押すとランプ用のタブが開き、「ESP32をUSB接続」ボタンからCP2102のポートを選ぶと送信が始まります。
接続している間はボタンが押せなくなり、その下に今のランプの状態が表示されます。

Meetのホーム画面のタブに引っかかった
会議中にマイクもカメラもオフにしているのに、ランプが緑にならず消灯したままになることがありました。原因は、別のタブで開いていたMeetのホーム画面(/home)です。ホーム画面には退出ボタンもマイクのボタンもないため「分からない」と判定され、全タブが緑のときだけ緑にする条件を満たせなくなっていました。
ホーム画面は会議の外なので、緑として扱うようにして解消しました。
if (new URL(tab.url).pathname === '/home') return 'G';
まとめ
今回は、自宅のちょっとした悩みを、手軽に解決することができました。初めてのESP32でしたが、AIに手伝ってもらったおかげで、配線も迷わず組めました。手元にあるセンサーやマイコンを伝えると、配線図まで出してくれます。学生の頃にも少し電子工作をしたことがありますが、AIが出てきたことで、電子工作のハードルがかなり下がったなと感じました。
スターターキットはさまざまなセンサーがそろっているわりに値段もお手頃なので、一家に一つ置いておくのはいかがでしょうか。
年末も近いため、冬休みの自由研究におすすめです。






