ATOM Lite をセサミスマートロックで利用する目的
そもそも、スマートフォンアプリで、施錠解錠も現在の状態も履歴も確認できるのになぜATOM Liteを導入するのか。
例えば、インターフォン越しに来客を確認してスマートロックを解除したい場合に、
スマホ取り出し – ロック解除 – アプリを開く – スマートロック解除
と、結構な手間がかかるので玄関まで走って手動で解除というのが定番でした。
インターフォンの近くにスマートロック操作用の施解錠スイッチがあれば、ポチッとするだけで解錠・施錠ができる。
まるで、オートロックのマンションのような仕組みが、自宅で安価に構築できるならば幸せな気分になれるのではないか。

スマートロック操作を一箇所だけでするのであれば、ブランクのFelicaカードをセサミのスマホアプリに登録してやれば、スマホをかざすだけで操作ができるのであるが、玄関横(もちろん屋内側、屋外側につけるなんて考える方はいないと思いますが・・・)にも欲しくて外出するときは、荷物が多くてもボタンをポチッとするだけで解錠してくれたら楽ちん。
となると問題はセサミのアプリに登録できるFelicaカードが1枚だけということ。

まあ、ブランクのFelicaを無理やり操作して同じIDのカードを作ればよいのでしょうが、ATOM Liteなんてデバイスがあるのですから、こっちを使う方がネタ的にも面白そうですしね。
M5Stack ATOM Lite とは?
ESP32 PICOベースのM5Stack ATOMシリーズ最小の開発モジュールです。

たった2.4cm四方の小さな筐体に、WiFiモジュール(2.4G SMDアンテナ)・Bluetoothやボタン・RGB LED・GPIOピン・4MBフラッシュメモリと盛りだくさんな内容となっています。

CPUは、ESP32シリーズの小型省電力モジュールのESP32-PICOを搭載しています。
240MHzデュアルコア・520KB SRAM
WiFi経由でPOSTするだけなら充分すぎるスペックです。
スイッチサイエンス社では、1個1,400円程で販売されています。

USBも標準装備されているので、シリアル変換のためのボードや変換ケーブルもいりません。
USB Type-CケーブルでPCと接続するだけで使用可能です。

開発環境はArduino-IDEを使います。
セサミスマートロックとは?
鍵のサムターンに開閉ロボットを取り付け、それをスマホからWiFiやBluetooth経由で操作して、物理的な鍵がなくても鍵の開閉ができる、CANDY HOUSE社が作成したスマートロックシステムです。
今回は、「SESAME4」に「WiFiモジュール」を追加設置しています。

台湾人のスタンフォード留学生 Jerming Gu が
『これからは、全てをスマホ一つにまとめよう。
そうすればもう、スマホさえ持ち歩けばいいのだ。』
のコンセプトの元、3Dプリンターを使って寮の鍵を開閉させるロボットの開発を始めたのがはじまりで、現在も精力的に新製品を開発しています。

2017年に日本法人「CANDY HOUSE JAPAN 株式会社」を設立し日本市場向け製品を次々と発表しています。
システム構成

点線で囲われた部分が、今回作成する部分です。
セサミアプリ~スマートロックの動作は、私が勝手に想像して作成したので、実際の動作とは異なる場合があります。

ATOM Lite で Web API に対して、GET/POST でリクエストを投げてスマートロックを操作します。
Web API を利用するには、API KEYの取得が必要となります。
1つのAPI KEYにつき一ヶ月30,000回までが無料で利用できます。
Sesame RESTful Web API でできること。
- Sesame の状態を取得
- Sesame の履歴を取得
- Web API による施解錠
今回は、「1.状態取得」と「3.Web API による施解錠」を利用します。

