New flexible usermod for seven segment displays (#2409)

* add first version

* added max/min brightness to autoldr functionality

* added more information to the readme
This commit is contained in:
Roman Reitschmied 2021-12-12 00:31:21 +01:00 committed by GitHub
parent fb338c0728
commit d3f35955d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 682 additions and 0 deletions

View File

@ -0,0 +1,129 @@
# Seven Segment Display Reloaded
Usermod that uses the overlay feature to create a configurable seven segment display.
Optimized for maximum configurability and use with seven segment clocks by parallyze (https://www.instructables.com/member/parallyze/instructables/)
Very loosely based on the existing usermod "seven segment display".
## Installation
Add the compile-time option `-D USERMOD_SSDR` to your `platformio.ini` (or `platformio_override.ini`) or use `#define USERMOD_SSDR` in `my_config.h`.
For the auto brightness option, the usermod SN_Photoresistor has to be installed as well. See SN_Photoresistor/readme.md for instructions.
## Settings
All settings can be controlled the usermod setting page.
Part of the settings can be controlled through MQTT with a raw payload or through a json request to /json/state.
### enabled
Enables/disables this overlay usermod
### inverted
Enables the inverted mode in which the background should be enabled and the digits should be black (leds off)
### Colon-blinking
Enables the blinking colon(s) if they are defined
### enable-auto-brightness
Enables the auto brightness feature. Can be only used with the usermod SN_Photoresistor installed.
### auto-brightness-min / auto-brightness-max
The lux value calculated from usermod SN_Photoresistor will be mapped to the values defined here.
The mapping is 0 - 1000 lux will be mapped to auto-brightness-min - auto-brightness-max
The mA current protection of WLED will override the calculated value if it is too high.
### Display-Mask
Defines the type of the time/date display.
For example "H:m" (default)
- H - 00-23 hours
- h - 01-12 hours
- k - 01-24 hours
- m - 00-59 minutes
- s - 00-59 seconds
- d - 01-31 day of month
- M - 01-12 month
- y - 21 last two positions of year
- Y - 2021 year
- : for a colon
### LED-Numbers
- LED-Numbers-Hours
- LED-Numbers-Minutes
- LED-Numbers-Seconds
- LED-Numbers-Colons
- LED-Numbers-Day
- LED-Numbers-Month
- LED-Numbers-Year
See following example for usage.
## Example
Example for Leds definition
```
< A >
/\ /\
F B
\/ \/
< G >
/\ /\
E C
\/ \/
< D >
```
Leds or Range of Leds are seperated by a comma ","
Segments are seperated by a semicolon ";" and are read as A;B;C;D;E;F;G
Digits are seperated by colon ":" -> A;B;C;D;E;F;G:A;B;C;D;E;F;G
Ranges are defined as lower to higher (lower first)
For example, an clock definition for the following clock (https://www.instructables.com/Lazy-7-Quick-Build-Edition/) is
- hour "59,46;47-48;50-51;52-53;54-55;57-58;49,56:0,13;1-2;4-5;6-7;8-9;11-12;3,10"
- minute "37-38;39-40;42-43;44,31;32-33;35-36;34,41:21-22;23-24;26-27;28,15;16-17;19-20;18,25"
or
- hour "6,7;8,9;11,12;13,0;1,2;4,5;3,10:52,53;54,55;57,58;59,46;47,48;50,51;49,56"
- minute "15,28;16,17;19,20;21,22;23,24;26,27;18,25:31,44;32,33;35,36;37,38;39,40;42,43;34,41"
depending on the orientation.
# The example detailed:
hour "59,46;47-48;50-51;52-53;54-55;57-58;49,56:0,13;1-2;4-5;6-7;8-9;11-12;3,10"
there are two digits seperated by ":"
- 59,46;47-48;50-51;52-53;54-55;57-58;49,56
- 0,13;1-2;4-5;6-7;8-9;11-12;3,10
In the first digit,
the **segment A** consists of the leds number **59 and 46**., **segment B** consists of the leds number **47, 48** and so on
The second digit starts again with **segment A** and leds **0 and 13**, **segment B** consists of the leds number **1 and 2** and so on
### first digit of the hour
- Segment A: 59, 46
- Segment B: 47, 48
- Segment C: 50, 51
- Segment D: 52, 53
- Segment E: 54, 55
- Segment F: 57, 58
- Segment G: 49, 56
### second digit of the hour
- Segment A: 0, 13
- Segment B: 1, 2
- Segment C: 4, 5
- Segment D: 6, 7
- Segment E: 8, 9
- Segment F: 11, 12
- Segment G: 3, 10

View File

@ -0,0 +1,544 @@
#pragma once
#include "wled.h"
class UsermodSSDR : public Usermod {
//#define REFRESHTIME 497
private:
//Runtime variables.
unsigned long umSSDRLastRefresh = 0;
unsigned long umSSDRResfreshTime = 3000;
bool umSSDRDisplayTime = false;
bool umSSDRInverted = false;
bool umSSDRColonblink = true;
bool umSSDREnableLDR = false;
String umSSDRHours = "";
String umSSDRMinutes = "";
String umSSDRSeconds = "";
String umSSDRColons = "";
String umSSDRDays = "";
String umSSDRMonths = "";
String umSSDRYears = "";
uint16_t umSSDRLength = 0;
uint16_t umSSDRBrightnessMin = 0;
uint16_t umSSDRBrightnessMax = 255;
bool* umSSDRMask = 0;
/*// H - 00-23 hours
// h - 01-12 hours
// k - 01-24 hours
// m - 00-59 minutes
// s - 00-59 seconds
// d - 01-31 day of month
// M - 01-12 month
// y - 21 last two positions of year
// Y - 2021 year
// : for a colon
*/
String umSSDRDisplayMask = "H:m"; //This should reflect physical equipment.
/* Segment order, seen from the front:
< A >
/\ /\
F B
\/ \/
< G >
/\ /\
E C
\/ \/
< D >
*/
uint8_t umSSDRNumbers[11][7] = {
// A B C D E F G
{ 1, 1, 1, 1, 1, 1, 0 }, // 0
{ 0, 1, 1, 0, 0, 0, 0 }, // 1
{ 1, 1, 0, 1, 1, 0, 1 }, // 2
{ 1, 1, 1, 1, 0, 0, 1 }, // 3
{ 0, 1, 1, 0, 0, 1, 1 }, // 4
{ 1, 0, 1, 1, 0, 1, 1 }, // 5
{ 1, 0, 1, 1, 1, 1, 1 }, // 6
{ 1, 1, 1, 0, 0, 0, 0 }, // 7
{ 1, 1, 1, 1, 1, 1, 1 }, // 8
{ 1, 1, 1, 1, 0, 1, 1 } // 9
};
//String to reduce flash memory usage
static const char _str_name[];
static const char _str_ldrEnabled[];
static const char _str_timeEnabled[];
static const char _str_inverted[];
static const char _str_colonblink[];
static const char _str_displayMask[];
static const char _str_hours[];
static const char _str_minutes[];
static const char _str_seconds[];
static const char _str_colons[];
static const char _str_days[];
static const char _str_months[];
static const char _str_years[];
static const char _str_minBrightness[];
static const char _str_maxBrightness[];
#ifdef USERMOD_ID_SN_PHOTORESISTOR
Usermod_SN_Photoresistor *ptr;
#else
void* ptr = nullptr;
#endif
void _overlaySevenSegmentDraw() {
int displayMaskLen = static_cast<int>(umSSDRDisplayMask.length());
bool colonsDone = false;
_setAllFalse();
for (int index = 0; index < displayMaskLen; index++) {
int timeVar = 0;
switch (umSSDRDisplayMask[index]) {
case 'h':
timeVar = hourFormat12(localTime);
_showElements(&umSSDRHours, timeVar, 0);
break;
case 'H':
timeVar = hour(localTime);
_showElements(&umSSDRHours, timeVar, 0);
break;
case 'k':
timeVar = hour(localTime) + 1;
_showElements(&umSSDRHours, timeVar, 0);
break;
case 'm':
timeVar = minute(localTime);
_showElements(&umSSDRMinutes, timeVar, 0);
break;
case 's':
timeVar = second(localTime);
_showElements(&umSSDRSeconds, timeVar, 0);
break;
case 'd':
timeVar = day(localTime);
_showElements(&umSSDRDays, timeVar, 0);
break;
case 'M':
timeVar = month(localTime);
_showElements(&umSSDRMonths, timeVar, 0);
break;
case 'y':
timeVar = second(localTime);
_showElements(&umSSDRYears, timeVar, 0);
break;
case 'Y':
timeVar = year(localTime);
_showElements(&umSSDRYears, timeVar, 0);
break;
case ':':
if (!colonsDone) { // only call _setColons once as all colons are printed when the first colon is found
_setColons();
colonsDone = true;
}
break;
}
}
_setMaskToLeds();
}
void _setColons() {
if ( umSSDRColonblink ) {
if ( second(localTime) % 2 == 0 ) {
_showElements(&umSSDRColons, 0, 1);
}
} else {
_showElements(&umSSDRColons, 0, 1);
}
}
void _showElements(String *map, int timevar, bool isColon) {
if (!(*map).equals("") && !(*map) == NULL) {
int length = String(timevar).length();
bool addZero = false;
if (length == 1) {
length = 2;
addZero = true;
}
int timeArr[length];
if(addZero) {
timeArr[1] = 0;
timeArr[0] = timevar;
} else {
int count = 0;
while (timevar) {
timeArr[count] = timevar%10;
timevar /= 10;
count++;
};
}
int colonsLen = static_cast<int>((*map).length());
int count = 0;
int countSegments = 0;
int countDigit = 0;
bool range = false;
int lastSeenLedNr = 0;
for (int index = 0; index < colonsLen; index++) {
switch ((*map)[index]) {
case '-':
lastSeenLedNr = _checkForNumber(count, index, map);
count = 0;
range = true;
break;
case ':':
_setLeds(_checkForNumber(count, index, map), lastSeenLedNr, range, countSegments, timeArr[countDigit], isColon);
count = 0;
range = false;
countDigit++;
countSegments = 0;
break;
case ';':
_setLeds(_checkForNumber(count, index, map), lastSeenLedNr, range, countSegments, timeArr[countDigit], isColon);
count = 0;
range = false;
countSegments++;
break;
case ',':
_setLeds(_checkForNumber(count, index, map), lastSeenLedNr, range, countSegments, timeArr[countDigit], isColon);
count = 0;
range = false;
break;
default:
count++;
break;
}
}
_setLeds(_checkForNumber(count, colonsLen, map), lastSeenLedNr, range, countSegments, timeArr[countDigit], isColon);
}
}
void _setLeds(int lednr, int lastSeenLedNr, bool range, int countSegments, int number, bool colon) {
if ((colon && umSSDRColonblink) || umSSDRNumbers[number][countSegments]) {
if (range) {
for(int i = lastSeenLedNr; i <= lednr; i++) {
umSSDRMask[i] = true;
}
} else {
umSSDRMask[lednr] = true;
}
}
}
void _setMaskToLeds() {
for(int i = 0; i <= umSSDRLength; i++) {
if ((!umSSDRInverted && !umSSDRMask[i]) || (umSSDRInverted && umSSDRMask[i])) {
strip.setPixelColor(i, 0x000000);
}
}
}
void _setAllFalse() {
for(int i = 0; i <= umSSDRLength; i++) {
umSSDRMask[i] = false;
}
}
int _checkForNumber(int count, int index, String *map) {
String number = (*map).substring(index - count, index);
return number.toInt();
}
void _publishMQTTint_P(const char *subTopic, int value)
{
if(mqtt == NULL) return;
char buffer[64];
char valBuffer[12];
sprintf_P(buffer, PSTR("%s/%S/%S"), mqttDeviceTopic, _str_name, subTopic);
sprintf_P(valBuffer, PSTR("%d"), value);
mqtt->publish(buffer, 2, true, valBuffer);
}
void _publishMQTTstr_P(const char *subTopic, String Value)
{
if(mqtt == NULL) return;
char buffer[64];
sprintf_P(buffer, PSTR("%s/%S/%S"), mqttDeviceTopic, _str_name, subTopic);
mqtt->publish(buffer, 2, true, Value.c_str(), Value.length());
}
bool _cmpIntSetting_P(char *topic, char *payload, const char *setting, void *value)
{
if (strcmp_P(topic, setting) == 0)
{
*((int *)value) = strtol(payload, NULL, 10);
_publishMQTTint_P(setting, *((int *)value));
return true;
}
return false;
}
bool _handleSetting(char *topic, char *payload) {
if (_cmpIntSetting_P(topic, payload, _str_timeEnabled, &umSSDRDisplayTime)) {
return true;
}
if (_cmpIntSetting_P(topic, payload, _str_ldrEnabled, &umSSDREnableLDR)) {
return true;
}
if (_cmpIntSetting_P(topic, payload, _str_inverted, &umSSDRInverted)) {
return true;
}
if (_cmpIntSetting_P(topic, payload, _str_colonblink, &umSSDRColonblink)) {
return true;
}
if (strcmp_P(topic, _str_displayMask) == 0) {
umSSDRDisplayMask = String(payload);
_publishMQTTstr_P(_str_displayMask, umSSDRDisplayMask);
return true;
}
return false;
}
void _updateMQTT()
{
_publishMQTTint_P(_str_timeEnabled, umSSDRDisplayTime);
_publishMQTTint_P(_str_ldrEnabled, umSSDREnableLDR);
_publishMQTTint_P(_str_inverted, umSSDRInverted);
_publishMQTTint_P(_str_colonblink, umSSDRColonblink);
_publishMQTTstr_P(_str_hours, umSSDRHours);
_publishMQTTstr_P(_str_minutes, umSSDRMinutes);
_publishMQTTstr_P(_str_seconds, umSSDRSeconds);
_publishMQTTstr_P(_str_colons, umSSDRColons);
_publishMQTTstr_P(_str_days, umSSDRDays);
_publishMQTTstr_P(_str_months, umSSDRMonths);
_publishMQTTstr_P(_str_years, umSSDRYears);
_publishMQTTstr_P(_str_displayMask, umSSDRDisplayMask);
_publishMQTTint_P(_str_minBrightness, umSSDRBrightnessMin);
_publishMQTTint_P(_str_maxBrightness, umSSDRBrightnessMax);
}
void _addJSONObject(JsonObject& root) {
JsonObject ssdrObj = root[FPSTR(_str_name)];
if (ssdrObj.isNull()) {
ssdrObj = root.createNestedObject(FPSTR(_str_name));
}
ssdrObj[FPSTR(_str_timeEnabled)] = umSSDRDisplayTime;
ssdrObj[FPSTR(_str_ldrEnabled)] = umSSDREnableLDR;
ssdrObj[FPSTR(_str_inverted)] = umSSDRInverted;
ssdrObj[FPSTR(_str_colonblink)] = umSSDRColonblink;
ssdrObj[FPSTR(_str_displayMask)] = umSSDRDisplayMask;
ssdrObj[FPSTR(_str_hours)] = umSSDRHours;
ssdrObj[FPSTR(_str_minutes)] = umSSDRMinutes;
ssdrObj[FPSTR(_str_seconds)] = umSSDRSeconds;
ssdrObj[FPSTR(_str_colons)] = umSSDRColons;
ssdrObj[FPSTR(_str_days)] = umSSDRDays;
ssdrObj[FPSTR(_str_months)] = umSSDRMonths;
ssdrObj[FPSTR(_str_years)] = umSSDRYears;
ssdrObj[FPSTR(_str_minBrightness)] = umSSDRBrightnessMin;
ssdrObj[FPSTR(_str_maxBrightness)] = umSSDRBrightnessMax;
}
public:
//Functions called by WLED
/*
* setup() is called once at boot. WiFi is not yet connected at this point.
* You can use it to initialize variables, sensors or similar.
*/
void setup() {
umSSDRLength = strip.getLengthTotal();
if (umSSDRMask != 0) {
umSSDRMask = (bool*) realloc(umSSDRMask, umSSDRLength * sizeof(bool));
} else {
umSSDRMask = (bool*) malloc(umSSDRLength * sizeof(bool));
}
_setAllFalse();
#ifdef USERMOD_ID_SN_PHOTORESISTOR
ptr = (Usermod_SN_Photoresistor*) usermods.lookup(USERMOD_ID_SN_PHOTORESISTOR);
#endif
DEBUG_PRINTLN(F("Setup done"));
}
/*
* loop() is called continuously. Here you can check for events, read sensors, etc.
*/
void loop() {
if (!umSSDRDisplayTime || strip.isUpdating()) {
return;
}
#ifdef USERMOD_ID_SN_PHOTORESISTOR
if(bri != 0 && umSSDREnableLDR && (millis() - umSSDRLastRefresh > umSSDRResfreshTime)) {
if (ptr != nullptr) {
uint16_t lux = ptr->getLastLDRValue();
uint16_t brightness = map(lux, 0, 1000, umSSDRBrightnessMin, umSSDRBrightnessMax);
if (bri != brightness) {
bri = brightness;
colorUpdated(1);
}
}
umSSDRLastRefresh = millis();
}
#endif
}
void handleOverlayDraw() {
if (umSSDRDisplayTime) {
_overlaySevenSegmentDraw();
}
}
/*
* addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API.
* Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI.
* Below it is shown how this could be used for e.g. a light sensor
*/
void addToJsonInfo(JsonObject& root) {
JsonObject user = root[F("u")];
if (user.isNull()) {
user = root.createNestedObject(F("u"));
}
JsonArray enabled = user.createNestedArray("Time enabled");
enabled.add(umSSDRDisplayTime);
JsonArray invert = user.createNestedArray("Time inverted");
invert.add(umSSDRInverted);
JsonArray blink = user.createNestedArray("Blinking colon");
blink.add(umSSDRColonblink);
JsonArray ldrEnable = user.createNestedArray("Auto Brightness enabled");
ldrEnable.add(umSSDREnableLDR);
}
/*
* addToJsonState() can be used to add custom entries to the /json/state part of the JSON API (state object).
* Values in the state object may be modified by connected clients
*/
void addToJsonState(JsonObject& root) {
JsonObject user = root[F("u")];
if (user.isNull()) {
user = root.createNestedObject(F("u"));
}
_addJSONObject(user);
}
/*
* readFromJsonState() can be used to receive data clients send to the /json/state part of the JSON API (state object).
* Values in the state object may be modified by connected clients
*/
void readFromJsonState(JsonObject& root) {
JsonObject user = root[F("u")];
if (!user.isNull()) {
JsonObject ssdrObj = user[FPSTR(_str_name)];
umSSDRDisplayTime = ssdrObj[FPSTR(_str_timeEnabled)] | umSSDRDisplayTime;
umSSDREnableLDR = ssdrObj[FPSTR(_str_ldrEnabled)] | umSSDREnableLDR;
umSSDRInverted = ssdrObj[FPSTR(_str_inverted)] | umSSDRInverted;
umSSDRColonblink = ssdrObj[FPSTR(_str_colonblink)] | umSSDRColonblink;
umSSDRDisplayMask = ssdrObj[FPSTR(_str_displayMask)] | umSSDRDisplayMask;
}
}
void onMqttConnect(bool sessionPresent) {
char subBuffer[48];
if (mqttDeviceTopic[0] != 0)
{
_updateMQTT();
//subscribe for sevenseg messages on the device topic
sprintf_P(subBuffer, PSTR("%s/%S/+/set"), mqttDeviceTopic, _str_name);
mqtt->subscribe(subBuffer, 2);
}
if (mqttGroupTopic[0] != 0)
{
//subcribe for sevenseg messages on the group topic
sprintf_P(subBuffer, PSTR("%s/%S/+/set"), mqttGroupTopic, _str_name);
mqtt->subscribe(subBuffer, 2);
}
}
bool onMqttMessage(char *topic, char *payload) {
//If topic beings iwth sevenSeg cut it off, otherwise not our message.
size_t topicPrefixLen = strlen_P(PSTR("/wledSS/"));
if (strncmp_P(topic, PSTR("/wledSS/"), topicPrefixLen) == 0) {
topic += topicPrefixLen;
} else {
return false;
}
//We only care if the topic ends with /set
size_t topicLen = strlen(topic);
if (topicLen > 4 &&
topic[topicLen - 4] == '/' &&
topic[topicLen - 3] == 's' &&
topic[topicLen - 2] == 'e' &&
topic[topicLen - 1] == 't')
{
//Trim /set and handle it
topic[topicLen - 4] = '\0';
_handleSetting(topic, payload);
}
return true;
}
void addToConfig(JsonObject &root) {
_addJSONObject(root);
}
bool readFromConfig(JsonObject &root) {
JsonObject top = root[FPSTR(_str_name)];
if (top.isNull()) {
DEBUG_PRINT(FPSTR(_str_name));
DEBUG_PRINTLN(F(": No config found. (Using defaults.)"));
return false;
}
umSSDRDisplayTime = (top[FPSTR(_str_timeEnabled)] | umSSDRDisplayTime);
umSSDREnableLDR = (top[FPSTR(_str_ldrEnabled)] | umSSDREnableLDR);
umSSDRInverted = (top[FPSTR(_str_inverted)] | umSSDRInverted);
umSSDRColonblink = (top[FPSTR(_str_colonblink)] | umSSDRColonblink);
umSSDRDisplayMask = top[FPSTR(_str_displayMask)] | umSSDRDisplayMask;
umSSDRHours = top[FPSTR(_str_hours)] | umSSDRHours;
umSSDRMinutes = top[FPSTR(_str_minutes)] | umSSDRMinutes;
umSSDRSeconds = top[FPSTR(_str_seconds)] | umSSDRSeconds;
umSSDRColons = top[FPSTR(_str_colons)] | umSSDRColons;
umSSDRDays = top[FPSTR(_str_days)] | umSSDRDays;
umSSDRMonths = top[FPSTR(_str_months)] | umSSDRMonths;
umSSDRYears = top[FPSTR(_str_years)] | umSSDRYears;
umSSDRBrightnessMin = top[FPSTR(_str_minBrightness)] | umSSDRBrightnessMin;
umSSDRBrightnessMax = top[FPSTR(_str_maxBrightness)] | umSSDRBrightnessMax;
DEBUG_PRINT(FPSTR(_str_name));
DEBUG_PRINTLN(F(" config (re)loaded."));
return true;
}
/*
* getId() allows you to optionally give your V2 usermod an unique ID (please define it in const.h!).
* This could be used in the future for the system to determine whether your usermod is installed.
*/
uint16_t getId() {
return USERMOD_ID_SSDR;
}
};
const char UsermodSSDR::_str_name[] PROGMEM = "UsermodSSDR";
const char UsermodSSDR::_str_timeEnabled[] PROGMEM = "enabled";
const char UsermodSSDR::_str_inverted[] PROGMEM = "inverted";
const char UsermodSSDR::_str_colonblink[] PROGMEM = "Colon-blinking";
const char UsermodSSDR::_str_displayMask[] PROGMEM = "Display-Mask";
const char UsermodSSDR::_str_hours[] PROGMEM = "LED-Numbers-Hours";
const char UsermodSSDR::_str_minutes[] PROGMEM = "LED-Numbers-Minutes";
const char UsermodSSDR::_str_seconds[] PROGMEM = "LED-Numbers-Seconds";
const char UsermodSSDR::_str_colons[] PROGMEM = "LED-Numbers-Colons";
const char UsermodSSDR::_str_days[] PROGMEM = "LED-Numbers-Day";
const char UsermodSSDR::_str_months[] PROGMEM = "LED-Numbers-Month";
const char UsermodSSDR::_str_years[] PROGMEM = "LED-Numbers-Year";
const char UsermodSSDR::_str_ldrEnabled[] PROGMEM = "enable-auto-brightness";
const char UsermodSSDR::_str_minBrightness[] PROGMEM = "auto-brightness-min";
const char UsermodSSDR::_str_maxBrightness[] PROGMEM = "auto-brightness-max";

View File

@ -64,6 +64,7 @@
#define USERMOD_ID_SEVEN_SEGMENT_DISPLAY 21 //Usermod "usermod_v2_seven_segment_display.h"
#define USERMOD_RGB_ROTARY_ENCODER 22 //Usermod "rgb-rotary-encoder.h"
#define USERMOD_ID_QUINLED_AN_PENTA 23 //Usermod "quinled-an-penta.h"
#define USERMOD_ID_SSDR 24 //Usermod "usermod_v2_seven_segment_display_reloaded.h"
//Access point behavior
#define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot

View File

@ -100,6 +100,10 @@
#include "../usermods/seven_segment_display/usermod_v2_seven_segment_display.h"
#endif
#ifdef USERMOD_SSDR
#include "../usermods/seven_segment_display_reloaded/usermod_seven_segment_reloaded.h"
#endif
#ifdef QUINLED_AN_PENTA
#include "../usermods/quinled-an-penta/quinled-an-penta.h"
#endif
@ -192,6 +196,10 @@ void registerUsermods()
usermods.add(new SevenSegmentDisplay());
#endif
#ifdef USERMOD_SSDR
usermods.add(new UsermodSSDR());
#endif
#ifdef QUINLED_AN_PENTA
usermods.add(new QuinLEDAnPentaUsermod());
#endif