Trying to understand BLE UUIDs and handles (with example) - bluetooth-lowenergy

As far as I understand, in BLE, UUIDs are universal IDs that serve to uniquely identify a BLE attribute. They can be 2, 4 or 128 bytes. When they are 2 or 4, the remaining bits until 128 are filled with a standard BLE base UUID: 0000-1000-8000-00805f9b34fb. Is this correct?
Having UUIDs, why are handles needed? From what I have seen in my example, they are displayed as part of the attribute 'identifier', e.g.
# bluetoothctl capture
Primary Service (Handle 0x0920)
/org/bluez/hci0/dev_0C_B8_15_F6_61_3E/service0028
000000ff-0000-1000-8000-00805f9b34fb
Unknown
According to the logs I'm printing (which I will show below), the handle is 28. Is this correct? If so, what is the meaning of that 'Handle 0x0920'?
I have implemented a GATT server example in an ESP32 board. For now, I'm printing some logs to verify that the numbers I see (UUIDs, handles...) match those I see when connecting with bluetoothctl. Here are the logs:
I (1009) ESP32-DHT11: Service (inst. ID 0, uuid ff) created
I (1009) ESP32-DHT11: Service (inst. = 0, uuid = ff) started --> handle = 28
I (1009) ESP32-DHT11: Charac. (uuid ff01) added to service ff
I (1019) ESP32-DHT11: Charac. ff01 --> handle = 2a
I (1019) ESP32-DHT11: Descriptor (uuid 2902) added to service ff
I (1029) ESP32-DHT11: Charac. descr. 2902 --> handle = 2b
I (1029) ESP32-DHT11: Adv. parmeters set successfully
And here is the complete bluetoothctl capture:
Primary Service (Handle 0x0009)
/org/bluez/hci0/dev_0C_B8_15_F6_61_3E/service0001
00001801-0000-1000-8000-00805f9b34fb
Generic Attribute Profile
Characteristic (Handle 0xae24)
/org/bluez/hci0/dev_0C_B8_15_F6_61_3E/service0001/char0002
00002a05-0000-1000-8000-00805f9b34fb
Service Changed
Descriptor (Handle 0x0015)
/org/bluez/hci0/dev_0C_B8_15_F6_61_3E/service0001/char0002/desc0004
00002902-0000-1000-8000-00805f9b34fb
Client Characteristic Configuration
Primary Service (Handle 0x0920)
/org/bluez/hci0/dev_0C_B8_15_F6_61_3E/service0028
000000ff-0000-1000-8000-00805f9b34fb
Unknown
Characteristic (Handle 0x6c84)
/org/bluez/hci0/dev_0C_B8_15_F6_61_3E/service0028/char0029
0000ff01-0000-1000-8000-00805f9b34fb
Unknown
Descriptor (Handle 0x0015)
/org/bluez/hci0/dev_0C_B8_15_F6_61_3E/service0028/char0029/desc002b
00002902-0000-1000-8000-00805f9b34fb
Client Characteristic Configuration
I believe the first three attributes belong to the GAP layer and therefore can be ignored. Let's focus in the 3 next. As you can see, the UUIDs in my logs match those in the bluetoothctl capture. If we consider handles the last part of the attributes identifiers, all of them match save one (/org/bluez/hci0/dev_0C_B8_15_F6_61_3E/service0028/char0029 --> 0x29, which should be 0x2a according to my logs) Why does this happen?
What are exactly the path-like 'identifiers'? (e.g. /org/bluez/hci0/dev_0C_B8_15_F6_61_3E/service0028/char0029)
Code
The source code for this is quite long, so I attach it here at the end:
main.c
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "nvs_flash.h"
#include "nvs.h"
#include "esp_system.h"
#include "esp_log.h"
#include "esp_bt.h"
#include "esp_bt_defs.h"
#include "esp_bt_main.h"
#include "esp_gap_ble_api.h"
#include "esp_gatts_api.h"
#include "esp_gatt_common_api.h"
#include "sdkconfig.h"
#include "app.h"
#define SENSOR_NAME "ESP32-DHT11"
#define TAG SENSOR_NAME
#define ARG_UNUSED(arg) ((void)arg)
static int16_t temp, hum;
static esp_attr_value_t sensor_data = {
.attr_max_len = (uint16_t)sizeof(temp),
.attr_len = (uint16_t)sizeof(temp),
.attr_value = (uint8_t*)(&temp),
};
static void gap_handler(esp_gap_ble_cb_event_t event,
esp_ble_gap_cb_param_t* param)
{
switch (event) {
case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
esp_ble_gap_start_advertising(&adv_params);
break;
default:
break;
}
}
// TODO service_def is global and opaque.
static uint32_t short_uuid(esp_bt_uuid_t uuid)
{
switch (uuid.len) {
case 2:
return uuid.uuid.uuid16;
case 4:
return uuid.uuid.uuid32;
default:
return (uint32_t)-1;
}
}
/**
* #brief Handle for the event REG. Triggered when an app. (a.k.a. profile) is
* registered.
*
*/
static esp_err_t gatts_register_evt_handler(esp_gatts_cb_event_t event,
esp_gatt_if_t gatts_if,
esp_ble_gatts_cb_param_t* param)
{
esp_err_t rc = esp_ble_gatts_create_service(gatts_if,
&service_def.service_id,
GATT_HANDLE_COUNT);
if (rc != ESP_OK) {
return rc;
}
ESP_LOGI(TAG,
"Service (inst. ID %x, uuid %x) created",
service_def.service_id.id.inst_id,
short_uuid(service_def.service_id.id.uuid));
return rc;
}
/**
* #brief Handle for the event CREATE (triggered after creating a GATTS
* service). Stores the handle of the previously created service, starts it and
* adds a characteristic to it.
*
*/
static esp_err_t gatts_create_evt_handler(esp_gatts_cb_event_t event,
esp_gatt_if_t gatts_if,
esp_ble_gatts_cb_param_t* param)
{
ARG_UNUSED(event);
ARG_UNUSED(gatts_if);
service_def.service_handle = param->create.service_handle;
esp_err_t rc = esp_ble_gatts_start_service(service_def.service_handle);
if (rc != ESP_OK) {
return rc;
}
ESP_LOGI(TAG,
"Service (inst. = %x, uuid = %x) started --> handle = %x",
service_def.service_id.id.inst_id,
short_uuid(service_def.service_id.id.uuid),
service_def.service_handle);
rc = esp_ble_gatts_add_char(service_def.service_handle,
&service_def.char_uuid,
ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
ESP_GATT_CHAR_PROP_BIT_READ
| ESP_GATT_CHAR_PROP_BIT_NOTIFY,
&sensor_data,
NULL);
if (rc != ESP_OK) {
return rc;
}
ESP_LOGI(TAG,
"Charac. (uuid %x) added to service %x",
short_uuid(service_def.char_uuid),
short_uuid(service_def.service_id.id.uuid));
return rc;
}
/**
* #brief Handles the event ADD_CHAR, which is triggered after a characteristic
* has been added successfully. When a charac. is added, its handle is generated
* (at runtime). This function gets it and stores it. Also, it adds a charac.
* descriptor to the service.
*
*/
static esp_err_t gatts_add_char_evt_handler(esp_gatts_cb_event_t event,
esp_gatt_if_t gatts_if,
esp_ble_gatts_cb_param_t* param)
{
ARG_UNUSED(event);
ARG_UNUSED(gatts_if);
service_def.char_handle = param->add_char.attr_handle;
ESP_LOGI(TAG,
"Charac. %x --> handle = %x",
short_uuid(service_def.char_uuid),
service_def.char_handle);
esp_err_t rc = esp_ble_gatts_add_char_descr(service_def.service_handle,
&service_def.descr_uuid,
ESP_GATT_PERM_READ
| ESP_GATT_PERM_WRITE,
NULL,
NULL);
if (rc != ESP_OK) {
return rc;
}
ESP_LOGI(TAG,
"Descriptor (uuid %x) added to service %x",
short_uuid(service_def.descr_uuid),
short_uuid(service_def.service_id.id.uuid));
return rc;
}
/**
* #brief Handles the event ADD_CHAR, which is triggered after a descriptor
* has been added successfully. It gets and stores such descriptor handle and
* overrides the default advertising data.
*
*/
static esp_err_t gatts_add_char_descr_evt_handler(
esp_gatts_cb_event_t event,
esp_gatt_if_t gatts_if,
esp_ble_gatts_cb_param_t* param)
{
ARG_UNUSED(event);
ARG_UNUSED(gatts_if);
service_def.descr_handle = param->add_char_descr.attr_handle;
ESP_LOGI(TAG,
"Charac. descr. %x --> handle = %x",
short_uuid(service_def.descr_uuid),
service_def.descr_handle);
esp_err_t rc = esp_ble_gap_config_adv_data(&adv_data);
if (rc != ESP_OK) {
return rc;
}
ESP_LOGI(TAG, "Adv. parmeters set successfully");
return rc;
}
/**
* #brief Handles the event CONNECT, which is triggered by a host connection.
* Updates the current connection params. with those from the incoming
* connection. Also, stores the incoming GATTS interface ID (which signals
* which service is being read) and connection ID.
*
* #todo Does gatts_if actually designate the app./profile rather than the
* service?
*
*/
static esp_err_t gatts_connect_evt_handler(esp_gatts_cb_event_t event,
esp_gatt_if_t gatts_if,
esp_ble_gatts_cb_param_t* param)
{
ARG_UNUSED(event);
update_conn_params(param->connect.remote_bda);
service_def.gatts_if = gatts_if;
service_def.client_write_conn = param->write.conn_id;
ESP_LOGI(TAG,
"Host connected, GATTS if. ID = %x, conn. ID = %x",
service_def.gatts_if,
service_def.client_write_conn);
return ESP_OK;
}
/**
* #brief Handles the event READ, which is triggered by a host read.
*
*/
static esp_err_t gatts_read_evt_handler(esp_gatts_cb_event_t event,
esp_gatt_if_t gatts_if,
esp_ble_gatts_cb_param_t* param)
{
ARG_UNUSED(event);
ESP_LOGI(TAG, "Read on %x detected", param->read.handle);
esp_gatt_rsp_t rsp = {0};
rsp.attr_value.handle = param->read.handle;
rsp.attr_value.len = sensor_data.attr_len;
memcpy(rsp.attr_value.value, sensor_data.attr_value, sensor_data.attr_len);
return esp_ble_gatts_send_response(gatts_if,
param->read.conn_id,
param->read.trans_id,
ESP_GATT_OK,
&rsp);
}
/**
* #brief GATT event handler.
*
* #see esp_ble_gatts_app_register
*/
static void gatt_handler(esp_gatts_cb_event_t event,
esp_gatt_if_t gatts_if,
esp_ble_gatts_cb_param_t* param)
{
esp_err_t rc = ESP_OK;
switch (event) {
case ESP_GATTS_REG_EVT:
rc = gatts_register_evt_handler(event, gatts_if, param);
break;
case ESP_GATTS_CREATE_EVT:
rc = gatts_create_evt_handler(event, gatts_if, param);
break;
case ESP_GATTS_ADD_CHAR_EVT:
rc = gatts_add_char_evt_handler(event, gatts_if, param);
break;
case ESP_GATTS_ADD_CHAR_DESCR_EVT:
rc = gatts_add_char_descr_evt_handler(event, gatts_if, param);
break;
case ESP_GATTS_CONNECT_EVT: {
rc = gatts_connect_evt_handler(event, gatts_if, param);
break;
}
case ESP_GATTS_READ_EVT:
rc = gatts_read_evt_handler(event, gatts_if, param);
break;
case ESP_GATTS_WRITE_EVT: {
ESP_LOGI(TAG,
"ESP_GATTS_WRITE_EVT %x %x",
service_def.descr_handle,
param->write.handle);
if (service_def.descr_handle == param->write.handle) {
uint16_t descr_value =
param->write.value[1] << 8 | param->write.value[0];
if (descr_value != 0x0000) {
ESP_LOGI(TAG, "notify enable");
esp_ble_gatts_send_indicate(gatts_if,
param->write.conn_id,
service_def.char_handle,
sensor_data.attr_len,
sensor_data.attr_value,
false);
} else {
ESP_LOGI(TAG, "notify disable");
}
esp_ble_gatts_send_response(gatts_if,
param->write.conn_id,
param->write.trans_id,
ESP_GATT_OK,
NULL);
} else {
esp_ble_gatts_send_response(gatts_if,
param->write.conn_id,
param->write.trans_id,
ESP_GATT_WRITE_NOT_PERMIT,
NULL);
}
break;
}
case ESP_GATTS_DISCONNECT_EVT:
service_def.gatts_if = 0;
esp_ble_gap_start_advertising(&adv_params);
break;
default:
break;
}
if (rc != ESP_OK) {
ESP_LOGE(TAG, "GATTS error %d", rc);
}
}
static int16_t asd = 0;
static int dht_read_data(int16_t* hum, int16_t* temp)
{
if (asd == 255) {
asd = 0;
} else {
asd++;
}
*hum = asd;
*temp = 2 * asd;
return ESP_OK;
}
static void read_temp_task(void* arg)
{
while (1) {
vTaskDelay(2000 / portTICK_PERIOD_MS);
if (dht_read_data(&hum, &temp) == ESP_OK) {
temp /= 10;
if (service_def.gatts_if > 0) {
esp_ble_gatts_send_indicate(service_def.gatts_if,
service_def.client_write_conn,
service_def.char_handle,
sensor_data.attr_len,
sensor_data.attr_value,
false);
}
} else {
ESP_LOGE(TAG, "DHT11 read failed");
}
}
}
void app_main(void)
{
init_service_def();
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES
|| ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ESP_ERROR_CHECK(nvs_flash_init());
}
esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT);
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
esp_bt_controller_init(&bt_cfg);
esp_bt_controller_enable(ESP_BT_MODE_BLE);
esp_bluedroid_init();
esp_bluedroid_enable();
esp_ble_gap_register_callback(gap_handler);
esp_ble_gatts_register_callback(gatt_handler);
esp_ble_gatts_app_register(0);
xTaskCreate(read_temp_task,
"temp",
configMINIMAL_STACK_SIZE * 3,
NULL,
5,
NULL);
}
app.c
#include "app.h"
#include <string.h>
static uint8_t adv_service_uuid128[32] = {
0xfb,
0x34,
0x9b,
0x5f,
0x80,
0x00,
0x00,
0x80,
0x00,
0x10,
0x00,
0x00,
0xFF,
0x00,
0x00,
0x00,
};
esp_ble_adv_data_t adv_data = {
.set_scan_rsp = false,
.include_name = true,
.include_txpower = false,
.min_interval = 0x0006,
.max_interval = 0x0010,
.appearance = 0x00,
.manufacturer_len = 0,
.p_manufacturer_data = NULL,
.service_data_len = 0,
.p_service_data = NULL,
.service_uuid_len = sizeof(adv_service_uuid128),
.p_service_uuid = adv_service_uuid128,
.flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
};
esp_ble_adv_params_t adv_params = {
.adv_int_min = 0x20,
.adv_int_max = 0x40,
.adv_type = ADV_TYPE_IND,
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
.channel_map = ADV_CHNL_ALL,
.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
};
service_info_t service_def;
void init_service_def(void)
{
service_def.service_id.is_primary = true;
service_def.service_id.id.inst_id = 0x00;
service_def.service_id.id.uuid.len = ESP_UUID_LEN_16;
service_def.service_id.id.uuid.uuid.uuid16 = GATT_SERVICE_UUID;
service_def.char_uuid.len = ESP_UUID_LEN_16;
service_def.char_uuid.uuid.uuid16 = GATT_CHARACTERISTIC_UUID;
service_def.descr_uuid.len = ESP_UUID_LEN_16;
service_def.descr_uuid.uuid.uuid16 = ESP_GATT_UUID_CHAR_CLIENT_CONFIG;
service_def.gatts_if = 0;
}
void update_conn_params(esp_bd_addr_t remote_bda)
{
esp_ble_conn_update_params_t conn_params = {0};
memcpy(conn_params.bda, remote_bda, sizeof(esp_bd_addr_t));
conn_params.latency = 0;
conn_params.max_int = 0x20;
conn_params.min_int = 0x10;
conn_params.timeout = 400;
esp_ble_gap_update_conn_params(&conn_params);
}
app.h
#ifndef gattex_app_h_
#define gattex_app_h_
#include <stdint.h>
#include <stddef.h>
#include "esp_gap_ble_api.h"
#include "esp_gatts_api.h"
#define GATT_SERVICE_UUID 0x00FF
#define GATT_CHARACTERISTIC_UUID 0xFF01
#define GATT_HANDLE_COUNT 4
typedef struct
{
uint16_t service_handle;
esp_gatt_srvc_id_t service_id;
uint16_t char_handle;
esp_bt_uuid_t char_uuid;
uint16_t descr_handle;
esp_bt_uuid_t descr_uuid;
esp_gatt_if_t gatts_if;
uint16_t client_write_conn;
} service_info_t;
extern esp_ble_adv_data_t adv_data;
extern esp_ble_adv_params_t adv_params;
extern service_info_t service_def;
void init_service_def(void);
void update_conn_params(esp_bd_addr_t remote_bda);
#endif

