Browse Source

feat(bluetooth): Proper bond management, identity support for non-splits

* Add `bt` behavior that can be used to perform certain actions,
  such as next/prev identity, reset identity, etc.
  NOTE: Multiple identities is only supported for non-split shields,
  due to missing Zephyr identity functionality for dual
  central/peripheral devices.
* Proper bond reset tied to action, that honors peripheral bonds,
  so folks can reset and pair to other hosts, without breaking
  bonds between splt halves.
xmkb
Pete Johanson 4 years ago
parent
commit
cf970efb98
  1. 5
      app/CMakeLists.txt
  2. 29
      app/Kconfig
  3. 1
      app/dts/behaviors.dtsi
  4. 9
      app/dts/behaviors/bluetooth.dtsi
  5. 8
      app/dts/bindings/behaviors/zmk,behavior-bluetooth.yaml
  6. 17
      app/include/dt-bindings/zmk/bt.h
  7. 13
      app/include/zmk/ble.h
  8. 62
      app/src/behaviors/behavior_bt.c
  9. 335
      app/src/ble.c
  10. 49
      app/src/split/bluetooth/central.c
  11. 82
      docs/docs/behavior/bluetooth.md
  12. 1
      docs/sidebars.js

5
app/CMakeLists.txt

@ -41,8 +41,11 @@ target_sources(app PRIVATE src/behaviors/behavior_toggle_layer.c)
target_sources(app PRIVATE src/behaviors/behavior_transparent.c) target_sources(app PRIVATE src/behaviors/behavior_transparent.c)
target_sources(app PRIVATE src/behaviors/behavior_none.c) target_sources(app PRIVATE src/behaviors/behavior_none.c)
target_sources(app PRIVATE src/behaviors/behavior_sensor_rotate_key_press.c) target_sources(app PRIVATE src/behaviors/behavior_sensor_rotate_key_press.c)
target_sources(app PRIVATE src/keymap.c) if(NOT CONFIG_ZMK_BLE_SPLIT_PERIPHERAL)
target_sources(app PRIVATE src/keymap.c)
endif()
target_sources_ifdef(CONFIG_ZMK_RGB_UNDERGLOW app PRIVATE src/behaviors/behavior_rgb_underglow.c) target_sources_ifdef(CONFIG_ZMK_RGB_UNDERGLOW app PRIVATE src/behaviors/behavior_rgb_underglow.c)
target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/behaviors/behavior_bt.c)
target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/ble.c) target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/ble.c)
target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/ble_unpair_combo.c) target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/ble_unpair_combo.c)
target_sources_ifdef(CONFIG_ZMK_SPLIT_BLE_ROLE_PERIPHERAL app PRIVATE src/split_listener.c) target_sources_ifdef(CONFIG_ZMK_SPLIT_BLE_ROLE_PERIPHERAL app PRIVATE src/split_listener.c)

29
app/Kconfig

@ -40,8 +40,6 @@ menuconfig ZMK_BLE
select BT_PERIPHERAL select BT_PERIPHERAL
select BT_GATT_DIS select BT_GATT_DIS
select BT_GATT_BAS select BT_GATT_BAS
select SETTINGS
# select BT_SETTINGS
if ZMK_BLE if ZMK_BLE
@ -49,6 +47,12 @@ config ZMK_BLE_INIT_PRIORITY
int "Init Priority" int "Init Priority"
default 50 default 50
config SETTINGS
default y
config BT_SETTINGS
default y
config SYSTEM_WORKQUEUE_STACK_SIZE config SYSTEM_WORKQUEUE_STACK_SIZE
default 2048 default 2048
@ -103,8 +107,11 @@ config ZMK_SPLIT_BLE_ROLE_CENTRAL
if ZMK_SPLIT_BLE_ROLE_CENTRAL if ZMK_SPLIT_BLE_ROLE_CENTRAL
config BT_MAX_PAIRED
default 2
config BT_MAX_CONN config BT_MAX_CONN
default 5 default 2
config BT_MAX_PAIRED config BT_MAX_PAIRED
# Bump this everywhere once we support switching active connections! # Bump this everywhere once we support switching active connections!
@ -120,15 +127,18 @@ if ZMK_SPLIT_BLE_ROLE_PERIPHERAL
config ZMK_USB config ZMK_USB
default n default n
config BT_MAX_PAIRED
default 1
config BT_MAX_CONN config BT_MAX_CONN
default 5 default 2
config BT_GAP_AUTO_UPDATE_CONN_PARAMS config BT_GAP_AUTO_UPDATE_CONN_PARAMS
default n default n
endif endif
endchoice endchoice
endif endif
@ -138,6 +148,17 @@ endif
endmenu endmenu
if ZMK_BLE && !ZMK_SPLIT_BLE
config BT_ID_MAX
default 5
# Used to update the name to include the identity used
config BT_DEVICE_NAME_DYNAMIC
default y
endif
config ZMK_KSCAN_MOCK_DRIVER config ZMK_KSCAN_MOCK_DRIVER
bool "Enable mock kscan driver to simulate key presses" bool "Enable mock kscan driver to simulate key presses"
default n default n

1
app/dts/behaviors.dtsi

@ -8,3 +8,4 @@
#include <behaviors/reset.dtsi> #include <behaviors/reset.dtsi>
#include <behaviors/sensor_rotate_key_press.dtsi> #include <behaviors/sensor_rotate_key_press.dtsi>
#include <behaviors/rgb_underglow.dtsi> #include <behaviors/rgb_underglow.dtsi>
#include <behaviors/bluetooth.dtsi>

9
app/dts/behaviors/bluetooth.dtsi

@ -0,0 +1,9 @@
/ {
behaviors {
bt: behavior_bluetooth {
compatible = "zmk,behavior-bluetooth";
label = "BLUETOOTH";
#binding-cells = <2>;
};
};
};

8
app/dts/bindings/behaviors/zmk,behavior-bluetooth.yaml

@ -0,0 +1,8 @@
# Copyright (c) 2020, Peter Johanson
# SPDX-License-Identifier: MIT
description: Bluetooth Behavior
compatible: "zmk,behavior-bluetooth"
include: two_param.yaml

17
app/include/dt-bindings/zmk/bt.h

@ -0,0 +1,17 @@
/*
* Copyright (c) 2020 Peter Johanson <peter@peterjohanson.com>
*
* SPDX-License-Identifier: MIT
*/
#define BT_RST_CMD 0
#define BT_IDENT_NEXT_CMD 1
#define BT_IDENT_PREV_CMD 2
#define BT_IDENT_SEL_CMD 3
#define BT_IDENT_CLR_CMD 4
#define BT_RST BT_RST_CMD 0
#define BT_IDENT_NEXT BT_IDENT_NEXT_CMD 0
#define BT_IDENT_PREV BT_IDENT_PREV_CMD 0
#define BT_IDENT_SEL BT_IDENT_SEL_CMD
#define BT_IDENT_CLR BT_IDENT_CLR_CMD 0

13
app/include/zmk/ble.h

@ -6,7 +6,20 @@
#pragma once #pragma once
#include <bluetooth/addr.h>
#include <zmk/keys.h> #include <zmk/keys.h>
int zmk_ble_adv_pause();
int zmk_ble_adv_resume();
int zmk_ble_identity_clear();
int zmk_ble_identity_next();
int zmk_ble_identity_prev();
int zmk_ble_identity_select(u8_t index);
int zmk_ble_unpair_all(); int zmk_ble_unpair_all();
bool zmk_ble_handle_key_user(struct zmk_key_event *key_event); bool zmk_ble_handle_key_user(struct zmk_key_event *key_event);
#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL)
void zmk_ble_set_peripheral_addr(bt_addr_le_t *addr);
#endif /* IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL) */

