Web会議用マイク・カメラON・OFFボタンの作成

Web会議にもいろいろな実施形態がありますが、発言時のみマイクをONにするという作法?で実施することも多いです。私は普段、1世代前のノートPCをWeb会議専用にしていて、画角の観点から手元から少し遠くへ配置しています。

これだとキーボードに身を乗り出さないと手が届きません。外付けキーボードを付けることも考えられましたがそれはそれで邪魔ですし、せっかくなのでUSB HIDデバイスを自作することにしました(明らかに後者が目的ですが)。

試作機は市販のHID開発用キットを使用しました。手軽に作れたのですが、完成後に送信するキーコードがTeams、Meet、Zoomで全部違うことに気づきました。これだと逐次設定を書き換えるか、ボタンの接続先を切り替える回路が必要で、切替回路を作るのが面倒になってしまいました…。

Teams
 マイク : CTRL + SHIFT + M
 カメラ : CTRL + SHIFT + O
Meet
 マイク : CTRL + D
 カメラ : CTRL + E
Zoom
 マイク : ALT + A
 カメラ : ALT + V

そこでやはり根っこから自作する方が面白くて実用性が上がると考え直し、Arduinoマイコンを用いたUSB HIDデバイス作成に切り替えました。こちらはDIPスイッチで動作モードを切り替えられるようします。

実は見た目は試作機と変わらないのですが、こちらは以下のようなArduino Microを使っています。

黄色い線がボタンとつながっています。黒い線はボタンをGNDへ接続しています。LEDは動作モードを確認するために付けました。簡単な回路図を起こしてみました。

DIPスイッチではTeams, Meet, Zoomのアプリを指定するとともに、カメラとマイクを同時にON/OFFできるモードを用意しました。これでボタン1つでいっぺんに切り替えられます。

ソースコードは以下のようになっています。

/**
 * ArdinoMeetingButton
 * by N.Amano
 * 2021/01/31 : ver 0.1 : 
 * 2021/02/02 : ver 1.0 : 正式リリース版
 * 2021/02/05 : ver 1.1 : チャタリング防止
 * 2021/02/15 : ver 1.2 : 微細な修正(誤動作抑止)
 */

#include "Keyboard.h"

#define btn1Pin 2 // マイクボタン、pin->button->gnd接続
#define btn2Pin 3 // カメラボタン、pin->button->gnd接続

// dip1 & dip2 = application select, 00=Teams, 01=Zoom, 10=Zoom, 11=not support
// dip3 button mode, 0 = alone, 1 = tandem(mic&camera, same action)
#define dip1Pin 7 // pin->button->gnd接続
#define dip2Pin 8 // pin->button->gnd接続
#define dip3Pin 9 // pin->button->gnd接続

// モード表示用LED
#define led1Pin 4  // pin->resistance(1k)->led->gnd
#define led2Pin 5  // pin->resistance(1k)->led->gnd
#define led3Pin 6  // pin->resistance(1k)->led->gnd

// 状態保持用変数
int prevBtn1 = 1; // ボタン1の前の状態
int prevBtn2 = 1; // ボタン2の前の状態
int apps = 0;     // アプリ選択状態
int same = 0;     // ボタン動作モード

int contBtn1 = 0;
int contBtn2 = 0;

// 初期化
void setup() {
  pinMode(btn1Pin, INPUT_PULLUP);
  pinMode(btn2Pin, INPUT_PULLUP);

  pinMode(dip1Pin, INPUT_PULLUP);
  pinMode(dip2Pin, INPUT_PULLUP);
  pinMode(dip3Pin, INPUT_PULLUP);

  pinMode(led1Pin, OUTPUT);
  pinMode(led2Pin, OUTPUT);
  pinMode(led3Pin, OUTPUT);
  
  Keyboard.begin();
}

// マイク反転キー送信
void sendCommandMic() {
   if (apps == 1) { // Teams, CTRL+SHIFT+M
    Keyboard.press(KEY_LEFT_CTRL);
    Keyboard.press(KEY_LEFT_SHIFT);
    Keyboard.press('m');
  } else if (apps == 2) { // Meet, CTRL+D
    Keyboard.press(KEY_LEFT_CTRL);
    Keyboard.press('d');
  } else if (apps == 3) { // Zoom, ALT+A
    Keyboard.press(KEY_LEFT_ALT);
    Keyboard.press('a');
  }
  delay(100);
  Keyboard.releaseAll();
  delay(100);
}

