【Flutter】FirebaseとM5Stackでエアコンを操作するシステムを作る

【Flutter】FirebaseとM5Stackでエアコンを操作するシステムを作る 技術メモ
本多
本多

外出先から自宅のエアコンを操作できたら素晴らしいと思いませんか?(すでにそんな商品ある?知らない。自分で作る!)

以下は(ほぼ)完成版ですが、本記事では、仕組みの解説と最低限の機能を作る過程を紹介します。

概要

システムの全体構成図

エアコンはリモコンから照射される赤外線によって、電源がONされたり温度が変わったりします。

そこで、今回は

  • 赤外線を照射できるATOM Matrix(M5Stack)を
  • Firebaseというサーバを経由して
  • スマホアプリから

操作することで、外出先からも自宅のエアコンを操作できるシステムを作りました。

準備(これらが準備できている前提で書いています)

ATOM Matrixからエアコンに適した赤外線を照射させる準備

手順
  • 必要ライブラリをインストール
  • リモコンの送信プロトコルを解析する
  • 送信プロトコルに応じたクラスを使用して、赤外線を照射する

必要ライブラリをインストール

以下のライブラリをPlatformIOでインストールします。

ライブラリ名用途
M5AtomATOMを制御する
FastLEDLEDを制御する
IRremoteESP8266赤外線を送受信する
Firebase ESP32 ClientFirebaseと通信する

リモコンの送信プロトコルを解析する

手順
  • 赤外線受信モジュールとAtom Matrixを接続する
  • 受信用のコードを書く
  • リモコンを赤外線受信モジュールに向けて、ボタンを押す

赤外線受信モジュールとAtom Matrixを接続する

33ピンに受信した信号が入ってくるように装着しました。

Atom Matrixに赤外線受信モジュールをつけた
赤外線受信モジュールとAtom Matrixを接続

受信用のコードを書く

必要ライブラリが入っている状態で、以下をコピペで動くと思います。

#include <Arduino.h>
#include "M5Atom.h"
#include <IRrecv.h>
#include <IRutils.h>

const auto recvPin = 33;             // 受信モジュールを接続するPIN
const auto captureBufferSize = 1024; // バッファサイズ
const auto timeoutMillSec = 50;      // タイムアウト時間
decode_results results;              // 受信結果を格納する変数

IRrecv irrecv(recvPin, captureBufferSize, timeoutMillSec, false); // 赤外線受信オブジェクト

void setup() {
  Serial.begin(115200);
  irrecv.enableIRIn();
}

void loop() {
  if (irrecv.decode(&results)) {
    Serial.print(resultToHumanReadableBasic(&results));
  }

  delay(500);
}

シリアルモニタで文字化けしないように、platformio.iniファイルの末尾に以下の一行を付け加えておきます。

monitor_speed = 115200

コンパイル・Atom Matrixへのアップロードを忘れずに行ってください!

また、シリアルモニタも立ち上げて起きます。

リモコンを赤外線受信モジュールに向けて、ボタンを押す

上手くいけば、シリアルモニタに以下のような表示がされると思います。

ここの「Protocol」をしっかりメモしておきましょう!

Protocol  : HITACHI_AC424
Code      : 0・・・0 (424 Bits)

送信プロトコルに応じたクラスを使用して、赤外線を照射する

IRremoteESP8266ライブラリにIRac.hという、いろんな種類のProtocolに対応した赤外線を照射するためのヘッダーファイルをインクルードします。

本記事の例では、ProtocolがHITACHI_AC424だったので、IRHitachiAc424クラスを使用して赤外線を照射します。

#include <Arduino.h>
#include "M5Atom.h"
#include <IRac.h>

/// 赤外線送信に関するもの
const auto IR_pin = 12;               // 赤外線LEDのピン
IRHitachiAc424 ac_controller(IR_pin); // 送信オブジェクト

void setup() {
  M5.begin(true, false, true);
  M5.dis.clear();
  ac_controller.begin(); // 赤外線照射できるようにする
}


/// <summary>
/// 12番ピンから赤外線を照射します。
/// </summary>
/// <param name="ac_controller">設定値が格納されたIRHitachiAc424クラス</param>
/// <param name="repeat_count">照射回数</param>
void send_IR(IRHitachiAc424 ac_controller, const uint16_t repeat_count = 1) {

  // 照射(照射中は別のLEDを光らせる)
  M5.dis.drawpix(12, 0x00f000);
  ac_controller.send(repeat_count); // (repeat_count + 1) 回照射する
  M5.dis.clear();
}


/// <summary>
/// エアコンをONします。
/// </summary>
void ac_on() {
  ac_controller.on();
  send_IR(ac_controller);
}


/// <summary>
/// エアコンをOFFします。
/// </summary>
void ac_off() {
  ac_controller.off();
  send_IR(ac_controller);
}


void loop() {

  // ボタンが押されたらエアコンONする
  if (M5.Btn.isPressed()) {
    ac_on();
  }

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

見づらくて申し訳ないですが、以下のように、ボタンを押したら赤外線が照射されていることが確認できました。(iPhone7の内カメラで撮影)

赤外線可視化
紫色に見えるのが赤外線
暖房・冷房を切り替えるには?

以下のようにsetModeで適切な値を渡せば、暖房・冷房を切り替える事ができます。

  // 暖房
  ac_controller.setMode(kHitachiAc424Heat);

  // 冷房
  ac_controller.setMode(kHitachiAc424Cool);

また、他にも以下のような関数があるので、チェックしてみてください。(Protocolによって異なります。)

  ac_controller.setMode(kHitachiAc424Heat); // 運転モード
  ac_controller.setFan(1);                  // 風速
  ac_controller.setTemp(24);                // 設定温度
  ac_controller.setSwingVToggle(1);         // 風向

Firebase(Realtime Database)をセットアップする

スマホアプリからAtom Matrixへ信号を伝えるために、Firebaseを経由させます。そのために、Firebase Realtime Databaseをセットアップしておきます。

手順
  • Firebaseに登録する(無料)
  • Realtime Databaseを作成する
  • ホスト名とデータベースシークレットをメモしておく

Firebaseに登録する(無料)

Firebase(外部リンク)にアクセスして、普通に登録します。多分1分以内に完了します。

Realtime Databaseを作成する

Firebaseトップページから、新しいプロジェクトを作成し、その中でRealtime Databaseを作成します。(本記事では、セキュリティルールはテストモードを想定しています。)

次に、『isPowerOn:false』『isSentIR:false』を追加しておきます。

Realtime Databaseを作成したあと
Realtime Databaseの中身

ホスト名とデータベースシークレットをメモしておく

Atom MatrixからFirebaseと通信できるように、ホスト名とデータベースシークレットを取得しておきます。

ホスト名は簡単で、以下の部分の『https://』を抜いた部分です。

Realtime Databaseのホスト名
Realtime Databaseのホスト名

データベースシークレットは「歯車マーク>サービスアカウント>Database secrets>表示する」で出てきます。

Realtime Databaseのデータベースシークレットの場所
Realtime Databaseのデータベースシークレット

Firebaseの値を見て、Atom Matrixから赤外線を照射する

前述のプログラムでは、Atom Matrixのボタンを押した時に赤外線を照射するようになっています。ここでは、Firebaseの値によって、赤外線を照射するように変更します。

とりあえず、ソースコードを貼り付けます。

#include <Arduino.h>
#include "M5Atom.h"
#include <IRac.h>
#include "WiFi.h"
#include <FirebaseESP32.h>

/// 赤外線送信に関するもの
const auto IR_pin = 12;               // 赤外線LEDのピン
IRHitachiAc424 ac_controller(IR_pin); // 送信オブジェクト

/// Firebaseに関するもの
FirebaseData firebaseData;
const char* const firebase_host = "Realtime Databaseのホスト名(https://は除く)";
const char* const firebase_auth = "データベースシークレット";

const char* const is_power_on_key = "isPowerOn";
const char* const is_sent_IR_key = "isSentIR";

/// お家のWi-Fiに関するもの
const char* const ssid = "Wi-FiのSSID";
const char* const password = "Wi-Fiのパスワード";


void setup() {
  M5.begin(true, false, true);
  M5.dis.clear();             // LEDを全消し
  WiFi.begin(ssid, password); // Wi-Fiに接続
  ac_controller.begin();      // 赤外線照射できるようにする

  // 接続試行中はLEDを点滅させる
  auto is_led_on = false;
  while (WiFi.status() != WL_CONNECTED) {
    is_led_on ? M5.dis.clear() : M5.dis.drawpix(0, 0x00f000); 
    is_led_on = !is_led_on;
    delay(200);
  }
  M5.dis.clear();

  delay(500); // なんとなく0.5秒くらい待ってみる。

  // Firebase接続
  Firebase.begin(firebase_host, firebase_auth);
  Firebase.reconnectWiFi(true);
}


/// <summary>
/// 12番ピンから赤外線を照射します。
/// </summary>
/// <param name="ac_controller">設定値が格納されたIRHitachiAc424クラス</param>
/// <param name="repeat_count">照射回数</param>
void send_IR(IRHitachiAc424 ac_controller, const uint16_t repeat_count = 1) {

  // 照射(照射中は別のLEDを光らせる)
  M5.dis.drawpix(12, 0x00f000);
  ac_controller.send(repeat_count); // (repeat_count + 1) 回照射する
  M5.dis.clear();

  // Firebaseの送信済みフラグ(isSentIR)を立てる
  Firebase.setBool(firebaseData, is_sent_IR_key, true);
}


/// <summary>
/// エアコンをONします。
/// </summary>
void ac_on() {
  ac_controller.on();
  send_IR(ac_controller);
}


/// <summary>
/// エアコンをOFFします。
/// </summary>
void ac_off() {
  ac_controller.off();
  send_IR(ac_controller);
}


void loop() {

  // isSentIRの値を取得する
  Firebase.getBool(firebaseData, is_sent_IR_key);
  auto is_sent_IR = firebaseData.boolData();

  // isSendIRがfalseだったら、isPowerOnの値によってエアコンON/OFFの赤外線を照射する
  if (!is_sent_IR) {
    Firebase.getBool(firebaseData, is_power_on_key);
    firebaseData.boolData() ? ac_on() : ac_off();
  }

  delay(500);
  M5.update();
}
挙動の解説(ざっくり)
  • 0.5秒に1回、送信済みフラグ(FirebaseのisSentIR)の値を取得
  • 送信フラグが下がっていたら、電源ON/OFF(FirebaseのisPowerOn)の値を取得して、赤外線を照射
  • 赤外線を照射したら、送信フラグを起こす
本多
本多

つまり、FIrebaseのisSentIRをfalseにすると、Atom Matrixから赤外線が照射されます。

スマホアプリからFirebaseの値を書き換える

最後に、スマホアプリからFirebaseの値を書き換えるアプリを作っていきます。

手順
  • Firebaseにアプリを追加する
  • Firebase関連のプラグインを導入する
  • 各ボタンを配置して、押下時にFirebaseの値を書き換える
  • おまけ(電源状態を表示する)

FIrebaseにアプリを追加する

以下の記事の『3.Firebaseにアプリを追加しておく【重要】』あたりを参考に追加します。

Firebase関連のプラグインを導入する

以下をpubspec.yamlファイルに記載してPub getする。

  firebase_core: ^0.5.3
  firebase_database: ^4.4.0

各ボタンを配置して、押下時にFirebaseの値を書き換える

以下をコピペして、main.dartから呼び出してあげます。

import 'package:flutter/material.dart';
import 'package:firebase_database/firebase_database.dart';

// Firebaseのノードの名前を表すキー
const isPowerOnKey = 'isPowerOn';
const isSentIRKey = 'isSentIR';

class RemoteController extends StatefulWidget {
  @override
  _RemoteControllerState createState() => _RemoteControllerState();
}

class _RemoteControllerState extends State<RemoteController> {

  /// DBへの参照インスタンス
  DatabaseReference _DBRef;

  @override
  void initState() {
    super.initState();
    _DBRef = FirebaseDatabase.instance.reference();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('リモコン'),
      ),
      body: Center(
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            RaisedButton(
              onPressed: () => {
                _DBRef.update({isPowerOnKey: true}),
                _DBRef.update({isSentIRKey: false}),
              },
              child: Text('電源ON'),
            ),
            RaisedButton(
              onPressed: () => {
                _DBRef.update({isPowerOnKey: false}),
                _DBRef.update({isSentIRKey: false}),
              },
              child: Text('電源OFF'),
            ),
          ],
        ),
      ),
    );
  }
}
本多
本多

