概要
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 (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"が表示されたことが分かります。