M5Stackでスマートミラーを作ってみた

この記事ではM5Stackを使ったスマートミラーの作り方を紹介します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4019/04622d7b-2a46-8dfa-f411-14615214530f.png)


## スマートミラーとは

スマートミラーに明確な定義はありませんが、一般的に鏡にインターネットから取得した情報を表示したものをスマートミラーと呼んでるようです。

## スマートミラーの仕組み

「明るい方向からは光を反射して鏡に見える」「暗い側からは光を透過する」という特性を持つマジックミラーという特殊な鏡があります。

マジックミラーの後ろにディスプレイと Wi-Fi 機能があるマイコンやタブレットを貼り付けて、その特性を利用して一部の文字や絵だけを鏡を浮かび上がらせるというのが、一般的なスマートミラーの仕組みです。

## スマートミラーの仕様

この記事で作成するスマートミラーの仕様は以下のとおりです。

- 普段は鏡として利用できる。
- 人が近づいたのを感知して、現在の天気と気温情報を取得して鏡に表示する。
- 画面に表示された情報は一定時間経つと自動で消える。元の鏡に戻る。
- 一定時間人が近寄らないと、Deep Sleep モードになって省エネで動作する。

マジックミラーの後ろに M5Stack を固定してインターネットから取得した天気情報を表示すればスマートミラーの要件は満たせます。

ただそれだと画面が常に表示されてる状態になるので、PIR モーションセンサを利用して人がスマートミラーに近づいた時だけ情報を表示するようにしてみました。

## 用意するもの
- マジックミラーフィルム: Rabbitgoo マジックミラー フィルムを使用。¥1,099
- アクリルパネル（58cm x 58cm x 3mm）2 枚
- ビニールテープ（黒）
- M5Stack
- GROVE PIR モーションセンサ 
- GROVE 4 ピンケーブル
- プロトワイヤ: M5Stack に付属する物を使用
- ネジ（M3 x 5mm）4 個
- ナット（M3）4 個
- 六角スペーサー メスメス（M3 x 20mm）2 個
- プラスチック板（5cm x 2cm x 1mm）

安く済ませるためフィルムタイプのマジックミラーを透明のレーザーカットしたアクリルパネルに貼って使いました。

### マジックミラー フィルム
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4019/033fdc6e-0b5a-3e30-1876-17c78adb7904.png)

‪PIR センサ（Passive Infra-Red)は、周りにある物体からの赤外線放射の変化を感知する事によって動きを検出するセンサです。今回使用した GROVE システムの PIR モーションセンサだと

- ‪検出角度:120 度
- 検出距離:最大 6m

の範囲の人の動きを検出できます。

これを利用して、スマートミラーに近づいたら画面が表示される機能を実装します。

### GROVE PIR モーションセンサ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4019/09ce6444-2fbf-277f-a42c-bd24a5deb13d.png)


## 事前準備
### OpenWeatherMap API

OpenWeatherMap は Web やモバイルアプリケーションの開発者に気象データの API を無料提供するオンラインサービスです。スマートミラーに表示する天気と気温情報を取得するため、こちらのサービスを使います。下記のページからユーザ登録をしてください。

https://openweathermap.org/


### Open Weather Map

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4019/134515ae-cd96-6fbe-d55d-084add446abe.png)


アカウントの作成が完了すると、メールで `API key` が送られてきます。

OpenWeatherMap API で定められたエンドポイントに場所名と `Api key` を GET パラメータで渡してリクエストを投げると、JSON で天気情報が返ってきます。

```http://api.openweathermap.org/data/2.5/weather?q=tokyo,jp&APPID=(API key)```


### Arduino IDE

今回作成するファームウェアは Arduino IDE で M5Stack に書き込みます。[M5Stack入門01](https://booth.pm/ja/items/1316190)の**1 章の環境構築**を参考にして Arduino IDE をセットアップしてください。

### ArduinoJson

ArduinoJson は Arduino 用の JSON パーサライブラリです。OpenWeatherMap API から取得した天気情報をパースするのに使います。

Arduino IDE を起動して、**メニュ → ツール → ライブラリ管理…** を選択してください。

### Arduino IDE
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4019/35b305c5-e847-a64d-7305-2d6e878da224.png)


ライブラリマネージャが起動したら、ArduinoJson を検索してバージョン 3.1.0 をインストールしてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4019/ec2d4fc3-13f5-7e88-e46b-1d8b596285de.png)

## 配線

M5Stack と GROVE PIR モーションセンサはプロトワイヤと GROVE 4 ピンケーブルで下記のようにつなぎます。

| **M5Stack** | GROVE PIR モーションセンサ |
| ----------- | ----------------- |
| G2（P2）      | D1                |
| 3V          | VCC               |
| GND         | GND               |

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4019/bd414c1a-3c3f-a71c-5930-2c08dbb923d1.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4019/4a810978-a13f-5bf7-9629-b54189f98aa4.png)

## ファームウェア

M5Stack のコードを Arduino IDE で書き換えて、近づいたときに天気予報が表示されるようにします。`weather_api_key`には OpenWeatherMap Api の API key を、 `wifi_ssid` と `wifi_pass` の部分は自宅の Wi-Fi 設定に書き換えてください。

### ino ファイル

```c

#include <M5Stack.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
WiFiClient client;
const String endpoint = "http://api.openweathermap.org/data/2.5/weather?q=tokyo,jp&APPID=";
const String key = "weather_api_key";
// Wifi接続情報
char ssid[] = "wifi_ssid";
char password[] = "wifi_pass";
#define PIR_MOTION_SENSOR 2
unsigned long lastTime;
char *previous_weather;
double previous_temp;
// Wifiをセットアップする
void setupWifi()
{
    Serial.printf("WIFI: Connecting to %s ", ssid);
    // Wifi接続を開始する
    WiFi.begin(ssid, password);
    // Wifi接続まで待つ
    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());
}
void displayWeather()
{
    // 天気をLCDに表示
    M5.Lcd.setCursor(20, 40);
    M5.Lcd.setTextSize(6);
    M5.Lcd.print(previous_weather);
    // 気温をLCDに表示
    M5.Lcd.setCursor(20, 150);
    M5.Lcd.setTextSize(6);
    M5.Lcd.printf("%.1f", previous_temp);
}
bool requestWeather()
{
    bool result = false;
    if ((WiFi.status() == WL_CONNECTED))
    {
        HTTPClient http_client;
        // URLを指定
        http_client.begin(endpoint + key);
        // GETリクエストを送信
        int http_code = http_client.GET();
        if (http_code > 0)
        {
            // レスポンス（JSON）を取得
            String json = http_client.getString();
            StaticJsonDocument<2000> json_buffer;
            auto error = deserializeJson(json_buffer, json);
            if (error)
            {
                Serial.print("deserializeJson() failed.");
                Serial.println(error.c_str());
            }
            else
            {
                // JSONから天気と気温情報を取り出す
                JsonObject json_object = json_buffer.as<JsonObject>();
                previous_weather = (char *)json_object["weather"][0]["main"].as<char *>();
                previous_temp = json_object["main"]["temp"].as<double>() - 273.15;
                // 天気情報を取得した時間を保存
                lastTime = millis();
                result = true;
            }
        }
        else
        {
            // Wifi接続に失敗した場合
            Serial.println("Error on http request");
        }
        // リソースを解放
        http_client.end();
    }
    return result;
}
void setup()
{
    // M5Stackオブジェクトを初期化する
    M5.begin();
    M5.Lcd.print("Waiting...");
    // LCDの色設定
    M5.Lcd.fillScreen(BLACK);
    M5.Lcd.setTextColor(WHITE);
    // Wifiに接続する
    setupWifi();
    // デジタルピン 2を入力に設定
    pinMode(PIR_MOTION_SENSOR, INPUT);
    // Deep Sleepからの復帰条件を設定。GPIO PIR_MOTION_SENSORに入力があると起動
    M5.setWakeupButton(PIR_MOTION_SENSOR);
    // 初回起動時に天気を取得してディスプレイに情報を表示する
    bool result = requestWeather();
    if (result)
    {
        displayWeather();
        delay(5 * 1000);
        M5.Lcd.clear();
    }
}
void loop()
{
    // 人感センサーがHIGHのときは天気情報取得処理を動かす
    if (digitalRead(PIR_MOTION_SENSOR))
    {
        unsigned long currentTime = millis();
        unsigned long thirtyMinutes = 1000 * 60 * 30;
        // 前回のデータ取得から30分経過していた場合は、天気情報を再取得する
        if (thirtyMinutes < (currentTime - lastTime))
        {
            bool result = requestWeather();
            if (result)
            {
                // APIから受け取ったデータでLCDの情報を更新
                displayWeather();
            }
        }
        else
        {
            // 30経過していない場合は、キャッシュの情報をLCDに表示
            displayWeather();
        }
        // 10秒経過したら画面の表示情報をクリアする
        delay(10 * 1000);
        M5.Lcd.clear();
    }
    else
    {
        // 1時間
        unsigned long oneHour = 1000 * 60 * 60 * 1;
        unsigned long currentTime = millis();
        if (oneHour < (currentTime - lastTime))
        {
            // 人感センサーがLOWで、かつ1時間起動がなかったら、節電のためM5StackをDeep Sleepに切り替えます
            M5.powerOFF();
        }
    }
    delay(1 * 1000);
    M5.update();
}
```

## スマートミラーの外装

ここからはスマートミラーの外装の作り方を解説していきます。

まず最初に M5Stack を挟み込むアクリルパネルを 2 枚用意します。

下の写真のアクリルパネルは Autodesk の CAD ソフト Fusion 360 で 3D モデルを書き出して、そのデータから中国深圳にある Elecrow のアクリルレーザーカットサービスを利用して作りました。Elecrow はカットするアクリルのサイズにもよりますが、5 枚 $5.80 から利用可能です。

**Fusion 360**

https://www.autodesk.co.jp/products/fusion-360/

**Elecrow アクリルレーザーカットサービス**

https://www.elecrow.com/5pcs-acrylic-laser-cutting-service.html?___store=japanese

あとから 2 枚のアクリルパネルに六角スペーサを取り付けるので、パネルには M3 のネジが入る穴をあけました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4019/286f8655-8d08-cf88-98d8-d29536252139.png)



次にスマートミラーの正面になる 1 枚のアクリルパネルにマジックミラーフィルムを貼り付けます。
アクリルパネルより大きめにフィルムをカットして、水溶液をアクリルパネルに多めに吹きかけてフィルムを貼り付けて、そのあとでフィルムをパネルのサイズにカットするとうまく貼れます。
最初はフィルムとパネルの間に気泡が入ってますが、数日経つと気泡が抜けて綺麗な見た目になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4019/aad0a7d5-cb62-025c-e3ed-10b1b67e02f9.png)


マジックフィルムの貼り付けが終わったら、マジックミラーフィルムを貼ったアクリルパネルの裏側に黒のビニールテープを貼ります。2 枚のアクリルパネルで M5Stack を挟んだ時に、マジックミラーフィルムを貼った側から M5Stack のディスプレイの部分が見えるように貼ってください。

これは鏡を正面から見たときの透過防止用です。マジックミラーは後ろが真っ暗だと鏡のように見えます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4019/90c90b30-e034-ca1f-0cb8-eb949d80391c.png)


ビニールテープを貼ったパネルの下のネジ穴に 2 本の六角スペーサーをネジで取り付けます。固定が終わったら、2 本のスペーサーを跨ぐようにプラスチック板をのっけて、テープで固定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4019/e6719f19-d315-3cf5-140f-5e149c7b23dc.png)


もう 1 枚のアクリルパネルもねじで取り付けます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4019/239f7033-31aa-5c77-1f8c-a23ff0ac1f98.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4019/47f9f002-91b2-dd09-fd22-e1b3900cb4bb.png)


最後に 2 枚のアクリルパネルの間に M5Stack を差し込んでください。

マジックミラーを正面から見たときに、黒いビニールテープを貼った部分と M5Stack のディスプレイの黒背景の部分が鏡のように表示されて、白文字の部分だけ鏡に浮かび上がったように表示されてたら完成です。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4019/4e96d415-af8e-c8df-38cd-345c372b07be.png)


電源を入れてスマートミラーに近づくと、天気と気温が画面に表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4019/930a711d-b1ad-b43a-841f-7d10e01d2b5f.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4019/ec4eea9f-63e4-e0a6-eba3-b9957828663c.png)

## 最後に

最初から筐体が用意されてる M5Stack ですが、外装を自作して被せたらそのまま使った時より愛着が湧きました。

3D モデルの作成やレーザーカットなどの手間はかかりますが、その分喜びもひとしおなので、ぜひオリジナルの外装の作成にもチャレンジしてみてください。