概要

GR-LYCHEEとスマホをBLEでつなげてみましょう。GR-LYCHEEにはESP32モジュールを搭載しており、Wi-FiとBLEの接続が可能です。今回はBLEを使って、スマホと通信してみます。


準備

ハードウェア

GR-LYCHEE、USBケーブル(マイクロBタイプ)を準備します。また、BLEが使用できるスマホも準備してください。

ソフトウェア

BLE動作確認用としてiPhoneアプリの「LightBlue」を使用します。


GATTプロファイル

ESP32初期ファームウェアには、次の通りにGATTプロファイルが用意されています。このプロファイルを利用して、GR-LYCHEEとスマートフォンのデータをやりとりします。今回のサンプルではGR-LYCHEEがサーバとなり、スマートフォンがクライアントです。

UUID アクセス・プロパティ サイズ(バイト) Characteristic番号
A002 Read 2 -
C300 Read 1 1
C301 Read 512 2
C302 Write 1 3
C303 Write Without Response 3 4
C304 Write 2 5
C305 Notify 5 6
C306 Indicate 5 7

UUID

Primary Service UUID(0x2800)の設定におけるサービスUUIDとして「A002」が設定されています。続いてCharacteristic宣言(0x2803)で、Characteristic UUID「C300」~「C306」を含めており、各アクセス・プロパティを定めています。

アクセス・プロパティ

Read: クライアントから読み込み可能
Write: クライアントから書き込み可能。書き込みに対して、サーバ(GR-LYCHEE)からのレスポンスがある。
Write Without Response: クライアントからの書き込み可能。書き込みに対して、サーバ(GR-LYCHEE)からのレスポンスはない。
Notify: サーバ(GR-LYCHEE)がクライアントにCharacteristicの変更を通知する。
Indicate: サーバ(GR-LYCHEE)がクライアントにCharacteristicの変更を通知する。Notifyに対して、Indicateはクライアントからの応答も要求する点が異なる。

サイズ

1度に扱えるバイトデータのサイズです。動的設定値の最大長になります。

Characteristic番号

ATコマンド「AT+BLEGATTSCHAR?」で得られる<char_index>の番号です。プログラムでCharacteristicを操作するために使用します。


サンプルプログラム

以下はBLEのサンプルプログラムです。

注意:IDE for GR V1.03ではリンカーオプション"--specs=nano.specs"の影響でパーサーが正しく動作せず、"fail to get service"が発生します。面倒ですが、IDE for GRのフォルダ"ide4gr-1.03\hardware\arduino\rza1lu\"にある"platform.txt"をこちらのplatform.txtファイルと置き換えてください。


#include <Arduino.h>
#include <ATParser_os.h>
#define BLE_NOTIFICATION_CHAR 6
#define BLE_INDICATION_CHAR   7
 
BufferedSerial esp32(P7_1, P0_1, 1024);
ATParser_os esp_parser(esp32);
Semaphore event_sem(0);
 
const char ble_name[] = "GR-LYCHEE";
bool ble_advertizing;
bool ble_connected;
uint8_t ble_conn_index;
uint8_t ble_srv_index;
 
bool button0_flag = false;
bool button1_flag = false;
bool ble_notification_on = false;
bool ble_indication_on = false;
 
void esp32_event() {
  event_sem.release();
}
 
void ble_client_read() {
  uint8_t mac[6] = {
    0  };
 
  digitalWrite(PIN_LED_YELLOW, HIGH);
  esp_parser.recv("%hhd,\"%hhx:%hhx:%hhx:%hhx:%hhx:%hhx\"",
  &ble_conn_index, &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]);
  printf("conn_index=%d, mac[%x:%x:%x:%x:%x:%x]\r\n",
  ble_conn_index, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
  digitalWrite(PIN_LED_YELLOW, LOW);
}
 
