M5Stack ATOM Liteでスマートボタンを作る

# はじめに
夜、ベッドに入った後に照明を消したいのですが、壁面のスイッチは遠くリモコンを毎晩寝室まで持っていくのは面倒です。
ここ数年はスマホから家電を操作できるスマートリモコンと呼ばれるものも大分増えてきましたが、私のように寝る前に暗い部屋でスマホの画面を見たくない人も居ると思います。そんな時、ベッドの周辺にも照明を消すための物理的なボタンがあると便利です。

無ければ作りましょう。とはいえ、壁面のスイッチを増やすには壁に穴を開けて電気工事を行う必要がありコストが高すぎる上に、日本では自宅であっても免許が必要です。そもそも賃貸の場合は、資格があっても勝手に工事はできません。

今回は、小さなハードウェアと簡単なプログラミングで、そんな悩みを解決するデバイスを作りたいと思います。

# 技術選定
まずは、照明を Web API で扱えるようにします。
こちらに関しては別の記事で紹介しているのでそちらを参照してください。
[Raspberry Pi Zero で赤外線リモコンを作る - Qiita](https://qiita.com/ww24/items/32d2a78c20e6dcc2e68c)

最近ではスマートリモコンの既製品がいくつか出回っており、家電量販店でも買えるのでそちらも検討してみてください。無理に自作しなくても大丈夫です！
ここまで導入すると、スマホから照明を消すことは出来るようになります。

今回は、スマホの眩しい画面を見ることなく寝室から消灯したいので、Web API を呼び出してくれる物理ボタンが必要です。
要件として、物理的なボタンのインターフェースを持ち、Wi-Fi アクセスポイントに繋がり、HTTP が喋れれば十分でしょう。

AWS IoT ボタンというものがありましたが、現在は販売終了しています。
https://aws.amazon.com/jp/iotbutton/

似たようなことを既製品でやろうとすると MESH になるでしょうか。
https://meshprj.com/jp/products/blocks/MESH-100BU.html

MESH は割と高価なことと、単体では Wi-Fi に繋がらず、スマートフォンを介さない場合は別途 Raspberry Pi 等をハブとする必要がある点が気になります。
https://meshprj.com/jp/hub/

他にもハードウェアとファームウェアが OSS として公開されている ReButton というデバイスもあります。
https://www.switch-science.com/catalog/5748/

今回は M5Stack シリーズの最小デバイス
M5Stack ATOM Lite を採用しました。
https://www.switch-science.com/catalog/6262/

## M5Stack ATOM Lite
https://docs.m5stack.com/#/en/core/atom_lite

ATOM Lite は ESP32-PICO マイコンをベースとして、ボタンや複数のセンサを組み込んだ小型のデバイスです。

特徴としては
- ボタン
- RGB LED, IR LED
- GPIO
- Bluetooth, Wi-Fi (2.4GHz, 802.11 b/g/n)
- Arduino IDE, Flow UI (Python) によるプログラミング
- 電源(5V/500mA)/PCからの書き込みIF: USB Type-C
- Grove 4ピンIF

今回のユースケースを満たすには丁度良いデバイスです。
プログラミングの必要はありますが、HTTP を喋ることもできます。

お値段も1,000円を切るお手頃価格。
こちらを使ってスマートボタンを実装します。

# 実装
今回は Arduino IDE で実装しました。

```c++
#include <M5Atom.h>
#include <WiFi.h>
#include <HTTPClient.h>

// Wi-Fi SSID
const char *ssid = "********";
// Wi-Fi Password
const char *password = "********";
// ボタン押下時の送信先
const char *endpoint_short = "http://********/api/v1/light/off";
// 長押し時の送信先
const char *endpoint_long = "http://********/api/v1/light/on";

// 長押し判定時間[ミリ秒]
const int32_t long_press_timeout = 1000;
// HTTP タイムアウト時間[ミリ秒]
const int32_t http_timeout = 10 * 1000;

const int32_t red = 0x004000;
const int32_t green = 0x300000;
const int32_t blue = 0x000040;
const int32_t white = 0x202020;

void setup()
{
  M5.begin(true, false, true);
  delay(10);
  M5.dis.setBrightness(10);
  M5.dis.drawpix(0, red);

  Serial.begin(115200);
  while (!Serial);
  delay(100);
  Serial.println("\nSerial started");

  connectWiFi();
  M5.dis.drawpix(0, green);
}

void connectWiFi()
{
  WiFi.disconnect(); // reset
  Serial.printf("Attempting to connect to SSID: %s\n", ssid);
  Serial.print("Connecting Wi-Fi");
  for (int i = 0; WiFi.status() != WL_CONNECTED; i++)
  {
    if (i % 10 == 0) {
      WiFi.begin(ssid, password);
    }
    Serial.print(".");
    delay(1000); // wait a second for connection
  }
  Serial.println("Connected");
}

void loop()
{
  if (WiFi.status() != WL_CONNECTED)
  {
    M5.dis.drawpix(0, red);
    connectWiFi();
    M5.dis.drawpix(0, green);
  }
  
  if (M5.Btn.wasPressed())
  {
    M5.dis.drawpix(0, blue);
    
    int32_t timeout = millis() + long_press_timeout;
    while (M5.Btn.isPressed() && millis() <= timeout)
    {
      delay(100);
      M5.Btn.read();
    }
    
    if (millis() <= timeout)
    {
      send(endpoint_short);
    }
    else
    {
      send(endpoint_long);
    }
  }

  delay(100);
  M5.update();
}

void send(String url)
{
  Serial.println("Start HTTP request");
  M5.dis.drawpix(0, white);
  
  HTTPClient http;
  http.begin(url);
  http.setReuse(false);
  http.setConnectTimeout(http_timeout);
  http.setTimeout(http_timeout);
  int status_code = http.POST("");
  Serial.printf("status_code=%d\r\n", status_code);
  Serial.println(HTTPClient::errorToString(status_code));
  if (status_code != 200)
  {
    Serial.println("request failed");
    M5.dis.drawpix(0, red);
  }
  http.end();

  delay(2000);
  M5.dis.drawpix(0, green);
}
```

短く1度押下すると消灯、長押しすると点灯させる HTTP Request を送信します。
ATOM Lite にはディスプレイが無いため、Wi-Fi の接続状況、HTTP リクエストの送信中、失敗、成功のステータスを RGB LED
 の色で判別できるようにしています。

# デモ
[デモ動画](https://youtu.be/9da5eu4Erb8)

# Tips
![20201123_230901.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1804/5734512c-dd12-0870-a60b-0901235ab447.jpeg)

写真のように、コンセントキャップをネジ止めすると、空いているコンセントに固定できるので便利です。

![20201123_233043.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1804/5a8c53f4-a1b1-a48e-0c45-7d8be271b49f.jpeg)

ネジは、M2皿ネジ 長さ4mmが丁度よいです。
長すぎても、本体に収まらないので注意してください。

大里 PC-471 [純チタン2種小ねじ 皿 0番1種 M2×4]
https://www.yodobashi.com/product-detail/100000001003884173/

コンセントではなく、磁石で固定できる場所に設置する際は、次のようなドーナツ状の磁石が便利です。

トラスコ中山 TRUSCO T08R03M2 [TRUSCO ネオジム丸径磁石 皿ネジ穴付 外径8mm×3mm M2 1個入り]
https://www.yodobashi.com/product-detail/100000001004291037/


# まとめ
安く手に入り拡張性も高く、使いやすいデバイスだと感じました。お陰で当初の問題は解決できました。