diff --git a/prj.conf b/prj.conf index 83d4f07..01bde3e 100644 --- a/prj.conf +++ b/prj.conf @@ -38,10 +38,10 @@ CONFIG_BT_BUF_ACL_TX_SIZE=251 CONFIG_BT_BUF_ACL_RX_SIZE=251 # Connection parameters -CONFIG_BT_PERIPHERAL_PREF_MIN_INT=24 -CONFIG_BT_PERIPHERAL_PREF_MAX_INT=24 +CONFIG_BT_PERIPHERAL_PREF_MIN_INT=12 +CONFIG_BT_PERIPHERAL_PREF_MAX_INT=12 CONFIG_BT_PERIPHERAL_PREF_LATENCY=0 -CONFIG_BT_PERIPHERAL_PREF_TIMEOUT=400 +CONFIG_BT_PERIPHERAL_PREF_TIMEOUT=1000 # BLE security (production) CONFIG_BT_SMP=y diff --git a/src/ble/ble_service.c b/src/ble/ble_service.c index 930d7be..b8195da 100644 --- a/src/ble/ble_service.c +++ b/src/ble/ble_service.c @@ -4,8 +4,8 @@ * * Original VesiScan-Basic BLE implementation ported to Zephyr/NCS: * - NUS (Nordic UART Service) for data exchange - * - Advertising: 40ms interval, 3-min timeout - * - Connection: 30ms interval, 0 latency, 4s supervision timeout + * - Advertising: 40ms interval, 10-min timeout + * - Connection: 15ms interval, 0 latency, 10s supervision timeout * - TX power +8 dBm, 2M PHY preferred * - Dev mode: no security / Production mode: bonding + passkey (TODO) ******************************************************************************/ @@ -35,6 +35,9 @@ LOG_MODULE_REGISTER(ble_svc, LOG_LEVEL_INF); static struct bt_conn *current_conn; static ble_data_rx_cb_t rx_callback; static bool dfu_advertising_mode; +static bool advertising_unlimited; +static uint8_t conn_param_update_attempts; +static int64_t last_rx_conn_param_update_ms; #if IS_ENABLED(CONFIG_BT_SMP) #define BLE_REQUIRED_SECURITY_LEVEL BT_SECURITY_L4 @@ -60,6 +63,8 @@ static struct k_sem nus_tx_done_sem; /* BLE API는 ISR/콜백에서 직접 호출하면 안전하지 않을 수 있으므로 k_work 사용 */ static struct k_work adv_restart_work; +static struct k_work_delayable adv_timeout_work; +static struct k_work_delayable conn_param_update_work; static const bt_addr_le_t fixed_identity_addr = { .type = BT_ADDR_LE_RANDOM, .a = { @@ -271,6 +276,85 @@ static void adv_restart_handler(struct k_work *work) led_set_state(LED_STATE_ADVERTISING); } +static void adv_timeout_handler(struct k_work *work) +{ + ARG_UNUSED(work); + + if (!ble_connection_st && !advertising_unlimited && !dfu_advertising_mode) + { + DBG_PRINTF("[BLE] Advertising timeout\r\n"); + (void)ble_advertising_stop(); + } +} + +static bool ble_conn_param_is_preferred(uint16_t interval, uint16_t latency, + uint16_t timeout) +{ + return (interval == BLE_MIN_CONN_INTERVAL) && + (latency == BLE_SLAVE_LATENCY) && + (timeout == BLE_CONN_SUP_TIMEOUT); +} + +static int ble_request_preferred_conn_params(bool count_attempt) +{ + struct bt_le_conn_param conn_param = + { + .interval_min = BLE_MIN_CONN_INTERVAL, + .interval_max = BLE_MAX_CONN_INTERVAL, + .latency = BLE_SLAVE_LATENCY, + .timeout = BLE_CONN_SUP_TIMEOUT, + }; + int err; + + if (current_conn == NULL) + { + return -ENOTCONN; + } + + if (count_attempt) + { + conn_param_update_attempts++; + } + + DBG_PRINTF("[BLE] Request params: interval=%u latency=%u timeout=%u attempt=%u\r\n", + BLE_MIN_CONN_INTERVAL, + BLE_SLAVE_LATENCY, + BLE_CONN_SUP_TIMEOUT, + conn_param_update_attempts); + + err = bt_conn_le_param_update(current_conn, &conn_param); + if (err) + { + DBG_ERR("[BLE] Param update failed err=%d -> disconnect\r\n", err); + (void)bt_conn_disconnect(current_conn, BT_HCI_ERR_UNACCEPT_CONN_PARAM); + return err; + } + + return 0; +} + +static void conn_param_update_handler(struct k_work *work) +{ + ARG_UNUSED(work); + + if (current_conn == NULL) + { + return; + } + + if (conn_param_update_attempts >= BLE_CONN_PARAM_MAX_ATTEMPTS) + { + return; + } + + if (ble_request_preferred_conn_params(true) == 0 && + conn_param_update_attempts < BLE_CONN_PARAM_MAX_ATTEMPTS) + { + k_work_schedule(&conn_param_update_work, + K_MSEC(BLE_CONN_PARAM_RETRY_INTERVAL_MS)); + } +} + /*============================================================================== * Advertising data *============================================================================*/ @@ -312,15 +396,10 @@ static void connected(struct bt_conn *conn, uint8_t err) bt_addr_le_to_str(bt_conn_get_dst(conn), addr_str, sizeof(addr_str)); DBG_CORE("[BLE] Peer: %s\r\n", addr_str); - /* Connection parameters (30ms interval) */ - struct bt_le_conn_param conn_param = - { - .interval_min = BLE_MIN_CONN_INTERVAL, - .interval_max = BLE_MAX_CONN_INTERVAL, - .latency = BLE_SLAVE_LATENCY, - .timeout = BLE_CONN_SUP_TIMEOUT, - }; - bt_conn_le_param_update(conn, &conn_param); + conn_param_update_attempts = 0U; + last_rx_conn_param_update_ms = 0; + k_work_schedule(&conn_param_update_work, + K_MSEC(BLE_CONN_PARAM_FIRST_DELAY_MS)); #if IS_ENABLED(CONFIG_BT_SMP) err = bt_conn_set_security(conn, BLE_REQUIRED_SECURITY_LEVEL); @@ -339,6 +418,10 @@ static void connected(struct bt_conn *conn, uint8_t err) static void disconnected(struct bt_conn *conn, uint8_t reason) { + k_work_cancel_delayable(&conn_param_update_work); + conn_param_update_attempts = 0U; + last_rx_conn_param_update_ms = 0; + if (current_conn) { bt_conn_unref(current_conn); @@ -346,17 +429,48 @@ static void disconnected(struct bt_conn *conn, uint8_t reason) } ble_connection_st = false; + advertising_unlimited = true; DBG_CORE("[BLE] Disconnected (reason 0x%02x)\r\n", reason); /* 워크큐에서 advertising 재시작 */ k_work_submit(&adv_restart_work); } +static bool le_param_req(struct bt_conn *conn, struct bt_le_conn_param *param) +{ + ARG_UNUSED(conn); + + if (!ble_conn_param_is_preferred(param->interval_min, + param->latency, + param->timeout) || + (param->interval_max != BLE_MAX_CONN_INTERVAL)) + { + DBG_PRINTF("[BLE] Override requested params: min=%u max=%u latency=%u timeout=%u\r\n", + param->interval_min, + param->interval_max, + param->latency, + param->timeout); + param->interval_min = BLE_MIN_CONN_INTERVAL; + param->interval_max = BLE_MAX_CONN_INTERVAL; + param->latency = BLE_SLAVE_LATENCY; + param->timeout = BLE_CONN_SUP_TIMEOUT; + } + + return true; +} + static void le_param_updated(struct bt_conn *conn, uint16_t interval, uint16_t latency, uint16_t timeout) { + ARG_UNUSED(conn); + DBG_PRINTF("[BLE] Params: interval=%d latency=%d timeout=%d\r\n", interval, latency, timeout); + + if (ble_conn_param_is_preferred(interval, latency, timeout)) + { + k_work_cancel_delayable(&conn_param_update_work); + } } #if IS_ENABLED(CONFIG_BT_SMP) @@ -379,6 +493,7 @@ BT_CONN_CB_DEFINE(conn_callbacks) = { .connected = connected, .disconnected = disconnected, + .le_param_req = le_param_req, .le_param_updated = le_param_updated, #if IS_ENABLED(CONFIG_BT_SMP) .security_changed = security_changed, @@ -401,6 +516,19 @@ static void nus_received(struct bt_conn *conn, const uint8_t *data, uint16_t len #endif DBG_CORE("[NUS RX] len=%u\r\n", len); + + if (current_conn == conn) + { + int64_t now_ms = k_uptime_get(); + + if ((last_rx_conn_param_update_ms == 0) || + ((now_ms - last_rx_conn_param_update_ms) >= BLE_CONN_PARAM_RETRY_INTERVAL_MS)) + { + last_rx_conn_param_update_ms = now_ms; + (void)ble_request_preferred_conn_params(false); + } + } + if (rx_callback) { rx_callback(data, len); @@ -433,6 +561,8 @@ int ble_service_init(ble_data_rx_cb_t rx_cb) k_sem_init(&nus_tx_done_sem, 0, 1); k_work_init(&adv_restart_work, adv_restart_handler); + k_work_init_delayable(&adv_timeout_work, adv_timeout_handler); + k_work_init_delayable(&conn_param_update_work, conn_param_update_handler); err = ble_configure_fixed_identity(); if (err) @@ -490,7 +620,7 @@ int ble_advertising_start(void) struct bt_le_adv_param adv_param = BT_LE_ADV_PARAM_INIT( BT_LE_ADV_OPT_CONN, APP_ADV_INTERVAL, /* min interval */ - APP_ADV_INTERVAL + 16, /* max interval (slight window) */ + APP_ADV_INTERVAL, /* max interval */ NULL /* undirected */ ); @@ -501,6 +631,12 @@ int ble_advertising_start(void) return err; } + k_work_cancel_delayable(&adv_timeout_work); + if (!advertising_unlimited && !dfu_advertising_mode) + { + k_work_schedule(&adv_timeout_work, K_MSEC(APP_ADV_DURATION * 10)); + } + DBG_PRINTF("[BLE] Advertising started (%s)\r\n", dfu_advertising_mode ? "SMP" : "NUS"); return 0; } @@ -514,6 +650,7 @@ int ble_advertising_stop(void) return err; } + k_work_cancel_delayable(&adv_timeout_work); DBG_PRINTF("[BLE] Advertising stopped\r\n"); return 0; } diff --git a/src/ble/ble_service.h b/src/ble/ble_service.h index 5896e5f..a11dee3 100644 --- a/src/ble/ble_service.h +++ b/src/ble/ble_service.h @@ -18,12 +18,16 @@ #define BLE_DEV_MODE 0 /* 1: Dev (no security), 0: Production */ #define APP_ADV_INTERVAL 64 /* 64 x 0.625ms = 40ms */ -#define APP_ADV_DURATION 18000 /* 18000 x 10ms = 180s (3 min) */ +#define APP_ADV_DURATION 60000 /* 60000 x 10ms = 600s (10 min) */ -#define BLE_MIN_CONN_INTERVAL 24 /* 30ms / 1.25ms = 24 */ -#define BLE_MAX_CONN_INTERVAL 24 /* 30ms / 1.25ms = 24 */ +#define BLE_MIN_CONN_INTERVAL 12 /* 15ms / 1.25ms = 12 */ +#define BLE_MAX_CONN_INTERVAL 12 /* 15ms / 1.25ms = 12 */ #define BLE_SLAVE_LATENCY 0 -#define BLE_CONN_SUP_TIMEOUT 400 /* 4000ms / 10ms = 400 */ +#define BLE_CONN_SUP_TIMEOUT 1000 /* 10000ms / 10ms = 1000 */ + +#define BLE_CONN_PARAM_FIRST_DELAY_MS 5000 +#define BLE_CONN_PARAM_RETRY_INTERVAL_MS 30000 +#define BLE_CONN_PARAM_MAX_ATTEMPTS 3 #define BLE_TX_POWER 8 /* +8 dBm */