電気錠のJEM-A端子にESP32を接続してHomebridge / HomeKitで使う

DIYする

玄関などの電気錠コントローラには、JEM-A端子がついていることがあります。JEM-A端子は2値の入力と出力の端子を持つ日本電気工業会の標準規格です。国内製品には搭載率が高いです。この端子にESP32を接続して、MQTTブローカー経由でHomebridgeとインタフェースして、Apple HomeKitのLock Mechanismアクセサリとして動作させました。インタフェースさえ確立すれば、あとはAppleのアプリが施錠機構として適切に制御してくれます。

JEMA端子

エアコンについていたJEM-A端子を使う話をこちらに書きましので参考にしてください。

家庭用エアコンに付いてたJEM-A HA端子をHomeKitから使う
廉価版の家庭用エアコンに、JEM-A HA端子が付いているのを発見しました。仕様を調べて、ESP32に接続し、MQTTとHomebridge経由でHomeKitから使えるようにしました。 参考記事のご紹介 Homebridgeについては以下...

以下おさらいです。JEM-A端子は、日本電機工業会規格(JEM)に従い、機器のオンオフの制御とその状態モニタを行う端子です。電気錠のほかに、エアコン、電動シャッター、床暖房、照明器具などに付いてます。4ピンの端子で、2本が制御信号、2本がモニタ信号です。おそらくは制御信号端子のところに押しボタンスイッチをつけて、モニター信号端子のところにLEDをつけて、人がスイッチを押すたびに、On/Offするようなアナログな仕組みを前提にしているのではないかと思います。これをコンピュータ回路と接続する場合には、フォトカプラなどで絶縁して使うのが一般的らしいです。

下の回路図は、ネットで見つけたパナソニックのJEMAアダプタの回路図です。電気錠のJEM-A端子に、自社のインターフォンを接続するためのアダプタのようです。右側が他社製品のJEM-A端子で、左側がパナソニック製品の機器側です。JEM-A端子側には100Ωの保護抵抗を直列に入れてフォトカプラが接続されています。

panasonic.bizから引用

電気錠のJEM-A端子

改造対象の電気錠にもJEM-A端子がついています。電気錠とコントローラは、10年くらい前のMIWAの製品です。電気錠制御盤の端に、JEM-A端子というラベルがあります。色々調べてみると、日本製の電気錠にJEM-A端子がついているのはお約束のようでした。インターフォンと連携するために用意されているようです。「ちょっと待ってください、今開けますから」と言って、インターフォンのボタンを押すと電気錠が解錠されるようなユーザシナリオ実現のために、Panasonicのようなインターフォンメーカーと、MIWAのような錠前メーカーが連携するためにある端子のようです。

JEM-A端子に使われるコネクタはXHコネクタと呼ばれるものです。これの4ピンです。

https://www.jst-mfg.com/product/pdf/jpn/XH.pdf

秋月とか千石とか共立とかマルツとかで1個当たり10円程度で売ってます。ピンや圧着工具(手元になかったのでとりあえずはんだ付けしました)なども必要です。圧着工具は、半田付けすれば無しでもなんとかなりますが、コネクタを外す工具は、絶対に持っていた方が良いです。ペンチやマイナスドライバーで外そうとすると苦労しますし、コネクタやケーブルを損傷させる可能性があります。

今回の電気錠は、オートロックのモードに設定されていました。解錠をしてもしばらく経過すると自動的に施錠されます。その時間を5秒から60秒にDIPスイッチで設定できます。モニター端子は、施錠の時にon (HIGH), 解錠の時にoff (LOW)になるようです。なので、もしコンピュータから解錠するとしたら、その手順は、

  1. コンピュータからJEM-A制御信号に250msの信号を送る
  2. 解錠される
  3. JEM-Aモニター信号が指定の時間(5~60s)offになり、その後onに戻る
  4. コンピュータはこの信号をモニターして解錠・施錠状態を表示する
  5. 施錠される

の順番になるのではと考えました。自動的に施錠される(オートロックされる)ので、施錠のために制御信号を送る必要はないと思われます。

一方で、人が手作業でドアを開錠した場合にも、モニター信号は反応すると思います。なのでそのイベントをiPhoneに通知できれば、手動による解錠も知ることができるかと思います。

JEM-A端子の電気特性を調べる

JEM-A端子には、コントロール用にC1, C2端子が、モニター用にM1, M2端子があります。これらにテスターを当てて電圧を調べました。コントロール端子、モニター端子(onの場合)とも、開放時の電圧は4.9Vでした。TTLレベルの回路のようです。使いやすそうで良かったです。また、それぞれに1kΩの付加抵抗を接続した時、C1-C2端子は0.8V、M1-M2端子は3.2Vでした。この結果、内部は以下のようになっていると予想しました。エアコンのJEM-A端子と似ているけど微妙に違います。規格PDFが有料かつ公開不可なので謎ですが、割と緩い規格なのかもしれません。

C1-C2には単に押しボタンスイッチを直結しても大丈夫のようです。フォトカプラも直結で大丈夫だと思われます。M1, M2の電圧は、施錠状態の時に5V、解錠状態の時に1.4Vになるようでした。500Ωの抵抗が入っている様子なので、M1-M2にもLED直結で問題ないと思います。フォトカプラに直結しても、中のLEDが明るめに点灯しそうですが、大丈夫と思われます。

ESP32に接続する

エアコンの端子とあまり変わりはなかったので、前回エアコンのJEM-A端子に接続した時と同じ回路で、ESP32を電気錠に接続することにしました。エアコンよりもM1側の内部抵抗が低いので、R1は大きくしても良かったのですが、現在の値でもTLP621の推奨動作条件順電流(16mA)の40%くらいと想定されるので、このままにしておきました。R2の100Ωは、単なる気休めで、なくても良いと思います。R3も、この値だと10mAくらいになり推奨順電流の60%くらいです。省エネということで。

まずは前回同様ブレッドボードに作りました(写真は前回の使い回しです)。ユニバーサル基板に実装出来たら追記します。

MQTTThingを設定する

ハードウェアは出来上がったので、今度はHomebridge側を構築します。Homebridgeで鍵 (Lock Mechanism) アクセサリを作れるプラグインはたくさんあります。今回はMQTTThingを使いました。MQTTを使えば動作の確認やデバッグが簡単ですし、将来、Apple HomeKit以外のシステム(例えばHome Assistant)で使うことになっても対応できます。

MQTTについてはこちらをご覧ください。

MQTTブローカーMosquittoを設置する
今回はMQTTブローカーをインストールします。MQTTがあると、スイッチ、センサ、リレー、サーボモータなどを接続したArduinoのようなコンピュータを、ネットワーク越しにIP接続したい時にとても便利です。また、MQTTを使うスマートホーム...

HomebridgeにMQTTThingプラグインをインストールして、以下のように設定しました。MQTTブローカーがlocalhostで動いていて、ユーザもパスワードも設定なしの場合です。高いセキュリティが要求されるアクセサリなので、後でパスワード設定しておきます。

電気錠の名前はFront Doorとしましたが、これはホームアプリで上書き変更できます。「玄関」などにすればSiriにお願いするときも日本語発音で伝わりやすいかもしれません。ちなみに英語で名前をつけた場合も、「フロントドア」とカタカナ発音でSiriに伝わります。

{
  "accessory": "mqttthing",
  "type": "lockMechanism",
  "name": "Front Door",
  "topics":
  {
    "getLockCurrentState": "mqttthing/lock/getCS",
    "getLockTargetState": "mqttthing/lock/getTS",
    "setLockTargetState": "mqttthing/lock/setTS"
  }
}

使用するTopicsは、

  • mqttthing/lock/getCS
  • mqttthing/lock/getTS
  • mqttthing/lock/setTS

としました。プラグイン名のmqttthingで始まるように設定しました。その先の名前も、サブクルライブする時にまとめてチェックしやすいように階層にしました。CS, TSは、Current State, Target Stateの略です。

使用するメッセージは、デフォルトのままなので指定していませんが、

  • U
  • S
  • J
  • ?

の文字です。それぞれ “Unsecured”, “Secured”, “Jammed”, “Unknown”の意味です。このメッセージもコンフィグファイルで変更できますが、今回はデフォルトのままで使いました。Jと?は使用しないです。

なお、トピックス名のgetとset、targetとcurrentが、HomeKitの中でどのように使い分けられているかについて詳しい説明は以下をご覧ください。

set, get と target, current の違い
HomeKitの動作説明で、setState, setTargetState, getCurrentStateなどの用語が出てきます。字面から意味をなんとなく想像していたのですが、わりと混乱してました。調べて理解したことをメモしておきます。...

施錠開錠のエミュレーション