プログラム仕様
WiFi環境の設定について。
- WiFi環境の設定については、スマホアプリ「EspTouch」を使い、WiFi経由で外部から設定できるものとします。
- ボタン5秒以上長押しで、WiFi環境再設定モードに移行します。
- WiFi環境再設定モードではLED赤点滅(1秒間隔)します。
- WiFi環境設定は「EspTouch」対応のためSmartConfigにて内部設定します。
- WiFi正常接続時はLED青点灯し、接続不可時は黄色点灯します。
- WiFi接続状態は随時チェックし切断したら再接続をおこなう。
Web API APIキーについて。
- APIキーはATOM Liteデバイスそれぞに個別のAPIキーを持つものとする。
- ATOM Liteの標準MACアドレスを使ってAPIキーを紐付けします。
- ATOM Liteの標準MACアドレスが、APIキー紐付けテーブルに設定されていない場合は、指定された既定APIキーを使うものとします。
SESAMEスマートロックの状態チェックについて。
- SESAMEスマートロックの状態チェックは、Tickerタイマーにより6分に1回おこなう。(1時間に10回)
- SESAMEスマートロックの状態が解錠されていればLEDを緑に点灯する。(セサミアプリに準ずる)
- SESAMEスマートロックの状態が施錠されていればLEDを赤に点灯する。(セサミアプリに準ずる)
- SESAMEスマートロックの状態チェック停止時間を指定できるようにする。
また、指定時間は日をまたいで指定できるようにする。(22時から6時等)
SESAMEスマートロックの施解錠操作について。
- コマンドコードは「88」(Toggle)を使い施解錠をおこなう。
- ボタンが押されたらLEDを青点滅する。施解錠操作後処理を1.5秒停止後点滅を停止する。
- 施解錠操作後SESAMEスマートロックの状態チェックをおこなう。
使用ライブラリ
使用ライブラリについては記述漏れもあるかとおもいますので、随時必要なライブラリを追加してください。
ほとんどのライブラリは、「ライブラリマネージャー」を使えばインストールできるのですが、一部のライブラリはgitHubからzipファイルをダウンロードしてインストールしなければなりません。
gitHubからのインストール方法
gitHubからのインストール方法を備忘録としてメモっておきます。
gitHubページの「Code」をクリックしてメニューから「Download ZIP」をクリックして、ZIPファイルをダウンロードします。

