From 1715a3d3231a934d188f700885b3432704927c47 Mon Sep 17 00:00:00 2001 From: Pete Johanson Date: Sun, 20 Dec 2020 09:49:42 -0500 Subject: [PATCH 01/11] feature(split): behavior locality support. * GATT characteristic allowing passng data + behavior label to invoke the behavior on the peripheral side. * Behaviors have a locality setting to specify where they run. * Build reset/power/RGB on peripheral. --- app/CMakeLists.txt | 4 +- app/dts/behaviors/ext_power.dtsi | 2 +- app/dts/behaviors/reset.dtsi | 2 +- app/dts/behaviors/rgb_underglow.dtsi | 2 +- app/include/drivers/behavior.h | 46 ++++++++++++++ .../zmk/events/position_state_changed.h | 11 +++- app/include/zmk/keymap.h | 5 +- app/include/zmk/split/bluetooth/central.h | 8 +++ app/include/zmk/split/bluetooth/service.h | 14 +++++ app/include/zmk/split/bluetooth/uuid.h | 1 + app/src/behaviors/behavior_ext_power.c | 1 + app/src/behaviors/behavior_reset.c | 1 + app/src/behaviors/behavior_rgb_underglow.c | 1 + app/src/keymap.c | 61 ++++++++++++++++--- app/src/kscan.c | 9 ++- app/src/split/bluetooth/central.c | 49 ++++++++++++++- app/src/split/bluetooth/service.c | 50 +++++++++++++++ 17 files changed, 247 insertions(+), 20 deletions(-) create mode 100644 app/include/zmk/split/bluetooth/central.h diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index c5b74aa536d..1e153fb630d 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -42,9 +42,10 @@ target_sources_ifdef(CONFIG_ZMK_WPM app PRIVATE src/events/wpm_state_changed.c) target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/events/ble_active_profile_changed.c) target_sources_ifdef(CONFIG_ZMK_BLE app PRIVATE src/events/battery_state_changed.c) target_sources_ifdef(CONFIG_USB app PRIVATE src/events/usb_conn_state_changed.c) +target_sources(app PRIVATE src/behaviors/behavior_reset.c) +target_sources_ifdef(CONFIG_ZMK_EXT_POWER app PRIVATE src/behaviors/behavior_ext_power.c) if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL) target_sources(app PRIVATE src/behaviors/behavior_key_press.c) - target_sources(app PRIVATE src/behaviors/behavior_reset.c) target_sources(app PRIVATE src/behaviors/behavior_hold_tap.c) target_sources(app PRIVATE src/behaviors/behavior_sticky_key.c) target_sources(app PRIVATE src/behaviors/behavior_caps_word.c) @@ -57,7 +58,6 @@ if ((NOT CONFIG_ZMK_SPLIT) OR CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL) target_sources(app PRIVATE src/behaviors/behavior_transparent.c) target_sources(app PRIVATE src/behaviors/behavior_none.c) target_sources(app PRIVATE src/behaviors/behavior_sensor_rotate_key_press.c) - target_sources_ifdef(CONFIG_ZMK_EXT_POWER app PRIVATE src/behaviors/behavior_ext_power.c) target_sources(app PRIVATE src/combo.c) target_sources(app PRIVATE src/conditional_layer.c) target_sources(app PRIVATE src/keymap.c) diff --git a/app/dts/behaviors/ext_power.dtsi b/app/dts/behaviors/ext_power.dtsi index 742e33aef8a..abb7f2ffe66 100644 --- a/app/dts/behaviors/ext_power.dtsi +++ b/app/dts/behaviors/ext_power.dtsi @@ -8,7 +8,7 @@ behaviors { /omit-if-no-ref/ ext_power: behavior_ext_power { compatible = "zmk,behavior-ext-power"; - label = "EXT_POWER_BEHAVIOR"; + label = "EXTPOWER"; #binding-cells = <1>; }; }; diff --git a/app/dts/behaviors/reset.dtsi b/app/dts/behaviors/reset.dtsi index c720bc88f2f..e4ba56588cf 100644 --- a/app/dts/behaviors/reset.dtsi +++ b/app/dts/behaviors/reset.dtsi @@ -16,7 +16,7 @@ /omit-if-no-ref/ bootloader: behavior_reset_dfu { compatible = "zmk,behavior-reset"; - label = "BOOTLOADER_RESET"; + label = "BOOTLOAD"; type = ; #binding-cells = <0>; }; diff --git a/app/dts/behaviors/rgb_underglow.dtsi b/app/dts/behaviors/rgb_underglow.dtsi index 60bdb3aaf31..265a1a52fe8 100644 --- a/app/dts/behaviors/rgb_underglow.dtsi +++ b/app/dts/behaviors/rgb_underglow.dtsi @@ -8,7 +8,7 @@ behaviors { /omit-if-no-ref/ rgb_ug: behavior_rgb_underglow { compatible = "zmk,behavior-rgb-underglow"; - label = "RGB_UNDERGLOW"; + label = "RGB_UG"; #binding-cells = <2>; }; }; diff --git a/app/include/drivers/behavior.h b/app/include/drivers/behavior.h index 2bdffea5876..fcb24f6fbbd 100644 --- a/app/include/drivers/behavior.h +++ b/app/include/drivers/behavior.h @@ -8,6 +8,8 @@ #include #include +#include +#include #include #include #include @@ -26,7 +28,14 @@ typedef int (*behavior_sensor_keymap_binding_callback_t)(struct zmk_behavior_bin const struct device *sensor, int64_t timestamp); +enum behavior_locality { + BEHAVIOR_LOCALITY_CENTRAL, + BEHAVIOR_LOCALITY_EVENT_SOURCE, + BEHAVIOR_LOCALITY_GLOBAL +}; + __subsystem struct behavior_driver_api { + enum behavior_locality locality; behavior_keymap_binding_callback_t binding_convert_central_state_dependent_params; behavior_keymap_binding_callback_t binding_pressed; behavior_keymap_binding_callback_t binding_released; @@ -60,6 +69,28 @@ static inline int z_impl_behavior_keymap_binding_convert_central_state_dependent return api->binding_convert_central_state_dependent_params(binding, event); } +/** + * @brief Determine where the behavior should be run + * @param behavior Pointer to the device structure for the driver instance. + * + * @retval Zero if successful. + * @retval Negative errno code if failure. + */ +__syscall int behavior_get_locality(const struct device *behavior, + enum behavior_locality *locality); + +static inline int z_impl_behavior_get_locality(const struct device *behavior, + enum behavior_locality *locality) { + if (behavior == NULL) { + return -EINVAL; + } + + const struct behavior_driver_api *api = (const struct behavior_driver_api *)behavior->api; + *locality = api->locality; + + return 0; +} + /** * @brief Handle the keymap binding being pressed * @param dev Pointer to the device structure for the driver instance. @@ -75,6 +106,11 @@ __syscall int behavior_keymap_binding_pressed(struct zmk_behavior_binding *bindi static inline int z_impl_behavior_keymap_binding_pressed(struct zmk_behavior_binding *binding, struct zmk_behavior_binding_event event) { const struct device *dev = device_get_binding(binding->behavior_dev); + + if (dev == NULL) { + return -EINVAL; + } + const struct behavior_driver_api *api = (const struct behavior_driver_api *)dev->api; if (api->binding_pressed == NULL) { @@ -98,6 +134,11 @@ __syscall int behavior_keymap_binding_released(struct zmk_behavior_binding *bind static inline int z_impl_behavior_keymap_binding_released(struct zmk_behavior_binding *binding, struct zmk_behavior_binding_event event) { const struct device *dev = device_get_binding(binding->behavior_dev); + + if (dev == NULL) { + return -EINVAL; + } + const struct behavior_driver_api *api = (const struct behavior_driver_api *)dev->api; if (api->binding_released == NULL) { @@ -125,6 +166,11 @@ static inline int z_impl_behavior_sensor_keymap_binding_triggered(struct zmk_behavior_binding *binding, const struct device *sensor, int64_t timestamp) { const struct device *dev = device_get_binding(binding->behavior_dev); + + if (dev == NULL) { + return -EINVAL; + } + const struct behavior_driver_api *api = (const struct behavior_driver_api *)dev->api; if (api->sensor_binding_triggered == NULL) { diff --git a/app/include/zmk/events/position_state_changed.h b/app/include/zmk/events/position_state_changed.h index e2f68720674..59619db6863 100644 --- a/app/include/zmk/events/position_state_changed.h +++ b/app/include/zmk/events/position_state_changed.h @@ -8,10 +8,19 @@ #include #include +#include + +#if IS_ENABLED(CONFIG_ZMK_BLE) +typedef const bt_addr_le_t *zmk_position_state_changed_source_t; +#else +typedef void *zmk_position_state_changed_source_t; +#endif + struct zmk_position_state_changed { + zmk_position_state_changed_source_t source; uint32_t position; bool state; int64_t timestamp; }; -ZMK_EVENT_DECLARE(zmk_position_state_changed); \ No newline at end of file +ZMK_EVENT_DECLARE(zmk_position_state_changed); diff --git a/app/include/zmk/keymap.h b/app/include/zmk/keymap.h index 7151930a3a5..0771542ce41 100644 --- a/app/include/zmk/keymap.h +++ b/app/include/zmk/keymap.h @@ -6,6 +6,8 @@ #pragma once +#include + typedef uint32_t zmk_keymap_layers_state_t; uint8_t zmk_keymap_layer_default(); @@ -18,7 +20,8 @@ int zmk_keymap_layer_toggle(uint8_t layer); int zmk_keymap_layer_to(uint8_t layer); const char *zmk_keymap_layer_label(uint8_t layer); -int zmk_keymap_position_state_changed(uint32_t position, bool pressed, int64_t timestamp); +int zmk_keymap_position_state_changed(zmk_position_state_changed_source_t source, uint32_t position, + bool pressed, int64_t timestamp); #define ZMK_KEYMAP_EXTRACT_BINDING(idx, drv_inst) \ { \ diff --git a/app/include/zmk/split/bluetooth/central.h b/app/include/zmk/split/bluetooth/central.h new file mode 100644 index 00000000000..ab46a8f5627 --- /dev/null +++ b/app/include/zmk/split/bluetooth/central.h @@ -0,0 +1,8 @@ + +#pragma once + +#include +#include + +int zmk_split_bt_invoke_behavior(const bt_addr_le_t *source, struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event, bool state); \ No newline at end of file diff --git a/app/include/zmk/split/bluetooth/service.h b/app/include/zmk/split/bluetooth/service.h index b9f9fc3e0b6..f0c1d79ff7d 100644 --- a/app/include/zmk/split/bluetooth/service.h +++ b/app/include/zmk/split/bluetooth/service.h @@ -6,5 +6,19 @@ #pragma once +#define ZMK_SPLIT_RUN_BEHAVIOR_DEV_LEN 9 + +struct zmk_split_run_behavior_data { + uint8_t position; + uint8_t state; + uint32_t param1; + uint32_t param2; +} __packed; + +struct zmk_split_run_behavior_payload { + struct zmk_split_run_behavior_data data; + char behavior_dev[ZMK_SPLIT_RUN_BEHAVIOR_DEV_LEN]; +} __packed; + int zmk_split_bt_position_pressed(uint8_t position); int zmk_split_bt_position_released(uint8_t position); \ No newline at end of file diff --git a/app/include/zmk/split/bluetooth/uuid.h b/app/include/zmk/split/bluetooth/uuid.h index a31884d9ebd..735f5751d0a 100644 --- a/app/include/zmk/split/bluetooth/uuid.h +++ b/app/include/zmk/split/bluetooth/uuid.h @@ -15,3 +15,4 @@ #define ZMK_BT_SPLIT_UUID(num) BT_UUID_128_ENCODE(num, 0x0096, 0x7107, 0xc967, 0xc5cfb1c2482a) #define ZMK_SPLIT_BT_SERVICE_UUID ZMK_BT_SPLIT_UUID(0x00000000) #define ZMK_SPLIT_BT_CHAR_POSITION_STATE_UUID ZMK_BT_SPLIT_UUID(0x00000001) +#define ZMK_SPLIT_BT_CHAR_RUN_BEHAVIOR_UUID ZMK_BT_SPLIT_UUID(0x00000002) diff --git a/app/src/behaviors/behavior_ext_power.c b/app/src/behaviors/behavior_ext_power.c index fdd890c9b28..27793318a40 100644 --- a/app/src/behaviors/behavior_ext_power.c +++ b/app/src/behaviors/behavior_ext_power.c @@ -71,6 +71,7 @@ static const struct behavior_driver_api behavior_ext_power_driver_api = { on_keymap_binding_convert_central_state_dependent_params, .binding_pressed = on_keymap_binding_pressed, .binding_released = on_keymap_binding_released, + .locality = BEHAVIOR_LOCALITY_GLOBAL, }; DEVICE_DT_INST_DEFINE(0, behavior_ext_power_init, device_pm_control_nop, NULL, NULL, APPLICATION, diff --git a/app/src/behaviors/behavior_reset.c b/app/src/behaviors/behavior_reset.c index e19cf329340..eb0477db542 100644 --- a/app/src/behaviors/behavior_reset.c +++ b/app/src/behaviors/behavior_reset.c @@ -36,6 +36,7 @@ static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding, static const struct behavior_driver_api behavior_reset_driver_api = { .binding_pressed = on_keymap_binding_pressed, + .locality = BEHAVIOR_LOCALITY_EVENT_SOURCE, }; #define RST_INST(n) \ diff --git a/app/src/behaviors/behavior_rgb_underglow.c b/app/src/behaviors/behavior_rgb_underglow.c index 0243b54b79a..33af6556bee 100644 --- a/app/src/behaviors/behavior_rgb_underglow.c +++ b/app/src/behaviors/behavior_rgb_underglow.c @@ -134,6 +134,7 @@ static const struct behavior_driver_api behavior_rgb_underglow_driver_api = { on_keymap_binding_convert_central_state_dependent_params, .binding_pressed = on_keymap_binding_pressed, .binding_released = on_keymap_binding_released, + .locality = BEHAVIOR_LOCALITY_GLOBAL, }; DEVICE_DT_INST_DEFINE(0, behavior_rgb_underglow_init, device_pm_control_nop, NULL, NULL, diff --git a/app/src/keymap.c b/app/src/keymap.c index 1643f6474ce..16ec31692f6 100644 --- a/app/src/keymap.c +++ b/app/src/keymap.c @@ -4,7 +4,12 @@ * SPDX-License-Identifier: MIT */ +#define IS_BLE_CENTRAL \ + (IS_ENABLED(CONFIG_ZMK_SPLIT) && IS_ENABLED(CONFIG_ZMK_BLE) && \ + IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL)) + #include +#include #include LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); @@ -14,6 +19,10 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #include #include +#if IS_BLE_CENTRAL +#include +#endif + #include #include #include @@ -152,7 +161,17 @@ const char *zmk_keymap_layer_label(uint8_t layer) { return zmk_keymap_layer_names[layer]; } -int zmk_keymap_apply_position_state(int layer, uint32_t position, bool pressed, int64_t timestamp) { +int invoke_locally(struct zmk_behavior_binding *binding, struct zmk_behavior_binding_event event, + bool pressed) { + if (pressed) { + return behavior_keymap_binding_pressed(binding, event); + } else { + return behavior_keymap_binding_released(binding, event); + } +} + +int zmk_keymap_apply_position_state(zmk_position_state_changed_source_t source, int layer, + uint32_t position, bool pressed, int64_t timestamp) { // We want to make a copy of this, since it may be converted from // relative to absolute before being invoked struct zmk_behavior_binding binding = zmk_keymap[layer][position]; @@ -169,7 +188,7 @@ int zmk_keymap_apply_position_state(int layer, uint32_t position, bool pressed, behavior = device_get_binding(binding.behavior_dev); if (!behavior) { - LOG_DBG("No behavior assigned to %d on layer %d", position, layer); + LOG_WRN("No behavior assigned to %d on layer %d", position, layer); return 1; } @@ -179,20 +198,44 @@ int zmk_keymap_apply_position_state(int layer, uint32_t position, bool pressed, return err; } - if (pressed) { - return behavior_keymap_binding_pressed(&binding, event); - } else { - return behavior_keymap_binding_released(&binding, event); + enum behavior_locality locality = BEHAVIOR_LOCALITY_CENTRAL; + err = behavior_get_locality(behavior, &locality); + if (err) { + LOG_ERR("Failed to get behavior locality %d", err); + return err; + } + + switch (locality) { + case BEHAVIOR_LOCALITY_CENTRAL: + return invoke_locally(&binding, event, pressed); + case BEHAVIOR_LOCALITY_EVENT_SOURCE: +#if IS_BLE_CENTRAL + if (!bt_addr_le_cmp(source, BT_ADDR_LE_NONE)) { + return invoke_locally(&binding, event, pressed); + } else { + return zmk_split_bt_invoke_behavior(source, &binding, event, pressed); + } +#else + return invoke_locally(&binding, event, pressed); +#endif + case BEHAVIOR_LOCALITY_GLOBAL: +#if IS_BLE_CENTRAL + zmk_split_bt_invoke_behavior(BT_ADDR_LE_ANY, &binding, event, pressed); +#endif + return invoke_locally(&binding, event, pressed); } + + return -ENOTSUP; } -int zmk_keymap_position_state_changed(uint32_t position, bool pressed, int64_t timestamp) { +int zmk_keymap_position_state_changed(zmk_position_state_changed_source_t source, uint32_t position, + bool pressed, int64_t timestamp) { if (pressed) { zmk_keymap_active_behavior_layer[position] = _zmk_keymap_layer_state; } for (int layer = ZMK_KEYMAP_LAYERS_LEN - 1; layer >= _zmk_keymap_layer_default; layer--) { if (zmk_keymap_layer_active_with_state(layer, zmk_keymap_active_behavior_layer[position])) { - int ret = zmk_keymap_apply_position_state(layer, position, pressed, timestamp); + int ret = zmk_keymap_apply_position_state(source, layer, position, pressed, timestamp); if (ret > 0) { LOG_DBG("behavior processing to continue to next layer"); continue; @@ -249,7 +292,7 @@ int zmk_keymap_sensor_triggered(uint8_t sensor_number, const struct device *sens int keymap_listener(const zmk_event_t *eh) { const struct zmk_position_state_changed *pos_ev; if ((pos_ev = as_zmk_position_state_changed(eh)) != NULL) { - return zmk_keymap_position_state_changed(pos_ev->position, pos_ev->state, + return zmk_keymap_position_state_changed(pos_ev->source, pos_ev->position, pos_ev->state, pos_ev->timestamp); } diff --git a/app/src/kscan.c b/app/src/kscan.c index 3c9c201e6b0..13ad2ccb256 100644 --- a/app/src/kscan.c +++ b/app/src/kscan.c @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -49,8 +50,12 @@ void zmk_kscan_process_msgq(struct k_work *item) { uint32_t position = zmk_matrix_transform_row_column_to_position(ev.row, ev.column); LOG_DBG("Row: %d, col: %d, position: %d, pressed: %s", ev.row, ev.column, position, (pressed ? "true" : "false")); - ZMK_EVENT_RAISE(new_zmk_position_state_changed((struct zmk_position_state_changed){ - .state = pressed, .position = position, .timestamp = k_uptime_get()})); + ZMK_EVENT_RAISE(new_zmk_position_state_changed((struct zmk_position_state_changed) { +#if IS_ENABLED(CONFIG_ZMK_BLE) + .source = BT_ADDR_LE_NONE, +#endif + .state = pressed, .position = position, .timestamp = k_uptime_get() + })); } } diff --git a/app/src/split/bluetooth/central.c b/app/src/split/bluetooth/central.c index 9a7f01b2b16..5ac4f83c45e 100644 --- a/app/src/split/bluetooth/central.c +++ b/app/src/split/bluetooth/central.c @@ -18,7 +18,9 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #include +#include #include +#include #include #include #include @@ -33,6 +35,7 @@ static const struct bt_uuid_128 split_service_uuid = BT_UUID_INIT_128(ZMK_SPLIT_ static struct bt_gatt_discover_params discover_params; static struct bt_gatt_subscribe_params subscribe_params; static struct bt_gatt_discover_params sub_discover_params; +static uint16_t run_behavior_handle; K_MSGQ_DEFINE(peripheral_event_msgq, sizeof(struct zmk_position_state_changed), CONFIG_ZMK_SPLIT_BLE_CENTRAL_POSITION_QUEUE_SIZE, 4); @@ -72,8 +75,10 @@ static uint8_t split_central_notify_func(struct bt_conn *conn, if (changed_positions[i] & BIT(j)) { uint32_t position = (i * 8) + j; bool pressed = position_state[i] & BIT(j); - struct zmk_position_state_changed ev = { - .position = position, .state = pressed, .timestamp = k_uptime_get()}; + struct zmk_position_state_changed ev = {.source = bt_conn_get_dst(conn), + .position = position, + .state = pressed, + .timestamp = k_uptime_get()}; k_msgq_put(&peripheral_event_msgq, &ev, K_NO_WAIT); k_work_submit(&peripheral_event_work); @@ -124,9 +129,28 @@ static uint8_t split_central_chrc_discovery_func(struct bt_conn *conn, subscribe_params.disc_params = &sub_discover_params; subscribe_params.end_handle = discover_params.end_handle; subscribe_params.value_handle = bt_gatt_attr_value_handle(attr); + + err = bt_gatt_discover(conn, &discover_params); + if (err) { + LOG_ERR("Discover failed (err %d)", err); + } + } else if (!bt_uuid_cmp(discover_params.uuid, + BT_UUID_DECLARE_128(ZMK_SPLIT_BT_CHAR_RUN_BEHAVIOR_UUID))) { + run_behavior_handle = bt_gatt_attr_value_handle(attr); + } else { subscribe_params.notify = split_central_notify_func; subscribe_params.value = BT_GATT_CCC_NOTIFY; split_central_subscribe(conn); + + memcpy(&uuid, BT_UUID_DECLARE_128(ZMK_SPLIT_BT_CHAR_RUN_BEHAVIOR_UUID), sizeof(uuid)); + discover_params.uuid = &uuid.uuid; + discover_params.start_handle = attr->handle + 1; + discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC; + + err = bt_gatt_discover(conn, &discover_params); + if (err) { + LOG_ERR("Discover failed (err %d)", err); + } } return subscribe_params.value_handle ? BT_GATT_ITER_STOP : BT_GATT_ITER_CONTINUE; @@ -340,6 +364,27 @@ static struct bt_conn_cb conn_callbacks = { .disconnected = split_central_disconnected, }; +int zmk_split_bt_invoke_behavior(const bt_addr_le_t *source, struct zmk_behavior_binding *binding, + struct zmk_behavior_binding_event event, bool state) { + struct zmk_split_run_behavior_payload payload = {.data = { + .param1 = binding->param1, + .param2 = binding->param2, + .position = event.position, + .state = state, + }}; + strncpy(payload.behavior_dev, binding->behavior_dev, ZMK_SPLIT_RUN_BEHAVIOR_DEV_LEN - 1); + payload.behavior_dev[ZMK_SPLIT_RUN_BEHAVIOR_DEV_LEN - 1] = '\0'; + + int err = bt_gatt_write_without_response(default_conn, run_behavior_handle, &payload, + sizeof(struct zmk_split_run_behavior_payload), true); + + if (err) { + LOG_ERR("Failed to write the behavior characteristic (err %d)", err); + } + + return err; +}; + int zmk_split_bt_central_init(const struct device *_arg) { bt_conn_cb_register(&conn_callbacks); diff --git a/app/src/split/bluetooth/service.c b/app/src/split/bluetooth/service.c index fbac6446446..bb0202c3a5d 100644 --- a/app/src/split/bluetooth/service.c +++ b/app/src/split/bluetooth/service.c @@ -15,6 +15,8 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #include #include +#include +#include #include #include #include @@ -24,12 +26,57 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); static uint8_t num_of_positions = ZMK_KEYMAP_LEN; static uint8_t position_state[POS_STATE_LEN]; +static struct zmk_split_run_behavior_payload behavior_run_payload; + static ssize_t split_svc_pos_state(struct bt_conn *conn, const struct bt_gatt_attr *attrs, void *buf, uint16_t len, uint16_t offset) { return bt_gatt_attr_read(conn, attrs, buf, len, offset, &position_state, sizeof(position_state)); } +static ssize_t split_svc_run_behavior(struct bt_conn *conn, const struct bt_gatt_attr *attrs, + const void *buf, uint16_t len, uint16_t offset, + uint8_t flags) { + struct zmk_split_run_behavior_payload *payload = attrs->user_data; + uint16_t end_addr = offset + len; + + LOG_DBG("offset %d len %d", offset, len); + + if (end_addr > sizeof(struct zmk_split_run_behavior_payload)) { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + memcpy(payload + offset, buf, len); + + // We run if: + // 1: We've gotten all the position/state/param data. + // 2: We have a null terminated string for the behavior device label. + if ((end_addr > sizeof(struct zmk_split_run_behavior_data)) && + payload->behavior_dev[end_addr - sizeof(struct zmk_split_run_behavior_data)] == '\0') { + struct zmk_behavior_binding binding = { + .param1 = payload->data.param1, + .param2 = payload->data.param2, + .behavior_dev = payload->behavior_dev, + }; + LOG_DBG("INVOKE THE BEHAVIOR: %s with params %d %d", log_strdup(binding.behavior_dev), + binding.param1, binding.param2); + struct zmk_behavior_binding_event event = {.position = payload->data.position, + .timestamp = k_uptime_get()}; + int err; + if (payload->data.state > 0) { + err = behavior_keymap_binding_pressed(&binding, event); + } else { + err = behavior_keymap_binding_released(&binding, event); + } + + if (err) { + LOG_ERR("Failed to invoke behavior %s: %d", log_strdup(binding.behavior_dev), err); + } + } + + return len; +} + static ssize_t split_svc_num_of_positions(struct bt_conn *conn, const struct bt_gatt_attr *attrs, void *buf, uint16_t len, uint16_t offset) { return bt_gatt_attr_read(conn, attrs, buf, len, offset, attrs->user_data, sizeof(uint8_t)); @@ -45,6 +92,9 @@ BT_GATT_SERVICE_DEFINE( BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, BT_GATT_PERM_READ_ENCRYPT, split_svc_pos_state, NULL, &position_state), BT_GATT_CCC(split_svc_pos_state_ccc, BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT), + BT_GATT_CHARACTERISTIC(BT_UUID_DECLARE_128(ZMK_SPLIT_BT_CHAR_RUN_BEHAVIOR_UUID), + BT_GATT_CHRC_WRITE_WITHOUT_RESP, BT_GATT_PERM_WRITE_ENCRYPT, NULL, + split_svc_run_behavior, &behavior_run_payload), BT_GATT_DESCRIPTOR(BT_UUID_NUM_OF_DIGITALS, BT_GATT_PERM_READ, split_svc_num_of_positions, NULL, &num_of_positions), ); From 3af83b8c2d0e56c38ffbc9ab15ce438372efa2ce Mon Sep 17 00:00:00 2001 From: Pete Johanson Date: Tue, 12 Jan 2021 10:49:00 -0500 Subject: [PATCH 02/11] refactor(split): Clean up split GATT discovery. * Use Zephyr auto CCC discovery instead of doing it ourselves. * Split service versus characteristic discovery into dedicated steps in the flow. * Fix for not searching properly when connecting to a peripheral a second time. --- app/src/split/bluetooth/central.c | 28 ++++++++-------------------- app/src/split_listener.c | 2 -- 2 files changed, 8 insertions(+), 22 deletions(-) diff --git a/app/src/split/bluetooth/central.c b/app/src/split/bluetooth/central.c index 5ac4f83c45e..d123f4ec8db 100644 --- a/app/src/split/bluetooth/central.c +++ b/app/src/split/bluetooth/central.c @@ -129,31 +129,18 @@ static uint8_t split_central_chrc_discovery_func(struct bt_conn *conn, subscribe_params.disc_params = &sub_discover_params; subscribe_params.end_handle = discover_params.end_handle; subscribe_params.value_handle = bt_gatt_attr_value_handle(attr); - - err = bt_gatt_discover(conn, &discover_params); - if (err) { - LOG_ERR("Discover failed (err %d)", err); - } - } else if (!bt_uuid_cmp(discover_params.uuid, - BT_UUID_DECLARE_128(ZMK_SPLIT_BT_CHAR_RUN_BEHAVIOR_UUID))) { - run_behavior_handle = bt_gatt_attr_value_handle(attr); - } else { subscribe_params.notify = split_central_notify_func; subscribe_params.value = BT_GATT_CCC_NOTIFY; split_central_subscribe(conn); - - memcpy(&uuid, BT_UUID_DECLARE_128(ZMK_SPLIT_BT_CHAR_RUN_BEHAVIOR_UUID), sizeof(uuid)); - discover_params.uuid = &uuid.uuid; - discover_params.start_handle = attr->handle + 1; - discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC; - - err = bt_gatt_discover(conn, &discover_params); - if (err) { - LOG_ERR("Discover failed (err %d)", err); - } + } else if (!bt_uuid_cmp(((struct bt_gatt_chrc *)attr->user_data)->uuid, + BT_UUID_DECLARE_128(ZMK_SPLIT_BT_CHAR_RUN_BEHAVIOR_UUID))) { + LOG_DBG("Found run behavior handle"); + run_behavior_handle = bt_gatt_attr_value_handle(attr); } - return subscribe_params.value_handle ? BT_GATT_ITER_STOP : BT_GATT_ITER_CONTINUE; + bool subscribed = (run_behavior_handle && subscribe_params.value_handle); + + return subscribed ? BT_GATT_ITER_STOP : BT_GATT_ITER_CONTINUE; } static uint8_t split_central_service_discovery_func(struct bt_conn *conn, @@ -355,6 +342,7 @@ static void split_central_disconnected(struct bt_conn *conn, uint8_t reason) { // Clean up previously discovered handles; subscribe_params.value_handle = 0; + run_behavior_handle = 0; start_scan(); } diff --git a/app/src/split_listener.c b/app/src/split_listener.c index 8ac56c9f20d..01cd89d912f 100644 --- a/app/src/split_listener.c +++ b/app/src/split_listener.c @@ -4,8 +4,6 @@ * SPDX-License-Identifier: MIT */ -#define DT_DRV_COMPAT zmk_split_listener - #include #include #include From 35094cc75a222e7cbb4fc0f701be6a2f88ecb5b9 Mon Sep 17 00:00:00 2001 From: Peter Johanson Date: Tue, 9 Nov 2021 05:04:54 +0000 Subject: [PATCH 03/11] fix(underglow): Handle cycling effects on splits. * Convert relative effect cycling to absolute effect selection. --- app/include/dt-bindings/zmk/rgb.h | 3 ++- app/include/zmk/rgb_underglow.h | 2 ++ app/src/behaviors/behavior_rgb_underglow.c | 12 ++++++++++++ app/src/rgb_underglow.c | 16 +++++++++++++--- 4 files changed, 29 insertions(+), 4 deletions(-) diff --git a/app/include/dt-bindings/zmk/rgb.h b/app/include/dt-bindings/zmk/rgb.h index 95aa5029198..c1a8008273c 100644 --- a/app/include/dt-bindings/zmk/rgb.h +++ b/app/include/dt-bindings/zmk/rgb.h @@ -17,7 +17,8 @@ #define RGB_SPD_CMD 10 #define RGB_EFF_CMD 11 #define RGB_EFR_CMD 12 -#define RGB_COLOR_HSB_CMD 13 +#define RGB_EFS_CMD 13 +#define RGB_COLOR_HSB_CMD 14 #define RGB_TOG RGB_TOG_CMD 0 #define RGB_ON RGB_ON_CMD 0 diff --git a/app/include/zmk/rgb_underglow.h b/app/include/zmk/rgb_underglow.h index 4d452a6f8f0..797f0b19f1d 100644 --- a/app/include/zmk/rgb_underglow.h +++ b/app/include/zmk/rgb_underglow.h @@ -17,6 +17,8 @@ int zmk_rgb_underglow_get_state(bool *state); int zmk_rgb_underglow_on(); int zmk_rgb_underglow_off(); int zmk_rgb_underglow_cycle_effect(int direction); +int zmk_rgb_underglow_calc_effect(int direction); +int zmk_rgb_underglow_select_effect(int effect); struct zmk_led_hsb zmk_rgb_underglow_calc_hue(int direction); struct zmk_led_hsb zmk_rgb_underglow_calc_sat(int direction); struct zmk_led_hsb zmk_rgb_underglow_calc_brt(int direction); diff --git a/app/src/behaviors/behavior_rgb_underglow.c b/app/src/behaviors/behavior_rgb_underglow.c index 33af6556bee..96f690485f6 100644 --- a/app/src/behaviors/behavior_rgb_underglow.c +++ b/app/src/behaviors/behavior_rgb_underglow.c @@ -77,6 +77,16 @@ on_keymap_binding_convert_central_state_dependent_params(struct zmk_behavior_bin binding->param2 = RGB_COLOR_HSB_VAL(color.h, color.s, color.b); break; } + case RGB_EFR_CMD: { + binding->param1 = RGB_EFS_CMD; + binding->param2 = zmk_rgb_underglow_calc_effect(-1); + break; + } + case RGB_EFF_CMD: { + binding->param1 = RGB_EFS_CMD; + binding->param2 = zmk_rgb_underglow_calc_effect(1); + break; + } default: return 0; } @@ -111,6 +121,8 @@ static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding, return zmk_rgb_underglow_change_spd(1); case RGB_SPD_CMD: return zmk_rgb_underglow_change_spd(-1); + case RGB_EFS_CMD: + return zmk_rgb_underglow_select_effect(binding->param2); case RGB_EFF_CMD: return zmk_rgb_underglow_cycle_effect(1); case RGB_EFR_CMD: diff --git a/app/src/rgb_underglow.c b/app/src/rgb_underglow.c index 40d99c7d15d..427552fac17 100644 --- a/app/src/rgb_underglow.c +++ b/app/src/rgb_underglow.c @@ -332,18 +332,28 @@ int zmk_rgb_underglow_off() { return zmk_rgb_underglow_save_state(); } -int zmk_rgb_underglow_cycle_effect(int direction) { +int zmk_rgb_underglow_calc_effect(int direction) { + return (state.current_effect + UNDERGLOW_EFFECT_NUMBER + direction) % UNDERGLOW_EFFECT_NUMBER; +} + +int zmk_rgb_underglow_select_effect(int effect) { if (!led_strip) return -ENODEV; - state.current_effect += UNDERGLOW_EFFECT_NUMBER + direction; - state.current_effect %= UNDERGLOW_EFFECT_NUMBER; + if (effect < 0 || effect >= UNDERGLOW_EFFECT_NUMBER) { + return -EINVAL; + } + state.current_effect = effect; state.animation_step = 0; return zmk_rgb_underglow_save_state(); } +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() { return state.on ? zmk_rgb_underglow_off() : zmk_rgb_underglow_on(); } From 2c305ce6441decc97d5b46d7e838c79ae7a77056 Mon Sep 17 00:00:00 2001 From: Peter Johanson Date: Sat, 20 Nov 2021 22:12:24 +0000 Subject: [PATCH 04/11] fix(split): Add queue for running remote behaviors --- app/Kconfig | 8 +++++ app/src/split/bluetooth/central.c | 59 ++++++++++++++++++++++++++----- 2 files changed, 58 insertions(+), 9 deletions(-) diff --git a/app/Kconfig b/app/Kconfig index 3502c652757..76035147ade 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -175,6 +175,14 @@ config ZMK_SPLIT_BLE_CENTRAL_POSITION_QUEUE_SIZE int "Max number of key position state events to queue when received from peripherals" default 5 +config ZMK_BLE_SPLIT_CENTRAL_SPLIT_RUN_STACK_SIZE + int "BLE split central write thread stack size" + default 512 + +config ZMK_BLE_SPLIT_CENTRAL_SPLIT_RUN_QUEUE_SIZE + int "Max number of behavior run events to queue to send to the peripheral(s)" + default 5 + endif if !ZMK_SPLIT_BLE_ROLE_CENTRAL diff --git a/app/src/split/bluetooth/central.c b/app/src/split/bluetooth/central.c index d123f4ec8db..c28941f16a6 100644 --- a/app/src/split/bluetooth/central.c +++ b/app/src/split/bluetooth/central.c @@ -352,6 +352,51 @@ static struct bt_conn_cb conn_callbacks = { .disconnected = split_central_disconnected, }; +K_THREAD_STACK_DEFINE(split_central_split_run_q_stack, + CONFIG_ZMK_BLE_SPLIT_CENTRAL_SPLIT_RUN_STACK_SIZE); + +struct k_work_q split_central_split_run_q; + +K_MSGQ_DEFINE(zmk_split_central_split_run_msgq, sizeof(struct zmk_split_run_behavior_payload), + CONFIG_ZMK_BLE_SPLIT_CENTRAL_SPLIT_RUN_QUEUE_SIZE, 4); + +void split_central_split_run_callback(struct k_work *work) { + struct zmk_split_run_behavior_payload payload; + + while (k_msgq_get(&zmk_split_central_split_run_msgq, &payload, K_NO_WAIT) == 0) { + int err = + bt_gatt_write_without_response(default_conn, run_behavior_handle, &payload, + sizeof(struct zmk_split_run_behavior_payload), true); + + if (err) { + LOG_ERR("Failed to write the behavior characteristic (err %d)", err); + } + } +} + +K_WORK_DEFINE(split_central_split_run_work, split_central_split_run_callback); + +static int split_bt_invoke_behavior_payload(struct zmk_split_run_behavior_payload payload) { + int err = k_msgq_put(&zmk_split_central_split_run_msgq, &payload, K_MSEC(100)); + if (err) { + switch (err) { + case -EAGAIN: { + LOG_WRN("Consumer message queue full, popping first message and queueing again"); + struct zmk_split_run_behavior_payload discarded_report; + k_msgq_get(&zmk_split_central_split_run_msgq, &discarded_report, K_NO_WAIT); + return split_bt_invoke_behavior_payload(payload); + } + default: + LOG_WRN("Failed to queue behavior to send (%d)", err); + return err; + } + } + + k_work_submit_to_queue(&split_central_split_run_q, &split_central_split_run_work); + + return 0; +}; + int zmk_split_bt_invoke_behavior(const bt_addr_le_t *source, struct zmk_behavior_binding *binding, struct zmk_behavior_binding_event event, bool state) { struct zmk_split_run_behavior_payload payload = {.data = { @@ -363,17 +408,13 @@ int zmk_split_bt_invoke_behavior(const bt_addr_le_t *source, struct zmk_behavior strncpy(payload.behavior_dev, binding->behavior_dev, ZMK_SPLIT_RUN_BEHAVIOR_DEV_LEN - 1); payload.behavior_dev[ZMK_SPLIT_RUN_BEHAVIOR_DEV_LEN - 1] = '\0'; - int err = bt_gatt_write_without_response(default_conn, run_behavior_handle, &payload, - sizeof(struct zmk_split_run_behavior_payload), true); - - if (err) { - LOG_ERR("Failed to write the behavior characteristic (err %d)", err); - } - - return err; -}; + return split_bt_invoke_behavior_payload(payload); +} int zmk_split_bt_central_init(const struct device *_arg) { + k_work_q_start(&split_central_split_run_q, split_central_split_run_q_stack, + K_THREAD_STACK_SIZEOF(split_central_split_run_q_stack), + CONFIG_ZMK_BLE_THREAD_PRIORITY); bt_conn_cb_register(&conn_callbacks); return start_scan(); From c41296b70515d4301f1b8a4a771ed40687f4e70a Mon Sep 17 00:00:00 2001 From: Peter Johanson Date: Mon, 22 Nov 2021 06:02:05 +0000 Subject: [PATCH 05/11] fix: Ensure power and underglow behaviors built. * Remove `/omit-if-no-ref/` from the behavior nodes. --- app/dts/behaviors/ext_power.dtsi | 2 +- app/dts/behaviors/rgb_underglow.dtsi | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/dts/behaviors/ext_power.dtsi b/app/dts/behaviors/ext_power.dtsi index abb7f2ffe66..18e824e21fa 100644 --- a/app/dts/behaviors/ext_power.dtsi +++ b/app/dts/behaviors/ext_power.dtsi @@ -6,7 +6,7 @@ / { behaviors { - /omit-if-no-ref/ ext_power: behavior_ext_power { + ext_power: behavior_ext_power { compatible = "zmk,behavior-ext-power"; label = "EXTPOWER"; #binding-cells = <1>; diff --git a/app/dts/behaviors/rgb_underglow.dtsi b/app/dts/behaviors/rgb_underglow.dtsi index 265a1a52fe8..54fe422e2ba 100644 --- a/app/dts/behaviors/rgb_underglow.dtsi +++ b/app/dts/behaviors/rgb_underglow.dtsi @@ -6,7 +6,7 @@ / { behaviors { - /omit-if-no-ref/ rgb_ug: behavior_rgb_underglow { + rgb_ug: behavior_rgb_underglow { compatible = "zmk,behavior-rgb-underglow"; label = "RGB_UG"; #binding-cells = <2>; From fa57fc74eaf8930df7008cbea741f40e4bc81c8b Mon Sep 17 00:00:00 2001 From: Peter Johanson Date: Sun, 2 Jan 2022 04:55:30 +0000 Subject: [PATCH 06/11] refactor(splits): Use index for event source. * Track peripherals by indexes slot, with all appropiate peripheral state stored in the slot. * Event sources tracked by peripheral slot index. --- app/include/zmk/ble.h | 11 + .../zmk/events/position_state_changed.h | 9 +- app/include/zmk/keymap.h | 4 +- app/include/zmk/split/bluetooth/central.h | 2 +- app/src/ble.c | 16 +- app/src/keymap.c | 25 +- app/src/kscan.c | 11 +- app/src/split/bluetooth/central.c | 279 +++++++++++++----- 8 files changed, 253 insertions(+), 104 deletions(-) diff --git a/app/include/zmk/ble.h b/app/include/zmk/ble.h index 0bdbab3838d..f813ddc80a5 100644 --- a/app/include/zmk/ble.h +++ b/app/include/zmk/ble.h @@ -9,6 +9,17 @@ #include #include +#define ZMK_BLE_IS_CENTRAL \ + (IS_ENABLED(CONFIG_ZMK_SPLIT) && IS_ENABLED(CONFIG_ZMK_BLE) && \ + IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL)) + +#if ZMK_BLE_IS_CENTRAL +#define ZMK_BLE_PROFILE_COUNT (CONFIG_BT_MAX_PAIRED - 1) +#define ZMK_BLE_SPLIT_PERIPHERAL_COUNT 1 +#else +#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(); diff --git a/app/include/zmk/events/position_state_changed.h b/app/include/zmk/events/position_state_changed.h index 59619db6863..7685fe0fd37 100644 --- a/app/include/zmk/events/position_state_changed.h +++ b/app/include/zmk/events/position_state_changed.h @@ -8,16 +8,9 @@ #include #include -#include - -#if IS_ENABLED(CONFIG_ZMK_BLE) -typedef const bt_addr_le_t *zmk_position_state_changed_source_t; -#else -typedef void *zmk_position_state_changed_source_t; -#endif struct zmk_position_state_changed { - zmk_position_state_changed_source_t source; + uint8_t source; uint32_t position; bool state; int64_t timestamp; diff --git a/app/include/zmk/keymap.h b/app/include/zmk/keymap.h index 0771542ce41..1195b94320e 100644 --- a/app/include/zmk/keymap.h +++ b/app/include/zmk/keymap.h @@ -20,8 +20,8 @@ int zmk_keymap_layer_toggle(uint8_t layer); int zmk_keymap_layer_to(uint8_t layer); const char *zmk_keymap_layer_label(uint8_t layer); -int zmk_keymap_position_state_changed(zmk_position_state_changed_source_t source, uint32_t position, - bool pressed, int64_t timestamp); +int zmk_keymap_position_state_changed(uint8_t source, uint32_t position, bool pressed, + int64_t timestamp); #define ZMK_KEYMAP_EXTRACT_BINDING(idx, drv_inst) \ { \ diff --git a/app/include/zmk/split/bluetooth/central.h b/app/include/zmk/split/bluetooth/central.h index ab46a8f5627..072408605cf 100644 --- a/app/include/zmk/split/bluetooth/central.h +++ b/app/include/zmk/split/bluetooth/central.h @@ -4,5 +4,5 @@ #include #include -int zmk_split_bt_invoke_behavior(const bt_addr_le_t *source, struct zmk_behavior_binding *binding, +int zmk_split_bt_invoke_behavior(uint8_t source, struct zmk_behavior_binding *binding, struct zmk_behavior_binding_event event, bool state); \ No newline at end of file diff --git a/app/src/ble.c b/app/src/ble.c index 68061129a0d..afc2e47f961 100644 --- a/app/src/ble.c +++ b/app/src/ble.c @@ -72,7 +72,7 @@ enum advertising_type { BT_LE_ADV_PARAM(BT_LE_ADV_OPT_CONNECTABLE | BT_LE_ADV_OPT_ONE_TIME, BT_GAP_ADV_FAST_INT_MIN_2, \ BT_GAP_ADV_FAST_INT_MAX_2, NULL) -static struct zmk_ble_profile profiles[PROFILE_COUNT]; +static struct zmk_ble_profile profiles[ZMK_BLE_PROFILE_COUNT]; static uint8_t active_profile; #define DEVICE_NAME CONFIG_BT_DEVICE_NAME @@ -260,7 +260,7 @@ static int ble_save_profile() { } int zmk_ble_prof_select(uint8_t index) { - if (index >= PROFILE_COUNT) { + if (index >= ZMK_BLE_PROFILE_COUNT) { return -ERANGE; } @@ -281,12 +281,13 @@ int zmk_ble_prof_select(uint8_t index) { int zmk_ble_prof_next() { LOG_DBG(""); - return zmk_ble_prof_select((active_profile + 1) % PROFILE_COUNT); + return zmk_ble_prof_select((active_profile + 1) % ZMK_BLE_PROFILE_COUNT); }; int zmk_ble_prof_prev() { LOG_DBG(""); - return zmk_ble_prof_select((active_profile + PROFILE_COUNT - 1) % PROFILE_COUNT); + return zmk_ble_prof_select((active_profile + ZMK_BLE_PROFILE_COUNT - 1) % + ZMK_BLE_PROFILE_COUNT); }; bt_addr_le_t *zmk_ble_active_profile_addr() { return &profiles[active_profile].peer; } @@ -324,8 +325,9 @@ static int ble_profiles_handle_set(const char *name, size_t len, settings_read_c return -EINVAL; } - if (idx >= PROFILE_COUNT) { - LOG_WRN("Profile address for index %d is larger than max of %d", idx, PROFILE_COUNT); + if (idx >= ZMK_BLE_PROFILE_COUNT) { + LOG_WRN("Profile address for index %d is larger than max of %d", idx, + ZMK_BLE_PROFILE_COUNT); return -EINVAL; } @@ -591,7 +593,7 @@ static int zmk_ble_init(const struct device *_arg) { bt_unpair(i, NULL); } - for (int i = 0; i < PROFILE_COUNT; i++) { + for (int i = 0; i < ZMK_BLE_PROFILE_COUNT; i++) { char setting_name[15]; sprintf(setting_name, "ble/profiles/%d", i); diff --git a/app/src/keymap.c b/app/src/keymap.c index 16ec31692f6..8acc5ec1d58 100644 --- a/app/src/keymap.c +++ b/app/src/keymap.c @@ -4,10 +4,6 @@ * SPDX-License-Identifier: MIT */ -#define IS_BLE_CENTRAL \ - (IS_ENABLED(CONFIG_ZMK_SPLIT) && IS_ENABLED(CONFIG_ZMK_BLE) && \ - IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_ROLE_CENTRAL)) - #include #include #include @@ -19,7 +15,8 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #include #include -#if IS_BLE_CENTRAL +#include +#if ZMK_BLE_IS_CENTRAL #include #endif @@ -170,8 +167,8 @@ int invoke_locally(struct zmk_behavior_binding *binding, struct zmk_behavior_bin } } -int zmk_keymap_apply_position_state(zmk_position_state_changed_source_t source, int layer, - uint32_t position, bool pressed, int64_t timestamp) { +int zmk_keymap_apply_position_state(uint8_t source, int layer, uint32_t position, bool pressed, + int64_t timestamp) { // We want to make a copy of this, since it may be converted from // relative to absolute before being invoked struct zmk_behavior_binding binding = zmk_keymap[layer][position]; @@ -209,8 +206,8 @@ int zmk_keymap_apply_position_state(zmk_position_state_changed_source_t source, case BEHAVIOR_LOCALITY_CENTRAL: return invoke_locally(&binding, event, pressed); case BEHAVIOR_LOCALITY_EVENT_SOURCE: -#if IS_BLE_CENTRAL - if (!bt_addr_le_cmp(source, BT_ADDR_LE_NONE)) { +#if ZMK_BLE_IS_CENTRAL + if (source == UINT_MAX) { return invoke_locally(&binding, event, pressed); } else { return zmk_split_bt_invoke_behavior(source, &binding, event, pressed); @@ -219,8 +216,10 @@ int zmk_keymap_apply_position_state(zmk_position_state_changed_source_t source, return invoke_locally(&binding, event, pressed); #endif case BEHAVIOR_LOCALITY_GLOBAL: -#if IS_BLE_CENTRAL - zmk_split_bt_invoke_behavior(BT_ADDR_LE_ANY, &binding, event, pressed); +#if ZMK_BLE_IS_CENTRAL + for (int i = 0; i < ZMK_BLE_SPLIT_PERIPHERAL_COUNT; i++) { + zmk_split_bt_invoke_behavior(i, &binding, event, pressed); + } #endif return invoke_locally(&binding, event, pressed); } @@ -228,8 +227,8 @@ int zmk_keymap_apply_position_state(zmk_position_state_changed_source_t source, return -ENOTSUP; } -int zmk_keymap_position_state_changed(zmk_position_state_changed_source_t source, uint32_t position, - bool pressed, int64_t timestamp) { +int zmk_keymap_position_state_changed(uint8_t source, uint32_t position, bool pressed, + int64_t timestamp) { if (pressed) { zmk_keymap_active_behavior_layer[position] = _zmk_keymap_layer_state; } diff --git a/app/src/kscan.c b/app/src/kscan.c index 13ad2ccb256..3425fabe310 100644 --- a/app/src/kscan.c +++ b/app/src/kscan.c @@ -50,12 +50,11 @@ void zmk_kscan_process_msgq(struct k_work *item) { uint32_t position = zmk_matrix_transform_row_column_to_position(ev.row, ev.column); LOG_DBG("Row: %d, col: %d, position: %d, pressed: %s", ev.row, ev.column, position, (pressed ? "true" : "false")); - ZMK_EVENT_RAISE(new_zmk_position_state_changed((struct zmk_position_state_changed) { -#if IS_ENABLED(CONFIG_ZMK_BLE) - .source = BT_ADDR_LE_NONE, -#endif - .state = pressed, .position = position, .timestamp = k_uptime_get() - })); + ZMK_EVENT_RAISE(new_zmk_position_state_changed( + (struct zmk_position_state_changed){.source = UINT8_MAX, + .state = pressed, + .position = position, + .timestamp = k_uptime_get()})); } } diff --git a/app/src/split/bluetooth/central.c b/app/src/split/bluetooth/central.c index c28941f16a6..0d52d051ea6 100644 --- a/app/src/split/bluetooth/central.c +++ b/app/src/split/bluetooth/central.c @@ -29,17 +29,112 @@ static int start_scan(void); #define POSITION_STATE_DATA_LEN 16 -static struct bt_conn *default_conn; +enum peripheral_slot_state { + PERIPHERAL_SLOT_STATE_OPEN, + PERIPHERAL_SLOT_STATE_CONNECTING, + PERIPHERAL_SLOT_STATE_CONNECTED, +}; + +struct peripheral_slot { + enum peripheral_slot_state state; + struct bt_conn *conn; + struct bt_gatt_discover_params discover_params; + struct bt_gatt_subscribe_params subscribe_params; + struct bt_gatt_discover_params sub_discover_params; + uint16_t run_behavior_handle; + uint8_t position_state[POSITION_STATE_DATA_LEN]; + uint8_t changed_positions[POSITION_STATE_DATA_LEN]; +}; + +static struct peripheral_slot peripherals[ZMK_BLE_SPLIT_PERIPHERAL_COUNT]; static const struct bt_uuid_128 split_service_uuid = BT_UUID_INIT_128(ZMK_SPLIT_BT_SERVICE_UUID); -static struct bt_gatt_discover_params discover_params; -static struct bt_gatt_subscribe_params subscribe_params; -static struct bt_gatt_discover_params sub_discover_params; -static uint16_t run_behavior_handle; K_MSGQ_DEFINE(peripheral_event_msgq, sizeof(struct zmk_position_state_changed), CONFIG_ZMK_SPLIT_BLE_CENTRAL_POSITION_QUEUE_SIZE, 4); +int peripheral_slot_index_for_conn(struct bt_conn *conn) { + for (int i = 0; i < ZMK_BLE_SPLIT_PERIPHERAL_COUNT; i++) { + if (peripherals[i].conn == conn) { + return i; + } + } + + return -EINVAL; +} + +struct peripheral_slot *peripheral_slot_for_conn(struct bt_conn *conn) { + int idx = peripheral_slot_index_for_conn(conn); + if (idx < 0) { + return NULL; + } + + return &peripherals[idx]; +} + +int release_peripheral_slot(int index) { + if (index < 0 || index >= ZMK_BLE_SPLIT_PERIPHERAL_COUNT) { + return -EINVAL; + } + + struct peripheral_slot *slot = &peripherals[index]; + + if (slot->state == PERIPHERAL_SLOT_STATE_OPEN) { + return -EINVAL; + } + + LOG_DBG("Releasing peripheral slot at %d", index); + + if (slot->conn != NULL) { + bt_conn_unref(slot->conn); + slot->conn = NULL; + } + slot->state = PERIPHERAL_SLOT_STATE_OPEN; + + for (int i = 0; i < POSITION_STATE_DATA_LEN; i++) { + slot->position_state[i] = 0U; + slot->changed_positions[i] = 0U; + } + + // Clean up previously discovered handles; + slot->subscribe_params.value_handle = 0; + slot->run_behavior_handle = 0; + + return 0; +} + +int reserve_peripheral_slot() { + for (int i = 0; i < ZMK_BLE_SPLIT_PERIPHERAL_COUNT; i++) { + if (peripherals[i].state == PERIPHERAL_SLOT_STATE_OPEN) { + // Be sure the slot is fully reinitialized. + release_peripheral_slot(i); + peripherals[i].state = PERIPHERAL_SLOT_STATE_CONNECTING; + return i; + } + } + + return -ENOMEM; +} + +int release_peripheral_slot_for_conn(struct bt_conn *conn) { + int idx = peripheral_slot_index_for_conn(conn); + if (idx < 0) { + return idx; + } + + return release_peripheral_slot(idx); +} + +int confirm_peripheral_slot_conn(struct bt_conn *conn) { + int idx = peripheral_slot_index_for_conn(conn); + if (idx < 0) { + return idx; + } + + peripherals[idx].state = PERIPHERAL_SLOT_STATE_CONNECTED; + return 0; +} + void peripheral_event_work_callback(struct k_work *work) { struct zmk_position_state_changed ev; while (k_msgq_get(&peripheral_event_msgq, &ev, K_NO_WAIT) == 0) { @@ -53,9 +148,12 @@ K_WORK_DEFINE(peripheral_event_work, peripheral_event_work_callback); static uint8_t split_central_notify_func(struct bt_conn *conn, struct bt_gatt_subscribe_params *params, const void *data, uint16_t length) { - static uint8_t position_state[POSITION_STATE_DATA_LEN]; + struct peripheral_slot *slot = peripheral_slot_for_conn(conn); - uint8_t changed_positions[POSITION_STATE_DATA_LEN]; + if (slot == NULL) { + LOG_ERR("No peripheral state found for connection"); + return BT_GATT_ITER_CONTINUE; + } if (!data) { LOG_DBG("[UNSUBSCRIBED]"); @@ -66,16 +164,18 @@ static uint8_t split_central_notify_func(struct bt_conn *conn, LOG_DBG("[NOTIFICATION] data %p length %u", data, length); for (int i = 0; i < POSITION_STATE_DATA_LEN; i++) { - changed_positions[i] = ((uint8_t *)data)[i] ^ position_state[i]; - position_state[i] = ((uint8_t *)data)[i]; + slot->changed_positions[i] = ((uint8_t *)data)[i] ^ slot->position_state[i]; + slot->position_state[i] = ((uint8_t *)data)[i]; + LOG_DBG("data: %d", slot->position_state[i]); } for (int i = 0; i < POSITION_STATE_DATA_LEN; i++) { for (int j = 0; j < 8; j++) { - if (changed_positions[i] & BIT(j)) { + if (slot->changed_positions[i] & BIT(j)) { uint32_t position = (i * 8) + j; - bool pressed = position_state[i] & BIT(j); - struct zmk_position_state_changed ev = {.source = bt_conn_get_dst(conn), + bool pressed = slot->position_state[i] & BIT(j); + struct zmk_position_state_changed ev = {.source = + peripheral_slot_index_for_conn(conn), .position = position, .state = pressed, .timestamp = k_uptime_get()}; @@ -90,7 +190,13 @@ static uint8_t split_central_notify_func(struct bt_conn *conn, } static void split_central_subscribe(struct bt_conn *conn) { - int err = bt_gatt_subscribe(conn, &subscribe_params); + struct peripheral_slot *slot = peripheral_slot_for_conn(conn); + if (slot == NULL) { + LOG_ERR("No peripheral state found for connection"); + return; + } + + int err = bt_gatt_subscribe(conn, &slot->subscribe_params); switch (err) { case -EALREADY: LOG_DBG("[ALREADY SUBSCRIBED]"); @@ -117,28 +223,34 @@ static uint8_t split_central_chrc_discovery_func(struct bt_conn *conn, return BT_GATT_ITER_STOP; } + struct peripheral_slot *slot = peripheral_slot_for_conn(conn); + if (slot == NULL) { + LOG_ERR("No peripheral state found for connection"); + return BT_GATT_ITER_STOP; + } + LOG_DBG("[ATTRIBUTE] handle %u", attr->handle); if (!bt_uuid_cmp(((struct bt_gatt_chrc *)attr->user_data)->uuid, BT_UUID_DECLARE_128(ZMK_SPLIT_BT_CHAR_POSITION_STATE_UUID))) { LOG_DBG("Found position state characteristic"); - discover_params.uuid = NULL; - discover_params.start_handle = attr->handle + 2; - discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC; - - subscribe_params.disc_params = &sub_discover_params; - subscribe_params.end_handle = discover_params.end_handle; - subscribe_params.value_handle = bt_gatt_attr_value_handle(attr); - subscribe_params.notify = split_central_notify_func; - subscribe_params.value = BT_GATT_CCC_NOTIFY; + 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); + slot->subscribe_params.notify = split_central_notify_func; + slot->subscribe_params.value = BT_GATT_CCC_NOTIFY; split_central_subscribe(conn); } else if (!bt_uuid_cmp(((struct bt_gatt_chrc *)attr->user_data)->uuid, BT_UUID_DECLARE_128(ZMK_SPLIT_BT_CHAR_RUN_BEHAVIOR_UUID))) { LOG_DBG("Found run behavior handle"); - run_behavior_handle = bt_gatt_attr_value_handle(attr); + slot->run_behavior_handle = bt_gatt_attr_value_handle(attr); } - bool subscribed = (run_behavior_handle && subscribe_params.value_handle); + bool subscribed = (slot->run_behavior_handle && slot->subscribe_params.value_handle); return subscribed ? BT_GATT_ITER_STOP : BT_GATT_ITER_CONTINUE; } @@ -154,18 +266,24 @@ static uint8_t split_central_service_discovery_func(struct bt_conn *conn, LOG_DBG("[ATTRIBUTE] handle %u", attr->handle); - if (bt_uuid_cmp(discover_params.uuid, BT_UUID_DECLARE_128(ZMK_SPLIT_BT_SERVICE_UUID))) { + struct peripheral_slot *slot = peripheral_slot_for_conn(conn); + if (slot == NULL) { + LOG_ERR("No peripheral state found for connection"); + return BT_GATT_ITER_STOP; + } + + if (bt_uuid_cmp(slot->discover_params.uuid, BT_UUID_DECLARE_128(ZMK_SPLIT_BT_SERVICE_UUID))) { LOG_DBG("Found other service"); return BT_GATT_ITER_CONTINUE; } LOG_DBG("Found split service"); - discover_params.uuid = NULL; - discover_params.func = split_central_chrc_discovery_func; - discover_params.start_handle = attr->handle + 1; - discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC; + 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, &discover_params); + int err = bt_gatt_discover(conn, &slot->discover_params); if (err) { LOG_ERR("Failed to start discovering split service characteristics (err %d)", err); } @@ -183,14 +301,20 @@ static void split_central_process_connection(struct bt_conn *conn) { return; } - if (conn == default_conn && !subscribe_params.value_handle) { - discover_params.uuid = &split_service_uuid.uuid; - discover_params.func = split_central_service_discovery_func; - discover_params.start_handle = 0x0001; - discover_params.end_handle = 0xffff; - discover_params.type = BT_GATT_DISCOVER_PRIMARY; + struct peripheral_slot *slot = peripheral_slot_for_conn(conn); + if (slot == NULL) { + LOG_ERR("No peripheral state found for connection"); + return; + } - err = bt_gatt_discover(default_conn, &discover_params); + if (!slot->subscribe_params.value_handle) { + slot->discover_params.uuid = &split_service_uuid.uuid; + slot->discover_params.func = split_central_service_discovery_func; + slot->discover_params.start_handle = 0x0001; + slot->discover_params.end_handle = 0xffff; + slot->discover_params.type = BT_GATT_DISCOVER_PRIMARY; + + err = bt_gatt_discover(slot->conn, &slot->discover_params); if (err) { LOG_ERR("Discover failed(err %d)", err); return; @@ -251,21 +375,33 @@ static bool split_central_eir_found(struct bt_data *data, void *user_data) { continue; } - default_conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, addr); - if (default_conn) { + uint8_t slot_idx = reserve_peripheral_slot(); + if (slot_idx < 0) { + LOG_ERR("Faild to reserve peripheral slot (err %d)", slot_idx); + continue; + } + + struct peripheral_slot *slot = &peripherals[slot_idx]; + + slot->conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, addr); + if (slot->conn) { LOG_DBG("Found existing connection"); - split_central_process_connection(default_conn); + split_central_process_connection(slot->conn); + err = bt_conn_le_phy_update(slot->conn, BT_CONN_LE_PHY_PARAM_2M); + if (err) { + LOG_ERR("Update phy conn failed (err %d)", err); + } } else { param = BT_LE_CONN_PARAM(0x0006, 0x0006, 30, 400); - err = bt_conn_le_create(addr, BT_CONN_LE_CREATE_CONN, param, &default_conn); + err = bt_conn_le_create(addr, BT_CONN_LE_CREATE_CONN, param, &slot->conn); if (err) { LOG_ERR("Create conn failed (err %d) (create conn? 0x%04x)", err, BT_HCI_OP_LE_CREATE_CONN); start_scan(); } - err = bt_conn_le_phy_update(default_conn, BT_CONN_LE_PHY_PARAM_2M); + err = bt_conn_le_phy_update(slot->conn, BT_CONN_LE_PHY_PARAM_2M); if (err) { LOG_ERR("Update phy conn failed (err %d)", err); start_scan(); @@ -314,8 +450,7 @@ static void split_central_connected(struct bt_conn *conn, uint8_t conn_err) { if (conn_err) { LOG_ERR("Failed to connect to %s (%u)", log_strdup(addr), conn_err); - bt_conn_unref(default_conn); - default_conn = NULL; + release_peripheral_slot_for_conn(conn); start_scan(); return; @@ -323,6 +458,7 @@ static void split_central_connected(struct bt_conn *conn, uint8_t conn_err) { LOG_DBG("Connected: %s", log_strdup(addr)); + confirm_peripheral_slot_conn(conn); split_central_process_connection(conn); } @@ -333,16 +469,7 @@ static void split_central_disconnected(struct bt_conn *conn, uint8_t reason) { LOG_DBG("Disconnected: %s (reason %d)", log_strdup(addr), reason); - if (default_conn != conn) { - return; - } - - bt_conn_unref(default_conn); - default_conn = NULL; - - // Clean up previously discovered handles; - subscribe_params.value_handle = 0; - run_behavior_handle = 0; + release_peripheral_slot_for_conn(conn); start_scan(); } @@ -357,16 +484,30 @@ K_THREAD_STACK_DEFINE(split_central_split_run_q_stack, struct k_work_q split_central_split_run_q; -K_MSGQ_DEFINE(zmk_split_central_split_run_msgq, sizeof(struct zmk_split_run_behavior_payload), +struct zmk_split_run_behavior_payload_wrapper { + uint8_t source; + struct zmk_split_run_behavior_payload payload; +}; + +K_MSGQ_DEFINE(zmk_split_central_split_run_msgq, + sizeof(struct zmk_split_run_behavior_payload_wrapper), CONFIG_ZMK_BLE_SPLIT_CENTRAL_SPLIT_RUN_QUEUE_SIZE, 4); void split_central_split_run_callback(struct k_work *work) { - struct zmk_split_run_behavior_payload payload; + struct zmk_split_run_behavior_payload_wrapper payload_wrapper; - while (k_msgq_get(&zmk_split_central_split_run_msgq, &payload, K_NO_WAIT) == 0) { - int err = - bt_gatt_write_without_response(default_conn, run_behavior_handle, &payload, - sizeof(struct zmk_split_run_behavior_payload), true); + LOG_DBG(""); + + while (k_msgq_get(&zmk_split_central_split_run_msgq, &payload_wrapper, K_NO_WAIT) == 0) { + if (peripherals[payload_wrapper.source].state != PERIPHERAL_SLOT_STATE_CONNECTED) { + LOG_ERR("Source not connected"); + continue; + } + + int err = bt_gatt_write_without_response( + peripherals[payload_wrapper.source].conn, + peripherals[payload_wrapper.source].run_behavior_handle, &payload_wrapper.payload, + sizeof(struct zmk_split_run_behavior_payload), true); if (err) { LOG_ERR("Failed to write the behavior characteristic (err %d)", err); @@ -376,15 +517,18 @@ void split_central_split_run_callback(struct k_work *work) { K_WORK_DEFINE(split_central_split_run_work, split_central_split_run_callback); -static int split_bt_invoke_behavior_payload(struct zmk_split_run_behavior_payload payload) { - int err = k_msgq_put(&zmk_split_central_split_run_msgq, &payload, K_MSEC(100)); +static int +split_bt_invoke_behavior_payload(struct zmk_split_run_behavior_payload_wrapper payload_wrapper) { + LOG_DBG(""); + + int err = k_msgq_put(&zmk_split_central_split_run_msgq, &payload_wrapper, K_MSEC(100)); if (err) { switch (err) { case -EAGAIN: { LOG_WRN("Consumer message queue full, popping first message and queueing again"); - struct zmk_split_run_behavior_payload discarded_report; + struct zmk_split_run_behavior_payload_wrapper discarded_report; k_msgq_get(&zmk_split_central_split_run_msgq, &discarded_report, K_NO_WAIT); - return split_bt_invoke_behavior_payload(payload); + return split_bt_invoke_behavior_payload(payload_wrapper); } default: LOG_WRN("Failed to queue behavior to send (%d)", err); @@ -397,18 +541,19 @@ static int split_bt_invoke_behavior_payload(struct zmk_split_run_behavior_payloa return 0; }; -int zmk_split_bt_invoke_behavior(const bt_addr_le_t *source, struct zmk_behavior_binding *binding, +int zmk_split_bt_invoke_behavior(uint8_t source, struct zmk_behavior_binding *binding, struct zmk_behavior_binding_event event, bool state) { struct zmk_split_run_behavior_payload payload = {.data = { .param1 = binding->param1, .param2 = binding->param2, .position = event.position, - .state = state, + .state = state ? 1 : 0, }}; strncpy(payload.behavior_dev, binding->behavior_dev, ZMK_SPLIT_RUN_BEHAVIOR_DEV_LEN - 1); payload.behavior_dev[ZMK_SPLIT_RUN_BEHAVIOR_DEV_LEN - 1] = '\0'; - return split_bt_invoke_behavior_payload(payload); + struct zmk_split_run_behavior_payload_wrapper wrapper = {.source = source, .payload = payload}; + return split_bt_invoke_behavior_payload(wrapper); } int zmk_split_bt_central_init(const struct device *_arg) { From c6e23e68bddb8eaf7b3740a9bc5ad555e4184665 Mon Sep 17 00:00:00 2001 From: Peter Johanson Date: Sun, 2 Jan 2022 04:57:37 +0000 Subject: [PATCH 07/11] refactor(behaviors): Always add reset behaviors. * Don'd omit unreferenced reset behaviors, so they are always available in split peripherals. --- app/dts/behaviors/reset.dtsi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/dts/behaviors/reset.dtsi b/app/dts/behaviors/reset.dtsi index e4ba56588cf..cb246814bf6 100644 --- a/app/dts/behaviors/reset.dtsi +++ b/app/dts/behaviors/reset.dtsi @@ -8,13 +8,13 @@ / { behaviors { - /omit-if-no-ref/ reset: behavior_reset { + reset: behavior_reset { compatible = "zmk,behavior-reset"; label = "RESET"; #binding-cells = <0>; }; - /omit-if-no-ref/ bootloader: behavior_reset_dfu { + bootloader: behavior_reset_dfu { compatible = "zmk,behavior-reset"; label = "BOOTLOAD"; type = ; From 74ae1985dd4e4041dfb8fbc025420a82c9b245d9 Mon Sep 17 00:00:00 2001 From: Peter Johanson Date: Sun, 30 Jan 2022 03:38:10 +0000 Subject: [PATCH 08/11] fix(split): Add define for local source. * Add `ZMK_POSITION_STATE_CHANGE_SOURCE_LOCAL` and use it consinstently to fix bug w/ local `&reset`, `&bootloader`, etc. --- app/include/zmk/events/position_state_changed.h | 2 ++ app/src/keymap.c | 2 +- app/src/kscan.c | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/include/zmk/events/position_state_changed.h b/app/include/zmk/events/position_state_changed.h index 7685fe0fd37..5323e943ac0 100644 --- a/app/include/zmk/events/position_state_changed.h +++ b/app/include/zmk/events/position_state_changed.h @@ -9,6 +9,8 @@ #include #include +#define ZMK_POSITION_STATE_CHANGE_SOURCE_LOCAL UINT8_MAX + struct zmk_position_state_changed { uint8_t source; uint32_t position; diff --git a/app/src/keymap.c b/app/src/keymap.c index 8acc5ec1d58..e586316f4f4 100644 --- a/app/src/keymap.c +++ b/app/src/keymap.c @@ -207,7 +207,7 @@ int zmk_keymap_apply_position_state(uint8_t source, int layer, uint32_t position return invoke_locally(&binding, event, pressed); case BEHAVIOR_LOCALITY_EVENT_SOURCE: #if ZMK_BLE_IS_CENTRAL - if (source == UINT_MAX) { + if (source == ZMK_POSITION_STATE_CHANGE_SOURCE_LOCAL) { return invoke_locally(&binding, event, pressed); } else { return zmk_split_bt_invoke_behavior(source, &binding, event, pressed); diff --git a/app/src/kscan.c b/app/src/kscan.c index 3425fabe310..c7cf2881025 100644 --- a/app/src/kscan.c +++ b/app/src/kscan.c @@ -51,7 +51,7 @@ void zmk_kscan_process_msgq(struct k_work *item) { LOG_DBG("Row: %d, col: %d, position: %d, pressed: %s", ev.row, ev.column, position, (pressed ? "true" : "false")); ZMK_EVENT_RAISE(new_zmk_position_state_changed( - (struct zmk_position_state_changed){.source = UINT8_MAX, + (struct zmk_position_state_changed){.source = ZMK_POSITION_STATE_CHANGE_SOURCE_LOCAL, .state = pressed, .position = position, .timestamp = k_uptime_get()})); From 8a1f41996ae2a38e78ecc2ffe9678c8af5a8e7b6 Mon Sep 17 00:00:00 2001 From: Peter Johanson Date: Sun, 30 Jan 2022 04:50:10 +0000 Subject: [PATCH 09/11] fix(split): Slightly improved logging on peripherals. --- app/src/split/bluetooth/service.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/split/bluetooth/service.c b/app/src/split/bluetooth/service.c index bb0202c3a5d..3b860197fcf 100644 --- a/app/src/split/bluetooth/service.c +++ b/app/src/split/bluetooth/service.c @@ -58,8 +58,8 @@ static ssize_t split_svc_run_behavior(struct bt_conn *conn, const struct bt_gatt .param2 = payload->data.param2, .behavior_dev = payload->behavior_dev, }; - LOG_DBG("INVOKE THE BEHAVIOR: %s with params %d %d", log_strdup(binding.behavior_dev), - binding.param1, binding.param2); + LOG_DBG("%s with params %d %d: pressed? %d", log_strdup(binding.behavior_dev), + binding.param1, binding.param2, payload->data.state); struct zmk_behavior_binding_event event = {.position = payload->data.position, .timestamp = k_uptime_get()}; int err; From 2d91ad810803d8759ea355fda0743a626ca37608 Mon Sep 17 00:00:00 2001 From: Peter Johanson Date: Sun, 30 Jan 2022 05:32:12 +0000 Subject: [PATCH 10/11] fix(split): Fix an off-by-one error in split svc. * Properly check end of behavior device string for null terminator. --- app/src/split/bluetooth/service.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/split/bluetooth/service.c b/app/src/split/bluetooth/service.c index 3b860197fcf..ca192d7d249 100644 --- a/app/src/split/bluetooth/service.c +++ b/app/src/split/bluetooth/service.c @@ -52,7 +52,7 @@ static ssize_t split_svc_run_behavior(struct bt_conn *conn, const struct bt_gatt // 1: We've gotten all the position/state/param data. // 2: We have a null terminated string for the behavior device label. if ((end_addr > sizeof(struct zmk_split_run_behavior_data)) && - payload->behavior_dev[end_addr - sizeof(struct zmk_split_run_behavior_data)] == '\0') { + payload->behavior_dev[end_addr - sizeof(struct zmk_split_run_behavior_data) - 1] == '\0') { struct zmk_behavior_binding binding = { .param1 = payload->data.param1, .param2 = payload->data.param2, From fa0d61345e51b6fbd399ebe2e2d5b8520623a450 Mon Sep 17 00:00:00 2001 From: Peter Johanson Date: Tue, 1 Feb 2022 03:29:50 +0000 Subject: [PATCH 11/11] refactor(splits): Minor cleanups to periph invocation * Add strlcpy from public domain version. * Leverage strlcpy to detect truncation of behavior dev strs, and log. * Use `offsetof` for cleaner detection on peripheral side. --- app/CMakeLists.txt | 1 + app/include/zmk/stdlib.h | 19 +++++++++++++++++++ app/src/split/bluetooth/central.c | 9 +++++++-- app/src/split/bluetooth/service.c | 4 +++- app/src/stdlib.c | 25 +++++++++++++++++++++++++ 5 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 app/include/zmk/stdlib.h create mode 100644 app/src/stdlib.c diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 1e153fb630d..7681efab68b 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -23,6 +23,7 @@ zephyr_linker_sources(RODATA include/linker/zmk-events.ld) # find_package(Zephyr) which defines the target. target_include_directories(app PRIVATE include) target_sources_ifdef(CONFIG_ZMK_SLEEP app PRIVATE src/power.c) +target_sources(app PRIVATE src/stdlib.c) target_sources(app PRIVATE src/activity.c) target_sources(app PRIVATE src/kscan.c) target_sources(app PRIVATE src/matrix_transform.c) diff --git a/app/include/zmk/stdlib.h b/app/include/zmk/stdlib.h new file mode 100644 index 00000000000..fa8fe6737a1 --- /dev/null +++ b/app/include/zmk/stdlib.h @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2022 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#pragma once + +#include /* for size_t */ + +/* + * ANSI C version of strlcpy + * Based on the NetBSD strlcpy man page. + * + * Nathan Myers , 2003/06/03 + * Placed in the public domain. + */ + +size_t strlcpy(char *dst, const char *src, size_t size); \ No newline at end of file diff --git a/app/src/split/bluetooth/central.c b/app/src/split/bluetooth/central.c index 0d52d051ea6..8a0e79eaa00 100644 --- a/app/src/split/bluetooth/central.c +++ b/app/src/split/bluetooth/central.c @@ -17,6 +17,7 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); +#include #include #include #include @@ -549,8 +550,12 @@ int zmk_split_bt_invoke_behavior(uint8_t source, struct zmk_behavior_binding *bi .position = event.position, .state = state ? 1 : 0, }}; - strncpy(payload.behavior_dev, binding->behavior_dev, ZMK_SPLIT_RUN_BEHAVIOR_DEV_LEN - 1); - payload.behavior_dev[ZMK_SPLIT_RUN_BEHAVIOR_DEV_LEN - 1] = '\0'; + const size_t payload_dev_size = sizeof(payload.behavior_dev); + if (strlcpy(payload.behavior_dev, binding->behavior_dev, payload_dev_size) >= + payload_dev_size) { + LOG_ERR("Truncated behavior label %s to %s before invoking peripheral behavior", + log_strdup(binding->behavior_dev), log_strdup(payload.behavior_dev)); + } struct zmk_split_run_behavior_payload_wrapper wrapper = {.source = source, .payload = payload}; return split_bt_invoke_behavior_payload(wrapper); diff --git a/app/src/split/bluetooth/service.c b/app/src/split/bluetooth/service.c index ca192d7d249..7de78506fce 100644 --- a/app/src/split/bluetooth/service.c +++ b/app/src/split/bluetooth/service.c @@ -51,8 +51,10 @@ static ssize_t split_svc_run_behavior(struct bt_conn *conn, const struct bt_gatt // We run if: // 1: We've gotten all the position/state/param data. // 2: We have a null terminated string for the behavior device label. + const size_t behavior_dev_offset = + offsetof(struct zmk_split_run_behavior_payload, behavior_dev); if ((end_addr > sizeof(struct zmk_split_run_behavior_data)) && - payload->behavior_dev[end_addr - sizeof(struct zmk_split_run_behavior_data) - 1] == '\0') { + payload->behavior_dev[end_addr - behavior_dev_offset - 1] == '\0') { struct zmk_behavior_binding binding = { .param1 = payload->data.param1, .param2 = payload->data.param2, diff --git a/app/src/stdlib.c b/app/src/stdlib.c new file mode 100644 index 00000000000..5bca1696c8d --- /dev/null +++ b/app/src/stdlib.c @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2022 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include + +/* + * ANSI C version of strlcpy + * Based on the NetBSD strlcpy man page. + * + * Nathan Myers , 2003/06/03 + * Placed in the public domain. + */ + +size_t strlcpy(char *dst, const char *src, size_t size) { + const size_t len = strlen(src); + if (size != 0) { + memcpy(dst, src, (len > size - 1) ? size - 1 : len); + dst[size - 1] = 0; + } + return len; +}