Skip to content

Commit

Permalink
Add Word Per Minute calculation feature (qmk#8054)
Browse files Browse the repository at this point in the history
* Add Word Per Minute calculation feature

* Fix copyright info

* Remove header from quantum.c, setup overloadable keycode inclusion for WPM, update docs

* Simplify logic for keycode filtering

* Adding link from summary to wpm_feature info

* Update docs/feature_wpm.md

Typo in function prototype example in docs

Co-Authored-By: James Young <[email protected]>

* Add WPM transport via i2c

Co-authored-by: James Young <[email protected]>
  • Loading branch information
brickbots and noroadsleft authored Mar 22, 2020
1 parent d8f3c28 commit bfb2f8e
Show file tree
Hide file tree
Showing 8 changed files with 169 additions and 0 deletions.
5 changes: 5 additions & 0 deletions common_features.mk
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,11 @@ ifeq ($(strip $(USB_HID_ENABLE)), yes)
include $(TMK_DIR)/protocol/usb_hid.mk
endif

ifeq ($(strip $(WPM_ENABLE)), yes)
SRC += $(QUANTUM_DIR)/wpm.c
OPT_DEFS += -DWPM_ENABLE
endif

ifeq ($(strip $(ENCODER_ENABLE)), yes)
SRC += $(QUANTUM_DIR)/encoder.c
OPT_DEFS += -DENCODER_ENABLE
Expand Down
1 change: 1 addition & 0 deletions docs/_summary.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
* [Terminal](feature_terminal.md)
* [Unicode](feature_unicode.md)
* [Userspace](feature_userspace.md)
* [WPM Calculation](feature_wpm.md)

* Hardware Features
* Displays
Expand Down
25 changes: 25 additions & 0 deletions docs/feature_wpm.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Word Per Minute (WPM) Calculcation

The WPM feature uses time between keystrokes to compute a rolling average words
per minute rate and makes this available for various uses.

Enable the WPM system by adding this to your `rules.mk`:

WPM_ENABLE = yes

For split keyboards using soft serial, the computed WPM
score will be available on the master AND slave half.

## Public Functions

`uint8_t get_current_wpm(void);`
This function returns the current WPM as an unsigned integer.


## Customized keys for WPM calc

By default, the WPM score only includes letters, numbers, space and some
punctuation. If you want to change the set of characters considered as part of
the WPM calculation, you can implement `wpm_keycode_user(uint16_t keycode)`
and return true for any characters you would like included in the calculation,
or false to not count that particular keycode.
10 changes: 10 additions & 0 deletions quantum/quantum.c
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,12 @@ bool process_record_quantum(keyrecord_t *record) {
}
#endif

#ifdef WPM_ENABLE
if (record->event.pressed) {
update_wpm(keycode);
}
#endif

#ifdef TAP_DANCE_ENABLE
preprocess_tap_dance(keycode, record);
#endif
Expand Down Expand Up @@ -645,6 +651,10 @@ void matrix_scan_quantum() {
encoder_read();
#endif

#ifdef WPM_ENABLE
decay_wpm();
#endif

#ifdef HAPTIC_ENABLE
haptic_task();
#endif
Expand Down
4 changes: 4 additions & 0 deletions quantum/quantum.h
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,10 @@ extern layer_state_t layer_state;
# include "via.h"
#endif

#ifdef WPM_ENABLE
# include "wpm.h"
#endif

// Function substitutions to ease GPIO manipulation
#if defined(__AVR__)
typedef uint8_t pin_t;
Expand Down
27 changes: 27 additions & 0 deletions quantum/split_common/transport.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ typedef struct _I2C_slave_buffer_t {
# ifdef ENCODER_ENABLE
uint8_t encoder_state[NUMBER_OF_ENCODERS];
# endif
# ifdef WPM_ENABLE
uint8_t current_wpm;
# endif
} I2C_slave_buffer_t;

static I2C_slave_buffer_t *const i2c_buffer = (I2C_slave_buffer_t *)i2c_slave_reg;
Expand All @@ -43,6 +46,7 @@ static I2C_slave_buffer_t *const i2c_buffer = (I2C_slave_buffer_t *)i2c_slave_re
# define I2C_RGB_START offsetof(I2C_slave_buffer_t, rgblight_sync)
# define I2C_KEYMAP_START offsetof(I2C_slave_buffer_t, smatrix)
# define I2C_ENCODER_START offsetof(I2C_slave_buffer_t, encoder_state)
# define I2C_WPM_START offsetof(I2C_slave_buffer_t, current_wpm)

# define TIMEOUT 100

Expand Down Expand Up @@ -79,6 +83,14 @@ bool transport_master(matrix_row_t matrix[]) {
encoder_update_raw(i2c_buffer->encoder_state);
# endif

# ifdef WPM_ENABLE
uint8_t current_wpm = get_current_wpm();
if(current_wpm != i2c_buffer->current_wpm) {
if (i2c_writeReg(SLAVE_I2C_ADDRESS, I2C_WPM_START, (void *)&current_wpm, sizeof(current_wpm), TIMEOUT) >= 0) {
i2c_buffer->current_wpm = current_wpm;
}
}
# endif
return true;
}

Expand All @@ -102,6 +114,10 @@ void transport_slave(matrix_row_t matrix[]) {
# ifdef ENCODER_ENABLE
encoder_state_raw(i2c_buffer->encoder_state);
# endif

# ifdef WPM_ENABLE
set_current_wpm(i2c_buffer->current_wpm);
# endif
}

void transport_master_init(void) { i2c_init(); }
Expand All @@ -126,6 +142,9 @@ typedef struct _Serial_m2s_buffer_t {
# ifdef BACKLIGHT_ENABLE
uint8_t backlight_level;
# endif
# ifdef WPM_ENABLE
uint8_t current_wpm;
# endif
} Serial_m2s_buffer_t;

# if defined(RGBLIGHT_ENABLE) && defined(RGBLIGHT_SPLIT)
Expand Down Expand Up @@ -228,6 +247,10 @@ bool transport_master(matrix_row_t matrix[]) {
encoder_update_raw((uint8_t *)serial_s2m_buffer.encoder_state);
# endif

# ifdef WPM_ENABLE
// Write wpm to slave
serial_m2s_buffer.current_wpm = get_current_wpm();
# endif
return true;
}

Expand All @@ -244,6 +267,10 @@ void transport_slave(matrix_row_t matrix[]) {
# ifdef ENCODER_ENABLE
encoder_state_raw((uint8_t *)serial_s2m_buffer.encoder_state);
# endif

# ifdef WPM_ENABLE
set_current_wpm(serial_m2s_buffer.current_wpm);
# endif
}

#endif
67 changes: 67 additions & 0 deletions quantum/wpm.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright 2020 Richard Sutherland ([email protected])
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include "wpm.h"

//WPM Stuff
static uint8_t current_wpm = 0;
static uint8_t latest_wpm = 0;
static uint16_t wpm_timer = 0;

//This smoothing is 40 keystrokes
static const float wpm_smoothing = 0.0487;

void set_current_wpm(uint8_t new_wpm) { current_wpm = new_wpm; }

uint8_t get_current_wpm(void) { return current_wpm; }

bool wpm_keycode(uint16_t keycode) { return wpm_keycode_kb(keycode); }

__attribute__((weak)) bool wpm_keycode_kb(uint16_t keycode) { return wpm_keycode_user(keycode); }

__attribute__((weak)) bool wpm_keycode_user(uint16_t keycode) {

if ((keycode >= QK_MOD_TAP && keycode <= QK_MOD_TAP_MAX) || (keycode >= QK_LAYER_TAP && keycode <= QK_LAYER_TAP_MAX) || (keycode >= QK_MODS && keycode <= QK_MODS_MAX)) {
keycode = keycode & 0xFF;
} else if (keycode > 0xFF) {
keycode = 0;
}
if((keycode >= KC_A && keycode <= KC_0) || (keycode >= KC_TAB && keycode <= KC_SLASH)) {
return true;
}

return false;
}


void update_wpm(uint16_t keycode) {
if(wpm_keycode(keycode)) {
if(wpm_timer > 0) {
latest_wpm = 60000 / timer_elapsed(wpm_timer) / 5;
current_wpm = (latest_wpm - current_wpm) * wpm_smoothing + current_wpm;
}
wpm_timer = timer_read();
}
}

void decay_wpm(void) {
if (timer_elapsed(wpm_timer) > 1000) {
current_wpm = (0 - current_wpm) * wpm_smoothing +
current_wpm;
wpm_timer = timer_read();
}
}
30 changes: 30 additions & 0 deletions quantum/wpm.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2020 Richard Sutherland ([email protected])
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

#include "quantum.h"

bool wpm_keycode(uint16_t keycode);
bool wpm_keycode_kb(uint16_t keycode);
bool wpm_keycode_user(uint16_t keycode);

void set_current_wpm(uint8_t);
uint8_t get_current_wpm(void);
void update_wpm(uint16_t);

void decay_wpm(void);

0 comments on commit bfb2f8e

Please sign in to comment.