iPhoneからBLEのCurrent Time Serviceで現在時刻を取得する（M5StickC編）

#概要

M5Stack/M5StickC で現在時刻を取得する方法として NTP ではなく BLE で取得する方法を探していたところ Current Time Service というのがありました。とりあえず Pythonista で試し返ってくる時刻データの内容を確認するためのサンプルプログラムがこちら。
[iPhoneからBLEのCurrent Time Serviceで現在時刻を取得する（Pythonista編）](https://qiita.com/inasawa/items/e1a4a85b9291e3edde84)

M5StickC でも同様にということで試したのですが、いろいろ苦労することになりました。

- スケッチ例の BLE_Client.ino は使いたいサービスをアドバタイズしているサーバに接続する方式ですが、iPhone の Current Time Service はアドバタイズされていない。
- サーバのデバイス名で接続したかったが、スキャンしても名前が取得できない。
- スキャンしたものから iPhone と思われるものに狙いを定めて（TxPower が 12 というのがそれらしかった）接続すると Service および Characteristic は見つかるが readValue しても中身が空。
- ペアリングが必要らしいと悩んでいたところで M5StickC をサーバーとし、iPhone側からペアリングを行なった後に M5StickC をクライアントとしてペアリングした iPhone に接続する例を見つけ参考にさせていただいた。元は別途 NTP などで時刻合わせの想定だったけど、iPhone との BLE連携アプリなので Current Time Service での時刻取得が最適。
 - Notifications From iPhone to ESP32-Watch  
https://www.hackster.io/takeru/notifications-from-iphone-to-esp32-watch-251a6e
- ペアリングしようとしても iPhone の設定アプリの Bluetooth画面に M5StickC のデバイス名が現れない。Bluetooth のテストアプリ（BLE Scanner等）では見え、そこで接続すると時刻取得に成功。
- 一旦成功した後は Bluetooth画面に名前が現れているので、そこで接続操作を行えばよくなる。
- その後、いろいろ試しているうちに成功した方法でも readValue しても中身が空になる現象が発生。ここで他の M5Stack で試したりしたが問題ない。ふと M5Burner で Erase してみたらその後は正常に値が取得できるようになった。

##課題

- 最初のペアリング時に iPhone側の設定アプリの Bluetooth画面に表示されないのはなぜ？        
 - 2014年11月の投稿だが、BLEのデバイスは表示されない、とある。  
https://stackoverflow.com/questions/27211573/bluetooth-low-energy-advertising-to-be-discoverable-in-ios-settings  
Bluetooth Low Energy devices are not discoverable in the Settings->Bluetooth page. This is only for Bluetooth 2.1/3.0 devices such as keyboards and headsets/handsfree devices.
A Bluetooth Low Energy peripheral is only discoverable by an app using Core Bluetooth.

- 最初に投稿した時の TFT_Clock.ino ベースのプログラムでは M5StickC の表示時刻が iPhone側より２秒ほど進んでいた。
 - loop()の中で時刻取得処理を行っていた部分が悪さをしていたようなので、setup()の中で時刻取得を完了させるようにすることで対応。
- Pythonista では iPhone/iPad間でアドバタイズに名前が含まれ、ペアリング無しでデータ取得ができた。iOS間は特別扱い？
 - 以下に、バックグラウンドで動くペリフェラルはローカルネームがアドバタイズされない、とある。  
そして特別な「overflow」エリアに置かれ、iOSデバイスだけが見ることができる。ずるい…。  
Core Bluetooth Programming Guide  
Core Bluetooth Background Execution Modes  
https://developer.apple.com/library/archive/documentation/NetworkingInternetWeb/Conceptual/CoreBluetooth_concepts/CoreBluetoothBackgroundProcessingForIOSApps/PerformingTasksWhileYourAppIsInTheBackground.html#//apple_ref/doc/uid/TP40013257-CH7-SW1

##環境

- iPhone 6 iOS 12.4.2 (BLE Server)
- M5StickC (BLE Client)
 - [Arduino 1.8.10](https://www.arduino.cc/en/Main/Software)
 - ESP32 Arduino 1.0.3


##実行例

1. M5StickC 側のアプリ実行後、iPhone側の設定アプリのBluetooth画面には現れない
1. Bluetooth のテストアプリ（今回は BLE Scanner を使用）で Connect
1. ペアリング要求画面が現れる
1. 以降はBluetooth画面にも表示されるようになる

１． ![2019-10-05-3 BLE Pairing.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133064/fe7dc0f8-2efc-3439-e3dd-bae1bdcbf110.png) ２．![2019-10-05-4 BLE Pairing.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133064/57ec74e6-8e29-8467-b89b-45873d9ec1ea.png) 
３． ![2019-10-05-5 BLE Pairing.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133064/8aab959d-2f55-8320-f510-6b1bcc67eefa.png) ４．![2019-10-05-6 BLE Pairing.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133064/7a77990c-caf0-8f87-c1b1-80b83421daba.png)

実行時のシリアルモニタ出力例

```console:
M5StickC initializing...OK
Start Advertising
MyServerCallbacks Server Connected: xx:xx:xx:xx:xx:xx
MyServerCallbacks Stop Advertising
Client Connecting to Server xx:xx:xx:xx:xx:xx
MyClientCallback Client Connected
Client Connected
Get Current Time from Server
The characteristic value length: 0
Get Current Time from Server
The characteristic value length: 0
Get Current Time from Server
The characteristic value length: 0
Get Current Time from Server
The characteristic value length: 0
Get Current Time from Server
The characteristic value length: 0
Get Current Time from Server
The characteristic value length: 0
Get Current Time from Server
The characteristic value length: 0
Get Current Time from Server
The characteristic value length: 0
Get Current Time from Server
The characteristic value length: 10  <-- ペアリング完了後に値が返る
2019-10-04 21:25:28.984 5 x02
```

TFT_Clock.ino 修正版のアナログ時計。
![2019-10-05-2 TFT_Clock_M5StickC.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133064/4c9ed8f9-e7a9-df4c-7c9e-aad293b85102.jpeg)


RTC.ino 修正版のデジタル時計。
![2019-10-05-1 RTC_M5StickC.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133064/39f7a9c0-492d-494f-9e88-779dc9cb90a7.jpeg)

#M5StickCプログラム
M5StickC用のスケッチ例 TFT_Clock.ino はコンパイル時刻を最初の時刻としますが、BLE Current Time Serviceからの時刻取得処理を追加したプログラムです。

```cpp:TFT_Clock.ino
/*
 An example analogue clock using a TFT LCD screen to show the time
 use of some of the drawing commands with the library.

 For a more accurate clock, it would be better to use the RTClib library.
 But this is just a demo. 
 
 This sketch uses font 4 only.

 Make sure all the display driver and pin comnenctions are correct by
 editting the User_Setup.h file in the TFT_eSPI library folder.

 #########################################################################
 ###### DON'T FORGET TO UPDATE THE User_Setup.h FILE IN THE LIBRARY ######
 #########################################################################
 
 Based on a sketch by Gilchrist 6/2/2014 1.0
 */
#include <M5StickC.h>

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

#define LOCAL_NAME "M5StickC Clock"
static BLEUUID CTSserviceUUID("1805");
static BLEUUID CTScharUUID("2a2b");

BLEServer  *pServer = NULL;
BLEClient  *pClient = NULL;
BLEAddress *pBLEAddress = NULL;

class MyClientCallback : public BLEClientCallbacks {
  void onConnect(BLEClient* pclient) {
    Serial.println("MyClientCallback Client Connected");
  }

  void onDisconnect(BLEClient* pclient) {
    Serial.println("MyClientCallback Client Disconnected");
  }
};

class MyServerCallbacks: public BLEServerCallbacks {
  void onConnect(BLEServer* pServer, esp_ble_gatts_cb_param_t *param) {
    pBLEAddress = new BLEAddress(param->connect.remote_bda);
    Serial.printf("MyServerCallbacks Server Connected: %s\n", pBLEAddress->toString().c_str());
    pServer->getAdvertising()->stop();
    Serial.println("MyServerCallbacks Stop Advertising");
  };

  void onDisconnect(BLEServer* pServer) {
    Serial.println("MyServerCallbacks Server Disconnected");
  }
};
//--------------------------------------------------------------

#define TFT_GREY 0x5AEB

float sx = 0, sy = 1, mx = 1, my = 0, hx = -1, hy = 0;    // Saved H, M, S x & y multipliers
float sdeg=0, mdeg=0, hdeg=0;
uint16_t osx=120, osy=120, omx=120, omy=120, ohx=120, ohy=120;  // Saved H, M, S x & y coords
uint16_t x0=0, x1=0, yy0=0, yy1=0;
uint32_t targetTime = 0;                    // for next 1 second timeout

static uint8_t conv2d(const char* p); // Forward declaration needed for IDE 1.6.x
uint8_t hh=conv2d(__TIME__), mm=conv2d(__TIME__+3), ss=conv2d(__TIME__+6);  // Get H, M, S from compile time

boolean initial = 1;

void setup(void) {
  M5.begin();
  // M5.Lcd.setRotation(0);
  
  //M5.Lcd.fillScreen(TFT_BLACK);
  //M5.Lcd.fillScreen(TFT_RED);
  //M5.Lcd.fillScreen(TFT_GREEN);
  //M5.Lcd.fillScreen(TFT_BLUE);
  //M5.Lcd.fillScreen(TFT_BLACK);
  M5.Lcd.fillScreen(TFT_GREY);
  
  M5.Lcd.setTextColor(TFT_WHITE, TFT_GREY);  // Adding a background colour erases previous text automatically
  
  // Draw clock face
  //M5.Lcd.fillCircle(120, 120, 118, TFT_GREEN);
  //M5.Lcd.fillCircle(120, 120, 110, TFT_BLACK);
  M5.Lcd.fillCircle(40, 40, 40, TFT_GREEN);
  M5.Lcd.fillCircle(40, 40, 36, TFT_BLACK);

  // Draw 12 lines
  for(int i = 0; i<360; i+= 30) {
    sx = cos((i-90)*0.0174532925);
    sy = sin((i-90)*0.0174532925);
    x0 = sx*38+40;
    yy0 = sy*38+40;
    x1 = sx*32+40;
    yy1 = sy*32+40;

    M5.Lcd.drawLine(x0, yy0, x1, yy1, TFT_GREEN);
  }

  // Draw 60 dots
  for(int i = 0; i<360; i+= 6) {
    sx = cos((i-90)*0.0174532925);
    sy = sin((i-90)*0.0174532925);
    x0 = sx*34+40;
    yy0 = sy*34+40;
    // Draw minute markers
    M5.Lcd.drawPixel(x0, yy0, TFT_WHITE);
    
    // Draw main quadrant dots
    if(i==0 || i==180) M5.Lcd.fillCircle(x0, yy0, 2, TFT_WHITE);
    if(i==90 || i==270) M5.Lcd.fillCircle(x0, yy0, 2, TFT_WHITE);
  }

  M5.Lcd.fillCircle(40, 40, 2, TFT_WHITE);

  // Draw text at position 120,260 using fonts 4
  // Only font numbers 2,4,6,7 are valid. Font 6 only contains characters [space] 0 1 2 3 4 5 6 7 8 9 : . - a p m
  // Font 7 is a 7 segment font and only contains characters [space] 0 1 2 3 4 5 6 7 8 9 : .
  //M5.Lcd.drawCentreString("M5Stack",120,260,4);

  //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  //+++++ for Get Current Time from BLE Current Time Service +++++
  //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  BLEDevice::init(LOCAL_NAME);
  BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT);
  pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyServerCallbacks());

  pClient = BLEDevice::createClient();
  pClient->setClientCallbacks(new MyClientCallback());

  pServer->getAdvertising()->start();
  Serial.println("Start Advertising");

  while (pBLEAddress == NULL) {
    delay(100);
  }
  Serial.printf("Client Connecting to Server %s\n", pBLEAddress->toString().c_str());
  pClient->connect(*pBLEAddress);
  Serial.println("Client Connected");
  Serial.println("Get Current Time from Server");
  while (true) {
    std::string value = pClient->getValue(CTSserviceUUID, CTScharUUID);
    Serial.printf("The characteristic value length: %d\n", value.length());
    if (value.length() == 10) {
      // 10 byte data
      // byte 0,1 Year (byte1*256+byte0)
      // byte 2   Month
      // byte 3   Day
      // byte 4   Hours
      // byte 5   Minutes
      // byte 6   Seconds
      // byte 7   Day of Week (Monday = 1, ... Sunday = 7)
      // byte 8   1/256th of a second (milliseconds = byte8*1000/256)
      // byte 9   Adjust Reason
      //          bit 0: Manual time update
      //          bit 1: External reference time update
      //          bit 2: Change of time zone
      //          bit 3: Change of DST (daylight savings time)
      hh = value[4];
      mm = value[5];
      ss = value[6];
      Serial.printf("%d-%02d-%02d %02d:%02d:%02d.%03d %d x%02x\n",
                     value[1] << 8 | value[0], value[2], value[3],
                     value[4], value[5], value[6], value[8]*1000/256, value[7], value[9]);
      break;
    }
    delay(100);
  }
  //--------------------------------------------------------------
  
  targetTime = millis() + 1000; 
}

void loop() {
  if (targetTime < millis()) {
    targetTime += 1000;
    ss++;              // Advance second
    if (ss==60) {
      ss=0;
      mm++;            // Advance minute
      if(mm>59) {
        mm=0;
        hh++;          // Advance hour
        if (hh>23) {
          hh=0;
        }
      }
    }
    
    // Pre-compute hand degrees, x & y coords for a fast screen update
    sdeg = ss*6;                  // 0-59 -> 0-354
    mdeg = mm*6+sdeg*0.01666667;  // 0-59 -> 0-360 - includes seconds
    hdeg = hh*30+mdeg*0.0833333;  // 0-11 -> 0-360 - includes minutes and seconds
    hx = cos((hdeg-90)*0.0174532925);    
    hy = sin((hdeg-90)*0.0174532925);
    mx = cos((mdeg-90)*0.0174532925);    
    my = sin((mdeg-90)*0.0174532925);
    sx = cos((sdeg-90)*0.0174532925);    
    sy = sin((sdeg-90)*0.0174532925);

    if (ss==0 || initial) {
      initial = 0;
      // Erase hour and minute hand positions every minute
      M5.Lcd.drawLine(ohx, ohy, 40, 40, TFT_BLACK);
      ohx = hx*15+40;    
      ohy = hy*15+40;
      M5.Lcd.drawLine(omx, omy, 40, 40, TFT_BLACK);
      omx = mx*20+40;    
      omy = my*20+40;
    }

    // Redraw new hand positions, hour and minute hands not erased here to avoid flicker
    M5.Lcd.drawLine(osx, osy, 40, 40, TFT_BLACK);
    osx = sx*25+40;    
    osy = sy*25+40;
    M5.Lcd.drawLine(osx, osy, 40, 40, TFT_RED);
    M5.Lcd.drawLine(ohx, ohy, 40, 40, TFT_WHITE);
    M5.Lcd.drawLine(omx, omy, 40, 40, TFT_WHITE);
    M5.Lcd.drawLine(osx, osy, 40, 40, TFT_RED);

    M5.Lcd.fillCircle(40, 40, 2, TFT_RED);
  }
}

static uint8_t conv2d(const char* p) {
  uint8_t v = 0;
  if ('0' <= *p && *p <= '9')
    v = *p - '0';
  return 10 * v + *++p - '0';
}
```