void ble_client_write() {
  uint8_t char_index, desc_index = 0;
  uint16_t len;
 
  digitalWrite(PIN_LED_ORANGE, HIGH);
  esp_parser.recv("%hhd,%hhd,%hhd,", &ble_conn_index, &ble_srv_index, &char_index);
  printf("conn=%d srv=%d char=%d\r\n", ble_conn_index, ble_srv_index, char_index);
 
  char c = esp_parser.getc();
  if (c != ',') {
    desc_index = c;
    esp_parser.getc(); // to read ',' after desc_index.
    printf("desc=%d\r\n", desc_index);
  }
 
  esp_parser.recv("%hhd,", &len);
  printf("length=%d\r\n", len);
 
  uint8_t *data = (uint8_t *)malloc(len * sizeof(uint8_t));
  for (int i = 0; i < len; i++) {
    data[i] = esp_parser.getc();
    printf("%x\r\n", data[i]);
  }
 
  if ((desc_index == 49) && (char_index == BLE_NOTIFICATION_CHAR)) {
    if (data[0] == 1) {
      printf("Notification On\r\n");
      ble_notification_on = true;
    } 
    else {
      printf("Notification Off\r\n");
      ble_notification_on = false;
    }
  } 
  else if ((desc_index == 49) && (char_index == BLE_INDICATION_CHAR)) {
    if (data[0] == 2) {
      printf("Indication On\r\n");
      ble_indication_on = true;
    } 
    else {
      printf("Indication Off\r\n");
      ble_indication_on = false;
    }
  }
  digitalWrite(PIN_LED_ORANGE, LOW);
}
 
void ble_client_disconn() {
  digitalWrite(PIN_LED_GREEN, LOW);
  printf("disconnected client\r\n");
  ble_connected = false;
}
 
void ble_client_conn() {
  uint8_t mac[6] = {
    0  };
  esp_parser.recv("%hhd,\"%hhx:%hhx:%hhx:%hhx:%hhx:%hhx\"",
  &ble_conn_index, &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]);
  printf("connected client conn_index is %d, mac[%x:%x:%x:%x:%x:%x]\r\n",
  ble_conn_index, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
 
  digitalWrite(PIN_LED_GREEN, HIGH);
  ble_advertizing = false;
  ble_connected = true;
}
 
void ub0_interrupt() {
  if (button0_flag == false) {
    button0_flag = true;
    event_sem.release();
  }
}
 
void ub1_interrupt() {
  if (button1_flag == false) {
    button1_flag = true;
    event_sem.release();
  }
}
 
void setup() {
  printf("Bluetooth sample started\r\n");
  pinMode(PIN_LED_GREEN, OUTPUT);
  pinMode(PIN_LED_YELLOW, OUTPUT);
  pinMode(PIN_LED_ORANGE, OUTPUT);
  pinMode(PIN_LED_RED, OUTPUT);
  pinMode(PIN_ESP_IO0, OUTPUT);
  pinMode(PIN_ESP_EN, OUTPUT);
  pinMode(PIN_SW0, INPUT);
  pinMode(PIN_SW1, INPUT);
  attachInterrupt(4, ub0_interrupt, FALLING);
  attachInterrupt(3, ub1_interrupt, FALLING);
 
  // Initializing esp32 access
  esp32.baud(115200);
  esp32.attach(Callback<void()>(esp32_event));
  digitalWrite(PIN_ESP_IO0, HIGH);
  digitalWrite(PIN_ESP_EN, LOW);
  delay(10);
  digitalWrite(PIN_ESP_EN, HIGH);
  esp_parser.setTimeout(1500);
  if (esp_parser.recv("ready")) {
    printf("ESP32 ready\r\n");
  } 
  else {
    printf("ESP32 error\r\n");
    digitalWrite(PIN_LED_RED, HIGH);
    while (1);
  }
 
  // Initializing esp32 as a server with GATT service
  esp_parser.setTimeout(5000);
  if (esp_parser.send("AT+BLEINIT=2") && esp_parser.recv("OK")
    && esp_parser.send("AT+BLENAME=\"%s\"", ble_name) && esp_parser.recv("OK")
    && esp_parser.send("AT+BLEGATTSSRVCRE") && esp_parser.recv("OK")
    && esp_parser.send("AT+BLEGATTSSRVSTART") && esp_parser.recv("OK")) {
    printf("GATT initialized\r\n");
  } 
  else {
    printf("fail to initialize\r\n");
    digitalWrite(PIN_LED_RED, HIGH);
    while (1);
  }
  uint8_t start, type;
  uint16_t uuid;
  esp_parser.send("AT+BLEGATTSSRV?");
  if (esp_parser.recv("+BLEGATTSSRV:%hhd,%hhd,%hhx,%hhd\r\nOK", &ble_srv_index, &start, &uuid, &type)) {
    printf("srv_index is %d\r\n", ble_srv_index);
  } 
  else {
    printf("fail to get service\r\n");
    digitalWrite(PIN_LED_RED, HIGH);
  }
 
  esp_parser.oob("+READ:", ble_client_read);
  esp_parser.oob("+WRITE:", ble_client_write);
  esp_parser.oob("+BLEDISCONN:", ble_client_disconn);
  esp_parser.oob("+BLECONN:", ble_client_conn);
}
 
void loop() {
  esp_parser.setTimeout(5000);
  if (esp_parser.send("AT+BLEADVSTART") && esp_parser.recv("OK")) {
    printf("Advertising started. Please connect to any client\r\n");
    ble_advertizing = true;
  } 
  else {
    printf("fail to start advertising\r\n");
    digitalWrite(PIN_LED_RED, HIGH);
    while (1);
  }
 
  while (ble_connected || ble_advertizing) {
    event_sem.wait();
    esp_parser.setTimeout(5);
    esp_parser.recv(" ");  //dummy read for parser callback
 
    // Set attribute of C300
    if (button0_flag) {
      static uint8_t data = 1; // write data
      esp_parser.setTimeout(5000);
      // AT+BLEGATTSSETATTR=<srv_index>,<char_index>[,<desc_index>],<length>
      if (esp_parser.send("AT+BLEGATTSSETATTR=%d,1,,1", ble_srv_index) && esp_parser.recv(">")) {
        if (esp_parser.putc(data) && esp_parser.recv("OK")) {
          printf("success to send\r\n");
        } 
        else {
          printf("fail to send\r\n");
        }
      } 
      else {
        printf("fail to command AT\r\n");
      }
      esp_parser.flush();
      button0_flag = false;
      data++;
    }
 
    // Set notification of C305
    if (button1_flag && ble_notification_on) {
      static uint8_t data = 0xff; // write data
      esp_parser.setTimeout(5000);
      // AT+BLEGATTSNTFY=<conn_index>,<srv_index>,<char_index>,<length>
      if (esp_parser.send("AT+BLEGATTSNTFY=%d,%d,%d,1", ble_conn_index, ble_srv_index, BLE_NOTIFICATION_CHAR)
        && esp_parser.recv(">")) {
        if (esp_parser.putc(data) && esp_parser.recv("OK")) {
          printf("success to notify\r\n");
        } 
        else {
          printf("fail to notify\r\n");
        }
      } 
      else {
        printf("fail to command AT\r\n");
      }
      esp_parser.flush();
      button1_flag = false;
      data--;
    }
  } 
 
}

動作確認

シリアルモニターを起動し、GR-LYCHEEのリセットボタンを押します。スマホアプリのLight Blueでは"GR-LYCHEE"が表示されます。シリアルモニターではGATTのイニシャライズとアドバタイジングがスタートされたことが表示されます。

Light BlueでGR-LYCHEEをタップすると、コネクションが確立され、プロパティが表示されます。また、GR-LYCHEEの緑LEDが点灯します。シリアルモニタでは、コネクション番号(conn_index)とMacアドレスが表示されます。コネクション番号はシングルコネクションでは常に0です。コネクション後に、クライアントがサーバのプロパティを読み込むため、8行に渡ってログが表示されます。

LightBlueのプロパティ一覧で、UUID「C300」をタップすると初期値の0x30が表示されます。GR-LYCHEEのUB0ボタンを押した後、LightBlueで「Read again」を行うと、0x01が書き込まれていることが分かります。書き込まれる値はボタンを押すごとに1を加算しているため、何回か繰り返すと0x02、0x03が順次書き込まれます。例えばGR-LYCHEEにセンサーを接続して、その値を更新すればスマホでセンサーの値を読むことができます。

次にNotificationを試してみます。UUID「C305」を表示して、「Listen for notifications」を押します。その後、GR-LYCHEEのUB1ボタンを押すたびに値が表示されることが分かります。

最後にスマホからGR-LYCHEEにデータを書きます。UUID「C302」を表示して、「Write new value」を押します。下の図では"11"を入力しており、入力が完了するとシリアルモニターに"11"が表示されたことが分かります。