M5Core2 で ATEM スイッチャーを作ってみた (※M5Core2ライブラリ ver.0.0.6へ対応)

# はじめに
M5Core2 で ATEM Mini Pro を操作するスイッチャーを作ってみました。

### 操作している様子(動画)
https://youtu.be/5ASHgIHe9pQ
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72479/519043ee-e8c0-c4d5-c4f4-354664989c0e.png" width="50%">

これまでは ATEM Mini Pro はボタン (入力ソース) が 4 つで、M5Stack はボタンが 3 つしかなかったので、用途が限定されていました (例えば、ボタンは一部の Program と Preview の切替のみ、液晶画面はタリーライトとして表示、など) が、M5Core2 は液晶画面がタッチパネルとなり、従来のボタンもタッチパネルの一部として認識されます。また、液晶も TFT から IPS へ変更されたため、視野角も広く表示もクリアです。

### ATEM Mini Pro と M5Stack、M5Core2
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72479/1ce7a190-6dc9-cb7e-e07a-91f966c570b1.png" width="75%">

### ATEM Mini Extreme
![スクリーンショット 2021-03-04 23.32.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72479/3e3cf387-ffca-8cb3-b643-a7dde8502038.png)
![ATEM_Extreme_Switcher.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72479/47087e96-1801-1413-853f-b3793a31e8eb.png)


# ATEM スイッチャーの画面・ボタン配置
液晶画面には入力ソースのボタン (タリーライト) を表示し、タッチパネルで Program (上段) と Preview (下段) を切り替えられるようにしました。
また、従来のボタン (ボタンA と ボタンB) は Program と Preview の入れ替え (AUTO と CUT) に割り当てました。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72479/5fecce89-208e-98fa-3cb8-9e8606b62279.png" width="75%">

# 準備
### 1. Arduino IDE
開発環境となる Arduino IDE をインストールします。

