From 282ca51d1a6799920f9d70d49a8fd1294a92627b Mon Sep 17 00:00:00 2001 From: Mauricio Walters Date: Mon, 1 Jul 2024 16:03:14 -0700 Subject: [PATCH 1/2] feat(underglow): support command for displaying board statuses --- .../bindings/zmk,underglow-indicators.yaml | 26 ++ app/include/dt-bindings/zmk/rgb.h | 2 + app/include/zmk/ble.h | 1 + app/include/zmk/endpoints.h | 2 + app/include/zmk/rgb_underglow.h | 1 + app/src/behaviors/behavior_rgb_underglow.c | 2 + app/src/ble.c | 19 + app/src/endpoints.c | 4 + app/src/rgb_underglow.c | 384 +++++++++++++++++- 9 files changed, 425 insertions(+), 16 deletions(-) create mode 100644 app/dts/bindings/zmk,underglow-indicators.yaml diff --git a/app/dts/bindings/zmk,underglow-indicators.yaml b/app/dts/bindings/zmk,underglow-indicators.yaml new file mode 100644 index 00000000000..677304c7c49 --- /dev/null +++ b/app/dts/bindings/zmk,underglow-indicators.yaml @@ -0,0 +1,26 @@ +# Copyright (c) 2020, The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: Underglow indicators + +compatible: "zmk,underglow-indicators" + +properties: + bat-lhs: + type: array + bat-rhs: + type: array + num-lock: + type: int + caps-lock: + type: int + scroll-lock: + type: int + layer-state: + type: array + ble-profiles: + type: array + usb-state: + type: int + output-fallback: + type: int diff --git a/app/include/dt-bindings/zmk/rgb.h b/app/include/dt-bindings/zmk/rgb.h index c1a8008273c..657ac2fd528 100644 --- a/app/include/dt-bindings/zmk/rgb.h +++ b/app/include/dt-bindings/zmk/rgb.h @@ -19,6 +19,7 @@ #define RGB_EFR_CMD 12 #define RGB_EFS_CMD 13 #define RGB_COLOR_HSB_CMD 14 +#define RGB_STATUS_CMD 15 #define RGB_TOG RGB_TOG_CMD 0 #define RGB_ON RGB_ON_CMD 0 @@ -33,6 +34,7 @@ #define RGB_SPD RGB_SPD_CMD 0 #define RGB_EFF RGB_EFF_CMD 0 #define RGB_EFR RGB_EFR_CMD 0 +#define RGB_STATUS RGB_STATUS_CMD 0 #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/ble.h b/app/include/zmk/ble.h index c44c5e16869..9326f2b2192 100644 --- a/app/include/zmk/ble.h +++ b/app/include/zmk/ble.h @@ -35,6 +35,7 @@ struct bt_conn *zmk_ble_active_profile_conn(void); bool zmk_ble_active_profile_is_open(void); bool zmk_ble_active_profile_is_connected(void); +int8_t zmk_ble_profile_status(uint8_t index); char *zmk_ble_active_profile_name(void); int zmk_ble_unpair_all(void); diff --git a/app/include/zmk/endpoints.h b/app/include/zmk/endpoints.h index a2ef3181a58..2a2a4286ff9 100644 --- a/app/include/zmk/endpoints.h +++ b/app/include/zmk/endpoints.h @@ -75,3 +75,5 @@ int zmk_endpoints_send_mouse_report(); #endif // IS_ENABLED(CONFIG_ZMK_POINTING) void zmk_endpoints_clear_current(void); + +bool zmk_endpoints_preferred_transport_is_active(); diff --git a/app/include/zmk/rgb_underglow.h b/app/include/zmk/rgb_underglow.h index be0ef252290..0c45e1c68f7 100644 --- a/app/include/zmk/rgb_underglow.h +++ b/app/include/zmk/rgb_underglow.h @@ -27,3 +27,4 @@ int zmk_rgb_underglow_change_sat(int direction); int zmk_rgb_underglow_change_brt(int direction); int zmk_rgb_underglow_change_spd(int direction); int zmk_rgb_underglow_set_hsb(struct zmk_led_hsb color); +int zmk_rgb_underglow_status(void); diff --git a/app/src/behaviors/behavior_rgb_underglow.c b/app/src/behaviors/behavior_rgb_underglow.c index c37e5217c73..88df9518c36 100644 --- a/app/src/behaviors/behavior_rgb_underglow.c +++ b/app/src/behaviors/behavior_rgb_underglow.c @@ -244,6 +244,8 @@ static int on_keymap_binding_pressed(struct zmk_behavior_binding *binding, return zmk_rgb_underglow_set_hsb((struct zmk_led_hsb){.h = (binding->param2 >> 16) & 0xFFFF, .s = (binding->param2 >> 8) & 0xFF, .b = binding->param2 & 0xFF}); + case RGB_STATUS_CMD: + return zmk_rgb_underglow_status(); } return -ENOTSUP; diff --git a/app/src/ble.c b/app/src/ble.c index e63c63b0cca..d6c07f270b1 100644 --- a/app/src/ble.c +++ b/app/src/ble.c @@ -129,6 +129,25 @@ bool zmk_ble_active_profile_is_connected(void) { return info.state == BT_CONN_STATE_CONNECTED; } +int8_t zmk_ble_profile_status(uint8_t index) { + if (index >= ZMK_BLE_PROFILE_COUNT) + return -1; + + bt_addr_le_t *addr = &profiles[index].peer; + struct bt_conn *conn; + int result; + if (!bt_addr_le_cmp(addr, BT_ADDR_LE_ANY)) { + result = 0; // disconnected + } else if ((conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, addr)) == NULL) { + result = 1; // paired + } else { + result = 2; // connected + bt_conn_unref(conn); + } + + return result; +} + #define CHECKED_ADV_STOP() \ err = bt_le_adv_stop(); \ advertising_status = ZMK_ADV_NONE; \ diff --git a/app/src/endpoints.c b/app/src/endpoints.c index ae0e5e7fd88..c772aaa19f8 100644 --- a/app/src/endpoints.c +++ b/app/src/endpoints.c @@ -362,6 +362,10 @@ static int endpoint_listener(const zmk_event_t *eh) { return 0; } +bool zmk_endpoints_preferred_transport_is_active(void) { + return preferred_transport == get_selected_transport(); +} + ZMK_LISTENER(endpoint_listener, endpoint_listener); #if IS_ENABLED(CONFIG_ZMK_USB) ZMK_SUBSCRIPTION(endpoint_listener, zmk_usb_conn_state_changed); diff --git a/app/src/rgb_underglow.c b/app/src/rgb_underglow.c index 3453fb44e0d..4cbe33d2687 100644 --- a/app/src/rgb_underglow.c +++ b/app/src/rgb_underglow.c @@ -20,10 +20,16 @@ #include #include +#include +#include +#include +#include +#include #include #include #include #include +#include #include LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL); @@ -58,10 +64,18 @@ struct rgb_underglow_state { uint8_t current_effect; uint16_t animation_step; bool on; + bool is_status_indicators_active; }; static const struct device *led_strip; +/** + * Updated when `zmk_status_update_pixels` is called + * + * This should contain rgb(0, 0, 0) for every pixel except for the + * ones defined in underglow_indicators + */ +static struct led_rgb status_pixels[STRIP_NUM_PIXELS]; static struct led_rgb pixels[STRIP_NUM_PIXELS]; static struct rgb_underglow_state state; @@ -70,6 +84,327 @@ static struct rgb_underglow_state state; static const struct device *const ext_power = DEVICE_DT_GET(DT_INST(0, zmk_ext_power_generic)); #endif +void zmk_rgb_set_ext_power(void); + +// region Underglow Status Indicators + +/* + * This function updates the LED strip based on the current underglow effects + * and any active status indicators. We blend the colors of any existing underglow (`pixels`) + * with the colors of the status (`status_pixels`) until they are applied completely. + */ +static struct led_rgb *zmk_led_blend_status_pixels(int blend) { + static struct led_rgb led_buffer[STRIP_NUM_PIXELS]; + int bat0 = zmk_battery_state_of_charge(); + + // fast path: no status indicators, battery level OK + if (blend == 0 && bat0 >= 20) { + return pixels; + } + + if (blend == 0) { + for (int i = 0; i < STRIP_NUM_PIXELS; i++) { + led_buffer[i] = pixels[i]; + } + } else if (blend >= 256) { + // Blending steps maxed. Use status_pixels + for (int i = 0; i < STRIP_NUM_PIXELS; i++) { + led_buffer[i] = status_pixels[i]; + } + } else if (blend < 256) { + // Blend status_pixels into pixels + uint16_t blend_l = blend; + uint16_t blend_r = 256 - blend; + for (int i = 0; i < STRIP_NUM_PIXELS; i++) { + led_buffer[i].r = + ((status_pixels[i].r * blend_l) >> 8) + ((pixels[i].r * blend_r) >> 8); + led_buffer[i].g = + ((status_pixels[i].g * blend_l) >> 8) + ((pixels[i].g * blend_r) >> 8); + led_buffer[i].b = + ((status_pixels[i].b * blend_l) >> 8) + ((pixels[i].b * blend_r) >> 8); + } + } + + return led_buffer; +} +#if defined(DT_N_S_underglow_indicators_EXISTS) + +#define UNDERGLOW_INDICATORS DT_PATH(underglow_indicators) +#define HAS_INDICATORS_BAT_LHS DT_NODE_HAS_PROP(UNDERGLOW_INDICATORS, bat_lhs) +#define HAS_INDICATORS_BAT_RHS DT_NODE_HAS_PROP(UNDERGLOW_INDICATORS, bat_rhs) +#define HAS_INDICATORS_NUM_LOCK DT_NODE_HAS_PROP(UNDERGLOW_INDICATORS, num_lock) +#define HAS_INDICATORS_CAPS_LOCK DT_NODE_HAS_PROP(UNDERGLOW_INDICATORS, caps_lock) +#define HAS_INDICATORS_SCROLL_LOCK DT_NODE_HAS_PROP(UNDERGLOW_INDICATORS, scroll_lock) +#define HAS_INDICATORS_LAYER_STATE DT_NODE_HAS_PROP(UNDERGLOW_INDICATORS, layer_state) +#define HAS_INDICATORS_BLE_PROFILES DT_NODE_HAS_PROP(UNDERGLOW_INDICATORS, ble_profiles) +#define HAS_INDICATORS_USB_STATE DT_NODE_HAS_PROP(UNDERGLOW_INDICATORS, usb_state) +#define HAS_INDICATORS_OUTPUT_FALLBACK DT_NODE_HAS_PROP(UNDERGLOW_INDICATORS, output_fallback) + +#define BATTERY_LEVEL_HIGH 40 +#define BATTERY_LEVEL_MEDIUM 20 + +#define HEXRGB(R, G, B) \ + ((struct led_rgb){.r = (CONFIG_ZMK_RGB_UNDERGLOW_BRT_MAX * (R)) / 0xff, \ + .g = (CONFIG_ZMK_RGB_UNDERGLOW_BRT_MAX * (G)) / 0xff, \ + .b = (CONFIG_ZMK_RGB_UNDERGLOW_BRT_MAX * (B)) / 0xff}) +static const struct led_rgb status_color_batt_low = HEXRGB(0xff, 0x00, 0x00); // red +static const struct led_rgb status_color_batt_med = HEXRGB(0xff, 0xff, 0x00); // yellow +static const struct led_rgb status_color_batt_high = HEXRGB(0x00, 0xff, 0x00); // green +static const struct led_rgb status_color_hid = HEXRGB(0xff, 0x00, 0x00); // red +static const struct led_rgb status_color_layer = HEXRGB(0xff, 0x00, 0xff); // magenta +static const struct led_rgb status_color_ble_active = HEXRGB(0xff, 0xff, 0xff); // white +static const struct led_rgb status_color_ble_connected = HEXRGB(0x00, 0xff, 0x68); // dull-green +static const struct led_rgb status_color_ble_paired = HEXRGB(0xff, 0x00, 0x00); // red +static const struct led_rgb status_color_ble_unused = HEXRGB(0x6b, 0x1f, 0xce); // lilac +static const struct led_rgb status_color_usb_active = HEXRGB(0xff, 0xff, 0xff); // white +static const struct led_rgb status_color_usb_connected = HEXRGB(0x00, 0xff, 0x68); // dull-green +static const struct led_rgb status_color_usb_powered = HEXRGB(0xff, 0x00, 0x00); // red +static const struct led_rgb status_color_usb_disconnected = HEXRGB(0x6b, 0x1f, 0xce); // lilac +static const struct led_rgb status_color_output_fallback = HEXRGB(0xff, 0x00, 0x00); // red + +static uint16_t status_animation_step; + +/** + * Update a buffer to reflect a given battery level using the provided indicators + */ +static void zmk_status_batt_level(struct led_rgb *led_buffer, int bat_level, + const uint8_t *indicators, int indicator_count) { + struct led_rgb bat_colour; + + if (bat_level > BATTERY_LEVEL_HIGH) { + bat_colour = status_color_batt_high; + } else if (bat_level > BATTERY_LEVEL_MEDIUM) { + bat_colour = status_color_batt_med; + } else { + bat_colour = status_color_batt_low; + } + + for (int i = 0; i < indicator_count; i++) { + int min_level = (i * 100) / (indicator_count - 1); + led_buffer[indicators[i]] = + bat_level >= min_level ? bat_colour : (struct led_rgb){.r = 0, .g = 0, .b = 0}; + } +} + +/** + * Update a buffer with the appropriate pixels set for the battery levels on both the lhs and rhs + */ +static void zmk_status_batt_pixels(struct led_rgb *buffer) { +#if HAS_INDICATORS_BAT_LHS + static const uint8_t bat_lhs_indicators[] = DT_PROP(UNDERGLOW_INDICATORS, bat_lhs); + static const int bat_lhs_indicator_count = DT_PROP_LEN(UNDERGLOW_INDICATORS, bat_lhs); + + zmk_status_batt_level(buffer, zmk_battery_state_of_charge(), bat_lhs_indicators, + bat_lhs_indicator_count); +#endif + +#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING) && HAS_INDICATORS_BAT_RHS + static const uint8_t bat_rhs_indicators[] = DT_PROP(UNDERGLOW_INDICATORS, bat_rhs); + static const int bat_rhs_indicator_count = DT_PROP_LEN(UNDERGLOW_INDICATORS, bat_rhs); + + uint8_t peripheral_level = 0; + int rc = zmk_split_get_peripheral_battery_level(0, &peripheral_level); + + if (rc == 0) { + zmk_status_batt_level(buffer, zmk_battery_state_of_charge(), bat_rhs_indicators, + bat_rhs_indicator_count); + } else if (rc == -ENOTCONN) { + // Set all pixels to red + for (int i = 0; i < bat_rhs_indicator_count; i++) { + buffer[bat_rhs_indicators[i]] = HEXRGB(0xff, 0x00, 0x00); // red + } + } else if (rc == -EINVAL) { + LOG_ERR("Invalid peripheral index requested for battery level read: 0"); + } +#endif +} + +static void zmk_status_hid_pixels(struct led_rgb *buffer) { +#if IS_ENABLED(CONFIG_ZMK_HID_INDICATORS) + zmk_hid_indicators_t led_flags = zmk_hid_indicators_get_current_profile(); + +#if HAS_INDICATORS_NUM_LOCK + if (led_flags & BIT(0)) + buffer[DT_PROP(UNDERGLOW_INDICATORS, num_lock)] = status_color_hid; +#endif + +#if HAS_INDICATORS_CAPS_LOCK + if (led_flags & BIT(1)) + buffer[DT_PROP(UNDERGLOW_INDICATORS, caps_lock)] = status_color_hid; +#endif + +#if HAS_INDICATORS_SCROLL_LOCK + if (led_flags & BIT(2)) + buffer[DT_PROP(UNDERGLOW_INDICATORS, scroll_lock)] = status_color_hid; +#endif + +#endif +} + +static void zmk_status_layer_pixels(struct led_rgb *buffer) { +#if HAS_INDICATORS_LAYER_STATE + static const uint8_t layer_state_indicators[] = DT_PROP(UNDERGLOW_INDICATORS, ble_profiles); + static const int layer_state_indicator_count = DT_PROP_LEN(UNDERGLOW_INDICATORS, ble_profiles); + + for (uint8_t i = 0; i < layer_state_indicator_count; i++) { + if (zmk_keymap_layer_active(i)) + buffer[layer_state_indicators[i]] = status_color_layer; + } +#endif +} + +static void zmk_status_ble_profile_pixels(struct led_rgb *buffer) { +#if HAS_INDICATORS_BLE_PROFILES + static const uint8_t ble_profile_indicators[] = DT_PROP(UNDERGLOW_INDICATORS, ble_profiles); + static const int ble_profile_indicator_count = DT_PROP_LEN(UNDERGLOW_INDICATORS, ble_profiles); + + struct zmk_endpoint_instance active_endpoint = zmk_endpoints_selected(); + int active_ble_profile_index = zmk_ble_active_profile_index(); + for (uint8_t i = 0; i < MIN(ZMK_BLE_PROFILE_COUNT, ble_profile_indicator_count); i++) { + int8_t status = zmk_ble_profile_status(i); + int ble_pixel_addr = ble_profile_indicators[i]; + if (status == 2 && active_endpoint.transport == ZMK_TRANSPORT_BLE && + active_ble_profile_index == i) { // connected AND active + buffer[ble_pixel_addr] = status_color_ble_active; + } else if (status == 2) { // connected + buffer[ble_pixel_addr] = status_color_ble_connected; + } else if (status == 1) { // paired + buffer[ble_pixel_addr] = status_color_ble_paired; + } else if (status == 0) { // unused + buffer[ble_pixel_addr] = status_color_ble_unused; + } + } +#endif +} + +static void zmk_status_usb_state_pixel(struct led_rgb *buffer) { +#if HAS_INDICATORS_USB_STATE + static int const pixel_address = DT_PROP(UNDERGLOW_INDICATORS, usb_state); + + struct zmk_endpoint_instance active_endpoint = zmk_endpoints_selected(); + enum zmk_usb_conn_state usb_state = zmk_usb_get_conn_state(); + + if (usb_state == ZMK_USB_CONN_HID && active_endpoint.transport == ZMK_TRANSPORT_USB) { + buffer[pixel_address] = status_color_usb_active; + } else if (usb_state == ZMK_USB_CONN_HID) { + buffer[pixel_address] = status_color_usb_connected; + } else if (usb_state == ZMK_USB_CONN_POWERED) { + buffer[pixel_address] = status_color_usb_powered; + } else if (usb_state == ZMK_USB_CONN_NONE) { + buffer[pixel_address] = status_color_usb_disconnected; + } +#endif +} + +static void zmk_status_output_fallback_pixel(struct led_rgb *buffer) { +#if HAS_INDICATORS_OUTPUT_FALLBACK + if (!zmk_endpoints_preferred_transport_is_active()) + buffer[DT_PROP(UNDERGLOW_INDICATORS, output_fallback)] = status_color_output_fallback; +#endif +} + +/** + * Update `status_pixels` with all the status colors + */ +static void zmk_status_update_pixels(void) { + static struct led_rgb status_pixels_buffer[STRIP_NUM_PIXELS]; + + // All pixels not used for indicating status should be blank + for (int i = 0; i < STRIP_NUM_PIXELS; i++) { + status_pixels_buffer[i] = (struct led_rgb){.r = 0, .g = 0, .b = 0}; + } + + // Update the buffer with the status indicators + zmk_status_batt_pixels(status_pixels_buffer); + zmk_status_hid_pixels(status_pixels_buffer); + zmk_status_layer_pixels(status_pixels_buffer); + zmk_status_ble_profile_pixels(status_pixels_buffer); + zmk_status_usb_state_pixel(status_pixels_buffer); + zmk_status_output_fallback_pixel(status_pixels_buffer); + + // Commit the buffer to status_pixels + for (int i = 0; i < STRIP_NUM_PIXELS; i++) { + status_pixels[i] = status_pixels_buffer[i]; + } +} + +/** + * Generates the blend step (out of 256) based off the current animation step + */ +static int zmk_status_blend_step(void) { + int16_t blend = 256; + if (status_animation_step < (500 / 25)) { + blend = ((status_animation_step * 256) / (500 / 25)); + } else if (status_animation_step > (8000 / 25)) { + blend = 256 - (((status_animation_step - (8000 / 25)) * 256) / (2000 / 25)); + } + if (blend < 0) + blend = 0; + if (blend > 256) + blend = 256; + + return blend; +} + +static void zmk_status_write_pixels_work(struct k_work *work) { + zmk_status_update_pixels(); + + struct led_rgb *foo = zmk_led_blend_status_pixels(zmk_status_blend_step()); + int err = led_strip_update_rgb(led_strip, foo, STRIP_NUM_PIXELS); + if (err < 0) { + LOG_ERR("Failed to update the RGB strip (%d)", err); + } + + if (!state.is_status_indicators_active) { + zmk_rgb_set_ext_power(); + } +} +K_WORK_DEFINE(underglow_write_work, zmk_status_write_pixels_work); + +static void zmk_rgb_underglow_status_update(struct k_timer *timer); +K_TIMER_DEFINE(underglow_status_update_timer, zmk_rgb_underglow_status_update, NULL); +static void zmk_rgb_underglow_status_update(struct k_timer *timer) { + if (!state.is_status_indicators_active) + return; + status_animation_step++; + if (status_animation_step > (10000 / 25)) { + state.is_status_indicators_active = false; + k_timer_stop(&underglow_status_update_timer); + } + if (!k_work_is_pending(&underglow_write_work)) + k_work_submit(&underglow_write_work); +} + +int zmk_rgb_underglow_status(void) { + if (!state.is_status_indicators_active) { + status_animation_step = 0; + } else { + if (status_animation_step > (500 / 25)) { + status_animation_step = 500 / 25; + } + } + state.is_status_indicators_active = true; + + zmk_status_update_pixels(); + struct led_rgb *foo = zmk_led_blend_status_pixels(zmk_status_blend_step()); + int err = led_strip_update_rgb(led_strip, foo, STRIP_NUM_PIXELS); + if (err < 0) { + LOG_ERR("Failed to update the RGB strip (%d)", err); + } + + zmk_rgb_set_ext_power(); + + k_timer_start(&underglow_status_update_timer, K_NO_WAIT, K_MSEC(25)); + + return 0; +} +#else +int zmk_rgb_underglow_status(void) { return 0; } +static int zmk_status_blend_step(void) { return 0; } +static void zmk_status_update_pixels(void){}; +#endif +// endregion + static struct zmk_led_hsb hsb_scale_min_max(struct zmk_led_hsb hsb) { hsb.b = CONFIG_ZMK_RGB_UNDERGLOW_BRT_MIN + (CONFIG_ZMK_RGB_UNDERGLOW_BRT_MAX - CONFIG_ZMK_RGB_UNDERGLOW_BRT_MIN) * hsb.b / BRT_MAX; @@ -191,7 +526,15 @@ static void zmk_rgb_underglow_tick(struct k_work *work) { break; } - int err = led_strip_update_rgb(led_strip, pixels, STRIP_NUM_PIXELS); + struct led_rgb *write_pixels; + if (state.is_status_indicators_active == true) { + zmk_status_update_pixels(); + write_pixels = zmk_led_blend_status_pixels(zmk_status_blend_step()); + } else { + write_pixels = pixels; + } + + int err = led_strip_update_rgb(led_strip, write_pixels, STRIP_NUM_PIXELS); if (err < 0) { LOG_ERR("Failed to update the RGB strip (%d)", err); } @@ -297,22 +640,39 @@ int zmk_rgb_underglow_get_state(bool *on_off) { return 0; } -int zmk_rgb_underglow_on(void) { - if (!led_strip) - return -ENODEV; - +void zmk_rgb_set_ext_power(void) { #if IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_EXT_POWER) - if (ext_power != NULL) { + if (ext_power == NULL) + return; + int c_power = ext_power_get(ext_power); + if (c_power < 0) { + LOG_ERR("Unable to examine EXT_POWER: %d", c_power); + c_power = 0; + } + int desired_state = state.on || state.is_status_indicators_active; + if (desired_state && !c_power) { int rc = ext_power_enable(ext_power); if (rc != 0) { LOG_ERR("Unable to enable EXT_POWER: %d", rc); } + } else if (!desired_state && c_power) { + int rc = ext_power_disable(ext_power); + if (rc != 0) { + LOG_ERR("Unable to disable EXT_POWER: %d", rc); + } } #endif +} + +int zmk_rgb_underglow_on(void) { + if (!led_strip) + return -ENODEV; state.on = true; + zmk_rgb_set_ext_power(); + state.animation_step = 0; - k_timer_start(&underglow_tick, K_NO_WAIT, K_MSEC(50)); + k_timer_start(&underglow_tick, K_NO_WAIT, K_MSEC(25)); return zmk_rgb_underglow_save_state(); } @@ -331,19 +691,11 @@ int zmk_rgb_underglow_off(void) { if (!led_strip) return -ENODEV; -#if IS_ENABLED(CONFIG_ZMK_RGB_UNDERGLOW_EXT_POWER) - if (ext_power != NULL) { - int rc = ext_power_disable(ext_power); - if (rc != 0) { - LOG_ERR("Unable to disable EXT_POWER: %d", rc); - } - } -#endif - k_work_submit_to_queue(zmk_workqueue_lowprio_work_q(), &underglow_off_work); k_timer_stop(&underglow_tick); state.on = false; + zmk_rgb_set_ext_power(); return zmk_rgb_underglow_save_state(); } From 8047bf8248274783a555f676a1aae2be68e83f4c Mon Sep 17 00:00:00 2001 From: Mauricio Walters Date: Tue, 2 Jul 2024 12:42:28 -0700 Subject: [PATCH 2/2] feat(status-indicators): support n-number of peripherals --- .../bindings/zmk,underglow-indicators.yaml | 10 +-- app/src/rgb_underglow.c | 69 ++++++++++++------- 2 files changed, 52 insertions(+), 27 deletions(-) diff --git a/app/dts/bindings/zmk,underglow-indicators.yaml b/app/dts/bindings/zmk,underglow-indicators.yaml index 677304c7c49..a6532c553cf 100644 --- a/app/dts/bindings/zmk,underglow-indicators.yaml +++ b/app/dts/bindings/zmk,underglow-indicators.yaml @@ -5,11 +5,13 @@ description: Underglow indicators compatible: "zmk,underglow-indicators" +child-binding: + child-binding: + properties: + peripheral-battery: + type: array + properties: - bat-lhs: - type: array - bat-rhs: - type: array num-lock: type: int caps-lock: diff --git a/app/src/rgb_underglow.c b/app/src/rgb_underglow.c index 4cbe33d2687..b967b939fad 100644 --- a/app/src/rgb_underglow.c +++ b/app/src/rgb_underglow.c @@ -130,8 +130,9 @@ static struct led_rgb *zmk_led_blend_status_pixels(int blend) { #if defined(DT_N_S_underglow_indicators_EXISTS) #define UNDERGLOW_INDICATORS DT_PATH(underglow_indicators) -#define HAS_INDICATORS_BAT_LHS DT_NODE_HAS_PROP(UNDERGLOW_INDICATORS, bat_lhs) -#define HAS_INDICATORS_BAT_RHS DT_NODE_HAS_PROP(UNDERGLOW_INDICATORS, bat_rhs) +#define UNDERGLOW_INDICATORS_PERIPHERALS DT_PATH(underglow_indicators, peripherals) + +#define HAS_INDICATORS_PERIPHERALS DT_NODE_EXISTS(UNDERGLOW_INDICATORS_PERIPHERALS) #define HAS_INDICATORS_NUM_LOCK DT_NODE_HAS_PROP(UNDERGLOW_INDICATORS, num_lock) #define HAS_INDICATORS_CAPS_LOCK DT_NODE_HAS_PROP(UNDERGLOW_INDICATORS, caps_lock) #define HAS_INDICATORS_SCROLL_LOCK DT_NODE_HAS_PROP(UNDERGLOW_INDICATORS, scroll_lock) @@ -150,6 +151,7 @@ static struct led_rgb *zmk_led_blend_status_pixels(int blend) { static const struct led_rgb status_color_batt_low = HEXRGB(0xff, 0x00, 0x00); // red static const struct led_rgb status_color_batt_med = HEXRGB(0xff, 0xff, 0x00); // yellow static const struct led_rgb status_color_batt_high = HEXRGB(0x00, 0xff, 0x00); // green +static const struct led_rgb status_color_batt_not_conn = HEXRGB(0xff, 0x00, 0x00); // red static const struct led_rgb status_color_hid = HEXRGB(0xff, 0x00, 0x00); // red static const struct led_rgb status_color_layer = HEXRGB(0xff, 0x00, 0xff); // magenta static const struct led_rgb status_color_ble_active = HEXRGB(0xff, 0xff, 0xff); // white @@ -190,31 +192,52 @@ static void zmk_status_batt_level(struct led_rgb *led_buffer, int bat_level, * Update a buffer with the appropriate pixels set for the battery levels on both the lhs and rhs */ static void zmk_status_batt_pixels(struct led_rgb *buffer) { -#if HAS_INDICATORS_BAT_LHS - static const uint8_t bat_lhs_indicators[] = DT_PROP(UNDERGLOW_INDICATORS, bat_lhs); - static const int bat_lhs_indicator_count = DT_PROP_LEN(UNDERGLOW_INDICATORS, bat_lhs); - - zmk_status_batt_level(buffer, zmk_battery_state_of_charge(), bat_lhs_indicators, - bat_lhs_indicator_count); -#endif +#if HAS_INDICATORS_PERIPHERALS +#define ZMK_STATUS_PERIPHERAL_PLUS_ONE(n) 1 + +#define ZMK_STATUS_PERIPHERAL_COUNT \ + (DT_FOREACH_CHILD(UNDERGLOW_INDICATORS_PERIPHERALS, ZMK_STATUS_PERIPHERAL_PLUS_ONE) 0) +#define ZMK_STATUS_PERIPHERAL_LED_COUNT(node_id) DT_PROP_LEN(node_id, peripheral_battery), +#define ZMK_STATUS_PERIPHERAL_LED_LIST(node_id, prop, idx) DT_PROP_BY_IDX(node_id, prop, idx), +#define ZMK_STATUS_PERIPHERAL_LEDS(node_id) \ + DT_FOREACH_PROP_ELEM(node_id, peripheral_battery, ZMK_STATUS_PERIPHERAL_LED_LIST) + + // Array with all the led-addresses for each peripheral + static const int peripheral_led_count[] = { + DT_FOREACH_CHILD(UNDERGLOW_INDICATORS_PERIPHERALS, ZMK_STATUS_PERIPHERAL_LED_COUNT)}; + + static const uint8_t peripheral_led_addresses[] = { + DT_FOREACH_CHILD(UNDERGLOW_INDICATORS_PERIPHERALS, ZMK_STATUS_PERIPHERAL_LEDS)}; + + for (int i = 0; i < ZMK_STATUS_PERIPHERAL_COUNT; i++) { + int offset = 0; + for (int j = 0; j < i; j++) { + offset += peripheral_led_count[j]; + } -#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING) && HAS_INDICATORS_BAT_RHS - static const uint8_t bat_rhs_indicators[] = DT_PROP(UNDERGLOW_INDICATORS, bat_rhs); - static const int bat_rhs_indicator_count = DT_PROP_LEN(UNDERGLOW_INDICATORS, bat_rhs); + const uint8_t *addresses = &peripheral_led_addresses[offset]; + int address_count = peripheral_led_count[i]; - uint8_t peripheral_level = 0; - int rc = zmk_split_get_peripheral_battery_level(0, &peripheral_level); + if (i == 0) { // Central peripheral + zmk_status_batt_level(buffer, zmk_battery_state_of_charge(), addresses, address_count); - if (rc == 0) { - zmk_status_batt_level(buffer, zmk_battery_state_of_charge(), bat_rhs_indicators, - bat_rhs_indicator_count); - } else if (rc == -ENOTCONN) { - // Set all pixels to red - for (int i = 0; i < bat_rhs_indicator_count; i++) { - buffer[bat_rhs_indicators[i]] = HEXRGB(0xff, 0x00, 0x00); // red + } else { + // Non-central peripherals require this config option to report battery level +#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING) + uint8_t peripheral_level = 0; + int rc = zmk_split_get_peripheral_battery_level(i - 1, &peripheral_level); + + if (rc == 0) { + zmk_status_batt_level(buffer, peripheral_level, addresses, address_count); + } else if (rc == -ENOTCONN) { + // Set all pixels to red + for (int j = 0; j < address_count; j++) { + buffer[addresses[j]] = status_color_batt_not_conn; + } + } else if (rc == -EINVAL) { + LOG_ERR("Invalid peripheral index requested for battery level read: 0"); + } +#endif } - } else if (rc == -EINVAL) { - LOG_ERR("Invalid peripheral index requested for battery level read: 0"); } #endif }