Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BLE GATT server doesn't read/write value (IDFGH-663) #3102

Closed
phatpaul opened this issue Feb 26, 2019 · 16 comments
Closed

BLE GATT server doesn't read/write value (IDFGH-663) #3102

phatpaul opened this issue Feb 26, 2019 · 16 comments

Comments

@phatpaul
Copy link
Contributor

Environment

  • Development Kit: [none]
  • Kit version (for WroverKit/PicoKit/DevKitC): []
  • Module or chip used: [ESP32-WROVER]
  • IDF version (run git describe --tags to find it): v3.1-151-g7063cdecd
  • Build System: [Make]
  • Compiler version (run xtensa-esp32-elf-gcc --version to find it): 1.22.0-80-g6c4433a5
  • Operating System: [Windows]
  • Power Supply: [external 3.3V]

Problem Description

BLE GATT server does not read or write my global array when characteristic is defined with ESP_GATT_AUTO_RSP.

Expected Behavior

The global array that I provided via pointer to the stack on initialization should be read from and written to by the stack when the characteristic is defined with ESP_GATT_AUTO_RSP.

Actual Behavior

I can read and write the characteristic via BLE, but it seems my global array is never changed.
So it seems there is some internal buffer in the BLE stack storing the value because I can read back what I wrote via BLE.
It seems the global array that I passed to the stack on creation is only used to initialize the internal value.

