From 7b937d18b81a8526081a83290e76a3772cf0c9f1 Mon Sep 17 00:00:00 2001 From: Chris Andreae Date: Sat, 28 May 2022 20:15:51 +0900 Subject: [PATCH 01/10] rgb_underglow: refresh more frequently for smoother RGB underglow --- app/src/rgb_underglow.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/rgb_underglow.c b/app/src/rgb_underglow.c index ddc0aef1f45..307f32a0365 100644 --- a/app/src/rgb_underglow.c +++ b/app/src/rgb_underglow.c @@ -280,7 +280,7 @@ static int zmk_rgb_underglow_init(const struct device *_arg) { #endif if (state.on) { - k_timer_start(&underglow_tick, K_NO_WAIT, K_MSEC(50)); + k_timer_start(&underglow_tick, K_NO_WAIT, K_MSEC(25)); } return 0; @@ -318,7 +318,7 @@ int zmk_rgb_underglow_on(void) { state.on = true; 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(); } From a0bfd928532fcc741614fc4f3fd157fa5ace8401 Mon Sep 17 00:00:00 2001 From: Donald Gordon Date: Wed, 13 Jul 2022 22:39:44 +1200 Subject: [PATCH 02/10] RGB underglow status support Adds Glove80's status indicator using RGB underglow support. Requires ZMK PR#999 and PR#1243. The underglow status is able to show layer state, battery levels, caps/num/scroll-lock, BLE and USB state. The underglow positions selected for each of these indicators is configured using the new devicetree node zmk,underglow-indicators, which takes an array of integer LED positions for each feature. --- .../bindings/zmk,underglow-indicators.yaml | 35 +++ 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 | 17 + app/src/endpoints.c | 4 + app/src/rgb_underglow.c | 292 ++++++++++++++++-- 9 files changed, 336 insertions(+), 20 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..d5cdde80a9a --- /dev/null +++ b/app/dts/bindings/zmk,underglow-indicators.yaml @@ -0,0 +1,35 @@ +# Copyright (c) 2020, The ZMK Contributors +# SPDX-License-Identifier: MIT + +description: Underglow indicators + +compatible: "zmk,underglow-indicators" + +properties: + bat-lhs: + type: array + required: true + bat-rhs: + type: array + required: true + capslock: + type: int + required: true + numlock: + type: int + required: true + scrolllock: + type: int + required: true + layer-state: + type: array + required: true + ble-state: + type: array + required: true + usb-state: + type: int + required: true + output-fallback: + type: int + required: true 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 773323c1cf9..a0bd588f3c7 100644 --- a/app/include/zmk/ble.h +++ b/app/include/zmk/ble.h @@ -33,6 +33,7 @@ bt_addr_le_t *zmk_ble_active_profile_addr(void); bool zmk_ble_active_profile_is_open(void); bool zmk_ble_active_profile_is_connected(void); char *zmk_ble_active_profile_name(void); +int8_t zmk_ble_profile_status(uint8_t index); int zmk_ble_unpair_all(void); diff --git a/app/include/zmk/endpoints.h b/app/include/zmk/endpoints.h index 70240183e36..4a2245f5185 100644 --- a/app/include/zmk/endpoints.h +++ b/app/include/zmk/endpoints.h @@ -68,6 +68,8 @@ int zmk_endpoints_toggle_transport(void); */ struct zmk_endpoint_instance zmk_endpoints_selected(void); +bool zmk_endpoints_preferred_transport_is_active(); + int zmk_endpoints_send_report(uint16_t usage_page); #if IS_ENABLED(CONFIG_ZMK_MOUSE) 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 7a478eb781f..36140d19297 100644 --- a/app/src/behaviors/behavior_rgb_underglow.c +++ b/app/src/behaviors/behavior_rgb_underglow.c @@ -131,6 +131,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 e0f34307672..a40fc9cb652 100644 --- a/app/src/ble.c +++ b/app/src/ble.c @@ -129,6 +129,23 @@ 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 827f2dcd814..1d2da70ea22 100644 --- a/app/src/endpoints.c +++ b/app/src/endpoints.c @@ -302,6 +302,10 @@ static struct zmk_endpoint_instance get_selected_instance(void) { return instance; } +bool zmk_endpoints_preferred_transport_is_active() { + return preferred_transport == get_selected_transport(); +} + static int zmk_endpoints_init(const struct device *_arg) { #if IS_ENABLED(CONFIG_SETTINGS) settings_subsys_init(); diff --git a/app/src/rgb_underglow.c b/app/src/rgb_underglow.c index 307f32a0365..cab91a31d05 100644 --- a/app/src/rgb_underglow.c +++ b/app/src/rgb_underglow.c @@ -12,6 +12,13 @@ #include #include +#include +#include +#include +#include +#include +#include + #include #include @@ -20,7 +27,6 @@ #include #include -#include #include #include #include @@ -58,11 +64,14 @@ struct rgb_underglow_state { uint8_t current_effect; uint16_t animation_step; bool on; + bool status_active; + uint16_t status_animation_step; }; static const struct device *led_strip; static struct led_rgb pixels[STRIP_NUM_PIXELS]; +static struct led_rgb status_pixels[STRIP_NUM_PIXELS]; static struct rgb_underglow_state state; @@ -70,6 +79,8 @@ 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); + 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; @@ -175,6 +186,199 @@ static void zmk_rgb_underglow_effect_swirl(void) { state.animation_step = state.animation_step % HUE_MAX; } +static int zmk_led_generate_status(void); + +static void zmk_led_write_pixels(void) { + static struct led_rgb led_buffer[STRIP_NUM_PIXELS]; + int bat0 = zmk_battery_state_of_charge(); + int blend = 0; + if (state.status_active) { + blend = zmk_led_generate_status(); + } + + // fast path: no status indicators, battery level OK + if (blend == 0 && bat0 >= 20) { + led_strip_update_rgb(led_strip, pixels, STRIP_NUM_PIXELS); + return; + } + + if (blend == 0) { + for (int i = 0; i < STRIP_NUM_PIXELS; i++) { + led_buffer[i] = pixels[i]; + } + } else if (blend >= 256) { + for (int i = 0; i < STRIP_NUM_PIXELS; i++) { + led_buffer[i] = status_pixels[i]; + } + } else if (blend < 256) { + 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); + } + } + + int err = led_strip_update_rgb(led_strip, led_buffer, STRIP_NUM_PIXELS); + if (err < 0) { + LOG_ERR("Failed to update the RGB strip (%d)", err); + } +} + +#define UNDERGLOW_INDICATORS DT_PATH(underglow_indicators) + +#if defined(DT_N_S_underglow_indicators_EXISTS) +#define UNDERGLOW_INDICATORS_ENABLED 1 +#else +#define UNDERGLOW_INDICATORS_ENABLED 0 +#endif + +#if !UNDERGLOW_INDICATORS_ENABLED +static int zmk_led_generate_status(void) { return 0; } +#else + +const uint8_t underglow_layer_state[] = DT_PROP(UNDERGLOW_INDICATORS, layer_state); +const uint8_t underglow_ble_state[] = DT_PROP(UNDERGLOW_INDICATORS, ble_state); +const uint8_t underglow_bat_lhs[] = DT_PROP(UNDERGLOW_INDICATORS, bat_lhs); +const uint8_t underglow_bat_rhs[] = DT_PROP(UNDERGLOW_INDICATORS, bat_rhs); + +#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 \ + }) +const struct led_rgb red = HEXRGB(0xff, 0x00, 0x00); +const struct led_rgb yellow = HEXRGB(0xff, 0xff, 0x00); +const struct led_rgb green = HEXRGB(0x00, 0xff, 0x00); +const struct led_rgb dull_green = HEXRGB(0x00, 0xff, 0x68); +const struct led_rgb magenta = HEXRGB(0xff, 0x00, 0xff); +const struct led_rgb white = HEXRGB(0xff, 0xff, 0xff); +const struct led_rgb lilac = HEXRGB(0x6b, 0x1f, 0xce); + +static void zmk_led_battery_level(int bat_level, const uint8_t *addresses, size_t addresses_len) { + struct led_rgb bat_colour; + + if (bat_level > 40) { + bat_colour = green; + } else if (bat_level > 20) { + bat_colour = yellow; + } else { + bat_colour = red; + } + + // originally, six levels, 0 .. 100 + + for (int i = 0; i < addresses_len; i++) { + int min_level = (i * 100) / (addresses_len - 1); + if (bat_level >= min_level) { + status_pixels[addresses[i]] = bat_colour; + } + } +} + +static void zmk_led_fill(struct led_rgb color, const uint8_t *addresses, size_t addresses_len) { + for (int i = 0; i < addresses_len; i++) { + status_pixels[addresses[i]] = color; + } +} + +#define ZMK_LED_NUMLOCK_BIT BIT(0) +#define ZMK_LED_CAPSLOCK_BIT BIT(1) +#define ZMK_LED_SCROLLLOCK_BIT BIT(2) + +static int zmk_led_generate_status(void) { + for (int i = 0; i < STRIP_NUM_PIXELS; i++) { + status_pixels[i] = (struct led_rgb){r : 0, g : 0, b : 0}; + } + + // BATTERY STATUS + zmk_led_battery_level(zmk_battery_state_of_charge(), underglow_bat_lhs, + DT_PROP_LEN(UNDERGLOW_INDICATORS, bat_lhs)); + +#if IS_ENABLED(CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING) + uint8_t peripheral_level = 0; + int rc = zmk_split_get_peripheral_battery_level(0, &peripheral_level); + + if (rc == 0) { + zmk_led_battery_level(peripheral_level, underglow_bat_rhs, + DT_PROP_LEN(UNDERGLOW_INDICATORS, bat_rhs)); + } else if (rc == -ENOTCONN) { + zmk_led_fill(red, underglow_bat_rhs, DT_PROP_LEN(UNDERGLOW_INDICATORS, bat_rhs)); + } else if (rc == -EINVAL) { + LOG_ERR("Invalid peripheral index requested for battery level read: 0"); + } +#endif + + // CAPSLOCK/NUMLOCK/SCROLLOCK STATUS + zmk_hid_indicators_t led_flags = zmk_hid_indicators_get_current_profile(); + + if (led_flags & ZMK_LED_CAPSLOCK_BIT) + status_pixels[DT_PROP(UNDERGLOW_INDICATORS, capslock)] = red; + if (led_flags & ZMK_LED_NUMLOCK_BIT) + status_pixels[DT_PROP(UNDERGLOW_INDICATORS, numlock)] = red; + if (led_flags & ZMK_LED_SCROLLLOCK_BIT) + status_pixels[DT_PROP(UNDERGLOW_INDICATORS, scrolllock)] = red; + + // LAYER STATUS + for (uint8_t i = 0; i < DT_PROP_LEN(UNDERGLOW_INDICATORS, layer_state); i++) { + if (zmk_keymap_layer_active(i)) + status_pixels[underglow_layer_state[i]] = magenta; + } + + struct zmk_endpoint_instance active_endpoint = zmk_endpoints_selected(); + + if (!zmk_endpoints_preferred_transport_is_active()) + status_pixels[DT_PROP(UNDERGLOW_INDICATORS, output_fallback)] = red; + + int active_ble_profile_index = zmk_ble_active_profile_index(); + for (uint8_t i = 0; + i < MIN(ZMK_BLE_PROFILE_COUNT, DT_PROP_LEN(UNDERGLOW_INDICATORS, ble_state)); i++) { + int8_t status = zmk_ble_profile_status(i); + int ble_pixel = underglow_ble_state[i]; + if (status == 2 && active_endpoint.transport == ZMK_TRANSPORT_BLE && + active_ble_profile_index == i) { // connected AND active + status_pixels[ble_pixel] = white; + } else if (status == 2) { // connected + status_pixels[ble_pixel] = dull_green; + } else if (status == 1) { // paired + status_pixels[ble_pixel] = red; + } else if (status == 0) { // unused + status_pixels[ble_pixel] = lilac; + } + } + + 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) { // connected AND active + status_pixels[DT_PROP(UNDERGLOW_INDICATORS, usb_state)] = white; + } else if (usb_state == ZMK_USB_CONN_HID) { // connected + status_pixels[DT_PROP(UNDERGLOW_INDICATORS, usb_state)] = dull_green; + } else if (usb_state == ZMK_USB_CONN_POWERED) { // powered + status_pixels[DT_PROP(UNDERGLOW_INDICATORS, usb_state)] = red; + } else if (usb_state == ZMK_USB_CONN_NONE) { // disconnected + status_pixels[DT_PROP(UNDERGLOW_INDICATORS, usb_state)] = lilac; + } + + int16_t blend = 256; + if (state.status_animation_step < (500 / 25)) { + blend = ((state.status_animation_step * 256) / (500 / 25)); + } else if (state.status_animation_step > (8000 / 25)) { + blend = 256 - (((state.status_animation_step - (8000 / 25)) * 256) / (2000 / 25)); + } + if (blend < 0) + blend = 0; + if (blend > 256) + blend = 256; + + return blend; +} +#endif // underglow_indicators exists + static void zmk_rgb_underglow_tick(struct k_work *work) { switch (state.current_effect) { case UNDERGLOW_EFFECT_SOLID: @@ -191,10 +395,7 @@ static void zmk_rgb_underglow_tick(struct k_work *work) { break; } - int err = led_strip_update_rgb(led_strip, pixels, STRIP_NUM_PIXELS); - if (err < 0) { - LOG_ERR("Failed to update the RGB strip (%d)", err); - } + zmk_led_write_pixels(); } K_WORK_DEFINE(underglow_tick_work, zmk_rgb_underglow_tick); @@ -303,20 +504,37 @@ 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.status_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(25)); @@ -328,7 +546,7 @@ static void zmk_rgb_underglow_off_handler(struct k_work *work) { pixels[i] = (struct led_rgb){r : 0, g : 0, b : 0}; } - led_strip_update_rgb(led_strip, pixels, STRIP_NUM_PIXELS); + zmk_led_write_pixels(); } K_WORK_DEFINE(underglow_off_work, zmk_rgb_underglow_off_handler); @@ -337,19 +555,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(); } @@ -380,6 +590,48 @@ int zmk_rgb_underglow_toggle(void) { return state.on ? zmk_rgb_underglow_off() : zmk_rgb_underglow_on(); } +static void zmk_led_write_pixels_work(struct k_work *work); +static void zmk_rgb_underglow_status_update(struct k_timer *timer); + +K_WORK_DEFINE(underglow_write_work, zmk_led_write_pixels_work); +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.status_active) + return; + state.status_animation_step++; + if (state.status_animation_step > (10000 / 25)) { + state.status_active = false; + k_timer_stop(&underglow_status_update_timer); + } + if (!k_work_is_pending(&underglow_write_work)) + k_work_submit(&underglow_write_work); +} + +static void zmk_led_write_pixels_work(struct k_work *work) { + zmk_led_write_pixels(); + if (!state.status_active) { + zmk_rgb_set_ext_power(); + } +} + +int zmk_rgb_underglow_status(void) { + if (!state.status_active) { + state.status_animation_step = 0; + } else { + if (state.status_animation_step > (500 / 25)) { + state.status_animation_step = 500 / 25; + } + } + state.status_active = true; + zmk_led_write_pixels(); + zmk_rgb_set_ext_power(); + + k_timer_start(&underglow_status_update_timer, K_NO_WAIT, K_MSEC(25)); + + return 0; +} + int zmk_rgb_underglow_set_hsb(struct zmk_led_hsb color) { if (color.h > HUE_MAX || color.s > SAT_MAX || color.b > BRT_MAX) { return -ENOTSUP; From 25038424369c48d12a2a55de93af94052e3b44d3 Mon Sep 17 00:00:00 2001 From: Donald Gordon Date: Wed, 28 Sep 2022 21:11:04 +0900 Subject: [PATCH 03/10] RGB underglow: dim or disable when battery low Disable underglow when battery below 10%, halve brightness when battery below 20% --- app/src/rgb_underglow.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/app/src/rgb_underglow.c b/app/src/rgb_underglow.c index cab91a31d05..bf884629f60 100644 --- a/app/src/rgb_underglow.c +++ b/app/src/rgb_underglow.c @@ -192,6 +192,7 @@ static void zmk_led_write_pixels(void) { static struct led_rgb led_buffer[STRIP_NUM_PIXELS]; int bat0 = zmk_battery_state_of_charge(); int blend = 0; + int reset_ext_power = 0; if (state.status_active) { blend = zmk_led_generate_status(); } @@ -201,6 +202,18 @@ static void zmk_led_write_pixels(void) { led_strip_update_rgb(led_strip, pixels, STRIP_NUM_PIXELS); return; } + // battery below minimum charge + if (bat0 < 10) { + memset(pixels, 0, sizeof(struct led_rgb) * STRIP_NUM_PIXELS); + if (state.on) { + int c_power = ext_power_get(ext_power); + if (c_power && !state.status_active) { + // power is on, RGB underglow is on, but battery is too low + state.on = false; + reset_ext_power = true; + } + } + } if (blend == 0) { for (int i = 0; i < STRIP_NUM_PIXELS; i++) { @@ -223,10 +236,23 @@ static void zmk_led_write_pixels(void) { } } + // battery below 20%, reduce LED brightness + if (bat0 < 20) { + for (int i = 0; i < STRIP_NUM_PIXELS; i++) { + led_buffer[i].r = led_buffer[i].r >> 1; + led_buffer[i].g = led_buffer[i].g >> 1; + led_buffer[i].b = led_buffer[i].b >> 1; + } + } + int err = led_strip_update_rgb(led_strip, led_buffer, STRIP_NUM_PIXELS); if (err < 0) { LOG_ERR("Failed to update the RGB strip (%d)", err); } + + if (reset_ext_power) { + zmk_rgb_set_ext_power(); + } } #define UNDERGLOW_INDICATORS DT_PATH(underglow_indicators) @@ -514,6 +540,12 @@ void zmk_rgb_set_ext_power(void) { c_power = 0; } int desired_state = state.on || state.status_active; + // force power off, when battery low (<10%) + if (state.on && !state.status_active) { + if (zmk_battery_state_of_charge() < 10) { + desired_state = false; + } + } if (desired_state && !c_power) { int rc = ext_power_enable(ext_power); if (rc != 0) { From 4cab1917506661f75fcc548a8cac2f051a197174 Mon Sep 17 00:00:00 2001 From: Donald Gordon Date: Sat, 21 Jan 2023 15:33:21 +1300 Subject: [PATCH 04/10] ext_power: add Kconfig for initial ext_power state Adds a Kconfig setting for the default EXT_POWER status at initialization time. Previously it was always initialized to on if no saved value was present. --- app/Kconfig | 9 +++++++++ app/src/ext_power_generic.c | 18 +++++++++++++----- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/app/Kconfig b/app/Kconfig index 4b52052aa88..68a2c92a4f9 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -405,6 +405,15 @@ config ZMK_EXT_POWER bool "Enable support to control external power output" default y +if ZMK_EXT_POWER + +config ZMK_EXT_POWER_START + bool "Enable external power output by default" + default y + +#ZMK_EXT_POWER +endif + #Power Management endmenu diff --git a/app/src/ext_power_generic.c b/app/src/ext_power_generic.c index 2586f436852..9b179fcbb0a 100644 --- a/app/src/ext_power_generic.c +++ b/app/src/ext_power_generic.c @@ -145,18 +145,26 @@ static int ext_power_generic_init(const struct device *dev) { k_work_init_delayable(&ext_power_save_work, ext_power_save_state_work); - // Set default value (on) if settings isn't set + // Set default value if settings isn't set settings_load_subtree("ext_power"); if (!data->settings_init) { - data->status = true; + data->status = IS_ENABLED(CONFIG_ZMK_EXT_POWER_START); k_work_schedule(&ext_power_save_work, K_NO_WAIT); - ext_power_enable(dev); + if (data->status) { + ext_power_enable(dev); + } else { + ext_power_disable(dev); + } } #else - // Default to the ext_power being open when no settings - ext_power_enable(dev); + // If no settings, set ext_power on/off + if (IS_ENABLED(CONFIG_ZMK_EXT_POWER_START)) { + ext_power_enable(dev); + } else { + ext_power_disable(dev); + } #endif if (config->init_delay_ms) { From eaf2f31375a2257170c58f3f2c1966076db3e5b8 Mon Sep 17 00:00:00 2001 From: Chris Andreae Date: Wed, 15 Feb 2023 14:10:21 +0900 Subject: [PATCH 05/10] Include the serial number in the Bluetooth device name --- app/Kconfig | 10 +++++++ app/boards/arm/glove80/glove80_lh_defconfig | 2 ++ app/boards/arm/glove80/usb_serial_number.c | 31 +++++++++++++------- app/src/ble.c | 32 +++++++++++++++++++++ 4 files changed, 64 insertions(+), 11 deletions(-) diff --git a/app/Kconfig b/app/Kconfig index 68a2c92a4f9..07912d281c6 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -212,6 +212,16 @@ config ZMK_BLE_CLEAR_BONDS_ON_START bool "Configuration that clears all bond information from the keyboard on startup." default n +config ZMK_BLE_DEVICE_NAME_APPEND_SN + bool "Append the device serial number to the Bluetooth device name" + default n + select BT_DEVICE_NAME_DYNAMIC + +config ZMK_BLE_DEVICE_NAME_SN_CHARS + int "Number of hexadecimal digits of serial number to append to the BT device name" + default 6 + depends on ZMK_BLE_DEVICE_NAME_APPEND_SN + # HID GATT notifications sent this way are *not* picked up by Linux, and possibly others. config BT_GATT_NOTIFY_MULTIPLE default n diff --git a/app/boards/arm/glove80/glove80_lh_defconfig b/app/boards/arm/glove80/glove80_lh_defconfig index a93f27cd8f2..b454e8be1bf 100644 --- a/app/boards/arm/glove80/glove80_lh_defconfig +++ b/app/boards/arm/glove80/glove80_lh_defconfig @@ -16,6 +16,8 @@ CONFIG_USB_DEVICE_VID=0x16c0 CONFIG_USB_DEVICE_MANUFACTURER="MoErgo" CONFIG_USB_DEVICE_SN="moergo.com:GLV80-0123456789ABCDEF" +CONFIG_BT_DEVICE_NAME="Glove80" + CONFIG_BT_DIS_PNP_PID=0x27db CONFIG_BT_DIS_PNP_VID=0x16c0 CONFIG_BT_DIS_MANUF="MoErgo" diff --git a/app/boards/arm/glove80/usb_serial_number.c b/app/boards/arm/glove80/usb_serial_number.c index 44d7ee20363..6254cdc4d73 100644 --- a/app/boards/arm/glove80/usb_serial_number.c +++ b/app/boards/arm/glove80/usb_serial_number.c @@ -14,14 +14,15 @@ LOG_MODULE_DECLARE(usb_descriptor); int base16_encode(const uint8_t *data, int length, uint8_t *result, int bufSize); +void fill_serial_number(char *buf, int length); +/** + * nrf52840 hwinfo returns a 64-bit hardware id. Glove80 uses this as a + * serial number, encoded as base16 into the last 16 characters of the + * CONFIG_USB_DEVICE_SN template. If insufficient template space is + * available, instead return the static serial number string. + */ uint8_t *usb_update_sn_string_descriptor(void) { - /* - * nrf52840 hwinfo returns a 64-bit hardware id. Glove80 uses this as a - * serial number, encoded as base16 into the last 16 characters of the - * CONFIG_USB_DEVICE_SN template. If insufficient template space is - * available, instead return the static serial number string. - */ const uint8_t template_len = sizeof(CONFIG_USB_DEVICE_SN); const uint8_t sn_len = 16; @@ -33,17 +34,25 @@ uint8_t *usb_update_sn_string_descriptor(void) { static uint8_t serial[sizeof(CONFIG_USB_DEVICE_SN)]; strncpy(serial, CONFIG_USB_DEVICE_SN, template_len); + const uint8_t offset = template_len - sn_len - 1; + fill_serial_number(serial + offset, sn_len); + serial[template_len - 1] = '\0'; + + return serial; +} + +/** + * Writes the first `length` bytes of the device serial number into buf + */ +void fill_serial_number(char *buf, int length) { uint8_t hwid[8]; memset(hwid, 0, sizeof(hwid)); uint8_t hwlen = hwinfo_get_device_id(hwid, sizeof(hwid)); if (hwlen > 0) { - const uint8_t offset = template_len - sn_len - 1; - LOG_HEXDUMP_DBG(&hwid, sn_len, "Serial Number"); - base16_encode(hwid, hwlen, serial + offset, sn_len + 1); + LOG_HEXDUMP_DBG(&hwid, 16, "Serial Number"); + base16_encode(hwid, hwlen, buf, length); } - - return serial; } int base16_encode(const uint8_t *data, int length, uint8_t *result, int bufSize) { diff --git a/app/src/ble.c b/app/src/ble.c index a40fc9cb652..7f2b9913579 100644 --- a/app/src/ble.c +++ b/app/src/ble.c @@ -62,9 +62,32 @@ enum advertising_type { static struct zmk_ble_profile profiles[ZMK_BLE_PROFILE_COUNT]; static uint8_t active_profile; +#if IS_ENABLED(CONFIG_ZMK_BLE_DEVICE_NAME_APPEND_SN) + +static char bt_device_name[sizeof(CONFIG_BT_DEVICE_NAME) + CONFIG_ZMK_BLE_DEVICE_NAME_SN_CHARS + 1]; + +void fill_serial_number(char *buf, int length); + +// configure the BT device name by appending a serial number prefix to +// CONFIG_BT_DEVICE_NAME +void init_bt_device_name() { + strncpy(bt_device_name, CONFIG_BT_DEVICE_NAME, sizeof(bt_device_name)); + bt_device_name[sizeof(CONFIG_BT_DEVICE_NAME) - 1] = ' '; + fill_serial_number(&bt_device_name[sizeof(CONFIG_BT_DEVICE_NAME)], + CONFIG_ZMK_BLE_DEVICE_NAME_SN_CHARS); + bt_device_name[sizeof(bt_device_name) - 1] = '\0'; +} + +#define DEVICE_NAME bt_device_name +#define DEVICE_NAME_LEN (sizeof(bt_device_name) - 1) + +#else + #define DEVICE_NAME CONFIG_BT_DEVICE_NAME #define DEVICE_NAME_LEN (sizeof(DEVICE_NAME) - 1) +#endif + BUILD_ASSERT(DEVICE_NAME_LEN <= 16, "ERROR: BLE device name is too long. Max length: 16"); static const struct bt_data zmk_ble_ad[] = { @@ -647,6 +670,10 @@ static void zmk_ble_ready(int err) { } static int zmk_ble_init(const struct device *_arg) { +#if IS_ENABLED(CONFIG_ZMK_BLE_DEVICE_NAME_APPEND_SN) + init_bt_device_name(); +#endif + int err = bt_enable(NULL); if (err) { @@ -667,7 +694,12 @@ static int zmk_ble_init(const struct device *_arg) { settings_load_subtree("ble"); settings_load_subtree("bt"); +#endif +#if IS_ENABLED(CONFIG_ZMK_BLE_DEVICE_NAME_APPEND_SN) + if (strcmp(bt_get_name(), bt_device_name) != 0) { + bt_set_name(bt_device_name); + } #endif #if IS_ENABLED(CONFIG_ZMK_BLE_CLEAR_BONDS_ON_START) From 57951a77516f88acd15a696b34db094e69580468 Mon Sep 17 00:00:00 2001 From: Chris Andreae Date: Sat, 18 Sep 2021 20:38:30 +0900 Subject: [PATCH 06/10] Add build environment using Nix --- .gitattributes | 1 + README-NIX.md | 39 ++++ default.nix | 49 +++++ nix/cmake-shell.nix | 17 ++ nix/generate-pin.rb | 34 +++ nix/manifest.json | 292 +++++++++++++++++++++++++ nix/pinned-nixpkgs.json | 4 + nix/pinned-nixpkgs.nix | 17 ++ nix/update-manifest/default.nix | 11 + nix/update-manifest/update-manifest.sh | 39 ++++ nix/west-shell.nix | 50 +++++ nix/zephyr.nix | 37 ++++ nix/zmk.nix | 106 +++++++++ 13 files changed, 696 insertions(+) create mode 100644 README-NIX.md create mode 100644 default.nix create mode 100644 nix/cmake-shell.nix create mode 100755 nix/generate-pin.rb create mode 100644 nix/manifest.json create mode 100644 nix/pinned-nixpkgs.json create mode 100644 nix/pinned-nixpkgs.nix create mode 100644 nix/update-manifest/default.nix create mode 100755 nix/update-manifest/update-manifest.sh create mode 100644 nix/west-shell.nix create mode 100644 nix/zephyr.nix create mode 100644 nix/zmk.nix diff --git a/.gitattributes b/.gitattributes index 3d05d86d31a..af12493a041 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4,3 +4,4 @@ # Docker on Windows. .bashrc text eol=lf *.sh text eol=lf +*.nix text eol=lf diff --git a/README-NIX.md b/README-NIX.md new file mode 100644 index 00000000000..a65c376a4a2 --- /dev/null +++ b/README-NIX.md @@ -0,0 +1,39 @@ +# Building Zephyrâ„¢ Mechanical Keyboard (ZMK) Firmware with Nix + +This extension is added by MoErgo for the Glove80 keyboard. + +Nix makes setup significantly easier. With this approach `west` is not needed. +You can however still choose to build using the standard Zephyr `west` toolchain +if you wish. + +# To build a target + +In ZMK root directory, + + nix-build -A *target* [-o *output_directory*] + +For example, + + nix-build -A glove80_left -o left + +The `output_directory` nix creates is a symlink. If you prefer not to rely on +symlink (perhaps because you are using WSL on Windows), you can make a copy of +the resulting `uf2` file using: + + cp -f $(nix-build -A *target* --no-out-link)/zmk.uf2 . + +# To build Glove80 + +In ZMK root directory, + + cp -f $(nix-build -A glove80_combined --no-out-link)/glove80.uf2 . + +# Adding new targets + +Edit default.nix and add an target based on zmk + +An example is: + + glove80_left = zmk.override { + board = "glove80_lh"; + }; diff --git a/default.nix b/default.nix new file mode 100644 index 00000000000..c28133742d9 --- /dev/null +++ b/default.nix @@ -0,0 +1,49 @@ +{ pkgs ? (import ./nix/pinned-nixpkgs.nix {}) }: +let + inherit (pkgs) newScope; + inherit (pkgs.lib) makeScope; +in + +makeScope newScope (self: with self; { + # To update the pinned Zephyr dependecies using west and update-manifest: + # nix shell -f . west -c west init -l app + # nix shell -f . west -c west update + # nix shell -f . update-manifest -c update-manifest > nix/manifest.json + # Note that any `group-filter` groups in west.yml need to be temporarily + # removed, as `west update-manifest` requires all dependencies to be fetched. + update-manifest = callPackage ./nix/update-manifest { }; + + west = pkgs.python3Packages.west; + + combine_uf2 = a: b: pkgs.runCommandNoCC "combined_${a.name}_${b.name}" {} + '' + mkdir -p $out + cat ${a}/zmk.uf2 ${b}/zmk.uf2 > $out/glove80.uf2 + ''; + + zephyr = callPackage ./nix/zephyr.nix { }; + + zmk = callPackage ./nix/zmk.nix { }; + + zmk_settings_reset = zmk.override { + shield = "settings_reset"; + }; + + glove80_left = zmk.override { + board = "glove80_lh"; + }; + + glove80_right = zmk.override { + board = "glove80_rh"; + }; + + glove80_combined = combine_uf2 glove80_left glove80_right; + + glove80_v0_left = zmk.override { + board = "glove80_v0_lh"; + }; + + glove80_v0_right = zmk.override { + board = "glove80_v0_rh"; + }; +}) diff --git a/nix/cmake-shell.nix b/nix/cmake-shell.nix new file mode 100644 index 00000000000..de68e6cf502 --- /dev/null +++ b/nix/cmake-shell.nix @@ -0,0 +1,17 @@ +{ pkgs ? (import ./pinned-nixpkgs.nix {}) }: + +let + zmkPkgs = (import ../default.nix { inherit pkgs; }); + inherit (zmkPkgs) zmk zephyr; + + zmkCmake = pkgs.writeShellScriptBin "zmk-cmake" '' + export PATH=${pkgs.lib.makeBinPath zmk.nativeBuildInputs}:$PATH + export CMAKE_PREFIX_PATH=${zephyr} + + cmake -G Ninja ${pkgs.lib.escapeShellArgs zmk.cmakeFlags} "-DUSER_CACHE_DIR=/tmp/.cache" "$@" + ''; +in +pkgs.stdenv.mkDerivation { + name = "zmk-cmake-shell"; + nativeBuildInputs = zmk.nativeBuildInputs ++ [zmkCmake]; +} diff --git a/nix/generate-pin.rb b/nix/generate-pin.rb new file mode 100755 index 00000000000..b6bf682dff2 --- /dev/null +++ b/nix/generate-pin.rb @@ -0,0 +1,34 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require 'json' +require 'net/http' +require 'shellwords' + +def get_channel_url(channel) + uri = URI("https://channels.nixos.org/#{channel}") + res = Net::HTTP.get_response(uri) + raise 'Not a redirect' unless res.is_a?(Net::HTTPRedirection) + res['location'] +end + +def fetch_tarball(url) + hash = `nix-prefetch-url --unpack #{Shellwords.escape(url)}` + raise 'Prefetch failed' unless $?.success? + hash.chomp +end + +def generate_pin(channel) + channel_url = get_channel_url(channel) + nixexprs = "#{channel_url}/nixexprs.tar.xz" + hash = fetch_tarball(nixexprs) + { url: nixexprs, sha256: hash } +end + +def main + channel = ENV.fetch('CHANNEL', 'nixpkgs-unstable') + pin = generate_pin(channel) + puts JSON.pretty_generate(pin) +end + +main diff --git a/nix/manifest.json b/nix/manifest.json new file mode 100644 index 00000000000..89545734d36 --- /dev/null +++ b/nix/manifest.json @@ -0,0 +1,292 @@ +[ + { + "name": "zephyr", + "url": "https://github.com/zmkfirmware/zephyr", + "revision": "1ae0eb5ce8adafcec993e6fb8f4eeb6f818a7772", + "clone-depth": 1, + "west-commands": "scripts/west-commands.yml", + "sha256": "184wg98wqa6l8xxsk72rcp91prm49h7lna7w6zfmdflv58nmyrla" + }, + { + "name": "canopennode", + "url": "https://github.com/zephyrproject-rtos/canopennode", + "revision": "53d3415c14d60f8f4bfca54bfbc5d5a667d7e724", + "path": "modules/lib/canopennode", + "sha256": "1vqrx1zi2wbvnwza4z39nrd33j9dxpp9j9rnn12r1gdwxfjazbw0" + }, + { + "name": "chre", + "url": "https://github.com/zephyrproject-rtos/chre", + "revision": "ef76d3456db07e4959df555047d6962279528c8d", + "path": "modules/lib/chre", + "sha256": "00jnv58slzgp4srmsamd7x34yf0hj1c6agmws4csv7w28013bwfm" + }, + { + "name": "cmsis", + "url": "https://github.com/zephyrproject-rtos/cmsis", + "revision": "093de61c2a7d12dc9253daf8692f61f793a9254a", + "path": "modules/hal/cmsis", + "groups": ["hal"], + "sha256": "0354fz5lb2vg3zj0ciwjmz4slh3s5rnvkmixikn36m51wk8vcq1j" + }, + { + "name": "fatfs", + "url": "https://github.com/zephyrproject-rtos/fatfs", + "revision": "a30531af3a95a9a3ea7d771ea8a578ebfed45514", + "path": "modules/fs/fatfs", + "groups": ["fs"], + "sha256": "04c33x1am766sdszmsmxd2rii6snyld68ca17qhg3ml2xiqy1z31" + }, + { + "name": "fff", + "url": "https://github.com/zephyrproject-rtos/fff", + "revision": "6ce5ba26486e93d5b7696a3e23f0585932c14b16", + "path": "modules/lib/fff", + "groups": ["ci"], + "sha256": "1wwrsicibk5nrzj7arxd4a22qr596vnc1ygp9zi409ygqvmzayzy" + }, + { + "name": "hal_atmel", + "url": "https://github.com/zephyrproject-rtos/hal_atmel", + "revision": "1d237f2e2f262751975b6da6e03af569b2b49b2b", + "path": "modules/hal/atmel", + "groups": ["hal"], + "sha256": "1p5qbcqn8adf7jrrpkkc6wjfxmdg9nan370r3pgsamgasyg2h31l" + }, + { + "name": "hal_gigadevice", + "url": "https://github.com/zephyrproject-rtos/hal_gigadevice", + "revision": "dd0e0322474462b58059e6fedaf1d67d2a0864d0", + "path": "modules/hal/gigadevice", + "groups": ["hal"], + "sha256": "0lqx861f39wkf26n9n0bxa3mlfx6l2idjg69hyi6g6b3xlqmnyh2" + }, + { + "name": "hal_nordic", + "url": "https://github.com/zephyrproject-rtos/hal_nordic", + "revision": "249199ec5a5c31d170659921048764e96d05cc0e", + "path": "modules/hal/nordic", + "groups": ["hal"], + "sha256": "1qiqw0hi3lvfgwl1g5phpzc2j2gvj3ccyrvxw3mvgnbvkvvliv2j" + }, + { + "name": "hal_nuvoton", + "url": "https://github.com/zephyrproject-rtos/hal_nuvoton", + "revision": "b4d31f33238713a568e23618845702fadd67386f", + "path": "modules/hal/nuvoton", + "groups": ["hal"], + "sha256": "0942ainpvf64878vkwh9sx4bgwzmf98d40wqa125qmczlbb0d7my" + }, + { + "name": "hal_quicklogic", + "url": "https://github.com/zephyrproject-rtos/hal_quicklogic", + "revision": "b3a66fe6d04d87fd1533a5c8de51d0599fcd08d0", + "path": "modules/hal/quicklogic", + "groups": ["hal"], + "sha256": "0hk1x72kibaw3xkspy9822vh28ax3bk11b80qn8l4dwrm0wx34sy" + }, + { + "name": "hal_renesas", + "url": "https://github.com/zephyrproject-rtos/hal_renesas", + "revision": "468d3f2146d18c7f86a4640fc641cc1d20a4a100", + "path": "modules/hal/renesas", + "groups": ["hal"], + "sha256": "10wk12myrz1rd6vyxbk4cfm7sj44ad4j7f49x043lfsma60ss402" + }, + { + "name": "hal_rpi_pico", + "url": "https://github.com/zephyrproject-rtos/hal_rpi_pico", + "revision": "a094c060e0c2d43c7f9d8f5c06cc0665117e0c18", + "path": "modules/hal/rpi_pico", + "groups": ["hal"], + "sha256": "0lxf3d0fanzhwzg68scqzyyv70gwg94gzpc4cx7kxic228832v8v" + }, + { + "name": "hal_stm32", + "url": "https://github.com/zephyrproject-rtos/hal_stm32", + "revision": "642e199c59828137dc6b1c7044a289d4269886d1", + "path": "modules/hal/stm32", + "groups": ["hal"], + "sha256": "1lp54hxcq1wazh2ls2hk3nmshssl4ia857i4qgw7nf3l0a5zw4ia" + }, + { + "name": "hal_telink", + "url": "https://github.com/zephyrproject-rtos/hal_telink", + "revision": "38573af589173259801ae6c2b34b7d4c9e626746", + "path": "modules/hal/telink", + "groups": ["hal"], + "sha256": "16lzqnzwl2ij0jvbg9x1cgh54kv76dbmpcn4xhd5m4wph3yix78z" + }, + { + "name": "hal_wurthelektronik", + "url": "https://github.com/zephyrproject-rtos/hal_wurthelektronik", + "revision": "24ca9873c3d608fad1fea0431836bc8f144c132e", + "path": "modules/hal/wurthelektronik", + "groups": ["hal"], + "sha256": "0s2b3j40b7qd85np46n4vh0zjmwymnpxd8r42nhss6xznn11g2h8" + }, + { + "name": "libmetal", + "url": "https://github.com/zephyrproject-rtos/libmetal", + "revision": "2f586b4f1276fb075ee145421bdf6cbe5403aa41", + "path": "modules/hal/libmetal", + "groups": ["hal"], + "sha256": "1r0k3yxa50x0627fr61k6ahmfwyjg9zqiagfjv62a8jpwc134mw4" + }, + { + "name": "liblc3", + "url": "https://github.com/zephyrproject-rtos/liblc3", + "revision": "448f3de31f49a838988a162ef1e23a89ddf2d2ed", + "path": "modules/lib/liblc3", + "sha256": "07r923k1y05sq1sl9740z33cz64pqm2n7x8rr2ws460fij64aixp" + }, + { + "name": "littlefs", + "url": "https://github.com/zephyrproject-rtos/littlefs", + "revision": "ca583fd297ceb48bced3c2548600dc615d67af24", + "path": "modules/fs/littlefs", + "groups": ["fs"], + "sha256": "10xpjrnp5n1j1xbay2qwmg2w314fw9pgzv3kz1mn3pgadhckfgdn" + }, + { + "name": "lvgl", + "url": "https://github.com/zmkfirmware/lvgl", + "revision": "70a7849726be8375e3d941153dc417823ea7f355", + "path": "modules/lib/gui/lvgl", + "sha256": "147mykkb72nwbjhrw4z7h0kkxw4p7kvy0w001s44rgplxhqqsg98" + }, + { + "name": "lz4", + "url": "https://github.com/zephyrproject-rtos/lz4", + "revision": "8e303c264fc21c2116dc612658003a22e933124d", + "path": "modules/lib/lz4", + "sha256": "1kqs7gxg17gvws01rir8p6gmzp54y12s1898lflhsb418122v8nf" + }, + { + "name": "mbedtls", + "url": "https://github.com/zephyrproject-rtos/mbedtls", + "revision": "7fed49c9b9f983ad6416986661ef637459723bcb", + "path": "modules/crypto/mbedtls", + "groups": ["crypto"], + "sha256": "084m54xgqqqfixj7h9zigh98l06c2g5ybwbs6y7yji22x0v5dx7f" + }, + { + "name": "mipi-sys-t", + "url": "https://github.com/zephyrproject-rtos/mipi-sys-t", + "revision": "0d521d8055f3b2b4842f728b0365d3f0ece9c37f", + "path": "modules/debug/mipi-sys-t", + "groups": ["debug"], + "sha256": "05yl16qbnj1xxngdyisd5c25aa88zgi6wd5ylqsgz419l0zrgrm6" + }, + { + "name": "nanopb", + "url": "https://github.com/zephyrproject-rtos/nanopb", + "revision": "dc4deed54fd4c7e1935e3b6387eedf21bb45dc38", + "path": "modules/lib/nanopb", + "sha256": "0kmzh65hyhnl88w9x0rcdypj4nl1brbxrnzhy3flgxmhr564y05s" + }, + { + "name": "nrf_hw_models", + "url": "https://github.com/zephyrproject-rtos/nrf_hw_models", + "revision": "65bc5305d432c08e24a3f343006d1e7deaff4908", + "path": "modules/bsim_hw_models/nrf_hw_models", + "sha256": "12xzqk22zc4g0j3xcmnz00c56pk6hiydsfgnh7gqm2ap2q3p90zm" + }, + { + "name": "open-amp", + "url": "https://github.com/zephyrproject-rtos/open-amp", + "revision": "8d53544871e1f300c478224faca6be8384ab0d04", + "path": "modules/lib/open-amp", + "sha256": "00lynjr3vj8a3gj3vh557gjwp7v7kzj7py0vxwvq8y349kh1ga50" + }, + { + "name": "picolibc", + "url": "https://github.com/zephyrproject-rtos/picolibc", + "revision": "04ada5951cbaf8e7b17f8226ce31cb6837c28ba7", + "path": "modules/lib/picolibc", + "sha256": "0sv6hv1bfcc7ys310bgifq7jk3r5zlq866wiq6qklmysjgrhva5h" + }, + { + "name": "segger", + "url": "https://github.com/zephyrproject-rtos/segger", + "revision": "d4e568a920b4bd087886170a5624c167b2d0665e", + "path": "modules/debug/segger", + "groups": ["debug"], + "sha256": "0a1rk0b2l1n2zkhlp1ia4cy2mijiaynk6qirw1gddnryj4qqr0nq" + }, + { + "name": "tflite-micro", + "url": "https://github.com/zephyrproject-rtos/tflite-micro", + "revision": "9156d050927012da87079064db59d07f03b8baf6", + "path": "modules/lib/tflite-micro", + "sha256": "1przq51rrhl032n831b2bl35f7kw22ypi9di2wxpfww5q4b6dbh1" + }, + { + "name": "tinycbor", + "url": "https://github.com/zephyrproject-rtos/tinycbor", + "revision": "9e1f34bc08123aaad7666d3652aaa839e8178b3b", + "path": "modules/lib/tinycbor", + "sha256": "0yqmbiqx4www3rfc1ym42j8gqwi1b8hkw27gjps25xz22zrh4a1p" + }, + { + "name": "tinycrypt", + "url": "https://github.com/zephyrproject-rtos/tinycrypt", + "revision": "3e9a49d2672ec01435ffbf0d788db6d95ef28de0", + "path": "modules/crypto/tinycrypt", + "groups": ["crypto"], + "sha256": "19d2q9y23yzz9i383q3cldjl3k5mryx9762cab23zy3ijdnmj2z6" + }, + { + "name": "TraceRecorderSource", + "url": "https://github.com/zephyrproject-rtos/TraceRecorderSource", + "revision": "9893bf1cf649a2c4ee2e27293f887994f3d0da5b", + "path": "modules/debug/TraceRecorder", + "groups": ["debug"], + "sha256": "00lghqq3n7qln6kq2ikwgswap62z81fqw0k28j975nnc1z932flg" + }, + { + "name": "trusted-firmware-a", + "url": "https://github.com/zephyrproject-rtos/trusted-firmware-a", + "revision": "d29cddecde614d81cbec1fb0086cdaebd77d3575", + "path": "modules/tee/tf-a/trusted-firmware-a", + "groups": ["tee"], + "sha256": "1xca872xx9wds8lgjxppg8zbcm01pf47al127hljkwzi6g4wcr69" + }, + { + "name": "tf-m-tests", + "url": "https://github.com/zephyrproject-rtos/tf-m-tests", + "revision": "c99a86b295c4887520da9d8402566d7f225c974e", + "path": "modules/tee/tf-m/tf-m-tests", + "groups": ["tee"], + "sha256": "0cc4029i9qfzq4vply9l2yjigaf9rq1zc6a44b8dc779kx31qx36" + }, + { + "name": "psa-arch-tests", + "url": "https://github.com/zephyrproject-rtos/psa-arch-tests", + "revision": "f4fc2442b8e29e2a03d9899e46e5a3ea3df8c2c9", + "path": "modules/tee/tf-m/psa-arch-tests", + "groups": ["tee"], + "sha256": "015qan6qfqyfc1lwpqb29zk1wxx02b6ng5wc1nfpfvknvhiwifay" + }, + { + "name": "uoscore-uedhoc", + "url": "https://github.com/zephyrproject-rtos/uoscore-uedhoc", + "revision": "e8920192b66db4f909eb9cd3f155d5245c1ae825", + "path": "modules/lib/uoscore-uedhoc", + "sha256": "0ngs6ad6lm47gqy5i5h47cr4b7k3qb85cnbidkk1092xban0ai0g" + }, + { + "name": "zcbor", + "url": "https://github.com/zephyrproject-rtos/zcbor", + "revision": "a0d6981f14d4001d6f0d608d1a427f9bc6bb6d02", + "path": "modules/lib/zcbor", + "sha256": "03xz79pi210kny55ks9cyr8i9m68f7kla3nv5zk2afg59ms0nwdc" + }, + { + "name": "zscilib", + "url": "https://github.com/zephyrproject-rtos/zscilib", + "revision": "ca070ddabdaf67175a2da901d0bd62e8899371c5", + "path": "modules/lib/zscilib", + "sha256": "1ss33lfz2xzz8b3fcnibz109x09lj9ajw3na8lsmhvn7mhg6zh3v" + } +] diff --git a/nix/pinned-nixpkgs.json b/nix/pinned-nixpkgs.json new file mode 100644 index 00000000000..c5350cef317 --- /dev/null +++ b/nix/pinned-nixpkgs.json @@ -0,0 +1,4 @@ +{ + "url": "https://releases.nixos.org/nixpkgs/nixpkgs-24.05pre559581.aa9d4729cbc9/nixexprs.tar.xz", + "sha256": "04d8m0kh426kn2dvk5006as68al1dcv9wlizyclq69pas9xd1l3h" +} diff --git a/nix/pinned-nixpkgs.nix b/nix/pinned-nixpkgs.nix new file mode 100644 index 00000000000..c62cf91d1f9 --- /dev/null +++ b/nix/pinned-nixpkgs.nix @@ -0,0 +1,17 @@ +{ system ? builtins.currentSystem }: + +let + pin = builtins.fromJSON (builtins.readFile ./pinned-nixpkgs.json); + + nixpkgsSrc = builtins.fetchTarball { + inherit (pin) url sha256; + }; +in + +import nixpkgsSrc { + inherit system; + config = { + allowUnfree = true; + }; + overlays = []; # prevent impure overlays +} diff --git a/nix/update-manifest/default.nix b/nix/update-manifest/default.nix new file mode 100644 index 00000000000..eb858c16dbb --- /dev/null +++ b/nix/update-manifest/default.nix @@ -0,0 +1,11 @@ +{ runCommand, lib, makeWrapper, west, remarshal, nix-prefetch-git, jq, git }: + +runCommand "update-manifest" { + nativeBuildInputs = [ makeWrapper ]; +} '' + mkdir -p $out/bin $out/libexec + cp ${./update-manifest.sh} $out/libexec/update-manifest.sh + makeWrapper $out/libexec/update-manifest.sh $out/bin/update-manifest \ + --set PATH ${lib.makeBinPath [ west remarshal nix-prefetch-git jq git ]} + patchShebangs $out +'' diff --git a/nix/update-manifest/update-manifest.sh b/nix/update-manifest/update-manifest.sh new file mode 100755 index 00000000000..54de8a3baf8 --- /dev/null +++ b/nix/update-manifest/update-manifest.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash + +set -euo pipefail + +ignored_modules=(hal_espressif bsim babblesim babblesim_base \ + babblesim_ext_2G4_libPhyComv1 babblesim_ext_2G4_channel_NtNcable \ + babblesim_ext_2G4_channel_multiatt babblesim_ext_2G4_modem_magic \ + babblesim_ext_2G4_modem_BLE_simple babblesim_ext_2G4_device_burst_interferer \ + babblesim_ext_2G4_device_WLAN_actmod babblesim_ext_2G4_phy_v1 \ + babblesim_ext_2G4_device_playback babblesim_ext_libCryptov1) + +prefetch_project() { + local p=$1 + local name + name="$(jq -r .name <<< "$p")" + + if [[ " ${ignored_modules[*]} " =~ " ${name} " ]]; then + echo "Skipping: $name" >&2 + return + fi + + echo "Prefetching: $name" >&2 + + sha256=$(nix-prefetch-git \ + --quiet \ + --fetch-submodules \ + --url "$(jq -r .url <<< "$p")" \ + --rev "$(jq -r .revision <<< "$p")" \ + | jq -r .sha256) + + jq --arg sha256 "$sha256" '. + $ARGS.named' <<< "$p" +} + + +west manifest --freeze | \ + yaml2json | \ + jq -c '.manifest.projects[]' | \ + while read -r p; do prefetch_project "$p"; done | \ + jq --slurp diff --git a/nix/west-shell.nix b/nix/west-shell.nix new file mode 100644 index 00000000000..a9be5f8db62 --- /dev/null +++ b/nix/west-shell.nix @@ -0,0 +1,50 @@ +{ pkgs ? (import ./pinned-nixpkgs.nix {}) }: + +let + # from zephyr/scripts/requirements-base.txt + pythonDependencies = ps: with ps; [ + pyelftools + pyyaml + packaging + progress + anytree + intelhex + west + ]; + + requiredStdenv = + if pkgs.stdenv.hostPlatform.isLinux + then pkgs.multiStdenv + else pkgs.stdenv; +in +with pkgs; +# requires multiStdenv to build 32-bit test binaries +requiredStdenv.mkDerivation { + name = "zmk-shell"; + + buildInputs = [ + # ZMK dependencies + gitFull + wget + autoconf + automake + bzip2 + ccache + dtc # devicetree compiler + dfu-util + gcc + libtool + ninja + cmake + xz + (python3.withPackages(pythonDependencies)) + + # ARM toolchain + gcc-arm-embedded + ]; + + ZEPHYR_TOOLCHAIN_VARIANT = "gnuarmemb"; + GNUARMEMB_TOOLCHAIN_PATH = gcc-arm-embedded; + + shellHook = "if [ ! -d \"zephyr\" ]; then west init -l app/ ; west update; west zephyr-export; fi; source zephyr/zephyr-env.sh"; +} diff --git a/nix/zephyr.nix b/nix/zephyr.nix new file mode 100644 index 00000000000..661584db7a3 --- /dev/null +++ b/nix/zephyr.nix @@ -0,0 +1,37 @@ +{ stdenv, lib, fetchgit }: +let + manifestJSON = builtins.fromJSON (builtins.readFile ./manifest.json); + + projects = lib.listToAttrs (lib.forEach manifestJSON ({ name, revision, url, sha256, ... }@args: ( + lib.nameValuePair name { + path = args.path or name; + src = fetchgit { + inherit name url sha256; + rev = revision; + }; + }) + )); +in + + +# Zephyr with no modules, from the frozen manifest. +# For now the modules are passed through as passthru +stdenv.mkDerivation { + name = "zephyr"; + src = projects.zephyr.src; + + dontBuild = true; + + # This awkward structure is required by + # COMMAND ${PYTHON_EXECUTABLE} ${ZEPHYR_BASE}/../tools/uf2/utils/uf2conv.py + installPhase = '' + mkdir -p $out/zephyr + mv * $out/zephyr + + # uf2 is gone, not sure what replaced it + ''; + + passthru = { + modules = map (p: p.src) (lib.attrValues (removeAttrs projects ["zephyr"])); + }; +} diff --git a/nix/zmk.nix b/nix/zmk.nix new file mode 100644 index 00000000000..bf4caec307d --- /dev/null +++ b/nix/zmk.nix @@ -0,0 +1,106 @@ +{ stdenvNoCC, lib, buildPackages +, cmake, ninja, dtc, gcc-arm-embedded +, zephyr +, board ? "glove80_lh" +, shield ? null +, keymap ? null +}: + + +let + # from zephyr/scripts/requirements-base.txt + packageOverrides = pyself: pysuper: { + can = pysuper.can.overrideAttrs (_: { + # horribly flaky test suite full of assertions about timing. + # > assert 0.1 <= took < inc(0.3) + # E assert 0.31151700019836426 < 0.3 + # E + where 0.3 = inc(0.3) + doCheck = false; + doInstallCheck = false; + }); + + canopen = pysuper.can.overrideAttrs (_: { + # Also has timing sensitive tests + # task = self.network.send_periodic(0x123, [1, 2, 3], 0.01) + # time.sleep(0.1) + # > self.assertTrue(9 <= bus.queue.qsize() <= 11) + # E AssertionError: False is not true + doCheck = false; + doInstallCheck = false; + }); + }; + + python = (buildPackages.python3.override { inherit packageOverrides; }).withPackages (ps: with ps; [ + pyelftools + pyyaml + canopen + packaging + progress + anytree + intelhex + + # TODO: this was required but not in shell.nix + pykwalify + ]); + + requiredZephyrModules = [ + "cmsis" "hal_nordic" "tinycrypt" "picolibc" "lvgl" "picolibc" "segger" + ]; + + zephyrModuleDeps = builtins.filter (x: builtins.elem x.name requiredZephyrModules) zephyr.modules; +in + +stdenvNoCC.mkDerivation { + name = "zmk_${board}"; + + sourceRoot = "source/app"; + + src = builtins.path { + name = "source"; + path = ./..; + filter = path: type: + let relPath = lib.removePrefix (toString ./.. + "/") (toString path); + in (lib.cleanSourceFilter path type) && ! ( + # Meta files + relPath == "nix" || lib.hasSuffix ".nix" path || + # Transient state + relPath == "build" || relPath == ".west" || + # Fetched by west + relPath == "modules" || relPath == "tools" || relPath == "zephyr" + ); + }; + + preConfigure = '' + cmakeFlagsArray+=("-DUSER_CACHE_DIR=$TEMPDIR/.cache") + ''; + + cmakeFlags = [ + # "-DZephyrBuildConfiguration_ROOT=${zephyr}/zephyr" + # TODO: is this required? if not, why not? + "-DZEPHYR_BASE=${zephyr}/zephyr" + "-DBOARD_ROOT=." + "-DBOARD=${board}" + "-DZEPHYR_TOOLCHAIN_VARIANT=gnuarmemb" + "-DGNUARMEMB_TOOLCHAIN_PATH=${gcc-arm-embedded}" + # TODO: maybe just use a cross environment for this gcc + "-DCMAKE_C_COMPILER=${gcc-arm-embedded}/bin/arm-none-eabi-gcc" + "-DCMAKE_CXX_COMPILER=${gcc-arm-embedded}/bin/arm-none-eabi-g++" + "-DCMAKE_AR=${gcc-arm-embedded}/bin/arm-none-eabi-ar" + "-DCMAKE_RANLIB=${gcc-arm-embedded}/bin/arm-none-eabi-ranlib" + "-DZEPHYR_MODULES=${lib.concatStringsSep ";" zephyrModuleDeps}" + ] ++ + (lib.optional (shield != null) "-DSHIELD=${shield}") ++ + (lib.optional (keymap != null) "-DKEYMAP_FILE=${keymap}"); + + nativeBuildInputs = [ cmake ninja python dtc gcc-arm-embedded ]; + buildInputs = [ zephyr ]; + + installPhase = '' + mkdir $out + cp zephyr/zmk.{uf2,hex,bin,elf} $out + cp zephyr/.config $out/zmk.kconfig + cp zephyr/zephyr.dts $out/zmk.dts + ''; + + passthru = { inherit zephyrModuleDeps; }; +} From 5f5c64f0acceedc7cf2d7b6069141152560b2ca9 Mon Sep 17 00:00:00 2001 From: Chris Andreae Date: Sun, 18 Sep 2022 17:07:01 +0900 Subject: [PATCH 07/10] Github build action: build Glove80 combined firmware using Nix --- .github/workflows/build.yml | 13 +++---- .github/workflows/nix-build.yml | 62 +++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/nix-build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a7d0560d042..c2fc6561d99 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,7 +14,7 @@ on: jobs: build: - if: ${{ always() }} + if: ${{ false }} runs-on: ubuntu-latest container: image: docker.io/zmkfirmware/zmk-build-arm:3.2 @@ -103,7 +103,7 @@ jobs: throw new Error('Failed to build one or more configurations'); } compile-matrix: - if: ${{ always() }} + if: ${{ false && always() }} runs-on: ubuntu-latest needs: [core-coverage, board-changes, nightly] outputs: @@ -143,7 +143,7 @@ jobs: shieldArgs: JSON.stringify(shieldArgs), })); core-coverage: - if: ${{ needs.get-changed-files.outputs.core-changes == 'true' }} + if: ${{ false && needs.get-changed-files.outputs.core-changes == 'true' }} runs-on: ubuntu-latest needs: get-changed-files outputs: @@ -172,7 +172,7 @@ jobs: return [...include, ...coreCoverage.include]; board-changes: - if: ${{ needs.get-changed-files.outputs.board-changes == 'true' }} + if: ${{ false && needs.get-changed-files.outputs.board-changes == 'true' }} runs-on: ubuntu-latest needs: [get-grouped-hardware, get-changed-files] outputs: @@ -256,7 +256,7 @@ jobs: }); }))).flat(); nightly: - if: ${{ github.event_name == 'schedule' }} + if: ${{ false && github.event_name == 'schedule' }} runs-on: ubuntu-latest needs: get-grouped-hardware outputs: @@ -301,6 +301,7 @@ jobs: return [...includeOnboard, ...includeInterconnect]; get-grouped-hardware: + if: ${{ false }} runs-on: ubuntu-latest outputs: organized-metadata: ${{ steps.organize-metadata.outputs.result }} @@ -378,7 +379,7 @@ jobs: return JSON.stringify(grouped).replace(/\\/g,"\\\\").replace(/`/g,"\\`"); result-encoding: string get-changed-files: - if: ${{ github.event_name != 'schedule' }} + if: ${{ false && github.event_name != 'schedule' }} runs-on: ubuntu-latest outputs: changed-files: ${{ steps.changed-files.outputs.all }} diff --git a/.github/workflows/nix-build.yml b/.github/workflows/nix-build.yml new file mode 100644 index 00000000000..d864503167e --- /dev/null +++ b/.github/workflows/nix-build.yml @@ -0,0 +1,62 @@ +name: Build + +on: + push: + paths: + - ".github/workflows/nix-build.yml" + - "default.nix" + - "app/**" + - "nix/**" + branches: + - "**" + tags: + - "**" + pull_request: + paths: + - ".github/workflows/nix-build.yml" + - "default.nix" + - "app/**" + - "nix/**" + +jobs: + build: + name: Build Glove80 Firmware + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2.4.0 + - uses: cachix/install-nix-action@v20 + with: + nix_path: nixpkgs=channel:nixos-22.05 + - uses: cachix/cachix-action@v12 + with: + name: moergo-glove80-zmk-dev + authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" + skipPush: "${{ github.repository != 'moergo-sc/zmk' }}" + - name: Build Glove80 combined firmware + run: nix-build -A glove80_combined -o combined + - name: Copy result out of nix store + run: cp combined/glove80.uf2 glove80.uf2 + - name: Upload result + uses: actions/upload-artifact@v3 + with: + name: glove80.uf2 + path: glove80.uf2 + release: + name: Create Release for Tag + if: >- + github.repository == 'moergo-sc/zmk' + && github.event_name == 'push' + && contains(github.ref, 'refs/tags/v') + needs: build + runs-on: ubuntu-latest + steps: + - name: Download compiled firmware artifact + uses: actions/download-artifact@v3 + with: + name: glove80.uf2 + - name: Create Release for Tag + uses: ncipollo/release-action@v1 + with: + artifacts: "glove80.uf2" + artifactErrorsFailBuild: true + generateReleaseNotes: true From 80e5f50ab581388631d6f446fcb631745ee8ee4a Mon Sep 17 00:00:00 2001 From: Chris Andreae Date: Mon, 11 Oct 2021 16:49:33 +0900 Subject: [PATCH 08/10] Docker container and lambda function for performing firmware builds Provides an entry point that builds and returns a combined LH + RH keyboard firmware when provided a keymap via a POST body. Wraps compilation with ccache, and includes a pre-warmed cache of the build in /tmp/ccache. To maximize chance of a direct cache hit, changes the lambda driver to always build in /tmp/build. some back of the envelope measurements (2012 xeon e3-1230v2, nixos) clean build, no cache -> 21.308 clean build, cache -> 7.145 modified keymap, clean build, cache -> 12.127 --- .github/workflows/build-container.yml | 110 +++++++++++++ .github/workflows/cleanup-container.yml | 43 +++++ .github/workflows/nix-build.yml | 2 +- lambda/Gemfile | 3 + lambda/Gemfile.lock | 13 ++ lambda/api_version.txt | 1 + lambda/app.rb | 68 ++++++++ lambda/compiler.rb | 118 ++++++++++++++ lambda/default.nix | 26 +++ lambda/gemset.nix | 12 ++ lambda/shell.nix | 9 + nix/ccache.nix | 43 +++++ nix/zmk.nix | 8 +- release.nix | 208 ++++++++++++++++++++++++ 14 files changed, 661 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/build-container.yml create mode 100644 .github/workflows/cleanup-container.yml create mode 100644 lambda/Gemfile create mode 100644 lambda/Gemfile.lock create mode 100644 lambda/api_version.txt create mode 100644 lambda/app.rb create mode 100644 lambda/compiler.rb create mode 100644 lambda/default.nix create mode 100644 lambda/gemset.nix create mode 100644 lambda/shell.nix create mode 100644 nix/ccache.nix create mode 100644 release.nix diff --git a/.github/workflows/build-container.yml b/.github/workflows/build-container.yml new file mode 100644 index 00000000000..e5c3224865d --- /dev/null +++ b/.github/workflows/build-container.yml @@ -0,0 +1,110 @@ +name: Build Compiler Service Container + +on: + push: + tags: + - "*" + pull_request_target: + branches: + - main + +jobs: + build: + # This job must never be run on a PR from outside the same repository + if: github.repository == 'moergo-sc/zmk' && (github.event.pull_request == null || github.event.pull_request.head.repo.full_name == github.repository) + runs-on: ubuntu-latest + # These permissions are needed to interact with GitHub's OIDC Token endpoint. + permissions: + id-token: write + contents: read + env: + ECR_REPOSITORY: zmk-builder-lambda + VERSIONS_BUCKET: glove80firmwarepipelines-compilerversionsbucket44-zubaquiyjdam + UPDATE_COMPILER_VERSIONS_FUNCTION: arn:aws:lambda:us-east-1:431227615537:function:Glove80FirmwarePipelineSt-UpdateCompilerVersions2A-CNxPOHb4VSuV + REVISION_TAG: ${{ github.event.pull_request && github.event.pull_request.head.sha || github.sha }} + PR_NUMBER: ${{ github.event.number }} + steps: + - uses: actions/checkout@v2.4.0 + with: + repository: moergo-sc/zmk + ref: ${{ github.event.pull_request && github.event.pull_request.head.sha || github.sha }} + fetch-depth: 0 + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + role-to-assume: arn:aws:iam::431227615537:role/GithubCompilerLambdaBuilder + aws-region: us-east-1 + - name: Extract container name from branch name + shell: bash + run: | + if [ "$GITHUB_HEAD_REF" ]; then + branch_ref="$GITHUB_HEAD_REF" + type="pr" + tag="pr${PR_NUMBER}.${GITHUB_HEAD_REF}" + elif [[ "$GITHUB_REF" == refs/tags/* ]]; then + branch_ref="$GITHUB_REF" + type="tag" + tag="${GITHUB_REF#refs/tags/}" + else + echo "Not a pull request or release tag" >&2 + exit 1 + fi + # Replace / with . in container tag names + tag="${tag//\//.}" + echo "VERSION_BRANCH=${branch_ref}" >> $GITHUB_ENV + echo "VERSION_TYPE=${type}" >> $GITHUB_ENV + echo "VERSION_NAME=${tag}" >> $GITHUB_ENV + id: extract_name + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + - uses: cachix/install-nix-action@v20 + with: + nix_path: nixpkgs=channel:nixos-22.05 + - uses: cachix/cachix-action@v12 + with: + name: moergo-glove80-zmk-dev + authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" + - name: Build lambda image + run: nix-build release.nix --arg revision "\"${REVISION_TAG}\"" -A lambdaImage -o lambdaImage + - name: Import OCI image into docker-daemon + env: + REGISTRY: ${{ steps.login-ecr.outputs.registry }} + run: skopeo --insecure-policy copy oci:lambdaImage docker-daemon:$REGISTRY/$ECR_REPOSITORY:$REVISION_TAG + - name: Push container image to Amazon ECR + env: + REGISTRY: ${{ steps.login-ecr.outputs.registry }} + run: docker push $REGISTRY/$ECR_REPOSITORY:$REVISION_TAG + - name: Create JSON metadata to represent the built container + env: + REGISTRY: ${{ steps.login-ecr.outputs.registry }} + shell: bash + run: | + digest="$(docker inspect --format='{{index .RepoDigests 0}}' $REGISTRY/$ECR_REPOSITORY:$REVISION_TAG)" + digest="${digest##*@}" + api_version="$(cat lambda/api_version.txt)" + timestamp="$(date -u +"%Y%m%d.%H%M%S")" + + if [ "$VERSION_TYPE" = "pr" ]; then + release_name="$VERSION_NAME.$timestamp" + else + release_name="$VERSION_NAME" + fi + + jq -n '$ARGS.named' \ + --arg name "$release_name" \ + --arg version_name "$VERSION_NAME" \ + --arg revision "$REVISION_TAG" \ + --arg release_time "$timestamp" \ + --arg branch "$VERSION_BRANCH" \ + --arg digest "$digest" \ + --arg api_version "$api_version" \ + > "/tmp/$VERSION_NAME.json" + - name: Upload image metadata file to versions bucket + run: aws s3 cp "/tmp/$VERSION_NAME.json" "s3://$VERSIONS_BUCKET/images/$VERSION_NAME.json" + - name: Notify the build pipeline that the compile containers have updated + run: >- + aws lambda invoke --function-name $UPDATE_COMPILER_VERSIONS_FUNCTION + --invocation-type Event + --cli-binary-format raw-in-base64-out + /dev/null diff --git a/.github/workflows/cleanup-container.yml b/.github/workflows/cleanup-container.yml new file mode 100644 index 00000000000..0af80562cca --- /dev/null +++ b/.github/workflows/cleanup-container.yml @@ -0,0 +1,43 @@ +name: Clean up PR Compiler Service Container + +on: + pull_request: + types: [closed] + branches: + - main + +jobs: + build: + if: github.repository == 'moergo-sc/zmk' + runs-on: ubuntu-latest + # These permissions are needed to interact with GitHub's OIDC Token endpoint. + permissions: + id-token: write + contents: read + env: + ECR_REPOSITORY: zmk-builder-lambda + VERSIONS_BUCKET: glove80firmwarepipelines-compilerversionsbucket44-zubaquiyjdam + UPDATE_COMPILER_VERSIONS_FUNCTION: arn:aws:lambda:us-east-1:431227615537:function:Glove80FirmwarePipelineSt-UpdateCompilerVersions2A-CNxPOHb4VSuV + PR_NUMBER: ${{ github.event.number }} + steps: + - name: Extract image tag name + shell: bash + run: | + tag="pr${PR_NUMBER}.${GITHUB_HEAD_REF}" + # Replace / with . in container tag names + tag="${tag//\//.}" + echo "VERSION_NAME=${tag}" >> $GITHUB_ENV + id: extract_name + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + role-to-assume: arn:aws:iam::431227615537:role/GithubCompilerLambdaBuilder + aws-region: us-east-1 + - name: Delete the image metadata file from the versions s3 bucket + run: aws s3 rm s3://$VERSIONS_BUCKET/images/$VERSION_NAME.json + - name: Notify the build pipeline that the compile containers have updated + run: >- + aws lambda invoke --function-name $UPDATE_COMPILER_VERSIONS_FUNCTION + --invocation-type Event + --cli-binary-format raw-in-base64-out + /dev/null diff --git a/.github/workflows/nix-build.yml b/.github/workflows/nix-build.yml index d864503167e..faaeabc220e 100644 --- a/.github/workflows/nix-build.yml +++ b/.github/workflows/nix-build.yml @@ -1,4 +1,4 @@ -name: Build +name: Build Glove80 Firmware on: push: diff --git a/lambda/Gemfile b/lambda/Gemfile new file mode 100644 index 00000000000..6b6bbf753ec --- /dev/null +++ b/lambda/Gemfile @@ -0,0 +1,3 @@ +source 'https://rubygems.org' +gem 'aws_lambda_ric' + diff --git a/lambda/Gemfile.lock b/lambda/Gemfile.lock new file mode 100644 index 00000000000..8b6c1f95c58 --- /dev/null +++ b/lambda/Gemfile.lock @@ -0,0 +1,13 @@ +GEM + remote: https://rubygems.org/ + specs: + aws_lambda_ric (2.0.0) + +PLATFORMS + ruby + +DEPENDENCIES + aws_lambda_ric + +BUNDLED WITH + 2.1.4 diff --git a/lambda/api_version.txt b/lambda/api_version.txt new file mode 100644 index 00000000000..0cfbf08886f --- /dev/null +++ b/lambda/api_version.txt @@ -0,0 +1 @@ +2 diff --git a/lambda/app.rb b/lambda/app.rb new file mode 100644 index 00000000000..8c3eb554cca --- /dev/null +++ b/lambda/app.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +require 'stringio' +require 'digest' +require 'json' +require './compiler' + +module LambdaFunction + # Handle a non-HTTP compile request, returning a JSON body of either the + # compiled result or an error. + class Handler + REVISION = ENV.fetch('REVISION', 'unknown') + + def self.process(event:, context:) + return { type: 'keep_alive' } if event.has_key?('keep_alive') + + parse_base64_param = ->(param, required: true) do + if event.include?(param) + Base64.strict_decode64(event.fetch(param)) + elsif required + return error(status: 400, message: "Missing required argument: #{param}") + end + rescue ArgumentError + return error(status: 400, message: "Invalid Base64 in #{param} input") + end + + keymap_data = parse_base64_param.('keymap') + kconfig_data = parse_base64_param.('kconfig', required: false) + + # Including kconfig settings that affect the RHS require building both + # firmware images, doubling compile time. Clients should omit rhs_kconfig + # where possible. + rhs_kconfig_data = parse_base64_param.('rhs_kconfig', required: false) + + result, log = + begin + log_compile(keymap_data, kconfig_data, rhs_kconfig_data) + + Compiler.new.compile(keymap_data, kconfig_data, rhs_kconfig_data) + rescue Compiler::CompileError => e + return error(status: e.status, message: e.message, detail: e.log) + end + + result = Base64.strict_encode64(result) + + { type: 'result', result: result, log: log, revision: REVISION } + rescue StandardError => e + error(status: 500, message: "Unexpected error: #{e.class}", detail: [e.message], exception: e) + end + + def self.log_compile(keymap_data, kconfig_data, rhs_kconfig_data) + keymap = Digest::SHA1.base64digest(keymap_data) + kconfig = kconfig_data ? Digest::SHA1.base64digest(kconfig_data) : 'nil' + rhs_kconfig = rhs_kconfig_data ? Digest::SHA1.base64digest(rhs_kconfig_data) : 'nil' + puts("Compiling with keymap: #{keymap}; kconfig: #{kconfig}; rhs_kconfig: #{rhs_kconfig}") + end + + def self.error(status:, message:, detail: nil, exception: nil) + reported_error = { type: 'error', status:, message:, detail:, revision: REVISION } + + exception_detail = { class: exception.class, backtrace: exception.backtrace } if exception + logged_error = reported_error.merge(exception: exception_detail) + puts(JSON.dump(logged_error)) + + reported_error + end + end +end diff --git a/lambda/compiler.rb b/lambda/compiler.rb new file mode 100644 index 00000000000..9e9b8848552 --- /dev/null +++ b/lambda/compiler.rb @@ -0,0 +1,118 @@ +# frozen_string_literal: true + +require 'tmpdir' +require 'base64' +require 'json' +require 'open3' +require 'yaml' + +class Compiler + class CompileError < RuntimeError + attr_reader :status, :log + + def initialize(message, status: 400, log:) + super(message) + @status = status + @log = log + end + end + + def compile(keymap_data, lhs_kconfig_data, rhs_kconfig_data) + if rhs_kconfig_data && !rhs_kconfig_data.empty? + lhs_result, lhs_output = compile_board('glove80_lh', keymap_data:, kconfig_data: lhs_kconfig_data, include_static_rhs: false) + rhs_result, rhs_output = compile_board('glove80_rh', keymap_data: nil, kconfig_data: rhs_kconfig_data, include_static_rhs: false) + [ + lhs_result.concat(rhs_result), + ["LHS Output:", *lhs_output, "RHS Output:", *rhs_output], + ] + else + compile_board('glove80_lh', keymap_data:, kconfig_data: lhs_kconfig_data, include_static_rhs: true) + end + end + + def compile_board(board, keymap_data:, kconfig_data:, include_static_rhs: false) + in_build_dir do + compile_command = ['compileZmk', '-b', board] + + if keymap_data + validate_devicetree!(keymap_data) + File.open('build.keymap', 'w') { |io| io.write(keymap_data) } + compile_command << '-k' << './build.keymap' + end + + if kconfig_data + File.open('build.conf', 'w') { |io| io.write(kconfig_data) } + compile_command << '-c' << './build.conf' + end + + if include_static_rhs + # Concatenate the pre-compiled glove80_rh image to the resulting uf2 + compile_command << '-m' + end + + compile_output = nil + + IO.popen(compile_command, 'rb', err: [:child, :out]) do |io| + compile_output = io.read + end + + compile_output = compile_output.split("\n") + + unless $?.success? + status = $?.exitstatus + raise CompileError.new("Compile failed with exit status #{status}", log: compile_output) + end + + unless File.exist?('zmk.uf2') + raise CompileError.new('Compile failed to produce result binary', status: 500, log: compile_output) + end + + result = File.read('zmk.uf2') + + [result, compile_output] + end + end + + PERMITTED_DTS_SECTIONS = %w[ + behaviors macros combos conditional_layers keymap underglow-indicators + ].freeze + + def validate_devicetree!(dtsi) + dts = "/dts-v1/;\n" + dtsi + + stdout, stderr, status = + Open3.capture3({}, 'dts2yml', unsetenv_others: true, stdin_data: dts) + + unless status.success? + raise CompileError.new('Syntax error checking device-tree input', log: stderr.split("\n")) + end + + data = + begin + YAML.safe_load(stdout) + rescue Psych::Exception => e + raise CompileError.new('Error parsing translated device-tree', status: 500, log: [e.message]) + end + + sections = data.flat_map(&:keys) + invalid_sections = sections - PERMITTED_DTS_SECTIONS + + unless invalid_sections.empty? + raise CompileError.new( + "Device-tree included the non-permitted root sections: #{invalid_sections.inspect}", log: []) + end + end + + # Lambda is single-process per container, and we get substantial speedups + # from ccache by always building in the same path + BUILD_DIR = '/tmp/build' + + def in_build_dir + FileUtils.remove_entry(BUILD_DIR, true) + Dir.mkdir(BUILD_DIR) + Dir.chdir(BUILD_DIR) + yield + ensure + FileUtils.remove_entry(BUILD_DIR, true) rescue nil + end +end diff --git a/lambda/default.nix b/lambda/default.nix new file mode 100644 index 00000000000..6a34b0d1122 --- /dev/null +++ b/lambda/default.nix @@ -0,0 +1,26 @@ +{ pkgs ? import {} }: + +with pkgs; + +let + bundleEnv = bundlerEnv { + name = "lambda-bundler-env"; + ruby = ruby_3_1; + gemfile = ./Gemfile; + lockfile = ./Gemfile.lock; + gemset = ./gemset.nix; + }; + + source = stdenv.mkDerivation { + name = "lambda-builder"; + version = "0.0.1"; + src = ./.; + installPhase = '' + cp -r ./ $out + ''; + }; + +in +{ + inherit bundleEnv source; +} diff --git a/lambda/gemset.nix b/lambda/gemset.nix new file mode 100644 index 00000000000..6b2fd1a0207 --- /dev/null +++ b/lambda/gemset.nix @@ -0,0 +1,12 @@ +{ + aws_lambda_ric = { + groups = ["default"]; + platforms = []; + source = { + remotes = ["https://rubygems.org"]; + sha256 = "19c4xlgnhgwf3n3z57z16nmr76jd2vihhshknm5zqip2g00awhi1"; + type = "gem"; + }; + version = "2.0.0"; + }; +} diff --git a/lambda/shell.nix b/lambda/shell.nix new file mode 100644 index 00000000000..2f1eca8bb75 --- /dev/null +++ b/lambda/shell.nix @@ -0,0 +1,9 @@ +{ pkgs ? (import {})}: + +let + lambda = import ./default.nix { inherit pkgs; }; +in +pkgs.stdenv.mkDerivation { + name = "lambda-shell"; + buildInputs = [lambda.bundleEnv.wrappedRuby]; +} diff --git a/nix/ccache.nix b/nix/ccache.nix new file mode 100644 index 00000000000..030153140e2 --- /dev/null +++ b/nix/ccache.nix @@ -0,0 +1,43 @@ +{ stdenv, lib, makeWrapper, ccache +, unwrappedCC ? stdenv.cc.cc, extraConfig ? "" }: + +# copied from ccache in nixpkgs, modified to glob over prefixes. Also doesn't +# pass lib. Why was it passing lib? +stdenv.mkDerivation { + name = "ccache-links"; + passthru = { + isClang = unwrappedCC.isClang or false; + isGNU = unwrappedCC.isGNU or false; + }; + nativeBuildInputs = [ makeWrapper ]; + buildCommand = '' + mkdir -p $out/bin + + wrap() { + local cname="$(basename $1)" + if [ -x "${unwrappedCC}/bin/$cname" ]; then + echo "Wrapping $1" + makeWrapper ${ccache}/bin/ccache $out/bin/$cname \ + --run ${lib.escapeShellArg extraConfig} \ + --add-flags ${unwrappedCC}/bin/$cname + fi + } + + wrapAll() { + for prog in "$@"; do + wrap "$prog" + done + } + + wrapAll ${unwrappedCC}/bin/{*cc,*c++,*gcc,*g++,*clang,*clang++} + + for executable in $(ls ${unwrappedCC}/bin); do + if [ ! -x "$out/bin/$executable" ]; then + ln -s ${unwrappedCC}/bin/$executable $out/bin/$executable + fi + done + for file in $(ls ${unwrappedCC} | grep -vw bin); do + ln -s ${unwrappedCC}/$file $out/$file + done + ''; +} diff --git a/nix/zmk.nix b/nix/zmk.nix index bf4caec307d..7ac3b62a627 100644 --- a/nix/zmk.nix +++ b/nix/zmk.nix @@ -4,6 +4,7 @@ , board ? "glove80_lh" , shield ? null , keymap ? null +, kconfig ? null }: @@ -66,7 +67,9 @@ stdenvNoCC.mkDerivation { # Transient state relPath == "build" || relPath == ".west" || # Fetched by west - relPath == "modules" || relPath == "tools" || relPath == "zephyr" + relPath == "modules" || relPath == "tools" || relPath == "zephyr" || + # Not part of ZMK + relPath == "lambda" || relPath == ".github" ); }; @@ -90,7 +93,8 @@ stdenvNoCC.mkDerivation { "-DZEPHYR_MODULES=${lib.concatStringsSep ";" zephyrModuleDeps}" ] ++ (lib.optional (shield != null) "-DSHIELD=${shield}") ++ - (lib.optional (keymap != null) "-DKEYMAP_FILE=${keymap}"); + (lib.optional (keymap != null) "-DKEYMAP_FILE=${keymap}") ++ + (lib.optional (kconfig != null) "-DCONF_FILE=${kconfig}"); nativeBuildInputs = [ cmake ninja python dtc gcc-arm-embedded ]; buildInputs = [ zephyr ]; diff --git a/release.nix b/release.nix new file mode 100644 index 00000000000..7366f6ed1e1 --- /dev/null +++ b/release.nix @@ -0,0 +1,208 @@ +{ pkgs ? (import ./nix/pinned-nixpkgs.nix {}), revision ? "HEAD" }: + +let + lib = pkgs.lib; + zmkPkgs = (import ./default.nix { inherit pkgs; }); + lambda = (import ./lambda { inherit pkgs; }); + ccacheWrapper = pkgs.callPackage ./nix/ccache.nix {}; + + nix-utils = pkgs.fetchFromGitHub { + owner = "iknow"; + repo = "nix-utils"; + rev = "c13c7a23836c8705452f051d19fc4dff05533b53"; + sha256 = "0ax7hld5jf132ksdasp80z34dlv75ir0ringzjs15mimrkw8zcac"; + }; + + ociTools = pkgs.callPackage "${nix-utils}/oci" {}; + + inherit (zmkPkgs) zmk zephyr; + + accounts = { + users.deploy = { + uid = 999; + group = "deploy"; + home = "/home/deploy"; + shell = "/bin/sh"; + }; + groups.deploy.gid = 999; + }; + + baseLayer = { + name = "base-layer"; + path = [ pkgs.busybox ]; + entries = ociTools.makeFilesystem { + inherit accounts; + tmp = true; + usrBinEnv = "${pkgs.busybox}/bin/env"; + binSh = "${pkgs.busybox}/bin/sh"; + }; + }; + + depsLayer = { + name = "deps-layer"; + path = [ pkgs.ccache ]; + includes = zmk.buildInputs ++ zmk.nativeBuildInputs ++ zmk.zephyrModuleDeps; + }; + + dts2yml = pkgs.writeShellScriptBin "dts2yml" '' + set -eo pipefail + + ${pkgs.gcc-arm-embedded}/bin/arm-none-eabi-cpp -P -D__DTS__ -E -nostdinc \ + -I "${zmk.src}/app/dts" -I "${zmk.src}/app/include" \ + -I "${zephyr}/zephyr/dts" -I "${zephyr}/zephyr/dts/common" -I "${zephyr}/zephyr/dts/arm" \ + -I "${zephyr}/zephyr/include" -I "${zephyr}/zephyr/include/zephyr"\ + -undef -x assembler-with-cpp - |\ + ${pkgs.dtc}/bin/dtc -I dts -O yaml + ''; + + zmkCompileScript = let + zmk' = zmk.override { + gcc-arm-embedded = ccacheWrapper.override { + unwrappedCC = pkgs.gcc-arm-embedded; + }; + }; + zmk_glove80_rh = zmk.override { board = "glove80_rh"; }; + realpath_coreutils = if pkgs.stdenv.isDarwin then pkgs.coreutils else pkgs.busybox; + in pkgs.writeShellScriptBin "compileZmk" '' + set -eo pipefail + + function usage() { + echo "Usage: compileZmk [-m] [-k keymap_file] [-c kconfig_file] [-b board]" + } + + function checkPath() { + if [ -z "$1" ]; then + return 0 + elif [ ! -f "$1" ]; then + echo "Error: Missing $2 file" >&2 + usage >&2 + exit 1 + fi + + ${realpath_coreutils}/bin/realpath "$1" + } + + keymap="${zmk.src}/app/boards/arm/glove80/glove80.keymap" + kconfig="" + board="glove80_lh" + merge_rhs="" + + while getopts "hk:c:d:b:m" opt; do + case "$opt" in + h|\?) + usage >&2 + exit 1 + ;; + k) + keymap="$OPTARG" + ;; + c) + kconfig="$OPTARG" + ;; + b) + board="$OPTARG" + ;; + m) + merge_rhs=t + ;; + esac + done + + if [ "$board" = "glove80_rh" -a -n "$merge_rhs" ]; then + echo "Cannot merge static RHS with built RHS" >&2 + exit 2 + fi + + keymap="$(checkPath "$keymap" keymap)" + kconfig="$(checkPath "$kconfig" Kconfig)" + + export PATH=${lib.makeBinPath (with pkgs; zmk'.nativeBuildInputs ++ [ ccache ])}:$PATH + export CMAKE_PREFIX_PATH=${zephyr} + + export CCACHE_BASEDIR=$PWD + export CCACHE_NOHASHDIR=t + export CCACHE_COMPILERCHECK=none + + if [ -n "$DEBUG" ]; then ccache -z; fi + + cmake -G Ninja -S ${zmk'.src}/app ${lib.escapeShellArgs zmk'.cmakeFlags} "-DUSER_CACHE_DIR=/tmp/.cache" "-DKEYMAP_FILE=$keymap" "-DCONF_FILE=$kconfig" "-DBOARD=$board" "-DBUILD_VERSION=${revision}" + + ninja + + if [ -n "$DEBUG" ]; then ccache -s; fi + + if [ -n "$merge_rhs" ]; then + cat zephyr/zmk.uf2 ${zmk_glove80_rh}/zmk.uf2 > zmk.uf2 + else + mv zephyr/zmk.uf2 zmk.uf2 + fi + ''; + + ccacheCache = pkgs.runCommandNoCC "ccache-cache" { + nativeBuildInputs = [ zmkCompileScript ]; + } '' + export CCACHE_DIR=$out + + mkdir /tmp/build + cd /tmp/build + + compileZmk -b glove80_lh -k ${zmk.src}/app/boards/arm/glove80/glove80.keymap + + rm -fr /tmp/build + mkdir /tmp/build + cd /tmp/build + + compileZmk -b glove80_rh -k ${zmk.src}/app/boards/arm/glove80/glove80.keymap + ''; + + entrypoint = pkgs.writeShellScriptBin "entrypoint" '' + set -euo pipefail + + if [ ! -d "$CCACHE_DIR" ]; then + cp -r ${ccacheCache} "$CCACHE_DIR" + chmod -R u=rwX,go=u-w "$CCACHE_DIR" + fi + + if [ ! -d /tmp/build ]; then + mkdir /tmp/build + fi + + exec "$@" + ''; + + startLambda = pkgs.writeShellScriptBin "startLambda" '' + set -euo pipefail + export PATH=${lib.makeBinPath [ zmkCompileScript dts2yml ]}:$PATH + cd ${lambda.source} + ${lambda.bundleEnv}/bin/bundle exec aws_lambda_ric "app.LambdaFunction::Handler.process" + ''; + + simulateLambda = pkgs.writeShellScriptBin "simulateLambda" '' + ${pkgs.aws-lambda-rie}/bin/aws-lambda-rie ${startLambda}/bin/startLambda + ''; + + lambdaImage = + let + appLayer = { + name = "app-layer"; + path = [ startLambda zmkCompileScript ]; + }; + in + ociTools.makeSimpleImage { + name = "zmk-builder-lambda"; + layers = [ baseLayer depsLayer appLayer ]; + config = { + User = "deploy"; + WorkingDir = "/tmp"; + Entrypoint = [ "${entrypoint}/bin/entrypoint" ]; + Cmd = [ "startLambda" ]; + Env = [ "CCACHE_DIR=/tmp/ccache" "REVISION=${revision}" ]; + }; + }; +in { + inherit lambdaImage zmkCompileScript dts2yml ccacheCache; + directLambdaImage = lambdaImage; + + # nix shell -f release.nix simulateLambda -c simulateLambda + inherit simulateLambda; +} From 22d4db93d9fcd0544e7c760a1ff0a5fdb76b933c Mon Sep 17 00:00:00 2001 From: moergo-sc Date: Sun, 19 Sep 2021 13:05:21 +1200 Subject: [PATCH 09/10] Configure Glove80 board definitions for custom features Additionally adds a board definition for Glove80-v0, an early version of Glove80. --- app/boards/arm/glove80/glove80.keymap | 35 ++++- app/boards/arm/glove80/glove80_lh.dts | 27 ++++ app/boards/arm/glove80/glove80_lh_defconfig | 18 +++ app/boards/arm/glove80/glove80_rh_defconfig | 7 +- app/boards/arm/glove80_v0/Kconfig | 7 + app/boards/arm/glove80_v0/Kconfig.board | 12 ++ app/boards/arm/glove80_v0/Kconfig.defconfig | 66 +++++++++ app/boards/arm/glove80_v0/board.cmake | 5 + app/boards/arm/glove80_v0/glove80_v0.dtsi | 100 +++++++++++++ app/boards/arm/glove80_v0/glove80_v0.keymap | 56 ++++++++ .../arm/glove80_v0/glove80_v0_lh-pinctrl.dtsi | 47 ++++++ app/boards/arm/glove80_v0/glove80_v0_lh.dts | 135 ++++++++++++++++++ .../arm/glove80_v0/glove80_v0_lh.keymap | 1 + app/boards/arm/glove80_v0/glove80_v0_lh.yaml | 15 ++ app/boards/arm/glove80_v0/glove80_v0_lh.yml | 10 ++ .../arm/glove80_v0/glove80_v0_lh_defconfig | 50 +++++++ .../arm/glove80_v0/glove80_v0_rh-pinctrl.dtsi | 47 ++++++ app/boards/arm/glove80_v0/glove80_v0_rh.dts | 118 +++++++++++++++ .../arm/glove80_v0/glove80_v0_rh.keymap | 1 + app/boards/arm/glove80_v0/glove80_v0_rh.yaml | 15 ++ app/boards/arm/glove80_v0/glove80_v0_rh.yml | 10 ++ .../arm/glove80_v0/glove80_v0_rh_defconfig | 50 +++++++ 22 files changed, 827 insertions(+), 5 deletions(-) create mode 100644 app/boards/arm/glove80_v0/Kconfig create mode 100644 app/boards/arm/glove80_v0/Kconfig.board create mode 100644 app/boards/arm/glove80_v0/Kconfig.defconfig create mode 100644 app/boards/arm/glove80_v0/board.cmake create mode 100644 app/boards/arm/glove80_v0/glove80_v0.dtsi create mode 100644 app/boards/arm/glove80_v0/glove80_v0.keymap create mode 100644 app/boards/arm/glove80_v0/glove80_v0_lh-pinctrl.dtsi create mode 100644 app/boards/arm/glove80_v0/glove80_v0_lh.dts create mode 100644 app/boards/arm/glove80_v0/glove80_v0_lh.keymap create mode 100644 app/boards/arm/glove80_v0/glove80_v0_lh.yaml create mode 100644 app/boards/arm/glove80_v0/glove80_v0_lh.yml create mode 100644 app/boards/arm/glove80_v0/glove80_v0_lh_defconfig create mode 100644 app/boards/arm/glove80_v0/glove80_v0_rh-pinctrl.dtsi create mode 100644 app/boards/arm/glove80_v0/glove80_v0_rh.dts create mode 100644 app/boards/arm/glove80_v0/glove80_v0_rh.keymap create mode 100644 app/boards/arm/glove80_v0/glove80_v0_rh.yaml create mode 100644 app/boards/arm/glove80_v0/glove80_v0_rh.yml create mode 100644 app/boards/arm/glove80_v0/glove80_v0_rh_defconfig diff --git a/app/boards/arm/glove80/glove80.keymap b/app/boards/arm/glove80/glove80.keymap index 60129bd94e9..6806be79f97 100644 --- a/app/boards/arm/glove80/glove80.keymap +++ b/app/boards/arm/glove80/glove80.keymap @@ -11,10 +11,13 @@ #include #include +#define HYPER LC(LS(LG(LALT))) + // layers #define DEFAULT 0 #define LOWER 1 #define MAGIC 2 +#define FACTORY_TEST 3 / { behaviors { @@ -26,9 +29,24 @@ tapping-term-ms = <200>; bindings = <&mo LOWER>, <&to LOWER>; }; + + magic: magic_hold_tap { + compatible = "zmk,behavior-hold-tap"; + #binding-cells = <2>; + flavor = "tap-preferred"; + tapping-term-ms = <200>; + bindings = <&mo>, <&rgb_ug_status_macro>; + }; }; macros { + rgb_ug_status_macro: rgb_ug_status_macro_0 { + compatible = "zmk,behavior-macro"; + #binding-cells = <0>; + bindings + = <&rgb_ug RGB_STATUS>; + }; + bt_0: bt_profile_macro_0 { compatible = "zmk,behavior-macro"; #binding-cells = <0>; @@ -80,7 +98,7 @@ &kp TAB &kp Q &kp W &kp E &kp R &kp T &kp Y &kp U &kp I &kp O &kp P &kp BSLH &kp ESC &kp A &kp S &kp D &kp F &kp G &kp H &kp J &kp K &kp L &kp SEMI &kp SQT &kp GRAVE &kp Z &kp X &kp C &kp V &kp B &kp LSHFT &kp LCTRL &layer_td &kp LGUI &kp RCTRL &kp RSHFT &kp N &kp M &kp COMMA &kp DOT &kp FSLH &kp PG_UP - &mo MAGIC &kp HOME &kp END &kp LEFT &kp RIGHT &kp BSPC &kp DEL &kp LALT &kp RALT &kp RET &kp SPACE &kp UP &kp DOWN &kp LBKT &kp RBKT &kp PG_DN + &magic MAGIC 0 &kp HOME &kp END &kp LEFT &kp RIGHT &kp BSPC &kp DEL &kp LALT &kp RALT &kp RET &kp SPACE &kp UP &kp DOWN &kp LBKT &kp RBKT &kp PG_DN >; }; @@ -97,12 +115,23 @@ magic_layer { bindings = < - &bt BT_CLR &none &none &none &none &none &none &none &none &none + &bt BT_CLR &none &none &none &none &none &none &none &none &bt BT_CLR_ALL &none &none &none &none &none &none &none &none &none &none &none &none &none &rgb_ug RGB_SPI &rgb_ug RGB_SAI &rgb_ug RGB_HUI &rgb_ug RGB_BRI &rgb_ug RGB_TOG &none &none &none &none &none &none &bootloader &rgb_ug RGB_SPD &rgb_ug RGB_SAD &rgb_ug RGB_HUD &rgb_ug RGB_BRD &rgb_ug RGB_EFF &none &none &none &none &none &bootloader &sys_reset &none &none &none &none &none &bt_2 &bt_3 &none &none &none &none &none &none &none &none &none &sys_reset - &none &none &none &none &none &bt_0 &bt_1 &out OUT_USB &none &none &none &none &none &none &none &none + &none &none &none &none &none &bt_0 &bt_1 &out OUT_USB &none &none &none &none &none &none &none &to FACTORY_TEST + >; + }; + + factory_test_layer { + bindings = < + &kp N0 &kp N6 &kp N2 &kp N8 &kp N4 &kp N4 &kp N8 &kp N2 &kp N6 &kp N0 + &kp N1 &kp N7 &kp N3 &kp N9 &kp N5 &kp N0 &kp N0 &kp N5 &kp N9 &kp N3 &kp N7 &kp N1 + &kp N2 &kp N8 &kp N4 &kp N0 &kp N6 &kp N1 &kp N1 &kp N6 &kp N0 &kp N4 &kp N8 &kp N2 + &kp N3 &kp N9 &kp N5 &kp N1 &kp N7 &kp N2 &kp N2 &kp N7 &kp N1 &kp N5 &kp N9 &kp N3 + &kp N4 &kp N0 &kp N6 &kp N2 &kp N8 &kp N3 &kp N4 &kp N5 &kp N6 &kp N6 &kp N5 &kp N4 &kp N3 &kp N8 &kp N2 &kp N6 &kp N0 &kp N4 + &kp N5 &kp N1 &kp N7 &kp N3 &kp N9 &kp N7 &kp N8 &kp N9 &kp N9 &kp N8 &kp N7 &kp N9 &kp N3 &kp N7 &kp N1 &kp N5 >; }; }; diff --git a/app/boards/arm/glove80/glove80_lh.dts b/app/boards/arm/glove80/glove80_lh.dts index 5ef54207127..43b620a3cbc 100644 --- a/app/boards/arm/glove80/glove80_lh.dts +++ b/app/boards/arm/glove80/glove80_lh.dts @@ -17,6 +17,7 @@ zmk,underglow = &led_strip; zmk,backlight = &back_led_backlight; zmk,battery = &vbatt; + zmk,underglow-indicators = &underglow_indicators; }; back_led_backlight: pwmleds { @@ -36,6 +37,32 @@ vbatt: vbatt { compatible = "zmk,battery-nrf-vddh"; }; + +/* + MoErgo 40 LEDs + + 34 28 22 16 10 + 35 29 23 17 11 6 + 36 30 24 18 12 7 + 37 31 25 19 13 8 + 38 32 26 20 14 9 + 39 33 27 21 15 + 0 1 2 + 3 4 5 +*/ + + underglow_indicators: underglow-indicators { + compatible = "zmk,underglow-indicators"; + layer-state = <35 29 23 17 11 6>; + bat-lhs = <36 30 24 18 12 7>; + bat-rhs = <37 31 25 19 13 8>; + capslock = <22>; + numlock = <16>; + scrolllock = <10>; + ble-state = <3 4 0 1>; + usb-state = <5>; + output-fallback = <15>; + }; }; &spi3 { diff --git a/app/boards/arm/glove80/glove80_lh_defconfig b/app/boards/arm/glove80/glove80_lh_defconfig index b454e8be1bf..46f0f1675a5 100644 --- a/app/boards/arm/glove80/glove80_lh_defconfig +++ b/app/boards/arm/glove80/glove80_lh_defconfig @@ -28,6 +28,17 @@ CONFIG_BT_CTLR_TX_PWR_PLUS_8=y # Work-around for Windows bug with battery notifications CONFIG_BT_GATT_ENFORCE_SUBSCRIPTION=n +# Allow unauthenticated re-pairing for already paired hosts. This would permit +# an attacker that can spoof the host's peer address to "steal" the keyboard +# pairing by overwriting it, but without access to the previous keys it can't +# establish a MITM, and the sudden loss of the keyboard would be very obvious to +# the previously-connected host. +CONFIG_BT_SMP_ALLOW_UNAUTH_OVERWRITE=y +CONFIG_ZMK_BLE_PASSKEY_ENTRY=n + +# Fetch peripheral battery level for status display reporting +CONFIG_ZMK_SPLIT_BLE_CENTRAL_BATTERY_LEVEL_FETCHING=y + # Enable MPU CONFIG_ARM_MPU=y @@ -53,6 +64,9 @@ CONFIG_CLOCK_CONTROL_NRF_K32SRC_XTAL=y # Enable RGB underglow CONFIG_ZMK_RGB_UNDERGLOW=y +# disable EXT_POWER until underglow gets turned on +CONFIG_ZMK_EXT_POWER_START=n + CONFIG_ZMK_RGB_UNDERGLOW_EXT_POWER=y CONFIG_ZMK_RGB_UNDERGLOW_ON_START=n CONFIG_ZMK_RGB_UNDERGLOW_BRT_STEP=4 @@ -81,6 +95,10 @@ CONFIG_ZMK_BACKLIGHT_AUTO_OFF_USB=y # space. CONFIG_ZMK_HID_CONSUMER_REPORT_USAGES_BASIC=y +# Enable USB boot protocol support +CONFIG_ZMK_USB_BOOT=y +CONFIG_ZMK_HID_INDICATORS=y + # Turn on debugging to disable optimization. Debug messages can result in larger # stacks, so enable stack protection and particularly a larger BLE peripheral stack. # CONFIG_DEBUG=y diff --git a/app/boards/arm/glove80/glove80_rh_defconfig b/app/boards/arm/glove80/glove80_rh_defconfig index ef29d682a54..d1792f65439 100644 --- a/app/boards/arm/glove80/glove80_rh_defconfig +++ b/app/boards/arm/glove80/glove80_rh_defconfig @@ -5,8 +5,8 @@ CONFIG_SOC_SERIES_NRF52X=y CONFIG_SOC_NRF52840_QIAA=y CONFIG_BOARD_GLOVE80_RH=y -# Enable both USB and BLE -CONFIG_ZMK_USB=y +# Enable BLE for split peripheral +CONFIG_ZMK_USB=n CONFIG_ZMK_BLE=y # Keyboard IDs @@ -48,6 +48,9 @@ CONFIG_CLOCK_CONTROL_NRF_K32SRC_XTAL=y # Enable RGB underglow CONFIG_ZMK_RGB_UNDERGLOW=y +# disable EXT_POWER until underglow gets turned on +CONFIG_ZMK_EXT_POWER_START=n + CONFIG_ZMK_RGB_UNDERGLOW_EXT_POWER=y CONFIG_ZMK_RGB_UNDERGLOW_ON_START=n CONFIG_ZMK_RGB_UNDERGLOW_BRT_STEP=4 diff --git a/app/boards/arm/glove80_v0/Kconfig b/app/boards/arm/glove80_v0/Kconfig new file mode 100644 index 00000000000..f971e6c81da --- /dev/null +++ b/app/boards/arm/glove80_v0/Kconfig @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: MIT + +config BOARD_ENABLE_DCDC + bool "Enable DCDC mode" + select SOC_DCDC_NRF52X + default y + depends on (BOARD_GLOVE80_V0_LH || BOARD_GLOVE80_V0_RH) diff --git a/app/boards/arm/glove80_v0/Kconfig.board b/app/boards/arm/glove80_v0/Kconfig.board new file mode 100644 index 00000000000..ce97e890fbc --- /dev/null +++ b/app/boards/arm/glove80_v0/Kconfig.board @@ -0,0 +1,12 @@ +# nice!nano board configuration + +# Copyright (c) 2020 Pete Johanson +# SPDX-License-Identifier: MIT + +config BOARD_GLOVE80_V0_LH + bool "Glove80 v0 LH" + depends on SOC_NRF52840_QIAA + +config BOARD_GLOVE80_V0_RH + bool "Glove80 v0 RH" + depends on SOC_NRF52840_QIAA diff --git a/app/boards/arm/glove80_v0/Kconfig.defconfig b/app/boards/arm/glove80_v0/Kconfig.defconfig new file mode 100644 index 00000000000..9e68b18dc89 --- /dev/null +++ b/app/boards/arm/glove80_v0/Kconfig.defconfig @@ -0,0 +1,66 @@ +# Copyright (c) 2021 The ZMK Contributors +# SPDX-License-Identifier: MIT + +if BOARD_GLOVE80_V0_LH +config BOARD + default "glove80 v0 lh" + +config ZMK_SPLIT_BLE_ROLE_CENTRAL + default y + +config ZMK_KEYBOARD_NAME + default "Glove80 V0 Left" +endif # BOARD_GLOVE80_V0_LH + +if BOARD_GLOVE80_V0_RH +config BOARD + default "glove80 v0 rh" + +config ZMK_KEYBOARD_NAME + default "Glove80 V0 Right" +endif # BOARD_GLOVE80_V0_RH + +if BOARD_GLOVE80_V0_LH || BOARD_GLOVE80_V0_RH + +config ZMK_SPLIT + default y + +config BT_CTLR + default BT + +config ZMK_BLE + default y + +config ZMK_USB + default y + +config ZMK_BATTERY_VOLTAGE_DIVIDER + default y + +config ZMK_BATTERY_NRF_VDDH + default y + +config PINCTRL + default y + +if USB + +config USB_NRFX + default y + +config USB_DEVICE_STACK + default y + +endif # USB + +if ZMK_BACKLIGHT + +config PWM + default y + +config LED_PWM + default y + +endif # ZMK_BACKLIGHT + +endif # BOARD_GLOVE80_V0_LH || BOARD_GLOVE80_V0_RH diff --git a/app/boards/arm/glove80_v0/board.cmake b/app/boards/arm/glove80_v0/board.cmake new file mode 100644 index 00000000000..fa847d50595 --- /dev/null +++ b/app/boards/arm/glove80_v0/board.cmake @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: MIT + +board_runner_args(nrfjprog "--nrf-family=NRF52" "--softreset") +include(${ZEPHYR_BASE}/boards/common/blackmagicprobe.board.cmake) +include(${ZEPHYR_BASE}/boards/common/nrfjprog.board.cmake) diff --git a/app/boards/arm/glove80_v0/glove80_v0.dtsi b/app/boards/arm/glove80_v0/glove80_v0.dtsi new file mode 100644 index 00000000000..0a30f4d3668 --- /dev/null +++ b/app/boards/arm/glove80_v0/glove80_v0.dtsi @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2020 Pete Johanson + * + * SPDX-License-Identifier: MIT + */ + +#include + +/ { + chosen { + zmk,kscan = &kscan0; + zmk,matrix_transform = &default_transform; + zephyr,code-partition = &code_partition; + zephyr,sram = &sram0; + zephyr,flash = &flash0; + zephyr,console = &cdc_acm_uart; + }; + + default_transform: keymap_transform_0 { + compatible = "zmk,matrix-transform"; + columns = <14>; + rows = <6>; + map = < +RC(0,0) RC(0,1) RC(0,2) RC(0,3) RC(0,4) RC(0,9) RC(0,10) RC(0,11) RC(0,12) RC(0,13) +RC(1,0) RC(1,1) RC(1,2) RC(1,3) RC(1,4) RC(1,5) RC(1,8) RC(1,9) RC(1,10) RC(1,11) RC(1,12) RC(1,13) +RC(2,0) RC(2,1) RC(2,2) RC(2,3) RC(2,4) RC(2,5) RC(2,8) RC(2,9) RC(2,10) RC(2,11) RC(2,12) RC(2,13) +RC(3,0) RC(3,1) RC(3,2) RC(3,3) RC(3,4) RC(3,5) RC(3,8) RC(3,9) RC(3,10) RC(3,11) RC(3,12) RC(3,13) +RC(4,0) RC(4,1) RC(4,2) RC(4,3) RC(4,4) RC(4,5) RC(0,6) RC(1,6) RC(2,6) RC(2,7) RC(1,7) RC(0,7) RC(4,8) RC(4,9) RC(4,10) RC(4,11) RC(4,12) RC(4,13) +RC(5,0) RC(5,1) RC(5,2) RC(5,3) RC(5,4) RC(3,6) RC(4,6) RC(5,6) RC(5,7) RC(4,7) RC(3,7) RC(5,9) RC(5,10) RC(5,11) RC(5,12) RC(5,13) + >; + }; + + kscan0: kscan { + compatible = "zmk,kscan-gpio-matrix"; + + diode-direction = "row2col"; + debounce-press-ms = <1>; + debounce-release-ms = <25>; + }; + +}; + +&adc { + status = "okay"; +}; + +&gpiote { + status = "okay"; +}; + +&gpio0 { + status = "okay"; +}; + +&gpio1 { + status = "okay"; +}; + +&usbd { + status = "okay"; + cdc_acm_uart: cdc_acm_uart { + compatible = "zephyr,cdc-acm-uart"; + }; +}; + +&flash0 { + /* + * For more information, see: + * http://docs.zephyrproject.org/latest/devices/dts/flash_partitions.html + */ + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + sd_partition: partition@0 { + reg = <0x00000000 0x00026000>; + }; + code_partition: partition@26000 { + reg = <0x00026000 0x000c6000>; + }; + + /* + * The flash starting at 0x000ec000 and ending at + * 0x000f3fff is reserved for use by the application. + */ + + /* + * Storage partition will be used by FCB/LittleFS/NVS + * if enabled. + */ + storage_partition: partition@ec000 { + reg = <0x000ec000 0x00008000>; + }; + + boot_partition: partition@f4000 { + reg = <0x000f4000 0x0000c000>; + }; + }; +}; diff --git a/app/boards/arm/glove80_v0/glove80_v0.keymap b/app/boards/arm/glove80_v0/glove80_v0.keymap new file mode 100644 index 00000000000..52a88125275 --- /dev/null +++ b/app/boards/arm/glove80_v0/glove80_v0.keymap @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2020 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include +#include +#include +#include + +#define HYPER LC(LS(LG(LALT))) + +// layers +#define DEFAULT 0 +#define LOWER 1 + +/ { + keymap { + compatible = "zmk,keymap"; + + default_layer { + // --------------------------------------------------------------------------------------------------------------------------------- + // | F1 | F2 | F3 | F4 | F5 | | F6 | F7 | F8 | F9 | F10 | + // | = | 1 | 2 | 3 | 4 | 5 | | 6 | 7 | 8 | 9 | 0 | - | + // | TAB | Q | W | E | R | T | | Y | U | I | O | P | \ | + // | ESC | A | S | D | F | G | | H | J | K | L | ; | ' | + // | ` | Z | X | C | V | B | LSHFT | LCTRL | LOWER | | LGUI | RCTRL | RSHFT | N | M | , | . | / | PGUP | + // | MAGIC | HOME| END | LEFT | RIGHT| | BSPC | DEL | LALT | | RALT | RET | SPACE | | UP | DOWN | [ | ] | PGDN | + + + // MAGIC is currently bound to the same as LAYER - these will be fixed later + bindings = < + &kp F1 &kp F2 &kp F3 &kp F4 &kp F5 &kp F6 &kp F7 &kp F8 &kp F9 &kp F10 + &kp EQUAL &kp N1 &kp N2 &kp N3 &kp N4 &kp N5 &kp N6 &kp N7 &kp N8 &kp N9 &kp N0 &kp MINUS + &kp TAB &kp Q &kp W &kp E &kp R &kp T &kp Y &kp U &kp I &kp O &kp P &kp BSLH + &kp ESC &kp A &kp S &kp D &kp F &kp G &kp H &kp J &kp K &kp L &kp SEMI &kp SQT + &kp GRAVE &kp Z &kp X &kp C &kp V &kp B &kp LSHFT &kp LCTRL &mo LOWER &kp LGUI &kp RCTRL &kp RSHFT &kp N &kp M &kp COMMA &kp DOT &kp FSLH &kp PG_UP + &mo LOWER &kp HOME &kp END &kp LEFT &kp RIGHT &kp BSPC &kp DEL &kp LALT &kp RALT &kp RET &kp SPACE &kp UP &kp DOWN &kp LBKT &kp RBKT &kp PG_DN + >; + }; + + lower_layer { + bindings = < + &bt BT_SEL 0 &bt BT_SEL 1 &bt BT_SEL 2 &bt BT_SEL 3 &bt BT_SEL 4 &none &none &none &kp F11 &kp F12 + &bt BT_CLR &bt BT_CLR &out OUT_USB &out OUT_BLE &out OUT_TOG &none &none &none &none &none &none &none + &bootloader &rgb_ug RGB_TOG &rgb_ug RGB_EFF &rgb_ug RGB_BRI &rgb_ug RGB_BRD &kp K_VOL_UP &none &none &none &none &none &bootloader + &sys_reset &none &none &none &none &kp K_VOL_DN &none &none &none &none &none &sys_reset + &ext_power EP_ON &ext_power EP_OFF &none &none &none &kp K_MUTE &none &kp HYPER &none &none &none &none &none &none &none &none &none &none + &none &kp CAPS &kp INS &none &none &none &none &none &none &none &none &none &none &none &none &none + >; + }; + }; +}; diff --git a/app/boards/arm/glove80_v0/glove80_v0_lh-pinctrl.dtsi b/app/boards/arm/glove80_v0/glove80_v0_lh-pinctrl.dtsi new file mode 100644 index 00000000000..f4437b601d2 --- /dev/null +++ b/app/boards/arm/glove80_v0/glove80_v0_lh-pinctrl.dtsi @@ -0,0 +1,47 @@ +&pinctrl { + spi1_default: spi1_default { + group1 { + psels = , + , + ; + }; + }; + + spi1_sleep: spi1_sleep { + group1 { + psels = , + , + ; + low-power-enable; + }; + }; + + pwm0_default: pwm0_default { + group1 { + psels = ; + }; + }; + + pwm0_sleep: pwm0_sleep { + group1 { + psels = ; + low-power-enable; + }; + }; + + uart0_default: uart0_default { + group1 { + psels = , + ; + }; + }; + + uart0_sleep: uart0_sleep { + group1 { + psels = , + ; + low-power-enable; + }; + }; + +}; diff --git a/app/boards/arm/glove80_v0/glove80_v0_lh.dts b/app/boards/arm/glove80_v0/glove80_v0_lh.dts new file mode 100644 index 00000000000..db8ae5a3334 --- /dev/null +++ b/app/boards/arm/glove80_v0/glove80_v0_lh.dts @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +/dts-v1/; +#include +#include +#include "glove80_v0.dtsi" +#include "glove80_v0_lh-pinctrl.dtsi" + +/ { + model = "glove80_v0_lh"; + compatible = "glove80_v0_lh"; + + chosen { + zmk,underglow = &led_strip; + zmk,backlight = &power_led_backlight; + zmk,battery = &vbatt; + zmk,underglow-indicators = &underglow_indicators; + }; + + power_led_backlight: pwmleds { + compatible = "pwm-leds"; + pwm_led_0 { + pwms = <&pwm0 0 PWM_USEC(20) PWM_POLARITY_NORMAL>; + }; + }; + + // Node name must match original "EXT_POWER" label to preserve user settings. + EXT_POWER { + compatible = "zmk,ext-power-generic"; + control-gpios = <&gpio0 30 GPIO_ACTIVE_HIGH>; /**[SC] WS2812_CE */ + init-delay-ms = <10>; + }; + + vbatt: vbatt-divider { + compatible = "zmk,battery-voltage-divider"; + io-channels = <&adc 5>; + output-ohms = <820000>; /** Double check that this is right */ + full-ohms = <(1500000 + 820000)>; + }; + + vbatt-vddh { + compatible = "zmk,battery-nrf-vddh"; + }; + +/* + MoErgo 40 LEDs + + 34 28 22 16 10 + 35 29 23 17 11 6 + 36 30 24 18 12 7 + 37 31 25 19 13 8 + 38 32 26 20 14 9 + 39 33 27 21 15 + 0 1 2 + 3 4 5 +*/ + + underglow_indicators: underglow-indicators { + compatible = "zmk,underglow-indicators"; + layer-state = <35 29 23 17 11 6>; + bat-lhs = <36 30 24 18 12 7>; + bat-rhs = <37 31 25 19 13 8>; + capslock = <22>; + numlock = <16>; + scrolllock = <10>; + ble-state = <3 4 0 1>; + usb-state = <5>; + output-fallback = <15>; + }; +}; + +&spi1 { + compatible = "nordic,nrf-spim"; + /* Cannot be used together with i2c0. */ + status = "okay"; + // Unused pins, needed for SPI definition, but not used by the ws2812 driver itself. + + pinctrl-0 = <&spi1_default>; + pinctrl-1 = <&spi1_sleep>; + pinctrl-names = "default", "sleep"; + led_strip: ws2812@0 { + compatible = "worldsemi,ws2812-spi"; + + /* SPI */ + reg = <0>; /* ignored, but necessary for SPI bindings */ + spi-max-frequency = <2000000>; + + /* WS2812 */ + chain-length = <40>; /* 18 keys have underglow at the moment */ + spi-one-frame = <0x70>; + spi-zero-frame = <0x40>; + + color-mapping = ; + }; +}; + +&pwm0 { + status = "okay"; + pinctrl-0 = <&pwm0_default>; + pinctrl-1 = <&pwm0_sleep>; + pinctrl-names = "default", "sleep"; +}; + + +// [SC] Not sure if this section is necessary +&uart0 { + compatible = "nordic,nrf-uarte"; + pinctrl-0 = <&uart0_default>; + pinctrl-1 = <&uart0_sleep>; + pinctrl-names = "default", "sleep"; +}; + +&kscan0 { + row-gpios + = <&gpio0 26 GPIO_ACTIVE_HIGH> // LH ROW1 + , <&gpio0 5 GPIO_ACTIVE_HIGH> // LH ROW2 + , <&gpio0 7 GPIO_ACTIVE_HIGH> // LH ROW3 + , <&gpio1 8 GPIO_ACTIVE_HIGH> // LH ROW4 + , <&gpio0 11 GPIO_ACTIVE_HIGH> // LH ROW5 + , <&gpio0 12 GPIO_ACTIVE_HIGH> // LH ROW6 + ; + col-gpios + = <&gpio1 1 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> // LH COL6 + , <&gpio1 3 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> // LH COL5 + , <&gpio1 5 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> // LH COL4 + , <&gpio1 7 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> // LH COL3 + , <&gpio1 6 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> // LH COL2 + , <&gpio1 4 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> // LH COL1 + , <&gpio0 2 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> // LH Thumb + ; +}; diff --git a/app/boards/arm/glove80_v0/glove80_v0_lh.keymap b/app/boards/arm/glove80_v0/glove80_v0_lh.keymap new file mode 100644 index 00000000000..1a5a04b3937 --- /dev/null +++ b/app/boards/arm/glove80_v0/glove80_v0_lh.keymap @@ -0,0 +1 @@ +#include "glove80_v0.keymap" \ No newline at end of file diff --git a/app/boards/arm/glove80_v0/glove80_v0_lh.yaml b/app/boards/arm/glove80_v0/glove80_v0_lh.yaml new file mode 100644 index 00000000000..56b575ac27e --- /dev/null +++ b/app/boards/arm/glove80_v0/glove80_v0_lh.yaml @@ -0,0 +1,15 @@ +identifier: glove80_v0_lh +name: Glove80_V0_LH +type: mcu +arch: arm +toolchain: + - zephyr + - gnuarmemb + - xtools +supported: + - adc + - usb_device + - ble + - ieee802154 + - pwm + - watchdog diff --git a/app/boards/arm/glove80_v0/glove80_v0_lh.yml b/app/boards/arm/glove80_v0/glove80_v0_lh.yml new file mode 100644 index 00000000000..407021c51df --- /dev/null +++ b/app/boards/arm/glove80_v0/glove80_v0_lh.yml @@ -0,0 +1,10 @@ +file_format: "1" +id: glove80_v0_lh +name: Glove80_V0_LH +type: board +arch: arm +outputs: + - usb + - ble +url: https://www.moergo.com +exposes: diff --git a/app/boards/arm/glove80_v0/glove80_v0_lh_defconfig b/app/boards/arm/glove80_v0/glove80_v0_lh_defconfig new file mode 100644 index 00000000000..4d5676a1231 --- /dev/null +++ b/app/boards/arm/glove80_v0/glove80_v0_lh_defconfig @@ -0,0 +1,50 @@ +# SPDX-License-Identifier: MIT + +CONFIG_SOC_SERIES_NRF52X=y +CONFIG_SOC_NRF52840_QIAA=y +CONFIG_BOARD_GLOVE80_V0_LH=y + +# Enable MPU +CONFIG_ARM_MPU=y + +# enable GPIO +CONFIG_GPIO=y + +CONFIG_BUILD_OUTPUT_UF2=y + +CONFIG_USE_DT_CODE_PARTITION=y + +CONFIG_MPU_ALLOW_FLASH_WRITE=y +CONFIG_NVS=y +CONFIG_SETTINGS_NVS=y +CONFIG_FLASH=y +CONFIG_FLASH_PAGE_LAYOUT=y +CONFIG_FLASH_MAP=y + +# Enable RGB underglow +CONFIG_ZMK_RGB_UNDERGLOW=y +CONFIG_WS2812_STRIP=y +CONFIG_SPI=y + +CONFIG_ZMK_RGB_UNDERGLOW_ON_START=n +CONFIG_ZMK_RGB_UNDERGLOW_EXT_POWER=y +CONFIG_ZMK_RGB_UNDERGLOW_BRT_STEP=4 + +CONFIG_ZMK_RGB_UNDERGLOW_EFF_START=3 +CONFIG_ZMK_RGB_UNDERGLOW_HUE_START=285 +CONFIG_ZMK_RGB_UNDERGLOW_SAT_START=75 +CONFIG_ZMK_RGB_UNDERGLOW_BRT_START=20 + +# The power LED is implemented as a backlight +CONFIG_ZMK_BACKLIGHT=y +CONFIG_ZMK_BACKLIGHT_ON_START=y +CONFIG_ZMK_BACKLIGHT_BRT_START=5 +CONFIG_ZMK_BACKLIGHT_AUTO_OFF_IDLE=y +CONFIG_ZMK_BACKLIGHT_AUTO_OFF_USB=y + +# Turn on logging, and set ZMK logging to debug output +# Only for debugging +CONFIG_ZMK_USB_LOGGING=n + +# Turn on debugging to disable optimization +CONFIG_DEBUG=n diff --git a/app/boards/arm/glove80_v0/glove80_v0_rh-pinctrl.dtsi b/app/boards/arm/glove80_v0/glove80_v0_rh-pinctrl.dtsi new file mode 100644 index 00000000000..08f649f759f --- /dev/null +++ b/app/boards/arm/glove80_v0/glove80_v0_rh-pinctrl.dtsi @@ -0,0 +1,47 @@ +&pinctrl { + spi1_default: spi1_default { + group1 { + psels = , + , + ; + }; + }; + + spi1_sleep: spi1_sleep { + group1 { + psels = , + , + ; + low-power-enable; + }; + }; + + pwm0_default: pwm0_default { + group1 { + psels = ; + }; + }; + + pwm0_sleep: pwm0_sleep { + group1 { + psels = ; + low-power-enable; + }; + }; + + uart0_default: uart0_default { + group1 { + psels = , + ; + }; + }; + + uart0_sleep: uart0_sleep { + group1 { + psels = , + ; + low-power-enable; + }; + }; + +}; diff --git a/app/boards/arm/glove80_v0/glove80_v0_rh.dts b/app/boards/arm/glove80_v0/glove80_v0_rh.dts new file mode 100644 index 00000000000..c2185da7817 --- /dev/null +++ b/app/boards/arm/glove80_v0/glove80_v0_rh.dts @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2021 The ZMK Contributors + * + * SPDX-License-Identifier: MIT + */ + +/dts-v1/; +#include +#include +#include "glove80_v0.dtsi" +#include "glove80_v0_rh-pinctrl.dtsi" + +/ { + model = "glove80_v0_rh"; + compatible = "glove80_v0_rh"; + + chosen { + zmk,underglow = &led_strip; + zmk,backlight = &power_led_backlight; + zmk,battery = &vbatt; + }; + + leds { + compatible = "gpio-leds"; + red_led: led_0 { + gpios = <&gpio0 20 GPIO_ACTIVE_HIGH>; + }; + }; + + power_led_backlight: pwmleds { + compatible = "pwm-leds"; + pwm_led_0 { + pwms = <&pwm0 0 PWM_USEC(20) PWM_POLARITY_NORMAL>; + }; + }; + + // Node name must match original "EXT_POWER" label to preserve user settings. + EXT_POWER { + compatible = "zmk,ext-power-generic"; + control-gpios = <&gpio0 21 GPIO_ACTIVE_HIGH>; /**[SC] WS2812_CE */ + init-delay-ms = <10>; + }; + + vbatt: vbatt-divider { + compatible = "zmk,battery-voltage-divider"; + io-channels = <&adc 5>; + output-ohms = <820000>; /** Double check that this is right */ + full-ohms = <(1500000 + 820000)>; + }; + + vbatt-vddh { + compatible = "zmk,battery-nrf-vddh"; + }; +}; + +&spi1 { + compatible = "nordic,nrf-spim"; + /* Cannot be used together with i2c0. */ + status = "okay"; + // Unused pins, needed for SPI definition, but not used by the ws2812 driver itself. + + pinctrl-0 = <&spi1_default>; + pinctrl-1 = <&spi1_sleep>; + pinctrl-names = "default", "sleep"; + led_strip: ws2812@0 { + compatible = "worldsemi,ws2812-spi"; + + /* SPI */ + reg = <0>; /* ignored, but necessary for SPI bindings */ + spi-max-frequency = <2000000>; + + /* WS2812 */ + chain-length = <40>; + spi-one-frame = <0x70>; + spi-zero-frame = <0x40>; + + color-mapping = ; + }; +}; + +&pwm0 { + status = "okay"; + pinctrl-0 = <&pwm0_default>; + pinctrl-1 = <&pwm0_sleep>; + pinctrl-names = "default", "sleep"; +}; + +// [SC] Not sure if this section is necessary +&uart0 { + compatible = "nordic,nrf-uarte"; + pinctrl-0 = <&uart0_default>; + pinctrl-1 = <&uart0_sleep>; + pinctrl-names = "default", "sleep"; +}; + +&default_transform { + col-offset = <7>; +}; + +&kscan0 { + row-gpios + = <&gpio0 26 GPIO_ACTIVE_HIGH> // RH ROW1 + , <&gpio0 5 GPIO_ACTIVE_HIGH> // RH ROW2 + , <&gpio0 7 GPIO_ACTIVE_HIGH> // RH ROW3 + , <&gpio1 8 GPIO_ACTIVE_HIGH> // RH ROW4 + , <&gpio0 11 GPIO_ACTIVE_HIGH> // RH ROW5 + , <&gpio0 12 GPIO_ACTIVE_HIGH> // RH ROW6 + ; + col-gpios + = <&gpio0 2 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> // RH Thumb + , <&gpio1 4 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> // RH COL1 + , <&gpio1 6 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> // RH COL2 + , <&gpio1 7 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> // RH COL3 + , <&gpio1 5 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> // RH COL4 + , <&gpio1 3 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> // RH COL5 + , <&gpio1 1 (GPIO_ACTIVE_HIGH | GPIO_PULL_DOWN)> // RH COL6 + ; +}; diff --git a/app/boards/arm/glove80_v0/glove80_v0_rh.keymap b/app/boards/arm/glove80_v0/glove80_v0_rh.keymap new file mode 100644 index 00000000000..1a5a04b3937 --- /dev/null +++ b/app/boards/arm/glove80_v0/glove80_v0_rh.keymap @@ -0,0 +1 @@ +#include "glove80_v0.keymap" \ No newline at end of file diff --git a/app/boards/arm/glove80_v0/glove80_v0_rh.yaml b/app/boards/arm/glove80_v0/glove80_v0_rh.yaml new file mode 100644 index 00000000000..222ae1770e2 --- /dev/null +++ b/app/boards/arm/glove80_v0/glove80_v0_rh.yaml @@ -0,0 +1,15 @@ +identifier: glove80_v0_rh +name: Glove80_V0_RH +type: mcu +arch: arm +toolchain: + - zephyr + - gnuarmemb + - xtools +supported: + - adc + - usb_device + - ble + - ieee802154 + - pwm + - watchdog diff --git a/app/boards/arm/glove80_v0/glove80_v0_rh.yml b/app/boards/arm/glove80_v0/glove80_v0_rh.yml new file mode 100644 index 00000000000..41333f08662 --- /dev/null +++ b/app/boards/arm/glove80_v0/glove80_v0_rh.yml @@ -0,0 +1,10 @@ +file_format: "1" +id: glove80_v0_rh +name: Glove80_V0_RH +type: board +arch: arm +outputs: + - usb + - ble +url: https://www.moergo.com +exposes: diff --git a/app/boards/arm/glove80_v0/glove80_v0_rh_defconfig b/app/boards/arm/glove80_v0/glove80_v0_rh_defconfig new file mode 100644 index 00000000000..71b5f2f6215 --- /dev/null +++ b/app/boards/arm/glove80_v0/glove80_v0_rh_defconfig @@ -0,0 +1,50 @@ +# SPDX-License-Identifier: MIT + +CONFIG_SOC_SERIES_NRF52X=y +CONFIG_SOC_NRF52840_QIAA=y +CONFIG_BOARD_GLOVE80_V0_RH=y + +# Enable MPU +CONFIG_ARM_MPU=y + +# enable GPIO +CONFIG_GPIO=y + +CONFIG_BUILD_OUTPUT_UF2=y + +CONFIG_USE_DT_CODE_PARTITION=y + +CONFIG_MPU_ALLOW_FLASH_WRITE=y +CONFIG_NVS=y +CONFIG_SETTINGS_NVS=y +CONFIG_FLASH=y +CONFIG_FLASH_PAGE_LAYOUT=y +CONFIG_FLASH_MAP=y + +# Disable RGB underglow for now in RH +CONFIG_ZMK_RGB_UNDERGLOW=y +CONFIG_WS2812_STRIP=y +CONFIG_SPI=y + +CONFIG_ZMK_RGB_UNDERGLOW_ON_START=y +CONFIG_ZMK_RGB_UNDERGLOW_EXT_POWER=y +CONFIG_ZMK_RGB_UNDERGLOW_BRT_STEP=4 + +CONFIG_ZMK_RGB_UNDERGLOW_EFF_START=3 +CONFIG_ZMK_RGB_UNDERGLOW_HUE_START=285 +CONFIG_ZMK_RGB_UNDERGLOW_SAT_START=75 +CONFIG_ZMK_RGB_UNDERGLOW_BRT_START=20 + +# The power LED is implemented as a backlight +CONFIG_ZMK_BACKLIGHT=y +CONFIG_ZMK_BACKLIGHT_ON_START=y +CONFIG_ZMK_BACKLIGHT_BRT_START=5 +CONFIG_ZMK_BACKLIGHT_AUTO_OFF_IDLE=y +CONFIG_ZMK_BACKLIGHT_AUTO_OFF_USB=y + +# Turn on logging, and set ZMK logging to debug output +# Only for debugging +CONFIG_ZMK_USB_LOGGING=n + +# Turn on debugging to disable optimization +CONFIG_DEBUG=n From 888377369fa92c3c27e421f466166ac6301d08f3 Mon Sep 17 00:00:00 2001 From: Chris Andreae Date: Mon, 18 Dec 2023 15:16:10 +0900 Subject: [PATCH 10/10] Add Bluetooth config addressing recent host compatibility issues --- app/boards/arm/glove80/glove80_lh_defconfig | 11 +++++++++++ app/boards/arm/glove80/glove80_rh_defconfig | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/app/boards/arm/glove80/glove80_lh_defconfig b/app/boards/arm/glove80/glove80_lh_defconfig index 46f0f1675a5..8add94c9723 100644 --- a/app/boards/arm/glove80/glove80_lh_defconfig +++ b/app/boards/arm/glove80/glove80_lh_defconfig @@ -23,8 +23,19 @@ CONFIG_BT_DIS_PNP_VID=0x16c0 CONFIG_BT_DIS_MANUF="MoErgo" CONFIG_BT_DIS_MODEL="Glove80" +### Bluetooth configuration workarounds + +# Use higher radio transmit power CONFIG_BT_CTLR_TX_PWR_PLUS_8=y +# Use non-legacy LLCP (required for compatibility with recent Linux kernel) +CONFIG_BT_LL_SW_LLCP=y +CONFIG_BT_LL_SW_LLCP_LEGACY=n + +# Disable 2M PHY (required for compatibility with recent Intel chipset drivers +# on Windows) +CONFIG_BT_CTLR_PHY_2M=n + # Work-around for Windows bug with battery notifications CONFIG_BT_GATT_ENFORCE_SUBSCRIPTION=n diff --git a/app/boards/arm/glove80/glove80_rh_defconfig b/app/boards/arm/glove80/glove80_rh_defconfig index d1792f65439..b3a38d8429d 100644 --- a/app/boards/arm/glove80/glove80_rh_defconfig +++ b/app/boards/arm/glove80/glove80_rh_defconfig @@ -21,8 +21,19 @@ CONFIG_BT_DIS_PNP_VID=0x16c0 CONFIG_BT_DIS_MANUF="MoErgo" CONFIG_BT_DIS_MODEL="Glove80 Right" +### Bluetooth configuration workarounds + +# Use higher radio transmit power CONFIG_BT_CTLR_TX_PWR_PLUS_8=y +# Use non-legacy LLCP (required for compatibility with recent Linux kernel) +CONFIG_BT_LL_SW_LLCP=y +CONFIG_BT_LL_SW_LLCP_LEGACY=n + +# Disable 2M PHY (required for compatibility with recent Intel chipset drivers +# on Windows) +CONFIG_BT_CTLR_PHY_2M=n + # Enable MPU CONFIG_ARM_MPU=y