HomeKitプログラミングしてSiriにLEDを点けてもらう

ESP32は技適マーク付き無線モジュール搭載で数百円で買えるマイコン開発ボードです。これに、Apple社のHomeKit対応プログラムを書き込み、iPhone, Mac, Apple Watchのホームアプリ

の画面からLEDを点灯できるようにします。HomePod, iPhone, Apple Watch, Macに向かって「Hey Siri, LEDを点けて」と言えばSiriがLEDを点けてくれます。

ESP32プログラミングにはArduino IDEを使用します。こちらの記事で、LED点滅(Lチカ)プログラミングができる準備をしましたのでご覧ください。

diysmarthome.hatenablog.com

HomeKitとHAP

巨大IT企業のGoogle, Apple, Amazonは、それぞれスマートホームのためのフレームワークを大々的に売り出してます。どれを選ぶか難しいところですが、Appleのエコシステムに深く取り込まれている人なら、Apple HomeKitが便利です。Google, Amazonに比べて対応製品が少なくて割高ですが、OS標準装備なので使える機能が豊富です。対応製品が少ない問題は、DIYして補っていきましょう。

HomeKitはApple社のスマートホーム向けのフレームワークで、iOS, macOS, tvOS, watchOSなどと連携します。音声エージェントSiriとも連携します。IoT機器・製品(HomeKitではアクセサリと呼びます)がHomeKitに接続するためのプロトコルが、HomeKit Accessory Protocol (HAP) です。

HomeKitに直接接続するアクセサリを自作する際には、このHAPに対応したプログラムを書かなければなりません。ソケット通信のプログラムとか、httpとかMQTTに対応したプログラムとか比べて、ハードルが格段に高そうな感じです。なので、ずっと諦めていたのですが、お手軽に対応できるライブラリがありそうなことに最近気づきました。ESP32などを対象に、Arduino IDE用のライブラリとして提供されています。これを使ってみることにしました。

Arduino IDEにHAPを導入する

Arduino IDEの「ツール」「ライブラリを管理...」メニューを開くと、追加可能なライブラリの一覧が表示されます。

この検索窓にhomekitと打ち込んでみたところ、4件がヒットしました。More Infoを見て、ESP32がサポートされていて、プログラミングが簡単そうなことから、HomeSpanというライブラリを選びました。これのインストールボタンを押します。

一番簡単そうなサンプルプログラム

HomeSpanのgithubにあるサンプルから、一番簡単そうなプログラムを探しました。簡単な電球プログラムのようです。

github.com

コメントがたくさん書いてあって、親切なソースコードです。実質は10行くらいの短いプログラムでした。このプログラムは、HomeKitアクセサリとしてちゃんと動いて、アクセサリがiPhoneMacのホームアプリ上に現れて、on/offの操作が可能です。でもLEDを点けたりなどの機能は全く無い、ダミーのプログラムです。

#include "HomeSpan.h" 

void setup() { 
  Serial.begin(115200);
  homeSpan.begin(Category::Lighting,"HomeSpan LightBulb");
  new SpanAccessory();
    new Service::AccessoryInformation();
  new Characteristic::Identify();
new Service::LightBulb(); new Characteristic::On(); } void loop(){ homeSpan.poll(); }

説明を付けると、以下のようになります。

#include "HomeSpan.h" //ライブラリの読み込み