Lock Mechanismがどのように動作するのを知る目的で、iPhoneやMacのホームアプリから操作をして、動作と流れるMQTTメッセージを確認しました。人がターミナルの上でMQTTのメッセージを見て、必要ならばメッセージをmosquitto_pubコマンドを使ってパブリッシュします。ESP32が行うべき処理のエミュレーションになります。これを元にESP32をプログラミングすれば良いわけです。

ホームアプリから解錠施錠する場合

先の手順でHomebridgeで設定して再起動するだけで、iPhoneとMacのホームに、電気錠アクセサリが自動的に現れます。

この先、どういうメッセージが流されているのか確認するために、MQTTをサブスクライブしておきます。

$ mosquitto_sub -h localhost -t mqttthing/lock/# -v

上記の「施錠済み」の状態で鍵アイコンをクリックすると、

「開錠中…」になります。MQTTには、以下のメッセージが流れました。

mqttthing/lock/setTS U

set Target State, つまりこの値(ここでは開錠)を目標に設定してほしいという依頼が流れたようです。このメッセージを受け取ったESP32は、電気錠を解錠すれば良いわけです。JEM-Aの制御端子を250msの間onにします。そしてJEM-Aのモニター端子をチェックして、これがonになったら、開錠が達成できたことになります。そこで、開錠できた事をget Current Stateで知らせます。

$ mosquitto_pub -h localhost -t mqttthing/lock/getCS -m U

すると表示が「施錠済み」に戻ります。

ここで再び鍵アイコンをクリックすると、set Target StateトピックスにSが流れます。表示は「施錠中…」になります。

そこで、get Current StateとしてSを流せば、

$ mosquitto_pub -h localhost -t mqttthing/lock/getCS -m S

すると表示が「施錠済み」に戻ります。

ここまでのMQTTメッセージの流れをまとめておくと以下になります。iPhoneのボタンを押して、解錠を試みて、解錠されたところで、施錠を試みて、施錠されるという順番です。

  1. mqttthing/lock/setTS U(鍵アイコンをクリック:解錠中…になる)
  2. mqttthing/lock/getCS U(解錠済みになる、他デバイスに通知)
  3. mqttthing/lock/setTS S(鍵アイコンをクリック:施錠中…になる)
  4. mqttthing/lock/getCS S(施錠済みになる、他デバイスに通知)

ESP32でのプログラムでは、setTSトピックスに開錠・施錠の設定指示が流れてきたら対応して、設定が終わったらgetCSで回答するという手順を作れば良いわけです。

他のiPhoneから解錠施錠される場合

HomeKitは、必要に応じてアクセサリの状態を通知してくれます。Lock Mechanismアクセサリの場合、自分の端末で操作していないのに、鍵の状態(Current Status)が変更されると、通知が来ます。例えばiPhoneで鍵を操作するとMacに通知が来て、Macで操作するとiPhoneに通知がきます。不正に鍵を開けられる場合に通知が来るので安全です。

Get Target Stateの使い方

ここまでの説明で出番のなかった、Get Target Stateはどういう場合に使うのでしょうか?getなのでHAPの外の世界の事象で発生するメッセージです。なので、例えば壁に電気錠を開錠・施錠する物理的なマニュアルスイッチが設置されていて、これが押された場合に、get Target Stateを使います。すると、iPhoneなどの表示が、開錠中…または施錠中…の表示になります。マニュアルスイッチで操作された後、施錠の状態が変化するはずですが、その結果をget Current Stateで伝えておくと、ホームの表示が更新されるとともに、通知が表示されます。

   

このように、get Target Stateとget Current Stateはこの順番で、セットで流す必要があります。逆の順番で流すと表示が混乱します。例えば、施錠済みの状態で、

get Current State でUを流すと、ボタンは解錠状態の白になるものの、アイコンは施錠状態、文字表示は「施錠中…」という表示になってしまいます。Targetが示されずにいきなりCurrentが変更されて混乱している様子です。これを治すにはget Target StateにUを流します。

一方、get Target StateにUを流し、get Current StateにUを流すという順番でメッセージを送れば、解錠中…の状態を経て、正しく解錠済みの表示になります。

   

ということで、外部マニュアルスイッチによって操作された場合は、get Target Stateとget Current Stateの両方を、この順番で流しておく必要があります。

ESP32のプログラミング

