SwitchBotを使ってESP32を赤外線で操作する

SwitchBotには、赤外線学習機能があるので、ESP32で赤外線を受信できるようにすれば、SwitchBotからESP32を操作できるようになります。

赤外線の送受信には、以下のM5ユニットを使います。

https://www.switch-science.com/catalog/5699/

今回は、その赤外線で操作されるESP32は、MP3プレイヤとして仕立てます。
なので、MP3を格納するSDカードスロットとスピーカを搭載したM5Core2を使いました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/529fe0e3-33c7-e5f3-2aa2-ef4f8cfaa5cc.png)

MP3再生と赤外線送受信には以下のライブラリを利用しました。

https://github.com/earlephilhower/ESP8266Audio

https://github.com/crankyoldgit/IRremoteESP8266

また、LCD表示には、以下のライブラリを使いました。

https://github.com/lovyan03/LovyanGFX

結局、platformio.iniは以下の通りでした。

```ini:platformio.ini
[env:m5stack-core2]
platform = espressif32
board = m5stack-core2
framework = arduino
monitor_speed = 115200
upload_port = COM14
monitor_port = COM14
build_flags = 
	-DBOARD_HAS_PSRAM
	-mfix-esp32-psram-cache-issue
lib_deps = 
	m5stack/M5Core2@^0.1.2
	earlephilhower/ESP8266Audio@^1.9.6
	lovyan03/LovyanGFX@^0.4.17
	crankyoldgit/IRremoteESP8266@^2.8.2
```

ソースは以下の感じです。