### 2. ボードマネジャー
M5 公式の手順通り、ボードマネジャーから M5stack を選択してインストールします。(2021年9月5日時点のバージョンは <font color=#FF0000>1.0.9</font> でした)

### 3. M5Core2 ライブラリ
M5 公式の手順通り、ライブラリを管理から M5Core2 を選択してインストールします。(2021年9月5日時点のバージョンは <font color=#FF0000>0.0.6</font> でした)

### 4. ATEM ライブラリ
ネットワーク経由で ATEM Mini Pro を操作する際に BMD (Black Magic Design) というプロトコルが使用されています。
http://skaarhoj.com/fileadmin/BMDPROTOCOL.html
SKAARHOJ さんが公開されている Arduino 用のライブラリを使用 (一部、コードを修正してインストール) します。
https://github.com/kasperskaarhoj/SKAARHOJ-Open-Engineering
必要なライブラリは以下の 4 つです。

- ATEMbase
- ATEMmax
- ATEMstd
- SkaarhojPgmspace

### 修正内容
修正箇所は ATEMbase ライブラリの 3 箇所です。

- ATEMbase.cpp (1 箇所)
- ATEMbase.h (2 箇所)

理由は Arduino Ethernet Shield と ESP8266 とで条件付きコンパイルされるためで、M5Core2 は ESP32 が使用されているため、修正が必要になります。

```c++:修正前
#ifdef ESP8266        
WiFiUDP Udp;
#else
EthernetUDP Udp;
#endif
```

```c++:修正後
WiFiUDP Udp;
```

修正済のソースコードを github に置きました。
https://github.com/kitazaki/M5Core2_atemswitcher/raw/main/ArduinoLibs.zip


# コード
ソースコードを github に置きました。
https://github.com/kitazaki/M5Core2_atemswitcher

使用する環境に合わせて、Wi-Fi (SSIDとパスワード) と ATEM Mini Pro (IPアドレス) の設定をする必要があります。

```c++
IPAddress switcherIp(0, 0, 0, 0);
const char* ssid = "";  // Wi-Fi SSID
const char* password =  "";  // Wi-Fi Password
```

### ATEM Mini / Pro / Pro ISO用 (M5Core2ライブラリ ver.0.0.2)

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

```c++:M5Core2_ATEM_01.ino
// This code is based on
// https://github.com/aaronpk/m5-core2-atem-controller/
//
// Requirements
//
// Download Arduino Libraries from here
// https://github.com/kasperskaarhoj/SKAARHOJ-Open-Engineering/tree/master/ArduinoLibs
//
// 1. copy these folders to libraries directory
// ATEMbase
// ATEMmax
// ATEMstd
// SkaarhojPgmspace
//
// 2. change code in ATEMbase.cpp (1 change) and ATEMbase.h (2 changes)
// refer to: https://oneguyoneblog.com/2020/07/13/autopilot-for-blackmagic-design-atem-switcher/
//
//  from                  to
//  --------------------  --------------------
//  #ifdef ESP8266        WiFiUDP Udp;
//  WiFiUDP Udp;
//  #else
//  EthernetUDP Udp;
//  #endif

#include <M5Core2.h>
#include <Fonts/EVA_20px.h>
#include <stdio.h>
#include <WiFi.h>

#include <SkaarhojPgmspace.h>
#include <ATEMmax.h>
#include <ATEMstd.h>

// Define the IP address of your ATEM switcher
IPAddress switcherIp(0, 0, 0, 0);

// Put your wifi SSID and password here
const char* ssid = "";  // Wi-Fi SSID
const char* password =  "";  // Wi-Fi Password

#define CAMERA_OFF 0
#define CAMERA_PREVIEW 1
#define CAMERA_PROGRAM 2

#define CAMERA_BUTTON_Y 42
#define MACRO_BUTTON_Y 150

ATEMstd AtemSwitcher;

TouchButton c1 = TouchButton(2,   CAMERA_BUTTON_Y, 72, 72, "c1");
TouchButton c2 = TouchButton(2+72, CAMERA_BUTTON_Y, 72, 72, "c2");
TouchButton c3 = TouchButton(2+72+72, CAMERA_BUTTON_Y, 72, 72, "c3");
TouchButton c4 = TouchButton(2+72+72+72,   CAMERA_BUTTON_Y, 72, 72, "c4");

TouchButton b1 = TouchButton(2,   MACRO_BUTTON_Y, 72, 72, "b1");
TouchButton b2 = TouchButton(2+72, MACRO_BUTTON_Y, 72, 72, "b2");
TouchButton b3 = TouchButton(2+72+72, MACRO_BUTTON_Y, 72, 72, "b3");
TouchButton b4 = TouchButton(2+72+72+72,   MACRO_BUTTON_Y, 72, 72, "b4");

void buttonWasPressed(TouchEvent& e) {
  TouchButton& b = *e.button;

  char buttonType = b.name[0];
  int buttonNumber = b.name[1]-'0';

  Serial.printf("debug: buttontype = %c, buttonNumber = %d\n", buttonType, buttonNumber);
  
  if(buttonType == 'c') {
    AtemSwitcher.changeProgramInput(buttonNumber);
  }
  else if( buttonType == 'b') {
    drawButton(buttonNumber, b.isPressed(), false);
//    AtemSwitcher.setMacroAction(buttonNumber-1, 0); 
    AtemSwitcher.changePreviewInput(buttonNumber);
  }
  else {
    if(M5.BtnA.isPressed()) {
      AtemSwitcher.doAuto();
      Serial.println("debug: Button A is Pressed");
    }
    else if(M5.BtnB.isPressed()) {
      AtemSwitcher.doCut();
      Serial.println("debug: Button B is Pressed");
    }
    else if(M5.BtnC.isPressed()) {
      // do something
      Serial.println("debug: Button C is Pressed");
    }
  }
}

void drawCameraButton(int buttonNumber, int state) {
  int font = 1;
  M5.Lcd.setTextSize(8);
  
  M5.Lcd.setTextColor(state == CAMERA_OFF ? WHITE : BLACK);

  int buttonColor;
  switch(state) {
    case CAMERA_OFF:
      buttonColor = TFT_DARKGREY; break;
    case CAMERA_PREVIEW:
      buttonColor = TFT_GREEN; break;
    case CAMERA_PROGRAM:
      buttonColor = TFT_RED; break;
  }

  //char* cameraName = AtemSwitcher.getInputShortName(buttonNumber-1);
  
  switch(buttonNumber) {
    case 1:
      M5.Lcd.fillRoundRect(2, CAMERA_BUTTON_Y, 72, 72, 6, buttonColor);
      M5.Lcd.drawCentreString("1", 2+2+(72/2), 42+10, font);
      break;
    case 2:
      M5.Lcd.fillRoundRect(2+72+2, CAMERA_BUTTON_Y, 72, 72, 6, buttonColor);
      M5.Lcd.drawCentreString("2", 2+2+72+2+(72/2), 42+10, font);
      break;
    case 3:
      M5.Lcd.fillRoundRect(2+72+2+72+2, CAMERA_BUTTON_Y, 72, 72, 6, buttonColor);
      M5.Lcd.drawCentreString("3", 2+2+72+2+72+2+(72/2), 42+10, font);
      break;
    case 4:
      M5.Lcd.fillRoundRect(2+72+2+72+2+72+2, CAMERA_BUTTON_Y, 72, 72, 6, buttonColor);
      M5.Lcd.drawCentreString("4", 2+2+72+2+72+2+72+2+(72/2), 42+10, font);
      break;
  }
}

void drawButton(int buttonNumber, bool isPressed, bool isRunning) {
  int font = 1;
  M5.Lcd.setTextSize(8);
  
  M5.Lcd.setTextColor(isPressed || isRunning ? BLACK : WHITE);

  int buttonColor = isPressed ? WHITE : (isRunning ? TFT_RED : TFT_DARKGREY);
  
  switch(buttonNumber) {
    case 1:
      M5.Lcd.fillRoundRect(2, MACRO_BUTTON_Y, 72, 72, 6, buttonColor);
      M5.Lcd.drawCentreString("1", 2+2+(72/2), MACRO_BUTTON_Y+10, font);
      break;
    case 2:
      M5.Lcd.fillRoundRect(2+72+2, MACRO_BUTTON_Y, 72, 72, 6, buttonColor);
      M5.Lcd.drawCentreString("2", 2+2+72+2+(72/2), MACRO_BUTTON_Y+10, font);
      break;
    case 3:
      M5.Lcd.fillRoundRect(2+72+2+72+2, MACRO_BUTTON_Y, 72, 72, 6, buttonColor);
      M5.Lcd.drawCentreString("3", 2+2+72+2+72+2+(72/2), MACRO_BUTTON_Y+10, font);
      break;
    case 4:
      M5.Lcd.fillRoundRect(2+72+2+72+2+72+2, MACRO_BUTTON_Y, 72, 72, 6, buttonColor);
      M5.Lcd.drawCentreString("4", 2+2+72+2+72+2+72+2+(72/2), MACRO_BUTTON_Y+10, font);
      break;
  }
}

void setupButtons() {
  M5.Touch.addHandler(buttonWasPressed, TE_BTNONLY + TE_TOUCH + TE_RELEASE);

  M5.Lcd.fillScreen(BLACK);

  M5.Lcd.setTextSize(2);
  M5.Lcd.setTextColor(WHITE);
  M5.Lcd.drawString("Program", 6, CAMERA_BUTTON_Y-20, 1);
  M5.Lcd.drawString("Preview", 6, MACRO_BUTTON_Y-20, 1);

  drawButton(1, false, false);
  drawButton(2, false, false);
  drawButton(3, false, false);
  drawButton(4, false, false);
  
  drawCameraButton(1, CAMERA_OFF);
  drawCameraButton(2, CAMERA_OFF);
  drawCameraButton(3, CAMERA_OFF);
  drawCameraButton(4, CAMERA_OFF);
}

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

  Serial.println("About to connect to wifi");
  WiFi.begin(ssid, password);

  int lpcnt=0 ;
  int lpcnt2=0 ;

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    lpcnt += 1 ;                           //
    if (lpcnt > 6) {                       // 6回目(3秒) で切断/再接続
      WiFi.disconnect(true,true);          //
      WiFi.begin(ssid, password);    //
      lpcnt = 0 ;                          //
      lpcnt2 += 1 ;                        // 再接続の回数をカウント
    }                                      //
    if (lpcnt2 > 3) {                      // 3回 接続できなければ、
      ESP.restart() ;                      // ソフトウェアリセット
    }                  
    Serial.print(".");
  }
  Serial.println("Connected to the WiFi network");

  AtemSwitcher.begin(switcherIp);
  AtemSwitcher.serialOutput(0x80);
  AtemSwitcher.connect();
  Serial.println("Connected to AtemSwitcher");

  setupButtons();
}

int currentRunningMacro = -1;
int tallyStates[4];

void loop() {
  M5.update();
  AtemSwitcher.runLoop();

  if(AtemSwitcher.getMacroRunStatusState() && 0x01 == 1) {
    int index = AtemSwitcher.getMacroRunStatusIndex();
    if(index != currentRunningMacro) {
      drawButton(index + 1, false, true);
    }
    currentRunningMacro = index;
  } else {
    if(currentRunningMacro > -1) {
      drawButton(currentRunningMacro + 1, false, false);
      currentRunningMacro = -1;
    }
  }

  for(int i=1; i<=4; i++) {
    int currentTallyState = AtemSwitcher.getProgramTally(i) ? CAMERA_PROGRAM : (AtemSwitcher.getPreviewTally(i) ? CAMERA_PREVIEW : CAMERA_OFF);

    if(currentTallyState != tallyStates[i-1]) {
      drawCameraButton(i, currentTallyState);
    }
    
    tallyStates[i-1] = currentTallyState;
  }
  
}
```

</div></details>

### ATEM Mini Extreme / Extreme ISO用 (M5Core2ライブラリ ver.0.0.2)

タッチした際に反応が返ってくるように(振動モーター対応)しました。

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

```c++:M5Core2_ATEM_02.ino
// for ATEM Mini Extreme / Extreme ISO
// This code is based on
// https://github.com/aaronpk/m5-core2-atem-controller/
//
// Requirements
//
// Download Arduino Libraries from here
// https://github.com/kasperskaarhoj/SKAARHOJ-Open-Engineering/tree/master/ArduinoLibs
//
// 1. copy these folders to libraries directory
// ATEMbase
// ATEMmax
// ATEMstd
// SkaarhojPgmspace
//
// 2. change code in ATEMbase.cpp (1 change) and ATEMbase.h (2 changes)
// refer to: https://oneguyoneblog.com/2020/07/13/autopilot-for-blackmagic-design-atem-switcher/
//
//  from                  to
//  --------------------  --------------------
//  #ifdef ESP8266        WiFiUDP Udp;
//  WiFiUDP Udp;
//  #else
//  EthernetUDP Udp;
//  #endif

#include <M5Core2.h>
#include <Fonts/EVA_20px.h>
#include <stdio.h>
#include <WiFi.h>

#include <SkaarhojPgmspace.h>
#include <ATEMmax.h>
#include <ATEMstd.h>

// Define the IP address of your ATEM switcher
IPAddress switcherIp(0, 0, 0, 0);

// Put your wifi SSID and password here
const char* ssid = "";  // Wi-Fi SSID
const char* password =  "";  // Wi-Fi Password

#define CAMERA_OFF 0
#define CAMERA_PREVIEW 1
#define CAMERA_PROGRAM 2

#define CAMERA_BUTTON_Y 42
#define MACRO_BUTTON_Y 150

ATEMstd AtemSwitcher;

TouchButton c1 = TouchButton(0, CAMERA_BUTTON_Y, 40, 72, "c1");
TouchButton c2 = TouchButton(40, CAMERA_BUTTON_Y, 40, 72, "c2");
TouchButton c3 = TouchButton(40+40, CAMERA_BUTTON_Y, 40, 72, "c3");
TouchButton c4 = TouchButton(40+40+40, CAMERA_BUTTON_Y, 40, 72, "c4");
TouchButton c5 = TouchButton(40+40+40+40, CAMERA_BUTTON_Y, 40, 72, "c1");
TouchButton c6 = TouchButton(40+40+40+40+40, CAMERA_BUTTON_Y, 40, 72, "c6");
TouchButton c7 = TouchButton(40+40+40+40+40+40, CAMERA_BUTTON_Y, 40, 72, "c7");
TouchButton c8 = TouchButton(40+40+40+40+40+40+40, CAMERA_BUTTON_Y, 40, 72, "c8");

TouchButton b1 = TouchButton(0, MACRO_BUTTON_Y, 40, 72, "b1");
TouchButton b2 = TouchButton(40, MACRO_BUTTON_Y, 40, 72, "b2");
TouchButton b3 = TouchButton(40+40, MACRO_BUTTON_Y, 40, 72, "b3");
TouchButton b4 = TouchButton(40+40+40, MACRO_BUTTON_Y, 40, 72, "b4");
TouchButton b5 = TouchButton(40+40+40+40, MACRO_BUTTON_Y, 40, 72, "b5");
TouchButton b6 = TouchButton(40+40+40+40+40, MACRO_BUTTON_Y, 40, 72, "b6");
TouchButton b7 = TouchButton(40+40+40+40+40+40, MACRO_BUTTON_Y, 40, 72, "b7");
TouchButton b8 = TouchButton(40+40+40+40+40+40+40, MACRO_BUTTON_Y, 40, 72, "b8");

void vibration() {
  M5.Axp.SetLDOEnable(3, true);
  delay(50);
  M5.Axp.SetLDOEnable(3, false);
}

void buttonWasPressed(TouchEvent& e) {
  TouchButton& b = *e.button;

  char buttonType = b.name[0];
  int buttonNumber = b.name[1]-'0';

  Serial.printf("debug: buttontype = %c, buttonNumber = %d\n", buttonType, buttonNumber);
  vibration();
  
  if(buttonType == 'c') {
    AtemSwitcher.changeProgramInput(buttonNumber);
  }
  else if( buttonType == 'b') {
    drawButton(buttonNumber, b.isPressed(), false);
//    AtemSwitcher.setMacroAction(buttonNumber-1, 0); 
    AtemSwitcher.changePreviewInput(buttonNumber);
  }
  else {
    if(M5.BtnA.isPressed()) {
      AtemSwitcher.doAuto();
      Serial.println("debug: Button A is Pressed");
    }
    else if(M5.BtnB.isPressed()) {
      AtemSwitcher.doCut();
      Serial.println("debug: Button B is Pressed");
    }
    else if(M5.BtnC.isPressed()) {
      // do something
      Serial.println("debug: Button C is Pressed");
    }
  }
}

void drawCameraButton(int buttonNumber, int state) {
  Serial.printf("debug: buttonNumber = %d, state = %d\n", buttonNumber, state);

  int font = 1;
  M5.Lcd.setTextSize(6);
  
  M5.Lcd.setTextColor(state == CAMERA_OFF ? WHITE : BLACK);

  int buttonColor;
  switch(state) {
    case CAMERA_OFF:
      buttonColor = TFT_DARKGREY; break;
    case CAMERA_PREVIEW:
      buttonColor = TFT_GREEN; break;
    case CAMERA_PROGRAM:
      buttonColor = TFT_RED; break;
  }

  switch(buttonNumber) {
    case 1:
      M5.Lcd.fillRoundRect(0, CAMERA_BUTTON_Y, 39, 72, 6, buttonColor);
      M5.Lcd.drawCentreString("1", 2+(40/2), CAMERA_BUTTON_Y+15, font);
      break;
    case 2:
      M5.Lcd.fillRoundRect(40, CAMERA_BUTTON_Y, 39, 72, 6, buttonColor);
      M5.Lcd.drawCentreString("2", 2+40+(40/2), CAMERA_BUTTON_Y+15, font);
      break;
    case 3:
      M5.Lcd.fillRoundRect(40+40, CAMERA_BUTTON_Y, 39, 72, 6, buttonColor);
      M5.Lcd.drawCentreString("3", 2+40+40+(40/2), CAMERA_BUTTON_Y+15, font);
      break;
    case 4:
      M5.Lcd.fillRoundRect(40+40+40, CAMERA_BUTTON_Y, 39, 72, 6, buttonColor);
      M5.Lcd.drawCentreString("4", 2+40+40+40+(40/2), CAMERA_BUTTON_Y+15, font);
      break;
    case 5:
      M5.Lcd.fillRoundRect(40+40+40+40, CAMERA_BUTTON_Y, 39, 72, 6, buttonColor);
      M5.Lcd.drawCentreString("5", 2+40+40+40+40+(40/2), CAMERA_BUTTON_Y+15, font);
      break;
    case 6:
      M5.Lcd.fillRoundRect(40+40+40+40+40, CAMERA_BUTTON_Y, 39, 72, 6, buttonColor);
      M5.Lcd.drawCentreString("6", 2+40+40+40+40+40+(40/2), CAMERA_BUTTON_Y+15, font);
      break;
    case 7:
      M5.Lcd.fillRoundRect(40+40+40+40+40+40, CAMERA_BUTTON_Y, 39, 72, 6, buttonColor);
      M5.Lcd.drawCentreString("7", 2+40+40+40+40+40+40+(40/2), CAMERA_BUTTON_Y+15, font);
      break;
    case 8:
      M5.Lcd.fillRoundRect(40+40+40+40+40+40+40, CAMERA_BUTTON_Y, 39, 72, 6, buttonColor);
      M5.Lcd.drawCentreString("8", 2+40+40+40+40+40+40+40+(40/2), CAMERA_BUTTON_Y+15, font);
      break;
  }
}

void drawButton(int buttonNumber, bool isPressed, bool isRunning) {
  int font = 1;
  M5.Lcd.setTextSize(6);
  
  M5.Lcd.setTextColor(isPressed || isRunning ? BLACK : WHITE);

  int buttonColor = isPressed ? WHITE : (isRunning ? TFT_RED : TFT_DARKGREY);
  
  switch(buttonNumber) {
    case 1:
      M5.Lcd.fillRoundRect(0, MACRO_BUTTON_Y, 39, 72, 6, buttonColor);
      M5.Lcd.drawCentreString("1", 2+(40/2), MACRO_BUTTON_Y+15, font);
      break;
    case 2:
      M5.Lcd.fillRoundRect(40, MACRO_BUTTON_Y, 39, 72, 6, buttonColor);
      M5.Lcd.drawCentreString("2", 2+40+(40/2), MACRO_BUTTON_Y+15, font);
      break;
    case 3:
      M5.Lcd.fillRoundRect(40+40, MACRO_BUTTON_Y, 39, 72, 6, buttonColor);
      M5.Lcd.drawCentreString("3", 2+40+40+(40/2), MACRO_BUTTON_Y+15, font);
      break;
    case 4:
      M5.Lcd.fillRoundRect(40+40+40, MACRO_BUTTON_Y, 39, 72, 6, buttonColor);
      M5.Lcd.drawCentreString("4", 2+40+40+40+(40/2), MACRO_BUTTON_Y+15, font);
      break;
    case 5:
      M5.Lcd.fillRoundRect(40+40+40+40, MACRO_BUTTON_Y, 39, 72, 6, buttonColor);
      M5.Lcd.drawCentreString("5", 2+40+40+40+40+(40/2), MACRO_BUTTON_Y+15, font);
      break;
    case 6:
      M5.Lcd.fillRoundRect(40+40+40+40+40, MACRO_BUTTON_Y, 39, 72, 6, buttonColor);
      M5.Lcd.drawCentreString("6", 2+40+40+40+40+40+(40/2), MACRO_BUTTON_Y+15, font);
      break;
    case 7:
      M5.Lcd.fillRoundRect(40+40+40+40+40+40, MACRO_BUTTON_Y, 39, 72, 6, buttonColor);
      M5.Lcd.drawCentreString("7", 2+40+40+40+40+40+40+(40/2), MACRO_BUTTON_Y+15, font);
      break;
    case 8:
      M5.Lcd.fillRoundRect(40+40+40+40+40+40+40, MACRO_BUTTON_Y, 39, 72, 6, buttonColor);
      M5.Lcd.drawCentreString("8", 2+40+40+40+40+40+40+40+(40/2), MACRO_BUTTON_Y+15, font);
      break;
  }
}

void setupButtons() {
  M5.Touch.addHandler(buttonWasPressed, TE_BTNONLY + TE_TOUCH + TE_RELEASE);

  M5.Lcd.fillScreen(BLACK);

  M5.Lcd.setTextSize(2);
  M5.Lcd.setTextColor(WHITE);
  M5.Lcd.drawString("Program", 6, CAMERA_BUTTON_Y-20, 1);
  M5.Lcd.drawString("Preview", 6, MACRO_BUTTON_Y-20, 1);

  drawButton(1, false, false);
  drawButton(2, false, false);
  drawButton(3, false, false);
  drawButton(4, false, false);
  drawButton(5, false, false);
  drawButton(6, false, false);
  drawButton(7, false, false);
  drawButton(8, false, false);
  
  drawCameraButton(1, CAMERA_OFF);
  drawCameraButton(2, CAMERA_OFF);
  drawCameraButton(3, CAMERA_OFF);
  drawCameraButton(4, CAMERA_OFF);
  drawCameraButton(5, CAMERA_OFF);
  drawCameraButton(6, CAMERA_OFF);
  drawCameraButton(7, CAMERA_OFF);
  drawCameraButton(8, CAMERA_OFF);
}

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

  Serial.println("About to connect to wifi");
  WiFi.begin(ssid, password);

  int lpcnt=0 ;
  int lpcnt2=0 ;

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    lpcnt += 1 ;                           //
    if (lpcnt > 6) {                       // 6回目(3秒) で切断/再接続
      WiFi.disconnect(true,true);          //
      WiFi.begin(ssid, password);    //
      lpcnt = 0 ;                          //
      lpcnt2 += 1 ;                        // 再接続の回数をカウント
    }                                      //
    if (lpcnt2 > 3) {                      // 3回 接続できなければ、
      ESP.restart() ;                      // ソフトウェアリセット
    }                  
    Serial.print(".");
  }
  Serial.println("Connected to the WiFi network");

  AtemSwitcher.begin(switcherIp);
  AtemSwitcher.serialOutput(0x80);
  AtemSwitcher.connect();
  Serial.println("Connected to AtemSwitcher");

  setupButtons();
}

int currentRunningMacro = -1;
int tallyNum = 8;
int tallyStates[8];

void loop() {
  M5.update();

  AtemSwitcher.runLoop();

  for(int i=1; i<=tallyNum; i++) {
    int currentTallyState = AtemSwitcher.getProgramTally(i) ? CAMERA_PROGRAM : (AtemSwitcher.getPreviewTally(i) ? CAMERA_PREVIEW : CAMERA_OFF);
    if(currentTallyState != tallyStates[i-1]) {
      drawCameraButton(i, currentTallyState);
    }
    tallyStates[i-1] = currentTallyState;
  }
}
```

</div></details>

### M5Core2ライブラリ ver.0.0.6に対応

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

```c++:M5Core2_ATEM_03.ino
// for ATEM Mini Extreme / Extreme ISO
// This code is based on
// https://github.com/aaronpk/m5-core2-atem-controller/
//
// Conditions
//
// include M5Core2 library ver.0.0.6
//
// Requirements
//
// Download Arduino Libraries from here
// https://github.com/kasperskaarhoj/SKAARHOJ-Open-Engineering/tree/master/ArduinoLibs
//
// 1. copy these folders to libraries directory
// ATEMbase
// ATEMmax
// ATEMstd
// SkaarhojPgmspace
//
// 2. change code in ATEMbase.cpp (1 change) and ATEMbase.h (2 changes)
// refer to: https://oneguyoneblog.com/2020/07/13/autopilot-for-blackmagic-design-atem-switcher/
//
//  from                  to
//  --------------------  --------------------
//  #ifdef ESP8266        WiFiUDP Udp;
//  WiFiUDP Udp;
//  #else
//  EthernetUDP Udp;
//  #endif

#include <M5Core2.h>
#include <Fonts/EVA_20px.h>
#include <stdio.h>
#include <WiFi.h>
#include <M5GFX.h>
#include <SkaarhojPgmspace.h>
#include <ATEMmax.h>
#include <ATEMstd.h>

// Define the IP address of your ATEM switcher
IPAddress switcherIp(0, 0, 0, 0);

// Put your wifi SSID and password here
const char* ssid = "";  // Wi-Fi SSID
const char* password =  "";  // Wi-Fi Password

#define CAMERA_OFF 0
#define CAMERA_PREVIEW 1
#define CAMERA_PROGRAM 2

#define CAMERA_BUTTON_Y 42
#define MACRO_BUTTON_Y 150

ATEMstd AtemSwitcher;

Button c1(0, CAMERA_BUTTON_Y, 40, 72, false, "c1");
Button c2(40, CAMERA_BUTTON_Y, 40, 72, false, "c2");
Button c3(40+40, CAMERA_BUTTON_Y, 40, 72, false, "c3");
Button c4(40+40+40, CAMERA_BUTTON_Y, 40, 72, false, "c4");
Button c5(40+40+40+40, CAMERA_BUTTON_Y, 40, 72, false, "c5");
Button c6(40+40+40+40+40, CAMERA_BUTTON_Y, 40, 72, false, "c6");
Button c7(40+40+40+40+40+40, CAMERA_BUTTON_Y, 40, 72, false, "c7");
Button c8(40+40+40+40+40+40+40, CAMERA_BUTTON_Y, 40, 72, false, "c8");

Button d1(0, MACRO_BUTTON_Y, 40, 72, false, "d1");
Button d2(40, MACRO_BUTTON_Y, 40, 72, false, "d2");
Button d3(40+40, MACRO_BUTTON_Y, 40, 72, false, "d3");
Button d4(40+40+40, MACRO_BUTTON_Y, 40, 72, false, "d4");
Button d5(40+40+40+40, MACRO_BUTTON_Y, 40, 72, false, "d5");
Button d6(40+40+40+40+40, MACRO_BUTTON_Y, 40, 72, false, "d6");
Button d7(40+40+40+40+40+40, MACRO_BUTTON_Y, 40, 72, false, "d7");
Button d8(40+40+40+40+40+40+40, MACRO_BUTTON_Y, 40, 72, false, "d8");

void vibration() {
  M5.Axp.SetLDOEnable(3, true);
  delay(50);
  M5.Axp.SetLDOEnable(3, false);
}

void buttonWasPressed(Event& e) {

  const char* buttonName = e.objName();
  const char* eventName = e.typeName();
  char buttonType = buttonName[0];
  int buttonNumber = buttonName[1]-'0';

  // to avoid buttonName == 'background', change buttonType from 'c or b' to 'c or d'
  Serial.printf("debug: buttonName = %s, eventName = %s, buttonType = %c, buttonNumber = %d\n", buttonName, eventName, buttonType, buttonNumber);
  vibration();
  
  if(buttonType == 'c') {
    AtemSwitcher.changeProgramInput(buttonNumber);
  }
  else if( buttonType == 'd') {
    if ( e.type == E_TOUCH) {
      drawButton(buttonNumber, true, false);
    } else {
      drawButton(buttonNumber, CAMERA_OFF, false);
    }

    AtemSwitcher.changePreviewInput(buttonNumber);
  }
  else {
    if(M5.BtnA.isPressed()) {
      AtemSwitcher.doAuto();
      Serial.println("debug: Button A is Pressed");
    }
    else if(M5.BtnB.isPressed()) {
      AtemSwitcher.doCut();
      Serial.println("debug: Button B is Pressed");
    }
    else if(M5.BtnC.isPressed()) {
      // do something
      Serial.println("debug: Button C is Pressed");
    }
  }
}

void drawCameraButton(int buttonNumber, int state) {
  Serial.printf("debug: buttonNumber = %d, state = %d\n", buttonNumber, state);

  int font = 1;
  M5.Lcd.setTextSize(6);
  
  M5.Lcd.setTextColor(state == CAMERA_OFF ? WHITE : BLACK);

  int buttonColor;
  switch(state) {
    case CAMERA_OFF:
      buttonColor = TFT_DARKGREY; break;
    case CAMERA_PREVIEW:
      buttonColor = TFT_GREEN; break;
    case CAMERA_PROGRAM:
      buttonColor = TFT_RED; break;
  }

  switch(buttonNumber) {
    case 1:
      M5.Lcd.fillRoundRect(0, CAMERA_BUTTON_Y, 39, 72, 6, buttonColor);
      M5.Lcd.drawCentreString("1", 2+(40/2), CAMERA_BUTTON_Y+15, font);
      break;
    case 2:
      M5.Lcd.fillRoundRect(40, CAMERA_BUTTON_Y, 39, 72, 6, buttonColor);
      M5.Lcd.drawCentreString("2", 2+40+(40/2), CAMERA_BUTTON_Y+15, font);
      break;
    case 3:
      M5.Lcd.fillRoundRect(40+40, CAMERA_BUTTON_Y, 39, 72, 6, buttonColor);
      M5.Lcd.drawCentreString("3", 2+40+40+(40/2), CAMERA_BUTTON_Y+15, font);
      break;
    case 4:
      M5.Lcd.fillRoundRect(40+40+40, CAMERA_BUTTON_Y, 39, 72, 6, buttonColor);
      M5.Lcd.drawCentreString("4", 2+40+40+40+(40/2), CAMERA_BUTTON_Y+15, font);
      break;
    case 5:
      M5.Lcd.fillRoundRect(40+40+40+40, CAMERA_BUTTON_Y, 39, 72, 6, buttonColor);
      M5.Lcd.drawCentreString("5", 2+40+40+40+40+(40/2), CAMERA_BUTTON_Y+15, font);
      break;
    case 6:
      M5.Lcd.fillRoundRect(40+40+40+40+40, CAMERA_BUTTON_Y, 39, 72, 6, buttonColor);
      M5.Lcd.drawCentreString("6", 2+40+40+40+40+40+(40/2), CAMERA_BUTTON_Y+15, font);
      break;
    case 7:
      M5.Lcd.fillRoundRect(40+40+40+40+40+40, CAMERA_BUTTON_Y, 39, 72, 6, buttonColor);
      M5.Lcd.drawCentreString("7", 2+40+40+40+40+40+40+(40/2), CAMERA_BUTTON_Y+15, font);
      break;
    case 8:
      M5.Lcd.fillRoundRect(40+40+40+40+40+40+40, CAMERA_BUTTON_Y, 39, 72, 6, buttonColor);
      M5.Lcd.drawCentreString("8", 2+40+40+40+40+40+40+40+(40/2), CAMERA_BUTTON_Y+15, font);
      break;
  }
}

void drawButton(int buttonNumber, bool isPressed, bool isRunning) {
  int font = 1;
  M5.Lcd.setTextSize(6);
  
  M5.Lcd.setTextColor(isPressed || isRunning ? BLACK : WHITE);

  int buttonColor = isPressed ? WHITE : (isRunning ? TFT_RED : TFT_DARKGREY);
  
  switch(buttonNumber) {
    case 1:
      M5.Lcd.fillRoundRect(0, MACRO_BUTTON_Y, 39, 72, 6, buttonColor);
      M5.Lcd.drawCentreString("1", 2+(40/2), MACRO_BUTTON_Y+15, font);
      break;
    case 2:
      M5.Lcd.fillRoundRect(40, MACRO_BUTTON_Y, 39, 72, 6, buttonColor);
      M5.Lcd.drawCentreString("2", 2+40+(40/2), MACRO_BUTTON_Y+15, font);
      break;
    case 3:
      M5.Lcd.fillRoundRect(40+40, MACRO_BUTTON_Y, 39, 72, 6, buttonColor);
      M5.Lcd.drawCentreString("3", 2+40+40+(40/2), MACRO_BUTTON_Y+15, font);
      break;
    case 4:
      M5.Lcd.fillRoundRect(40+40+40, MACRO_BUTTON_Y, 39, 72, 6, buttonColor);
      M5.Lcd.drawCentreString("4", 2+40+40+40+(40/2), MACRO_BUTTON_Y+15, font);
      break;
    case 5:
      M5.Lcd.fillRoundRect(40+40+40+40, MACRO_BUTTON_Y, 39, 72, 6, buttonColor);
      M5.Lcd.drawCentreString("5", 2+40+40+40+40+(40/2), MACRO_BUTTON_Y+15, font);
      break;
    case 6:
      M5.Lcd.fillRoundRect(40+40+40+40+40, MACRO_BUTTON_Y, 39, 72, 6, buttonColor);
      M5.Lcd.drawCentreString("6", 2+40+40+40+40+40+(40/2), MACRO_BUTTON_Y+15, font);
      break;
    case 7:
      M5.Lcd.fillRoundRect(40+40+40+40+40+40, MACRO_BUTTON_Y, 39, 72, 6, buttonColor);
      M5.Lcd.drawCentreString("7", 2+40+40+40+40+40+40+(40/2), MACRO_BUTTON_Y+15, font);
      break;
    case 8:
      M5.Lcd.fillRoundRect(40+40+40+40+40+40+40, MACRO_BUTTON_Y, 39, 72, 6, buttonColor);
      M5.Lcd.drawCentreString("8", 2+40+40+40+40+40+40+40+(40/2), MACRO_BUTTON_Y+15, font);
      break;
  }
}

void setupButtons() {
  M5.Buttons.addHandler(buttonWasPressed, E_TAP + E_TOUCH + E_RELEASE);

  M5.Lcd.fillScreen(BLACK);

  M5.Lcd.setTextSize(2);
  M5.Lcd.setTextColor(WHITE);
  M5.Lcd.drawString("Program", 6, CAMERA_BUTTON_Y-20, 1);
  M5.Lcd.drawString("Preview", 6, MACRO_BUTTON_Y-20, 1);

  drawButton(1, false, false);
  drawButton(2, false, false);
  drawButton(3, false, false);
  drawButton(4, false, false);
  drawButton(5, false, false);
  drawButton(6, false, false);
  drawButton(7, false, false);
  drawButton(8, false, false);
  
  drawCameraButton(1, CAMERA_OFF);
  drawCameraButton(2, CAMERA_OFF);
  drawCameraButton(3, CAMERA_OFF);
  drawCameraButton(4, CAMERA_OFF);
  drawCameraButton(5, CAMERA_OFF);
  drawCameraButton(6, CAMERA_OFF);
  drawCameraButton(7, CAMERA_OFF);
  drawCameraButton(8, CAMERA_OFF);
}

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

  Serial.println("About to connect to wifi");
  WiFi.begin(ssid, password);

  int lpcnt=0 ;
  int lpcnt2=0 ;

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    lpcnt += 1 ;                           //
    if (lpcnt > 6) {                       // 6回目(3秒) で切断/再接続
      WiFi.disconnect(true,true);          //
      WiFi.begin(ssid, password);    //
      lpcnt = 0 ;                          //
      lpcnt2 += 1 ;                        // 再接続の回数をカウント
    }                                      //
    if (lpcnt2 > 3) {                      // 3回 接続できなければ、
      ESP.restart() ;                      // ソフトウェアリセット
    }                  
    Serial.print(".");
  }
  Serial.println("Connected to the WiFi network");

  AtemSwitcher.begin(switcherIp);
  AtemSwitcher.serialOutput(0x80);
  AtemSwitcher.connect();
  Serial.println("Connected to AtemSwitcher");

  setupButtons();
}

int currentRunningMacro = -1;
int tallyNum = 8;
int tallyStates[8];

void loop() {
  M5.update();

  AtemSwitcher.runLoop();

  for(int i=1; i<=tallyNum; i++) {
    int currentTallyState = AtemSwitcher.getProgramTally(i) ? CAMERA_PROGRAM : (AtemSwitcher.getPreviewTally(i) ? CAMERA_PREVIEW : CAMERA_OFF);
    if(currentTallyState != tallyStates[i-1]) {
      drawCameraButton(i, currentTallyState);
    }
    tallyStates[i-1] = currentTallyState;
  }
}
```

</div></details>

### 参考にしたページ
https://aaronparecki.com/2020/12/30/10/
https://oneguyoneblog.com/2020/07/13/autopilot-for-blackmagic-design-atem-switcher/