はじめに
M5PaperはM5Stackシリーズの中で電子ペーパーを搭載したバージョンです。
ESP32が内蔵されているので、Wi-FiやBluetooth等が使えるおもしろいデバイスになっています。
これを使用してSwitchBotの操作端末を作ってみます。
SwitchBotの操作には、SwitchBot API v1.1を使います。
開発言語はc++、環境はVS Codeです。
開発環境
開発環境の構築はほとんどこちらの記事を参考にさせていただきました。
追加で下記のライブラリをインポートしています。
protohaus/ESPRandom@^1.4.1
adafruit/RTClib@^2.1.1
プロジェクトの作成
上の記事で使われていたFactoryTestをベースにします。
ビルド&インストールすると、下記のような画面が出力されるはずです。
今回はHome画面を書き換えて実装したいと思います。
実装の流れ
- SwitchbotのトークンやdeviceID等の情報を取得する(取得方法やリクエストの方法は下記の記事を参考にしてください)
- Home画面のUIを変更する
- コールバック関数を登録してSwitchBot APIをリクエストする
Home画面のUI変更
完成はこれです。
今回は、寝室とリビングのシャッターのスイッチを押すボタンを4つ作りました。
Home画面はFrame_Homeというクラスで制御されているため、下記のように改修しました。
- UI配置
M5Paper_FactoryTestプロジェクトには、はじめからスイッチ(ON-OFF)やボタン、テキストボックスといったUIが用意されています。
今回は、ボタンクラスのEPDGUI_Buttonを使いました。
引数で位置とサイズを指定しています。
_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を設定する関数が用意されていたので、コピペしてボタン用に改修しました。
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);
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(自分)を登録
使い道としては、ボタンが押されたときに他のボタンを無効にするなどが思い浮かびます。
_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関数で登録しています。
_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クラスは前の記事を参照ください。
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クラス改修結果
#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;
}
#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”でデバックしてください。
さいごに
M5Paper自体が白基調なので、公式からこのようなデバイスがでてもおかしくないと思いました。
Web APIさえ提供されていれば他のデバイスも操作できるので、次はルンバの清掃ボタンを追加したいです。
残作業:
・3Dプリンターで台を作って壁に設置
・初期状態では60秒でオートパワーセーブするので常時化&省電力化
・Github登録