ECHONET Lite対応の部屋のエアコンをPython3から操作してみた

仕事の中でECHONET Lite(エコーネットライト)というプロトコルを知ったのですが、このプロトコルで部屋にあるSharp製のエアコン(型番 AY-H22S-W ) を操作できそうなので使ってみました。

ECHONET Liteとは

まず、ECHONET Liteとはからですが、ホームページが存在しています。とても情報量豊富、リンクも纏まっていて、かつ日本語なので、ここを読むのが一番早いです。

echonet.jp

そこから引用すると以下のように書かれています。家電の制御に使えるプロトコルのようです。

エコーネットとはスマートハウスを実現する通信プロトコルです。現在、家庭内でもWiFi規格などの無線ネットワークが簡単に活用できる中、スマートフォンやコントローラから、家にあるエアコン、照明などを制御したいとか、電力の無駄遣いを抑えるために家の電気代を把握したいとかいう要望が増えています。

このように省エネ、快適、安全・安心な生活を実現するためには、どのメーカーの機器でも共通に理解できる約束(通信プロトコル)が必要で、その役割を果たすのがエコーネットです。

引用元: エコーネットとは? | ECHONET

部屋のエアコンが対応しているか調べてみた

IoT系のお仕事で、繋ぎ先の機器がECHONET Liteを話せると聞いて調べ始めましたが、成り立ちをみる限り家電でも使えそうです。

もしかして、うちのエアコンも使えたりして・・ということで調べてみました。

対応機種はECHONETのページから探すことができます。

Sharp製品のAY-H22S-Wという型番ですが・・・ありました!!

Sharpのホームページにも「ECHONET Lite連携 動作確認済機種」としてしっかり記載されてました。

jp.sharp

このエアコン、WiFi繋ぐとスマホから操作できるというところに惹かれて購入したのですが、このECHONET Liteプロトコル使えばWiFi経由でPythonからも操作できそうです。

調べてみる & やってみる

エコーネット規格 (一般公開) | ECHONET

このリンクに資料が大量にあったのですが、ECHONET規格書から以下の2つのPDF資料を読むことで、ECHONET Liteで通信するところまでいけました。

ECHONET Lite規格書 Ver.1.13(日本語版)
  • 第2部 ECHONET Lite 通信ミドルウェア仕様
    • ファイル名: ECHONET-Lite_Ver.1.13_02.pdf
APPENDIX ECHONET機器オブジェクト詳細規定 Release M Revised (日本語)
  • APPENDIX ECHONET機器オブジェクト詳細規定Release M Revised
    • ファイル名: Appendix_Release_M_Revised.pdf

ECHONET Liteへの通信

ECHONET-Lite_Ver.1.13_02.pdf に記載があります。

ECHONET Lite ノードは、ポート 3610 にて、UDP ユニキャスト、および、マルチキャストのパケットを待ち受けるものとする。

3610 ポートで UDP でやりとりすれば良さそうです。

ECHONET Liteフレーム

送受信するフレームについても規定があります。このフレームでECHONET Lite対応の機器に命令を出すします。

フレームの構成は、 ECHONET-Lite_Ver.1.13_02.pdf3.2 電文構成 に記載がありますが、以下の図のように、バイト単位で意味を持たせながら以下のようなフレームを構成します。

引用元: ECHONET-Lite_Ver.1.13_02.pdf

PythonでEHD (ヘッダー部分)を作る

まずは、ヘッダー部分を作っていきます。内容はコメントに入れ込んでいます。「第2部 ECHONET Lite 通信ミドルウェア仕様」のドキュメントに沿って書いています。

値は16進数です。最後にByte変換しますが、一旦Stringで設定します。

# ---------------------------------------------------
# 3.2.1 ECHONET Lite ヘッダ(EHD)
# ---------------------------------------------------
# 3.2.1.1 ECHONET Lite ヘッダ 1(EHD1)
EHD1 = "10"  # ECHONET Lite規格

# 3.2.1.2 ECHONET Lite ヘッダ 2(EHD2)
EHD2 = "81"  # 形式1(規定電文形式)

# 3.2.2 Transaction ID(TID)
TID = "0001"  # IDなのでこの検証ではどの値でもOK

# フレームのヘッダ-を構成
EHD = EHD1 + EHD2 + TID

PythonでEDATA(データ部分)を作る

今度はデータ部分のEDATAです。

例として、電源のOn/Off情報と、温度設定を取得してみることにします。

# ---------------------------------------------------
# 3.2.1 ECHONET Lite データ(EDATA)
# ---------------------------------------------------
# 3.2.4 ECHONETオブジェクト
# EOJ = ECHONET Lite オブジェクト

# SEOJ = 送信元ECHONET Lite オブジェクト
SEOJ_CLS_GROUP = "05"     # 管理・操作関連クラスグループ
SEOJ_CLS_CODE = "ff"      # コントローラー
SEOJ_CLS_INSTANCE = "01"  # インスタンス番号
SEOJ = SEOJ_CLS_GROUP + SEOJ_CLS_CODE + SEOJ_CLS_INSTANCE

# DEOJ = 送信先ECHONET Lite オブジェクト
DEOJ_CLS_GROUP = "01"     # 空調関連機器クラスグループ
DEOJ_CLS_CODE = "30"      # 家庭用エアコンクラス
DEOJ_CLS_INSTANCE = "01"  # All Instanses
DEOJ = DEOJ_CLS_GROUP + DEOJ_CLS_CODE + DEOJ_CLS_INSTANCE


# 3.2.5 ECHONET Lite サービス(ESV)
ESV = "62"  # プロパティ値読み出し要求 (Get)

###############
# APPENDIX ECHONET機器オブジェクト詳細規定
# - 空調関連機器クラスグループ
#  - 家庭用エアコンクラス規定
# を確認

# 3.2.6 処理プロパティカウンタ
OPC = "02"  # 2件


# プロパティ 1件目
EPC1 = "80"    # 動作状態の取得(On or Off)
PDC1 = "00"    # Getの場合は0でOK
EDT1 = ""      # Getの場合はEDTは不要
PROP1 = EPC1 + PDC1 + EDT1

# プロパティ 2件目
EPC2 = "b3"    # 温度設定の取得
PDC2 = "00"    # Getの場合は0でOK
EDT2 = ""      # Getの場合はEDTは不要
PROP2 = EPC2 + PDC2 + EDT2


# フレームのデータ部分であるEDATAを構成
EDATA = SEOJ + DEOJ + ESV + OPC + PROP1 + PROP2

ECHONET Liteのコマンド完成

ヘッダとデータ部分を合わせるとECHONET Liteの要求コマンドができます。送信用と受信用のSocketを作って通信しているのがポイントです。

echonet_command = EHD + EDATA

今回の場合は中身はこんなデータになります。送る時には bytes.fromhex(command) とByte変換してあげます。

1081000105ff0101300162028000b300

エアコンにコマンド送信と結果取得

UDPでエアコンにECHONET Liteのコマンド投げてみます。

送信用とは別途、応答を受け取るための受信用のSocketを準備してあげるのがポイントです。

import socket

echonet_port = 3610
aircon_ip = "192.168.1.10"  # 224.0.23.0のアドレスを使うとマルチキャストもできます

# 応答待ち受け用ソケット
recv_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
recv_sock.bind(("0.0.0.0", echonet_port))

# 要求送信用ソケットでコマンド送信
send_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
send_sock.sendto(bytes.fromhex(echonet_command), (aircon_ip, echonet_port))

# 応答を受け取る
data, addr = recv_sock.recvfrom(1024)
send_sock.close()
recv_sock.close()

dataに応答が入っています。このままだと見づらいので、16進数の配列で出力して中身見てみます。

>>> data
b'\x10\x81\x00\x01\x010\x01\x05\xff\x01r\x02\x80\x010\xb3\x01\x1c'

>>> [hex(b) for b in data]
['0x10', '0x81', '0x0', '0x1', '0x1', '0x30', '0x1', '0x5', '0xff', '0x1', '0x72', '0x2', '0x80', '0x1', '0x30', '0xb3', '0x1', '0x1c']

まず、 data[10]0x72 となっていますが、0x7* が返ってきている場合は正常に応答していることを示しています。

次に、hex(data[12]) -> 0x80 で要求した、エアコンのOn/Off情報に関する応答が data[14] に入っています。0x30 はONを示します。

f:id:yomon8:20200916105318p:plain

>>> hex(data[14])
'0x30'

そして、data[15] -> 0xb3 の応答として data[17] に入っている 0x1c = 28 が温度ですね。

f:id:yomon8:20200916105341p:plain

>>> print(f"{data[17]} 度")
28

エアコンの電源をOffにする

上ではエアコンの電源のOn/Off情報を取得しましたが、ESVという値を書き換えることで、同じプロパティからエアコンの操作を行うこともできます。

f:id:yomon8:20200916105318p:plain

以下のように上記を修正して実行すれば、エアコンが停止されるはずです。

OPC = "01"  # 今回操作するプロパティは電源の1件だけなので1を設定
ESV = "60"  # プロパティ値書き込み要求(応答不要) 
EPC1 = "80"    # 動作状態の取得(On or Off)
PDC1 = "01"    # 1Byteを指定
EDT1 = "31"      # 0x31がOFFを表す
PROP1 = EPC1 + PDC1 + EDT1

EDATA = SEOJ + DEOJ + ESV + OPC + PROP1
echonet_command = EHD + EDATA
# echonet_command  -> '1081000105ff010130016101800131'

# 応答不要なので、送信Socketだけ準備してUDPで送ります。
send_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
send_sock.sendto(bytes.fromhex(echonet_command), (aircon_ip, echonet_port))
send_sock .close()

参考

自分の勉強のためにあえて愚直な実装してみましたが、その際に以下の2つのリポジトリを大いに参考にさせていただきました。

特にnodeのリポジトリはすぐに動かすことができるので、そもそも繋がっているか心配なタイミングでは大変助かりました。

github.com

github.com