Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions usermods/OLED_72x40/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# WLED Usermod: OLED 72x40 (SSD1306)

A custom WLED usermod designed for the **ESP32-C3 Super Mini** development board featuring an integrated **0.42-inch OLED display** (72x40 pixels).

## 📱 About This Device

This specific board is a compact IoT platform based on the **ESP32-C3FN4/FH4** with built-in 4MB Flash, Wi-Fi, and Bluetooth 5.0. It is widely used for teeny-tiny projects like telemetry displays, altitude trackers, or localized status monitors.

![ESP32-C3 Super Mini](esp32-c3-super-mini.png)
*Image source: AlexYeryomin/ESP32C3-OLED-72x40*

### Technical Specifications

* **Controller:** ESP32-C3
* **OLED Resolution:** 72x40 pixels (Effective area)
* **Driver:** SSD1306 (requires 128x64 driver initialization with specific offsets to avoid clipping)
* **I2C Pins:** SDA (GPIO 5), SCL (GPIO 6)
* **Onboard LED:** GPIO 8 (used for heartbeat status)
* **Function Button:** GPIO 9 (normally used for Bootloader mode), assigned to toggling brightness between 0% and current value.

---

## 🛠 Features & Functions

### 1. Adaptive Dashboard

The usermod displays a real-time WLED status dashboard including:

* **Effect Name:** Shows the current WLED mode/effect.
* **Brightness Visualizer:** A horizontal scrolling graph showing brightness (`bri`) levels over time.
* **System Stats:** Quick view of Speed (S), Intensity (I), and Brightness (B) percentages.

### 2. Smart Coordinate Mapping (Flipping Support)

Because this 72x40 display uses a 128x64 driver, normal library rotations can cause the image to "fall off" the physical glass. This usermod includes **Dynamic Offset Calculation**:

* **Normal Mode:** Centers the 72x40 UI in the top-left area.
* **Flipped Mode:** Automatically shifts coordinates to the bottom-right of the 128x64 memory buffer so the UI remains perfectly centered and visible when rotated 180°.

### 3. LED Heartbeat & Network Status

The onboard **GPIO 8** LED provides visual feedback of the device's connectivity:

* **Blinking:** The device is disconnected from the network.
* **Pulsing (Sinusoidal):** The device is successfully connected to WiFi.

### 4. Splash Screen & Info

Upon boot, the display shows the **Akemi Logo** and current **mDNS name or IP Address** for 5 seconds to help you locate the device on your network.

### 5. Configurable Sleep Timer

To prevent OLED burn-in, the screen automatically blanks after a period of inactivity (default: 60 seconds). It wakes up instantly when:

* The physical button is pressed.
* Settings are changed via the WLED Web UI or API.

---

## ⚙️ Configuration

The following settings are available directly in the **WLED Usermod Settings** page (v0.15.3+):

* **Enabled:** Toggle the OLED on/off.
* **Flip Display:** Rotates the UI 180° and adjusts internal offsets.
* **X/Y-Offset:** Fine-tune the UI position on your specific glass.
* **Sleep Timeout:** Set how many seconds to wait before the screen turns off.

## 🔗 References & Inspiration

