M5Paperを使ってSwitchBot操作パネル作る

# はじめに
[**M5Paper**](http://docs.m5stack.com/en/core/m5paper)はM5Stackシリーズの中で電子ペーパーを搭載したバージョンです。
ESP32が内蔵されているので、Wi-FiやBluetooth等が使えるおもしろいデバイスになっています。

<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1402522/2003e7d0-4c0b-3bbe-170f-53eb29c4d789.png">
※公式から借用

これを使用してSwitchBotの操作端末を作ってみます。
SwitchBotの操作には、[SwitchBot API v1.1](https://github.com/OpenWonderLabs/SwitchBotAPI)を使います。
開発言語はｃ++、環境はVS Codeです。


# 開発環境
開発環境の構築はほとんどこちらの記事を参考にさせていただきました。

https://qiita.com/Nabeshin/items/197d1e864d195f4c9edd

追加で下記のライブラリをインポートしています。

	protohaus/ESPRandom@^1.4.1
	adafruit/RTClib@^2.1.1


# プロジェクトの作成
上の記事で使われていた[**FactoryTest**](https://github.com/m5stack/M5Paper_FactoryTest)をベースにします。
ビルド＆インストールすると、下記のような画面が出力されるはずです。
今回は**Home画面**を書き換えて実装したいと思います。

**メイン画面：**
<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1402522/58e1fc3e-6e1f-c894-9fc8-e67532ef1fc5.png">

**Home画面：**
<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1402522/27a9fdc9-fcdc-4cbd-08a7-350388792b22.png">

# 実装の流れ
- SwitchbotのトークンやdeviceID等の情報を取得する（取得方法やリクエストの方法は下記の記事を参考にしてください）

https://qiita.com/akitaro7/items/1d4d60e285ef39168929

- Home画面のUIを変更する
- コールバック関数を登録してSwitchBot APIをリクエストする


# Home画面のUI変更
完成はこれです。
今回は、寝室とリビングのシャッターのスイッチを押すボタンを4つ作りました。
<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1402522/825d74ae-cc29-cb39-6b84-474535f7a293.png">

Home画面は**Frame_Home**というクラスで制御されているため、下記のように改修しました。

- UI配置
M5Paper_FactoryTestプロジェクトには、はじめからスイッチ(ON-OFF)やボタン、テキストボックスといったUIが用意されています。
今回は、ボタンクラスのEPDGUI_Buttonを使いました。
引数で位置とサイズを指定しています。
~~~*.cpp
    _btn_living_shutter_up   = new EPDGUI_Button(20, 44 + 72, 228, 228);
    _btn_living_shutter_down = new EPDGUI_Button(20, 324 + 72, 228, 228);
    _btn_room_shutter_up     = new EPDGUI_Button(288, 44 + 72, 228, 228);
    _btn_room_shutter_down   = new EPDGUI_Button(288, 324 + 72, 228, 228);
~~~

- アイコン・イメージ文字列の設定
InitSwitchというスイッチUIを設定する関数が用意されていたので、コピペしてボタン用に改修しました。

~~~*.cpp
    InitButton(_btn_living_shutter_up, "Open", "Living", ImageResource_home_icon_light_on_92x92);
    InitButton(_btn_living_shutter_down, "Close", "Living", ImageResource_home_icon_conditioner_off_92x92);
    InitButton(_btn_room_shutter_up, "Open", "Bedroom", ImageResource_home_icon_light_on_92x92);
    InitButton(_btn_room_shutter_down, "Close", "Bedroom", ImageResource_home_icon_conditioner_off_92x92);
~~~

~~~*.cpp
void Frame_Home::InitButton(EPDGUI_Button *btn, String title, String subtitle, const uint8_t *img) {
    memcpy(btn->CanvasNormal()->frameBuffer(),
           ImageResource_home_button_background_228x228, 228 * 228 / 2);
    btn->CanvasNormal()->setTextSize(36);
    btn->CanvasNormal()->setTextDatum(TC_DATUM);
    btn->CanvasNormal()->drawString(title, 114, 136);
    btn->CanvasNormal()->setTextSize(26);
    btn->CanvasNormal()->drawString(subtitle, 114, 183);
    btn->CanvasNormal()->pushImage(68, 20, 92, 92, img);
}
~~~

- コールバック関数の登録

今回は使っていませんが、下記のようにオブジェクトを登録(複数可)しておくとコールバック関数でオブジェクトを参照できるようです。
例)_btn_living_shutter_upの0番目に_btn_living_shutter_up（自分）を登録
使い道としては、ボタンが押されたときに他のボタンを無効にするなどが思い浮かびます。
~~~*.cpp
    _btn_living_shutter_up->AddArgs(EPDGUI_Button::EVENT_RELEASED, 0, _btn_living_shutter_up);
    _btn_living_shutter_down->AddArgs(EPDGUI_Button::EVENT_RELEASED, 0, _btn_living_shutter_down);
    _btn_room_shutter_up->AddArgs(EPDGUI_Button::EVENT_RELEASED, 0, _btn_room_shutter_up);
    _btn_room_shutter_down->AddArgs(EPDGUI_Button::EVENT_RELEASED, 0, _btn_room_shutter_down);
~~~

コールバック関数をBind関数で登録しています。
~~~*.cpp
    _btn_living_shutter_up->Bind(EPDGUI_Button::EVENT_RELEASED, btn_living_shutter_up_cb);
    _btn_living_shutter_down->Bind(EPDGUI_Button::EVENT_RELEASED, btn_living_shutter_down_cb);
    _btn_room_shutter_up->Bind(EPDGUI_Button::EVENT_RELEASED, btn_living_room_up_cb);
    _btn_room_shutter_down->Bind(EPDGUI_Button::EVENT_RELEASED, btn_living_room_down_cb);
~~~

コールバック関数たち。
Control_Switchbotクラスは前の[**記事**](https://qiita.com/akitaro7/items/1d4d60e285ef39168929)を参照ください。
~~~*.cpp
void btn_living_shutter_up_cb(epdgui_args_vector_t &args)
{
    String res;
    Control_Switchbot::getInstance()->pushSwitchBot(PRM_SWBOT_DVC_LIVING_SHUTTER_UP_ID, &res);
}

void btn_living_shutter_down_cb(epdgui_args_vector_t &args)
{
    String res;
    Control_Switchbot::getInstance()->pushSwitchBot(PRM_SWBOT_DVC_LIVING_SHUTTER_DOWN_ID, &res);
}

void btn_living_room_up_cb(epdgui_args_vector_t &args)
{
    String res;
    Control_Switchbot::getInstance()->pushSwitchBot(PRM_SWBOT_DVC_ROOM_SHUTTER_UP_ID, &res);
}

void btn_living_room_down_cb(epdgui_args_vector_t &args)
{
    String res;
    Control_Switchbot::getInstance()->pushSwitchBot(PRM_SWBOT_DVC_ROOM_SHUTTER_DOWN_ID, &res);
}
~~~


## Frame_Homeクラス改修結果

~~~frame_home.cpp
#include "frame_home.h"

#include "control_switchbot.h"

void Frame_Home::InitSwitch(EPDGUI_Switch *sw, String title, String subtitle,
                            const uint8_t *img1, const uint8_t *img2) {
    memcpy(sw->Canvas(0)->frameBuffer(),
           ImageResource_home_button_background_228x228, 228 * 228 / 2);
    sw->Canvas(0)->setTextSize(36);
    sw->Canvas(0)->setTextDatum(TC_DATUM);
    sw->Canvas(0)->drawString(title, 114, 136);
    sw->Canvas(0)->setTextSize(26);
    sw->Canvas(0)->drawString(subtitle, 114, 183);
    memcpy(sw->Canvas(1)->frameBuffer(), sw->Canvas(0)->frameBuffer(),
           228 * 228 / 2);
    sw->Canvas(0)->pushImage(68, 20, 92, 92, img1);
    sw->Canvas(1)->pushImage(68, 20, 92, 92, img2);
}

void Frame_Home::InitButton(EPDGUI_Button *btn, String title, String subtitle, const uint8_t *img) {
    memcpy(btn->CanvasNormal()->frameBuffer(),
           ImageResource_home_button_background_228x228, 228 * 228 / 2);
    btn->CanvasNormal()->setTextSize(36);
    btn->CanvasNormal()->setTextDatum(TC_DATUM);
    btn->CanvasNormal()->drawString(title, 114, 136);
    btn->CanvasNormal()->setTextSize(26);
    btn->CanvasNormal()->drawString(subtitle, 114, 183);
    btn->CanvasNormal()->pushImage(68, 20, 92, 92, img);
}

void key_home_air_adjust_cb(epdgui_args_vector_t &args) {
    int operation     = ((EPDGUI_Button *)(args[0]))->GetCustomString().toInt();
    EPDGUI_Switch *sw = ((EPDGUI_Switch *)(args[1]));
    if (sw->getState() == 0) {
        return;
    }
    int temp = sw->GetCustomString().toInt();
    char buf[10];
    if (operation == 1) {
        temp++;

    } else {
        temp--;
    }
    sprintf(buf, "%d", temp);
    sw->SetCustomString(buf);
    sprintf(buf, "%d℃", temp);
    sw->Canvas(1)->setTextSize(36);
    sw->Canvas(1)->setTextDatum(TC_DATUM);
    sw->Canvas(1)->fillRect(114 - 100, 108, 200, 38, 0);
    sw->Canvas(1)->drawString(buf, 114, 108);
    sw->Canvas(1)->pushCanvas(sw->getX(), sw->getY(), UPDATE_MODE_A2);
}

void btn_living_shutter_up_cb(epdgui_args_vector_t &args)
{
    String res;
    Control_Switchbot::getInstance()->pushSwitchBot(PRM_SWBOT_DVC_LIVING_SHUTTER_UP_ID, &res);
}

void btn_living_shutter_down_cb(epdgui_args_vector_t &args)
{
    String res;
    Control_Switchbot::getInstance()->pushSwitchBot(PRM_SWBOT_DVC_LIVING_SHUTTER_DOWN_ID, &res);
}

void btn_living_room_up_cb(epdgui_args_vector_t &args)
{
    String res;
    Control_Switchbot::getInstance()->pushSwitchBot(PRM_SWBOT_DVC_ROOM_SHUTTER_UP_ID, &res);
}

void btn_living_room_down_cb(epdgui_args_vector_t &args)
{
    String res;
    Control_Switchbot::getInstance()->pushSwitchBot(PRM_SWBOT_DVC_ROOM_SHUTTER_DOWN_ID, &res);
}

void key_home_air_state0_cb(epdgui_args_vector_t &args) {
    EPDGUI_Button *b1 = ((EPDGUI_Button *)(args[0]));
    EPDGUI_Button *b2 = ((EPDGUI_Button *)(args[1]));
    b1->SetEnable(false);
    b2->SetEnable(false);
}

void key_home_air_state1_cb(epdgui_args_vector_t &args) {
    EPDGUI_Button *b1 = ((EPDGUI_Button *)(args[0]));
    EPDGUI_Button *b2 = ((EPDGUI_Button *)(args[1]));
    b1->SetEnable(true);
    b2->SetEnable(true);
}

Frame_Home::Frame_Home(void) {
    _frame_name = "Frame_Home";

    _btn_living_shutter_up   = new EPDGUI_Button(20, 44 + 72, 228, 228);
    _btn_living_shutter_down = new EPDGUI_Button(20, 324 + 72, 228, 228);
    _btn_room_shutter_up     = new EPDGUI_Button(288, 44 + 72, 228, 228);
    _btn_room_shutter_down   = new EPDGUI_Button(288, 324 + 72, 228, 228);

    _btn_living_shutter_up->AddArgs(EPDGUI_Button::EVENT_RELEASED, 0, _btn_living_shutter_up);
    _btn_living_shutter_down->AddArgs(EPDGUI_Button::EVENT_RELEASED, 0, _btn_living_shutter_down);
    _btn_room_shutter_up->AddArgs(EPDGUI_Button::EVENT_RELEASED, 0, _btn_room_shutter_up);
    _btn_room_shutter_down->AddArgs(EPDGUI_Button::EVENT_RELEASED, 0, _btn_room_shutter_down);

    _btn_living_shutter_up->Bind(EPDGUI_Button::EVENT_RELEASED, btn_living_shutter_up_cb);
    _btn_living_shutter_down->Bind(EPDGUI_Button::EVENT_RELEASED, btn_living_shutter_down_cb);
    _btn_room_shutter_up->Bind(EPDGUI_Button::EVENT_RELEASED, btn_living_room_up_cb);
    _btn_room_shutter_down->Bind(EPDGUI_Button::EVENT_RELEASED, btn_living_room_down_cb);

    M5EPD_Canvas canvas_temp(&M5.EPD);
    canvas_temp.createRender(36);
    uint8_t language = GetLanguage();

    InitButton(_btn_living_shutter_up, "Open", "Living", ImageResource_home_icon_light_on_92x92);
    InitButton(_btn_living_shutter_down, "Close", "Living", ImageResource_home_icon_conditioner_off_92x92);
    InitButton(_btn_room_shutter_up, "Open", "Bedroom", ImageResource_home_icon_light_on_92x92);
    InitButton(_btn_room_shutter_down, "Close", "Bedroom", ImageResource_home_icon_conditioner_off_92x92);

    if (language == LANGUAGE_JA) {
        exitbtn("ホーム");
        _canvas_title->drawString("コントロールパネル", 270, 34);
    } else if (language == LANGUAGE_ZH) {
        exitbtn("主页");
        _canvas_title->drawString("控制面板", 270, 34);
    } else {
        exitbtn("Home");
        _canvas_title->drawString("Control Panel", 270, 34);
    }

    _key_exit->AddArgs(EPDGUI_Button::EVENT_RELEASED, 0, (void *)(&_is_run));
    _key_exit->Bind(EPDGUI_Button::EVENT_RELEASED, &Frame_Base::exit_cb);
}

Frame_Home::~Frame_Home(void) {    
    delete _btn_living_shutter_up;
    delete _btn_living_shutter_down;
    delete _btn_room_shutter_up;
    delete _btn_room_shutter_down;
}

int Frame_Home::init(epdgui_args_vector_t &args) {
    _is_run = 1;
    M5.EPD.Clear();
    _canvas_title->pushCanvas(0, 8, UPDATE_MODE_NONE);
    EPDGUI_AddObject(_key_exit);

    EPDGUI_AddObject(_btn_living_shutter_up);
    EPDGUI_AddObject(_btn_living_shutter_down);
    EPDGUI_AddObject(_btn_room_shutter_up);
    EPDGUI_AddObject(_btn_room_shutter_down);

    return 3;
}
~~~

~~~frame_home.h
#ifndef _FRAME_HOME_H_
#define _FRAME_HOME_H_

#include "frame_base.h"
#include "../epdgui/epdgui.h"
#include "control_switchbot.h"

class Frame_Home : public Frame_Base {
   public:
    Frame_Home();
    ~Frame_Home();
    int init(epdgui_args_vector_t &args);
    void InitSwitch(EPDGUI_Switch *sw, String title, String subtitle,
                    const uint8_t *img1, const uint8_t *img2);
    void InitButton(EPDGUI_Button *btn, String title, String subtitle, const uint8_t *img);

   private:
    EPDGUI_Button *_btn_living_shutter_up;
    EPDGUI_Button *_btn_living_shutter_down;
    EPDGUI_Button *_btn_room_shutter_up;
    EPDGUI_Button *_btn_room_shutter_down;
    Control_Switchbot *ctrlSwbot;
};

#endif  //_FRAME_HOME_H_
~~~

# 動作確認
動作確認の流れは下記の通りです。
- ビルド＆インストール
- **メイン画面**から**WLAN画面**へ遷移し、Wi-Fiを設定
- **Home画面**でボタン押下

_動かない場合は、”Upload and Monitor”でデバックしてください。_
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1402522/9facdfcf-91c5-e2d6-2716-887e1fd270bd.png)

# さいごに
M5Paper自体が白基調なので、公式からこのようなデバイスがでてもおかしくないと思いました。
Web APIさえ提供されていれば他のデバイスも操作できるので、次はルンバの清掃ボタンを追加したいです。


残作業：
・3Dプリンターで台を作って壁に設置
・初期状態では60秒でオートパワーセーブするので常時化＆省電力化
・Github登録