Zoomをミュートする物理ボタンを作る その2


# 課題
以前ATOMMatrixで[Zoomミュートボタン](https://qiita.com/akaneburyo/items/076da6a4bd350e5820e9)を作成したのですが、以下のような課題を感じておりいつしか使わなくなっていました。
- 電源が必要なため有線でしか使えない(通信はBLEなのに)
- Teams会議で使えない

そこで新たにいくつか部品を追加し、アップデートしました。

# 目指したこと
- ワイヤレス
- Zoom/Teams等色々切り替えて使いたい
- 欲を言うとボリュームも変えたい

# できたもの
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208118/e95365df-9636-9dde-774d-a6d2174be2ea.jpeg" width="320px" />

![movie.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208118/518497a1-f9c3-f560-2ad8-4c924bb9547c.gif)

<details><summary>ソースコード</summary><div>

```C
#include "M5Atom.h"
#include <FastLED.h>       // https://github.com/FastLED/FastLED
#include <BleKeyboard.h>   // https://github.com/T-vK/ESP32-BLE-Keyboard
#include <RotaryEncoder.h> // https://github.com/mathertel/RotaryEncoder

#define PIN_MATRIX_LED 27
#define NUM_MATRIX_LEDS 25

#define PIN_KEY_LED 23
#define PIN_KEY_BUTTON 33

#define PIN_ENCODER_A 22
#define PIN_ENCODER_B 25

#define KEY_TYPE_PRESS 1
#define KEY_TYPE_WRITE 2
#define KEY_TYPE_MEDIA 3

typedef struct {
  const uint8_t cmd[4];
  const unsigned char length;
  const unsigned char type;
} Key;

typedef struct {
  const CRGB matrix[NUM_MATRIX_LEDS];
  const Key keyDown;
  const Key knobR;
  const Key knobL;
} Mode;

const Mode mode[] = {
  {
    // Teams Mode
    {
      0x000000,0x000000,0x4B53BC,0x000000,0x000000,
      0x000000,0x000000,0x4B53BC,0x000000,0x000000,
      0x000000,0x000000,0x4B53BC,0x000000,0x000000,
      0x000000,0x000000,0x4B53BC,0x000000,0x000000,
      0x4B53BC,0x4B53BC,0x4B53BC,0x4B53BC,0x4B53BC
    },
    {
      {KEY_LEFT_SHIFT, KEY_RIGHT_GUI, 'm'},
      3,
      KEY_TYPE_PRESS,
    },
    {
      {32, 0},
      2,
      KEY_TYPE_MEDIA,
    },
    {
      {64, 0},
      2,
      KEY_TYPE_MEDIA,
    },
  },{
    // Zoom Mode
    {
      0x2d8cff,0x2d8cff,0x2d8cff,0x2d8cff,0x2d8cff,
      0x000000,0x000000,0x000000,0x2d8cff,0x000000,
      0x000000,0x000000,0x2d8cff,0x000000,0x000000,
      0x000000,0x2d8cff,0x000000,0x000000,0x000000,
      0x2d8cff,0x2d8cff,0x2d8cff,0x2d8cff,0x2d8cff
    },
    {
      {KEY_LEFT_SHIFT, KEY_RIGHT_GUI, 'a'},
      3,
      KEY_TYPE_PRESS,
    },
    {
      {32, 0},
      2,
      KEY_TYPE_MEDIA,
    },
    {
      {64, 0},
      2,
      KEY_TYPE_MEDIA,
    }
  },{
    // Gather Mode
    {
      0x4358D8,0x4358D8,0x4358D8,0x4358D8,0x4358D8,
      0x4358D8,0x000000,0x000000,0x000000,0x4358D8,
      0x4358D8,0x4358D8,0x4358D8,0x000000,0x4358D8,
      0x000000,0x000000,0x000000,0x000000,0x4358D8,
      0x4358D8,0x4358D8,0x4358D8,0x4358D8,0x4358D8
    },
    {
      {KEY_LEFT_SHIFT, KEY_RIGHT_GUI, 'a'},
      3,
      KEY_TYPE_PRESS,
    },
    {
      {32, 0},
      2,
      KEY_TYPE_MEDIA,
    },
    {
      {64, 0},
      2,
      KEY_TYPE_MEDIA,
    }
  }
};

hw_timer_t *timer = NULL;

CRGB matrix[NUM_MATRIX_LEDS];
CRGB keyLed;

BleKeyboard bleKeyboard("Meeting Controller");
RotaryEncoder encoder(PIN_ENCODER_A, PIN_ENCODER_B);

Button key = Button(PIN_KEY_BUTTON, 0, 10);

volatile int pos = 0;
volatile int newPos = 0;

bool isMute = true;
static unsigned char modeIndex = 0;

void sendCmd(const Key key) {
  if (key.type == KEY_TYPE_WRITE) {
    bleKeyboard.write(key.cmd, key.length);
    Serial.println("write");
  }

  if (key.type == KEY_TYPE_PRESS) {
    for (int index = 0; index < key.length; index++) {
      bleKeyboard.press(key.cmd[index]);
    }
    delay(10);
    bleKeyboard.releaseAll();
    Serial.println("press");
  }

  if (key.type == KEY_TYPE_MEDIA) {
    const MediaKeyReport _cmd = {key.cmd[0], key.cmd[1]};
    bleKeyboard.write(_cmd);
  }
}

void setMatrix(const CRGB *c){
  for (int index = 0; index < NUM_MATRIX_LEDS; index++){
    matrix[index] = c[index];
  }
  FastLED.show();
}

void setKeyLed(CRGB c){
  keyLed = c;
  FastLED.show();
}

void IRAM_ATTR onTimer(){
  encoder.tick();
  newPos = encoder.getPosition();
}

void setup() {
  M5.begin(true, false, true);
  bleKeyboard.begin();

  FastLED.addLeds<WS2812, PIN_MATRIX_LED, GRB>(matrix, NUM_MATRIX_LEDS);
  FastLED.addLeds<NEOPIXEL, PIN_KEY_LED>(&keyLed, 1);
  FastLED.setBrightness(5);

  timer = timerBegin(0, 80, true);
  timerAttachInterrupt(timer, &onTimer, true);
  timerAlarmWrite(timer, 1 * 100, true);
  timerAlarmEnable(timer);

  while(!bleKeyboard.isConnected()) {
    delay(100);
  }

  setMatrix(mode[modeIndex].matrix);
  setKeyLed(isMute ? CRGB::Red : CRGB::Lime);
}

void loop() {
  M5.update();
  key.read();

  if(M5.Btn.wasPressed()){
    modeIndex = (modeIndex + 1) % (sizeof(mode) / sizeof(Mode));
    setMatrix(mode[modeIndex].matrix);
  }

  if (key.wasPressed()) {
    sendCmd(mode[modeIndex].keyDown);
    isMute = !isMute;
    setKeyLed(isMute ? CRGB::Red : CRGB::Lime);
  }

  if (pos != newPos) {
    pos = newPos;
    RotaryEncoder::Direction dir = encoder.getDirection();
    if (dir == RotaryEncoder::Direction::CLOCKWISE) {
      sendCmd(mode[modeIndex].knobR);
    } else {
      sendCmd(mode[modeIndex].knobL);
    }
  }
}
```

</div></details>

以前からの改善点としては、バッテリーを追加してワイヤレスで使用できるようにした他、キースイッチとロータリーエンコーダーを追加して操作の幅を広めました。
ATOM MatrixのLEDの部分押下でモード切替、キースイッチでミュート/ミュート解除、ノブでボリュームを操作できます。(つまみがTONEなのは気にしないでください)

LEDの制御用に[FastLED](https://github.com/FastLED/FastLED)を、
ロータリーエンコーダーからの入力用に[RotaryEncoder](https://github.com/mathertel/RotaryEncoder)を追加しました。  
基本的には[以前](https://qiita.com/akaneburyo/items/076da6a4bd350e5820e9)と同じく[BleKeyboard](https://github.com/T-vK/ESP32-BLE-Keyboard)を使用してキー入力を行うのみですが、`Mode`の定義を増やすことで様々なコマンドに対応できるよう修正しました。


使用した主な部品は以下です。

|  部品  |  価格(購入時)  |
| :-- | :-- |
| [ATOM TailBAT](https://www.switch-science.com/catalog/6348/) | 940円 | 
| [ロータリーエンコーダ](https://akizukidenshi.com/catalog/g/gP-06357/) | 80円 |
| [ロータリーエンコーダＤＩＰ化基板](https://akizukidenshi.com/catalog/g/gP-07241/) | 60円 |
| [Grove - Mech Keycap](https://akizukidenshi.com/catalog/g/gM-13761/) | 540円 |

## 感想
目指していた
- ワイヤレス
- Zoom/Teams等色々切り替えて使いたい
- 欲を言うとボリュームも変えたい

は達成できました。

ただLEDを多用するためかバッテリーがあまり持たず、長い打ち合わせだと途中で切れてしまいます…  
日頃利用しているBluetooth機器の凄まじさ(?)を感じました。

---
ご意見/ご指摘/気になったこと/もっとこうすれば良くなる等、コメントいただけると大喜びします！
ありがとうございました！