* [AlexYeryomin/ESP32C3-OLED-72x40](https://github.com/AlexYeryomin/ESP32C3-OLED-72x40) - Driver implementation and original Micropython demo.
* [Kevin's Blog: ESP32-C3 0.42 OLED](https://emalliab.wordpress.com/2025/02/12/esp32-c3-0-42-oled/) - Deep dive into coordinate offsets and hardware constraints.
234 changes: 234 additions & 0 deletions usermods/OLED_72x40/UsermodOLED72x40.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
#pragma once

#include "wled.h"
#include <U8g2lib.h>
#include <Wire.h>

#define OLED_SDA 5
#define OLED_SCL 6
#define LED_PIN 8

// 128x40 bitmap generated by LCD Matrix Studio
const unsigned char akemi_logo [] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xC0, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x07, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x06, 0x7E, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x3C, 0x60, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x1C, 0x18, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x1C, 0x18, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x18, 0x20, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x1E, 0x3C, 0x60, 0x00, 0x38, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xE0, 0x00, 0x0C, 0x00, 0x80, 0x01,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xE0, 0x00,
0x02, 0x00, 0xC0, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x1F, 0xC3, 0xE0, 0x00, 0x01, 0x00, 0x43, 0xC3, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x1F, 0xE7, 0xE0, 0x00, 0x01, 0x02, 0x46, 0x43,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xE0, 0x00,
0x01, 0x02, 0x44, 0x4F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
0x1F, 0xFF, 0xE0, 0x01, 0x01, 0x02, 0x45, 0xC9, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x38, 0x1F, 0xFF, 0xE0, 0x1C, 0x01, 0x32, 0x47, 0x09,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x20, 0x00, 0x00, 0x10,
0x01, 0x12, 0x42, 0x0D, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C,
0x30, 0x00, 0x00, 0x30, 0x01, 0xD2, 0x4B, 0x07, 0xE0, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x0C, 0x2E, 0xE7, 0x70, 0x30, 0x00, 0xDE, 0x79, 0xE0,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x06, 0xE7, 0x60, 0x60,
0x00, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06,
0x06, 0xC6, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, 0x08, 0xC6, 0x30, 0xC0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x10, 0xC6, 0x08, 0xC0,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
0xE0, 0xC6, 0x0F, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xE0, 0xC6, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC6, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xC6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xC6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC6, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0xC6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00
};


class UsermodOLED72x40 : public Usermod {
private:
U8G2_SSD1306_128X64_NONAME_F_HW_I2C* u8g2 = nullptr;
bool enabled = true, initDone = false, displayOff = false;
unsigned long lastUpdate = 0, lastInteraction = 0;
unsigned long screenTimeoutMS = 60000;
int xOff = 28, yOff = 24;
byte vValues[72];
bool flipDisplay = false;

public:
void setup() {
pinMode(LED_PIN, OUTPUT);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please use pin manager to check if the pin is free before writing to it.

Wire.begin(OLED_SDA, OLED_SCL);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should not be necessary, if you use the usermod global I2C pins.

WiFi.setTxPower(WIFI_POWER_8_5dBm);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why?

Copy link
Author

@doccaz doccaz Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are known issues with the WiFi in this board that can make it quite unuseable. But it doesn't happen on all environments, maybe this should be a configuration option, I guess.
Reference: https://www.youtube.com/watch?v=UHTdhCrSA3g

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah ok, actually its already an option, so you don't need to repeat that in your UM

WLED/wled00/wled.h

Lines 379 to 380 in 6b953d9

#if defined(LOLIN_WIFI_FIX) && (defined(CONFIG_IDF_TARGET_ESP32C3) || defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3))
WLED_GLOBAL uint8_t txPower _INIT(WIFI_POWER_8_5dBm);

just add -DLOLIN_WIFI_FIX to your build flags.

// Wire.setClock(100000); // Set to 100kHz (Standard Mode) to avoid frequent connection issues
u8g2 = new U8G2_SSD1306_128X64_NONAME_F_HW_I2C(U8G2_R0, U8X8_PIN_NONE);

if (u8g2->begin()) {
u8g2->setContrast(255);
u8g2->setFlipMode(flipDisplay ? 1 : 0);
memset(vValues, 0, sizeof(vValues));
u8g2->clearBuffer();

// FIXED X-OFFSET FOR SPLASH
// Since akemi_logo is 128px wide (16 bytes), we don't shift X.
// We only shift Y to align the 40px height within the 64px buffer.
int splashX = 0;
int splashY = flipDisplay ? (64 - 40 - yOff) : yOff;

// Draw the 128x40 bitmap
u8g2->drawBitmap(splashX, splashY, 16, 40, akemi_logo);

// The IP address/mDNS text still needs the relative shift to stay
// centered under the logo area
int textX = flipDisplay ? (128 - 72 - xOff) : xOff;

u8g2->setFont(u8g2_font_4x6_tf);
u8g2->setCursor(textX + 12, splashY + 38);

if (Network.isConnected()) u8g2->print(Network.localIP());
else u8g2->print(cmDNS);

u8g2->sendBuffer();
delay(5000);
Copy link
Member

@softhack007 softhack007 Feb 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, such a long delay in the usermod setup function is a no-go.
This will delay the main loop for 5 seconds. Please find a different way.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. It was just for showing a silly "bootsplash", I'll think of some other way.

lastInteraction = millis();
initDone = true;
}
}

void loop() {
// 1. LED HEARTBEAT (Always runs)
static unsigned long ledTimer = 0;
if (!Network.isConnected()) {
if (millis() - ledTimer > 200) { ledTimer = millis(); digitalWrite(LED_PIN, !digitalRead(LED_PIN)); }
} else {
float pulse = (sin(millis() / 2000.0 * PI) + 1) * 127.5;
analogWrite(LED_PIN, (int)pulse);
}

if (!enabled || !initDone) return;

// 2. TIMEOUT LOGIC (Moved before the strip updating guard)
if (screenTimeoutMS > 0 && (millis() - lastInteraction > screenTimeoutMS)) {
if (!displayOff) { u8g2->setPowerSave(1); displayOff = true; }
return;
}

// 3. DRAWING LOGIC (Bypassed if screen is blanked)
if (strip.isUpdating()) return;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this might stall your usermod completely if someone has lots of LEDs. better:
if (strip.isUpdating() && (millis() - lastUpdate < 100)) return;

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted! Thanks for taking the time to review this so thoroughly. First time contributing to WLED.


if (millis() - lastUpdate > 100) {
lastUpdate = millis();
if (displayOff) { u8g2->setPowerSave(0); displayOff = false; }

u8g2->clearBuffer();

// ADJUST OFFSETS BASED ON FLIP
// Normal: xOff=28, yOff=24
// Flipped: We need to shift the content to the opposite side of the 128x64 buffer
int currentX = flipDisplay ? (128 - 72 - xOff) : xOff;
int currentY = flipDisplay ? (64 - 40 - yOff) : yOff;

u8g2->setFont(u8g2_font_5x7_tf);
char lineBuffer[17];
extractModeName(effectCurrent, JSON_mode_names, lineBuffer, 16);
u8g2->drawStr(currentX, currentY + 7, lineBuffer);
u8g2->drawHLine(currentX, currentY + 9, 72);

for (int i = 0; i < 71; i++) vValues[i] = vValues[i+1];
vValues[71] = map(bri, 0, 255, 0, 15);
for (int i = 0; i < 71; i++) {
u8g2->drawLine(currentX + i, currentY + 26, currentX + i, currentY + 26 - vValues[i]);
}
Comment on lines +159 to +163
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Brightness graph drops the newest sample.

vValues[71] is populated but never rendered.

🐛 Proposed fix
-          for (int i = 0; i < 71; i++) {
+          for (int i = 0; i < 72; i++) {
               u8g2->drawLine(currentX + i, currentY + 26, currentX + i, currentY + 26 - vValues[i]);
           }
🤖 Prompt for AI Agents
In `@usermods/OLED_72x40/UsermodOLED72x40.h` around lines 159 - 163, The
brightness graph code fills vValues[71] but the drawing loop only iterates i <
71 so the newest sample at index 71 is never rendered; update the render loop in
the UsermodOLED72x40 (the block that calls u8g2->drawLine with currentX/currentY
and vValues[i]) to include index 71 (e.g., iterate i < 72 or i <= 71) so
vValues[71] is drawn, keeping the existing shift logic (for (int i = 0; i < 71;
i++) vValues[i] = vValues[i+1]) and the assignment vValues[71] = map(bri, 0,
255, 0, 15).


u8g2->setFont(u8g2_font_4x6_tf);
u8g2->setCursor(currentX, currentY + 38);
u8g2->print("S:"); u8g2->print(map(effectSpeed, 0, 255, 0, 99)); u8g2->print("% ");
u8g2->print("I:"); u8g2->print(map(effectIntensity, 0, 255, 0, 99)); u8g2->print("% ");
u8g2->print("B:"); u8g2->print(map(bri, 0, 255, 0, 100)); u8g2->print("%");

u8g2->sendBuffer();
}
}

void onStateChange(uint8_t mode) {
lastInteraction = millis();
lastUpdate = 0;
if (displayOff && u8g2) { u8g2->setPowerSave(0); displayOff = false; }
}

bool handleButton(uint8_t b) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will react on all buttons defined in the LEDs setting page, is this what you want?

lastInteraction = millis();
if (displayOff && u8g2) { u8g2->setPowerSave(0); displayOff = false; }
return false;
}

void appendConfigData(JsonArray &config) {
JsonObject top = config.createNestedObject();
top[F("usermod")] = F("OLED_72x40");

// This creates the UI elements in the Usermod settings page
config.createNestedObject()[F("OLED_72x40:enabled")];
config.createNestedObject()[F("OLED_72x40:flipDisplay")];
config.createNestedObject()[F("OLED_72x40:x-offset")];
config.createNestedObject()[F("OLED_72x40:y-offset")];
config.createNestedObject()[F("OLED_72x40:sleepTimeout")];
config.createNestedObject()[F("OLED_72x40:bootButtonPin")];
}
void addToConfig(JsonObject& root) {
JsonObject top = root.createNestedObject(F("OLED_72x40"));
top[F("enabled")] = enabled;
top[F("flipDisplay")] = flipDisplay; // Registration for UI
top["bootButtonPin"] = 9;
top["x-offset"] = xOff;
top["y-offset"] = yOff;
top["sleepTimeout"] = screenTimeoutMS / 1000;
}

bool readFromConfig(JsonObject& root) {
JsonObject top = root[F("OLED_72x40")];
if (top.isNull()) return false;

enabled = top[F("enabled")] | enabled;
flipDisplay = top[F("flipDisplay")] | flipDisplay;
xOff = top[F("x-offset")] | 28;
yOff = top[F("y-offset")] | 24;

// Guard against 0 or negative timeout values
unsigned long timeoutSec = top[F("sleepTimeout")] | 60;
screenTimeoutMS = timeoutSec * 1000;

if (initDone && u8g2) u8g2->setFlipMode(flipDisplay ? 1 : 0);

// Apply the button pin 9 default logic
int pin = top[F("bootButtonPin")] | 9;
if (btnPin[0] == -1 || btnPin[0] != pin) {
btnPin[0] = pin;
Copy link
Member

@softhack007 softhack007 Feb 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

usermods should not interfere with the button handling code in button.cpp.
Its OK to read from the global btnPin[] and buttonType[] arrays. However modifying these arrays "behind the back" of the WLED core invites trouble.

Edit: It might be better to just make the "button index" (from LEDs preferences) configurable in your UM settings, and leave the button handling (and de-bouncing) to the wled core. You'll still receive the handleButton(b) event, and you could simply compare if b is the configured button index.

buttonType[0] = BTN_TYPE_PUSH;
}
Comment on lines +199 to +229
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

bootButtonPin setting is never persisted.

addToConfig() always writes 9, so UI changes are lost on save.

🛠️ Proposed fix
-      top["bootButtonPin"] = 9;
+      top["bootButtonPin"] = bootButtonPin;
@@
-      int pin = top[F("bootButtonPin")] | 9;
-      if (btnPin[0] == -1 || btnPin[0] != pin) {
-        btnPin[0] = pin;
+      bootButtonPin = top[F("bootButtonPin")] | bootButtonPin;
+      if (btnPin[0] == -1 || btnPin[0] != bootButtonPin) {
+        btnPin[0] = bootButtonPin;
         buttonType[0] = BTN_TYPE_PUSH;
       }
// Add alongside other member fields
int bootButtonPin = 9;
🤖 Prompt for AI Agents
In `@usermods/OLED_72x40/UsermodOLED72x40.h` around lines 199 - 229, The
addToConfig() currently writes a literal 9 so UI changes are never persisted;
introduce a class member int bootButtonPin = 9 and use that member in
addToConfig() instead of the hardcoded 9, update readFromConfig() to read into
bootButtonPin (e.g. bootButtonPin = top["bootButtonPin"] | 9) and then use
bootButtonPin when assigning btnPin[0] (instead of reading pin from config and
not saving it back to the member), ensuring the new member is declared in the
UsermodOLED72x40 class so the value is preserved across config read/write and
used when initializing buttonType and btnPin[0].

return true;
}

uint16_t getId() { return USERMOD_ID_OLED_72x40; }
};
Binary file added usermods/OLED_72x40/esp32-c3-super-mini.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions wled00/const.h
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@
#define USERMOD_ID_LD2410 52 //Usermod "usermod_ld2410.h"
#define USERMOD_ID_POV_DISPLAY 53 //Usermod "usermod_pov_display.h"
#define USERMOD_ID_PIXELS_DICE_TRAY 54 //Usermod "pixels_dice_tray.h"
#define USERMOD_ID_OLED_72x40 55 //Usermod "OLED_72x40.h"

//Access point behavior
#define AP_BEHAVIOR_BOOT_NO_CONN 0 //Open AP when no connection after boot
Expand Down
8 changes: 8 additions & 0 deletions wled00/usermods_list.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,10 @@
#include "../usermods/Internal_Temperature_v2/usermod_internal_temperature.h"
#endif

#ifdef USERMOD_OLED_72x40
#include "../usermods/OLED_72x40/UsermodOLED72x40.h"
#endif

#if defined(WLED_USE_SD_MMC) || defined(WLED_USE_SD_SPI)
// This include of SD.h and SD_MMC.h must happen here, else they won't be
// resolved correctly (when included in mod's header only)
Expand Down Expand Up @@ -470,4 +474,8 @@ void registerUsermods()
#ifdef USERMOD_POV_DISPLAY
UsermodManager::add(new PovDisplayUsermod());
#endif

#ifdef USERMOD_OLED_72x40
UsermodManager::add(new UsermodOLED72x40());
#endif
}