Merge branch 'zmkfirmware:main' into murphpad
This commit is contained in:
commit
8ed7588767
56 changed files with 4514 additions and 1525 deletions
2
.github/workflows/clang-format-lint.yml
vendored
2
.github/workflows/clang-format-lint.yml
vendored
|
@ -23,7 +23,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: DoozyX/clang-format-lint-action@v0.11
|
- uses: DoozyX/clang-format-lint-action@v0.12
|
||||||
with:
|
with:
|
||||||
source: "./app"
|
source: "./app"
|
||||||
extensions: "h,c"
|
extensions: "h,c"
|
||||||
|
|
|
@ -42,7 +42,7 @@
|
||||||
, <7 0 &gpio0 24 0> /* D6/A7 D7*/
|
, <7 0 &gpio0 24 0> /* D6/A7 D7*/
|
||||||
, <8 0 &gpio0 10 0> /* D8/A8 B4*/
|
, <8 0 &gpio0 10 0> /* D8/A8 B4*/
|
||||||
, <9 0 &gpio1 6 0> /* D9/A9 B5*/
|
, <9 0 &gpio1 6 0> /* D9/A9 B5*/
|
||||||
, <10 0 &gpio1 13 0> /* D10/A10 B6*/
|
, <10 0 &gpio1 11 0> /* D10/A10 B6*/
|
||||||
;
|
;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,7 +9,6 @@ config ZMK_KEYBOARD_NAME
|
||||||
config ZMK_USB
|
config ZMK_USB
|
||||||
default y
|
default y
|
||||||
|
|
||||||
endif
|
|
||||||
|
|
||||||
if ZMK_DISPLAY
|
if ZMK_DISPLAY
|
||||||
|
|
||||||
|
@ -46,3 +45,5 @@ choice LVGL_COLOR_DEPTH
|
||||||
endchoice
|
endchoice
|
||||||
|
|
||||||
endif # LVGL
|
endif # LVGL
|
||||||
|
|
||||||
|
endif
|
||||||
|
|
|
@ -6,7 +6,6 @@ if SHIELD_TIDBIT
|
||||||
config ZMK_KEYBOARD_NAME
|
config ZMK_KEYBOARD_NAME
|
||||||
default "tidbit"
|
default "tidbit"
|
||||||
|
|
||||||
endif
|
|
||||||
|
|
||||||
if ZMK_DISPLAY
|
if ZMK_DISPLAY
|
||||||
|
|
||||||
|
@ -43,3 +42,5 @@ choice LVGL_COLOR_DEPTH
|
||||||
endchoice
|
endchoice
|
||||||
|
|
||||||
endif # LVGL
|
endif # LVGL
|
||||||
|
|
||||||
|
endif
|
||||||
|
|
|
@ -83,7 +83,7 @@ static int kscan_gpio_config_interrupts(const struct device *dev, gpio_flags_t f
|
||||||
int err = gpio_pin_interrupt_configure(dev, cfg->pin, flags);
|
int err = gpio_pin_interrupt_configure(dev, cfg->pin, flags);
|
||||||
|
|
||||||
if (err) {
|
if (err) {
|
||||||
LOG_ERR("Unable to enable matrix GPIO interrupt");
|
LOG_ERR("Unable to enable direct GPIO interrupt");
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,7 +93,7 @@ static int bvd_sample_fetch(const struct device *dev, enum sensor_channel chan)
|
||||||
&val);
|
&val);
|
||||||
|
|
||||||
uint16_t millivolts = val * (uint64_t)drv_cfg->full_ohm / drv_cfg->output_ohm;
|
uint16_t millivolts = val * (uint64_t)drv_cfg->full_ohm / drv_cfg->output_ohm;
|
||||||
LOG_DBG("ADC raw %d ~ %d mV => %d mV\n", drv_data->adc_raw, val, millivolts);
|
LOG_DBG("ADC raw %d ~ %d mV => %d mV", drv_data->adc_raw, val, millivolts);
|
||||||
uint8_t percent = lithium_ion_mv_to_pct(millivolts);
|
uint8_t percent = lithium_ion_mv_to_pct(millivolts);
|
||||||
LOG_DBG("Percent: %d", percent);
|
LOG_DBG("Percent: %d", percent);
|
||||||
|
|
||||||
|
|
9
app/include/zmk/battery.h
Normal file
9
app/include/zmk/battery.h
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 The ZMK Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
uint8_t zmk_battery_state_of_charge();
|
|
@ -17,7 +17,7 @@ testcases=$(find $path -name native_posix.keymap -exec dirname \{\} \;)
|
||||||
num_cases=$(echo "$testcases" | wc -l)
|
num_cases=$(echo "$testcases" | wc -l)
|
||||||
if [ $num_cases -gt 1 ]; then
|
if [ $num_cases -gt 1 ]; then
|
||||||
echo "" > ./build/tests/pass-fail.log
|
echo "" > ./build/tests/pass-fail.log
|
||||||
echo "$testcases" | xargs -L 1 -P 4 ./run-test.sh
|
echo "$testcases" | xargs -L 1 -P ${J:-4} ./run-test.sh
|
||||||
err=$?
|
err=$?
|
||||||
sort -k2 ./build/tests/pass-fail.log
|
sort -k2 ./build/tests/pass-fail.log
|
||||||
exit $err
|
exit $err
|
||||||
|
|
|
@ -15,10 +15,15 @@
|
||||||
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
LOG_MODULE_DECLARE(zmk, CONFIG_ZMK_LOG_LEVEL);
|
||||||
|
|
||||||
#include <zmk/event_manager.h>
|
#include <zmk/event_manager.h>
|
||||||
|
#include <zmk/battery.h>
|
||||||
#include <zmk/events/battery_state_changed.h>
|
#include <zmk/events/battery_state_changed.h>
|
||||||
|
|
||||||
const struct device *battery;
|
const struct device *battery;
|
||||||
|
|
||||||
|
static uint8_t last_state_of_charge = 0;
|
||||||
|
|
||||||
|
uint8_t zmk_battery_state_of_charge() { return last_state_of_charge; }
|
||||||
|
|
||||||
static int zmk_battery_update(const struct device *battery) {
|
static int zmk_battery_update(const struct device *battery) {
|
||||||
struct sensor_value state_of_charge;
|
struct sensor_value state_of_charge;
|
||||||
|
|
||||||
|
@ -36,17 +41,23 @@ static int zmk_battery_update(const struct device *battery) {
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_DBG("Setting BAS GATT battery level to %d.", state_of_charge.val1);
|
if (last_state_of_charge != state_of_charge.val1) {
|
||||||
|
last_state_of_charge = state_of_charge.val1;
|
||||||
|
|
||||||
rc = bt_bas_set_battery_level(state_of_charge.val1);
|
LOG_DBG("Setting BAS GATT battery level to %d.", last_state_of_charge);
|
||||||
|
|
||||||
if (rc != 0) {
|
rc = bt_bas_set_battery_level(last_state_of_charge);
|
||||||
LOG_WRN("Failed to set BAS GATT battery level (err %d)", rc);
|
|
||||||
return rc;
|
if (rc != 0) {
|
||||||
|
LOG_WRN("Failed to set BAS GATT battery level (err %d)", rc);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = ZMK_EVENT_RAISE(new_zmk_battery_state_changed(
|
||||||
|
(struct zmk_battery_state_changed){.state_of_charge = last_state_of_charge}));
|
||||||
}
|
}
|
||||||
|
|
||||||
return ZMK_EVENT_RAISE(new_zmk_battery_state_changed(
|
return rc;
|
||||||
(struct zmk_battery_state_changed){.state_of_charge = state_of_charge.val1}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void zmk_battery_work(struct k_work *work) {
|
static void zmk_battery_work(struct k_work *work) {
|
||||||
|
|
|
@ -177,6 +177,11 @@ static const struct behavior_driver_api behavior_sticky_key_driver_api = {
|
||||||
.binding_released = on_sticky_key_binding_released,
|
.binding_released = on_sticky_key_binding_released,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static int sticky_key_keycode_state_changed_listener(const zmk_event_t *eh);
|
||||||
|
|
||||||
|
ZMK_LISTENER(behavior_sticky_key, sticky_key_keycode_state_changed_listener);
|
||||||
|
ZMK_SUBSCRIPTION(behavior_sticky_key, zmk_keycode_state_changed);
|
||||||
|
|
||||||
static int sticky_key_keycode_state_changed_listener(const zmk_event_t *eh) {
|
static int sticky_key_keycode_state_changed_listener(const zmk_event_t *eh) {
|
||||||
struct zmk_keycode_state_changed *ev = as_zmk_keycode_state_changed(eh);
|
struct zmk_keycode_state_changed *ev = as_zmk_keycode_state_changed(eh);
|
||||||
if (ev == NULL) {
|
if (ev == NULL) {
|
||||||
|
@ -212,7 +217,10 @@ static int sticky_key_keycode_state_changed_listener(const zmk_event_t *eh) {
|
||||||
if (sticky_key->timer_started) {
|
if (sticky_key->timer_started) {
|
||||||
stop_timer(sticky_key);
|
stop_timer(sticky_key);
|
||||||
if (sticky_key->config->quick_release) {
|
if (sticky_key->config->quick_release) {
|
||||||
|
// continue processing the event. Release the sticky key afterwards.
|
||||||
|
ZMK_EVENT_RAISE_AFTER(eh, behavior_sticky_key);
|
||||||
release_sticky_key_behavior(sticky_key, ev->timestamp);
|
release_sticky_key_behavior(sticky_key, ev->timestamp);
|
||||||
|
return ZMK_EV_EVENT_CAPTURED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sticky_key->modified_key_usage_page = ev->usage_page;
|
sticky_key->modified_key_usage_page = ev->usage_page;
|
||||||
|
@ -229,9 +237,6 @@ static int sticky_key_keycode_state_changed_listener(const zmk_event_t *eh) {
|
||||||
return ZMK_EV_EVENT_BUBBLE;
|
return ZMK_EV_EVENT_BUBBLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
ZMK_LISTENER(behavior_sticky_key, sticky_key_keycode_state_changed_listener);
|
|
||||||
ZMK_SUBSCRIPTION(behavior_sticky_key, zmk_keycode_state_changed);
|
|
||||||
|
|
||||||
void behavior_sticky_key_timer_handler(struct k_work *item) {
|
void behavior_sticky_key_timer_handler(struct k_work *item) {
|
||||||
struct active_sticky_key *sticky_key =
|
struct active_sticky_key *sticky_key =
|
||||||
CONTAINER_OF(item, struct active_sticky_key, release_timer);
|
CONTAINER_OF(item, struct active_sticky_key, release_timer);
|
||||||
|
|
|
@ -192,7 +192,7 @@ static inline bool candidate_is_completely_pressed(struct combo_cfg *candidate)
|
||||||
return pressed_keys[candidate->key_position_len - 1] != NULL;
|
return pressed_keys[candidate->key_position_len - 1] != NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void cleanup();
|
static int cleanup();
|
||||||
|
|
||||||
static int filter_timed_out_candidates(int64_t timestamp) {
|
static int filter_timed_out_candidates(int64_t timestamp) {
|
||||||
int num_candidates = 0;
|
int num_candidates = 0;
|
||||||
|
@ -224,7 +224,7 @@ static int clear_candidates() {
|
||||||
}
|
}
|
||||||
|
|
||||||
static int capture_pressed_key(const zmk_event_t *ev) {
|
static int capture_pressed_key(const zmk_event_t *ev) {
|
||||||
for (int i = 0; i < CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY; i++) {
|
for (int i = 0; i < CONFIG_ZMK_COMBO_MAX_KEYS_PER_COMBO; i++) {
|
||||||
if (pressed_keys[i] != NULL) {
|
if (pressed_keys[i] != NULL) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -236,23 +236,25 @@ static int capture_pressed_key(const zmk_event_t *ev) {
|
||||||
|
|
||||||
const struct zmk_listener zmk_listener_combo;
|
const struct zmk_listener zmk_listener_combo;
|
||||||
|
|
||||||
static void release_pressed_keys() {
|
static int release_pressed_keys() {
|
||||||
// release the first key that was pressed
|
for (int i = 0; i < CONFIG_ZMK_COMBO_MAX_KEYS_PER_COMBO; i++) {
|
||||||
if (pressed_keys[0] == NULL) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ZMK_EVENT_RELEASE(pressed_keys[0])
|
|
||||||
pressed_keys[0] = NULL;
|
|
||||||
|
|
||||||
// reprocess events (see tests/combo/fully-overlapping-combos-3 for why this is needed)
|
|
||||||
for (int i = 1; i < CONFIG_ZMK_COMBO_MAX_COMBOS_PER_KEY; i++) {
|
|
||||||
if (pressed_keys[i] == NULL) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const zmk_event_t *captured_event = pressed_keys[i];
|
const zmk_event_t *captured_event = pressed_keys[i];
|
||||||
|
if (pressed_keys[i] == NULL) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
pressed_keys[i] = NULL;
|
pressed_keys[i] = NULL;
|
||||||
ZMK_EVENT_RAISE(captured_event);
|
if (i == 0) {
|
||||||
|
LOG_DBG("combo: releasing position event %d",
|
||||||
|
as_zmk_position_state_changed(captured_event)->position);
|
||||||
|
ZMK_EVENT_RELEASE(captured_event)
|
||||||
|
} else {
|
||||||
|
// reprocess events (see tests/combo/fully-overlapping-combos-3 for why this is needed)
|
||||||
|
LOG_DBG("combo: reraising position event %d",
|
||||||
|
as_zmk_position_state_changed(captured_event)->position);
|
||||||
|
ZMK_EVENT_RAISE(captured_event);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return CONFIG_ZMK_COMBO_MAX_KEYS_PER_COMBO;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline int press_combo_behavior(struct combo_cfg *combo, int32_t timestamp) {
|
static inline int press_combo_behavior(struct combo_cfg *combo, int32_t timestamp) {
|
||||||
|
@ -360,14 +362,14 @@ static bool release_combo_key(int32_t position, int64_t timestamp) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void cleanup() {
|
static int cleanup() {
|
||||||
k_delayed_work_cancel(&timeout_task);
|
k_delayed_work_cancel(&timeout_task);
|
||||||
clear_candidates();
|
clear_candidates();
|
||||||
if (fully_pressed_combo != NULL) {
|
if (fully_pressed_combo != NULL) {
|
||||||
activate_combo(fully_pressed_combo);
|
activate_combo(fully_pressed_combo);
|
||||||
fully_pressed_combo = NULL;
|
fully_pressed_combo = NULL;
|
||||||
}
|
}
|
||||||
release_pressed_keys();
|
return release_pressed_keys();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void update_timeout_task() {
|
static void update_timeout_task() {
|
||||||
|
@ -399,6 +401,7 @@ static int position_state_down(const zmk_event_t *ev, struct zmk_position_state_
|
||||||
update_timeout_task();
|
update_timeout_task();
|
||||||
|
|
||||||
struct combo_cfg *candidate_combo = candidates[0].combo;
|
struct combo_cfg *candidate_combo = candidates[0].combo;
|
||||||
|
LOG_DBG("combo: capturing position event %d", data->position);
|
||||||
int ret = capture_pressed_key(ev);
|
int ret = capture_pressed_key(ev);
|
||||||
switch (num_candidates) {
|
switch (num_candidates) {
|
||||||
case 0:
|
case 0:
|
||||||
|
@ -418,13 +421,18 @@ static int position_state_down(const zmk_event_t *ev, struct zmk_position_state_
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int position_state_up(struct zmk_position_state_changed *ev) {
|
static int position_state_up(const zmk_event_t *ev, struct zmk_position_state_changed *data) {
|
||||||
cleanup();
|
int released_keys = cleanup();
|
||||||
if (release_combo_key(ev->position, ev->timestamp)) {
|
if (release_combo_key(data->position, data->timestamp)) {
|
||||||
return ZMK_EV_EVENT_HANDLED;
|
return ZMK_EV_EVENT_HANDLED;
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
if (released_keys > 1) {
|
||||||
|
// The second and further key down events are re-raised. To preserve
|
||||||
|
// correct order for e.g. hold-taps, reraise the key up event too.
|
||||||
|
ZMK_EVENT_RAISE(ev);
|
||||||
|
return ZMK_EV_EVENT_CAPTURED;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void combo_timeout_handler(struct k_work *item) {
|
static void combo_timeout_handler(struct k_work *item) {
|
||||||
|
@ -447,7 +455,7 @@ static int position_state_changed_listener(const zmk_event_t *ev) {
|
||||||
if (data->state) { // keydown
|
if (data->state) { // keydown
|
||||||
return position_state_down(ev, data);
|
return position_state_down(ev, data);
|
||||||
} else { // keyup
|
} else { // keyup
|
||||||
return position_state_up(data);
|
return position_state_up(ev, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,24 +22,25 @@ int zmk_event_manager_handle_from(zmk_event_t *event, uint8_t start_index) {
|
||||||
uint8_t len = __event_subscriptions_end - __event_subscriptions_start;
|
uint8_t len = __event_subscriptions_end - __event_subscriptions_start;
|
||||||
for (int i = start_index; i < len; i++) {
|
for (int i = start_index; i < len; i++) {
|
||||||
struct zmk_event_subscription *ev_sub = __event_subscriptions_start + i;
|
struct zmk_event_subscription *ev_sub = __event_subscriptions_start + i;
|
||||||
if (ev_sub->event_type == event->event) {
|
if (ev_sub->event_type != event->event) {
|
||||||
ret = ev_sub->listener->callback(event);
|
continue;
|
||||||
if (ret < 0) {
|
}
|
||||||
LOG_DBG("Listener returned an error: %d", ret);
|
ret = ev_sub->listener->callback(event);
|
||||||
goto release;
|
switch (ret) {
|
||||||
} else if (ret > 0) {
|
case ZMK_EV_EVENT_BUBBLE:
|
||||||
switch (ret) {
|
continue;
|
||||||
case ZMK_EV_EVENT_HANDLED:
|
case ZMK_EV_EVENT_HANDLED:
|
||||||
LOG_DBG("Listener handled the event");
|
LOG_DBG("Listener handled the event");
|
||||||
ret = 0;
|
ret = 0;
|
||||||
goto release;
|
goto release;
|
||||||
case ZMK_EV_EVENT_CAPTURED:
|
case ZMK_EV_EVENT_CAPTURED:
|
||||||
LOG_DBG("Listener captured the event");
|
LOG_DBG("Listener captured the event");
|
||||||
event->last_listener_index = i;
|
event->last_listener_index = i;
|
||||||
// Listeners are expected to free events they capture
|
// Listeners are expected to free events they capture
|
||||||
return 0;
|
return 0;
|
||||||
}
|
default:
|
||||||
}
|
LOG_DBG("Listener returned an error: %d", ret);
|
||||||
|
goto release;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,7 @@ void zmk_kscan_process_msgq(struct k_work *item) {
|
||||||
while (k_msgq_get(&zmk_kscan_msgq, &ev, K_NO_WAIT) == 0) {
|
while (k_msgq_get(&zmk_kscan_msgq, &ev, K_NO_WAIT) == 0) {
|
||||||
bool pressed = (ev.state == ZMK_KSCAN_EVENT_STATE_PRESSED);
|
bool pressed = (ev.state == ZMK_KSCAN_EVENT_STATE_PRESSED);
|
||||||
uint32_t position = zmk_matrix_transform_row_column_to_position(ev.row, ev.column);
|
uint32_t position = zmk_matrix_transform_row_column_to_position(ev.row, ev.column);
|
||||||
LOG_DBG("Row: %d, col: %d, position: %d, pressed: %s\n", ev.row, ev.column, position,
|
LOG_DBG("Row: %d, col: %d, position: %d, pressed: %s", ev.row, ev.column, position,
|
||||||
(pressed ? "true" : "false"));
|
(pressed ? "true" : "false"));
|
||||||
ZMK_EVENT_RAISE(new_zmk_position_state_changed((struct zmk_position_state_changed){
|
ZMK_EVENT_RAISE(new_zmk_position_state_changed((struct zmk_position_state_changed){
|
||||||
.state = pressed, .position = position, .timestamp = k_uptime_get()}));
|
.state = pressed, .position = position, .timestamp = k_uptime_get()}));
|
||||||
|
|
|
@ -1,2 +1 @@
|
||||||
s/.*hid_listener_keycode_//p
|
s/.*hid_listener_keycode_//p
|
||||||
s/.*combo//p
|
|
|
@ -1,2 +1 @@
|
||||||
s/.*hid_listener_keycode_//p
|
s/.*hid_listener_keycode_//p
|
||||||
s/.*combo//p
|
|
|
@ -1,2 +1 @@
|
||||||
s/.*hid_listener_keycode_//p
|
s/.*hid_listener_keycode_//p
|
||||||
s/.*combo//p
|
|
|
@ -1,2 +1 @@
|
||||||
s/.*hid_listener_keycode_//p
|
s/.*hid_listener_keycode_//p
|
||||||
s/.*combo//p
|
|
|
@ -1,2 +1 @@
|
||||||
s/.*hid_listener_keycode_//p
|
s/.*hid_listener_keycode_//p
|
||||||
s/.*combo//p
|
|
|
@ -1,2 +1 @@
|
||||||
s/.*hid_listener_keycode_//p
|
s/.*hid_listener_keycode_//p
|
||||||
s/.*combo//p
|
|
|
@ -1,2 +1 @@
|
||||||
s/.*hid_listener_keycode_//p
|
s/.*hid_listener_keycode_//p
|
||||||
s/.*combo//p
|
|
|
@ -1,2 +1 @@
|
||||||
s/.*hid_listener_keycode_//p
|
s/.*hid_listener_keycode_//p
|
||||||
s/.*combo//p
|
|
|
@ -1,2 +1 @@
|
||||||
s/.*hid_listener_keycode_//p
|
s/.*hid_listener_keycode_//p
|
||||||
s/.*combo//p
|
|
1
app/tests/combo/press-release-long-combo/events.patterns
Normal file
1
app/tests/combo/press-release-long-combo/events.patterns
Normal file
|
@ -0,0 +1 @@
|
||||||
|
s/.*hid_listener_keycode_//p
|
|
@ -0,0 +1,4 @@
|
||||||
|
pressed: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00
|
||||||
|
pressed: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00
|
||||||
|
released: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00
|
||||||
|
released: usage_page 0x07 keycode 0x07 implicit_mods 0x00 explicit_mods 0x00
|
35
app/tests/combo/press-release-long-combo/native_posix.keymap
Normal file
35
app/tests/combo/press-release-long-combo/native_posix.keymap
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
#include <dt-bindings/zmk/keys.h>
|
||||||
|
#include <behaviors.dtsi>
|
||||||
|
#include <dt-bindings/zmk/kscan-mock.h>
|
||||||
|
|
||||||
|
/ {
|
||||||
|
combos {
|
||||||
|
compatible = "zmk,combos";
|
||||||
|
combo_one {
|
||||||
|
timeout-ms = <80>;
|
||||||
|
key-positions = <0 1 2 3>;
|
||||||
|
bindings = <&kp Z>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
keymap {
|
||||||
|
compatible = "zmk,keymap";
|
||||||
|
label ="Default keymap";
|
||||||
|
|
||||||
|
default_layer {
|
||||||
|
bindings = <
|
||||||
|
&kp A &kp B
|
||||||
|
&kp C &kp D
|
||||||
|
>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
&kscan {
|
||||||
|
events = <
|
||||||
|
ZMK_MOCK_PRESS(1,1,10)
|
||||||
|
ZMK_MOCK_PRESS(0,1,10)
|
||||||
|
ZMK_MOCK_RELEASE(0,1,100)
|
||||||
|
ZMK_MOCK_RELEASE(1,1,100)
|
||||||
|
>;
|
||||||
|
};
|
|
@ -1,2 +1 @@
|
||||||
s/.*hid_listener_keycode_//p
|
s/.*hid_listener_keycode_//p
|
||||||
s/.*combo/combo/p
|
|
|
@ -1,2 +1 @@
|
||||||
s/.*hid_listener_keycode_//p
|
s/.*hid_listener_keycode_//p
|
||||||
s/.*combo/combo/p
|
|
||||||
|
|
|
@ -1,2 +1 @@
|
||||||
s/.*hid_listener_keycode_//p
|
s/.*hid_listener_keycode_//p
|
||||||
s/.*combo/combo/p
|
|
|
@ -0,0 +1 @@
|
||||||
|
s/.*hid_listener_keycode_//p
|
|
@ -0,0 +1,10 @@
|
||||||
|
pressed: usage_page 0x07 keycode 0x08 implicit_mods 0x00 explicit_mods 0x00
|
||||||
|
pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
|
||||||
|
released: usage_page 0x07 keycode 0x08 implicit_mods 0x00 explicit_mods 0x00
|
||||||
|
pressed: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00
|
||||||
|
released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
|
||||||
|
released: usage_page 0x07 keycode 0x05 implicit_mods 0x00 explicit_mods 0x00
|
||||||
|
pressed: usage_page 0x07 keycode 0x08 implicit_mods 0x00 explicit_mods 0x00
|
||||||
|
pressed: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
|
||||||
|
released: usage_page 0x07 keycode 0x08 implicit_mods 0x00 explicit_mods 0x00
|
||||||
|
released: usage_page 0x07 keycode 0x04 implicit_mods 0x00 explicit_mods 0x00
|
|
@ -0,0 +1,26 @@
|
||||||
|
#include <dt-bindings/zmk/keys.h>
|
||||||
|
#include <behaviors.dtsi>
|
||||||
|
#include <dt-bindings/zmk/kscan_mock.h>
|
||||||
|
#include "../behavior_keymap.dtsi"
|
||||||
|
|
||||||
|
&sk {
|
||||||
|
quick-release;
|
||||||
|
};
|
||||||
|
|
||||||
|
&kscan {
|
||||||
|
events = <
|
||||||
|
ZMK_MOCK_PRESS(0,0,10)
|
||||||
|
ZMK_MOCK_RELEASE(0,0,10)
|
||||||
|
ZMK_MOCK_PRESS(1,0,10)
|
||||||
|
/* second key is pressed shortly after the first. It should not be capitalized. */
|
||||||
|
ZMK_MOCK_PRESS(1,1,10)
|
||||||
|
ZMK_MOCK_RELEASE(1,0,10)
|
||||||
|
ZMK_MOCK_RELEASE(1,1,10)
|
||||||
|
|
||||||
|
/* repeat test to check if cleanup is done correctly */
|
||||||
|
ZMK_MOCK_PRESS(0,0,10)
|
||||||
|
ZMK_MOCK_RELEASE(0,0,10)
|
||||||
|
ZMK_MOCK_PRESS(1,0,10)
|
||||||
|
ZMK_MOCK_RELEASE(1,0,10)
|
||||||
|
>;
|
||||||
|
};
|
|
@ -10,7 +10,6 @@ module.exports = {
|
||||||
"plugin:react/recommended",
|
"plugin:react/recommended",
|
||||||
"plugin:mdx/recommended",
|
"plugin:mdx/recommended",
|
||||||
"prettier",
|
"prettier",
|
||||||
"prettier/react",
|
|
||||||
],
|
],
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
ecmaFeatures: {
|
ecmaFeatures: {
|
||||||
|
|
BIN
docs/docs/assets/features/beta-testing/pr-repo-branch.png
Normal file
BIN
docs/docs/assets/features/beta-testing/pr-repo-branch.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 74 KiB |
BIN
docs/docs/assets/features/beta-testing/repo-branch.png
Normal file
BIN
docs/docs/assets/features/beta-testing/repo-branch.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 62 KiB |
BIN
docs/docs/assets/features/beta-testing/repo-url.png
Normal file
BIN
docs/docs/assets/features/beta-testing/repo-url.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 77 KiB |
|
@ -56,7 +56,7 @@ For example, if you press `&mt LEFT_SHIFT A` and then release it without pressin
|
||||||
```
|
```
|
||||||
&mt {
|
&mt {
|
||||||
retro-tap;
|
retro-tap;
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Home row mods
|
#### Home row mods
|
||||||
|
|
73
docs/docs/behaviors/mod-morph.md
Normal file
73
docs/docs/behaviors/mod-morph.md
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
---
|
||||||
|
title: Mod-Morph Behavior
|
||||||
|
sidebar_label: Mod-Morph
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
The Mod-Morph behavior sends a different keypress, depending on whether a specified modifier is being held during the keypress.
|
||||||
|
|
||||||
|
- If you tap the key by itself, the first keycode is sent.
|
||||||
|
- If you tap the key while holding the specified modifier, the second keycode is sent.
|
||||||
|
|
||||||
|
## Mod-Morph
|
||||||
|
|
||||||
|
The Mod-Morph behavior acts as one of two keycodes, depending on if the required modifier is being held during the keypress.
|
||||||
|
|
||||||
|
When the modifier is being held it is sent along with the morphed keycode. This can cause problems when the morphed keycode and modifier have an existing relationship (such as `shift-delete` or `ctrl-v` on many operating systems).
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
An example of how to implement the mod-morph "Grave Escape":
|
||||||
|
|
||||||
|
```
|
||||||
|
/ {
|
||||||
|
behaviors {
|
||||||
|
gresc: grave_escape {
|
||||||
|
compatible = "zmk,behavior-mod-morph";
|
||||||
|
label = "GRAVE_ESCAPE";
|
||||||
|
#binding-cells = <0>;
|
||||||
|
bindings = <&kp ESC>, <&kp GRAVE>;
|
||||||
|
mods = <(MOD_LGUI|MOD_LSFT|MOD_RGUI|MOD_RSFT)>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
keymap {
|
||||||
|
...
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that this specific mod-morph exists in ZMK by default using code `&gresc`.
|
||||||
|
|
||||||
|
### Behavior Binding
|
||||||
|
|
||||||
|
- Reference: `&gresc`
|
||||||
|
- Parameter: None
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
&gresc
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mods
|
||||||
|
|
||||||
|
This is how you determine what modifiers will activate the morphed version of the keycode.
|
||||||
|
|
||||||
|
Available Modifiers:
|
||||||
|
|
||||||
|
- `MOD_LSFT`
|
||||||
|
- `MOD_RSFT`
|
||||||
|
- `MOD_LCTL`
|
||||||
|
- `MOD_RCTL`
|
||||||
|
- `MOD_LALT`
|
||||||
|
- `MOD_RALT`
|
||||||
|
- `MOD_LGUI`
|
||||||
|
- `MOD_RGUI`
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
mods = <(MOD_LGUI|MOD_LSFT|MOD_RGUI|MOD_RSFT)>;
|
||||||
|
```
|
|
@ -40,8 +40,8 @@ You can configure a different tapping term in your keymap:
|
||||||
/ {
|
/ {
|
||||||
keymap {
|
keymap {
|
||||||
...
|
...
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
### Additional information
|
### Additional information
|
||||||
|
|
|
@ -38,8 +38,8 @@ You can configure a different `release-after-ms` in your keymap:
|
||||||
/ {
|
/ {
|
||||||
keymap {
|
keymap {
|
||||||
...
|
...
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
### Advanced usage
|
### Advanced usage
|
||||||
|
|
|
@ -32,8 +32,8 @@ You can configure a different `release-after-ms` in your keymap:
|
||||||
/ {
|
/ {
|
||||||
keymap {
|
keymap {
|
||||||
...
|
...
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
### Advanced usage
|
### Advanced usage
|
||||||
|
|
100
docs/docs/features/beta-testing.md
Normal file
100
docs/docs/features/beta-testing.md
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
---
|
||||||
|
title: Beta Testing
|
||||||
|
sidebar_label: Beta Testing
|
||||||
|
---
|
||||||
|
|
||||||
|
import Tabs from '@theme/Tabs';
|
||||||
|
import TabItem from '@theme/TabItem';
|
||||||
|
|
||||||
|
You may find that ZMK does not support a feature or keyboard that you are interesting in using. You may find that someone
|
||||||
|
has already taken the time to submit the feature you need as a [Pull Request](https://github.com/zmkfirmware/zmk/pulls). If you find the feature you need as a pull request,
|
||||||
|
this page is for you!
|
||||||
|
|
||||||
|
## Developer Repositories and Branches
|
||||||
|
|
||||||
|
For a developer to submit a pull request to ZMK, they must first clone the original ZMK repository. After they have a copy
|
||||||
|
of the source code, they may create a feature branch to work within. When they have finished, they will publish the feature
|
||||||
|
branch and create the pull request.
|
||||||
|
|
||||||
|
### Finding the Repository Page from the Pull Request
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Finding the Repository URL
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Finding the Repository Branch
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Testing features
|
||||||
|
|
||||||
|
Testing features will require you to modify the `west.yml` file. You will need to add a new remote for the pull request you
|
||||||
|
would like to test, and change the selected remote and revision (or branch) for the `zmk` project.
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
<Tabs
|
||||||
|
defaultValue="zmk"
|
||||||
|
values={[
|
||||||
|
{label: 'Default', value: 'zmk'},
|
||||||
|
{label: 'PR685: Macros', value: 'macros'},
|
||||||
|
{label: 'PR649: Add &sleep behavior', value: 'sleep'},
|
||||||
|
]}>
|
||||||
|
<TabItem value="zmk">
|
||||||
|
|
||||||
|
```
|
||||||
|
manifest:
|
||||||
|
remotes:
|
||||||
|
- name: zmkfirmware
|
||||||
|
url-base: https://github.com/zmkfirmware
|
||||||
|
projects:
|
||||||
|
- name: zmk
|
||||||
|
remote: zmkfirmware
|
||||||
|
revision: main
|
||||||
|
import: app/west.yml
|
||||||
|
self:
|
||||||
|
path: config
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="macros">
|
||||||
|
|
||||||
|
```
|
||||||
|
manifest:
|
||||||
|
remotes:
|
||||||
|
- name: zmkfirmware
|
||||||
|
url-base: https://github.com/zmkfirmware
|
||||||
|
- name: okke-formsma
|
||||||
|
url-base: https://github.com/okke-formsma
|
||||||
|
projects:
|
||||||
|
- name: zmk
|
||||||
|
remote: okke-formsma
|
||||||
|
revision: macros
|
||||||
|
import: app/west.yml
|
||||||
|
self:
|
||||||
|
path: config
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
<TabItem value="sleep">
|
||||||
|
|
||||||
|
```
|
||||||
|
manifest:
|
||||||
|
remotes:
|
||||||
|
- name: zmkfirmware
|
||||||
|
url-base: https://github.com/zmkfirmware
|
||||||
|
- name: mcrosson
|
||||||
|
url-base: https://github.com/mcrosson
|
||||||
|
projects:
|
||||||
|
- name: zmk
|
||||||
|
remote: mcrosson
|
||||||
|
revision: feat-behavior-sleep
|
||||||
|
import: app/west.yml
|
||||||
|
self:
|
||||||
|
path: config
|
||||||
|
```
|
||||||
|
|
||||||
|
</TabItem>
|
||||||
|
</Tabs>
|
|
@ -18,7 +18,6 @@ Combos configured in your `.keymap` file, but are separate from the `keymap` nod
|
||||||
timeout-ms = <50>;
|
timeout-ms = <50>;
|
||||||
key-positions = <0 1>;
|
key-positions = <0 1>;
|
||||||
bindings = <&kp ESC>;
|
bindings = <&kp ESC>;
|
||||||
layers = <-1>;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -28,7 +27,7 @@ Combos configured in your `.keymap` file, but are separate from the `keymap` nod
|
||||||
- The `compatible` property should always be `"zmk,combos"` for combos.
|
- The `compatible` property should always be `"zmk,combos"` for combos.
|
||||||
- `timeout-ms` is the number of milliseconds that all keys of the combo must be pressed.
|
- `timeout-ms` is the number of milliseconds that all keys of the combo must be pressed.
|
||||||
- `key-positions` is an array of key positions. See the info section below about how to figure out the positions on your board.
|
- `key-positions` is an array of key positions. See the info section below about how to figure out the positions on your board.
|
||||||
- `layers = <0 1...>` will allow limiting a combo to specific layers. this is an _optional_ parameter and defaults to `-1` which is global scope.
|
- `layers = <0 1...>` will allow limiting a combo to specific layers. This is an _optional_ parameter, when omitted it defaults to global scope.
|
||||||
- `bindings` is the behavior that is activated when the behavior is pressed.
|
- `bindings` is the behavior that is activated when the behavior is pressed.
|
||||||
- (advanced) you can specify `slow-release` if you want the combo binding to be released when all key-positions are released. The default is to release the combo as soon as any of the keys in the combo is released.
|
- (advanced) you can specify `slow-release` if you want the combo binding to be released when all key-positions are released. The default is to release the combo as soon as any of the keys in the combo is released.
|
||||||
|
|
||||||
|
|
|
@ -41,4 +41,4 @@ Here, the left encoder is configured to control volume up and down while the rig
|
||||||
|
|
||||||
## Adding Encoder Support
|
## Adding Encoder Support
|
||||||
|
|
||||||
See the [New Keyboard Shield](../development/new-shield#encoders) documentation for how to add or modify additional encoders to your shield.
|
See the [New Keyboard Shield](/docs/development/new-shield#encoders) documentation for how to add or modify additional encoders to your shield.
|
||||||
|
|
|
@ -55,10 +55,16 @@ If you have a shield with RGB underglow, you must add a `boards/` directory with
|
||||||
Inside the `boards/` folder, you define a `<board>.overlay` for each different board.
|
Inside the `boards/` folder, you define a `<board>.overlay` for each different board.
|
||||||
For example, the Kyria shield has a `boards/nice_nano.overlay` file that defines the RGB underglow for the `nice_nano` board specifically.
|
For example, the Kyria shield has a `boards/nice_nano.overlay` file that defines the RGB underglow for the `nice_nano` board specifically.
|
||||||
|
|
||||||
The first step to adding support for underglow is to select you SPI output. With nRF52 boards, you can just use `&spi1` and define the pins you want to use.
|
### nRF52-based boards
|
||||||
For other boards, you must select an SPI definition that has the `MOSI` pin as your data pin going to your LED strip.
|
|
||||||
|
|
||||||
Here's an example of an nRF52 SPI definition:
|
With nRF52 boards, you can just use `&spi1` and define the pins you want to use.
|
||||||
|
|
||||||
|
To identify which pin number you need to put in the config you need do to a bit of math. You need the hardware port and run it through a function.
|
||||||
|
**32 \* X + Y** = `<Pin number>` where X is first part of the hardware port "PX.01" and Y is the second part of the hardware port "P1.Y".
|
||||||
|
|
||||||
|
(_P1.13_ would give you _32 \* 1 + 13_ = `<45>` and P0.15 would give you _32 \* 0 + 15_ = `<15>`)
|
||||||
|
|
||||||
|
Here's an example on a definition that uses P0.06:
|
||||||
|
|
||||||
```
|
```
|
||||||
&spi1 {
|
&spi1 {
|
||||||
|
@ -87,11 +93,15 @@ Here's an example of an nRF52 SPI definition:
|
||||||
|
|
||||||
:::info
|
:::info
|
||||||
|
|
||||||
If you are configuring SPI for an nRF52840 (or other nRF52) based board, double check that you are using pins that aren't restricted to low frequency I/O.
|
If you are configuring SPI for an nRF52 based board, double check that you are using pins that aren't restricted to low frequency I/O.
|
||||||
Ignoring these restrictions may result in poor wireless performance. You can find the list of low frequency I/O pins [here](https://infocenter.nordicsemi.com/index.jsp?topic=%2Fps_nrf52840%2Fpin.html&cp=4_0_0_6_0).
|
Ignoring these restrictions may result in poor wireless performance. You can find the list of low frequency I/O pins for the nRF52840 [here](https://infocenter.nordicsemi.com/index.jsp?topic=%2Fps_nrf52840%2Fpin.html&cp=4_0_0_6_0).
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
### Other boards
|
||||||
|
|
||||||
|
For other boards, you must select an SPI definition that has the `MOSI` pin as your data pin going to your LED strip.
|
||||||
|
|
||||||
Here's another example for a non-nRF52 board on `spi1`:
|
Here's another example for a non-nRF52 board on `spi1`:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
|
@ -10,6 +10,9 @@ module.exports = {
|
||||||
projectName: "zmk", // Usually your repo name.
|
projectName: "zmk", // Usually your repo name.
|
||||||
plugins: [path.resolve(__dirname, "src/docusaurus-tree-sitter-plugin")],
|
plugins: [path.resolve(__dirname, "src/docusaurus-tree-sitter-plugin")],
|
||||||
themeConfig: {
|
themeConfig: {
|
||||||
|
colorMode: {
|
||||||
|
respectPrefersColorScheme: true,
|
||||||
|
},
|
||||||
googleAnalytics: {
|
googleAnalytics: {
|
||||||
trackingID: "UA-145201102-2",
|
trackingID: "UA-145201102-2",
|
||||||
anonymizeIP: true,
|
anonymizeIP: true,
|
||||||
|
@ -29,6 +32,11 @@ module.exports = {
|
||||||
position: "left",
|
position: "left",
|
||||||
},
|
},
|
||||||
{ to: "blog", label: "Blog", position: "left" },
|
{ to: "blog", label: "Blog", position: "left" },
|
||||||
|
{
|
||||||
|
to: "power-profiler",
|
||||||
|
label: "Power Profiler",
|
||||||
|
position: "left",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
href: "https://github.com/zmkfirmware/zmk",
|
href: "https://github.com/zmkfirmware/zmk",
|
||||||
label: "GitHub",
|
label: "GitHub",
|
||||||
|
|
4465
docs/package-lock.json
generated
4465
docs/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -17,13 +17,13 @@
|
||||||
"@docusaurus/core": "^2.0.0-alpha.66",
|
"@docusaurus/core": "^2.0.0-alpha.66",
|
||||||
"@docusaurus/preset-classic": "^2.0.0-alpha.66",
|
"@docusaurus/preset-classic": "^2.0.0-alpha.66",
|
||||||
"@fortawesome/fontawesome-svg-core": "^1.2.32",
|
"@fortawesome/fontawesome-svg-core": "^1.2.32",
|
||||||
"@fortawesome/free-solid-svg-icons": "^5.15.1",
|
"@fortawesome/free-solid-svg-icons": "^5.15.3",
|
||||||
"@fortawesome/react-fontawesome": "^0.1.12",
|
"@fortawesome/react-fontawesome": "^0.1.14",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"react": "^16.8.4",
|
"react": "^16.14.0",
|
||||||
"react-async": "^10.0.1",
|
"react-async": "^10.0.1",
|
||||||
"react-copy-to-clipboard": "^5.0.2",
|
"react-copy-to-clipboard": "^5.0.2",
|
||||||
"react-dom": "^16.8.4",
|
"react-dom": "^16.14.0",
|
||||||
"react-toastify": "^6.0.9",
|
"react-toastify": "^6.0.9",
|
||||||
"web-tree-sitter": "^0.17.1"
|
"web-tree-sitter": "^0.17.1"
|
||||||
},
|
},
|
||||||
|
@ -40,10 +40,10 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint": "^7.12.0",
|
"eslint": "^7.25.0",
|
||||||
"eslint-config-prettier": "^6.14.0",
|
"eslint-config-prettier": "^8.3.0",
|
||||||
"eslint-plugin-mdx": "^1.8.2",
|
"eslint-plugin-mdx": "^1.13.0",
|
||||||
"eslint-plugin-react": "^7.21.5",
|
"eslint-plugin-react": "^7.23.2",
|
||||||
"null-loader": "^3.0.0",
|
"null-loader": "^3.0.0",
|
||||||
"prettier": "2.1.2",
|
"prettier": "2.1.2",
|
||||||
"string-replace-loader": "2.3"
|
"string-replace-loader": "2.3"
|
||||||
|
|
|
@ -14,6 +14,7 @@ module.exports = {
|
||||||
"features/displays",
|
"features/displays",
|
||||||
"features/encoders",
|
"features/encoders",
|
||||||
"features/underglow",
|
"features/underglow",
|
||||||
|
"features/beta-testing",
|
||||||
],
|
],
|
||||||
Behaviors: [
|
Behaviors: [
|
||||||
"behaviors/key-press",
|
"behaviors/key-press",
|
||||||
|
@ -21,6 +22,7 @@ module.exports = {
|
||||||
"behaviors/misc",
|
"behaviors/misc",
|
||||||
"behaviors/hold-tap",
|
"behaviors/hold-tap",
|
||||||
"behaviors/mod-tap",
|
"behaviors/mod-tap",
|
||||||
|
"behaviors/mod-morph",
|
||||||
"behaviors/sticky-key",
|
"behaviors/sticky-key",
|
||||||
"behaviors/sticky-layer",
|
"behaviors/sticky-layer",
|
||||||
"behaviors/reset",
|
"behaviors/reset",
|
||||||
|
|
100
docs/src/components/custom-board-form.js
Normal file
100
docs/src/components/custom-board-form.js
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 The ZMK Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
|
function CustomBoardForm({
|
||||||
|
bindPsuType,
|
||||||
|
bindOutputV,
|
||||||
|
bindEfficiency,
|
||||||
|
bindQuiescentMicroA,
|
||||||
|
bindOtherQuiescentMicroA,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className="profilerSection">
|
||||||
|
<h3>Custom Board</h3>
|
||||||
|
<div className="row">
|
||||||
|
<div className="col col--4">
|
||||||
|
<div className="profilerInput">
|
||||||
|
<label>Power Supply Type</label>
|
||||||
|
<select {...bindPsuType}>
|
||||||
|
<option hidden value="">
|
||||||
|
Select a PSU type
|
||||||
|
</option>
|
||||||
|
<option value="LDO">LDO</option>
|
||||||
|
<option value="SWITCHING">Switching</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="col col--4">
|
||||||
|
<div className="profilerInput">
|
||||||
|
<label>
|
||||||
|
Output Voltage{" "}
|
||||||
|
<span tooltip="Output Voltage of the PSU used by the system">
|
||||||
|
ⓘ
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<input {...bindOutputV} type="range" min="1.8" step=".1" max="5" />
|
||||||
|
<span>{parseFloat(bindOutputV.value).toFixed(1)}V</span>
|
||||||
|
</div>
|
||||||
|
{bindPsuType.value === "SWITCHING" && (
|
||||||
|
<div className="profilerInput">
|
||||||
|
<label>
|
||||||
|
PSU Efficiency{" "}
|
||||||
|
<span tooltip="The estimated efficiency with a VIN of 3.8 and the output voltage entered above">
|
||||||
|
ⓘ
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
{...bindEfficiency}
|
||||||
|
type="range"
|
||||||
|
min=".50"
|
||||||
|
step=".01"
|
||||||
|
max="1"
|
||||||
|
/>
|
||||||
|
<span>{Math.round(bindEfficiency.value * 100)}%</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="col col--4">
|
||||||
|
<div className="profilerInput">
|
||||||
|
<label>
|
||||||
|
PSU Quiescent{" "}
|
||||||
|
<span tooltip="The standby usage of the PSU">ⓘ</span>
|
||||||
|
</label>
|
||||||
|
<div className="inputBox">
|
||||||
|
<input {...bindQuiescentMicroA} type="number" />
|
||||||
|
<span>µA</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="profilerInput">
|
||||||
|
<label>
|
||||||
|
Other Quiescent{" "}
|
||||||
|
<span tooltip="Any other standby usage of the board (voltage dividers, extra ICs, etc)">
|
||||||
|
ⓘ
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<div className="inputBox">
|
||||||
|
<input {...bindOtherQuiescentMicroA} type="number" />
|
||||||
|
<span>µA</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomBoardForm.propTypes = {
|
||||||
|
bindPsuType: PropTypes.Object,
|
||||||
|
bindOutputV: PropTypes.Object,
|
||||||
|
bindEfficiency: PropTypes.Object,
|
||||||
|
bindQuiescentMicroA: PropTypes.Object,
|
||||||
|
bindOtherQuiescentMicroA: PropTypes.Object,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CustomBoardForm;
|
266
docs/src/components/power-estimate.js
Normal file
266
docs/src/components/power-estimate.js
Normal file
|
@ -0,0 +1,266 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 The ZMK Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
import { displayPower, underglowPower, zmkBase } from "../data/power";
|
||||||
|
import "../css/power-estimate.css";
|
||||||
|
|
||||||
|
// Average monthly discharge percent
|
||||||
|
const lithiumIonMonthlyDischargePercent = 5;
|
||||||
|
// Average voltage of a lithium ion battery based of discharge graphs
|
||||||
|
const lithiumIonAverageVoltage = 3.8;
|
||||||
|
// Average discharge efficiency of li-ion https://en.wikipedia.org/wiki/Lithium-ion_battery
|
||||||
|
const lithiumIonDischargeEfficiency = 0.85;
|
||||||
|
// Range of the discharge efficiency
|
||||||
|
const lithiumIonDischargeEfficiencyRange = 0.05;
|
||||||
|
|
||||||
|
// Proportion of time spent typing (keys being pressed down and scanning). Estimated to 2%.
|
||||||
|
const timeSpentTyping = 0.02;
|
||||||
|
|
||||||
|
// Nordic power profiler kit accuracy
|
||||||
|
const measurementAccuracy = 0.2;
|
||||||
|
|
||||||
|
const batVolt = lithiumIonAverageVoltage;
|
||||||
|
|
||||||
|
const palette = [
|
||||||
|
"#bbdefb",
|
||||||
|
"#90caf9",
|
||||||
|
"#64b5f6",
|
||||||
|
"#42a5f5",
|
||||||
|
"#2196f3",
|
||||||
|
"#1e88e5",
|
||||||
|
"#1976d2",
|
||||||
|
];
|
||||||
|
|
||||||
|
function formatUsage(microWatts) {
|
||||||
|
if (microWatts > 1000) {
|
||||||
|
return (microWatts / 1000).toFixed(1) + "mW";
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.round(microWatts) + "µW";
|
||||||
|
}
|
||||||
|
|
||||||
|
function voltageEquivalentCalc(powerSupply) {
|
||||||
|
if (powerSupply.type === "LDO") {
|
||||||
|
return batVolt;
|
||||||
|
} else if (powerSupply.type === "SWITCHING") {
|
||||||
|
return powerSupply.outputVoltage / powerSupply.efficiency;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatMinutes(minutes, precision, floor) {
|
||||||
|
let message = "";
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
let units = ["year", "month", "week", "day", "hour", "minute"];
|
||||||
|
let multiples = [60 * 24 * 365, 60 * 24 * 30, 60 * 24 * 7, 60 * 24, 60, 1];
|
||||||
|
|
||||||
|
for (let i = 0; i < units.length; i++) {
|
||||||
|
if (minutes >= multiples[i]) {
|
||||||
|
const timeCount = floor
|
||||||
|
? Math.floor(minutes / multiples[i])
|
||||||
|
: Math.ceil(minutes / multiples[i]);
|
||||||
|
minutes -= timeCount * multiples[i];
|
||||||
|
count++;
|
||||||
|
message +=
|
||||||
|
timeCount + (timeCount > 1 ? ` ${units[i]}s ` : ` ${units[i]} `);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count == precision) return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
return message || "0 minutes";
|
||||||
|
}
|
||||||
|
|
||||||
|
function PowerEstimate({
|
||||||
|
board,
|
||||||
|
splitType,
|
||||||
|
batteryMilliAh,
|
||||||
|
usage,
|
||||||
|
underglow,
|
||||||
|
display,
|
||||||
|
}) {
|
||||||
|
if (!board || !board.powerSupply.type || !batteryMilliAh) {
|
||||||
|
return (
|
||||||
|
<div className="powerEstimate">
|
||||||
|
<h3>
|
||||||
|
<span>{splitType !== "standalone" ? splitType + ": " : " "}...</span>
|
||||||
|
</h3>
|
||||||
|
<div className="powerEstimateBar">
|
||||||
|
<div
|
||||||
|
className="powerEstimateBarSection"
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
background: "#e0e0e0",
|
||||||
|
mixBlendMode: "overlay",
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const powerUsage = [];
|
||||||
|
let totalUsage = 0;
|
||||||
|
|
||||||
|
const voltageEquivalent = voltageEquivalentCalc(board.powerSupply);
|
||||||
|
|
||||||
|
// Lithium ion self discharge
|
||||||
|
const lithiumMonthlyDischargemAh =
|
||||||
|
parseInt(batteryMilliAh) * (lithiumIonMonthlyDischargePercent / 100);
|
||||||
|
const lithiumDischargeMicroA = (lithiumMonthlyDischargemAh * 1000) / 30 / 24;
|
||||||
|
const lithiumDischargeMicroW = lithiumDischargeMicroA * batVolt;
|
||||||
|
|
||||||
|
totalUsage += lithiumDischargeMicroW;
|
||||||
|
powerUsage.push({
|
||||||
|
title: "Battery Self Discharge",
|
||||||
|
usage: lithiumDischargeMicroW,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Quiescent current
|
||||||
|
const quiescentMicroATotal =
|
||||||
|
parseInt(board.powerSupply.quiescentMicroA) +
|
||||||
|
parseInt(board.otherQuiescentMicroA);
|
||||||
|
const quiescentMicroW = quiescentMicroATotal * voltageEquivalent;
|
||||||
|
|
||||||
|
totalUsage += quiescentMicroW;
|
||||||
|
powerUsage.push({
|
||||||
|
title: "Board Quiescent Usage",
|
||||||
|
usage: quiescentMicroW,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ZMK overall usage
|
||||||
|
const zmkMicroA =
|
||||||
|
zmkBase[splitType].idle +
|
||||||
|
(splitType !== "peripheral" ? zmkBase.hostConnection * usage.bondedQty : 0);
|
||||||
|
|
||||||
|
const zmkMicroW = zmkMicroA * voltageEquivalent;
|
||||||
|
const zmkUsage = zmkMicroW * (1 - usage.percentAsleep);
|
||||||
|
|
||||||
|
totalUsage += zmkUsage;
|
||||||
|
powerUsage.push({
|
||||||
|
title: "ZMK Base Usage",
|
||||||
|
usage: zmkUsage,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ZMK typing usage
|
||||||
|
const zmkTypingMicroA = zmkBase[splitType].typing * timeSpentTyping;
|
||||||
|
|
||||||
|
const zmkTypingMicroW = zmkTypingMicroA * voltageEquivalent;
|
||||||
|
const zmkTypingUsage = zmkTypingMicroW * (1 - usage.percentAsleep);
|
||||||
|
|
||||||
|
totalUsage += zmkTypingUsage;
|
||||||
|
powerUsage.push({
|
||||||
|
title: "ZMK Typing Usage",
|
||||||
|
usage: zmkTypingUsage,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (underglow.glowEnabled) {
|
||||||
|
const underglowAverageLedMicroA =
|
||||||
|
underglow.glowBrightness *
|
||||||
|
(underglowPower.ledOn - underglowPower.ledOff) +
|
||||||
|
underglowPower.ledOff;
|
||||||
|
|
||||||
|
const underglowMicroA =
|
||||||
|
underglowPower.firmware +
|
||||||
|
underglow.glowQuantity * underglowAverageLedMicroA;
|
||||||
|
|
||||||
|
const underglowMicroW = underglowMicroA * voltageEquivalent;
|
||||||
|
|
||||||
|
const underglowUsage = underglowMicroW * (1 - usage.percentAsleep);
|
||||||
|
|
||||||
|
totalUsage += underglowUsage;
|
||||||
|
powerUsage.push({
|
||||||
|
title: "RGB Underglow",
|
||||||
|
usage: underglowUsage,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (display.displayEnabled && display.displayType) {
|
||||||
|
const { activePercent, active, sleep } = displayPower[display.displayType];
|
||||||
|
|
||||||
|
const displayMicroA = active * activePercent + sleep * (1 - activePercent);
|
||||||
|
const displayMicroW = displayMicroA * voltageEquivalent;
|
||||||
|
const displayUsage = displayMicroW * (1 - usage.percentAsleep);
|
||||||
|
|
||||||
|
totalUsage += displayUsage;
|
||||||
|
powerUsage.push({
|
||||||
|
title: "Display",
|
||||||
|
usage: displayUsage,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the average minutes of use
|
||||||
|
const estimatedAvgEffectiveMicroWH =
|
||||||
|
batteryMilliAh * batVolt * lithiumIonDischargeEfficiency * 1000;
|
||||||
|
|
||||||
|
const estimatedAvgMinutes = Math.round(
|
||||||
|
(estimatedAvgEffectiveMicroWH / totalUsage) * 60
|
||||||
|
);
|
||||||
|
|
||||||
|
// Calculate worst case for battery life
|
||||||
|
const worstLithiumIonDischargeEfficiency =
|
||||||
|
lithiumIonDischargeEfficiency - lithiumIonDischargeEfficiencyRange;
|
||||||
|
|
||||||
|
const estimatedWorstEffectiveMicroWH =
|
||||||
|
batteryMilliAh * batVolt * worstLithiumIonDischargeEfficiency * 1000;
|
||||||
|
|
||||||
|
const highestTotalUsage = totalUsage * (1 + measurementAccuracy);
|
||||||
|
|
||||||
|
const estimatedWorstMinutes = Math.round(
|
||||||
|
(estimatedWorstEffectiveMicroWH / highestTotalUsage) * 60
|
||||||
|
);
|
||||||
|
|
||||||
|
// Calculate range (+-) of minutes using average - worst
|
||||||
|
const estimatedRange = estimatedAvgMinutes - estimatedWorstMinutes;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="powerEstimate">
|
||||||
|
<h3>
|
||||||
|
<span>{splitType !== "standalone" ? splitType + ": " : " "}</span>
|
||||||
|
{formatMinutes(estimatedAvgMinutes, 2, true)} (±
|
||||||
|
{formatMinutes(estimatedRange, 1, false).trim()})
|
||||||
|
</h3>
|
||||||
|
<div className="powerEstimateBar">
|
||||||
|
{powerUsage.map((p, i) => (
|
||||||
|
<div
|
||||||
|
key={p.title}
|
||||||
|
className={
|
||||||
|
"powerEstimateBarSection" + (i > 1 ? " rightSection" : "")
|
||||||
|
}
|
||||||
|
style={{
|
||||||
|
width: (p.usage / totalUsage) * 100 + "%",
|
||||||
|
background: palette[i],
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="powerEstimateTooltipWrap">
|
||||||
|
<div className="powerEstimateTooltip">
|
||||||
|
<div>
|
||||||
|
{p.title} - {Math.round((p.usage / totalUsage) * 100)}%
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: ".875rem" }}>
|
||||||
|
~{formatUsage(p.usage)} estimated avg. consumption
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
PowerEstimate.propTypes = {
|
||||||
|
board: PropTypes.Object,
|
||||||
|
splitType: PropTypes.string,
|
||||||
|
batteryMilliAh: PropTypes.number,
|
||||||
|
usage: PropTypes.Object,
|
||||||
|
underglow: PropTypes.Object,
|
||||||
|
display: PropTypes.Object,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PowerEstimate;
|
|
@ -4,6 +4,24 @@
|
||||||
* SPDX-License-Identifier: CC-BY-NC-SA-4.0
|
* SPDX-License-Identifier: CC-BY-NC-SA-4.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--codes-os-fg: black;
|
||||||
|
--codes-os-windows-bg: #caedfd;
|
||||||
|
--codes-os-linux-bg: #fff2ca;
|
||||||
|
--codes-os-android-bg: #d8eed9;
|
||||||
|
--codes-os-macos-bg: #ececec;
|
||||||
|
--codes-os-ios-bg: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
html[data-theme="dark"] {
|
||||||
|
--codes-os-fg: #f5f6f7;
|
||||||
|
--codes-os-windows-bg: #032535;
|
||||||
|
--codes-os-linux-bg: #332600;
|
||||||
|
--codes-os-android-bg: #112712;
|
||||||
|
--codes-os-macos-bg: #121212;
|
||||||
|
--codes-os-ios-bg: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
.codes.os.legend {
|
.codes.os.legend {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
@ -132,7 +150,7 @@ html[data-theme="light"] .codes.os.legend {
|
||||||
}
|
}
|
||||||
|
|
||||||
.codes .os {
|
.codes .os {
|
||||||
color: black;
|
color: var(--codes-os-fg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.codes td.os {
|
.codes td.os {
|
||||||
|
@ -144,23 +162,23 @@ html[data-theme="light"] .codes.os.legend {
|
||||||
}
|
}
|
||||||
|
|
||||||
.codes .os.windows {
|
.codes .os.windows {
|
||||||
background: #caedfd;
|
background: var(--codes-os-windows-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.codes .os.linux {
|
.codes .os.linux {
|
||||||
background: #fff2ca;
|
background: var(--codes-os-linux-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.codes .os.android {
|
.codes .os.android {
|
||||||
background: #d8eed9;
|
background: var(--codes-os-android-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.codes .os.macos {
|
.codes .os.macos {
|
||||||
background: #ececec;
|
background: var(--codes-os-macos-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.codes .os.ios {
|
.codes .os.ios {
|
||||||
background: #ffffff;
|
background: var(--codes-os-ios-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.codes .footnotes {
|
.codes .footnotes {
|
||||||
|
|
81
docs/src/css/power-estimate.css
Normal file
81
docs/src/css/power-estimate.css
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 The ZMK Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
.powerEstimate {
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.powerEstimate > h3 > span {
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.powerEstimateBar {
|
||||||
|
height: 64px;
|
||||||
|
width: 100%;
|
||||||
|
box-shadow: rgba(0, 0, 0, 0.03) 0px 10px 20px 0px,
|
||||||
|
rgba(0, 0, 0, 0.1) 0px 1px 4px 0px;
|
||||||
|
border-radius: 64px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.powerEstimateBarSection {
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.powerEstimateBarSection.rightSection {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.powerEstimateTooltipWrap {
|
||||||
|
position: absolute;
|
||||||
|
visibility: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(calc(-100% - 8px));
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.powerEstimateBarSection:hover .powerEstimateTooltipWrap {
|
||||||
|
visibility: visible;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.powerEstimateTooltip {
|
||||||
|
display: block;
|
||||||
|
position: relative;
|
||||||
|
box-shadow: var(--ifm-global-shadow-tl);
|
||||||
|
width: 260px;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: var(--ifm-background-surface-color);
|
||||||
|
transform: translateX(-15px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rightSection .powerEstimateTooltip {
|
||||||
|
transform: translateX(15px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.powerEstimateTooltip:after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 27px;
|
||||||
|
margin-left: -8px;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-top: 8px solid var(--ifm-background-surface-color);
|
||||||
|
border-right: 8px solid transparent;
|
||||||
|
border-left: 8px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rightSection .powerEstimateTooltip:after {
|
||||||
|
left: unset;
|
||||||
|
right: 27px;
|
||||||
|
margin-right: -8px;
|
||||||
|
}
|
195
docs/src/css/power-profiler.css
Normal file
195
docs/src/css/power-profiler.css
Normal file
|
@ -0,0 +1,195 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 The ZMK Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
.profilerSection {
|
||||||
|
margin: 10px 0;
|
||||||
|
padding: 10px 20px;
|
||||||
|
background: var(--ifm-background-surface-color);
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: rgba(0, 0, 0, 0.03) 0px 10px 20px 0px,
|
||||||
|
rgba(0, 0, 0, 0.1) 0px 1px 4px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profilerInput {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profilerInput label {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profilerDisclaimer {
|
||||||
|
padding: 20px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
span[tooltip] {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
span[tooltip]::before {
|
||||||
|
content: attr(tooltip);
|
||||||
|
font-size: 13px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
position: absolute;
|
||||||
|
width: 220px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: var(--ifm-background-surface-color);
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
box-shadow: rgba(0, 0, 0, 0.03) 0px 10px 20px 0px,
|
||||||
|
rgba(0, 0, 0, 0.1) 0px 1px 4px 0px;
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
transform: translate(-50%, -100%);
|
||||||
|
left: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
span[tooltip]::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
border-top: 8px solid var(--ifm-background-surface-color);
|
||||||
|
border-right: 8px solid transparent;
|
||||||
|
border-left: 8px solid transparent;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
left: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
span[tooltip]:hover::before {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
span[tooltip]:hover::after {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"].toggleInput {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"] + .toggle {
|
||||||
|
margin: 6px 2px;
|
||||||
|
height: 20px;
|
||||||
|
width: 48px;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
border-radius: 20px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"] + .toggle > .toggleThumb {
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 20px;
|
||||||
|
transform: translate(2px, 2px);
|
||||||
|
width: 16px;
|
||||||
|
background: var(--ifm-color-white);
|
||||||
|
box-shadow: var(--ifm-global-shadow-lw);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"]:checked + .toggle {
|
||||||
|
background: var(--ifm-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"]:checked + .toggle > .toggleThumb {
|
||||||
|
transform: translate(30px, 2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
border: solid 1px rgba(0, 0, 0, 0.5);
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
height: 34px;
|
||||||
|
width: 200px;
|
||||||
|
|
||||||
|
background: inherit;
|
||||||
|
color: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
margin: 0;
|
||||||
|
padding: 3px 5px;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
select > option {
|
||||||
|
background: var(--ifm-background-surface-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputBox {
|
||||||
|
border: solid 1px rgba(0, 0, 0, 0.5);
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputBox > input {
|
||||||
|
background: inherit;
|
||||||
|
color: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
margin: 0;
|
||||||
|
padding: 3px 10px;
|
||||||
|
border: none;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
text-align: right;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inputBox > span {
|
||||||
|
background: rgba(0, 0, 0, 0.05);
|
||||||
|
border-left: solid 1px rgba(0, 0, 0, 0.5);
|
||||||
|
padding: 3px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Chrome, Safari, Edge, Opera */
|
||||||
|
.inputBox > input::-webkit-outer-spin-button,
|
||||||
|
.inputBox > input::-webkit-inner-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Firefox */
|
||||||
|
.inputBox > input[type="number"] {
|
||||||
|
-moz-appearance: textfield;
|
||||||
|
}
|
||||||
|
|
||||||
|
.disclaimerHolder {
|
||||||
|
position: absolute;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 99;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.disclaimer {
|
||||||
|
padding: 20px 20px;
|
||||||
|
background: var(--ifm-background-surface-color);
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: rgba(0, 0, 0, 0.03) 0px 10px 20px 0px,
|
||||||
|
rgba(0, 0, 0, 0.1) 0px 1px 4px 0px;
|
||||||
|
width: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.disclaimer > button {
|
||||||
|
border: none;
|
||||||
|
background: var(--ifm-color-primary);
|
||||||
|
color: var(--ifm-color-white);
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 5px 15px;
|
||||||
|
}
|
78
docs/src/data/power.js
Normal file
78
docs/src/data/power.js
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 The ZMK Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file holds all current measurements related to ZMK features and hardware
|
||||||
|
* All current measurements are in micro amps. Measurements were taken on a Nordic Power Profiler Kit
|
||||||
|
* The test device to get these values was three nice!nanos (nRF52840).
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const zmkBase = {
|
||||||
|
hostConnection: 23, // How much current it takes to have an idle host connection
|
||||||
|
standalone: {
|
||||||
|
idle: 0, // No extra idle current
|
||||||
|
typing: 315, // Current while holding down a key. Represents polling+BLE notification power
|
||||||
|
},
|
||||||
|
central: {
|
||||||
|
idle: 490, // Idle current for connection to right half
|
||||||
|
typing: 380, // Current while holding down a key. Represents polling+BLE notification power
|
||||||
|
},
|
||||||
|
peripheral: {
|
||||||
|
idle: 20, // Idle current for connection to left half
|
||||||
|
typing: 365, // Current while holding down a key. Represents polling+BLE notification power
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ZMK board power measurements
|
||||||
|
*
|
||||||
|
* Power supply can be an LDO or switching
|
||||||
|
* Quiescent and other quiescent are measured in micro amps
|
||||||
|
*
|
||||||
|
* Switching efficiency represents the efficiency of converting from
|
||||||
|
* 3.8V (average li-ion voltage) to the output voltage of the power supply
|
||||||
|
*/
|
||||||
|
export const zmkBoards = {
|
||||||
|
"nice!nano": {
|
||||||
|
name: "nice!nano",
|
||||||
|
powerSupply: {
|
||||||
|
type: "LDO",
|
||||||
|
outputVoltage: 3.3,
|
||||||
|
quiescentMicroA: 55,
|
||||||
|
},
|
||||||
|
otherQuiescentMicroA: 4,
|
||||||
|
},
|
||||||
|
"nice!60": {
|
||||||
|
powerSupply: {
|
||||||
|
type: "SWITCHING",
|
||||||
|
outputVoltage: 3.3,
|
||||||
|
efficiency: 0.95,
|
||||||
|
quiescentMicroA: 4,
|
||||||
|
},
|
||||||
|
otherQuiescentMicroA: 4,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const underglowPower = {
|
||||||
|
firmware: 60, // ZMK power usage while underglow feature is turned on (SPIM mostly)
|
||||||
|
ledOn: 20000, // Estimated power consumption of a WS2812B at 100% (can be anywhere from 10mA to 30mA)
|
||||||
|
ledOff: 460, // Quiescent current of a WS2812B
|
||||||
|
};
|
||||||
|
|
||||||
|
export const displayPower = {
|
||||||
|
// Based on GoodDisplay's 1.02in epaper
|
||||||
|
EPAPER: {
|
||||||
|
activePercent: 0.05, // Estimated one refresh per minute taking three seconds
|
||||||
|
active: 1500, // Power draw during refresh
|
||||||
|
sleep: 5, // Idle power draw of an epaper
|
||||||
|
},
|
||||||
|
// 128x32 SSD1306
|
||||||
|
OLED: {
|
||||||
|
activePercent: 0.5, // Estimated sleeping half the time (based on idle)
|
||||||
|
active: 10000, // Estimated power draw when about half the pixels are on
|
||||||
|
sleep: 7, // Deep sleep power draw (display off)
|
||||||
|
},
|
||||||
|
};
|
297
docs/src/pages/power-profiler.js
Normal file
297
docs/src/pages/power-profiler.js
Normal file
|
@ -0,0 +1,297 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 The ZMK Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import classnames from "classnames";
|
||||||
|
import Layout from "@theme/Layout";
|
||||||
|
import styles from "./styles.module.css";
|
||||||
|
import PowerEstimate from "../components/power-estimate";
|
||||||
|
import CustomBoardForm from "../components/custom-board-form";
|
||||||
|
import { useInput } from "../utils/hooks";
|
||||||
|
import { zmkBoards } from "../data/power";
|
||||||
|
import "../css/power-profiler.css";
|
||||||
|
|
||||||
|
const Disclaimer = `This profiler makes many assumptions about typing
|
||||||
|
activity, battery characteristics, hardware behavior, and
|
||||||
|
doesn't account for error of user inputs. For example battery
|
||||||
|
mAh, which is often incorrectly advertised higher than it's actual capacity.
|
||||||
|
While it tries to estimate power usage using real power readings of ZMK,
|
||||||
|
every person will have different results that may be worse or even
|
||||||
|
better than the estimation given here.`;
|
||||||
|
|
||||||
|
function PowerProfiler() {
|
||||||
|
const { value: board, bind: bindBoard } = useInput("");
|
||||||
|
const { value: split, bind: bindSplit } = useInput(false);
|
||||||
|
const { value: batteryMilliAh, bind: bindBatteryMilliAh } = useInput(110);
|
||||||
|
|
||||||
|
const { value: psuType, bind: bindPsuType } = useInput("");
|
||||||
|
const { value: outputV, bind: bindOutputV } = useInput(3.3);
|
||||||
|
const { value: quiescentMicroA, bind: bindQuiescentMicroA } = useInput(55);
|
||||||
|
const {
|
||||||
|
value: otherQuiescentMicroA,
|
||||||
|
bind: bindOtherQuiescentMicroA,
|
||||||
|
} = useInput(0);
|
||||||
|
const { value: efficiency, bind: bindEfficiency } = useInput(0.9);
|
||||||
|
|
||||||
|
const { value: bondedQty, bind: bindBondedQty } = useInput(1);
|
||||||
|
const { value: percentAsleep, bind: bindPercentAsleep } = useInput(0.5);
|
||||||
|
|
||||||
|
const { value: glowEnabled, bind: bindGlowEnabled } = useInput(false);
|
||||||
|
const { value: glowQuantity, bind: bindGlowQuantity } = useInput(10);
|
||||||
|
const { value: glowBrightness, bind: bindGlowBrightness } = useInput(1);
|
||||||
|
|
||||||
|
const { value: displayEnabled, bind: bindDisplayEnabled } = useInput(false);
|
||||||
|
const { value: displayType, bind: bindDisplayType } = useInput("");
|
||||||
|
|
||||||
|
const [disclaimerAcknowledged, setDisclaimerAcknowledged] = useState(
|
||||||
|
typeof window !== "undefined"
|
||||||
|
? localStorage.getItem("zmkPowerProfilerDisclaimer") === "true"
|
||||||
|
: false
|
||||||
|
);
|
||||||
|
|
||||||
|
const currentBoard =
|
||||||
|
board === "custom"
|
||||||
|
? {
|
||||||
|
powerSupply: {
|
||||||
|
type: psuType,
|
||||||
|
outputVoltage: outputV,
|
||||||
|
quiescentMicroA: quiescentMicroA,
|
||||||
|
efficiency,
|
||||||
|
},
|
||||||
|
otherQuiescentMicroA: otherQuiescentMicroA,
|
||||||
|
}
|
||||||
|
: zmkBoards[board];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout
|
||||||
|
title={`ZMK Power Profiler`}
|
||||||
|
description="Estimate your keyboard's power usage and battery life on ZMK."
|
||||||
|
>
|
||||||
|
<header className={classnames("hero hero--primary", styles.heroBanner)}>
|
||||||
|
<div className="container">
|
||||||
|
<h1 className="hero__title">ZMK Power Profiler</h1>
|
||||||
|
<p className="hero__subtitle">
|
||||||
|
{"Estimate your keyboard's power usage and battery life on ZMK."}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
<section className="container">
|
||||||
|
<div className="profilerSection">
|
||||||
|
<h3>Keyboard Specifications</h3>
|
||||||
|
<div className="row">
|
||||||
|
<div className="col col--4">
|
||||||
|
<div className="profilerInput">
|
||||||
|
<label>Board</label>
|
||||||
|
<select {...bindBoard}>
|
||||||
|
<option hidden value="">
|
||||||
|
Select a board
|
||||||
|
</option>
|
||||||
|
{Object.keys(zmkBoards).map((b) => (
|
||||||
|
<option key={b}>{b}</option>
|
||||||
|
))}
|
||||||
|
<option value="custom">Custom</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="col col--4">
|
||||||
|
<div className="profilerInput">
|
||||||
|
<label>Split Keyboard</label>
|
||||||
|
<input
|
||||||
|
id="split"
|
||||||
|
checked={split}
|
||||||
|
{...bindSplit}
|
||||||
|
className="toggleInput"
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
<label htmlFor="split" className="toggle">
|
||||||
|
<div className="toggleThumb" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="col col--4">
|
||||||
|
<div className="profilerInput">
|
||||||
|
<label>Battery Size</label>
|
||||||
|
<div className="inputBox">
|
||||||
|
<input {...bindBatteryMilliAh} type="number" />
|
||||||
|
<span>mAh</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{board === "custom" && (
|
||||||
|
<CustomBoardForm
|
||||||
|
bindPsuType={bindPsuType}
|
||||||
|
bindOutputV={bindOutputV}
|
||||||
|
bindEfficiency={bindEfficiency}
|
||||||
|
bindQuiescentMicroA={bindQuiescentMicroA}
|
||||||
|
bindOtherQuiescentMicroA={bindOtherQuiescentMicroA}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="profilerSection">
|
||||||
|
<h3>Usage Values</h3>
|
||||||
|
<div className="row">
|
||||||
|
<div className="col col--4">
|
||||||
|
<div className="profilerInput">
|
||||||
|
<label>
|
||||||
|
Bonded Bluetooth Profiles{" "}
|
||||||
|
<span tooltip="The average number of host devices connected at once">
|
||||||
|
ⓘ
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<input {...bindBondedQty} type="range" min="1" max="5" />
|
||||||
|
<span>{bondedQty}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="col col--4">
|
||||||
|
<div className="profilerInput">
|
||||||
|
<label>
|
||||||
|
Percentage Asleep{" "}
|
||||||
|
<span tooltip="How much time the keyboard is in deep sleep (15 min. default timeout)">
|
||||||
|
ⓘ
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
{...bindPercentAsleep}
|
||||||
|
type="range"
|
||||||
|
min="0"
|
||||||
|
step=".1"
|
||||||
|
max="1"
|
||||||
|
/>
|
||||||
|
<span>{Math.round(percentAsleep * 100)}%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="profilerSection">
|
||||||
|
<h3>Features</h3>
|
||||||
|
<div className="row">
|
||||||
|
<div className="col col--4">
|
||||||
|
<div className="profilerInput">
|
||||||
|
<label>RGB Underglow</label>
|
||||||
|
<input
|
||||||
|
checked={glowEnabled}
|
||||||
|
id="glow"
|
||||||
|
{...bindGlowEnabled}
|
||||||
|
className="toggleInput"
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
<label htmlFor="glow" className="toggle">
|
||||||
|
<div className="toggleThumb" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{glowEnabled && (
|
||||||
|
<>
|
||||||
|
<div className="profilerInput">
|
||||||
|
<label>LED Quantity</label>
|
||||||
|
<div className="inputBox">
|
||||||
|
<input {...bindGlowQuantity} type="number" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="profilerInput">
|
||||||
|
<label>Brightness</label>
|
||||||
|
<input
|
||||||
|
{...bindGlowBrightness}
|
||||||
|
type="range"
|
||||||
|
min="0"
|
||||||
|
step=".01"
|
||||||
|
max="1"
|
||||||
|
/>
|
||||||
|
<span>{Math.round(glowBrightness * 100)}%</span>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="col col--4">
|
||||||
|
<div className="profilerInput">
|
||||||
|
<label>Display</label>
|
||||||
|
<input
|
||||||
|
checked={displayEnabled}
|
||||||
|
id="display"
|
||||||
|
{...bindDisplayEnabled}
|
||||||
|
className="toggleInput"
|
||||||
|
type="checkbox"
|
||||||
|
/>
|
||||||
|
<label htmlFor="display" className="toggle">
|
||||||
|
<div className="toggleThumb" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{displayEnabled && (
|
||||||
|
<div className="profilerInput">
|
||||||
|
<label>Display Type</label>
|
||||||
|
<select {...bindDisplayType}>
|
||||||
|
<option hidden selected>
|
||||||
|
Select type
|
||||||
|
</option>
|
||||||
|
<option value="EPAPER">ePaper</option>
|
||||||
|
<option value="OLED">OLED</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{split ? (
|
||||||
|
<>
|
||||||
|
<PowerEstimate
|
||||||
|
board={currentBoard}
|
||||||
|
splitType="central"
|
||||||
|
batteryMilliAh={batteryMilliAh}
|
||||||
|
usage={{ bondedQty, percentAsleep }}
|
||||||
|
underglow={{ glowEnabled, glowBrightness, glowQuantity }}
|
||||||
|
display={{ displayEnabled, displayType }}
|
||||||
|
/>
|
||||||
|
<PowerEstimate
|
||||||
|
board={currentBoard}
|
||||||
|
splitType="peripheral"
|
||||||
|
batteryMilliAh={batteryMilliAh}
|
||||||
|
usage={{ bondedQty, percentAsleep }}
|
||||||
|
underglow={{ glowEnabled, glowBrightness, glowQuantity }}
|
||||||
|
display={{ displayEnabled, displayType }}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<PowerEstimate
|
||||||
|
board={currentBoard}
|
||||||
|
splitType="standalone"
|
||||||
|
batteryMilliAh={batteryMilliAh}
|
||||||
|
usage={{ bondedQty, percentAsleep }}
|
||||||
|
underglow={{ glowEnabled, glowBrightness, glowQuantity }}
|
||||||
|
display={{ displayEnabled, displayType }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<div className="row">
|
||||||
|
<div className="col col--8 col--offset-2 profilerDisclaimer">
|
||||||
|
Disclaimer: {Disclaimer}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
{!disclaimerAcknowledged && (
|
||||||
|
<div className="disclaimerHolder">
|
||||||
|
<div className="disclaimer">
|
||||||
|
<h3>Disclaimer</h3>
|
||||||
|
<p>{Disclaimer}</p>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setDisclaimerAcknowledged(true);
|
||||||
|
localStorage.setItem("zmkPowerProfilerDisclaimer", true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
I Understand
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PowerProfiler;
|
23
docs/src/utils/hooks.js
Normal file
23
docs/src/utils/hooks.js
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021 The ZMK Contributors
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
export const useInput = (initialValue) => {
|
||||||
|
const [value, setValue] = useState(initialValue);
|
||||||
|
|
||||||
|
return {
|
||||||
|
value,
|
||||||
|
setValue,
|
||||||
|
bind: {
|
||||||
|
value,
|
||||||
|
onChange: (event) => {
|
||||||
|
const target = event.target;
|
||||||
|
setValue(target.type === "checkbox" ? target.checked : target.value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
Loading…
Add table
Reference in a new issue