The UUID (e.g. 00001801-0000-1000-8000-00805f9b34fb) is the identifier of the GATT service/characteristic etc. This is used for identification GATT entries at a high level. There is a reserved range for Bluetooth SIG adopted UUIDs. Outside of that range can be used for Custom UUIDs
The handle (e.g. 0x0009) is the identifier for that specific entry in the local GATT database that gets built during service discovery. For example, there might a characteristic UUID used under multiple GATT services. However each database entry will have a different handle.
bluetoothctl communicates with the Bluetooth Daemon using D-Bus for inter process communication (IPC). For D-Bus there are three bits of information that are required: the D-Bus Service Name (org.bluez), the D-Bus interface (e.g. org.bluez.GattService1) and the D-Bus object path (e.g. /org/bluez/hci0/dev_0C_B8_15_F6_61_3E/service0001). The BlueZ API for GATT is documented at: https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/doc/gatt-api.txt

The LE attribute data is stored as a sequence of handles with data as in this example:
Handle
0001 UUID = 2800 (Primary service) Value = 1800 (UUID of the primary service)
The attributes that belong to this primary service follow:
0002 UUID = 2803 (Characteristic info) Value = Handle (0003) permissions and UUID of the following characteristic
0003 UUID = as in previous handle Value = Value of the characteristic
0004 UUID = 2803 (Characteristic info) Value = Handle (0005) permissions and UUID of the following characteristic
0005 UUID = as in previous handle Value = Value of the characteristic
Some characteristics are followed by a descriptor (e.g. notify enable)
0006 UUID = 2803 (Characteristic info) Value = Handle (0007) permissions and UUID of the following characteristic
0007 UUID = as in previous handle Value = Value of the characteristic
0008 UUID = 2902 (Descriptor for notify enable) Value = 00 00 (notify off) or 00 01 (notify on)
There might then be another primary service
0009 UUID = 2800 (Primary service) Value = 1801 (UUID of the primary service)
etc....
A client only needs the value handle (e.g. 0003 above) to read/write the characteristic. If it does not know the handle, it must read this database to find the handle from the UUID. It will read all the handles looking for UUID=2803 entries. A 2-byte UUID (e.g. 1801) may also appear as a 16-byte standard value with the 2 bytes at bytes 3/4: 00001801-0000-1000-8000-00805F9B34FB.
Normally, the client will read the database on connection and set up the dbus objects using the handles it finds. So the first characteristic in the example is a member of the primary service with handle 0001, and has an info handle of 0002, so it will appear as ..../service0001/char0002. It will read and write using handle 0003. The other handles listed first (like Characteristic (Handle 0xae24)) seem to invented by bluetoothctl for its own purposes and are unknown to the LE device, so confusion is created by two types of handle.

