M5Paper で BLE Current Time Service を利用して Real Time Clock を設定する

M5Stack シリーズの一つ M5Paper には real time clock (RTC) BM8563 が搭載されており、M5.RTC ライブラリを利用して時刻の書き込み・読み込みを行うことができる。

この RTC の時刻を合わせる方法として、Wi-Fi を用いて適当な NTP サーバーへ接続し現在時刻を取得する例が Web 上に多く存在するが、Web 接続可能な Wi-Fi が常に使用可能とは限らない。

M5Paper には Bluetooth 機能も付属している。Bluetooth 規格の一種である Bluetooth Low Energy (BLE) には Current Time Service (CTS, リンク先はPDF) という現在時刻を取得できるサービスが存在する。Accessory Design Guidelines for Apple Devices (PDF) には、iOS 7.0 以降は Current Time Service が利用可能であると記載されている。すなわち、iPhone の時計が携帯電話回線経由で常にほぼ正確な時刻に合わせられていることを前提とするならば、M5Paper を BLE で iPhone に接続し、Current Time Service を用いて時刻を取得することで、M5Paper を Wi-Fi に接続することなしにほぼ正確な時刻を取得することが可能となる。

M5StickC を用いて Current Time Service より現在時刻を取得する試みはすでに存在する が、この手法では BLE デバイス検索アプリを別途用意する必要があり、また接続時に iPhone からのペアリング操作が毎回必要であり手間が大きかった。

今回、接続のための専用アプリが不要で、かつ一度ペアリングした以降は再接続時も iPhone 側での操作が不要である ESP32 用 Current Time Service クライアントライブラリ ddlsmurf/ESP32-ANCS-AMS-Notifications を見つけたので、これを紹介するとともに、M5Paper での利用テストプログラムを制作したので公開する。

早速であるが以下がそのプログラムである。公式の RTC 利用例前述のライブラリ を合体させた。初回起動時にはペアリング待ち状態となり、その間に iPhone 側の設定アプリからペアリングを行う。一度ペアリングされた以降は再度同一のプログラムを実行すれば自動で M5Paper が iPhone へペアリングされ時刻情報が取得される。ペアリング情報はおそらく内部的に利用されている ESP32 のライブラリがうまく管理してくれており、スケッチを書き換えても維持されるようだ。

#include <M5EPD.h>

//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//+++++ Get Current Time from BLE Current Time Service +++++
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
#include <esp32notifications.h>

void setupTime(int year, int month, int date, int hour, int minute, int second);

void checkCTSAndUpdateRTC() {
  BLENotifications notifications;
  notifications.begin("CTS_test");  // BLE begin.
  while (1) {
    if (notifications.clientCTS->ready()) break;
    delay(100);
  }
  ble_cts_current_time_char_t *time = notifications.clientCTS->readTime();
  if (time->exact_time_256.seconds < 59) {  // Inclimentating time is complicated ... so forget about seconds = 59.
    delay(int(1000. - float(time->exact_time_256.fractions256) * 1000. / 256.));  // wait until next second.
    time->exact_time_256.seconds += 1;  // incliment.
  }
  setupTime(time->exact_time_256.year, time->exact_time_256.month, time->exact_time_256.day,
            time->exact_time_256.hours, time->exact_time_256.minutes, time->exact_time_256.seconds);
  notifications.stop();  // BLE stop.
}
//-----------------------------------------------------------


//++++++++++++++++++++++++
//+++++ RTC settings +++++
//++++++++++++++++++++++++
M5EPD_Canvas canvas(&M5.EPD);

rtc_time_t RTCtime;
rtc_date_t RTCDate;

char timeStrbuff[64];

void flushTime(){
    M5.RTC.getTime(&RTCtime);
    M5.RTC.getDate(&RTCDate);

    sprintf(timeStrbuff,"%d/%02d/%02d %02d:%02d:%02d",
                        RTCDate.year,RTCDate.mon,RTCDate.day,
                        RTCtime.hour,RTCtime.min,RTCtime.sec);

    canvas.drawString(timeStrbuff, 0, 0);
    canvas.pushCanvas(100,200,UPDATE_MODE_DU4);
}

void setupTime(int year, int month, int date, int hour, int minute, int second){
  RTCtime.hour = hour;
  RTCtime.min = minute;
  RTCtime.sec = second;
  M5.RTC.setTime(&RTCtime);

  RTCDate.year = year;
  RTCDate.mon = month;
  RTCDate.day = date;
  M5.RTC.setDate(&RTCDate);
}
//---------------------------


void setup(void) {
  M5.begin();
  M5.EPD.SetRotation(90);
  M5.EPD.Clear(true);
  M5.RTC.begin();
  canvas.createCanvas(400, 300);
  canvas.setTextSize(3);

  checkCTSAndUpdateRTC();
}

void loop() {
    M5.update();
    if( M5.BtnL.pressedFor(2000))  // shutdown.
    {
        canvas.drawString("Shutdown...", 0, 0);
        canvas.pushCanvas(100,200,UPDATE_MODE_DU4);
        delay(600);
        M5.disableEPDPower();
        M5.disableEXTPower();
        M5.disableMainPower();
        esp_deep_sleep_start();
        while(1);
    }
    flushTime();
    delay(500);
}

ぜひこのコードをご活用いただき、NTP にアクセスできない場所でもほぼ正確な時刻を M5Paper に教えてあげてほしい。

追記: M5Paper_FactoryTest にこの機能を実装した。GitHub の diff を載せる。Setting - Sync Time を押すと同期が実行される。

参考

コードの一部などを以下のサイトから拝借した。