Help: NimBLE Characteristics Properties (Flags) Aren't Properly Applying From ble_gatt_svc_def

User avatar
CseaTec
Posts: 5
Joined: Thu Jul 07, 2022 12:21 pm

Help: NimBLE Characteristics Properties (Flags) Aren't Properly Applying From ble_gatt_svc_def

Postby CseaTec » Wed Apr 17, 2024 12:35 pm

I redeveloping an existing product with existing smartphone apps that previously used a different BT BLE module. I'm using an ESP32 now which is generally new to me. I've done plenty of 8-bit ESP8266 Arduino projects in the past, but this is my first foray into the ESP-IDE and the ESP-IDF with a ESP32-WROOM-32E.

I'm pretty far along in my initial development and I was tackling the Bluetooth. I tried the BlueDroid API at first and got it working, but the code size was ridiculous so I decided to try the "lighter" NimBLE API instead.

With the latest ESP-IDF V5.2.1, I then recreated a very basic NimBLE GATT Server that I modeled after an example in the ESP-IDE and some I found online (bleprph). Everything is working EXCEPT...

For some reason, the characteristic access properties (i.e. read, write, notify) are not being applied to the actual characteristics that get advertised. Here is my code which sets up the service and characteristic definitions:

Code: Select all

static const struct ble_gatt_svc_def gatt_svr_svcs[] =
{
	{
		.type = BLE_GATT_SVC_TYPE_PRIMARY,
		.uuid = &gatt_svr_svc_uuid.u,
		.characteristics = (struct ble_gatt_chr_def[])
		{
			{
				.uuid = &gatt_svr_char_tx_uuid.u,
				.access_cb = gatt_svr_chr_access_cb,
				.val_handle = &notification_handle,
				.flags = BLE_GATT_CHR_F_NOTIFY
			},
			{
				.uuid = &gatt_svr_char_rx_uuid.u,
				.access_cb = gatt_svr_chr_access_cb,
				.val_handle = &write_handle,
				.flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_NO_RSP
			},

			{0}	// End of characteristic definitions
		}
	},
	{0}	// End of services definitions
};
After this assignment, all that is really required is a call to your own gatt_svr_init() routine. Mine looks like this, which is basically a cut/paste from the bleprph example project:

Code: Select all

int gatt_svr_init(void)
{
	esp_err_t ret;

	ble_svc_gap_init();
	ble_svc_gatt_init();

	ret = ble_gatts_count_cfg(gatt_svr_svcs);
	if (ret) {
		printf("BT NimBLE failed to count GATTS configuration, error code = %x\n", ret);
		return ret;
	}

	ret = ble_gatts_add_svcs(gatt_svr_svcs);
	if (ret) {
		printf("BT NimBLE failed to add GATTS services, error code = %x\n", ret);
		return ret;
	}

	return 0;
}
Everything with the UUIDs of the one service and its two characteristics sets up fine EXCEPT for the access properties:
  • Instead of the Notify characteristic having just BLE_GATT_CHR_PROP_NOTIFY (0x10), it gets both BLE_GATT_CHR_PROP_NOTIFY and BLE_GATT_CHR_PROP_READ (0x02), which advertise as 0x12. THIS IS TOTALLY FINE so I'm okay with this, but it would seem I have no control over it now matter what I call out in the notify characteristic's flags.
  • Instead of the Write characteristic having BOTH BLE_GATT_CHR_PROP_WRITE (0x08) ORed with BLE_GATT_CHR_F_WRITE_NO_RSP (0x04) which should combine to 0x0C, the advertised data for this characteristic shows 0x0A, which is BLE_GATT_CHR_PROP_WRITE (0x08) ORed with BLE_GATT_CHR_PROP_READ (0x02). THIS IS PROBLEMATIC FOR ME since the existing smartphone apps send writes deliberately with "no response", and one of my iOS apps rejects the write_with_noresponse attempt completely and doesn't even do it as a result.


I then dug into the NimBLE architecture that exists in the IDF library, and I found the NimBLE library file ble_gatts.c that does most of the work (since I'm making a GATT Server here). There I found a long chain of routines that call each other, with the pivotal final routines never getting called (based upon some printf statements I used to track each down):

My GATTS initialization routine calls ble_gatts_count_cfg() and ble_gatts_add_svcs(). A lot of stuff gets added to the BLE Stack for later execution by the NimBLE host task. This all kicks off from a later and internally generated (and confirmed) call to ble_gatts_start(), which then calls...
  • ble_gatts_register_svcs() which then calls...
  • ble_gatts_register_svcs() which then calls...
  • ble_gatts_register_svcs() which then calls...
  • ble_gatts_register_svcs() which then calls...
    [*}ble_att_svr_register() which registers to the BLE Stack the host callback routine...
  • ble_gatts_chr_def_access() which then calls...
    [*}ble_gatts_chr_properties() which translates all of the 16-bit BLE_GATT_CHR_F_* flags into 8-bit BLE_GATT_CHR_PROP_* properties
    [*}...and then ble_gatts_chr_def_access() presumably finishes up the characteristic property assignments by writing into the characteristics assigned os_mbuf.
I have proven that everything above gets called up until where ble_gatts_chr_def_access() gets added to the BLE Stack. It never gets called from there, and I don't understand what access is required to spawn that routine from the stack to do its job. So, it would seem that my "flag" assignments in my original ble_gatt_svc_def never find their way into become "properties" in the corresponding characteristics.

What's even more weird is the fact that the characteristics do receive "reasonably close" property assignments at all! I assume this is coming from some sort of "default" assignment that NimBLE uses to start off any new low-level service/characteristic definitions. Which routine in the NimBLE API is doing that I have not been able to figure out. I'm not averse to hacking the NimBLE API to brute-force assign the properties I desperately need to my measly little write characteristic, but I don't know where to find this low-level assignment.

Bottom line, the high-level assignment process that is blasted all over the internet GitHub examples and guides for NimBLE does not seem to do the job. I must be doing something wrong since I don't see anyone else complaining about this issue. Any help or incite will be greatly appreciated.
Matteo Giovanetti
Electrical Engineer
CseaTec LLC
Pompano Beach, FL USA

User avatar
CseaTec
Posts: 5
Joined: Thu Jul 07, 2022 12:21 pm

SOLVED: Help: NimBLE Characteristics Properties (Flags) Aren't Properly Applying From ble_gatt_svc_def

Postby CseaTec » Thu Apr 18, 2024 12:24 pm

Wow. I'm not sure how I feel about this given I lost about 5 days of project progress because of this. (I had posted here for help about 4 days into the nightmare.)

It turns out the issue with the characteristics not having the correct properties flags was not at all due to the ESP32 and its code. It was due to stale information in the iPhone I was using to communicate with it! As it turns out, once an iPhone establishes a connection with a BLE device (via your custom app or even the LightBlue testing app), the phone will remember the services and characteristics based upon the devices MAC address. Then, even if you change the services and characteristics, the iPhone will not update these settings no matter how many times you try. Why Apple and iOS do this, I have no idea. IMO, this is very problematic. To make matters worse, you cannot "Forget" a BLE device on an iPhone like you can Classic Bluetooth paired devices. And even though the BLE device does show up in the iPhone's Bluetooth settings screen AFTER you connect to it via your app or LightBlue, you cannot tap on it to bring up the usual (BT classic device) menu where you can "forget" it.

:!: THE SOLUTION: Restart the iPhone! That was all it took. Now, the service and its characteristics show the correct properties flags.

The roundabout way and hundreds of experiments that finally led me to this finding, I will not bore you with. I'm angry and frustrated :evil: , but also relieved. Maybe my suffrage will save someone else the agony.
Matteo Giovanetti
Electrical Engineer
CseaTec LLC
Pompano Beach, FL USA

chegewara
Posts: 2364
Joined: Wed Jun 14, 2017 9:00 pm

Re: Help: NimBLE Characteristics Properties (Flags) Aren't Properly Applying From ble_gatt_svc_def

Postby chegewara » Wed Apr 24, 2024 12:35 am

Yes, it is not obvious but its annoying during development until you learn it hard way.

Another option is to use "forget LE device" on iPhone. Its somewhere in there, but honestly i dont know where.
You can also try to connect with nrf connect and use option to refresh services with "discover services" option (it is on android and should be on iPhone too) which does not require to restart smartphone.

Who is online

Users browsing this forum: No registered users and 64 guests