Steps to reproduce

  1. Add a few debug statements to the example at esp-idf\examples\bluetooth\gatt_server_service_table\main\gatts_table_creat_demo.c
    (or the better organized example project here: https://github.com/eagi223/esp-idf_Bluetooth_Multi-Service )
  2. ...
    When I first read the characteristic, it returns what I have initialized the global to. I read [11 22 33 44], just as expected.
    Then I write the value via BLE to 0x1234.
    I can then read via BLE and it returns [12 34] as expected.
    But my global array has not been changed.
    Also, if I change my global array value via other means (not via BLE), the value I read via BLE is not changed.

Code to reproduce this issue

We have some characteristics defined using ESP_GATT_AUTO_RSP, and I supplied a pointer to the value when creating the gatt table.
The value is stored in a global uint8_t array.

static const uint8_t char_value[4]                 = {0x11, 0x22, 0x33, 0x44}; // global array to store char value
...
const esp_gatts_attr_db_t serv_gatt_db[SERV_NUM_ATTR] =
{
...
    /* Characteristic Value */
    [IDX_CHAR_VAL_A] =
    {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&GATTS_CHAR_UUID_TEST_A, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
      GATTS_DEMO_CHAR_VAL_LEN_MAX, sizeof(char_value), (uint8_t *)char_value}},
...

I added some debug hints to print out the value of char_value[4] in gatts_profile_event_handler:

        case ESP_GATTS_READ_EVT:
            ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_READ_EVT char_value:");
            ESP_LOG_BUFFER_HEX_LEVEL(GATTS_TABLE_TAG, char_value, sizeof(char_value), ESP_LOG_INFO);
       	    break;
        case ESP_GATTS_WRITE_EVT:
...
            ESP_LOGI(GATTS_TABLE_TAG, "GATT_WRITE_EVT, char_value :");
            ESP_LOG_BUFFER_HEX_LEVEL(GATTS_TABLE_TAG, char_value, sizeof(char_value), ESP_LOG_INFO);
...
        case ESP_GATTS_EXEC_WRITE_EVT: 
...
            ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_EXEC_WRITE_EVT, char_value :");
            ESP_LOG_BUFFER_HEX_LEVEL(GATTS_TABLE_TAG, char_value, sizeof(char_value), ESP_LOG_INFO);
...

Debug Logs

I (627) GATTS_TABLE_DEMO: create attribute table successfully, the number handle = 8
I (627) GATTS_TABLE_DEMO: SERVICE_START_EVT, status 0, service_handle 40
I (637) GATTS_TABLE_DEMO: advertising start successfully
I (236277) GATTS_TABLE_DEMO: ESP_GATTS_CONNECT_EVT, conn_id = 0
I (236277) GATTS_TABLE_DEMO: 64 1d c8 4e fe 1d
I (236957) GATTS_TABLE_DEMO: update connection params status = 0, min_int = 16,
max_int = 32,conn_int = 30,latency = 0, timeout = 400
I (237747) GATTS_TABLE_DEMO: update connection params status = 0, min_int = 0, m
ax_int = 0,conn_int = 6,latency = 0, timeout = 2000
I (237897) GATTS_TABLE_DEMO: update connection params status = 0, min_int = 0, m
ax_int = 0,conn_int = 30,latency = 0, timeout = 400
I (245967) GATTS_TABLE_DEMO: ESP_GATTS_READ_EVT char_value:
I (245967) GATTS_TABLE_DEMO: 11 22 33 44
I (264337) GATTS_TABLE_DEMO: GATT_WRITE_EVT, handle = 42, value len = 2, value :+1: 
I (264337) GATTS_TABLE_DEMO: 12 34
I (264337) GATTS_TABLE_DEMO: GATT_WRITE_EVT, char_value :
I (264347) GATTS_TABLE_DEMO: 11 22 33 44
I (277017) GATTS_TABLE_DEMO: ESP_GATTS_READ_EVT char_value:
I (277017) GATTS_TABLE_DEMO: 11 22 33 44

Other items if possible

will provide if requested

Questions:

  • How and when do I access the internal value?

  • If the BLE stack is supposed to write to my global array automatically, does it do so before or after triggering ESP_GATTS_WRITE_EVT (or ESP_GATTS_EXEC_WRITE_EVT)?

  • If the BLE stack is supposed to read from my global array automatically, does it do so before or after triggering ESP_GATTS_READ_EVT?

  • Where is the intended behavior documented?

Note: I realize that I could use ESP_GATT_RSP_BY_APP and write code to handle the read/write to my global array. But I am using long-write and long-read, because the size of array may be larger than MTU. i.e. static uint8_t char_value[128] I want to use ESP_GATT_AUTO_RSP because it seems that the BLE stack is (somehow) taking care of the chunking required for long-write and long-read automatically.

BTW: I asked this on esp32.com a week ago and got no response: https://www.esp32.com/viewtopic.php?f=13&t=9345 . Also here's a relevant post: https://www.esp32.com/viewtopic.php?f=13&t=2459

@Weijian-Espressif
Copy link
Collaborator

Hi phatpaul :
If you set ESP_GATT_AUTO_RSP, the Bluetooth stack will have a buffer to store the initialized value of the char. When the master reads or writes the char, the buffer will be read or updated. if you change your global array value via other means (not via BLE), please call esp_ble_gatts_set_attr_value() to update the buffer.

@Weijian-Espressif
Copy link
Collaborator

you can use esp_ble_gatts_get_attr_value() and esp_ble_gatts_set_attr_value() to read and update the internal buffer value.

@phatpaul
Copy link
Contributor Author

Great thanks @Weijian-Espressif . That's the function I was looking for.

But it doesn't seem well documented. Perhaps add some notes that mention ESP_GATT_AUTO_RSP to the documentation of those functions? And it would be helpful if the example made use of those.

Also, my other questions, which I will try to figure out experimentally:

  • Is the internal value updated before or after triggering ESP_GATTS_WRITE_EVT (or ESP_GATTS_EXEC_WRITE_EVT)? (it would only be useful if the event triggers after the internal value is updated , so I can do something with the value)

  • Is the internal value send out BLE before or after triggering ESP_GATTS_READ_EVT? (it would only be useful if event is triggered before sending, so I can change the value before it is sent)

@phatpaul
Copy link
Contributor Author

phatpaul commented Feb 27, 2019

So it looks like the answer is

  • ESP_GATTS_ (EXEC_) WRITE_EVT triggers After a Write, which is good.

(I am using ESP_GATTS_EXEC_WRITE_EVT to receive long writes. Seems I have to buffer the attr handle from one of the Prepare writes so I know which characteristic is being written to on a ESP_GATTS_EXEC_WRITE_EVT - as there is no reference to the handle given in that event?!)

But

  • ESP_GATTS_READ_EVT triggers After a Read, which is not very helpful.

Is there another event which happens earlier during a read? I want to have the opportunity to set the value before it is sent out. (I suppose I could set the BLE internal char value whenever my value changes. But it seems wasteful to frequently copy it to the BLE stack internal variable even if it is not being read by BLE)

@Weijian-Espressif
Copy link
Collaborator

@phatpaul,
you are right,
ESP_GATTS_ (EXEC_) WRITE_EVT/ ESP_GATTS_READ_EVT triggers After a Write/Read.
According to your needs, I think you can set it to ESP_GATT_RSP_BY_APP, when ESP_GATTS_READ_EVT triggers, you send response with your global array value. when ESP_GATTS_WRITE_EVT triggers, you update your global array value. you can refer to our gatt_server demo.

@phatpaul
Copy link
Contributor Author

@Weijian-Espressif thanks for confirming.

I want to use ESP_GATT_AUTO_RSP because my characteristic length might be larger than MTU. I didn't want to write my own code to handle the chunking for long-read and long-write.

I saw there is some code to implement long-write in the example, but I don't see any example for long-read. (example_write_event_env() and example_exec_write_event_env())

  • Can you please provide an example implementation of long-read by App?

(I found this implementation in C++, but a C example would be helpful - https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/BLECharacteristic.cpp#L348 )

@Weijian-Espressif
Copy link
Collaborator

@phatpaul,
if you want to ESP_GATT_RSP_BY_APP, when you get ESP_GATTS_READ_EVT, you need to call esp_ble_gatts_send_response() to send your value, but the length cannot exceed 600 bytes.
Does this way meet your needs?

@phatpaul
Copy link
Contributor Author

phatpaul commented Mar 1, 2019

@Weijian-Espressif thanks, that worked for sending long value with ESP_GATT_RSP_BY_APP.

  • It seems it will not send any response if MTU is less than the length of my response.
    I see a warning that says W (27859) BT_GATT: attribute value too long, to be truncated to 22
    But I didn't receive a truncated 22-byte value, I got error 0x2: GATT READ NOT PERMIT.
    So it seems I need to check MTU and truncate my response before calling
    esp_ble_gatts_send_response()?

  • Will there be repeated ESP_GATTS_READ_EVT events for a single long-read? Do I need to check param->read.is_long? Like this:

if (!param->read.is_long) {
// If is.long is false then this is the first (or only) request to read data
...
    memcpy(rsp->attr_value.value, vol_db, 128);
    rsp->attr_value.len = 128;
...
}
if (param->read.need_rsp){ // needs external response?
    rsp.attr_value.handle = param->read.handle;
    esp_ble_gatts_send_response(gatts_if, param->read.conn_id, param->read.trans_id, status, &rsp);
}
  • If I use ESP_GATT_RSP_BY_APP it seems there is no internal variable in the stack? When I try to use esp_ble_gatts_get_attr_value() I get 0 length nothing.
    So if use ESP_GATT_RSP_BY_APP, then I also have to use the external long-write functions
    example_write_event_env() and example_exec_write_event_env(), correct?

  • Now I'm getting a better idea of the API. I think the example should have a service with characteristics demonstrating the different possible implementations.

    • Long (600 byte) characteristic read/write/notify using ESP_GATT_AUTO_RSP
    • Long (600 byte) characteristic read/write/notify using ESP_GATT_RSP_BY_APP
    • Short (20 byte) characteristic read-only using ESP_GATT_AUTO_RSP
    • Short (20 byte) characteristic read/write/notify using ESP_GATT_AUTO_RSP
    • Short (20 byte) characteristic read/write/notify using ESP_GATT_RSP_BY_APP

    In the example some characteristic should be able to

    • trigger an action like turn on/off an LED
    • return an analog value which is determined at the time of the read (i.e. a timestamp or analog input value)
    • notify on an external stimulus like an IO pin level change.

    Thoughts? Do you want me to help add these to the example?

@phatpaul
Copy link
Contributor Author

phatpaul commented Mar 4, 2019

@Weijian-Espressif let me take back my previous report that esp_ble_gatts_send_response() works for me.
It does not work to send a response which is longer than MTU.

I like the behavior that if I use ESP_GATT_AUTO_RSP, and MTU is still = 23, the long read just works.
But when I tried ESP_GATT_RSP_BY_APP and tried to read a 128B characteristic without requesting MTU increase, I just got a warning and no data

W (248683509) BT_GATT: attribute value too long, to be truncated to 22
  • So is it required to write some external code to allow sending > MTU if ESP_GATT_RSP_BY_APP?

@Weijian-Espressif
Copy link
Collaborator

@phatpaul , The length of a response must less than MTU size.

@phatpaul
Copy link
Contributor Author

phatpaul commented Mar 7, 2019

Is the source code available for how the stack implements the long-read and chunking internally?
If so would you point it out please. I'd rather not re-invent it if I have to do it at a higher level.

@Weijian-Espressif
Copy link
Collaborator

@phatpaul please refer.

// support read and long read
void gatts_proc_read(esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param, uint8_t *p_rsp_v, uint16_t v_len)
{
    if(!param->read.need_rsp) {
        return;
    }
    uint16_t value_len = gatts_mtu - 1;
    if(v_len - param->read.offset < (gatts_mtu - 1)) {
        value_len = v_len - param->read.offset;
    }
    esp_gatt_rsp_t rsp;
    memset(&rsp, 0, sizeof(esp_gatt_rsp_t));
    rsp.attr_value.handle = param->read.handle;
    rsp.attr_value.len = value_len;
    rsp.attr_value.offset = param->read.offset;
    memcpy(rsp.attr_value.value, &p_rsp_v[param->read.offset], value_len);
    esp_ble_gatts_send_response(gatts_if, param->read.conn_id, param->read.trans_id, ESP_GATT_OK, &rsp);

}
case ESP_GATTS_READ_EVT: {
        ESP_LOGI(GATTS_TAG, "GATT_READ_EVT, conn_id %d, trans_id %d, handle %d offset %d\n", param->read.conn_id, param->read.trans_id, param->read.handle, param->read.offset);
        gatts_proc_read(gatts_if, param, read_rsp, sizeof(read_rsp));
        break;
    }

gatts_demo.c.zip

@phatpaul
Copy link
Contributor Author

phatpaul commented Mar 8, 2019

Thanks @Weijian-Espressif that helped a lot.

Here's my solution, since I want to trigger the APP read handler once when a long read is begun, and buffer the value from the APP read handler so it can be pushed out in chunks during subsequent read events. I based it on your example plus the existing example_prepare_write_event_env() functions.

Would you please review it to look for memory leak etc? Feel free to include it in the example.

typedef struct {
    uint8_t                 *prepare_buf;
    int                     prepare_len;
    uint16_t                handle;
} prepare_type_env_t;

static prepare_type_env_t prepare_write_env;
static prepare_type_env_t prepare_read_env;

...
// support read and long read
void gatts_proc_read(esp_gatt_if_t gatts_if, prepare_type_env_t *prepare_read_env, esp_ble_gatts_cb_param_t *param, uint8_t *p_rsp_v, uint16_t v_len)
{
	if(!param->read.need_rsp) {
		return;
	}
	uint16_t value_len = gatts_mtu - 1;
	if(v_len - param->read.offset < (gatts_mtu - 1)) { // read response will fit in one MTU?
		value_len = v_len - param->read.offset;
	}
	else if (param->read.offset == 0) // it's the start of a long read  (could also use param->read.is_long here?)
	{
		ESP_LOGI(TAG, "long read, handle = %d, value len = %d", param->read.handle, v_len);

		if (v_len > PREPARE_BUF_MAX_SIZE) {
			ESP_LOGE(TAG, "long read too long");
			return;
		}
		if (prepare_read_env->prepare_buf != NULL) {
			ESP_LOGW(TAG, "long read buffer not free");
			free(prepare_read_env->prepare_buf);
			prepare_read_env->prepare_buf = NULL;
		}

		prepare_read_env->prepare_buf = (uint8_t *)malloc(PREPARE_BUF_MAX_SIZE * sizeof(uint8_t));
		prepare_read_env->prepare_len = 0;
		if (prepare_read_env->prepare_buf == NULL) {
			ESP_LOGE(TAG, "long read no mem");
			return;
		}
		memcpy(prepare_read_env->prepare_buf, p_rsp_v, v_len);
		prepare_read_env->prepare_len = v_len;
		prepare_read_env->handle = param->read.handle;
	}
	esp_gatt_rsp_t rsp;
	memset(&rsp, 0, sizeof(esp_gatt_rsp_t));
	rsp.attr_value.handle = param->read.handle;
	rsp.attr_value.len = value_len;
	rsp.attr_value.offset = param->read.offset;
	memcpy(rsp.attr_value.value, &p_rsp_v[param->read.offset], value_len);
	esp_ble_gatts_send_response(gatts_if, param->read.conn_id, param->read.trans_id, ESP_GATT_OK, &rsp);

}

// continuation of read, use buffered value
void gatts_proc_long_read(esp_gatt_if_t gatts_if, prepare_type_env_t *prepare_read_env, esp_ble_gatts_cb_param_t *param)
{

	if (prepare_read_env->prepare_buf && (prepare_read_env->handle == param->read.handle)) // something buffered?
	{
		gatts_proc_read(gatts_if, prepare_read_env, param, prepare_read_env->prepare_buf, prepare_read_env->prepare_len);
		if(prepare_read_env->prepare_len - param->read.offset < (gatts_mtu - 1)) // last read?
		{
			free(prepare_read_env->prepare_buf);
			prepare_read_env->prepare_buf = NULL;
			prepare_read_env->prepare_len = 0;
			ESP_LOGI(TAG,"long_read ended");
		}
	}
	else
	{
		ESP_LOGE(TAG,"long_read not buffered");
	}
}
static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
{
...
        case ESP_GATTS_READ_EVT:
        {
            ESP_LOGI(TAG, "GATT_READ_EVT, conn_id %d, trans_id %d, handle %d offset %d", param->read.conn_id, param->read.trans_id, param->read.handle, param->read.offset);

			// Note that if char is defined with ESP_GATT_AUTO_RSP, then this event is triggered after the internal value has been sent.  So this event is not very useful.
			// Otherwise if it is ESP_GATT_RSP_BY_APP, then call helper function gatts_proc_read()
			if (!param->read.is_long) {
				// If is.long is false then this is the first (or only) request to read data
				int attrIndex;

				/*
				 *  Add calls to handle read events for each external service here
				 *  Template:
				 *  if( (attrIndex = getAttributeIndexBy#SERVICE#Handle(param->read.handle)) < #SERVICE MAX INDEX# )
				 *  {
				 *      ESP_LOGI(TAG, "#SERVICE# READ");
				 *      handle#SERVICE#ReadEvent(attrIndex, param, &rsp);
				 *  }
				 */

				if( (attrIndex = getAttributeIndexByMyHandle(param->read.handle)) < MY_SERV_NUM_ATTR )
				{
					//ESP_LOGI(TAG,"MY READ");
					handleMyReadEvent(attrIndex, &rsp);
				}

				/* END read handler calls for external services */
				gatts_proc_read(gatts_if, &prepare_read_env, param, rsp.attr_value.value, rsp.attr_value.len);
			}
			else  // a continuation of a long read.  Dont invoke the handle#SERVICE#ReadEvent again, just keep pumping out buffered data.
			{
				gatts_proc_long_read(gatts_if, &prepare_read_env, param);
			}
        }
//Handle any reads of characteristics here
void handleMyReadEvent(int attrIndex, esp_gatt_rsp_t* rsp)
{
	switch( attrIndex )
	{
	// Characteristic read values

	// Note that if char is defined with ESP_GATT_AUTO_RSP, this event is triggered AFTER the internal value has been sent.
	// So this event is not useful for setting the value with esp_ble_gatts_set_attr_value() - should do that somewhere else.
	// But if char is defined with ESP_GATT_RSP_BY_APP, then build the response here before it is sent:

	case MY_CHAR_VAL:
	{
		memcpy(rsp->attr_value.value, vol_db, VOL_DB_SIZE);
		rsp->attr_value.len = VOL_DB_SIZE;
		break;
	}

	}
}

@projectgus projectgus changed the title BLE GATT server doesn't read/write value BLE GATT server doesn't read/write value (IDFGH-663) Mar 12, 2019
@Weijian-Espressif
Copy link
Collaborator

@phatpaul, If there are no more questions, we will close this issue.

@phatpaul
Copy link
Contributor Author

phatpaul commented Apr 1, 2019 via email

@Alvin1Zhang
Copy link
Collaborator

@phatpaul Thanks for reporting the issue. Feel free to reopen if the issue still exists. Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants