Skip to content

Commit 0902b62

Browse files
committed
Added FPS indication in info
1 parent 517a85f commit 0902b62

File tree

13 files changed

+712
-686
lines changed

13 files changed

+712
-686
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
### Development versions after 0.11.1 release
44

5+
#### Build 2102050
6+
7+
- Version bump to 0.12.0-a0 "Hikari"
8+
- Added FPS indication in info
9+
- Bumped max outputs from 7 to 10 busses for ESP32
10+
511
#### Build 2101310
612

713
- First alpha configurable multipin

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "wled",
3-
"version": "0.11.1",
3+
"version": "0.12.0-a0",
44
"description": "Tools for WLED project",
55
"main": "tools/cdata.js",
66
"directories": {

platformio.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
default_envs = travis_esp8266, travis_esp32
1313

1414
# Release binaries
15-
; default_envs = nodemcuv2, esp01_1m_full, esp32dev, custom_WS2801, custom_APA102, custom_LEDPIN_16, custom_LEDPIN_4, custom_LEDPIN_3, custom32_LEDPIN_16, custom32_APA102
15+
; default_envs = nodemcuv2, esp01_1m_full, esp32dev
1616

1717
# Single binaries (uncomment your board)
1818
; default_envs = nodemcuv2

wled00/FX.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -651,7 +651,8 @@ class WS2812FX {
651651
currentMilliamps,
652652
// setStripLen(uint8_t strip, uint16_t len),
653653
// getStripLen(uint8_t strip=0),
654-
triwave16(uint16_t);
654+
triwave16(uint16_t),
655+
getFps();
655656

656657
uint32_t
657658
now,
@@ -808,6 +809,8 @@ class WS2812FX {
808809
uint16_t _usedSegmentData = 0;
809810
uint16_t _transitionDur = 750;
810811

812+
uint16_t _cumulativeFps = 2;
813+
811814
void load_gradient_palette(uint8_t);
812815
void handle_palette(void);
813816

wled00/FX_fcn.cpp

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,12 @@ void WS2812FX::show(void) {
324324
// all of the data has been sent.
325325
// See https://github.com/Makuna/NeoPixelBus/wiki/ESP32-NeoMethods#neoesp32rmt-methods
326326
busses.show();
327-
_lastShow = millis();
327+
unsigned long now = millis();
328+
unsigned long diff = now - _lastShow;
329+
uint16_t fpsCurr = 200;
330+
if (diff > 0) fpsCurr = 1000 / diff;
331+
_cumulativeFps = (3 * _cumulativeFps + fpsCurr) >> 2;
332+
_lastShow = now;
328333
}
329334

330335
/**
@@ -335,6 +340,15 @@ bool WS2812FX::isUpdating() {
335340
return !busses.canAllShow();
336341
}
337342

343+
/**
344+
* Returns the refresh rate of the LED strip. Useful for finding out whether a given setup is fast enough.
345+
* Only updates on show() or is set to 0 fps if last show is more than 2 secs ago, so accurary varies
346+
*/
347+
uint16_t WS2812FX::getFps() {
348+
if (millis() - _lastShow > 2000) return 0;
349+
return _cumulativeFps +1;
350+
}
351+
338352
/**
339353
* Forces the next frame to be computed on all active segments.
340354
*/

wled00/const.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
#ifdef ESP8266
1717
#define WLED_MAX_BUSSES 3
1818
#else
19-
#define WLED_MAX_BUSSES 7
19+
#define WLED_MAX_BUSSES 10
2020
#endif
2121

2222
//Usermod IDs

wled00/data/index.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -464,7 +464,7 @@ function populateInfo(i)
464464
}
465465
}
466466
var vcn = "Kuuhaku";
467-
if (i.ver.startsWith("0.11.")) vcn = "Mirai";
467+
if (i.ver.startsWith("0.12.")) vcn = "Hikari";
468468
if (i.cn) vcn = i.cn;
469469

470470
cn += `v${i.ver} "${vcn}"<br><br><table class="infot">
@@ -473,7 +473,8 @@ function populateInfo(i)
473473
${inforow("Signal strength",i.wifi.signal +"% ("+ i.wifi.rssi, " dBm)")}
474474
${inforow("Uptime",getRuntimeStr(i.uptime))}
475475
${inforow("Free heap",heap," kB")}
476-
${inforow("Estimated current",pwru)}
476+
${inforow("Estimated current",pwru)}
477+
${inforow("Frames / second",i.leds.fps)}
477478
${inforow("MAC address",i.mac)}
478479
${inforow("Filesystem",i.fs.u + "/" + i.fs.t + " kB (" +Math.round(i.fs.u*100/i.fs.t) + "%)")}
479480
${inforow("Environment",i.arch + " " + i.core + " (" + i.lwip + ")")}

wled00/data/settings_leds.htm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
if (bquot > 100) {var msg = "Too many LEDs for me to handle!"; if (bmax < 10000) msg += " Consider using an ESP32."; alert(msg); return;}
3636
if (d.Sf.reportValidity()) d.Sf.submit();
3737
}
38-
function S(){GetV();setABL(); if (maxST>4) bmax=32000; d.getElementById('m1').innerHTML = bmax;}
38+
function S(){GetV();setABL(); if (maxST>4) bmax=64000; d.getElementById('m1').innerHTML = bmax;}
3939
function enABL()
4040
{
4141
var en = d.getElementById('able').checked;

wled00/html_other.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ function B(){window.history.back()}function U(){document.getElementById("uf").st
4242
.bt{background:#333;color:#fff;font-family:Verdana,sans-serif;border:.3ch solid #333;display:inline-block;font-size:20px;margin:8px;margin-top:12px}input[type=file]{font-size:16px}body{font-family:Verdana,sans-serif;text-align:center;background:#222;color:#fff;line-height:200%}#msg{display:none}
4343
</style></head><body><h2>WLED Software Update</h2><form method="POST"
4444
action="/update" id="uf" enctype="multipart/form-data" onsubmit="U()">
45-
Installed version: 0.11.1<br>Download the latest binary: <a
45+
Installed version: 0.12.0-a0<br>Download the latest binary: <a
4646
href="https://github.com/Aircoookie/WLED/releases" target="_blank"><img
4747
src="https://img.shields.io/github/release/Aircoookie/WLED.svg?style=flat-square">
4848
</a><br><input type="file" class="bt" name="update" accept=".bin" required><br>

wled00/html_settings.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ Do not enable if WiFi is working correctly, increases power consumption.</i><div
7171
// Autogenerated from wled00/data/settings_leds.htm, do not edit!!
7272
const char PAGE_settings_leds[] PROGMEM = R"=====(<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta
7373
name="viewport" content="width=500"><title>LED Settings</title><script>
74-
var d=document,laprev=55,maxST=1,bmax=5e3,bquot=0;function H(){window.open("https://github.com/Aircoookie/WLED/wiki/Settings#led-settings")}function B(){window.open("/settings","_self")}function trySubmit(){var e=d.getElementsByTagName("input");for(i=0;i<e.length;i++){var n=e[i].name.substring(0,2);if(("L0"==n||"L1"==n||"RL"==n||"BT"==n||"IR"==n||"AX"==n)&&""!=e[i].value&&"-1"!=e[i].value){if(e[i].value>5&&e[i].value<12)return alert("Sorry, pins 6-11 can not be used."),void e[i].focus();if(d.um_p&&d.um_p.some(n=>n==parseInt(e[i].value,10)))return alert("Usermod pin conflict!"),void e[i].focus();for(j=i+1;j<e.length;j++){var t=e[j].name.substring(0,2);if(("L0"==t||"L1"==t||"RL"==t||"BT"==t||"IR"==t||"AX"==t)&&""!=e[j].value&&e[i].value==e[j].value)return alert("Pin conflict!"),void e[i].focus()}}}if(bquot>100){var a="Too many LEDs for me to handle!";return bmax<1e4&&(a+=" Consider using an ESP32."),void alert(a)}d.Sf.reportValidity()&&d.Sf.submit()}function S(){GetV(),setABL(),maxST>4&&(bmax=32e3),d.getElementById("m1").innerHTML=bmax}function enABL(){var e=d.getElementById("able").checked;d.Sf.LA.value=e?laprev:0,d.getElementById("abl").style.display=e?"inline":"none",d.getElementById("psu2").style.display=e?"inline":"none",d.Sf.LA.value>0&&setABL()}function enLA(){var e=d.Sf.LAsel.value;d.Sf.LA.value=e,d.getElementById("LAdis").style.display=50==e?"inline":"none",UI()}function setABL(){switch(d.getElementById("able").checked=!0,d.Sf.LAsel.value=50,parseInt(d.Sf.LA.value)){case 0:d.getElementById("able").checked=!1,enABL();break;case 30:d.Sf.LAsel.value=30;break;case 35:d.Sf.LAsel.value=35;break;case 55:d.Sf.LAsel.value=55;break;case 255:d.Sf.LAsel.value=255;break;default:d.getElementById("LAdis").style.display="inline"}UI()}function getMem(e,n,t){return e<32?bmax<1e4&&3==t?e>29?20*n:15*n:bmax>1e4?e>29?8*n:6*n:e>29?4*n:3*n:e>31&&e<48?5:44==e||45==e?4*n:3*n}function UI(){var e=!1,t=0;d.getElementById("ampwarning").style.display=d.Sf.MA.value>7200?"inline":"none",255==d.Sf.LA.value?laprev=12:d.Sf.LA.value>0&&(laprev=d.Sf.LA.value);var i=d.getElementsByTagName("select");for(u=0;u<i.length;u++)if("LT"==i[u].name.substring(0,2)){n=i[u].name.substring(2);var a=i[u].value;d.getElementById("p0d"+n).innerHTML=a>49?"Data pin:":a>41?"Pins:":"Pin:",d.getElementById("p1d"+n).innerHTML=a>49?"Clk:":"";var l=d.getElementsByName("L1"+n)[0];for(t+=getMem(a,d.getElementsByName("LC"+n)[0].value,d.getElementsByName("L0"+n)[0].value),p=1;p<5;p++){(l=d.getElementsByName("L"+p+n)[0])&&(a>49&&1==p||a>41&&a<50&&p+40<a?(l.style.display="inline",l.required=!0):(l.style.display="none",l.required=!1,l.value=""))}30!=a&&31!=a&&44!=a&&45!=a||(e=!0),d.getElementById("dig"+n).style.display=a>31&&a<48?"none":"inline",d.getElementById("psd"+n).innerHTML=a>31&&a<48?"Index:":"Start:"}var o=d.querySelectorAll(".wc"),s=o.length;for(u=0;u<s;u++)o[u].style.display=e?"inline":"none";if(d.activeElement==d.getElementsByName("LC")[0]){var u=d.getElementsByClassName("iST").length;1==u&&(d.getElementsByName("LC0")[0].value=d.getElementsByName("LC")[0].value)}var r=d.getElementsByTagName("input"),m=0,v=0;for(u=0;u<r.length;u++){if("LC"!=r[u].name.substring(0,2)||"LC"==r[u].name);else{var y=parseInt(r[u].value,10);y&&(m+=y,y>v&&(v=y))}}d.getElementById("m0").innerHTML=t,bquot=t/bmax*100,d.getElementById("dbar").style.background=`linear-gradient(90deg, ${bquot>60?bquot>90?"red":"orange":"#ccc"} 0 ${bquot}%%, #444 ${bquot}%% 100%%)`,d.getElementById("ledwarning").style.display=v>800||bquot>80?"inline":"none",d.getElementById("wreason").innerHTML=bquot>80?"than 60%% of max. LED memory":"800 LEDs per pin";var g=Math.ceil((100+m*laprev)/500)/2;g=g>5?Math.ceil(g):g;i="";var L=30==d.Sf.LAsel.value,c=255==d.Sf.LAsel.value;g<1.02&&!L&&!c?i="ESP 5V pin with 1A USB supply":(i+=L?"12V ":c?"WS2815 12V ":"5V ",i+=g,i+="A supply connected to LEDs");var f=Math.ceil((100+m*laprev)/1500)/2,B="(for most effects, ~";B+=f=f>5?Math.ceil(f):f,B+="A is enough)<br>",d.getElementById("psu").innerHTML=i,d.getElementById("psu2").innerHTML=c?"":B}function addLEDs(e){if(e>1)return maxST=e,void(d.getElementById("+").style.display="inline");var n=d.getElementsByClassName("iST"),t=n.length;if(!(1==e&&t>=maxST||-1==e&&0==t)){var i=d.getElementById("mLC");if(1==e){var a=`<div class="iST">\n ${t>0?'<hr style="width:260px">':""}\n ${t+1}:\n <select name="LT${t}" onchange="UI()">\n <option value="22">WS281x</option>\n <option value="30">SK6812 RGBW</option>\n <option value="31">TM1814</option>\n <option value="24">400kHz</option>\n <option value="50">WS2801</option>\n <option value="51">APA102</option>\n <option value="52">LPD8806</option>\n <option value="53">P9813</option>\n <option value="41">PWM White</option>\n <option value="42">PWM WWCW</option>\n <option value="43">PWM RGB</option>\n <option value="44">PWM RGBW</option>\n <option value="45">PWM RGBWC</option>\n </select>&nbsp;\n Color Order:\n <select name="CO${t}">\n <option value="0">GRB</option>\n <option value="1">RGB</option>\n <option value="2">BRG</option>\n <option value="3">RBG</option>\n <option value="4">BGR</option>\n <option value="5">GBR</option>\n </select><br>\n <span id="p0d${t}">Pin:</span> <input type="number" name="L0${t}" min="0" max="40" required style="width:35px" oninput="UI()"/>\n <span id="p1d${t}">Clock:</span> <input type="number" name="L1${t}" min="0" max="40" style="width:35px"/>\n <span id="p2d${t}"></span><input type="number" name="L2${t}" min="0" max="40" style="width:35px"/>\n <span id="p3d${t}"></span><input type="number" name="L3${t}" min="0" max="40" style="width:35px"/>\n <span id="p4d${t}"></span><input type="number" name="L4${t}" min="0" max="40" style="width:35px"/>\n <br>\n <span id="psd${t}">Start:</span> <input type="number" name="LS${t}" min="0" max="8191" value="0" required />&nbsp;\n <div id="dig${t}" style="display:inline">\n Count: <input type="number" name="LC${t}" min="0" max="2048" value="1" required oninput="UI()" /><br>\n Reverse: <input type="checkbox" name="CV${t}"></div><br>\n </div>`;i.insertAdjacentHTML("beforeend",a)}-1==e&&(n[--t].remove(),--t),d.getElementById("+").style.display=t<maxST-1?"inline":"none",d.getElementById("-").style.display=t>0?"inline":"none",UI()}}function GetV() {var d=document;
74+
var d=document,laprev=55,maxST=1,bmax=5e3,bquot=0;function H(){window.open("https://github.com/Aircoookie/WLED/wiki/Settings#led-settings")}function B(){window.open("/settings","_self")}function trySubmit(){var e=d.getElementsByTagName("input");for(i=0;i<e.length;i++){var n=e[i].name.substring(0,2);if(("L0"==n||"L1"==n||"RL"==n||"BT"==n||"IR"==n||"AX"==n)&&""!=e[i].value&&"-1"!=e[i].value){if(e[i].value>5&&e[i].value<12)return alert("Sorry, pins 6-11 can not be used."),void e[i].focus();if(d.um_p&&d.um_p.some(n=>n==parseInt(e[i].value,10)))return alert("Usermod pin conflict!"),void e[i].focus();for(j=i+1;j<e.length;j++){var t=e[j].name.substring(0,2);if(("L0"==t||"L1"==t||"RL"==t||"BT"==t||"IR"==t||"AX"==t)&&""!=e[j].value&&e[i].value==e[j].value)return alert("Pin conflict!"),void e[i].focus()}}}if(bquot>100){var a="Too many LEDs for me to handle!";return bmax<1e4&&(a+=" Consider using an ESP32."),void alert(a)}d.Sf.reportValidity()&&d.Sf.submit()}function S(){GetV(),setABL(),maxST>4&&(bmax=64e3),d.getElementById("m1").innerHTML=bmax}function enABL(){var e=d.getElementById("able").checked;d.Sf.LA.value=e?laprev:0,d.getElementById("abl").style.display=e?"inline":"none",d.getElementById("psu2").style.display=e?"inline":"none",d.Sf.LA.value>0&&setABL()}function enLA(){var e=d.Sf.LAsel.value;d.Sf.LA.value=e,d.getElementById("LAdis").style.display=50==e?"inline":"none",UI()}function setABL(){switch(d.getElementById("able").checked=!0,d.Sf.LAsel.value=50,parseInt(d.Sf.LA.value)){case 0:d.getElementById("able").checked=!1,enABL();break;case 30:d.Sf.LAsel.value=30;break;case 35:d.Sf.LAsel.value=35;break;case 55:d.Sf.LAsel.value=55;break;case 255:d.Sf.LAsel.value=255;break;default:d.getElementById("LAdis").style.display="inline"}UI()}function getMem(e,n,t){return e<32?bmax<1e4&&3==t?e>29?20*n:15*n:bmax>1e4?e>29?8*n:6*n:e>29?4*n:3*n:e>31&&e<48?5:44==e||45==e?4*n:3*n}function UI(){var e=!1,t=0;d.getElementById("ampwarning").style.display=d.Sf.MA.value>7200?"inline":"none",255==d.Sf.LA.value?laprev=12:d.Sf.LA.value>0&&(laprev=d.Sf.LA.value);var i=d.getElementsByTagName("select");for(u=0;u<i.length;u++)if("LT"==i[u].name.substring(0,2)){n=i[u].name.substring(2);var a=i[u].value;d.getElementById("p0d"+n).innerHTML=a>49?"Data pin:":a>41?"Pins:":"Pin:",d.getElementById("p1d"+n).innerHTML=a>49?"Clk:":"";var l=d.getElementsByName("L1"+n)[0];for(t+=getMem(a,d.getElementsByName("LC"+n)[0].value,d.getElementsByName("L0"+n)[0].value),p=1;p<5;p++){(l=d.getElementsByName("L"+p+n)[0])&&(a>49&&1==p||a>41&&a<50&&p+40<a?(l.style.display="inline",l.required=!0):(l.style.display="none",l.required=!1,l.value=""))}30!=a&&31!=a&&44!=a&&45!=a||(e=!0),d.getElementById("dig"+n).style.display=a>31&&a<48?"none":"inline",d.getElementById("psd"+n).innerHTML=a>31&&a<48?"Index:":"Start:"}var o=d.querySelectorAll(".wc"),s=o.length;for(u=0;u<s;u++)o[u].style.display=e?"inline":"none";if(d.activeElement==d.getElementsByName("LC")[0]){var u=d.getElementsByClassName("iST").length;1==u&&(d.getElementsByName("LC0")[0].value=d.getElementsByName("LC")[0].value)}var r=d.getElementsByTagName("input"),m=0,v=0;for(u=0;u<r.length;u++){if("LC"!=r[u].name.substring(0,2)||"LC"==r[u].name);else{var y=parseInt(r[u].value,10);y&&(m+=y,y>v&&(v=y))}}d.getElementById("m0").innerHTML=t,bquot=t/bmax*100,d.getElementById("dbar").style.background=`linear-gradient(90deg, ${bquot>60?bquot>90?"red":"orange":"#ccc"} 0 ${bquot}%%, #444 ${bquot}%% 100%%)`,d.getElementById("ledwarning").style.display=v>800||bquot>80?"inline":"none",d.getElementById("wreason").innerHTML=bquot>80?"than 60%% of max. LED memory":"800 LEDs per pin";var g=Math.ceil((100+m*laprev)/500)/2;g=g>5?Math.ceil(g):g;i="";var L=30==d.Sf.LAsel.value,c=255==d.Sf.LAsel.value;g<1.02&&!L&&!c?i="ESP 5V pin with 1A USB supply":(i+=L?"12V ":c?"WS2815 12V ":"5V ",i+=g,i+="A supply connected to LEDs");var f=Math.ceil((100+m*laprev)/1500)/2,B="(for most effects, ~";B+=f=f>5?Math.ceil(f):f,B+="A is enough)<br>",d.getElementById("psu").innerHTML=i,d.getElementById("psu2").innerHTML=c?"":B}function addLEDs(e){if(e>1)return maxST=e,void(d.getElementById("+").style.display="inline");var n=d.getElementsByClassName("iST"),t=n.length;if(!(1==e&&t>=maxST||-1==e&&0==t)){var i=d.getElementById("mLC");if(1==e){var a=`<div class="iST">\n ${t>0?'<hr style="width:260px">':""}\n ${t+1}:\n <select name="LT${t}" onchange="UI()">\n <option value="22">WS281x</option>\n <option value="30">SK6812 RGBW</option>\n <option value="31">TM1814</option>\n <option value="24">400kHz</option>\n <option value="50">WS2801</option>\n <option value="51">APA102</option>\n <option value="52">LPD8806</option>\n <option value="53">P9813</option>\n <option value="41">PWM White</option>\n <option value="42">PWM WWCW</option>\n <option value="43">PWM RGB</option>\n <option value="44">PWM RGBW</option>\n <option value="45">PWM RGBWC</option>\n </select>&nbsp;\n Color Order:\n <select name="CO${t}">\n <option value="0">GRB</option>\n <option value="1">RGB</option>\n <option value="2">BRG</option>\n <option value="3">RBG</option>\n <option value="4">BGR</option>\n <option value="5">GBR</option>\n </select><br>\n <span id="p0d${t}">Pin:</span> <input type="number" name="L0${t}" min="0" max="40" required style="width:35px" oninput="UI()"/>\n <span id="p1d${t}">Clock:</span> <input type="number" name="L1${t}" min="0" max="40" style="width:35px"/>\n <span id="p2d${t}"></span><input type="number" name="L2${t}" min="0" max="40" style="width:35px"/>\n <span id="p3d${t}"></span><input type="number" name="L3${t}" min="0" max="40" style="width:35px"/>\n <span id="p4d${t}"></span><input type="number" name="L4${t}" min="0" max="40" style="width:35px"/>\n <br>\n <span id="psd${t}">Start:</span> <input type="number" name="LS${t}" min="0" max="8191" value="0" required />&nbsp;\n <div id="dig${t}" style="display:inline">\n Count: <input type="number" name="LC${t}" min="0" max="2048" value="1" required oninput="UI()" /><br>\n Reverse: <input type="checkbox" name="CV${t}"></div><br>\n </div>`;i.insertAdjacentHTML("beforeend",a)}-1==e&&(n[--t].remove(),--t),d.getElementById("+").style.display=t<maxST-1?"inline":"none",d.getElementById("-").style.display=t>0?"inline":"none",UI()}}function GetV() {var d=document;
7575
%CSS%%SCSS%</head><body onload="S()"><form
7676
id="form_s" name="Sf" method="post"><div class="helpB"><button type="button"
7777
onclick="H()">?</button></div><button type="button" onclick="B()">Back</button>
@@ -373,7 +373,7 @@ HTTP traffic is unencrypted. An attacker in the same network can intercept form
373373
<h3>Software Update</h3><button type="button" onclick="U()">Manual OTA Update
374374
</button><br>Enable ArduinoOTA: <input type="checkbox" name="AO"><br><h3>About
375375
</h3><a href="https://github.com/Aircoookie/WLED/" target="_blank">WLED</a>
376-
version 0.11.1<br><br><a
376+
version 0.12.0-a0<br><br><a
377377
href="https://github.com/Aircoookie/WLED/wiki/Contributors-and-credits"
378378
target="_blank">Contributors, dependencies and special thanks</a><br>
379379
A huge thank you to everyone who helped me create WLED!<br><br>

0 commit comments

Comments
 (0)