M5Stackで始めるIoT開発入門〜学習リモコン〜 - Tue, Dec 20, 2022
Writer: 平山快
M5Stack + MQTT + IRセンサーで学習リモコンの作成
1.はじめに
本記事は、IoT開発初心者である私がM5Stackで学習リモコンを作成し、ネットワーク経由で家電を操作してみようという内容です。
M5StackはWi-FiとBluetoothによる無線通信機能を備えたESP32や液晶ディスプレイ、microSDカードスロットなどの周辺機器がまとまった小型のマイコンモジュールです。ユニット(センサーなど)を接続することで簡単にセンサーから値を取得できます。この記事を通して、M5Stackでの開発やMQTTなど基本的なIoT開発の仕方を勉強するきっかけや参考になればいいと思います。
2.開発環境と構成図
今回作成するシステムの構成図はこんな感じです。ただ、学習リモコンを作るだけではIoTにはならないので、ネットワーク経由でも操作できるようにしました。IoTでの通信といえばMQTTと聞いたので勉強も兼ねて実装していきたいと思います。
開発環境は以下の通りです。M5GO IoTスターターキットを購入すれば、IRセンサーの他にENVⅢ(温湿度・気圧)センサーなども付いてくるので初めてIoT開発する方にはおすすめです。前提として、Arduino IDEでのM5Stackの環境は整っていることとします。
- 端末:M5GO (M5STACK BASICでもOK)
- センサー:IRセンサー(赤外線センサー)
- サーバー:Raspberry Pi 4 (Linuxの入ったPCなら代替可)
3.学習リモコン作成
3.1.準備
最初に学習リモコンから作成していきたいと思います。まずは、IRremoteESP8266
のライブラリをインストールします。
3.2.受信編
次に既存のリモコンから赤外線データを取得します。今回私は、電気をON・OFFするこちらのアイリスオオヤマ製のリモコンを使用します。
まずは、IRremoteESP8266
のサンプルコードを取得します。
赤外線を受信するサンプルコードは、「ファイル」→「スケッチ例」→「IRremoteESP8266」の「IRrecvDumpV2」から取得できます。
M5GOでは、受信するPIN番号が36、送信するPIN番号が26番です。なので、受信するPIN番号を36に変更する必要があります。
const uint16_t kRecvPin = 36;
変更したら、コンパイル+書き込みを行い、シリアルモニターを開いてIRセンサーに向けてリモコンのボタンを押しデータ受信させます。今回は「ON・OFF」、「明るく」、「暗い」のデータを受信します。赤外線データを受信すると以下のようなにシリアルモニタに表示されるのでuint16_t raw_data[85]
のメモを取ります。
3.3.送信編
赤外線データを受信したら、次に学習リモコンを作成します。現時点では、Web UIやMQTTの実装はしていないので、ボタンが押された場合データを送信するように実装します。 Aボタンが「切/入」、Bボタンが「明るく」、Cボタンが「暗く」のデータを送信するように実装していきます。 コードについては、コード内にコメントを書いたので説明は省略します。コンパイル+書き込みを行い、実際に家電(今回は電気)を操作できたら学習リモコンは完成です!
#include <M5Stack.h>
#include <IRremoteESP8266.h>
#include <IRsend.h>
#define DATA_SIZE 85 // 送信する赤外線データのサイズ
#define TRANSMIT_CAPTURE_SIZE 38 // 周波数. 赤外線リモコンの仕様が38khzなので、38で固定
int ir_send_pin = 26; // 送信するPIN番号
IRsend irsend(ir_send_pin);
// 送信する赤外線データ
uint16_t on_off[85] = {2022, 1004, 5584,... , 1494, 532, 512}; // 切/入
uint16_t brightly[85] = {2050, 978, 5586,..., 1550, 480, 508}; // 明るく
uint16_t dark[85] = {2020, 1006, 5578,..., 488, 516, 536}; // 暗く
void setup() {
// M5Stackの初期化
M5.begin();
M5.Power.begin();
// 26番ピンの設定
pinMode(ir_send_pin, OUTPUT);
digitalWrite(ir_send_pin, 1);
}
void loop() {
M5.update();
// 各ボタンが押された際に、それぞれの赤外線データを送信する
if (M5.BtnA.wasPressed()) {
irsend.sendRaw(on_off, DATA_SIZE, TRANSMIT_CAPTURE_SIZE);
} else if (M5.BtnB.wasPressed()) {
irsend.sendRaw(brightly, DATA_SIZE, TRANSMIT_CAPTURE_SIZE);
} else if (M5.BtnC.wasPressed()) {
irsend.sendRaw(dark, DATA_SIZE, TRANSMIT_CAPTURE_SIZE);
}
}
4.MQTT
MQTT(Message Queue Telemetry Transport)とは、IoT向けのメッセージングプロトコルです。publish/subscribeモデルという仕組みに基づいており、双方向通信可能、非常に軽量で効率的なためIoTでの通信で用いられます。
publish/subscribeモデルでは、メッセージの送信側(Publisher)と受信側(Subscriber)の間にMQTTサーバー(Broker)が入ってメッセージを扱います。
今回はMQTT Brokerとして、OSSであるMosquittoをRaspBerry Piにインストールしていきます。また、動作確認もしたいのでクライアントツールもインストールします。
$ sudo apt-get update
$ sudo apt-get install -y mosquitto
$ sudo apt-get install mosquitto-clients
それでは、Mosquittoを起動させたいと思います。以下のコマンドでMosquiitoを起動させてください。
sudo systemctl start mosquitto
ちなみにサービスが起動しているかを知りたい場合は、sudo systemctl status mosquitto
で確認できます。その他systemctl
について知りたい場合は、systemctl コマンドに分かりやすくまとまっていたのでそちらを参考にしてください。
最後に動作確認をするためにクライアントツールを使用します。まずはmosquitto_sub -h localhost -t test/1
を入力します。-h
はホスト名、-t
はトピック名です。
そして別のターミナルからmosquitto_pub -h localhost -t test/1 -m "hello world!"
を入力すると、mosquitto_sub
にhello world!と表示されると思います。送信側はトピック名を指定し送信し、受信側は受信したいトピック名を指定することで必要な情報のみを取得できます。なので、mosquitto_pub -h localhost -t test/2 -m "hello world!"
としてもmosquitto_sub
にhello world!は表示されません。
5.Web UIの作成
WebSocketとは
WebSocketとは、ブラウザとWebサーバーとの間で双方向通信を行うための通信規格のことです。ブラウザ(クライアント)とWebサーバー間でWebSocketでコネクションを確立した後に、MQTTで通信を行うのをMQTT over WebSocketといいます。MQTT over WebSocketはMQTTで端末と通信を行い、端末の制御を行うWeb UIを提供するアプリの場合によく使われると思います。
MQTTサーバーの設定変更
早速、MQTTの設定を変更したいと思います。MosquittoをWebSocketに対応させるには、/etc/mosquitto/mosquitto.conf
に以下を追記します。
# Place your local configuration in /etc/mosquitto/conf.d/
#
# A full description of the configuration file is at
# /usr/share/doc/mosquitto/examples/mosquitto.conf.example
# ==追記する箇所==
listener 1883
allow_anonymous true
listener 8080
protocol websockets
# ==============
pid_file /run/mosquitto/mosquitto.pid
persistence true
persistence_location /var/lib/mosquitto/
log_dest file /var/log/mosquitto/mosquitto.log
include_dir /etc/mosquitto/conf.d
その後、mosquittoを再起動して完了です。
$ sudo systemctl restart mosquitto
Web UIと簡易Webサーバーの実装
ブラウザから家電を操作するためのUIを実装していきます。MQTT over WebSocketについては、Paho MQTTのJSクライアントを使用します。 MQTTで通信する実装は以下の通りです。
// MQTT over WebSocketの初期化
var ip_addr = location.hostname; // 自分自身のipアドレス
var port = 8080; // WebSocketのポート
var client = new Paho.MQTT.Client(ip_addr, port, "clientId_" + new Date().getTime());
function OnButtonClick(cmd) {
var data = cmd; // on_off,brightly,dark
message = new Paho.MQTT.Message(data); // MQTTのメッセージパケットを作る
message.destinationName = "M5Stack"; // トピック名を設定
client.send(message); // MQTTで送信(Pub)する
}
var options = {
onSuccess: function () {
console.log("success");
},
onFailure: function (message) {
console.log("err",message);
}
};
client.connect(options);
Web UIはシンプルにボタンを3つ配置するだけにします。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>電灯リモートスイッチ</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.js" type="text/javascript"></script>
<script type="text/javascript" src="client.js"></script>
</head>
<body lang="en">
<h1>電灯リモートスイッチ</h1>
<div>
<div>
<form>
<input type="button" value="切/入" onclick="OnButtonClick('0');">
<input type="button" value="明るく" onclick="OnButtonClick('1');">
<input type="button" value="暗く" onclick="OnButtonClick('2');">
</form>
</div>
</div>
</body>
</html>
簡易Webサーバーは、PythonでもNode.jsでもOKです。
import http.server
import socketserver
Handler = http.server.SimpleHTTPRequestHandler
with socketserver.TCPServer(('ip_addr',8000),Handler) as httpd:
print("Serving on port 8000...")
httpd.serve_forever()
6.リモートで操作を行う
最後に、Web UIからリモートで操作を行う実装をしていきたいと思います。3.3.送信編で作成した学習リモコンのコードを改変してMQTT通信で受信したデータに応じて赤外線データを送信するようにしていきます。
最終的には、以下のような実装になります。
そしてWebサーバーの起動、コンパイル+書き込みを行い、Web UIから操作できればOKです。
#include <PubSubClient.h>
#include <WiFi.h>
#include <M5Stack.h>
#include <IRremoteESP8266.h>
#include <IRsend.h>
// ================================赤外線センサー関連================================
#define DATA_SIZE 85 // 送信する赤外線データのサイズ
#define TRANSMIT_CAPTURE_SIZE 38 // 周波数. 赤外線リモコンの仕様が38khzなので、38で固定
int ir_send_pin = 26; // 送信するPIN番号
IRsend irsend(ir_send_pin);
// 送信する赤外線データ
uint16_t on_off[85] = {2022, 1004, 5584,... , 1494, 532, 512}; // 切/入
uint16_t brightly[85] = {2050, 978, 5586,..., 1550, 480, 508}; // 明るく
uint16_t dark[85] = {2020, 1006, 5578,..., 488, 516, 536}; // 暗く
// ================================================================
// ================================MQTT関連================================
WiFiClient wifiClient;
PubSubClient mqttClient(wifiClient);
#define TOPIC "M5Stack" // MQTTのトピック名
#define QOS 0
#define URL "x.x.x.x" // ==BrokerのIPアドレス(RaspBerry PiのIPアドレス)==
#define PORT 1883 // Brokerのポート番号
#define ON_OFF 0
#define BRIGHTLY 1
#define DARK 2
const char* ssid = "SSID"; // ==WiFiのSSID==
const char* password = "PASSWORD"; // ==WiFiのパスワード==
// ================================================================
void setup() {
// M5Stackの初期化
M5.begin();
M5.Power.begin();
// 26番ピンの設定
pinMode(ir_send_pin, OUTPUT);
digitalWrite(ir_send_pin, 1);
delay(100);
Serial.begin(115200);
// WiFi接続
connectWiFi();
// mqttの設定
mqttClient.setServer(URL, PORT);
mqttClient.setCallback(callback);
}
void loop() {
if (!mqttClient.connected()) {
if (mqttClient.connect(TOPIC)) {
Serial.println("Connected.");
mqttClient.subscribe(TOPIC, QOS);
Serial.println("Subscribed.");
}
else {
// 接続失敗
Serial.println("Failed.");
delay(5000);
}
}
mqttClient.loop();
}
void callback(char* topic, byte* payload, unsigned int length) {
Serial.print("Message arrived [");
Serial.print(topic);
Serial.print("] ");
int code = 0;
if (length != 1) {
for (int i = 0; i < length; i++) {
Serial.print((char)payload[i]);
}
} else {
code = (char)payload[0] - '0';
Serial.print(code);
if (code == ON_OFF) {
irsend.sendRaw(on_off, 85, TRANSMIT_CAPTURE_SIZE);
} else if (code == BRIGHTLY) {
irsend.sendRaw(brightly, 85, TRANSMIT_CAPTURE_SIZE);
} else if (code == DARK) {
irsend.sendRaw(dark, 85, TRANSMIT_CAPTURE_SIZE);
}
}
Serial.println();
}
void connectWiFi()
{
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
M5.Lcd.print(".");
}
M5.Lcd.println("Successed");
M5.Lcd.print("IP: ");
M5.Lcd.println(WiFi.localIP());
}