```cpp:src/main.cpp
#include <Arduino.h>
#include <M5Core2.h>
#include <driver/i2s.h>
#include <AudioGeneratorMP3.h>
#include <AudioOutputI2S.h>
#include <AudioFileSourceID3.h>
#include <AudioFileSourceSD.h>
#include <IRsend.h>
#include <IRrecv.h>
#include <IRutils.h>
#include <SD.h>

#define DEFAULT_AUDIO_GAIN  10.0
#define MUTE_AUDIO_GAIN     2.0

#define AUDIO_BASE_FOLDER  "/Music"

#define FONT_COLOR TFT_BLUE
#define BACK_COLOR TFT_CYAN

#define VOLUME_STEP 10

#define IR_SEND_PIN  32
#define IR_RECV_PIN  33

#define MAKE_CODE(code) ((code << 16) + code)

typedef enum{
  CODE_POWER_ON = 1,
  CODE_POWER,
  CODE_POWER_OFF,
  CODE_VOLUME_DOWN,
  CODE_VOLUME_UP,
  CODE_TRACK_RESUME,
  CODE_TRACK_PREV,
  CODE_TRACK_PAUSE,
  CODE_TRACK_NEXT,
  CODE_TRACK_STOP,
  CODE_MUTE,
} REMOCON_CODE;

typedef enum{
  STANDBY,
  STOPPED,
  PLAYING,
  PAUSED,
} AUDIO_STATE;

#define LGFX_USE_V1
#define LGFX_AUTODETECT
#include <LovyanGFX.hpp>
#include <LGFX_AUTODETECT.hpp>
static LGFX lcd;

static IRsend irsend(IR_SEND_PIN);
static IRrecv irrecv(IR_RECV_PIN);
static decode_results results;

static AudioOutputI2S *out = NULL;
static AudioGeneratorMP3 *mp3 = NULL;
static AudioFileSourceSD *file_sd = NULL;
static AudioFileSourceID3 *id3 = NULL;
static float g_audio_gain = DEFAULT_AUDIO_GAIN;
static bool g_audio_muted = false;
static int g_audio_index = -1;
static AUDIO_STATE g_audio_state = STANDBY;
static int cur_position = CODE_POWER_ON;
static bool sd_mounted = false;

static long m5_initialize(void);
long audio_playNextMp3(bool next);

void print_CODE(void)
{
  lcd.setCursor(0, 0);
  lcd.fillScreen(TFT_BLACK);
  switch(cur_position){
    case CODE_POWER_ON: lcd.println("ON"); break;
    case CODE_POWER: lcd.println("POWER"); break;
    case CODE_POWER_OFF: lcd.println("OFF"); break;
    case CODE_VOLUME_DOWN: lcd.println("-"); break;
    case CODE_VOLUME_UP: lcd.println("+"); break;
    case CODE_TRACK_RESUME: lcd.println("Play"); break;
    case CODE_TRACK_PREV: lcd.println("Prev"); break;
    case CODE_TRACK_PAUSE: lcd.println("Pause"); break;
    case CODE_TRACK_NEXT: lcd.println("Next"); break;
    case CODE_TRACK_STOP: lcd.println("Stop"); break;
    case CODE_MUTE: lcd.println("Mute"); break;
  }
}

long ir_initialize(void)
{
  irsend.begin();
  irrecv.enableIRIn();
  irrecv.disableIRIn();

  return 0;
}

long ir_send(uint32_t data, uint16_t repeat)
{
  irsend.sendNEC(data, 32, repeat);

  return 0;
}

long ir_recv_start(void)
{
  irrecv.enableIRIn();
  irrecv.resume();

  return 0;
}

long ir_recv_end(void)
{
  irrecv.disableIRIn();

  return 0;
}

uint32_t ir_check_recv(void)
{
  if( irrecv.decode(&results) ){
    uint64_t value = results.value;
    irrecv.resume();
    if( (value >> 32) != 0 )
      return 0;    
    return (uint32_t)(value & 0xffffffff);
  }

  return 0;
}

long audio_initialize(void)
{
  out = new AudioOutputI2S(I2S_NUM_0, AudioOutputI2S::EXTERNAL_I2S); // M5Core2, M5Atom Echo
  out->SetPinout(12, 0, 2); // M5Core2
//  out->SetPinout(19, 33, 22); // M5Atom Echo
//  out = new AudioOutputI2S(I2S_NUM_0, AudioOutputI2S::INTERNAL_DAC); // M5StackFire, M5StickC+SpeakerHat

  out->SetOutputModeMono(true);
  out->SetGain(g_audio_gain / 100.0);

  return 0;
}

long audio_stopMp3(void)
{
  if( mp3 != NULL )
    mp3->stop();
  if( id3 != NULL ){
    id3->RegisterMetadataCB(NULL, NULL);
    id3->close();
  }
  if( file_sd != NULL )
    file_sd->close();

  if( mp3 != NULL ){
    delete mp3;
    mp3 = NULL;
  }
  if( id3 != NULL ){
    delete id3;
    id3 = NULL;
  }
  if( file_sd != NULL ){
    delete file_sd;
    file_sd = NULL;
  }

  g_audio_state = STOPPED;

  return 0;
}

long audio_updateGain(void){
  if( g_audio_gain < 0 ) g_audio_gain = 0;
  if( g_audio_gain > 100 ) g_audio_gain = 100;

  if( g_audio_state == PAUSED ){
    out->SetGain(0 / 100.0);
    out->flush();
  }else if( g_audio_muted ){
    out->SetGain(MUTE_AUDIO_GAIN / 100.0);
  }else{
    out->SetGain(g_audio_gain / 100.0);
  }

  return 0;
}

void audio_MDCallback(void *cbData, const char *type, bool isUnicode, const char *string)
{
  (void)cbData;
  if (string[0] == '\0') { return; }
  if (strcmp(type, "eof") == 0){
    Serial.println("ID3: eof");
    return;
  }
  Serial.printf("ID3: %s: %s\n", type, string);
  lcd.printf("%s: %s\n", type, string);
}

long audio_playMp3(const char *p_fname, bool reset_gain = false)
{
  audio_stopMp3();

  file_sd = new AudioFileSourceSD(p_fname);
  if( !file_sd->isOpen() ){
    delete file_sd;
    file_sd = NULL;
    return -1;
  }

  lcd.setCursor(0, 0);
  lcd.fillScreen(BACK_COLOR);

  id3 = new AudioFileSourceID3(file_sd);
  id3->RegisterMetadataCB(audio_MDCallback, (void*)"ID3TAG");
  mp3 = new AudioGeneratorMP3();
  g_audio_state = PLAYING;
  mp3->begin(id3, out);

  return 0;
}

long audio_playNextMp3(bool next){
  File base = SD.open(AUDIO_BASE_FOLDER);
  if( !base )
    return -1;

  File file;
  int num_of_sound = 0;
  for( int i = 0 ; file = base.openNextFile(); i++ ){
    num_of_sound++;
    file.close();
  }
  base.rewindDirectory();

  if( !next ){
    g_audio_index--;
    if( g_audio_index < 0 ) g_audio_index = num_of_sound -1;
  }else{
    g_audio_index++;
    if( g_audio_index >= num_of_sound ) g_audio_index = 0;
  }

  long ret;
  for( int i = 0 ; file = base.openNextFile(); i++ ){
    if( g_audio_index == i ){
      ret = audio_playMp3(file.name());
      file.close();
      base.close();
      return ret;
    }
    file.close();
  }

  base.close();
  return -1;
}

void audio_loop(void)
{
  if( mp3 != NULL ){
    if (mp3->isRunning()) {
      if( g_audio_state == PLAYING ){
        if (!mp3->loop()){
          lcd.setCursor(0, 0);
          lcd.fillScreen(BACK_COLOR);
          audio_playNextMp3(true);
//          mp3->stop();
//          g_audio_state = STOPPED;
        }
      }
    }
  }
}

void power_onoff(bool onoff)
{
  if( !onoff ){
    if( g_audio_state != STANDBY ){
      audio_stopMp3();
      g_audio_state = STANDBY;
      g_audio_muted = false;

      lcd.setCursor(0, 0);
      lcd.fillScreen(TFT_BLACK);
    }
  }else{
    if( g_audio_state == STANDBY ){
      lcd.setCursor(0, 0);
      lcd.fillScreen(BACK_COLOR);

      long ret = audio_playNextMp3(true);
      if( ret == 0 ){
        g_audio_state = PLAYING;
      }
    }
  }
}

void power_toggle(void){
  if( g_audio_state != STANDBY )
    power_onoff(false);
  else
    power_onoff(true);
}

void volume_relative(int16_t step){
  g_audio_gain += step;
  g_audio_muted = false;
  audio_updateGain();
}

void mute_toggle(void){
  g_audio_muted = !g_audio_muted;
  audio_updateGain();
}

void track_resume(void){
  if( g_audio_state == PAUSED ){
    g_audio_state = PLAYING;
    audio_updateGain();
  }else{
    lcd.setCursor(0, 0);
    lcd.fillScreen(BACK_COLOR);
    long ret = audio_playNextMp3(true);
    if( ret == 0 )
      g_audio_state = PLAYING;
  }
}

void track_stop(void){
  audio_stopMp3();
  lcd.setCursor(0, 0);
  lcd.fillScreen(BACK_COLOR);
}

void track_prev(void){
  if( g_audio_state == PAUSED ){
    g_audio_state = PLAYING;
    audio_updateGain();
  }

  lcd.setCursor(0, 0);
  lcd.fillScreen(BACK_COLOR);
  long ret = audio_playNextMp3(false);
  if( ret == 0 )
    g_audio_state = PLAYING;
}

void track_next(void){
  if( g_audio_state == PAUSED ){
    g_audio_state = PLAYING;
    audio_updateGain();
  }

  lcd.setCursor(0, 0);
  lcd.fillScreen(BACK_COLOR);
  long ret = audio_playNextMp3(true);
  if( ret == 0 )
    g_audio_state = PLAYING;
}

void track_pause(void){
  if( g_audio_state == PLAYING ){
    g_audio_state = PAUSED;
    audio_updateGain();
  }
}

void track_pause_toggle(void){
  if( g_audio_state == PLAYING ){
    track_pause();
  }else{
    track_resume();
  }
}

void setup() {
  // put your setup code here, to run once:

  long ret = m5_initialize();
  if( ret != 0 )
    Serial.println("m5_initialize error");

  ret = ir_initialize();
  if( ret != 0 )
    Serial.println("ir_initialize error");

  lcd.init();
  lcd.setFont(&fonts::lgfxJapanGothic_24);
  lcd.setColorDepth(16);
  lcd.setBrightness(128);
  lcd.setRotation(1);

  lcd.setCursor(0, 0);
  lcd.fillScreen(TFT_BLACK);
  lcd.setTextColor(FONT_COLOR);

  File file = SD.open(AUDIO_BASE_FOLDER);
  if( !file ){
    Serial.println("SD not mounted");
    print_CODE();
  }else{
    file.close();
    sd_mounted = true;

    ret = audio_initialize();
    if( ret != 0 )
      Serial.println("audio_initialize error");
  }

  ir_recv_start();

  Serial.println("[setup finished]");
}

void loop() {
  // put your main code here, to run repeatedly:

  M5.update();

  if( sd_mounted ){
    audio_loop();

    uint32_t irdata = ir_check_recv();
    if( irdata != 0 ){
      Serial.printf("Ir recv: %d\n", irdata);
      switch(irdata){
        case MAKE_CODE(CODE_POWER_ON): power_onoff(true); break;
        case MAKE_CODE(CODE_POWER): power_toggle(); break;
        case MAKE_CODE(CODE_POWER_OFF): power_onoff(false); break;
        case MAKE_CODE(CODE_VOLUME_DOWN): volume_relative(-VOLUME_STEP); break;
        case MAKE_CODE(CODE_VOLUME_UP): volume_relative(VOLUME_STEP); break;
        case MAKE_CODE(CODE_TRACK_RESUME): track_resume(); break;
        case MAKE_CODE(CODE_TRACK_PREV): track_prev(); break;
        case MAKE_CODE(CODE_TRACK_PAUSE): track_pause(); break;
        case MAKE_CODE(CODE_TRACK_NEXT): track_next(); break;
        case MAKE_CODE(CODE_TRACK_STOP): track_stop(); break;
        case MAKE_CODE(CODE_MUTE): mute_toggle(); break;
      }
    }

    if( M5.BtnA.wasPressed() ){
      track_prev();
    }else if( M5.BtnB.wasPressed() ){
      track_pause_toggle();
    }else if( M5.BtnC.wasPressed() ){
      track_next();
    }
  }else{
    uint32_t irdata = ir_check_recv();
    if( irdata != 0 ){
      Serial.printf("Ir recv: %d\n", irdata);
      switch(irdata){
        case MAKE_CODE(CODE_POWER_ON): lcd.println("recv: CODE_POWER_ON"); break;
        case MAKE_CODE(CODE_POWER): lcd.println("recv: CODE_POWER"); break;
        case MAKE_CODE(CODE_POWER_OFF): lcd.println("recv: CODE_POWER_OFF"); break;
        case MAKE_CODE(CODE_VOLUME_DOWN): lcd.println("recv: CODE_VOLUME_DOWN"); break;
        case MAKE_CODE(CODE_VOLUME_UP): lcd.println("recv: CODE_VOLUME_UP"); break;
        case MAKE_CODE(CODE_TRACK_RESUME): lcd.println("recv: CODE_TRACK_RESUME"); break;
        case MAKE_CODE(CODE_TRACK_PREV): lcd.println("recv: CODE_TRACK_PREV"); break;
        case MAKE_CODE(CODE_TRACK_PAUSE): lcd.println("recv: CODE_TRACK_PAUSE"); break;
        case MAKE_CODE(CODE_TRACK_NEXT): lcd.println("recv: CODE_TRACK_NEXT"); break;
        case MAKE_CODE(CODE_TRACK_STOP): lcd.println("recv: CODE_TRACK_STOP"); break;
        case MAKE_CODE(CODE_MUTE): lcd.println("recv: CODE_MUTE"); break;
      }
    }

    if( M5.BtnA.wasPressed() ){
      cur_position--;
      if( cur_position < CODE_POWER_ON )
        cur_position = CODE_MUTE;
      print_CODE();
    }else if( M5.BtnB.wasPressed() ){
      ir_send(MAKE_CODE(cur_position), 1);
      Serial.println("ir sended");
      lcd.println("ir sended");
    }else if(M5.BtnC.wasPressed()){
      cur_position++;
      if( cur_position > CODE_MUTE )
        cur_position = CODE_POWER_ON;
      print_CODE();
    }
  }

  delay(1);
}

static long m5_initialize(void)
{
  M5.begin(true, true, true, true);
  M5.Axp.SetSpkEnable(true);

  Serial.println("[initializing]");

  return 0;
}
```

今までの寄せ集めで、あまり新しいことはやっていませんので、以下を参考にしてください。

https://qiita.com/poruruba/items/60fe119ef20e3cd38e2e

https://qiita.com/poruruba/items/3a9afd186991a0227f1d

# SwitchBotに赤外線登録

まずは、SwitchBot赤外線を記録します。
M5Core2では、SDカードを外して起動すると、赤外線送信モードになるようにしています。

AndroidからSwitchBotアプリを立ち上げ、右上の「＋」ボタンを押ます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/15a08e2f-d75a-7bc1-acea-35a09176b8c4.png)

赤外線リモコンを選択し、今回は「スピーカー」を選択します。適当に選択して登録を完了します。名前は適当に「マイスピーカー」にしました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/1bdbc6c6-2ee9-c175-117e-78fc3c8add41.png)

「マイスピーカー」を選択したのち、右上の鉛筆マークを選択すると、赤外線学習モードになります。
この状態で、ESP32を赤外線送信モードにして赤外線送信すれば学習されます。M5Core2の3つのボタンのうち左右で送信したいボタンのマークを選択し、真ん中のボタンを押すことで送信します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/45d390e5-255d-6847-9c08-d36229546d0c.png)

あとは、右上の歯車マークを押して「無効のボタンを表示する」のチェックをOffにすれば、使わないボタンは非表示にできます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/4f9284da-1cc8-bc2d-2d58-98f72c1585d1.png)

