mbl_mw_metawearboard_initialize() keeps printing the status 16 (MBL_MW_STATUS_ERROR_TIMEOUT)

edited May 2020 in C++

Hi all!

I'm writing my first application for using the MMR device, and I'm attempting to initialise the MblMwMetaWearBoard using the C++ SDK (version 0.18.4):

MblMwBtleConnection btle_conn = { nullptr, write_gatt_char, read_gatt_char, enable_char_notify, on_disconnect };
MblMwMetaWearBoard* board = mbl_mw_metawearboard_create(&btle_conn);
mbl_mw_metawearboard_initialize(board, nullptr, [](void* context, MblMwMetaWearBoard* board, int32_t status)
{
    (void) context;
    (void) board;

    if (status)
    {
        std::cout << "Error initialising board: " << status << "\n";
    }
    else
    {
        std::cout << "Board initialised.\n";
    }
});

This code is directly taken from MbientLab's own cppdocs. The first thing I'd like to double-check is that the IF-condition in the lambda function above should read if (status) rather than if (!status), since status should have a value of 0 only if the board is initialised properly, i.e. there might be a typo in the lambda function in the cppdocs?


Secondly, when I run this code, my output is: Error initialising board: 16. Consequently, as expected, mbl_mw_metawearboard_is_initialized(board) will return a value of 0, and any further commands like mbl_mw_sensor_fusion_set_mode(board, MBL_MW_SENSOR_FUSION_MODE_IMU_PLUS) will throw a std::out_of_range exception.

Therefore, I also wish to check that my implementations of the various functions in btle_conn have been written correctly. I especially want to check that my understanding of enable_char_notify is correct, because I've been combing through various sample codes and I've not found that many examples of it.

The only thing that I'm certain about is that my implementation of read_gatt_char works, with the inspiration for highlow_to_uuid taken from here and with the latest version of gattlib-master being used to set up the BTLE connection with the MMR device. I'm also using this sample code as a reference:

static void read_gatt_char(void* context, const void* caller, const MblMwGattChar* characteristic, MblMwFnIntVoidPtrArray handler)
{
    (void) context;

    const uuid_t gatt_char_uuid = highlow_to_uuid(characteristic->uuid_high, characteristic->uuid_low);
    int ret;

    for (int i = 0; i < characteristics_count; ++i)
    {
        ret = gattlib_uuid_cmp(&characteristics[i].uuid, &gatt_char_uuid);
        if (ret == GATTLIB_SUCCESS)
        {
            uint8_t* buffer;
            size_t buffer_len;
            ret = gattlib_read_char_by_uuid(gatt_connection, &characteristics[i].uuid, (void**)&buffer, &buffer_len);
            if (ret != GATTLIB_SUCCESS)
            {
                std::cout << "Read operation failed.\n";
                return;
            }
            std::cout << "Read by UUID completed: ";
            for (size_t j = 0; j < buffer_len; ++j)
            {
                std::cout << (char)(buffer[j]);
            }
            std::cout << "\nRead " << buffer_len << " bytes\n";

            handler(caller, buffer, buffer_len);
            return;
        }
    }

    std::cout << "Characteristic " << std::hex << characteristic->uuid_high
          << " - " << characteristic->uuid_low << std::dec << "does not exist.\n";
}

The reason I know this works is that the output from mbl_mw_metawearboard_initialize() includes all the Gatt characteristics that have been read correctly from my MMR device:

Read by UUID completed: 1.5.0
Read 5 bytes
Read by UUID completed: 5
Read 1 bytes
Read by UUID completed: 0.4
Read 3 bytes
Read by UUID completed: MbientLab Inc
Read 13 bytes
Read by UUID completed: 047D22
Read 6 bytes

As for the rest of the functions, I'm not sure if they currently work as intended. Below is my implementation for write_gatt_char:

static void write_gatt_char(void* context, const void* caller, MblMwGattCharWriteType write_type, const MblMwGattChar* characteristic, const uint8_t* value, uint8_t length)
{
    (void) context;
    (void) caller;
    (void) write_type;

    const uuid_t gatt_char_uuid = highlow_to_uuid(characteristic->uuid_high, characteristic->uuid_low);
    int ret;

    for (int i = 0; i < characteristics_count; ++i)
    {
        ret = gattlib_uuid_cmp(&characteristics[i].uuid, &gatt_char_uuid);
        if (ret == GATTLIB_SUCCESS)
        {
            std::cout << "Write by UUID: ";
            for (size_t j = 0; j < length; ++j)
            {
                std::cout << (int)(value[j]);
            }
            std::cout << "\nWrote " << (size_t)length << " bytes\n";
            ret = gattlib_write_char_by_uuid(gatt_connection, &characteristics[i].uuid, (void*)value, length);
            // ret = gattlib_write_char_by_handle(gatt_connection, characteristics[i].value_handle, (void*)value, length);
            if (ret != GATTLIB_SUCCESS)
            {
                std::cout << "Write operation failed.\n";
            }
            return;
        }
    }

    std::cout << "Characteristic " << std::hex << characteristic->uuid_high
          << " - " << characteristic->uuid_low << std::dec << "does not exist.\n";
}