あとはビルドすればOK。ボタン押下でFirebaseの値が書き換わっていることを確認してみましょう。

おまけ(電源状態を表示する)

エアコンのリモコンアプリ(簡易版)
アプリ画面

現在の電源設定をアプリ上に表示させるようにしたコードが以下です。(Firebaseの値を見て、ON/OFFを表示させるだけ。)

import 'package:flutter/material.dart';
import 'package:firebase_database/firebase_database.dart';

// Firebaseのノードの名前を表すキー
const isPowerOnKey = 'isPowerOn';
const isSentIRKey = 'isSentIR';

class RemoteController extends StatefulWidget {
  @override
  _RemoteControllerState createState() => _RemoteControllerState();
}

class _RemoteControllerState extends State<RemoteController> {

  /// DBへの参照インスタンス
  DatabaseReference _DBRef;

  /// 現在の電源状態(falseで初期化しておく)
  bool isPowerOn = false;

  @override
  void initState() {
    super.initState();
    _DBRef = FirebaseDatabase.instance.reference();
    _DBRef.onChildAdded.listen(_onChildChangedOrAdded);   // 参照するFirebaseにノードが追加されたら実行する関数を追加
    _DBRef.onChildChanged.listen(_onChildChangedOrAdded); // 参照するFirebaseの値が変更されたら実行する関数を追加
  }

