Lamp-Da 0.1
A compact lantern project
Loading...
Searching...
No Matches
Writing your own lighting mode

This document can be viewed as an introduction to development on the project, as well as a way for beginners to write a new "lighting mode" for one of the boards specified in this project.

0. Introduction: enable your first mode

First, the directory structure:

  • when writing a mode, you will mostly interact with code in src/modes
  • most board behaviors and lower level implementations is in src/system
  • the two worlds integrate together in src/user

The examples used in this guide are provided in modes/examples.

We will start with reading the 00_intro_mode.hpp example:

namespace modes::examples {
struct IntroMode : public BasicMode
{
static void loop(auto& ctx) {
ctx.lamp.fill(colors::Chartreuse);
ctx.lamp.setBrightness(ctx.lamp.tick % ctx.lamp.getMaxBrightness());
}
};
} // namespace modes::examples

We recommend that you put your modes inside a modes:: namespace.

For example, if you are going to write some games for this board, feel free to create a new modes/custom/games/ directory, then a modes/custom/games/my_game_mode.hpp file, with the following content:

// keep the global namespaces clean! (& it gives you access to modes:: also)
namespace modes::games {
struct MyGameMode : public BasicMode
{
/*
... some BasicMode callbacks implementation ...
*/
};
} // namespace modes::games

There are several rules while writing a mode:

  • your new mode object needs to inherit from public BasicMode
  • all your code shall be contained inside your new mode object
  • all the mode methods and callbacks must be defined static
  • all the mode members must be declared static constexpr

What you will observe as you experiment, is that mode structures such as IntroMode and MyGameMode are completely state-less and are never actually constructed during the runtime of the program. Hence, all the following usages are strongly discouraged:

int badGlobalVariable = 5;
const int badConstant = 3;
static int forbiddenStuff = 1;
// +everything shall be inside the mode struct!
struct VeryBadMode : public BasicMode
{
//
// the bad way
//
int badMember = 8;
const int badConstant = 3;
static int forbiddenWhat = 5;
constexpr int badMistake = 1;
static void loop(auto& ctx) {
static int counter = 0; // !! do NOT use inline static, use StateTy !!
counter += 1;
}
void _stateful_method() {
}
//
// the good way
//
static constexpr int goodConstant = 0;
static void _stateful_method(auto& ctx)
{
// ... some interaction with ctx.state ...
}
static constexpr int _stateless_helper(int ax, int ay, int bx, int by)
{
int dx = abs(ax - bx);
int dy = abs(ay - by);
return dx * dx + dy * dy;
}
};

You can view that structure as a collection of static callbacks that implements your mode.

Note that only these callbacks will be called at runtime.

For example, if we look at the default BasicMode implementation of the void loop(auto& ctx) callback:

static void loop(auto& ctx) { return; }

We see that by default, the .loop() callback does nothing.

Coming back at 00_intro_mode.hpp we see that it implements it as follows:

static void loop(auto& ctx) {
ctx.lamp.fill(colors::Chartreuse);
ctx.lamp.setBrightness(ctx.lamp.tick % ctx.lamp.getMaxBrightness());
}

Here the ctx object makes available two important members:

  • the ctx.lamp object that exposes to you user the lamp hardware
  • the ctx.state object that exposes to you your own mode state

We will later see how to make your mode stateful by defining a custom StateTy.

Here, looking at the LampTy documentation, we see the following:

void LMBD_INLINE setBrightness(const brightness_t brightness, /* ... */)
void LMBD_INLINE fill(uint32_t color)
volatile const uint32_t tick;

The IntroMode loop function thus fills the lamp with colors::Chartreuse (see documentation for modes/include/colors/palettes.hpp) and then, vary the brightness of the lamp depending on the current tick number.

One remark here, is that "colors" are meaningful only if the board is set up with an RGB LED strip, meaning that this mode will fail if make simple or make cct have been used during build.

To support multiple board flavors within a mode, use the following:

struct IntroMode : public BasicMode
{
static void loop(auto& ctx) {
// this code is NOT included if "lamp flavor" is not RGB-indexable
if constexpr (ctx.lamp.flavor == hardware::LampTypes::indexable) {
ctx.lamp.fill(colors::Chartreuse);
}
ctx.lamp.setBrightness(ctx.lamp.tick % ctx.lamp.getMaxBrightness());
}
};

If you tried to modify modes/examples/00_intro_mode.hpp by yourself during this guide, you may have noticed that it is not compiled / not included at all.

This is because you need to enable the mode inside the global mode manager.

In order to do that, you can edit src/user/indexable_functions.cpp as follows:

// ...
// we include our mode file last
namespace user {
//
// list your groups & modes here
//
using IntroGroup = modes::GroupFor<modes::examples::IntroMode>;
using ManagerTy = modes::ManagerFor<IntroGroup>;
/* !! you will need to comment the default mode manager config !!
using ManagerTy = modes::ManagerFor</* all the default groups */>;
Example implementation of the simplest possible mode.
Legacy implementation of the Indexable modes.

Here, we declare a mode group named IntroGroup that contains only one mode, our new modes::examples::IntroMode and nothing else. We are then able to pass a list to the manager of all the groups we want to include in our configuration, here our IntroGroup that only includes IntroMode.

Hidden groups

It can be needed to have extra groups not accessible by standard actions. The template option hiddenGroupCnt allows the programmer to add groups that wont be accessible by standard user inputs.

Be aware that once entered, those groups can be circulated by standard user commands. Modes in thoses groups can also be saved as favorites and other standard actions. The only difference with the standard groups will be the way to go to those groups.

A manager can only be created with at least one accessible group.

Enter an hidden group simply using the enter_group method of the manager, with the id of the hidden group as a parameter.

The types to use for managers with hidden groups is ManagerFoHiddenConfig and ManagerForHiddenGroups.

Example:

using ManagerTy = modes::ManagerForHiddenGroups<1,
group1,
group2,
group3,
group4 // group 4 is an hidden group
>;
using ManagerTy = modes::ManagerForHiddenGroups<3,
group1,
group2, // group 2 is an hidden group
group3, // group 3 is an hidden group
group4 // group 4 is an hidden group
>;