espressif / esp-idf

Espressif IoT Development Framework. Official development framework for Espressif SoCs.
Apache License 2.0
13.51k stars 7.26k forks source link

[TW#14557] GATT_C and GATT_S callback issue #875

Closed lucashutch closed 7 years ago

lucashutch commented 7 years ago

Hi All,

I am having a bit of trouble with the gatt client and server callbacks.

I register a gatt callback for each of the client and peripheral applications as follows (based off the examples in the idf)

esp_ble_gattc_register_callback(esp_gattc_cb);
esp_ble_gatts_register_callback(gatts_event_handler);

My problem occurs when both of the client and server applications are running and both callbacks are registered. When I connect to a remote peripheral using the gatt_c function esp_ble_gattc_open(gattc_if, mac_address, true); where gattc_if = 4 and the mac_address of the peripheral i want to connect to.

The callback for the central esp_gattc_cb is called with the ESP_GATTC_OPEN_EVT event as expected with the expected gattc_if number, in my case gattc_if = 4. However i find that gatts_event_handler is also called with the ESP_GATTS_CONNECT_EVT event wich is not expected. It also gets called with gatts_if = 3 which is most definitely un-expected.

Please let me know if any more information is needed. I have attached a log of the peripheral connecting, then disconnecting 10s later (the disconnection is triggered by the peripheral and is expected)

Is this expected behaviour? Is this a bug? Does anyone know a work around or fix for this?

@heyinling @Weijian-Espressif

I (72244) ble_central_start_scanning: scan timeout set to 500s
I (74425) GATT_CLIENT: Connecting to: d0:5f:b8:28:01:a9
E (74457) gatts_event_handler: Peripheral event: 14
I (74457) GATT_Peripheral: d0:5f:b8:28:01:a9 connected, channel: 3
E (74458) esp_gattc_cb: Client event: 40
E (74463) esp_gattc_cb: Client event: 2
I (74467) GATT_CLIENT: d0:5f:b8:28:01:a9 Connected. num Connected: 1, profile #: 0, channel: 4
E (74476) esp_gattc_cb: Client event: 40
E (74481) esp_gattc_cb: Client event: 40
I (74487) gap_event_handler_peripheral: max_int: 64, min_int: 6, conn_int: 0
I (74493) gap_event_handler_central: max_int: 64, min_int: 6, conn_int: 0
E (74501) gatts_event_handler: Peripheral event: 23
E (78505) esp_gattc_cb: Client event: 18
E (78505) esp_gattc_cb: Client event: 38
E (78505) esp_gattc_cb: Client event: 38
I (78775) gap_event_handler_peripheral: max_int: 64, min_int: 6, conn_int: 64
I (78775) gap_event_handler_central: max_int: 64, min_int: 6, conn_int: 64
E (84775) gatts_event_handler: Peripheral event: 15
I (84776) GATT_Peripheral: Client disconnected, Re-starting advertising
E (84777) esp_gattc_cb: Client event: 41
E (84782) esp_gattc_cb: Client event: 5
I (84786) GATT_CLIENT: Device Disconnected. num Connected: 0, profile #: 0
E (84794) esp_gattc_cb: Client event: 41
E (84798) esp_gattc_cb: Client event: 41
heyinling commented 7 years ago

@lucazader the current design is to dispatch connect/disconnect events to all active applications. each application keeps a "virtual" connection. the connection will only be closed once all applications call gattc_close or gatts_close close their virtual connection. And API esp_ble_gap_disconnect will do disconnect for all applications.

The design is useful in some scenarios. For example, we have 2 GATTS applications for different services. Once the connection is established, both application can provide service on this connection.

lucashutch commented 7 years ago

Hi @heyinling

Thanks for the response.

Ok so this is expected behaviour.

What might be the best way to make sure that the open event is handled by only the relevant application.

So in my application, how can I filter the connection event so only the client application will respond to the connection event?

Thanks for your help so far!

heyinling commented 7 years ago

@lucazader we could first cache the remote address in application context when calling open. then compare the remote address from connect event, find out which application should handle the connection. If the application don't want to handle this connection, it could close the connection when handling connect event.

I think your application is a common use scenario. maybe you could implement a adaptation layer:

  1. register callback of adaptation layer to stack, and application only receive events dispatched by adaptation callbacks
  2. adaptation callback only dispatch open events to application who open this connection, do close connection for other applications
  3. if multiple applications want to use the same connection, they need to call open by themselves (stack support calling open multiple times on an existed connection)
lucashutch commented 7 years ago

Hi @heyinling

I think I am getting more of what you were saying.

The connection event will be sent to all applications, however the open/close event will only be sent to whichever application opened the connection

However I now have a different issue. When My device is advertising as a peripheral and a central connected (such as a phone) I don't get the ESP_GATTS_OPEN_EVT. Yet as far as I can tell the esp32 peripheral seems to be working correctly, sending notifications, receiving data etc as it should be.

This means that for the client i can handle opened connections using the ESP_GATTC_OPEN_EVT. however for the peripheral i have to handle a connection using ESP_GATTS_CONNECT_EVT however this also is called whenever any device is connected.

Is this expected?

heyinling commented 7 years ago

adaptation callback only dispatch open events to application who open this connection, do close connection for other applications

my mistake here. GATTC/GATTS open event is response to api gattc/gatts_open. connect event indicates there's a new connection that app could use. In you case, you did not call GATTS open, then it will only report connect event to GATTS app.

Does you application only use GATTS as peripheral role and GATTC as central role? If yes need to first check if the connection is initiated by ESP32, and then dispatch connect events.

lucashutch commented 7 years ago

Hi @heyinling

Thanks for your response.

Yes I use gatts in peripheral and gattc in central. Ok I will look into adding something like this to determine if it is a client connecting to me, or me connecting to a client.

Thanks for your assistance!

lucashutch commented 7 years ago

HI @heyinling

I am still having a bit of trouble trying to isolate some of these events from each other.

Currently when a phone (as a gatt_c central role) connects to my esp32, everything works correctly. I get the connection (and disconnection) callbacks to both of the gatt_c and gatt_s handlers. However because the gatt_c has all of it's connection handling now in the open and close callbacks.

However, when my gatt_c scans for and connects to a remote peripheral, this is handled on the gatt_c side fine, due to handling using the open/close callbacks. But i also get the connected callback in the gatt_s. But I cant easily determine if this callback is due to the remote peripheral connecting, or if it is a phone connecting. The only way that i can think of accomplishing this is to have a storage of the mac addresses in the client side, and then checking against those in the gatt_s connected/disconnected callback. if they don't match then this would be a phone (or similar) connecting to the gatt_s. However this is not ideal, as this means the gatt_s has to have knowledge of what is happening on the gatt_c side of the application.

Can you think of a better way to filter out the connections? As this seems like a very hacky and overall not good way to work around this.

lucashutch commented 7 years ago

@heyinling

Ideally there would be an option to enable or disable sending connection/disconnection events to all registered handlers, or to only the appropriate handlers.

As in if there is a connection event on the server side of the stack, it would only call back to the callbacks registered by esp_ble_gatts_register_callback. And then if an event occurs on the client side of the stack, it would only callback to callbacks registered using esp_ble_gattc_register_callback.

This seems like a reasonable feature to have, and would still allow multiple gatt_s to handle and register on the same connection.

Only when a user would want to have both a gatt_s and a gatt_c running concurrently on the same connection would you need to have it callback to all of the registered callbacks.

I would suggest that this would be a very niche operation. Most BLE use cases are for one role per connection.

lucashutch commented 7 years ago

@heyinling @Weijian-Espressif Any comments on the above?

heyinling commented 7 years ago

@lucazader I think at least we can provide an example of adaptation layer for your scenario. Might need some time to discuss and implement this.

lucashutch commented 7 years ago

Hi @heyinling

Have you had the time to discuss this adaptation layer?

Thanks!

heyinling commented 7 years ago

@lucazader I think the following suggestions could work in your scenario:

  1. ignore connect/disconnect events on GATTC side
  2. use open event to check if gatt_open succeed. open event will only be sent to the application called open
  3. use esp_ble_gap_disconnect instead of gatt_close to disconnect. it will close physical connection
lucashutch commented 7 years ago

Hi @heyinling

Yes this works well for the GATTC side of my application.

However I am also running the GATTS at the same time. The issue I am running into is when the GATTC connects to a client, the GATTC side of the application handles this nicely. However I also get a ESP_GATTS_CONNECT_EVT on the GATTS side of the application. And i can't filter this out using the ESP_GATTS_OPEN_EVT because i cant handle incoming connections on the GATTS side using the OPEN_EVT as this does not arrive as the link was not established by the esp32.

This results in the following behaviour: Case 1: Phone(gatt_c, central) connects to ESP32 (gatt_s, peripheral + gatt_c, central):

this use case causes no issues

Case 2: ESP32 (gatt_s, peripheral + gatt_c, central) connects to ESP32((gatt_s, peripheral)

This use case causes an issue Where the esp32 gatt_s thinks it is connected due to the CONNECT_EVT callback that is not filtered out.

Would your suggestion solve the above? If not, what is the best way to get around the use case above with the filtering needed

heyinling commented 7 years ago

For GATTS side, it don't need to handle connection event either.

I think adding some logic in read/write event handling could do the job.

And one more thing is handling disconnect events. we want to do cleanup all tasks once the connection is closed (by remote side). we might need to create a task pool and close the tasks when we recv corresponding disconnect events.

lucashutch commented 7 years ago

Thanks for all of your help @heyinling

Using some of your suggestions and a bit of additional logic I have worked around this issue.

alanmini commented 5 years ago

Hi @heyinling

Thanks for the response.

Ok so this is expected behaviour.

What might be the best way to make sure that the open event is handled by only the relevant application.

So in my application, how can I filter the connection event so only the client application will respond to the connection event?

Thanks for your help so far!

i have same question,can you help me ,please,thanks!