  void _onChildChangedOrAdded(Event e) {
    if (e.snapshot.key == isPowerOnKey) {
      setState(() {
        isPowerOn = e.snapshot.value;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('リモコン'),
      ),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          Text(
            '現在の電源:' + (isPowerOn ? 'ON' : 'OFF'),
            style: TextStyle(fontSize: 24),
          ),
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: [
              RaisedButton(
                onPressed: () => {
                  _DBRef.update({isPowerOnKey: true}),
                  _DBRef.update({isSentIRKey: false}),
                },
                child: Text('電源ON'),
              ),
              RaisedButton(
                onPressed: () => {
                  _DBRef.update({isPowerOnKey: false}),
                  _DBRef.update({isSentIRKey: false}),
                },
                child: Text('電源OFF'),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

全ソースコード

本記事で紹介したソースコードの全貌は、以下に格納しています。(顔がいっぱいあってすみません。)

赤外線受信のサンプルコード(Atom Matrix)

赤外線送信のサンプルコード(Atom Matrix)

赤外線送信のサンプルコード(FIrebase連携)(Atom Matrix)

スマホアプリのサンプルコード

おわりに

おわり!

あとは、自分好みにアレンジしくことをおすすめします。例えば、エアコンのタイマーは「切」か「入」しか選べない気がしますが、どちらもできるようにするとか…

使用したマイコンなどはこちら
Atom Matrix(Amazonより引用)
赤外線受信モジュール(Amazonより引用)

コメント

タイトルとURLをコピーしました