Sending commands from nRF52 acting as central to peripheral OBD II BLE Device

I am currently trying to use my nRF52 as a BLE central to connect to a OBD II BLE Device. I am able to use the name scan filters and connect here

case NRF_BLE_SCAN_EVT_FILTER_MATCH:
        {
            // Check if this is the OBD II device by comparing its name
            uint8_t * adv_data = p_adv_report->data.p_data;
            uint16_t adv_data_len = p_adv_report->data.len;

            if(compare_device_name(adv_data, adv_data_len))
            {
                // Initiate connection
                err_code = sd_ble_gap_connect(&p_adv_report->peer_addr,
                                              p_scan_evt->p_scan_params,
                                              &m_scan.conn_params,
                                              APP_BLE_CONN_CFG_TAG);
                APP_ERROR_CHECK(err_code);
            }
            break;

After connecting, I assign the handle here 

case BLE_GAP_EVT_CONNECTED:
            obd_connected = true;
            obd_conn_handle = p_ble_evt->evt.gap_evt.conn_handle;
            NRF_LOG_INFO("Connected to OBD with conn_handle: %d", obd_conn_handle);
            NRF_LOG_INFO("Role: %d", ble_conn_state_role(obd_conn_handle));

            // Start timer to discover services after 5 seconds
            timerStart(TIMER_OBD_SERVICE_DISCO, APP_TIMER_OBD_SERVICE_DISCO_MS);
  
            break;

I then try and write some initialization commands using the ble_nus_send function by sending it the message, length and handle here

ret_code_t ble_send(char* message, uint16_t length, uint16_t conn_handle)
{
    ret_code_t err_code;

    err_code = ble_nus_data_send(&m_nus, message, &length, conn_handle);
    APP_ERROR_CHECK(err_code);
    
    return err_code;
}

This is always returning err_code 8 which says NRF_ERROR_INVALID_STATE and this is where it is failing

if (!p_client->is_notification_enabled)
    {
        return NRF_ERROR_INVALID_STATE;
    }

How would I enable notifications on the client end?

Parents
  • Hello,

    Do you have any information indicating that the OBD II uses the Nordic UART Service (NUS)?

    Also, the API that you are pointing to, bt_nus_data_send() is the API intended for the peripheral, not the central. For a central_uart application, you should use:

    bt_nus_client_send()

    like it is done in the central_uart sample application (ncs\nrf\samples\bluetooth\central_uart).

    So the reason it fails using bt_nus_data_send() is that it assumes that the connected device has enabled notifications to the nRF's Nordic UART service, which is not the case. Typically central's don't have services and characteristics that the peripheral will enable notifications on (subscribe to). 

    So if you want to connect to, and use the OBD II device over BLE, you need to connect to it using e.g. nRF Connect for Desktop -> Bluetooth Low Energy, and look at what Services and Characteristics it has. Then you need to implement a central application (like the central_uart application) that uses those services and characteristics. You can look at the central_uart sample to see how it does service discovery, and how it uses the characteristics that is present in the peripheral_uart sample, and apply this to the services and characteristics that you find on your OBD II.

    Best regards,

    Edvin

  • So basically, I should start here and edit the UUID to use my custom UUID's I am looking for?

    uint32_t ble_nus_c_init(ble_nus_c_t * p_ble_nus_c, ble_nus_c_init_t * p_ble_nus_c_init)
    {
        uint32_t      err_code;
        ble_uuid_t    uart_uuid;
        ble_uuid128_t nus_base_uuid = NUS_BASE_UUID;
    
        VERIFY_PARAM_NOT_NULL(p_ble_nus_c);
        VERIFY_PARAM_NOT_NULL(p_ble_nus_c_init);
        VERIFY_PARAM_NOT_NULL(p_ble_nus_c_init->p_gatt_queue);
    
        err_code = sd_ble_uuid_vs_add(&nus_base_uuid, &p_ble_nus_c->uuid_type);
        VERIFY_SUCCESS(err_code);
    
        uart_uuid.type = p_ble_nus_c->uuid_type;
        uart_uuid.uuid = BLE_UUID_NUS_SERVICE;
    
        p_ble_nus_c->conn_handle           = BLE_CONN_HANDLE_INVALID;
        p_ble_nus_c->evt_handler           = p_ble_nus_c_init->evt_handler;
        p_ble_nus_c->error_handler         = p_ble_nus_c_init->error_handler;
        p_ble_nus_c->handles.nus_tx_handle = BLE_GATT_HANDLE_INVALID;
        p_ble_nus_c->handles.nus_rx_handle = BLE_GATT_HANDLE_INVALID;
        p_ble_nus_c->p_gatt_queue          = p_ble_nus_c_init->p_gatt_queue;
    
        return ble_db_discovery_evt_register(&uart_uuid);
    }
    

    I scanned for the device on the nrf connect app and this is what I got. 

    The service I am interested in is the FFE0 and the characteristic used to read and write is FFE1

  • Strange. It looks like it uses a short UUID (16 bit), but it is not registered with Bluetooth:

    https://www.bluetooth.com/wp-content/uploads/Files/Specification/HTML/Assigned_Numbers/out/en/Assigned_Numbers.pdf?v=1726574177481

    (page 76)

    Perhaps it is possible to purchase a 16-bit UUID and not publish it's use. I don't remember.

    But yes, it looks like the service you are looking for is 0xFFE0, and the characteristics are 0xFFE1 and 0xFFEE. In this case, when it is a 16 bit UUID, I suggest you look into the ble_app_hrs_c example, as this uses a 16 bit UUID. 

    I can't tell from your screenshot, but it looks like the 0xFFE1 is a characteristic on which you would want to enable notifications, and the 0xFFEE is probably one you would want to write to (the arrows in the app will tell you).

    However, on what format the OBDBLE device expects it's data, I don't know. You will have to check with the producer of this device.

    But your steps should be: Look at the ble_app_hrs_c example on how to do discovery on a 16 bit (short) UUID. Then you can look at the ble_app_uart_c example for how to enable notifications, and write to a characteristic. I suspect it is quite similar to the ble_app_uart/ble_app_uart_c application, apart from the 16-bit UUID.

    Best regards,

    Edvin

  • So do I need to add the service UUID here?

    uint32_t ble_obd_c_init(ble_obd_c_t * p_ble_obd_c, ble_obd_c_init_t * p_ble_obd_c_init)
    {
        uint32_t      err_code;
        ble_uuid_t    obd_uuid;
        ble_uuid128_t obd_base_uuid = OBD_BASE_UUID;
    
        VERIFY_PARAM_NOT_NULL(p_ble_obd_c);
        VERIFY_PARAM_NOT_NULL(p_ble_obd_c_init);
        VERIFY_PARAM_NOT_NULL(p_ble_obd_c_init->p_gatt_queue);
    
        err_code = sd_ble_uuid_vs_add(&obd_base_uuid, &p_ble_obd_c->uuid_type);
        VERIFY_SUCCESS(err_code);
    
        obd_uuid.type = p_ble_obd_c->uuid_type;
        obd_uuid.uuid = BLE_UUID_OBD_SERVICE;
    
        p_ble_obd_c->conn_handle           = BLE_CONN_HANDLE_INVALID;
        p_ble_obd_c->evt_handler           = p_ble_obd_c_init->evt_handler;
        p_ble_obd_c->error_handler         = p_ble_obd_c_init->error_handler;
        p_ble_obd_c->handles.obd_tx_handle = BLE_GATT_HANDLE_INVALID;
        p_ble_obd_c->handles.obd_rx_handle = BLE_GATT_HANDLE_INVALID;
        p_ble_obd_c->p_gatt_queue          = p_ble_obd_c_init->p_gatt_queue;
    
        return ble_db_discovery_evt_register(&obd_uuid);
    }

    I have the base UUID, service UUID and characteristic defined as follows:

    #define BASE_UUID {0xFB, 0x34, 0x9B, 0x5F, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}

    #define BLE_UUID_OBD_SERVICE 0xFFE0                  /**< The UUID of the OBD Service. */
    #define BLE_UUID_OBD_CHARACTERISTIC 0xFFE1  /**< The UUID of the RX and TX Characteristic. */

Reply
  • So do I need to add the service UUID here?

    uint32_t ble_obd_c_init(ble_obd_c_t * p_ble_obd_c, ble_obd_c_init_t * p_ble_obd_c_init)
    {
        uint32_t      err_code;
        ble_uuid_t    obd_uuid;
        ble_uuid128_t obd_base_uuid = OBD_BASE_UUID;
    
        VERIFY_PARAM_NOT_NULL(p_ble_obd_c);
        VERIFY_PARAM_NOT_NULL(p_ble_obd_c_init);
        VERIFY_PARAM_NOT_NULL(p_ble_obd_c_init->p_gatt_queue);
    
        err_code = sd_ble_uuid_vs_add(&obd_base_uuid, &p_ble_obd_c->uuid_type);
        VERIFY_SUCCESS(err_code);
    
        obd_uuid.type = p_ble_obd_c->uuid_type;
        obd_uuid.uuid = BLE_UUID_OBD_SERVICE;
    
        p_ble_obd_c->conn_handle           = BLE_CONN_HANDLE_INVALID;
        p_ble_obd_c->evt_handler           = p_ble_obd_c_init->evt_handler;
        p_ble_obd_c->error_handler         = p_ble_obd_c_init->error_handler;
        p_ble_obd_c->handles.obd_tx_handle = BLE_GATT_HANDLE_INVALID;
        p_ble_obd_c->handles.obd_rx_handle = BLE_GATT_HANDLE_INVALID;
        p_ble_obd_c->p_gatt_queue          = p_ble_obd_c_init->p_gatt_queue;
    
        return ble_db_discovery_evt_register(&obd_uuid);
    }

    I have the base UUID, service UUID and characteristic defined as follows:

    #define BASE_UUID {0xFB, 0x34, 0x9B, 0x5F, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}

    #define BLE_UUID_OBD_SERVICE 0xFFE0                  /**< The UUID of the OBD Service. */
    #define BLE_UUID_OBD_CHARACTERISTIC 0xFFE1  /**< The UUID of the RX and TX Characteristic. */

Children
  • yes, but it is not a vendor specific UUID (if it is only 16 bit long). So you can look at the ble_hrs_c_init() function for reference:

    uint32_t ble_hrs_c_init(ble_hrs_c_t * p_ble_hrs_c, ble_hrs_c_init_t * p_ble_hrs_c_init)
    {
        VERIFY_PARAM_NOT_NULL(p_ble_hrs_c);
        VERIFY_PARAM_NOT_NULL(p_ble_hrs_c_init);
    
        ble_uuid_t hrs_uuid;
    
        hrs_uuid.type = BLE_UUID_TYPE_BLE;
        hrs_uuid.uuid = BLE_UUID_HEART_RATE_SERVICE;
    
        p_ble_hrs_c->evt_handler                 = p_ble_hrs_c_init->evt_handler;
        p_ble_hrs_c->error_handler               = p_ble_hrs_c_init->error_handler;
        p_ble_hrs_c->p_gatt_queue                = p_ble_hrs_c_init->p_gatt_queue;
        p_ble_hrs_c->conn_handle                 = BLE_CONN_HANDLE_INVALID;
        p_ble_hrs_c->peer_hrs_db.hrm_cccd_handle = BLE_GATT_HANDLE_INVALID;
        p_ble_hrs_c->peer_hrs_db.hrm_handle      = BLE_GATT_HANDLE_INVALID;
    
        return ble_db_discovery_evt_register(&hrs_uuid);
    }

    As you can see, it is mostly the same, but you don't need the sd_ble_uuid_vs_add() (softdevice_bluetooth low energy_uuid_vendor specific_add())

    Vendor specific refers to 128-bit UUIDs, as the are specified by the vendor. 16 bit uuids are "standard" UUIDs. Look at the ble_app_hrs_c example for reference.

    BR,
    Edvin

Related