M5Stack Core2とAlexaでインターフォンに自動で対応してくれるIoTデバイスを作ってみる

# はじめに

作りたいものは**このタイトルと以下の写真**を見てもらえたらなんとなく察してもらえるのでは？と思っています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209689/e7c4d9a3-69a5-2442-bcb1-4910e9104471.png)

私の住んでいるアパートのピンポンに出るための`通話ボタン`と入り口のオートロックを開けるための`解錠ボタン`を自動で押してくれるためのデバイスを作りたい！というわけです。なおかつそれをAlexaで操作したいと考えています。

# 使用したもの

使用したハードウェアは以下の通りです。

- [M5Stack Core2](https://www.switch-science.com/catalog/6530/)
- [M5Stack用Servo2モジュール](https://www.switch-science.com/catalog/6737/)
- [M5GO Bottom2](https://www.switch-science.com/catalog/6785/)

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">テンション上がる <a href="https://t.co/xtrchhHIzy">pic.twitter.com/xtrchhHIzy</a></p>&mdash; ゆーふぉーろくはち (@ufoo_yuta) <a href="https://twitter.com/ufoo_yuta/status/1464421438211772422?ref_src=twsrc%5Etfw">November 27, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# 開発環境のセットアップ

今回はm5stackの開発環境を[vscode](https://azure.microsoft.com/ja-jp/products/visual-studio-code/)と[PlatformIO](https://platformio.org/install/ide?install=vscode)を使って開発を行います。少し昔は[Arduinoの拡張](https://marketplace.visualstudio.com/items?itemName=vsciot-vscode.vscode-arduino)を使っていたりしていたのですが、ライブラリの管理の便利さやgit管理のやりやすさの観点から個人的にはPlatformIOの方が便利だと思っています。
今回は以下のライブラリを用いています。

- adafruit/Adafruit PWM Servo Driver Library@^2.4.0
- m5stack/M5Core2@^0.0.9
- vintlabs/FauxmoESP@^3.4
- me-no-dev/AsyncTCP@^1.1.1

# 実装

ソースコード全体はGitHubにすべて公開しております。

https://github.com/ufoo68/alexa_intercom

## サーボモーターを動かす

[公式が用意してくれたサンプルコード](https://github.com/m5stack/M5-ProductExampleCodes/tree/master/Module/SERVO2)を拝借します。基本的にサーボモーターはPWMと呼ばれる一定周期でONになっている電圧を信号として角度制御を行ったりしている[^1]のですが、ここでは詳細は説明しません。とにかくPWMというやつを指定する必要があるのですが、そんなハードウェアあたりを意識したくない人（私とか）としては直接角度を指定してサーボを制御したいわけです。
そのための関数（`servo_angle_write`）を以下のコードをコピペすることで作成することができます。

```cpp
#include <Adafruit_PWMServoDriver.h>

Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(0x40, Wire);

#define SERVOMIN 102  // This is the 'minimum' pulse length count (out of 4096)
#define SERVOMAX 512  // This is the 'maximum' pulse length count (out of 4096)
#define USMIN 500     // This is the rounded 'minimum' microsecond length based on the minimum pulse of 102
#define USMAX 2500    // This is the rounded 'maximum' microsecond length based on the maximum pulse of 512
#define SERVO_FREQ 50 // Analog servos run at ~50 Hz updates

void setServoPulse(uint8_t n, double pulse)
{
  double pulselength;
  pulselength = 1000000; // 1,000,000 us per second
  pulselength /= 50;     // 50 Hz
  Serial.print(pulselength);
  Serial.println(" us per period");
  pulselength /= 4096; // 12 bits of resolution
  Serial.print(pulselength);
  Serial.println(" us per bit");
  pulse *= 1000;
  pulse /= pulselength;
  Serial.println(pulse);
  pwm.setPWM(n, 0, pulse);
}

void servo_angle_write(uint8_t n, int Angle)
{
  double pulse = Angle;
  pulse = pulse / 90 + 0.5;
  setServoPulse(n, pulse);
}

// この関数をsetup()で呼び出す
void servoSetup() {
  pwm.begin();
  pwm.setPWMFreq(50);
}

void setup()
{
  // https://docs.m5stack.com/en/module/servo2?id=module-servo-2
  M5.begin(true, true, true, false, kMBusModeInput);
  // servo2 moduleの対応Pinに合わせるため
  Wire.begin(21, 22);
  servoSetup();
}
```

ちなみに`setup`内で行った初期設定↓は、M5Stack Core2でM5Stack用Servo2モジュールを使う場合には必要となるので、必ず設定しておきましょう。

```cpp
M5.begin(true, true, true, false, kMBusModeInput);
Wire.begin(21, 22);
```

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">サーボ動いた <a href="https://t.co/lBkJAJoRKF">pic.twitter.com/lBkJAJoRKF</a></p>&mdash; ゆーふぉーろくはち (@ufoo_yuta) <a href="https://twitter.com/ufoo_yuta/status/1465635286096875528?ref_src=twsrc%5Etfw">November 30, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

## OTAを設定する

基本的にマイコンの開発はUSBケーブルをつないだ状態でプログラムを書き込むのが普通なのですが、一々マイコンをケーブルにつなぐのは面倒なわけです。
そこで用いるのがOTAという仕組みで、これを用いるとWiFi経由でプログラムの書き込みを行うことができます。[^2]
導入方法はとても簡単で、以下のコードを追加するだけです。

```cpp
#include <WiFi.h>
#include <ArduinoOTA.h>
#include "wifi_setting.h"

void wifiSetup()
{
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  while (WiFi.status() != WL_CONNECTED)
  {
    Serial.print(".");
    delay(100);
  }
  Serial.println();
  Serial.printf("[WIFI] STATION Mode, SSID: %s, IP address: %s\n", WiFi.SSID().c_str(), WiFi.localIP().toString().c_str());
}

// setup内で呼び出す
void otaSetup()
{
  ArduinoOTA.setHostname("m5stack-core2");

  ArduinoOTA
      .onStart([]()
               {
                 String type;
                 if (ArduinoOTA.getCommand() == U_FLASH)
                   type = "sketch";
                 else // U_SPIFFS
                   type = "filesystem";
                 Serial.println("Start updating " + type);
               })
      .onEnd([]()
             { Serial.println("\nEnd"); })
      .onProgress([](unsigned int progress, unsigned int total)
                  { Serial.printf("Progress: %u%%\r", (progress / (total / 100))); })
      .onError([](ota_error_t error)
               {
                 Serial.printf("Error[%u]: ", error);
                 if (error == OTA_AUTH_ERROR)
                   Serial.println("Auth Failed");
                 else if (error == OTA_BEGIN_ERROR)
                   Serial.println("Begin Failed");
                 else if (error == OTA_CONNECT_ERROR)
                   Serial.println("Connect Failed");
                 else if (error == OTA_RECEIVE_ERROR)
                   Serial.println("Receive Failed");
                 else if (error == OTA_END_ERROR)
                   Serial.println("End Failed");
               });

  ArduinoOTA.begin();
}

void setup()
{
  wifiSetup();
  otaSetup();
}
```

ちなみに`wifiSetup()`としてwifiの初期設定のコードを実装しております。OTAを使うためには当然、wifiに接続させる必要があります。
また、OTA用の環境を設定する必要があるので、PlatformIOの設定ファイルを以下のように追加で作成しました。

```ini:platformio.ini
[env:m5stack-core2]
platform = espressif32
board = m5stack-core2
framework = arduino
monitor_speed = 115200
lib_deps = 
	adafruit/Adafruit PWM Servo Driver Library@^2.4.0
	m5stack/M5Core2@^0.0.9
	vintlabs/FauxmoESP@^3.4
	me-no-dev/AsyncTCP@^1.1.1

[env:m5stack-core2_ota]
platform = espressif32
board = m5stack-core2
framework = arduino
lib_deps = 
	adafruit/Adafruit PWM Servo Driver Library@^2.4.0
	m5stack/M5Core2@^0.0.9
	vintlabs/FauxmoESP@^3.4
	me-no-dev/AsyncTCP@^1.1.1
monitor_speed = 115200
upload_protocol = espota
upload_port = m5stack-core2
```

`[env:m5stack-core2_ota]`が今回OTA用に追加した項目です。

## Alexaデバイスとして登録する

Alexaデバイスとして登録をすることで[Alexaのアプリ](https://apps.apple.com/us/app/amazon-alexa/id944011620)からM5Stack Core2を操作することができます。そのために[fauxmoESP](https://github.com/vintlabs/fauxmoESP)というライブラリを用います。[^3]
まずは以下のコードを追加します。

```cpp
#include <fauxmoESP.h>

// setup内で呼び出す
void alexaDeviceSetup()
{
  fauxmo.createServer(true);
  fauxmo.setPort(80);
  fauxmo.enable(true);
  fauxmo.addDevice("intercom");
  fauxmo.addDevice("door");
  fauxmo.onSetState([](unsigned char device_id, const char *device_name, bool state, unsigned char value)
                    {
                      Serial.printf("[MAIN] Device #%d (%s) state: %s value: %d\n", device_id, device_name, state ? "ON" : "OFF", value);
                      servo_angle_write(device_id, 90);
                      delay(500);
                      servo_angle_write(device_id, 70);
                      delay(500);
                      servo_angle_write(device_id, 90);
                    });
}

void setup()
{
  alexaDeviceSetup();
}
```

`fauxmo.addDevice()`でAlexaデバイスとして登録するデバイス名を設定します。どうやら複数設定できるようです。設定をするとAlexaのアプリからデバイスへ接続できます。

![iOS の画像.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/209689/49184f49-38fd-f0ec-418b-5b4b7843864e.jpeg)

fauxmoはデバイスをライトとしてAlexaデバイスに認識させるためのライブラリとなっています。なので、見かけ上は[Philips Hue](https://www.philips-hue.com/ja-jp)としてライトのON/OFFや明るさ制御の命令をAlexaから受け取ることができます。この制御命令を利用してサーボモーターの動作スイッチとすることとします。この処理内容は`fauxmo.onSetState`で記載しています。
以上でAlexaとM5Stack Core2の連携が実現できました。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Alexaと連携できた <a href="https://t.co/aEmmY2ISLV">pic.twitter.com/aEmmY2ISLV</a></p>&mdash; ゆーふぉーろくはち (@ufoo_yuta) <a href="https://twitter.com/ufoo_yuta/status/1465638192359477252?ref_src=twsrc%5Etfw">November 30, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

# さいごに

後はサーボモーターの動作角度とかを微調整しながら実証実験だと思っていたのですが、ここで悲劇が起きました

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">剥がれたwww <a href="https://t.co/8gprKwzC4n">pic.twitter.com/8gprKwzC4n</a></p>&mdash; ゆーふぉーろくはち (@ufoo_yuta) <a href="https://twitter.com/ufoo_yuta/status/1465641831224414210?ref_src=twsrc%5Etfw">November 30, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

まあ、そりゃそうだなと思ったわけですがwww
M5Stack Core2がいろんなモジュールを重ねて重くなったので、壁への取り付けを一旦諦めました。。。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">んで、m5stackの方は壁への張り付けを諦めた。<br>何とも情けない <a href="https://t.co/0IuiLDJoeB">pic.twitter.com/0IuiLDJoeB</a></p>&mdash; ゆーふぉーろくはち (@ufoo_yuta) <a href="https://twitter.com/ufoo_yuta/status/1466748029147709443?ref_src=twsrc%5Etfw">December 3, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

とりあえず動けばいいやと思い、サーボを一旦動かしてみます。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">実証実験してないけどわかる。これ多分押せてない <a href="https://t.co/r7kLgaNAgN">pic.twitter.com/r7kLgaNAgN</a></p>&mdash; ゆーふぉーろくはち (@ufoo_yuta) <a href="https://twitter.com/ufoo_yuta/status/1466746584331620354?ref_src=twsrc%5Etfw">December 3, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

正直サーボモーターを壁紙で貼れる用の剥離両面テープで固定していたので、当然そうなるとは思っていましたwww
結局一番難関かと思われる壁への取り付け方法という課題を残してしまったのですが、ソフトウェアとしてはだいたいやることをやった気がするので一旦記事にまとめてみました。再掲しますがコードは↓にすべて公開しております。サーボモーター制御のみに留まらず、色んなことに応用できると思うので是非試してみてください！！

https://github.com/ufoo68/alexa_intercom

[^1]: [サーボモータの制御](http://mitt.la.coocan.jp/pic/pic5_23.html)
[^2]: [PlatformIOでESP32(M5Stick-C)のOTA機能を使う](https://wak-tech.com/archives/1906)
[^3]: [fauxmoESP で スマートホームデバイス自作](https://homemadegarbage.com/espalexa04)