Research/Embedded System

Thingy:52 Multiconnection with Raspberry Pi using C

유아현 2021. 9. 21. 00:52

# GattLib 

 

GattLib is a library used to access Generic Attribute Profile (GATT) protocol of BLE (Bluetooth Low Energy) devices. It allows to build applications that could easily communicate with BLE devices. It supports Bluez v4 and v5 which are official Linux Bluetooth protocol.

 

GitHub - labapart/gattlib: Library to access GATT information from BLE (Bluetooth Low Energy) devices

Library to access GATT information from BLE (Bluetooth Low Energy) devices - GitHub - labapart/gattlib: Library to access GATT information from BLE (Bluetooth Low Energy) devices

github.com

 

This project is based on GattLib to allow multiconnection between Thingy and Raspberry Pi. It creates multiple threads to connect with multiple Thingys and collect environment sensor data with current date and time.

 

 

 

 

# Building GattLib 

 

  1. You must download GattLib ARM 32-bit DEB
  2. Install following packages
    sudo apt install libbluetooth-dev libreadline-dev​
  3. Install CMake to build your project. 
  4. To build your code, follow the steps below. Executable file will be located in build folder.
    cd <gattlib-src-root>
    mkdir build && cd build
    cmake ..
    make​
  5. In order to build your own code with different file name, copy CMakeLists.txt to your folder and edit into your build file name.

 

 

# GattLib APIs

 

int gattlib_adapter_open(const char* adapter_name, void** adapter);
  • Open Bluetooth adapter
  • Parameter
    • adapter_name: with value NULL, the default adapter will be selected
    • adapter: context of the newly opened adapter
  • Return GATTLIB_SUCCESS on success or GATTLIB_* error code

 

int gattlib_adapter_scan_enable(void* adapter, gattlib_discovered_device_t discovered_device_cb, size_t timeout, void *user_data);
  • Enable Bluetooth scanning on a given adapter
  • Parameter
    • adapter: context of the newly opened adapter
    • Discovered_device_cb: function callback called for each new Bluetooth device discovered
    • timeout: duration of Bluetooth scanning, when timeout = 0, scan indefinitely
    • user_data: data passed to the callback 'discovered_device_cb()'
  • Return GATTLIB_SUCCESS on success or GATTLIB_* error code

 

gatt_connection_t *gattlib_connect(void *adapter, const char *dst, unsigned long options);
  • Connect to BLE device
  • Parameter
    • adapter: local Adapter interface, when passing NULL, use default adapter
    • dst: remote Bluetooth address
    • options: options to connect BLE device, see GATTLIB_CONNECTION_OPTIONS_*

 

int gattlib_uuid_to_string(const uuid_t *uuid, char *str, size_t size);
  • Convert UUID into string
  • Parameter
    • uuid: UUID to convert
    • str: buffer that will contain string
    • size: size of the buffer
  • Return GATTLIB_SUCCESS on success or GATTLIB_* error code

 

void gattlib_register_notification(gatt_connection_t* connection, gattlib_event_handler_t notification_handler, void* user_data);
  • Register a handle of GATT notifications
  • Parameter
    • connection: active GATT connection
    • notification_handler: handler to call on notification
    • user_data: user specific data to pass to the handler

 

int gattlib_notification_start(gatt_connection_t* connection, const uuid_t* uuid);
  • Enable notification on GATT characteristic represented by its UUID
  • Parameter
    • connection: active GATT connection
    • uuid: characteristic that will trigger notification
  • Return GATTLIB_SUCCESS on success or GATTLIB_* error code

 

 

# Multiconnection Using Thread

 

Notification_sync connects multiple Thingys with Raspberry Pi to get environment sensor notifications every 1 second. Check the full code here. To build the project, follow the steps below.

cd <notification_sync-root>
mkdir build && cd build
cmake ..
make
./notification_sync

 

Bluetooth packet includes connected Thingy name, temperature, humidity, gas, color and counts of each environment data.

struct Packet {
	char* name;
	
	int8_t temp_integer;
	uint8_t temp_decimal;
	int cnt_temp;
	
	uint8_t humidity;
	int cnt_humd;
	
	uint16_t eco2, tvoc;
	int cnt_gas;
	
	uint16_t red, green, blue, clear;
	int cnt_color;
};

 

Function ble_discovered_device discovers bluetooth devices nearby. If the discovered device name starts with Thingy, it creates connection thread.

static void ble_discovered_device(void *adapter, const char* addr, const char* name, void *user_data) {
	struct connection_t *connection;
	int ret;
	
	if (name) {
		printf("Discovered %s - '%s'\n", addr, name);
	} else {
		printf("Discovered %s\n", addr);
	}
	
	if(name && strncmp(name, "Thingy", 6) == 0) {
		connection = malloc(sizeof(struct connection_t));
		if (connection == NULL) {
			fprintf(stderr, "Fail to allocate connection.\n");
			return;
		}
		connection->addr = strdup(addr);
		connection->name = strdup(name);
		
		ret = pthread_create(&connection->thread, NULL, ble_connect_device, connection);
		if (ret != 0) {
			fprintf(stderr, "Fail to create BLE conneciton thread.\n");
			free(connection);
			return;
		}
		LIST_INSERT_HEAD(&g_ble_connections, connection, entries);
	}
}

 

Created thread *ble_connect_device connects to assigned device address and create csv file for each connected Thingy to log the environment data. Then it registers and starts notification. The data packets are passed to the notificaiton handler.

static void *ble_connect_device(void *arg) {
	struct connection_t *connection = arg;
	char* addr = connection->addr;
	char* name = connection->name;
	gatt_connection_t* gatt_connection;
	gattlib_primary_service_t* services;
	gattlib_characteristic_t* characteristics;
	int services_count, characteristics_count, characteristics_num;
	int ret, i;
	struct Packet *p;

	
	pthread_mutex_lock(&g_mutex);
	printf("--------------- START %s ---------------\n", name);
	
	//Connect to assigned device address
	gatt_connection = gattlib_connect(NULL, addr, GATTLIB_CONNECTION_OPTIONS_LEGACY_DEFAULT);
	if (gatt_connection == NULL) {
		fprintf(stderr, "Fail to connect to the bluetooth device.\n");
		printf("--------------- DONE %s ---------------\n", name);
	} else {
		puts("Succeeded to connect to the bluetooth device.\n");
	}

	printf("--------------- DONE %s ---------------\n", name);
			
	//Create CSV File for each connected Thingy
	fp = fopen(name, "w");
	//fp = fopen("Thingy", "w");
	fputs("DateTime,Name,Temperature,Humidity,CO2,VOC,Red,Green,Blue,Clear\n", fp);
	fclose(fp);
	
	//Create Packet for each connected Thingy 
	p = malloc(sizeof(struct Packet));
	p->name = name;
	p->cnt_temp = 0;
	p->cnt_humd = 0;
	p->cnt_gas = 0;
	p->cnt_color = 0;
	pthread_mutex_unlock(&g_mutex);
	
	/*TEMPERATURE DATA*/
	//Change UUID string into UUID
	gattlib_string_to_uuid(UUID_ENV_TEMP, strlen(UUID_ENV_TEMP), &temp_uuid);
	//Register and start notification (pass Packet to the handler)
	gattlib_register_notification(gatt_connection, notification_handler, ((struct Packet *)p));
	ret = gattlib_notification_start(gatt_connection, &temp_uuid);
	if (ret) {
		fprintf(stderr, "Fail to start notification.\n");
		printf("Error code: %d\n", ret);
		free(p);
		gattlib_disconnect(gatt_connection);
	}
	
	/*HUMIDITY DATA*/
	//Change UUID string into UUID
	gattlib_string_to_uuid(UUID_ENV_HUMD, strlen(UUID_ENV_HUMD), &hum_uuid);
	//Register and start notification (pass Packet to the handler)
	gattlib_register_notification(gatt_connection, notification_handler, ((struct Packet *)p));
	ret = gattlib_notification_start(gatt_connection, &hum_uuid);
	if (ret) {
		fprintf(stderr, "Fail to start notification.\n");
		printf("Error code: %d\n", ret);
		free(p);
		gattlib_disconnect(gatt_connection);
	}
	
	/*GAS DATA*/
	//Change UUID string into UUID
	gattlib_string_to_uuid(UUID_ENV_GAS, strlen(UUID_ENV_GAS), &gas_uuid);
	//Register and start notification (pass Packet to the handler)
	gattlib_register_notification(gatt_connection, notification_handler, ((struct Packet *)p));
	ret = gattlib_notification_start(gatt_connection, &gas_uuid);
	if (ret) {
		fprintf(stderr, "Fail to start notification.\n");
		printf("Error code: %d\n", ret);
		free(p);
		gattlib_disconnect(gatt_connection);
	}
	
	/*COLOR DATA*/
	//Change UUID string into UUID
	gattlib_string_to_uuid(UUID_ENV_COLOR, strlen(UUID_ENV_COLOR), &color_uuid);
	//Register and start notification (pass Packet to the handler)
	gattlib_register_notification(gatt_connection, notification_handler, ((struct Packet *)p));
	ret = gattlib_notification_start(gatt_connection, &color_uuid);
	if (ret) {
		fprintf(stderr, "Fail to start notification.\n");
		printf("Error code: %d\n", ret);
		free(p);
		gattlib_disconnect(gatt_connection);
	}
}

 

Notification handler synchronize temperature, humidity, gas and color data per 1 second and logs the synchronized data with current date and time to csv file. 

void notification_handler(const uuid_t* uuid, const void* data, size_t data_length, struct Packet* p) {
	
	//Local Variables
	char uuid_str[MAX_LEN_UUID_STR + 1];
	
	//Timestamp
	time_t t;
	t = time(NULL);
	struct tm tm = *localtime(&t);

	fp = fopen(p->name, "a");

	gattlib_uuid_to_string(uuid, uuid_str, sizeof(uuid_str));
	
	if(!strcmp(uuid_str, UUID_ENV_TEMP)) {
		p->temp_integer = *(int8_t*)data;
		p->temp_decimal = *(uint8_t*)(data+1);
		p->cnt_temp = 1;
	}
	else if(!strcmp(uuid_str, UUID_ENV_HUMD)) {
		p->humidity = *(uint8_t*)data;
		p->cnt_humd = 1;
	}
	else if(!strcmp(uuid_str, UUID_ENV_GAS)) {
		p->eco2 = *(uint16_t*)data;
		p->tvoc = *(uint16_t*)(data+1);
		p->cnt_gas = 1;
	}
	else if(!strcmp(uuid_str, UUID_ENV_COLOR)) {
		p->red = *(uint16_t*)data;
		p->green = *(uint16_t*)(data+1);
		p->blue = *(uint16_t*)(data+2);
		p->clear = *(uint16_t*)(data+3);
		p->cnt_color = 1;
	}
	else {
		printf("Notification Handler Error\n");
	}
	
	if(p->cnt_temp > 0) {
		if(p->cnt_humd > 0) {
			if(p->cnt_gas > 0) {
				if(p->cnt_color > 0) {
					fprintf(fp, "%d-%d-%d %d:%d:%d,%s,%d.%u,%u,%u,%u,%u,%u,%u,%u\n", tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, 
					p->name, p->temp_integer, p->temp_decimal, p->humidity, p->eco2, p->tvoc, p->red, p->green, p->blue, p->clear);
		
					p->cnt_temp--;
					p->cnt_humd--;
					p->cnt_gas--;
					p->cnt_color--;
				}
			}
		}
	}
	fflush(fp);
	fclose(fp);
}