Below is a snippet for implementing a combination dial-like mechanism you can use in your QMK keymap. This is useful for entering long passwords using a rotary encoder, or activating secret macros etc. The algorithm looks for a sequence of clockwise (CW) or counter-clockwise (CCW) turns of the rotary encoder for a certain number of ticks/clicks. It executes the macro if all turns are successfully completed, or resets to the beginning if a turn is too long or too short.
For this example we will output a string as a macro, like entering a password. In this case it is "TA DA!":
void emit_passphrase_secret(void) {
SEND_STRING("TA DA!")
}
Then we define the secret combination, which is the number of ticks in alternating directions. In this example we look for a sequence of three turns: (1) one full turn CW, (2) one half turn CCW, (3) one and a half turns CW. My encoder has twenty ticks for a full turn, so we can write:
#define COMBINATION { 20, 10, 30 }
#define START_CW true
We set the direction for the first turn with the START_CW
macro, which is true
for CW and false
for CCW.
Lastly we define a tolerance value such that turn_ticks +/- tolerance
will be accepted if a turn requires turn_ticks
ticks.
This is necessary because most rotary encoders are not precise in counting ticks; for example mine usually misses the first tick after a direction switch.
#define TOLERANCE 3
Below is the main function that keeps track of the "state" of the lock (i.e. how many turns we have successfully completed), which will be called at each tick of the encoder.
void update_dial(bool direction) {
const static uint8_t combo[] = COMBINATION;
const static size_t combo_len = sizeof(combo);
static bool direction_to_go = START_CW;
static uint8_t state = 0, clicks = 0, grace = 0;
if (direction == direction_to_go) { // check correct direction
if (++clicks == combo[state] - TOLERANCE) { // increment clicks and check if dialed enough
clicks = grace = 0;
direction_to_go = !direction_to_go; // flip expected direction
if (++state == combo_len) { // increment state then check if we are at the end
state = 0; // emit secret passphrase and reset to start
direction_to_go = START_CW;
emit_passphrase_secret();
}
}
} else { // wrong direction so fail, except...
if (clicks != 0 || ++grace > 2 * TOLERANCE) { // a few extra ticks just after changing direction is acceptable
state = clicks = grace = 0; // reset to start
direction_to_go = true;
}
}
}
Finally we hook this method up to our encoder_update_user
method so it gets updated at each tick:
void encoder_update_user(uint8_t index, bool clockwise) {
if (index == 0) { // checking the first and only encoder, in this case
switch (get_highest_layer(layer_state)) {
// I'd recommend to check only when in a specific layer, in this case _FUNC
/* code for other layers... */
case _FUNC:
update_dial(clockwise);
break;
/* code for other layers... */
}
}
}
IMPORTANT: I don't recommend anyone store their passwords in their keyboard's firmware unless you are 100% sure no one else will have physical access to it. This lock is not a guaranteed method of keeping your password safe. For instance someone can imitate your turns to unlock it, or if they know what they are doing dump the keyboard's firmware and extract the password from there.
Also make sure not to commit your passphrase if you are keeping your keymap in a public repository. Here are some tips on storing your secret macros with QMK from drashna: https://github.com/qmk/qmk_firmware/blob/master/users/drashna/keyrecords/secrets.md
For an example implementation using drashna's userspace framework with secrets, you can check out my QMK userspace. rotary_lock.c
and rotary_lock.h
contains the above implementation using emit_passphrase_secret
function defined in secrets.c
(which isn't checked in). rules.mk
has the compilation rules so that the rotary lock functions are compiled only if secrets.c
exists and if USE_SECRETS
is defined, so that you can only enable it for certain keyboards.