Add PWM brightness control with 10-level adjustment

4-level to 10-level PWM backlight control (10%-100% in 10% steps),
persisted to NVS flash. Adds touch gesture (hold top/bottom zone 1.5s),
Device menu entry, and CLI command (brightness -c/-s <0-9>).

Replaces digitalWrite on/off with ledcWrite PWM for smooth dimming.

Closes justcallmekoko/ESP32Marauder#1091

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
datboip
2026-02-27 23:41:47 -05:00
parent 932384925b
commit 4ce3ee7812
5 changed files with 199 additions and 14 deletions

View File

@@ -1,5 +1,9 @@
#include "CommandLine.h"
// Brightness functions defined in esp32_marauder.ino
extern void brightnessCycle();
extern uint8_t getBrightnessLevel();
CommandLine::CommandLine() {
}
@@ -284,6 +288,7 @@ void CommandLine::runCommand(String input) {
#endif
Serial.println(HELP_BT_SKIM_CMD);
#endif
Serial.println(HELP_BRIGHTNESS_CMD);
Serial.println(HELP_FOOT);
return;
}
@@ -1381,6 +1386,28 @@ void CommandLine::runCommand(String input) {
#endif
}
// Brightness command
else if (cmd_args.get(0) == BRIGHTNESS_CMD) {
int c_arg = this->argSearch(&cmd_args, "-c");
int s_arg = this->argSearch(&cmd_args, "-s");
if (c_arg != -1) {
brightnessCycle();
} else if (s_arg != -1) {
uint8_t lvl = cmd_args.get(s_arg + 1).toInt();
if (lvl < 10) {
extern void brightnessSave(uint8_t level);
brightnessSave(lvl);
Serial.print(F("[Brightness] Set to level "));
Serial.println(lvl);
} else {
Serial.println(F("Level must be 0-9"));
}
} else {
Serial.print(F("[Brightness] Current level: "));
Serial.println(getBrightnessLevel());
}
}
// Update command
if (cmd_args.get(0) == UPDATE_CMD) {
//int w_sw = this->argSearch(&cmd_args, "-w"); // Web update

View File

@@ -195,6 +195,10 @@ const char PROGMEM HELP_BT_SPOOFAT_CMD[] = "spoofat -t <index>";
//onst char PROGMEM HELP_BT_SPAM_ALL_CMD[] = "btspamall";
const char PROGMEM HELP_BT_WARDRIVE_CMD[] = "btwardrive";
const char PROGMEM HELP_BT_SKIM_CMD[] = "sniffskim";
const char PROGMEM BRIGHTNESS_CMD[] = "brightness";
const char PROGMEM HELP_BRIGHTNESS_CMD[] = "brightness [-c cycle] [-s <0-9>]";
const char PROGMEM HELP_FOOT[] = "==================================";

View File

@@ -190,6 +190,33 @@ void MenuFunctions::main(uint32_t currentTime)
#endif
// Brightness gesture: hold top or bottom zone 1.5s to enter brightness mode
#ifdef HAS_ILI9341
if (pressed && (wifi_scan_obj.currentScanMode == WIFI_SCAN_OFF ||
wifi_scan_obj.currentScanMode == WIFI_CONNECTED)) {
uint16_t zoneUp = TFT_HEIGHT * 25 / 100;
uint16_t zoneDown = TFT_HEIGHT * 75 / 100;
if (t_y < zoneUp || t_y >= zoneDown) {
uint32_t hold_start = millis();
uint16_t hx, hy;
bool held = false;
while (display_obj.updateTouch(&hx, &hy)) {
if (millis() - hold_start >= 1500) {
held = true;
break;
}
delay(10);
}
if (held) {
// Wait for release before entering brightness mode
while (display_obj.updateTouch(&hx, &hy)) delay(10);
this->brightnessMode();
return;
}
}
}
#endif
// This is if there are scans/attacks going on
#ifdef HAS_ILI9341
if ((wifi_scan_obj.currentScanMode != WIFI_SCAN_OFF) &&
@@ -2682,6 +2709,10 @@ void MenuFunctions::RunSetup()
this->changeMenu(&saveFileMenu, true);
});
this->addNodes(&deviceMenu, "Brightness", TFTYELLOW, NULL, KEYBOARD_ICO, [this]() {
this->brightnessMode();
});
this->addNodes(&deviceMenu, text_table1[17], TFTWHITE, NULL, DEVICE_INFO, [this]() {
wifi_scan_obj.currentScanMode = SHOW_INFO;
this->changeMenu(&infoMenu, true);
@@ -3706,5 +3737,83 @@ void MenuFunctions::displayCurrentMenu(int start_index)
this->displayMenuButtons();
}
// ============================================================
// BRIGHTNESS ADJUSTMENT MODE
// Hold top/bottom zone 1.5s to enter. TAP TOP = brighter, TAP BOTTOM = dimmer.
// TAP MIDDLE or wait 3s = save & exit.
// ============================================================
void MenuFunctions::brightnessMode() {
extern void brightnessSave(uint8_t level);
extern uint8_t getBrightnessLevel();
const uint8_t levels[] = {26, 51, 77, 102, 128, 153, 179, 204, 230, 255};
const uint8_t numLevels = 10;
uint8_t level = getBrightnessLevel();
display_obj.tft.fillScreen(TFT_BLACK);
display_obj.tft.setTextColor(TFT_CYAN, TFT_BLACK);
display_obj.tft.drawCentreString("BRIGHTNESS", TFT_WIDTH/2, 30, 2);
display_obj.tft.setTextColor(TFT_DARKGREY, TFT_BLACK);
display_obj.tft.drawCentreString("TAP TOP = BRIGHTER", TFT_WIDTH/2, 10, 1);
display_obj.tft.drawCentreString("TAP BOTTOM = DIMMER", TFT_WIDTH/2, TFT_HEIGHT - 20, 1);
display_obj.tft.setTextColor(TFT_RED, TFT_BLACK);
display_obj.tft.drawCentreString("TAP MIDDLE or WAIT 3s = SAVE", TFT_WIDTH/2, TFT_HEIGHT/2 + 50, 1);
auto drawBar = [&]() {
uint16_t barX = 30, barY = TFT_HEIGHT/2 - 25, barW = TFT_WIDTH - 60, barH = 30;
display_obj.tft.drawRect(barX, barY, barW, barH, TFT_WHITE);
uint16_t fillW = (barW - 4) * (level + 1) / numLevels;
display_obj.tft.fillRect(barX + 2, barY + 2, barW - 4, barH - 4, TFT_BLACK);
display_obj.tft.fillRect(barX + 2, barY + 2, fillW, barH - 4, TFT_CYAN);
display_obj.tft.fillRect(0, barY + barH + 5, TFT_WIDTH, 20, TFT_BLACK);
display_obj.tft.setTextColor(TFT_WHITE, TFT_BLACK);
String pct = String(levels[level] * 100 / 255) + "%";
display_obj.tft.drawCentreString(pct, TFT_WIDTH/2, barY + barH + 8, 2);
};
drawBar();
uint16_t zoneUp = TFT_HEIGHT * 25 / 100;
uint16_t zoneDown = TFT_HEIGHT * 75 / 100;
uint32_t lastTouch = millis();
while (true) {
// Auto-save after 3s of no touch
if (millis() - lastTouch >= 3000) {
brightnessSave(level);
break;
}
uint16_t tx, ty;
if (display_obj.updateTouch(&tx, &ty)) {
lastTouch = millis();
// Wait for release
while (display_obj.updateTouch(&tx, &ty)) delay(10);
if (ty < zoneUp) {
if (level < numLevels - 1) {
level++;
ledcWrite(TFT_BL, levels[level]);
drawBar();
}
} else if (ty >= zoneDown) {
if (level > 0) {
level--;
ledcWrite(TFT_BL, levels[level]);
drawBar();
}
} else {
// Middle = save now
brightnessSave(level);
break;
}
delay(150);
}
delay(30);
}
this->changeMenu(current_menu, true);
}
#endif

View File

@@ -277,6 +277,7 @@ class MenuFunctions
void changeMenu(Menu* menu, bool simple_change = false);
void drawStatusBar();
void displayCurrentMenu(int start_index = 0);
void brightnessMode();
void main(uint32_t currentTime);
void RunSetup();
void orientDisplay();

View File

@@ -123,27 +123,69 @@ const String PROGMEM version_number = MARAUDER_VERSION;
uint32_t currentTime = 0;
// PWM Brightness Control
#ifdef HAS_SCREEN
#include <Preferences.h>
#define BL_CHANNEL 0
#define BL_FREQ 5000
#define BL_RESOLUTION 8
const uint8_t BL_LEVELS[] = {26, 51, 77, 102, 128, 153, 179, 204, 230, 255};
const uint8_t BL_NUM_LEVELS = 10;
uint8_t bl_level_idx = 9; // default full brightness
Preferences bl_prefs;
#endif
void brightnessInit() {
#ifdef HAS_SCREEN
ledcAttach(TFT_BL, BL_FREQ, BL_RESOLUTION);
bl_prefs.begin("backlight", false);
bl_level_idx = bl_prefs.getUChar("level", 9);
if (bl_level_idx >= BL_NUM_LEVELS) bl_level_idx = 9;
ledcWrite(TFT_BL, BL_LEVELS[bl_level_idx]);
#endif
}
void brightnessCycle() {
#ifdef HAS_SCREEN
bl_level_idx = (bl_level_idx + 1) % BL_NUM_LEVELS;
ledcWrite(TFT_BL, BL_LEVELS[bl_level_idx]);
bl_prefs.putUChar("level", bl_level_idx);
Serial.print(F("[Brightness] Level "));
Serial.print(bl_level_idx + 1);
Serial.print(F("/"));
Serial.print(BL_NUM_LEVELS);
Serial.print(F(" ("));
Serial.print(BL_LEVELS[bl_level_idx] * 100 / 255);
Serial.println(F("%)"));
#endif
}
uint8_t getBrightnessLevel() {
#ifdef HAS_SCREEN
return bl_level_idx;
#else
return 0;
#endif
}
void brightnessSave(uint8_t level) {
#ifdef HAS_SCREEN
if (level >= BL_NUM_LEVELS) level = BL_NUM_LEVELS - 1;
bl_level_idx = level;
ledcWrite(TFT_BL, BL_LEVELS[bl_level_idx]);
bl_prefs.putUChar("level", bl_level_idx);
#endif
}
void backlightOn() {
#ifdef HAS_SCREEN
#if defined(MARAUDER_MINI)
digitalWrite(TFT_BL, LOW);
#endif
#if !defined(MARAUDER_MINI)
digitalWrite(TFT_BL, HIGH);
#endif
ledcWrite(TFT_BL, BL_LEVELS[bl_level_idx]);
#endif
}
void backlightOff() {
#ifdef HAS_SCREEN
#if defined(MARAUDER_MINI)
digitalWrite(TFT_BL, HIGH);
#endif
#if !defined(MARAUDER_MINI)
digitalWrite(TFT_BL, LOW);
#endif
ledcWrite(TFT_BL, 0);
#endif
}
@@ -235,6 +277,8 @@ void setup()
display_obj.tft.setTextColor(TFT_WHITE, TFT_BLACK);
#endif
// Init PWM brightness AFTER display init (so ledcAttach overrides TFT_eSPI's pinMode)
brightnessInit();
backlightOff();
#ifdef HAS_SCREEN