diff --git a/.github/workflows/ble-test.yml b/.github/workflows/ble-test.yml index 3bc8a718077..8f545002e73 100644 --- a/.github/workflows/ble-test.yml +++ b/.github/workflows/ble-test.yml @@ -27,7 +27,7 @@ jobs: run: | cd app/tests/ble export TESTS=$(ls -d * | grep -v central | jq -R -s -c 'split("\n")[:-1]') - echo "::set-output name=test-dirs::${TESTS}" + echo "test-dirs=${TESTS}" > $GITHUB_OUTPUT run-tests: needs: collect-tests strategy: @@ -40,7 +40,7 @@ jobs: - name: Checkout uses: actions/checkout@v3 - name: Cache west modules - uses: actions/cache@v3.0.2 + uses: actions/cache@v3 env: cache-name: cache-zephyr-modules with: @@ -72,7 +72,7 @@ jobs: run: BSIM_COMPONENTS_PATH="${GITHUB_WORKSPACE}/tools/bsim/components/" BSIM_OUT_PATH="${GITHUB_WORKSPACE}/tools/bsim/" ./run-ble-test.sh tests/ble/${{ matrix.test }} - name: Archive artifacts if: ${{ always() }} - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: - name: "log-files" + name: "${{ matrix.test }}-log-files" path: app/build/**/*.log diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ec63081279b..eba4ff3a0b4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,7 +25,7 @@ jobs: run: | cd app/tests/ export TESTS=$(ls -d * | grep -v ble | jq -R -s -c 'split("\n")[:-1]') - echo "::set-output name=test-dirs::${TESTS}" + echo "test-dirs=${TESTS}" >> $GITHUB_OUTPUT run-tests: needs: collect-tests strategy: @@ -38,7 +38,7 @@ jobs: - name: Checkout uses: actions/checkout@v3 - name: Cache west modules - uses: actions/cache@v3.0.2 + uses: actions/cache@v3 env: cache-name: cache-zephyr-modules with: @@ -65,7 +65,7 @@ jobs: run: west test tests/${{ matrix.test }} - name: Archive artifacts if: ${{ always() }} - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: - name: "log-files" + name: "${{ matrix.test }}-log-files" path: app/build/**/*.log diff --git a/.gitignore b/.gitignore index 93c801d9aa9..4ddd08521ed 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,6 @@ /zmk-config /build *.DS_Store -__pycache__ \ No newline at end of file +__pycache__ +.python-version +.venv diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 40c654dbaa8..e5f489765e8 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -88,7 +88,7 @@ target_sources_ifdef(CONFIG_USB_DEVICE_STACK app PRIVATE src/usb.c) target_sources_ifdef(CONFIG_ZMK_USB app PRIVATE src/usb_hid.c) target_sources_ifdef(CONFIG_ZMK_RGB_UNDERGLOW app PRIVATE src/rgb_underglow.c) target_sources_ifdef(CONFIG_ZMK_BACKLIGHT app PRIVATE src/backlight.c) -target_sources(app PRIVATE src/workqueue.c) +target_sources_ifdef(CONFIG_ZMK_LOW_PRIORITY_WORK_QUEUE app PRIVATE src/workqueue.c) target_sources(app PRIVATE src/main.c) add_subdirectory(src/display/) diff --git a/app/Kconfig b/app/Kconfig index a9b1b39e696..4b52052aa88 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -142,29 +142,43 @@ menuconfig ZMK_BLE if ZMK_BLE -config ZMK_BLE_EXPERIMENTAL_FEATURES - bool "Experimental: Enable experimental/advanced BLE settings/features" - imply ZMK_BLE_PASSKEY_ENTRY - imply BT_GATT_AUTO_SEC_REQ +config ZMK_BLE_EXPERIMENTAL_CONN + bool "Experimental BLE connection changes" + help + Enables a combination of settings that are planned to be default in future versions of ZMK + to improve connection stability. This includes changes to timing on BLE pairing initation, + restores use of the updated/new LLCP implementation, and disables 2M PHY support. + +config ZMK_BLE_EXPERIMENTAL_SEC + bool "Experimental BLE security changes" imply BT_SMP_ALLOW_UNAUTH_OVERWRITE help - Enables a combination of settings that are planned to be default in future versions of ZMK. - This includes changes to timing on BLE pairing initation, BT Secure Connection passkey entry, - restores use of the updated/new LLCP implementation, disables 2M PHY support, and allows - overwrite of keys from previously paired hosts. + Enables a combination of settings that are planned to be officially supported in the future. + This includes enabling BT Secure Connection passkey entry, and allows overwrite of keys from + previously paired hosts. + +config ZMK_BLE_EXPERIMENTAL_FEATURES + bool "Experimental BLE connection and security settings/features" + select ZMK_BLE_EXPERIMENTAL_CONN + select ZMK_BLE_EXPERIMENTAL_SEC + help + Enables experimental connection changes and security features. config ZMK_BLE_PASSKEY_ENTRY bool "Require passkey entry on the keyboard to complete pairing" default n select RING_BUFFER +config BT_SMP_ALLOW_UNAUTH_OVERWRITE + imply ZMK_BLE_PASSKEY_ENTRY + choice BT_LL_SW_LLCP_IMPL - default BT_LL_SW_LLCP_LEGACY if !ZMK_BLE_EXPERIMENTAL_FEATURES + default BT_LL_SW_LLCP_LEGACY if !ZMK_BLE_EXPERIMENTAL_CONN endchoice config BT_CTLR_PHY_2M - default n if ZMK_BLE_EXPERIMENTAL_FEATURES + default n if ZMK_BLE_EXPERIMENTAL_CONN # BT_TINYCRYPT_ECC is required for BT_SMP_SC_PAIR_ONLY when using HCI config BT_TINYCRYPT_ECC @@ -203,7 +217,7 @@ config BT_GATT_NOTIFY_MULTIPLE default n config BT_GATT_AUTO_SEC_REQ - default n + default (ZMK_SPLIT_BLE && !ZMK_SPLIT_ROLE_CENTRAL) config BT_DEVICE_APPEARANCE default 961 @@ -241,6 +255,7 @@ rsource "src/display/Kconfig" menuconfig ZMK_RGB_UNDERGLOW bool "RGB Adressable LED Underglow" select LED_STRIP + select ZMK_LOW_PRIORITY_WORK_QUEUE if ZMK_RGB_UNDERGLOW @@ -363,6 +378,7 @@ config ZMK_BATTERY_REPORTING bool "Battery level detection/reporting" default n select SENSOR + select ZMK_LOW_PRIORITY_WORK_QUEUE imply BT_BAS if ZMK_BLE config ZMK_IDLE_TIMEOUT @@ -557,6 +573,11 @@ config ZMK_BATTERY_REPORT_INTERVAL int "Battery level report interval in seconds" default 60 +config ZMK_LOW_PRIORITY_WORK_QUEUE + bool "Work queue for low priority items" + +if ZMK_LOW_PRIORITY_WORK_QUEUE + config ZMK_LOW_PRIORITY_THREAD_STACK_SIZE int "Low priority thread stack size" default 768 @@ -565,6 +586,8 @@ config ZMK_LOW_PRIORITY_THREAD_PRIORITY int "Low priority thread priority" default 10 +endif + #Advanced endmenu diff --git a/app/boards/arm/adv360pro/adv360pro.dtsi b/app/boards/arm/adv360pro/adv360pro.dtsi index c837e518458..1bffb81c6bb 100644 --- a/app/boards/arm/adv360pro/adv360pro.dtsi +++ b/app/boards/arm/adv360pro/adv360pro.dtsi @@ -44,7 +44,9 @@ >; }; - ext-power { + + // Node name must match original "EXT_POWER" label to preserve user settings. + EXT_POWER { compatible = "zmk,ext-power-generic"; control-gpios = <&gpio0 13 GPIO_ACTIVE_HIGH>; }; diff --git a/app/boards/arm/corneish_zen/widgets/battery_status.c b/app/boards/arm/corneish_zen/widgets/battery_status.c index 9a2189d1e2f..0d5b0dc54bd 100644 --- a/app/boards/arm/corneish_zen/widgets/battery_status.c +++ b/app/boards/arm/corneish_zen/widgets/battery_status.c @@ -67,7 +67,7 @@ void battery_status_update_cb(struct battery_status_state state) { static struct battery_status_state battery_status_get_state(const zmk_event_t *eh) { return (struct battery_status_state) { - .level = bt_bas_get_battery_level(), + .level = zmk_battery_state_of_charge(), #if IS_ENABLED(CONFIG_USB_DEVICE_STACK) .usb_present = zmk_usb_is_powered(), #endif /* IS_ENABLED(CONFIG_USB_DEVICE_STACK) */ diff --git a/app/boards/seeeduino_xiao_ble.overlay b/app/boards/seeeduino_xiao_ble.overlay index d2ffbe461cb..e0934691d84 100644 --- a/app/boards/seeeduino_xiao_ble.overlay +++ b/app/boards/seeeduino_xiao_ble.overlay @@ -31,5 +31,24 @@ }; &qspi { - status = "disabled"; + status = "okay"; + pinctrl-0 = <&qspi_default>; + pinctrl-1 = <&qspi_sleep>; + pinctrl-names = "default", "sleep"; + p25q16h: p25q16h@0 { + compatible = "nordic,qspi-nor"; + reg = <0>; + sck-frequency = <104000000>; + quad-enable-requirements = "S2B1v1"; + jedec-id = [85 60 15]; + sfdp-bfp = [ + e5 20 f1 ff ff ff ff 00 44 eb 08 6b 08 3b 80 bb + ee ff ff ff ff ff 00 ff ff ff 00 ff 0c 20 0f 52 + 10 d8 08 81 + ]; + size = <16777216>; + has-dpd; + t-enter-dpd = <3000>; + t-exit-dpd = <8000>; + }; }; diff --git a/app/boards/shields/Kconfig.defconfig b/app/boards/shields/Kconfig.defconfig deleted file mode 100644 index 58dd45d6e9b..00000000000 --- a/app/boards/shields/Kconfig.defconfig +++ /dev/null @@ -1,14 +0,0 @@ - - - -config ZMK_KEYBOARD_NAME - default "cradios" - -# Unable to use interrupts as the same pin number is used -# across A & B controllers, and STM32F303CCT6 can't enable -# interrutps for multiple controllers for the same "line" -# for the external interrupts. -config ZMK_KSCAN_GPIO_POLLING - default y - - diff --git a/app/boards/shields/Kconfig.shield b/app/boards/shields/Kconfig.shield deleted file mode 100644 index cab78898de8..00000000000 --- a/app/boards/shields/Kconfig.shield +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright (c) 2020 Pete Johanson -# SPDX-License-Identifier: MIT - -config SHIELD_CRADIOS - def_bool $(shields_list_contains,cradios) diff --git a/app/boards/shields/nice_view/widgets/peripheral_status.c b/app/boards/shields/nice_view/widgets/peripheral_status.c index 4c0c22637be..33dafdb9f1f 100644 --- a/app/boards/shields/nice_view/widgets/peripheral_status.c +++ b/app/boards/shields/nice_view/widgets/peripheral_status.c @@ -71,7 +71,7 @@ static void battery_status_update_cb(struct battery_status_state state) { static struct battery_status_state battery_status_get_state(const zmk_event_t *eh) { return (struct battery_status_state) { - .level = bt_bas_get_battery_level(), + .level = zmk_battery_state_of_charge(), #if IS_ENABLED(CONFIG_USB_DEVICE_STACK) .usb_present = zmk_usb_is_powered(), #endif /* IS_ENABLED(CONFIG_USB_DEVICE_STACK) */ diff --git a/app/boards/shields/nice_view/widgets/status.c b/app/boards/shields/nice_view/widgets/status.c index 96b7d450a7d..96ff1e6300e 100644 --- a/app/boards/shields/nice_view/widgets/status.c +++ b/app/boards/shields/nice_view/widgets/status.c @@ -211,7 +211,7 @@ static void battery_status_update_cb(struct battery_status_state state) { static struct battery_status_state battery_status_get_state(const zmk_event_t *eh) { return (struct battery_status_state) { - .level = bt_bas_get_battery_level(), + .level = zmk_battery_state_of_charge(), #if IS_ENABLED(CONFIG_USB_DEVICE_STACK) .usb_present = zmk_usb_is_powered(), #endif /* IS_ENABLED(CONFIG_USB_DEVICE_STACK) */ diff --git a/app/include/dt-bindings/zmk/bt.h b/app/include/dt-bindings/zmk/bt.h index 7af89ddb011..aaad4dc5b8b 100644 --- a/app/include/dt-bindings/zmk/bt.h +++ b/app/include/dt-bindings/zmk/bt.h @@ -8,7 +8,7 @@ #define BT_NXT_CMD 1 #define BT_PRV_CMD 2 #define BT_SEL_CMD 3 -// #define BT_FULL_RESET_CMD 4 +#define BT_CLR_ALL_CMD 4 #define BT_DISC_CMD 5 /* @@ -20,4 +20,5 @@ defines these aliases up front. #define BT_NXT BT_NXT_CMD 0 #define BT_PRV BT_PRV_CMD 0 #define BT_SEL BT_SEL_CMD +#define BT_CLR_ALL BT_CLR_ALL_CMD 0 #define BT_DISC BT_DISC_CMD diff --git a/app/include/zmk/activity.h b/app/include/zmk/activity.h index 9c858b15d42..2aad024a848 100644 --- a/app/include/zmk/activity.h +++ b/app/include/zmk/activity.h @@ -8,4 +8,4 @@ enum zmk_activity_state { ZMK_ACTIVITY_ACTIVE, ZMK_ACTIVITY_IDLE, ZMK_ACTIVITY_SLEEP }; -enum zmk_activity_state zmk_activity_get_state(); \ No newline at end of file +enum zmk_activity_state zmk_activity_get_state(void); diff --git a/app/include/zmk/backlight.h b/app/include/zmk/backlight.h index a0f52431175..af8fc76d03d 100644 --- a/app/include/zmk/backlight.h +++ b/app/include/zmk/backlight.h @@ -6,12 +6,12 @@ #pragma once -int zmk_backlight_on(); -int zmk_backlight_off(); -int zmk_backlight_toggle(); -bool zmk_backlight_is_on(); +int zmk_backlight_on(void); +int zmk_backlight_off(void); +int zmk_backlight_toggle(void); +bool zmk_backlight_is_on(void); int zmk_backlight_set_brt(uint8_t brightness); -uint8_t zmk_backlight_get_brt(); +uint8_t zmk_backlight_get_brt(void); uint8_t zmk_backlight_calc_brt(int direction); -uint8_t zmk_backlight_calc_brt_cycle(); +uint8_t zmk_backlight_calc_brt_cycle(void); diff --git a/app/include/zmk/battery.h b/app/include/zmk/battery.h index 3d2a1c0fe96..11c86776996 100644 --- a/app/include/zmk/battery.h +++ b/app/include/zmk/battery.h @@ -6,5 +6,5 @@ #pragma once -uint8_t zmk_battery_state_of_charge(); -bool zmk_battery_charging(); +uint8_t zmk_battery_state_of_charge(void); +bool zmk_battery_charging(void); diff --git a/app/include/zmk/ble.h b/app/include/zmk/ble.h index 4323d0980e4..773323c1cf9 100644 --- a/app/include/zmk/ble.h +++ b/app/include/zmk/ble.h @@ -20,20 +20,21 @@ #define ZMK_BLE_PROFILE_COUNT CONFIG_BT_MAX_PAIRED #endif -int zmk_ble_clear_bonds(); -int zmk_ble_prof_next(); -int zmk_ble_prof_prev(); +void zmk_ble_clear_bonds(void); +int zmk_ble_prof_next(void); +int zmk_ble_prof_prev(void); int zmk_ble_prof_select(uint8_t index); +void zmk_ble_clear_all_bonds(void); int zmk_ble_prof_disconnect(uint8_t index); -int zmk_ble_active_profile_index(); +int zmk_ble_active_profile_index(void); int zmk_ble_profile_index(const bt_addr_le_t *addr); -bt_addr_le_t *zmk_ble_active_profile_addr(); -bool zmk_ble_active_profile_is_open(); -bool zmk_ble_active_profile_is_connected(); -char *zmk_ble_active_profile_name(); +bt_addr_le_t *zmk_ble_active_profile_addr(void); +bool zmk_ble_active_profile_is_open(void); +bool zmk_ble_active_profile_is_connected(void); +char *zmk_ble_active_profile_name(void); -int zmk_ble_unpair_all(); +int zmk_ble_unpair_all(void); #if IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL) int zmk_ble_put_peripheral_addr(const bt_addr_le_t *addr); diff --git a/app/include/zmk/display.h b/app/include/zmk/display.h index 45a4bee0350..1ef41f48d36 100644 --- a/app/include/zmk/display.h +++ b/app/include/zmk/display.h @@ -10,10 +10,10 @@ #pragma once -struct k_work_q *zmk_display_work_q(); +struct k_work_q *zmk_display_work_q(void); -bool zmk_display_is_initialized(); -int zmk_display_init(); +bool zmk_display_is_initialized(void); +int zmk_display_init(void); /** * @brief Macro to define a ZMK event listener that handles the thread safety of fetching diff --git a/app/include/zmk/events/battery_state_changed.h b/app/include/zmk/events/battery_state_changed.h index 5a8c625e079..157490d9849 100644 --- a/app/include/zmk/events/battery_state_changed.h +++ b/app/include/zmk/events/battery_state_changed.h @@ -14,4 +14,12 @@ struct zmk_battery_state_changed { uint8_t state_of_charge; }; -ZMK_EVENT_DECLARE(zmk_battery_state_changed); \ No newline at end of file +ZMK_EVENT_DECLARE(zmk_battery_state_changed); + +struct zmk_peripheral_battery_state_changed { + uint8_t source; + // TODO: Other battery channels + uint8_t state_of_charge; +}; + +ZMK_EVENT_DECLARE(zmk_peripheral_battery_state_changed); \ No newline at end of file diff --git a/app/include/zmk/hid.h b/app/include/zmk/hid.h index 30534b02d21..d1d3b7d47db 100644 --- a/app/include/zmk/hid.h +++ b/app/include/zmk/hid.h @@ -252,7 +252,7 @@ struct zmk_hid_mouse_report { #endif // IS_ENABLED(CONFIG_ZMK_MOUSE) -zmk_mod_flags_t zmk_hid_get_explicit_mods(); +zmk_mod_flags_t zmk_hid_get_explicit_mods(void); int zmk_hid_register_mod(zmk_mod_t modifier); int zmk_hid_unregister_mod(zmk_mod_t modifier); bool zmk_hid_mod_is_pressed(zmk_mod_t modifier); @@ -260,18 +260,18 @@ bool zmk_hid_mod_is_pressed(zmk_mod_t modifier); int zmk_hid_register_mods(zmk_mod_flags_t explicit_modifiers); int zmk_hid_unregister_mods(zmk_mod_flags_t explicit_modifiers); int zmk_hid_implicit_modifiers_press(zmk_mod_flags_t implicit_modifiers); -int zmk_hid_implicit_modifiers_release(); +int zmk_hid_implicit_modifiers_release(void); int zmk_hid_masked_modifiers_set(zmk_mod_flags_t masked_modifiers); -int zmk_hid_masked_modifiers_clear(); +int zmk_hid_masked_modifiers_clear(void); int zmk_hid_keyboard_press(zmk_key_t key); int zmk_hid_keyboard_release(zmk_key_t key); -void zmk_hid_keyboard_clear(); +void zmk_hid_keyboard_clear(void); bool zmk_hid_keyboard_is_pressed(zmk_key_t key); int zmk_hid_consumer_press(zmk_key_t key); int zmk_hid_consumer_release(zmk_key_t key); -void zmk_hid_consumer_clear(); +void zmk_hid_consumer_clear(void); bool zmk_hid_consumer_is_pressed(zmk_key_t key); int zmk_hid_press(uint32_t usage); @@ -283,11 +283,11 @@ int zmk_hid_mouse_button_press(zmk_mouse_button_t button); int zmk_hid_mouse_button_release(zmk_mouse_button_t button); int zmk_hid_mouse_buttons_press(zmk_mouse_button_flags_t buttons); int zmk_hid_mouse_buttons_release(zmk_mouse_button_flags_t buttons); -void zmk_hid_mouse_clear(); +void zmk_hid_mouse_clear(void); #endif // IS_ENABLED(CONFIG_ZMK_MOUSE) -struct zmk_hid_keyboard_report *zmk_hid_get_keyboard_report(); -struct zmk_hid_consumer_report *zmk_hid_get_consumer_report(); +struct zmk_hid_keyboard_report *zmk_hid_get_keyboard_report(void); +struct zmk_hid_consumer_report *zmk_hid_get_consumer_report(void); #if IS_ENABLED(CONFIG_ZMK_USB_BOOT) zmk_hid_boot_report_t *zmk_hid_get_boot_report(); diff --git a/app/include/zmk/hog.h b/app/include/zmk/hog.h index b4e45d9186d..5ea99126644 100644 --- a/app/include/zmk/hog.h +++ b/app/include/zmk/hog.h @@ -9,7 +9,7 @@ #include #include -int zmk_hog_init(); +int zmk_hog_init(const struct device *_arg); int zmk_hog_send_keyboard_report(struct zmk_hid_keyboard_report_body *body); int zmk_hog_send_consumer_report(struct zmk_hid_consumer_report_body *body); diff --git a/app/include/zmk/keymap.h b/app/include/zmk/keymap.h index 9bf81e1e60f..0d7dbaf33b3 100644 --- a/app/include/zmk/keymap.h +++ b/app/include/zmk/keymap.h @@ -14,10 +14,10 @@ typedef uint32_t zmk_keymap_layers_state_t; -uint8_t zmk_keymap_layer_default(); -zmk_keymap_layers_state_t zmk_keymap_layer_state(); +uint8_t zmk_keymap_layer_default(void); +zmk_keymap_layers_state_t zmk_keymap_layer_state(void); bool zmk_keymap_layer_active(uint8_t layer); -uint8_t zmk_keymap_highest_layer_active(); +uint8_t zmk_keymap_highest_layer_active(void); int zmk_keymap_layer_activate(uint8_t layer); int zmk_keymap_layer_deactivate(uint8_t layer); int zmk_keymap_layer_toggle(uint8_t layer); diff --git a/app/include/zmk/rgb_underglow.h b/app/include/zmk/rgb_underglow.h index 797f0b19f1d..be0ef252290 100644 --- a/app/include/zmk/rgb_underglow.h +++ b/app/include/zmk/rgb_underglow.h @@ -12,10 +12,10 @@ struct zmk_led_hsb { uint8_t b; }; -int zmk_rgb_underglow_toggle(); +int zmk_rgb_underglow_toggle(void); int zmk_rgb_underglow_get_state(bool *state); -int zmk_rgb_underglow_on(); -int zmk_rgb_underglow_off(); +int zmk_rgb_underglow_on(void); +int zmk_rgb_underglow_off(void); int zmk_rgb_underglow_cycle_effect(int direction); int zmk_rgb_underglow_calc_effect(int direction); int zmk_rgb_underglow_select_effect(int effect); @@ -26,4 +26,4 @@ int zmk_rgb_underglow_change_hue(int direction); int zmk_rgb_underglow_change_sat(int direction); int zmk_rgb_underglow_change_brt(int direction); int zmk_rgb_underglow_change_spd(int direction); -int zmk_rgb_underglow_set_hsb(struct zmk_led_hsb color); \ No newline at end of file +int zmk_rgb_underglow_set_hsb(struct zmk_led_hsb color); diff --git a/app/include/zmk/split/bluetooth/central.h b/app/include/zmk/split/bluetooth/central.h index 4706b3aa831..5e9e09ff6a1 100644 --- a/app/include/zmk/split/bluetooth/central.h +++ b/app/include/zmk/split/bluetooth/central.h @@ -16,3 +16,9 @@ int zmk_split_bt_invoke_behavior(uint8_t source, struct zmk_behavior_binding *bi int zmk_split_bt_update_hid_indicator(zmk_hid_indicators_t indicators); #endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS) + +#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING) + +int zmk_split_get_peripheral_battery_level(uint8_t source, uint8_t *level); + +#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING) \ No newline at end of file diff --git a/app/include/zmk/usb.h b/app/include/zmk/usb.h index 9e92a83664c..540cdd9c7e5 100644 --- a/app/include/zmk/usb.h +++ b/app/include/zmk/usb.h @@ -18,8 +18,12 @@ enum zmk_usb_conn_state { ZMK_USB_CONN_HID, }; -enum usb_dc_status_code zmk_usb_get_status(); -enum zmk_usb_conn_state zmk_usb_get_conn_state(); +enum usb_dc_status_code zmk_usb_get_status(void); +enum zmk_usb_conn_state zmk_usb_get_conn_state(void); -static inline bool zmk_usb_is_powered() { return zmk_usb_get_conn_state() != ZMK_USB_CONN_NONE; } -static inline bool zmk_usb_is_hid_ready() { return zmk_usb_get_conn_state() == ZMK_USB_CONN_HID; } +static inline bool zmk_usb_is_powered(void) { + return zmk_usb_get_conn_state() != ZMK_USB_CONN_NONE; +} +static inline bool zmk_usb_is_hid_ready(void) { + return zmk_usb_get_conn_state() == ZMK_USB_CONN_HID; +} diff --git a/app/include/zmk/usb_hid.h b/app/include/zmk/usb_hid.h index f9091778501..c0cbc08a7ad 100644 --- a/app/include/zmk/usb_hid.h +++ b/app/include/zmk/usb_hid.h @@ -8,9 +8,9 @@ #include -int zmk_usb_hid_send_keyboard_report(); -int zmk_usb_hid_send_consumer_report(); +int zmk_usb_hid_send_keyboard_report(void); +int zmk_usb_hid_send_consumer_report(void); #if IS_ENABLED(CONFIG_ZMK_MOUSE) -int zmk_usb_hid_send_mouse_report(); +int zmk_usb_hid_send_mouse_report(void); #endif // IS_ENABLED(CONFIG_ZMK_MOUSE) void zmk_usb_hid_set_protocol(uint8_t protocol); diff --git a/app/include/zmk/workqueue.h b/app/include/zmk/workqueue.h index 41e94580846..5c9addad7bd 100644 --- a/app/include/zmk/workqueue.h +++ b/app/include/zmk/workqueue.h @@ -1 +1 @@ -struct k_work_q *zmk_workqueue_lowprio_work_q(); +struct k_work_q *zmk_workqueue_lowprio_work_q(void); diff --git a/app/keymap-module/modules/modules.cmake b/app/keymap-module/modules/modules.cmake index e260da8fc37..c286809df1c 100644 --- a/app/keymap-module/modules/modules.cmake +++ b/app/keymap-module/modules/modules.cmake @@ -97,7 +97,6 @@ foreach(root ${BOARD_ROOT}) if(DEFINED SHIELD) foreach(s ${SHIELD_AS_LIST}) if(NOT ${s} IN_LIST SHIELD_LIST) - message(WARNING "Didn't find ${s}") continue() endif() message(STATUS "Adding ${SHIELD_DIR_${s}}") diff --git a/app/run-ble-test.sh b/app/run-ble-test.sh index 9984caa14e2..226bd385c67 100755 --- a/app/run-ble-test.sh +++ b/app/run-ble-test.sh @@ -34,6 +34,14 @@ if [ -z "$BLE_TESTS_NO_CENTRAL_BUILD" ]; then fi cp build/tests/ble/private_central/zephyr/zephyr.exe "${BSIM_OUT_PATH}/bin/ble_test_private_central.exe" + + if ! [ -e build/tests/ble/no_auto_sec_central ]; then + west build -d build/tests/ble/no_auto_sec_central -b nrf52_bsim tests/ble/central -- -DCONFIG_BT_ATT_RETRY_ON_SEC_ERR=n > /dev/null 2>&1 + else + west build -d build/tests/ble/no_auto_sec_central + fi + + cp build/tests/ble/no_auto_sec_central/zephyr/zephyr.exe "${BSIM_OUT_PATH}/bin/ble_test_no_auto_sec_central.exe" fi testcases=$(find $path -name nrf52_bsim.keymap -exec dirname \{\} \;) diff --git a/app/src/activity.c b/app/src/activity.c index 41fe2e15dc4..f713dde59cb 100644 --- a/app/src/activity.c +++ b/app/src/activity.c @@ -24,7 +24,7 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #include #endif -bool is_usb_power_present() { +bool is_usb_power_present(void) { #if IS_ENABLED(CONFIG_USB_DEVICE_STACK) return zmk_usb_is_powered(); #else @@ -42,7 +42,7 @@ static uint32_t activity_last_uptime; #define MAX_SLEEP_MS CONFIG_ZMK_IDLE_SLEEP_TIMEOUT #endif -int raise_event() { +int raise_event(void) { return ZMK_EVENT_RAISE(new_zmk_activity_state_changed( (struct zmk_activity_state_changed){.state = activity_state})); } @@ -55,7 +55,7 @@ int set_state(enum zmk_activity_state state) { return raise_event(); } -enum zmk_activity_state zmk_activity_get_state() { return activity_state; } +enum zmk_activity_state zmk_activity_get_state(void) { return activity_state; } int activity_event_listener(const zmk_event_t *eh) { activity_last_uptime = k_uptime_get(); @@ -80,11 +80,11 @@ void activity_work_handler(struct k_work *work) { K_WORK_DEFINE(activity_work, activity_work_handler); -void activity_expiry_function() { k_work_submit(&activity_work); } +void activity_expiry_function(struct k_timer *_timer) { k_work_submit(&activity_work); } K_TIMER_DEFINE(activity_timer, activity_expiry_function, NULL); -int activity_init() { +int activity_init(const struct device *_device) { activity_last_uptime = k_uptime_get(); k_timer_start(&activity_timer, K_SECONDS(1), K_SECONDS(1)); diff --git a/app/src/backlight.c b/app/src/backlight.c index f633ddb728b..9497f313af6 100644 --- a/app/src/backlight.c +++ b/app/src/backlight.c @@ -42,7 +42,7 @@ struct backlight_state { static struct backlight_state state = {.brightness = CONFIG_ZMK_BACKLIGHT_BRT_START, .on = IS_ENABLED(CONFIG_ZMK_BACKLIGHT_ON_START)}; -static int zmk_backlight_update() { +static int zmk_backlight_update(void) { uint8_t brt = zmk_backlight_get_brt(); LOG_DBG("Update backlight brightness: %d%%", brt); @@ -98,7 +98,7 @@ static int zmk_backlight_init(const struct device *_arg) { return zmk_backlight_update(); } -static int zmk_backlight_update_and_save() { +static int zmk_backlight_update_and_save(void) { int rc = zmk_backlight_update(); if (rc != 0) { return rc; @@ -112,20 +112,20 @@ static int zmk_backlight_update_and_save() { #endif } -int zmk_backlight_on() { +int zmk_backlight_on(void) { state.brightness = MAX(state.brightness, CONFIG_ZMK_BACKLIGHT_BRT_STEP); state.on = true; return zmk_backlight_update_and_save(); } -int zmk_backlight_off() { +int zmk_backlight_off(void) { state.on = false; return zmk_backlight_update_and_save(); } -int zmk_backlight_toggle() { return state.on ? zmk_backlight_off() : zmk_backlight_on(); } +int zmk_backlight_toggle(void) { return state.on ? zmk_backlight_off() : zmk_backlight_on(); } -bool zmk_backlight_is_on() { return state.on; } +bool zmk_backlight_is_on(void) { return state.on; } int zmk_backlight_set_brt(uint8_t brightness) { state.brightness = MIN(brightness, BRT_MAX); @@ -133,14 +133,14 @@ int zmk_backlight_set_brt(uint8_t brightness) { return zmk_backlight_update_and_save(); } -uint8_t zmk_backlight_get_brt() { return state.on ? state.brightness : 0; } +uint8_t zmk_backlight_get_brt(void) { return state.on ? state.brightness : 0; } uint8_t zmk_backlight_calc_brt(int direction) { int brt = state.brightness + (direction * CONFIG_ZMK_BACKLIGHT_BRT_STEP); return CLAMP(brt, 0, BRT_MAX); } -uint8_t zmk_backlight_calc_brt_cycle() { +uint8_t zmk_backlight_calc_brt_cycle(void) { if (state.brightness == BRT_MAX) { return 0; } else { diff --git a/app/src/battery.c b/app/src/battery.c index 5ffae0b66e6..a39c810a1bf 100644 --- a/app/src/battery.c +++ b/app/src/battery.c @@ -27,8 +27,8 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); static uint8_t last_state_of_charge = 0; static bool charging = 0; -uint8_t zmk_battery_state_of_charge() { return last_state_of_charge; } -bool zmk_battery_charging() { return charging; } +uint8_t zmk_battery_state_of_charge(void) { return last_state_of_charge; } +bool zmk_battery_charging(void) { return charging; } #if DT_HAS_CHOSEN(zmk_battery) static const struct device *const battery = DEVICE_DT_GET(DT_CHOSEN(zmk_battery)); @@ -108,7 +108,9 @@ static void zmk_battery_timer(struct k_timer *timer) { K_TIMER_DEFINE(battery_timer, zmk_battery_timer, NULL); static void zmk_battery_start_reporting() { - k_timer_start(&battery_timer, K_NO_WAIT, K_SECONDS(CONFIG_ZMK_BATTERY_REPORT_INTERVAL)); + if (device_is_ready(battery)) { + k_timer_start(&battery_timer, K_NO_WAIT, K_SECONDS(CONFIG_ZMK_BATTERY_REPORT_INTERVAL)); + } } static int zmk_battery_init(const struct device *_arg) { diff --git a/app/src/behaviors/behavior_bt.c b/app/src/behaviors/behavior_bt.c index 18a626b9bae..13ea2495891 100644 --- a/app/src/behaviors/behavior_bt.c +++ b/app/src/behaviors/behavior_bt.c @@ -24,13 +24,17 @@ static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding, struct zmk_behavior_binding_event event) { switch (binding->param1) { case BT_CLR_CMD: - return zmk_ble_clear_bonds(); + zmk_ble_clear_bonds(); + return 0; case BT_NXT_CMD: return zmk_ble_prof_next(); case BT_PRV_CMD: return zmk_ble_prof_prev(); case BT_SEL_CMD: return zmk_ble_prof_select(binding->param2); + case BT_CLR_ALL_CMD: + zmk_ble_clear_all_bonds(); + return 0; case BT_DISC_CMD: return zmk_ble_prof_disconnect(binding->param2); default: diff --git a/app/src/ble.c b/app/src/ble.c index a5f973a42cf..e0f34307672 100644 --- a/app/src/ble.c +++ b/app/src/ble.c @@ -82,7 +82,7 @@ static bt_addr_le_t peripheral_addrs[ZMK_SPLIT_BLE_PERIPHERAL_COUNT]; #endif /* IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL) */ -static void raise_profile_changed_event() { +static void raise_profile_changed_event(void) { ZMK_EVENT_RAISE(new_zmk_ble_active_profile_changed((struct zmk_ble_active_profile_changed){ .index = active_profile, .profile = &profiles[active_profile]})); } @@ -93,7 +93,7 @@ static void raise_profile_changed_event_callback(struct k_work *work) { K_WORK_DEFINE(raise_profile_changed_event_work, raise_profile_changed_event_callback); -bool zmk_ble_active_profile_is_open() { +bool zmk_ble_active_profile_is_open(void) { return !bt_addr_le_cmp(&profiles[active_profile].peer, BT_ADDR_LE_ANY); } @@ -112,7 +112,7 @@ void set_profile_address(uint8_t index, const bt_addr_le_t *addr) { k_work_submit(&raise_profile_changed_event_work); } -bool zmk_ble_active_profile_is_connected() { +bool zmk_ble_active_profile_is_connected(void) { struct bt_conn *conn; struct bt_conn_info info; bt_addr_le_t *addr = zmk_ble_active_profile_addr(); @@ -161,7 +161,7 @@ bool zmk_ble_active_profile_is_connected() { } \ advertising_status = ZMK_ADV_CONN; -int update_advertising() { +int update_advertising(void) { int err = 0; bt_addr_le_t *addr; struct bt_conn *conn; @@ -210,21 +210,34 @@ static void update_advertising_callback(struct k_work *work) { update_advertisin K_WORK_DEFINE(update_advertising_work, update_advertising_callback); -int zmk_ble_clear_bonds() { - LOG_DBG(""); - - if (bt_addr_le_cmp(&profiles[active_profile].peer, BT_ADDR_LE_ANY)) { - LOG_DBG("Unpairing!"); - bt_unpair(BT_ID_DEFAULT, &profiles[active_profile].peer); - set_profile_address(active_profile, BT_ADDR_LE_ANY); +static void clear_profile_bond(uint8_t profile) { + if (bt_addr_le_cmp(&profiles[profile].peer, BT_ADDR_LE_ANY)) { + bt_unpair(BT_ID_DEFAULT, &profiles[profile].peer); + set_profile_address(profile, BT_ADDR_LE_ANY); } +} + +void zmk_ble_clear_bonds(void) { + LOG_DBG("zmk_ble_clear_bonds()"); + clear_profile_bond(active_profile); update_advertising(); +}; - return 0; +void zmk_ble_clear_all_bonds(void) { + LOG_DBG("zmk_ble_clear_all_bonds()"); + + // Unpair all profiles + for (int i = 0; i < ZMK_BLE_PROFILE_COUNT; i++) { + clear_profile_bond(i); + } + + // Automatically switch to profile 0 + zmk_ble_prof_select(0); + update_advertising(); }; -int zmk_ble_active_profile_index() { return active_profile; } +int zmk_ble_active_profile_index(void) { return active_profile; } int zmk_ble_profile_index(const bt_addr_le_t *addr) { for (int i = 0; i < ZMK_BLE_PROFILE_COUNT; i++) { @@ -243,7 +256,7 @@ static void ble_save_profile_work(struct k_work *work) { static struct k_work_delayable ble_save_work; #endif -static int ble_save_profile() { +static int ble_save_profile(void) { #if IS_ENABLED(CONFIG_SETTINGS) return k_work_reschedule(&ble_save_work, K_MSEC(CONFIG_ZMK_SETTINGS_SAVE_DEBOUNCE)); #else @@ -271,12 +284,12 @@ int zmk_ble_prof_select(uint8_t index) { return 0; }; -int zmk_ble_prof_next() { +int zmk_ble_prof_next(void) { LOG_DBG(""); return zmk_ble_prof_select((active_profile + 1) % ZMK_BLE_PROFILE_COUNT); }; -int zmk_ble_prof_prev() { +int zmk_ble_prof_prev(void) { LOG_DBG(""); return zmk_ble_prof_select((active_profile + ZMK_BLE_PROFILE_COUNT - 1) % ZMK_BLE_PROFILE_COUNT); @@ -303,9 +316,9 @@ int zmk_ble_prof_disconnect(uint8_t index) { return result; } -bt_addr_le_t *zmk_ble_active_profile_addr() { return &profiles[active_profile].peer; } +bt_addr_le_t *zmk_ble_active_profile_addr(void) { return &profiles[active_profile].peer; } -char *zmk_ble_active_profile_name() { return profiles[active_profile].name; } +char *zmk_ble_active_profile_name(void) { return profiles[active_profile].name; } #if IS_ENABLED(CONFIG_ZMK_SPLIT_ROLE_CENTRAL) @@ -447,12 +460,6 @@ static void connected(struct bt_conn *conn, uint8_t err) { LOG_DBG("Connected %s", addr); -#if !IS_ENABLED(CONFIG_BT_GATT_AUTO_SEC_REQ) - if (bt_conn_set_security(conn, BT_SECURITY_L2)) { - LOG_ERR("Failed to set security"); - } -#endif // !IS_ENABLED(CONFIG_BT_GATT_AUTO_SEC_REQ) - update_advertising(); if (is_conn_active_profile(conn)) { diff --git a/app/src/display/widgets/battery_status.c b/app/src/display/widgets/battery_status.c index e35f890ac6c..feb054db736 100644 --- a/app/src/display/widgets/battery_status.c +++ b/app/src/display/widgets/battery_status.c @@ -63,8 +63,9 @@ void battery_status_update_cb(struct battery_status_state state) { } static struct battery_status_state battery_status_get_state(const zmk_event_t *eh) { + const struct zmk_battery_state_changed *ev = as_zmk_battery_state_changed(eh); return (struct battery_status_state) { - .level = bt_bas_get_battery_level(), + .level = ev->state_of_charge, #if IS_ENABLED(CONFIG_USB_DEVICE_STACK) .usb_present = zmk_usb_is_powered(), #endif /* IS_ENABLED(CONFIG_USB_DEVICE_STACK) */ diff --git a/app/src/endpoints.c b/app/src/endpoints.c index 098e04e2776..827f2dcd814 100644 --- a/app/src/endpoints.c +++ b/app/src/endpoints.c @@ -322,7 +322,7 @@ static int zmk_endpoints_init(const struct device *_arg) { return 0; } -static void disconnect_current_endpoint() { +static void disconnect_current_endpoint(void) { zmk_hid_keyboard_clear(); zmk_hid_consumer_clear(); #if IS_ENABLED(CONFIG_ZMK_MOUSE) diff --git a/app/src/events/battery_state_changed.c b/app/src/events/battery_state_changed.c index 508ee971d69..ffb4297cdda 100644 --- a/app/src/events/battery_state_changed.c +++ b/app/src/events/battery_state_changed.c @@ -7,4 +7,6 @@ #include #include -ZMK_EVENT_IMPL(zmk_battery_state_changed); \ No newline at end of file +ZMK_EVENT_IMPL(zmk_battery_state_changed); + +ZMK_EVENT_IMPL(zmk_peripheral_battery_state_changed); \ No newline at end of file diff --git a/app/src/ext_power_generic.c b/app/src/ext_power_generic.c index 52896f19871..2586f436852 100644 --- a/app/src/ext_power_generic.c +++ b/app/src/ext_power_generic.c @@ -46,7 +46,7 @@ static void ext_power_save_state_work(struct k_work *work) { static struct k_work_delayable ext_power_save_work; #endif -int ext_power_save_state() { +int ext_power_save_state(void) { #if IS_ENABLED(CONFIG_SETTINGS) int ret = k_work_reschedule(&ext_power_save_work, K_MSEC(CONFIG_ZMK_SETTINGS_SAVE_DEBOUNCE)); return MIN(ret, 0); diff --git a/app/src/hid.c b/app/src/hid.c index 1ea2afb1621..8b0c23f37ed 100644 --- a/app/src/hid.c +++ b/app/src/hid.c @@ -47,7 +47,7 @@ static zmk_mod_flags_t masked_modifiers = 0; #define GET_MODIFIERS (keyboard_report.body.modifiers) -zmk_mod_flags_t zmk_hid_get_explicit_mods() { return explicit_modifiers; } +zmk_mod_flags_t zmk_hid_get_explicit_mods(void) { return explicit_modifiers; } int zmk_hid_register_mod(zmk_mod_t modifier) { explicit_modifier_counts[modifier]++; @@ -117,7 +117,7 @@ static zmk_hid_boot_report_t *boot_report_rollover(uint8_t modifiers) { #define TOGGLE_KEYBOARD(code, val) WRITE_BIT(keyboard_report.body.keys[code / 8], code % 8, val) #if IS_ENABLED(CONFIG_ZMK_USB_BOOT) -zmk_hid_boot_report_t *zmk_hid_get_boot_report() { +zmk_hid_boot_report_t *zmk_hid_get_boot_report(void) { if (keys_held > HID_BOOT_KEY_LEN) { return boot_report_rollover(keyboard_report.body.modifiers); } @@ -187,7 +187,7 @@ static inline bool check_keyboard_usage(zmk_key_t usage) { } #if IS_ENABLED(CONFIG_ZMK_USB_BOOT) -zmk_hid_boot_report_t *zmk_hid_get_boot_report() { +zmk_hid_boot_report_t *zmk_hid_get_boot_report(void) { if (keys_held > HID_BOOT_KEY_LEN) { return boot_report_rollover(keyboard_report.body.modifiers); } @@ -268,7 +268,7 @@ int zmk_hid_implicit_modifiers_press(zmk_mod_flags_t new_implicit_modifiers) { return current == GET_MODIFIERS ? 0 : 1; } -int zmk_hid_implicit_modifiers_release() { +int zmk_hid_implicit_modifiers_release(void) { implicit_modifiers = 0; zmk_mod_flags_t current = GET_MODIFIERS; SET_MODIFIERS(explicit_modifiers); @@ -282,7 +282,7 @@ int zmk_hid_masked_modifiers_set(zmk_mod_flags_t new_masked_modifiers) { return current == GET_MODIFIERS ? 0 : 1; } -int zmk_hid_masked_modifiers_clear() { +int zmk_hid_masked_modifiers_clear(void) { masked_modifiers = 0; zmk_mod_flags_t current = GET_MODIFIERS; SET_MODIFIERS(explicit_modifiers); @@ -312,7 +312,9 @@ bool zmk_hid_keyboard_is_pressed(zmk_key_t code) { return check_keyboard_usage(code); } -void zmk_hid_keyboard_clear() { memset(&keyboard_report.body, 0, sizeof(keyboard_report.body)); } +void zmk_hid_keyboard_clear(void) { + memset(&keyboard_report.body, 0, sizeof(keyboard_report.body)); +} int zmk_hid_consumer_press(zmk_key_t code) { TOGGLE_CONSUMER(0U, code); @@ -324,7 +326,9 @@ int zmk_hid_consumer_release(zmk_key_t code) { return 0; }; -void zmk_hid_consumer_clear() { memset(&consumer_report.body, 0, sizeof(consumer_report.body)); } +void zmk_hid_consumer_clear(void) { + memset(&consumer_report.body, 0, sizeof(consumer_report.body)); +} bool zmk_hid_consumer_is_pressed(zmk_key_t key) { for (int idx = 0; idx < CONFIG_ZMK_HID_CONSUMER_REPORT_SIZE; idx++) { @@ -426,21 +430,21 @@ int zmk_hid_mouse_buttons_release(zmk_mouse_button_flags_t buttons) { } return 0; } -void zmk_hid_mouse_clear() { memset(&mouse_report.body, 0, sizeof(mouse_report.body)); } +void zmk_hid_mouse_clear(void) { memset(&mouse_report.body, 0, sizeof(mouse_report.body)); } #endif // IS_ENABLED(CONFIG_ZMK_MOUSE) -struct zmk_hid_keyboard_report *zmk_hid_get_keyboard_report() { +struct zmk_hid_keyboard_report *zmk_hid_get_keyboard_report(void) { return &keyboard_report; } -struct zmk_hid_consumer_report *zmk_hid_get_consumer_report() { +struct zmk_hid_consumer_report *zmk_hid_get_consumer_report(void) { return &consumer_report; } #if IS_ENABLED(CONFIG_ZMK_MOUSE) -struct zmk_hid_mouse_report *zmk_hid_get_mouse_report() { +struct zmk_hid_mouse_report *zmk_hid_get_mouse_report(void) { return &mouse_report; } diff --git a/app/src/hog.c b/app/src/hog.c index 1baf00b53b5..204609145c2 100644 --- a/app/src/hog.c +++ b/app/src/hog.c @@ -220,7 +220,7 @@ BT_GATT_SERVICE_DEFINE( BT_GATT_CHARACTERISTIC(BT_UUID_HIDS_CTRL_POINT, BT_GATT_CHRC_WRITE_WITHOUT_RESP, BT_GATT_PERM_WRITE, NULL, write_ctrl_point, &ctrl_point)); -struct bt_conn *destination_connection() { +struct bt_conn *destination_connection(void) { struct bt_conn *conn; bt_addr_le_t *addr = zmk_ble_active_profile_addr(); LOG_DBG("Address pointer %p", addr); @@ -258,8 +258,10 @@ void send_keyboard_report_callback(struct k_work *work) { }; int err = bt_gatt_notify_cb(conn, ¬ify_params); - if (err) { - LOG_ERR("Error notifying %d", err); + if (err == -EPERM) { + bt_conn_set_security(conn, BT_SECURITY_L2); + } else if (err) { + LOG_DBG("Error notifying %d", err); } bt_conn_unref(conn); @@ -308,7 +310,9 @@ void send_consumer_report_callback(struct k_work *work) { }; int err = bt_gatt_notify_cb(conn, ¬ify_params); - if (err) { + if (err == -EPERM) { + bt_conn_set_security(conn, BT_SECURITY_L2); + } else if (err) { LOG_DBG("Error notifying %d", err); } @@ -359,7 +363,9 @@ void send_mouse_report_callback(struct k_work *work) { }; int err = bt_gatt_notify_cb(conn, ¬ify_params); - if (err) { + if (err == -EPERM) { + bt_conn_set_security(conn, BT_SECURITY_L2); + } else if (err) { LOG_DBG("Error notifying %d", err); } @@ -367,25 +373,25 @@ void send_mouse_report_callback(struct k_work *work) { } }; -int zmk_hog_send_mouse_report(struct zmk_hid_mouse_report_body *report) { - struct bt_conn *conn = destination_connection(); - if (conn == NULL) { - return 1; - } - - struct bt_gatt_notify_params notify_params = { - .attr = &hog_svc.attrs[13], - .data = report, - .len = sizeof(*report), - }; +K_WORK_DEFINE(hog_mouse_work, send_mouse_report_callback); - int err = bt_gatt_notify_cb(conn, ¬ify_params); +int zmk_hog_send_mouse_report(struct zmk_hid_mouse_report_body *report) { + int err = k_msgq_put(&zmk_hog_mouse_msgq, report, K_MSEC(100)); if (err) { - LOG_DBG("Error notifying %d", err); - return err; + switch (err) { + case -EAGAIN: { + LOG_WRN("Consumer message queue full, popping first message and queueing again"); + struct zmk_hid_mouse_report_body discarded_report; + k_msgq_get(&zmk_hog_mouse_msgq, &discarded_report, K_NO_WAIT); + return zmk_hog_send_mouse_report(report); + } + default: + LOG_WRN("Failed to queue mouse report to send (%d)", err); + return err; + } } - bt_conn_unref(conn); + k_work_submit_to_queue(&hog_work_q, &hog_mouse_work); return 0; }; diff --git a/app/src/keymap.c b/app/src/keymap.c index d275feafbb5..5e444b61dab 100644 --- a/app/src/keymap.c +++ b/app/src/keymap.c @@ -96,9 +96,9 @@ static inline int set_layer_state(uint8_t layer, bool state) { return 0; } -uint8_t zmk_keymap_layer_default() { return _zmk_keymap_layer_default; } +uint8_t zmk_keymap_layer_default(void) { return _zmk_keymap_layer_default; } -zmk_keymap_layers_state_t zmk_keymap_layer_state() { return _zmk_keymap_layer_state; } +zmk_keymap_layers_state_t zmk_keymap_layer_state(void) { return _zmk_keymap_layer_state; } bool zmk_keymap_layer_active_with_state(uint8_t layer, zmk_keymap_layers_state_t state_to_test) { // The default layer is assumed to be ALWAYS ACTIVE so we include an || here to ensure nobody @@ -110,7 +110,7 @@ bool zmk_keymap_layer_active(uint8_t layer) { return zmk_keymap_layer_active_with_state(layer, _zmk_keymap_layer_state); }; -uint8_t zmk_keymap_highest_layer_active() { +uint8_t zmk_keymap_highest_layer_active(void) { for (uint8_t layer = ZMK_KEYMAP_LAYERS_LEN - 1; layer > 0; layer--) { if (zmk_keymap_layer_active(layer)) { return layer; diff --git a/app/src/rgb_underglow.c b/app/src/rgb_underglow.c index 9d4f2cf1cab..ddc0aef1f45 100644 --- a/app/src/rgb_underglow.c +++ b/app/src/rgb_underglow.c @@ -130,13 +130,13 @@ static struct led_rgb hsb_to_rgb(struct zmk_led_hsb hsb) { return rgb; } -static void zmk_rgb_underglow_effect_solid() { +static void zmk_rgb_underglow_effect_solid(void) { for (int i = 0; i < STRIP_NUM_PIXELS; i++) { pixels[i] = hsb_to_rgb(hsb_scale_min_max(state.color)); } } -static void zmk_rgb_underglow_effect_breathe() { +static void zmk_rgb_underglow_effect_breathe(void) { for (int i = 0; i < STRIP_NUM_PIXELS; i++) { struct zmk_led_hsb hsb = state.color; hsb.b = abs(state.animation_step - 1200) / 12; @@ -151,7 +151,7 @@ static void zmk_rgb_underglow_effect_breathe() { } } -static void zmk_rgb_underglow_effect_spectrum() { +static void zmk_rgb_underglow_effect_spectrum(void) { for (int i = 0; i < STRIP_NUM_PIXELS; i++) { struct zmk_led_hsb hsb = state.color; hsb.h = state.animation_step; @@ -163,7 +163,7 @@ static void zmk_rgb_underglow_effect_spectrum() { state.animation_step = state.animation_step % HUE_MAX; } -static void zmk_rgb_underglow_effect_swirl() { +static void zmk_rgb_underglow_effect_swirl(void) { for (int i = 0; i < STRIP_NUM_PIXELS; i++) { struct zmk_led_hsb hsb = state.color; hsb.h = (HUE_MAX / STRIP_NUM_PIXELS * i + state.animation_step) % HUE_MAX; @@ -232,7 +232,7 @@ static int rgb_settings_set(const char *name, size_t len, settings_read_cb read_ struct settings_handler rgb_conf = {.name = "rgb/underglow", .h_set = rgb_settings_set}; -static void zmk_rgb_underglow_save_state_work() { +static void zmk_rgb_underglow_save_state_work(struct k_work *_work) { settings_save_one("rgb/underglow/state", &state, sizeof(state)); } @@ -286,7 +286,7 @@ static int zmk_rgb_underglow_init(const struct device *_arg) { return 0; } -int zmk_rgb_underglow_save_state() { +int zmk_rgb_underglow_save_state(void) { #if IS_ENABLED(CONFIG_SETTINGS) int ret = k_work_reschedule(&underglow_save_work, K_MSEC(CONFIG_ZMK_SETTINGS_SAVE_DEBOUNCE)); return MIN(ret, 0); @@ -303,7 +303,7 @@ int zmk_rgb_underglow_get_state(bool *on_off) { return 0; } -int zmk_rgb_underglow_on() { +int zmk_rgb_underglow_on(void) { if (!led_strip) return -ENODEV; @@ -333,7 +333,7 @@ static void zmk_rgb_underglow_off_handler(struct k_work *work) { K_WORK_DEFINE(underglow_off_work, zmk_rgb_underglow_off_handler); -int zmk_rgb_underglow_off() { +int zmk_rgb_underglow_off(void) { if (!led_strip) return -ENODEV; @@ -376,7 +376,7 @@ int zmk_rgb_underglow_cycle_effect(int direction) { return zmk_rgb_underglow_select_effect(zmk_rgb_underglow_calc_effect(direction)); } -int zmk_rgb_underglow_toggle() { +int zmk_rgb_underglow_toggle(void) { return state.on ? zmk_rgb_underglow_off() : zmk_rgb_underglow_on(); } diff --git a/app/src/split/bluetooth/CMakeLists.txt b/app/src/split/bluetooth/CMakeLists.txt index 241a9b8d8c0..6e0ad617284 100644 --- a/app/src/split/bluetooth/CMakeLists.txt +++ b/app/src/split/bluetooth/CMakeLists.txt @@ -8,4 +8,8 @@ if (NOT CONFIG_ZMK_SPLIT_ROLE_CENTRAL) endif() if (CONFIG_ZMK_SPLIT_ROLE_CENTRAL) target_sources(app PRIVATE central.c) +endif() + +if (CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_PROXY) + target_sources(app PRIVATE central_bas_proxy.c) endif() \ No newline at end of file diff --git a/app/src/split/bluetooth/Kconfig b/app/src/split/bluetooth/Kconfig index 858e7308fef..4da50528343 100644 --- a/app/src/split/bluetooth/Kconfig +++ b/app/src/split/bluetooth/Kconfig @@ -16,12 +16,36 @@ config ZMK_SPLIT_ROLE_CENTRAL select BT_GATT_AUTO_DISCOVER_CCC select BT_SCAN_WITH_IDENTITY +# Bump this value needed for concurrent GATT discovery of splits +config BT_L2CAP_TX_BUF_COUNT + default 5 if ZMK_SPLIT_ROLE_CENTRAL + if ZMK_SPLIT_ROLE_CENTRAL config ZMK_SPLIT_BLE_CENTRAL_PERIPHERALS int "Number of peripherals that will connect to the central." default 1 +menuconfig ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING + bool "Fetch Peripheral Battery Level Info" + help + Adds internal support for fetching the battery levels from peripherals + and generating events in the ZMK eventing system. + +if ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING + +config ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_QUEUE_SIZE + int "Max number of battery level events to queue when received from peripherals" + default ZMK_SPLIT_BLE_CENTRAL_PERIPHERALS + +config ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_PROXY + bool "Proxy Peripheral Battery Level Info" + help + Adds support for reporting the battery levels of connected split + peripherals through an additional Battery Level service. + +endif + config ZMK_SPLIT_BLE_CENTRAL_POSITION_QUEUE_SIZE int "Max number of key position state events to queue when received from peripherals" default 5 diff --git a/app/src/split/bluetooth/central.c b/app/src/split/bluetooth/central.c index 3635322431c..e586155760d 100644 --- a/app/src/split/bluetooth/central.c +++ b/app/src/split/bluetooth/central.c @@ -27,6 +27,7 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #include #include #include +#include #include static int start_scanning(void); @@ -47,6 +48,10 @@ struct peripheral_slot { struct bt_gatt_subscribe_params sensor_subscribe_params; struct bt_gatt_discover_params sub_discover_params; uint16_t run_behavior_handle; +#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING) + struct bt_gatt_subscribe_params batt_lvl_subscribe_params; + struct bt_gatt_read_params batt_lvl_read_params; +#endif /* IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING) */ #if IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS) uint16_t update_hid_indicators; #endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS) @@ -265,6 +270,110 @@ static uint8_t split_central_notify_func(struct bt_conn *conn, return BT_GATT_ITER_CONTINUE; } +#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING) + +static uint8_t peripheral_battery_levels[ZMK_SPLIT_BLE_PERIPHERAL_COUNT] = {0}; + +int zmk_split_get_peripheral_battery_level(uint8_t source, uint8_t *level) { + if (source >= ARRAY_SIZE(peripheral_battery_levels)) { + return -EINVAL; + } + + if (peripherals[source].state != PERIPHERAL_SLOT_STATE_CONNECTED) { + return -ENOTCONN; + } + + *level = peripheral_battery_levels[source]; + return 0; +} + +K_MSGQ_DEFINE(peripheral_batt_lvl_msgq, sizeof(struct zmk_peripheral_battery_state_changed), + CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_QUEUE_SIZE, 4); + +void peripheral_batt_lvl_change_callback(struct k_work *work) { + struct zmk_peripheral_battery_state_changed ev; + while (k_msgq_get(&peripheral_batt_lvl_msgq, &ev, K_NO_WAIT) == 0) { + LOG_DBG("Triggering peripheral battery level change %u", ev.state_of_charge); + peripheral_battery_levels[ev.source] = ev.state_of_charge; + ZMK_EVENT_RAISE(new_zmk_peripheral_battery_state_changed(ev)); + } +} + +K_WORK_DEFINE(peripheral_batt_lvl_work, peripheral_batt_lvl_change_callback); + +static uint8_t split_central_battery_level_notify_func(struct bt_conn *conn, + struct bt_gatt_subscribe_params *params, + const void *data, uint16_t length) { + struct peripheral_slot *slot = peripheral_slot_for_conn(conn); + + if (!slot) { + LOG_ERR("No peripheral state found for connection"); + return BT_GATT_ITER_CONTINUE; + } + + if (!data) { + LOG_DBG("[UNSUBSCRIBED]"); + params->value_handle = 0U; + return BT_GATT_ITER_STOP; + } + + if (length == 0) { + LOG_ERR("Zero length battery notification received"); + return BT_GATT_ITER_CONTINUE; + } + + LOG_DBG("[BATTERY LEVEL NOTIFICATION] data %p length %u", data, length); + uint8_t battery_level = ((uint8_t *)data)[0]; + LOG_DBG("Battery level: %u", battery_level); + struct zmk_peripheral_battery_state_changed ev = { + .source = peripheral_slot_index_for_conn(conn), .state_of_charge = battery_level}; + k_msgq_put(&peripheral_batt_lvl_msgq, &ev, K_NO_WAIT); + k_work_submit(&peripheral_batt_lvl_work); + + return BT_GATT_ITER_CONTINUE; +} + +static uint8_t split_central_battery_level_read_func(struct bt_conn *conn, uint8_t err, + struct bt_gatt_read_params *params, + const void *data, uint16_t length) { + if (err > 0) { + LOG_ERR("Error during reading peripheral battery level: %u", err); + return BT_GATT_ITER_STOP; + } + + struct peripheral_slot *slot = peripheral_slot_for_conn(conn); + + if (!slot) { + LOG_ERR("No peripheral state found for connection"); + return BT_GATT_ITER_CONTINUE; + } + + if (!data) { + LOG_DBG("[READ COMPLETED]"); + return BT_GATT_ITER_STOP; + } + + LOG_DBG("[BATTERY LEVEL READ] data %p length %u", data, length); + + if (length == 0) { + LOG_ERR("Zero length battery notification received"); + return BT_GATT_ITER_CONTINUE; + } + + uint8_t battery_level = ((uint8_t *)data)[0]; + + LOG_DBG("Battery level: %u", battery_level); + + struct zmk_peripheral_battery_state_changed ev = { + .source = peripheral_slot_index_for_conn(conn), .state_of_charge = battery_level}; + k_msgq_put(&peripheral_batt_lvl_msgq, &ev, K_NO_WAIT); + k_work_submit(&peripheral_batt_lvl_work); + + return BT_GATT_ITER_CONTINUE; +} + +#endif /* IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING) */ + static int split_central_subscribe(struct bt_conn *conn, struct bt_gatt_subscribe_params *params) { int err = bt_gatt_subscribe(conn, params); switch (err) { @@ -306,10 +415,6 @@ static uint8_t split_central_chrc_discovery_func(struct bt_conn *conn, if (bt_uuid_cmp(chrc_uuid, BT_UUID_DECLARE_128(ZMK_SPLIT_BT_CHAR_POSITION_STATE_UUID)) == 0) { LOG_DBG("Found position state characteristic"); - slot->discover_params.uuid = NULL; - slot->discover_params.start_handle = attr->handle + 2; - slot->discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC; - slot->subscribe_params.disc_params = &slot->sub_discover_params; slot->subscribe_params.end_handle = slot->discover_params.end_handle; slot->subscribe_params.value_handle = bt_gatt_attr_value_handle(attr); @@ -342,9 +447,27 @@ static uint8_t split_central_chrc_discovery_func(struct bt_conn *conn, LOG_DBG("Found update HID indicators handle"); slot->update_hid_indicators = bt_gatt_attr_value_handle(attr); #endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS) +#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING) + } else if (!bt_uuid_cmp(((struct bt_gatt_chrc *)attr->user_data)->uuid, + BT_UUID_BAS_BATTERY_LEVEL)) { + LOG_DBG("Found battery level characteristics"); + slot->batt_lvl_subscribe_params.disc_params = &slot->sub_discover_params; + slot->batt_lvl_subscribe_params.end_handle = slot->discover_params.end_handle; + slot->batt_lvl_subscribe_params.value_handle = bt_gatt_attr_value_handle(attr); + slot->batt_lvl_subscribe_params.notify = split_central_battery_level_notify_func; + slot->batt_lvl_subscribe_params.value = BT_GATT_CCC_NOTIFY; + split_central_subscribe(conn, &slot->batt_lvl_subscribe_params); + + slot->batt_lvl_read_params.func = split_central_battery_level_read_func; + slot->batt_lvl_read_params.handle_count = 1; + slot->batt_lvl_read_params.single.handle = bt_gatt_attr_value_handle(attr); + slot->batt_lvl_read_params.single.offset = 0; + bt_gatt_read(conn, &slot->batt_lvl_read_params); +#endif /* IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING) */ } - bool subscribed = (slot->run_behavior_handle && slot->subscribe_params.value_handle); + bool subscribed = slot->run_behavior_handle && slot->subscribe_params.value_handle; + #if ZMK_KEYMAP_HAS_SENSORS subscribed = subscribed && slot->sensor_subscribe_params.value_handle; #endif /* ZMK_KEYMAP_HAS_SENSORS */ @@ -352,6 +475,9 @@ static uint8_t split_central_chrc_discovery_func(struct bt_conn *conn, #if IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS) subscribed = subscribed && slot->update_hid_indicators; #endif // IS_ENABLED(CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS) +#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING) + subscribed = subscribed && slot->batt_lvl_subscribe_params.value_handle; +#endif /* IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING) */ return subscribed ? BT_GATT_ITER_STOP : BT_GATT_ITER_CONTINUE; } @@ -382,7 +508,6 @@ static uint8_t split_central_service_discovery_func(struct bt_conn *conn, LOG_DBG("Found split service"); slot->discover_params.uuid = NULL; slot->discover_params.func = split_central_chrc_discovery_func; - slot->discover_params.start_handle = attr->handle + 1; slot->discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC; int err = bt_gatt_discover(conn, &slot->discover_params); @@ -397,14 +522,6 @@ static void split_central_process_connection(struct bt_conn *conn) { LOG_DBG("Current security for connection: %d", bt_conn_get_security(conn)); -#if !IS_ENABLED(CONFIG_BT_GATT_AUTO_SEC_REQ) - err = bt_conn_set_security(conn, BT_SECURITY_L2); - if (err) { - LOG_ERR("Failed to set security (reason %d)", err); - return; - } -#endif // !IS_ENABLED(CONFIG_BT_GATT_AUTO_SEC_REQ) - struct peripheral_slot *slot = peripheral_slot_for_conn(conn); if (slot == NULL) { LOG_ERR("No peripheral state found for connection"); @@ -436,7 +553,7 @@ static void split_central_process_connection(struct bt_conn *conn) { start_scanning(); } -static int stop_scanning() { +static int stop_scanning(void) { LOG_DBG("Stopping peripheral scanning"); is_scanning = false; @@ -605,6 +722,13 @@ static void split_central_disconnected(struct bt_conn *conn, uint8_t reason) { LOG_DBG("Disconnected: %s (reason %d)", addr, reason); +#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING) + struct zmk_peripheral_battery_state_changed ev = { + .source = peripheral_slot_index_for_conn(conn), .state_of_charge = 0}; + k_msgq_put(&peripheral_batt_lvl_msgq, &ev, K_NO_WAIT); + k_work_submit(&peripheral_batt_lvl_work); +#endif // IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING) + err = release_peripheral_slot_for_conn(conn); if (err < 0) { diff --git a/app/src/split/bluetooth/central_bas_proxy.c b/app/src/split/bluetooth/central_bas_proxy.c new file mode 100644 index 00000000000..9515e556730 --- /dev/null +++ b/app/src/split/bluetooth/central_bas_proxy.c @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include +#include +#include +#include + +#include + +LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); + +#include +#include +#include +#include + +static void blvl_ccc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) { + ARG_UNUSED(attr); + + bool notif_enabled = (value == BT_GATT_CCC_NOTIFY); + + LOG_INF("BAS Notifications %s", notif_enabled ? "enabled" : "disabled"); +} + +static ssize_t read_blvl(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) { + const uint8_t source = (uint8_t)(uint32_t)attr->user_data; + uint8_t level = 0; + int rc = zmk_split_get_peripheral_battery_level(source, &level); + + if (rc == -EINVAL) { + LOG_ERR("Invalid peripheral index requested for battery level read: %d", source); + return 0; + } + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &level, sizeof(uint8_t)); +} + +static const struct bt_gatt_cpf aux_level_cpf = { + .format = 0x04, // uint8 + .exponent = 0x0, + .unit = 0x27AD, // Percentage + .name_space = 0x01, // Bluetooth SIG + .description = 0x0108, // "auxiliary" +}; + +#define PERIPH_CUD_(x) "Peripheral " #x +#define PERIPH_CUD(x) PERIPH_CUD_(x) + +// How many GATT attributes each battery level adds to our service +#define PERIPH_BATT_LEVEL_ATTR_COUNT 5 +// The second generated attribute is the one used to send GATT notifications +#define PERIPH_BATT_LEVEL_ATTR_NOTIFY_IDX 1 + +#define PERIPH_BATT_LEVEL_ATTRS(i, _) \ + BT_GATT_CHARACTERISTIC(BT_UUID_BAS_BATTERY_LEVEL, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_READ, read_blvl, NULL, i), \ + BT_GATT_CCC(blvl_ccc_cfg_changed, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE), \ + BT_GATT_CPF(&aux_level_cpf), BT_GATT_CUD(PERIPH_CUD(i), BT_GATT_PERM_READ), + +BT_GATT_SERVICE_DEFINE(bas_aux, BT_GATT_PRIMARY_SERVICE(BT_UUID_BAS), + LISTIFY(CONFIG_ZMK_SPLIT_BLE_CENTRAL_PERIPHERALS, PERIPH_BATT_LEVEL_ATTRS, + ())); + +int peripheral_batt_lvl_listener(const zmk_event_t *eh) { + const struct zmk_peripheral_battery_state_changed *ev = + as_zmk_peripheral_battery_state_changed(eh); + if (ev == NULL) { + return ZMK_EV_EVENT_BUBBLE; + }; + + if (ev->source >= CONFIG_ZMK_SPLIT_BLE_CENTRAL_PERIPHERALS) { + LOG_WRN("Got battery level event for an out of range peripheral index"); + return ZMK_EV_EVENT_BUBBLE; + } + + LOG_DBG("Peripheral battery level event: %u", ev->state_of_charge); + + // Offset by the index of the source plus the specific offset to find the attribute to notify + // on. + int index = (PERIPH_BATT_LEVEL_ATTR_COUNT * ev->source) + PERIPH_BATT_LEVEL_ATTR_NOTIFY_IDX; + + int rc = bt_gatt_notify(NULL, &bas_aux.attrs[index], &ev->state_of_charge, sizeof(uint8_t)); + if (rc < 0 && rc != -ENOTCONN) { + LOG_WRN("Failed to notify hosts of peripheral battery level: %d", rc); + } + + return ZMK_EV_EVENT_BUBBLE; +}; + +ZMK_LISTENER(peripheral_batt_lvl_listener, peripheral_batt_lvl_listener); +ZMK_SUBSCRIPTION(peripheral_batt_lvl_listener, zmk_peripheral_battery_state_changed); diff --git a/app/src/split/bluetooth/peripheral.c b/app/src/split/bluetooth/peripheral.c index 704e2eed408..d54c312b529 100644 --- a/app/src/split/bluetooth/peripheral.c +++ b/app/src/split/bluetooth/peripheral.c @@ -142,9 +142,9 @@ static struct bt_conn_auth_info_cb zmk_peripheral_ble_auth_info_cb = { .pairing_complete = auth_pairing_complete, }; -bool zmk_split_bt_peripheral_is_connected() { return is_connected; } +bool zmk_split_bt_peripheral_is_connected(void) { return is_connected; } -bool zmk_split_bt_peripheral_is_bonded() { return is_bonded; } +bool zmk_split_bt_peripheral_is_bonded(void) { return is_bonded; } static int zmk_peripheral_ble_init(const struct device *_arg) { int err = bt_enable(NULL); diff --git a/app/src/usb.c b/app/src/usb.c index 9d27900c3c6..98b48bfe9e6 100644 --- a/app/src/usb.c +++ b/app/src/usb.c @@ -28,9 +28,9 @@ static void raise_usb_status_changed_event(struct k_work *_work) { K_WORK_DEFINE(usb_status_notifier_work, raise_usb_status_changed_event); -enum usb_dc_status_code zmk_usb_get_status() { return usb_status; } +enum usb_dc_status_code zmk_usb_get_status(void) { return usb_status; } -enum zmk_usb_conn_state zmk_usb_get_conn_state() { +enum zmk_usb_conn_state zmk_usb_get_conn_state(void) { LOG_DBG("state: %d", usb_status); switch (usb_status) { case USB_DC_SUSPEND: diff --git a/app/src/usb_hid.c b/app/src/usb_hid.c index 3412314086f..f3542ffa4a3 100644 --- a/app/src/usb_hid.c +++ b/app/src/usb_hid.c @@ -147,13 +147,13 @@ static int zmk_usb_hid_send_report(const uint8_t *report, size_t len) { } } -int zmk_usb_hid_send_keyboard_report() { +int zmk_usb_hid_send_keyboard_report(void) { size_t len; uint8_t *report = get_keyboard_report(&len); return zmk_usb_hid_send_report(report, len); } -int zmk_usb_hid_send_consumer_report() { +int zmk_usb_hid_send_consumer_report(void) { #if IS_ENABLED(CONFIG_ZMK_USB_BOOT) if (hid_protocol == HID_PROTOCOL_BOOT) { return -ENOTSUP; diff --git a/app/src/workqueue.c b/app/src/workqueue.c index a9a8bce5202..1aa4f59a125 100644 --- a/app/src/workqueue.c +++ b/app/src/workqueue.c @@ -13,11 +13,11 @@ K_THREAD_STACK_DEFINE(lowprio_q_stack, CONFIG_ZMK_LOW_PRIORITY_THREAD_STACK_SIZE static struct k_work_q lowprio_work_q; -struct k_work_q *zmk_workqueue_lowprio_work_q() { +struct k_work_q *zmk_workqueue_lowprio_work_q(void) { return &lowprio_work_q; } -static int workqueue_init() { +static int workqueue_init(const struct device *_device) { static const struct k_work_queue_config queue_config = {.name = "Low Priority Work Queue"}; k_work_queue_start(&lowprio_work_q, lowprio_q_stack, K_THREAD_STACK_SIZEOF(lowprio_q_stack), CONFIG_ZMK_LOW_PRIORITY_THREAD_PRIORITY, &queue_config); diff --git a/app/src/wpm.c b/app/src/wpm.c index 00a5942ecb8..efb5a5d343e 100644 --- a/app/src/wpm.c +++ b/app/src/wpm.c @@ -32,7 +32,7 @@ static uint8_t last_wpm_state; static uint8_t wpm_update_counter; static uint32_t key_pressed_count; -int zmk_wpm_get_state() { return wpm_state; } +int zmk_wpm_get_state(void) { return wpm_state; } int wpm_event_listener(const zmk_event_t *eh) { const struct zmk_keycode_state_changed *ev = as_zmk_keycode_state_changed(eh); @@ -68,11 +68,11 @@ void wpm_work_handler(struct k_work *work) { K_WORK_DEFINE(wpm_work, wpm_work_handler); -void wpm_expiry_function() { k_work_submit(&wpm_work); } +void wpm_expiry_function(struct k_timer *_timer) { k_work_submit(&wpm_work); } K_TIMER_DEFINE(wpm_timer, wpm_expiry_function, NULL); -int wpm_init() { +int wpm_init(const struct device *_device) { wpm_state = 0; wpm_update_counter = 0; k_timer_start(&wpm_timer, K_SECONDS(WPM_UPDATE_INTERVAL_SECONDS), diff --git a/app/tests/ble/central/src/main.c b/app/tests/ble/central/src/main.c index 67521e60da0..1e2c786e33d 100644 --- a/app/tests/ble/central/src/main.c +++ b/app/tests/ble/central/src/main.c @@ -40,6 +40,10 @@ LOG_MODULE_REGISTER(ble_central, 4); static bool disconnect_and_reconnect = false; static bool clear_bond_on_disconnect = false; static bool halt_after_bonding = false; +static bool read_hid_report_on_connect = false; +static bool skip_set_security_on_connect = false; +static bool skip_discovery_on_connect = false; +static bool read_directly_on_discovery = false; static int32_t wait_on_start = 0; static void ble_central_native_posix_options(void) { @@ -59,6 +63,26 @@ static void ble_central_native_posix_options(void) { .type = 'b', .dest = (void *)&clear_bond_on_disconnect, .descript = "Clear bonds on disconnect and reconnect"}, + {.is_switch = true, + .option = "skip_set_security_on_connect", + .type = 'b', + .dest = (void *)&skip_set_security_on_connect, + .descript = "Skip set security level after connecting"}, + {.is_switch = true, + .option = "read_hid_report_on_connect", + .type = 'b', + .dest = (void *)&read_hid_report_on_connect, + .descript = "Read the peripheral HID report after connecting"}, + {.is_switch = true, + .option = "skip_discovery_on_connect", + .type = 'b', + .dest = (void *)&skip_discovery_on_connect, + .descript = "Skip GATT characteristic discovery after connecting"}, + {.is_switch = true, + .option = "read_directly_on_discovery", + .type = 'b', + .dest = (void *)&read_directly_on_discovery, + .descript = "Read HIDS report after GATT characteristic discovery"}, {.option = "wait_on_start", .name = "milliseconds", .type = 'u', @@ -94,6 +118,16 @@ static uint8_t notify_func(struct bt_conn *conn, struct bt_gatt_subscribe_params return BT_GATT_ITER_CONTINUE; } +static struct bt_gatt_read_params read_params; +static const struct bt_uuid_16 hids_uuid = BT_UUID_INIT_16(BT_UUID_HIDS_REPORT_VAL); + +static uint8_t read_cb(struct bt_conn *conn, uint8_t err, struct bt_gatt_read_params *params, + const void *data, uint16_t length) { + LOG_DBG("Read err: %d, length %d", err, length); + + return BT_GATT_ITER_CONTINUE; +} + static uint8_t discover_func(struct bt_conn *conn, const struct bt_gatt_attr *attr, struct bt_gatt_discover_params *params) { int err; @@ -117,15 +151,24 @@ static uint8_t discover_func(struct bt_conn *conn, const struct bt_gatt_attr *at LOG_DBG("[Discover failed] (err %d)", err); } } else if (!bt_uuid_cmp(discover_params.uuid, BT_UUID_HIDS_REPORT)) { - memcpy(&uuid, BT_UUID_GATT_CCC, sizeof(uuid)); - discover_params.uuid = &uuid.uuid; - discover_params.start_handle = attr->handle + 2; - discover_params.type = BT_GATT_DISCOVER_DESCRIPTOR; - subscribe_params.value_handle = bt_gatt_attr_value_handle(attr); + if (read_directly_on_discovery) { + read_params.single.handle = bt_gatt_attr_value_handle(attr); + read_params.single.offset = 0; + read_params.handle_count = 1; + read_params.func = read_cb; - err = bt_gatt_discover(conn, &discover_params); - if (err) { - LOG_DBG("[Discover failed] (err %d)", err); + bt_gatt_read(conn, &read_params); + } else { + memcpy(&uuid, BT_UUID_GATT_CCC, sizeof(uuid)); + discover_params.uuid = &uuid.uuid; + discover_params.start_handle = attr->handle + 2; + discover_params.type = BT_GATT_DISCOVER_DESCRIPTOR; + subscribe_params.value_handle = bt_gatt_attr_value_handle(attr); + + err = bt_gatt_discover(conn, &discover_params); + if (err) { + LOG_DBG("[Discover failed] (err %d)", err); + } } } else { subscribe_params.notify = notify_func; @@ -278,12 +321,23 @@ static void connected(struct bt_conn *conn, uint8_t conn_err) { LOG_DBG("[Connected]: %s", addr); if (conn == default_conn) { - if (bt_conn_get_security(conn) >= BT_SECURITY_L2) { + if (bt_conn_get_security(conn) >= BT_SECURITY_L2 && !skip_discovery_on_connect) { + LOG_DBG("[Discovering characteristics for the connection]"); discover_conn(conn); - } else { + } else if (!skip_set_security_on_connect) { LOG_DBG("[Setting the security for the connection]"); bt_conn_set_security(conn, BT_SECURITY_L2); } + + if (read_hid_report_on_connect) { + read_params.func = read_cb; + read_params.handle_count = 0; + read_params.by_uuid.start_handle = 0x0001; + read_params.by_uuid.end_handle = 0xFFFF; + read_params.by_uuid.uuid = &hids_uuid.uuid; + + bt_gatt_read(conn, &read_params); + } } } @@ -313,7 +367,7 @@ static void security_changed(struct bt_conn *conn, bt_security_t level, enum bt_ first_connect = false; if (do_disconnect) { k_work_reschedule(&disconnect_work, K_MSEC(500)); - } else { + } else if (!skip_discovery_on_connect) { discover_conn(conn); } } diff --git a/app/tests/ble/profiles/overwrite-enabled-reconnect-without-bond-then-output-to-selection/centrals.txt b/app/tests/ble/profiles/overwrite-enabled-reconnect-without-bond-then-output-to-selection/centrals.txt new file mode 100644 index 00000000000..53d70e32353 --- /dev/null +++ b/app/tests/ble/profiles/overwrite-enabled-reconnect-without-bond-then-output-to-selection/centrals.txt @@ -0,0 +1 @@ +./ble_test_central.exe -d=2 -disconnect_and_reconnect -clear_bond_on_disconnect diff --git a/app/tests/ble/profiles/overwrite-enabled-reconnect-without-bond-then-output-to-selection/events.patterns b/app/tests/ble/profiles/overwrite-enabled-reconnect-without-bond-then-output-to-selection/events.patterns new file mode 100644 index 00000000000..cca5a2d4ed3 --- /dev/null +++ b/app/tests/ble/profiles/overwrite-enabled-reconnect-without-bond-then-output-to-selection/events.patterns @@ -0,0 +1 @@ +s/^d_02: @[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9][0-9][0-9][0-9] .{19}//p diff --git a/app/tests/ble/profiles/overwrite-enabled-reconnect-without-bond-then-output-to-selection/nrf52_bsim.conf b/app/tests/ble/profiles/overwrite-enabled-reconnect-without-bond-then-output-to-selection/nrf52_bsim.conf new file mode 100644 index 00000000000..e1bee6a7ed3 --- /dev/null +++ b/app/tests/ble/profiles/overwrite-enabled-reconnect-without-bond-then-output-to-selection/nrf52_bsim.conf @@ -0,0 +1 @@ +CONFIG_BT_SMP_ALLOW_UNAUTH_OVERWRITE=y \ No newline at end of file diff --git a/app/tests/ble/profiles/overwrite-enabled-reconnect-without-bond-then-output-to-selection/nrf52_bsim.keymap b/app/tests/ble/profiles/overwrite-enabled-reconnect-without-bond-then-output-to-selection/nrf52_bsim.keymap new file mode 100644 index 00000000000..789cec44337 --- /dev/null +++ b/app/tests/ble/profiles/overwrite-enabled-reconnect-without-bond-then-output-to-selection/nrf52_bsim.keymap @@ -0,0 +1,25 @@ +#include +#include +#include +#include + +&kscan { + events = + ; +}; + +/ { + keymap { + compatible = "zmk,keymap"; + label = "Default keymap"; + + default_layer { + bindings = < + &kp A &kp B + &bt BT_SEL 0 &bt BT_SEL 1>; + }; + }; +}; diff --git a/app/tests/ble/profiles/overwrite-enabled-reconnect-without-bond-then-output-to-selection/snapshot.log b/app/tests/ble/profiles/overwrite-enabled-reconnect-without-bond-then-output-to-selection/snapshot.log new file mode 100644 index 00000000000..da2a0c772d7 --- /dev/null +++ b/app/tests/ble/profiles/overwrite-enabled-reconnect-without-bond-then-output-to-selection/snapshot.log @@ -0,0 +1,36 @@ + bt_id: No static addresses stored in controller + ble_central: _posix_zephyr_main: [Bluetooth initialized] + ble_central: start_scan: [Scanning successfully started] + ble_central: device_found: [DEVICE]: ED:3B:20:15:18:12 (random), AD evt type 0, AD data len 15, RSSI -59 + ble_central: eir_found: [AD]: 9 data_len 0 + ble_central: eir_found: [AD]: 25 data_len 2 + ble_central: eir_found: [AD]: 1 data_len 1 + ble_central: eir_found: [AD]: 2 data_len 4 + ble_central: connected: [Connected]: ED:3B:20:15:18:12 (random) + ble_central: connected: [Setting the security for the connection] + ble_central: pairing_complete: Pairing complete + ble_central: disconnected: [Disconnected]: ED:3B:20:15:18:12 (random) (reason 0x16) + ble_central: start_scan: [Scanning successfully started] + ble_central: device_found: [DEVICE]: ED:3B:20:15:18:12 (random), AD evt type 0, AD data len 15, RSSI -59 + ble_central: eir_found: [AD]: 9 data_len 0 + ble_central: eir_found: [AD]: 25 data_len 2 + ble_central: eir_found: [AD]: 1 data_len 1 + ble_central: eir_found: [AD]: 2 data_len 4 + ble_central: connected: [Connected]: ED:3B:20:15:18:12 (random) + ble_central: connected: [Setting the security for the connection] + ble_central: pairing_complete: Pairing complete + ble_central: discover_conn: [Discovery started for conn] + ble_central: discover_func: [ATTRIBUTE] handle 23 + ble_central: discover_func: [ATTRIBUTE] handle 28 + ble_central: discover_func: [ATTRIBUTE] handle 30 + ble_central: discover_func: [SUBSCRIBED] + ble_central: notify_func: payload + 00 00 04 00 00 00 00 00 |........ + ble_central: notify_func: payload + 00 00 00 00 00 00 00 00 |........ + ble_central: notify_func: payload + 00 00 05 00 00 00 00 00 |........ + ble_central: notify_func: payload + 00 00 00 00 00 00 00 00 |........ + ble_central: notify_func: payload + 00 00 00 00 00 00 00 00 |........ diff --git a/app/tests/ble/security/read-hid-after-connect-with-auto-sec/centrals.txt b/app/tests/ble/security/read-hid-after-connect-with-auto-sec/centrals.txt new file mode 100644 index 00000000000..a660de60c75 --- /dev/null +++ b/app/tests/ble/security/read-hid-after-connect-with-auto-sec/centrals.txt @@ -0,0 +1 @@ +./ble_test_central.exe -d=2 -skip_set_security_on_connect -read_hid_report_on_connect -skip_discovery_on_connect diff --git a/app/tests/ble/security/read-hid-after-connect-with-auto-sec/events.patterns b/app/tests/ble/security/read-hid-after-connect-with-auto-sec/events.patterns new file mode 100644 index 00000000000..cca5a2d4ed3 --- /dev/null +++ b/app/tests/ble/security/read-hid-after-connect-with-auto-sec/events.patterns @@ -0,0 +1 @@ +s/^d_02: @[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9][0-9][0-9][0-9] .{19}//p diff --git a/app/tests/ble/security/read-hid-after-connect-with-auto-sec/nrf52_bsim.conf b/app/tests/ble/security/read-hid-after-connect-with-auto-sec/nrf52_bsim.conf new file mode 100644 index 00000000000..e69de29bb2d diff --git a/app/tests/ble/security/read-hid-after-connect-with-auto-sec/nrf52_bsim.keymap b/app/tests/ble/security/read-hid-after-connect-with-auto-sec/nrf52_bsim.keymap new file mode 100644 index 00000000000..7c67425e6a2 --- /dev/null +++ b/app/tests/ble/security/read-hid-after-connect-with-auto-sec/nrf52_bsim.keymap @@ -0,0 +1,24 @@ +#include +#include +#include +#include + +&kscan { + events = + ; +}; + +/ { + keymap { + compatible = "zmk,keymap"; + + default_layer { + bindings = < + &kp A &kp B + &bt BT_SEL 0 &bt BT_SEL 1>; + }; + }; +}; diff --git a/app/tests/ble/security/read-hid-after-connect-with-auto-sec/snapshot.log b/app/tests/ble/security/read-hid-after-connect-with-auto-sec/snapshot.log new file mode 100644 index 00000000000..1ec7918f17e --- /dev/null +++ b/app/tests/ble/security/read-hid-after-connect-with-auto-sec/snapshot.log @@ -0,0 +1,13 @@ + bt_id: No static addresses stored in controller + ble_central: _posix_zephyr_main: [Bluetooth initialized] + ble_central: start_scan: [Scanning successfully started] + ble_central: device_found: [DEVICE]: ED:3B:20:15:18:12 (random), AD evt type 0, AD data len 15, RSSI -59 + ble_central: eir_found: [AD]: 9 data_len 0 + ble_central: eir_found: [AD]: 25 data_len 2 + ble_central: eir_found: [AD]: 1 data_len 1 + ble_central: eir_found: [AD]: 2 data_len 4 + ble_central: connected: [Connected]: ED:3B:20:15:18:12 (random) + ble_central: pairing_complete: Pairing complete + ble_central: read_cb: Read err: 0, length 8 + ble_central: read_cb: Read err: 0, length 12 + ble_central: read_cb: Read err: 10, length 0 diff --git a/app/tests/ble/security/read-hid-after-connect-without-auto-sec/centrals.txt b/app/tests/ble/security/read-hid-after-connect-without-auto-sec/centrals.txt new file mode 100644 index 00000000000..3b20ace116a --- /dev/null +++ b/app/tests/ble/security/read-hid-after-connect-without-auto-sec/centrals.txt @@ -0,0 +1 @@ +./ble_test_no_auto_sec_central.exe -d=2 -skip_set_security_on_connect -read_hid_report_on_connect -skip_discovery_on_connect diff --git a/app/tests/ble/security/read-hid-after-connect-without-auto-sec/events.patterns b/app/tests/ble/security/read-hid-after-connect-without-auto-sec/events.patterns new file mode 100644 index 00000000000..cca5a2d4ed3 --- /dev/null +++ b/app/tests/ble/security/read-hid-after-connect-without-auto-sec/events.patterns @@ -0,0 +1 @@ +s/^d_02: @[0-9][0-9]:[0-9][0-9]:[0-9][0-9].[0-9][0-9][0-9][0-9][0-9][0-9] .{19}//p diff --git a/app/tests/ble/security/read-hid-after-connect-without-auto-sec/nrf52_bsim.conf b/app/tests/ble/security/read-hid-after-connect-without-auto-sec/nrf52_bsim.conf new file mode 100644 index 00000000000..e69de29bb2d diff --git a/app/tests/ble/security/read-hid-after-connect-without-auto-sec/nrf52_bsim.keymap b/app/tests/ble/security/read-hid-after-connect-without-auto-sec/nrf52_bsim.keymap new file mode 100644 index 00000000000..7c67425e6a2 --- /dev/null +++ b/app/tests/ble/security/read-hid-after-connect-without-auto-sec/nrf52_bsim.keymap @@ -0,0 +1,24 @@ +#include +#include +#include +#include + +&kscan { + events = + ; +}; + +/ { + keymap { + compatible = "zmk,keymap"; + + default_layer { + bindings = < + &kp A &kp B + &bt BT_SEL 0 &bt BT_SEL 1>; + }; + }; +}; diff --git a/app/tests/ble/security/read-hid-after-connect-without-auto-sec/snapshot.log b/app/tests/ble/security/read-hid-after-connect-without-auto-sec/snapshot.log new file mode 100644 index 00000000000..fc32198c14b --- /dev/null +++ b/app/tests/ble/security/read-hid-after-connect-without-auto-sec/snapshot.log @@ -0,0 +1,10 @@ + bt_id: No static addresses stored in controller + ble_central: _posix_zephyr_main: [Bluetooth initialized] + ble_central: start_scan: [Scanning successfully started] + ble_central: device_found: [DEVICE]: ED:3B:20:15:18:12 (random), AD evt type 0, AD data len 15, RSSI -59 + ble_central: eir_found: [AD]: 9 data_len 0 + ble_central: eir_found: [AD]: 25 data_len 2 + ble_central: eir_found: [AD]: 1 data_len 1 + ble_central: eir_found: [AD]: 2 data_len 4 + ble_central: connected: [Connected]: ED:3B:20:15:18:12 (random) + ble_central: read_cb: Read err: 15, length 0 diff --git a/docs/blog/2023-12-17-nodefree-config.md b/docs/blog/2023-12-17-nodefree-config.md new file mode 100644 index 00000000000..359a264f8cc --- /dev/null +++ b/docs/blog/2023-12-17-nodefree-config.md @@ -0,0 +1,189 @@ +--- +title: "Community Spotlight Series #2: Node-free Config" +author: Cem Aksoylar +author_title: Documentation maintainer +author_url: https://github.com/caksoylar +author_image_url: https://avatars.githubusercontent.com/u/7876996 +tags: [keyboards, firmware, community] +--- + +This blog continues our series of posts where we highlight projects within the ZMK ecosystem +that we think are interesting and that the users might benefit from knowing about them. You might +be aware that ZMK configurations in the [Devicetree format](/docs/config#devicetree-files) +use the [C preprocessor](https://en.wikipedia.org/wiki/C_preprocessor) so that directives like +`#define RAISE 2` or `#include ` can be used in them. In this installment we are +highlighting the [`zmk-nodefree-config` project](https://github.com/urob/zmk-nodefree-config) +by [urob](https://github.com/urob) that contains helper methods that utilizes this fact +for users who prefer editing and maintaining their ZMK config directly using the Devicetree +syntax format. + +In the rest of the post we leave it to urob to introduce and explain the motivations of the +project, and various ways it can be used to help maintain ZMK keymaps. Stay tuned for future +installments in the series! + +## Overview + +Loosely speaking the _nodefree_ repo -- more on the name later -- is a +collection of helper functions that simplify configuring keymap files. Unlike +the graphical keymap editor covered in the [previous spotlight +post](https://zmk.dev/blog/2023/11/09/keymap-editor), it is aimed at users who +edit and maintain directly the source code of their keymap files. + +The provided helpers fall into roughly one of three categories: + +1. Helpers that eliminate boilerplate, reduce the complexity of keymaps, and improve readability. +2. Helpers that improve portability of "position-based" properties such as combos. +3. Helpers that define international and other unicode characters. + +The reminder of this post details each of these three categories. + +## Eliminating Boilerplate + +In ZMK, keymaps are configured using so-called _Devicetree_ files. Devicetree files +define a collection of nested _nodes_, whereas each node in turn specifies a variety of +_properties_ through which one can customize the keymap. + +For example, the following snippet sets up a +[mod-morph](https://zmk.dev/docs/behaviors/mod-morph) behavior that sends . +("dot") when pressed by itself and sends : ("colon") when shifted: + +```dts {6-7} showLineNumbers +/ { + behaviors { + dot_colon: dot_colon_behavior { + compatible = "zmk,behavior-mod-morph"; + #binding-cells = <0>; + bindings = <&kp DOT>, <&kp COLON>; + mods = <(MOD_LSFT|MOD_RSFT)>; + }; + }; +}; +``` + +Adding this snippet to the keymap will create a new node `dot_colon_behavior` +(nested underneath the `behaviors` and root `/` nodes), and assigns it four +properties (`compatible`, `#binding-cells`, etc). Here, the crucial properties are `bindings` +and `mods`, which spell out the actual functionality of the new behavior. The rest +of the snippet (including the nested node-structure) is boilerplate. + +The idea of the _nodefree_ repo is to use C preprocessor macros to improve +readability by eliminating as much boilerplate as possible. Besides hiding +redundant behavior properties from the user, it also automatically creates and +nests all required behavior nodes, making for a "node-free" and less +error-prone user experience (hence the name of the repo). + +For example, using `ZMK_BEHAVIOR`, one of the repo's helper functions, the +above snippet simplifies to: + +```dts showLineNumbers +ZMK_BEHAVIOR(dot_colon, mod_morph, + bindings = <&kp DOT>, <&kp COLON>; + mods = <(MOD_LSFT|MOD_RSFT)>; +) +``` + +For complex keymap files, the gains from eliminating boilerplate can be +enormous. To provide a benchmark, consider my [personal +config](https://github.com/urob/zmk-config), which uses the _nodefree_ repo to +create various behaviors, set up combos, and add layers to the keymap. Without +the _nodefree_ helpers, the total size of my keymap would have been 41 kB. Using +the helper macros, the actual size is instead reduced to a more sane 12 kB.[^1] + +[^1]: + To compute the impact on file size, I ran `pcpp +--passthru-unfound-includes` on the `base.keymap` file, comparing two + variants. First, I ran the pre-processor on the actual file. Second, I ran + it on a version where I commented out all the _nodefree_ headers, + preventing any of the helper functions from getting expanded. The + difference isolates precisely the size gains from eliminating boilerplate, + which in my ZMK config are especially large due to a vast number of + behaviors used to add various Unicode characters to my keymap. + +## Simplifying "Position-based" Behaviors + +In ZMK, there are several features that are position-based. As of today, these +are [combos](/docs/features/combos) and [positional +hold-taps](/docs/behaviors/hold-tap#positional-hold-tap-and-hold-trigger-key-positions), +with behaviors like the ["Swapper"](https://github.com/zmkfirmware/zmk/pull/1366) and [Leader +key](https://github.com/zmkfirmware/zmk/pull/1380) currently +developed by [Nick Conway](https://github.com/nickconway) in pull requests also utilizing them. + +Configuring these behaviors involves lots of key counting, which can be +cumbersome and error-prone, especially on larger keyboards. It also reduces the +portability of configuration files across keyboards with different layouts. + +To facilitate configuring position-based behaviors, the _nodefree_ repo comes +with a community-maintained library of "key-position labels" for a variety of +popular layouts. The idea is to provide a standardized naming convention that +is consistent across different keyboards. For instance, the labels for a 36-key +layout are as follows: + +``` + ╭─────────────────────┬─────────────────────╮ + │ LT4 LT3 LT2 LT1 LT0 │ RT0 RT1 RT2 RT3 RT4 │ + │ LM4 LM3 LM2 LM1 LM0 │ RM0 RM1 RM2 RM3 RM4 │ + │ LB4 LB3 LB2 LB1 LB0 │ RB0 RB1 RB2 RB3 RB4 │ + ╰───────╮ LH2 LH1 LH0 │ RH0 RH1 RH2 ╭───────╯ + ╰─────────────┴─────────────╯ +``` + +The labels are all of the following form: + +- `L/R` for **L**eft/**R**ight side +- `T/M/B/H` for **T**op/**M**iddle/**B**ottom and t**H**umb row. +- `0/1/2/3/4` for the finger position, counting from the inside to the outside + +The library currently contains definitions for 17 physical +layouts, ranging from the tiny [Osprette](https://github.com/smores56/osprette) to the large-ish +[Glove80](https://www.moergo.com/collections/glove80-keyboards). +While some of these layouts contain more keys than others, the idea behind the +library is that keys that for all practical purposes are in the "same" location +share the same label. That is, the 3 rows containing the alpha keys are +always labeled `T/M/B` with `LM1` and `RM1` defining the home position of +the index fingers. For larger boards, the numbers row is always labeled +`N`. For even larger boards, the function key row and the row below `B` are +labeled `C` and `F` (mnemonics for **C**eiling and **F**loor), etc. + +Besides sparing the user from counting keys, the library also makes it easy to +port an entire, say, combo configuration from one keyboard to the next by simply +switching layout headers. + +## Unicode and International Keycodes + +The final category of helpers is targeted at people who wish to type international characters +without switching the input language of their operation system. To do so, the repo comes with +helper functions that can be used to define Unicode behaviors. + +In addition, the repo also ships with a community-maintained library of +language-files that define Unicode behaviors for all relevant characters in a +given language. For instance, after loading the German language file, one can +add `&de_ae` to the keymap, which will send ä/Ä when pressed or shifted. + +## About Me + +My path to ZMK and programmable keyboards started in the early pandemic, when I +built a [Katana60](https://geekhack.org/index.php?topic=88719.0) and learned +how to touch-type Colemak. Soon after I purchased a Planck, which turned out +to be the real gateway drug for me. + +Committed to making the best out of the Planck's 48 keys, I have since +discovered my love for tinkering with tiny layouts and finding new ways of +[squeezing out](https://xkcd.com/2583/) a bit more ergonomics. Along the way, I +also made the switch from QMK to ZMK, whose "object-oriented" approach to +behaviors I found more appealing for complex keymaps.[^2] + +[^2]: + I am using the term object-oriented somewhat loosely here. What I mean by + that is the differentiation between abstract behavior classes (such as + hold-taps) and specific behavior instances that are added to the keymap. + Allowing to set up multiple, reusable instances of each behavior has been a + _huge_ time-saver compared to QMK's more limited behavior settings that are + either global or key-specific. + +These days I mostly type on a Corne-ish Zen and are waiting for the day when I +will finally put together the +[Hypergolic](https://github.com/davidphilipbarr/hypergolic) that's been sitting +on my desk for months. My current keymap is designed for 34 keys, making +liberal use of combos and [timerless homerow +mods](https://github.com/urob/zmk-config#timeless-homerow-mods) to make up for +a lack of keys. diff --git a/docs/blog/2024-01-05-zmk-tools.md b/docs/blog/2024-01-05-zmk-tools.md new file mode 100755 index 00000000000..b48e57357b5 --- /dev/null +++ b/docs/blog/2024-01-05-zmk-tools.md @@ -0,0 +1,158 @@ +--- +title: "Community Spotlight Series #3: ZMK Tools and ZMK Locale Generator" +author: Cem Aksoylar +author_title: Documentation maintainer +author_url: https://github.com/caksoylar +author_image_url: https://avatars.githubusercontent.com/u/7876996 +tags: [keyboards, firmware, community] +--- + +This blog continues our series of posts where we highlight projects within the ZMK ecosystem +that we think are interesting and that the users might benefit from knowing about them. + +In this installment, we are highlighting two projects (and a bonus one!) from [Joel Spadin](https://github.com/joelspadin), +a member of the core ZMK team. +The first one is [ZMK Tools](#zmk-tools), a handy Visual Studio Code extension to ease working with ZMK configurations, and the second is [ZMK Locale Generator](#zmk-locale-generator), a tool to help users that use non-US English keyboard locales in their operating systems. + +In the rest of the post we leave it to Joel to introduce and explain the motivations of his ZMK-related projects. +Stay tuned for future installments in the series! + +## ZMK Tools + +[ZMK Tools](https://github.com/joelspadin/zmk-tools) is an extension for [Visual Studio Code](https://code.visualstudio.com) that helps with editing a ZMK user config repo or a fork of ZMK. I originally created it to add some code completion in `.keymap` files, but then I realized that with the web version of VS Code, I could also let you set up a user config repo and build firmware, much like the [user setup script](/docs/user-setup#user-config-setup-script), except without downloading a single thing. + +### User Config Setup in Browser + +Here is how you can use ZMK Tools to get started with writing a ZMK keymap entirely within your browser. More detailed instructions can be found on the [ZMK Tools README](https://github.com/joelspadin/zmk-tools/blob/main/README.md). + +1. Open the [ZMK config template repo](https://github.com/zmkfirmware/unified-zmk-config-template) on GitHub. +2. Click the **Use this template** button and follow the instructions to create your own repo. + - If you don't see this button, make sure you're signed in to GitHub first. + - You can name the repo anything you want, but "zmk-config" is the conventional name. +3. From the GitHub page for your new repo, press . (period) and it will re-open the repo in github.dev. +4. Press Ctrl + P and enter the following to install the ZMK Tools extension: + ``` + ext install spadin.zmk-tools + ``` +5. Press Ctrl + Shift + P and run the **ZMK: Add Keyboard** command. +6. Follow the prompts to select a keyboard. ZMK Tools will copy the default keymap for that keyboard if you don't already have one, and it will automatically add it to your `build.yaml` file so GitHub will build it for you. + +You can then edit your `.keymap` and `.conf` files. Once you're done: + +1. Click the **Source Control** tab on the side bar. +2. Hover over the header for the **Changes** list and click the `+` (Stage All Changes) button. +3. Write a commit message and click **Commit & Push** to push your changes to GitHub. + +GitHub will start building the new firmware. To check the results: + +1. Use your browser's back button to go back to your repo's GitHub page. +2. Click the **Actions** tab at the top of the page. +3. Click the latest build (it should show the commit message you entered earlier). If it's still in progress, wait for it to finish. +4. If the build was successful, go to the **Artifacts** section and click **firmware** to download the firmware. If it failed, check the error and go back to github.dev to fix it. + +### Keymap Code Completion + +ZMK Tools also provides some basic code completion in `.keymap` files. It will suggest any of ZMK's built-in behaviors inside `bindings` and `sensor-bindings` properties, and it will automatically add the necessary headers. + +For example, with the cursor at the end of line 6 in the following keymap... + +```dts {6} showLineNumbers +/ { + keymap { + compatible = "zmk,keymap"; + default_layer { + bindings = < + & + >; + }; + }; +}; +``` + +...it will suggest things such as `&kp`, `&mo`, etc., and upon entering one, it will recognize that `#include ` is missing and add it to the top of the keymap: + +```dts {1} showLineNumbers +#include +/ { + keymap { + compatible = "zmk,keymap"; + default_layer { + bindings = < + &kp + >; + }; + }; +}; +``` + +Press space after `&kp`, and it will suggest all of ZMK's key codes. Upon entering one, it will again recognize that `#include ` is missing and add it too: + +```dts {2} showLineNumbers +#include +#include +/ { + keymap { + compatible = "zmk,keymap"; + default_layer { + bindings = < + &kp A + >; + }; + }; +}; +``` + +This can be very helpful for making sure you spelled key codes correctly and included all the correct headers. + +### Future Work + +Unfortunately, all the code completion info currently comes from a config file baked into the extension, so it won't pick up any custom behaviors or key code aliases you've defined. I'd like to make that work eventually, but it's a much more difficult problem to solve. + +ZMK Tools will discover all the boards/shields from both ZMK and your user config repo. With some recent changes in ZMK to allow pulling in features from other Zephyr modules, it's now possible to use board/shields defined in other repos, but ZMK Tools doesn't know about this yet. I'd like to support this too, but making it work in the web version of the extension will be challenging. + +## ZMK Locale Generator + +ZMK's key codes follow the [HID specification](https://www.usb.org/hid), and many key codes indicate the _position_ of a key on US keyboard layout, not the key's function. If your operating system is set to a different keyboard locale, then the character each key types won't necessarily line up with the key code name. For example, on a German "QWERTZ" layout, `&kp Y` will type Z and `&kp Z` will type Y, so you have to write your layout as if it were QWERTY instead. Other layouts can be even more confusing! + +[ZMK Locale Generator](https://github.com/joelspadin/zmk-locale-generator) is another tool I made to help with this. It reads [CLDR keyboard layouts](https://cldr.unicode.org/index/charts/keyboards) and generates `#define`s to alias key codes to names that make sense in other locales. To use it, first go to the [latest release](https://github.com/joelspadin/zmk-locale-generator/releases/latest) and download the header that matches the locale you use. Next, copy it into the same folder as your keymap and `#include` it: + +```dts +#include "keys_de.h" + +/ { + ... +}; +``` + +If you open the header file in a text editor, you'll see that it contains many of the standard ZMK key codes, except they are prefixed by the locale code. Depending on the locale, it may also define key codes for special characters specific to that locale, e.g. `DE_A_UMLAUT` for "ä" and `DE_SZ` for "ß". If you use these in your keymap, then ZMK will send the correct key codes to type those characters. + +```dts +#include "keys_de.h" + +/ { + keymap { + compatible = "zmk,keymap"; + default_layer { + bindings = < + &kp DE_Q &kp DE_W &kp DE_E &kp DE_R &kp DE_T &kp DE_Z ... + >; + }; + } +}; +``` + +I should note that, as a native English speaker and typer, I don't use any of this myself! I just saw that many people were asking for help with this, and I realized I could automate a solution. If you find something that isn't generated correctly, please [file an issue](https://github.com/joelspadin/zmk-locale-generator/issues) or PR a fix on GitHub. + +## Keyboard Latency Testing + +The last project I want to mention is a tool for testing keyboard latency. It requires only a Rasbperry Pi, an optocoupler IC, a resistor, and some wire. If you've ever wondered how ZMK's latency compares to other keyboards, you can [check the results here](https://github.com/joelspadin/keyboard-latency-tester/blob/main/results/chart.ipynb)! + +I don't have a very large collection of keyboards though, so the data is pretty limited so far. If you want to try it on your own keyboard, see the instructions on the [keyboard latency tester README](https://github.com/joelspadin/keyboard-latency-tester), and please send me a PR with your results! + +## About Me + +I got a degree in electrical engineering but promptly became a software engineer instead. I still like tinkering with electronics though, so I discovered ZMK when I was making wireless macropad with a nice!nano, and I became a regular contributor after that. I use mostly larger keyboards with standard layouts and rarely use anything more complicated than momentary layers, so I've mostly focused on improving core features and tooling. + +The keyboards I regularly use are a Ducky One 2 TKL that I leave at work, a Freebird TKL[^1], a custom [wireless numpad](https://github.com/joelspadin/NumBLE), and a Yamaha CP4. + +[^1] Running QMK, but I have designs to make a wireless PCB for it someday... diff --git a/docs/docs/behaviors/bluetooth.md b/docs/docs/behaviors/bluetooth.md index b34614e6205..41ec0ab16d7 100644 --- a/docs/docs/behaviors/bluetooth.md +++ b/docs/docs/behaviors/bluetooth.md @@ -10,7 +10,10 @@ between the keyboard and the host. By default, ZMK supports five "profiles" for computer/laptop/keyboard should receive the keyboard input; many of the commands here operate on those profiles. :::note Connection Management -When pairing to a host device ZMK saves bond information to the selected profile. It will not replace this when you initiate pairing with another device. To pair with a new device select an unused profile with `BT_SEL`, `BT_NXT` or `BT_PRV` bindings, or by clearing an existing profile using `BT_CLR`. +When pairing to a host device ZMK saves bond information to the selected +profile. It will not replace this when you initiate pairing with another device. +To pair with a new device, select a profile that doesn't have a pairing with `BT_SEL`, `BT_NXT` or +`BT_PRV` bindings, or clear an already paired profile using `BT_CLR` or `BT_CLR_ALL`. A ZMK device may show as "connected" on multiple hosts at the same time. This is working as intended, and only the host associated with the active profile will receive keystrokes. @@ -35,13 +38,14 @@ This will allow you to reference the actions defined in this header such as `BT_ Here is a table describing the command for each define: -| Define | Action | -| --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `BT_CLR` | Clear bond information between the keyboard and host for the selected profile. | -| `BT_NXT` | Switch to the next profile, cycling through to the first one when the end is reached. | -| `BT_PRV` | Switch to the previous profile, cycling through to the last one when the beginning is reached. | -| `BT_SEL` | Select the 0-indexed profile by number; must include a number as an argument in the keymap to work correctly, e.g. `BT_SEL 0`. | -| `BT_DISC` | Disconnect from the 0-indexed profile by number, if it's currently connected and inactive; must include a number as an argument in the keymap to work correctly, e.g. `BT_DISC 0`. | +| Define | Action | +| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `BT_CLR` | Clear bond information between the keyboard and host for the selected profile. | +| `BT_CLR_ALL` | Clear bond information between the keyboard and host for all profiles. | +| `BT_NXT` | Switch to the next profile, cycling through to the first one when the end is reached. | +| `BT_PRV` | Switch to the previous profile, cycling through to the last one when the beginning is reached. | +| `BT_SEL` | Select the 0-indexed profile by number; must include a number as an argument in the keymap to work correctly, e.g. `BT_SEL 0`. | +| `BT_DISC` | Disconnect from the 0-indexed profile by number, if it's currently connected and inactive; must include a number as an argument in the keymap to work correctly, e.g. `BT_DISC 0`. | :::note Selected profile persistence The profile that is selected by the `BT_SEL`/`BT_PRV`/`BT_NXT` actions will be saved to flash storage and hence persist across restarts and firmware flashes. @@ -56,7 +60,7 @@ The bluetooth behavior completes an bluetooth action given on press. - Reference: `&bt` - Parameter #1: The bluetooth command define, e.g. `BT_CLR` -- Parameter #2: Only applies to `BT_SEL` and is the 0-indexed profile by number +- Parameter #2: Only applies to `BT_SEL` and `BT_DISC`, and is the 0-indexed profile by number ### Examples diff --git a/docs/docs/config/bluetooth.md b/docs/docs/config/bluetooth.md index 61477cb5381..9149b36bf37 100644 --- a/docs/docs/config/bluetooth.md +++ b/docs/docs/config/bluetooth.md @@ -9,8 +9,10 @@ See [Configuration Overview](index.md) for instructions on how to change these s ## Kconfig -| Option | Type | Description | Default | -| -------------------------------------- | ---- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -| `CONFIG_ZMK_BLE_EXPERIMENTAL_FEATURES` | bool | Enables a combination of settings that are planned to be default in future versions of ZMK. This includes changes to timing on BLE pairing initation, BT Secure Connection passkey entry, restores use of the updated/new LLCP implementation, disables 2M PHY support, and allows overwrite of keys from previously paired hosts. | n | -| `CONFIG_ZMK_BLE_PASSKEY_ENTRY` | bool | Enable passkey entry during pairing for enhanced security. (Note: After enabling this, you will need to re-pair all previously paired hosts) | n | -| `CONFIG_BT_GATT_ENFORCE_SUBSCRIPTION` | bool | Low level setting for GATT subscriptions. Set to `n` to work around an annoying Windows bug with battery notifications. | y | +| Option | Type | Description | Default | +| -------------------------------------- | ---- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | +| `CONFIG_ZMK_BLE_EXPERIMENTAL_CONN` | bool | Enables a combination of settings that are planned to be default in future versions of ZMK to improve connection stability. This includes changes to timing on BLE pairing initation, restores use of the updated/new LLCP implementation, and disables 2M PHY support. | n | +| `CONFIG_ZMK_BLE_EXPERIMENTAL_SEC` | bool | Enables a combination of settings that are planned to be officially supported in the future. This includes enabling BT Secure Connection passkey entry, and allows overwrite of keys from previously paired hosts. | n | +| `CONFIG_ZMK_BLE_EXPERIMENTAL_FEATURES` | bool | Aggregate config that enables both `CONFIG_ZMK_BLE_EXPERIMENTAL_CONN` and `CONFIG_ZMK_BLE_EXPERIMENTAL_SEC`. | n | +| `CONFIG_ZMK_BLE_PASSKEY_ENTRY` | bool | Enable passkey entry during pairing for enhanced security. (Note: After enabling this, you will need to re-pair all previously paired hosts.) | n | +| `CONFIG_BT_GATT_ENFORCE_SUBSCRIPTION` | bool | Low level setting for GATT subscriptions. Set to `n` to work around an annoying Windows bug with battery notifications. | y | diff --git a/docs/docs/config/system.md b/docs/docs/config/system.md index 4629ea0f790..b10f184d40a 100644 --- a/docs/docs/config/system.md +++ b/docs/docs/config/system.md @@ -104,15 +104,18 @@ Note that `CONFIG_BT_MAX_CONN` and `CONFIG_BT_MAX_PAIRED` should be set to the s Following split keyboard settings are defined in [zmk/app/src/split/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/src/split/Kconfig) (generic) and [zmk/app/src/split/Kconfig](https://github.com/zmkfirmware/zmk/blob/main/app/src/split/bluetooth/Kconfig) (bluetooth). -| Config | Type | Description | Default | -| ----------------------------------------------------- | ---- | ------------------------------------------------------------------------ | ------- | -| `CONFIG_ZMK_SPLIT` | bool | Enable split keyboard support | n | -| `CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS` | bool | Enable split keyboard support for passing indicator state to peripherals | n | -| `CONFIG_ZMK_SPLIT_BLE` | bool | Use BLE to communicate between split keyboard halves | y | -| `CONFIG_ZMK_SPLIT_ROLE_CENTRAL` | bool | `y` for central device, `n` for peripheral | | -| `CONFIG_ZMK_SPLIT_BLE_CENTRAL_POSITION_QUEUE_SIZE` | int | Max number of key state events to queue when received from peripherals | 5 | -| `CONFIG_ZMK_SPLIT_BLE_CENTRAL_SPLIT_RUN_STACK_SIZE` | int | Stack size of the BLE split central write thread | 512 | -| `CONFIG_ZMK_SPLIT_BLE_CENTRAL_SPLIT_RUN_QUEUE_SIZE` | int | Max number of behavior run events to queue to send to the peripheral(s) | 5 | -| `CONFIG_ZMK_SPLIT_BLE_PERIPHERAL_STACK_SIZE` | int | Stack size of the BLE split peripheral notify thread | 650 | -| `CONFIG_ZMK_SPLIT_BLE_PERIPHERAL_PRIORITY` | int | Priority of the BLE split peripheral notify thread | 5 | -| `CONFIG_ZMK_SPLIT_BLE_PERIPHERAL_POSITION_QUEUE_SIZE` | int | Max number of key state events to queue to send to the central | 10 | +| Config | Type | Description | Default | +| ------------------------------------------------------- | ---- | -------------------------------------------------------------------------- | ------------------------------------------ | +| `CONFIG_ZMK_SPLIT` | bool | Enable split keyboard support | n | +| `CONFIG_ZMK_SPLIT_ROLE_CENTRAL` | bool | `y` for central device, `n` for peripheral | | +| `CONFIG_ZMK_SPLIT_PERIPHERAL_HID_INDICATORS` | bool | Enable split keyboard support for passing indicator state to peripherals | n | +| `CONFIG_ZMK_SPLIT_BLE` | bool | Use BLE to communicate between split keyboard halves | y | +| `CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING` | bool | Enable fetching split peripheral battery levels to the central side | n | +| `CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_PROXY` | bool | Enable central reporting of split battery levels to hosts | n | +| `CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_QUEUE_SIZE` | int | Max number of battery level events to queue when received from peripherals | `CONFIG_ZMK_SPLIT_BLE_CENTRAL_PERIPHERALS` | +| `CONFIG_ZMK_SPLIT_BLE_CENTRAL_POSITION_QUEUE_SIZE` | int | Max number of key state events to queue when received from peripherals | 5 | +| `CONFIG_ZMK_SPLIT_BLE_CENTRAL_SPLIT_RUN_STACK_SIZE` | int | Stack size of the BLE split central write thread | 512 | +| `CONFIG_ZMK_SPLIT_BLE_CENTRAL_SPLIT_RUN_QUEUE_SIZE` | int | Max number of behavior run events to queue to send to the peripheral(s) | 5 | +| `CONFIG_ZMK_SPLIT_BLE_PERIPHERAL_STACK_SIZE` | int | Stack size of the BLE split peripheral notify thread | 650 | +| `CONFIG_ZMK_SPLIT_BLE_PERIPHERAL_PRIORITY` | int | Priority of the BLE split peripheral notify thread | 5 | +| `CONFIG_ZMK_SPLIT_BLE_PERIPHERAL_POSITION_QUEUE_SIZE` | int | Max number of key state events to queue to send to the central | 10 |