M5Stack ATOM Lite を使って セサミスマートロックを操作してみました。

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円程で販売されています。

サイズがわずか24 x 24 mmの、M5Stackシリーズ開発モジュールです。ESP32-PICOを搭載し、スマートホームデバイスや、小型のおもちゃへの組み込…
www.switch-science.com

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

開発環境はArduino-IDEを使います。

セサミスマートロックとは?

鍵のサムターンに開閉ロボットを取り付け、それをスマホからWiFiやBluetooth経由で操作して、物理的な鍵がなくても鍵の開閉ができる、CANDY HOUSE社が作成したスマートロックシステムです。

今回は、「SESAME4」に「WiFiモジュール」を追加設置しています。

あなたは家を出る時に何を持つ?鍵、財布、スマホ…?「SESAME(セサミ)」はスマホで家の鍵を開閉出来る スマートロック です。取付も簡単で鍵も簡単にシェア…
jp.candyhouse.co

台湾人のスタンフォード留学生 Jerming Gu が

『これからは、全てをスマホ一つにまとめよう。
そうすればもう、スマホさえ持ち歩けばいいのだ。』

のコンセプトの元、3Dプリンターを使って寮の鍵を開閉させるロボットの開発を始めたのがはじまりで、現在も精力的に新製品を開発しています。

スタンフォード大学寮に設置されたプロトタイプ

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

システム構成

点線で囲われた部分が、今回作成する部分です。

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

CANDY HOUSE 開発ドキュメント
doc.candyhouse.co

ATOM Lite で Web API に対して、GET/POST でリクエストを投げてスマートロックを操作します。

Web API を利用するには、API KEYの取得が必要となります。
1つのAPI KEYにつき一ヶ月30,000回までが無料で利用できます。

Sesame RESTful Web API でできること。

  1. Sesame の状態を取得
  2. Sesame の履歴を取得
  3. Web API による施解錠

今回は、「1.状態取得」と「3.Web API による施解錠」を利用します。

Web API
doc.candyhouse.co

プログラム仕様

WiFi環境の設定について。

  1. WiFi環境の設定については、スマホアプリ「EspTouch」を使い、WiFi経由で外部から設定できるものとします。
  2. ボタン5秒以上長押しで、WiFi環境再設定モードに移行します。
  3. WiFi環境再設定モードではLED赤点滅(1秒間隔)します。
  4. WiFi環境設定は「EspTouch」対応のためSmartConfigにて内部設定します。
  5. WiFi正常接続時はLED青点灯し、接続不可時は黄色点灯します。
  6. WiFi接続状態は随時チェックし切断したら再接続をおこなう。

Web API APIキーについて。

  1. APIキーはATOM Liteデバイスそれぞに個別のAPIキーを持つものとする。
  2. ATOM Liteの標準MACアドレスを使ってAPIキーを紐付けします。
  3. ATOM Liteの標準MACアドレスが、APIキー紐付けテーブルに設定されていない場合は、指定された既定APIキーを使うものとします。

SESAMEスマートロックの状態チェックについて。

  1. SESAMEスマートロックの状態チェックは、Tickerタイマーにより6分に1回おこなう。(1時間に10回)
  2. SESAMEスマートロックの状態が解錠されていればLEDを緑に点灯する。(セサミアプリに準ずる)
  3. SESAMEスマートロックの状態が施錠されていればLEDを赤に点灯する。(セサミアプリに準ずる)
  4. SESAMEスマートロックの状態チェック停止時間を指定できるようにする。
    また、指定時間は日をまたいで指定できるようにする。(22時から6時等)

SESAMEスマートロックの施解錠操作について。

  1. コマンドコードは「88」(Toggle)を使い施解錠をおこなう。
  2. ボタンが押されたらLEDを青点滅する。施解錠操作後処理を1.5秒停止後点滅を停止する。
  3. 施解錠操作後SESAMEスマートロックの状態チェックをおこなう。

使用ライブラリ

使用ライブラリについては記述漏れもあるかとおもいますので、随時必要なライブラリを追加してください。

ほとんどのライブラリは、「ライブラリマネージャー」を使えばインストールできるのですが、一部のライブラリはgitHubからzipファイルをダウンロードしてインストールしなければなりません。

