How does one make a key that turns the LEDs off?

Hello, all! I recently got my keyboardio, and I’m enjoying it quite a bit. This is beautiful work, J+K; you have my thanks.

I have a couple Qs about how to get things set up exactly how I like them, which I’ll ask in this thread and another thread. First Q: I’d like to make Fn+LED turn off LEDs. (Actually, in full generality, I’d like to know how to make keys that jump me to a particular point in the LED mode rotation.) Is there an easy way to do this at this time? I glanced briefly through the kaleidoscope LEDControl plugin, but the only defined key macro I could find was Key_LEDNext. Can anyone quickly tell me how to (eg) define Key_LEDOff, or various Key_LED#n macros that jump to the nth LED mode in the rotation? Thanks!

1 Like

Both of these can be done with a macro. For the first, to turn all LEDs off, a macro like the following will work:

static void turnLEDsOff(uint8_t key_state) {
  if (keyToggledOn(key_state)) {
    LEDOff.activate();
  }
}

For the second, there are at least two ways : you can either jump to an index, or to a particular mode (by name). The first using LEDControl.set_mode(n), where n is the index of the mode. The index will be the same as the LED effect order in Kaleidoscope.use() (counting only the LED effects). Or, you can call, say, LEDRainbowWaveEffect.activate(). Both of these from a macro much like the example above.

To use these as macros, add, say, MACRO_LED_OFF to the macro enum in your sketch, and use M(MACRO_LED_OFF) in your keymap, and add a case statement in macroActions, similar to the existing ones.

Hope this helps!

5 Likes

Could someone help me figure out the problem with this code, which is based on algernon’s sample above? This isn’t a language I know, so I’ve been trying to put something together based on what I see in the rest of the file, plus application of general programming principles. That often gets me decent results, but not always!

What I want to do is assign a macro to the LED button that will turn off LEDs if they’re on, and if LEDs are off when the button is pressed, it will turn on the previously selected LED pattern, if an LED pattern had been selected since the last time the keyboard turned on. (I don’t expect it to last across the keyboard itself turning off.)

That’s complicated to write out in plain English, so here’s the pseudocode for what I’m trying to do:

led_mode variable is initialized to be my current favorite LED pattern
when button is pressed
    if LEDs are on
        save current LED selection to the led_mode variable
        turn LEDs off
    else
        turn LEDs on, loading previous LED selection from the led_mode variable

Here’s what I’ve got so far:

/** turnLedsOnAndOff turns off LEDs, saving the current state, and turns them back on
 *  expanded from a sample Algernon posted at https://community.keyboard.io/t/how-does-one-make-a-key-that-turns-the-leds-off/554/2
 */
static void turnLedsOnAndOff(uint8_t key_state) {
  int currentLedMode = 4; /*the intention is to set the default mode to the chasing rainbow effect*/
  if (keyToggledOn(key_state)) {
    currentLedMode = LEDControl.get_mode(); /*first, store the current mode*/
    LEDOff.activate();
  }
  else {
    LEDControl.set_mode(currentLedMode);
  }
}

When I try to run make flash, here’s the output:

BOARD_HARDWARE_PATH="/…/Arduino/hardware" /…/Arduino/hardware/keyboardio/avr/libraries/Kaleidoscope/bin//kaleidoscope-builder flash
Building output/Model01-Firmware/Model01-Firmware (0.0.0-gv1.22-3-gb745-dirty) …
/…/Arduino/Model01-Firmware/Model01-Firmware.ino: In function ‘void turnLedsOnAndOff(uint8_t)’:
/…/Arduino/Model01-Firmware/Model01-Firmware.ino:220:42: error: invalid conversion from ‘kaleidoscope::LEDMode*’ to ‘int’ [-fpermissive]
currentLedMode = LEDControl.get_mode(); /first, store the current mode/

exit status 1
make: *** [flash] Error 1

I think the problem is that I declared the wrong variable type (which makes me miss Python, where I wouldn’t have to declare the type at all…). I took a look at some of the other files, but I haven’t figured out what variable type ‘kaleidoscope::LEDMode*’ is (if that is indeed the problem). Does anyone know what variable type it is, and how I would find it?

I had an additional error when I started to write this post, but I fixed that one!

If you see issues with this function beyond the issue I’m asking about, I would welcome those comments too–I’m sure I’d run into those problems as soon as this one is fixed.

I’m not versed in anything to do with the LED modes, but I can tell you right away that you’ll want to use the static keyword in the declaration of the variable you use to save the LED mode state when it toggles off. Otherwise, that variable will go out of scope when the macro function returns, and that state will be lost.

1 Like

I’ve had a quick peek at LedControl now, and I think you want to use LedControl::get_mode_index(), which returns an integer (uint8_t), rather than get_mode(), which returns a pointer to an LEDMode object.

1 Like

Also, you probably only want this macro to do things when the key toggles on — first checking the saved state, then either turning on the saved LED mode, or saving the current mode, then turning the LEDs off.