Related

ESP-IDF NimBLE Control the Relay and send the relay status to Client

I am so new in BLE and I found a code from github for send and receive ble data.
My question how can I convert the printf("Data from the client: %.*s\n", ctxt->om->om_len, ctxt->om->om_data);receive BLE data.
Its mean ı want to create a pointer for stored this value. Its need to be int or a thing of the hex value.
Thank you for your help.
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_wifi.h"
#include "esp_system.h"
#include "esp_event.h"
#include "nvs_flash.h"
#include "driver/gpio.h"
#include <stdio.h>
#include "esp_log.h"
#include "esp_nimble_hci.h"
#include "nimble/nimble_port.h"
#include "nimble/nimble_port_freertos.h"
#include "host/ble_hs.h"
#include "services/gap/ble_svc_gap.h"
#include "services/gatt/ble_svc_gatt.h"
#include "sdkconfig.h"
#include "string.h"
char *TAG = "BLE-Server";
uint8_t ble_addr_type;
void ble_app_advertise(void);
// Write data to ESP32 defined as server
static int device_write(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg)
{
printf("Data from the client: %.*s\n", ctxt->om->om_len, ctxt->om->om_data);
return 0;
}
// Read data from ESP32 defined as server
static int device_read(uint16_t con_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg)
{
os_mbuf_append(ctxt->om, "Data from the server", strlen("Data from the server"));
return 0;
}
// Array of pointers to other service definitions
// UUID - Universal Unique Identifier
static const struct ble_gatt_svc_def gatt_svcs[] = {
{.type = BLE_GATT_SVC_TYPE_PRIMARY,
.uuid = BLE_UUID16_DECLARE(0x180), // Define UUID for device type
.characteristics = (struct ble_gatt_chr_def[]){
{.uuid = BLE_UUID16_DECLARE(0xFEF4), // Define UUID for reading
.flags = BLE_GATT_CHR_F_READ,
.access_cb = device_read},
{.uuid = BLE_UUID16_DECLARE(0xDEAD), // Define UUID for writing
.flags = BLE_GATT_CHR_F_WRITE,
.access_cb = device_write},
{0}}},
{0}};
// BLE event handling
static int ble_gap_event(struct ble_gap_event *event, void *arg)
{
switch (event->type)
{
// Advertise if connected
case BLE_GAP_EVENT_CONNECT:
ESP_LOGI("GAP", "BLE GAP EVENT CONNECT %s", event->connect.status == 0 ? "OK!" : "FAILED!");
if (event->connect.status != 0)
{
ble_app_advertise();
}
break;
// Advertise again after completion of the event
case BLE_GAP_EVENT_ADV_COMPLETE:
ESP_LOGI("GAP", "BLE GAP EVENT");
ble_app_advertise();
break;
default:
break;
}
return 0;
}
// Define the BLE connection
void ble_app_advertise(void)
{
// GAP - device name definition
struct ble_hs_adv_fields fields;
const char *device_name;
memset(&fields, 0, sizeof(fields));
device_name = ble_svc_gap_device_name(); // Read the BLE device name
fields.name = (uint8_t *)device_name;
fields.name_len = strlen(device_name);
fields.name_is_complete = 1;
ble_gap_adv_set_fields(&fields);
// GAP - device connectivity definition
struct ble_gap_adv_params adv_params;
memset(&adv_params, 0, sizeof(adv_params));
adv_params.conn_mode = BLE_GAP_CONN_MODE_UND; // connectable or non-connectable
adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN; // discoverable or non-discoverable
ble_gap_adv_start(ble_addr_type, NULL, BLE_HS_FOREVER, &adv_params, ble_gap_event, NULL);
}
// The application
void ble_app_on_sync(void)
{
ble_hs_id_infer_auto(0, &ble_addr_type); // Determines the best address type automatically
ble_app_advertise(); // Define the BLE connection
}
// The infinite task
void host_task(void *param)
{
nimble_port_run(); // This function will return only when nimble_port_stop() is executed
}
void app_main(void)
{
nvs_flash_init(); // 1 - Initialize NVS flash using
esp_nimble_hci_and_controller_init(); // 2 - Initialize ESP controller
nimble_port_init(); // 3 - Initialize the host stack
ble_svc_gap_device_name_set("BLE-Server"); // 4 - Initialize NimBLE configuration - server name
ble_svc_gap_init(); // 4 - Initialize NimBLE configuration - gap service
ble_svc_gatt_init(); // 4 - Initialize NimBLE configuration - gatt service
ble_gatts_count_cfg(gatt_svcs); // 4 - Initialize NimBLE configuration - config gatt services
ble_gatts_add_svcs(gatt_svcs); // 4 - Initialize NimBLE configuration - queues gatt services.
ble_hs_cfg.sync_cb = ble_app_on_sync; // 5 - Initialize application
nimble_port_freertos_init(host_task); // 6 - Run the thread
gpio_set_direction(GPIO_NUM_4, GPIO_MODE_OUTPUT);
int level = 0;
while (true) {
gpio_set_level(GPIO_NUM_4, level);
level = !level;
vTaskDelay(300 / portTICK_PERIOD_MS);
}
}
You can just copy the data to an array of bytes/uint8_t and use it.
I think it’s not wise to use a pointer to this data, because it’s not static.
For example:
uint8_t incoming[36];
memcpy(incoming,ctxt->om->om_data,ctxt->om->om_len);
The Results
1st=50 2nd=51 3rd=52 4nd=0
Receive Data = 1234
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_wifi.h"
#include "esp_system.h"
#include "esp_event.h"
#include "nvs_flash.h"
#include "driver/gpio.h"
#include <stdio.h>
#include "esp_log.h"
#include "esp_nimble_hci.h"
#include "nimble/nimble_port.h"
#include "nimble/nimble_port_freertos.h"
#include "host/ble_hs.h"
#include "services/gap/ble_svc_gap.h"
#include "services/gatt/ble_svc_gatt.h"
#include "sdkconfig.h"
#include "string.h"
char *TAG = "BLE-Server";
uint8_t ble_addr_type;
uint8_t rx_data[4];
void ble_app_advertise(void);
// Write data to ESP32 defined as server
static int device_write(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg)
{
printf("Data from the client: %.*s\n", ctxt->om->om_len, ctxt->om->om_data);
memcpy(rx_data,ctxt->om->om_data,ctxt->om->om_len);
return 0;
}
// Read data from ESP32 defined as server
static int device_read(uint16_t con_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg)
{
os_mbuf_append(ctxt->om, "Data from the server", strlen("Data from the server"));
return 0;
}
// Array of pointers to other service definitions
// UUID - Universal Unique Identifier
static const struct ble_gatt_svc_def gatt_svcs[] = {
{.type = BLE_GATT_SVC_TYPE_PRIMARY,
.uuid = BLE_UUID16_DECLARE(0x180), // Define UUID for device type
.characteristics = (struct ble_gatt_chr_def[]){
{.uuid = BLE_UUID16_DECLARE(0xFEF4), // Define UUID for reading
.flags = BLE_GATT_CHR_F_READ,
.access_cb = device_read},
{.uuid = BLE_UUID16_DECLARE(0xDEAD), // Define UUID for writing
.flags = BLE_GATT_CHR_F_WRITE,
.access_cb = device_write},
{0}}},
{0}};
// BLE event handling
static int ble_gap_event(struct ble_gap_event *event, void *arg)
{
switch (event->type)
{
// Advertise if connected
case BLE_GAP_EVENT_CONNECT:
ESP_LOGI("GAP", "BLE GAP EVENT CONNECT %s", event->connect.status == 0 ? "OK!" : "FAILED!");
if (event->connect.status != 0)
{
ble_app_advertise();
}
break;
// Advertise again after completion of the event
case BLE_GAP_EVENT_ADV_COMPLETE:
ESP_LOGI("GAP", "BLE GAP EVENT");
ble_app_advertise();
break;
default:
break;
}
return 0;
}
// Define the BLE connection
void ble_app_advertise(void)
{
// GAP - device name definition
struct ble_hs_adv_fields fields;
const char *device_name;
memset(&fields, 0, sizeof(fields));
device_name = ble_svc_gap_device_name(); // Read the BLE device name
fields.name = (uint8_t *)device_name;
fields.name_len = strlen(device_name);
fields.name_is_complete = 1;
ble_gap_adv_set_fields(&fields);
// GAP - device connectivity definition
struct ble_gap_adv_params adv_params;
memset(&adv_params, 0, sizeof(adv_params));
adv_params.conn_mode = BLE_GAP_CONN_MODE_UND; // connectable or non-connectable
adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN; // discoverable or non-discoverable
ble_gap_adv_start(ble_addr_type, NULL, BLE_HS_FOREVER, &adv_params, ble_gap_event, NULL);
}
// The application
void ble_app_on_sync(void)
{
ble_hs_id_infer_auto(0, &ble_addr_type); // Determines the best address type automatically
ble_app_advertise(); // Define the BLE connection
}
// The infinite task
void host_task(void *param)
{
nimble_port_run(); // This function will return only when nimble_port_stop() is executed
}
void app_main(void)
{
nvs_flash_init(); // 1 - Initialize NVS flash using
esp_nimble_hci_and_controller_init(); // 2 - Initialize ESP controller
nimble_port_init(); // 3 - Initialize the host stack
ble_svc_gap_device_name_set("BLE-Server"); // 4 - Initialize NimBLE configuration - server name
ble_svc_gap_init(); // 4 - Initialize NimBLE configuration - gap service
ble_svc_gatt_init(); // 4 - Initialize NimBLE configuration - gatt service
ble_gatts_count_cfg(gatt_svcs); // 4 - Initialize NimBLE configuration - config gatt services
ble_gatts_add_svcs(gatt_svcs); // 4 - Initialize NimBLE configuration - queues gatt services.
ble_hs_cfg.sync_cb = ble_app_on_sync; // 5 - Initialize application
nimble_port_freertos_init(host_task); // 6 - Run the thread
gpio_set_direction(GPIO_NUM_4, GPIO_MODE_OUTPUT);
int level = 0;
while (true) {
uint8_t ctc_data;
ctc_data=rx_data[2];
gpio_set_level(GPIO_NUM_4, level);
level = !level;
printf("1st=%u 2nd=%u 3rd=%u 4nd=%u\n",rx_data[1],ctc_data,rx_data[3],rx_data[4]);
printf("Receive Data = %.*s\n", 4, rx_data);
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}

What is rosidl_runtime_c__double__Sequence type?

I'm trying to use a teensy 4.1 as an interface between an encoder and ROS thanks to micro-ros (arduino version).
I would like to publish position of a wheel to the /jointState topic with the teensy but there is no example on the micro-ros arduino Github repo.
I've tried to inspect the sensormsgs/msg/jointState message struct but everything is a bit fuzzy and I don't understand how to make it works. I can't understand what is rosidl_runtime_c__double__Sequence type.
I've tried several things but I always get an error about operand types
no match for 'operator=' (operand types are 'rosidl_runtime_c__String' and 'const char [18]')
msg.name.data[0] = "drivewhl_1g_joint";
Here is my arduino code
#include <micro_ros_arduino.h>
#include <stdio.h>
#include <rcl/rcl.h>
#include <rcl/error_handling.h>
#include <rclc/rclc.h>
#include <rclc/executor.h>
#include <sensor_msgs/msg/joint_state.h>
rcl_publisher_t publisher;
sensor_msgs__msg__JointState msg;
rclc_executor_t executor;
rclc_support_t support;
rcl_allocator_t allocator;
rcl_node_t node;
rcl_timer_t timer;
#define LED_PIN 13
#define RCCHECK(fn) { rcl_ret_t temp_rc = fn; if((temp_rc != RCL_RET_OK)){error_loop();}}
#define RCSOFTCHECK(fn) { rcl_ret_t temp_rc = fn; if((temp_rc != RCL_RET_OK)){}}
void error_loop(){
while(1){
digitalWrite(LED_PIN, !digitalRead(LED_PIN));
delay(100);
}
}
void timer_callback(rcl_timer_t * timer, int64_t last_call_time)
{
RCLC_UNUSED(last_call_time);
if (timer != NULL) {
RCSOFTCHECK(rcl_publish(&publisher, &msg, NULL));
//
//Do not work
//msg.name=["drivewhl_1g_joint","drivewhl_1d_joint","drivewhl_2g_joint","drivewhl_2d_joint"];
//msg.position=["1.3","0.2", "0","0"];
msg.name.size = 1;
msg.name.data[0] = "drivewhl_1g_joint";
msg.position.size = 1;
msg.position.data[0] = 1.85;
}
}
void setup() {
set_microros_transports();
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, HIGH);
delay(2000);
allocator = rcl_get_default_allocator();
//create init_options
RCCHECK(rclc_support_init(&support, 0, NULL, &allocator));
// create node
RCCHECK(rclc_node_init_default(&node, "micro_ros_arduino_node", "", &support));
// create publisher
RCCHECK(rclc_publisher_init_default(
&publisher,
&node,
ROSIDL_GET_MSG_TYPE_SUPPORT(sensor_msgs, msg, JointState),
"JointState"));
// create timer,
const unsigned int timer_timeout = 1000;
RCCHECK(rclc_timer_init_default(
&timer,
&support,
RCL_MS_TO_NS(timer_timeout),
timer_callback));
// create executor
RCCHECK(rclc_executor_init(&executor, &support.context, 1, &allocator));
RCCHECK(rclc_executor_add_timer(&executor, &timer));
}
void loop() {
delay(100);
RCSOFTCHECK(rclc_executor_spin_some(&executor, RCL_MS_TO_NS(100)));
}
I'm a beginner with Ros and C, it may be a very dumb question but I don't know how to solve it. Thanks for your help !
rosidl_runtime_c__String__Sequence is a structure used to old string data that is to be transmitted. Specifically it is a sequence of rosidl_runtime_c__String data. You're running into an error because rosidl_runtime_c__String is also a struct itself with no custom operators defined. Thus, your assignment fails since the types are not directly convertible. What you need to do instead is use the rosidl_runtime_c__String.data field. You can see slightly more info here
void timer_callback(rcl_timer_t * timer, int64_t last_call_time)
{
RCLC_UNUSED(last_call_time);
if (timer != NULL) {
//msg.name=["drivewhl_1g_joint","drivewhl_1d_joint","drivewhl_2g_joint","drivewhl_2d_joint"];
//msg.position=["1.3","0.2", "0","0"];
msg.name.size = 1;
msg.name.data[0].data = "drivewhl_1g_joint";
msg.name.data[0].size = 17; //Size in bytes excluding null terminator
msg.position.size = 1;
msg.position.data[0] = 1.85;
RCSOFTCHECK(rcl_publish(&publisher, &msg, NULL));
}
}
I also spent quite some time trying to get publishing JointState message from my esp32 running microros, and also couldn't find working example. Finally, i was successful, maybe it will help someone.
In simple words:
.capacity contains max number of elements
.size contains actual number of elements (strlen in case of string)
.data should be allocated as using malloc as .capacity * sizeof()
each string within sequence should be allocated separately
This is my code that allocates memory for 12 joints, named j0-j11. Good luck!
...
// Declarations
rcl_publisher_t pub_joint;
sensor_msgs__msg__JointState joint_state_msg;
...
// Create publisher
RCCHECK(rclc_publisher_init_default(&pub_joint, &node,
ROSIDL_GET_MSG_TYPE_SUPPORT(sensor_msgs, msg, JointState),
"/hexapod/joint_state"));
//Allocate memory
joint_state_msg.name.capacity = 12;
joint_state_msg.name.size = 12;
joint_state_msg.name.data = (std_msgs__msg__String*) malloc(joint_state_msg.name.capacity*sizeof(std_msgs__msg__String));
for(int i=0;i<12;i++) {
joint_state_msg.name.data[i].data = malloc(5);
joint_state_msg.name.data[i].capacity = 5;
sprintf(joint_state_msg.name.data[i].data,"j%d",i);
joint_state_msg.name.data[i].size = strlen(joint_state_msg.name.data[i].data);
}
joint_state_msg.position.size=12;
joint_state_msg.position.capacity=12;
joint_state_msg.position.data = malloc(joint_state_msg.position.capacity*sizeof(double));
joint_state_msg.velocity.size=12;
joint_state_msg.velocity.capacity=12;
joint_state_msg.velocity.data = malloc(joint_state_msg.velocity.capacity*sizeof(double));
joint_state_msg.effort.size=12;
joint_state_msg.effort.capacity=12;
joint_state_msg.effort.data = malloc(joint_state_msg.effort.capacity*sizeof(double));
for(int i=0;i<12;i++) {
joint_state_msg.position.data[i]=0.0;
joint_state_msg.velocity.data[i]=0.0;
joint_state_msg.effort.data[i]=0.0;
}
....
//Publish
RCSOFTCHECK(rcl_publish(&pub_joint, &joint_state_msg, NULL));

Qt Wrapper class on libWebSockets - not emitting a signal from the callback method

I am writing a WebSocket client application, based on the example, where the client application needs to pass the WebSocket Protocol while establishing connection with the server. Since QtWebSockets does not support the Websocket protocols, I am writing a C++ wrapper to use libWebSockets library and emit
connected, disconnected, textReceived kind of signals similar to QWebSocket.
My client application is able to connect to the server and is receiving the text message from the server, and I have copied minimum example code below, here I am facing an issue that when I emit a signal from the callback function the signal is not actually published and the slot I have connected to this signal never executed. I have verified that the object address passed in the session data is correct (copied the Logs of my program). What am I doing wrong here.
WebSocket.h
class WebSocket : public QObject
{
Q_OBJECT
public:
WebSocket(QObject *parent = Q_NULLPTR);
~WebSocket();
signals:
void connected();
void disconnected();
void textMessageReceived(const QString &message);
private:
static int callback_dumb_increment(struct lws *wsi, enum lws_callback_reasons reason,
void *user, void *in, size_t len);
struct lws_context *context;
struct lws *client_wsi;
static const struct lws_protocols protocols[];
};
WebSocket.cpp
const struct lws_protocols WebSocket::protocols[] = {
{
"dumb_protocol",
callback_dumb_increment,
sizeof(per_session_data),
0,
},
{ NULL, NULL, 0, 0 }
};
WebSocket::WebSocket(QObject *parent) : QObject(parent)
{
struct lws_context_creation_info info;
struct lws_client_connect_info i;
int n = 0;
memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */
info.protocols = protocols;
qDebug() << "[parent] address: " << this;
info.user = this;
/*
* since we know this lws context is only ever going to be used with
* one client wsis / fds / sockets at a time, let lws know it doesn't
* have to use the default allocations for fd tables up to ulimit -n.
* It will just allocate for 1 internal and 1 (+ 1 http2 nwsi) that we
* will use.
*/
info.fd_limit_per_thread = 1 + 1 + 1;
context = lws_create_context(&info);
if (!context) {
qDebug() << "lws init failed";
}
memset(&i, 0, sizeof i); /* otherwise uninitialized garbage */
i.context = context;
i.port = 7070;
i.address = "localhost";
i.path = "/";
i.host = i.address;
i.origin = i.address;
i.pwsi = &client_wsi;
lws_client_connect_via_info(&i);
while (n >= 0 && client_wsi)
n = lws_service(context, 0);
}
int WebSocket::callback_dumb_increment( struct lws *wsi, enum lws_callback_reasons reason,
void *user, void *in, size_t len )
{
/* This will be same for every connected peer */
void *userdata = lws_context_user(lws_get_context(wsi));
qDebug() << "userData address: " << userdata;
QString message = "";
switch (reason) {
case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
if (in)
qDebug() << "CLIENT_CONNECTION_ERROR: " << (char *)in;
else
qDebug() << "CLIENT_CONNECTION_ERROR: (null)";
wsi = NULL;
break;
case LWS_CALLBACK_CLIENT_ESTABLISHED:
qDebug() << __func__ << " established";
emit static_cast<WebSocket*>(userdata)->connected();
break;
case LWS_CALLBACK_CLIENT_RECEIVE:
message = QString::fromUtf8((const char *)in);
qDebug() << "RX: " << message;
emit static_cast<WebSocket*>(userdata)->textMessageReceived(message);
break;
case LWS_CALLBACK_CLIENT_CLOSED:
wsi = NULL;
emit static_cast<WebSocket*>(userdata)->disconnected();
break;
default:
break;
}
return lws_callback_http_dummy(wsi, reason, user, in, len);
}
SocketClient.h
class SocketClient : public QObject
{
Q_OBJECT
public:
explicit SocketClient(QObject *parent = Q_NULLPTR);
~SocketClient();
public slots:
void onConnected();
void onTextMessageReceived(QString message);
private:
std::shared_ptr<WebSocket> webSock = nullptr;
};
SocketClient.cpp
SocketClient::SocketClient(QObject *parent) :QObject(parent)
{
webSock = std::make_shared<WebSocket>(this);
connect(webSock .get(), &WebSocket::connected, this, &SocketClient::onConnected);
connect(webSock .get(), &WebSocket::textMessageReceived,
this, &SocketClient::onTextMessageReceived);
}
Logs:
[parent] address: WebSocket(0x1b2cae32140)
userData address: 0x1b2cae32140
WebSocket::callback_dumb_increment established
userData address: 0x1b2cae32140
RX: "Hello World"

