Help us understand the problem. What is going on with this article?

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

概要

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

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

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

課題

環境

  • iPhone 6 iOS 12.4.2 (BLE Server)
  • M5StickC (BLE Client)

実行例

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

1. 2019-10-05-3 BLE Pairing.png 2.2019-10-05-4 BLE Pairing.png
3. 2019-10-05-5 BLE Pairing.png 4.2019-10-05-6 BLE Pairing.png

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

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

RTC.ino 修正版のデジタル時計。
2019-10-05-1 RTC_M5StickC.jpeg

M5StickCプログラム

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

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';
}
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした