2016년 5월 3일 화요일

Gear S2 로 Car Door 열기 - 5편 (Gear S2 app 을 BLE 로 변경)

최초 esp8266 Wifi Module 에서 Bluetooth 4.0 BLE 를 지원하는 HM-10 으로 변경하면서 270 일 이상의 배터리 사용이라는 장점을 얻었지만 gear s2 app 을 다시 만들어야 하는 상황에 처했다.

테스트를 할 때는 Android App 중의 MSMBle 와 BLE Scanner 를 사용했다.
MSMBle 를 사용한 테스트 방법을 잠깐 살펴 보면,

1. App 을 실행하고 Scan 을 하면 BLE 장치가 검색된다.
2. 장치를 선택하면 서비스가 검색된다.
 Writable 한 Custom characteristic 이 노란색으로 표시된다.

 3. 세번째 노란색 characteristic 을 선택하면 Read/Write 가 가능한 field 가 나온다.
 차문을 열기 위해서는 relay 를 on 해야 하므로 PIO 11 번째 pin 을 1 로 바꿔준다.
 배터리를 위해 일정 시간후에 'AT+PIOB0' 으로 pin 을 0 으로 다시 바꿔 줘야 한다.
 이렇게 하면 안드로이드 휴대폰으로는 차문을 열 수 있는데, 나는 gear s2 로 열어야 하니까 이런 동작을 Gear S2 로 할 수 있는지 공부가 필요했다. 기본 Bluetooth 지원이 4.1 이니까 당연히 BLE 를 사용할 수 있을 것이라고 예상 했다.

우선 reference 를 찾아야 했다. tizen 이 그리 널리 사용되지 않아서 그런지 google 신도 어쩔 수 없는 듯 했다. https://developer.tizen.org 에서 dev guide 를 차근차근 찾아 봤다.

Tutorial > Bluetooth : Managing Bluetooth devices
API Ref > Bluetooth, Bluetooth GATT

튜토리얼이 유일한 단서 였다. 그런데 친절하지는 않았다. 그래도 맨 바닥에 헤딩하는 것 보다는 나으니 고마워 해야 했다 ㅠㅜ;;;

구현 순서는 이렇다.
1. bluetooth 를 초기화 한다.
2. le device 를 scan 한다.
3. HM-10 에 gatt connect 를 한다.
4. gatt client 를 만든다.
5. 제공하는 service 를 검색한다.
6. 원하는 service 의 characteristic 을 찾는다.
7. 값을 read 해 본다.
8. 'AT+PIOB1' 을 write 한다.
9. 종료 전 자원 정리.

동작은 명료하고 간단하지만 구현하고 테스트하는데는 엄청난 고통이 수반된다. 이유인 즉슨, Tizen IDE 로 Gear S2 를 사용하려면 Wifi 로 연결해서 debugging 을 하는데, 인터넷에 떠도는 가이드들을 보면 Bluetooth 를 꺼야 한다는 글들이 보인다. 이것으로 짐작되지만 연결이 많이 불안하다. 단순한 connect / disconnect 도 tizen ide 로 debugging 을 하면 제대로 안되는 경우가 많다. app 을 완성하고 wifi 를 끈 상태에서 테스트를 해 보면 정상 동작은 한다. ㅠㅜ.

그럼, line by line 설명을 더하면,
static bool
app_create(void *data)
{
....
   int ret = BT_ERROR_NONE;
1. bluetooth 를 초기화 한다.
   ret = bt_initialize();
   if (ret != BT_ERROR_NONE)
   {
      dlog_print(DLOG_ERROR, LOG_TAG, "[bt_initialize] failed.");
   }
2. le device 를 scan 한다.
   ret = bt_adapter_le_start_scan(__bt_adapter_le_scan_result_cb, NULL);
   if (ret != BT_ERROR_NONE)
      dlog_print(DLOG_ERROR, LOG_TAG, "[bt_adapter_le_start_scan] failed.");
   else
      dlog_print(DLOG_INFO, LOG_TAG, "[bt_adapter_le_start_scan] succeed.");
3. HM-10 에 gatt connect 를 한다.
   ret = bt_gatt_connect("74:DA:EA:B3:44:A3", true);
   if (ret != BT_ERROR_NONE)
      dlog_print(DLOG_ERROR, LOG_TAG, "Failed to connect LE device.");
   bt_gatt_set_connection_state_changed_cb(__bt_gatt_connection_state_changed_cb, ad);
...
}

여기까지 초기화와 기본 설정을 해 주고,

void
__bt_gatt_connection_state_changed_cb(int result, bool connected,
                                      const char *remote_address, void *user_data)
{
   appdata_s *ad = user_data;
   int ret;
   if (connected) {
      dlog_print(DLOG_INFO, LOG_TAG, "LE connected");
      bt_adapter_le_stop_scan();
4. gatt client 를 만든다.
      ret = bt_gatt_client_create("74:DA:EA:B3:44:A3", &ad->cli);
      if (ret == BT_ERROR_NONE)
         dlog_print(DLOG_INFO, LOG_TAG, "bt_gatt_client_create Success");
5. 제공하는 service 를 검색한다.
      ret = bt_gatt_client_foreach_services(ad->cli, __bt_gatt_client_foreach_svc_cb, NULL);
      if (ret != BT_ERROR_NONE)
         dlog_print(DLOG_INFO, LOG_TAG, "failed");
   } else
      dlog_print(DLOG_INFO, LOG_TAG, "LE disconnected");
}

이렇게 gatt client 로 service 검색까지 해 준다.
여기 까지만 해 주면 HM-10 과 연결이 되고 service 검색까지 완료 된다.
이제 남은 것은 characteristic 에 실제 명령어 내려 주는 것.

void
item_clicked_cb(void *data, Evas_Object *obj, void *event_info)
{
...
   Eext_Object_Item * item = eext_rotary_selector_selected_item_get(obj);
   const char *text = eext_rotary_selector_item_part_text_get(item, "selector,main_text");
   if (!strncmp(text, "CAR", sizeof("CAR"))){
      ret = bt_gatt_client_get_service(ad->cli, "0000ffe0-0000-1000-8000-00805f9b34fb", &svc);
      if (ret != BT_ERROR_NONE) {
         dlog_print(DLOG_INFO, LOG_TAG, "bt_gatt_client_get_service failed : %d", ret);
      }
6. 원하는 service 의 characteristic 을 찾는다.
      ret = bt_gatt_service_get_characteristic(svc, "0000ffe1-0000-1000-8000-00805f9b34fb", &chr);
      if (ret != BT_ERROR_NONE) {
         dlog_print(DLOG_INFO, LOG_TAG, "bt_gatt_service_get_characteristic failed : %d", ret);
      }
7. 값을 read 해 본다.
      ret = bt_gatt_client_read_value(chr, __bt_gatt_client_read_complete_cb, NULL);
      if (ret != BT_ERROR_NONE) {
         dlog_print(DLOG_INFO, LOG_TAG, "bt_gatt_client_read_value failed : %d", ret);
         return;
      }
      return;
   }
...
}

이렇게 하면 HM-10 에서 값을 읽어 오고 쓸 준비가 끝났다.

void
__bt_gatt_client_read_complete_cb(int result, bt_gatt_h gatt_handle, void *data)
{
...
   int len;
   int ret = bt_gatt_get_value(gatt_handle, &rvalue, &len);
   if (ret != BT_ERROR_NONE) {
      dlog_print(DLOG_INFO, LOG_TAG, "bt_gatt_get_value failed : %d", ret);
   } else {
      dlog_print(DLOG_INFO, LOG_TAG, "after value : %d: %s", len, rvalue);
8. 'AT+PIOB1' 을 write 한다.
      char wvalue[] = {"AT+PIOB1"};
      ret = __bt_gatt_client_set_value("str", wvalue, gatt_handle);
      if (ret != BT_ERROR_NONE) {
         dlog_print(DLOG_INFO, LOG_TAG, "bt_gatt_set_value failed : %d", ret);
         return;
      }
      ret = bt_gatt_client_write_value(gatt_handle, __bt_gatt_client_write_complete_cb, NULL);
...
}

app 을 종료 할 때 항상 disconnect 해 주고 deinitialize 해 줘야한다. 안 그러면 SLEEP 에 들어 갈 수 없기 때문에 배터리가 오래 못 간다.

static Eina_Bool
_naviframe_pop_cb(void *data, Elm_Object_Item *it)
{
...
9. 종료 전 자원 정리.
   bt_adapter_unset_state_changed_cb();
   bt_adapter_unset_device_discovery_state_changed_cb();
   bt_device_unset_service_searched_cb();
   bt_socket_unset_data_received_cb();
   bt_socket_unset_connection_state_changed_cb();

   dlog_print (DLOG_INFO, LOG_TAG, "bt_gatt_disconnect");
   int ret = bt_gatt_disconnect("74:DA:EA:B3:44:A3");
   if (ret != BT_ERROR_NONE)
      dlog_print(DLOG_ERROR, LOG_TAG, "Failed to connect LE device.");

   dlog_print (DLOG_INFO, LOG_TAG, "bt_deinitialize");
   ret = bt_deinitialize();
   if (ret != BT_ERROR_NONE)
   {
      dlog_print(DLOG_ERROR, LOG_TAG, "[bt_deinitialize] Failed.");
   }
...
}

github 에 정리를 하겠지만, sample app 조차 검색 안되서 정말 고통스러 웠던 시행착오를 다른 사람들은 하지 않기를 바란다;;; 

댓글 없음: