
外出先から自宅のエアコンを操作できたら素晴らしいと思いませんか?(すでにそんな商品ある?知らない。自分で作る!)
以下は(ほぼ)完成版ですが、本記事では、仕組みの解説と最低限の機能を作る過程を紹介します。
概要

エアコンはリモコンから照射される赤外線によって、電源がONされたり温度が変わったりします。
そこで、今回は
- 赤外線を照射できるATOM Matrix(M5Stack)を
- Firebaseというサーバを経由して
- スマホアプリから
操作することで、外出先からも自宅のエアコンを操作できるシステムを作りました。
- Flutterの環境構築
- Atom Matrixと赤外線受信モジュール
- VSCodeにPlatformIOをインストール
ATOM Matrixからエアコンに適した赤外線を照射させる準備
- 必要ライブラリをインストール
- リモコンの送信プロトコルを解析する
- 送信プロトコルに応じたクラスを使用して、赤外線を照射する
必要ライブラリをインストール
以下のライブラリをPlatformIOでインストールします。
ライブラリ名 | 用途 |
---|---|
M5Atom | ATOMを制御する |
FastLED | LEDを制御する |
IRremoteESP8266 | 赤外線を送受信する |
Firebase ESP32 Client | Firebaseと通信する |
リモコンの送信プロトコルを解析する
- 赤外線受信モジュールとAtom Matrixを接続する
- 受信用のコードを書く
- リモコンを赤外線受信モジュールに向けて、ボタンを押す
赤外線受信モジュールとAtom Matrixを接続する
33ピンに受信した信号が入ってくるように装着しました。

受信用のコードを書く
必要ライブラリが入っている状態で、以下をコピペで動くと思います。
#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』を追加しておきます。

ホスト名とデータベースシークレットをメモしておく
Atom MatrixからFirebaseと通信できるように、ホスト名とデータベースシークレットを取得しておきます。
ホスト名は簡単で、以下の部分の『https://』を抜いた部分です。

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

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)
スマホアプリのサンプルコード
おわりに
おわり!
あとは、自分好みにアレンジしくことをおすすめします。例えば、エアコンのタイマーは「切」か「入」しか選べない気がしますが、どちらもできるようにするとか…
コメント