Skip to content

Commit f6c3f4e

Browse files
fhirschmannclaude
andcommitted
feat: selectable idle animation (standard or cyberpunk Data Drop)
Add an option in the LED settings to choose the idle animation between the standard ESPuino idle dots and the cyberpunk 'Data Drop' animation. Animation_Idle dispatches on a new gLedSettings.idleAnimation value loaded from NVS (key 'idleAnim'); the standard idle dots logic is restored for that path. Persisted via the web interface and exposed in the settings JSON. Defaults to standard, so a freshly flashed device starts with the standard animation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent b49f131 commit f6c3f4e

8 files changed

Lines changed: 104 additions & 51 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ neon logo that doubles as the SVG favicon ([`8d9725c`](../../commit/8d9725c)):
9999
| Configurable idle LED and progress bar colors | [`bdc54e5`](../../commit/bdc54e5) |
100100
| Ready sound on cold start | [`c051c40`](../../commit/c051c40) |
101101
| Cyberpunk "Data Drop" idle LED animation | [`f20b111`](../../commit/f20b111) |
102+
| Selectable idle animation (standard idle dots or cyberpunk "Data Drop") in the LED settings; defaults to standard | [`7174669`](../../commit/7174669) |
102103
| Improved button responsiveness and track navigation seek options | [`76e1535`](../../commit/76e1535) |
103104
| Unlocking controls via button press while locked | [`d83e15f`](../../commit/d83e15f) |
104105
| Support for a 6th button | [`b116151`](../../commit/b116151) |

html/locales/de.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@
4646
"controlled": "Kontroll-LED",
4747
"numidledots": "Anzahl LEDs im Leerlauf",
4848
"idlecolor": "Farbe der Leerlauf-LEDs",
49+
"idleanimation": "Leerlauf-Animation",
50+
"idleanim": {
51+
"standard": "Standard",
52+
"cyberpunk": "Cyberpunk (Data Drop)"
53+
},
4954
"progcolorstart": "Startfarbe Fortschrittsanzeige",
5055
"progcolorend": "Endfarbe Fortschrittsanzeige",
5156
"offsetpause": "Pausen-LEDs zentrieren",

html/locales/en.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@
4646
"controlled": "Control-LED",
4747
"numidledots": "Number of LEDs in idle state",
4848
"idlecolor": "Color of the idle dots",
49+
"idleanimation": "Idle animation",
50+
"idleanim": {
51+
"standard": "Standard",
52+
"cyberpunk": "Cyberpunk (Data Drop)"
53+
},
4954
"progcolorstart": "Start color of progress bar",
5055
"progcolorend": "End color of progress bar",
5156
"offsetpause": "Center pause LEDs",

html/locales/fr.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@
4646
"controlled": "LED de contrôle",
4747
"numidledots": "Nombre de LED en état de veille",
4848
"idlecolor": "Couleur des LED en veille",
49+
"idleanimation": "Animation de veille",
50+
"idleanim": {
51+
"standard": "Standard",
52+
"cyberpunk": "Cyberpunk (Data Drop)"
53+
},
4954
"progcolorstart": "Couleur de début du progrès",
5055
"progcolorend": "Couleur de fin du progrès",
5156
"offsetpause": "LED pause centre",

html/management.html

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2246,6 +2246,11 @@ <h5 class="modal-title" data-i18n="tools.nvs.erase.title"></h5>
22462246
max="128"><br>
22472247
<label for="led_idleColor" data-i18n="settingsex.led.idlecolor"></label>: <br>
22482248
<input type="color" class="form-control" id="led_idleColor" name="led_idleColor"><br>
2249+
<label for="led_idleAnimation" data-i18n="settingsex.led.idleanimation"></label>: <br>
2250+
<select class="form-control form-select" id="led_idleAnimation" name="led_idleAnimation">
2251+
<option value="0" data-i18n="settingsex.led.idleanim.standard"></option>
2252+
<option value="1" data-i18n="settingsex.led.idleanim.cyberpunk"></option>
2253+
</select><br>
22492254
<label for="led_progColorStart" data-i18n="settingsex.led.progcolorstart"></label>: <br>
22502255
<input type="color" class="form-control" id="led_progColorStart" name="led_progColorStart"><br>
22512256
<label for="led_progColorEnd" data-i18n="settingsex.led.progcolorend"></label>: <br>
@@ -3755,6 +3760,7 @@ <h5 class="modal-title" data-i18n="tools.nvs.erase.title"></h5>
37553760
listControlColors(ledSettings.numControl, ledSettings.controlColors);
37563761
document.getElementById('led_idleDots').value = ledSettings.numIdleDots;
37573762
document.getElementById('led_idleColor').value = getHexColor(ledSettings.idleColor);
3763+
document.getElementById('led_idleAnimation').value = ledSettings.idleAnimation != null ? ledSettings.idleAnimation : 0;
37583764
document.getElementById('led_progColorStart').value = getHexColor(ledSettings.progColorStart);
37593765
document.getElementById('led_progColorEnd').value = getHexColor(ledSettings.progColorEnd);
37603766
$('#led_hueAtmo').bootstrapSlider('setValue', ledSettings.hueAtmo);
@@ -4181,6 +4187,7 @@ <h5 class="modal-title" data-i18n="tools.nvs.erase.title"></h5>
41814187
controlColors: [],
41824188
numIdleDots: Number(document.getElementById("led_idleDots").value),
41834189
idleColor: hexColorToInt(document.getElementById("led_idleColor").value),
4190+
idleAnimation: Number(document.getElementById("led_idleAnimation").value),
41844191
progColorStart: hexColorToInt(document.getElementById("led_progColorStart").value),
41854192
progColorEnd: hexColorToInt(document.getElementById("led_progColorEnd").value),
41864193
hueAtmo: Number($('#led_hueAtmo').val()),

