Redesign the plugin setup procedure

Instead of a low-level interface where one has to set the EEPROM-stored layers
and the lookup method separately, introduce a `setup` method that combines the
two in a much easier to grasp interface. It takes a layer number, and an
optional mode, and sets things up accordingly.

With this new setup procedure comes a new way of how the plugin works: instead
of being able to override the keymap in EEPROM, we extend it (or use a custom
implementation, for advanced use-cases). The default layer can still be set via
Focus, thus effectively overriding the keymap in PROGMEM. To better support
this, a new Focus command is introduced too: `keymap.roLayers`, which returns
the number of layers in PROGMEM.

The `keymap.transfer` Focus command is removed, because it can be done much more
reliably from the host side, building on top of `keymap.map`.

The rest of the lower-level interface is still there, though undocumented, for
advanced use-cases the new simplified setup does not fit.

Signed-off-by: Gergely Nagy <algernon@keyboard.io>
pull/365/head
Gergely Nagy 6 years ago
parent a066d82747
commit 83f1805d5d

@ -9,26 +9,15 @@
[st:broken]: https://img.shields.io/badge/broken-X-black.svg?style=flat&colorA=e05d44&colorB=494e52
[st:experimental]: https://img.shields.io/badge/experimental----black.svg?style=flat&colorA=dfb317&colorB=494e52
While keyboards usually ship with a keymap programmed in, to be able to change
that keymap, without flashing new firmware, we need a way to place the keymap
into a place we can update at run-time, and which persists across reboots.
Fortunately, we have a bit of `EEPROM` on the keyboard, and can use it to store
either the full keymap (and saving space in the firmware then), or store an
overlay there. In the latter case, whenever there is a non-transparent key on
the overlay, we will use that instead of the keyboard default.
In short, this plugin allows us to change our keymaps, without having to compile
and flash new firmware. It does so through the use of the
[FocusSerial][plugin:focusSerial] plugin.
While keyboards usually ship with a keymap programmed in, to be able to change that keymap, without flashing new firmware, we need a way to place the keymap into a place we can update at run-time, and which persists across reboots. Fortunately, we have a bit of `EEPROM` on the keyboard, and can use it to store either the full keymap (and saving space in the firmware then), or store additional layers there.
In short, this plugin allows us to change our keymaps, without having to compile and flash new firmware. It does so through the use of the [FocusSerial][plugin:focusSerial] plugin.
[plugin:focusSerial]: https://github.com/keyboardio/Kaleidoscope-FocusSerial
## Using the plugin
Using the plugin is reasonably simple: after including the header, enable the
plugin, and configure how many layers at most we want to store in `EEPROM`.
There are other settings one can tweak, but these two steps are enough to get
started with.
Using the plugin is reasonably simple: after including the header, enable the plugin, and configure how many layers at most we want to store in `EEPROM`. There are other settings one can tweak, but these two steps are enough to get started with.
Once these are set up, we can update the keymap via [Focus][plugin:focusSerial].
@ -38,53 +27,40 @@ Once these are set up, we can update the keymap via [Focus][plugin:focusSerial].
#include <Kaleidoscope-FocusSerial.h>
KALEIDOSCOPE_INIT_PLUGINS(EEPROMKeymap,
Focus,
FocusKeymapTransferCommand);
Focus);
void setup() {
Kaleidoscope.setup();
EEPROMKeymap.max_layers(1);
EEPROMKeymap.setup(1);
}
```
## Plugin methods
The plugin provides the `EEPROMKeymap` object, which has the following methods:
The plugin provides the `EEPROMKeymap` object, which has the following method:
### `.max_layers(max)`
### `.setup(layers[, mode])`
> Tells the extension to reserve space in EEPROM for up to `max` layers. Can
> only be called once, any subsequent call will be a no-op.
> This should be set to the number of keymap layers you want to be
> able to program from EEPROM (probably the number of layers you have
> defined in your keymap).
> Reserve space in EEPROM for up to `layers` layers, and set things up to work according to the specified `mode` (see below for a list of supported modes). To be called from the `setup` method of one's sketch.
>
> Supported modes are:
> - `EEPROMKeymap.Mode::EXTEND`: Extend the keymap with layers from EEPROM, treating them as extensions of the main keymap embedded in the firmware. The first layer in EEPROM will have a number one higher than the last layer in PROGMEM. In this case, the total number of layers will be the number of them in PROGMEM plus `layers`.
> - `EEPROMKeymap.Mode::CUSTOM`: For advanced use cases where the `EXTEND` mode is not appropriate. In this case, the plugin merely reserves a slice of EEPROM for the requested amount of layers, but does no other configuration - that's entirely up to the Sketch.
## Focus commands
The plugin provides a `keymap.map` Focus command unconditionally, and a
`keymap.transfer` via the `FocusKeymapTransferCommand` object.
The plugin provides the `keymap.map` and a `keymap.roLayers` commands.
### `keymap.map [codes...]`
> Without arguments, displays the keymap currently in effect. Each key is
> printed as its raw, 16-bit keycode.
> Without arguments, displays the keymap currently in effect. Each key is printed as its raw, 16-bit keycode.
>
> With arguments, it stores as many keys as given. One does not need to set all
> keys, on all layers: the command will start from the first key on the first
> layer, and go on as long as it has input. It will not go past the layer set
> via the `.max_layers()` method.
> With arguments, it stores as many keys as given. One does not need to set all keys, on all layers: the command will start from the first key on the first layer, and go on as long as it has input. It will not go past the total amount of layers (that is, `layer_count`).
### `keymap.transfer LAYER`
### `keymap.roLayers`
> Transfers the `LAYER` from the built-in memory of the keyboard into `EEPROM`
> storage.
>
> Useful mostly when one wants to remove the built-in keymap, and wants to
> easily transfer it into `EEPROM` first.
>
> This is generally not needed, and it is recommended to not enable this
> command, unless the feature this command implements is truly needed.
> Returns the number of read-only layers. This only makes sense for the `EEPROMKeymap.Mode::EXTEND` mode, where it returns the number of layers in PROGMEM. In any other case, it doesn't return anything, doing so is left for another event handler that understands what the correct value would be.
## Dependencies
@ -93,7 +69,6 @@ The plugin provides a `keymap.map` Focus command unconditionally, and a
## Further reading
Starting from the [example][plugin:example] is the recommended way of getting
started with the plugin.
Starting from the [example][plugin:example] is the recommended way of getting started with the plugin.
[plugin:example]: https://github.com/keyboardio/Kaleidoscope-EEPROM-Keymap/blob/master/examples/EEPROM-Keymap/EEPROM-Keymap.ino

@ -40,12 +40,12 @@ KEYMAPS(
)
// *INDENT-ON*
KALEIDOSCOPE_INIT_PLUGINS(EEPROMKeymap, Focus, FocusKeymapTransferCommand);
KALEIDOSCOPE_INIT_PLUGINS(EEPROMKeymap, Focus);
void setup() {
Kaleidoscope.setup();
EEPROMKeymap.max_layers(1);
EEPROMKeymap.setup(1, EEPROMKeymap.Mode::EXTEND);
}
void loop() {

@ -18,4 +18,3 @@
#pragma once
#include <Kaleidoscope/EEPROM-Keymap.h>
#include <Kaleidoscope/EEPROM-Keymap-Transfer.h>

@ -1,50 +0,0 @@
/* -*- mode: c++ -*-
* Kaleidoscope-EEPROM-Keymap -- EEPROM-based keymap support.
* Copyright (C) 2017, 2018 Keyboard.io, Inc
*
* 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, version 3.
*
* 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 <Kaleidoscope-EEPROM-Settings.h>
#include <Kaleidoscope-EEPROM-Keymap.h>
#include <Kaleidoscope-FocusSerial.h>
namespace kaleidoscope {
namespace eeprom {
EventHandlerResult FocusKeymapTransferCommand::onFocusEvent(const char *command) {
const char *cmd = PSTR("keymap.transfer");
if (::Focus.handleHelp(command, cmd))
return EventHandlerResult::OK;
if (strcmp_P(command, cmd) != 0)
return EventHandlerResult::OK;
uint8_t layer = Serial.parseInt();
for (uint8_t row = 0; row < ROWS; row++) {
for (uint8_t col = 0; col < COLS; col++) {
Key k = Layer.getKeyFromPROGMEM(layer, row, col);
uint16_t pos = ((layer * ROWS * COLS) + (row * COLS) + col);
::EEPROMKeymap.updateKey(pos, k);
}
}
return EventHandlerResult::EVENT_CONSUMED;
}
}
}
kaleidoscope::eeprom::FocusKeymapTransferCommand FocusKeymapTransferCommand;

@ -1,34 +0,0 @@
/* -*- mode: c++ -*-
* Kaleidoscope-EEPROM-Keymap -- EEPROM-based keymap support.
* Copyright (C) 2017-2018 Keyboard.io, Inc
*
* 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, version 3.
*
* 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 <Kaleidoscope.h>
#include <Kaleidoscope/EEPROM-Keymap.h>
namespace kaleidoscope {
namespace eeprom {
class FocusKeymapTransferCommand : public Plugin {
public:
FocusKeymapTransferCommand() {}
EventHandlerResult onFocusEvent(const char *command);
};
}
}
extern kaleidoscope::eeprom::FocusKeymapTransferCommand FocusKeymapTransferCommand;

@ -20,16 +20,30 @@
#include <Kaleidoscope-FocusSerial.h>
namespace kaleidoscope {
EEPROMKeymap::Mode EEPROMKeymap::mode_;
uint16_t EEPROMKeymap::keymap_base_;
uint8_t EEPROMKeymap::max_layers_;
uint8_t EEPROMKeymap::progmem_layers_;
EventHandlerResult EEPROMKeymap::onSetup() {
::EEPROMSettings.onSetup();
Layer.getKey = ::EEPROMKeymap.getKeyOverride;
progmem_layers_ = layer_count;
return EventHandlerResult::OK;
}
void EEPROMKeymap::setup(uint8_t max, Mode mode) {
switch (mode) {
case Mode::CUSTOM:
break;
case Mode::EXTEND:
layer_count = progmem_layers_ + max;
Layer.getKey = getKeyExtended;
break;
}
mode_ = mode;
max_layers(max);
}
void EEPROMKeymap::max_layers(uint8_t max) {
max_layers_ = max;
keymap_base_ = ::EEPROMSettings.requestSlice(max_layers_ * ROWS * COLS * 2);
@ -49,22 +63,16 @@ Key EEPROMKeymap::getKey(uint8_t layer, byte row, byte col) {
return key;
}
Key EEPROMKeymap::getKeyOverride(uint8_t layer, byte row, byte col) {
Key EEPROMKeymap::getKeyExtended(uint8_t layer, byte row, byte col) {
Key key;
key = getKey(layer, row, col);
/*
* If we read a transparent key from EEPROM, or we're trying to read from a
* layer higher than what is available there (max_layers), check if we're below
* the layer count in PROGMEM (layer_count). If we are, read from PROGMEM,
* otherwise leave the key as-is (either transparent or NoKey).
*/
if ((key == Key_Transparent || layer >= max_layers_) &&
(layer < layer_count))
key = Layer.getKeyFromPROGMEM(layer, row, col);
// If the layer is within PROGMEM bounds, look it up from there
if (layer < progmem_layers_) {
return Layer.getKeyFromPROGMEM(layer, row, col);
}
return key;
// If the layer is outside of PROGMEM, look up from EEPROM
return getKey(layer - progmem_layers_, row, col);
}
uint16_t EEPROMKeymap::keymap_base(void) {
@ -90,14 +98,24 @@ void EEPROMKeymap::printKey(Key k) {
EventHandlerResult EEPROMKeymap::onFocusEvent(const char *command) {
const char *cmd = PSTR("keymap.map");
if (::Focus.handleHelp(command, cmd))
if (::Focus.handleHelp(command, PSTR("keymap.map\nkeymap.roLayers")))
return EventHandlerResult::OK;
if (strncmp_P(command, PSTR("keymap."), 7) != 0)
return EventHandlerResult::OK;
if (strcmp_P(command, cmd) != 0)
if (strcmp_P(command + 7, PSTR("roLayers")) == 0) {
if (mode_ != Mode::EXTEND)
return EventHandlerResult::OK;
Serial.println(progmem_layers_);
return EventHandlerResult::EVENT_CONSUMED;
}
if (strcmp_P(command + 7, PSTR("map")) != 0)
return EventHandlerResult::OK;
if (Serial.peek() == '\n') {
for (uint8_t layer = 0; layer < max_layers_; layer++) {
for (uint8_t layer = 0; layer < layer_count; layer++) {
for (uint8_t row = 0; row < ROWS; row++) {
for (uint8_t col = 0; col < COLS; col++) {
Key k = Layer.getKey(layer, row, col);
@ -110,8 +128,19 @@ EventHandlerResult EEPROMKeymap::onFocusEvent(const char *command) {
Serial.println();
} else {
uint16_t i = 0;
while ((Serial.peek() != '\n') && (i < ROWS * COLS * max_layers_)) {
updateKey(i, parseKey());
uint8_t layers = layer_count;
if (layers > 0)
layers--;
while ((Serial.peek() != '\n') && (i < ROWS * COLS * layers)) {
Key k = parseKey();
if (mode_ == Mode::EXTEND) {
uint8_t layer = i / (ROWS * COLS);
if (layer >= progmem_layers_)
updateKey(i - (progmem_layers_ * ROWS * COLS), k);
} else {
updateKey(i, k);
}
i++;
}
}

@ -23,23 +23,32 @@
namespace kaleidoscope {
class EEPROMKeymap : public kaleidoscope::Plugin {
public:
enum class Mode {
CUSTOM,
EXTEND,
};
EEPROMKeymap(void) {}
EventHandlerResult onSetup();
EventHandlerResult onFocusEvent(const char *command);
static void setup(uint8_t max, Mode mode = Mode::EXTEND);
static void max_layers(uint8_t max);
static uint16_t keymap_base(void);
static Key getKey(uint8_t layer, byte row, byte col);
static Key getKeyOverride(uint8_t layer, byte row, byte col);
static Key getKeyExtended(uint8_t layer, byte row, byte col);
static void updateKey(uint16_t base_pos, Key key);
private:
static uint16_t keymap_base_;
static uint8_t max_layers_;
static uint8_t progmem_layers_;
static Mode mode_;
static Key parseKey(void);
static void printKey(Key key);

Loading…
Cancel
Save