この手順を、ESP32にプログラムする手順を考えます。以下の処理をすれば良いです。

  • set TSを受け取ったらJEM-Aの制御線にパルスを出す(HomeKitからの依頼)
  • JEM-Aのモニタ線が変化した場合:
    1. これがHomeKitからの依頼で変化した場合はget CSでモニタ線状態を送信し、
    2. これが外部からの操作ならばget TSとget CSを送信する

これを実現するために、HomeKitからの依頼によりJEM-Aにパルスを送ったかどうかを記録しておくためのフラグを一つ用意して、以下のようなプログラムを考えました。

boolean:「MQTTからの要請で設定した」= false;
MTQQのsetTSトピックスにメッセージが来た時の処理
  受け取ったメッセージ(UまたはS)と現状が違ったら
    JEM-Aの制御線に250msのパルスを送る
    (施錠状態の時は5~60秒間解錠、解錠状態の場合は施錠される)
    「MQTTからの要請で設定した」フラグをtrueにする
loopの中でJEM-Aモニタ線が変化した時の処理
  もし「MQTTからの要請で設定した」フラグ==falseならば
    get Target Stateに今の状態(UまたはS)を送る(外部からの操作)   get Target Stateに今の状態(UまたはS)を送る
  「MQTTからの要請で設定した」=falseにする

JEM-A端子のM1, M2の値は、LEDを繋いでみた限りでは綺麗に切り替わってました。チャッタリングのような過渡的なノイズはない様子です。でも念のために、JEM-A端子の値が300ミリ秒間変化しない場合に動作するようプログラムしました。実際に完成したプログラムは以下になりました。(ちょっと長いです)

//ESP32 lock mechanism with EspMQTTClient library.
// Aug. 28, 2022. (first version)
#include "EspMQTTClient.h"
EspMQTTClient *client;
//input & output pins and values
#define JEMAOUT 13 //GPIO for photo relay to JEM-A control line.
#define JEMA_ON 1  //value for pulse on
#define JEMA_OFF 0 //value for pulse off
#define JEMAIN 12  //GPIO for photo relay driven by JEM-A monitor line. Pull-up.
#define JEMA_U 1   //value for the lock is Unsecure (Unlocked)
#define JEMA_S 0   //value for the lock is Secure (Locked)
int JEMA_current; //current status of the lock
boolean JEMA_pulsed; //if a pluse has been sent to JEMAOUT
//WiFi
const char SSID[] = "XXXXXXXX"; //WiFi SSID
const char PASS[] = "xxxxxxxx"; //WiFi password
//MQTT
char CLIENTID[] = "ESP32_xx:xx:xx:xx:xx:xx"; //MAC address is set in setup()
//for example, this will be set to "ESP32_84:CC:A8:7A:5F:44"
const char  MQTTADD[] = "192.168.xxx.xxx"; //Broker IP address
const short MQTTPORT = 1883; //Broker port
const char  MQTTUSER[] = "";//Can be omitted if not needed
const char  MQTTPASS[] = "";//Can be omitted if not needed
const char  SUBTOPIC[] = "mqttthing/lock/setTS"; //mqtt topic to subscribe
const char  PUBTARGET[] = "mqttthing/lock/getTS"; //mqtt topic to publish
const char  PUBCURRENT[] = "mqttthing/lock/getCS"; //mqtt topic to publish
const char  PUBDEBUG[] = "mqttthing/lock/debug"; //for debug message

void setup() {
  //Digital I/O
  pinMode(JEMAOUT, OUTPUT);
  pinMode(JEMAIN, INPUT_PULLUP);
  JEMA_current=digitalRead(JEMAIN);
  //Serial
  Serial.begin(115200);
  while (!Serial);
  Serial.println("ESP32 Lock Mechanism started.");
  //MQTT
  String wifiMACString = WiFi.macAddress(); //WiFi MAC address
  wifiMACString.toCharArray(&CLIENTID[6], 18, 0); //"ESP32_xx:xx:xx:xx:xx:xx"
  Serial.print("SSID: ");Serial.println(SSID);
  Serial.print("MQTT broker address: ");Serial.println(MQTTADD);
  Serial.print("MQTT clientID: ");Serial.println(CLIENTID);
  client = new EspMQTTClient(SSID,PASS,MQTTADD,MQTTUSER,MQTTPASS,CLIENTID,MQTTPORT);
}

void onMessageReceived(const String& msg) { // topic = mqttthing/lock/setTS
  //Serial.println(msg);
  client->publish(PUBDEBUG, "Set TS received.");
  boolean newValue, changed=false;
  if(msg.compareTo("U")==0) { //target state is Unsecure (unlock)
    if(digitalRead(JEMAIN) == JEMA_U) return; //already Unsecured (unlocked). do nothing.
  }
  else if(msg.compareTo("S")==0) { //target state is Secure (lock)
    if(digitalRead(JEMAIN) == JEMA_S) return; //already Secured. do nothing.
  }
  //now send a pluse to toggle the electric lock
  digitalWrite(JEMAOUT, JEMA_ON); //activate JEM-A control line for 0.25 sec
  delay(250);
  digitalWrite(JEMAOUT, JEMA_OFF);
  JEMA_pulsed=true;//flag to show a pulse has sent.
}

int counter, lastvalue;//counter and memory for same-JEMAIN-value check
void onConnectionEstablished() {
  Serial.println("WiFi/MQTT onnection established.");
  client->subscribe(SUBTOPIC, onMessageReceived); //set callback function
  client->publish(PUBDEBUG, "ESP32 Lock Mechanism is ready.");
  JEMA_current=digitalRead(JEMAIN);//update the current state
  JEMA_pulsed=false;//reset the pulse-flag.
  lastvalue=999; //force update at next loop()
}

void loop() {
  client->loop();
  delay(10); //10ms delay
  int newvalue = digitalRead(JEMAIN);//get current JEMA value
  if(newvalue != lastvalue) {
    lastvalue = newvalue;
    counter=0;//set the counter
  }else if(++counter == 30) {//when 300ms passed after last change
    if(JEMA_current == newvalue) return; //no change, do nothing.
    //JEMA monitor status has chenged
    if(newvalue == JEMA_S){
      if(!JEMA_pulsed) client->publish(PUBTARGET,"S");
      client->publish(PUBCURRENT,"S");
    }
    else if(newvalue == JEMA_U){
      if(!JEMA_pulsed) client->publish(PUBTARGET,"U");
      client->publish(PUBCURRENT,"U");
    }
    JEMA_pulsed = false;
    JEMA_current = newvalue;
  }
}

あとはHomeKitにお任せ

以上で、

  • 電気錠
  • フォトカプラの回路
  • ESP32(と説明しませんでしたがMQTTブローカー
  • HomebridgeとMQTTThingプラグイン
  • Apple HomeKit

の接続が完了しました。ここまで設定すれば、iPhoneやMacのホームに設定した電気錠が現れます。

   

そして、電気錠としてのあるべき振る舞いは全てHomeKitが面倒を見てくれます。その結果、以下のような使い方が可能になります。

まずは、世界中のどこからでも、iPhoneやMacのホームから解錠ができます。他の端末や手動で解錠された場合は通知が来ます。

次に、家族、同居人、オフィスならば同僚、一時的な滞在者など、Apple IDを持っている人なら解錠できる人として追加できますし、いつでも削除できます。解錠操作は、登録されたApple IDで二段階認証されたiPhoneまたはMacからのみ可能です。パスワードを共有する方式よりも安全です。

さらには、Siriも電気錠を正しく扱ってくれます。電気錠DIYでありがちなエピソードに、外から大声で叫んだら、家の中のスマートスピーカーが反応して解錠してしまうというオチがあります。今回作った電気錠も、「へいSiri、玄関を開けて」というように発声してSiriに開けてもらうことが可能です。「ドアを開けて」など、多少揺らぎのある発話をしても解錠してくれます。ただし音声解錠は、解錠する権限を持つユーザのiPhoneかApple Watchに、持ち主の声でお願いした時だけです。HomePodやMacのSiriにお願いしても、すぐには解錠しません。「続きの操作は個人用デバイスで行う必要があります」と音声応答した後、手元のiPhoneに確認の通知がきます。これをタップすることで解錠されます。Lock Mechanismは、照明やエアコンなどの通常のアクセサリよりセキュアな扱いがされています。

まとめ

JEM-A端子が付いているオートロックの電気錠に、フォトカプラ経由でESP32を接続して、MQTTメッセージで解錠できる電気錠にしました。これをHomebridge+MQTTThingプラグインと組み合わせることで、HomeKitのLock Mechanizmアクセサリにしました。これでどこからでも電気錠を解錠できますし、通知も来ます。Siriにお願いして解錠することもできました。

コメント

タイトルとURLをコピーしました