src/Led.cpp

Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,10 @@ bool Led_LoadSettings(LedSettings &settings) {
111111
// Get the idle color from NVS
112112
settings.idleColor = gPrefsSettings.getUInt("idleColor", IDLE_COLOR);
113113

114+
// Get the idle animation style from NVS (0 = standard idle dots, 1 = cyberpunk "Data Drop").
115+
// Defaults to standard so a freshly flashed device starts with the standard animation.
116+
settings.idleAnimation = gPrefsSettings.getUChar("idleAnim", 0);
117+
114118
// Get offset LED pause from NVS
115119
settings.offsetLedPause = gPrefsSettings.getBool("offsetPause", OFFSET_PAUSE_LEDS);
116120

@@ -373,7 +377,7 @@ CRGB::HTMLColorCode Led_GetIdleColor() {
373377
} else {
374378
idleColor = CRGB::Green;
375379
if (Wlan_IsConnected()) {
376-
idleColor = (CRGB::HTMLColorCode)gLedSettings.idleColor;
380+
idleColor = (CRGB::HTMLColorCode) gLedSettings.idleColor;
377381
}
378382
}
379383
}
@@ -925,26 +929,48 @@ AnimationReturnType Animation_Rewind(const bool startNewAnimation, CRGBSet &leds
925929
// --------------------------------
926930
AnimationReturnType Animation_Idle(const bool startNewAnimation, CRGBSet &leds) {
927931
// return values
928-
int32_t animationDelay = 120;
932+
int32_t animationDelay = 0;
929933
bool animationActive = true;
930934
bool animationRefresh = true;
931935

932-
// static vars
933-
static int16_t step = 0;
934-
935-
if (startNewAnimation) {
936-
step = 0;
937-
}
938-
939-
CRGB idleColor = (CRGB)Led_GetIdleColor();
940-
941-
leds.fadeToBlackBy(40);
942-
if (leds.size() > 0) {
943-
int16_t idx = (leds.size() - 1) - (step % leds.size());
944-
if (idx >= 0 && idx < leds.size()) {
945-
leds[idx] = idleColor;
936+
if (gLedSettings.idleAnimation == 1) {
937+
// Cyberpunk "Data Drop": a single dot travels along the ring, leaving a fading trail
938+
static int16_t step = 0;
939+
if (startNewAnimation) {
940+
step = 0;
941+
}
942+
CRGB idleColor = (CRGB) Led_GetIdleColor();
943+
leds.fadeToBlackBy(40);
944+
if (leds.size() > 0) {
945+
int16_t idx = (leds.size() - 1) - (step % leds.size());
946+
if (idx >= 0 && idx < leds.size()) {
947+
leds[idx] = idleColor;
948+
}
949+
step = (step + 1) % leds.size();
950+
}
951+
animationDelay = 120;
952+
} else {
953+
// Standard ESPuino idle animation: evenly spaced idle dots rotating around the ring
954+
static int16_t ledIndex = 0;
955+
if (startNewAnimation) {
956+
ledIndex = 0;
957+
}
958+
if (ledIndex < leds.size()) {
959+
CRGB::HTMLColorCode idleColor = Led_GetIdleColor();
960+
leds = CRGB::Black;
961+
Led_DrawIdleDots(leds, ledIndex, idleColor);
962+
if (OPMODE_BLUETOOTH_SOURCE == System_GetOperationMode()) {
963+
// animate a bit faster in BT-Source to distinguish between the bluetooth modes
964+
animationDelay = 30 * 10;
965+
} else {
966+
animationDelay = 50 * 10;
967+
}
968+
ledIndex++;
969+
} else {
970+
animationActive = false;
971+
animationRefresh = false;
972+
ledIndex = 0;
946973
}
947-
step = (step + 1) % leds.size();
948974
}
949975

950976
return AnimationReturnType(animationActive, animationDelay, animationRefresh);
@@ -1037,7 +1063,7 @@ AnimationReturnType Animation_Progress(const bool startNewAnimation, CRGBSet &le
10371063
lastPos = gPlayProperties.currentRelPos;
10381064
leds = CRGB::Black;
10391065
if (gLedSettings.numIndicatorLeds == 1) {
1040-
leds[0] = blend((CRGB)gLedSettings.progressColorStart, (CRGB)gLedSettings.progressColorEnd, (uint8_t)(gPlayProperties.currentRelPos * 255 / 100));
1066+
leds[0] = blend((CRGB) gLedSettings.progressColorStart, (CRGB) gLedSettings.progressColorEnd, (uint8_t) (gPlayProperties.currentRelPos * 255 / 100));
10411067
} else {
10421068
const uint32_t ledValue = std::clamp<uint32_t>(map(gPlayProperties.currentRelPos, 0, 98, 0, leds.size() * gLedSettings.dimmableStates), 0, leds.size() * gLedSettings.dimmableStates);
10431069
const uint8_t fullLeds = ledValue / gLedSettings.dimmableStates;
@@ -1046,14 +1072,14 @@ AnimationReturnType Animation_Progress(const bool startNewAnimation, CRGBSet &le
10461072
if (System_AreControlsLocked()) {
10471073
leds[Led_Address(led)] = CRGB::Red;
10481074
} else if (!gPlayProperties.pausePlay) {
1049-
leds[Led_Address(led)] = blend((CRGB)gLedSettings.progressColorStart, (CRGB)gLedSettings.progressColorEnd, (led * 255) / (leds.size() - 1));
1075+
leds[Led_Address(led)] = blend((CRGB) gLedSettings.progressColorStart, (CRGB) gLedSettings.progressColorEnd, (led * 255) / (leds.size() - 1));
10501076
}
10511077
}
10521078
if (lastLed > 0) {
10531079
if (System_AreControlsLocked()) {
10541080
leds[Led_Address(fullLeds)] = CRGB::Red;
10551081
} else {
1056-
leds[Led_Address(fullLeds)] = blend((CRGB)gLedSettings.progressColorStart, (CRGB)gLedSettings.progressColorEnd, (fullLeds * 255) / (leds.size() - 1));
1082+
leds[Led_Address(fullLeds)] = blend((CRGB) gLedSettings.progressColorStart, (CRGB) gLedSettings.progressColorEnd, (fullLeds * 255) / (leds.size() - 1));
10571083
}
10581084
leds[Led_Address(fullLeds)] = Led_DimColor(leds[Led_Address(fullLeds)], lastLed);
10591085
}

src/Led.h

Lines changed: 32 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -47,40 +47,41 @@ typedef struct {
4747

4848
#include <FastLED.h>
4949

50-
enum class LedPlaylistProgressStates : uint8_t {
51-
FillBar = 0,
52-
Wait,
53-
EmptyBar,
54-
EmptyBarToTarget,
55-
Done
56-
};
50+
enum class LedPlaylistProgressStates : uint8_t {
51+
FillBar = 0,
52+
Wait,
53+
EmptyBar,
54+
EmptyBarToTarget,
55+
Done
56+
};
5757

58-
struct LedSettings {
59-
uint8_t numIndicatorLeds = NUM_INDICATOR_LEDS;
60-
uint8_t numControlLeds = NUM_CONTROL_LEDS;
61-
std::vector<uint32_t> controlLedColors;
62-
uint8_t numIdleDots = NUM_LEDS_IDLE_DOTS;
63-
uint32_t idleColor = IDLE_COLOR;
64-
bool offsetLedPause = OFFSET_PAUSE_LEDS;
65-
uint32_t progressColorStart = PROGRESS_COLOR_START;
66-
uint32_t progressColorEnd = PROGRESS_COLOR_END;
67-
int16_t atmoHue = ATMO_HUE;
68-
int16_t atmoSaturation = ATMO_SATURATION;
69-
uint8_t dimmableStates = DIMMABLE_STATES;
70-
bool neopixelReverseRotation;
71-
uint8_t ledOffset;
72-
bool Led_Pause;
58+
struct LedSettings {
59+
uint8_t numIndicatorLeds = NUM_INDICATOR_LEDS;
60+
uint8_t numControlLeds = NUM_CONTROL_LEDS;
61+
std::vector<uint32_t> controlLedColors;
62+
uint8_t numIdleDots = NUM_LEDS_IDLE_DOTS;
63+
uint32_t idleColor = IDLE_COLOR;
64+
uint8_t idleAnimation = 0; // 0 = standard idle dots, 1 = cyberpunk "Data Drop"
65+
bool offsetLedPause = OFFSET_PAUSE_LEDS;
66+
uint32_t progressColorStart = PROGRESS_COLOR_START;
67+
uint32_t progressColorEnd = PROGRESS_COLOR_END;
68+
int16_t atmoHue = ATMO_HUE;
69+
int16_t atmoSaturation = ATMO_SATURATION;
70+
uint8_t dimmableStates = DIMMABLE_STATES;
71+
bool neopixelReverseRotation;
72+
uint8_t ledOffset;
73+
bool Led_Pause;
7374

74-
bool Led_NightMode = false;
75-
bool Led_AmbientLight = false;
76-
uint8_t Led_InitialBrightness = LED_INITIAL_BRIGHTNESS;
77-
uint8_t Led_Brightness = LED_INITIAL_BRIGHTNESS;
78-
uint8_t Led_NightBrightness = LED_INITIAL_NIGHT_BRIGHTNESS;
79-
uint8_t Led_AmbientBrightness = LED_INITIAL_BRIGHTNESS;
80-
};
75+
bool Led_NightMode = false;
76+
bool Led_AmbientLight = false;
77+
uint8_t Led_InitialBrightness = LED_INITIAL_BRIGHTNESS;
78+
uint8_t Led_Brightness = LED_INITIAL_BRIGHTNESS;
79+
uint8_t Led_NightBrightness = LED_INITIAL_NIGHT_BRIGHTNESS;
80+
uint8_t Led_AmbientBrightness = LED_INITIAL_BRIGHTNESS;
81+
};
8182

82-
bool Led_GetNightmode();
83-
bool Led_GetAmbientLight();
83+
bool Led_GetNightmode();
84+
bool Led_GetAmbientLight();
8485
#endif
8586

8687
void Led_Init(void);

src/Web.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -949,6 +949,7 @@ WebsocketCodeType JSONToSettings(JsonObject doc) {
949949
success = success && (gPrefsSettings.putUChar("numControl", ledObj["numControl"].as<uint8_t>()) != 0);
950950
success = success && (gPrefsSettings.putUChar("numIdleDots", ledObj["numIdleDots"].as<uint8_t>()) != 0);
951951
success = success && (gPrefsSettings.putUInt("idleColor", ledObj["idleColor"].as<uint32_t>()) != 0);
952+
success = success && (gPrefsSettings.putUChar("idleAnim", ledObj["idleAnimation"].as<uint8_t>()) != 0);
952953
success = success && (gPrefsSettings.putBool("offsetPause", ledObj["offsetPause"].as<bool>()) != 0);
953954
success = success && (gPrefsSettings.putUInt("progColorStart", ledObj["progColorStart"].as<uint32_t>()) != 0);
954955
success = success && (gPrefsSettings.putUInt("progColorEnd", ledObj["progColorEnd"].as<uint32_t>()) != 0);
@@ -1308,6 +1309,7 @@ static void settingsToJSON(JsonObject obj, const String section) {
13081309
}
13091310
ledObj["numIdleDots"].set(gPrefsSettings.getUChar("numIdleDots", NUM_LEDS_IDLE_DOTS));
13101311
ledObj["idleColor"].set(gPrefsSettings.getUInt("idleColor", IDLE_COLOR));
1312+
ledObj["idleAnimation"].set(gPrefsSettings.getUChar("idleAnim", 0));
13111313
ledObj["offsetPause"].set(gPrefsSettings.getBool("offsetPause", OFFSET_PAUSE_LEDS));
13121314
ledObj["progColorStart"].set(gPrefsSettings.getUInt("progColorStart", PROGRESS_COLOR_START));
13131315
ledObj["progColorEnd"].set(gPrefsSettings.getUInt("progColorEnd", PROGRESS_COLOR_END));
@@ -1418,6 +1420,7 @@ static void settingsToJSON(JsonObject obj, const String section) {
14181420
ledSettings["numControl"].set(NUM_CONTROL_LEDS); // NUM_CONTROL_LEDS
14191421
ledSettings["numIdleDots"].set(NUM_LEDS_IDLE_DOTS); // NUM_LEDS_IDLE_DOTS
14201422
ledSettings["idleColor"].set(IDLE_COLOR); // IDLE_COLOR
1423+
ledSettings["idleAnimation"].set(0u); // standard idle animation
14211424
ledSettings["offsetPause"].set(OFFSET_PAUSE_LEDS); // OFFSET_PAUSE_LEDS
14221425
ledSettings["progColorStart"].set(PROGRESS_COLOR_START); // PROGRESS_COLOR_START
14231426
ledSettings["progColorEnd"].set(PROGRESS_COLOR_END); // PROGRESS_COLOR_END

0 commit comments

Comments
 (0)