Capturing berr-counter tx/rx from ip link show

I would like to be able to capture the berr-counter values in a shell script. I can view the values with:
ip -det link show can0 which gives:
2: can0: <NOARP,ECHO> mtu 16 qdisc pfifo_fast state DOWN mode DEFAULT group default qlen 1000
link/can promiscuity 0
can state STOPPED (berr-counter tx 144 rx 128) restart-ms 100
bitrate 125000 sample-point 0.866
tq 133 prop-seg 6 phase-seg1 6 phase-seg2 2 sjw 1
flexcan: tseg1 4..16 tseg2 2..8 sjw 1..4 brp 1..256 brp-inc 1
clock 30000000
I could just parse this output and capture the tx/rx berr-counter, but I would rather capture these values directly. So, I have been trying find where to access these values. I dug into https://github.com/shemminger/iproute2 's code and found where these values are being printed in ip/iplink_can.c in the function:
static void can_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
There is the code:
if (tb[IFLA_CAN_BERR_COUNTER]) {
struct can_berr_counter *bc =
RTA_DATA(tb[IFLA_CAN_BERR_COUNTER]);
fprintf(f, "(berr-counter tx %d rx %d) ", bc->txerr, bc->rxerr);
}
And at the bottom of the same file there is a struct:
struct link_util can_link_util = {
.id = "can",
.maxattr = IFLA_CAN_MAX,
.parse_opt = can_parse_opt,
.print_opt = can_print_opt,
.print_xstats = can_print_xstats,
.print_help = can_print_help,
};
But I can't find anywhere where can_print_opt, or can_link_util.print_opt are called, and I haven't found any success sifting through all of the struct rtattr in the repo.
I'm not sure where to go from here to get these values other than just grabbing them from the output of ip -det link show can0
Maybe a little bit late, but I was trying the same thing : access CAN interface state and error counters from within a userspace application, without calling ip and parsing output.
As you did, I explored iproute2's code, and then read some documentation about netlink for interacting with network devices. Mainly what you have to do is to send an RTM_GETLINK message to a netlink socket, then parse the response, that is a nested list of netlink attributes.
I found this very interesting starting point : http://iijean.blogspot.com/2010/03/howto-get-list-of-network-interfaces-in.html
In this blog the link to full code is broken, but it's available here : https://gist.github.com/cl4u2/5204374.
Note that instead of doing all this "manually", it is also possible to use libnetlink.
Based on this, I was able to write a test code - quick and dirty - that does what you want. You only need to determine my ifIndex_ variable, which is the integer index of your CAN network interface (can be determined by a SIOCGIFINDEX ioctl on your socketcan socket).
printf("Starting rtnetlink stats reading ...\n");
struct sockaddr_nl local;
struct {
struct nlmsghdr nlh;
struct ifinfomsg ifinfo;
} request;
struct sockaddr_nl kernel;
struct msghdr rtnl_msg;
struct iovec io;
pid_t pid = getpid();
qint64 rtnetlink_socket = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
memset(&local, 0, sizeof(local));
local.nl_family = AF_NETLINK;
local.nl_pid = pid;
local.nl_groups = 0;
if (bind(rtnetlink_socket, (struct sockaddr *) &local, sizeof(local)) < 0) {
printf("Binding failed !\n");
return true;
}
printf("Binding successful.\n");
memset(&rtnl_msg, 0, sizeof(rtnl_msg));
memset(&kernel, 0, sizeof(kernel));
memset(&request, 0, sizeof(request));
kernel.nl_family = AF_NETLINK;
request.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
request.nlh.nlmsg_type = RTM_GETLINK;
request.nlh.nlmsg_flags = NLM_F_REQUEST; // NLM_F_ROOT|NLM_F_MATCH| were originally specified and return all interfaces.
request.nlh.nlmsg_pid = pid;
request.nlh.nlmsg_seq = 1; // Must be monotonically increasing, but we send only one.
// Interface is specified only with index.
request.ifinfo.ifi_family = AF_PACKET;
request.ifinfo.ifi_index = ifIndex_;
request.ifinfo.ifi_change = 0;
io.iov_base = &request;
io.iov_len = request.nlh.nlmsg_len;
rtnl_msg.msg_iov = &io;
rtnl_msg.msg_iovlen = 1;
rtnl_msg.msg_name = &kernel;
rtnl_msg.msg_namelen = sizeof(kernel);
if (sendmsg(rtnetlink_socket, &rtnl_msg, 0) < 0) {
printf("Sendmsg finished with an error.\n");
return true;
}
printf("Sendmsg finished successfully.\n");
// Reply reception
int end = 0;
int replyMaxSize = 8192;
char reply[replyMaxSize];
while (!end) {
int len;
struct nlmsghdr *msg_ptr;
struct msghdr rtnl_reply;
struct iovec io_reply;
memset(&io_reply, 0, sizeof(io_reply));
memset(&rtnl_reply, 0, sizeof(rtnl_reply));
io.iov_base = reply;
io.iov_len = replyMaxSize;
rtnl_reply.msg_iov = &io;
rtnl_reply.msg_iovlen = 1;
rtnl_reply.msg_name = &kernel;
rtnl_reply.msg_namelen = sizeof(kernel);
printf("Waiting for data ...\n");
len = recvmsg(rtnetlink_socket, &rtnl_reply, 0);
printf("Received data with length %d.\n", len);
if (len) {
for (msg_ptr = (struct nlmsghdr *) reply; NLMSG_OK(msg_ptr, len); msg_ptr = NLMSG_NEXT(msg_ptr, len)) {
switch(msg_ptr->nlmsg_type) {
case NLMSG_DONE:
end++;
printf("Received NLMSG_DONE end message.\n");
break;
case RTM_NEWLINK:
printf("Received RTM_NEWLINK message with multipart flag : %d.\n", msg_ptr->nlmsg_flags & NLM_F_MULTI);
if (!(msg_ptr->nlmsg_flags & NLM_F_MULTI)) { end++; }
struct ifinfomsg *iface;
struct rtattr *attribute;
struct rtattr *subAttr;
int msgLen, attrPayloadLen;
iface = (struct ifinfomsg*)NLMSG_DATA(msg_ptr);
msgLen = msg_ptr->nlmsg_len - NLMSG_LENGTH(sizeof(*iface));
for (attribute = IFLA_RTA(iface); RTA_OK(attribute, msgLen); attribute = RTA_NEXT(attribute, msgLen)) {
switch(attribute->rta_type) {
case IFLA_IFNAME:
printf("Interface %d name : %s\n", iface->ifi_index, (char *) RTA_DATA(attribute));
break;
case IFLA_LINKINFO:
attrPayloadLen = RTA_PAYLOAD(attribute);
printf("Found link information. Parsing %d payload bytes ...\n", attrPayloadLen);
for (subAttr = (struct rtattr *)RTA_DATA(attribute); RTA_OK(subAttr, attrPayloadLen); subAttr = RTA_NEXT(subAttr, attrPayloadLen)) {
struct rtattr *subSubAttr;
int subAttrPayloadLen = RTA_PAYLOAD(subAttr);
printf("Found sub-attribute. Type : %d, length : %d.\n", subAttr->rta_type, subAttr->rta_len);
switch (subAttr->rta_type) {
case IFLA_INFO_KIND:
printf("\t Link kind : %s.\n", (char *) RTA_DATA(subAttr));
break;
case IFLA_INFO_DATA:
printf("Found link information data. Parsing %d payload bytes ...\n", RTA_PAYLOAD(subAttr));
for (subSubAttr = (struct rtattr *)RTA_DATA(subAttr); RTA_OK(subSubAttr, subAttrPayloadLen); subSubAttr = RTA_NEXT(subSubAttr, subAttrPayloadLen)) {
printf("Found sub-sub-attribute. Type : %d, length : %d.\n", subSubAttr->rta_type, subSubAttr->rta_len);
switch (subSubAttr->rta_type) {
case IFLA_CAN_STATE:
{
int state = *(int *)RTA_DATA(subSubAttr);
printf("State : %d\n", state);
break;
}
case IFLA_CAN_BERR_COUNTER:
{
struct can_berr_counter *bc = (struct can_berr_counter *)RTA_DATA(subSubAttr);
printf("Error counters : (berr-counter tx %d rx %d)\n", bc->txerr, bc->rxerr);
break;
}
default:
break;
}
}
break;
case IFLA_INFO_XSTATS:
default:
break;
}
}
break;
default:
printf("New attribute. Type : %d, length : %d.\n", attribute->rta_type, attribute->rta_len);
break;
}
}
printf("Finished parsing attributes.\n");
break;
case NLMSG_ERROR:
printf("Could not read link details for interface %d.\n", ifIndex_);
end++;
break;
default:
printf("Received unexpected message ID : %d.\n", msg_ptr->nlmsg_type);
break;
}
printf("Finished parsing message.\n");
}
printf("Finished parsing data.\n");
}
}
close(rtnetlink_socket);
return true;