If keyToggledOn(state) isn’t true, it’s usually best to do nothing.

1 Like

Aha, thank you very much! I’ll give it a try tomorrow when I’m back where the keyboard is.

1 Like

Oh, very good catch. Yeah, I would have had a lot of unintended consequences from that.

1 Like

Here’s what I ended up with, in case anyone else wants to use it. It’s a little sloppy in that it depends on “off” being state 0 in the index instead of using some other method to determine whether LEDs are on or off.

/** turnLedsOnAndOff turns off LEDs, saving the current state, and turns them back on
 *  expanded by aedifica from a sample algernon posted at https://community.keyboard.io/t/how-does-one-make-a-key-that-turns-the-leds-off/554/2 with tips from merlin
 */
static void turnLedsOnAndOff(uint8_t key_state) {
  static int lastLedMode = 2; /* set the default mode to the chasing rainbow effect */
  if (keyToggledOn(key_state)) { /*when button is pressed*/
    if (LEDControl.get_mode_index() != 0) { /* if LEDs are on (this does depend on "off" being at index 0) */
      lastLedMode = LEDControl.get_mode_index(); /* first, store the current mode */
      LEDOff.activate();
    }
    else {
      LEDControl.set_mode(lastLedMode);
    }
  }
}

and as algernon pointed out, one also needs to define it as a macro. I added this to the macro_t case switch statement:

  case MACRO_LED_ON_AND_OFF:
    turnLedsOnAndOff(keyState);
    break;

and the macro enum statement is now

enum { MACRO_VERSION_INFO,
       MACRO_ANY,
       MACRO_LED_ON_AND_OFF
     };

Now when I accidentally hit the LED key when reaching for the B, it will just toggle the LEDs on and off instead of making me cycle through all of the effects to get back to the one I want! Key_LEDEffectNext now lives on the Fn layer so I can still cycle through other effects when I want to.

3 Likes

This might be turned into if (LEDControl.get_mode() != &LEDOff), to remove the dependency on LEDOff having index zero. (Untested, mind you.)

3 Likes

Even better! Yes, I’ve just changed to that and it is working.

3 Likes

I took a bunch of the code from this thread and modified it to fit my needs. I’m posting it here in case it helps anybody else.

My use case is as follows:

  1. I want a hit of the LED button to quickly turn the LEDs on if they are off (and restore to the last mode), and I want them to turn off if they are on (so an accidental hit of the key doesn’t mean I need to cycle through again to get back to what I want).
  2. I wanted to make Fn+LED cycle the LED modes forward, and Fn+Shift+LED to cycle the LED modes backward.
  3. Since I have a quick toggle, I didn’t want to include the Off mode in my toggling.
  4. I actually removed Key_LEDEffectNext from my keymap, so I wanted to make the boot greeting continue to function (because it is cool).

Here’s my change to the MACROS enum:

enum { MACRO_VERSION_INFO,
   MACRO_ANY,
   MACRO_LED_NEXT_PREV,
   MACRO_LED_ON_AND_OFF
 };

I added M(MACRO_LED_ON_AND_OFF) in place of Key_LEDEffectNext on the first layer of the keymap, and placed M(MACRO_LED_NEXT_PREV) in the same place on the Function layer.

I added to the Macros section near anyKeyMacro

//Global int for the last LED mode (defaults to LEDOff, or whatever LED mode you have as 1st)
static int lastLedMode = -1;
/** turnLedsOnAndOff turns off LEDs, saving the current state, and turns them back on
 *  expanded by aedifica from a sample algernon posted at 
 *  https://community.keyboard.io/t/how-does-one-make-a-key-that-turns-the-leds-off/554/2 
 *  with tips from merlin
 */
static void turnLedsOnAndOff(uint8_t key_state) {
  if (keyToggledOn(key_state)) { /*when button is pressed*/
    if (LEDControl.get_mode() != &LEDOff) { /* if LEDs are on */
      lastLedMode = LEDControl.get_mode_index(); /* first, store the current mode */
      LEDOff.activate(); /* then activate the "off" mode */
    } else if(lastLedMode >= 0) {
      LEDControl.set_mode(lastLedMode); /* set our LED to the last mode */
    } else {
      //Either do the first item on the list that isn't the Off mode...
      nextPrevLedMode(key_state, true);
      //Or set it to something you want by default...
      //StalkerEffect.activate();
    }
  }
}

/* Toggle forward regularly, and toggle in reverse if shift is held */
static void nextPrevLedMode(uint8_t key_state, bool skipOff) {
  //Ensure a key was pressed
  if (keyToggledOn(key_state)) {
    if(
      kaleidoscope::hid::wasModifierKeyActive(Key_LeftShift)
      || kaleidoscope::hid::wasModifierKeyActive(Key_RightShift)
    ) {
      //shift held, so go backward
      do {
        LEDControl.prev_mode();
      } while (
        skipOff && LEDControl.get_mode() == &LEDOff
      );
    } else {
      //No shift, so go forward
      do {
        LEDControl.next_mode();
     } while (
        skipOff && LEDControl.get_mode() == &LEDOff
      );
    }
    //Set the last LED mode
    lastLedMode = LEDControl.get_mode_index();
  }
}

And finally I added macroAction case statement the following two cases:

  case MACRO_LED_ON_AND_OFF:
    turnLedsOnAndOff(keyState);
    break;

  case MACRO_LED_NEXT_PREV:
    nextPrevLedMode(keyState, true);
    break;  

You can edit the turnLedsOnAndOff function and explicitly set the initial LED mode you wish to use, by name. If one isn’t set, it’ll just choose the first one in the list of LED modes that isn’t LEDOff.

Now, doing all of this will break the BootGreeting function that flashes the LED button for 10 seconds, because I removed the Key_LEDEffectNext key from the mapping in favor of a macro. The boot greeting code explicitly looks for that key, and since it can’t find it it doesn’t activate. I modified the BootGreeting code so that it accepts a parameter for a row and column position, which lets you hardcode a specific key to enable. Doing this, you can actually make any key that you desire light up (such as the any key or the butterfly key, or just the LED key like before.

If this bothers you, you can add the following to your sketch:

//Cutom bootgreeting effect
static kaleidoscope::BootGreetingEffect BootGreetingEffectHardcoded(0,6);

and then replace the BootGreetingEffect in your Keyboardio.use call:

Kaleidoscope.use(
    // The boot greeting effect pulses the LED button for 10 seconds after the keyboard is first connected
    &BootGreetingEffectHardcoded,

    // The hardware test mode, which can be invoked by tapping Prog, LED and the left Fn button at the same time.
    &TestMode,

You’ll want to replace the Library versions of Kaleidoscope-LEDEffect-BootGreeting (.h. /cpp) the following versions below:

Kaleidoscope-LEDEffect-BootGreeting.h

/* -*- mode: c++ -*-
 * Kaleidoscope-LEDEffect-BootGreeting -- Small greeting at boot time
 * Copyright (C) 2017  Gergely Nagy
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once

#include "Kaleidoscope-LEDControl.h"

namespace kaleidoscope {
class BootGreetingEffect : public KaleidoscopePlugin {
 public:
  BootGreetingEffect(void) {}
  BootGreetingEffect(byte, byte);

  void begin(void) final;
  static byte key_row;
  static byte key_col;

 private:
  static void loopHook(const bool post_clear);
  static bool done_;
  static byte row_;
  static byte col_;
};
}

extern kaleidoscope::BootGreetingEffect BootGreetingEffect;

Kaleidoscope-LEDEffect-BootGreeting.cpp

/* -*- mode: c++ -*-
 * Kaleidoscope-LEDEffect-BootGreeting -- Small greeting at boot time
 * Copyright (C) 2017  Gergely Nagy
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "Kaleidoscope-LEDEffect-BootGreeting.h"
#include "LEDUtils.h"

namespace kaleidoscope {

bool BootGreetingEffect::done_;
byte BootGreetingEffect::row_;
byte BootGreetingEffect::col_;
byte BootGreetingEffect::key_row = 255;
byte BootGreetingEffect::key_col = 255;

BootGreetingEffect::BootGreetingEffect(byte pos_row, byte pos_col){
	key_row = pos_row;
	key_col = pos_col;
}

void BootGreetingEffect::begin(void) {
  Kaleidoscope.useLoopHook(loopHook);

  // Find the LED key.
  for (uint8_t r = 0; r < ROWS; r++) {
    for (uint8_t c = 0; c < COLS; c++) {
      Key k = Layer.lookupOnActiveLayer(r, c);

      if (
		k == Key_LEDEffectNext
		|| (key_row != 255 && key_col != 255 && key_row == r && key_col == c)
	  ) {
        row_ = r;
        col_ = c;
        return;
      }
    }
  }

  // We didn't find the LED key. Let's just pretend we're "done".
  done_ = true;
}

void BootGreetingEffect::loopHook(const bool post_clear) {
  if (!post_clear || done_)
    return;

  if (millis() > 9200) {
    done_ = true;
    ::LEDControl.refreshAt(row_, col_);
    return;
  }

  cRGB color = breath_compute();
  ::LEDControl.setCrgbAt(row_, col_, color);
}
}

kaleidoscope::BootGreetingEffect BootGreetingEffect;

The end result is that a hit of the LED button will toggle the LEDs on or off, the boot greeting still works, and you can scroll forward or backward through the array of LED modes by use Fn+LED (+optional shift for reverse).

6 Likes

We should probably modify BootGreeting so that the key to breathe is customisable, so you won’t have to make a copy if you only want to change that one thing.

2 Likes

I tried that a little bit, but was having issues with it running the boot loop before it read my settings. Probably a bit of something to do with where I was trying to set those settings, to be fair. I just wanted to get it working, though, so that works for me for now.

I also briefly tried setting a customizable key by Key_*, but wasn’t sure how that would work with my M() macro call, hence why I just took the easy way out and did row / column.

1 Like