Добавление второго сервиса GATT в модуль Movesense - PullRequest
0 голосов
/ 14 октября 2018

Я использую пример кода CustomGATTSvc, чтобы ознакомиться с интерфейсом GATT в модуле Movesense и столкнулся с проблемой при попытке добавить дополнительные службы в код.Мой проект крайне нуждается в способности:

  1. Получить и установить RTC на модуле
  2. Использовать регистратор данных и журнал для хранения и получения данных акселерометра.

Все это должно быть достигнуто с помощью интерфейса GATT, так как мы ищем разработку мобильного приложения в Cordova, которое, как я понимаю, не поддерживает библиотеку Movesense.

В качестве первого шага я попытался добавить в Службу термометра работоспособности вторую службу, которая уже существует в примере кода.Я пытался реализовать службу текущего времени ( все, что связано с службой текущего времени, заключено в #define CURRENT_TIME_SVC ) -

#define THERMOMETER_SERV_UUID           0x1809  // Health Thermometer
#define CURRENT_TIME_SERV_UUID          0x1805  // Current Time Service

const uint16_t measCharUUID16 = 0x2A1C;
const uint16_t intervalCharUUID16 = 0x2A21;
const uint16_t healthThermometerSvcUUID16 = THERMOMETER_SERV_UUID; // Health Temperature probe

#ifdef CURRENT_TIME_SVC
const uint16_t timeCharUUID16 = 0x2A2C; //Random UUID for Current Time
const uint16_t timeSvcUUID16 = CURRENT_TIME_SERV_UUID;  //Current Time
#endif

В функции configGattSvc () я настраиваю службы иследующие характеристики:

  WB_RES::GattSvc customGattThermometerSvc;
  WB_RES::GattChar characteristics[2];
  WB_RES::GattChar &measChar = characteristics[0];
  WB_RES::GattChar &intervalChar = characteristics[1];
  //const uint16_t healthThermometerSvcUUID16 = 0x1809;

#ifdef CURRENT_TIME_SVC
  WB_RES::GattSvc customGattTimeSvc;
  WB_RES::GattChar characteristicsTime[1];
  WB_RES::GattChar &timeChar = characteristicsTime[0];
  //const uint16_t timeSvcUUID16 = 0x1805;
#endif

  // Define the CMD characteristics
  WB_RES::GattProperty measCharProp = WB_RES::GattProperty::INDICATE;
  WB_RES::GattProperty intervalCharProps[2] = {WB_RES::GattProperty::READ, WB_RES::GattProperty::WRITE};
  WB_RES::GattProperty timeCharProps[3] = {WB_RES::GattProperty::READ, WB_RES::GattProperty::WRITE, WB_RES::GattProperty::NOTIFY};

  measChar.props = whiteboard::MakeArray<WB_RES::GattProperty>( &measCharProp, 1);
  measChar.uuid = whiteboard::MakeArray<uint8_t>( reinterpret_cast<const uint8_t*>(&measCharUUID16), 2);

  intervalChar.props = whiteboard::MakeArray<WB_RES::GattProperty>( intervalCharProps, 2);
  intervalChar.uuid = whiteboard::MakeArray<uint8_t>( reinterpret_cast<const uint8_t*>(&intervalCharUUID16), 2);
  intervalChar.initial_value = whiteboard::MakeArray<uint8_t>( reinterpret_cast<const uint8_t*>(&mMeasIntervalSecs), 2);

#ifdef CURRENT_TIME_SVC
  timeChar.props = whiteboard::MakeArray<WB_RES::GattProperty>( timeCharProps, 3);
  timeChar.uuid = whiteboard::MakeArray<uint8_t>( reinterpret_cast<const uint8_t*>(&timeCharUUID16), 2);
  timeChar.initial_value = whiteboard::MakeArray<uint8_t>( reinterpret_cast<const uint8_t*>(&mTimeSecs), 2);
#endif

  // Combine chars to service
  customGattThermometerSvc.uuid = whiteboard::MakeArray<uint8_t>( reinterpret_cast<const uint8_t*>(&healthThermometerSvcUUID16), 2);
  customGattThermometerSvc.chars = whiteboard::MakeArray<WB_RES::GattChar>(characteristics, 2);

  // Create custom service
  asyncPost(WB_RES::LOCAL::COMM_BLE_GATTSVC(), AsyncRequestOptions::Empty, customGattThermometerSvc);

#ifdef CURRENT_TIME_SVC
  // Combine Time chars to service
  customGattTimeSvc.uuid = whiteboard::MakeArray<uint8_t>( reinterpret_cast<const uint8_t*>(&timeSvcUUID16), 2);
  customGattTimeSvc.chars = whiteboard::MakeArray<WB_RES::GattChar>(characteristicsTime, 1);
  // Create custom service
  asyncPost(WB_RES::LOCAL::COMM_BLE_GATTSVC(), AsyncRequestOptions::Empty, customGattTimeSvc);
#endif

В onGetResult я расширил код, включив в него подписку Current Time Service и Characteristic, следующим образом:

void CustomGATTSvcClient::onGetResult(whiteboard::RequestId requestId, whiteboard::ResourceId resourceId, whiteboard::Result resultCode, const whiteboard::Value& rResultData)
{
  DEBUGLOG("CustomGATTSvcClient::onGetResult");
  switch(resourceId.localResourceId)
  {
    case WB_RES::LOCAL::COMM_BLE_GATTSVC_SVCHANDLE::LID:
    {
      const WB_RES::GattSvc &svc = rResultData.convertTo<const WB_RES::GattSvc &>();
      for (size_t i=0; i<svc.chars.size(); i++) {
        const WB_RES::GattChar &c = svc.chars[i];
        uint16_t uuid16 = *reinterpret_cast<const uint16_t*>(&(c.uuid[0]));

        if(uuid16 == measCharUUID16)
        mMeasCharHandle = c.handle.hasValue() ? c.handle.getValue() : 0;
        else if(uuid16 == intervalCharUUID16)
        mIntervalCharHandle = c.handle.hasValue() ? c.handle.getValue() : 0;
        #ifdef CURRENT_TIME_SVC
        else if(uuid16 == timeCharUUID16)
        mTimeCharHandle = c.handle.hasValue() ? c.handle.getValue() : 0;
        #endif
      }

      if (!mIntervalCharHandle || !mMeasCharHandle)
      {
        DEBUGLOG("ERROR: Not all chars were configured!");
        return;
      }
#ifdef CURRENT_TIME_SVC
      if (!mTimeCharHandle)
      {
        DEBUGLOG("ERROR: Not all chars were configured!");
        return;
      }
#endif
      char pathBuffer[32]= {'\0'};
      snprintf(pathBuffer, sizeof(pathBuffer), "/Comm/Ble/GattSvc/%d/%d", mTemperatureSvcHandle, mIntervalCharHandle);
      getResource(pathBuffer, mIntervalCharResource);
      snprintf(pathBuffer, sizeof(pathBuffer), "/Comm/Ble/GattSvc/%d/%d", mTemperatureSvcHandle, mMeasCharHandle);
      getResource(pathBuffer, mMeasCharResource);
#ifdef CURRENT_TIME_SVC
      snprintf(pathBuffer, sizeof(pathBuffer), "/Comm/Ble/GattSvc/%d/%d", mTimeSvcHandle, mTimeCharHandle);
      getResource(pathBuffer, mTimeCharResource);
#endif

      // Subscribe to listen to intervalChar notifications (someone writes new value to intervalChar)
      asyncSubscribe(mIntervalCharResource, AsyncRequestOptions::Empty);
      // Subscribe to listen to measChar notifications (someone enables/disables the INDICATE characteristic)
      asyncSubscribe(mMeasCharResource, AsyncRequestOptions::Empty);
#ifdef CURRENT_TIME_SVC
      // Subscribe to listen to timeChar notifications (someone writes new value to timeChar)
      asyncSubscribe(mTimeCharResource, AsyncRequestOptions::Empty);
#endif
    }
    break;

    case WB_RES::LOCAL::MEAS_TEMP::LID:
    {
      // Temperature result or error
      if (resultCode == whiteboard::HTTP_CODE_OK)
      {
        WB_RES::TemperatureValue value = rResultData.convertTo<WB_RES::TemperatureValue>();
        float temperature = value.measurement;

        // Convert K to C
        temperature -= 273.15;

        // Return data
        //uint8_t buffer[5]; // 1 byte or flags, 4 for FLOAT "in Celsius" value
        uint8_t buffer[5];
        buffer[0]=0;
        // convert normal float to IEEE-11073 "medical" FLOAT type into buffer
        floatToFLOAT(temperature, &buffer[1]);

        // Write the result to measChar. This results INDICATE to be triggered in GATT service
        WB_RES::Characteristic newMeasCharValue;
        newMeasCharValue.bytes = whiteboard::MakeArray<uint8_t>(buffer, sizeof(buffer));
        asyncPut(WB_RES::LOCAL::COMM_BLE_GATTSVC_SVCHANDLE_CHARHANDLE(), AsyncRequestOptions::Empty, mTemperatureSvcHandle,
        mMeasCharHandle, newMeasCharValue);
      }
    }
    break;
#ifdef CURRENT_TIME_SVC
    case WB_RES::LOCAL::TIME::LID:
    {
      // Return with the RTC Time
      if (resultCode == whiteboard::HTTP_CODE_OK)
      {
        WB_RES::DetailedTime value = rResultData.convertTo<WB_RES::DetailedTime>();
        int64 tm = value.utcTime;
        uint8_t buffer[2];
        // Could have an endian issue here, will have to check once connection works
        buffer[0] - (tm&0xFF00)>>8;
        buffer[1] = tm &0xFF;
        // Here we need to get the current time and then Put it back to the device connected

        WB_RES::Characteristic newTimeCharValue;
        newTimeCharValue.bytes = whiteboard::MakeArray<uint8_t>(buffer, sizeof(buffer));
        asyncPut(WB_RES::LOCAL::COMM_BLE_GATTSVC_SVCHANDLE_CHARHANDLE(), AsyncRequestOptions::Empty, mTimeSvcHandle,
        mTimeCharHandle, newTimeCharValue);
      }
    }
    break;
#endif
  }
}

Я добавил Time Local Resource вonGetResult, так как я до сих пор не уверен, как мне удастся «получить» время RTC от стручка.Как получить доступ к ресурсу / Time?

onNotify было изменено следующим образом:

void CustomGATTSvcClient::onNotify(whiteboard::ResourceId resourceId, const whiteboard::Value& value, const whiteboard::ParameterList& rParameters)
{
  switch(resourceId.localResourceId)
  {
    case WB_RES::LOCAL::COMM_BLE_GATTSVC_SVCHANDLE_CHARHANDLE::LID:
    {
      WB_RES::LOCAL::COMM_BLE_GATTSVC_SVCHANDLE_CHARHANDLE::SUBSCRIBE::ParameterListRef parameterRef(rParameters);
      if (parameterRef.getCharHandle() == mIntervalCharHandle)
      {
        const WB_RES::Characteristic &charValue = value.convertTo<const WB_RES::Characteristic &>();
        uint16_t interval = *reinterpret_cast<const uint16_t*>(&charValue.bytes[0]);
        DEBUGLOG(": mMeasCharResource: len: %d, new interval: %d", charValue.bytes.size(), interval);
        // Update the interval
        if (interval >= 1 && interval <= 65535)
        mMeasIntervalSecs = interval;
        // restart timer if exists
        if (mMeasurementTimer != whiteboard::ID_INVALID_TIMER) {
          stopTimer(mMeasurementTimer);
          mMeasurementTimer = startTimer(mMeasIntervalSecs*1000, true);
        }
      }
      else if (parameterRef.getCharHandle() == mMeasCharHandle)
      {
        const WB_RES::Characteristic &charValue = value.convertTo<const WB_RES::Characteristic &>();
        bool bNotificationsEnabled = charValue.notifications.hasValue() ? charValue.notifications.getValue() : false;
        DEBUGLOG(": mMeasCharHandle. bNotificationsEnabled: %d", bNotificationsEnabled);
        // Start or stop the timer
        if (mMeasurementTimer != whiteboard::ID_INVALID_TIMER)
        {
          stopTimer(mMeasurementTimer);
          mMeasurementTimer = whiteboard::ID_INVALID_TIMER;
        }
        if (bNotificationsEnabled)
        mMeasurementTimer = startTimer(mMeasIntervalSecs*1000, true);
      }
#ifdef CURRENT_TIME_SVC
      else if (parameterRef.getCharHandle() == mTimeCharHandle)
      {
        // Received Time information!
        const WB_RES::Characteristic &charValue = value.convertTo<const WB_RES::Characteristic &>();
        uint16_t tm = *reinterpret_cast<const uint16_t*>(&charValue.bytes[0]);
        DEBUGLOG(": mMeasCharResource: len: %d, new interval: %d", charValue.bytes.size(), tm);
        // Update the interval
        mTimeSecs = tm;
      }
#endif
    }
    break;
  }
}

До сих пор я думаю, что код должен быть правильным и работающим, но у меня проблема споследний фрагмент кода из onPostResult:

void CustomGATTSvcClient::onPostResult(whiteboard::RequestId requestId, whiteboard::ResourceId resourceId, whiteboard::Result resultCode, const whiteboard::Value& rResultData)
{
  DEBUGLOG("CustomGATTSvcClient::onPostResult: %d", resultCode);
  if (resultCode == whiteboard::HTTP_CODE_CREATED) {
#if 1
  // This is the code that I propose using when having more than one service but it doesn't seem to work.

    const WB_RES::GattSvc &svc = rResultData.convertTo<const WB_RES::GattSvc &>();
    uint16_t uuid16 = *reinterpret_cast<const uint16_t*>(&(svc.uuid[0]));

    if(uuid16 == healthThermometerSvcUUID16) {
      mTemperatureSvcHandle = (int32_t)rResultData.convertTo<uint16_t>();
      DEBUGLOG("Custom Gatt service was created. handle: %d", mTemperatureSvcHandle);
      asyncGet(WB_RES::LOCAL::COMM_BLE_GATTSVC_SVCHANDLE(), AsyncRequestOptions::Empty, mTemperatureSvcHandle);
    }
    #ifdef CURRENT_TIME_SVC
    else if(uuid16 == timeSvcUUID16) {
      mTimeSvcHandle = (int32_t)rResultData.convertTo<uint16_t>();
      DEBUGLOG("Custom Gatt service was created. handle: %d", mTimeSvcHandle);
      asyncGet(WB_RES::LOCAL::COMM_BLE_GATTSVC_SVCHANDLE(), AsyncRequestOptions::Empty, mTimeSvcHandle);
    }
    #endif
#else
    // This is the code that does work with a single Service but is doesn't work as soon as I add a second service.
    // Custom Gatt service was created
    mTemperatureSvcHandle = (int32_t)rResultData.convertTo<uint16_t>();
    DEBUGLOG("Custom Gatt service was created. handle: %d", mTemperatureSvcHandle);

    // Request more info about created svc so we get the char handles
    asyncGet(WB_RES::LOCAL::COMM_BLE_GATTSVC_SVCHANDLE(), AsyncRequestOptions::Empty, mTemperatureSvcHandle);
#endif
  }
}

Вы увидите, что у меня есть #if 1 .. (code1) .. #else .. (code2) .. #endif в этом фрагменте кода.Код в первом разделе #if 1 (code1) - это то, что я написал для работы с двумя службами GATT, которые создает код.Я чувствую, что, поскольку есть две службы, необходимо провести тест (с UUID службы), чтобы определить, какая служба адресована.Код после #else (code2) - это код исходного кода, который только что использовал временную службу работоспособности.

Когда я компилирую с использованием (code1) , все компилируется нормально, но я не могу подписаться на службу здравоохранения температуры.Когда я переключаюсь на использование (code2) , служба поддержания работоспособности работает нормально, и я могу без проблем подписаться на нее.

Для тестирования интерфейса GATT я использую Bluetility, браузер с низким энергопотреблением Bluetooth.https://github.com/jnross/Bluetility

У меня следующие вопросы:

  1. Весь мой код компилируется нормально, но как только я использую (code1) Я не могуподпишитесь на и получите доступ к сервису Health Temp.Что я делаю неправильно? (code2) не будет работать с более чем одной службой, поскольку она работает только со службой Health Temp.
  2. Я начал реализовывать службу Current Time, но до исправления Q1, выше, я не уверенкак реализовать код для получения и установки текущего времени.
  3. После успешного выполнения вышеизложенного я начну выяснять, как добавить другой сервис, который получит доступ к ресурсу акселерометра ("/ Meas /Acc / 13 "), а также использовать регистратор данных для хранения данных акселерометра и журнала для извлечения их в той же службе GATT.

Я был бы признателен за помощь, которая сможет указать мне правильный путьдля достижения моей конечной цели.Заранее спасибо.

1 Ответ

0 голосов
/ 16 октября 2018

Я думаю, что у вас есть несоответствие типов в обработке ответов.

void CustomGATTSvcClient::onPostResult(...)
    {
      if (resultCode == whiteboard::HTTP_CODE_CREATED) {
      const WB_RES::GattSvc &svc = rResultData.convertTo<const WB_RES::GattSvc &>();

-> https://bitbucket.org/suunto/movesense-device-lib/src/master/MovesenseCoreLib/resources/movesense-api/comm/ble_gattsvc.yaml

определяет / Comm / Ble / GattSvc POST-запрос с кодом 201 (создан) ответтип GattSvcHandle, который является целым числом вместо struct WB_RES :: GattSvc

, следовательно, также не работает следующая логика сравнения.

Предложение решения: Можете ли вы попробовать свою логику сравнения в onPostResult:

if(mTemperatureSvcHandle == 0) {
      mTemperatureSvcHandle = (int32_t)rResultData.convertTo<uint16_t>();
      asyncGet(WB_RES::LOCAL::COMM_BLE_GATTSVC_SVCHANDLE(), AsyncRequestOptions::Empty, mTemperatureSvcHandle);
    }
    else {
      mTimeSvcHandle = (int32_t)rResultData.convertTo<uint16_t>();
      asyncGet(WB_RES::LOCAL::COMM_BLE_GATTSVC_SVCHANDLE(), AsyncRequestOptions::Empty, mTimeSvcHandle);}

(Это решение - просто обходной путь, позволяющий проверить, решает ли это проблемы, возникающие при одновременном запуске двух служб, поскольку это не учитывает асинхронность публикации, и в конце вам, вероятно, потребуется более элегантный способидентификации услуги, связанной с postResult)

...