Using BPF/XDP with Mininet

I've created the following network topology in Mininet to run an algorithm I've implemented using the Linux kernel eXpress Data Path.
The objective is to sample packets on the incoming link s1-eth1 on Switch 1 using XDP and store metadata in a shared BPF map. The execution is successful when run on multiple VMs (instead of using Mininet to create an emulation).
However, when using XDP on Mininet (to listen on the emulated network interface), packets aren't recorded.
To further diagnose the cause, I ran Wireshark to listen on the s1-eth1 interface, which does record packets hitting the interface, but for some reason these same packets aren't being registered through the XDP pipeline.
#define KBUILD_MODNAME "foo"
#include <linux/bpf.h>
#include <linux/in.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <linux/if_vlan.h>
#include <linux/ip.h>
#include <linux/ipv6.h>
//BPF_TABLE("percpu_array", uint32_t, long, dropcnt, 256);
BPF_HASH(proto_map, uint32_t, uint32_t, 256);
//Packet Counter to keep track of number of packets flowing through XDP
BPF_ARRAY(pkt_count, uint64_t, 1);
//Map to keep track of the current EPOCH SIZE
BPF_ARRAY(epoch_size_map, uint64_t, 1);
static inline int parse_ipv4(void *data, u64 nh_off, void *data_end,
__be32 *src, __be32 *dest)
{
struct iphdr *iph = data + nh_off;
if (iph + 1 > data_end)
return 0;
*src = iph->saddr;
*dest = iph->daddr;
return iph->protocol;
}
static inline int bitXor(int* x, int* y)
{
int a = *x & *y;
int b = ~*x & ~*y;
int z = ~a & ~b;
return z;
}
int xdp_dsa(struct CTXTYPE *ctx) {
void* data_end = (void*)(long)ctx->data_end;
void* data = (void*)(long)ctx->data;
struct ethhdr *eth = data;
// drop packets
int rc = RETURNCODE; // let pass XDP_PASS or redirect to tx via XDP_TX
uint32_t *value;
uint32_t *counter_value;
uint32_t *epoch_size;
uint16_t h_proto;
uint64_t nh_off = 0;
uint32_t ipproto;
uint64_t magic_value = 12345678;
uint32_t packet = 0;
__be32 src_ip = 0, dest_ip = 0;
nh_off = sizeof(*eth);
if (data + nh_off > data_end)
pkt_count.increment(packet);
return rc;
h_proto = eth->h_proto;
if (h_proto == htons(ETH_P_IP))
ipproto = parse_ipv4(data, nh_off, data_end, &src_ip, &dest_ip);
/*
else if (h_proto == htons(ETH_P_IPV6))
index = parse_ipv6(data, nh_off, data_end);
*/
else
ipproto = 0; //i.e. unknown protocol
/*XOR the srcIP, destIP, and ipproto to encode, then hash*/
int xor_src_dest = bitXor(&src_ip, &dest_ip);
int xor_srcdst_ipproto = bitXor(&xor_src_dest, &ipproto);
uint32_t zero = 0;
//Predecided initial epoch size
uint32_t init_epoch_size = 10;
//Variable to store the current epoch size (to check end of epoch)
uint32_t cur_epoch_size;
//Lookup epoch size from shared map (to check whether intialized else read)
epoch_size = epoch_size_map.lookup(&zero);
// Start condition (epoch size map is initialized with zero), then set to initial epoch size
// Else read the current epoch size into a variable
if(epoch_size)
{
if(*epoch_size == 0)
{
*epoch_size = init_epoch_size;
}
else
{
cur_epoch_size = *epoch_size;
}
}
counter_value = pkt_count.lookup(&packet);
if (counter_value)
{
if (*counter_value < cur_epoch_size)
{
value = proto_map.lookup_or_init(&xor_srcdst_ipproto, &zero);
if (value)
{
pkt_count.increment(packet);
*value += 1;
}
}
else if (*counter_value == cur_epoch_size)
{
pkt_count.update(&packet, &magic_value);
}
else if(*counter_value == magic_value)
{
return rc;
}
}
return rc;
}
Any ideas?

Resources