Introduced dynamic LED modes

This PR introduces the concept of dynamic LED modes. Those are LED modes whose class instances
have a restricted lifetime that lasts only as long as a LED mode is active. By this means
it is possible to support a greater amount of LED modes - especially RAM-hungry ones - in the same firmware build. The amount of RAM used to store dynamic LED modes is now bounded
by the maximum size (`sizeof(...)`) of the largest dynamic LED mode.

Old-style LED modes are furtheron called _static_ in the terminology of this PR. They are still supported and blend in nicely with the newly introduced dynamic LED modes.

All changes are entirely backward compatible. No user sketches or existing user plugins require changes.

The greatest benefit of this change is that it drastically reduces the consumption of RAM
when multiple complex LED modes are used. Currently the most complex stock LED mode is
the wavepool effect. Its plugin requires around 140 bytes of RAM that are statically allocated and cannot be shared with any other features.
With this change it becomes possible to have a large number of such resource-hungry LED modes in parallel without a significant gain in RAM consumption.

For the stock firmware this change means a small (~30 byte) growth in terms of PROGMEM. On the other hand it reduces the amount of statically consumed RAM by ~90 bytes. As the current atmel architectures come with around ten times as much PROGMEM as RAM, this means a great improvement as RAM is the more critical resource.

If the wavepool effect, a especially RAM-hungry LED mode is added to the stock firmware,
the saving of RAM increases to 160 bytes which is almost 8% of RAM of the Keyboardio Model01.

A new interface class `LEDModeInterface` was introduced that those plugins
that export dynamic LED modes inherit from. To remain backward compatible, the `LEDMode` class that all pre-existing LED mode plugins inherited from is also derived from `LEDModeInterface`.

The new interface class currently lives in header `LEDMode.h` (see information about this new header below). This is because `LEDMode` and `LEDModeInterface` will
always be used together by dynamic LED modes. Thus, an extra header for `LEDModeInterface` would only mean extra include work for users writing plugins.

Those plugins that export dynamic LED modes must furtheron provide a exported type `DynamicLEDMode`.
This can either be done by defining a nested class of that name or by typedef-ing a class that is defined at global scope to `DynamicLEDMode`. See the modified stock LED modes for examples.

Some of those plugins that export dynamic led modes require access to their particular
dynamic LED mode. By adding the macro `ACCESS_THIS_LED_MODE` to the plugin class definition,
additional data and methods (an integer `led_mode_id_) are synthesized, that enable the plugin class to gain access to their particular dynamic LED mode instance (as long as it is active).

The synthesized integer member `led_mode_id_` can be used to query if the currently active LED mode is the oned handled by the plugin class instance (note that there might be more than one plugin instance of the same class and thus also several dynamic LED modes, see e.g. the solid color LED mode).

A query in the plugin's event handler e.g. looks as follows.
```cpp
  if (::LEDControl.get_mode_index() != led_mode_id_)
    return EventHandlerResult::OK;
```

All stock LED modes have been adapted to export dynamic LED modes (if possible).
This does not apply to all of them as for some the transition would have provided no gain.
It would even have meant a deterioration of resource consumption for those few pre-existing stock LED mode plugins that hardly have no (static) data-members at all (like e.g. `LEDOff`).

To reduce the amount of compile unit and header interdependencies, the class `LEDMode` has been moved to a header/implementation file of its own.

The `LEDControl` class now does not have a static array anymore to store LED mode pointers.
Instead, it delegates the core LED mode handling to a newly introduced `LEDModeManager` class
that lives in internal namespace. The `LEDModeManager` class is there to restrict access
to LED modes but also to wrap up core LED mode handling. If this functionality would
have been added to class `LEDControl`, far too much of the internals of LED mode handling would have been exposed to users through header `LEDControl.h`.

The new internal header `array_like_storage.h` contains a template class that is used to generate
array-like storages. Here array-like means that the contained pieces of information
are stored contiguously in memory in the same way as they would be when defining
language intrinsic (C-style) arrays. This type of storage is especially useful to generate array-like data struktures
in PROGMEM at compile time based on a list of global objects or POD data. By casting the array-like storage's address
to the content's pointer type, an array-like indexed access is possible.

In this PR an array-like data structure is used to generate a PROGMEM
array of LED mode factories. Array-like data structures could also become useful in other places and for future applications.

The most complex part of the implementation of the new LED mode handling is wrapped up in
`LEDModeManager.h` and `LEDModeManager.cpp` to hide it from users' site.

There, recursive template classes are used to setup an array-like data structure of `LEDModeFactory` instances in PROGMEM. Each of the stored `LEDModeFactory`s are associated with one LED mode-plugin as specified in the sketch. The template mechanism filters out any other plugins unrelated to LED modes. `LEDModeFactory`s thereby handle both static and dynamic LED modes.

Class `LEDModeManager` provides access to the LED mode factories and LED modes in general. It exports methods to query the number of LED modes and to activate a LED mode by its mode-ID. Most of this is only available to `LEDControl` that represents the actual user interface.

When a dynamic LED mode is activated, a dedicated `LEDModeFactory` generates an instance of the dynamic LED mode class in the
LED mode buffer. This buffer is shared by all dynamic LED modes. Its size has been determined at compile time by examining all exported dynamic LED mode types and determining the maximum necessary amount of RAM to store any of those.

All LED mode handling related data structures are generated at compile time, based on
the list of plugins that are passed to `KALEIDOSCOPE_INIT_PLUGINS(...)`. This function macro invokes a new function macro `_INIT_LED_MODE_MANAGER` from `LEDModeManager.h` that handles the LED mode related stuff.

Signed-off-by: Florian Fleissner <florian.fleissner@inpartik.de>
pull/617/head
Florian Fleissner 6 years ago committed by Florian Fleissner
parent af1c2ba659
commit f54e11a9b2

@ -16,6 +16,7 @@
#pragma once #pragma once
#include <kaleidoscope/plugin/LEDMode.h>
#include <kaleidoscope/plugin/LEDControl.h> #include <kaleidoscope/plugin/LEDControl.h>
#include <kaleidoscope/plugin/LEDControl/LEDUtils.h> #include <kaleidoscope/plugin/LEDControl/LEDUtils.h>
#include <kaleidoscope/plugin/LEDControl/LED-Off.h> #include <kaleidoscope/plugin/LEDControl/LED-Off.h>

@ -49,6 +49,7 @@ extern HARDWARE_IMPLEMENTATION KeyboardHardware;
#include "kaleidoscope/layers.h" #include "kaleidoscope/layers.h"
#include "kaleidoscope/macro_map.h" #include "kaleidoscope/macro_map.h"
#include "kaleidoscope_internal/event_dispatch.h" #include "kaleidoscope_internal/event_dispatch.h"
#include "kaleidoscope_internal/LEDModeManager.h"
#include "kaleidoscope/macro_helpers.h" #include "kaleidoscope/macro_helpers.h"
#include "kaleidoscope/plugin.h" #include "kaleidoscope/plugin.h"

@ -38,23 +38,23 @@ void ColormapEffect::max_layers(uint8_t max_) {
map_base_ = ::LEDPaletteTheme.reserveThemes(max_layers_); map_base_ = ::LEDPaletteTheme.reserveThemes(max_layers_);
} }
void ColormapEffect::onActivate(void) { void ColormapEffect::TransientLEDMode::onActivate(void) {
if (!Kaleidoscope.has_leds) if (!Kaleidoscope.has_leds)
return; return;
top_layer_ = Layer.top(); parent_->top_layer_ = Layer.top();
if (top_layer_ <= max_layers_) if (parent_->top_layer_ <= parent_->max_layers_)
::LEDPaletteTheme.updateHandler(map_base_, top_layer_); ::LEDPaletteTheme.updateHandler(parent_->map_base_, parent_->top_layer_);
} }
void ColormapEffect::refreshAt(byte row, byte col) { void ColormapEffect::TransientLEDMode::refreshAt(byte row, byte col) {
if (top_layer_ <= max_layers_) if (parent_->top_layer_ <= parent_->max_layers_)
::LEDPaletteTheme.refreshAt(map_base_, top_layer_, row, col); ::LEDPaletteTheme.refreshAt(parent_->map_base_, parent_->top_layer_, row, col);
} }
EventHandlerResult ColormapEffect::onLayerChange() { EventHandlerResult ColormapEffect::onLayerChange() {
if (::LEDControl.get_mode() == this) if (::LEDControl.get_mode_index() == led_mode_id_)
onActivate(); ::LEDControl.get_mode<TransientLEDMode>()->onActivate();
return EventHandlerResult::OK; return EventHandlerResult::OK;
} }

@ -22,7 +22,9 @@
namespace kaleidoscope { namespace kaleidoscope {
namespace plugin { namespace plugin {
class ColormapEffect : public LEDMode { class ColormapEffect : public Plugin,
public LEDModeInterface,
public AccessTransientLEDMode {
public: public:
ColormapEffect(void) {} ColormapEffect(void) {}
@ -31,9 +33,28 @@ class ColormapEffect : public LEDMode {
EventHandlerResult onLayerChange(); EventHandlerResult onLayerChange();
EventHandlerResult onFocusEvent(const char *command); EventHandlerResult onFocusEvent(const char *command);
// This class' instance has dynamic lifetime
//
class TransientLEDMode : public LEDMode {
public:
// Please note that storing the parent ptr is only required
// for those LED modes that require access to
// members of their parent class. Most LED modes can do without.
//
TransientLEDMode(const ColormapEffect *parent) : parent_(parent) {}
protected: protected:
void onActivate(void) final;
void refreshAt(byte row, byte col) final; friend class ColormapEffect;
virtual void onActivate(void) final;
virtual void refreshAt(byte row, byte col) final;
private:
const ColormapEffect *parent_;
};
private: private:
static uint8_t top_layer_; static uint8_t top_layer_;

@ -30,7 +30,6 @@ bool FingerPainter::edit_mode_;
EventHandlerResult FingerPainter::onSetup() { EventHandlerResult FingerPainter::onSetup() {
color_base_ = ::LEDPaletteTheme.reserveThemes(1); color_base_ = ::LEDPaletteTheme.reserveThemes(1);
::LEDControl.mode_add(this);
return EventHandlerResult::OK; return EventHandlerResult::OK;
} }

@ -21,6 +21,11 @@
namespace kaleidoscope { namespace kaleidoscope {
namespace plugin { namespace plugin {
// This class is implemented as a persistent LED mode
// as there is no benefit of transforming it into a dynamic
// LED mode in terms of PROGMEM or RAM.
//
class FingerPainter : public LEDMode { class FingerPainter : public LEDMode {
public: public:
FingerPainter(void) {} FingerPainter(void) {}

@ -56,10 +56,18 @@ void HardwareTestMode::testLeds(void) {
setLeds(blue); setLeds(blue);
setLeds(green); setLeds(green);
setLeds(red); setLeds(red);
// This works under the assumption that LEDRainbowEffect
// has been registered with KALEIDOSCOPE_INIT_PLUGINS in
// the sketch. Otherwise LEDRainbowEffect would not be
// known to LEDControl.
//
::LEDControl.activate(&::LEDRainbowEffect);
// rainbow for 10 seconds // rainbow for 10 seconds
::LEDRainbowEffect.update_delay(5); ::LEDRainbowEffect.update_delay(5);
for (auto i = 0; i < 300; i++) { for (auto i = 0; i < 300; i++) {
::LEDRainbowEffect.update(); ::LEDControl.update();
::LEDControl.syncLeds(); ::LEDControl.syncLeds();
} }
} }

@ -17,16 +17,11 @@
#include <Kaleidoscope.h> #include <Kaleidoscope.h>
#include <Kaleidoscope-Heatmap.h> #include <Kaleidoscope-Heatmap.h>
#include <Kaleidoscope-LEDControl.h>
namespace kaleidoscope { namespace kaleidoscope {
namespace plugin { namespace plugin {
// store the number of times each key has been strock
uint16_t Heatmap::heatmap_[ROWS][COLS];
// max of heatmap_ (we divide by it so we start at 1)
uint16_t Heatmap::highest_ = 1;
// next heatmap computation time
uint32_t Heatmap::next_heatmap_comp_time_ = 0;
// in the cRGB struct the order is blue, green, red (It should be called cBGR…) // in the cRGB struct the order is blue, green, red (It should be called cBGR…)
// default heat_colors black green yellow red // default heat_colors black green yellow red
static const cRGB heat_colors_default_[] PROGMEM = {{0, 0, 0}, {25, 255, 25}, {25, 255, 255}, {25, 25, 255}}; static const cRGB heat_colors_default_[] PROGMEM = {{0, 0, 0}, {25, 255, 25}, {25, 255, 255}, {25, 25, 255}};
@ -37,7 +32,17 @@ uint8_t Heatmap::heat_colors_length = 4;
// number of millisecond to wait between each heatmap computation // number of millisecond to wait between each heatmap computation
uint16_t Heatmap::update_delay = 1000; uint16_t Heatmap::update_delay = 1000;
cRGB Heatmap::computeColor(float v) { Heatmap::TransientLEDMode::TransientLEDMode(const Heatmap *parent)
: parent_(parent),
// store the number of times each key has been strock
heatmap_{},
// max of heatmap_ (we divide by it so we start at 1)
highest_(1),
// next heatmap computation time
next_heatmap_comp_time_(0)
{}
cRGB Heatmap::TransientLEDMode::computeColor(float v) {
// compute the color corresponding to a value between 0 and 1 // compute the color corresponding to a value between 0 and 1
/* /*
@ -95,7 +100,7 @@ cRGB Heatmap::computeColor(float v) {
return {b, g, r}; return {b, g, r};
} }
void Heatmap::shiftStats(void) { void Heatmap::TransientLEDMode::shiftStats(void) {
// this method is called when: // this method is called when:
// 1. a value in heatmap_ reach INT8_MAX // 1. a value in heatmap_ reach INT8_MAX
// 2. highest_ reach heat_colors_length*512 (see Heatmap::loopHook) // 2. highest_ reach heat_colors_length*512 (see Heatmap::loopHook)
@ -112,6 +117,15 @@ void Heatmap::shiftStats(void) {
} }
void Heatmap::resetMap(void) { void Heatmap::resetMap(void) {
if (::LEDControl.get_mode_index() != led_mode_id_)
return;
::LEDControl.get_mode<TransientLEDMode>()->resetMap();
}
void Heatmap::TransientLEDMode::resetMap() {
// this method can be used as a way to work around an existing bug with a single key // this method can be used as a way to work around an existing bug with a single key
// getting special attention or if the user just wants a button to reset the map // getting special attention or if the user just wants a button to reset the map
for (uint8_t r = 0; r < ROWS; r++) { for (uint8_t r = 0; r < ROWS; r++) {
@ -138,6 +152,15 @@ EventHandlerResult Heatmap::onKeyswitchEvent(Key &mapped_key, byte row, byte col
if (!keyToggledOn(key_state)) if (!keyToggledOn(key_state))
return EventHandlerResult::OK; return EventHandlerResult::OK;
// if the LED mode is not current, skip it
if (::LEDControl.get_mode_index() != led_mode_id_)
return EventHandlerResult::OK;
return ::LEDControl.get_mode<TransientLEDMode>()
->onKeyswitchEvent(mapped_key, row, col, key_state);
}
EventHandlerResult Heatmap::TransientLEDMode::onKeyswitchEvent(Key &mapped_key, byte row, byte col, uint8_t key_state) {
// increment the heatmap_ value related to the key // increment the heatmap_ value related to the key
heatmap_[row][col]++; heatmap_[row][col]++;
@ -160,6 +183,13 @@ EventHandlerResult Heatmap::beforeEachCycle() {
if (!Kaleidoscope.has_leds) if (!Kaleidoscope.has_leds)
return EventHandlerResult::OK; return EventHandlerResult::OK;
if (::LEDControl.get_mode_index() != led_mode_id_)
return EventHandlerResult::OK;
return ::LEDControl.get_mode<TransientLEDMode>()->beforeEachCycle();
}
EventHandlerResult Heatmap::TransientLEDMode::beforeEachCycle() {
// this methode is called frequently by Kaleidoscope // this methode is called frequently by Kaleidoscope
// even if the module isn't activated // even if the module isn't activated
@ -175,7 +205,7 @@ EventHandlerResult Heatmap::beforeEachCycle() {
return EventHandlerResult::OK; return EventHandlerResult::OK;
} }
void Heatmap::update(void) { void Heatmap::TransientLEDMode::update(void) {
if (!Kaleidoscope.has_leds) if (!Kaleidoscope.has_leds)
return; return;

@ -22,7 +22,9 @@
namespace kaleidoscope { namespace kaleidoscope {
namespace plugin { namespace plugin {
class Heatmap : public LEDMode { class Heatmap : public Plugin,
public LEDModeInterface,
public AccessTransientLEDMode {
public: public:
Heatmap(void) {} Heatmap(void) {}
@ -34,16 +36,38 @@ class Heatmap : public LEDMode {
EventHandlerResult onKeyswitchEvent(Key &mapped_key, byte row, byte col, uint8_t key_state); EventHandlerResult onKeyswitchEvent(Key &mapped_key, byte row, byte col, uint8_t key_state);
EventHandlerResult beforeEachCycle(); EventHandlerResult beforeEachCycle();
// This class' instance has dynamic lifetime
//
class TransientLEDMode : public LEDMode {
public:
// Please note that storing the parent ptr is only required
// for those LED modes that require access to
// members of their parent class. Most LED modes can do without.
//
TransientLEDMode(const Heatmap *parent);
void resetMap();
EventHandlerResult onKeyswitchEvent(Key &mapped_key, byte row, byte col, uint8_t key_state);
EventHandlerResult beforeEachCycle();
protected: protected:
void update(void) final;
virtual void update() final;
private: private:
static uint16_t heatmap_[ROWS][COLS];
static uint16_t highest_;
static uint32_t next_heatmap_comp_time_;
static void shiftStats(void); const Heatmap *parent_;
static cRGB computeColor(float v);
uint16_t heatmap_[ROWS][COLS];
uint16_t highest_;
uint32_t next_heatmap_comp_time_;
void shiftStats(void);
cRGB computeColor(float v);
friend class Heatmap;
};
}; };
} }
} }

@ -20,26 +20,31 @@
namespace kaleidoscope { namespace kaleidoscope {
namespace plugin { namespace plugin {
cRGB LEDActiveLayerColorEffect::active_color_;
const cRGB *LEDActiveLayerColorEffect::colormap_; const cRGB *LEDActiveLayerColorEffect::colormap_;
LEDActiveLayerColorEffect::TransientLEDMode::TransientLEDMode(
const LEDActiveLayerColorEffect *parent)
: parent_(parent),
active_color_{0, 0, 0}
{}
void LEDActiveLayerColorEffect::setColormap(const cRGB colormap[]) { void LEDActiveLayerColorEffect::setColormap(const cRGB colormap[]) {
colormap_ = colormap; colormap_ = colormap;
} }
cRGB LEDActiveLayerColorEffect::getActiveColor() { cRGB LEDActiveLayerColorEffect::TransientLEDMode::getActiveColor() {
cRGB color; cRGB color;
uint8_t top_layer = ::Layer.top(); uint8_t top_layer = ::Layer.top();
color.r = pgm_read_byte(&(colormap_[top_layer].r)); color.r = pgm_read_byte(&(parent_->colormap_[top_layer].r));
color.g = pgm_read_byte(&(colormap_[top_layer].g)); color.g = pgm_read_byte(&(parent_->colormap_[top_layer].g));
color.b = pgm_read_byte(&(colormap_[top_layer].b)); color.b = pgm_read_byte(&(parent_->colormap_[top_layer].b));
return color; return color;
} }
void LEDActiveLayerColorEffect::onActivate(void) { void LEDActiveLayerColorEffect::TransientLEDMode::onActivate(void) {
if (!Kaleidoscope.has_leds) if (!Kaleidoscope.has_leds)
return; return;
@ -47,13 +52,14 @@ void LEDActiveLayerColorEffect::onActivate(void) {
::LEDControl.set_all_leds_to(active_color_); ::LEDControl.set_all_leds_to(active_color_);
} }
void LEDActiveLayerColorEffect::refreshAt(byte row, byte col) { void LEDActiveLayerColorEffect::TransientLEDMode::refreshAt(byte row, byte col) {
::LEDControl.setCrgbAt(row, col, active_color_); ::LEDControl.setCrgbAt(row, col, active_color_);
} }
EventHandlerResult LEDActiveLayerColorEffect::onLayerChange() { EventHandlerResult LEDActiveLayerColorEffect::onLayerChange() {
if (::LEDControl.get_mode() == this) if (::LEDControl.get_mode_index() == led_mode_id_)
onActivate(); ::LEDControl.get_mode<TransientLEDMode>()->onActivate();
return EventHandlerResult::OK; return EventHandlerResult::OK;
} }

@ -21,22 +21,45 @@
namespace kaleidoscope { namespace kaleidoscope {
namespace plugin { namespace plugin {
class LEDActiveLayerColorEffect : public LEDMode { class LEDActiveLayerColorEffect : public Plugin,
public LEDModeInterface,
public AccessTransientLEDMode {
public: public:
LEDActiveLayerColorEffect(void) {} LEDActiveLayerColorEffect(void) {}
EventHandlerResult onLayerChange(); EventHandlerResult onLayerChange();
void setColormap(const cRGB colormap[]); void setColormap(const cRGB colormap[]);
// This class' instance has dynamic lifetime
//
class TransientLEDMode : public LEDMode {
public:
// Please note that storing the parent ptr is only required
// for those LED modes that require access to
// members of their parent class. Most LED modes can do without.
//
TransientLEDMode(const LEDActiveLayerColorEffect *parent);
protected: protected:
void onActivate(void) final;
void refreshAt(byte row, byte col) final; virtual void onActivate(void) final;
virtual void refreshAt(byte row, byte col) final;
private: private:
static const cRGB *colormap_;
static cRGB active_color_;
static cRGB getActiveColor(); const LEDActiveLayerColorEffect *parent_;
cRGB active_color_;
cRGB getActiveColor();
friend class LEDActiveLayerColorEffect;
};
private:
static const cRGB *colormap_;
}; };
} }
} }

@ -21,10 +21,15 @@ namespace kaleidoscope {
namespace plugin { namespace plugin {
uint16_t AlphaSquareEffect::length = 1000; uint16_t AlphaSquareEffect::length = 1000;
uint32_t AlphaSquareEffect::end_time_left_, AlphaSquareEffect::end_time_right_;
Key AlphaSquareEffect::last_key_left_, AlphaSquareEffect::last_key_right_;
void AlphaSquareEffect::update(void) { AlphaSquareEffect::TransientLEDMode::TransientLEDMode(AlphaSquareEffect */*parent*/)
: end_time_left_(0),
end_time_right_(0),
last_key_left_(Key{}),
last_key_right_(Key{})
{}
void AlphaSquareEffect::TransientLEDMode::update(void) {
if (!Kaleidoscope.has_leds) if (!Kaleidoscope.has_leds)
return; return;
@ -38,7 +43,7 @@ void AlphaSquareEffect::update(void) {
} }
} }
void AlphaSquareEffect::refreshAt(byte row, byte col) { void AlphaSquareEffect::TransientLEDMode::refreshAt(byte row, byte col) {
bool timed_out; bool timed_out;
uint8_t display_col = 2; uint8_t display_col = 2;
Key key = last_key_left_; Key key = last_key_left_;
@ -59,7 +64,7 @@ EventHandlerResult AlphaSquareEffect::onKeyswitchEvent(Key &mappedKey, byte row,
if (!Kaleidoscope.has_leds) if (!Kaleidoscope.has_leds)
return EventHandlerResult::OK; return EventHandlerResult::OK;
if (::LEDControl.get_mode() != &::AlphaSquareEffect) if (::LEDControl.get_mode_index() != led_mode_id_)
return EventHandlerResult::OK; return EventHandlerResult::OK;
if (keyState & INJECTED) if (keyState & INJECTED)
@ -72,15 +77,17 @@ EventHandlerResult AlphaSquareEffect::onKeyswitchEvent(Key &mappedKey, byte row,
return EventHandlerResult::OK; return EventHandlerResult::OK;
uint8_t display_col = 2; uint8_t display_col = 2;
Key prev_key = last_key_left_; auto this_led_mode = ::LEDControl.get_mode<TransientLEDMode>();
Key prev_key = this_led_mode->last_key_left_;
if (col < COLS / 2) { if (col < COLS / 2) {
last_key_left_ = mappedKey; this_led_mode->last_key_left_ = mappedKey;
end_time_left_ = millis() + length; this_led_mode->end_time_left_ = millis() + length;
} else { } else {
prev_key = last_key_right_; prev_key = this_led_mode->last_key_right_;
last_key_right_ = mappedKey; this_led_mode->last_key_right_ = mappedKey;
end_time_right_ = millis() + length; this_led_mode->end_time_right_ = millis() + length;
display_col = 10; display_col = 10;
} }

@ -22,7 +22,9 @@
namespace kaleidoscope { namespace kaleidoscope {
namespace plugin { namespace plugin {
class AlphaSquareEffect : public LEDMode { class AlphaSquareEffect : public Plugin,
public LEDModeInterface,
public AccessTransientLEDMode {
public: public:
AlphaSquareEffect(void) {} AlphaSquareEffect(void) {}
@ -30,13 +32,22 @@ class AlphaSquareEffect : public LEDMode {
EventHandlerResult onKeyswitchEvent(Key &mappedKey, byte row, byte col, uint8_t keyState); EventHandlerResult onKeyswitchEvent(Key &mappedKey, byte row, byte col, uint8_t keyState);
// This class' instance has dynamic lifetime
//
class TransientLEDMode : public LEDMode {
public:
TransientLEDMode(AlphaSquareEffect *parent);
protected: protected:
void update(void) final; void update(void) final;
void refreshAt(byte row, byte col) final; void refreshAt(byte row, byte col) final;
private: private:
static uint32_t end_time_left_, end_time_right_; uint32_t end_time_left_, end_time_right_;
static Key last_key_left_, last_key_right_; Key last_key_left_, last_key_right_;
friend class AlphaSquareEffect;
};
}; };
} }
} }

@ -16,21 +16,22 @@
*/ */
#include <Kaleidoscope-LED-Stalker.h> #include <Kaleidoscope-LED-Stalker.h>
#include <Kaleidoscope-LEDControl.h>
namespace kaleidoscope { namespace kaleidoscope {
namespace plugin { namespace plugin {
uint8_t StalkerEffect::map_[ROWS][COLS];
StalkerEffect::ColorComputer *StalkerEffect::variant; StalkerEffect::ColorComputer *StalkerEffect::variant;
uint16_t StalkerEffect::step_length = 50; uint16_t StalkerEffect::step_length = 50;
uint16_t StalkerEffect::step_start_time_;
cRGB StalkerEffect::inactive_color = (cRGB) { cRGB StalkerEffect::inactive_color = (cRGB) {
0, 0, 0 0, 0, 0
}; };
void StalkerEffect::onActivate(void) { StalkerEffect::TransientLEDMode::TransientLEDMode(const StalkerEffect *parent)
memset(map_, 0, sizeof(map_)); : parent_(parent),
} step_start_time_(0),
map_{}
{}
EventHandlerResult StalkerEffect::onKeyswitchEvent(Key &mapped_key, byte row, byte col, uint8_t keyState) { EventHandlerResult StalkerEffect::onKeyswitchEvent(Key &mapped_key, byte row, byte col, uint8_t keyState) {
if (!Kaleidoscope.has_leds) if (!Kaleidoscope.has_leds)
@ -39,35 +40,38 @@ EventHandlerResult StalkerEffect::onKeyswitchEvent(Key &mapped_key, byte row, by
if (row >= ROWS || col >= COLS) if (row >= ROWS || col >= COLS)
return EventHandlerResult::OK; return EventHandlerResult::OK;
if (::LEDControl.get_mode_index() != led_mode_id_)
return EventHandlerResult::OK;
if (keyIsPressed(keyState)) { if (keyIsPressed(keyState)) {
map_[row][col] = 0xff; ::LEDControl.get_mode<TransientLEDMode>()->map_[row][col] = 0xff;
} }
return EventHandlerResult::OK; return EventHandlerResult::OK;
} }
void StalkerEffect::update(void) { void StalkerEffect::TransientLEDMode::update(void) {
if (!Kaleidoscope.has_leds) if (!Kaleidoscope.has_leds)
return; return;
if (!variant) if (!parent_->variant)
return; return;
uint16_t elapsed = Kaleidoscope.millisAtCycleStart() - step_start_time_; uint16_t elapsed = Kaleidoscope.millisAtCycleStart() - step_start_time_;
if (elapsed < step_length) if (elapsed < parent_->step_length)
return; return;
for (byte r = 0; r < ROWS; r++) { for (byte r = 0; r < ROWS; r++) {
for (byte c = 0; c < COLS; c++) { for (byte c = 0; c < COLS; c++) {
uint8_t step = map_[r][c]; uint8_t step = map_[r][c];
if (step) { if (step) {
::LEDControl.setCrgbAt(r, c, variant->compute(&step)); ::LEDControl.setCrgbAt(r, c, parent_->variant->compute(&step));
} }
map_[r][c] = step; map_[r][c] = step;
if (!map_[r][c]) if (!map_[r][c])
::LEDControl.setCrgbAt(r, c, inactive_color); ::LEDControl.setCrgbAt(r, c, parent_->inactive_color);
} }
} }

@ -24,7 +24,9 @@
namespace kaleidoscope { namespace kaleidoscope {
namespace plugin { namespace plugin {
class StalkerEffect : public LEDMode { class StalkerEffect : public Plugin,
public LEDModeInterface,
public AccessTransientLEDMode {
public: public:
class ColorComputer { class ColorComputer {
public: public:
@ -39,13 +41,30 @@ class StalkerEffect : public LEDMode {
EventHandlerResult onKeyswitchEvent(Key &mapped_key, byte row, byte col, uint8_t keyState); EventHandlerResult onKeyswitchEvent(Key &mapped_key, byte row, byte col, uint8_t keyState);
// This class' instance has dynamic lifetime
//
class TransientLEDMode : public LEDMode {
public:
// Please note that storing the parent ptr is only required
// for those LED modes that require access to
// members of their parent class. Most LED modes can do without.
//
TransientLEDMode(const StalkerEffect *parent);
protected: protected:
void onActivate(void) final;
void update(void) final; virtual void update() final;
private: private:
static uint16_t step_start_time_;
static uint8_t map_[ROWS][COLS]; const StalkerEffect *parent_;
uint16_t step_start_time_;
uint8_t map_[ROWS][COLS];
friend class StalkerEffect;
};
}; };
namespace stalker { namespace stalker {

@ -27,52 +27,64 @@ namespace plugin {
#define MS_PER_FRAME 40 // 40 = 25 fps #define MS_PER_FRAME 40 // 40 = 25 fps
#define FRAMES_PER_DROP 120 // max time between raindrops during idle animation #define FRAMES_PER_DROP 120 // max time between raindrops during idle animation
int8_t WavepoolEffect::surface[2][WP_WID * WP_HGT];
uint8_t WavepoolEffect::page = 0;
uint8_t WavepoolEffect::frames_since_event = 0;
uint16_t WavepoolEffect::idle_timeout = 5000; // 5 seconds uint16_t WavepoolEffect::idle_timeout = 5000; // 5 seconds
int16_t WavepoolEffect::ripple_hue = WavepoolEffect::rainbow_hue; // automatic hue int16_t WavepoolEffect::ripple_hue = WavepoolEffect::rainbow_hue; // automatic hue
// map native keyboard coordinates (16x4) into geometric space (14x5) // map native keyboard coordinates (16x4) into geometric space (14x5)
PROGMEM const uint8_t WavepoolEffect::rc2pos[ROWS * COLS] = { PROGMEM const uint8_t WavepoolEffect::TransientLEDMode::rc2pos[ROWS * COLS] = {
0, 1, 2, 3, 4, 5, 6, 59, 66, 7, 8, 9, 10, 11, 12, 13, 0, 1, 2, 3, 4, 5, 6, 59, 66, 7, 8, 9, 10, 11, 12, 13,
14, 15, 16, 17, 18, 19, 34, 60, 65, 35, 22, 23, 24, 25, 26, 27, 14, 15, 16, 17, 18, 19, 34, 60, 65, 35, 22, 23, 24, 25, 26, 27,
28, 29, 30, 31, 32, 33, 48, 61, 64, 49, 36, 37, 38, 39, 40, 41, 28, 29, 30, 31, 32, 33, 48, 61, 64, 49, 36, 37, 38, 39, 40, 41,
42, 43, 44, 45, 46, 47, 58, 62, 63, 67, 50, 51, 52, 53, 54, 55, 42, 43, 44, 45, 46, 47, 58, 62, 63, 67, 50, 51, 52, 53, 54, 55,
}; };
WavepoolEffect::TransientLEDMode::TransientLEDMode(const WavepoolEffect *parent)
: parent_(parent),
frames_since_event_(0),
surface_{},
page_(0)
{}
EventHandlerResult WavepoolEffect::onKeyswitchEvent(Key &mapped_key, byte row, byte col, uint8_t key_state) { EventHandlerResult WavepoolEffect::onKeyswitchEvent(Key &mapped_key, byte row, byte col, uint8_t key_state) {
if (row >= ROWS || col >= COLS) if (row >= ROWS || col >= COLS)
return EventHandlerResult::OK; return EventHandlerResult::OK;
if (::LEDControl.get_mode_index() != led_mode_id_)
return EventHandlerResult::OK;
return ::LEDControl.get_mode<TransientLEDMode>()
->onKeyswitchEvent(mapped_key, row, col, key_state);
}
EventHandlerResult WavepoolEffect::TransientLEDMode::onKeyswitchEvent(Key &mapped_key, byte row, byte col, uint8_t key_state) {
if (keyIsPressed(key_state)) { if (keyIsPressed(key_state)) {
uint8_t offset = (row * COLS) + col; uint8_t offset = (row * COLS) + col;
surface[page][pgm_read_byte(rc2pos + offset)] = 0x7f; surface_[page_][pgm_read_byte(rc2pos + offset)] = 0x7f;
frames_since_event = 0; frames_since_event_ = 0;
} }
return EventHandlerResult::OK; return EventHandlerResult::OK;
} }
void WavepoolEffect::raindrop(uint8_t x, uint8_t y, int8_t *page) { void WavepoolEffect::TransientLEDMode::raindrop(uint8_t x, uint8_t y, int8_t *page_) {
uint8_t rainspot = (y * WP_WID) + x; uint8_t rainspot = (y * WP_WID) + x;
page[rainspot] = 0x7f; page_[rainspot] = 0x7f;
if (y > 0) page[rainspot - WP_WID] = 0x60; if (y > 0) page_[rainspot - WP_WID] = 0x60;
if (y < (WP_HGT - 1)) page[rainspot + WP_WID] = 0x60; if (y < (WP_HGT - 1)) page_[rainspot + WP_WID] = 0x60;
if (x > 0) page[rainspot - 1] = 0x60; if (x > 0) page_[rainspot - 1] = 0x60;
if (x < (WP_WID - 1)) page[rainspot + 1] = 0x60; if (x < (WP_WID - 1)) page_[rainspot + 1] = 0x60;
} }
// this is a lot smaller than the standard library's rand(), // this is a lot smaller than the standard library's rand(),
// and still looks random-ish // and still looks random-ish
uint8_t WavepoolEffect::wp_rand() { uint8_t WavepoolEffect::TransientLEDMode::wp_rand() {
static uint16_t offset = 0x400; static uint16_t offset = 0x400;
offset = ((offset + 1) & 0x4fff) | 0x400; offset = ((offset + 1) & 0x4fff) | 0x400;
return (millis() / MS_PER_FRAME) + pgm_read_byte(offset); return (millis() / MS_PER_FRAME) + pgm_read_byte(offset);
} }
void WavepoolEffect::update(void) { void WavepoolEffect::TransientLEDMode::update(void) {
// limit the frame rate; one frame every 64 ms // limit the frame rate; one frame every 64 ms
static uint8_t prev_time = 0; static uint8_t prev_time = 0;
@ -89,11 +101,11 @@ void WavepoolEffect::update(void) {
static uint8_t current_hue = 0; static uint8_t current_hue = 0;
current_hue ++; current_hue ++;
frames_since_event ++; frames_since_event_ ++;
// needs two pages of height map to do the calculations // needs two pages of height map to do the calculations
int8_t *newpg = &surface[page ^ 1][0]; int8_t *newpg = &surface_[page_ ^ 1][0];
int8_t *oldpg = &surface[page][0]; int8_t *oldpg = &surface_[page_][0];
// rain a bit while idle // rain a bit while idle
static uint8_t frames_till_next_drop = 0; static uint8_t frames_till_next_drop = 0;
@ -112,11 +124,11 @@ void WavepoolEffect::update(void) {
raindrop(prev_x, prev_y, oldpg); raindrop(prev_x, prev_y, oldpg);
prev_x = prev_y = -1; prev_x = prev_y = -1;
} }
if (frames_since_event if (frames_since_event_
>= (frames_till_next_drop >= (frames_till_next_drop
+ (idle_timeout / MS_PER_FRAME))) { + (idle_timeout / MS_PER_FRAME))) {
frames_till_next_drop = 4 + (wp_rand() % FRAMES_PER_DROP); frames_till_next_drop = 4 + (wp_rand() % FRAMES_PER_DROP);
frames_since_event = idle_timeout / MS_PER_FRAME; frames_since_event_ = idle_timeout / MS_PER_FRAME;
uint8_t x = wp_rand() % WP_WID; uint8_t x = wp_rand() % WP_WID;
uint8_t y = wp_rand() % WP_HGT; uint8_t y = wp_rand() % WP_HGT;
@ -195,7 +207,7 @@ void WavepoolEffect::update(void) {
uint8_t value = (intensity >= 128) ? 255 : intensity << 1; uint8_t value = (intensity >= 128) ? 255 : intensity << 1;
int16_t hue = ripple_hue; int16_t hue = ripple_hue;
if (ripple_hue == rainbow_hue) { if (ripple_hue == WavepoolEffect::rainbow_hue) {
// color starts white but gets dimmer and more saturated as it fades, // color starts white but gets dimmer and more saturated as it fades,
// with hue wobbling according to height map // with hue wobbling according to height map
hue = (current_hue + height + (height >> 1)) & 0xff; hue = (current_hue + height + (height >> 1)) & 0xff;
@ -209,10 +221,10 @@ void WavepoolEffect::update(void) {
#ifdef INTERPOLATE #ifdef INTERPOLATE
// swap pages every other frame // swap pages every other frame
if (!(now & 1)) page ^= 1; if (!(now & 1)) page_ ^= 1;
#else #else
// swap pages every frame // swap pages every frame
page ^= 1; page_ ^= 1;
#endif #endif
} }

@ -28,7 +28,9 @@
namespace kaleidoscope { namespace kaleidoscope {
namespace plugin { namespace plugin {
class WavepoolEffect : public LEDMode { class WavepoolEffect : public Plugin,
public LEDModeInterface,
public AccessTransientLEDMode {
public: public:
WavepoolEffect(void) {} WavepoolEffect(void) {}
@ -39,17 +41,38 @@ class WavepoolEffect : public LEDMode {
static int16_t ripple_hue; static int16_t ripple_hue;
static constexpr int16_t rainbow_hue = INT16_MAX; static constexpr int16_t rainbow_hue = INT16_MAX;
// This class' instance has dynamic lifetime
//
class TransientLEDMode : public LEDMode {
public:
// Please note that storing the parent ptr is only required
// for those LED modes that require access to
// members of their parent class. Most LED modes can do without.
//
TransientLEDMode(const WavepoolEffect *parent);
EventHandlerResult onKeyswitchEvent(Key &mapped_key, byte row, byte col, uint8_t key_state);
protected: protected:
void update(void) final;
virtual void update() final;
private: private:
static uint8_t frames_since_event;
static int8_t surface[2][WP_WID * WP_HGT]; const WavepoolEffect *parent_;
static uint8_t page;
uint8_t frames_since_event_;
int8_t surface_[2][WP_WID * WP_HGT];
uint8_t page_;
static PROGMEM const uint8_t rc2pos[ROWS * COLS]; static PROGMEM const uint8_t rc2pos[ROWS * COLS];
static void raindrop(uint8_t x, uint8_t y, int8_t *page); void raindrop(uint8_t x, uint8_t y, int8_t *page);
static uint8_t wp_rand(); uint8_t wp_rand();
friend class WavepoolEffect;
};
}; };
} }

@ -16,88 +16,72 @@
#include "Kaleidoscope-LEDControl.h" #include "Kaleidoscope-LEDControl.h"
#include "Kaleidoscope-FocusSerial.h" #include "Kaleidoscope-FocusSerial.h"
#include "kaleidoscope_internal/LEDModeManager.h"
using namespace kaleidoscope::internal;
namespace kaleidoscope { namespace kaleidoscope {
namespace plugin { namespace plugin {
LEDMode *LEDControl::modes[LED_MAX_MODES]; static constexpr uint8_t uninitialized_mode_id = 255;
uint8_t LEDControl::mode;
uint16_t LEDControl::syncDelay = 32; // 32ms interval => 30Hz refresh rate uint8_t LEDControl::mode_id = uninitialized_mode_id;
uint8_t LEDControl::num_led_modes_ = LEDModeManager::numLEDModes();
LEDMode *LEDControl::cur_led_mode_;
uint16_t LEDControl::syncDelay = 32;
uint16_t LEDControl::syncTimer; uint16_t LEDControl::syncTimer;
bool LEDControl::paused = false; bool LEDControl::paused = false;
void LEDMode::activate(void) {
::LEDControl.activate(this);
}
kaleidoscope::EventHandlerResult LEDMode::onSetup() {
::LEDControl.mode_add(this);
setup();
return EventHandlerResult::OK;
}
LEDControl::LEDControl(void) { LEDControl::LEDControl(void) {
mode = 0;
memset(modes, 0, LED_MAX_MODES * sizeof(modes[0]));
} }
void LEDControl::next_mode(void) { void LEDControl::next_mode(void) {
mode++; mode_id++;
if (mode >= LED_MAX_MODES || !modes[mode]) { if (mode_id >= num_led_modes_) {
return set_mode(0); return set_mode(0);
} }
return set_mode(mode); return set_mode(mode_id);
} }
void LEDControl::prev_mode(void) { void LEDControl::prev_mode(void) {
if (mode == 0) { if (mode_id == 0) {
// wrap around // wrap around
mode = LED_MAX_MODES - 1; mode_id = num_led_modes_ - 1;
// then count down until reaching a valid mode
while (mode > 0 && !modes[mode]) mode--;
} else { } else {
mode--; mode_id--;
} }
return set_mode(mode); return set_mode(mode_id);
} }
void void
LEDControl::set_mode(uint8_t mode_) { LEDControl::set_mode(uint8_t mode_) {
if (mode_ >= LED_MAX_MODES) if (mode_ >= num_led_modes_)
return; return;
mode = mode_; mode_id = mode_;
refreshAll();
}
uint8_t LEDControl::get_mode_index(void) { // Cache the LED mode
return mode; //
} cur_led_mode_ = LEDModeManager::getLEDMode(mode_id);
LEDMode *LEDControl::get_mode(void) { refreshAll();
return modes[mode];
} }
void LEDControl::activate(LEDMode *mode) { void LEDControl::activate(LEDModeInterface *plugin) {
for (uint8_t i = 0; i < LED_MAX_MODES; i++) { for (uint8_t i = 0; i < num_led_modes_; i++) {
if (modes[i] == mode)
return set_mode(i); led_mode_management::LEDModeFactory fac;
}
}
int8_t LEDControl::mode_add(LEDMode *mode) { LEDModeManager::retreiveLEDModeFactoryFromPROGMEM(i, fac);
for (int i = 0; i < LED_MAX_MODES; i++) {
if (modes[i])
continue;
modes[i] = mode; if (fac.isAssociatedWithPlugin(plugin)) {
return i; set_mode(i);
return;
}
} }
return -1;
} }
void LEDControl::set_all_leds_to(uint8_t r, uint8_t g, uint8_t b) { void LEDControl::set_all_leds_to(uint8_t r, uint8_t g, uint8_t b) {
@ -139,13 +123,14 @@ void LEDControl::syncLeds(void) {
kaleidoscope::EventHandlerResult LEDControl::onSetup() { kaleidoscope::EventHandlerResult LEDControl::onSetup() {
set_all_leds_to({0, 0, 0}); set_all_leds_to({0, 0, 0});
for (uint8_t i = 0; i < LED_MAX_MODES; i++) { LEDModeManager::setupPersistentLEDModes();
if (modes[i])
(modes[i]->setup)();
}
syncTimer = millis() + syncDelay; syncTimer = millis() + syncDelay;
if (mode_id == uninitialized_mode_id) {
set_mode(0);
}
return EventHandlerResult::OK; return EventHandlerResult::OK;
} }
@ -249,10 +234,10 @@ EventHandlerResult FocusLEDCommand::onFocusEvent(const char *command) {
} else if (peek == 'p') { } else if (peek == 'p') {
::LEDControl.prev_mode(); ::LEDControl.prev_mode();
} else { } else {
uint8_t mode; uint8_t mode_id;
::Focus.read(mode); ::Focus.read(mode_id);
::LEDControl.set_mode(mode); ::LEDControl.set_mode(mode_id);
} }
break; break;
} }

@ -18,88 +18,20 @@
#include <Kaleidoscope.h> #include <Kaleidoscope.h>
#define LED_MAX_MODES 24
#define LED_TOGGLE B00000001 // Synthetic, internal #define LED_TOGGLE B00000001 // Synthetic, internal
#define Key_LEDEffectNext Key(0, KEY_FLAGS | SYNTHETIC | IS_INTERNAL | LED_TOGGLE) #define Key_LEDEffectNext Key(0, KEY_FLAGS | SYNTHETIC | IS_INTERNAL | LED_TOGGLE)
#define Key_LEDEffectPrevious Key(1, KEY_FLAGS | SYNTHETIC | IS_INTERNAL | LED_TOGGLE) #define Key_LEDEffectPrevious Key(1, KEY_FLAGS | SYNTHETIC | IS_INTERNAL | LED_TOGGLE)
#define _DEPRECATED_MESSAGE_LED_CONTROL_MODE_ADD \
"LEDControl::mode_add(LEDMode *mode) is deprecated. LEDModes are now \n" \
"automatically registered. You can safely remove any calls to \n" \
"LEDControl::mode_add from your code."
namespace kaleidoscope { namespace kaleidoscope {
namespace plugin { namespace plugin {
/** Base class for LED modes.
*
* LED modes are a special kind of plugin, they are in charge of updating LED
* colors, setting a theme. While it is possible to have other plugins
* override the mode's colors, the LED mode is the baseline.
*
* Most of its functionality is called via @ref LEDControl, with only a few
* public methods.
*
* A LED mode **must** implement at least one of @ref onActivate or @ref
* update, and possibly @ref refreshAt too.
*/
class LEDMode : public kaleidoscope::Plugin {
friend class LEDControl;
protected:
// These methods should only be called by LEDControl.
/** One-time setup, called at keyboard boot.
*
* Any hooks that need registering, any one-time setup that needs to be
* performed, shall be done here. This is purely for preparation purposes, the
* LEDs should not be touched yet at this time.
*/
virtual void setup(void) {}
/** Function to call whenever the mode is activated.
*
* Like @ref setup, this method need not touch LEDs, @ref update will be
* called right after it. The purpose of this callback is to allow a plugin to
* do some preparation whenever it is activated, instead of only on boot, or
* always at each cycle.
*
* However, unlike @ref setup, this method can change LED colors, if so
* desired. Either to provide an initial state, or a static color set. In the
* latter case, consider implementing @ref refreshAt too, because other
* plugins may override some of the colors set at activation time, and @ref
* refreshAt can be used to restore them when needed.
*
* Before the callback runs, LEDs will be blanked.
*/
virtual void onActivate(void) {}
/** Update the LEDs once per cycle.
*
* Usually the brains of the plugin, which updates the LEDs each cycle. It is
* called after the matrix has been scanned, once per cycle.
*/
virtual void update(void) {}
/** Refresh the color of a given key. class LEDMode;
*
* If we have another plugin that overrides colors set by the active LED mode
* (either at @onActivate time, or via @ref update), if that plugin wants to
* restore whatever color the mode would set the key color to, this is the
* method it will call.
*
* @param row is the row coordinate of the key to refresh the color of.
* @param col is the column coordinate of the key to refresh the color of.
*/
virtual void refreshAt(byte row, byte col) {}
public:
/** Activate the current object as the LED mode.
*/
void activate(void);
/** Plugin initialization.
*
* Called via `Kaleidoscope.use()`, registers the LED mode, and does the
* necessary initialization steps. Calls @ref setup at the end.
*/
kaleidoscope::EventHandlerResult onSetup();
};
class LEDControl : public kaleidoscope::Plugin { class LEDControl : public kaleidoscope::Plugin {
public: public:
@ -112,19 +44,26 @@ class LEDControl : public kaleidoscope::Plugin {
if (!Kaleidoscope.has_leds) if (!Kaleidoscope.has_leds)
return; return;
if (modes[mode]) cur_led_mode_->update();
modes[mode]->update();
} }
static void refreshAt(byte row, byte col) { static void refreshAt(byte row, byte col) {
if (!Kaleidoscope.has_leds) if (!Kaleidoscope.has_leds)
return; return;
if (modes[mode]) cur_led_mode_->refreshAt(row, col);
modes[mode]->refreshAt(row, col); }
static void set_mode(uint8_t mode_id);
static uint8_t get_mode_index() {
return mode_id;
}
static LEDMode *get_mode() {
return cur_led_mode_;
} }
static void set_mode(uint8_t mode); template<typename LEDMode__>
static uint8_t get_mode_index(); static LEDMode__ *get_mode() {
static LEDMode *get_mode(); return static_cast<LEDMode__*>(cur_led_mode_);
}
static void refreshAll() { static void refreshAll() {
if (!Kaleidoscope.has_leds) if (!Kaleidoscope.has_leds)
return; return;
@ -133,11 +72,14 @@ class LEDControl : public kaleidoscope::Plugin {
return; return;
set_all_leds_to({0, 0, 0}); set_all_leds_to({0, 0, 0});
if (modes[mode])
modes[mode]->onActivate(); cur_led_mode_->onActivate();
} }
static int8_t mode_add(LEDMode *mode); DEPRECATED(LED_CONTROL_MODE_ADD)
static int8_t mode_add(LEDMode *mode) {
return 0;
}
static void setCrgbAt(int8_t i, cRGB crgb); static void setCrgbAt(int8_t i, cRGB crgb);
static void setCrgbAt(byte row, byte col, cRGB color); static void setCrgbAt(byte row, byte col, cRGB color);
@ -147,7 +89,11 @@ class LEDControl : public kaleidoscope::Plugin {
static void set_all_leds_to(uint8_t r, uint8_t g, uint8_t b); static void set_all_leds_to(uint8_t r, uint8_t g, uint8_t b);
static void set_all_leds_to(cRGB color); static void set_all_leds_to(cRGB color);
static void activate(LEDMode *mode); // We restict activate to LEDModeInterface to make sure that
// a compiler error is thrown when activate() is accidentally
// applied to a non-LED mode plugin.
//
static void activate(LEDModeInterface *plugin);
static uint16_t syncDelay; static uint16_t syncDelay;
static bool paused; static bool paused;
@ -158,8 +104,9 @@ class LEDControl : public kaleidoscope::Plugin {
private: private:
static uint16_t syncTimer; static uint16_t syncTimer;
static LEDMode *modes[LED_MAX_MODES]; static uint8_t mode_id;
static uint8_t mode; static uint8_t num_led_modes_;
static LEDMode *cur_led_mode_;
}; };
class FocusLEDCommand : public Plugin { class FocusLEDCommand : public Plugin {

@ -20,6 +20,10 @@
namespace kaleidoscope { namespace kaleidoscope {
namespace plugin { namespace plugin {
// This is still an old style persistent LEDMode as it does not have
// any members and thus there would not be any gain from making it dynamic.
//
class LEDOff : public LEDMode { class LEDOff : public LEDMode {
public: public:
LEDOff(void) { } LEDOff(void) { }

@ -20,7 +20,7 @@
namespace kaleidoscope { namespace kaleidoscope {
namespace plugin { namespace plugin {
void LEDBreatheEffect::update(void) { void LEDBreatheEffect::TransientLEDMode::update(void) {
if (!Kaleidoscope.has_leds) if (!Kaleidoscope.has_leds)
return; return;
@ -29,7 +29,7 @@ void LEDBreatheEffect::update(void) {
return; return;
last_update_ = now; last_update_ = now;
cRGB color = breath_compute(hue, saturation); cRGB color = breath_compute(parent_->hue, parent_->saturation);
::LEDControl.set_all_leds_to(color); ::LEDControl.set_all_leds_to(color);
} }
} }

@ -20,18 +20,34 @@
namespace kaleidoscope { namespace kaleidoscope {
namespace plugin { namespace plugin {
class LEDBreatheEffect : public LEDMode { class LEDBreatheEffect : public Plugin,
public LEDModeInterface {
public: public:
LEDBreatheEffect(void) {} LEDBreatheEffect(void) {}
uint8_t hue = 170; uint8_t hue = 170;
uint8_t saturation = 255; uint8_t saturation = 255;
// This class' instance has dynamic lifetime
//
class TransientLEDMode : public LEDMode {
public:
// Please note that storing the parent ptr is only required
// for those LED modes that require access to
// members of their parent class. Most LED modes can do without.
//
TransientLEDMode(const LEDBreatheEffect *parent)
: parent_(parent) {}
protected: protected:
void update(void) final; virtual void update(void) final;
private: private:
const LEDBreatheEffect *parent_;
uint16_t last_update_ = 0; uint16_t last_update_ = 0;
};
}; };
} }
} }

@ -19,19 +19,19 @@
namespace kaleidoscope { namespace kaleidoscope {
namespace plugin { namespace plugin {
void LEDChaseEffect::update(void) { void LEDChaseEffect::TransientLEDMode::update(void) {
if (!Kaleidoscope.has_leds) if (!Kaleidoscope.has_leds)
return; return;
uint16_t now = Kaleidoscope.millisAtCycleStart(); uint16_t now = Kaleidoscope.millisAtCycleStart();
if ((now - last_update_) < update_delay_) { if ((now - last_update_) < parent_->update_delay_) {
return; return;
} }
last_update_ = now; last_update_ = now;
// The red LED is at `pos_`; the blue one follows behind. `direction_` is // The red LED is at `pos_`; the blue one follows behind. `direction_` is
// either +1 or -1; `distance_` is the gap between them. // either +1 or -1; `distance_` is the gap between them.
int8_t pos2 = pos_ - (direction_ * distance_); int8_t pos2 = pos_ - (direction_ * parent_->distance_);
// First, we turn off the LEDs that were turned on in the previous update. // First, we turn off the LEDs that were turned on in the previous update.
// `pos_` is always in the valid range (0 <= pos_ < LED_COUNT), but after it // `pos_` is always in the valid range (0 <= pos_ < LED_COUNT), but after it

@ -20,7 +20,8 @@
namespace kaleidoscope { namespace kaleidoscope {
namespace plugin { namespace plugin {
class LEDChaseEffect : public LEDMode { class LEDChaseEffect : public Plugin,
public LEDModeInterface {
public: public:
LEDChaseEffect(void) {} LEDChaseEffect(void) {}
@ -37,15 +38,34 @@ class LEDChaseEffect : public LEDMode {
distance_ = value; distance_ = value;
} }
// This class' instance has dynamic lifetime
//
class TransientLEDMode : public LEDMode {
public:
// Please note that storing the parent ptr is only required
// for those LED modes that require access to
// members of their parent class. Most LED modes can do without.
//
TransientLEDMode(const LEDChaseEffect *parent)
: parent_(parent) {}
protected: protected:
void update(void) final;
virtual void update() final;
private: private:
const LEDChaseEffect *parent_;
int8_t pos_ = 0; int8_t pos_ = 0;
int8_t direction_ = 1; int8_t direction_ = 1;
uint16_t last_update_ = 0;
};
private:
uint8_t distance_ = 5; uint8_t distance_ = 5;
uint16_t update_delay_ = 150; uint16_t update_delay_ = 150;
uint16_t last_update_ = 0;
}; };
} }
} }

@ -19,18 +19,18 @@
namespace kaleidoscope { namespace kaleidoscope {
namespace plugin { namespace plugin {
void LEDRainbowEffect::update(void) { void LEDRainbowEffect::TransientLEDMode::update(void) {
if (!Kaleidoscope.has_leds) if (!Kaleidoscope.has_leds)
return; return;
uint16_t now = millis(); uint16_t now = millis();
if ((now - rainbow_last_update) < rainbow_update_delay) { if ((now - rainbow_last_update) < parent_->rainbow_update_delay) {
return; return;
} else { } else {
rainbow_last_update = now; rainbow_last_update = now;
} }
cRGB rainbow = hsvToRgb(rainbow_hue, rainbow_saturation, rainbow_value); cRGB rainbow = hsvToRgb(rainbow_hue, rainbow_saturation, parent_->rainbow_value);
rainbow_hue += rainbow_steps; rainbow_hue += rainbow_steps;
if (rainbow_hue >= 255) { if (rainbow_hue >= 255) {
@ -50,12 +50,12 @@ void LEDRainbowEffect::update_delay(byte delay) {
// --------- // ---------
void LEDRainbowWaveEffect::update(void) { void LEDRainbowWaveEffect::TransientLEDMode::update(void) {
if (!Kaleidoscope.has_leds) if (!Kaleidoscope.has_leds)
return; return;
uint16_t now = millis(); uint16_t now = millis();
if ((now - rainbow_last_update) < rainbow_update_delay) { if ((now - rainbow_last_update) < parent_->rainbow_update_delay) {
return; return;
} else { } else {
rainbow_last_update = now; rainbow_last_update = now;
@ -66,7 +66,7 @@ void LEDRainbowWaveEffect::update(void) {
if (key_hue >= 255) { if (key_hue >= 255) {
key_hue -= 255; key_hue -= 255;
} }
cRGB rainbow = hsvToRgb(key_hue, rainbow_saturation, rainbow_value); cRGB rainbow = hsvToRgb(key_hue, rainbow_saturation, parent_->rainbow_value);
::LEDControl.setCrgbAt(i, rainbow); ::LEDControl.setCrgbAt(i, rainbow);
} }
rainbow_hue += rainbow_wave_steps; rainbow_hue += rainbow_wave_steps;

@ -20,7 +20,8 @@
namespace kaleidoscope { namespace kaleidoscope {
namespace plugin { namespace plugin {
class LEDRainbowEffect : public LEDMode { class LEDRainbowEffect : public Plugin,
public LEDModeInterface {
public: public:
LEDRainbowEffect(void) {} LEDRainbowEffect(void) {}
@ -32,21 +33,40 @@ class LEDRainbowEffect : public LEDMode {
byte update_delay(void) { byte update_delay(void) {
return rainbow_update_delay; return rainbow_update_delay;
} }
void update(void) final;
// This class' instance has dynamic lifetime
//
class TransientLEDMode : public LEDMode {
public:
// Please note that storing the parent ptr is only required
// for those LED modes that require access to
// members of their parent class. Most LED modes can do without.
//
TransientLEDMode(const LEDRainbowEffect *parent)
: parent_(parent) {}
virtual void update() final;
private: private:
const LEDRainbowEffect *parent_;
uint16_t rainbow_hue = 0; // stores 0 to 614 uint16_t rainbow_hue = 0; // stores 0 to 614
uint8_t rainbow_steps = 1; // number of hues we skip in a 360 range per update uint8_t rainbow_steps = 1; // number of hues we skip in a 360 range per update
uint16_t rainbow_last_update = 0; uint16_t rainbow_last_update = 0;
uint16_t rainbow_update_delay = 40; // delay between updates (ms)
byte rainbow_saturation = 255; byte rainbow_saturation = 255;
};
private:
uint16_t rainbow_update_delay = 40; // delay between updates (ms)
byte rainbow_value = 50; byte rainbow_value = 50;
}; };
class LEDRainbowWaveEffect : public LEDMode { class LEDRainbowWaveEffect : public Plugin, public LEDModeInterface {
public: public:
LEDRainbowWaveEffect(void) {} LEDRainbowWaveEffect(void) {}
@ -58,16 +78,34 @@ class LEDRainbowWaveEffect : public LEDMode {
byte update_delay(void) { byte update_delay(void) {
return rainbow_update_delay; return rainbow_update_delay;
} }
void update(void) final;
// This class' instance has dynamic lifetime
//
class TransientLEDMode : public LEDMode {
public:
// Please note that storing the parent ptr is only required
// for those LED modes that require access to
// members of their parent class. Most LED modes can do without.
//
TransientLEDMode(const LEDRainbowWaveEffect *parent)
: parent_(parent) {}
virtual void update() final;
private: private:
const LEDRainbowWaveEffect *parent_;
uint16_t rainbow_hue = 0; // stores 0 to 614 uint16_t rainbow_hue = 0; // stores 0 to 614
uint8_t rainbow_wave_steps = 1; // number of hues we skip in a 360 range per update uint8_t rainbow_wave_steps = 1; // number of hues we skip in a 360 range per update
uint16_t rainbow_last_update = 0; uint16_t rainbow_last_update = 0;
uint16_t rainbow_update_delay = 40; // delay between updates (ms)
byte rainbow_saturation = 255; byte rainbow_saturation = 255;
};
uint16_t rainbow_update_delay = 40; // delay between updates (ms)
byte rainbow_value = 50; byte rainbow_value = 50;
}; };
} }

@ -18,18 +18,18 @@
namespace kaleidoscope { namespace kaleidoscope {
namespace plugin { namespace plugin {
LEDSolidColor::LEDSolidColor(uint8_t r, uint8_t g, uint8_t b) {
this->r = r;
this->g = g;
this->b = b;
}
void LEDSolidColor::onActivate(void) { void LEDSolidColor::TransientLEDMode::onActivate(void) {
::LEDControl.set_all_leds_to(r, g, b); ::LEDControl.set_all_leds_to(parent_->r_,
parent_->g_,
parent_->b_);
} }
void LEDSolidColor::refreshAt(byte row, byte col) { void LEDSolidColor::TransientLEDMode::refreshAt(byte row, byte col) {
::LEDControl.setCrgbAt(row, col, CRGB(r, g, b)); ::LEDControl.setCrgbAt(row, col,
CRGB(parent_->r_,
parent_->g_,
parent_->b_));
} }
} }

@ -20,16 +20,38 @@
namespace kaleidoscope { namespace kaleidoscope {
namespace plugin { namespace plugin {
class LEDSolidColor : public LEDMode { class LEDSolidColor : public Plugin,
public LEDModeInterface {
public: public:
LEDSolidColor(uint8_t r, uint8_t g, uint8_t b);
LEDSolidColor(uint8_t r, uint8_t g, uint8_t b)
: r_(r), g_(g), b_(b)
{}
// This class' instance has dynamic lifetime
//
class TransientLEDMode : public LEDMode {
public:
// Please note that storing the parent ptr is only required
// for those LED modes that require access to
// members of their parent class. Most LED modes can do without.
//
TransientLEDMode(const LEDSolidColor *parent)
: parent_(parent) {}
protected: protected:
void onActivate(void) final; virtual void onActivate(void) final;
void refreshAt(byte row, byte col) final; virtual void refreshAt(byte row, byte col) final;
private: private:
uint8_t r, g, b;
const LEDSolidColor *parent_;
};
private:
uint8_t r_, g_, b_;
}; };
} }
} }

@ -0,0 +1,30 @@
/* Kaleidoscope-LEDControl - LED control plugin for Kaleidoscope
* 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/plugin/LEDMode.h>
#include <kaleidoscope/plugin/LEDControl.h>
#include <kaleidoscope/event_handler_result.h>
namespace kaleidoscope {
namespace plugin {
void LEDModeInterface::activate() {
LEDControl::activate(this);
}
} // end namespace plugin
} // end namespace kaleidoscope

@ -0,0 +1,144 @@
/* Kaleidoscope-LEDControl - LED control plugin for Kaleidoscope
* 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/plugin.h"
namespace kaleidoscope {
namespace internal {
// Forward declaration
class LEDModeManager;
} // end namespace internal
namespace plugin {
class LEDModeInterface {
public:
void activate();
// This auxiliary class helps to generate a verbose error message
// in case that there is no TransientLEDMode typedef or nested
// class present in a derived class of LEDModeInterface.
//
struct _____LEDModeInterface_derived_class_is_missing_a_TransientLEDMode_typedef_or_nested_class_____ {};
typedef _____LEDModeInterface_derived_class_is_missing_a_TransientLEDMode_typedef_or_nested_class_____ NoLEDMode;
// By redefining TransientLEDMode, derived plugins export
// a LED mode that becomes part of the set of LED modes whose
// lifetime is handled dynamically.
//
typedef NoLEDMode DynamicLEDMode;
};
class AccessTransientLEDMode {
public:
// This method is called when a plugin's LED mode is activated.
// Derived plugins may reimplement it to store the id of their
// exported LED mode. A plugin can thus check
// whether their LED mode is currently active.
//
void registerLEDModeActivated(uint8_t led_mode_id) {
led_mode_id_ = led_mode_id;
}
protected:
uint8_t led_mode_id_ = 255; /* 255 means uninitialized */
};
/** Base class for LED modes.
*
* LED modes are a special kind of plugin, they are in charge of updating LED
* colors, setting a theme. While it is possible to have other plugins
* override the mode's colors, the LED mode is the baseline.
*
* Most of its functionality is called via @ref LEDControl, with only a few
* public methods.
*
* A LED mode **must** implement at least one of @ref onActivate or @ref
* update, and possibly @ref refreshAt too.
*/
class LEDMode : public kaleidoscope::Plugin,
public LEDModeInterface {
friend class LEDControl;
friend class kaleidoscope::internal::LEDModeManager;
protected:
// These methods should only be called by LEDControl.
/** One-time setup, called at keyboard boot.
*
* Any hooks that need registering, any one-time setup that needs to be
* performed, shall be done here. This is purely for preparation purposes, the
* LEDs should not be touched yet at this time.
*/
virtual void setup(void) {}
/** Function to call whenever the mode is activated.
*
* Like @ref setup, this method need not touch LEDs, @ref update will be
* called right after it. The purpose of this callback is to allow a plugin to
* do some preparation whenever it is activated, instead of only on boot, or
* always at each cycle.
*
* However, unlike @ref setup, this method can change LED colors, if so
* desired. Either to provide an initial state, or a static color set. In the
* latter case, consider implementing @ref refreshAt too, because other
* plugins may override some of the colors set at activation time, and @ref
* refreshAt can be used to restore them when needed.
*
* Before the callback runs, LEDs will be blanked.
*/
virtual void onActivate(void) {}
/** Update the LEDs once per cycle.
*
* Usually the brains of the plugin, which updates the LEDs each cycle. It is
* called after the matrix has been scanned, once per cycle.
*/
virtual void update(void) {}
/** Refresh the color of a given key.
*
* If we have another plugin that overrides colors set by the active LED mode
* (either at @onActivate time, or via @ref update), if that plugin wants to
* restore whatever color the mode would set the key color to, this is the
* method it will call.
*
* @param row is the row coordinate of the key to refresh the color of.
* @param col is the column coordinate of the key to refresh the color of.
*/
virtual void refreshAt(byte row, byte col) {}
public:
/** Plugin initialization.
*
* Called via `Kaleidoscope.use()`, registers the LED mode, and does the
* necessary initialization steps. Calls @ref setup at the end.
*/
kaleidoscope::EventHandlerResult onSetup() {
setup();
return EventHandlerResult::OK;
}
};
} // end namespace plugin
} // end namespace kaleidoscope

@ -19,6 +19,7 @@
#include "Kaleidoscope.h" #include "Kaleidoscope.h"
#include "Kaleidoscope-Model01-TestMode.h" #include "Kaleidoscope-Model01-TestMode.h"
#include "Kaleidoscope-LEDEffect-Rainbow.h" #include "Kaleidoscope-LEDEffect-Rainbow.h"
#include "kaleidoscope_internal/LEDModeManager.h"
namespace kaleidoscope { namespace kaleidoscope {
namespace plugin { namespace plugin {
@ -73,9 +74,17 @@ void TestMode::test_leds(void) {
set_leds(blue); set_leds(blue);
// make all the LEDs bright white (1.6A) // make all the LEDs bright white (1.6A)
set_leds(brightWhite); set_leds(brightWhite);
// This works under the assumption that LEDRainbowEffect
// has been registered with KALEIDOSCOPE_INIT_PLUGINS in
// the sketch. Otherwise LEDRainbowEffect would not be
// known to LEDControl.
//
::LEDControl.activate(&::LEDRainbowEffect);
// rainbow for 10 seconds // rainbow for 10 seconds
for (auto i = 0; i < 1000; i++) { for (auto i = 0; i < 1000; i++) {
::LEDRainbowEffect.update(); ::LEDControl.update();
::LEDControl.syncLeds(); ::LEDControl.syncLeds();
} }
waitForKeypress(); waitForKeypress();

@ -26,18 +26,18 @@ TriColor::TriColor(cRGB base_color, cRGB mod_color, cRGB esc_color) {
esc_color_ = esc_color; esc_color_ = esc_color;
} }
void TriColor::update(void) { void TriColor::TransientLEDMode::update(void) {
for (uint8_t r = 0; r < ROWS; r++) { for (uint8_t r = 0; r < ROWS; r++) {
for (uint8_t c = 0; c < COLS; c++) { for (uint8_t c = 0; c < COLS; c++) {
Key k = Layer.lookup(r, c); Key k = Layer.lookup(r, c);
// Special keys are always mod_color // Special keys are always mod_color
if (k.flags != 0) { if (k.flags != 0) {
::LEDControl.setCrgbAt(r, c, mod_color_); ::LEDControl.setCrgbAt(r, c, parent_->mod_color_);
continue; continue;
} }
cRGB color = mod_color_; cRGB color = parent_->mod_color_;
switch (k.keyCode) { switch (k.keyCode) {
case Key_A.keyCode ... Key_0.keyCode: case Key_A.keyCode ... Key_0.keyCode:
@ -46,10 +46,10 @@ void TriColor::update(void) {
case Key_Keypad1.keyCode ... Key_KeypadDot.keyCode: case Key_Keypad1.keyCode ... Key_KeypadDot.keyCode:
case Key_F1.keyCode ... Key_F4.keyCode: case Key_F1.keyCode ... Key_F4.keyCode:
case Key_F9.keyCode ... Key_F12.keyCode: case Key_F9.keyCode ... Key_F12.keyCode:
color = base_color_; color = parent_->base_color_;
break; break;
case Key_Escape.keyCode: case Key_Escape.keyCode:
color = esc_color_; color = parent_->esc_color_;
break; break;
} }

@ -22,13 +22,32 @@
namespace kaleidoscope { namespace kaleidoscope {
namespace plugin { namespace plugin {
class TriColor : public LEDMode { class TriColor : public Plugin,
public LEDModeInterface {
public: public:
TriColor(cRGB base_color, cRGB mod_color, cRGB esc_color); TriColor(cRGB base_color, cRGB mod_color, cRGB esc_color);
TriColor(cRGB base_color, cRGB mod_color) : TriColor(base_color, mod_color, mod_color) {} TriColor(cRGB base_color, cRGB mod_color) : TriColor(base_color, mod_color, mod_color) {}
// This class' instance has dynamic lifetime
//
class TransientLEDMode : public LEDMode {
public:
// Please note that storing the parent ptr is only required
// for those LED modes that require access to
// members of their parent class. Most LED modes can do without.
//
TransientLEDMode(const TriColor *parent)
: parent_(parent) {}
protected: protected:
void update(void) final;
virtual void update(void) final;
private:
const TriColor *parent_;
};
private: private:
cRGB base_color_; cRGB base_color_;

@ -0,0 +1,112 @@
/* Kaleidoscope - Firmware for computer input devices
* Copyright (C) 2013-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_internal/LEDModeManager.h"
#include "kaleidoscope/plugin/LEDMode.h"
namespace kaleidoscope {
namespace internal {
namespace {
// We want to export as little symbols as possible. That's why the
// internal state of the LED mode management lives in
// an anonymous namespace.
uint8_t cur_mode_id = 255; // We use 255 as a flag value that signals
// uninitialized. That's why we can only have
// LED mode ids in the range [0..254].
// A pointer to cache the current transient LED mode.
//
kaleidoscope::plugin::LEDMode *cur_led_mode = nullptr;
bool current_led_mode_dynamic = false;
}
kaleidoscope::plugin::LEDMode *LEDModeManager::getLEDMode(uint8_t mode_id) {
// If we end up here, the requested LED mode is a dynamic one.
// Check if the requested LED mode is already active
//
if (cur_mode_id == mode_id) {
return cur_led_mode;
}
// If there is already an active LED mode, its obviously the wrong one
// (see test above). To generate a new transient LED mode, we need
// to destroy the current one. To achieve this, we call its
// (possibly - see explanation below) virtual destuctor and let
// it take care of the cleanup.
//
// Please note that due to the fact that transient LED modes are
// allocated using placement new within a pre-existing static buffer,
// there is no need to free any memory after the destructor of the
// current LED mode was called. We can just reuse the buffer
// for the next LED mode instance.
//
// Please note: Currently, LEDMode has no virtual destructor.
// That's why the explicit destructor call below is a noop
// that is optimized out by the compiler. It is there
// to enable the possible future introduction of a virtual
// destructor for class LEDMode.
//
// A virtual destructor would enable LEDModes to do any types of
// cleanup on destruction. But as it's a virtual method,
// introducing such a destructor would create additional
// entries in vtables and mean higher PROGMEM consumption.
// If it will be introduced in future versions of Kaleidoscope
// uncomment the commented lines below.
//
if (current_led_mode_dynamic) {
cur_led_mode->~LEDMode();
}
// Store the current mode id to enable cache access (see above).
//
cur_mode_id = mode_id;
// Get a factory struct for the creation of the new LED mode.
//
led_mode_management::LEDModeFactory fac;
retreiveLEDModeFactoryFromPROGMEM(mode_id, fac);
// The factories for persistent LED modes serve to pass through the LED
// mode plugin's pointer in parent_plugin_.
//
if (fac.isPersistentLEDMode()) {
current_led_mode_dynamic = false;
cur_led_mode = fac.getPersistentLEDMode();
} else {
current_led_mode_dynamic = true;
// Generate a new led mode by calling the factory function
// (fac.generate_led_mode_).
//
// We store a pointer to the newly created LED mode to enable
// cached access (see check above).
//
cur_led_mode = fac.generateTransientLEDMode(led_mode_buffer_, mode_id);
}
return cur_led_mode;
}
} // end namespace internal
} // end namespace kaleidoscope

@ -0,0 +1,474 @@
/* Kaleidoscope - Firmware for computer input devices
* Copyright (C) 2013-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_internal/array_like_storage.h"
#include "kaleidoscope/plugin.h"
#include "kaleidoscope/plugin/LEDMode.h"
#include "kaleidoscope_internal/type_traits/has_method.h"
#include <stddef.h>
// To enable placement new, we need to supply a global operator
// function.
//
inline void* operator new (size_t, void* __p) throw() {
return __p;
}
namespace kaleidoscope {
namespace plugin {
// Forward declarations to avoid header inclusions
class LEDControl;
class LEDModeInterface;
} // end namespace plugin
namespace internal {
namespace led_mode_management {
template<bool T> struct Bool2Type {};
// Functions of this type allocate a new LED mode within a global
// buffer and give it access to it's parent plugin.
// This allows a transient LED mode to access member data of its parent
// plugin, which can help to reduce its RAM footprint.
// Some transient LED modes might ignore the parent pointer entirely, e.g. if
// they do not have a configurable internal state.
//
// We use a factory function instead of a polymorphic
// class to generate LED modes. By this means we can avoid the additional
// cost of vtables which safes some program memory.
// Conceptually, the factory function approach is similar to having
// a polymorphic class with only one method, that is accessed directly
// instead of the detour via the vtable.
//
typedef kaleidoscope::plugin::LEDMode*
(*LEDModeFactoryFunc)(void *raw_buffer,
kaleidoscope::plugin::LEDModeInterface *parent_plugin,
uint8_t mode_id
);
template<typename ParentPlugin__>
inline void registerLEDModeActivated(Bool2Type<true>,
ParentPlugin__ *parent_plugin,
uint8_t mode_id) {
parent_plugin->registerLEDModeActivated(mode_id);
}
template<typename ParentPlugin__>
inline void registerLEDModeActivated(Bool2Type<false>,
ParentPlugin__ *parent_plugin,
uint8_t mode_id) {
// Noop
}
DEFINE_HAS_METHOD_TRAITS(Plugin, registerLEDModeActivated, void, (uint8_t led_mode_id))
// A templated implementation of LEDModeFactoryFunc.
//
// Please note that the actual type of ParentPlugin__ is only
// known to the context where the function is invoked or in our case
// the function pointer to generateLEDMode is generated (at compile time).
//
// The caller can be completely agnostic of ParentPlugin__ an just pass
// a pointer to kaleidoscope::Plugin for argument parent_plugin.
//
// The function generateLEDMode knows the actual type of parent_plugin.
// Or, more precise, we can rely on the fact that we store a pointer to a plugin
// of appropriate type together with the generateLEDMode-pointer in
// an LEDModeFactory instance in PROGMEM. Thus, generateLEDMode will only
// be called with a parent_plugin of appropriate type.
//
template<typename ParentPlugin__>
static kaleidoscope::plugin::LEDMode *
generateLEDMode(void *raw_buffer,
kaleidoscope::plugin::LEDModeInterface *parent_plugin,
uint8_t mode_id
) {
// We know the type of the parent plugin via the template parameter
// ParentPlugin__, thus it is safe to cast to the actual type.
//
auto parent_plugin_actual = static_cast<ParentPlugin__*>(parent_plugin);
// Types defined by template parameters like ParentPlugin__ must
// be declared explicitly using the "typename" keyword.
// Because of this lengthy syntax, we use a typedef to
// shorten the constructor call below.
//
typedef typename ParentPlugin__::TransientLEDMode TLM;
// Generate a transient LED mode within the LED mode buffer.
//
auto led_mode_ptr
= new (raw_buffer) TLM{parent_plugin_actual};
constexpr bool accesses_transient_led_mode
= Plugin_HasMethod_registerLEDModeActivated<ParentPlugin__>::value;
// Register the newly created LED mode with its parent plugin.
// Please note that this call is optimized away by the compiler
// for all those plugins that do not reimplement registerLEDModeActivated.
//
registerLEDModeActivated(Bool2Type<accesses_transient_led_mode>(),
parent_plugin_actual, mode_id);
return led_mode_ptr;
}
// A data structure that contains all the information that is required
// to generate a transient LED mode. Instances of this class are stored
// in an array-like data structure in PROGMEM.
//
struct LEDModeFactory {
// Don't try to make this a class with a constructor. This
// will turn it into a non POD which cannot be used as
// return type of a constexpr.
bool isPersistentLEDMode() const {
return !generate_led_mode_;
}
kaleidoscope::plugin::LEDMode *getPersistentLEDMode() const {
return static_cast<kaleidoscope::plugin::LEDMode*>(parent_plugin_);
}
kaleidoscope::plugin::LEDMode *generateTransientLEDMode(
uint8_t *buffer, uint8_t mode_id) const {
return (*generate_led_mode_)(buffer, parent_plugin_, mode_id);
}
bool isAssociatedWithPlugin(kaleidoscope::plugin::LEDModeInterface *plugin) const {
return parent_plugin_ == plugin;
}
kaleidoscope::plugin::LEDModeInterface *parent_plugin_;
LEDModeFactoryFunc generate_led_mode_;
};
// The traits class remove_pointer is part of the C++ standard library
// but not present on Arduino.
//
template< class T > struct remove_pointer {
typedef T type;
};
template< class T > struct remove_pointer<T*> {
typedef T type;
};
enum PluginType {
PluginType_NoLEDMode,
PluginType_PersistentLEDMode,
PluginType_TransientLEDMode
};
// The following three functions enable determining the PluginType
// at compile time by examining a constexpr pointer to one of the
// global plugin instances (pointers to global variables/objects are
// constexpr).
//
static constexpr int ledModePluginType(kaleidoscope::plugin::LEDMode *) {
return PluginType_PersistentLEDMode;
}
static constexpr int ledModePluginType(kaleidoscope::plugin::LEDModeInterface *) {
return PluginType_TransientLEDMode;
}
// Invoked for all plugins that inherit neither from LEDMode
// nor from LEDModeInterface.
//
static constexpr bool ledModePluginType(void *) {
return PluginType_NoLEDMode;
}
// The following traits classes are used to distinguish between three cases
//
// 1) A plugin is unrelated to LEDModes (i.e. non of the other two applies).
//
// 2) A plugin is an old syle persistent LEDMode (plugin is derived from
// LED mode = persistent LED mode)
//
// 3) A plugin exports a LED mode with dynamic lifetime (= transient LED mode)
// Checks if a plugin is related to LED modes in any kind of ways.
// This traits check if for a plugin
// an entry in the plugin factories array needs to be reserved.
//
constexpr bool pluginControlsLEDMode(int led_mode_plugin_type) {
return led_mode_plugin_type != PluginType_NoLEDMode;
}
// The template GenerateLEDModeFactory generates LED mode factories
// in three different ways, depending on the type of a plugin (PluginType)
// Generates a dummy factory (discarded at compile time) for
// those plugins that are unrelated to LED modes. This template
// is selected for LEDModePluginType__ == NoLEDMode.
//
template<int LEDModePluginType__>
struct GenerateLEDModeFactory {
static constexpr LEDModeFactory apply(kaleidoscope::Plugin */* non LED mode plugin*/) {
return LEDModeFactory{nullptr, nullptr};
}
};
template<>
struct GenerateLEDModeFactory<PluginType_TransientLEDMode> {
// This version of apply must be templated as we must know the actual
// type of plugin as the instanciatioin of template function
// generateLEDMode depends upon it.
//
template<typename Plugin__>
static constexpr LEDModeFactory apply(Plugin__ *plugin) {
return LEDModeFactory{
plugin,
generateLEDMode<Plugin__> // pointer to template instantiation of
// generateLEDMode<...>
};
}
};
template<>
struct GenerateLEDModeFactory<PluginType_PersistentLEDMode> {
static constexpr LEDModeFactory apply(kaleidoscope::plugin::LEDModeInterface *plugin) {
// For persistent LED modes, we use the LED factory to simply store
// the plugin pointer (a persistent LED mode is itself the LEDMode).
// Thus, no factory function is required.
//
return LEDModeFactory{plugin, nullptr};
}
};
// The following template TransientLEDModeSize is used to determine
// the size of a transient LED mode. It is only active for dynamic
// LED modes. For all other plugins, it simply returns zero.
//
template<typename PluginPtr__, int LEDModePluginType__>
struct TransientLEDModeSize {
static constexpr size_t value = 0;
};
template<typename PluginPtr__>
struct TransientLEDModeSize<PluginPtr__, PluginType_TransientLEDMode> {
typedef typename remove_pointer<PluginPtr__>::type Plugin__;
static constexpr size_t value = sizeof(typename Plugin__::TransientLEDMode);
};
// This helper class serves to determine the maximum memory footprint
// of any transient LED mode in the sketch.
// It implements a template type recursion
// that examins all specified LED modes. For this calculation
// it is no problem that also the type NoLEDMode is considered
// in this check as it is an empty class (size == 1 byte) and
// thus does not affect the maximum size computation.
//
template<typename PluginPtr__, typename...MorePluginPtrs__>
struct TransientLEDModeMaxSize {
static constexpr size_t this_size
= TransientLEDModeSize<PluginPtr__, ledModePluginType(PluginPtr__())>::value;
static constexpr size_t nested_size = TransientLEDModeMaxSize<MorePluginPtrs__...>::value;
static constexpr size_t value = (this_size > nested_size) ? this_size : nested_size;
};
// Specialization to end recursion
//
template<typename PluginPtr__>
struct TransientLEDModeMaxSize<PluginPtr__> {
static constexpr size_t value = TransientLEDModeSize<PluginPtr__, ledModePluginType(PluginPtr__())>::value;
};
} // end namespace led_mode_management
class LEDModeManager {
public:
// Everything in this class private on purpose.
//
// Only the class LEDControl is supposed to gain
// access to any inventory of this class to ensure that
// the handling of transient LED modes is safely handled
// by a well defined central instance.
//
friend class kaleidoscope::plugin::LEDControl;
static uint8_t numLEDModes();
#if 0
// This method could be used in rare cases
// where a stack allocation of a LED mode may not be avoided.
//
template<typename ParentPlugin__>
static auto
generateLEDModeTemporary(void *raw_buffer,
ParentPlugin__ *parent_plugin
) -> typename ParentPlugin__::TransientLEDMode * {
auto led_mode = static_cast<typename ParentPlugin__::TransientLEDMode *>(
led_mode_management::generateLEDMode<ParentPlugin__>(
raw_buffer,
parent_plugin,
0 /*dummy*/
)
);
parent_plugin->registerLEDModeActivated(255 /* dymmy to disable access
to transient LED modes through LEDControl */
);
return led_mode;
}
#endif
private:
// For the sake of convenience make type LEDModeFactory
// available in class namespace
//
typedef led_mode_management::LEDModeFactory LEDModeFactory;
static kaleidoscope::plugin::LEDMode *getLEDMode(uint8_t mode_id);
static void retreiveLEDModeFactoryFromPROGMEM(uint8_t mode_id,
LEDModeFactory &fac);
static void setupPersistentLEDModes();
// Persistent LED mode plugins are derived from kaleidoscope::plugin::LEDMode.
// The standard dictates that for them this more specialized overload
// of setupLEDMode is supposed to be called instead of the more
// general one below.
//
static void setupLEDMode(kaleidoscope::plugin::LEDMode *persistent_led_mode) {
persistent_led_mode->setup();
}
static void setupLEDMode(kaleidoscope::Plugin */*not_a_persistent_led_mode*/) {}
static uint8_t led_mode_buffer_[];
};
} // end namespace internal
} // end namespace kaleidoscope
// Some auxiliary macros that are mapped to the list of
// plugins defined via KALEIDOSCOPE_INIT_PLUGINS follow.
// Evaluates to a boolean value that signals whether
// a plugin is related to LED modes and thus needs to be considered
// during setup of the LED mode factory array.
//
#define _LED_MODE_MANAGEMENT__PLUGIN_CONTROLS_LED_MODE(PLUGIN) \
pluginControlsLEDMode(ledModePluginType(&::PLUGIN))
// Generates a LEDModeFactory for each plugin. For all those
// plugins that do not export transient LED modes, a nullptr-initialized
// factory is created. This does not mean a performance penalty, as
// those empty factories are then directly discarded, still at compile time.
//
#define _LED_MODE_MANAGEMENT__GENERATE_LED_MODE_FACTORY(PLUGIN) \
kaleidoscope::internal::led_mode_management __NL__ \
::GenerateLEDModeFactory<ledModePluginType(&::PLUGIN)>::apply(&::PLUGIN)
// Retreive the pointer type of the exported transient LED mode of a plugin.
//
#define _LED_MODE_MANAGEMENT__PLUGIN_PTR_TYPE(PLUGIN) \
decltype(&::PLUGIN)
#define _LED_MODE_MANAGEMENT__SETUP_STATIC_LED_MODE(PLUGIN) \
LEDModeManager::setupLEDMode(&::PLUGIN);
#define _INIT_LED_MODE_MANAGER(...) \
namespace kaleidoscope { __NL__ \
namespace internal { __NL__ \
namespace led_mode_management { __NL__ \
__NL__ \
/* Setup the array-like data structure that stores __NL__ \
* LEDModeFactories */ __NL__ \
typedef kaleidoscope::internal::ArrayLikeStorage< __NL__ \
LEDModeFactory, __NL__ \
/* Map the list of global plugin __NL__ \
* objects to a list of led mode __NL__ \
* factories */ __NL__ \
MAP_LIST( __NL__ \
/* Generate a series of boolean values. True for __NL__ \
* each plugin that is related to LED modes, __NL__ \
* false otherwise. */ __NL__ \
_LED_MODE_MANAGEMENT__PLUGIN_CONTROLS_LED_MODE, __NL__ \
__VA_ARGS__ __NL__ \
) __NL__ \
> ArrayType; __NL__ \
__NL__ \
/* Generate the actual instance of template class __NL__ \
* TypedPluginArray and initialize it with a list of __NL__ \
* LEDModeFactories. __NL__ \
*/ __NL__ \
const PROGMEM ArrayType led_mode_factories( __NL__ \
MAP_LIST( __NL__ \
_LED_MODE_MANAGEMENT__GENERATE_LED_MODE_FACTORY, __NL__ \
__VA_ARGS__ __NL__ \
) __NL__ \
); __NL__ \
} /* end namespace led_mode_management */ __NL__ \
__NL__ \
/* Store the number of LEDModeFactories. __NL__ \
*/ __NL__ \
const PROGMEM uint8_t num_led_modes __NL__ \
= led_mode_management::ArrayType::n_entries; __NL__ \
__NL__ \
uint8_t LEDModeManager::numLEDModes() { __NL__ \
return pgm_read_byte(&num_led_modes); __NL__ \
} __NL__ \
__NL__ \
void LEDModeManager::retreiveLEDModeFactoryFromPROGMEM( __NL__ \
uint8_t mode_id, __NL__ \
LEDModeFactory &fac) { __NL__ \
memcpy_P(&fac, __NL__ \
&reinterpret_cast<const LEDModeFactory*>( __NL__ \
&led_mode_management::led_mode_factories __NL__ \
)[mode_id], __NL__ \
sizeof(LEDModeFactory) __NL__ \
); __NL__ \
} __NL__ \
__NL__ \
static constexpr size_t max_led_mode_size __NL__ \
= led_mode_management::TransientLEDModeMaxSize< __NL__ \
MAP_LIST( __NL__ \
_LED_MODE_MANAGEMENT__PLUGIN_PTR_TYPE, __NL__ \
__VA_ARGS__ __NL__ \
) __NL__ \
>::value; __NL__ \
__NL__ \
/* This buffer is dimensioned in a way that it can hold __NL__ \
* the largest of all transient LED modes defined in the sketch. __NL__ \
*/ __NL__ \
uint8_t LEDModeManager::led_mode_buffer_[max_led_mode_size]; __NL__ \
__NL__ \
void LEDModeManager::setupPersistentLEDModes() { __NL__ \
MAP( __NL__ \
_LED_MODE_MANAGEMENT__SETUP_STATIC_LED_MODE, __NL__ \
__VA_ARGS__ __NL__ \
) __NL__ \
} __NL__ \
} /* end namespace internal */ __NL__ \
} /* end namespace kaleidoscope */

@ -0,0 +1,114 @@
/* Kaleidoscope - Firmware for computer input devices
* Copyright (C) 2013-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 <stdint.h>
namespace kaleidoscope {
namespace internal {
// The ArrayLikeStorage class stores data of a specific
// type in a recursive fashion. It is used to generate array like
// data structures at compile time that are initialized from a
// list of entities only a subset of which is supposed to be
// stored in the container.
//
template<typename StoredType__,
bool IsAppropriateType__,
bool...MoreTypeInfo__>
class ArrayLikeStorage {
public:
typedef ArrayLikeStorage<StoredType__, MoreTypeInfo__...> NestedArray;
template<typename...MoreEntities__>
constexpr ArrayLikeStorage(StoredType__ entry, MoreEntities__...more_entities)
: entry_(entry),
nested_array_(more_entities...) {}
static constexpr uint8_t n_entries
= NestedArray::n_entries + 1;
typedef StoredType__ ContentType;
private:
StoredType__ entry_;
NestedArray nested_array_;
} __attribute__((packed)); // Make sure that there are no padding
// bytes added by the compiler.
// This is important to let the class
// have the same layout as a POD array.
template<typename StoredType__,
bool...MoreTypeInfo__>
class ArrayLikeStorage<StoredType__,
false /* Not of appropriate type */,
MoreTypeInfo__...> {
public:
typedef ArrayLikeStorage<StoredType__, MoreTypeInfo__...> NestedArray;
template<typename AnyType__, typename...MoreEntities__>
constexpr ArrayLikeStorage(AnyType__/* non-matching entity */,
MoreEntities__...more_entities)
: nested_array_(more_entities...) {}
static constexpr uint8_t n_entries
= NestedArray::n_entries;
typedef StoredType__ ContentType;
private:
NestedArray nested_array_;
} __attribute__((packed));
template<typename StoredType__>
struct ArrayLikeStorage<StoredType__, true /* is of appropriate type */> {
public:
constexpr ArrayLikeStorage(StoredType__ entry)
: entry_(entry)
{}
static constexpr uint8_t n_entries = 1;
typedef StoredType__ ContentType;
private:
StoredType__ entry_;
} __attribute__((packed));
template<typename StoredType__>
struct ArrayLikeStorage<StoredType__, false /* not of appropriate type */> {
public:
template<typename AnyType__>
constexpr ArrayLikeStorage(AnyType__/* non-matching entity */) {}
static constexpr uint8_t n_entries = 0;
typedef StoredType__ ContentType;
} __attribute__((packed));
} // end namespace internal
} // end namespace kaleidoscope

@ -198,4 +198,8 @@
__NL__ \ __NL__ \
_PREPARE_EVENT_HANDLER_SIGNATURE_CHECK __NL__ \ _PREPARE_EVENT_HANDLER_SIGNATURE_CHECK __NL__ \
__NL__ \ __NL__ \
_FOR_EACH_EVENT_HANDLER(_REGISTER_EVENT_HANDLER) _FOR_EACH_EVENT_HANDLER(_REGISTER_EVENT_HANDLER) __NL__ \
__NL__ \
/* This generates a PROGMEM array-kind-of data structure that contains */ __NL__ \
/* LEDModeFactory entries */ __NL__ \
_INIT_LED_MODE_MANAGER(__VA_ARGS__)

Loading…
Cancel
Save