Arduino IDE で「スケッチ」-「ライブラリをインクルード」-「.ZIP形式のライブラリをインストール…」をクリックして、ダウンロードしたZIPファイルを選択する。
主な使用ライブラリー
M5ATOM
ATOM Lite 用のライブラリー
NTPClient
NTPサーバーに接続して日付や時間を取得するライブラリー
ArduinoJson
JSON形式のエンコード・デコードを行うライブラリー
arduino-AES_CMAC
AESCMAC暗号化ライブラリー
Crypto
Arduino Cryptography Library
Arduino 暗号化ライブラリ arduino-AES_CMAC ライブラリは当ライブラリのAESに依存します。
base64_encode
Base64エンコード・デコードライブラリ
サンプルスケッチ説明
前述した、プログラム仕様に基づいてサンプルプログラムを作成いたしました。
数週間自宅で稼働したスケッチを公開しておきます。
サンプルスケッチのダウンロードはこちらからできます。
//---------------------------------------------------------------------- // Arukas Amart Keys // Atom Liteでセサミスマートロックを操作 // 有限会社さくらシステム 近藤秀尚 // https://sakura-system.com // Ver.1.0 2023.06.05 //---------------------------------------------------------------------- #include <M5Atom.h> #include <HTTPClient.h> #include <WiFi.h> #include <WiFiUdp.h> #include <NTPClient.h> #include <Ticker.h> #include <ArduinoJson.h> #include <AES_CMAC.h> #include <AES.h> #include <arduino_base64.hpp> #include <driver/adc.h> //#define DEBUG 1 // デバッグ用 // LED関連 #define LED_WAIT CRGB::Red // WiFi準備待ち受け時の点滅色1 #define LED_WAITBLINK CRGB::Black // WiFi準備待ち受け時の点滅色2 #define LED_PUSH CRGB::Navy // ボタンプッシュ時の点滅色1 #define LED_PUSHBLINK CRGB::Black // ボタンプッシュ時の点滅色2 #define LED_WIFIOK CRGB::Navy // WiFi正常時点灯色 #define LED_WIFING CRGB::Yellow // WiFi異常時点灯色 #define LED_LOCK CRGB::Maroon // スマートロック施錠時点灯色 #define LED_UNLOCK CRGB::Green // スマートロック解錠時点灯色 #define LED_MOVE CRGB::Yellow // スマートロック移動時(施錠・解錠の途中)点灯色 #define LED_OFF CRGB::Black // 消灯時の色 #define LED_WAITBLINK_TIME 1000 // WiFi準備待ち受け時の点滅間隔 #define LED_PUSHBLINK_TIME 300 // ボタンプッシュ時の点滅間隔 int LEDBlink = LOW; // 時間関連 #define UTC 0 // UTC #define JST 3600 * 9 // 日本標準時 const int NTP_INTVAL = 1 * 6000; // 1分毎にNTPサーバをチェック // NTP関連 WiFiUDP NTP_udp ; NTPClient ntp(NTP_udp, "ntp.nict.jp", UTC, NTP_INTVAL ); // NTP結果保持 struct NTPTime { time_t utc; time_t t; struct tu *tu; struct tm *tm; const char *wd[7] = {"Sun","Mon","Tue","Wed","Thr","Fri","Sat"}; String strDate; String strTime; String strWeek; String strDateTime; int year, mon, mday, hour, min, sec; }; struct NTPTime ntpTime; // セサミWeb API関連 // API Keyと履歴表示名の設定 複数台の機器(ATOM Lite)でAPI Keyと履歴表示名を機器毎に設定する。 const int MACs_NUM = 2; // 機器(ATOM Lite)の台数 const String MACs[] = {"一台目の標準MACアドレス","二台目の標準MACアドレス"}; // 機器(ATOM Lite)の標準MACアドレス (MACアドレスで機器判定する。) const String APIKEYs[] = {"一台目のAPIKEY","二台目のAPIKEY"}; // 各機器のAPIKey const String HISTORYs[] = {"一台目の履歴表示名","二台目の履歴表示名"}; // 各機器の履歴表示名 int MyMAC; String MyAPIKEY; // APIKey String MyHISTORY; // 履歴表示名 String SESAME_APIKEY; String SESAME_HISTORY; const String SESAME_UUID = "スマートロックのUUID"; // スマートロックのUUID const char SESAME_SECRETKEY[] = "スマートロックのシークレットキー"; // スマートロックのシークレットキー const int SESAME_CHECK_INTVAL = 6 * 60000 ; // セサミスマートロック状態チェックインターバル const int SESAME_CHECK_OFF_START = 2 ; // セサミスマートロック状態チェック停止開始時間 2時から5時まで状態チェックは停止 const int SESAME_CHECK_OFF_END = 5 ; // セサミスマートロック状態チェック提示終了時間 const String SESAME_LOCK = "locked"; // セサミスマートロック状態チェック施錠時のステータス文字列 const String SESAME_UNLOCK = "unlocked"; // セサミスマートロック状態チェック解錠時のステータス文字列 const String SESAME_MOVE = "moved"; // セサミスマートロック状態チェック解錠から施錠へ移動中のステータス文字列 struct SesameAPIOperation { int command = 88; // Web API 操作コマンド String history; // 履歴表示名 String sign; // 暗号化済みシークレットキー int batteryPercentage; // バッテリー残量 double batteryVoltage; // バッテリー電圧 int keyPosition; // キーポジション String keyStatus; // キーの状況 long timeStamp; // タイムスタンプ bool wm2State; // ステータス }; struct SesameAPIOperation sesameAPI; // AESCMAC暗号化関連 uint8_t AesCmac_mac[16]; uint8_t AesCmac_key[16]; uint8_t AesCmac_mes[3]; AESTiny128 AesCmac_aes128; AES_CMAC AesCmac_cmac(AesCmac_aes128); // タイマー関連 Ticker TickerBlink ; Ticker TickerCheck ; //---------------------------------------------------------------------- // NTPから日付時間の取得 //---------------------------------------------------------------------- void getNTPDateTime(){ char s[30]; if (WiFi.status() == WL_CONNECTED) { ntp.update(); } // NTPから時間の取得 ntpTime.utc = ntp.getEpochTime(); // UTC 秒数 ntpTime.t = ntp.getEpochTime() + JST; // 日本標準時秒数 // 日本標準時年月日時分秒を取得 ntpTime.tm = localtime(&ntpTime.t); ntpTime.year = ntpTime.tm->tm_year + 1900; ntpTime.mon = ntpTime.tm->tm_mon + 1; ntpTime.mday = ntpTime.tm->tm_mday; ntpTime.hour = ntpTime.tm->tm_hour; ntpTime.min = ntpTime.tm->tm_min; ntpTime.sec = ntpTime.tm->tm_sec; // 表示用日付 sprintf(s, "%04d.%02d.%02d", ntpTime.year, ntpTime.mon, ntpTime.mday); ntpTime.strDate = String(s); // 表示用時間 sprintf(s, "%02d:%02d:%02d", ntpTime.hour, ntpTime.min, ntpTime.sec); ntpTime.strTime = String(s); // 表示用曜日 sprintf(s, "%s", ntpTime.wd[ ntpTime.tm->tm_wday]); ntpTime.strWeek = String(s); // 表示用日時 ntpTime.strDateTime = ntpTime.strDate + "(" + ntpTime.strWeek + ")" + ntpTime.strTime; } //---------------------------------------------------------------------- // シリアルモニタへの表示 //---------------------------------------------------------------------- void printScreenSl( String argText ){ // シリアルモニタ表示 ///* #if defined(DEBUG) if (argText == "" || WiFi.status() != WL_CONNECTED ){ Serial.println(argText); } else { getNTPDateTime(); Serial.println(ntpTime.strDateTime + ":" + argText) ; } #else delay(10); #endif //*/ // Serial.println(argText) ; return; } void printScreen( String argText ){ // シリアルモニタ表示 ///* #if defined(DEBUG) Serial.print(argText) ; #else delay(10); #endif //*/ return; } //---------------------------------------------------------------------- // LEDコントロール //---------------------------------------------------------------------- void ledControl( CRGB argColor ) { M5.dis.drawpix(0, argColor); } //---------------------------------------------------------------------- // セサミスマートロック状態チェック停止判定 // true : チェック停止時間内 // false : チェック停止時間外 //---------------------------------------------------------------------- bool sesameStatusCkStop() { bool retSts = false; int stH, enH, nowH; getNTPDateTime(); stH = SESAME_CHECK_OFF_START; enH = SESAME_CHECK_OFF_END ; nowH = ntpTime.hour; if ( stH > enH ) { // 指定時間が日をまたいでいる ex.) 22時から3時 if ( nowH <= enH ) { nowH += 24; } enH += 24; } if ( nowH >= stH && nowH <= enH ) { retSts = true; } return retSts; } //---------------------------------------------------------------------- // セサミスマートロック状態取得 //---------------------------------------------------------------------- void sesameGetStatus() { if ( sesameStatusCkStop() ){ printScreenSl("SESAME Status Check is Offtime."); return; } else { printScreenSl("SESAME Status Check is Ontime."); } // Web API セサミスマートロック状態取得 HTTPClient http; http.begin("https://app.candyhouse.co/api/sesame2/" + SESAME_UUID ); http.addHeader("x-api-key", SESAME_APIKEY); http.GET(); String response = http.getString(); http.end(); // JSONから配列へデコード printScreenSl("SESAME Json:" + response) ; StaticJsonDocument<300> doc; DeserializationError error = deserializeJson(doc, response); if (error) { printScreen("deserializeJson() failed: "); printScreenSl(error.c_str()); return; } // 状態取得情報の保存 sesameAPI.batteryPercentage = doc["batteryPercentage"]; sesameAPI.batteryVoltage = doc["batteryVoltage"]; sesameAPI.keyPosition = doc["position"]; String SesameStatus = doc["CHSesame2Status"]; sesameAPI.keyStatus = SesameStatus; sesameAPI.timeStamp = doc["timestamp"]; sesameAPI.wm2State = doc["wm2State"]; // スマートロックの状況により、LEDの色を変更する。 printScreenSl("SESAME Status:" + sesameAPI.keyStatus); if ( sesameAPI.keyStatus.equals(SESAME_LOCK)) { ledControl(LED_LOCK); } else if ( sesameAPI.keyStatus.equals(SESAME_UNLOCK)) { ledControl(LED_UNLOCK); } else if ( sesameAPI.keyStatus.equals(SESAME_MOVE)) { ledControl(LED_MOVE); } else { ledControl(LED_OFF); } } //---------------------------------------------------------------------- // ボタン操作LED点滅 //---------------------------------------------------------------------- void buttonPushLEDBlink() { if ( LEDBlink == HIGH ) { M5.dis.drawpix(0, LED_PUSHBLINK); LEDBlink = LOW ; } else { M5.dis.drawpix(0, LED_PUSH); LEDBlink = HIGH ; } } //---------------------------------------------------------------------- // WiFi設定待ち受けLED点滅 //---------------------------------------------------------------------- void wifiWaitLEDBlink() { if ( LEDBlink == HIGH ) { M5.dis.drawpix(0, LED_WAITBLINK); LEDBlink = LOW ; } else { M5.dis.drawpix(0, LED_WAIT); LEDBlink = HIGH ; } } //---------------------------------------------------------------------- // WiFi Connect 待ち処理 //---------------------------------------------------------------------- bool WiFiConnectWait(int argRetry=60){ bool csts = false ; for( int i = 0 ; i < argRetry; ++i) { if ( WiFi.status() == WL_CONNECTED ) { csts = true ; break ; } delay(500); printScreen("."); } printScreenSl(""); return csts ; } //---------------------------------------------------------------------- // WiFi Smart Connect 接続先検索処理 (スマホアプリ「EspTouch」により設定する。) //---------------------------------------------------------------------- bool lookingForWiFiWithSmartConnect(){ printScreenSl("Smart Connect Start."); LEDBlink = LOW; TickerBlink.attach_ms(LED_WAITBLINK_TIME, wifiWaitLEDBlink ); // LED 点滅 if ( WiFi.isConnected()) { // ボタン長押しでWiFi再設定に備えて、接続済みであれば一旦切断する WiFi.disconnect(); delay(100); ntp.end(); delay(100); } WiFi.mode(WIFI_AP_STA); delay(100); WiFi.beginSmartConfig(); // Smart Config 開始 printScreenSl("Waitting Smart Connect."); bool ret = WiFiConnectWait(200); // 接続できたら接続モードを変更します。 if ( WiFi.isConnected()) { WiFi.disconnect(); WiFi.mode(WIFI_STA); WiFi.reconnect(); WiFiConnectWait(); } printScreenSl("Smart Connect Terminated."); TickerBlink.detach(); // LED 点滅停止 return ret ; } //---------------------------------------------------------------------- // WiFi開始処理 //---------------------------------------------------------------------- void WiFiBegin() { printScreenSl("Start setting WiFi connection.") ; WiFi.mode(WIFI_STA); WiFi.begin() ; // WiFi 接続開始 WiFiConnectWait(); if (WiFi.status() != WL_CONNECTED) { lookingForWiFiWithSmartConnect(); // WiFi 接続できなかった時はSmartConnectにて接続先を設定する。 } if (WiFi.status() == WL_CONNECTED) { // WiFi接続に成功 //printScreenSl("connect:" + WiFi.SSID() + " " + String(WiFi.psk()) + " " + WiFi.localIP().toString()) ; printScreenSl("connect:" + WiFi.SSID() + " " + WiFi.localIP().toString()) ; ledControl(LED_WIFIOK); ntp.begin(); // NTP 開始 ntp.update(); } else { // WiFi接続に失敗 printScreenSl("Failed to connect") ; ledControl(LED_WIFING); } } //---------------------------------------------------------------------- // デバッグ用配列の表示 //---------------------------------------------------------------------- void arrayPrint(uint8_t argArry[], int argSize){ for (int i = 0; i < argSize; ++i) { #if defined(DEBUG) if (argArry[i] < 0x10) { printScreen("0"); } printScreen(String(argArry[i], HEX) + " "); #else delay(10); #endif } } //---------------------------------------------------------------------- // AES-CMAC用Keyの作成 // SESAME Secret KeyをAES-CMAC用にuint8_tの配列にセット //---------------------------------------------------------------------- void genarateAESCMACKey(){ for(int i=0,j=0 ; i < 32 ; ++i,++j){ char str[3] ; str[0] = SESAME_SECRETKEY[i]; ++i; str[1] = SESAME_SECRETKEY[i]; unsigned long keynum = strtoul(str,NULL,16); AesCmac_key[j] = keynum; } printScreen("Key: "); arrayPrint(AesCmac_key, sizeof(AesCmac_key) ); printScreenSl(""); } //---------------------------------------------------------------------- // SESAME API 履歴表示名のBase62エンコード //---------------------------------------------------------------------- void genarateSesameHistory(){ const char* rawData = SESAME_HISTORY.c_str(); size_t rawLength = strlen(rawData); // char encoded[BASE64::encodeLength(rawLength)]; // BASE64::encode((const uint8_t*)rawData, rawLength, encoded); char encoded[base64::encodeLength(rawLength)]; base64::encode((const uint8_t*)rawData, rawLength, encoded); sesameAPI.history = encoded; printScreenSl("Histroy:" + sesameAPI.history); } //---------------------------------------------------------------------- // スマートロックの操作 Toggle //---------------------------------------------------------------------- void keyToggle(){ printScreenSl("Key Toggle Command 88."); // AES-CMAC暗号化messageの生成 getNTPDateTime(); printScreenSl(String(ntpTime.utc)); // 1. timestamp 1684745692 (UTC)) String d = String(ntpTime.utc,HEX); // 2. timestamp to Hex String "646b2ddc" printScreenSl(d); for(int i=0,j=2; i < 6; ++i,--j){ // 3. Little endian to uint8_t[] And remove most-significant byte. uint8_t[] = { 0x2D, 0x6b, 0x64 } char str[3] ; str[0] = d[i]; ++i; str[1] = d[i]; unsigned long m = strtoul(str,NULL,16); // String to unsigned long "2d"->0x2d "6b"->0x6b "64"->0x64 AesCmac_mes[j] = m; } printScreen("message: "); arrayPrint(AesCmac_mes, sizeof(AesCmac_mes) ); printScreenSl(""); // AES-CMAC暗号化 AesCmac_cmac.generateMAC(AesCmac_mac, AesCmac_key, AesCmac_mes, sizeof(AesCmac_mes)); delay(10); printScreen("AES-CMAC: "); arrayPrint(AesCmac_mac, sizeof(AesCmac_mac) ); printScreenSl(""); // 暗号化したデータを文字列に変換 sesameAPI.sign = ""; for( int i=0; i < sizeof(AesCmac_mac); ++i) { sesameAPI.sign += String(AesCmac_mac[i], HEX); } // JSONへの変換 StaticJsonDocument<300> doc; doc["cmd"] = sesameAPI.command ; doc["history"] = sesameAPI.history; doc["sign"] = sesameAPI.sign; String oJson = ""; serializeJson(doc, oJson); printScreenSl(oJson); // Web APIへPOST HTTPClient http; if (!http.begin("https://app.candyhouse.co/api/sesame2/" + SESAME_UUID + "/cmd")) { Serial.println("Failed HTTPClient begin!"); return; } http.addHeader("Content-Type", "application/json"); http.addHeader("x-api-key", SESAME_APIKEY); int responseCode = http.POST(oJson); String body = http.getString(); printScreenSl(String(responseCode)); printScreenSl(body); http.end(); } //---------------------------------------------------------------------- // MACアドレスからAPIKeyとHISTORYを求める。 //---------------------------------------------------------------------- void getAPIKey() { printScreenSl("getAPIKey"); uint8_t mac[6]; // MACアドレスの取得 esp_efuse_mac_get_default(mac); char mymac[13]; sprintf(mymac,"%02X%02X%02X%02X%02X%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); String myMAC = mymac; printScreenSl("MACアドレス:" + myMAC); printScreenSl("MACs Size:" + String(MACs_NUM)); bool MacFound = false; for(int i=0; i < MACs_NUM; ++i){ if (myMAC.equals(MACs[i])) { MacFound = true; MyMAC = i; } } if (!MacFound) {MyMAC = 0;} // 該当のMACアドレスが見つからない場合は、最初に設定されているAPIKeyと履歴表示名を使用する。 MyAPIKEY = APIKEYs[MyMAC]; SESAME_APIKEY = MyAPIKEY; MyHISTORY = HISTORYs[MyMAC]; SESAME_HISTORY = MyHISTORY.c_str(); printScreenSl("MACs Index:" + String(MyMAC)); printScreenSl("APIKEY:" + SESAME_APIKEY); printScreenSl("HISTORY:" + SESAME_HISTORY); } //---------------------------------------------------------------------- // 開始処理 //---------------------------------------------------------------------- void mySetup() { WiFiBegin(); getAPIKey(); genarateAESCMACKey(); genarateSesameHistory(); } //---------------------------------------------------------------------- // 初期設定処理 //---------------------------------------------------------------------- void setup() { //----おまじない ATOM Liteの不具合(ノイズによるボタン連打)対策 pinMode(0, OUTPUT); digitalWrite(0, LOW); adc_power_acquire(); //----おまじない ここまで // 本体初期化(UART, I2C, LED) M5.begin(true, false, true); // LED全消灯(赤, 緑, 青) M5.dis.drawpix(0, CRGB::Black); // Serial.begin(115200); mySetup(); printScreenSl("Start"); sesameGetStatus(); TickerCheck.attach_ms(SESAME_CHECK_INTVAL, sesameGetStatus ); // スマートロックの状態を指定時間毎にチェックする } void loop() { if (WiFi.status() != WL_CONNECTED) { WiFiBegin(); } else { ntp.update(); M5.update(); if ( M5.Btn.pressedFor(5000)){ // ボタン長押し (WiFiの再設定) lookingForWiFiWithSmartConnect(); } if ( M5.Btn.wasPressed()){ // ボタンが押された (スマートロックの操作) LEDBlink = LOW; TickerBlink.attach_ms(LED_PUSHBLINK_TIME, buttonPushLEDBlink ); // LED 点滅開始 keyToggle(); // Web APIにてスマートロック操作 delay(1500); TickerBlink.detach(); // LED 点滅停止 sesameGetStatus(); // Web APIにてスマートロックの状態取得 } } delay(100); }
動作環境
サンプルスケッチは、ATOM Liteを玄関とインターフォン横の2台で、一台のセサミスマートロックを操作します。
ATOM Liteデバイスは各々API Keyを持ちますので、2つのAPI Keyが必要となります。

セサミWeb API利用のためにはAPI Keyが必要
下記ページににて、Web API利用のためのAPI Keyを取得する必要があります。
API Keyを取得するためには、スマートロックデバイスのUUIDとSecret Keyが必要です。
ここでは、API Keyの取得方法について詳しくは説明しません。
(API Keyの取得方法はこれまで何度も変わっているため)
ヒント:
スマホアプリ「セサミ」から操作するスマートロックデバイスをクリックして履歴を表示します。
右上の「・・・」をクリックしてデバイスの詳細を表示して、「このセサミの鍵をシェア」-「ゲスト」で、セサミデバイスの鍵のQRコードを表示します。
右上の「共有」アイコンをクリックして共有するなりして、API Key取得ページの「デバイスのORコードをアップロード」をすると、API Keyにデバイスが紐付けられます。
API Key取得ページで「デバイスを操作」をクリックして、スマートロックが操作できることを確認します。
MACアドレスとAPI Key
ATOM LiteデバイスはMACアドレスにAPI Keyと履歴表示名が紐付けられます。
ArukasSmartKeys.ino 60行目
// セサミWeb API関連 // API Keyと履歴表示名の設定 複数台の機器(ATOM Lite)でAPI Keyと履歴表示名を機器毎に設定する。 const int MACs_NUM = 2; // 機器(ATOM Lite)の台数 const String MACs[] = {"一台目の標準MACアドレス","二台目の標準MACアドレス"}; // 機器(ATOM Lite)の標準MACアドレス (MACアドレスで機器判定する。) const String APIKEYs[] = {"一台目のAPIKEY","二台目のAPIKEY"}; // 各機器のAPIKey const String HISTORYs[] = {"一台目の履歴表示名","二台目の履歴表示名"}; // 各機器の履歴表示名
MACs_NUM・MACs・APIKEYs・HISTORYsに適切な値を設定します。
ATOM LiteデバイスのMACアドレスは、ダウンロードしたフォルダに収納されたスケッチ「GetChipInformation.ino」をATOM Liteに書き込むとシリアルモニタにチップ情報が表示されます。

「MAC Adress:」に続く12桁の英数字がMACアドレスとなります。
履歴表示名は半角英数字が無難だと思われます。
今回は「InterPhone」と「FrontDoor」にしました。
使用するATOM Liteデバイスが1台の場合には、MACs配列にはNULL等を設定して、APIKEYs・HISTORYs配列には最初の要素に値を入れれば、MACs配列にMACアドレスが見つからない場合は最初の要素(インデックス0 : [0])を参照します。
UUIDとシークレットキー
スマートロックのUUIDとシークレットキーを設定します。
ArukasSmartKeys.ino 71行目
const String SESAME_UUID = "スマートロックのUUID"; // スマートロックのUUID const char SESAME_SECRETKEY[] = "スマートロックのシークレットキー"; // スマートロックのシークレットキー
スマートロック状態チェック処理
スマートロックの状態を一定時間間隔でチェックして、スマートロックの現在の状態をLEDの点滅色で示します。
スマートロック状態チェック設定をします。
ArukasSmartKeys.ino 73行目
const int SESAME_CHECK_INTVAL = 6 * 60000 ; // セサミスマートロック状態チェックインターバル const int SESAME_CHECK_OFF_START = 2 ; // セサミスマートロック状態チェック停止開始時間 2時から5時まで状態チェックは停止 const int SESAME_CHECK_OFF_END = 5 ; // セサミスマートロック状態チェック提示終了時間
SESAME_CHECK_INTVAL:スマートロックの状態をチェックする間隔を秒で指定します。
スマートロック状態チェック停止指定
指定された停止開始時間から停止終了時間の間は、スマートロックの状態チェックを行いません。
この機能により、API Keyの使用回数を減らすことができます。
指定時間は日をまたいでもOKです。
6分間隔でスマーロック状態をチェック・・・6回/1h
状態チェック停止指定:開始 23(時) 終了 6(時) とすると、23時から翌6時までの8時間チェックが停止しますので、1日当たり48回(31日で1,488回)使用回数を減らすことができます。
SESAME_CHECK_OFF_START:スマートロック状態チェック停止開始時間を24時制の時で指定します。
SESAME_CHECK_OFF_END:スマートロック状態チェック停止終了時間を24時制の時で指定します。
WiFi設定
WiFi設定はESPRESSIF社の「ESP-TOUCH」を使って設定します。
Google Play
App Store

ATOM Liteにスケッチを書き込み後、LEDが赤色点滅している間に、スマホで「ESPTouch」を立ち上げる。
「EspTouch」と「EspTouch V2」の選択で、「EspTouch」を選択する。
「WiFi Password」を設定して「START」をクリックする。
設定に失敗した場合は、ATOM Liteのボタンを5秒以上長押しするか、「リセットボタン」を押して再度設定してください。
一度失敗しても、何度か設定していると必ず設定できます。
設定に成功すると、LEDが青色に点灯します。失敗すると黄色に点灯します。
注意事項
setup()の「おまじない」は、絶対消さないでください。
void setup() { //----おまじない ATOM Liteの不具合(ノイズによるボタン連打)対策 pinMode(0, OUTPUT); digitalWrite(0, LOW); adc_power_acquire(); //----おまじない ここまで
ATOM LiteはWiFiモジュールを使い高負荷なループの処理を行うと、M5.Btn.wasPressed()が頻繁にTrueになり、ボタンを押していないにも関わらず、ボタンが連打されるというオカルトチックな動作をします。
これは、デバッグ時などシリアルモニタに出力しながら処理をしていると発現しないのですが、デバッグ用のコードを除去した途端に高負荷になり、おばけ連打が発動します。
私のATOM Liteでは、このおまじないで何台ものATOM Liteを手懐けることに成功しています。
Web API が反応しない時がある。
Web API に対してPOSTでリクエストを送信するときに、スマートロックのシークレットキーをAESCMACにて暗号化して送信しますが、この暗号化のときにUTCのタイムスタンプ(秒)を使って暗号化しているのですが、どうもWeb API側で復号化に失敗しているようなのです。
この現象は、2台あるATOM Liteデバイスのどちらか一方で起きると、API Keyが違う他方のデバイスも同じ現象が発生するので、ロジカルな問題であることだけは確かなようです。
AES-CMAC使用方法

NGになった元の秒数はどれも暗号化キーが変わる時間までが、タイトな場合が多いようです。
(サンプル抽出数が少ないので絶対原因とは言い切れませんが。)
NTPClientライブラリで取得するタイムスタンプの正確性も関係してきますし、WiFiの通信速度やAPI側のタイムスタンプの正確さも加味されるので、ある程度は仕方がないと諦めるしかないようですね。
NG | OK | NG | OK | |
---|---|---|---|---|
タイムスタンプ | 1685937392 | 1685837454 | 1685075928 | 1685075975 |
16進 | 647D5CF0 | 647D5D2E | 647037D8 | 64703807 |
暗号化キー | 5C7D64 | 5D7D64 | 377064 | 387064 |
暗号化キーが変わるタイムスタンプ | 1685937407 | 1685937663 | 1685075967 | 1685076223 |
暗号化キーが変わるまでの秒数 | 15 | 209 | 39 | 223 |
また、一度NGになるとロックされたように、Web API が暫くの間操作を受け付けなくなります。
Web API から実行ステータスなどのリターンがないので、エラー時のリカバリも不可能で、原因も全く掴めていません。
NGになったアクセスも、使用回数にカウントされますので注意が必要です。
ボタンを押しても反応がないから「何度も繰り返して押す」を繰り返すと、1ヶ月の上限使用回数を超えてしまい料金が発生する可能性があるので注意しましょう。
暗号化ライブラリが完璧かどうかも保証の限りではありませんし、真相は闇の中です。
ATOM Lite SESAMEスマートロック操作サンプルスケッチ ArukasSmartKeys
「ATOM Lite SESAMEスマートロック操作 ArukasSmartKeys」の紹介
M5Stack ATOM Liteに、サンプルスケッチArukasSmartKeys.inoをコンパイルして書き込めば、SESAMEスマートロックを操作することができます。
動作確認は、
OS:Windows10 Pro
開発環境: Arduino IDE バージョン:2.1.0
で行っています。
ATOM Liteやセサミスマートロック・Web APIの仕様変更やライブラリー等のアップデートにより動作しなくなることがあります。
ダウンロード
ダウンロードにあたり下記事項を確認してダウンロードしてください。
解凍後には必ず「ATOM Lite SESAMEスマートロック操作ArukasSmartKeys.txt」を一読し、免責・禁止事項・注意事項をご確認ください。て、データが壊れたやハッキングにあって解錠されて盗難や強盗等の責任は一切受け付けません。
・免責
住居の安全を守る鍵のネタ記事なので、もしこの記事を参考にして、ハッキング等の被害により鍵を操作されて盗難や強盗にあったとしても、有限会社さくらシステム及び製作者はその責を一切負いません。
この記事は、あくまで技術検証のための記事であり、スマートロックを始めとするいかなる設備について利用方法を推奨・保証するものではありません。
また、バグ等でいかなる損害を被っても当方は一切の責任負いません。
・禁止事項
有限会社さくらシステム及び製作者に許可なく、あらゆるメディア・コンテンツに再掲することを禁じます。
・注意事項
当モジュール及びサンプルプログラムは、技術検証を目的として作成されていますので、負荷試験を始めとする製品化を行うための基準を満たすための試験を一切行っておりませんので、予期せぬ結果を招く場合があります。
その場合であっても、上記免責事項により有限会社さくらシステム及び製作者はその責を一切負いません。
免責事項など
免責事項
住居の安全を守る鍵のネタ記事なので、もしこの記事を読んで真似をして、ハッキングにあって解錠されて盗難や強盗にあったとしても、有限会社さくらシステム及び製作者はその責を一切負いません。
この記事は、あくまで技術検証のための記事であり、スマートロックを始めとするいかなる設備について利用方法を推奨・保証するものではありません。
また、バグ等でいかなる損害を被っても当方は一切の責任負いません。
禁止事項
有限会社さくらシステム及び製作者に許可なく、あらゆるメディア・コンテンツ・SNS等に再掲することを禁じます。
注意事項
当モジュール及びサンプルプログラムは、技術検証を目的として作成されていますので、負荷試験を始めとする製品化を行うための基準を満たすための試験を一切行っておりませんので、予期せぬ結果を招く場合があります。
その場合であっても、上記免責事項により有限会社さくらシステム及び製作者はその責を一切負いません
AESCMAC暗号化まわりの不具合があるようで、暗号化メッセージ作成のために取得したタイムスタンプの値によってはWebAPIが動作しない現象を確認しています。この現象が発生すると最大256秒間(暗号化メッセージの値が変わるまで)WebAPIが動作しません。2台あるデバイスのどちらもが同時に発生するので、Arduinoの暗号化ライブラリにバグがあるのか原因は掴めていません。
まぁ、「ポチッ」としたら動いたらめっけもん的な、生暖かい目で見守ってください。