実際に送信しているコードは、ソースコード上の以下の部分です。区別できれば良いので適当ですが。

```cpp:src/main.cpp
#define MAKE_CODE(code) ((code << 16) + code)

typedef enum{
  CODE_POWER_ON = 1,
  CODE_POWER,
  CODE_POWER_OFF,
  CODE_VOLUME_DOWN,
  CODE_VOLUME_UP,
  CODE_TRACK_RESUME,
  CODE_TRACK_PREV,
  CODE_TRACK_PAUSE,
  CODE_TRACK_NEXT,
  CODE_TRACK_STOP,
  CODE_MUTE,
} REMOCON_CODE;
```

# 使い方

SDカードをM5Core2に挿入してリセットをかければ待ち状態になります。
電源ONにすると自動的にSDカード内の「/Music」フォルダにあるMP3を順に再生します。
ちなみに、MP3ファイルに書かれたアーティスト名や歌名は、UTF-16で書かれていることが多いかと思いますが、対応していないので文字化けしますのであしからず。。。

# HTTPによるWebAPIでも操作する

赤外線だけじゃなくて、WebAPI呼び出しでも音楽生成を操作したいですよね。
そんな時は、SwitchBotがWebAPI呼び出しに対応しているので、それを使えばよいです。

Node.jsで行う場合のソースコードを以下に示します。
大したことはしていないです。

