From 10b605e1b7590ef998385ab29fb0a5d9dc616766 Mon Sep 17 00:00:00 2001 From: ReFil <31960031+ReFil@users.noreply.github.com> Date: Tue, 13 Sep 2022 12:15:50 +0100 Subject: [PATCH] feat(lighting): Kinesis lighting functionality moved peripheral updating from behaviour bnased to dedicated bluetooth services, added custom lighting effects to the adv360 --- app/Kconfig | 2 +- app/include/dt-bindings/zmk/rgb.h | 5 +- app/include/zmk/backlight.h | 6 + app/include/zmk/rgb_underglow.h | 8 + app/include/zmk/split/bluetooth/central.h | 5 +- app/include/zmk/split/bluetooth/service.h | 10 + app/include/zmk/split/bluetooth/uuid.h | 2 + app/src/backlight.c | 60 +++- app/src/behaviors/behavior_backlight.c | 2 +- app/src/behaviors/behavior_rgb_underglow.c | 7 + app/src/rgb_underglow.c | 398 +++++++++++++++++++-- app/src/split/bluetooth/Kconfig | 14 + app/src/split/bluetooth/central.c | 149 +++++++- app/src/split/bluetooth/service.c | 52 +++ 14 files changed, 680 insertions(+), 40 deletions(-) diff --git a/app/Kconfig b/app/Kconfig index 97ad5c8c287..ad8d09d761a 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -236,7 +236,7 @@ config ZMK_RGB_UNDERGLOW_SPD_START config ZMK_RGB_UNDERGLOW_EFF_START int "RGB underglow start effect int value related to the effect enum list" - range 0 3 + range 0 5 default 0 config ZMK_RGB_UNDERGLOW_ON_START diff --git a/app/include/dt-bindings/zmk/rgb.h b/app/include/dt-bindings/zmk/rgb.h index c1a8008273c..af01f989713 100644 --- a/app/include/dt-bindings/zmk/rgb.h +++ b/app/include/dt-bindings/zmk/rgb.h @@ -18,7 +18,8 @@ #define RGB_EFF_CMD 11 #define RGB_EFR_CMD 12 #define RGB_EFS_CMD 13 -#define RGB_COLOR_HSB_CMD 14 +#define RGB_MEFS_CMD 14 +#define RGB_COLOR_HSB_CMD 15 #define RGB_TOG RGB_TOG_CMD 0 #define RGB_ON RGB_ON_CMD 0 @@ -33,6 +34,8 @@ #define RGB_SPD RGB_SPD_CMD 0 #define RGB_EFF RGB_EFF_CMD 0 #define RGB_EFR RGB_EFR_CMD 0 +#define RGB_EFS RGB_EFS_CMD +#define RFB_MEFS RGB_MEFS_CMD #define RGB_COLOR_HSB_VAL(h, s, v) (((h) << 16) + ((s) << 8) + (v)) #define RGB_COLOR_HSB(h, s, v) RGB_COLOR_HSB_CMD##(RGB_COLOR_HSB_VAL(h, s, v)) #define RGB_COLOR_HSV RGB_COLOR_HSB \ No newline at end of file diff --git a/app/include/zmk/backlight.h b/app/include/zmk/backlight.h index a0f52431175..00097626963 100644 --- a/app/include/zmk/backlight.h +++ b/app/include/zmk/backlight.h @@ -6,11 +6,17 @@ #pragma once +struct backlight_state { + uint8_t brightness; + bool on; +}; + int zmk_backlight_on(); int zmk_backlight_off(); int zmk_backlight_toggle(); bool zmk_backlight_is_on(); +int zmk_backlight_update_vals(struct backlight_state new_state); int zmk_backlight_set_brt(uint8_t brightness); uint8_t zmk_backlight_get_brt(); uint8_t zmk_backlight_calc_brt(int direction); diff --git a/app/include/zmk/rgb_underglow.h b/app/include/zmk/rgb_underglow.h index 797f0b19f1d..6547aaaedfc 100644 --- a/app/include/zmk/rgb_underglow.h +++ b/app/include/zmk/rgb_underglow.h @@ -6,12 +6,19 @@ #pragma once +#include + struct zmk_led_hsb { uint16_t h; uint8_t s; uint8_t b; }; +struct zmk_periph_led { + uint8_t layer; + zmk_leds_flags_t indicators; +}; + int zmk_rgb_underglow_toggle(); int zmk_rgb_underglow_get_state(bool *state); int zmk_rgb_underglow_on(); @@ -19,6 +26,7 @@ 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); +int zmk_rgb_underglow_set_periph(struct zmk_periph_led periph); 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/include/zmk/split/bluetooth/central.h b/app/include/zmk/split/bluetooth/central.h index 072408605cf..6e580a56d9d 100644 --- a/app/include/zmk/split/bluetooth/central.h +++ b/app/include/zmk/split/bluetooth/central.h @@ -5,4 +5,7 @@ #include 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 + struct zmk_behavior_binding_event event, bool state); + +int zmk_split_bt_update_led(struct zmk_periph_led *periph); +int zmk_split_bt_update_bl(struct backlight_state *periph); \ 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 f0c1d79ff7d..d8b2ab1bfdf 100644 --- a/app/include/zmk/split/bluetooth/service.h +++ b/app/include/zmk/split/bluetooth/service.h @@ -20,5 +20,15 @@ struct zmk_split_run_behavior_payload { char behavior_dev[ZMK_SPLIT_RUN_BEHAVIOR_DEV_LEN]; } __packed; +struct zmk_split_update_led_data { + uint8_t layer; + uint8_t indicators; +} __packed; + +struct zmk_split_update_bl_data { + uint8_t brightness; + bool on; +} __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 735f5751d0a..6abd785b3e9 100644 --- a/app/include/zmk/split/bluetooth/uuid.h +++ b/app/include/zmk/split/bluetooth/uuid.h @@ -16,3 +16,5 @@ #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) +#define ZMK_SPLIT_BT_CHAR_UPDATE_LED_UUID ZMK_BT_SPLIT_UUID(0x00000003) +#define ZMK_SPLIT_BT_CHAR_UPDATE_BL_UUID ZMK_BT_SPLIT_UUID(0x00000004) diff --git a/app/src/backlight.c b/app/src/backlight.c index 28a93e73d15..f3b1ad8ebd3 100644 --- a/app/src/backlight.c +++ b/app/src/backlight.c @@ -19,6 +19,12 @@ #include #include #include +#include +#include + +#if ZMK_BLE_IS_CENTRAL +#include +#endif LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); @@ -34,15 +40,27 @@ static const struct device *const backlight_dev = DEVICE_DT_GET(DT_CHOSEN(zmk_ba #define BRT_MAX 100 -struct backlight_state { - uint8_t brightness; - bool on; -}; - static struct backlight_state state = {.brightness = CONFIG_ZMK_BACKLIGHT_BRT_START, .on = IS_ENABLED(CONFIG_ZMK_BACKLIGHT_ON_START)}; +#if ZMK_BLE_IS_CENTRAL +static struct k_work_delayable bl_update_work; + +static void zmk_backlight_central_send() { + //#if IS_ENABLED(CONFIG_ZMK_BACKLIGHT_AUTO_OFF_IDLE) + // state.on = state.on || (!state.on && (zmk_activity_get_state() == ZMK_ACTIVITY_IDLE); + //#endif + int err = zmk_split_bt_update_bl(&state); + if (err) { + LOG_ERR("send failed (err %d)", err); + } +} +#endif + static int zmk_backlight_update() { +#if ZMK_BLE_IS_CENTRAL + zmk_backlight_central_send(); +#endif uint8_t brt = zmk_backlight_get_brt(); LOG_DBG("Update backlight brightness: %d%%", brt); @@ -75,7 +93,7 @@ static void backlight_save_work_handler(struct k_work *work) { settings_save_one("backlight/state", &state, sizeof(state)); } -static K_DELAYED_WORK_DEFINE(backlight_save_work, backlight_save_work_handler); +static struct k_work_delayable backlight_save_work; #endif static int zmk_backlight_init(const struct device *_arg) { @@ -90,6 +108,10 @@ static int zmk_backlight_init(const struct device *_arg) { if (rc != 0) { LOG_ERR("Failed to load backlight settings: %d", rc); } + k_work_init_delayable(&backlight_save_work, backlight_save_work_handler); +#endif +#if ZMK_BLE_IS_CENTRAL + k_work_init_delayable(&bl_update_work, zmk_backlight_central_send); #endif #if IS_ENABLED(CONFIG_ZMK_BACKLIGHT_AUTO_OFF_USB) state.on = zmk_usb_is_powered(); @@ -104,8 +126,8 @@ static int zmk_backlight_update_and_save() { } #if IS_ENABLED(CONFIG_SETTINGS) - k_delayed_work_cancel(&backlight_save_work); - return k_delayed_work_submit(&backlight_save_work, K_MSEC(CONFIG_ZMK_SETTINGS_SAVE_DEBOUNCE)); + int ret = k_work_reschedule(&backlight_save_work, K_MSEC(CONFIG_ZMK_SETTINGS_SAVE_DEBOUNCE)); + return MIN(ret, 0); #else return 0; #endif @@ -124,6 +146,12 @@ int zmk_backlight_off() { int zmk_backlight_toggle() { return state.on ? zmk_backlight_off() : zmk_backlight_on(); } +int zmk_backlight_update_vals(struct backlight_state new_state) { + state.on = new_state.on; + state.brightness = new_state.brightness; + return zmk_backlight_update_and_save(); +} + bool zmk_backlight_is_on() { return state.on; } int zmk_backlight_set_brt(uint8_t brightness) { @@ -172,7 +200,17 @@ static int backlight_event_listener(const zmk_event_t *eh) { return backlight_auto_state(&prev_state, zmk_usb_is_powered()); } #endif - +#if ZMK_BLE_IS_CENTRAL + if (as_zmk_split_peripheral_status_changed(eh)) { + LOG_DBG("event called"); + const struct zmk_split_peripheral_status_changed *ev; + ev = as_zmk_split_peripheral_status_changed(eh); + if (ev->connected) + return k_work_schedule(&bl_update_work, K_MSEC(2500)); + else + return k_work_cancel_delayable(&bl_update_work); + } +#endif return -ENOTSUP; } @@ -188,4 +226,8 @@ ZMK_SUBSCRIPTION(backlight, zmk_activity_state_changed); ZMK_SUBSCRIPTION(backlight, zmk_usb_conn_state_changed); #endif +#if ZMK_BLE_IS_CENTRAL +ZMK_SUBSCRIPTION(backlight, zmk_split_peripheral_status_changed); +#endif + SYS_INIT(zmk_backlight_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); diff --git a/app/src/behaviors/behavior_backlight.c b/app/src/behaviors/behavior_backlight.c index a1eaaf86195..1728a4d04c4 100644 --- a/app/src/behaviors/behavior_backlight.c +++ b/app/src/behaviors/behavior_backlight.c @@ -88,7 +88,7 @@ static const struct behavior_driver_api behavior_backlight_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, + .locality = BEHAVIOR_LOCALITY_CENTRAL, }; DEVICE_DT_INST_DEFINE(0, behavior_backlight_init, NULL, NULL, NULL, APPLICATION, diff --git a/app/src/behaviors/behavior_rgb_underglow.c b/app/src/behaviors/behavior_rgb_underglow.c index 3459cd22a56..d1e04587207 100644 --- a/app/src/behaviors/behavior_rgb_underglow.c +++ b/app/src/behaviors/behavior_rgb_underglow.c @@ -19,6 +19,7 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #if DT_HAS_COMPAT_STATUS_OKAY(DT_DRV_COMPAT) static int behavior_rgb_underglow_init(const struct device *dev) { return 0; } +static uint8_t old_effect; static int on_keymap_binding_convert_central_state_dependent_params(struct zmk_behavior_binding *binding, @@ -98,6 +99,7 @@ on_keymap_binding_convert_central_state_dependent_params(struct zmk_behavior_bin static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding, struct zmk_behavior_binding_event event) { + LOG_DBG("RGB behaviour triggered (%d/%d)", binding->param1, binding->param2); switch (binding->param1) { case RGB_TOG_CMD: return zmk_rgb_underglow_toggle(); @@ -123,6 +125,9 @@ static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding, return zmk_rgb_underglow_change_spd(-1); case RGB_EFS_CMD: return zmk_rgb_underglow_select_effect(binding->param2); + case RGB_MEFS_CMD: + old_effect = zmk_rgb_underglow_calc_effect(0); + return zmk_rgb_underglow_select_effect(binding->param2); case RGB_EFF_CMD: return zmk_rgb_underglow_cycle_effect(1); case RGB_EFR_CMD: @@ -138,6 +143,8 @@ static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding, static int on_keymap_binding_released(struct zmk_behavior_binding *binding, struct zmk_behavior_binding_event event) { + if (binding->param1 == RGB_MEFS_CMD) + return zmk_rgb_underglow_select_effect(old_effect); return ZMK_BEHAVIOR_OPAQUE; } diff --git a/app/src/rgb_underglow.c b/app/src/rgb_underglow.c index 25d4466eaf9..95cda7a1d84 100644 --- a/app/src/rgb_underglow.c +++ b/app/src/rgb_underglow.c @@ -24,6 +24,18 @@ #include #include #include +#include +#include +#include +#include +#include +#include + +#if ZMK_BLE_IS_CENTRAL +#include +#else +#include +#endif LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); @@ -34,6 +46,8 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #define SAT_MAX 100 #define BRT_MAX 100 +#define LED_BRIGHTNESS 5 + BUILD_ASSERT(CONFIG_ZMK_RGB_UNDERGLOW_BRT_MIN <= CONFIG_ZMK_RGB_UNDERGLOW_BRT_MAX, "ERROR: RGB underglow maximum brightness is less than minimum brightness"); @@ -42,6 +56,9 @@ enum rgb_underglow_effect { UNDERGLOW_EFFECT_BREATHE, UNDERGLOW_EFFECT_SPECTRUM, UNDERGLOW_EFFECT_SWIRL, + UNDERGLOW_EFFECT_KINESIS, + UNDERGLOW_EFFECT_BATTERY, + UNDERGLOW_EFFECT_TEST, UNDERGLOW_EFFECT_NUMBER // Used to track number of underglow effects }; @@ -59,6 +76,16 @@ static struct led_rgb pixels[STRIP_NUM_PIXELS]; static struct rgb_underglow_state state; +static struct zmk_periph_led led_data; + +static bool last_ble_state[2]; + +static bool triggered; + +#if ZMK_BLE_IS_CENTRAL +static struct zmk_periph_led old_led_data; +#endif + #if IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_EXT_POWER) static const struct device *ext_power; #endif @@ -123,6 +150,12 @@ static struct led_rgb hsb_to_rgb(struct zmk_led_hsb hsb) { return rgb; } +int zmk_rgb_underglow_set_periph(struct zmk_periph_led periph) { + led_data = periph; + LOG_DBG("Update led_data %d %d", led_data.layer, led_data.indicators); + return 0; +} + static void zmk_rgb_underglow_effect_solid() { for (int i = 0; i < STRIP_NUM_PIXELS; i++) { pixels[i] = hsb_to_rgb(hsb_scale_min_max(state.color)); @@ -168,6 +201,296 @@ static void zmk_rgb_underglow_effect_swirl() { state.animation_step = state.animation_step % HUE_MAX; } +#if ZMK_BLE_IS_CENTRAL +static struct k_work_delayable led_update_work; + +static void zmk_rgb_underglow_central_send() { + int err = zmk_split_bt_update_led(&led_data); + if (err) { + LOG_ERR("send failed (err %d)", err); + } +} +#endif + +static void zmk_rgb_underglow_effect_kinesis() { +#if ZMK_BLE_IS_CENTRAL + // leds for central(left) side + + old_led_data.layer = led_data.layer; + old_led_data.indicators = led_data.indicators; + led_data.indicators = zmk_leds_get_current_flags(); + led_data.layer = zmk_keymap_highest_layer_active(); + + pixels[0].r = (led_data.indicators & ZMK_LED_CAPSLOCK_BIT) * LED_BRIGHTNESS; + pixels[0].g = (led_data.indicators & ZMK_LED_CAPSLOCK_BIT) * LED_BRIGHTNESS; + pixels[0].b = (led_data.indicators & ZMK_LED_CAPSLOCK_BIT) * LED_BRIGHTNESS; + // set second led as bluetooth state + switch (zmk_ble_active_profile_index()) { + case 0: + pixels[1].r = LED_BRIGHTNESS; + pixels[1].g = LED_BRIGHTNESS; + pixels[1].b = LED_BRIGHTNESS; + break; + case 1: + pixels[1].r = 0; + pixels[1].g = 0; + pixels[1].b = LED_BRIGHTNESS; + break; + case 2: + pixels[1].r = LED_BRIGHTNESS; + pixels[1].g = 0; + pixels[1].b = 0; + break; + case 3: + pixels[1].r = 0; + pixels[1].g = LED_BRIGHTNESS; + pixels[1].b = 0; + break; + } + // blink second led slowly if bluetooth not paired, quickly if not connected + if (zmk_ble_active_profile_is_open()) { + pixels[1].r = pixels[1].r * last_ble_state[0]; + pixels[1].g = pixels[1].g * last_ble_state[0]; + pixels[1].b = pixels[1].b * last_ble_state[0]; + if (state.animation_step > 3) { + last_ble_state[0] = !last_ble_state[0]; + state.animation_step = 0; + } + state.animation_step++; + } else if (!zmk_ble_active_profile_is_connected()) { + pixels[1].r = pixels[1].r * last_ble_state[1]; + pixels[1].g = pixels[1].g * last_ble_state[1]; + pixels[1].b = pixels[1].b * last_ble_state[1]; + if (state.animation_step > 14) { + last_ble_state[1] = !last_ble_state[1]; + state.animation_step = 0; + } + state.animation_step++; + } + // set third led as layer state + switch (led_data.layer) { + case 0: + pixels[2].r = 0; + pixels[2].g = 0; + pixels[2].b = 0; + break; + case 1: + pixels[2].r = LED_BRIGHTNESS; + pixels[2].g = LED_BRIGHTNESS; + pixels[2].b = LED_BRIGHTNESS; + break; + case 2: + pixels[2].r = 0; + pixels[2].g = 0; + pixels[2].b = LED_BRIGHTNESS; + break; + case 3: + pixels[2].r = 0; + pixels[2].g = LED_BRIGHTNESS; + pixels[2].b = 0; + break; + case 4: + pixels[2].r = LED_BRIGHTNESS; + pixels[2].g = 0; + pixels[2].b = 0; + break; + case 5: + pixels[2].r = LED_BRIGHTNESS; + pixels[2].g = 0; + pixels[2].b = LED_BRIGHTNESS; + break; + case 6: + pixels[2].r = 0; + pixels[2].g = LED_BRIGHTNESS; + pixels[2].b = LED_BRIGHTNESS; + break; + case 7: + pixels[2].r = LED_BRIGHTNESS; + pixels[2].g = LED_BRIGHTNESS; + pixels[2].b = 0; + break; + default: + pixels[2].r = 0; + pixels[2].g = 0; + pixels[2].b = 0; + break; + } + if (old_led_data.layer != led_data.layer || old_led_data.indicators != led_data.indicators) { + zmk_rgb_underglow_central_send(); + } +#else + // leds for peripheral(right) side + /*if (zmk_ble_active_profile_is_open()) { + pixels[0].r = LED_BRIGHTNESS * last_ble_state[0]; + pixels[0].g = 0; + pixels[0].b = 0; + pixels[1].r = LED_BRIGHTNESS * last_ble_state[0]; + pixels[1].g = 0; + pixels[1].b = 0; + pixels[2].r = LED_BRIGHTNESS * last_ble_state[0]; + pixels[2].g = 0; + pixels[2].b = 0; + if (state.animation_step > 3) { + last_ble_state[0] = !last_ble_state[0]; + state.animation_step = 0; + } + state.animation_step++; + } else */ + if (!zmk_split_bt_peripheral_is_connected()) { + pixels[0].r = LED_BRIGHTNESS * last_ble_state[1]; + pixels[0].g = 0; + pixels[0].b = 0; + pixels[1].r = LED_BRIGHTNESS * last_ble_state[1]; + pixels[1].g = 0; + pixels[1].b = 0; + pixels[2].r = LED_BRIGHTNESS * last_ble_state[1]; + pixels[2].g = 0; + pixels[2].b = 0; + if (state.animation_step > 14) { + last_ble_state[1] = !last_ble_state[1]; + state.animation_step = 0; + } + state.animation_step++; + } else { + // set first led as LED_NUMLOCK + pixels[2].r = (led_data.indicators & ZMK_LED_NUMLOCK_BIT) * LED_BRIGHTNESS; + pixels[2].g = (led_data.indicators & ZMK_LED_NUMLOCK_BIT) * LED_BRIGHTNESS; + pixels[2].b = (led_data.indicators & ZMK_LED_NUMLOCK_BIT) * LED_BRIGHTNESS; + // set second led as scroll Lock + pixels[1].r = (led_data.indicators & ZMK_LED_SCROLLLOCK_BIT) * LED_BRIGHTNESS; + pixels[1].g = (led_data.indicators & ZMK_LED_SCROLLLOCK_BIT) * LED_BRIGHTNESS; + pixels[1].b = (led_data.indicators & ZMK_LED_SCROLLLOCK_BIT) * LED_BRIGHTNESS; + // set third led as layer + switch (led_data.layer) { + case 0: + pixels[0].r = 0; + pixels[0].g = 0; + pixels[0].b = 0; + break; + case 1: + pixels[0].r = LED_BRIGHTNESS; + pixels[0].g = LED_BRIGHTNESS; + pixels[0].b = LED_BRIGHTNESS; + break; + case 2: + pixels[0].r = 0; + pixels[0].g = 0; + pixels[0].b = LED_BRIGHTNESS; + break; + case 3: + pixels[0].r = 0; + pixels[0].g = LED_BRIGHTNESS; + pixels[0].b = 0; + break; + case 4: + pixels[0].r = LED_BRIGHTNESS; + pixels[0].g = 0; + pixels[0].b = 0; + break; + case 5: + pixels[0].r = LED_BRIGHTNESS; + pixels[0].g = 0; + pixels[0].b = LED_BRIGHTNESS; + break; + case 6: + pixels[0].r = 0; + pixels[0].g = LED_BRIGHTNESS; + pixels[0].b = LED_BRIGHTNESS; + break; + case 7: + pixels[0].r = LED_BRIGHTNESS; + pixels[0].g = LED_BRIGHTNESS; + pixels[0].b = 0; + break; + default: + pixels[0].r = 0; + pixels[0].g = 0; + pixels[0].b = 0; + break; + } + } +#endif +} + +static void zmk_rgb_underglow_effect_test() { + triggered = true; + struct led_rgb rgb; + rgb.r = 0; + rgb.g = 0; + rgb.b = 0; + + for (int i = 0; i < STRIP_NUM_PIXELS; i++) { + struct zmk_led_hsb hsb = state.color; + hsb.h = state.animation_step; + + pixels[i] = hsb_to_rgb(hsb_scale_min_max(hsb)); + } + if (state.animation_step < (HUE_MAX * 3)) { + struct zmk_led_hsb hsb = state.color; + hsb.h = state.animation_step; + rgb.r = 0; + + pixels[0] = rgb; + pixels[1] = rgb; + pixels[2] = hsb_to_rgb(hsb_scale_min_max(hsb)); + } + if (state.animation_step < (HUE_MAX * 2)) { + struct zmk_led_hsb hsb = state.color; + hsb.h = state.animation_step - HUE_MAX; + rgb.r = 0; + rgb.g = 0; + rgb.b = 0; + pixels[0] = rgb; + pixels[1] = hsb_to_rgb(hsb_scale_min_max(hsb)); + pixels[2] = rgb; + } + if (state.animation_step < HUE_MAX) { + struct zmk_led_hsb hsb = state.color; + hsb.h = state.animation_step; + rgb.r = 0; + rgb.g = 0; + rgb.b = 0; + pixels[0] = hsb_to_rgb(hsb_scale_min_max(hsb)); + pixels[1] = rgb; + pixels[2] = rgb; + } + + state.animation_step += 20; + if (state.animation_step > (HUE_MAX * 3)) { + + rgb.r = 255; + rgb.g = 255; + rgb.b = 255; + for (int i = 0; i < STRIP_NUM_PIXELS; i++) + pixels[i] = rgb; + } +} + +static void zmk_rgb_underglow_effect_battery() { + uint8_t soc = zmk_battery_state_of_charge(); + struct led_rgb rgb; + if (soc > 80) { + rgb.r = 0; + rgb.g = 255; + rgb.b = 0; + } else if (soc > 50 && soc < 80) { + rgb.r = 255; + rgb.g = 255; + rgb.b = 0; + } else if (soc > 20 && soc < 51) { + rgb.r = 255; + rgb.g = 140; + rgb.b = 0; + } else { + rgb.r = 255; + rgb.g = 0; + rgb.b = 0; + } + for (int i = 0; i < STRIP_NUM_PIXELS; i++) { + pixels[i] = rgb; + } +} + static void zmk_rgb_underglow_tick(struct k_work *work) { switch (state.current_effect) { case UNDERGLOW_EFFECT_SOLID: @@ -182,6 +505,15 @@ static void zmk_rgb_underglow_tick(struct k_work *work) { case UNDERGLOW_EFFECT_SWIRL: zmk_rgb_underglow_effect_swirl(); break; + case UNDERGLOW_EFFECT_KINESIS: + zmk_rgb_underglow_effect_kinesis(); + break; + case UNDERGLOW_EFFECT_BATTERY: + zmk_rgb_underglow_effect_battery(); + break; + case UNDERGLOW_EFFECT_TEST: + zmk_rgb_underglow_effect_test(); + break; } led_strip_update_rgb(led_strip, pixels, STRIP_NUM_PIXELS); @@ -229,6 +561,15 @@ static void zmk_rgb_underglow_save_state_work() { static struct k_work_delayable underglow_save_work; #endif +int zmk_rgb_underglow_save_state() { +#if IS_ENABLED(CONFIG_SETTINGS) + int ret = k_work_reschedule(&underglow_save_work, K_MSEC(CONFIG_ZMK_SETTINGS_SAVE_DEBOUNCE)); + return MIN(ret, 0); +#else + return 0; +#endif +} + static int zmk_rgb_underglow_init(const struct device *_arg) { led_strip = device_get_binding(STRIP_LABEL); if (led_strip) { @@ -265,30 +606,22 @@ static int zmk_rgb_underglow_init(const struct device *_arg) { LOG_ERR("Failed to register the ext_power settings handler (err %d)", err); return err; } - + led_data.indicators = 0; k_work_init_delayable(&underglow_save_work, zmk_rgb_underglow_save_state_work); settings_load_subtree("rgb/underglow"); #endif -#if IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_USB) - state.on = zmk_usb_is_powered(); +#if ZMK_BLE_IS_CENTRAL + k_work_init_delayable(&led_update_work, zmk_rgb_underglow_central_send); #endif - if (state.on) { - k_timer_start(&underglow_tick, K_NO_WAIT, K_MSEC(50)); - } - - return 0; -} - -int zmk_rgb_underglow_save_state() { -#if IS_ENABLED(CONFIG_SETTINGS) - int ret = k_work_reschedule(&underglow_save_work, K_MSEC(CONFIG_ZMK_SETTINGS_SAVE_DEBOUNCE)); - return MIN(ret, 0); -#else + zmk_rgb_underglow_save_state(); + k_work_submit(&underglow_work); + zmk_rgb_underglow_off(); + zmk_rgb_underglow_on(); + triggered = false; return 0; -#endif } int zmk_rgb_underglow_get_state(bool *on_off) { @@ -462,15 +795,12 @@ static int rgb_underglow_auto_state(bool *prev_state, bool new_state) { if (state.on == new_state) { return 0; } - if (new_state) { - state.on = *prev_state; - *prev_state = false; + state.on = new_state && *prev_state; + *prev_state = !new_state; + if (state.on) return zmk_rgb_underglow_on(); - } else { - state.on = false; - *prev_state = true; + else return zmk_rgb_underglow_off(); - } } static int rgb_underglow_event_listener(const zmk_event_t *eh) { @@ -485,11 +815,29 @@ static int rgb_underglow_event_listener(const zmk_event_t *eh) { #if IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_AUTO_OFF_USB) if (as_zmk_usb_conn_state_changed(eh)) { + led_data.indicators = zmk_led_indicators_get_current_flags(); + led_data.layer = zmk_keymap_highest_layer_active(); + int err = zmk_split_bt_update_led(&led_data); + if (err) { + LOG_ERR("send failed (err %d)", err); + } static bool prev_state = false; return rgb_underglow_auto_state(&prev_state, zmk_usb_is_powered()); } #endif +#if ZMK_BLE_IS_CENTRAL + if (as_zmk_split_peripheral_status_changed(eh)) { + LOG_DBG("event called"); + const struct zmk_split_peripheral_status_changed *ev; + ev = as_zmk_split_peripheral_status_changed(eh); + if (ev->connected) + return k_work_reschedule(&led_update_work, K_MSEC(2500)); + else + return k_work_cancel_delayable(&led_update_work); + } +#endif + return -ENOTSUP; } @@ -505,4 +853,8 @@ ZMK_SUBSCRIPTION(rgb_underglow, zmk_activity_state_changed); ZMK_SUBSCRIPTION(rgb_underglow, zmk_usb_conn_state_changed); #endif +#if ZMK_BLE_IS_CENTRAL +ZMK_SUBSCRIPTION(rgb_underglow, zmk_split_peripheral_status_changed); +#endif + SYS_INIT(zmk_rgb_underglow_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); diff --git a/app/src/split/bluetooth/Kconfig b/app/src/split/bluetooth/Kconfig index f6976cffcb6..45a85f72d4f 100644 --- a/app/src/split/bluetooth/Kconfig +++ b/app/src/split/bluetooth/Kconfig @@ -29,6 +29,20 @@ 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 +config ZMK_BLE_SPLIT_CENTRAL_SPLIT_LED_STACK_SIZE + int "BLE split central LED write thread stack size" + default 512 +config ZMK_BLE_SPLIT_CENTRAL_SPLIT_LED_QUEUE_SIZE + int "Max number of led update events to queue to send to the peripheral(s)" + default 5 + +config ZMK_BLE_SPLIT_CENTRAL_SPLIT_BL_STACK_SIZE + int "BLE split central LED write thread stack size" + default 512 +config ZMK_BLE_SPLIT_CENTRAL_SPLIT_BL_QUEUE_SIZE + int "Max number of led update events to queue to send to the peripheral(s)" + default 5 + endif # ZMK_SPLIT_ROLE_CENTRAL if !ZMK_SPLIT_ROLE_CENTRAL diff --git a/app/src/split/bluetooth/central.c b/app/src/split/bluetooth/central.c index e94a59aece8..82cb7bdf749 100644 --- a/app/src/split/bluetooth/central.c +++ b/app/src/split/bluetooth/central.c @@ -20,10 +20,13 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #include #include #include +#include +#include #include #include #include #include +#include #include static int start_scan(void); @@ -43,6 +46,8 @@ struct peripheral_slot { struct bt_gatt_subscribe_params subscribe_params; struct bt_gatt_discover_params sub_discover_params; uint16_t run_behavior_handle; + uint16_t update_led_handle; + uint16_t update_bl_handle; uint8_t position_state[POSITION_STATE_DATA_LEN]; uint8_t changed_positions[POSITION_STATE_DATA_LEN]; }; @@ -126,6 +131,8 @@ int release_peripheral_slot(int index) { // Clean up previously discovered handles; slot->subscribe_params.value_handle = 0; slot->run_behavior_handle = 0; + slot->update_led_handle = 0; + slot->update_bl_handle = 0; return 0; } @@ -265,9 +272,18 @@ static uint8_t split_central_chrc_discovery_func(struct bt_conn *conn, BT_UUID_DECLARE_128(ZMK_SPLIT_BT_CHAR_RUN_BEHAVIOR_UUID))) { LOG_DBG("Found run behavior handle"); slot->run_behavior_handle = bt_gatt_attr_value_handle(attr); + } else if (!bt_uuid_cmp(((struct bt_gatt_chrc *)attr->user_data)->uuid, + BT_UUID_DECLARE_128(ZMK_SPLIT_BT_CHAR_UPDATE_LED_UUID))) { + LOG_DBG("Found update led handle"); + slot->update_led_handle = bt_gatt_attr_value_handle(attr); + } else if (!bt_uuid_cmp(((struct bt_gatt_chrc *)attr->user_data)->uuid, + BT_UUID_DECLARE_128(ZMK_SPLIT_BT_CHAR_UPDATE_BL_UUID))) { + LOG_DBG("Found update bl handle"); + slot->update_bl_handle = bt_gatt_attr_value_handle(attr); } - bool subscribed = (slot->run_behavior_handle && slot->subscribe_params.value_handle); + bool subscribed = (slot->update_bl_handle && slot->update_led_handle && + slot->run_behavior_handle && slot->subscribe_params.value_handle); return subscribed ? BT_GATT_ITER_STOP : BT_GATT_ITER_CONTINUE; } @@ -481,6 +497,8 @@ static void split_central_connected(struct bt_conn *conn, uint8_t conn_err) { confirm_peripheral_slot_conn(conn); split_central_process_connection(conn); + ZMK_EVENT_RAISE(new_zmk_split_peripheral_status_changed( + (struct zmk_split_peripheral_status_changed){.connected = true})); } static void split_central_disconnected(struct bt_conn *conn, uint8_t reason) { @@ -490,7 +508,8 @@ static void split_central_disconnected(struct bt_conn *conn, uint8_t reason) { bt_addr_le_to_str(bt_conn_get_dst(conn), addr, sizeof(addr)); LOG_DBG("Disconnected: %s (reason %d)", log_strdup(addr), reason); - + ZMK_EVENT_RAISE(new_zmk_split_peripheral_status_changed( + (struct zmk_split_peripheral_status_changed){.connected = false})); err = release_peripheral_slot_for_conn(conn); if (err < 0) { @@ -501,8 +520,7 @@ static void split_central_disconnected(struct bt_conn *conn, uint8_t reason) { } static struct bt_conn_cb conn_callbacks = { - .connected = split_central_connected, - .disconnected = split_central_disconnected, + .connected = split_central_connected, .disconnected = split_central_disconnected, }; K_THREAD_STACK_DEFINE(split_central_split_run_q_stack, @@ -586,10 +604,133 @@ int zmk_split_bt_invoke_behavior(uint8_t source, struct zmk_behavior_binding *bi return split_bt_invoke_behavior_payload(wrapper); } +K_THREAD_STACK_DEFINE(split_central_split_led_q_stack, + CONFIG_ZMK_BLE_SPLIT_CENTRAL_SPLIT_LED_STACK_SIZE); + +struct k_work_q split_central_split_led_q; + +K_MSGQ_DEFINE(zmk_split_central_split_led_msgq, sizeof(struct zmk_split_update_led_data), + CONFIG_ZMK_BLE_SPLIT_CENTRAL_SPLIT_LED_QUEUE_SIZE, 2); + +void split_central_split_led_callback(struct k_work *work) { + struct zmk_split_update_led_data payload; + + while (k_msgq_get(&zmk_split_central_split_led_msgq, &payload, K_NO_WAIT) == 0) { + if (peripherals[0].state != PERIPHERAL_SLOT_STATE_CONNECTED) { + LOG_ERR("Source not connected"); + continue; + } + + int err = bt_gatt_write_without_response(peripherals[0].conn, + peripherals[0].update_led_handle, &payload, + sizeof(struct zmk_split_update_led_data), true); + + if (err) { + LOG_ERR("Failed to write the update led characteristic (err %d)", err); + } + } +} + +K_WORK_DEFINE(split_central_split_led_work, split_central_split_led_callback); + +static int split_bt_update_led_payload(struct zmk_split_update_led_data payload) { + LOG_DBG(""); + + int err = k_msgq_put(&zmk_split_central_split_led_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_update_led_data discarded_report; + k_msgq_get(&zmk_split_central_split_led_msgq, &discarded_report, K_NO_WAIT); + return split_bt_update_led_payload(payload); + } + default: + LOG_WRN("Failed to queue behavior to send (%d)", err); + return err; + } + } + + k_work_submit_to_queue(&split_central_split_led_q, &split_central_split_led_work); + + return 0; +}; + +int zmk_split_bt_update_led(struct zmk_periph_led *periph) { + struct zmk_split_update_led_data payload = {.layer = periph->layer, + .indicators = periph->indicators}; + + return split_bt_update_led_payload(payload); +} + +K_THREAD_STACK_DEFINE(split_central_split_bl_q_stack, + CONFIG_ZMK_BLE_SPLIT_CENTRAL_SPLIT_BL_STACK_SIZE); + +struct k_work_q split_central_split_bl_q; + +K_MSGQ_DEFINE(zmk_split_central_split_bl_msgq, sizeof(struct zmk_split_update_bl_data), + CONFIG_ZMK_BLE_SPLIT_CENTRAL_SPLIT_BL_QUEUE_SIZE, 2); + +void split_central_split_bl_callback(struct k_work *work) { + struct zmk_split_update_bl_data payload; + + while (k_msgq_get(&zmk_split_central_split_bl_msgq, &payload, K_NO_WAIT) == 0) { + if (peripherals[0].state != PERIPHERAL_SLOT_STATE_CONNECTED) { + LOG_ERR("Source not connected"); + continue; + } + + int err = + bt_gatt_write_without_response(peripherals[0].conn, peripherals[0].update_bl_handle, + &payload, sizeof(struct zmk_split_update_bl_data), true); + + if (err) { + LOG_ERR("Failed to write the update bl characteristic (err %d)", err); + } + } +} + +K_WORK_DEFINE(split_central_split_bl_work, split_central_split_bl_callback); + +static int split_bt_update_bl_payload(struct zmk_split_update_bl_data payload) { + LOG_DBG(""); + + int err = k_msgq_put(&zmk_split_central_split_bl_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_update_bl_data discarded_report; + k_msgq_get(&zmk_split_central_split_bl_msgq, &discarded_report, K_NO_WAIT); + return split_bt_update_bl_payload(payload); + } + default: + LOG_WRN("Failed to queue behavior to send (%d)", err); + return err; + } + } + + k_work_submit_to_queue(&split_central_split_bl_q, &split_central_split_bl_work); + + return 0; +}; + +int zmk_split_bt_update_bl(struct backlight_state *periph) { + struct zmk_split_update_bl_data payload = {.brightness = periph->brightness, .on = periph->on}; + + return split_bt_update_bl_payload(payload); +} + int zmk_split_bt_central_init(const struct device *_arg) { k_work_queue_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, NULL); + k_work_queue_start(&split_central_split_led_q, split_central_split_led_q_stack, + K_THREAD_STACK_SIZEOF(split_central_split_led_q_stack), + CONFIG_ZMK_BLE_THREAD_PRIORITY, NULL); + k_work_queue_start(&split_central_split_bl_q, split_central_split_bl_q_stack, + K_THREAD_STACK_SIZEOF(split_central_split_bl_q_stack), + CONFIG_ZMK_BLE_THREAD_PRIORITY, NULL); bt_conn_cb_register(&conn_callbacks); return start_scan(); diff --git a/app/src/split/bluetooth/service.c b/app/src/split/bluetooth/service.c index 5da5401d682..4511bbf8567 100644 --- a/app/src/split/bluetooth/service.c +++ b/app/src/split/bluetooth/service.c @@ -20,6 +20,8 @@ LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); #include #include #include +#include +#include #define POS_STATE_LEN 16 @@ -27,6 +29,8 @@ 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 struct zmk_split_update_led_data update_led_data; +static struct zmk_split_update_bl_data update_bl_data; 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) { @@ -79,6 +83,48 @@ static ssize_t split_svc_run_behavior(struct bt_conn *conn, const struct bt_gatt return len; } +static ssize_t split_svc_update_led(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_update_led_data *payload = attrs->user_data; + uint16_t end_addr = offset + len; + + LOG_DBG("offset %d len %d", offset, len); + + 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_update_led_data))) { + struct zmk_periph_led periph = {.layer = payload->layer, .indicators = payload->indicators}; + zmk_rgb_underglow_set_periph(periph); + LOG_DBG("Update leds with params %d and %d", periph.layer, periph.indicators); + } + + return len; +} + +static ssize_t split_svc_update_bl(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_update_bl_data *payload = attrs->user_data; + uint16_t end_addr = offset + len; + + LOG_DBG("offset %d len %d", offset, len); + + 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_update_bl_data))) { + struct backlight_state periph = {.brightness = payload->brightness, .on = payload->on}; + zmk_backlight_update_vals(periph); + LOG_DBG("Update leds with params %d and %d", periph.on, periph.brightness); + } + + 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)); @@ -97,6 +143,12 @@ BT_GATT_SERVICE_DEFINE( 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_CHARACTERISTIC(BT_UUID_DECLARE_128(ZMK_SPLIT_BT_CHAR_UPDATE_LED_UUID), + BT_GATT_CHRC_WRITE_WITHOUT_RESP, BT_GATT_PERM_WRITE_ENCRYPT, NULL, + split_svc_update_led, &update_led_data), + BT_GATT_CHARACTERISTIC(BT_UUID_DECLARE_128(ZMK_SPLIT_BT_CHAR_UPDATE_BL_UUID), + BT_GATT_CHRC_WRITE_WITHOUT_RESP, BT_GATT_PERM_WRITE_ENCRYPT, NULL, + split_svc_update_bl, &update_bl_data), BT_GATT_DESCRIPTOR(BT_UUID_NUM_OF_DIGITALS, BT_GATT_PERM_READ, split_svc_num_of_positions, NULL, &num_of_positions), );