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

fix issue with the BLE MTU #39

Merged
merged 2 commits into from
May 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 93 additions & 24 deletions main/ble.c
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ static uint8_t adv_config_done = 0;
#define EXT_ADV_MAX_EVENTS 0

#define GATTS_DEMO_CHAR_VAL_LEN_MAX 0x40
#define BLE_SEND_BUF_SIZE 490

static uint8_t dev_name[32] = {0};
static uint8_t manufacturer[]="MeatPi";
Expand Down Expand Up @@ -208,8 +209,13 @@ static const uint8_t heart_measurement_ccc[2] = {0x00, 0x00};
static const uint8_t char_value[20] = {0x11, 0x22, 0x33, 0x44};
#define CHAR_DECLARATION_SIZE (sizeof(uint8_t))
#define SVC_INST_ID 0
static uint16_t spp_mtu_size = 23;
static uint16_t spp_conn_id = 0xffff;
static esp_gatt_if_t spp_gatts_if = 0xff;
// If the client sends a larger MTU size, the ble_max_data_size will be set to
// the minium of BLE_SEND_BUF_SIZE and (spp_mtu_size - 3).
// Since the default MTU size is 23 this is initially set to 20
static uint16_t ble_max_data_size = 20;
static bool is_connected = false;
static uint8_t test1[] = {0x66 ,0x33 ,0x22 ,0x11 ,0xBB ,0x00 ,0x00 ,0x00 ,0x11 ,0x00 ,0x00 ,0x00 ,0x33 ,0x00 ,0x00 ,0x00 ,0xA4 ,0x3C ,0xD9 ,0x49};
/* Full Database Description - Used to add attributes into the database */
Expand All @@ -225,6 +231,11 @@ static const esp_gatts_attr_db_t gatt_db[HRS_IDX_NB] =
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read_write_notify}},