```js:switchbot.js
"use strict";

const base_url = "https://api.switch-bot.com/v1.0";

const { URLSearchParams } = require('url');
const fetch = require('node-fetch');
const Headers = fetch.Headers;

class SwitchBot{
    constructor(token){
      this.authorization = token;
    }

    setToken(token){
      this.authorization = token;
    }

    async getDeviceList(){
      var json = await do_get_with_authorization(base_url + "/devices", null, this.authorization);
      if( json.statusCode != 100 )
        throw "statusCode is not 100";
      return json.body;
    }

    async getDeviceStatus(deviceId){
      var json = await do_get_with_authorization(base_url + "/devices/" + deviceId + "/status", null, this.authorization);
      if( json.statusCode != 100 )
        throw "statusCode is not 100";
      return json.body;
    }

    async sendDeviceControlCommand(deviceId, commandType, command, parameter ){
      var params = {
        command: command,
        parameter: parameter,
        commandType: commandType
      };
      var json = await do_post_with_authorization(base_url + "/devices/" + deviceId + "/commands", params, this.authorization);
      if( json.statusCode != 100 )
        throw "statusCode is not 100";
    }
    
    async speaker_setMute(deviceId){
    	return this.sendDeviceControlCommand(deviceId, "command", "setMute", "default");
    }
    async speaker_Next(deviceId){
    	return this.sendDeviceControlCommand(deviceId, "command", "Next", "default");
    }
    async speaker_Previous(deviceId){
    	return this.sendDeviceControlCommand(deviceId, "command", "Previous", "default");
    }
    async speaker_Pause(deviceId){
    	return this.sendDeviceControlCommand(deviceId, "command", "Pause", "default");
    }
    async speaker_Play(deviceId){
    	return this.sendDeviceControlCommand(deviceId, "command", "Play", "default");
    }
    async speaker_Stop(deviceId){
    	return this.sendDeviceControlCommand(deviceId, "command", "Stop", "default");
    }
    async speaker_volumeAdd(deviceId){
    	return this.sendDeviceControlCommand(deviceId, "command", "volumeAdd", "default");
    }
    async speaker_volumeSub(deviceId){
    	return this.sendDeviceControlCommand(deviceId, "command", "volumeSub", "default");
    }
    async turnOn(deviceId){
    	return this.sendDeviceControlCommand(deviceId, "command", "turnOn", "default");
    }
    async turnOff(deviceId){
    	return this.sendDeviceControlCommand(deviceId, "command", "turnOff", "default");
    }
}

module.exports = SwitchBot;

function do_get_with_authorization(url, qs, authorization) {
  const headers = new Headers({ Authorization: authorization });
  var params = new URLSearchParams(qs);

  var params_str = params.toString();
  var postfix = (params_str == "") ? "" : ((url.indexOf('?') >= 0) ? ('&' + params_str) : ('?' + params_str));
  return fetch(url + postfix, {
      method: 'GET',
      headers: headers
    })
    .then((response) => {
      if (!response.ok)
        throw 'status is not 200';
      return response.json();
    });
}

function do_post_with_authorization(url, body, authorization) {
  const headers = new Headers({ "Content-Type": "application/json", Authorization: authorization });

  return fetch(url, {
      method: 'POST',
      body: JSON.stringify(body),
      headers: headers
    })
    .then((response) => {
      if (!response.ok)
        throw 'status is not 200';
      return response.json();
    });
}
```

以下参考です。

https://qiita.com/poruruba/items/d570c283246b9807b0de

https://github.com/OpenWonderLabs/SwitchBotAPI

# 赤外線送信用M5StickC

要は、M5StickCを音楽生成のためのリモコンにします。
(ただし、M5StickCの赤外線送信距離はすごく短いです)

```cpp:src/main.cpp
#include <Arduino.h>
#include <M5StickC.h>
#include <IRsend.h>

#define IR_SEND_PIN  9
#define FONT_COLOR TFT_BLUE

#define MAKE_CODE(code) ((code << 16) + code)

typedef enum{
  CODE_POWER_ON = 1,
  CODE_POWER,
  CODE_POWER_OFF,
  CODE_VOLUME_DOWN,
  CODE_VOLUME_UP,
  CODE_TRACK_RESUME,
  CODE_TRACK_PREV,
  CODE_TRACK_PAUSE,
  CODE_TRACK_NEXT,
  CODE_TRACK_STOP,
  CODE_MUTE,
} REMOCON_CODE;

#define LGFX_USE_V1
#define LGFX_AUTODETECT
#include <LovyanGFX.hpp>
#include <LGFX_AUTODETECT.hpp>
static LGFX lcd;
static IRsend irsend(IR_SEND_PIN);

static int cur_position = CODE_POWER_ON;

static long m5_initialize(void);

void print_CODE(void)
{
  lcd.setCursor(0, 0);
  lcd.fillScreen(TFT_BLACK);
  switch(cur_position){
    case CODE_POWER_ON: lcd.println("ON"); break;
    case CODE_POWER: lcd.println("POWER"); break;
    case CODE_POWER_OFF: lcd.println("OFF"); break;
    case CODE_VOLUME_DOWN: lcd.println("-"); break;
    case CODE_VOLUME_UP: lcd.println("+"); break;
    case CODE_TRACK_RESUME: lcd.println("Play"); break;
    case CODE_TRACK_PREV: lcd.println("Prev"); break;
    case CODE_TRACK_PAUSE: lcd.println("Pause"); break;
    case CODE_TRACK_NEXT: lcd.println("Next"); break;
    case CODE_TRACK_STOP: lcd.println("Stop"); break;
    case CODE_MUTE: lcd.println("Mute"); break;
  }
}

long ir_initialize(void)
{
  irsend.begin();

  return 0;
}

long ir_send(uint32_t data, uint16_t repeat)
{
  irsend.sendNEC(data, 32, repeat);

  return 0;
}

void setup() {
  // put your setup code here, to run once:

  long ret;
  
  ret = m5_initialize();
  if( ret != 0 )
    Serial.println("m5_initialize error");

  ret = ir_initialize();
  if( ret != 0 )
    Serial.println("ir_initialize error");

  lcd.init();
  lcd.setFont(&fonts::lgfxJapanGothic_24);
  lcd.setColorDepth(16);
  lcd.setBrightness(128);
  lcd.setRotation(0);
  lcd.setTextColor(FONT_COLOR);
  lcd.fillScreen(TFT_BLACK);

  print_CODE();

  Serial.println("[setup finished]");
}

void loop() {
  // put your main code here, to run repeatedly:

  M5.update();

  if( M5.BtnA.wasPressed() ){
    ir_send(MAKE_CODE(cur_position), 1);
    Serial.println("ir sended");
    lcd.println("ir sended");
  }else if( M5.BtnB.wasPressed() ){
    cur_position++;
    if( cur_position > CODE_MUTE )
      cur_position = CODE_POWER_ON;
    print_CODE();
  }else if(M5.Axp.GetBtnPress() == 2){
    cur_position--;
    if( cur_position < CODE_POWER_ON )
      cur_position = CODE_MUTE;
    print_CODE();
  }
}

static long m5_initialize(void)
{
  M5.begin(true, true, true);

  Serial.println("[initializing]");

  return 0;
}
```


以上