// カメラ反転キー送信
void sendCommandCamera() {
  if (apps == 1) { // Teams, CTRL+SHIFT+O
    Keyboard.press(KEY_LEFT_CTRL);
    Keyboard.press(KEY_LEFT_SHIFT);
    Keyboard.press('o');
  } else if (apps == 2) { // Meet, CTRL+E
    Keyboard.press(KEY_LEFT_CTRL);
    Keyboard.press('e');
  } else if (apps == 3) { // Zoom, ALT+V
    Keyboard.press(KEY_LEFT_ALT);
    Keyboard.press('v');
  }
  delay(100);
  Keyboard.releaseAll();
  delay(100);
}

void readDIP() {
  int d1 = digitalRead(dip1Pin);
  int d2 = digitalRead(dip2Pin);
  int d3 = digitalRead(dip3Pin);
  if (d1 == 1) {
    if (d2 == 1) {
      apps = 1;
      digitalWrite(led1Pin, LOW);
      digitalWrite(led2Pin, LOW);
    } else {
      apps = 2;
      digitalWrite(led2Pin, HIGH);
      digitalWrite(led1Pin, LOW);
    }
  } else {
    if (d2 == 1) {
      apps = 3;
      digitalWrite(led1Pin, HIGH);
      digitalWrite(led2Pin, LOW);
    } else {
      apps = 1;
      digitalWrite(led1Pin, LOW);
      digitalWrite(led2Pin, LOW);
    }
  }
  if (d3 == 0) {
    same = 1;
    digitalWrite(led3Pin, HIGH);
  } else {
    same = 0;
    digitalWrite(led3Pin, LOW);
  }
}

// 常時動作
void loop() {
  // DIP switch for APP select
  readDIP();

  // ボタン操作
  int btn1State = 1;
  int btn2State = 1;
  int btn1now = digitalRead(btn1Pin);
  int btn2now = digitalRead(btn2Pin);

  if (btn1now == 0) {
    contBtn1++;
    if (contBtn1 > 10) {
      btn1State = 0;
    }
  } else {
    contBtn1 = 0;
  }
  if (btn2now == 0) {
    contBtn2++;
    if (contBtn2 > 10) {
      btn2State = 0;
    }
  } else {
    contBtn2 = 0;
  }

  if ((prevBtn1 == 1) && (btn1State == 0)) {
    // button1 pushed
    if (same == 0) {
      sendCommandMic();    
    } else {
      sendCommandMic();    
      sendCommandCamera();    
    }
  }

  if ((prevBtn2 == 1) && (btn2State == 0)) {
    // button2 pushed
    if (same == 0) {
      sendCommandCamera();    
    } else {
      sendCommandCamera();    
      sendCommandMic();    
    }
  }

  prevBtn1 = btn1State;
  prevBtn2 = btn2State;
  
  delay(10);
}


2021/02/10 : チャタリング・誤動作防止のためソースコード修正。
利用中にブレッドボード上の配線を動かしてしまい、誤動作してしまいました。考えてみたら(今回の誤動作は原因は違うものの)チャタリング防止をしていなかったので、ソースコードを修正して対応しました。

柔らかいタッパーだと押したときに歪み、それが回路の動作異常を起こしたり、うまく押せないことがあるので、2号機を作成してみました。Arduino Microも少し小さなものに変更し、全体をコンパクトにしました。埋め込みスイッチを流用することでケースの剛性を確保しています。こちらはブレッドボードでもなく基板上に全て固定しているので、全体として安定性を高めた形です。トグルスイッチはマイクとカメラの連動を切り替える部分を外出ししたものです(内部のDIPスイッチでも変更可能)。トグルスイッチとDIPスイッチは並行に入れればOKです。

2021/02/26
ボタンを押したときの音が大きくて気になったので、静音キーボード的に作ってみました。同時にできるだけの小型化を試みました。

本来はアクリル板等に穴を開けてキースイッチを固定すべきと理解していますが、手間を省いてキースイッチは基板に穴を開けて配線を通し、接着剤で固定してしまいました。マイコン上のLEDが眩しい…。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です