-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
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
feat(split): allow central to connect to multiple peripherals #836
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -47,12 +47,6 @@ static uint8_t passkey_digit = 0; | |
|
||
#endif /* IS_ENABLED(CONFIG_ZMK_BLE_PASSKEY_ENTRY) */ | ||
|
||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL) | ||
#define PROFILE_COUNT (CONFIG_BT_MAX_PAIRED - 1) | ||
#else | ||
#define PROFILE_COUNT CONFIG_BT_MAX_PAIRED | ||
#endif | ||
|
||
enum advertising_type { | ||
ZMK_ADV_NONE, | ||
ZMK_ADV_DIR, | ||
|
@@ -84,7 +78,7 @@ static const struct bt_data zmk_ble_ad[] = { | |
|
||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL) | ||
|
||
static bt_addr_le_t peripheral_addr; | ||
static bt_addr_le_t peripheral_addrs[ZMK_SPLIT_BLE_PERIPHERAL_COUNT]; | ||
|
||
#endif /* IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL) */ | ||
|
||
|
@@ -283,9 +277,34 @@ char *zmk_ble_active_profile_name() { return profiles[active_profile].name; } | |
|
||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL) | ||
|
||
void zmk_ble_set_peripheral_addr(bt_addr_le_t *addr) { | ||
memcpy(&peripheral_addr, addr, sizeof(bt_addr_le_t)); | ||
settings_save_one("ble/peripheral_address", addr, sizeof(bt_addr_le_t)); | ||
int zmk_ble_put_peripheral_addr(const bt_addr_le_t *addr) { | ||
for (int i = 0; i < ZMK_SPLIT_BLE_PERIPHERAL_COUNT; i++) { | ||
// If the address is recognized and already stored in settings, return | ||
// index and no additional action is necessary. | ||
if (!bt_addr_le_cmp(&peripheral_addrs[i], addr)) { | ||
return i; | ||
} | ||
|
||
// If the peripheral address slot is open, store new peripheral in the | ||
// slot and return index. This compares against BT_ADDR_LE_ANY as that | ||
// is the zero value. | ||
if (!bt_addr_le_cmp(&peripheral_addrs[i], BT_ADDR_LE_ANY)) { | ||
char addr_str[BT_ADDR_LE_STR_LEN]; | ||
bt_addr_le_to_str(addr, addr_str, sizeof(addr_str)); | ||
LOG_DBG("Storing peripheral %s in slot %d", addr_str, i); | ||
bt_addr_le_copy(&peripheral_addrs[i], addr); | ||
|
||
char setting_name[32]; | ||
sprintf(setting_name, "ble/peripheral_addresses/%d", i); | ||
settings_save_one(setting_name, addr, sizeof(bt_addr_le_t)); | ||
|
||
return i; | ||
} | ||
} | ||
|
||
// The peripheral does not match a known peripheral and there is no | ||
// available slot. | ||
return -ENOMEM; | ||
} | ||
|
||
#endif /* IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL) */ | ||
|
@@ -340,15 +359,20 @@ static int ble_profiles_handle_set(const char *name, size_t len, settings_read_c | |
} | ||
} | ||
#if IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL) | ||
else if (settings_name_steq(name, "peripheral_address", &next) && !next) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this line will solve multiple problems i have currently with my split setup |
||
else if (settings_name_steq(name, "peripheral_addresses", &next) && next) { | ||
if (len != sizeof(bt_addr_le_t)) { | ||
return -EINVAL; | ||
} | ||
|
||
int err = read_cb(cb_arg, &peripheral_addr, sizeof(bt_addr_le_t)); | ||
if (err <= 0) { | ||
LOG_ERR("Failed to handle peripheral address from settings (err %d)", err); | ||
return err; | ||
int i = atoi(next); | ||
if (i < 0 || i >= ZMK_SPLIT_BLE_PERIPHERAL_COUNT) { | ||
LOG_ERR("Failed to store peripheral address in memory"); | ||
} else { | ||
int err = read_cb(cb_arg, &peripheral_addrs[i], sizeof(bt_addr_le_t)); | ||
if (err <= 0) { | ||
LOG_ERR("Failed to handle peripheral address from settings (err %d)", err); | ||
return err; | ||
} | ||
} | ||
} | ||
#endif | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,7 +26,7 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); | |
#include <zmk/event_manager.h> | ||
#include <zmk/events/position_state_changed.h> | ||
|
||
static int start_scan(void); | ||
static int start_scanning(void); | ||
|
||
#define POSITION_STATE_DATA_LEN 16 | ||
|
||
|
@@ -49,6 +49,8 @@ struct peripheral_slot { | |
|
||
static struct peripheral_slot peripherals[ZMK_SPLIT_BLE_PERIPHERAL_COUNT]; | ||
|
||
static bool is_scanning = false; | ||
|
||
static const struct bt_uuid_128 split_service_uuid = BT_UUID_INIT_128(ZMK_SPLIT_BT_SERVICE_UUID); | ||
|
||
K_MSGQ_DEFINE(peripheral_event_msgq, sizeof(struct zmk_position_state_changed), | ||
|
@@ -130,8 +132,9 @@ int release_peripheral_slot(int index) { | |
return 0; | ||
} | ||
|
||
int reserve_peripheral_slot() { | ||
for (int i = 0; i < ZMK_SPLIT_BLE_PERIPHERAL_COUNT; i++) { | ||
int reserve_peripheral_slot(const bt_addr_le_t *addr) { | ||
int i = zmk_ble_put_peripheral_addr(addr); | ||
if (i >= 0) { | ||
if (peripherals[i].state == PERIPHERAL_SLOT_STATE_OPEN) { | ||
// Be sure the slot is fully reinitialized. | ||
release_peripheral_slot(i); | ||
|
@@ -344,9 +347,54 @@ static void split_central_process_connection(struct bt_conn *conn) { | |
|
||
LOG_DBG("New connection params: Interval: %d, Latency: %d, PHY: %d", info.le.interval, | ||
info.le.latency, info.le.phy->rx_phy); | ||
|
||
// Restart scanning if necessary. | ||
start_scanning(); | ||
} | ||
|
||
static int stop_scanning() { | ||
LOG_DBG("Stopping peripheral scanning"); | ||
is_scanning = false; | ||
|
||
int err = bt_le_scan_stop(); | ||
if (err < 0) { | ||
LOG_ERR("Stop LE scan failed (err %d)", err); | ||
return err; | ||
} | ||
|
||
return 0; | ||
} | ||
|
||
static bool split_central_eir_found(struct bt_data *data, void *user_data) { | ||
static bool split_central_eir_found(const bt_addr_le_t *addr) { | ||
LOG_DBG("Found the split service"); | ||
|
||
// Stop scanning so we can connect to the peripheral device. | ||
int err = stop_scanning(); | ||
if (err < 0) { | ||
return false; | ||
} | ||
|
||
int slot_idx = reserve_peripheral_slot(addr); | ||
if (slot_idx < 0) { | ||
LOG_ERR("Failed to reserve peripheral slot (err %d)", slot_idx); | ||
return false; | ||
} | ||
|
||
struct peripheral_slot *slot = &peripherals[slot_idx]; | ||
|
||
LOG_DBG("Initiating new connnection"); | ||
struct bt_le_conn_param *param = BT_LE_CONN_PARAM(0x0006, 0x0006, 30, 400); | ||
err = bt_conn_le_create(addr, BT_CONN_LE_CREATE_CONN, param, &slot->conn); | ||
if (err < 0) { | ||
LOG_ERR("Create conn failed (err %d) (create conn? 0x%04x)", err, BT_HCI_OP_LE_CREATE_CONN); | ||
release_peripheral_slot(slot_idx); | ||
start_scanning(); | ||
} | ||
|
||
return false; | ||
} | ||
|
||
static bool split_central_eir_parse(struct bt_data *data, void *user_data) { | ||
bt_addr_le_t *addr = user_data; | ||
int i; | ||
|
||
|
@@ -361,9 +409,7 @@ static bool split_central_eir_found(struct bt_data *data, void *user_data) { | |
} | ||
|
||
for (i = 0; i < data->data_len; i += 16) { | ||
struct bt_le_conn_param *param; | ||
struct bt_uuid_128 uuid; | ||
int err; | ||
|
||
if (!bt_uuid_create(&uuid.uuid, &data->data[i], 16)) { | ||
LOG_ERR("Unable to load UUID"); | ||
|
@@ -381,46 +427,7 @@ static bool split_central_eir_found(struct bt_data *data, void *user_data) { | |
continue; | ||
} | ||
|
||
LOG_DBG("Found the split service"); | ||
|
||
zmk_ble_set_peripheral_addr(addr); | ||
|
||
err = bt_le_scan_stop(); | ||
if (err) { | ||
LOG_ERR("Stop LE scan failed (err %d)", err); | ||
continue; | ||
} | ||
|
||
uint8_t slot_idx = reserve_peripheral_slot(); | ||
if (slot_idx < 0) { | ||
LOG_ERR("Faild to reserve peripheral slot (err %d)", slot_idx); | ||
continue; | ||
} | ||
|
||
struct peripheral_slot *slot = &peripherals[slot_idx]; | ||
|
||
slot->conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, addr); | ||
if (slot->conn) { | ||
LOG_DBG("Found existing connection"); | ||
split_central_process_connection(slot->conn); | ||
err = bt_conn_le_phy_update(slot->conn, BT_CONN_LE_PHY_PARAM_2M); | ||
if (err) { | ||
LOG_ERR("Update phy conn failed (err %d)", err); | ||
} | ||
} else { | ||
param = BT_LE_CONN_PARAM(0x0006, 0x0006, 30, 400); | ||
|
||
LOG_DBG("Initiating new connnection"); | ||
|
||
err = bt_conn_le_create(addr, BT_CONN_LE_CREATE_CONN, param, &slot->conn); | ||
if (err) { | ||
LOG_ERR("Create conn failed (err %d) (create conn? 0x%04x)", err, | ||
BT_HCI_OP_LE_CREATE_CONN); | ||
start_scan(); | ||
} | ||
} | ||
|
||
return false; | ||
return split_central_eir_found(addr); | ||
} | ||
} | ||
|
||
|
@@ -436,15 +443,34 @@ static void split_central_device_found(const bt_addr_le_t *addr, int8_t rssi, ui | |
|
||
/* We're only interested in connectable events */ | ||
if (type == BT_GAP_ADV_TYPE_ADV_IND || type == BT_GAP_ADV_TYPE_ADV_DIRECT_IND) { | ||
bt_data_parse(ad, split_central_eir_found, (void *)addr); | ||
bt_data_parse(ad, split_central_eir_parse, (void *)addr); | ||
} | ||
} | ||
|
||
static int start_scan(void) { | ||
int err; | ||
static int start_scanning(void) { | ||
// No action is necessary if central is already scanning. | ||
if (is_scanning) { | ||
LOG_DBG("Scanning already running"); | ||
return 0; | ||
} | ||
|
||
err = bt_le_scan_start(BT_LE_SCAN_PASSIVE, split_central_device_found); | ||
if (err) { | ||
// If all the devices are connected, there is no need to scan. | ||
bool has_unconnected = false; | ||
for (int i = 0; i < CONFIG_ZMK_SPLIT_BLE_CENTRAL_PERIPHERALS; i++) { | ||
if (peripherals[i].conn == NULL) { | ||
has_unconnected = true; | ||
break; | ||
} | ||
} | ||
if (!has_unconnected) { | ||
LOG_DBG("All devices are connected, scanning is unnecessary"); | ||
return 0; | ||
} | ||
|
||
// Start scanning otherwise. | ||
is_scanning = true; | ||
int err = bt_le_scan_start(BT_LE_SCAN_PASSIVE, split_central_device_found); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is fine for now, but ideally a future enhancement will add a backoff on scanning, since multi-peripheral setups are more likely to have one of them only on some of the time. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm exploring this, bt_le_scan_start(&test_scan_params, split_central_device_found) where, static struct bt_le_scan_param test_scan_params = {
.type = BT_LE_SCAN_TYPE_PASSIVE,
.options = BT_LE_SCAN_OPT_FILTER_DUPLICATE,
.interval = BT_GAP_SCAN_FAST_INTERVAL,
.window = BT_GAP_SCAN_FAST_WINDOW,
.timeout = 1500,
.interval_coded = 0,
.window_coded = 0,
}; Setting any
Any ideas? I was hoping to register scan callbacks where after the 15s timeout, scanning is restarted with new params with a longer interval and window and an indefinite timeout. |
||
if (err < 0) { | ||
LOG_ERR("Scanning failed to start (err %d)", err); | ||
return err; | ||
} | ||
|
@@ -471,7 +497,7 @@ static void split_central_connected(struct bt_conn *conn, uint8_t conn_err) { | |
|
||
release_peripheral_slot_for_conn(conn); | ||
|
||
start_scan(); | ||
start_scanning(); | ||
return; | ||
} | ||
|
||
|
@@ -495,7 +521,7 @@ static void split_central_disconnected(struct bt_conn *conn, uint8_t reason) { | |
return; | ||
} | ||
|
||
start_scan(); | ||
start_scanning(); | ||
} | ||
|
||
static struct bt_conn_cb conn_callbacks = { | ||
|
@@ -527,6 +553,10 @@ void split_central_split_run_callback(struct k_work *work) { | |
LOG_ERR("Source not connected"); | ||
continue; | ||
} | ||
if (!peripherals[payload_wrapper.source].run_behavior_handle) { | ||
LOG_ERR("Run behavior handle not found"); | ||
continue; | ||
} | ||
|
||
int err = bt_gatt_write_without_response( | ||
peripherals[payload_wrapper.source].conn, | ||
|
@@ -590,7 +620,7 @@ int zmk_split_bt_central_init(const struct device *_arg) { | |
CONFIG_ZMK_BLE_THREAD_PRIORITY, NULL); | ||
bt_conn_cb_register(&conn_callbacks); | ||
|
||
return start_scan(); | ||
return start_scanning(); | ||
} | ||
|
||
SYS_INIT(zmk_split_bt_central_init, APPLICATION, CONFIG_ZMK_BLE_INIT_PRIORITY); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if the recorded peripheral appears after slot
i+1
, and takesi
th slot?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't quite understand what
i+1
you are referring to.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if the
addr
is actually recorded at some slotj
, wherej
>i
, but somehow thei
th slot is open, so theaddr
takes away thei
th slot as well.This may be a rare case, and
j
th slot anyhow could be recycled for theaddr
should triggerdisconnected
function in normal cases.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Slot 0 is filled, then slot 1 is filled, and so on. There is no code to delete just a single
ble/peripheral_addresses/%d
setting so it's not possible to havei
open andj
occupied wherei < j
.