The resulting output from mbl_mw_metawearboard_initialize() includes this:

Write by UUID: 1128
Wrote 2 bytes

I wanted to ask what exactly this 1128 represents. This would help me understand what exactly needs to be written to the MMR device. A look at the source code says that the related UUID belongs to a METAWEAR_COMMAND_CHAR -- may I ask what exactly does this do?


Below is my implementation for enable_char_notify:

static void enable_char_notify(void* context, const void* caller, const MblMwGattChar* characteristic, MblMwFnIntVoidPtrArray handler, MblMwFnVoidVoidPtrInt ready)
{
    (void) context;

    const uuid_t gatt_char_uuid = highlow_to_uuid(characteristic->uuid_high, characteristic->uuid_low);
    int ret;

    for (int i = 0; i < characteristics_count; ++i)
    {
        ret = gattlib_uuid_cmp(&characteristics[i].uuid, &gatt_char_uuid);
        if (ret == GATTLIB_SUCCESS)
        {
            ret = gattlib_notification_start(gatt_connection, &characteristics[i].uuid);
            notifyHandlers.insert({ caller, handler });
            if (ret != GATTLIB_SUCCESS)
            {
                std::cout << "Enabling notification failed.\n";
                ready(caller, MBL_MW_STATUS_ERROR_ENABLE_NOTIFY);
            }
            else
            {
                ready(caller, MBL_MW_STATUS_OK);
            }
            return;
        }
    }

    std::cout << "Characteristic " << std::hex << characteristic->uuid_high
          << " - " << characteristic->uuid_low << std::dec << "does not exist.\n";
    ready(caller, MBL_MW_STATUS_ERROR_ENABLE_NOTIFY);
}

According to the cppdocs, the handler argument is run every time the specific Gatt characteristic has changed. I wanted to check if I have to call handler anywhere in enable_char_notify. When I looked through the source code of the MetaWear SDK, it seemed to me that read_gatt_char might actually be called indirectly through enable_char_notify -- please correct me if I'm wrong.


Lastly, here is my implementation of on_disconnect:

static void on_disconnect(void* context, const void* caller, MblMwFnVoidVoidPtrInt handler)
{
    // call this handler every time connection is lost, use 0 for 'value' parameter
    (void) context;
    dcHandlers.insert({ caller, handler });
}

I lifted it completely from the cppdocs; it seems trivial so I didn't change a thing -- again, please correct me if my understanding is wrong.

Thanks in advance!
Li

Comments

  • Forgot to mention:

    • PC is Ubuntu 16.04
    • gattlib was built using Linux bluetooth library version 5.54

    Cheers!

  • Hi there! Would it please be possible to get some hints about this issue? I just need to know whether my implementations of write_gatt_char and enable_char_notify functions make sense. Thanks so much in advance!! :)

  • Update: I improved my code but continued to encounter the same problem. Follow-up is here.

  • Hello Liweiyap, did you finally solved the issue? Can you share the code you are using? I am following the examples of the tutorials but nothing works.

  • Hi, sadly, I didn't manage to get the issue resolved; we just gave up and moved on to other things. The furthest I got to is here: https://github.com/liweiyap/metawear-impl

    I made another issue elsewhere on this forum, but Mbientlab never replied.

    The really unfortunate thing is that Mbientlab provides barely any support for customers who want to make use of the C++ API, I mean, relatively to customers who are interested in the Python/Java/Swift APIs. Every time you post questions on this forum, Mbientlab might give a passive-aggressive and brusque response to go read the documentation. However, the code examples provided in the documentation are almost completely useless in helping the customer to understand whatever the heck is going on.

    For example, the read_gatt_char_v2() function in the documentation here. What the hell is this code? It's only when you read the Java/Python equivalents that you realise you have to provide some kind of std::promise function. My suggestion to you would be to try to follow the logic behind the Java/Python equivalents and create something similar in C++, e.g. write closures if you have to. Back then, I tried to but I failed, and I had a deadline to meet anyway; once the deadline passed, I had to move on and try other things (and Mbientlab never replied anyway).

    Ironically, Mbientlab has a damn excellent library for making BT connections and it's called Warble. I personally found much more success in using it compared to bluez on Linux. I just don't understand why Mbientlab can't be bothered to improve the C++ API's documentation with example code that uses Warble. I mean, doesn't Mbientlab want to retain its customers?

  • Actually, if you could get your code to work, could you likewise share it with me? I would be really interested to know where in my own code I went wrong. I'd really appreciate your help :)

  • Thank you Mr Li.
    Reading at your comment, I think I will give up too and continue to develop in Python. The reason to go to C++ was the performance, using compiled program, but I will reduce the amount of calculation needed in my application (a sport analysis tool) to stay with Python. I agree with you, I have seen several posts on this community forum with some strong comments from Mbientlab such as "just read the doc". Obviously it is what any developper is doing, we turn to community only when we can't find or understand the doc.
    Thanks for your support,

  • edited January 3

    Hey sorry about this. I am working on updating the docs but it takes time (I just published the new CPP SDK).

Sign In or Register to comment.