gitHubからのインストール方法

gitHubからのインストール方法を備忘録としてメモっておきます。

gitHubページの「Code」をクリックしてメニューから「Download ZIP」をクリックして、ZIPファイルをダウンロードします。

Arduino IDE で「スケッチ」-「ライブラリをインクルード」-「.ZIP形式のライブラリをインストール…」をクリックして、ダウンロードしたZIPファイルを選択する。

主な使用ライブラリー

M5ATOM

ATOM Lite 用のライブラリー

M5Stack Atom Arduino Library. Contribute to m5stack/M5Atom development by creati…
github.com

NTPClient

NTPサーバーに接続して日付や時間を取得するライブラリー

Connect to a NTP server. Contribute to arduino-libraries/NTPClient development b…
github.com

ArduinoJson

JSON形式のエンコード・デコードを行うライブラリー

📟 JSON library for Arduino and embedded C++. Simple and efficient. – bblanchon/A…
github.com

arduino-AES_CMAC

AESCMAC暗号化ライブラリー

AES-CMAC library for Arduino. Contribute to IndustrialShields/arduino-AES_CMAC d…
github.com

Crypto

Arduino Cryptography Library
Arduino 暗号化ライブラリ arduino-AES_CMAC ライブラリは当ライブラリのAESに依存します。

base64_encode

Base64エンコード・デコードライブラリ

Convert between binary and base64 encoded string. Contribute to dojyorin/arduino…
github.com

サンプルスケッチ説明

前述した、プログラム仕様に基づいてサンプルプログラムを作成いたしました。

数週間自宅で稼働したスケッチを公開しておきます。

サンプルスケッチのダウンロードはこちらからできます。

ArukasSmartKeys.ino
//----------------------------------------------------------------------
//  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行目

C++
// セサミ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行目

C++
const String SESAME_UUID = "スマートロックのUUID";  // スマートロックのUUID
const char SESAME_SECRETKEY[] = "スマートロックのシークレットキー";   // スマートロックのシークレットキー

スマートロック状態チェック処理

スマートロックの状態を一定時間間隔でチェックして、スマートロックの現在の状態をLEDの点滅色で示します。

スマートロック状態チェック設定をします。
ArukasSmartKeys.ino 73行目

C++
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」を使って設定します。

Espressif Systems developed the ESP-TOUCH protocol to seamlessly configure Wi-Fi…
www.espressif.com

Google Play

ESP装置のSmartconfigを利用して、WIFI接続するために使用
play.google.com

App Store

「Espressif Esptouch」のレビューをチェック、カスタマー評価を比較、スクリーンショットと詳細情報を確認することができます。「Espressif …
apps.apple.com

ATOM Liteにスケッチを書き込み後、LEDが赤色点滅している間に、スマホで「ESPTouch」を立ち上げる。
「EspTouch」と「EspTouch V2」の選択で、「EspTouch」を選択する。
「WiFi Password」を設定して「START」をクリックする。
設定に失敗した場合は、ATOM Liteのボタンを5秒以上長押しするか、「リセットボタン」を押して再度設定してください。

一度失敗しても、何度か設定していると必ず設定できます。

設定に成功すると、LEDが青色に点灯します。失敗すると黄色に点灯します。

注意事項

setup()の「おまじない」は、絶対消さないでください。

C++
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使用方法

Web API
doc.candyhouse.co

NGになった元の秒数はどれも暗号化キーが変わる時間までが、タイトな場合が多いようです。
(サンプル抽出数が少ないので絶対原因とは言い切れませんが。)
NTPClientライブラリで取得するタイムスタンプの正確性も関係してきますし、WiFiの通信速度やAPI側のタイムスタンプの正確さも加味されるので、ある程度は仕方がないと諦めるしかないようですね。

NGOKNGOK
タイムスタンプ1685937392168583745416850759281685075975
16進647D5CF0647D5D2E647037D864703807
暗号化キー5C7D645D7D64377064387064
暗号化キーが変わるタイムスタンプ1685937407168593766316850759671685076223
暗号化キーが変わるまでの秒数1520939223

また、一度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の暗号化ライブラリにバグがあるのか原因は掴めていません。

まぁ、「ポチッ」としたら動いたらめっけもん的な、生暖かい目で見守ってください。