M5 Atome Lite・Atom Matrixでおうちハック! ECHONET Lite によるスマートハウス操作 入門編
1. はじめに
マイコンでスマートハウスを操作してみたくありませんか?このページは、スマートハウスに対応した電化製品をうごかすアプリをつくってみたい人に向けたチュートリアル(入門編)です。マイコンには小型IoTデバイス「M5 Atom Lite」や「M5 Atom Matrix」を想定し、ECHONET Liteという通信プロトコルを用いたスマートハウスの操作の基本を体験します。
※実践編はこちら
2. スマートハウスでできること
https://gyazo.com/edabd134b54e86024d3a0c8c69588e85
スマートハウスとは、住宅内の機器(照明、エアコン、給湯器、センサーなど)をネットワークでつなぎ、制御・自動化する仕組みです。スマートハウスでは、家電の操作や自動スケジューリング、電力使用量の管理、センサー連動などの機能を組み合わせて、エネルギーの見える化・最適化、 快適性・利便性向上のための生活のサポート、 安全・安心の強化、家電・設備の統合管理と拡張などが期待できます。
スマートハウスの主な機能:
家電の遠隔操作
自動スケジューリング
電力使用量の管理
センサー連動
スマートハウスに期待できること:
エネルギーの見える化・最適化
快適性・利便性向上のための生活のサポート
安全・安心の強化
家電・設備の統合管理と拡張性
3. ECHONET Liteとは?
ECHONET Liteは、スマートハウスのための通信規格です。スマートハウス内の家電製品やセンサ、設備系機器などをIoT化し、様々なサービスを実現することができます。経済産業省の支援のもと、日本で国際標準化され、多くの家電メーカーが採用しています。
特徴:
軽量な通信プロトコル(UDPベース)
家電を「オブジェクト」として扱う
標準化されたプロパティによる操作
4. ECHONET Lite通信の基礎
https://gyazo.com/94396ec1fb475707926956688aa0b3d2
マイコンから家電までの通信制御の流れは下記のようになります。
マイコン・アプリ → UDP通信 → ECHONET Lite電文 → 家電制御
スマートハウス機器の制御にはネットワーク通信が使われます。ここでは、その基礎となる情報通信のしくみを説明します。OSI参照モデルでは、ネットワーク通信を7つの階層に分けて定義しています。物理層(Physical Layer)はデバイス同士を物理的に接続する仕組みを定めています。LANケーブルの規格などがここに該当します。データリンク層からアプリケーション層では情報を正しく相手に届けるための規則(プロトコル)を定めています。
そのうち、下記の層については図中の番号で示しています。
データリンク層(①Wi-Fi)
Wi-Fi(IEEE 802.11)を利用し、M5と家電機器を同一ネットワークに接続します。
ネットワーク層(②IP)
各デバイスにはローカルIPアドレス(例:192.168.1.xxx)が割り当てられ、UDP通信時の送信先・送信元の識別に使われます。マルチキャストアドレス(224.0.23.0)を使用して、ネットワーク上のすべてのECHONET Lite対応機器に一括送信することも可能です。
トランスポート層(③UDP)
UDP(User Datagram Protocol)は軽量で高速な通信方式です。信頼性(到達確認)は低いですが、スマートハウス用途では低遅延が重視されるため適しています。ポート番号3610がECHONET Liteで標準使用されています。
アプリケーション層(④UDP通信、ECHONET Liteの電文構築)
code:ECHONET Lite電文
EHD1,EHD2,TID:ヘッダー情報
SEOJ:送信元オブジェクト
DEOJ:宛先オブジェクト
ESV:サービスコード(例:0x60=SetC)
EPC:プロパティコード
EDT:実際のデータ
5. Atom LiteからECHONET Lite電文を送信してみよう
実際にArduinoコードで電文を構築し、UDPで送信してみましょう。
サンプル構成:
M5 Atom Lite→ Wi-Fi → UDP電文送信
家電(照明)受信 → 状態変更
code:ECHONET_Lite_sample.ino
const char* ssid = "YOURSSID"; //自分の環境に合わせて書き換える
const char* password = "YOURPASSWORD"; //自分の環境に合わせて書き換える
WiFiUDP udp;
IPAddress deviceIP(10, 106, 15, 186); // 宛先(照明エミュレータなど)
const int echonetPort = 3610;
bool lightOn = false;
bool lastButton = false;
void sendECHONET(bool powerOn) {
byte echonetFrame[] = {
0x10, 0x81, // EHD
0x00, 0x01, // TID
0x05, 0xFF, 0x01, // SEOJ
0x02, 0x90, 0x01, // DEOJ
0x60, // ESV (SetC)
0x01, // OPC
0x80, // EPC (動作状態)
0x01, // PDC
powerOn ? 0x30 : 0x31 // EDT (0x30: ON, 0x31: OFF)
};
udp.beginPacket(deviceIP, echonetPort);
udp.write(echonetFrame, sizeof(echonetFrame));
udp.endPacket();
Serial.printf("ECHONET Lite電文を送信しました(%s)\n", powerOn ? "ON" : "OFF");
}
void setup() {
M5.begin(true, false, true); // LED使用のためI2C無効化(M5 Atom用)
Serial.begin(115200);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting...");
}
Serial.println("Connected!");
// WiFi接続成功時にLEDを白色に点灯
M5.dis.drawpix(0, 0xFFFFFF);
}
void loop() {
M5.update();
bool button = M5.Btn.isPressed();
if (button && !lastButton) {
// ボタン押下時のみトグル処理(チャタリング防止)
lightOn = !lightOn;
sendECHONET(lightOn);
// LED表示切替
uint32_t color = lightOn ? 0x00FF00 : 0xFF0000; // 緑 or 赤
M5.dis.drawpix(0, color);
}
lastButton = button;
delay(20); // ポーリング間隔
}
解説
Wi-Fi接続したM5 Atomを使って、ECHONET Lite形式のON/OFF電文をUDPで送信します。
内蔵ボタンを押すたびに照明のON/OFFを切り替え、その状態をLEDの色(緑=ON、赤=OFF)で表示します。
table:動作結果
ボタンを押す ON/OFFの状態を切り替える
ONのとき ON電文送信 (EDT=0x30)LED緑 点灯
OFFのとき OFF電文送信(EDT=0x31)LED赤 点灯
ONとOFFを区別するために、ECHONET Lite電文の最後の EDT(データ本体)部分だけが変わります。
ON:0x30(=動作状態ON)
OFF:0x31(=動作状態OFF)
この切り替えは、 三項演算子(条件 ? 真の場合 : 偽の場合)を使って以下のように書いています。
code:powerOn
powerOn ? 0x30 : 0x31
//powerOn(sendECHONETの引数) が true(=ON状態)なら 0x30、そうでなければ 0x31(=OFF)
Pythonの三項演算子とはやや表記が異なるので補足で説明しました。
受信側について
照明を想定しています。ECHONET Lite対応の照明がない環境でも試せるように、照明家電のエミュレータのPythonコードも乗せておきます。必要に応じて使用してください。
code:EL_lighiting_emu.py
import socket
import threading
import tkinter as tk
UDP_PORT = 3610
class LightEmulator:
def __init__(self, master):
self.master = master
master.title("ECHONET Lite 照明エミュレータ")
self.status_label = tk.Label(master, text="状態: 未受信", font=("Helvetica", 16))
self.status_label.pack(pady=10)
self.canvas = tk.Canvas(master, width=100, height=100)
self.canvas.pack()
self.lamp = self.canvas.create_oval(10, 10, 90, 90, fill="gray")
# スレッドでUDP受信開始
self.thread = threading.Thread(target=self.udp_listener, daemon=True)
self.thread.start()
def udp_listener(self):
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(("0.0.0.0", UDP_PORT))
print(f"UDPポート{UDP_PORT}で待機中...")
while True:
data, addr = sock.recvfrom(1024)
print(f"受信: {addr}")
self.master.after(0, self.handle_packet, data)
def handle_packet(self, data):
if len(data) < 15:
return
if esv == 0x60 and epc == 0x80:
if edt == 0x30: # ON
self.canvas.itemconfig(self.lamp, fill="yellow")
self.status_label.config(text="状態: ON")
elif edt == 0x31: # OFF
self.canvas.itemconfig(self.lamp, fill="gray")
self.status_label.config(text="状態: OFF")
else:
self.status_label.config(text=f"状態: 不明(EDT={hex(edt)})")
# 起動
if __name__ == "__main__":
root = tk.Tk()
app = LightEmulator(root)
root.mainloop()
https://gyazo.com/71c7de59f100448c29cdb7f29fbd171b