62
app/src/behaviors/behavior_bt.c

@ -0,0 +1,62 @@
/*
* Copyright (c) 2020 Peter Johanson <peter@peterjohanson.com>
*
* SPDX-License-Identifier: MIT
*/
#define DT_DRV_COMPAT zmk_behavior_bluetooth
#include <device.h>
#include <drivers/behavior.h>
#include <dt-bindings/zmk/bt.h>
#include <bluetooth/conn.h>
#include <logging/log.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#include <zmk/ble.h>
static int on_keymap_binding_pressed(struct device *dev, u32_t position, u32_t command, u32_t arg)
{
switch (command)
{
case BT_RST_CMD:
return zmk_ble_unpair_all();
case BT_IDENT_CLR_CMD:
return zmk_ble_identity_clear();
#if CONFIG_BT_ID_MAX != 1
case BT_IDENT_NEXT_CMD:
return zmk_ble_identity_next();
case BT_IDENT_PREV_CMD:
return zmk_ble_identity_prev();
case BT_IDENT_SEL_CMD:
return zmk_ble_identity_select(arg);
#endif /* BT_ID_MAX != 1 */
}
return -ENOTSUP;
}
static int behavior_bt_init(struct device *dev)
{
return 0;
};
static int on_keymap_binding_released(struct device *dev, u32_t position, u32_t command, u32_t arg)
{
return 0;
}
static const struct behavior_driver_api behavior_bt_driver_api = {
.binding_pressed = on_keymap_binding_pressed,
.binding_released = on_keymap_binding_released,
};
DEVICE_AND_API_INIT(behavior_bt, DT_INST_LABEL(0),
behavior_bt_init,
NULL,
NULL,
APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT,
&behavior_bt_driver_api);

335
app/src/ble.c

@ -8,6 +8,8 @@
#include <init.h> #include <init.h>
#include <math.h> #include <math.h>
#include <stdlib.h>
#include <stdio.h>
#include <settings/settings.h> #include <settings/settings.h>
#include <bluetooth/bluetooth.h> #include <bluetooth/bluetooth.h>
@ -15,6 +17,13 @@
#include <bluetooth/hci.h> #include <bluetooth/hci.h>
#include <bluetooth/uuid.h> #include <bluetooth/uuid.h>
#include <bluetooth/gatt.h> #include <bluetooth/gatt.h>
#include <bluetooth/hci_err.h>
#if IS_ENABLED(CONFIG_SETTINGS)
#include <settings/settings.h>
#endif
#include <logging/log.h> #include <logging/log.h>
@ -28,15 +37,248 @@ static struct bt_conn *auth_passkey_entry_conn;
static u8_t passkey_entries[6] = {0, 0, 0, 0, 0, 0}; static u8_t passkey_entries[6] = {0, 0, 0, 0, 0, 0};
static u8_t passkey_digit = 0; static u8_t passkey_digit = 0;
#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_ROLE_PERIPHERAL) #define ZMK_BT_LE_ADV_PARAM_INIT(_id, _options, _int_min, _int_max, _peer) \
#define ZMK_ADV_PARAMS BT_LE_ADV_PARAM(BT_LE_ADV_OPT_CONNECTABLE | \ { \
BT_LE_ADV_OPT_USE_NAME | \ .id = _id, \
BT_LE_ADV_OPT_ONE_TIME, \ .sid = 0, \
.secondary_max_skip = 0, \
.options = (_options), \
.interval_min = (_int_min), \
.interval_max = (_int_max), \
.peer = (_peer), \
}
#define ZMK_BT_LE_ADV_PARAM(_id, _options, _int_min, _int_max, _peer) \
((struct bt_le_adv_param[]) { \
ZMK_BT_LE_ADV_PARAM_INIT(_id, _options, _int_min, _int_max, _peer) \
})
#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL)
#define ZMK_ADV_PARAMS(_id) ZMK_BT_LE_ADV_PARAM(_id, \
BT_LE_ADV_OPT_CONNECTABLE | \
BT_LE_ADV_OPT_ONE_TIME | \
BT_LE_ADV_OPT_USE_NAME, \
BT_GAP_ADV_FAST_INT_MIN_2, \ BT_GAP_ADV_FAST_INT_MIN_2, \
BT_GAP_ADV_FAST_INT_MAX_2, NULL) BT_GAP_ADV_FAST_INT_MAX_2, NULL)
#else #else
#define ZMK_ADV_PARAMS BT_LE_ADV_CONN_NAME #define ZMK_ADV_PARAMS(_id) ZMK_BT_LE_ADV_PARAM(_id, \
BT_LE_ADV_OPT_CONNECTABLE | \
BT_LE_ADV_OPT_USE_NAME, \
BT_GAP_ADV_FAST_INT_MIN_2, \
BT_GAP_ADV_FAST_INT_MAX_2, NULL)
#endif
static const struct bt_data zmk_ble_ad[] = {
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
BT_DATA_BYTES(BT_DATA_UUID16_SOME,
#if !IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_ROLE_PERIPHERAL)
0x12, 0x18, /* HID Service */
#endif
0x0f, 0x18 /* Battery Service */
),
#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_ROLE_PERIPHERAL)
BT_DATA_BYTES(BT_DATA_UUID128_ALL,
ZMK_SPLIT_BT_SERVICE_UUID)
#endif
};
#define IDENTITY_COUNT CONFIG_BT_ID_MAX
#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL)
static bt_addr_le_t peripheral_addr;
#endif /* IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL) */
static u8_t active_identity = 0;
int zmk_ble_adv_pause()
{
int err = bt_le_adv_stop();
if (err) {
LOG_ERR("Failed to stop advertising (err %d)", err);
return err;
}
return 0;
};
int zmk_ble_adv_resume()
{
struct bt_le_adv_param *adv_params = ZMK_ADV_PARAMS(active_identity);
LOG_DBG("");
int err = bt_le_adv_start(adv_params, zmk_ble_ad, ARRAY_SIZE(zmk_ble_ad), NULL, 0);
if (err)
{
LOG_ERR("Advertising failed to start (err %d)", err);
return err;
}
return 0;
};
static void disconnect_host_connection(struct bt_conn *conn, void *arg)
{
struct bt_conn_info info;
bt_conn_get_info(conn, &info);
if (info.role != BT_CONN_ROLE_SLAVE) {
return;
}
bt_conn_disconnect(conn, BT_HCI_ERR_LOCALHOST_TERM_CONN);
};
static int activate_profile(u8_t index)
{
int err;
if (index >= IDENTITY_COUNT) {
return -EINVAL;
}
if (active_identity != index) {
LOG_DBG("Persisting new active identity");
active_identity = index;
#if IS_ENABLED(CONFIG_SETTINGS)
err = settings_save_one("ble/active_identity", &active_identity, sizeof(u8_t));
if (err) {
LOG_WRN("Failed to persist active_identity (err %d)", err);
}
#endif
#if IS_ENABLED(CONFIG_BT_DEVICE_NAME_DYNAMIC)
char name[CONFIG_BT_DEVICE_NAME_MAX];
snprintf(name, sizeof(name), "%s (Profile %d)", CONFIG_ZMK_KEYBOARD_NAME, active_identity + 1);
bt_set_name(name);
#endif /* IS_ENABLED(CONFIG_BT_DEVICE_NAME_DYNAMIC) */
}
return zmk_ble_adv_resume();
};
static int deactivate_profile(u8_t index)
{
int err = zmk_ble_adv_pause();
if (err) {
LOG_WRN("Failed to pause advertising %d", err);
}
bt_conn_foreach(BT_CONN_TYPE_ALL, disconnect_host_connection, NULL);
return 0;
};
int zmk_ble_identity_select(u8_t index)
{
LOG_DBG("index %d", index);
if (index >= IDENTITY_COUNT) {
return -EINVAL;
}
int err = deactivate_profile(active_identity);
if (err) {
LOG_WRN("Failed to deactivate profile");
return err;
}
return activate_profile(index);
};
static void unpair_non_peripheral_bonds(const struct bt_bond_info *info, void *user_data) {
char addr[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(&info->addr, addr, sizeof(addr));
#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL)
if (!bt_addr_le_cmp(&info->addr, &peripheral_addr)) {
LOG_DBG("Skipping unpairing peripheral %s", log_strdup(addr));
return;
}
#endif /* IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL) */
LOG_DBG("Unpairing %s", log_strdup(addr));
bt_unpair(active_identity, &info->addr);
}
int zmk_ble_identity_clear()
{
LOG_DBG("");
int err = deactivate_profile(active_identity);
if (err) {
return err;
}
bt_foreach_bond(active_identity, unpair_non_peripheral_bonds, NULL);
return activate_profile(active_identity);
};
int zmk_ble_identity_next()
{
LOG_DBG("active_identity %d IDENTITY_COUNT %d", active_identity, IDENTITY_COUNT);
return zmk_ble_identity_select((active_identity + 1) % IDENTITY_COUNT);
}
int zmk_ble_identity_prev()
{
LOG_DBG("");
return zmk_ble_identity_select((active_identity + IDENTITY_COUNT - 1) % IDENTITY_COUNT);
}
#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_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));
}
#endif /* IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL) */
#if IS_ENABLED(CONFIG_SETTINGS)
static int ble_profiles_handle_set(const char *name, size_t len, settings_read_cb read_cb, void *cb_arg)
{
const char *next;
LOG_DBG("Setting BLE value %s", log_strdup(name));
if (settings_name_steq(name, "active_identity", &next) && !next) {
if (len != sizeof(active_identity)) {
return -EINVAL;
}
int err = read_cb(cb_arg, &active_identity, sizeof(active_identity));
if (err <= 0) {
LOG_ERR("Failed to handle profile from settings (err %d)", err);
return err;
}
LOG_DBG("Loaded active identity %d", active_identity);
#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL)
} else if (settings_name_steq(name, "peripheral_address", &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;
}
#endif #endif
}
return 0;
};
struct settings_handler profiles_handler = {
.name = "ble",
.h_set = ble_profiles_handle_set
};
#endif /* IS_ENABLED(CONFIG_SETTINGS) */
static void connected(struct bt_conn *conn, u8_t err) static void connected(struct bt_conn *conn, u8_t err)
{ {
@ -71,6 +313,12 @@ static void disconnected(struct bt_conn *conn, u8_t reason)
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
LOG_DBG("Disconnected from %s (reason 0x%02x)", log_strdup(addr), reason); LOG_DBG("Disconnected from %s (reason 0x%02x)", log_strdup(addr), reason);
#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL)
if (bt_addr_le_cmp(&peripheral_addr, BT_ADDR_LE_ANY) && bt_addr_le_cmp(&peripheral_addr, bt_conn_get_dst(conn))) {
zmk_ble_adv_resume();
}
#endif
} }
static void security_changed(struct bt_conn *conn, bt_security_t level, static void security_changed(struct bt_conn *conn, bt_security_t level,
@ -146,19 +394,6 @@ static struct bt_conn_auth_cb zmk_ble_auth_cb_display = {
.cancel = auth_cancel, .cancel = auth_cancel,
}; };
static const struct bt_data zmk_ble_ad[] = {
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
BT_DATA_BYTES(BT_DATA_UUID16_SOME,
#if !IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_ROLE_PERIPHERAL)
0x12, 0x18, /* HID Service */
#endif
0x0f, 0x18 /* Battery Service */
),
#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_ROLE_PERIPHERAL)
BT_DATA_BYTES(BT_DATA_UUID128_ALL,
ZMK_SPLIT_BT_SERVICE_UUID)
#endif
};
static void zmk_ble_ready(int err) static void zmk_ble_ready(int err)
{ {
@ -169,17 +404,42 @@ static void zmk_ble_ready(int err)
return; return;
} }
err = bt_le_adv_start(ZMK_ADV_PARAMS, zmk_ble_ad, ARRAY_SIZE(zmk_ble_ad), NULL, 0); zmk_ble_identity_select(active_identity);
if (err)
{
LOG_ERR("Advertising failed to start (err %d)", err);
return;
}
} }
#if CONFIG_BT_ID_MAX != 1
static int initialize_identities()
{
bt_addr_le_t addrs[CONFIG_BT_ID_MAX];
size_t count = CONFIG_BT_ID_MAX;
LOG_DBG("");
bt_id_get(addrs, &count);
for (int i = 0; i < count; i++) {
char addr[BT_ADDR_LE_STR_LEN];
bt_addr_le_to_str(&addrs[i], addr, sizeof(addr));
LOG_DBG("Existing identity %s", log_strdup(addr));
}
for (int i = count; i < CONFIG_BT_ID_MAX; i++) {
LOG_DBG("Initializing identity %d", i);
int id = bt_id_create(NULL, NULL);
if (id < 0) {
LOG_ERR("Failed to create new identity with id %d", i);
return id;
}
}
return 0;
};
#endif /* CONFIG_BT_ID_MAX != 1 */
static int zmk_ble_init(struct device *_arg) static int zmk_ble_init(struct device *_arg)
{ {
int err = bt_enable(NULL); int err;
err = bt_enable(NULL);
if (err) if (err)
{ {
@ -187,11 +447,23 @@ static int zmk_ble_init(struct device *_arg)
return err; return err;
} }
if (IS_ENABLED(CONFIG_BT_SETTINGS)) #if IS_ENABLED(CONFIG_SETTINGS)
{ settings_subsys_init();
settings_load();
err = settings_register(&profiles_handler);
if (err) {
LOG_ERR("Failed to setup the profile settings handler (err %d)", err);
return err;
} }
settings_load();
#endif
#if CONFIG_BT_ID_MAX != 1
initialize_identities();
#endif /* CONFIG_BT_ID_MAX != 1 */
bt_conn_cb_register(&conn_callbacks); bt_conn_cb_register(&conn_callbacks);
bt_conn_auth_cb_register(&zmk_ble_auth_cb_display); bt_conn_auth_cb_register(&zmk_ble_auth_cb_display);
@ -203,7 +475,12 @@ static int zmk_ble_init(struct device *_arg)
int zmk_ble_unpair_all() int zmk_ble_unpair_all()
{ {
LOG_DBG(""); LOG_DBG("");
return bt_unpair(BT_ID_DEFAULT, NULL); int err = bt_unpair(BT_ID_DEFAULT, NULL);
if (err) {
LOG_ERR("Failed to unpair devices (err %d)", err);
}
return err;
}; };
bool zmk_ble_handle_key_user(struct zmk_key_event *key_event) bool zmk_ble_handle_key_user(struct zmk_key_event *key_event)

49
app/src/split/bluetooth/central.c

@ -10,12 +10,14 @@
#include <bluetooth/conn.h> #include <bluetooth/conn.h>
#include <bluetooth/uuid.h> #include <bluetooth/uuid.h>
#include <bluetooth/gatt.h> #include <bluetooth/gatt.h>
#include <bluetooth/hci.h>
#include <sys/byteorder.h> #include <sys/byteorder.h>
#include <logging/log.h> #include <logging/log.h>
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
#include <zmk/ble.h>
#include <zmk/split/bluetooth/uuid.h> #include <zmk/split/bluetooth/uuid.h>
#include <zmk/event-manager.h> #include <zmk/event-manager.h>
#include <zmk/events/position-state-changed.h> #include <zmk/events/position-state-changed.h>
@ -71,6 +73,18 @@ static u8_t split_central_notify_func(struct bt_conn *conn,
return BT_GATT_ITER_CONTINUE; return BT_GATT_ITER_CONTINUE;
} }
static int split_central_subscribe(struct bt_conn *conn)
{
int err = bt_gatt_subscribe(conn, &subscribe_params);
if (err && err != -EALREADY) {
LOG_ERR("Subscribe failed (err %d)", err);
} else {
LOG_DBG("[SUBSCRIBED]");
}
return 0;
}
static u8_t split_central_discovery_func(struct bt_conn *conn, static u8_t split_central_discovery_func(struct bt_conn *conn,
const struct bt_gatt_attr *attr, const struct bt_gatt_attr *attr,
struct bt_gatt_discover_params *params) struct bt_gatt_discover_params *params)
@ -112,12 +126,7 @@ static u8_t split_central_discovery_func(struct bt_conn *conn,
subscribe_params.value = BT_GATT_CCC_NOTIFY; subscribe_params.value = BT_GATT_CCC_NOTIFY;
subscribe_params.ccc_handle = attr->handle; subscribe_params.ccc_handle = attr->handle;
err = bt_gatt_subscribe(conn, &subscribe_params); split_central_subscribe(conn);
if (err && err != -EALREADY) {
LOG_ERR("Subscribe failed (err %d)", err);
} else {
LOG_DBG("[SUBSCRIBED]");
}
return BT_GATT_ITER_STOP; return BT_GATT_ITER_STOP;
} }
@ -137,16 +146,20 @@ static void split_central_process_connection(struct bt_conn *conn) {
} }
if (conn == default_conn) { if (conn == default_conn) {
discover_params.uuid = &uuid.uuid; if (subscribe_params.value) {
discover_params.func = split_central_discovery_func; split_central_subscribe(conn);
discover_params.start_handle = 0x0001; } else {
discover_params.end_handle = 0xffff; discover_params.uuid = &uuid.uuid;
discover_params.type = BT_GATT_DISCOVER_PRIMARY; discover_params.func = split_central_discovery_func;
discover_params.start_handle = 0x0001;
discover_params.end_handle = 0xffff;
discover_params.type = BT_GATT_DISCOVER_PRIMARY;
err = bt_gatt_discover(default_conn, &discover_params); err = bt_gatt_discover(default_conn, &discover_params);
if (err) { if (err) {
LOG_ERR("Discover failed(err %d)", err); LOG_ERR("Discover failed(err %d)", err);
return; return;
}
} }
} }
@ -194,6 +207,8 @@ static bool split_central_eir_found(struct bt_data *data, void *user_data)
LOG_DBG("Found the split service"); LOG_DBG("Found the split service");
zmk_ble_set_peripheral_addr(addr);
err = bt_le_scan_stop(); err = bt_le_scan_stop();
if (err) { if (err) {
LOG_ERR("Stop LE scan failed (err %d)", err); LOG_ERR("Stop LE scan failed (err %d)", err);
@ -206,10 +221,11 @@ static bool split_central_eir_found(struct bt_data *data, void *user_data)
split_central_process_connection(default_conn); split_central_process_connection(default_conn);
} else { } else {
param = BT_LE_CONN_PARAM(0x0006, 0x0006, 30, 400); param = BT_LE_CONN_PARAM(0x0006, 0x0006, 30, 400);
err = bt_conn_le_create(addr, BT_CONN_LE_CREATE_CONN, err = bt_conn_le_create(addr, BT_CONN_LE_CREATE_CONN,
param, &default_conn); param, &default_conn);
if (err) { if (err) {
LOG_ERR("Create conn failed (err %d)", err); LOG_ERR("Create conn failed (err %d) (create conn? 0x%04x)", err, BT_HCI_OP_LE_CREATE_CONN);
start_scan(); start_scan();
} }
@ -263,6 +279,7 @@ static void split_central_connected(struct bt_conn *conn, u8_t conn_err)
bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr));
if (conn_err) { if (conn_err) {
LOG_ERR("Failed to connect to %s (%u)", addr, conn_err); LOG_ERR("Failed to connect to %s (%u)", addr, conn_err);

82
docs/docs/behavior/bluetooth.md

@ -0,0 +1,82 @@
---
title: Bluetooth Behavior
sidebar_label: Bluetooth
---
## Summary
The bluetooth behavior allows management of various settings and states related to the bluetooth connection(s)
between the keyboard and the host.
## Bluetooth Command Defines
Bluetooth command defines are provided through the [`dt-bindings/zmk/bt.h`](https://github.com/zmkfirmware/zmk/blob/main/app/include/dt-bindings/zmk/bt.h) header,
which is added at the top of the keymap file:
```
#include <dt-bindings/zmk/bt.h>
```
This will allow you to reference the actions defined in this header such as `BT_IDENT_CLR_CMD`.
Here is a table describing the command for each define:
| Define | Action |
| ------------------- | ----------------------------------------------------- |
| `BT_IDENT_CLR_CMD` | Clear paired keyboards (for the current identity)[^1] |
| `BT_IDENT_NEXT_CMD` | Switch to the next identity[^1] |
| `BT_IDENT_PREV_CMD` | Switch to the previous identity |
| `BT_IDENT_SEL_CMD` | Switch to a specific numbered identity |
| `BT_RST_CMD` | Hard reset of all bluetooth bonds |
Because the `BT_IDENT_SEL_CMD` command takes an additional parameter, the numeric index of the identity to select, _all_ the commands for the bluetooth behavior must take that additional parameter, and ignore the value. To make this easier,
there are alias defines that add those default parameters for you:
| Define | Action |
| --------------- | --------------------------------------------------------------------------------------- |
| `BT_IDENT_CLR` | Alias for `BT_IDENT_CLR_CMD 0` to clear paired keyboards (for the current identity)[^1] |
| `BT_IDENT_NEXT` | Alias for `BT_IDENT_NEXT_CMD 0` to switch to the next identity[^1] |
| `BT_IDENT_PREV` | Alias for `BT_IDENT_PREV_CMD 0` to switch to the previous identity |
| `BT_IDENT_SEL` | Alias for `BT_IDENT_SEL_CMD` to switch to a specific numbered identity |
| `BT_RST` | Alias for `BT_RST_CMD 0` to reset all bonds[^2] |
[^1]: Multiple keyboard identities/profiles is only support on non-split keyboards as of this time.
[^2]: This may interrupt pairing between both halves of a split keyboard. For split keyboards, it is preferred to use the [/docs/bond-reset] combo to clear bonds on both devices
## Bluetooth Behavior
The bluetooth behavior completes an bluetooth action given on press.
### Behavior Binding
- Reference: `&bt`
- Parameter #1: The bluetooth command define, e.g. `BT_IDENT_CLR_CMD` or `BT_RST_CMD`
- Parameter #2: The parameter for the command, when used to identify an identity/profile to select, e.g. `0`
### Examples
1. Behavior to clear the paired host:
```
&bt BT_IDENT_CLR
```
1. Behavior to hard reset all bonded devices[^2]:
```
&bt BT_IDENT_CLR
```
Examples for non-split keyboards with multiple identities:
1. Behavior to switch to the next identity:
```
&bt BT_IDENT_NEXT
```
1. Behavior to switch to the specific numbered identity:
```
&bt BT_IDENT_SEL 1
```

1
docs/sidebars.js

@ -22,6 +22,7 @@ module.exports = {
"behavior/hold-tap", "behavior/hold-tap",
"behavior/mod-tap", "behavior/mod-tap",
"behavior/reset", "behavior/reset",
"behavior/bluetooth",
"behavior/lighting", "behavior/lighting",
], ],
Development: [ Development: [

Loading…
Cancel
Save