// NOTE: This is the notify characteristic used to send data to the client
// It currently uses GATTS_DEMO_CHAR_VAL_LEN_MAX which is set to 64 (0x40).
// However more bytes might be sent over this characteristic.
// In the ESP SPP demo code the characteristic size is 512 which seems to
// be the max MTU supported by BLE.
/* 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,
Expand Down Expand Up @@ -514,6 +525,23 @@ static void gatts_profile_event_handler(esp_gatts_cb_event_t event,
case ESP_GATTS_EXEC_WRITE_EVT:
break;
case ESP_GATTS_MTU_EVT:
// NOTE: The ESP32 documentation doesn't explain how MTU negotiation works.
// From the ESP SPP server demo, the characteristic is declared with a size of 512
// and then this event determines the actual MTU size.
// From experimentation the esp_ble_gatt_set_local_mtu function is related to this.
// It seems the value set by esp_ble_gatt_set_local_mtu is sent to the client during
// the connection. Then if the client supports it, it will send this ESP_GATTS_MTU_EVT
// with the MTU value the client can support.
//
// As noted in above the call to esp_ble_gatt_set_local_mtu is not sent by
// Car Scanner ELM OBD2 on Android. So it uses the default MTU of 23.
spp_mtu_size = param->mtu.mtu;
// Each BLE packet has 3 header bytes, so the actual amount of data that
// can be sent is (MTU - 3).
//
// set the max data size to the minimum of (spp_mtu_size - 3) and BLE_SEND_BUF_SIZE
ble_max_data_size = BLE_SEND_BUF_SIZE <= (spp_mtu_size - 3) ? BLE_SEND_BUF_SIZE : (spp_mtu_size -3);
ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_MTU_EVT: %d", spp_mtu_size);
break;
case ESP_GATTS_CONF_EVT:
break;
Expand Down Expand Up @@ -623,7 +651,7 @@ static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_
static void ble_task(void *pvParameters)
{
static xdev_buffer tx_buffer;
static uint8_t ble_send_buf[490];
static uint8_t ble_send_buf[BLE_SEND_BUF_SIZE];
static uint32_t ble_send_buf_len = 0;
static uint32_t num_msg = 0;
static int64_t time_old = 0;
Expand Down Expand Up @@ -660,40 +688,66 @@ static void ble_task(void *pvParameters)
{
while((xQueuePeek(*xBle_TX_Queue, ( void * ) &tx_buffer, 0) == pdTRUE))
{
if(ble_send_buf_len + tx_buffer.usLen < sizeof(ble_send_buf))
// figure out how many packets are needed to send this tx_buffer
int num_req_packets = ((ble_send_buf_len + tx_buffer.usLen) / ble_max_data_size);
// Round up. Only part of a packet might be needed and integer math rounds down.
if((ble_send_buf_len + tx_buffer.usLen) % ble_max_data_size) {
num_req_packets++;
}

if(free_packet < num_req_packets)
{
xQueueReceive(*xBle_TX_Queue, ( void * ) &tx_buffer, 0);
// We don't have enough free_packets to send this item
break;
}

memcpy(ble_send_buf+ble_send_buf_len, tx_buffer.ucElement, tx_buffer.usLen);
num_msg++;
ble_send_buf_len += tx_buffer.usLen;
if(esp_timer_get_time() - time_old > 1000*1000)
{
time_old = esp_timer_get_time();
xQueueReceive(*xBle_TX_Queue, ( void * ) &tx_buffer, 0);
num_msg++;
if(esp_timer_get_time() - time_old > 1000*1000)
{
time_old = esp_timer_get_time();

// ESP_LOGI(GATTS_TABLE_TAG, "msg %u/sec", num_msg);
num_msg = 0;
}
// ESP_LOGI(GATTS_TABLE_TAG, "msg %u/sec", num_msg);
num_msg = 0;
}
else
int tx_buffer_copied = 0;
while(tx_buffer_copied < tx_buffer.usLen)
{
// xEventGroupWaitBits(s_ble_event_group,
// BLE_CONNECTED_BIT,
// pdFALSE,
// pdFALSE,
// portMAX_DELAY);
// ESP_LOG_BUFFER_HEXDUMP(GATTS_TABLE_TAG, ble_send_buf, ble_send_buf_len, ESP_LOG_INFO);
// vTaskDelay(pdMS_TO_TICKS(30000));
ble_send(ble_send_buf, ble_send_buf_len);
ble_send_buf_len = 0;
if(--free_packet == 0)
int ble_send_buf_remaining = ble_max_data_size - ble_send_buf_len;
int tx_buffer_remaining = tx_buffer.usLen - tx_buffer_copied;
// only copy bytes that will fit in the ble_send_buf
int copy_len = tx_buffer_remaining >= ble_send_buf_remaining ? ble_send_buf_remaining : tx_buffer_remaining;
memcpy(ble_send_buf+ble_send_buf_len, tx_buffer.ucElement+tx_buffer_copied, copy_len);
ble_send_buf_len += copy_len;
tx_buffer_copied += copy_len;

// If we have a full ble_send_buf, send it. Otherwise wait to fill it with more bytes,
// If there are no more bytes to fill it with, then it will be sent at the end.
if(ble_send_buf_len == ble_max_data_size)
{
break;
// xEventGroupWaitBits(s_ble_event_group,
// BLE_CONNECTED_BIT,
// pdFALSE,
// pdFALSE,
// portMAX_DELAY);
// ESP_LOG_BUFFER_HEXDUMP(GATTS_TABLE_TAG, ble_send_buf, ble_send_buf_len, ESP_LOG_INFO);
// vTaskDelay(pdMS_TO_TICKS(30000));
ble_send(ble_send_buf, ble_send_buf_len);
ble_send_buf_len = 0;
if(--free_packet == 0 && tx_buffer_remaining > 0)
{
// We did a computation above to make sure we had a enough
// free_packets. If we are down to zero and there are still
// bytes to send, then something went wrong
ESP_LOGE(GATTS_TABLE_TAG, "Ran out of free_packets too soon");
break;
}
}
}
}
if(free_packet != 0 && ble_send_buf_len != 0)
{
// ESP_LOG_BUFFER_HEXDUMP(GATTS_TABLE_TAG, ble_send_buf, ble_send_buf_len, ESP_LOG_INFO);
ble_send(ble_send_buf, ble_send_buf_len);
ble_send_buf_len = 0;
}
Expand Down Expand Up @@ -743,6 +797,9 @@ void ble_send(uint8_t* buf, uint8_t buf_len)
if(ble_tx_ready())
{
esp_ble_gatts_send_indicate(spp_gatts_if, spp_conn_id, profile_handle_table[IDX_CHAR_VAL_A],buf_len, buf, false);
// The ESP SPP server demo adds a 20ms delay after each send.
// It doesn't seem like it is needed in the WiCAN case.
// vTaskDelay(20 / portTICK_PERIOD_MS);
}
}
static uint32_t ble_pass_key = 0;
Expand Down Expand Up @@ -816,6 +873,18 @@ void ble_init(QueueHandle_t *xTXp_Queue, QueueHandle_t *xRXp_Queue, uint8_t conn
return;
}

// The documentation on how this works is not clear. It is used in the ESP
// SPP server demo. From experimentation, the value set here is sometimes
// sent back to us via the ESP_GATTS_MTU_EVT. Only when it is sent back,
// does it mean the MTU has been increased from the default of 23. From
// looking at the ESP code its max value is 517.
//
// Tested configurations:
// - Realdash on Android: this value is sent back
// - ble-serial on MacOS: this value is sent back
// - Carscanner on Android: this value is not sent back. And based on
// experimentation the message length is capped at 20 bytes which is
// consistent with the default MTU setting of 23.
esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(517);
if (local_mtu_ret){
ESP_LOGE(GATTS_TABLE_TAG, "set local MTU failed, error code = %x", local_mtu_ret);
Expand Down
23 changes: 23 additions & 0 deletions main/elm327.c
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,9 @@ const xelm327_cmd_t elm327_commands[] = {

int8_t elm327_process_cmd(uint8_t *buf, uint8_t len, twai_message_t *frame, QueueHandle_t *q)
{
// Because the cmd_buffer and cmd_len are static they keep their value
// across multiple calls. So if a buf is an incomplete command the next
// call will keep add to the cmd_buffer until the ending CR is found.
static char cmd_buffer[128];
static uint8_t cmd_len = 0;
static char cmd_response[128];
Expand All @@ -571,6 +574,7 @@ int8_t elm327_process_cmd(uint8_t *buf, uint8_t len, twai_message_t *frame, Queu
{
if(buf[i] == '\r' || cmd_len > 126)
{
// ESP_LOGI(TAG, "end of command i: %d, cmd_len: %u", i, cmd_len);
cmd_buffer[cmd_len] = 0;
cmd_response[0] = 0;
cmd_found_flag = 0;
Expand Down Expand Up @@ -615,6 +619,25 @@ int8_t elm327_process_cmd(uint8_t *buf, uint8_t len, twai_message_t *frame, Queu
strcat(cmd_response, "\r>");

elm327_response((char*)cmd_response, 0, q);

if(cmd_buffer[2] == 'z')
{
// When the command is a reset ignore any other commands in the buffer.
// This approach fixes an issue seen in the Carscanner Android app.
// It might actually match a real ELM327 chip since the documentation
// for the chip say an ATZ is like a full reset of the chip. So it
// would make sense that fully reset would imply anything in the
// incoming serial buffer would be cleared.
//
// In the Carscanner app a reset (ATZ) and a echo off (ATE0) are sent
// in a single BLE message. Without the code below elm327_process_cmd
// would respond to the ATZ and the ATE0. When it does this
// Carscanner gets out of sync. The Carscanner log shows all following
// commands with a response from the previous command.
cmd_len = 0;
memset(cmd_response, 0, sizeof(cmd_response));
return 0;
}
}
else if(!strncmp(cmd_buffer, "vti", 3) || !strncmp(cmd_buffer, "sti", 3))
{
Expand Down