void setup() { 
  Serial.begin(115200);//動作確認と設定のためのシリアルを用意
//HomeSpanライブラリはhomeSpanという名前のグローバルインスタンス
//用意するので、それを始動します。引数はカテゴリと初期名前です。 homeSpan.begin(Category::Lighting,"HomeSpan LightBulb");
//次にHAPアクセサリを作ります。 new SpanAccessory();
//AccessoryInformationは必須のサービスです。 new Service::AccessoryInformation();
//AccessoryInformationサービスにはIndentifyが必須
  new Characteristic::Identify();
//Identifyの他に、メーカ名、シリアル番号なども設定可能

//次に電球のサービスを定義します。 new Service::LightBulb();
//このサービスはOnキャラクタリスティックが必要です。 new Characteristic::On(); } void loop(){ homeSpan.poll();//homeSpanを機能し続けます。 }

コンパイルもできて、ダウンロードできました。でも、何かが動いている様子はありません。この段階ではiPhoneのホームアプリにも応答がありません。

シリアルモニターで設定する

プログラムの冒頭でシリアルポートの設定をしていることから推測できるように、シリアル経由で動作確認や設定ができます。Arduino IDEからシリアルモニターを開けると、色々とメッセージが出ていました。

************************************************************
Welcome to HomeSpan!
Apple HomeKit for the Espressif ESP-32 WROOM and Arduino IDE
************************************************************

** Please ensure serial monitor is set to transmit 

Message Logs:     Level 0
Status LED:       Pin -  *** WARNING: Status LED Pin is UNDEFINED
Device Control:   Pin -  *** WARNING: Device Control Pin is UNDEFINED
Sketch Version:   n/a
HomeSpan Version: 1.5.1
Arduino-ESP Ver.: 2.0.3
ESP-IDF Version:  4.4.1
ESP32 Chip:       ESP32-D0WDQ6 Rev 1 dual-core 4MB Flash
ESP32 Board:      esp32
PWM Resources:    16 channels, 8 timers, max 20-bit duty resolution
Sodium Version:   1.0.12-idf  Lib 9.4
MbedTLS Version:  mbed TLS 2.28.0
Sketch Compiled:  Jun 27 2022 12:46:53
Partition:        app0

Device Name:      HomeSpan LightBulb

(略) 
     
*** WIFI CREDENTIALS DATA NOT FOUND. 
YOU MAY CONFIGURE BY TYPING 'W (return)'. 

HomeSpan LightBulb is READY!

最後に、Wを押してWiFi設定せよと出ているので、設定しました。2.4GHzのWiFi一覧が表示されて、該当を選んでパスワードを送ると、接続できたようです。また、?やもしくは単に改行だけを送ると、ヘルプが出てきました。

*** HomeSpan Commands ***

  s - print connection status
  i - print summary information about the HAP Database
  d - print the full HAP Accessory Attributes Database in JSON format

  W - configure WiFi Credentials and restart
  X - delete WiFi Credentials and restart
  S  - change the HomeKit Pairing Setup Code to 
  Q  - change the HomeKit Setup ID for QR Codes to 
  O - change the OTA password
  A - start the HomeSpan Setup Access Point

  V - delete value settings for all saved Characteristics
  U - unpair device by deleting all Controller data
  H - delete HomeKit Device ID as well as all Controller data and restart

  R - restart device
  F - factory reset and restart
  E - erase ALL stored data and restart

  L  - change the Log Level setting to 

  ? - print this list of commands

*** End Commands ***

これによるとSコマンドでペアリングのための8桁の数字を設定できるようです。12345678などの簡単な組み合わせは拒否されてしまうので、31415926のような数字にしました。S31415926とタイプします。

WiFiとペアリングコードを設定し終われば、ホームアプリケーションに現れるようです。最初のうちは応答が悪かったのですが(HomeKit製品のペアリングではよくあることです)、シリアルモニタでVとかUとかのコマンドを打っているうちに、ホームアプリに現れるようになりました。

該当するアクセサリをタップすると、設定コードを質問されます。Sコマンドで設定した8桁の数字を入力します。これでESP32がホームアプリの上にタイルとして現れました。これをタップすると画面上の状態がon/offします。ただし、それに対応するプログラミングをしていないので、何も起こりません。

LEDを点ける

LEDを点灯するには、setup()の次の2行の部分を書き換えます。ここでは、電球のサービスを定義しています。

  //次に電球のサービスを定義します。
  new Service::LightBulb();
  //このサービスはOnキャラクタリスティックが必要です。
  new Characteristic::On();

HomeSpanのサイトには、マニュアルやチュートリアルがたくさん用意されています。そのサンプルプログラムなどを読むと、Service::LightBulbクラスのサブクラスを作って、そこにLED点滅機能を作り込むと良いようです。また、そのクラスのコンストラクタで、Characteristic::Onクラスもインスタンス化しておきます。その際にインスタンスへの参照を残しておくと、現在の電球の状態を知ることができるようです。

全体のプログラムは以下になります。

#include "HomeSpan.h"

class LEDtester : Service::LightBulb {//定義したクラス
private:
  int LEDpin;//LEDピン番号
  SpanCharacteristic *LEDstatus;
public:
  LEDtester(int thePin) : Service::LightBulb() {
    LEDstatus = new Characteristic::On();
    LEDpin=thePin;//引数をLEDのピン番号にする
    pinMode(LEDpin,OUTPUT);
  } // end constructor()

  boolean update(){
    digitalWrite(LEDpin, LEDstatus->getNewVal());
    return(true);
  } // end update()  
};//クラス定義はここまで

void setup() { 
  Serial.begin(115200);
  homeSpan.begin(Category::Lighting,"HomeSpan LightBulb");
  new SpanAccessory();
  new Service::AccessoryInformation();
  new Characteristic::Identify();
  new LEDtester(13);//電球機能を別クラスにした
} 

void loop(){
  homeSpan.poll();
}

前のバージョンのsetup()にあった電球サービス関係のインスタンス化の部分

  new Service::LightBulb();
  new Characteristic::On();

が、LEDtesterというクラスのインスタンス

  new LEDtester(13);

に変わってます。LEDtesterは、Service::LightBulbのサブクラスとして定義しました。このクラスのコンストラクタの中で、Characteristic::Onのインスタンス化も行ってます。Characteristic::Onのインスタンスメソッドには、getNewValというメソッドがあって、新たに設定された値を返してくれるようです。1か0の値を持つon/offタイプの電球なので、次の値である1か0を返してくれるもののようです。

これでホームアプリからLEDを点灯・消灯できるようになりました。(写真はLチカのと同じです)「へいSiri、Home Span Light Bulbを点けて」と言えば、LEDを点けてくれます。消してといえば消してくれます。デフォルトの名前は長いですが、「LED」でも「あかり」でも好きな名前に、ホームアプリからいつでも書き換えられます。

まとめ

ESP32にHomeKitアクセサリのプログラムを組み込んで、ホームアプリやSiriからLEDを点灯できるようにしました。思ってたより簡単で、DIYも色々できそうです。