From 593c1d6b057ffd5c0c36b5ae0603c0b69f3f6dfb Mon Sep 17 00:00:00 2001 From: srg74 <28492985+srg74@users.noreply.github.com> Date: Sat, 18 Jan 2020 16:01:31 -0500 Subject: [PATCH 01/90] Create Enclosure_with_OLED_temp_ESP07 --- usermods/Enclosure_with_OLED_temp_ESP07 | 1 + 1 file changed, 1 insertion(+) create mode 100644 usermods/Enclosure_with_OLED_temp_ESP07 diff --git a/usermods/Enclosure_with_OLED_temp_ESP07 b/usermods/Enclosure_with_OLED_temp_ESP07 new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/usermods/Enclosure_with_OLED_temp_ESP07 @@ -0,0 +1 @@ + From 7bf92db4c976e83263cb1b88fa8770c643cda015 Mon Sep 17 00:00:00 2001 From: srg74 <28492985+srg74@users.noreply.github.com> Date: Sat, 18 Jan 2020 16:05:49 -0500 Subject: [PATCH 02/90] Delete Enclosure_with_OLED_temp_ESP07 --- usermods/Enclosure_with_OLED_temp_ESP07 | 1 - 1 file changed, 1 deletion(-) delete mode 100644 usermods/Enclosure_with_OLED_temp_ESP07 diff --git a/usermods/Enclosure_with_OLED_temp_ESP07 b/usermods/Enclosure_with_OLED_temp_ESP07 deleted file mode 100644 index 8b1378917..000000000 --- a/usermods/Enclosure_with_OLED_temp_ESP07 +++ /dev/null @@ -1 +0,0 @@ - From 5d947cc8e1f0373a1ad836e4ca7d6e10301cbdc8 Mon Sep 17 00:00:00 2001 From: srg74 <28492985+srg74@users.noreply.github.com> Date: Sat, 18 Jan 2020 16:06:26 -0500 Subject: [PATCH 03/90] Create readme.md --- .../Enclosure_with_OLED_temp_ESP07/readme.md | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 usermods/Enclosure_with_OLED_temp_ESP07/readme.md diff --git a/usermods/Enclosure_with_OLED_temp_ESP07/readme.md b/usermods/Enclosure_with_OLED_temp_ESP07/readme.md new file mode 100644 index 000000000..2541e6955 --- /dev/null +++ b/usermods/Enclosure_with_OLED_temp_ESP07/readme.md @@ -0,0 +1,34 @@ +# Almost universal controller board for outdoor applications +This usermod is using ideas from @mrVanboy and @400killer +## Features +- SSD1306 128x32 and 128x64 I2C OLED display (optional) +- Dallas temperature sensor (optional) + +## Hardware +![Hardware connection](assets/controller.jpg) + +## Requirements +Functionality checked with: +- ESP-07S +- PlatformIO +- SSD1306 128x32 I2C OLED display +- DS18B20 (temperature sensor) +- KY-022 (infrared receiver) +- Push button (N.O. momentary switch) + +### Platformio +Uncomment `U8g2@~2.27.3`,`DallasTemperature@~3.8.0`,`OneWire@~2.3.5 under` `[common]` section in `platformio.ini`: +```ini +# platformio.ini +... +[common] +... +lib_deps_external = + ... + #For use SSD1306 0.91" OLED display uncomment following + U8g2@~2.27.3 + #For Dallas sensor uncomment following 2 lines + DallasTemperature@~3.8.0 + OneWire@~2.3.5 +... +``` From 5d8bfc293088554987640e46bbe40cb9b091d999 Mon Sep 17 00:00:00 2001 From: srg74 <28492985+srg74@users.noreply.github.com> Date: Sat, 18 Jan 2020 16:07:27 -0500 Subject: [PATCH 04/90] Create readme.md --- usermods/Enclosure_with_OLED_temp_ESP07/assets/readme.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 usermods/Enclosure_with_OLED_temp_ESP07/assets/readme.md diff --git a/usermods/Enclosure_with_OLED_temp_ESP07/assets/readme.md b/usermods/Enclosure_with_OLED_temp_ESP07/assets/readme.md new file mode 100644 index 000000000..4466821bc --- /dev/null +++ b/usermods/Enclosure_with_OLED_temp_ESP07/assets/readme.md @@ -0,0 +1,9 @@ +# Enclosure and PCB + +## IP67 rated enclosure +![Enclosure](assets/controller.jpg) + +## PCB +![PCB](assets/controller.jpg) +``` +``` From 551afe0cd79fba4b087eeeb02d1f8121fff2fba9 Mon Sep 17 00:00:00 2001 From: srg74 <28492985+srg74@users.noreply.github.com> Date: Sat, 18 Jan 2020 16:08:14 -0500 Subject: [PATCH 05/90] Add files via upload --- .../wled06_usermod.ino | 212 ++++++++++++++++++ 1 file changed, 212 insertions(+) create mode 100644 usermods/Enclosure_with_OLED_temp_ESP07/wled06_usermod.ino diff --git a/usermods/Enclosure_with_OLED_temp_ESP07/wled06_usermod.ino b/usermods/Enclosure_with_OLED_temp_ESP07/wled06_usermod.ino new file mode 100644 index 000000000..b3f1c5db5 --- /dev/null +++ b/usermods/Enclosure_with_OLED_temp_ESP07/wled06_usermod.ino @@ -0,0 +1,212 @@ +#include // from https://github.com/olikraus/u8g2/ +#include //Dallastemperature sensor +//The SCL and SDA pins are defined here. +//Lolin32 boards use SCL=5 SDA=4 +#define U8X8_PIN_SCL 5 +#define U8X8_PIN_SDA 4 +// Dallas sensor +OneWire oneWire(12); +DallasTemperature sensor(&oneWire); +long temptimer = millis(); +long lastMeasure = 0; +#define Celsius // Show temperature mesaurement in Celcius otherwise is in Fahrenheit + +// If display does not work or looks corrupted check the +// constructor reference: +// https://github.com/olikraus/u8g2/wiki/u8x8setupcpp +// or check the gallery: +// https://github.com/olikraus/u8g2/wiki/gallery +// --> First choise of cheap I2C OLED 128X32 +<<<<<<< HEAD +U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA +// --> Second choise of cheap I2C OLED 128X64 +//U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA +======= +//U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA +// --> Second choise of cheap I2C OLED 128X64 +U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA +>>>>>>> 1680c014057bb993974950f37b14369b398ca647 +// gets called once at boot. Do all initialization that doesn't depend on +// network here +void userSetup() { + sensor.begin(); //Start Dallas temperature sensor + u8x8.begin(); + u8x8.setPowerSave(0); + u8x8.setContrast(10); //Contrast setup will help to preserve OLED lifetime. In case OLED need to be brighter increase number up to 255 + u8x8.setFont(u8x8_font_chroma48medium8_r); + u8x8.drawString(0, 0, "Loading..."); +} + +// gets called every time WiFi is (re-)connected. Initialize own network +// interfaces here +void userConnected() {} + +// needRedraw marks if redraw is required to prevent often redrawing. +bool needRedraw = true; + +// Next variables hold the previous known values to determine if redraw is +// required. +String knownSsid = ""; +IPAddress knownIp; +uint8_t knownBrightness = 0; +uint8_t knownMode = 0; +uint8_t knownPalette = 0; + +long lastUpdate = 0; +long lastRedraw = 0; +bool displayTurnedOff = false; +// How often we are redrawing screen +#define USER_LOOP_REFRESH_RATE_MS 5000 + +void userLoop() { + +//----> Dallas temperature sensor MQTT publishing + temptimer = millis(); +// Timer to publishe new temperature every 60 seconds + if (temptimer - lastMeasure > 60000) + { + lastMeasure = temptimer; +//Check if MQTT Connected, otherwise it will crash the 8266 + if (mqtt != nullptr) + { + sensor.requestTemperatures(); +//Gets prefered temperature scale based on selection in definitions section + #ifdef Celsius + float board_temperature = sensor.getTempCByIndex(0); + #else + float board_temperature = sensor.getTempFByIndex(0); + #endif +//Create character string populated with user defined device topic from the UI, and the read temperature. Then publish to MQTT server. + char subuf[38]; + strcpy(subuf, mqttDeviceTopic); + strcat(subuf, "/temperature"); + mqtt->publish(subuf, 0, true, String(board_temperature).c_str()); + } + } + + // Check if we time interval for redrawing passes. + if (millis() - lastUpdate < USER_LOOP_REFRESH_RATE_MS) { + return; + } + lastUpdate = millis(); + + // Turn off display after 3 minutes with no change. + if(!displayTurnedOff && millis() - lastRedraw > 3*60*1000) { + u8x8.setPowerSave(1); + displayTurnedOff = true; + } + + // Check if values which are shown on display changed from the last time. + if (((apActive) ? String(apSSID) : WiFi.SSID()) != knownSsid) { + needRedraw = true; + } else if (knownIp != (apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP())) { + needRedraw = true; + } else if (knownBrightness != bri) { + needRedraw = true; + } else if (knownMode != strip.getMode()) { + needRedraw = true; + } else if (knownPalette != strip.getSegment(0).palette) { + needRedraw = true; + } + + if (!needRedraw) { + return; + } + needRedraw = false; + + if (displayTurnedOff) + { + u8x8.setPowerSave(0); + displayTurnedOff = false; + } + lastRedraw = millis(); + + // Update last known values. + #if defined(ESP8266) + knownSsid = apActive ? WiFi.softAPSSID() : WiFi.SSID(); + #else + knownSsid = WiFi.SSID(); + #endif + knownIp = apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP(); + knownBrightness = bri; + knownMode = strip.getMode(); + knownPalette = strip.getSegment(0).palette; + u8x8.clear(); + u8x8.setFont(u8x8_font_chroma48medium8_r); + + // First row with Wifi name + u8x8.setCursor(1, 0); + u8x8.print(knownSsid.substring(0, u8x8.getCols() > 1 ? u8x8.getCols() - 2 : 0)); + // Print `~` char to indicate that SSID is longer, than owr dicplay + if (knownSsid.length() > u8x8.getCols()) + u8x8.print("~"); + + // Second row with IP or Psssword + u8x8.setCursor(1, 1); + // Print password in AP mode and if led is OFF. + if (apActive && bri == 0) + u8x8.print(apPass); + else + u8x8.print(knownIp); + + // Third row with mode name + u8x8.setCursor(2, 2); + uint8_t qComma = 0; + bool insideQuotes = false; + uint8_t printedChars = 0; + char singleJsonSymbol; + + // Find the mode name in JSON + for (size_t i = 0; i < strlen_P(JSON_mode_names); i++) { + singleJsonSymbol = pgm_read_byte_near(JSON_mode_names + i); + switch (singleJsonSymbol) { + case '"': + insideQuotes = !insideQuotes; + break; + case '[': + case ']': + break; + case ',': + qComma++; + default: + if (!insideQuotes || (qComma != knownMode)) + break; + u8x8.print(singleJsonSymbol); + printedChars++; + } + if ((qComma > knownMode) || (printedChars > u8x8.getCols() - 2)) + break; + } + // Fourth row with palette name + u8x8.setCursor(2, 3); + qComma = 0; + insideQuotes = false; + printedChars = 0; + // Looking for palette name in JSON. + for (size_t i = 0; i < strlen_P(JSON_palette_names); i++) { + singleJsonSymbol = pgm_read_byte_near(JSON_palette_names + i); + switch (singleJsonSymbol) { + case '"': + insideQuotes = !insideQuotes; + break; + case '[': + case ']': + break; + case ',': + qComma++; + default: + if (!insideQuotes || (qComma != knownPalette)) + break; + u8x8.print(singleJsonSymbol); + printedChars++; + } + if ((qComma > knownMode) || (printedChars > u8x8.getCols() - 2)) + break; + } + + u8x8.setFont(u8x8_font_open_iconic_embedded_1x1); + u8x8.drawGlyph(0, 0, 80); // wifi icon + u8x8.drawGlyph(0, 1, 68); // home icon + u8x8.setFont(u8x8_font_open_iconic_weather_2x2); + u8x8.drawGlyph(0, 2, 66 + (bri > 0 ? 3 : 0)); // sun/moon icon +} From b7d84c002d7f9838dfee706501cb401b985b03c1 Mon Sep 17 00:00:00 2001 From: srg74 <28492985+srg74@users.noreply.github.com> Date: Sat, 18 Jan 2020 16:08:49 -0500 Subject: [PATCH 06/90] Add files via upload --- .../assets/controller.jpg | Bin 0 -> 153419 bytes .../assets/pcb.png | Bin 0 -> 240670 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 usermods/Enclosure_with_OLED_temp_ESP07/assets/controller.jpg create mode 100644 usermods/Enclosure_with_OLED_temp_ESP07/assets/pcb.png diff --git a/usermods/Enclosure_with_OLED_temp_ESP07/assets/controller.jpg b/usermods/Enclosure_with_OLED_temp_ESP07/assets/controller.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d518ca3e310870f82eda70d8ae65c4313045aacf GIT binary patch literal 153419 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

{5^`$)Z`0Xr}DRJOdm-B+-y*hgTU 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 z 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 literal 0 HcmV?d00001 diff --git a/usermods/Enclosure_with_OLED_temp_ESP07/assets/pcb.png b/usermods/Enclosure_with_OLED_temp_ESP07/assets/pcb.png new file mode 100644 index 0000000000000000000000000000000000000000..cf146918ac4f0071330ca928b4ab27e1dce1f98c GIT binary patch literal 240670 zcmeEuV{~RsvuJGFwrx94jEU`JV%wNlPdKq{POORTiEZ0=-tT{vpzi(f&T{wP54I)TvLeoFMxHF(RKj=fk*!*gMwsc z;r!L6ZLO~5s-+;$XX;?bWNhYOV$S4g=lG8lh=3>GU)0Xr)tJQ7&eqUmSdY z@qft7WF-G$as4hxrlp`vBJSX9PQuB=!Nfu)1V=(bBH(Oh!KW%A^>6XNB|$PPS64?q zW@Zl$4<-+GCI@FrW>#KaUS<|HW;Qm)zZ{G%UiPlWo{aV``DI7Yi#1+<|;@=_K%_ePXAR;S8I#^v1IS^Z@2zB$o$V6W>zK^=Kt3HS5)91DxZ?G zwfSGm|L7ND75EqD|3mw?9RcQljQ?Mi`7clZMg8ll5S#$>e>a;DoP0mEHVB9ah^&OD zx+mzlKD5y{$t9uM!(!|?beM!0A8dv|m|#Rr#7BJ}-3@&=4}JaFwwhm$pS4}gHAbTP zkeHa~Q6ybYsiF4<25&tMmmQ;WGBkkweD;K=%MSCy36G;JUYElOT-0n1Fg(!MKq(RO zJk%K&D~SIs{%>pi{~Qe4ERwy>>2r2Tc8TkHmG@^y+)Bp|vAqVUTJ3XWjVlJXzCK-7 z%U-i&US1K3RQm^s@tn*N{%ibA7Zmq5FE<#fLiNJq^S39%A!WJm)-VVi6SK_R%aQU( z`j*%8HhH;1O)y$3{^9!W8GAEHsC30g+AbyXu*pt)=Mlhybv<2~87CQ0n4SK~r;sa@ zlG`*<9T(!K@v``up`D;!7LLw=5LJZ(=T~2b2p2&fEyBr{lg6Qg7n@X_SLm z@z4?DZ|6G;_32Ho^VHdks}2uYmZ;+}dD-E-khRYrhi+N@DS3v~TR((ZU}u*E{qIbo zBmPXqKk57#D;jEi`zkEAnz2u+mDzyrdduzTW)B20w8;PA(5}Sr#hA|i{oDMD;o3b$ z%J|EjU{$~?(7H4@Lb2ai>3uc{)5?wKk6eRp^=?B$v(8`yl-}6`;I91;`pnP&=J}D2 zKKhC5r|3Z1l=pNDDIk+oz)bpl+j8=jDt0{9x$V6Nl2Cfb&ytYM+rJ$$ z`N)c}3i7GCe~Z6EDC;4y!XBme)4B3%yfR}oo*0Rfy!!Kgv^^k*qi z8XFp-OI6uXvul|Cn|-`i{(D&jSo2;zHun%8p~*mV!*3})?n~n%6#G_L;J_vjnZ5LL zU-C$K^H{^26`43L3#)RFBo{8rV^5nt5+3M6Vo0U1!}XKLZel2}9?Q+hAC+eqDY_gq zTN9j*mfq@<)x2E(x{vKepnkhP!!s@&w;1<9Vi8m0(67qJ=V-zw9XURdHGWO5bNJX9 zQ6S{%A&`q|bATKlNsB3d^5}dd_p17Uw-CuU>nd(}!;>W)E60*a$b&rRVGAhy^w8l6 z6{(rN!7L2PH@UAirRc=rZP2(vXsZ1Na_&DEb;rskEf}iQd)F{5;A5!@P?SDrqt<)VW8|p#HerT zaCPlnCLv@cS2Xea06FY>osj0y{Js3WmqdUenu036G_c|0rQ8|c^39z&X?woDkGH8vFXtagg{FzrTAJ8&> z!a-yHGIRAV>Z8bB1_Wti^2wCo%p{`hBo$=xmBVhE<PCkWky5$U(z(rZA z=#xX(h=5e^{}k3@QlQ>JY0JK@!2>f-!h%!f-ju{Wo!m-1PB*8yjNFNXgdEwTf#=*kBl3>zRKPnILO6%s_C<07io?N+7DgP0qIrrv zJ5ks|eF2b^NFn<$2=hewmcTb&;eD6h;x8<~gn?Pn!O zty+y*jcQy&SN&)rla31fDs!IRp0=deOg*W`T5+1kuC`FlHk{g+D(*f3^r~+O`qWK< z7ZYwn>==!%K<*AVddj%-%qomFrhK$h^NB(9B&Pk-!t&&FF8}w=UnSMQs3anWxVOAy zsMXp5f@0>+`}?fZbjg_-FtnZlOJwxBKys@~^nFEE`t@BMY_?>?65~@8+MEswc4F#W*`J z`pP6!7A0c^?_`l6d$(EINcO9>5`AKQ^-X1ZMFbjw3lQS;WffuW=%nRkhM;_PGz&_! z?3kQ$jFh6Y%M%rE-_*CM;OKKD3g!!EbXt9*WgtVd2(xkh_libl7)~HXN1Cu6Qm4SWgf;gx}`O|Q|bum>z)Bbt!s=LmA5C2TJPfS-NN5TYYW(sU9;+- zgdN<%^|!xUm9=UyGFfWjZ1?ipottD%`2 z=#;3UO3iZxNm@gZQY*)|8d`YA5C9MXNS=jg9d9tDRfmCu(ZU$MUU4n+XN4d|@NTUI z=BPp-$y>f^wEp&N!1=4u zQuUvLHMBhCFE=YqL_!e0ndbmYH$F(jNy0-o7m4tQaVFQ!fn@ak;x&h)g?$Rj2}Yy1 z_h2!v-9>HZWgmDmD~pCqCXUkNQ&#Vcn`=TeOk7$&rmx0@z~v4}>9I8UM754GRK zygGQa`?I{t+tPVizh1px&Nh%o_i_W6(SKQv3`Z}I%?EUAF=e^`TK`(Oa~^ElI1Cq%?GPlSTW{z>dFwz*dKZK?aVtY8t; z{QJQlH@441A2Vf}f`#790hO5`3U}9f_lT{AxC2$`G&|X!o(1frj0h87;HQ);A7L1{f(;u#5ld>1X2rJRnnY2oTUU$F0-02cm zs2qn*aI`->;_rt{_oSXQ3I|_eR%00F{QQ~}Yd2ncn=Y9N+4ElK8)o~bMI3MD1xD(QNZw9e znkZE|$`08Rn1QCUEP?xIDmh^KUCa9+q#*tg zk0EWPWdLk`rBWEW%cH5_DHWU2M~pc}y1Kz#t$V`v7ArC~GIh`4J?$P4m)5KcchFo~ zBBv)GLlAK0cYWeGUOBCp)-C8GBWPBD=;dRq4IkpW7AADM`TbgXYqq4h=|~Kxn^E>PZ7=84-P1L?X!i=-a^3Hc+QpCiV%vKi-VR9dp~08m)Hl zg@L38!y-@HzhX*Cnp33&NkZcCV$IhW`r&dS|UBThK zWcC0c)(IbY%u_t=qXT)hfMziCE89gRHUAAB8Y8Xd-21P`H-+Yd+oDM+8MI2%Us5f0ObO$| zM5N>q2x!oJ=lDcE-?;KkyPuB}*(?U@QgL@QvHlFao;f65G`l4QDdwayw2pxC{&+l2Nrb*eDGLx(54jefyM^AxN(}(em z!C}LRQewvSJwOEDuGR|~T6B5-%yJrwRA`OG;nRA3m`Jro76>s`=#Cl1rx@tnD?P*> zfk*D9k+oFBf`9L3;Sd*iQ<(EoIMxbxaa9W=4nez8<=E8r`@DC`>V%h6)WRGO!xk9 zq)C?u*sZVj&0v_&1vof7JX&P%m4eKDkta)JPQ-YVuu9#0X_fHmLXXMdeeu;Y^t)a13rJR@Rsy;x@)-~XT#!5U5=(5TtCOO$ z1hdVzk#OT?2_G!dSt1)Y7>s+Qk4`rl+;qgZe9?Hs;+ zd#BV~T`HFC5V8(Lav&g_P}fdByYqSn3^%BJF>I?!sE&5XiFMhH&+6gU3*guEPX3T;5D z|CqITJZ5K>VKsAOumI`Suh&^J-KI;Tjm4>BoAmr(%y@bb%kW;;%HTWjFz5xd{pHv!}?cu3C}-p z{WvjDVC*2HQSB^^n@lL0`VXh{vc+5qdyks%CTlZzPZ|oV06Bh^HCb%dU<}wN+ly9O zcD+B80ThMF2&Zdn6UgLcM01F0K24#d__!hEt+g{Rr9;jYQ*a-MyTu+A2qDZU)kOAe zV%E(u#JuWyb^Zy7yQZcG+|seY_$xny0;&5{fMW8ww22po%e=`@Q>A0;c~*~9H$HT! zycPV$j$Sv$feNbWaG|PgD4X%sWWUjR+Yz;kaufuCgc=D%hS29Ne)};49bH`vtR+YS zRW7aR$=m*KI^rebNVl%@iTOOE1KM%CY1&3kqtG72Ax*#JNks6=1Dm9r3d^L znH-l^vLBNU#T6wQ9~!s~)V~h`3n0eLW7h-j(A$U@BXNx9B({EP$n8shFwNNZ6nNFJ*u1%_zxZ_iB%B~@=$65!DEFyVLf}z_5MCT@J$w3~iu~P8!;(fF zJb6E+&+j10#~~#^=(FlPlr1SUi{T)B*T?t-PgXW6rGMi9Ae0Rl*#~049Zu*RW5BWBg)-!Gwt%G1srSbrP5Y87xd- zvBl|#^);VvoRpJErrUDWD`V2-+CQ?}Uno67^(^XiuR5NHaI36=+Gw>uu=w3}Lmh>J z6cCPhTv{{rb&CHWXfq;mP&D~AVLo!W=kozYo*4y~f^(8XlA(1-1n-?92D4-AF12q+ zMThz}l<@n4QE1V{>@!dd1NTx);%D-~s-|J$lhCWgW+;e2`S);OKV4asn5YtRsEPMz zEVmru-!X`G0*itTD@>H(OeJs5RLpYQJvFMF#G+cHY7T(mlnkBHa+_$AUew<;f1LMH zZ9th1&YeuMWXh!4%1g#dJL$EVQOIq>{&cJds?W7^t`w~LPFRZvePcB~1u3uL#Hc?m zld;(aeg7j-H$z!j#hPnUtXAhZr;VfL$T!;PpNzUEqPd1Me@8&-o6HxD( zSE)N6Z)GvU+6a~6-eS=y1=V+UvY<7$P!u&<>~Z%;q*6w;SE97I$~AmS8B6=#%Ic~m zGF&fuh+4y0fs)kHeZ*tihC^vdvFZ79 zh&|s)B9QaITb{}Fs^wVZmbXZwdPVUD!lf=2en7^iA{3VR^F)b|Yu-KMyN?f^gx5|} z*R9*GrZ9gUFN*(;!((#Z3K$GJLFALdU&mUAOT_(}y)vWJP{~Redci*`r%W$0+ zRDJ0~IhiMy{*8FWc*okzjZwMCLk2fye6MVPC-%? zBhbO@nt%h5??yU|NtlNTd@MNloculqrok?hF+PjT;!w~8M7#z0MtQ39!~l(D6+vU% znv_eHRW4S5@=_DtEUoUX+(GELbU8i$j&i_K8K@1XOl_8J2A_A^M8GL$Jt>n0OFX3c z{9%5%XYY>IgYn10};ba6X6~!@(LHjkM0}|w>dDWs4?m3*-$fpiZ0UDOT@KLwaVg26 z$v8QwxZ548@@~-=X!W1RT5Vw`_EjToPLEoJDmDLI&A!>A!UJy~_pU-obY8~Yrklyy9?ixg`_9J$n_gh z0Dx|Xtqv5chVgm>FC#IATDwHW-muDqbAA|JD2+DlQ{Ov>1Eu~3PkjXP8}?mzNog+z zkiPnubN>hF&qW{^PJfs-I%rRwv6_(TVfl$25&PB;5^iL-{>=$T(SsHjs{>bFRe`1H z(j9z$g6-r0zAyo><1l~RNJJm{Ci=Rf_i*%KSvWB?7JaAB zRfQDAAMt79^?-}tmRkv*1KYWaVlf;W7Fs5~w&YOA%G41i3-_ns9$XtN7C2b>+Lm_XA^RY zCC&3vCrtROeMZo8PG3eNH|!Z<3WS|v9+&kOy}C>ztLgfBn4JooupJeLm zq!_fDA*TmNKweL0;p{dKkUKfMWoEtEg+$Jv44 z?@ek-8l(

?ZI?qv7~g`q6cK05kDYHf)q;S>^*6s>gyEi|QFc?ax;+hV#OSO6y*n z6(nZ#_vI;%Wq7>V zdBg5r*;i5C=-nP!#;1xg5-~Y(oTt~OPIarQCmS!__`Rq4!!cWsuLR)^*Kb*qg6~e|s zL~1c2EQJK0FNs;Ox4jvt{;vFX>`2SNS@QDBM8|0Ghe5-2io=kwJJ5SdIm>e;s|QaI zD}cVX=PBs@GuUO(uL22lPJwTsy*8gDYeDI^3mlF$G(#Kd1?Izm{%Nq2Rt&j!vK(4=5{&1!^ifRgxA2qfw_G2|oc_~U|7-E80d|gTEe1MgbKUq;7s5yp5+VWvJKJr@ zOy;h3^-j~@ZYI0;>s<(othNRB!Ud(7l4XVPV3*}#4?nG8Nh6OKD=*~)w-0AN8 zB5g3xU<|Bcs2yC`LZx#sH}0RkZN}f9<+o3fb)?4$Tbx`z`p~!x*mlDevJSKrO^0Y+ zgSa$Z@PN|Yr0>%5V%(T7##4OEB05q(8lsj@5Z3d~fB&d?TeznY|LAPReKJm{dIm*Y z1u7i2l3j}kDJwy2Qpz2>;*dJ>&}toHhIkPS{~$yI?;(V9;bZo{zJdm_Q`|6v777Wu zv}C%YS9Rm_^`@8h+(jPkpYYei6?qgO;AD}H8~EvjBjb?_{Ju}TFyfN&DgKe@6m!B> zD=`bv=^P>7{pvAB(d~G`hRY2lFR;VGFr-Vf#ptgoMM5>-zBfEVoa6be-}%^KZp@3V zOhvhUZ%k@b2KeRIu8hMT1^BZTzgfsf%k4K;sUhPS|d`+cJcf(`OY0vyI>q{1w;5YBLv7##Fb5zm7RarcX8#6Dj zM<3_4<{s`~$=zRoV=V>YQc;-Kr~V1r?(s+eNL z^sYOVep%7j?RKYn3Tcde-+>1JX9T=_T9l*W1wXwhSOaEjY#evUd&EF7#w_qWRg zj#;t}>S17!UijT7Q;y4MXl7OD9M7Q*{UT&uoB)!RX)_B+dDtvd2vien^GZUzCd(aH zcm_987yj+JSg@MUVvqDZ*Hf!g*eSsAeFJT_hv&U*hgKP^T0L;?E*Z{emd#vP**g&l zS`AO=CW}y0N6}J?xZMHPjcO{{l^jor880jU9f&nWW7%@Oz>zD2mbZly&8RNschY*V zj_mBHt>EHbqn-k?Lh2*rY=16@>!n|6ePm})2HAAFH^`n-_ss|qDY8%s2`VsydNAWC zFM^4eNQLbC*)N7Q7FM@szqFJ>FUZ>F|KYPAKE%aA~cW`M8!p}~pY%N}zvcQCL*acV~Ex;Z|d>WE*Ja11*GN#m0wOUSog(JGaBwyU{oJZe`ZYmcs!+ zunK(kg6^FX`+*dqmE;b`wk|!l%&s8v_)r~Ev2hqV`2w~nL=$+WUfa;1JI@}zN}yd{ zKxyV}5jyo9{FF-oPh38gxAD&(!ae*-JjJ|th3(}(u(7f#c)!U8fXPc|cB5nBD8QM7 zZ@j!hvz?5nuwjjvL#CRG+la*BqF!YTqg2)G6Qj2(w4aq@BSd(_A%y&rhLhri8*&%< z^4;|sefyvs+r>;7v3ftwnpLR{7U$>C7l^+|VDYXFiVtg$afU;?`pnR7Jcv81O?3O& zTE=$l(2U+E+YbDx1{ZU#w9aL}O7|6H@;tFDX9g3>K46uum54N`=j_QEVC53-_THnnmQjFH^1B1pAlv!%bQLfXw|wefw^^@ zqiK+)l>3vJt$>|n7`{q;IB=D~T*t&{kfxLPHwnGSdj4nj0f)qc|6MU8+3^0z0+aRa z<2y0JagE!IJIxN~m5UWU6dyO%tmV+-Qqu181ZA+17 zg^Kbc4cPj$X@6(EQhd29?oq>X)N#UMG9c63c*$dst~0jxD+K<8wO?xbZ9 zPQ7AHFAi>YUBsKxr>E4vNDYD%)k91tlV#&!@uYIgQr-V7x3#^;H`lJ@vhVA)&3gaL zz;%9quD7A|5|%ftk?5@2Ob0t^5ooHpLhh<$Nlrxc?m)g`!j=z!U@K8k%w|)86krq= zIrR`ur^eQEYc-2M$$y7!|H?0tE}LO3s;z_JF}5|;S@yKgjikf69<=3!X#8h`WnqY( z$pOu^HDpmJtI<={q-7_Ia-VZO0&|UHVz^U_knKe3XO~w+1Bozo+EA>1ZWI_~M5L%t z&u%ClAtw{*|D$?v7R`<1@lB4EP>eDkzaUn+&wEH6Qql}w17a@Vy9j`ql7+{uA^pwR zc)C|cqG&3J83Q*NHC9u-{}vM3Q?~C@Cml(xV-%VckZ`g*$ETuTvcG+Wi{o(Mo}QD8 znhH`Vl-`L>l8%>{Cgl?w|4=BMuHZ%?OhmxbIN0f&B8pQw8f_;@lL zC+b_qaIA)upDYMDNA55ggf>TG{v*vzKFu`);phWf_*uPWAR>H?7Xffd(Zg%e)X8?m zz6$Z{25$Q)fx?o=BPOaq=}Zz7SeYjDDb_vBtdU9=-fCrMNB+43qtOmms81Yv{tU{S z^g|iWw^onydFhaF4q8nT!6z}k4t0I%$9yd=4yMceGG(u?ryh_2Ttgq#gBAg)_ADOhc*tE)FHR8Vs+E(lf?=V#re-r_1N2-d{^(8L#WI$}LVs9)`w!b! z*sIYy(wQlZ=1DYGyH&oN@MlapR)-%K*X6H5`iC^4ldc(udS$pU^!*(&8d%ZNr>M~axZXPs>nWxUZsaw2!519ss3J2wF zQy=4CQrw0*Ek!2J zRi81Qv!Mzfjszz@k2ZKD1d5OMXu)H0XiwqINpEU*XxkiBr)`!ZD1-AxnF9RAM$pbj zJx`8F8;pi@Myl{mVni9Rqao7l=4O6-fIpHW47f{rgdl%X^<*lXtcmQh@HgjPJ)tbp z%_Jl#jwrZ2Gmg)yqL&*o{Ge7@*P(YCB-R$n8@MI`JDh75ZJaU1wfZ)cvLVr92{l{ z9}h&M4Es$2+@_RPXz(~^?H$3L0;rlpFj-%@IGx5Kit57%KjQK&_=C)l6KMRJjJEOw z&CFxIDJ>ce0apmolMRgT& zCwb&~e}36epQt(4{%%?*RPU$d=mmjuK?0X(1 zLXh8rx4_>eAs??dB!-k#)ri9AdmU&CQ2;~tMY5CKU(;U^DrhwBD_MW90qJ3neH;0b z$B=TQ8~(JkNy%xbO(CsKv|dRA#$EfbDz>ZZgw3EzvCNtveEO~kf{Wo*qN>F-1c!0n zQSLNN1#f!J=wjt@qu$T;F9sXTB*g@M*pFp3mudF6;zms#dl=Tzgp{Gsa7zfTkr(-*^4iz>&7_X?L@kw~dk!G|1OcIbe{5vs;n8!f^zSPQ! zZ41hCbx@KOp7?j4h6FYf=w0LdA%T!z4NXRHMtJMoDLRr=c4kVr3ZB&AbM+WEJ7QpB_cFIm zV@~HN=qq^xi83P{s+GPmY3!u+H$C-zY!dr$i3xO|jINv8fKK$HrfsY4Fv8{HsJmC; zF!x6`p$7A#tCxXE;5k*=nW2mInLzZWYDKc9~X${uTJt%&d&y&(a*HO%&Je?>)o{E)e8n_8{ z&l;ta?xbw8(e{U*;P$(&Q^u{a{NQ%{QHCdfe4RU-;-@~Q;(1|ND4xf4O&7I*jmkSI z7I>%j%8rD^#RsV$C&fS83wn^m#qSFFcf1fa#1m$Y9(it=7hRw+#L=h=0EVaBC2HNz z?7s`ZO0@c?09}qO%6i3Y2xw+{VAfQq%S4NIxr*gap_P$#o2cb0uU*`B^Fsn$&{9SF z=E9uszvhi9a9j6llI|mARSCMy~7)J>Nkf}of#L_ zlXkFsk;pZC!M>}AhusfbgreMlASz=G?drhqB}eud?H1(6kq)xRr&l0MzIU+?u}JU* zcqXs5ZA11iEL~$=QdCE&c`$zuAMtVj2ACCw!Ztw#1~FBX%|rr-E9Y^q23(_Vc|)8J zg30FZFBOPjD#HE*a*Z*yOP6}9p%vOvnsXv&`Al6b-=(G*<_L`(efM;*+S-pPREi1- znjJ_)ev()D4cUE-T#tK=NL}n-eO$%e<)I482;$1t#I<(WGi`NF?w zG4o3MQ<2lnj*B5ZZ(z~l66TW}>srZAKYQ}x@j0EhM3$-7bq7+p32V<@>W738&s|pn zUA8|4m@V~nW^beMc16Aw_3n48UVM}u_6gVlCPnBZ{LXb87{UQfDgTW~YkxWfb;mMC zuimtdZNSYH^<^@XmfOY;QC&5Dct=!2PIwlR9bC2_63V}?l)Ywan_KT1<#c2&>>wVx_B$=@UEKl zc~)c?8`Z1M4C-%I@|E|_+i&{$hoN$D!)_B@9$5!^Q_|$djOp};(<>$I}B{-A4TiC&YrfR$Q zK}u)(-PAa}@5AEB^1`wY7B!`4NHXBz5rHoTDb=-pz}>hncu3VDEalHtd98=3??E7# z07tJr)Wz!^2`R|AaWmKykxn%Uc@@%YC{!~djh?bZoe4$=K&_^QlTgZF&jekk==^z; zopt`PZML)H!e@eO{KCbGwb)&;)O+6BLTK`Fz~$bJOi=G+;T+Dg7fd zsr*~GdE9ipyKlcdmEz$Q_;U*smT!@UrZ@jiyDw|WM@XY^;Q4*EFgBQ7b5m~RxnCOk zkD=g_C|Ivz^_P6BixB!)l>}VcbPEf%Oz)gR5CKJc3~C~PT>WWrffn7(kz`x;N;*Yk zoNR((wkoTgnd@_jY}O!Memp83WHjE%c?eozFsT%>6_=EC5GsBONm%@iF0B*>0LM*3 z-DG^Bf~~wt!bk*SCOYzjVs1BMx&3EkY`R?3cKG9!Ja6 z8aqE_P`ehr=w7z7Ll}w?Mc$0ba37A(QYChnh)Mq07to!J$1e4_R#!84Z|TeKAO|S0#x`GK`l3{=JU4yVOvj5QeT^}0HpBZg=}#s z6K`#)jwJv$Q(nLpsp7~+R;{0%xJj(caQN0 zoObIaJ;TXX@D$KEUS6X&D}+^;^VQLlz`I@`$;aaC%2PZErDD@euf-QpLWzI{^ zMdn^XSB;~zjg%Cb3CoCF&&P#foikWBH``u7nWI*su6&34nqDua(= z4D8wvLw*ev?h`nmG#AYUZTb{GncrLdZ&wm5K?p>M*d;WYRR@Ga8G3xQHHL`odo{}& zf!fo2AZy_)$9SiacS~Rnvp=6;9;xyLND%bJcm!rootc+$SIWyrTc-Q0=(w)sf+$RA zL+}t?hyo4ZgI7JF_uX-D#YMf@!`8?Sjz+V&ne%ortfHd&rFz zK}zJWa53$sWAoJ}>wE&Yd+jwGKR+`@E&z2tqB{QTvzOyDxzD zDxUo0ps1HK81<_m&NB@u6u}rh*XU917YOA!4X(?6IgYgyUf}@Ms2I@Sm+x= z%ycv~$(BN!wjH|-y}i&u`jbRdj8y%%i%GttWAfp9zs@3Y*-J`&y11&0?z7IFc}p~b znq$b7xY#uQTXE{=G)|?b1UnRYYSJVkD)`DWe?~ut$)??<528Z;UTpNEs9f%ZUOfv& zh)a;6j!6#>f$m1cJ%7{~J8ixi`K@3$%4#~Dc7J~ffZ~2BO%fXWc~4U>n(xs+6+@as zVf1GP78@0c-J*@Kd5bQEo*@TDkxCHw6y2RmJ)YXb$fP+abnK-$%Wg5zK1WGkT0DoH z_4o6oWM~HUI!{Eqc!gi6ihXZCKP5ry4R!YzUB1hwkF;V*~2HNugITgH~&2W^HO%d z+JxLOvSsiV32Sw7DVr5PNFKvH8~N2RRn@UkL$+@kz8EorN>ugx(L}L#O9oUr9|s;G zl9H9OWsFZoH2hwTN4V(NSQ6=}C-+(m*o^>t(~9iAnp(y5_*uf+x&EyL3!-D~!5IKJL z3*XjZdTrasQXd@fL+Z;$9yf8_uVmmOJCxyr!uHB~%j5i|vwT9iU5Dk1Q@i;UF*3Fo zUkbsMg2D3oO?Sj-f4fn+8+}a2Ry~w~ zmM-n8Tc5ddXAKuz+LOO(_MUrx7uMz7a&F*?xyM&6wbm2dPwbEXOfs0aK;Du6YMXJG zDo)4=>&a=JgR4ea$JUn8LC-PmVB-k<&Syu6GS5T<;s!RbLzPy`y@MG%m0q6GC^PQN zNr7oqDX;pPu%Ku0D0gxYtaLJcZ@RaMB*gV?d&Ihaa8QCLg`KC9FEf4*GOwA(#@1mTXQMHZFpoVL~$Mq9Uaz}jBt>KZSL9=5=!gG zQeGWJ$zUpunu@6^vmznkigekmi_yZD^QFE0o@erpr49am(kDL++cy*IM30*avZtix zVj=&YFS|tFrpfP#LQY0e(FU-(i$~B{(ckYd3W=obk9>ily)$kNT-TY<YCcxZw#IS zb)^vwG&TCi&b;Jx#3U&U2!XrXf$ie{&pW2_*oxl>M{|lV-sJyIuBcU7wt9OvL`X-2 zX>}n@o{QKYj;rhsP!Q6wYUzCE_T{o1De+_E)8pXcn`G|)0Z2f%zdy7Hm~W8Q zZTeiTT{{=bNoO^H!{C+!I6DVbH8u6GqE*bt#@?`$Te?G3Toe%zmR?)i2n%|n6>DBC zZg(x*HvT6QCR}7F@@CcWJjdM(fiN6X(U+Z*i^WPYva@ivy#4ow#5uHwiWG0$Zcs#e zcJJOUD_71@gG88DRiCgw^!WW;$m+Ckb?bNEzAaNuyrOAfX_C}I--e^R-4S7PH;zRa zn<|pGgP(K?WduT3!D`;k{X`{h(Y#Ct$pZnjuC5M@{h35WCItMb{2rGpjLH>4RNJOb zn>KxOP=3$Nm~qqX_4St5z~EzRYwNLRZG$5!Dp@vf{!;q%87-X>F=M%hY7mseJ`O{P z9x5~?#f7SFtfLn*q&Vk=-<7?tuHo(QaO*iIpPa{40A_l6=FGgiF1MWn+Lo2t53$dr?A1(qmRYfA0eUysJj| zu5ihDIH&tQTp$Yv4KyNH^L;7(2-*)`S|NTGGVlYGs(WvG9vQF1-hvb4zIR{5@d}Y1 zV~xEEt6?!lTp;v>IB%@TWFXPC*a~TF3YypIPi0`QJf+n6iz>v0;E~+QNh?|Vcvqo( z8N9CsR-lmvr!{N7g0B?UCZN%X-)l#(5G)48{$jS%rcJwbL2y-O&dklLt?l<@c)0W8 zy1GVK%NuPjSFGgcFPB}rHcDnDcAG#_G9^Az5;6T(j#Z8Q(o>oiSaACj^k zYdw^GbFmJeUAOKV_1o|Z^LV+yc7N%CoV107#Yjo%YT3SRopkHohgk;6IQ8>je*jVO z-~+qHS&m2M&uH`V^LehVTg5XLePvx;g6)J8{;=fDH}g({g>Mo3(wrFmc^&o$TTRV&ILYsV@XJwx zmC=p4l@6oQGvc!7(MSzaNM6Pv<4@Q%n9j(O-i^606aj}AVPqVVre1_$#9Re)(O3&=>@(Kbq zaw`%l41CXptp5W&0_O@|aqT z#O++MSatqe@!PiQbp+V8eV+^9xA@3mcBw7r}|o5$+#@S-~U(e)Pdt z2!oCNw1`5uo2j%UABRflqy76VPK>r28@>0Od1hY0%$W~_j~{;>xUVUB-hDSOf>s+? zYVL73Y)>)>F`o0=9buN@;_b$bLHRd5xwnI$`$K{r(i)(ttZXOz98~Z?N<2F^YsSUD z^A_SWZCXowsBoQjTHcD+UdwwvGBWIPOtJdVd1Bg#s|yR2g2k+>X|ecXgKQnwONz|B z0hwt~%3w&OFSbhg$<-Z7k5OcEn+PI79|j?S2^qB+IJ|@9%DW$tD^EFIQj?OPcyoJ{ zpd0t&kd0|yk2Kwb%wK(Xj@&W#GwI(WQ?34@vZm8vjDTV3R285?)6h^ZQBH1D?{mY~ zDy*uicE+@6w=aO4bz(3FVbPR4@4x>@7zT0;n2VbZc{CYU=n1dnR9V?B4ASx5E-v1bDsNpgj%#8N*P_JPB+k*I`BKjDHdW zA*o>1BgTg?#)nVNfZYnCV80K+?I3fT&CYwRHJFk2GO^P~)I8Prv0}wm&wZ*a@|E#? zCAEnbY1gh@bUeX4zkF6(+W;oFhCwJm!E;%vs&=b5QDkB@TG?M-3Nd=jkrq47%F03r ziZBX3KNLy}1RAV+4NHi@s#TQ=3m%&ZyAMH%F!?@$aqn_qX~(z<)3**wMY)Pa39S8+ z-YH(b`S?lc+BpL;3Ej%NVLXSiC=Zmt#Ols_Yh}~cZK@N8KMcCFfmpH+HDSMz-KVn40#Ui^&VTE%?}cisxEqK_2CpiGlNv z$dGL3G-&C-$a-6J+YgS|C=-hChRgTAcH8KJg-*K8^|J#r$q#Nq7HyTXK z=-dKfpUHs6lwe!$H$OjJ6@t|wMQ(RP6_k!J50?BQ5H=XJ8j1j7tC$K_Q%}-XPA$)c z3_a}B0uq3AX>6=9@d3f)U)tEi_E0=q)`r5*nh&9e(@LU1!Lq^429_wKdhAtp{Fsq4 zYUp6~dtV=k30S!ZWsjO#V`J?>qlb~9Lm?4v=&lmU#TubN&|YX@J7q-vezDOKHhEZw zD}}_v91PV!5qE*lJ1TJsl$#Knyx39Y=zNJlb4zb%sKq;O#-03tKjoG|*MqiFUS6IR zmaRruxOQV~4Ob1Xj{{_`UUoMc2CrHKwJj+tQ(N+I=8-{CT?os7?gC0!qZ$meZE-)+ z&D(cMdMf>Ap`m~tW3Xeigdb0j1@zRb6_LzW<;g5Nwt?BXpD5CqDU)(-s{GO5~IoA`8Q^(B8fsA8thRAw;K4+wIOx5ny0$9R(4^87SgQUxJgFcc$Zu_xVPk&RZ4bRlH7B{wMyaoaQ^3V z!*eraWS0yn!}Q@h->sDGZ$GDsjE+t~e)Q*scf(Jj$E)aZxe#0zNyX*zG%J$oo zb|ekh_&iM9^%o-hTlBU$2Os2vm1Br0Sb5eBbJR}Z5Q3K9QKyKQ>I+g_~esej>|5)T#csY&(EWB6|mk|FAEEE zU=-xRx?HP+BbAsK1e#NG?8qJT>~{(|dPqAFvS3Mb3RdSV6>N2Eg=yUr6&2yAs;avP z7yq(+dAYb~sE?AXuf z)m4$P-+K9>Ao`b7CixCva~Z!I1Er+}#i|(|0O$Ua(D<3_(k%31pwKXW6oV|&NvU2b zO#Ta>eDbo}vGtOheptD=w>G&WJoVIV9L3hc!TonpQLdQk>N+emvO>XfW7mjIP-+YX zE1UsDf7mAPV5MM5VX2IRg;E9M#KYB4s#d?YNOqs4WIXmGIMk9+A-@X`Vn_vaUD>DUrCmsrC^PlZ5YZqg&;dwPR4eTS)H*%v;kEEq#8@C1N34sUq{uOHUkXqIOASEVdiEkCH zbetX(3qn)y`#LzWe}IJm^Z3KyU49$N?`yB!`XGeg5ZsGktyaXc!So(5PKZnAO~4v~ zld)w*#3_Shc&9k2hJZ8Qhq@u~5sg)b^TSS7&Z+7l;CxW&i8tsm%0AGS7EI}nJ0_y6 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 z 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

OKghv!PvB=#*P@(BkfP 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 Date: Sat, 18 Jan 2020 16:09:39 -0500 Subject: [PATCH 07/90] Update readme.md --- usermods/Enclosure_with_OLED_temp_ESP07/assets/readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/usermods/Enclosure_with_OLED_temp_ESP07/assets/readme.md b/usermods/Enclosure_with_OLED_temp_ESP07/assets/readme.md index 4466821bc..0634f9a3b 100644 --- a/usermods/Enclosure_with_OLED_temp_ESP07/assets/readme.md +++ b/usermods/Enclosure_with_OLED_temp_ESP07/assets/readme.md @@ -1,9 +1,9 @@ # Enclosure and PCB ## IP67 rated enclosure -![Enclosure](assets/controller.jpg) +![Enclosure](controller.jpg) ## PCB -![PCB](assets/controller.jpg) +![PCB](controller.jpg) ``` ``` From 761245049f8c0e1ddec394d631cf171419af66dd Mon Sep 17 00:00:00 2001 From: srg74 <28492985+srg74@users.noreply.github.com> Date: Sat, 18 Jan 2020 16:10:03 -0500 Subject: [PATCH 08/90] Update readme.md --- usermods/Enclosure_with_OLED_temp_ESP07/assets/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usermods/Enclosure_with_OLED_temp_ESP07/assets/readme.md b/usermods/Enclosure_with_OLED_temp_ESP07/assets/readme.md index 0634f9a3b..43331df7f 100644 --- a/usermods/Enclosure_with_OLED_temp_ESP07/assets/readme.md +++ b/usermods/Enclosure_with_OLED_temp_ESP07/assets/readme.md @@ -4,6 +4,6 @@ ![Enclosure](controller.jpg) ## PCB -![PCB](controller.jpg) +![PCB](pcb.png) ``` ``` From b00c1dd0556ad426ba8b03ab2313d908afdd0bf1 Mon Sep 17 00:00:00 2001 From: srg74 <28492985+srg74@users.noreply.github.com> Date: Sat, 18 Jan 2020 16:14:32 -0500 Subject: [PATCH 09/90] Update readme.md --- usermods/Enclosure_with_OLED_temp_ESP07/readme.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/usermods/Enclosure_with_OLED_temp_ESP07/readme.md b/usermods/Enclosure_with_OLED_temp_ESP07/readme.md index 2541e6955..69cd8e96e 100644 --- a/usermods/Enclosure_with_OLED_temp_ESP07/readme.md +++ b/usermods/Enclosure_with_OLED_temp_ESP07/readme.md @@ -2,7 +2,10 @@ This usermod is using ideas from @mrVanboy and @400killer ## Features - SSD1306 128x32 and 128x64 I2C OLED display (optional) +- On screen IP address and controller status (e.g. ON or OFF, recent effect) +- Auto display shutoff for saving display lifetime - Dallas temperature sensor (optional) +- Reporting temperature to MQTT broker ## Hardware ![Hardware connection](assets/controller.jpg) From 40c041c9e77c8895b77e66cc0a69acae753b54dd Mon Sep 17 00:00:00 2001 From: srg74 <28492985+srg74@users.noreply.github.com> Date: Sat, 18 Jan 2020 16:15:37 -0500 Subject: [PATCH 10/90] Update readme.md --- usermods/Enclosure_with_OLED_temp_ESP07/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usermods/Enclosure_with_OLED_temp_ESP07/readme.md b/usermods/Enclosure_with_OLED_temp_ESP07/readme.md index 69cd8e96e..5476fa19f 100644 --- a/usermods/Enclosure_with_OLED_temp_ESP07/readme.md +++ b/usermods/Enclosure_with_OLED_temp_ESP07/readme.md @@ -2,7 +2,7 @@ This usermod is using ideas from @mrVanboy and @400killer ## Features - SSD1306 128x32 and 128x64 I2C OLED display (optional) -- On screen IP address and controller status (e.g. ON or OFF, recent effect) +- On screen IP address, SSID and controller status (e.g. ON or OFF, recent effect) - Auto display shutoff for saving display lifetime - Dallas temperature sensor (optional) - Reporting temperature to MQTT broker From 2fc2a0cbdba9e06654a1409003da3d77f5bbadee Mon Sep 17 00:00:00 2001 From: srg74 <28492985+srg74@users.noreply.github.com> Date: Sat, 18 Jan 2020 16:16:51 -0500 Subject: [PATCH 11/90] Update readme.md --- usermods/Enclosure_with_OLED_temp_ESP07/readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/usermods/Enclosure_with_OLED_temp_ESP07/readme.md b/usermods/Enclosure_with_OLED_temp_ESP07/readme.md index 5476fa19f..c4dbbcfc5 100644 --- a/usermods/Enclosure_with_OLED_temp_ESP07/readme.md +++ b/usermods/Enclosure_with_OLED_temp_ESP07/readme.md @@ -10,8 +10,8 @@ This usermod is using ideas from @mrVanboy and @400killer ## Hardware ![Hardware connection](assets/controller.jpg) -## Requirements -Functionality checked with: +## Functionality +checked with: - ESP-07S - PlatformIO - SSD1306 128x32 I2C OLED display @@ -19,7 +19,7 @@ Functionality checked with: - KY-022 (infrared receiver) - Push button (N.O. momentary switch) -### Platformio +### Platformio requirements Uncomment `U8g2@~2.27.3`,`DallasTemperature@~3.8.0`,`OneWire@~2.3.5 under` `[common]` section in `platformio.ini`: ```ini # platformio.ini From 9a9d2cb964e810d86f3335ba6492b33551b67521 Mon Sep 17 00:00:00 2001 From: srg74 <28492985+srg74@users.noreply.github.com> Date: Sat, 18 Jan 2020 16:17:45 -0500 Subject: [PATCH 12/90] Update readme.md --- usermods/Enclosure_with_OLED_temp_ESP07/readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/usermods/Enclosure_with_OLED_temp_ESP07/readme.md b/usermods/Enclosure_with_OLED_temp_ESP07/readme.md index c4dbbcfc5..0bf8653bd 100644 --- a/usermods/Enclosure_with_OLED_temp_ESP07/readme.md +++ b/usermods/Enclosure_with_OLED_temp_ESP07/readme.md @@ -1,10 +1,10 @@ # Almost universal controller board for outdoor applications This usermod is using ideas from @mrVanboy and @400killer ## Features -- SSD1306 128x32 and 128x64 I2C OLED display (optional) +- SSD1306 128x32 and 128x64 I2C OLED display - On screen IP address, SSID and controller status (e.g. ON or OFF, recent effect) - Auto display shutoff for saving display lifetime -- Dallas temperature sensor (optional) +- Dallas temperature sensor - Reporting temperature to MQTT broker ## Hardware From 8831d74cbc03ea998e715e54544818b74d97019c Mon Sep 17 00:00:00 2001 From: srg74 <28492985+srg74@users.noreply.github.com> Date: Sat, 18 Jan 2020 16:29:28 -0500 Subject: [PATCH 13/90] Update readme.md --- usermods/Enclosure_with_OLED_temp_ESP07/assets/readme.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/usermods/Enclosure_with_OLED_temp_ESP07/assets/readme.md b/usermods/Enclosure_with_OLED_temp_ESP07/assets/readme.md index 43331df7f..4d1005d9c 100644 --- a/usermods/Enclosure_with_OLED_temp_ESP07/assets/readme.md +++ b/usermods/Enclosure_with_OLED_temp_ESP07/assets/readme.md @@ -5,5 +5,3 @@ ## PCB ![PCB](pcb.png) -``` -``` From 43e0186efe6013f883ae56cca540bd09aca2a7f2 Mon Sep 17 00:00:00 2001 From: Ser Ko Date: Sat, 18 Jan 2020 16:46:48 -0500 Subject: [PATCH 14/90] Update readme.md --- usermods/Enclosure_with_OLED_temp_ESP07/readme.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/usermods/Enclosure_with_OLED_temp_ESP07/readme.md b/usermods/Enclosure_with_OLED_temp_ESP07/readme.md index 0bf8653bd..13cee46b6 100644 --- a/usermods/Enclosure_with_OLED_temp_ESP07/readme.md +++ b/usermods/Enclosure_with_OLED_temp_ESP07/readme.md @@ -10,8 +10,7 @@ This usermod is using ideas from @mrVanboy and @400killer ## Hardware ![Hardware connection](assets/controller.jpg) -## Functionality -checked with: +## Functionality checked with: - ESP-07S - PlatformIO - SSD1306 128x32 I2C OLED display From 57c0b408e5bb104e40affd988361caa872cf4e04 Mon Sep 17 00:00:00 2001 From: Ser Ko Date: Sat, 18 Jan 2020 17:26:06 -0500 Subject: [PATCH 15/90] Update readme.md --- .../Enclosure_with_OLED_temp_ESP07/readme.md | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/usermods/Enclosure_with_OLED_temp_ESP07/readme.md b/usermods/Enclosure_with_OLED_temp_ESP07/readme.md index 13cee46b6..aabf65e0d 100644 --- a/usermods/Enclosure_with_OLED_temp_ESP07/readme.md +++ b/usermods/Enclosure_with_OLED_temp_ESP07/readme.md @@ -1,22 +1,22 @@ # Almost universal controller board for outdoor applications This usermod is using ideas from @mrVanboy and @400killer ## Features -- SSD1306 128x32 and 128x64 I2C OLED display -- On screen IP address, SSID and controller status (e.g. ON or OFF, recent effect) -- Auto display shutoff for saving display lifetime -- Dallas temperature sensor -- Reporting temperature to MQTT broker +* SSD1306 128x32 and 128x64 I2C OLED display +* On screen IP address, SSID and controller status (e.g. ON or OFF, recent effect) +* Auto display shutoff for saving display lifetime +* Dallas temperature sensor +* Reporting temperature to MQTT broker ## Hardware ![Hardware connection](assets/controller.jpg) ## Functionality checked with: -- ESP-07S -- PlatformIO -- SSD1306 128x32 I2C OLED display -- DS18B20 (temperature sensor) -- KY-022 (infrared receiver) -- Push button (N.O. momentary switch) +* ESP-07S +* PlatformIO +* SSD1306 128x32 I2C OLED display +* DS18B20 (temperature sensor) +* KY-022 (infrared receiver) +* Push button (N.O. momentary switch) ### Platformio requirements Uncomment `U8g2@~2.27.3`,`DallasTemperature@~3.8.0`,`OneWire@~2.3.5 under` `[common]` section in `platformio.ini`: @@ -33,4 +33,4 @@ lib_deps_external = DallasTemperature@~3.8.0 OneWire@~2.3.5 ... -``` +``` \ No newline at end of file From 1d897e491a9d25180cfc98d5e9da73d733f84834 Mon Sep 17 00:00:00 2001 From: Ser Ko Date: Sat, 18 Jan 2020 17:31:36 -0500 Subject: [PATCH 16/90] Update readme.md --- .../Enclosure_with_OLED_temp_ESP07/readme.md | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/usermods/Enclosure_with_OLED_temp_ESP07/readme.md b/usermods/Enclosure_with_OLED_temp_ESP07/readme.md index aabf65e0d..8a20dc1ba 100644 --- a/usermods/Enclosure_with_OLED_temp_ESP07/readme.md +++ b/usermods/Enclosure_with_OLED_temp_ESP07/readme.md @@ -1,22 +1,22 @@ # Almost universal controller board for outdoor applications This usermod is using ideas from @mrVanboy and @400killer ## Features -* SSD1306 128x32 and 128x64 I2C OLED display -* On screen IP address, SSID and controller status (e.g. ON or OFF, recent effect) -* Auto display shutoff for saving display lifetime -* Dallas temperature sensor -* Reporting temperature to MQTT broker +* SSD1306 128x32 and 128x64 I2C OLED display +* On screen IP address, SSID and controller status (e.g. ON or OFF, recent effect) +* Auto display shutoff for saving display lifetime +* Dallas temperature sensor +* Reporting temperature to MQTT broker ## Hardware ![Hardware connection](assets/controller.jpg) ## Functionality checked with: -* ESP-07S -* PlatformIO -* SSD1306 128x32 I2C OLED display -* DS18B20 (temperature sensor) -* KY-022 (infrared receiver) -* Push button (N.O. momentary switch) +* ESP-07S +* PlatformIO +* SSD1306 128x32 I2C OLED display +* DS18B20 (temperature sensor) +* KY-022 (infrared receiver) +* Push button (N.O. momentary switch) ### Platformio requirements Uncomment `U8g2@~2.27.3`,`DallasTemperature@~3.8.0`,`OneWire@~2.3.5 under` `[common]` section in `platformio.ini`: From 71021da2619061c72ac286ad7dcd803ca87557ad Mon Sep 17 00:00:00 2001 From: Ser Ko Date: Sat, 18 Jan 2020 17:36:04 -0500 Subject: [PATCH 17/90] Update readme.md --- .../Enclosure_with_OLED_temp_ESP07/readme.md | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/usermods/Enclosure_with_OLED_temp_ESP07/readme.md b/usermods/Enclosure_with_OLED_temp_ESP07/readme.md index 8a20dc1ba..c8c873529 100644 --- a/usermods/Enclosure_with_OLED_temp_ESP07/readme.md +++ b/usermods/Enclosure_with_OLED_temp_ESP07/readme.md @@ -1,22 +1,22 @@ # Almost universal controller board for outdoor applications This usermod is using ideas from @mrVanboy and @400killer ## Features -* SSD1306 128x32 and 128x64 I2C OLED display -* On screen IP address, SSID and controller status (e.g. ON or OFF, recent effect) -* Auto display shutoff for saving display lifetime -* Dallas temperature sensor -* Reporting temperature to MQTT broker +* SSD1306 128x32 and 128x64 I2C OLED display +* On screen IP address, SSID and controller status (e.g. ON or OFF, recent effect) +* Auto display shutoff for saving display lifetime +* Dallas temperature sensor +* Reporting temperature to MQTT broker ## Hardware ![Hardware connection](assets/controller.jpg) ## Functionality checked with: -* ESP-07S -* PlatformIO -* SSD1306 128x32 I2C OLED display -* DS18B20 (temperature sensor) -* KY-022 (infrared receiver) -* Push button (N.O. momentary switch) +* ESP-07S +* PlatformIO +* SSD1306 128x32 I2C OLED display +* DS18B20 (temperature sensor) +* KY-022 (infrared receiver) +* Push button (N.O. momentary switch) ### Platformio requirements Uncomment `U8g2@~2.27.3`,`DallasTemperature@~3.8.0`,`OneWire@~2.3.5 under` `[common]` section in `platformio.ini`: From 0cae048ada14931162d7536f65acddb9db96571d Mon Sep 17 00:00:00 2001 From: srg74 <28492985+srg74@users.noreply.github.com> Date: Sat, 18 Jan 2020 17:40:52 -0500 Subject: [PATCH 18/90] Update readme.md --- .../Enclosure_with_OLED_temp_ESP07/readme.md | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/usermods/Enclosure_with_OLED_temp_ESP07/readme.md b/usermods/Enclosure_with_OLED_temp_ESP07/readme.md index c8c873529..10894266b 100644 --- a/usermods/Enclosure_with_OLED_temp_ESP07/readme.md +++ b/usermods/Enclosure_with_OLED_temp_ESP07/readme.md @@ -1,22 +1,22 @@ # Almost universal controller board for outdoor applications This usermod is using ideas from @mrVanboy and @400killer ## Features -* SSD1306 128x32 and 128x64 I2C OLED display -* On screen IP address, SSID and controller status (e.g. ON or OFF, recent effect) -* Auto display shutoff for saving display lifetime -* Dallas temperature sensor -* Reporting temperature to MQTT broker +* SSD1306 128x32 and 128x64 I2C OLED display +* On screen IP address, SSID and controller status (e.g. ON or OFF, recent effect) +* Auto display shutoff for saving display lifetime +* Dallas temperature sensor +* Reporting temperature to MQTT broker ## Hardware ![Hardware connection](assets/controller.jpg) ## Functionality checked with: -* ESP-07S -* PlatformIO -* SSD1306 128x32 I2C OLED display -* DS18B20 (temperature sensor) -* KY-022 (infrared receiver) -* Push button (N.O. momentary switch) +* ESP-07S +* PlatformIO +* SSD1306 128x32 I2C OLED display +* DS18B20 (temperature sensor) +* KY-022 (infrared receiver) +* Push button (N.O. momentary switch) ### Platformio requirements Uncomment `U8g2@~2.27.3`,`DallasTemperature@~3.8.0`,`OneWire@~2.3.5 under` `[common]` section in `platformio.ini`: @@ -33,4 +33,4 @@ lib_deps_external = DallasTemperature@~3.8.0 OneWire@~2.3.5 ... -``` \ No newline at end of file +``` From 71af63dfc71ea17f1bef9176d6032ea826c320a3 Mon Sep 17 00:00:00 2001 From: srg74 <28492985+srg74@users.noreply.github.com> Date: Sat, 18 Jan 2020 17:45:11 -0500 Subject: [PATCH 19/90] Update readme.md --- usermods/Enclosure_with_OLED_temp_ESP07/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usermods/Enclosure_with_OLED_temp_ESP07/readme.md b/usermods/Enclosure_with_OLED_temp_ESP07/readme.md index 10894266b..598c0c0a3 100644 --- a/usermods/Enclosure_with_OLED_temp_ESP07/readme.md +++ b/usermods/Enclosure_with_OLED_temp_ESP07/readme.md @@ -10,7 +10,7 @@ This usermod is using ideas from @mrVanboy and @400killer ## Hardware ![Hardware connection](assets/controller.jpg) -## Functionality checked with: +## Functionality checked with * ESP-07S * PlatformIO * SSD1306 128x32 I2C OLED display From 4d646e67f3d0362d5e0d99775dfee211ad1e5fe3 Mon Sep 17 00:00:00 2001 From: srg74 <28492985+srg74@users.noreply.github.com> Date: Sat, 18 Jan 2020 20:03:18 -0500 Subject: [PATCH 20/90] Create ccpp.yml --- .github/workflows/ccpp.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .github/workflows/ccpp.yml diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml new file mode 100644 index 000000000..1247fb050 --- /dev/null +++ b/.github/workflows/ccpp.yml @@ -0,0 +1,19 @@ +name: C/C++ CI + +on: [push] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v1 + - name: configure + run: ./configure + - name: make + run: make + - name: make check + run: make check + - name: make distcheck + run: make distcheck From 1c5742c7d402109bd7ad07a6deabe2fa842e8de8 Mon Sep 17 00:00:00 2001 From: srg74 <28492985+srg74@users.noreply.github.com> Date: Sat, 18 Jan 2020 20:04:15 -0500 Subject: [PATCH 21/90] Delete ccpp.yml --- .github/workflows/ccpp.yml | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 .github/workflows/ccpp.yml diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml deleted file mode 100644 index 1247fb050..000000000 --- a/.github/workflows/ccpp.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: C/C++ CI - -on: [push] - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v1 - - name: configure - run: ./configure - - name: make - run: make - - name: make check - run: make check - - name: make distcheck - run: make distcheck From 81aab35231e1380d03ebfb99aacda76533133556 Mon Sep 17 00:00:00 2001 From: srg74 <28492985+srg74@users.noreply.github.com> Date: Sat, 18 Jan 2020 20:22:44 -0500 Subject: [PATCH 22/90] Create main.yml --- .github/workflows/main.yml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 000000000..c4a9ef92b --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,9 @@ +name: Test +on: push +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Build all example sketches + uses: antimatter15/esp8266-arduino-builder-action@v4.0.0 From 1042ac141a96a5eec039611ca141e3a6bc4d39f1 Mon Sep 17 00:00:00 2001 From: srg74 <28492985+srg74@users.noreply.github.com> Date: Sat, 18 Jan 2020 20:27:35 -0500 Subject: [PATCH 23/90] Delete main.yml --- .github/workflows/main.yml | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index c4a9ef92b..000000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,9 +0,0 @@ -name: Test -on: push -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - name: Build all example sketches - uses: antimatter15/esp8266-arduino-builder-action@v4.0.0 From 38b2adcaadda3fc7da64166c49b84bcfa1cd3aea Mon Sep 17 00:00:00 2001 From: srg74 Date: Sun, 19 Jan 2020 18:53:11 -0500 Subject: [PATCH 24/90] Fixed wled06_usermod.ino And added link to readme.md --- usermods/Enclosure_with_OLED_temp_ESP07/readme.md | 2 ++ .../Enclosure_with_OLED_temp_ESP07/wled06_usermod.ino | 8 +------- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/usermods/Enclosure_with_OLED_temp_ESP07/readme.md b/usermods/Enclosure_with_OLED_temp_ESP07/readme.md index 598c0c0a3..047647d09 100644 --- a/usermods/Enclosure_with_OLED_temp_ESP07/readme.md +++ b/usermods/Enclosure_with_OLED_temp_ESP07/readme.md @@ -1,5 +1,7 @@ # Almost universal controller board for outdoor applications This usermod is using ideas from @mrVanboy and @400killer +## Firmware used +- [Original project repository](https://github.com/srg74/Controller-for-WLED-firmware) - Main controller repository ## Features * SSD1306 128x32 and 128x64 I2C OLED display * On screen IP address, SSID and controller status (e.g. ON or OFF, recent effect) diff --git a/usermods/Enclosure_with_OLED_temp_ESP07/wled06_usermod.ino b/usermods/Enclosure_with_OLED_temp_ESP07/wled06_usermod.ino index b3f1c5db5..31bba9e67 100644 --- a/usermods/Enclosure_with_OLED_temp_ESP07/wled06_usermod.ino +++ b/usermods/Enclosure_with_OLED_temp_ESP07/wled06_usermod.ino @@ -17,15 +17,9 @@ long lastMeasure = 0; // or check the gallery: // https://github.com/olikraus/u8g2/wiki/gallery // --> First choise of cheap I2C OLED 128X32 -<<<<<<< HEAD U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA // --> Second choise of cheap I2C OLED 128X64 //U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA -======= -//U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA -// --> Second choise of cheap I2C OLED 128X64 -U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA ->>>>>>> 1680c014057bb993974950f37b14369b398ca647 // gets called once at boot. Do all initialization that doesn't depend on // network here void userSetup() { @@ -209,4 +203,4 @@ void userLoop() { u8x8.drawGlyph(0, 1, 68); // home icon u8x8.setFont(u8x8_font_open_iconic_weather_2x2); u8x8.drawGlyph(0, 2, 66 + (bri > 0 ? 3 : 0)); // sun/moon icon -} +} \ No newline at end of file From 607a66b983fb3bb0967d713d74f612d678e47ce8 Mon Sep 17 00:00:00 2001 From: srg74 <28492985+srg74@users.noreply.github.com> Date: Sun, 19 Jan 2020 18:54:48 -0500 Subject: [PATCH 25/90] Update readme.md --- usermods/Enclosure_with_OLED_temp_ESP07/readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/usermods/Enclosure_with_OLED_temp_ESP07/readme.md b/usermods/Enclosure_with_OLED_temp_ESP07/readme.md index 047647d09..4af36d59c 100644 --- a/usermods/Enclosure_with_OLED_temp_ESP07/readme.md +++ b/usermods/Enclosure_with_OLED_temp_ESP07/readme.md @@ -1,7 +1,7 @@ # Almost universal controller board for outdoor applications This usermod is using ideas from @mrVanboy and @400killer -## Firmware used -- [Original project repository](https://github.com/srg74/Controller-for-WLED-firmware) - Main controller repository +## Project repository +- [Original repository](https://github.com/srg74/Controller-for-WLED-firmware) - Main controller repository ## Features * SSD1306 128x32 and 128x64 I2C OLED display * On screen IP address, SSID and controller status (e.g. ON or OFF, recent effect) From b58a17a99e612ea303d8bf678079c3f7c77e6e3f Mon Sep 17 00:00:00 2001 From: srg74 Date: Mon, 20 Jan 2020 15:28:39 -0500 Subject: [PATCH 26/90] Display size is irrelevant --- usermods/Enclosure_with_OLED_temp_ESP07/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usermods/Enclosure_with_OLED_temp_ESP07/readme.md b/usermods/Enclosure_with_OLED_temp_ESP07/readme.md index 4af36d59c..817fb7e4a 100644 --- a/usermods/Enclosure_with_OLED_temp_ESP07/readme.md +++ b/usermods/Enclosure_with_OLED_temp_ESP07/readme.md @@ -29,7 +29,7 @@ Uncomment `U8g2@~2.27.3`,`DallasTemperature@~3.8.0`,`OneWire@~2.3.5 under` `[com ... lib_deps_external = ... - #For use SSD1306 0.91" OLED display uncomment following + #For use SSD1306 OLED display uncomment following U8g2@~2.27.3 #For Dallas sensor uncomment following 2 lines DallasTemperature@~3.8.0 From 3d0d027216b1ea0ec988bd587db038a682daafd8 Mon Sep 17 00:00:00 2001 From: srg74 Date: Fri, 24 Jan 2020 16:52:41 -0500 Subject: [PATCH 27/90] Update platformio.ini --- platformio.ini | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/platformio.ini b/platformio.ini index efd1bfe82..c382f9243 100644 --- a/platformio.ini +++ b/platformio.ini @@ -7,15 +7,15 @@ data_dir = ./wled00/data ;lib_extra_dirs = ./wled00/src lib_dir = ./wled00/src ; Please uncomment one of the 5 lines below to select your board -env_default = nodemcuv2 -; env_default = esp01 -; env_default = esp01_1m -; env_default = esp07 -; env_default = d1_mini -; env_default = esp32dev -; env_default = esp8285_4CH_MagicHome -; env_default = esp8285_4CH_H801 -; env_default = esp8285_5CH_H801 +default_envs = nodemcuv2 +; default_envs = esp01 +; default_envs = esp01_1m +; default_envs = esp07 +; default_envs = d1_mini +; default_envs = esp32dev +; default_envs = esp8285_4CH_MagicHome +; default_envs = esp8285_4CH_H801 +; default_envs = esp8285_5CH_H801 [common] framework = arduino From 47738c751cfc97ed80d7a02b52628e590e83b481 Mon Sep 17 00:00:00 2001 From: Ser Ko Date: Wed, 5 Feb 2020 12:03:24 -0500 Subject: [PATCH 28/90] Update wled06_usermod.ino Controller design changed. New version with relay. Dallas sensor pin changed to GPIO 13. --- usermods/Enclosure_with_OLED_temp_ESP07/wled06_usermod.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usermods/Enclosure_with_OLED_temp_ESP07/wled06_usermod.ino b/usermods/Enclosure_with_OLED_temp_ESP07/wled06_usermod.ino index 31bba9e67..cfd94a872 100644 --- a/usermods/Enclosure_with_OLED_temp_ESP07/wled06_usermod.ino +++ b/usermods/Enclosure_with_OLED_temp_ESP07/wled06_usermod.ino @@ -5,7 +5,7 @@ #define U8X8_PIN_SCL 5 #define U8X8_PIN_SDA 4 // Dallas sensor -OneWire oneWire(12); +OneWire oneWire(13); DallasTemperature sensor(&oneWire); long temptimer = millis(); long lastMeasure = 0; From f105436b41a1577f22b4ad09c1912670a55c09d8 Mon Sep 17 00:00:00 2001 From: Ser Ko Date: Wed, 5 Feb 2020 12:23:06 -0500 Subject: [PATCH 29/90] Corrected comment Since 2 common size of OLED display can be used (0.91" and 0.96") removed comment about display size --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index c382f9243..842009550 100644 --- a/platformio.ini +++ b/platformio.ini @@ -51,7 +51,7 @@ lib_deps_external = https://github.com/crankyoldgit/IRremoteESP8266.git #Time@1.5 #Timezone@1.2.1 - #For use SSD1306 0.91" OLED display uncomment following + #For use SSD1306 OLED display uncomment following #U8g2@~2.27.2 #For Dallas sensor uncomment following 2 lines #DallasTemperature@~3.8.0 From adcd7fb1700b88059adc1d8931473087c5fe0ae3 Mon Sep 17 00:00:00 2001 From: Ser Ko Date: Wed, 5 Feb 2020 12:25:26 -0500 Subject: [PATCH 30/90] Added tested display sizes --- usermods/Enclosure_with_OLED_temp_ESP07/wled06_usermod.ino | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/usermods/Enclosure_with_OLED_temp_ESP07/wled06_usermod.ino b/usermods/Enclosure_with_OLED_temp_ESP07/wled06_usermod.ino index cfd94a872..2c55a9526 100644 --- a/usermods/Enclosure_with_OLED_temp_ESP07/wled06_usermod.ino +++ b/usermods/Enclosure_with_OLED_temp_ESP07/wled06_usermod.ino @@ -16,9 +16,9 @@ long lastMeasure = 0; // https://github.com/olikraus/u8g2/wiki/u8x8setupcpp // or check the gallery: // https://github.com/olikraus/u8g2/wiki/gallery -// --> First choise of cheap I2C OLED 128X32 +// --> First choise of cheap I2C OLED 128X32 0.91" U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA -// --> Second choise of cheap I2C OLED 128X64 +// --> Second choise of cheap I2C OLED 128X64 0.96" or 1.3" //U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA // gets called once at boot. Do all initialization that doesn't depend on // network here From 8ca86181e4b31efa86a36070adb67652f251785d Mon Sep 17 00:00:00 2001 From: Ser Ko Date: Wed, 19 Feb 2020 19:27:09 -0500 Subject: [PATCH 31/90] Updated wled06_usermod.ino User mode file updated for flip display in case of using on WLED Wemos shield --- .vscode/extensions.json | 12 ++++++------ .../wled06_usermod.ino | 1 + 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 8281e64cc..0f0d7401d 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,7 +1,7 @@ { - // See http://go.microsoft.com/fwlink/?LinkId=827846 - // for the documentation about the extensions.json format - "recommendations": [ - "platformio.platformio-ide" - ] -} \ No newline at end of file + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ] +} diff --git a/usermods/Enclosure_with_OLED_temp_ESP07/wled06_usermod.ino b/usermods/Enclosure_with_OLED_temp_ESP07/wled06_usermod.ino index 2c55a9526..99d155ca6 100644 --- a/usermods/Enclosure_with_OLED_temp_ESP07/wled06_usermod.ino +++ b/usermods/Enclosure_with_OLED_temp_ESP07/wled06_usermod.ino @@ -25,6 +25,7 @@ U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_ void userSetup() { sensor.begin(); //Start Dallas temperature sensor u8x8.begin(); + //u8x8.setFlipMode(1); //Uncoment if using WLED Wemos shield u8x8.setPowerSave(0); u8x8.setContrast(10); //Contrast setup will help to preserve OLED lifetime. In case OLED need to be brighter increase number up to 255 u8x8.setFont(u8x8_font_chroma48medium8_r); From 3e3f46a683c45a612104dcb2657b49d152e5510d Mon Sep 17 00:00:00 2001 From: Ser Ko Date: Thu, 20 Feb 2020 20:46:00 -0500 Subject: [PATCH 32/90] Added new mod --- .../readme.md | 25 ++ .../wled06_usermod.ino | 213 ++++++++++++++++++ 2 files changed, 238 insertions(+) create mode 100644 usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md create mode 100644 usermods/Wemos_D1_mini+Wemos32_mini_shield/wled06_usermod.ino diff --git a/usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md b/usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md new file mode 100644 index 000000000..7a489f03f --- /dev/null +++ b/usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md @@ -0,0 +1,25 @@ +# Wemos D1 mini and Wemos32 mini shield +## Project repository +- [Original repository](https://github.com/srg74/WLED-wemos-shield) - WLED Wemos shield repository +## Features +* SSD1306 128x32 or 128x64 I2C OLED display +* On screen IP address, SSID and controller status (e.g. ON or OFF, recent effect) +* Auto display shutoff for saving display lifetime +* Dallas temperature sensor +* Reporting temperature to MQTT broker +* Relay for energy saving + +## Hardware +![Shield](https://github.com/srg74/WLED-wemos-shield/blob/master/resources/Images/Assembly_8.jpg) + +## Wiki +![Wiki](https://github.com/srg74/WLED-wemos-shield/wiki) + +## Functionality checked with +* Wemos D1 mini original v3.1 and clones +* Wemos32 mini +* PlatformIO +* SSD1306 128x32 I2C OLED display +* DS18B20 (temperature sensor) +* Push button (N.O. momentary switch) + diff --git a/usermods/Wemos_D1_mini+Wemos32_mini_shield/wled06_usermod.ino b/usermods/Wemos_D1_mini+Wemos32_mini_shield/wled06_usermod.ino new file mode 100644 index 000000000..0e1b1e66b --- /dev/null +++ b/usermods/Wemos_D1_mini+Wemos32_mini_shield/wled06_usermod.ino @@ -0,0 +1,213 @@ +#include // from https://github.com/olikraus/u8g2/ +#include //Dallastemperature sensor + +//The SCL and SDA pins are defined here. +//ESP8266 Wemos D1 mini boards use SCL=5 SDA=4 +#define U8X8_PIN_SCL 5 +#define U8X8_PIN_SDA 4 +//ESP32 Wemos32 mini boards use SCL=22 SDA=21 +//#define U8X8_PIN_SCL 22 +//#define U8X8_PIN_SDA 21 + +// Dallas sensor +OneWire oneWire(13); //ESP8266 boards +//OneWire oneWire(23); //ESP32 boards +DallasTemperature sensor(&oneWire); +long temptimer = millis(); +long lastMeasure = 0; +#define Celsius // Show temperature mesaurement in Celcius otherwise is in Fahrenheit + +// If display does not work or looks corrupted check the +// constructor reference: +// https://github.com/olikraus/u8g2/wiki/u8x8setupcpp +// or check the gallery: +// https://github.com/olikraus/u8g2/wiki/gallery +// --> First choise of cheap I2C OLED 128X32 0.91" +U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA +// --> Second choise of cheap I2C OLED 128X64 0.96" or 1.3" +//U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA +// gets called once at boot. Do all initialization that doesn't depend on +// network here +void userSetup() { + sensor.begin(); //Start Dallas temperature sensor + u8x8.begin(); + u8x8.setPowerSave(0); + u8x8.setFlipMode(1); + u8x8.setContrast(10); //Contrast setup will help to preserve OLED lifetime. In case OLED need to be brighter increase number up to 255 + u8x8.setFont(u8x8_font_chroma48medium8_r); + u8x8.drawString(0, 0, "Loading..."); +} + +// gets called every time WiFi is (re-)connected. Initialize own network +// interfaces here +void userConnected() {} + +// needRedraw marks if redraw is required to prevent often redrawing. +bool needRedraw = true; + +// Next variables hold the previous known values to determine if redraw is +// required. +String knownSsid = ""; +IPAddress knownIp; +uint8_t knownBrightness = 0; +uint8_t knownMode = 0; +uint8_t knownPalette = 0; + +long lastUpdate = 0; +long lastRedraw = 0; +bool displayTurnedOff = false; +// How often we are redrawing screen +#define USER_LOOP_REFRESH_RATE_MS 5000 + +void userLoop() { + +//----> Dallas temperature sensor MQTT publishing + temptimer = millis(); +// Timer to publishe new temperature every 60 seconds + if (temptimer - lastMeasure > 60000) + { + lastMeasure = temptimer; +//Check if MQTT Connected, otherwise it will crash the 8266 + if (mqtt != nullptr) + { + sensor.requestTemperatures(); +//Gets prefered temperature scale based on selection in definitions section + #ifdef Celsius + float board_temperature = sensor.getTempCByIndex(0); + #else + float board_temperature = sensor.getTempFByIndex(0); + #endif +//Create character string populated with user defined device topic from the UI, and the read temperature. Then publish to MQTT server. + char subuf[38]; + strcpy(subuf, mqttDeviceTopic); + strcat(subuf, "/temperature"); + mqtt->publish(subuf, 0, true, String(board_temperature).c_str()); + } + } + + // Check if we time interval for redrawing passes. + if (millis() - lastUpdate < USER_LOOP_REFRESH_RATE_MS) { + return; + } + lastUpdate = millis(); + + // Turn off display after 3 minutes with no change. + if(!displayTurnedOff && millis() - lastRedraw > 3*60*1000) { + u8x8.setPowerSave(1); + displayTurnedOff = true; + } + + // Check if values which are shown on display changed from the last time. + if (((apActive) ? String(apSSID) : WiFi.SSID()) != knownSsid) { + needRedraw = true; + } else if (knownIp != (apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP())) { + needRedraw = true; + } else if (knownBrightness != bri) { + needRedraw = true; + } else if (knownMode != strip.getMode()) { + needRedraw = true; + } else if (knownPalette != strip.getSegment(0).palette) { + needRedraw = true; + } + + if (!needRedraw) { + return; + } + needRedraw = false; + + if (displayTurnedOff) + { + u8x8.setPowerSave(0); + displayTurnedOff = false; + } + lastRedraw = millis(); + + // Update last known values. + #if defined(ESP8266) + knownSsid = apActive ? WiFi.softAPSSID() : WiFi.SSID(); + #else + knownSsid = WiFi.SSID(); + #endif + knownIp = apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP(); + knownBrightness = bri; + knownMode = strip.getMode(); + knownPalette = strip.getSegment(0).palette; + u8x8.clear(); + u8x8.setFont(u8x8_font_chroma48medium8_r); + + // First row with Wifi name + u8x8.setCursor(1, 0); + u8x8.print(knownSsid.substring(0, u8x8.getCols() > 1 ? u8x8.getCols() - 2 : 0)); + // Print `~` char to indicate that SSID is longer, than owr dicplay + if (knownSsid.length() > u8x8.getCols()) + u8x8.print("~"); + + // Second row with IP or Psssword + u8x8.setCursor(1, 1); + // Print password in AP mode and if led is OFF. + if (apActive && bri == 0) + u8x8.print(apPass); + else + u8x8.print(knownIp); + + // Third row with mode name + u8x8.setCursor(2, 2); + uint8_t qComma = 0; + bool insideQuotes = false; + uint8_t printedChars = 0; + char singleJsonSymbol; + + // Find the mode name in JSON + for (size_t i = 0; i < strlen_P(JSON_mode_names); i++) { + singleJsonSymbol = pgm_read_byte_near(JSON_mode_names + i); + switch (singleJsonSymbol) { + case '"': + insideQuotes = !insideQuotes; + break; + case '[': + case ']': + break; + case ',': + qComma++; + default: + if (!insideQuotes || (qComma != knownMode)) + break; + u8x8.print(singleJsonSymbol); + printedChars++; + } + if ((qComma > knownMode) || (printedChars > u8x8.getCols() - 2)) + break; + } + // Fourth row with palette name + u8x8.setCursor(2, 3); + qComma = 0; + insideQuotes = false; + printedChars = 0; + // Looking for palette name in JSON. + for (size_t i = 0; i < strlen_P(JSON_palette_names); i++) { + singleJsonSymbol = pgm_read_byte_near(JSON_palette_names + i); + switch (singleJsonSymbol) { + case '"': + insideQuotes = !insideQuotes; + break; + case '[': + case ']': + break; + case ',': + qComma++; + default: + if (!insideQuotes || (qComma != knownPalette)) + break; + u8x8.print(singleJsonSymbol); + printedChars++; + } + if ((qComma > knownMode) || (printedChars > u8x8.getCols() - 2)) + break; + } + + u8x8.setFont(u8x8_font_open_iconic_embedded_1x1); + u8x8.drawGlyph(0, 0, 80); // wifi icon + u8x8.drawGlyph(0, 1, 68); // home icon + u8x8.setFont(u8x8_font_open_iconic_weather_2x2); + u8x8.drawGlyph(0, 2, 66 + (bri > 0 ? 3 : 0)); // sun/moon icon +} \ No newline at end of file From 451a6841c9a4fc7686e2392c11230d020d65adf0 Mon Sep 17 00:00:00 2001 From: srg74 <28492985+srg74@users.noreply.github.com> Date: Thu, 20 Feb 2020 20:48:25 -0500 Subject: [PATCH 33/90] Update readme.md --- usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md b/usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md index 7a489f03f..a92c0a47f 100644 --- a/usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md +++ b/usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md @@ -12,8 +12,7 @@ ## Hardware ![Shield](https://github.com/srg74/WLED-wemos-shield/blob/master/resources/Images/Assembly_8.jpg) -## Wiki -![Wiki](https://github.com/srg74/WLED-wemos-shield/wiki) +- [Wiki](https://github.com/srg74/WLED-wemos-shield/wiki) ## Functionality checked with * Wemos D1 mini original v3.1 and clones From 5ca1e9268c21ced2b7a5104af55e91ea9239053e Mon Sep 17 00:00:00 2001 From: srg74 <28492985+srg74@users.noreply.github.com> Date: Thu, 20 Feb 2020 20:49:33 -0500 Subject: [PATCH 34/90] Update readme.md --- usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md b/usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md index a92c0a47f..a16beb297 100644 --- a/usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md +++ b/usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md @@ -12,7 +12,7 @@ ## Hardware ![Shield](https://github.com/srg74/WLED-wemos-shield/blob/master/resources/Images/Assembly_8.jpg) -- [Wiki](https://github.com/srg74/WLED-wemos-shield/wiki) +- [Wemos shield project Wiki](https://github.com/srg74/WLED-wemos-shield/wiki) ## Functionality checked with * Wemos D1 mini original v3.1 and clones From 6451522f89de23c7fbd45a8c332d7be457537ca1 Mon Sep 17 00:00:00 2001 From: srg74 <28492985+srg74@users.noreply.github.com> Date: Thu, 20 Feb 2020 20:50:38 -0500 Subject: [PATCH 35/90] Update readme.md --- usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md b/usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md index a16beb297..fa0e4324a 100644 --- a/usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md +++ b/usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md @@ -1,6 +1,7 @@ # Wemos D1 mini and Wemos32 mini shield ## Project repository - [Original repository](https://github.com/srg74/WLED-wemos-shield) - WLED Wemos shield repository +- [Wemos shield project Wiki](https://github.com/srg74/WLED-wemos-shield/wiki) ## Features * SSD1306 128x32 or 128x64 I2C OLED display * On screen IP address, SSID and controller status (e.g. ON or OFF, recent effect) @@ -12,8 +13,6 @@ ## Hardware ![Shield](https://github.com/srg74/WLED-wemos-shield/blob/master/resources/Images/Assembly_8.jpg) -- [Wemos shield project Wiki](https://github.com/srg74/WLED-wemos-shield/wiki) - ## Functionality checked with * Wemos D1 mini original v3.1 and clones * Wemos32 mini From bdc44a4070f8d168b3b9ca911164e5d309b9f9a3 Mon Sep 17 00:00:00 2001 From: srg74 <28492985+srg74@users.noreply.github.com> Date: Thu, 20 Feb 2020 20:53:17 -0500 Subject: [PATCH 36/90] Update readme.md --- usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md b/usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md index fa0e4324a..2584c919c 100644 --- a/usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md +++ b/usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md @@ -2,6 +2,7 @@ ## Project repository - [Original repository](https://github.com/srg74/WLED-wemos-shield) - WLED Wemos shield repository - [Wemos shield project Wiki](https://github.com/srg74/WLED-wemos-shield/wiki) +- [Precompiled WLED firmware](https://github.com/srg74/WLED-wemos-shield/tree/master/resources/Firmware) ## Features * SSD1306 128x32 or 128x64 I2C OLED display * On screen IP address, SSID and controller status (e.g. ON or OFF, recent effect) From 863498f76219f2fc1dca1bb4e155d69182600bca Mon Sep 17 00:00:00 2001 From: srg74 <28492985+srg74@users.noreply.github.com> Date: Thu, 20 Feb 2020 22:43:59 -0500 Subject: [PATCH 37/90] Update readme.md --- .../readme.md | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md b/usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md index 2584c919c..8a1d7ecab 100644 --- a/usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md +++ b/usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md @@ -4,21 +4,21 @@ - [Wemos shield project Wiki](https://github.com/srg74/WLED-wemos-shield/wiki) - [Precompiled WLED firmware](https://github.com/srg74/WLED-wemos-shield/tree/master/resources/Firmware) ## Features -* SSD1306 128x32 or 128x64 I2C OLED display -* On screen IP address, SSID and controller status (e.g. ON or OFF, recent effect) -* Auto display shutoff for saving display lifetime -* Dallas temperature sensor -* Reporting temperature to MQTT broker -* Relay for energy saving +- SSD1306 128x32 or 128x64 I2C OLED display +- On screen IP address, SSID and controller status (e.g. ON or OFF, recent effect) +- Auto display shutoff for saving display lifetime +- Dallas temperature sensor +- Reporting temperature to MQTT broker +- Relay for energy saving ## Hardware ![Shield](https://github.com/srg74/WLED-wemos-shield/blob/master/resources/Images/Assembly_8.jpg) ## Functionality checked with -* Wemos D1 mini original v3.1 and clones -* Wemos32 mini -* PlatformIO -* SSD1306 128x32 I2C OLED display -* DS18B20 (temperature sensor) -* Push button (N.O. momentary switch) +- Wemos D1 mini original v3.1 and clones +- Wemos32 mini +- PlatformIO +- SSD1306 128x32 I2C OLED display +- DS18B20 (temperature sensor) +- Push button (N.O. momentary switch) From b1961033b36a4c8c062f162e59f4788c912af330 Mon Sep 17 00:00:00 2001 From: srg74 <28492985+srg74@users.noreply.github.com> Date: Thu, 20 Feb 2020 22:48:20 -0500 Subject: [PATCH 38/90] Update readme.md --- usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md | 1 - 1 file changed, 1 deletion(-) diff --git a/usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md b/usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md index 8a1d7ecab..be97d4b8c 100644 --- a/usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md +++ b/usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md @@ -21,4 +21,3 @@ - SSD1306 128x32 I2C OLED display - DS18B20 (temperature sensor) - Push button (N.O. momentary switch) - From fb59f1f0a0e6d710f7e5b0402184334ed48c73ab Mon Sep 17 00:00:00 2001 From: srg74 <28492985+srg74@users.noreply.github.com> Date: Fri, 21 Feb 2020 16:55:58 -0500 Subject: [PATCH 39/90] Update wled06_usermod.ino --- .../wled06_usermod.ino | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/usermods/Wemos_D1_mini+Wemos32_mini_shield/wled06_usermod.ino b/usermods/Wemos_D1_mini+Wemos32_mini_shield/wled06_usermod.ino index 0e1b1e66b..9e6e0b793 100644 --- a/usermods/Wemos_D1_mini+Wemos32_mini_shield/wled06_usermod.ino +++ b/usermods/Wemos_D1_mini+Wemos32_mini_shield/wled06_usermod.ino @@ -1,17 +1,20 @@ #include // from https://github.com/olikraus/u8g2/ #include //Dallastemperature sensor - +#ifdef ARDUINO_ARCH_ESP32 //ESP32 boards +uint8_t SCL_PIN = 22; +uint8_t SDA_PIN = 21; +OneWire oneWire(23); +#else //ESP8266 boards +uint8_t SCL_PIN = 5; +uint8_t SDA_PIN = 4; +OneWire oneWire(13); +#endif //The SCL and SDA pins are defined here. -//ESP8266 Wemos D1 mini boards use SCL=5 SDA=4 -#define U8X8_PIN_SCL 5 -#define U8X8_PIN_SDA 4 -//ESP32 Wemos32 mini boards use SCL=22 SDA=21 -//#define U8X8_PIN_SCL 22 -//#define U8X8_PIN_SDA 21 +//ESP8266 Wemos D1 mini board use SCL=5 SDA=4 while ESP32 Wemos32 mini board use SCL=22 SDA=21 +#define U8X8_PIN_SCL SCL_PIN +#define U8X8_PIN_SDA SDA_PIN // Dallas sensor -OneWire oneWire(13); //ESP8266 boards -//OneWire oneWire(23); //ESP32 boards DallasTemperature sensor(&oneWire); long temptimer = millis(); long lastMeasure = 0; @@ -210,4 +213,4 @@ void userLoop() { u8x8.drawGlyph(0, 1, 68); // home icon u8x8.setFont(u8x8_font_open_iconic_weather_2x2); u8x8.drawGlyph(0, 2, 66 + (bri > 0 ? 3 : 0)); // sun/moon icon -} \ No newline at end of file +} From 9a0db83f83c6cbfc4dcc17763dd6983de72086a6 Mon Sep 17 00:00:00 2001 From: srg74 <28492985+srg74@users.noreply.github.com> Date: Sun, 23 Feb 2020 21:03:36 -0500 Subject: [PATCH 40/90] Added Heltec WiFi-Kit-8 with 0.91" display --- .../Wemos_D1_mini+Wemos32_mini_shield/wled06_usermod.ino | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/usermods/Wemos_D1_mini+Wemos32_mini_shield/wled06_usermod.ino b/usermods/Wemos_D1_mini+Wemos32_mini_shield/wled06_usermod.ino index 9e6e0b793..3b7a05fe8 100644 --- a/usermods/Wemos_D1_mini+Wemos32_mini_shield/wled06_usermod.ino +++ b/usermods/Wemos_D1_mini+Wemos32_mini_shield/wled06_usermod.ino @@ -2,7 +2,8 @@ #include //Dallastemperature sensor #ifdef ARDUINO_ARCH_ESP32 //ESP32 boards uint8_t SCL_PIN = 22; -uint8_t SDA_PIN = 21; +uint8_t SDA_PIN = 21; +// uint8_t RST_PIN = 16; // Uncoment for Heltec WiFi-Kit-8 OneWire oneWire(23); #else //ESP8266 boards uint8_t SCL_PIN = 5; @@ -13,7 +14,7 @@ OneWire oneWire(13); //ESP8266 Wemos D1 mini board use SCL=5 SDA=4 while ESP32 Wemos32 mini board use SCL=22 SDA=21 #define U8X8_PIN_SCL SCL_PIN #define U8X8_PIN_SDA SDA_PIN - +//#define U8X8_PIN_RESET RST_PIN // Uncoment for Heltec WiFi-Kit-8 // Dallas sensor DallasTemperature sensor(&oneWire); long temptimer = millis(); @@ -29,6 +30,8 @@ long lastMeasure = 0; U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA // --> Second choise of cheap I2C OLED 128X64 0.96" or 1.3" //U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA +// --> Third choise Heltec WiFi-Kit-8 with build-in OLED 128X32 0.91" +//U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_RESET, U8X8_PIN_SCL, U8X8_PIN_SDA); // Constracor for Heltec WiFi-Kit-8 // gets called once at boot. Do all initialization that doesn't depend on // network here void userSetup() { From a8e7aa99e8b6ba7413175cee738a6288bfcd765e Mon Sep 17 00:00:00 2001 From: srg74 <28492985+srg74@users.noreply.github.com> Date: Sun, 23 Feb 2020 22:48:58 -0500 Subject: [PATCH 41/90] Update wled06_usermod.ino --- usermods/Wemos_D1_mini+Wemos32_mini_shield/wled06_usermod.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usermods/Wemos_D1_mini+Wemos32_mini_shield/wled06_usermod.ino b/usermods/Wemos_D1_mini+Wemos32_mini_shield/wled06_usermod.ino index 3b7a05fe8..5373d54fe 100644 --- a/usermods/Wemos_D1_mini+Wemos32_mini_shield/wled06_usermod.ino +++ b/usermods/Wemos_D1_mini+Wemos32_mini_shield/wled06_usermod.ino @@ -3,11 +3,11 @@ #ifdef ARDUINO_ARCH_ESP32 //ESP32 boards uint8_t SCL_PIN = 22; uint8_t SDA_PIN = 21; -// uint8_t RST_PIN = 16; // Uncoment for Heltec WiFi-Kit-8 OneWire oneWire(23); #else //ESP8266 boards uint8_t SCL_PIN = 5; uint8_t SDA_PIN = 4; +// uint8_t RST_PIN = 16; // Uncoment for Heltec WiFi-Kit-8 OneWire oneWire(13); #endif //The SCL and SDA pins are defined here. From d5def30c5964e6ca0f0da75447cb48d1dfc4d818 Mon Sep 17 00:00:00 2001 From: Ser Ko Date: Mon, 24 Feb 2020 16:36:03 -0500 Subject: [PATCH 42/90] Update wled06_usermod.ino --- .../Wemos_D1_mini+Wemos32_mini_shield/wled06_usermod.ino | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/usermods/Wemos_D1_mini+Wemos32_mini_shield/wled06_usermod.ino b/usermods/Wemos_D1_mini+Wemos32_mini_shield/wled06_usermod.ino index 3b7a05fe8..9e6e0b793 100644 --- a/usermods/Wemos_D1_mini+Wemos32_mini_shield/wled06_usermod.ino +++ b/usermods/Wemos_D1_mini+Wemos32_mini_shield/wled06_usermod.ino @@ -2,8 +2,7 @@ #include //Dallastemperature sensor #ifdef ARDUINO_ARCH_ESP32 //ESP32 boards uint8_t SCL_PIN = 22; -uint8_t SDA_PIN = 21; -// uint8_t RST_PIN = 16; // Uncoment for Heltec WiFi-Kit-8 +uint8_t SDA_PIN = 21; OneWire oneWire(23); #else //ESP8266 boards uint8_t SCL_PIN = 5; @@ -14,7 +13,7 @@ OneWire oneWire(13); //ESP8266 Wemos D1 mini board use SCL=5 SDA=4 while ESP32 Wemos32 mini board use SCL=22 SDA=21 #define U8X8_PIN_SCL SCL_PIN #define U8X8_PIN_SDA SDA_PIN -//#define U8X8_PIN_RESET RST_PIN // Uncoment for Heltec WiFi-Kit-8 + // Dallas sensor DallasTemperature sensor(&oneWire); long temptimer = millis(); @@ -30,8 +29,6 @@ long lastMeasure = 0; U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA // --> Second choise of cheap I2C OLED 128X64 0.96" or 1.3" //U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA -// --> Third choise Heltec WiFi-Kit-8 with build-in OLED 128X32 0.91" -//U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_RESET, U8X8_PIN_SCL, U8X8_PIN_SDA); // Constracor for Heltec WiFi-Kit-8 // gets called once at boot. Do all initialization that doesn't depend on // network here void userSetup() { From 70bf957a8a3b0199960771980da957d910fd7279 Mon Sep 17 00:00:00 2001 From: srg74 <28492985+srg74@users.noreply.github.com> Date: Mon, 24 Feb 2020 16:44:56 -0500 Subject: [PATCH 43/90] Added Heltec WiFi-Kit-8 --- usermods/Wemos_D1_mini+Wemos32_mini_shield/wled06_usermod.ino | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/usermods/Wemos_D1_mini+Wemos32_mini_shield/wled06_usermod.ino b/usermods/Wemos_D1_mini+Wemos32_mini_shield/wled06_usermod.ino index c0f6ce85b..58a4e0e6a 100644 --- a/usermods/Wemos_D1_mini+Wemos32_mini_shield/wled06_usermod.ino +++ b/usermods/Wemos_D1_mini+Wemos32_mini_shield/wled06_usermod.ino @@ -14,7 +14,7 @@ OneWire oneWire(13); //ESP8266 Wemos D1 mini board use SCL=5 SDA=4 while ESP32 Wemos32 mini board use SCL=22 SDA=21 #define U8X8_PIN_SCL SCL_PIN #define U8X8_PIN_SDA SDA_PIN - +//#define U8X8_PIN_RESET RST_PIN // Uncoment for Heltec WiFi-Kit-8 // Dallas sensor DallasTemperature sensor(&oneWire); long temptimer = millis(); @@ -30,6 +30,8 @@ long lastMeasure = 0; U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA // --> Second choise of cheap I2C OLED 128X64 0.96" or 1.3" //U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA +// --> Third choise of Heltec WiFi-Kit-8 OLED 128X32 0.91" +//U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_RESET, U8X8_PIN_SCL, U8X8_PIN_SDA); // Constracor for Heltec WiFi-Kit-8 // gets called once at boot. Do all initialization that doesn't depend on // network here void userSetup() { From a78f17abec323055836fb1a517603e7ae5ade751 Mon Sep 17 00:00:00 2001 From: srg74 <28492985+srg74@users.noreply.github.com> Date: Mon, 24 Feb 2020 16:48:01 -0500 Subject: [PATCH 44/90] Update readme.md --- usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md b/usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md index be97d4b8c..765e56831 100644 --- a/usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md +++ b/usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md @@ -1,4 +1,5 @@ # Wemos D1 mini and Wemos32 mini shield +- Added third choice of controller Heltec WiFi-Kit-8. Totally DIY but with OLED display. ## Project repository - [Original repository](https://github.com/srg74/WLED-wemos-shield) - WLED Wemos shield repository - [Wemos shield project Wiki](https://github.com/srg74/WLED-wemos-shield/wiki) From 81da8261a00fe9e3e7a4ec7780fae3be8c5b2783 Mon Sep 17 00:00:00 2001 From: Ser Ko Date: Wed, 26 Feb 2020 19:16:33 -0500 Subject: [PATCH 45/90] Added board heltec_wifi_kit_8 --- platformio.ini | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/platformio.ini b/platformio.ini index 5549cc179..3300c58ae 100644 --- a/platformio.ini +++ b/platformio.ini @@ -14,6 +14,7 @@ default_envs = nodemcuv2 ; default_envs = esp01_1m ; default_envs = esp07 ; default_envs = d1_mini +; default_envs = heltec_wifi_kit_8 ; default_envs = d1_mini_debug ; default_envs = esp32dev ; default_envs = esp8285_4CH_MagicHome @@ -171,6 +172,12 @@ platform = ${common.platform_latest} board_build.ldscript = ${common.ldscript_4m1m} build_flags = ${common.build_flags_esp8266} +[env:heltec_wifi_kit_8] +board = d1_mini +platform = ${common.platform_latest} +board_build.ldscript = ${common.ldscript_4m1m} +build_flags = ${common.build_flags_esp8266} + [env:esp32dev] board = esp32dev platform = espressif32@1.11.2 From 57d3120b45f7ce48149db3732ecabab9da9b1656 Mon Sep 17 00:00:00 2001 From: Ser Ko Date: Wed, 26 Feb 2020 19:21:57 -0500 Subject: [PATCH 46/90] Grammar correction --- usermods/Wemos_D1_mini+Wemos32_mini_shield/wled06_usermod.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usermods/Wemos_D1_mini+Wemos32_mini_shield/wled06_usermod.ino b/usermods/Wemos_D1_mini+Wemos32_mini_shield/wled06_usermod.ino index 58a4e0e6a..576795fb4 100644 --- a/usermods/Wemos_D1_mini+Wemos32_mini_shield/wled06_usermod.ino +++ b/usermods/Wemos_D1_mini+Wemos32_mini_shield/wled06_usermod.ino @@ -31,7 +31,7 @@ U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_ // --> Second choise of cheap I2C OLED 128X64 0.96" or 1.3" //U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA // --> Third choise of Heltec WiFi-Kit-8 OLED 128X32 0.91" -//U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_RESET, U8X8_PIN_SCL, U8X8_PIN_SDA); // Constracor for Heltec WiFi-Kit-8 +//U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_RESET, U8X8_PIN_SCL, U8X8_PIN_SDA); // Constructor for Heltec WiFi-Kit-8 // gets called once at boot. Do all initialization that doesn't depend on // network here void userSetup() { From e3269f6edce9fe21212bf7a3fee17af56ae86037 Mon Sep 17 00:00:00 2001 From: Ser Ko Date: Wed, 26 Feb 2020 20:36:27 -0500 Subject: [PATCH 47/90] Update wled06_usermod.ino --- usermods/Wemos_D1_mini+Wemos32_mini_shield/wled06_usermod.ino | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/usermods/Wemos_D1_mini+Wemos32_mini_shield/wled06_usermod.ino b/usermods/Wemos_D1_mini+Wemos32_mini_shield/wled06_usermod.ino index 576795fb4..d75cf0a74 100644 --- a/usermods/Wemos_D1_mini+Wemos32_mini_shield/wled06_usermod.ino +++ b/usermods/Wemos_D1_mini+Wemos32_mini_shield/wled06_usermod.ino @@ -32,8 +32,7 @@ U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_ //U8X8_SSD1306_128X64_NONAME_HW_I2C u8x8(U8X8_PIN_NONE, U8X8_PIN_SCL, U8X8_PIN_SDA); // Pins are Reset, SCL, SDA // --> Third choise of Heltec WiFi-Kit-8 OLED 128X32 0.91" //U8X8_SSD1306_128X32_UNIVISION_HW_I2C u8x8(U8X8_PIN_RESET, U8X8_PIN_SCL, U8X8_PIN_SDA); // Constructor for Heltec WiFi-Kit-8 -// gets called once at boot. Do all initialization that doesn't depend on -// network here +// gets called once at boot. Do all initialization that doesn't depend on network here void userSetup() { sensor.begin(); //Start Dallas temperature sensor u8x8.begin(); From ed3234d949294221d7a064cd2ec09b1a0e218303 Mon Sep 17 00:00:00 2001 From: srg74 Date: Wed, 4 Mar 2020 23:01:00 -0500 Subject: [PATCH 48/90] Update UserMod Updating UserMod as per request from discourse forum user. He want to use with ESP32 also as QuinLED board support to types - ESP8266 ans ESP32. Removed .txt file as it create an extra step in setup. --- usermods/QuinLED_Dig_Uno_Temp_MQTT/readme.txt | 3 +-- usermods/QuinLED_Dig_Uno_Temp_MQTT/wled00.txt | 8 -------- .../wled06_usermod.ino | 19 +++++++++++++++---- 3 files changed, 16 insertions(+), 14 deletions(-) delete mode 100644 usermods/QuinLED_Dig_Uno_Temp_MQTT/wled00.txt diff --git a/usermods/QuinLED_Dig_Uno_Temp_MQTT/readme.txt b/usermods/QuinLED_Dig_Uno_Temp_MQTT/readme.txt index 612873360..eb8da7ee1 100644 --- a/usermods/QuinLED_Dig_Uno_Temp_MQTT/readme.txt +++ b/usermods/QuinLED_Dig_Uno_Temp_MQTT/readme.txt @@ -1,8 +1,7 @@ -These files allow WLED 0.8.6 to report the temp sensor on the Quinled board to MQTT. I use it to report the board temp to Home Assistant via MQTT, so it will send notifications if something happens and the board start to heat up. +These files allow WLED 0.9.1 to report the temp sensor on the Quinled board to MQTT. I use it to report the board temp to Home Assistant via MQTT, so it will send notifications if something happens and the board start to heat up. This code uses Aircookie's WLED software. It has a premade file for user modifications. I use it to publish the temperature from the dallas temperature sensor on the Quinled board. The entries for the top of the WLED00 file, initializes the required libraries, and variables for the sensor. The .ino file waits for 60 seconds, and checks to see if the MQTT server is connected (thanks Aircoookie). It then poles the sensor, and published it using the MQTT service already running, using the main topic programmed in the WLED UI. To install: -Add the entries in the WLED00 file to the top of the same file from Aircoookies WLED. Replace the WLED06_usermod.ino file in Aircoookies WLED folder. diff --git a/usermods/QuinLED_Dig_Uno_Temp_MQTT/wled00.txt b/usermods/QuinLED_Dig_Uno_Temp_MQTT/wled00.txt deleted file mode 100644 index 661a7e249..000000000 --- a/usermods/QuinLED_Dig_Uno_Temp_MQTT/wled00.txt +++ /dev/null @@ -1,8 +0,0 @@ -//Intiating code for QuinLED Dig-Uno temp sensor -//Uncomment Celsius if that is your prefered temperature scale -#include -OneWire oneWire(14); -DallasTemperature sensors(&oneWire); -long temptimer = millis(); -long lastMeasure = 0; -//#define Celsius diff --git a/usermods/QuinLED_Dig_Uno_Temp_MQTT/wled06_usermod.ino b/usermods/QuinLED_Dig_Uno_Temp_MQTT/wled06_usermod.ino index 1309a4f83..d4b77ccc1 100644 --- a/usermods/QuinLED_Dig_Uno_Temp_MQTT/wled06_usermod.ino +++ b/usermods/QuinLED_Dig_Uno_Temp_MQTT/wled06_usermod.ino @@ -1,8 +1,19 @@ -//starts Dallas Temp service on boot +//Intiating code for QuinLED Dig-Uno temp sensor +//Uncomment Celsius if that is your prefered temperature scale +#include //Dallastemperature sensor +#ifdef ARDUINO_ARCH_ESP32 //ESP32 boards +OneWire oneWire(18); +#else //ESP8266 boards +OneWire oneWire(14); +#endif +DallasTemperature sensor(&oneWire); +long temptimer = millis(); +long lastMeasure = 0; +#define Celsius // Show temperature mesaurement in Celcius otherwise is in Fahrenheit void userSetup() { // Start the DS18B20 sensor - sensors.begin(); + sensor.begin(); } //gets called every time WiFi is (re-)connected. Initialize own network interfaces here @@ -21,11 +32,11 @@ void userLoop() //Check if MQTT Connected, otherwise it will crash the 8266 if (mqtt != nullptr){ - sensors.requestTemperatures(); + sensor.requestTemperatures(); //Gets prefered temperature scale based on selection in definitions section #ifdef Celsius - float board_temperature = sensors.getTempCByIndex(0); + float board_temperature = sensor.getTempCByIndex(0); #else float board_temperature = sensors.getTempFByIndex(0); #endif From 8d669b12b6dc4edc0a0ba63cf0f3ef8ed0bf70d5 Mon Sep 17 00:00:00 2001 From: srg74 Date: Wed, 4 Mar 2020 23:15:12 -0500 Subject: [PATCH 49/90] Truing to fix failed build --- platformio.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platformio.ini b/platformio.ini index a8d21c846..91bdd8675 100644 --- a/platformio.ini +++ b/platformio.ini @@ -152,8 +152,8 @@ lib_deps = #For use SSD1306 OLED display uncomment following #U8g2@~2.27.2 #For Dallas sensor uncomment following 2 lines - #DallasTemperature@~3.8.0 - #OneWire@~2.3.5 + DallasTemperature@~3.8.0 + OneWire@~2.3.5 lib_ignore = AsyncTCP From c2ab3f96a7a52ec9f80ce25f56b302d80a4c3a73 Mon Sep 17 00:00:00 2001 From: srg74 <28492985+srg74@users.noreply.github.com> Date: Fri, 13 Mar 2020 20:42:15 -0400 Subject: [PATCH 50/90] gitignore update --- .gitignore | 2 ++ platformio.ini | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ea49cb51f..df0f9e85c 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ /wled00/Release /wled00/extLibs /platformio_override.ini +.DS_Store +.gitignore diff --git a/platformio.ini b/platformio.ini index 91bdd8675..cf0ba25c8 100644 --- a/platformio.ini +++ b/platformio.ini @@ -27,7 +27,7 @@ default_envs = d1_mini, esp01, esp01_1m_ota, esp32dev ; default_envs = esp01_1m_ota ; default_envs = esp01_1m_full ; default_envs = esp07 -; default_envs = d1_mini +default_envs = d1_mini ; default_envs = heltec_wifi_kit_8 ; default_envs = d1_mini_debug ; default_envs = esp32dev From 37a31a9b9470ac4ffde15a68f7728b95f248f74d Mon Sep 17 00:00:00 2001 From: srg74 <28492985+srg74@users.noreply.github.com> Date: Fri, 13 Mar 2020 20:44:05 -0400 Subject: [PATCH 51/90] Update platformio.ini --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index cf0ba25c8..91bdd8675 100644 --- a/platformio.ini +++ b/platformio.ini @@ -27,7 +27,7 @@ default_envs = d1_mini, esp01, esp01_1m_ota, esp32dev ; default_envs = esp01_1m_ota ; default_envs = esp01_1m_full ; default_envs = esp07 -default_envs = d1_mini +; default_envs = d1_mini ; default_envs = heltec_wifi_kit_8 ; default_envs = d1_mini_debug ; default_envs = esp32dev From 594c0b85507ec7e5ae29cfa72102d6a2aa58ba08 Mon Sep 17 00:00:00 2001 From: Travis J Dean Date: Wed, 25 Mar 2020 04:00:55 -0400 Subject: [PATCH 52/90] Transform ino to h/cpp. Class WLED created. --- wled00/wled.cpp | 503 ++++++++++++++ wled00/wled.h | 529 +++++++++++++++ wled00/wled00.ino | 612 +----------------- wled00/wled05_init.ino | 339 ---------- ...{wled06_usermod.ino => wled06_usermod.cpp} | 1 + wled00/{wled12_alexa.ino => wled_alexa.cpp} | 10 +- wled00/wled_alexa.h | 16 + wled00/{wled16_blynk.ino => wled_blynk.cpp} | 6 +- wled00/wled_blynk.h | 13 + wled00/{wled09_button.ino => wled_button.cpp} | 2 + wled00/wled_button.h | 12 + wled00/{wled14_colors.ino => wled_colors.cpp} | 12 +- wled00/wled_colors.h | 20 + ...{wled13_cronixie.ino => wled_cronixie.cpp} | 20 +- wled00/wled_cronixie.h | 13 + wled00/{wled21_dmx.ino => wled_dmx.cpp} | 9 +- wled00/wled_dmx.h | 11 + wled00/{wled01_eeprom.ino => wled_eeprom.cpp} | 6 +- wled00/wled_eeprom.h | 22 + wled00/{wled04_file.ino => wled_file.cpp} | 14 +- wled00/wled_file.h | 12 + wled00/{wled15_hue.ino => wled_hue.cpp} | 7 +- wled00/wled_hue.h | 10 + wled00/{wled20_ir.ino => wled_ir.cpp} | 5 +- wled00/wled_ir.h | 9 + wled00/{wled19_json.ino => wled_json.cpp} | 5 +- wled00/wled_json.h | 15 + wled00/{wled08_led.ino => wled_led.cpp} | 8 +- wled00/wled_led.h | 19 + wled00/{wled17_mqtt.ino => wled_mqtt.cpp} | 5 +- wled00/wled_mqtt.h | 9 + wled00/{wled07_notify.ino => wled_notify.cpp} | 6 +- wled00/wled_notify.h | 17 + wled00/{wled10_ntp.ino => wled_ntp.cpp} | 62 +- wled00/wled_ntp.h | 76 +++ wled00/{wled11_ol.ino => wled_overlay.cpp} | 5 +- wled00/wled_overlay.h | 12 + wled00/{wled18_server.ino => wled_server.cpp} | 7 +- wled00/wled_server.h | 18 + wled00/{wled03_set.ino => wled_set.cpp} | 4 + wled00/wled_set.h | 12 + wled00/{wled02_xml.ino => wled_xml.cpp} | 7 +- wled00/wled_xml.h | 15 + 43 files changed, 1441 insertions(+), 1074 deletions(-) create mode 100644 wled00/wled.cpp create mode 100644 wled00/wled.h delete mode 100644 wled00/wled05_init.ino rename wled00/{wled06_usermod.ino => wled06_usermod.cpp} (97%) rename wled00/{wled12_alexa.ino => wled_alexa.cpp} (89%) create mode 100644 wled00/wled_alexa.h rename wled00/{wled16_blynk.ino => wled_blynk.cpp} (94%) create mode 100644 wled00/wled_blynk.h rename wled00/{wled09_button.ino => wled_button.cpp} (98%) create mode 100644 wled00/wled_button.h rename wled00/{wled14_colors.ino => wled_colors.cpp} (96%) create mode 100644 wled00/wled_colors.h rename wled00/{wled13_cronixie.ino => wled_cronixie.cpp} (97%) create mode 100644 wled00/wled_cronixie.h rename wled00/{wled21_dmx.ino => wled_dmx.cpp} (89%) create mode 100644 wled00/wled_dmx.h rename wled00/{wled01_eeprom.ino => wled_eeprom.cpp} (99%) create mode 100644 wled00/wled_eeprom.h rename wled00/{wled04_file.ino => wled_file.cpp} (95%) create mode 100644 wled00/wled_file.h rename wled00/{wled15_hue.ino => wled_hue.cpp} (99%) create mode 100644 wled00/wled_hue.h rename wled00/{wled20_ir.ino => wled_ir.cpp} (99%) create mode 100644 wled00/wled_ir.h rename wled00/{wled19_json.ino => wled_json.cpp} (99%) create mode 100644 wled00/wled_json.h rename wled00/{wled08_led.ino => wled_led.cpp} (98%) create mode 100644 wled00/wled_led.h rename wled00/{wled17_mqtt.ino => wled_mqtt.cpp} (98%) create mode 100644 wled00/wled_mqtt.h rename wled00/{wled07_notify.ino => wled_notify.cpp} (99%) create mode 100644 wled00/wled_notify.h rename wled00/{wled10_ntp.ino => wled_ntp.cpp} (58%) create mode 100644 wled00/wled_ntp.h rename wled00/{wled11_ol.ino => wled_overlay.cpp} (98%) create mode 100644 wled00/wled_overlay.h rename wled00/{wled18_server.ino => wled_server.cpp} (99%) create mode 100644 wled00/wled_server.h rename wled00/{wled03_set.ino => wled_set.cpp} (99%) create mode 100644 wled00/wled_set.h rename wled00/{wled02_xml.ino => wled_xml.cpp} (99%) create mode 100644 wled00/wled_xml.h diff --git a/wled00/wled.cpp b/wled00/wled.cpp new file mode 100644 index 000000000..50a83f27e --- /dev/null +++ b/wled00/wled.cpp @@ -0,0 +1,503 @@ +#include "wled.h" + +WLED::WLED() { + +} + +//turns all LEDs off and restarts ESP +void WLED::reset() +{ + briT = 0; + long dly = millis(); + while (millis() - dly < 250) + { + yield(); //enough time to send response to client + } + setAllLeds(); + DEBUG_PRINTLN("MODULE RESET"); + ESP.restart(); +} + +bool oappendi(int i) +{ + char s[11]; + sprintf(s, "%ld", i); + return oappend(s); +} + +bool oappend(const char *txt) +{ + uint16_t len = strlen(txt); + if (olen + len >= OMAX) + return false; //buffer full + strcpy(obuf + olen, txt); + olen += len; + return true; +} + +void WLED::loop() +{ + handleIR(); //2nd call to function needed for ESP32 to return valid results -- should be good for ESP8266, too + handleConnection(); + handleSerial(); + handleNotifications(); + handleTransitions(); +#ifdef WLED_ENABLE_DMX + handleDMX(); +#endif + userLoop(); + + yield(); + handleIO(); + handleIR(); + handleNetworkTime(); + handleAlexa(); + + handleOverlays(); + yield(); +#ifdef WLED_USE_ANALOG_LEDS + strip.setRgbwPwm(); +#endif + + if (doReboot) + reset(); + + if (!realtimeMode) //block stuff if WARLS/Adalight is enabled + { + if (apActive) + dnsServer.processNextRequest(); +#ifndef WLED_DISABLE_OTA + if (WLED_CONNECTED && aOtaEnabled) + ArduinoOTA.handle(); +#endif + handleNightlight(); + yield(); + + handleHue(); + handleBlynk(); + + yield(); + if (!offMode) + strip.service(); + } + yield(); +#ifdef ESP8266 + MDNS.update(); +#endif + if (millis() - lastMqttReconnectAttempt > 30000) + initMqtt(); + +//DEBUG serial logging +#ifdef WLED_DEBUG + if (millis() - debugTime > 9999) + { + DEBUG_PRINTLN("---DEBUG INFO---"); + DEBUG_PRINT("Runtime: "); + DEBUG_PRINTLN(millis()); + DEBUG_PRINT("Unix time: "); + DEBUG_PRINTLN(now()); + DEBUG_PRINT("Free heap: "); + DEBUG_PRINTLN(ESP.getFreeHeap()); + DEBUG_PRINT("Wifi state: "); + DEBUG_PRINTLN(WiFi.status()); + if (WiFi.status() != lastWifiState) + { + wifiStateChangedTime = millis(); + } + lastWifiState = WiFi.status(); + DEBUG_PRINT("State time: "); + DEBUG_PRINTLN(wifiStateChangedTime); + DEBUG_PRINT("NTP last sync: "); + DEBUG_PRINTLN(ntpLastSyncTime); + DEBUG_PRINT("Client IP: "); + DEBUG_PRINTLN(WiFi.localIP()); + DEBUG_PRINT("Loops/sec: "); + DEBUG_PRINTLN(loops / 10); + loops = 0; + debugTime = millis(); + } + loops++; +#endif // WLED_DEBU +} + +void WLED::wledInit() +{ + EEPROM.begin(EEPSIZE); + ledCount = EEPROM.read(229) + ((EEPROM.read(398) << 8) & 0xFF00); + if (ledCount > MAX_LEDS || ledCount == 0) + ledCount = 30; + +#ifdef ESP8266 +#if LEDPIN == 3 + if (ledCount > MAX_LEDS_DMA) + ledCount = MAX_LEDS_DMA; //DMA method uses too much ram +#endif +#endif + Serial.begin(115200); + Serial.setTimeout(50); + DEBUG_PRINTLN(); + DEBUG_PRINT("---WLED "); + DEBUG_PRINT(versionString); + DEBUG_PRINT(" "); + DEBUG_PRINT(VERSION); + DEBUG_PRINTLN(" INIT---"); +#ifdef ARDUINO_ARCH_ESP32 + DEBUG_PRINT("esp32 "); + DEBUG_PRINTLN(ESP.getSdkVersion()); +#else + DEBUG_PRINT("esp8266 "); + DEBUG_PRINTLN(ESP.getCoreVersion()); +#endif + int heapPreAlloc = ESP.getFreeHeap(); + DEBUG_PRINT("heap "); + DEBUG_PRINTLN(ESP.getFreeHeap()); + + strip.init(EEPROM.read(372), ledCount, EEPROM.read(2204)); //init LEDs quickly + strip.setBrightness(0); + + DEBUG_PRINT("LEDs inited. heap usage ~"); + DEBUG_PRINTLN(heapPreAlloc - ESP.getFreeHeap()); + +#ifndef WLED_DISABLE_FILESYSTEM +#ifdef ARDUINO_ARCH_ESP32 + SPIFFS.begin(true); +#endif + SPIFFS.begin(); +#endif + + DEBUG_PRINTLN("Load EEPROM"); + loadSettingsFromEEPROM(true); + beginStrip(); + userSetup(); + if (strcmp(clientSSID, DEFAULT_CLIENT_SSID) == 0) + showWelcomePage = true; + WiFi.persistent(false); + + if (macroBoot > 0) + applyMacro(macroBoot); + Serial.println("Ada"); + + //generate module IDs + escapedMac = WiFi.macAddress(); + escapedMac.replace(":", ""); + escapedMac.toLowerCase(); + if (strcmp(cmDNS, "x") == 0) //fill in unique mdns default + { + strcpy(cmDNS, "wled-"); + sprintf(cmDNS + 5, "%*s", 6, escapedMac.c_str() + 6); + } + if (mqttDeviceTopic[0] == 0) + { + strcpy(mqttDeviceTopic, "wled/"); + sprintf(mqttDeviceTopic + 5, "%*s", 6, escapedMac.c_str() + 6); + } + if (mqttClientID[0] == 0) + { + strcpy(mqttClientID, "WLED-"); + sprintf(mqttClientID + 5, "%*s", 6, escapedMac.c_str() + 6); + } + + strip.service(); + +#ifndef WLED_DISABLE_OTA + if (aOtaEnabled) + { + ArduinoOTA.onStart([]() { +#ifdef ESP8266 + wifi_set_sleep_type(NONE_SLEEP_T); +#endif + DEBUG_PRINTLN("Start ArduinoOTA"); + }); + if (strlen(cmDNS) > 0) + ArduinoOTA.setHostname(cmDNS); + } +#endif +#ifdef WLED_ENABLE_DMX + dmx.init(512); // initialize with bus length +#endif + //HTTP server page init + initServer(); +} + +void WLED::beginStrip() +{ + // Initialize NeoPixel Strip and button + strip.setShowCallback(handleOverlayDraw); + +#ifdef BTNPIN + pinMode(BTNPIN, INPUT_PULLUP); +#endif + + if (bootPreset > 0) + applyPreset(bootPreset, turnOnAtBoot); + colorUpdated(NOTIFIER_CALL_MODE_INIT); + +//init relay pin +#if RLYPIN >= 0 + pinMode(RLYPIN, OUTPUT); +#if RLYMDE + digitalWrite(RLYPIN, bri); +#else + digitalWrite(RLYPIN, !bri); +#endif +#endif + + //disable button if it is "pressed" unintentionally +#ifdef BTNPIN + if (digitalRead(BTNPIN) == LOW) + buttonEnabled = false; +#else + buttonEnabled = false; +#endif +} + +void WLED::initAP(bool resetAP = false) +{ + if (apBehavior == AP_BEHAVIOR_BUTTON_ONLY && !resetAP) + return; + + if (!apSSID[0] || resetAP) + strcpy(apSSID, "WLED-AP"); + if (resetAP) + strcpy(apPass, DEFAULT_AP_PASS); + DEBUG_PRINT("Opening access point "); + DEBUG_PRINTLN(apSSID); + WiFi.softAPConfig(IPAddress(4, 3, 2, 1), IPAddress(4, 3, 2, 1), IPAddress(255, 255, 255, 0)); + WiFi.softAP(apSSID, apPass, apChannel, apHide); + + if (!apActive) //start captive portal if AP active + { + DEBUG_PRINTLN("Init AP interfaces"); + server.begin(); + if (udpPort > 0 && udpPort != ntpLocalPort) + { + udpConnected = notifierUdp.begin(udpPort); + } + if (udpRgbPort > 0 && udpRgbPort != ntpLocalPort && udpRgbPort != udpPort) + { + udpRgbConnected = rgbUdp.begin(udpRgbPort); + } + + dnsServer.setErrorReplyCode(DNSReplyCode::NoError); + dnsServer.start(53, "*", WiFi.softAPIP()); + } + apActive = true; +} + +void WLED::initConnection() +{ + WiFi.disconnect(); //close old connections +#ifdef ESP8266 + WiFi.setPhyMode(WIFI_PHY_MODE_11N); +#endif + + if (staticIP[0] != 0 && staticGateway[0] != 0) + { + WiFi.config(staticIP, staticGateway, staticSubnet, IPAddress(8, 8, 8, 8)); + } + else + { + WiFi.config(0U, 0U, 0U); + } + + lastReconnectAttempt = millis(); + + if (!WLED_WIFI_CONFIGURED) + { + DEBUG_PRINT("No connection configured. "); + if (!apActive) + initAP(); //instantly go to ap mode + return; + } + else if (!apActive) + { + if (apBehavior == AP_BEHAVIOR_ALWAYS) + { + initAP(); + } + else + { + DEBUG_PRINTLN("Access point disabled."); + WiFi.softAPdisconnect(true); + } + } + showWelcomePage = false; + + DEBUG_PRINT("Connecting to "); + DEBUG_PRINT(clientSSID); + DEBUG_PRINTLN("..."); + +#ifdef ESP8266 + WiFi.hostname(serverDescription); +#endif + + WiFi.begin(clientSSID, clientPass); + +#ifdef ARDUINO_ARCH_ESP32 + WiFi.setSleep(!noWifiSleep); + WiFi.setHostname(serverDescription); +#else + wifi_set_sleep_type((noWifiSleep) ? NONE_SLEEP_T : MODEM_SLEEP_T); +#endif +} + +void WLED::initInterfaces() +{ + DEBUG_PRINTLN("Init STA interfaces"); + + if (hueIP[0] == 0) + { + hueIP[0] = WiFi.localIP()[0]; + hueIP[1] = WiFi.localIP()[1]; + hueIP[2] = WiFi.localIP()[2]; + } + + //init Alexa hue emulation + if (alexaEnabled) + alexaInit(); + +#ifndef WLED_DISABLE_OTA + if (aOtaEnabled) + ArduinoOTA.begin(); +#endif + + strip.service(); + // Set up mDNS responder: + if (strlen(cmDNS) > 0) + { + if (!aOtaEnabled) + MDNS.begin(cmDNS); + + DEBUG_PRINTLN("mDNS started"); + MDNS.addService("http", "tcp", 80); + MDNS.addService("wled", "tcp", 80); + MDNS.addServiceTxt("wled", "tcp", "mac", escapedMac.c_str()); + } + server.begin(); + + if (udpPort > 0 && udpPort != ntpLocalPort) + { + udpConnected = notifierUdp.begin(udpPort); + if (udpConnected && udpRgbPort != udpPort) + udpRgbConnected = rgbUdp.begin(udpRgbPort); + } + if (ntpEnabled) + ntpConnected = ntpUdp.begin(ntpLocalPort); + + initBlynk(blynkApiKey); + e131.begin((e131Multicast) ? E131_MULTICAST : E131_UNICAST, e131Universe, E131_MAX_UNIVERSE_COUNT); + reconnectHue(); + initMqtt(); + interfacesInited = true; + wasConnected = true; +} + +byte stacO = 0; +uint32_t lastHeap; +unsigned long heapTime = 0; + +void WLED::handleConnection() +{ + if (millis() < 2000 && (!WLED_WIFI_CONFIGURED || apBehavior == AP_BEHAVIOR_ALWAYS)) + return; + if (lastReconnectAttempt == 0) + initConnection(); + + //reconnect WiFi to clear stale allocations if heap gets too low + if (millis() - heapTime > 5000) + { + uint32_t heap = ESP.getFreeHeap(); + if (heap < 9000 && lastHeap < 9000) + { + DEBUG_PRINT("Heap too low! "); + DEBUG_PRINTLN(heap); + forceReconnect = true; + } + lastHeap = heap; + heapTime = millis(); + } + + byte stac = 0; + if (apActive) + { +#ifdef ESP8266 + stac = wifi_softap_get_station_num(); +#else + wifi_sta_list_t stationList; + esp_wifi_ap_get_sta_list(&stationList); + stac = stationList.num; +#endif + if (stac != stacO) + { + stacO = stac; + DEBUG_PRINT("Connected AP clients: "); + DEBUG_PRINTLN(stac); + if (!WLED_CONNECTED && WLED_WIFI_CONFIGURED) + { //trying to connect, but not connected + if (stac) + WiFi.disconnect(); //disable search so that AP can work + else + initConnection(); //restart search + } + } + } + if (forceReconnect) + { + DEBUG_PRINTLN("Forcing reconnect."); + initConnection(); + interfacesInited = false; + forceReconnect = false; + wasConnected = false; + return; + } + if (!WLED_CONNECTED) + { + if (interfacesInited) + { + DEBUG_PRINTLN("Disconnected!"); + interfacesInited = false; + initConnection(); + } + if (millis() - lastReconnectAttempt > ((stac) ? 300000 : 20000) && WLED_WIFI_CONFIGURED) + initConnection(); + if (!apActive && millis() - lastReconnectAttempt > 12000 && (!wasConnected || apBehavior == AP_BEHAVIOR_NO_CONN)) + initAP(); + } + else if (!interfacesInited) + { //newly connected + DEBUG_PRINTLN(""); + DEBUG_PRINT("Connected! IP address: "); + DEBUG_PRINTLN(WiFi.localIP()); + initInterfaces(); + userConnected(); + + //shut down AP + if (apBehavior != AP_BEHAVIOR_ALWAYS && apActive) + { + dnsServer.stop(); + WiFi.softAPdisconnect(true); + apActive = false; + DEBUG_PRINTLN("Access point disabled."); + } + } +} + +//by https://github.com/tzapu/WiFiManager/blob/master/WiFiManager.cpp +int WLED::getSignalQuality(int rssi) +{ + int quality = 0; + + if (rssi <= -100) + { + quality = 0; + } + else if (rssi >= -50) + { + quality = 100; + } + else + { + quality = 2 * (rssi + 100); + } + return quality; +} \ No newline at end of file diff --git a/wled00/wled.h b/wled00/wled.h new file mode 100644 index 000000000..4f9b79afd --- /dev/null +++ b/wled00/wled.h @@ -0,0 +1,529 @@ +#ifndef WLED_H +#define WLED_H + +/* + Main sketch, global variable declarations +*/ +/* + * @title WLED project sketch + * @version 0.9.1 + * @author Christian Schwinne + */ + +//ESP8266-01 (blue) got too little storage space to work with all features of WLED. To use it, you must use ESP8266 Arduino Core v2.4.2 and the setting 512K(No SPIFFS). + +//ESP8266-01 (black) has 1MB flash and can thus fit the whole program. Use 1M(64K SPIFFS). +//Uncomment some of the following lines to disable features to compile for ESP8266-01 (max flash size 434kB): + +//You are required to disable over-the-air updates: +//#define WLED_DISABLE_OTA //saves 14kb + +//You need to choose some of these features to disable: +//#define WLED_DISABLE_ALEXA //saves 11kb +//#define WLED_DISABLE_BLYNK //saves 6kb +//#define WLED_DISABLE_CRONIXIE //saves 3kb +//#define WLED_DISABLE_HUESYNC //saves 4kb +//#define WLED_DISABLE_INFRARED //there is no pin left for this on ESP8266-01, saves 12kb +#define WLED_ENABLE_MQTT //saves 12kb +#define WLED_ENABLE_ADALIGHT //saves 500b only +//#define WLED_ENABLE_DMX //uses 3.5kb + +#define WLED_DISABLE_FILESYSTEM //SPIFFS is not used by any WLED feature yet +//#define WLED_ENABLE_FS_SERVING //Enable sending html file from SPIFFS before serving progmem version +//#define WLED_ENABLE_FS_EDITOR //enable /edit page for editing SPIFFS content. Will also be disabled with OTA lock + +//to toggle usb serial debug (un)comment the following line +//#define WLED_DEBUG + +//library inclusions +#include +#ifdef WLED_ENABLE_DMX +#include +DMXESPSerial dmx; +#endif +#ifdef ESP8266 +#include +#include +#include +extern "C" +{ +#include +} +#else //ESP32 +#include +#include "esp_wifi.h" +#include +#include +#include "SPIFFS.h" +#endif + +#include +#include +#include +#include +#ifndef WLED_DISABLE_OTA +#include +#endif +#include +#include "src/dependencies/time/TimeLib.h" +#include "src/dependencies/timezone/Timezone.h" +#ifndef WLED_DISABLE_ALEXA +#define ESPALEXA_ASYNC +#define ESPALEXA_NO_SUBPAGE +#define ESPALEXA_MAXDEVICES 1 +// #define ESPALEXA_DEBUG +#include "src/dependencies/espalexa/Espalexa.h" +#endif +#ifndef WLED_DISABLE_BLYNK +#include "src/dependencies/blynk/BlynkSimpleEsp.h" +#endif +#include "src/dependencies/e131/ESPAsyncE131.h" +#include "src/dependencies/async-mqtt-client/AsyncMqttClient.h" +#include "src/dependencies/json/AsyncJson-v6.h" +#include "src/dependencies/json/ArduinoJson-v6.h" +#include "html_ui.h" +#include "html_settings.h" +#include "html_other.h" +#include "FX.h" +#include "ir_codes.h" +#include "const.h" + +#ifndef CLIENT_SSID +#define CLIENT_SSID DEFAULT_CLIENT_SSID +#endif + +#ifndef CLIENT_PASS +#define CLIENT_PASS "" +#endif + +#if IR_PIN < 0 +#ifndef WLED_DISABLE_INFRARED +#define WLED_DISABLE_INFRARED +#endif +#endif + +#ifndef WLED_DISABLE_INFRARED +#include +#include +#include +#endif + +// remove flicker because PWM signal of RGB channels can become out of phase +#if defined(WLED_USE_ANALOG_LEDS) && defined(ESP8266) +#include "src/dependencies/arduino/core_esp8266_waveform.h" +#endif + +// enable additional debug output +#ifdef WLED_DEBUG +#ifndef ESP8266 +#include +#endif +#endif + +//version code in format yymmddb (b = daily build) +#define VERSION 2003222 + +char versionString[] = "0.9.1"; + +//AP and OTA default passwords (for maximum change them!) +char apPass[65] = DEFAULT_AP_PASS; +char otaPass[33] = DEFAULT_OTA_PASS; + +//Hardware CONFIG (only changeble HERE, not at runtime) +//LED strip pin, button pin and IR pin changeable in NpbWrapper.h! + +byte auxDefaultState = 0; //0: input 1: high 2: low +byte auxTriggeredState = 0; //0: input 1: high 2: low +char ntpServerName[33] = "0.wled.pool.ntp.org"; //NTP server to use + +//WiFi CONFIG (all these can be changed via web UI, no need to set them here) +char clientSSID[33] = CLIENT_SSID; +char clientPass[65] = CLIENT_PASS; +char cmDNS[33] = "x"; //mDNS address (placeholder, will be replaced by wledXXXXXXXXXXXX.local) +char apSSID[33] = ""; //AP off by default (unless setup) +byte apChannel = 1; //2.4GHz WiFi AP channel (1-13) +byte apHide = 0; //hidden AP SSID +byte apBehavior = AP_BEHAVIOR_BOOT_NO_CONN; //access point opens when no connection after boot by default +IPAddress staticIP(0, 0, 0, 0); //static IP of ESP +IPAddress staticGateway(0, 0, 0, 0); //gateway (router) IP +IPAddress staticSubnet(255, 255, 255, 0); //most common subnet in home networks +bool noWifiSleep = false; //disabling modem sleep modes will increase heat output and power usage, but may help with connection issues + +//LED CONFIG +uint16_t ledCount = 30; //overcurrent prevented by ABL +bool useRGBW = false; //SK6812 strips can contain an extra White channel +#define ABL_MILLIAMPS_DEFAULT 850; //auto lower brightness to stay close to milliampere limit +bool turnOnAtBoot = true; //turn on LEDs at power-up +byte bootPreset = 0; //save preset to load after power-up + +byte col[]{255, 160, 0, 0}; //current RGB(W) primary color. col[] should be updated if you want to change the color. +byte colSec[]{0, 0, 0, 0}; //current RGB(W) secondary color +byte briS = 128; //default brightness + +byte nightlightTargetBri = 0; //brightness after nightlight is over +byte nightlightDelayMins = 60; +bool nightlightFade = true; //if enabled, light will gradually dim towards the target bri. Otherwise, it will instantly set after delay over +bool nightlightColorFade = false; //if enabled, light will gradually fade color from primary to secondary color. +bool fadeTransition = true; //enable crossfading color transition +uint16_t transitionDelay = 750; //default crossfade duration in ms + +bool skipFirstLed = false; //ignore first LED in strip (useful if you need the LED as signal repeater) +byte briMultiplier = 100; //% of brightness to set (to limit power, if you set it to 50 and set bri to 255, actual brightness will be 127) + +//User Interface CONFIG +char serverDescription[33] = "WLED"; //Name of module +bool syncToggleReceive = false; //UIs which only have a single button for sync should toggle send+receive if this is true, only send otherwise + +//Sync CONFIG +bool buttonEnabled = true; +byte irEnabled = 0; //Infrared receiver + +uint16_t udpPort = 21324; //WLED notifier default port +uint16_t udpRgbPort = 19446; //Hyperion port + +bool receiveNotificationBrightness = true; //apply brightness from incoming notifications +bool receiveNotificationColor = true; //apply color +bool receiveNotificationEffects = true; //apply effects setup +bool notifyDirect = false; //send notification if change via UI or HTTP API +bool notifyButton = false; //send if updated by button or infrared remote +bool notifyAlexa = false; //send notification if updated via Alexa +bool notifyMacro = false; //send notification for macro +bool notifyHue = true; //send notification if Hue light changes +bool notifyTwice = false; //notifications use UDP: enable if devices don't sync reliably + +bool alexaEnabled = true; //enable device discovery by Amazon Echo +char alexaInvocationName[33] = "Light"; //speech control name of device. Choose something voice-to-text can understand + +char blynkApiKey[36] = ""; //Auth token for Blynk server. If empty, no connection will be made + +uint16_t realtimeTimeoutMs = 2500; //ms timeout of realtime mode before returning to normal mode +int arlsOffset = 0; //realtime LED offset +bool receiveDirect = true; //receive UDP realtime +bool arlsDisableGammaCorrection = true; //activate if gamma correction is handled by the source +bool arlsForceMaxBri = false; //enable to force max brightness if source has very dark colors that would be black + +#define E131_MAX_UNIVERSE_COUNT 9 +uint16_t e131Universe = 1; //settings for E1.31 (sACN) protocol (only DMX_MODE_MULTIPLE_* can span over consequtive universes) +uint8_t DMXMode = DMX_MODE_MULTIPLE_RGB; //DMX mode (s.a.) +uint16_t DMXAddress = 1; //DMX start address of fixture, a.k.a. first Channel [for E1.31 (sACN) protocol] +uint8_t DMXOldDimmer = 0; //only update brightness on change +uint8_t e131LastSequenceNumber[E131_MAX_UNIVERSE_COUNT]; //to detect packet loss +bool e131Multicast = false; //multicast or unicast +bool e131SkipOutOfSequence = false; //freeze instead of flickering + +bool mqttEnabled = false; +char mqttDeviceTopic[33] = ""; //main MQTT topic (individual per device, default is wled/mac) +char mqttGroupTopic[33] = "wled/all"; //second MQTT topic (for example to group devices) +char mqttServer[33] = ""; //both domains and IPs should work (no SSL) +char mqttUser[41] = ""; //optional: username for MQTT auth +char mqttPass[41] = ""; //optional: password for MQTT auth +char mqttClientID[41] = ""; //override the client ID +uint16_t mqttPort = 1883; + +bool huePollingEnabled = false; //poll hue bridge for light state +uint16_t huePollIntervalMs = 2500; //low values (< 1sec) may cause lag but offer quicker response +char hueApiKey[47] = "api"; //key token will be obtained from bridge +byte huePollLightId = 1; //ID of hue lamp to sync to. Find the ID in the hue app ("about" section) +IPAddress hueIP = (0, 0, 0, 0); //IP address of the bridge +bool hueApplyOnOff = true; +bool hueApplyBri = true; +bool hueApplyColor = true; + +//Time CONFIG +bool ntpEnabled = false; //get internet time. Only required if you use clock overlays or time-activated macros +bool useAMPM = false; //12h/24h clock format +byte currentTimezone = 0; //Timezone ID. Refer to timezones array in wled10_ntp.ino +int utcOffsetSecs = 0; //Seconds to offset from UTC before timzone calculation + +byte overlayDefault = 0; //0: no overlay 1: analog clock 2: single-digit clocl 3: cronixie +byte overlayMin = 0, overlayMax = ledCount - 1; //boundaries of overlay mode + +byte analogClock12pixel = 0; //The pixel in your strip where "midnight" would be +bool analogClockSecondsTrail = false; //Display seconds as trail of LEDs instead of a single pixel +bool analogClock5MinuteMarks = false; //Light pixels at every 5-minute position + +char cronixieDisplay[7] = "HHMMSS"; //Cronixie Display mask. See wled13_cronixie.ino +bool cronixieBacklight = true; //Allow digits to be back-illuminated + +bool countdownMode = false; //Clock will count down towards date +byte countdownYear = 20, countdownMonth = 1; //Countdown target date, year is last two digits +byte countdownDay = 1, countdownHour = 0; +byte countdownMin = 0, countdownSec = 0; + +byte macroBoot = 0; //macro loaded after startup +byte macroNl = 0; //after nightlight delay over +byte macroCountdown = 0; +byte macroAlexaOn = 0, macroAlexaOff = 0; +byte macroButton = 0, macroLongPress = 0, macroDoublePress = 0; + +//Security CONFIG +bool otaLock = false; //prevents OTA firmware updates without password. ALWAYS enable if system exposed to any public networks +bool wifiLock = false; //prevents access to WiFi settings when OTA lock is enabled +bool aOtaEnabled = true; //ArduinoOTA allows easy updates directly from the IDE. Careful, it does not auto-disable when OTA lock is on + +uint16_t userVar0 = 0, userVar1 = 0; + +#ifdef WLED_ENABLE_DMX +//dmx CONFIG +byte DMXChannels = 7; // number of channels per fixture +byte DMXFixtureMap[15] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +// assigns the different channels to different functions. See wled21_dmx.ino for more information. +uint16_t DMXGap = 10; // gap between the fixtures. makes addressing easier because you don't have to memorize odd numbers when climbing up onto a rig. +uint16_t DMXStart = 10; // start address of the first fixture +#endif + +//internal global variable declarations +//wifi +bool apActive = false; +bool forceReconnect = false; +uint32_t lastReconnectAttempt = 0; +bool interfacesInited = false; +bool wasConnected = false; + +//color +byte colOld[]{0, 0, 0, 0}; //color before transition +byte colT[]{0, 0, 0, 0}; //color that is currently displayed on the LEDs +byte colIT[]{0, 0, 0, 0}; //color that was last sent to LEDs +byte colSecT[]{0, 0, 0, 0}; +byte colSecOld[]{0, 0, 0, 0}; +byte colSecIT[]{0, 0, 0, 0}; + +byte lastRandomIndex = 0; //used to save last random color so the new one is not the same + +//transitions +bool transitionActive = false; +uint16_t transitionDelayDefault = transitionDelay; +uint16_t transitionDelayTemp = transitionDelay; +unsigned long transitionStartTime; +float tperLast = 0; //crossfade transition progress, 0.0f - 1.0f +bool jsonTransitionOnce = false; + +//nightlight +bool nightlightActive = false; +bool nightlightActiveOld = false; +uint32_t nightlightDelayMs = 10; +uint8_t nightlightDelayMinsDefault = nightlightDelayMins; +unsigned long nightlightStartTime; +byte briNlT = 0; //current nightlight brightness +byte colNlT[]{0, 0, 0, 0}; //current nightlight color + +//brightness +unsigned long lastOnTime = 0; +bool offMode = !turnOnAtBoot; +byte bri = briS; +byte briOld = 0; +byte briT = 0; +byte briIT = 0; +byte briLast = 128; //brightness before turned off. Used for toggle function +byte whiteLast = 128; //white channel before turned off. Used for toggle function + +//button +bool buttonPressedBefore = false; +bool buttonLongPressed = false; +unsigned long buttonPressedTime = 0; +unsigned long buttonWaitTime = 0; + +//notifications +bool notifyDirectDefault = notifyDirect; +bool receiveNotifications = true; +unsigned long notificationSentTime = 0; +byte notificationSentCallMode = NOTIFIER_CALL_MODE_INIT; +bool notificationTwoRequired = false; + +//effects +byte effectCurrent = 0; +byte effectSpeed = 128; +byte effectIntensity = 128; +byte effectPalette = 0; + +//network +bool udpConnected = false, udpRgbConnected = false; + +//ui style +bool showWelcomePage = false; + +//hue +byte hueError = HUE_ERROR_INACTIVE; +//uint16_t hueFailCount = 0; +float hueXLast = 0, hueYLast = 0; +uint16_t hueHueLast = 0, hueCtLast = 0; +byte hueSatLast = 0, hueBriLast = 0; +unsigned long hueLastRequestSent = 0; +bool hueAuthRequired = false; +bool hueReceived = false; +bool hueStoreAllowed = false, hueNewKey = false; + +//overlays +byte overlayCurrent = overlayDefault; +byte overlaySpeed = 200; +unsigned long overlayRefreshMs = 200; +unsigned long overlayRefreshedTime; + +//cronixie +byte dP[]{0, 0, 0, 0, 0, 0}; +bool cronixieInit = false; + +//countdown +unsigned long countdownTime = 1514764800L; +bool countdownOverTriggered = true; + +//timer +byte lastTimerMinute = 0; +byte timerHours[] = {0, 0, 0, 0, 0, 0, 0, 0}; +byte timerMinutes[] = {0, 0, 0, 0, 0, 0, 0, 0}; +byte timerMacro[] = {0, 0, 0, 0, 0, 0, 0, 0}; +byte timerWeekday[] = {255, 255, 255, 255, 255, 255, 255, 255}; //weekdays to activate on +//bit pattern of arr elem: 0b11111111: sun,sat,fri,thu,wed,tue,mon,validity + +//blynk +bool blynkEnabled = false; + +//preset cycling +bool presetCyclingEnabled = false; +byte presetCycleMin = 1, presetCycleMax = 5; +uint16_t presetCycleTime = 1250; +unsigned long presetCycledTime = 0; +byte presetCycCurr = presetCycleMin; +bool presetApplyBri = true; +bool saveCurrPresetCycConf = false; + +//realtime +byte realtimeMode = REALTIME_MODE_INACTIVE; +IPAddress realtimeIP = (0, 0, 0, 0); +unsigned long realtimeTimeout = 0; + +//mqtt +long lastMqttReconnectAttempt = 0; +long lastInterfaceUpdate = 0; +byte interfaceUpdateCallMode = NOTIFIER_CALL_MODE_INIT; +char mqttStatusTopic[40] = ""; //this must be global because of async handlers + +#if AUXPIN >= 0 + //auxiliary debug pin +byte auxTime = 0; +unsigned long auxStartTime = 0; +bool auxActive = false, auxActiveBefore = false; +#endif + +//alexa udp +String escapedMac; +#ifndef WLED_DISABLE_ALEXA +Espalexa espalexa; +EspalexaDevice *espalexaDevice; +#endif + +//dns server +DNSServer dnsServer; + +//network time +bool ntpConnected = false; +time_t local = 0; +unsigned long ntpLastSyncTime = 999000000L; +unsigned long ntpPacketSentTime = 999000000L; +IPAddress ntpServerIP; +uint16_t ntpLocalPort = 2390; +#define NTP_PACKET_SIZE 48 + +//maximum number of LEDs - MAX_LEDS is coming from the JSON response getting too big, MAX_LEDS_DMA will become a timing issue +#define MAX_LEDS 1500 +#define MAX_LEDS_DMA 500 + +//string temp buffer (now stored in stack locally) +#define OMAX 2048 +char *obuf; +uint16_t olen = 0; + +//presets +uint16_t savedPresets = 0; +int8_t currentPreset = -1; +bool isPreset = false; + +byte errorFlag = 0; + +String messageHead, messageSub; +byte optionType; + +bool doReboot = false; //flag to initiate reboot from async handlers +bool doPublishMqtt = false; + +//server library objects +AsyncWebServer server(80); +AsyncClient *hueClient = NULL; +AsyncMqttClient *mqtt = NULL; + +//function prototypes +void colorFromUint32(uint32_t, bool = false); +void serveMessage(AsyncWebServerRequest *, uint16_t, String, String, byte); +void handleE131Packet(e131_packet_t *, IPAddress); +void arlsLock(uint32_t, byte); +void handleOverlayDraw(); + +//udp interface objects +WiFiUDP notifierUdp, rgbUdp; +WiFiUDP ntpUdp; +ESPAsyncE131 e131(handleE131Packet); +bool e131NewData = false; + +//led fx library object +WS2812FX strip = WS2812FX(); + +#define WLED_CONNECTED (WiFi.status() == WL_CONNECTED) +#define WLED_WIFI_CONFIGURED (strlen(clientSSID) >= 1 && strcmp(clientSSID, DEFAULT_CLIENT_SSID) != 0) + +//debug macros +#ifdef WLED_DEBUG +#define DEBUG_PRINT(x) Serial.print(x) +#define DEBUG_PRINTLN(x) Serial.println(x) +#define DEBUG_PRINTF(x) Serial.printf(x) +unsigned long debugTime = 0; +int lastWifiState = 3; +unsigned long wifiStateChangedTime = 0; +int loops = 0; +#else +#define DEBUG_PRINT(x) +#define DEBUG_PRINTLN(x) +#define DEBUG_PRINTF(x) +#endif + + +// TODO: Inline? +//append new c string to temp buffer efficiently +bool oappend(const char *txt); +//append new number to temp buffer efficiently +bool oappendi(int i); + +class WLED +{ +public: + static WLED &instance() + { + static WLED instance; + return instance; + } + + WLED(); + + void wledInit(); + + void reset(); + void loop(); + + + //boot starts here + void setup() + { + wledInit(); + } + + void loop(); + +private: + void wledInit(); + void beginStrip(); + + void handleConnection(); + void initAP(bool resetAP = false); + void initConnection(); + void initInterfaces(); +}; +#endif // WLED_H diff --git a/wled00/wled00.ino b/wled00/wled00.ino index 7c64796d6..fc06f2114 100644 --- a/wled00/wled00.ino +++ b/wled00/wled00.ino @@ -1,617 +1,13 @@ /* - Main sketch, global variable declarations + Arduino Studio support file. */ -/* - * @title WLED project sketch - * @version 0.9.1 - * @author Christian Schwinne - */ +#include "wled.h" -//ESP8266-01 (blue) got too little storage space to work with all features of WLED. To use it, you must use ESP8266 Arduino Core v2.4.2 and the setting 512K(No SPIFFS). - -//ESP8266-01 (black) has 1MB flash and can thus fit the whole program. Use 1M(64K SPIFFS). -//Uncomment some of the following lines to disable features to compile for ESP8266-01 (max flash size 434kB): - -//You are required to disable over-the-air updates: -//#define WLED_DISABLE_OTA //saves 14kb - -//You need to choose some of these features to disable: -//#define WLED_DISABLE_ALEXA //saves 11kb -//#define WLED_DISABLE_BLYNK //saves 6kb -//#define WLED_DISABLE_CRONIXIE //saves 3kb -//#define WLED_DISABLE_HUESYNC //saves 4kb -//#define WLED_DISABLE_INFRARED //there is no pin left for this on ESP8266-01, saves 12kb -#define WLED_ENABLE_MQTT //saves 12kb -#define WLED_ENABLE_ADALIGHT //saves 500b only -//#define WLED_ENABLE_DMX //uses 3.5kb - -#define WLED_DISABLE_FILESYSTEM //SPIFFS is not used by any WLED feature yet -//#define WLED_ENABLE_FS_SERVING //Enable sending html file from SPIFFS before serving progmem version -//#define WLED_ENABLE_FS_EDITOR //enable /edit page for editing SPIFFS content. Will also be disabled with OTA lock - -//to toggle usb serial debug (un)comment the following line -//#define WLED_DEBUG - -//library inclusions -#include -#ifdef WLED_ENABLE_DMX - #include - DMXESPSerial dmx; -#endif -#ifdef ESP8266 - #include - #include - #include - extern "C" { - #include - } -#else //ESP32 - #include - #include "esp_wifi.h" - #include - #include - #include "SPIFFS.h" -#endif - -#include -#include -#include -#include -#ifndef WLED_DISABLE_OTA - #include -#endif -#include -#include "src/dependencies/time/TimeLib.h" -#include "src/dependencies/timezone/Timezone.h" -#ifndef WLED_DISABLE_ALEXA - #define ESPALEXA_ASYNC - #define ESPALEXA_NO_SUBPAGE - #define ESPALEXA_MAXDEVICES 1 - // #define ESPALEXA_DEBUG - #include "src/dependencies/espalexa/Espalexa.h" -#endif -#ifndef WLED_DISABLE_BLYNK - #include "src/dependencies/blynk/BlynkSimpleEsp.h" -#endif -#include "src/dependencies/e131/ESPAsyncE131.h" -#include "src/dependencies/async-mqtt-client/AsyncMqttClient.h" -#include "src/dependencies/json/AsyncJson-v6.h" -#include "src/dependencies/json/ArduinoJson-v6.h" -#include "html_ui.h" -#include "html_settings.h" -#include "html_other.h" -#include "FX.h" -#include "ir_codes.h" -#include "const.h" - -#ifndef CLIENT_SSID -#define CLIENT_SSID DEFAULT_CLIENT_SSID -#endif - -#ifndef CLIENT_PASS -#define CLIENT_PASS "" -#endif - - -#if IR_PIN < 0 - #ifndef WLED_DISABLE_INFRARED - #define WLED_DISABLE_INFRARED - #endif -#endif - -#ifndef WLED_DISABLE_INFRARED - #include - #include - #include -#endif - -// remove flicker because PWM signal of RGB channels can become out of phase -#if defined(WLED_USE_ANALOG_LEDS) && defined(ESP8266) - #include "src/dependencies/arduino/core_esp8266_waveform.h" -#endif - -// enable additional debug output -#ifdef WLED_DEBUG - #ifndef ESP8266 - #include - #endif -#endif - -//version code in format yymmddb (b = daily build) -#define VERSION 2003222 - -char versionString[] = "0.9.1"; - - -//AP and OTA default passwords (for maximum change them!) -char apPass[65] = DEFAULT_AP_PASS; -char otaPass[33] = DEFAULT_OTA_PASS; - - -//Hardware CONFIG (only changeble HERE, not at runtime) -//LED strip pin, button pin and IR pin changeable in NpbWrapper.h! - -byte auxDefaultState = 0; //0: input 1: high 2: low -byte auxTriggeredState = 0; //0: input 1: high 2: low -char ntpServerName[33] = "0.wled.pool.ntp.org";//NTP server to use - - -//WiFi CONFIG (all these can be changed via web UI, no need to set them here) -char clientSSID[33] = CLIENT_SSID; -char clientPass[65] = CLIENT_PASS; -char cmDNS[33] = "x"; //mDNS address (placeholder, will be replaced by wledXXXXXXXXXXXX.local) -char apSSID[33] = ""; //AP off by default (unless setup) -byte apChannel = 1; //2.4GHz WiFi AP channel (1-13) -byte apHide = 0; //hidden AP SSID -byte apBehavior = AP_BEHAVIOR_BOOT_NO_CONN; //access point opens when no connection after boot by default -IPAddress staticIP(0, 0, 0, 0); //static IP of ESP -IPAddress staticGateway(0, 0, 0, 0); //gateway (router) IP -IPAddress staticSubnet(255, 255, 255, 0); //most common subnet in home networks -bool noWifiSleep = false; //disabling modem sleep modes will increase heat output and power usage, but may help with connection issues - - -//LED CONFIG -uint16_t ledCount = 30; //overcurrent prevented by ABL -bool useRGBW = false; //SK6812 strips can contain an extra White channel -#define ABL_MILLIAMPS_DEFAULT 850; //auto lower brightness to stay close to milliampere limit -bool turnOnAtBoot = true; //turn on LEDs at power-up -byte bootPreset = 0; //save preset to load after power-up - -byte col[] {255, 160, 0, 0}; //current RGB(W) primary color. col[] should be updated if you want to change the color. -byte colSec[] {0, 0, 0, 0}; //current RGB(W) secondary color -byte briS = 128; //default brightness - -byte nightlightTargetBri = 0; //brightness after nightlight is over -byte nightlightDelayMins = 60; -bool nightlightFade = true; //if enabled, light will gradually dim towards the target bri. Otherwise, it will instantly set after delay over -bool nightlightColorFade = false; //if enabled, light will gradually fade color from primary to secondary color. -bool fadeTransition = true; //enable crossfading color transition -uint16_t transitionDelay = 750; //default crossfade duration in ms - -bool skipFirstLed = false; //ignore first LED in strip (useful if you need the LED as signal repeater) -byte briMultiplier = 100; //% of brightness to set (to limit power, if you set it to 50 and set bri to 255, actual brightness will be 127) - - -//User Interface CONFIG -char serverDescription[33] = "WLED"; //Name of module -bool syncToggleReceive = false; //UIs which only have a single button for sync should toggle send+receive if this is true, only send otherwise - - -//Sync CONFIG -bool buttonEnabled = true; -byte irEnabled = 0; //Infrared receiver - -uint16_t udpPort = 21324; //WLED notifier default port -uint16_t udpRgbPort = 19446; //Hyperion port - -bool receiveNotificationBrightness = true; //apply brightness from incoming notifications -bool receiveNotificationColor = true; //apply color -bool receiveNotificationEffects = true; //apply effects setup -bool notifyDirect = false; //send notification if change via UI or HTTP API -bool notifyButton = false; //send if updated by button or infrared remote -bool notifyAlexa = false; //send notification if updated via Alexa -bool notifyMacro = false; //send notification for macro -bool notifyHue = true; //send notification if Hue light changes -bool notifyTwice = false; //notifications use UDP: enable if devices don't sync reliably - -bool alexaEnabled = true; //enable device discovery by Amazon Echo -char alexaInvocationName[33] = "Light"; //speech control name of device. Choose something voice-to-text can understand - -char blynkApiKey[36] = ""; //Auth token for Blynk server. If empty, no connection will be made - -uint16_t realtimeTimeoutMs = 2500; //ms timeout of realtime mode before returning to normal mode -int arlsOffset = 0; //realtime LED offset -bool receiveDirect = true; //receive UDP realtime -bool arlsDisableGammaCorrection = true; //activate if gamma correction is handled by the source -bool arlsForceMaxBri = false; //enable to force max brightness if source has very dark colors that would be black - -#define E131_MAX_UNIVERSE_COUNT 9 -uint16_t e131Universe = 1; //settings for E1.31 (sACN) protocol (only DMX_MODE_MULTIPLE_* can span over consequtive universes) -uint8_t DMXMode = DMX_MODE_MULTIPLE_RGB; //DMX mode (s.a.) -uint16_t DMXAddress = 1; //DMX start address of fixture, a.k.a. first Channel [for E1.31 (sACN) protocol] -uint8_t DMXOldDimmer = 0; //only update brightness on change -uint8_t e131LastSequenceNumber[E131_MAX_UNIVERSE_COUNT]; //to detect packet loss -bool e131Multicast = false; //multicast or unicast -bool e131SkipOutOfSequence = false; //freeze instead of flickering - -bool mqttEnabled = false; -char mqttDeviceTopic[33] = ""; //main MQTT topic (individual per device, default is wled/mac) -char mqttGroupTopic[33] = "wled/all"; //second MQTT topic (for example to group devices) -char mqttServer[33] = ""; //both domains and IPs should work (no SSL) -char mqttUser[41] = ""; //optional: username for MQTT auth -char mqttPass[41] = ""; //optional: password for MQTT auth -char mqttClientID[41] = ""; //override the client ID -uint16_t mqttPort = 1883; - -bool huePollingEnabled = false; //poll hue bridge for light state -uint16_t huePollIntervalMs = 2500; //low values (< 1sec) may cause lag but offer quicker response -char hueApiKey[47] = "api"; //key token will be obtained from bridge -byte huePollLightId = 1; //ID of hue lamp to sync to. Find the ID in the hue app ("about" section) -IPAddress hueIP = (0, 0, 0, 0); //IP address of the bridge -bool hueApplyOnOff = true; -bool hueApplyBri = true; -bool hueApplyColor = true; - - -//Time CONFIG -bool ntpEnabled = false; //get internet time. Only required if you use clock overlays or time-activated macros -bool useAMPM = false; //12h/24h clock format -byte currentTimezone = 0; //Timezone ID. Refer to timezones array in wled10_ntp.ino -int utcOffsetSecs = 0; //Seconds to offset from UTC before timzone calculation - -byte overlayDefault = 0; //0: no overlay 1: analog clock 2: single-digit clocl 3: cronixie -byte overlayMin = 0, overlayMax = ledCount - 1; //boundaries of overlay mode - -byte analogClock12pixel = 0; //The pixel in your strip where "midnight" would be -bool analogClockSecondsTrail = false; //Display seconds as trail of LEDs instead of a single pixel -bool analogClock5MinuteMarks = false; //Light pixels at every 5-minute position - -char cronixieDisplay[7] = "HHMMSS"; //Cronixie Display mask. See wled13_cronixie.ino -bool cronixieBacklight = true; //Allow digits to be back-illuminated - -bool countdownMode = false; //Clock will count down towards date -byte countdownYear = 20, countdownMonth = 1; //Countdown target date, year is last two digits -byte countdownDay = 1, countdownHour = 0; -byte countdownMin = 0, countdownSec = 0; - - -byte macroBoot = 0; //macro loaded after startup -byte macroNl = 0; //after nightlight delay over -byte macroCountdown = 0; -byte macroAlexaOn = 0, macroAlexaOff = 0; -byte macroButton = 0, macroLongPress = 0, macroDoublePress = 0; - - -//Security CONFIG -bool otaLock = false; //prevents OTA firmware updates without password. ALWAYS enable if system exposed to any public networks -bool wifiLock = false; //prevents access to WiFi settings when OTA lock is enabled -bool aOtaEnabled = true; //ArduinoOTA allows easy updates directly from the IDE. Careful, it does not auto-disable when OTA lock is on - - -uint16_t userVar0 = 0, userVar1 = 0; - -#ifdef WLED_ENABLE_DMX - //dmx CONFIG - byte DMXChannels = 7; // number of channels per fixture - byte DMXFixtureMap[15] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - // assigns the different channels to different functions. See wled21_dmx.ino for more information. - uint16_t DMXGap = 10; // gap between the fixtures. makes addressing easier because you don't have to memorize odd numbers when climbing up onto a rig. - uint16_t DMXStart = 10; // start address of the first fixture -#endif - - -//internal global variable declarations -//wifi -bool apActive = false; -bool forceReconnect = false; -uint32_t lastReconnectAttempt = 0; -bool interfacesInited = false; -bool wasConnected = false; - -//color -byte colOld[] {0, 0, 0, 0}; //color before transition -byte colT[] {0, 0, 0, 0}; //color that is currently displayed on the LEDs -byte colIT[] {0, 0, 0, 0}; //color that was last sent to LEDs -byte colSecT[] {0, 0, 0, 0}; -byte colSecOld[] {0, 0, 0, 0}; -byte colSecIT[] {0, 0, 0, 0}; - -byte lastRandomIndex = 0; //used to save last random color so the new one is not the same - -//transitions -bool transitionActive = false; -uint16_t transitionDelayDefault = transitionDelay; -uint16_t transitionDelayTemp = transitionDelay; -unsigned long transitionStartTime; -float tperLast = 0; //crossfade transition progress, 0.0f - 1.0f -bool jsonTransitionOnce = false; - -//nightlight -bool nightlightActive = false; -bool nightlightActiveOld = false; -uint32_t nightlightDelayMs = 10; -uint8_t nightlightDelayMinsDefault = nightlightDelayMins; -unsigned long nightlightStartTime; -byte briNlT = 0; //current nightlight brightness -byte colNlT[] {0, 0, 0, 0}; //current nightlight color - -//brightness -unsigned long lastOnTime = 0; -bool offMode = !turnOnAtBoot; -byte bri = briS; -byte briOld = 0; -byte briT = 0; -byte briIT = 0; -byte briLast = 128; //brightness before turned off. Used for toggle function -byte whiteLast = 128; //white channel before turned off. Used for toggle function - -//button -bool buttonPressedBefore = false; -bool buttonLongPressed = false; -unsigned long buttonPressedTime = 0; -unsigned long buttonWaitTime = 0; - -//notifications -bool notifyDirectDefault = notifyDirect; -bool receiveNotifications = true; -unsigned long notificationSentTime = 0; -byte notificationSentCallMode = NOTIFIER_CALL_MODE_INIT; -bool notificationTwoRequired = false; - -//effects -byte effectCurrent = 0; -byte effectSpeed = 128; -byte effectIntensity = 128; -byte effectPalette = 0; - -//network -bool udpConnected = false, udpRgbConnected = false; - -//ui style -bool showWelcomePage = false; - -//hue -byte hueError = HUE_ERROR_INACTIVE; -//uint16_t hueFailCount = 0; -float hueXLast = 0, hueYLast = 0; -uint16_t hueHueLast = 0, hueCtLast = 0; -byte hueSatLast = 0, hueBriLast = 0; -unsigned long hueLastRequestSent = 0; -bool hueAuthRequired = false; -bool hueReceived = false; -bool hueStoreAllowed = false, hueNewKey = false; - -//overlays -byte overlayCurrent = overlayDefault; -byte overlaySpeed = 200; -unsigned long overlayRefreshMs = 200; -unsigned long overlayRefreshedTime; - -//cronixie -byte dP[] {0, 0, 0, 0, 0, 0}; -bool cronixieInit = false; - -//countdown -unsigned long countdownTime = 1514764800L; -bool countdownOverTriggered = true; - -//timer -byte lastTimerMinute = 0; -byte timerHours[] = {0, 0, 0, 0, 0, 0, 0, 0}; -byte timerMinutes[] = {0, 0, 0, 0, 0, 0, 0, 0}; -byte timerMacro[] = {0, 0, 0, 0, 0, 0, 0, 0}; -byte timerWeekday[] = {255, 255, 255, 255, 255, 255, 255, 255}; //weekdays to activate on -//bit pattern of arr elem: 0b11111111: sun,sat,fri,thu,wed,tue,mon,validity - -//blynk -bool blynkEnabled = false; - -//preset cycling -bool presetCyclingEnabled = false; -byte presetCycleMin = 1, presetCycleMax = 5; -uint16_t presetCycleTime = 1250; -unsigned long presetCycledTime = 0; byte presetCycCurr = presetCycleMin; -bool presetApplyBri = true; -bool saveCurrPresetCycConf = false; - -//realtime -byte realtimeMode = REALTIME_MODE_INACTIVE; -IPAddress realtimeIP = (0,0,0,0); -unsigned long realtimeTimeout = 0; - -//mqtt -long lastMqttReconnectAttempt = 0; -long lastInterfaceUpdate = 0; -byte interfaceUpdateCallMode = NOTIFIER_CALL_MODE_INIT; -char mqttStatusTopic[40] = ""; //this must be global because of async handlers - -#if AUXPIN >= 0 - //auxiliary debug pin - byte auxTime = 0; - unsigned long auxStartTime = 0; - bool auxActive = false, auxActiveBefore = false; -#endif - -//alexa udp -String escapedMac; -#ifndef WLED_DISABLE_ALEXA - Espalexa espalexa; - EspalexaDevice* espalexaDevice; -#endif - -//dns server -DNSServer dnsServer; - -//network time -bool ntpConnected = false; -time_t local = 0; -unsigned long ntpLastSyncTime = 999000000L; -unsigned long ntpPacketSentTime = 999000000L; -IPAddress ntpServerIP; -uint16_t ntpLocalPort = 2390; -#define NTP_PACKET_SIZE 48 - -//maximum number of LEDs - MAX_LEDS is coming from the JSON response getting too big, MAX_LEDS_DMA will become a timing issue -#define MAX_LEDS 1500 -#define MAX_LEDS_DMA 500 - -//string temp buffer (now stored in stack locally) -#define OMAX 2048 -char* obuf; -uint16_t olen = 0; - -//presets -uint16_t savedPresets = 0; -int8_t currentPreset = -1; -bool isPreset = false; - -byte errorFlag = 0; - -String messageHead, messageSub; -byte optionType; - -bool doReboot = false; //flag to initiate reboot from async handlers -bool doPublishMqtt = false; - -//server library objects -AsyncWebServer server(80); -AsyncClient* hueClient = NULL; -AsyncMqttClient* mqtt = NULL; - -//function prototypes -void colorFromUint32(uint32_t, bool = false); -void serveMessage(AsyncWebServerRequest*, uint16_t, String, String, byte); -void handleE131Packet(e131_packet_t*, IPAddress); -void arlsLock(uint32_t,byte); -void handleOverlayDraw(); - -//udp interface objects -WiFiUDP notifierUdp, rgbUdp; -WiFiUDP ntpUdp; -ESPAsyncE131 e131(handleE131Packet); -bool e131NewData = false; - -//led fx library object -WS2812FX strip = WS2812FX(); - -#define WLED_CONNECTED (WiFi.status() == WL_CONNECTED) -#define WLED_WIFI_CONFIGURED (strlen(clientSSID) >= 1 && strcmp(clientSSID,DEFAULT_CLIENT_SSID) != 0) - -//debug macros -#ifdef WLED_DEBUG - #define DEBUG_PRINT(x) Serial.print (x) - #define DEBUG_PRINTLN(x) Serial.println (x) - #define DEBUG_PRINTF(x) Serial.printf (x) - unsigned long debugTime = 0; - int lastWifiState = 3; - unsigned long wifiStateChangedTime = 0; - int loops = 0; -#else - #define DEBUG_PRINT(x) - #define DEBUG_PRINTLN(x) - #define DEBUG_PRINTF(x) -#endif - -//filesystem -#ifndef WLED_DISABLE_FILESYSTEM - #include - #ifdef ARDUINO_ARCH_ESP32 - #include "SPIFFS.h" - #endif - #include "SPIFFSEditor.h" -#endif - - - -//turns all LEDs off and restarts ESP -void reset() -{ - briT = 0; - long dly = millis(); - while (millis() - dly < 250) - { - yield(); //enough time to send response to client - } - setAllLeds(); - DEBUG_PRINTLN("MODULE RESET"); - ESP.restart(); -} - - -//append new c string to temp buffer efficiently -bool oappend(const char* txt) -{ - uint16_t len = strlen(txt); - if (olen + len >= OMAX) return false; //buffer full - strcpy(obuf + olen, txt); - olen += len; - return true; -} - - -//append new number to temp buffer efficiently -bool oappendi(int i) -{ - char s[11]; - sprintf(s, "%ld", i); - return oappend(s); -} - - -//boot starts here void setup() { - wledInit(); + //auto& wled = Wled(); } - -//main program loop void loop() { - handleIR(); //2nd call to function needed for ESP32 to return valid results -- should be good for ESP8266, too - handleConnection(); - handleSerial(); - handleNotifications(); - handleTransitions(); -#ifdef WLED_ENABLE_DMX - handleDMX(); -#endif - userLoop(); - yield(); - handleIO(); - handleIR(); - handleNetworkTime(); - handleAlexa(); - handleOverlays(); - yield(); -#ifdef WLED_USE_ANALOG_LEDS - strip.setRgbwPwm(); -#endif - - if (doReboot) reset(); - - if (!realtimeMode) //block stuff if WARLS/Adalight is enabled - { - if (apActive) dnsServer.processNextRequest(); -#ifndef WLED_DISABLE_OTA - if (WLED_CONNECTED && aOtaEnabled) ArduinoOTA.handle(); -#endif - handleNightlight(); - yield(); - - handleHue(); - handleBlynk(); - - yield(); - if (!offMode) strip.service(); - } - yield(); -#ifdef ESP8266 - MDNS.update(); -#endif - if (millis() - lastMqttReconnectAttempt > 30000) initMqtt(); - - //DEBUG serial logging -#ifdef WLED_DEBUG - if (millis() - debugTime > 9999) - { - DEBUG_PRINTLN("---DEBUG INFO---"); - DEBUG_PRINT("Runtime: "); DEBUG_PRINTLN(millis()); - DEBUG_PRINT("Unix time: "); DEBUG_PRINTLN(now()); - DEBUG_PRINT("Free heap: "); DEBUG_PRINTLN(ESP.getFreeHeap()); - DEBUG_PRINT("Wifi state: "); DEBUG_PRINTLN(WiFi.status()); - if (WiFi.status() != lastWifiState) - { - wifiStateChangedTime = millis(); - } - lastWifiState = WiFi.status(); - DEBUG_PRINT("State time: "); DEBUG_PRINTLN(wifiStateChangedTime); - DEBUG_PRINT("NTP last sync: "); DEBUG_PRINTLN(ntpLastSyncTime); - DEBUG_PRINT("Client IP: "); DEBUG_PRINTLN(WiFi.localIP()); - DEBUG_PRINT("Loops/sec: "); DEBUG_PRINTLN(loops / 10); - loops = 0; - debugTime = millis(); - } - loops++; -#endif -} +} \ No newline at end of file diff --git a/wled00/wled05_init.ino b/wled00/wled05_init.ino deleted file mode 100644 index 29ae0e7ab..000000000 --- a/wled00/wled05_init.ino +++ /dev/null @@ -1,339 +0,0 @@ -/* - * Setup code - */ - -void wledInit() -{ - EEPROM.begin(EEPSIZE); - ledCount = EEPROM.read(229) + ((EEPROM.read(398) << 8) & 0xFF00); - if (ledCount > MAX_LEDS || ledCount == 0) ledCount = 30; - - #ifdef ESP8266 - #if LEDPIN == 3 - if (ledCount > MAX_LEDS_DMA) ledCount = MAX_LEDS_DMA; //DMA method uses too much ram - #endif - #endif - Serial.begin(115200); - Serial.setTimeout(50); - DEBUG_PRINTLN(); - DEBUG_PRINT("---WLED "); DEBUG_PRINT(versionString); DEBUG_PRINT(" "); DEBUG_PRINT(VERSION); DEBUG_PRINTLN(" INIT---"); - #ifdef ARDUINO_ARCH_ESP32 - DEBUG_PRINT("esp32 "); DEBUG_PRINTLN(ESP.getSdkVersion()); - #else - DEBUG_PRINT("esp8266 "); DEBUG_PRINTLN(ESP.getCoreVersion()); - #endif - int heapPreAlloc = ESP.getFreeHeap(); - DEBUG_PRINT("heap "); - DEBUG_PRINTLN(ESP.getFreeHeap()); - - strip.init(EEPROM.read(372),ledCount,EEPROM.read(2204)); //init LEDs quickly - strip.setBrightness(0); - - DEBUG_PRINT("LEDs inited. heap usage ~"); - DEBUG_PRINTLN(heapPreAlloc - ESP.getFreeHeap()); - - #ifndef WLED_DISABLE_FILESYSTEM - #ifdef ARDUINO_ARCH_ESP32 - SPIFFS.begin(true); - #endif - SPIFFS.begin(); - #endif - - DEBUG_PRINTLN("Load EEPROM"); - loadSettingsFromEEPROM(true); - beginStrip(); - userSetup(); - if (strcmp(clientSSID,DEFAULT_CLIENT_SSID) == 0) showWelcomePage = true; - WiFi.persistent(false); - - if (macroBoot>0) applyMacro(macroBoot); - Serial.println("Ada"); - - //generate module IDs - escapedMac = WiFi.macAddress(); - escapedMac.replace(":", ""); - escapedMac.toLowerCase(); - if (strcmp(cmDNS,"x") == 0) //fill in unique mdns default - { - strcpy(cmDNS, "wled-"); - sprintf(cmDNS+5, "%*s", 6, escapedMac.c_str()+6); - } - if (mqttDeviceTopic[0] == 0) - { - strcpy(mqttDeviceTopic, "wled/"); - sprintf(mqttDeviceTopic+5, "%*s", 6, escapedMac.c_str()+6); - } - if (mqttClientID[0] == 0) - { - strcpy(mqttClientID, "WLED-"); - sprintf(mqttClientID+5, "%*s", 6, escapedMac.c_str()+6); - } - - strip.service(); - - #ifndef WLED_DISABLE_OTA - if (aOtaEnabled) - { - ArduinoOTA.onStart([]() { - #ifdef ESP8266 - wifi_set_sleep_type(NONE_SLEEP_T); - #endif - DEBUG_PRINTLN("Start ArduinoOTA"); - }); - if (strlen(cmDNS) > 0) ArduinoOTA.setHostname(cmDNS); - } - #endif - #ifdef WLED_ENABLE_DMX - dmx.init(512); // initialize with bus length - #endif - //HTTP server page init - initServer(); -} - - -void beginStrip() -{ - // Initialize NeoPixel Strip and button - strip.setShowCallback(handleOverlayDraw); - -#ifdef BTNPIN - pinMode(BTNPIN, INPUT_PULLUP); -#endif - - if (bootPreset > 0) applyPreset(bootPreset, turnOnAtBoot); - colorUpdated(NOTIFIER_CALL_MODE_INIT); - - //init relay pin - #if RLYPIN >= 0 - pinMode(RLYPIN, OUTPUT); - #if RLYMDE - digitalWrite(RLYPIN, bri); - #else - digitalWrite(RLYPIN, !bri); - #endif - #endif - - //disable button if it is "pressed" unintentionally -#ifdef BTNPIN - if(digitalRead(BTNPIN) == LOW) buttonEnabled = false; -#else - buttonEnabled = false; -#endif -} - - -void initAP(bool resetAP=false){ - if (apBehavior == AP_BEHAVIOR_BUTTON_ONLY && !resetAP) return; - - if (!apSSID[0] || resetAP) strcpy(apSSID, "WLED-AP"); - if (resetAP) strcpy(apPass,DEFAULT_AP_PASS); - DEBUG_PRINT("Opening access point "); - DEBUG_PRINTLN(apSSID); - WiFi.softAPConfig(IPAddress(4, 3, 2, 1), IPAddress(4, 3, 2, 1), IPAddress(255,255,255,0)); - WiFi.softAP(apSSID, apPass, apChannel, apHide); - - if (!apActive) //start captive portal if AP active - { - DEBUG_PRINTLN("Init AP interfaces"); - server.begin(); - if (udpPort > 0 && udpPort != ntpLocalPort) - { - udpConnected = notifierUdp.begin(udpPort); - } - if (udpRgbPort > 0 && udpRgbPort != ntpLocalPort && udpRgbPort != udpPort) - { - udpRgbConnected = rgbUdp.begin(udpRgbPort); - } - - dnsServer.setErrorReplyCode(DNSReplyCode::NoError); - dnsServer.start(53, "*", WiFi.softAPIP()); - } - apActive = true; -} - -void initConnection() -{ - WiFi.disconnect(); //close old connections - #ifdef ESP8266 - WiFi.setPhyMode(WIFI_PHY_MODE_11N); - #endif - - if (staticIP[0] != 0 && staticGateway[0] != 0) - { - WiFi.config(staticIP, staticGateway, staticSubnet, IPAddress(8,8,8,8)); - } else - { - WiFi.config(0U, 0U, 0U); - } - - lastReconnectAttempt = millis(); - - if (!WLED_WIFI_CONFIGURED) - { - DEBUG_PRINT("No connection configured. "); - if (!apActive) initAP(); //instantly go to ap mode - return; - } else if (!apActive) { - if (apBehavior == AP_BEHAVIOR_ALWAYS) - { - initAP(); - } else - { - DEBUG_PRINTLN("Access point disabled."); - WiFi.softAPdisconnect(true); - } - } - showWelcomePage = false; - - DEBUG_PRINT("Connecting to "); - DEBUG_PRINT(clientSSID); - DEBUG_PRINTLN("..."); - - #ifdef ESP8266 - WiFi.hostname(serverDescription); - #endif - - WiFi.begin(clientSSID, clientPass); - - #ifdef ARDUINO_ARCH_ESP32 - WiFi.setSleep(!noWifiSleep); - WiFi.setHostname(serverDescription); - #else - wifi_set_sleep_type((noWifiSleep) ? NONE_SLEEP_T : MODEM_SLEEP_T); - #endif -} - -void initInterfaces() { - DEBUG_PRINTLN("Init STA interfaces"); - - if (hueIP[0] == 0) - { - hueIP[0] = WiFi.localIP()[0]; - hueIP[1] = WiFi.localIP()[1]; - hueIP[2] = WiFi.localIP()[2]; - } - - //init Alexa hue emulation - if (alexaEnabled) alexaInit(); - - #ifndef WLED_DISABLE_OTA - if (aOtaEnabled) ArduinoOTA.begin(); - #endif - - strip.service(); - // Set up mDNS responder: - if (strlen(cmDNS) > 0) - { - if (!aOtaEnabled) MDNS.begin(cmDNS); - - DEBUG_PRINTLN("mDNS started"); - MDNS.addService("http", "tcp", 80); - MDNS.addService("wled", "tcp", 80); - MDNS.addServiceTxt("wled", "tcp", "mac", escapedMac.c_str()); - } - server.begin(); - - if (udpPort > 0 && udpPort != ntpLocalPort) - { - udpConnected = notifierUdp.begin(udpPort); - if (udpConnected && udpRgbPort != udpPort) udpRgbConnected = rgbUdp.begin(udpRgbPort); - } - if (ntpEnabled) ntpConnected = ntpUdp.begin(ntpLocalPort); - - initBlynk(blynkApiKey); - e131.begin((e131Multicast) ? E131_MULTICAST : E131_UNICAST , e131Universe, E131_MAX_UNIVERSE_COUNT); - reconnectHue(); - initMqtt(); - interfacesInited = true; - wasConnected = true; -} - -byte stacO = 0; -uint32_t lastHeap; -unsigned long heapTime = 0; - -void handleConnection() { - if (millis() < 2000 && (!WLED_WIFI_CONFIGURED || apBehavior == AP_BEHAVIOR_ALWAYS)) return; - if (lastReconnectAttempt == 0) initConnection(); - - //reconnect WiFi to clear stale allocations if heap gets too low - if (millis() - heapTime > 5000) - { - uint32_t heap = ESP.getFreeHeap(); - if (heap < 9000 && lastHeap < 9000) { - DEBUG_PRINT("Heap too low! "); - DEBUG_PRINTLN(heap); - forceReconnect = true; - } - lastHeap = heap; - heapTime = millis(); - } - - byte stac = 0; - if (apActive) { - #ifdef ESP8266 - stac = wifi_softap_get_station_num(); - #else - wifi_sta_list_t stationList; - esp_wifi_ap_get_sta_list(&stationList); - stac = stationList.num; - #endif - if (stac != stacO) - { - stacO = stac; - DEBUG_PRINT("Connected AP clients: "); - DEBUG_PRINTLN(stac); - if (!WLED_CONNECTED && WLED_WIFI_CONFIGURED) { //trying to connect, but not connected - if (stac) WiFi.disconnect(); //disable search so that AP can work - else initConnection(); //restart search - } - } - } - if (forceReconnect) { - DEBUG_PRINTLN("Forcing reconnect."); - initConnection(); - interfacesInited = false; - forceReconnect = false; - wasConnected = false; - return; - } - if (!WLED_CONNECTED) { - if (interfacesInited) { - DEBUG_PRINTLN("Disconnected!"); - interfacesInited = false; - initConnection(); - } - if (millis() - lastReconnectAttempt > ((stac) ? 300000 : 20000) && WLED_WIFI_CONFIGURED) initConnection(); - if (!apActive && millis() - lastReconnectAttempt > 12000 && (!wasConnected || apBehavior == AP_BEHAVIOR_NO_CONN)) initAP(); - } else if (!interfacesInited) { //newly connected - DEBUG_PRINTLN(""); - DEBUG_PRINT("Connected! IP address: "); - DEBUG_PRINTLN(WiFi.localIP()); - initInterfaces(); - userConnected(); - - //shut down AP - if (apBehavior != AP_BEHAVIOR_ALWAYS && apActive) - { - dnsServer.stop(); - WiFi.softAPdisconnect(true); - apActive = false; - DEBUG_PRINTLN("Access point disabled."); - } - } -} - -//by https://github.com/tzapu/WiFiManager/blob/master/WiFiManager.cpp -int getSignalQuality(int rssi) -{ - int quality = 0; - - if (rssi <= -100) { - quality = 0; - } else if (rssi >= -50) { - quality = 100; - } else { - quality = 2 * (rssi + 100); - } - return quality; -} diff --git a/wled00/wled06_usermod.ino b/wled00/wled06_usermod.cpp similarity index 97% rename from wled00/wled06_usermod.ino rename to wled00/wled06_usermod.cpp index 010d0f386..c780ffeeb 100644 --- a/wled00/wled06_usermod.ino +++ b/wled00/wled06_usermod.cpp @@ -1,3 +1,4 @@ +#include "wled.h" /* * This file allows you to add own functionality to WLED more easily * See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality diff --git a/wled00/wled12_alexa.ino b/wled00/wled_alexa.cpp similarity index 89% rename from wled00/wled12_alexa.ino rename to wled00/wled_alexa.cpp index 5163640e2..b68843cbc 100644 --- a/wled00/wled12_alexa.ino +++ b/wled00/wled_alexa.cpp @@ -1,10 +1,6 @@ -/* - * Alexa Voice On/Off/Brightness Control. Emulates a Philips Hue bridge to Alexa. - * - * This was put together from these two excellent projects: - * https://github.com/kakopappa/arduino-esp8266-alexa-wemo-switch - * https://github.com/probonopd/ESP8266HueEmulator - */ +#include "wled_alexa.h" +#include "wled.h" +#include "src/dependencies/espalexa/EspalexaDevice.h" #ifndef WLED_DISABLE_ALEXA void onAlexaChange(EspalexaDevice* dev); diff --git a/wled00/wled_alexa.h b/wled00/wled_alexa.h new file mode 100644 index 000000000..4bf7bd123 --- /dev/null +++ b/wled00/wled_alexa.h @@ -0,0 +1,16 @@ +#ifndef WLED_ALEXA_H +#define WLED_ALEXA_H +/* + * Alexa Voice On/Off/Brightness Control. Emulates a Philips Hue bridge to Alexa. + * + * This was put together from these two excellent projects: + * https://github.com/kakopappa/arduino-esp8266-alexa-wemo-switch + * https://github.com/probonopd/ESP8266HueEmulator + */ +class EspalexaDevice; +void onAlexaChange(EspalexaDevice* dev); +void alexaInit(); +void handleAlexa(); +void onAlexaChange(EspalexaDevice* dev); + +#define WLED_ALEXA_H \ No newline at end of file diff --git a/wled00/wled16_blynk.ino b/wled00/wled_blynk.cpp similarity index 94% rename from wled00/wled16_blynk.ino rename to wled00/wled_blynk.cpp index ea4d68771..c6754a231 100644 --- a/wled00/wled16_blynk.ino +++ b/wled00/wled_blynk.cpp @@ -1,6 +1,6 @@ -/* - * Remote light control with the free Blynk app - */ +#include "wled_blynk.h" +#include "wled.h" +#include "src/dependencies/blynk/Blynk/BlynkHandlers.h" uint16_t blHue = 0; byte blSat = 255; diff --git a/wled00/wled_blynk.h b/wled00/wled_blynk.h new file mode 100644 index 000000000..e8c8341b5 --- /dev/null +++ b/wled00/wled_blynk.h @@ -0,0 +1,13 @@ +#ifndef WLED_BLYNK_H +#define WLED_BLYNK_H +#include +/* + * Remote light control with the free Blynk app + */ + +void initBlynk(const char* auth); +void handleBlynk(); +void updateBlynk(); +// Unsure if the macro expansions need to accessed through the declaration... TODO + +#endif //WLED_BLYNK_H \ No newline at end of file diff --git a/wled00/wled09_button.ino b/wled00/wled_button.cpp similarity index 98% rename from wled00/wled09_button.ino rename to wled00/wled_button.cpp index 1cd868bec..12fe5b49b 100644 --- a/wled00/wled09_button.ino +++ b/wled00/wled_button.cpp @@ -1,3 +1,5 @@ +#include "wled_button.h" +#include "wled.h" /* * Physical IO */ diff --git a/wled00/wled_button.h b/wled00/wled_button.h new file mode 100644 index 000000000..5f8944773 --- /dev/null +++ b/wled00/wled_button.h @@ -0,0 +1,12 @@ +#ifndef WLED_BUTTON_H +#define WLED_BUTTON_H +#include +/* + * Physical IO + */ + +void shortPressAction(); +void handleButton(); +void handleIO(); + +#endif // WLED_BUTTON_H \ No newline at end of file diff --git a/wled00/wled14_colors.ino b/wled00/wled_colors.cpp similarity index 96% rename from wled00/wled14_colors.ino rename to wled00/wled_colors.cpp index a1db307ae..dcd32e574 100644 --- a/wled00/wled14_colors.ino +++ b/wled00/wled_colors.cpp @@ -1,6 +1,5 @@ -/* - * Color conversion methods - */ +#include "wled_colors.h" +#include "wled.h" void colorFromUint32(uint32_t in, bool secondary) { @@ -18,7 +17,7 @@ void colorFromUint32(uint32_t in, bool secondary) } //load a color without affecting the white channel -void colorFromUint24(uint32_t in, bool secondary = false) +void colorFromUint24(uint32_t in, bool secondary) { if (secondary) { colSec[0] = in >> 16 & 0xFF; @@ -32,7 +31,7 @@ void colorFromUint24(uint32_t in, bool secondary = false) } //relatively change white brightness, minumum A=5 -void relativeChangeWhite(int8_t amount, byte lowerBoundary =0) +void relativeChangeWhite(int8_t amount, byte lowerBoundary) { int16_t new_val = (int16_t) col[3] + amount; if (new_val > 0xFF) new_val = 0xFF; @@ -149,7 +148,8 @@ void colorRGBtoXY(byte* rgb, float* xy) //rgb to coordinates (https://www.develo xy[0] = X / (X + Y + Z); xy[1] = Y / (X + Y + Z); } -#endif +#endif // WLED_DISABLE_HUESYNC + void colorFromDecOrHexString(byte* rgb, char* in) { diff --git a/wled00/wled_colors.h b/wled00/wled_colors.h new file mode 100644 index 000000000..2b3ff32c5 --- /dev/null +++ b/wled00/wled_colors.h @@ -0,0 +1,20 @@ +#ifndef WLED_COLORS_H +#define WLED_COLORS_H +#include +/* + * Color conversion methods + */ + +void colorFromUint32(uint32_t in, bool secondary); +void colorFromUint24(uint32_t in, bool secondary = false); +void relativeChangeWhite(int8_t amount, byte lowerBoundary = 0); +void colorHStoRGB(uint16_t hue, byte sat, byte* rgb); //hue, sat to rgb +void colorCTtoRGB(uint16_t mired, byte* rgb); //white spectrum to rgb + +void colorXYtoRGB(float x, float y, byte* rgb); // only defined if huesync disabled TODO +void colorRGBtoXY(byte* rgb, float* xy); // only defined if huesync disabled TODO + +void colorFromDecOrHexString(byte* rgb, char* in); +void colorRGBtoRGBW(byte* rgb); //rgb to rgbw (http://codewelt.com/rgbw). (RGBW_MODE_LEGACY) + +#endif //WLED_COLORS_H \ No newline at end of file diff --git a/wled00/wled13_cronixie.ino b/wled00/wled_cronixie.cpp similarity index 97% rename from wled00/wled13_cronixie.ino rename to wled00/wled_cronixie.cpp index dd687ca14..107af752a 100644 --- a/wled00/wled13_cronixie.ino +++ b/wled00/wled_cronixie.cpp @@ -1,9 +1,8 @@ -/* - * Support for the Cronixie clock - */ +#include "wled_cronixie.h" +#include "wled.h" + #ifndef WLED_DISABLE_CRONIXIE byte _digitOut[6] = {10,10,10,10,10,10}; -#endif byte getSameCodeLength(char code, int index, char const cronixieDisplay[]) { @@ -23,7 +22,6 @@ byte getSameCodeLength(char code, int index, char const cronixieDisplay[]) void setCronixie() { - #ifndef WLED_DISABLE_CRONIXIE /* * digit purpose index * 0-9 | 0-9 (incl. random) @@ -144,12 +142,10 @@ void setCronixie() DEBUG_PRINTLN((int)dP[5]); _overlayCronixie(); //refresh - #endif } void _overlayCronixie() { - #ifndef WLED_DISABLE_CRONIXIE byte h = hour(local); byte h0 = h; byte m = minute(local); @@ -211,12 +207,10 @@ void _overlayCronixie() } } } - #endif } void _drawOverlayCronixie() { - #ifndef WLED_DISABLE_CRONIXIE byte offsets[] = {5, 0, 6, 1, 7, 2, 8, 3, 9, 4}; for (uint16_t i = 0; i < 6; i++) @@ -239,5 +233,11 @@ void _drawOverlayCronixie() } } } - #endif } + +#else // WLED_DISABLE_CRONIXIE +byte getSameCodeLength(char code, int index, char const cronixieDisplay[]) {} +void setCronixie() {} +void _overlayCronixie() {} +void _drawOverlayCronixie() {} +#endif diff --git a/wled00/wled_cronixie.h b/wled00/wled_cronixie.h new file mode 100644 index 000000000..9dea69b33 --- /dev/null +++ b/wled00/wled_cronixie.h @@ -0,0 +1,13 @@ +#ifndef WLED_CRONIXIE_H +#define WLED_CRONIXIE_H +#include +/* + * Support for the Cronixie clock + */ + +byte getSameCodeLength(char code, int index, char const cronixieDisplay[]); +void setCronixie(); +void _overlayCronixie(); +void _drawOverlayCronixie(); + +#endif // WLED_CRONIXIE_H \ No newline at end of file diff --git a/wled00/wled21_dmx.ino b/wled00/wled_dmx.cpp similarity index 89% rename from wled00/wled21_dmx.ino rename to wled00/wled_dmx.cpp index 4230f07d8..f620f7779 100644 --- a/wled00/wled21_dmx.ino +++ b/wled00/wled_dmx.cpp @@ -1,10 +1,7 @@ -/* - * Support for DMX via MAX485. - * Needs the espdmx library. You might have to change the output pin within the library. Sketchy, i know. - * https://github.com/Rickgg/ESP-Dmx - */ -#ifdef WLED_ENABLE_DMX +#include "wled_dmx.h" +#include "wled.h" +#ifdef WLED_ENABLE_DMX void handleDMX() { // TODO: calculate brightness manually if no shutter channel is set diff --git a/wled00/wled_dmx.h b/wled00/wled_dmx.h new file mode 100644 index 000000000..94cbb0d25 --- /dev/null +++ b/wled00/wled_dmx.h @@ -0,0 +1,11 @@ +#ifndef WLED_DMX_H +#define WLED_DMX_H +/* + * Support for DMX via MAX485. + * Needs the espdmx library. You might have to change the output pin within the library. Sketchy, i know. + * https://github.com/Rickgg/ESP-Dmx + */ + +void handleDMX(); + +#endif //WLED_DMX_H \ No newline at end of file diff --git a/wled00/wled01_eeprom.ino b/wled00/wled_eeprom.cpp similarity index 99% rename from wled00/wled01_eeprom.ino rename to wled00/wled_eeprom.cpp index 1f0e8426f..0ddbf57fd 100644 --- a/wled00/wled01_eeprom.ino +++ b/wled00/wled_eeprom.cpp @@ -1,7 +1,5 @@ -/* - * Methods to handle saving and loading to non-volatile memory - * EEPROM Map: https://github.com/Aircoookie/WLED/wiki/EEPROM-Map - */ +#include "wled_eeprom.h" +#include "wled.h" #define EEPSIZE 2560 //Maximum is 4096 diff --git a/wled00/wled_eeprom.h b/wled00/wled_eeprom.h new file mode 100644 index 000000000..fe5c2cbfe --- /dev/null +++ b/wled00/wled_eeprom.h @@ -0,0 +1,22 @@ +#ifndef WLED_EPPROM_H +#define WLED_EPPROM_H +#include +/* + * Methods to handle saving and loading to non-volatile memory + * EEPROM Map: https://github.com/Aircoookie/WLED/wiki/EEPROM-Map + */ + +void commit(); +void clearEEPROM(); +void writeStringToEEPROM(uint16_t pos, char* str, uint16_t len); +void readStringFromEEPROM(uint16_t pos, char* str, uint16_t len); +void saveSettingsToEEPROM(); +void loadSettingsFromEEPROM(bool first); +void savedToPresets(); +bool applyPreset(byte index, bool loadBri = true); +void savePreset(byte index, bool persist = true); +void loadMacro(byte index, char* m); +void applyMacro(byte index); +void saveMacro(byte index, String mc, bool persist = true); //only commit on single save, not in settings + +#endif //WLED_EPPROM_H diff --git a/wled00/wled04_file.ino b/wled00/wled_file.cpp similarity index 95% rename from wled00/wled04_file.ino rename to wled00/wled_file.cpp index de7523281..700d66eea 100644 --- a/wled00/wled04_file.ino +++ b/wled00/wled_file.cpp @@ -1,6 +1,14 @@ -/* - * Utility for SPIFFS filesystem & Serial console - */ +#include "wled_file.h" +#include "wled.h" +//filesystem +#ifndef WLED_DISABLE_FILESYSTEM +#include +#ifdef ARDUINO_ARCH_ESP32 +#include "SPIFFS.h" +#endif +#include "SPIFFSEditor.h" +#endif + enum class AdaState { Header_A, Header_d, diff --git a/wled00/wled_file.h b/wled00/wled_file.h new file mode 100644 index 000000000..7457ec4cd --- /dev/null +++ b/wled00/wled_file.h @@ -0,0 +1,12 @@ +#ifndef WLED_FILE_H +#define WLED_FILE_H +#include +#include +/* + * Utility for SPIFFS filesystem & Serial console + */ + +void handleSerial(); +bool handleFileRead(AsyncWebServerRequest*, String path); + +#endif // WLED_FILE_H \ No newline at end of file diff --git a/wled00/wled15_hue.ino b/wled00/wled_hue.cpp similarity index 99% rename from wled00/wled15_hue.ino rename to wled00/wled_hue.cpp index 5f6b8f5bf..082685d70 100644 --- a/wled00/wled15_hue.ino +++ b/wled00/wled_hue.cpp @@ -1,7 +1,8 @@ -/* - * Sync to Philips hue lights - */ +#include "wled_hue.h" +#include "wled.h" + #ifndef WLED_DISABLE_HUESYNC + void handleHue() { if (hueReceived) diff --git a/wled00/wled_hue.h b/wled00/wled_hue.h new file mode 100644 index 000000000..dcf397290 --- /dev/null +++ b/wled00/wled_hue.h @@ -0,0 +1,10 @@ +#ifndef WLED_HUE_H +#define WLED_HUE_H +/* + * Sync to Philips hue lights + */ + +void handleHue(); +void reconnectHue(); + +#endif //WLED_HUE_H \ No newline at end of file diff --git a/wled00/wled20_ir.ino b/wled00/wled_ir.cpp similarity index 99% rename from wled00/wled20_ir.ino rename to wled00/wled_ir.cpp index 64c131c21..53f0d23f9 100644 --- a/wled00/wled20_ir.ino +++ b/wled00/wled_ir.cpp @@ -1,6 +1,5 @@ -/* - * Infrared sensor support for generic 24/40/44 key RGB remotes - */ +#include "wled_ir.h" +#include "wled.h" #if defined(WLED_DISABLE_INFRARED) void handleIR(){} diff --git a/wled00/wled_ir.h b/wled00/wled_ir.h new file mode 100644 index 000000000..3479f410b --- /dev/null +++ b/wled00/wled_ir.h @@ -0,0 +1,9 @@ +#ifndef WLED_IR_H +#define WLED_IR_H +/* + * Infrared sensor support for generic 24/40/44 key RGB remotes + */ + +void handleIR(); + +#endif //WLED_IR_H \ No newline at end of file diff --git a/wled00/wled19_json.ino b/wled00/wled_json.cpp similarity index 99% rename from wled00/wled19_json.ino rename to wled00/wled_json.cpp index df9bceadc..43d3136dd 100644 --- a/wled00/wled19_json.ino +++ b/wled00/wled_json.cpp @@ -1,6 +1,5 @@ -/* - * JSON API (De)serialization - */ +#include "wled_json.h" +#include "wled.h" void deserializeSegment(JsonObject elem, byte it) { diff --git a/wled00/wled_json.h b/wled00/wled_json.h new file mode 100644 index 000000000..83830fafa --- /dev/null +++ b/wled00/wled_json.h @@ -0,0 +1,15 @@ +#ifndef WLED_JSON_H +#define WLED_JSON_H +/* + * JSON API (De)serialization + */ + +void deserializeSegment(JsonObject elem, byte it); +bool deserializeState(JsonObject root); +void serializeSegment(JsonObject& root, WS2812FX::Segment& seg, byte id); +void serializeState(JsonObject root); +void serializeInfo(JsonObject root); +void serveJson(AsyncWebServerRequest* request); +void serveLiveLeds(AsyncWebServerRequest* request); + +#endif //WLED_JSON_H \ No newline at end of file diff --git a/wled00/wled08_led.ino b/wled00/wled_led.cpp similarity index 98% rename from wled00/wled08_led.ino rename to wled00/wled_led.cpp index 63009f983..aefb004a9 100644 --- a/wled00/wled08_led.ino +++ b/wled00/wled_led.cpp @@ -1,6 +1,6 @@ -/* - * LED methods - */ +#include "wled_led.h" +#include "wled.h" + void setValuesFromMainSeg() { WS2812FX::Segment& seg = strip.getSegment(strip.getMainSegmentId()); @@ -50,7 +50,7 @@ void setAllLeds() { } -void setLedsStandard(bool justColors = false) +void setLedsStandard(bool justColors) { for (byte i=0; i<4; i++) { diff --git a/wled00/wled_led.h b/wled00/wled_led.h new file mode 100644 index 000000000..aebe95e48 --- /dev/null +++ b/wled00/wled_led.h @@ -0,0 +1,19 @@ +#ifndef WLED_LED_H +#define WLED_LED_H +#include +/* + * LED methods + */ + +void setValuesFromMainSeg(); +void resetTimebase(); +void toggleOnOff(); +void setAllLeds(); +void setLedsStandard(bool justColors = false); +bool colorChanged(); +void colorUpdated(int callMode); +void updateInterfaces(uint8_t callMode); +void handleTransitions(); +void handleNightlight(); + +#endif \ No newline at end of file diff --git a/wled00/wled17_mqtt.ino b/wled00/wled_mqtt.cpp similarity index 98% rename from wled00/wled17_mqtt.ino rename to wled00/wled_mqtt.cpp index 741c807f8..d4fc80c06 100644 --- a/wled00/wled17_mqtt.ino +++ b/wled00/wled_mqtt.cpp @@ -1,6 +1,5 @@ -/* - * MQTT communication protocol for home automation - */ +#include "wled_mqtt.h" +#include "wled.h" #ifdef WLED_ENABLE_MQTT #define MQTT_KEEP_ALIVE_TIME 60 // contact the MQTT broker every 60 seconds diff --git a/wled00/wled_mqtt.h b/wled00/wled_mqtt.h new file mode 100644 index 000000000..f1c7160e5 --- /dev/null +++ b/wled00/wled_mqtt.h @@ -0,0 +1,9 @@ +#ifndef WLED_MQTT_H +#define WLED_MQTT_H +/* + * MQTT communication protocol for home automation + */ +bool initMqtt(); +void publishMqtt(); + +#endif //WLED_MQTT_H \ No newline at end of file diff --git a/wled00/wled07_notify.ino b/wled00/wled_notify.cpp similarity index 99% rename from wled00/wled07_notify.ino rename to wled00/wled_notify.cpp index 8fcbdbe82..7827aba96 100644 --- a/wled00/wled07_notify.ino +++ b/wled00/wled_notify.cpp @@ -1,6 +1,6 @@ -/* - * UDP notifier - */ +#include "wled_notify.h" +#include "wled.h" +#include "src/dependencies/e131/ESPAsyncE131.h" #define WLEDPACKETSIZE 29 #define UDP_IN_MAXSIZE 1472 diff --git a/wled00/wled_notify.h b/wled00/wled_notify.h new file mode 100644 index 000000000..63d01142b --- /dev/null +++ b/wled00/wled_notify.h @@ -0,0 +1,17 @@ +#ifndef WLED_NOTIFY_H +#define WLED_NOTIFY_H +#include +#include "const.h" +/* + * UDP notifier + */ +union e131_packet_t; // Will this compile? +class IPAddress; + +void notify(byte callMode, bool followUp=false); +void arlsLock(uint32_t timeoutMs, byte md = REALTIME_MODE_GENERIC); +void handleE131Packet(e131_packet_t* p, IPAddress clientIP); +void handleNotifications(); +void setRealtimePixel(uint16_t i, byte r, byte g, byte b, byte w); + +#endif // WLED_NOTIFY_H \ No newline at end of file diff --git a/wled00/wled10_ntp.ino b/wled00/wled_ntp.cpp similarity index 58% rename from wled00/wled10_ntp.ino rename to wled00/wled_ntp.cpp index 2c5a64c45..82b99f963 100644 --- a/wled00/wled10_ntp.ino +++ b/wled00/wled_ntp.cpp @@ -1,63 +1,7 @@ -/* - * Acquires time from NTP server - */ +#include "wled_ntp.h" +#include "wled.h" +#include "wled_eeprom.h" -TimeChangeRule UTCr = {Last, Sun, Mar, 1, 0}; // UTC -Timezone tzUTC(UTCr, UTCr); - -TimeChangeRule BST = {Last, Sun, Mar, 1, 60}; // British Summer Time -TimeChangeRule GMT = {Last, Sun, Oct, 2, 0}; // Standard Time -Timezone tzUK(BST, GMT); - -TimeChangeRule CEST = {Last, Sun, Mar, 2, 120}; //Central European Summer Time -TimeChangeRule CET = {Last, Sun, Oct, 3, 60}; //Central European Standard Time -Timezone tzEUCentral(CEST, CET); - -TimeChangeRule EEST = {Last, Sun, Mar, 3, 180}; //Central European Summer Time -TimeChangeRule EET = {Last, Sun, Oct, 4, 120}; //Central European Standard Time -Timezone tzEUEastern(EEST, EET); - -TimeChangeRule EDT = {Second, Sun, Mar, 2, -240 }; //Daylight time = UTC - 4 hours -TimeChangeRule EST = {First, Sun, Nov, 2, -300 }; //Standard time = UTC - 5 hours -Timezone tzUSEastern(EDT, EST); - -TimeChangeRule CDT = {Second, Sun, Mar, 2, -300 }; //Daylight time = UTC - 5 hours -TimeChangeRule CST = {First, Sun, Nov, 2, -360 }; //Standard time = UTC - 6 hours -Timezone tzUSCentral(CDT, CST); - -Timezone tzCASaskatchewan(CST, CST); //Central without DST - -TimeChangeRule MDT = {Second, Sun, Mar, 2, -360 }; //Daylight time = UTC - 6 hours -TimeChangeRule MST = {First, Sun, Nov, 2, -420 }; //Standard time = UTC - 7 hours -Timezone tzUSMountain(MDT, MST); - -Timezone tzUSArizona(MST, MST); //Mountain without DST - -TimeChangeRule PDT = {Second, Sun, Mar, 2, -420 }; //Daylight time = UTC - 7 hours -TimeChangeRule PST = {First, Sun, Nov, 2, -480 }; //Standard time = UTC - 8 hours -Timezone tzUSPacific(PDT, PST); - -TimeChangeRule ChST = {Last, Sun, Mar, 1, 480}; // China Standard Time = UTC + 8 hours -Timezone tzChina(ChST, ChST); - -TimeChangeRule JST = {Last, Sun, Mar, 1, 540}; // Japan Standard Time = UTC + 9 hours -Timezone tzJapan(JST, JST); - -TimeChangeRule AEDT = {Second, Sun, Oct, 2, 660 }; //Daylight time = UTC + 11 hours -TimeChangeRule AEST = {First, Sun, Apr, 3, 600 }; //Standard time = UTC + 10 hours -Timezone tzAUEastern(AEDT, AEST); - -TimeChangeRule NZDT = {Second, Sun, Sep, 2, 780 }; //Daylight time = UTC + 13 hours -TimeChangeRule NZST = {First, Sun, Apr, 3, 720 }; //Standard time = UTC + 12 hours -Timezone tzNZ(NZDT, NZST); - -TimeChangeRule NKST = {Last, Sun, Mar, 1, 510}; //Pyongyang Time = UTC + 8.5 hours -Timezone tzNK(NKST, NKST); - -TimeChangeRule IST = {Last, Sun, Mar, 1, 330}; // India Standard Time = UTC + 5.5 hours -Timezone tzIndia(IST, IST); - -Timezone* timezones[] = {&tzUTC, &tzUK, &tzEUCentral, &tzEUEastern, &tzUSEastern, &tzUSCentral, &tzUSMountain, &tzUSArizona, &tzUSPacific, &tzChina, &tzJapan, &tzAUEastern, &tzNZ, &tzNK, &tzIndia, &tzCASaskatchewan}; void handleNetworkTime() { diff --git a/wled00/wled_ntp.h b/wled00/wled_ntp.h new file mode 100644 index 000000000..142933421 --- /dev/null +++ b/wled00/wled_ntp.h @@ -0,0 +1,76 @@ +#ifndef WLED_NTP_H +#define WLED_NTP_H +#include +#include "timezone/Timezone.h" + +/* + * Acquires time from NTP server + */ +TimeChangeRule UTCr = {Last, Sun, Mar, 1, 0}; // UTC +Timezone tzUTC(UTCr, UTCr); + +TimeChangeRule BST = {Last, Sun, Mar, 1, 60}; // British Summer Time +TimeChangeRule GMT = {Last, Sun, Oct, 2, 0}; // Standard Time +Timezone tzUK(BST, GMT); + +TimeChangeRule CEST = {Last, Sun, Mar, 2, 120}; //Central European Summer Time +TimeChangeRule CET = {Last, Sun, Oct, 3, 60}; //Central European Standard Time +Timezone tzEUCentral(CEST, CET); + +TimeChangeRule EEST = {Last, Sun, Mar, 3, 180}; //Central European Summer Time +TimeChangeRule EET = {Last, Sun, Oct, 4, 120}; //Central European Standard Time +Timezone tzEUEastern(EEST, EET); + +TimeChangeRule EDT = {Second, Sun, Mar, 2, -240 }; //Daylight time = UTC - 4 hours +TimeChangeRule EST = {First, Sun, Nov, 2, -300 }; //Standard time = UTC - 5 hours +Timezone tzUSEastern(EDT, EST); + +TimeChangeRule CDT = {Second, Sun, Mar, 2, -300 }; //Daylight time = UTC - 5 hours +TimeChangeRule CST = {First, Sun, Nov, 2, -360 }; //Standard time = UTC - 6 hours +Timezone tzUSCentral(CDT, CST); + +Timezone tzCASaskatchewan(CST, CST); //Central without DST + +TimeChangeRule MDT = {Second, Sun, Mar, 2, -360 }; //Daylight time = UTC - 6 hours +TimeChangeRule MST = {First, Sun, Nov, 2, -420 }; //Standard time = UTC - 7 hours +Timezone tzUSMountain(MDT, MST); + +Timezone tzUSArizona(MST, MST); //Mountain without DST + +TimeChangeRule PDT = {Second, Sun, Mar, 2, -420 }; //Daylight time = UTC - 7 hours +TimeChangeRule PST = {First, Sun, Nov, 2, -480 }; //Standard time = UTC - 8 hours +Timezone tzUSPacific(PDT, PST); + +TimeChangeRule ChST = {Last, Sun, Mar, 1, 480}; // China Standard Time = UTC + 8 hours +Timezone tzChina(ChST, ChST); + +TimeChangeRule JST = {Last, Sun, Mar, 1, 540}; // Japan Standard Time = UTC + 9 hours +Timezone tzJapan(JST, JST); + +TimeChangeRule AEDT = {Second, Sun, Oct, 2, 660 }; //Daylight time = UTC + 11 hours +TimeChangeRule AEST = {First, Sun, Apr, 3, 600 }; //Standard time = UTC + 10 hours +Timezone tzAUEastern(AEDT, AEST); + +TimeChangeRule NZDT = {Second, Sun, Sep, 2, 780 }; //Daylight time = UTC + 13 hours +TimeChangeRule NZST = {First, Sun, Apr, 3, 720 }; //Standard time = UTC + 12 hours +Timezone tzNZ(NZDT, NZST); + +TimeChangeRule NKST = {Last, Sun, Mar, 1, 510}; //Pyongyang Time = UTC + 8.5 hours +Timezone tzNK(NKST, NKST); + +TimeChangeRule IST = {Last, Sun, Mar, 1, 330}; // India Standard Time = UTC + 5.5 hours +Timezone tzIndia(IST, IST); + +Timezone* timezones[] = {&tzUTC, &tzUK, &tzEUCentral, &tzEUEastern, &tzUSEastern, &tzUSCentral, &tzUSMountain, &tzUSArizona, &tzUSPacific, &tzChina, &tzJapan, &tzAUEastern, &tzNZ, &tzNK, &tzIndia, &tzCASaskatchewan}; + +void handleNetworkTime(); +void sendNTPPacket(); +bool checkNTPResponse(); +void updateLocalTime(); +void getTimeString(char* out); +bool checkCountdown(); +void setCountdown(); +byte weekdayMondayFirst(); +void checkTimers(); + +#endif // WLED_NTP_H \ No newline at end of file diff --git a/wled00/wled11_ol.ino b/wled00/wled_overlay.cpp similarity index 98% rename from wled00/wled11_ol.ino rename to wled00/wled_overlay.cpp index 93f7b96df..93348e79b 100644 --- a/wled00/wled11_ol.ino +++ b/wled00/wled_overlay.cpp @@ -1,6 +1,5 @@ -/* - * Used to draw clock overlays over the strip - */ +#include "wled_overlay.h" +#include "wled.h" void initCronixie() { diff --git a/wled00/wled_overlay.h b/wled00/wled_overlay.h new file mode 100644 index 000000000..47573978d --- /dev/null +++ b/wled00/wled_overlay.h @@ -0,0 +1,12 @@ +#ifndef WLED_OVERLAYS_H +#define WLED_OVERLAYS_H +#include +/* + * Used to draw clock overlays over the strip + */ + +void initCronixie(); +void handleOverlays(); +void handleOverlayDraw(); + +#endif // WLED_OVERLAY_H \ No newline at end of file diff --git a/wled00/wled18_server.ino b/wled00/wled_server.cpp similarity index 99% rename from wled00/wled18_server.ino rename to wled00/wled_server.cpp index f11a64bec..c73e027f1 100644 --- a/wled00/wled18_server.ino +++ b/wled00/wled_server.cpp @@ -1,6 +1,5 @@ -/* - * Server page definitions - */ +#include "wled_server.h" +#include "wled.h" //Is this an IP? bool isIp(String str) { @@ -303,7 +302,7 @@ String msgProcessor(const String& var) } -void serveMessage(AsyncWebServerRequest* request, uint16_t code, String headl, String subl="", byte optionT=255) +void serveMessage(AsyncWebServerRequest* request, uint16_t code, String headl, String subl, byte optionT) { messageHead = headl; messageSub = subl; diff --git a/wled00/wled_server.h b/wled00/wled_server.h new file mode 100644 index 000000000..08679e179 --- /dev/null +++ b/wled00/wled_server.h @@ -0,0 +1,18 @@ +#ifndef WLED_SERVER_H +#define WLED_SERVER_H +/* + * Server page declarations + */ + +bool isIp(String str); +bool captivePortal(AsyncWebServerRequest *request); +void initServer(); +void serveIndexOrWelcome(AsyncWebServerRequest *request); +void serveIndex(AsyncWebServerRequest* request); +String msgProcessor(const String& var); +void serveMessage(AsyncWebServerRequest* request, uint16_t code, String headl, String subl="", byte optionT=255); +String settingsProcessor(const String& var); +String dmxProcessor(const String& var); +void serveSettings(AsyncWebServerRequest* request); + +#endif //WLED_SERVER_H \ No newline at end of file diff --git a/wled00/wled03_set.ino b/wled00/wled_set.cpp similarity index 99% rename from wled00/wled03_set.ino rename to wled00/wled_set.cpp index fc82256d2..d81cac916 100644 --- a/wled00/wled03_set.ino +++ b/wled00/wled_set.cpp @@ -1,3 +1,7 @@ +#include "wled_set.h" +#include "wled.h" +#include "wled_hue.h" +#include "wled_colors.h" /* * Receives client input */ diff --git a/wled00/wled_set.h b/wled00/wled_set.h new file mode 100644 index 000000000..7efde0d75 --- /dev/null +++ b/wled00/wled_set.h @@ -0,0 +1,12 @@ +#ifndef WLED_SET_H +#define WLED_SET_H +#include +#include + +void _setRandomColor(bool _sec,bool fromButton=false); +bool isAsterisksOnly(const char* str, byte maxLen); +void handleSettingsSet(AsyncWebServerRequest *request, byte subPage); +int getNumVal(const String* req, uint16_t pos); +bool updateVal(const String* req, const char* key, byte* val, byte minv=0, byte maxv=255); + +#endif // WLED_SET_H \ No newline at end of file diff --git a/wled00/wled02_xml.ino b/wled00/wled_xml.cpp similarity index 99% rename from wled00/wled02_xml.ino rename to wled00/wled_xml.cpp index 100be4822..07009cf3c 100644 --- a/wled00/wled02_xml.ino +++ b/wled00/wled_xml.cpp @@ -1,6 +1,7 @@ -/* - * Sending XML status files to client - */ +#include "wled_xml.h" +#include "wled.h" +#include "wled_eeprom.h" + //build XML response to HTTP /win API request char* XML_response(AsyncWebServerRequest *request, char* dest = nullptr) diff --git a/wled00/wled_xml.h b/wled00/wled_xml.h new file mode 100644 index 000000000..5cfb1dbb7 --- /dev/null +++ b/wled00/wled_xml.h @@ -0,0 +1,15 @@ +#ifndef WLED_XML_H +#define WLED_XML_H +#include +#include + +/* + * Sending XML status files to client + */ +char* XML_response(AsyncWebServerRequest *request, char* dest = nullptr); +char* URL_response(AsyncWebServerRequest *request); +void sappend(char stype, const char* key, int val); +void sappends(char stype, const char* key, char* val); +void getSettingsJS(byte subPage, char* dest); + +#endif // WLED_XML_H \ No newline at end of file From 12131764d1e7ce305063021b6b4f581593eabfb3 Mon Sep 17 00:00:00 2001 From: Travis J Dean Date: Wed, 25 Mar 2020 04:36:55 -0400 Subject: [PATCH 53/90] Prior to refactoring includes and forward definitions. --- .../src/dependencies/espalexa/EspalexaDevice.h | 2 +- wled00/wled.cpp | 18 ++++++++++++++++-- wled00/wled.h | 5 +---- wled00/wled00.ino | 6 +++--- wled00/wled_alexa.cpp | 1 + wled00/wled_alexa.h | 5 +++-- wled00/wled_eeprom.cpp | 5 ++++- wled00/wled_eeprom.h | 1 + wled00/wled_file.cpp | 4 ++++ wled00/wled_server.cpp | 5 +++++ wled00/wled_server.h | 1 + wled00/wled_set.cpp | 11 +++++++---- wled00/wled_set.h | 4 ++++ .../{wled06_usermod.cpp => wled_usermod.cpp} | 0 wled00/wled_usermod.h | 8 ++++++++ 15 files changed, 59 insertions(+), 17 deletions(-) rename wled00/{wled06_usermod.cpp => wled_usermod.cpp} (100%) create mode 100644 wled00/wled_usermod.h diff --git a/wled00/src/dependencies/espalexa/EspalexaDevice.h b/wled00/src/dependencies/espalexa/EspalexaDevice.h index 4785591fe..b964e0e32 100644 --- a/wled00/src/dependencies/espalexa/EspalexaDevice.h +++ b/wled00/src/dependencies/espalexa/EspalexaDevice.h @@ -1,6 +1,6 @@ #ifndef EspalexaDevice_h #define EspalexaDevice_h - +#include #include "Arduino.h" typedef class EspalexaDevice; diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 50a83f27e..97ed38787 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -1,4 +1,18 @@ #include "wled.h" +#include "wled_led.h" +#include "wled_ir.h" +#include "wled_notify.h" +#include "wled_alexa.h" +#include "wled_overlay.h" +#include "wled_file.h" +#include "wled_button.h" +#include "wled_ntp.h" +#include "wled_usermod.h" +#include "wled_blynk.h" +#include "wled_hue.h" +#include "wled_mqtt.h" +#include "wled_eeprom.h" +#include "wled_server.h" WLED::WLED() { @@ -251,7 +265,7 @@ void WLED::beginStrip() #endif } -void WLED::initAP(bool resetAP = false) +void WLED::initAP(bool resetAP) { if (apBehavior == AP_BEHAVIOR_BUTTON_ONLY && !resetAP) return; @@ -483,7 +497,7 @@ void WLED::handleConnection() } //by https://github.com/tzapu/WiFiManager/blob/master/WiFiManager.cpp -int WLED::getSignalQuality(int rssi) +int getSignalQuality(int rssi) { int quality = 0; diff --git a/wled00/wled.h b/wled00/wled.h index 4f9b79afd..8ef30cbfe 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -491,6 +491,7 @@ int loops = 0; bool oappend(const char *txt); //append new number to temp buffer efficiently bool oappendi(int i); +int getSignalQuality(int rssi); class WLED { @@ -503,8 +504,6 @@ public: WLED(); - void wledInit(); - void reset(); void loop(); @@ -515,8 +514,6 @@ public: wledInit(); } - void loop(); - private: void wledInit(); void beginStrip(); diff --git a/wled00/wled00.ino b/wled00/wled00.ino index fc06f2114..ac993bf62 100644 --- a/wled00/wled00.ino +++ b/wled00/wled00.ino @@ -3,11 +3,11 @@ */ #include "wled.h" +WLED wled; void setup() { - //auto& wled = Wled(); + wled.instance(); // Force creation of static instance } void loop() { - - + wled.loop(); } \ No newline at end of file diff --git a/wled00/wled_alexa.cpp b/wled00/wled_alexa.cpp index b68843cbc..ce63a0192 100644 --- a/wled00/wled_alexa.cpp +++ b/wled00/wled_alexa.cpp @@ -1,6 +1,7 @@ #include "wled_alexa.h" #include "wled.h" #include "src/dependencies/espalexa/EspalexaDevice.h" +#include "const.h" #ifndef WLED_DISABLE_ALEXA void onAlexaChange(EspalexaDevice* dev); diff --git a/wled00/wled_alexa.h b/wled00/wled_alexa.h index 4bf7bd123..94ad0342b 100644 --- a/wled00/wled_alexa.h +++ b/wled00/wled_alexa.h @@ -7,10 +7,11 @@ * https://github.com/kakopappa/arduino-esp8266-alexa-wemo-switch * https://github.com/probonopd/ESP8266HueEmulator */ -class EspalexaDevice; +#include "src/dependencies/espalexa/EspalexaDevice.h" + void onAlexaChange(EspalexaDevice* dev); void alexaInit(); void handleAlexa(); void onAlexaChange(EspalexaDevice* dev); -#define WLED_ALEXA_H \ No newline at end of file +#endif // WLED_ALEXA_H \ No newline at end of file diff --git a/wled00/wled_eeprom.cpp b/wled00/wled_eeprom.cpp index 0ddbf57fd..1e2b0e620 100644 --- a/wled00/wled_eeprom.cpp +++ b/wled00/wled_eeprom.cpp @@ -1,7 +1,10 @@ #include "wled_eeprom.h" #include "wled.h" +#include "wled_cronixie.h" +#include "wled_ntp.h" +#include "wled_set.h" +#include "wled_led.h" -#define EEPSIZE 2560 //Maximum is 4096 //eeprom Version code, enables default settings instead of 0 init on update #define EEPVER 18 diff --git a/wled00/wled_eeprom.h b/wled00/wled_eeprom.h index fe5c2cbfe..19f61925a 100644 --- a/wled00/wled_eeprom.h +++ b/wled00/wled_eeprom.h @@ -5,6 +5,7 @@ * Methods to handle saving and loading to non-volatile memory * EEPROM Map: https://github.com/Aircoookie/WLED/wiki/EEPROM-Map */ +#define EEPSIZE 2560 //Maximum is 4096 void commit(); void clearEEPROM(); diff --git a/wled00/wled_file.cpp b/wled00/wled_file.cpp index 700d66eea..81b86d6d3 100644 --- a/wled00/wled_file.cpp +++ b/wled00/wled_file.cpp @@ -1,5 +1,8 @@ #include "wled_file.h" #include "wled.h" +#include "wled_led.h" +#include "wled_notify.h" + //filesystem #ifndef WLED_DISABLE_FILESYSTEM #include @@ -21,6 +24,7 @@ enum class AdaState { Data_Blue }; +// Maybe Adalight should not be in filehandling? TODO void handleSerial() { #ifdef WLED_ENABLE_ADALIGHT diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index c73e027f1..542abea4c 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -1,5 +1,10 @@ #include "wled_server.h" #include "wled.h" +#include "wled_file.h" +#include "wled_set.h" +#include "wled_json.h" +#include "wled_xml.h" + //Is this an IP? bool isIp(String str) { diff --git a/wled00/wled_server.h b/wled00/wled_server.h index 08679e179..a03f83e1a 100644 --- a/wled00/wled_server.h +++ b/wled00/wled_server.h @@ -1,5 +1,6 @@ #ifndef WLED_SERVER_H #define WLED_SERVER_H +#include /* * Server page declarations */ diff --git a/wled00/wled_set.cpp b/wled00/wled_set.cpp index d81cac916..aff3f0cda 100644 --- a/wled00/wled_set.cpp +++ b/wled00/wled_set.cpp @@ -1,10 +1,13 @@ #include "wled_set.h" #include "wled.h" -#include "wled_hue.h" #include "wled_colors.h" -/* - * Receives client input - */ +#include "wled_hue.h" +#include "wled_led.h" +#include "wled_blynk.h" +#include "wled_eeprom.h" +#include "wled_alexa.h" +#include "wled_cronixie.h" +#include "wled_xml.h" void _setRandomColor(bool _sec,bool fromButton=false) { diff --git a/wled00/wled_set.h b/wled00/wled_set.h index 7efde0d75..ebee8c928 100644 --- a/wled00/wled_set.h +++ b/wled00/wled_set.h @@ -2,10 +2,14 @@ #define WLED_SET_H #include #include +/* + * Receives client input + */ void _setRandomColor(bool _sec,bool fromButton=false); bool isAsterisksOnly(const char* str, byte maxLen); void handleSettingsSet(AsyncWebServerRequest *request, byte subPage); +bool handleSet(AsyncWebServerRequest *request, const String& req); int getNumVal(const String* req, uint16_t pos); bool updateVal(const String* req, const char* key, byte* val, byte minv=0, byte maxv=255); diff --git a/wled00/wled06_usermod.cpp b/wled00/wled_usermod.cpp similarity index 100% rename from wled00/wled06_usermod.cpp rename to wled00/wled_usermod.cpp diff --git a/wled00/wled_usermod.h b/wled00/wled_usermod.h new file mode 100644 index 000000000..61ef6b785 --- /dev/null +++ b/wled00/wled_usermod.h @@ -0,0 +1,8 @@ +#ifndef WLED_USERMOD_H +#define WLED_USERMOD_H + +void userSetup(); +void userConnected(); +void userLoop(); + +#endif // WLED_USERMOD_H \ No newline at end of file From 6f5e71164ae2174b47354c489b0ef0dda9fd66bd Mon Sep 17 00:00:00 2001 From: Travis J Dean Date: Wed, 25 Mar 2020 05:14:23 -0400 Subject: [PATCH 54/90] Further fixing of includes. --- wled00/wled.h | 2 +- wled00/wled_alexa.cpp | 4 +++- wled00/wled_button.cpp | 6 +++++- wled00/wled_eeprom.cpp | 6 +++--- wled00/wled_hue.cpp | 2 ++ wled00/wled_hue.h | 6 ++++++ wled00/wled_json.cpp | 2 ++ wled00/wled_json.h | 6 ++++++ wled00/wled_led.cpp | 5 +++++ wled00/wled_mqtt.cpp | 2 ++ wled00/wled_notify.cpp | 5 +++-- wled00/wled_notify.h | 2 +- 12 files changed, 39 insertions(+), 9 deletions(-) diff --git a/wled00/wled.h b/wled00/wled.h index 8ef30cbfe..c04dfa87b 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -514,7 +514,7 @@ public: wledInit(); } -private: +public: // TODO: privacy void wledInit(); void beginStrip(); diff --git a/wled00/wled_alexa.cpp b/wled00/wled_alexa.cpp index ce63a0192..1eb33e71d 100644 --- a/wled00/wled_alexa.cpp +++ b/wled00/wled_alexa.cpp @@ -1,7 +1,9 @@ #include "wled_alexa.h" #include "wled.h" -#include "src/dependencies/espalexa/EspalexaDevice.h" #include "const.h" +#include "wled_led.h" +#include "wled_eeprom.h" +#include "wled_colors.h" #ifndef WLED_DISABLE_ALEXA void onAlexaChange(EspalexaDevice* dev); diff --git a/wled00/wled_button.cpp b/wled00/wled_button.cpp index 12fe5b49b..3825cc846 100644 --- a/wled00/wled_button.cpp +++ b/wled00/wled_button.cpp @@ -1,5 +1,9 @@ #include "wled_button.h" #include "wled.h" +#include "wled_led.h" +#include "wled_eeprom.h" +#include "wled_set.h" + /* * Physical IO */ @@ -46,7 +50,7 @@ void handleButton() if (dur > 6000) //long press { - initAP(true); + WLED::instance().initAP(true); } else if (!buttonLongPressed) { //short press if (macroDoublePress) diff --git a/wled00/wled_eeprom.cpp b/wled00/wled_eeprom.cpp index 1e2b0e620..04b86289a 100644 --- a/wled00/wled_eeprom.cpp +++ b/wled00/wled_eeprom.cpp @@ -592,7 +592,7 @@ void savedToPresets() } } -bool applyPreset(byte index, bool loadBri = true) +bool applyPreset(byte index, bool loadBri) { if (index == 255 || index == 0) { @@ -630,7 +630,7 @@ bool applyPreset(byte index, bool loadBri = true) return true; } -void savePreset(byte index, bool persist = true) +void savePreset(byte index, bool persist) { if (index > 16) return; if (index < 1) {saveSettingsToEEPROM();return;} @@ -698,7 +698,7 @@ void applyMacro(byte index) } -void saveMacro(byte index, String mc, bool persist = true) //only commit on single save, not in settings +void saveMacro(byte index, String mc, bool persist) //only commit on single save, not in settings { index-=1; if (index > 15) return; diff --git a/wled00/wled_hue.cpp b/wled00/wled_hue.cpp index 082685d70..abdb4ffa7 100644 --- a/wled00/wled_hue.cpp +++ b/wled00/wled_hue.cpp @@ -1,5 +1,7 @@ #include "wled_hue.h" #include "wled.h" +#include "wled_colors.h" +#include "wled_eeprom.h" #ifndef WLED_DISABLE_HUESYNC diff --git a/wled00/wled_hue.h b/wled00/wled_hue.h index dcf397290..e9aa22b2a 100644 --- a/wled00/wled_hue.h +++ b/wled00/wled_hue.h @@ -3,8 +3,14 @@ /* * Sync to Philips hue lights */ +#include +#include void handleHue(); void reconnectHue(); +void onHueError(void* arg, AsyncClient* client, int8_t error); +void onHueConnect(void* arg, AsyncClient* client); +void sendHuePoll(); +void onHueData(void* arg, AsyncClient* client, void *data, size_t len); #endif //WLED_HUE_H \ No newline at end of file diff --git a/wled00/wled_json.cpp b/wled00/wled_json.cpp index 43d3136dd..17359462d 100644 --- a/wled00/wled_json.cpp +++ b/wled00/wled_json.cpp @@ -1,5 +1,7 @@ #include "wled_json.h" #include "wled.h" +#include "wled_eeprom.h" +#include "wled_led.h" void deserializeSegment(JsonObject elem, byte it) { diff --git a/wled00/wled_json.h b/wled00/wled_json.h index 83830fafa..a7c2473aa 100644 --- a/wled00/wled_json.h +++ b/wled00/wled_json.h @@ -3,6 +3,12 @@ /* * JSON API (De)serialization */ +#include +#include "ESPAsyncWebServer.h" +#include "src/dependencies/json/ArduinoJson-v6.h" +#include "src/dependencies/json/AsyncJson-v6.h" +#include "fx.h" +// TODO: AsynicWebServerRequest conflict? void deserializeSegment(JsonObject elem, byte it); bool deserializeState(JsonObject root); diff --git a/wled00/wled_led.cpp b/wled00/wled_led.cpp index aefb004a9..bd657a53b 100644 --- a/wled00/wled_led.cpp +++ b/wled00/wled_led.cpp @@ -1,5 +1,10 @@ #include "wled_led.h" #include "wled.h" +#include "wled_notify.h" +#include "wled_blynk.h" +#include "wled_eeprom.h" +#include "wled_mqtt.h" +#include "wled_colors.h" void setValuesFromMainSeg() { diff --git a/wled00/wled_mqtt.cpp b/wled00/wled_mqtt.cpp index d4fc80c06..ce69513ba 100644 --- a/wled00/wled_mqtt.cpp +++ b/wled00/wled_mqtt.cpp @@ -1,5 +1,7 @@ #include "wled_mqtt.h" #include "wled.h" +#include "wled_notify.h" +#include "wled_led.h" #ifdef WLED_ENABLE_MQTT #define MQTT_KEEP_ALIVE_TIME 60 // contact the MQTT broker every 60 seconds diff --git a/wled00/wled_notify.cpp b/wled00/wled_notify.cpp index 7827aba96..7913b4525 100644 --- a/wled00/wled_notify.cpp +++ b/wled00/wled_notify.cpp @@ -1,12 +1,13 @@ #include "wled_notify.h" #include "wled.h" #include "src/dependencies/e131/ESPAsyncE131.h" +#include "wled_led.h" #define WLEDPACKETSIZE 29 #define UDP_IN_MAXSIZE 1472 -void notify(byte callMode, bool followUp=false) +void notify(byte callMode, bool followUp) { if (!udpConnected) return; switch (callMode) @@ -71,7 +72,7 @@ void notify(byte callMode, bool followUp=false) } -void arlsLock(uint32_t timeoutMs, byte md = REALTIME_MODE_GENERIC) +void arlsLock(uint32_t timeoutMs, byte md) { if (!realtimeMode){ for (uint16_t i = 0; i < ledCount; i++) diff --git a/wled00/wled_notify.h b/wled00/wled_notify.h index 63d01142b..2a2865b3b 100644 --- a/wled00/wled_notify.h +++ b/wled00/wled_notify.h @@ -5,7 +5,7 @@ /* * UDP notifier */ -union e131_packet_t; // Will this compile? +//union e131_packet_t; // Will this compile? class IPAddress; void notify(byte callMode, bool followUp=false); From ccf5e66f316f8d92063cf85a174abfec32226474 Mon Sep 17 00:00:00 2001 From: Travis J Dean Date: Wed, 25 Mar 2020 05:43:12 -0400 Subject: [PATCH 55/90] Compiles, but does not link. --- wled00/wled_hue.cpp | 2 ++ wled00/wled_hue.h | 2 +- wled00/wled_mqtt.cpp | 3 +++ wled00/wled_notify.h | 1 + wled00/wled_overlay.cpp | 2 ++ wled00/wled_overlay.h | 2 ++ wled00/wled_server.h | 2 ++ wled00/wled_set.cpp | 4 ++-- wled00/wled_xml.cpp | 3 ++- 9 files changed, 17 insertions(+), 4 deletions(-) diff --git a/wled00/wled_hue.cpp b/wled00/wled_hue.cpp index abdb4ffa7..61756ec0e 100644 --- a/wled00/wled_hue.cpp +++ b/wled00/wled_hue.cpp @@ -2,6 +2,8 @@ #include "wled.h" #include "wled_colors.h" #include "wled_eeprom.h" +#include "wled_notify.h" +#include "wled_led.h" #ifndef WLED_DISABLE_HUESYNC diff --git a/wled00/wled_hue.h b/wled00/wled_hue.h index e9aa22b2a..8b26f8b6f 100644 --- a/wled00/wled_hue.h +++ b/wled00/wled_hue.h @@ -4,7 +4,7 @@ * Sync to Philips hue lights */ #include -#include +class AsyncClient; void handleHue(); void reconnectHue(); diff --git a/wled00/wled_mqtt.cpp b/wled00/wled_mqtt.cpp index ce69513ba..5e96a18f2 100644 --- a/wled00/wled_mqtt.cpp +++ b/wled00/wled_mqtt.cpp @@ -2,6 +2,9 @@ #include "wled.h" #include "wled_notify.h" #include "wled_led.h" +#include "wled_colors.h" +#include "wled_xml.h" +#include "wled_set.h" #ifdef WLED_ENABLE_MQTT #define MQTT_KEEP_ALIVE_TIME 60 // contact the MQTT broker every 60 seconds diff --git a/wled00/wled_notify.h b/wled00/wled_notify.h index 2a2865b3b..04c65aa43 100644 --- a/wled00/wled_notify.h +++ b/wled00/wled_notify.h @@ -1,6 +1,7 @@ #ifndef WLED_NOTIFY_H #define WLED_NOTIFY_H #include +#include "src/dependencies/e131/ESPAsyncE131.h" #include "const.h" /* * UDP notifier diff --git a/wled00/wled_overlay.cpp b/wled00/wled_overlay.cpp index 93348e79b..8cb707866 100644 --- a/wled00/wled_overlay.cpp +++ b/wled00/wled_overlay.cpp @@ -1,5 +1,7 @@ #include "wled_overlay.h" #include "wled.h" +#include "wled_cronixie.h" +#include "wled_ntp.h" void initCronixie() { diff --git a/wled00/wled_overlay.h b/wled00/wled_overlay.h index 47573978d..ec18f45b4 100644 --- a/wled00/wled_overlay.h +++ b/wled00/wled_overlay.h @@ -8,5 +8,7 @@ void initCronixie(); void handleOverlays(); void handleOverlayDraw(); +void _overlayAnalogCountdown(); +void _overlayAnalogClock(); #endif // WLED_OVERLAY_H \ No newline at end of file diff --git a/wled00/wled_server.h b/wled00/wled_server.h index a03f83e1a..46adc9629 100644 --- a/wled00/wled_server.h +++ b/wled00/wled_server.h @@ -4,6 +4,8 @@ /* * Server page declarations */ +class AsyncWebServerRequest; + bool isIp(String str); bool captivePortal(AsyncWebServerRequest *request); diff --git a/wled00/wled_set.cpp b/wled00/wled_set.cpp index aff3f0cda..66d9ff1ec 100644 --- a/wled00/wled_set.cpp +++ b/wled00/wled_set.cpp @@ -9,7 +9,7 @@ #include "wled_cronixie.h" #include "wled_xml.h" -void _setRandomColor(bool _sec,bool fromButton=false) +void _setRandomColor(bool _sec,bool fromButton) { lastRandomIndex = strip.get_random_wheel_index(lastRandomIndex); if (_sec){ @@ -337,7 +337,7 @@ int getNumVal(const String* req, uint16_t pos) //helper to get int value at a position in string -bool updateVal(const String* req, const char* key, byte* val, byte minv=0, byte maxv=255) +bool updateVal(const String* req, const char* key, byte* val, byte minv, byte maxv) { int pos = req->indexOf(key); if (pos < 1) return false; diff --git a/wled00/wled_xml.cpp b/wled00/wled_xml.cpp index 07009cf3c..4f2b3f45b 100644 --- a/wled00/wled_xml.cpp +++ b/wled00/wled_xml.cpp @@ -1,10 +1,11 @@ #include "wled_xml.h" #include "wled.h" #include "wled_eeprom.h" +#include "wled_ntp.h" //build XML response to HTTP /win API request -char* XML_response(AsyncWebServerRequest *request, char* dest = nullptr) +char* XML_response(AsyncWebServerRequest *request, char* dest) { char sbuf[(dest == nullptr)?1024:1]; //allocate local buffer if none passed obuf = (dest == nullptr)? sbuf:dest; From f2329476ec18578e9acea0f9ebe1f6e332234aaa Mon Sep 17 00:00:00 2001 From: Travis J Dean Date: Thu, 26 Mar 2020 04:12:55 -0400 Subject: [PATCH 56/90] Fix linker errors. All global vars declared extern. --- wled00/wled.cpp | 364 +++++++++++++++++++++++++++++++++ wled00/wled.h | 469 +++++++++++++++++++++---------------------- wled00/wled_colors.h | 2 +- wled00/wled_ntp.cpp | 56 ++++++ wled00/wled_ntp.h | 57 ------ 5 files changed, 652 insertions(+), 296 deletions(-) diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 97ed38787..a720cb0b4 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -13,6 +13,370 @@ #include "wled_mqtt.h" #include "wled_eeprom.h" #include "wled_server.h" +// Global Variable definitions +char versionString[] = "0.9.1"; + +//AP and OTA default passwords (for maximum change them!) +char apPass[65] = DEFAULT_AP_PASS; +char otaPass[33] = DEFAULT_OTA_PASS; + +//Hardware CONFIG (only changeble HERE, not at runtime) +//LED strip pin, button pin and IR pin changeable in NpbWrapper.h! + +byte auxDefaultState = 0; //0: input 1: high 2: low +byte auxTriggeredState = 0; //0: input 1: high 2: low +char ntpServerName[33] = "0.wled.pool.ntp.org"; //NTP server to use + +//WiFi CONFIG (all these can be changed via web UI, no need to set them here) +char clientSSID[33] = CLIENT_SSID; +char clientPass[65] = CLIENT_PASS; +char cmDNS[33] = "x"; //mDNS address (placeholder, will be replaced by wledXXXXXXXXXXXX.local) +char apSSID[33] = ""; //AP off by default (unless setup) +byte apChannel = 1; //2.4GHz WiFi AP channel (1-13) +byte apHide = 0; //hidden AP SSID +byte apBehavior = AP_BEHAVIOR_BOOT_NO_CONN; //access point opens when no connection after boot by default +IPAddress staticIP(0, 0, 0, 0); //static IP of ESP +IPAddress staticGateway(0, 0, 0, 0); //gateway (router) IP +IPAddress staticSubnet(255, 255, 255, 0); //most common subnet in home networks +bool noWifiSleep = false; //disabling modem sleep modes will increase heat output and power usage, but may help with connection issues + +//LED CONFIG +uint16_t ledCount = 30; //overcurrent prevented by ABL +bool useRGBW = false; //SK6812 strips can contain an extra White channel +#define ABL_MILLIAMPS_DEFAULT 850; //auto lower brightness to stay close to milliampere limit +bool turnOnAtBoot = true; //turn on LEDs at power-up +byte bootPreset = 0; //save preset to load after power-up + +byte col[]{255, 160, 0, 0}; //current RGB(W) primary color. col[] should be updated if you want to change the color. +byte colSec[]{0, 0, 0, 0}; //current RGB(W) secondary color +byte briS = 128; //default brightness + +byte nightlightTargetBri = 0; //brightness after nightlight is over +byte nightlightDelayMins = 60; +bool nightlightFade = true; //if enabled, light will gradually dim towards the target bri. Otherwise, it will instantly set after delay over +bool nightlightColorFade = false; //if enabled, light will gradually fade color from primary to secondary color. +bool fadeTransition = true; //enable crossfading color transition +uint16_t transitionDelay = 750; //default crossfade duration in ms + +bool skipFirstLed = false; //ignore first LED in strip (useful if you need the LED as signal repeater) +byte briMultiplier = 100; //% of brightness to set (to limit power, if you set it to 50 and set bri to 255, actual brightness will be 127) + +//User Interface CONFIG +char serverDescription[33] = "WLED"; //Name of module +bool syncToggleReceive = false; //UIs which only have a single button for sync should toggle send+receive if this is true, only send otherwise + +//Sync CONFIG +bool buttonEnabled = true; +byte irEnabled = 0; //Infrared receiver + +uint16_t udpPort = 21324; //WLED notifier default port +uint16_t udpRgbPort = 19446; //Hyperion port + +bool receiveNotificationBrightness = true; //apply brightness from incoming notifications +bool receiveNotificationColor = true; //apply color +bool receiveNotificationEffects = true; //apply effects setup +bool notifyDirect = false; //send notification if change via UI or HTTP API +bool notifyButton = false; //send if updated by button or infrared remote +bool notifyAlexa = false; //send notification if updated via Alexa +bool notifyMacro = false; //send notification for macro +bool notifyHue = true; //send notification if Hue light changes +bool notifyTwice = false; //notifications use UDP: enable if devices don't sync reliably + +bool alexaEnabled = true; //enable device discovery by Amazon Echo +char alexaInvocationName[33] = "Light"; //speech control name of device. Choose something voice-to-text can understand + +char blynkApiKey[36] = ""; //Auth token for Blynk server. If empty, no connection will be made + +uint16_t realtimeTimeoutMs = 2500; //ms timeout of realtime mode before returning to normal mode +int arlsOffset = 0; //realtime LED offset +bool receiveDirect = true; //receive UDP realtime +bool arlsDisableGammaCorrection = true; //activate if gamma correction is handled by the source +bool arlsForceMaxBri = false; //enable to force max brightness if source has very dark colors that would be black + +#define E131_MAX_UNIVERSE_COUNT 9 +uint16_t e131Universe = 1; //settings for E1.31 (sACN) protocol (only DMX_MODE_MULTIPLE_* can span over consequtive universes) +uint8_t DMXMode = DMX_MODE_MULTIPLE_RGB; //DMX mode (s.a.) +uint16_t DMXAddress = 1; //DMX start address of fixture, a.k.a. first Channel [for E1.31 (sACN) protocol] +uint8_t DMXOldDimmer = 0; //only update brightness on change +uint8_t e131LastSequenceNumber[E131_MAX_UNIVERSE_COUNT]; //to detect packet loss +bool e131Multicast = false; //multicast or unicast +bool e131SkipOutOfSequence = false; //freeze instead of flickering + +bool mqttEnabled = false; +char mqttDeviceTopic[33] = ""; //main MQTT topic (individual per device, default is wled/mac) +char mqttGroupTopic[33] = "wled/all"; //second MQTT topic (for example to group devices) +char mqttServer[33] = ""; //both domains and IPs should work (no SSL) +char mqttUser[41] = ""; //optional: username for MQTT auth +char mqttPass[41] = ""; //optional: password for MQTT auth +char mqttClientID[41] = ""; //override the client ID +uint16_t mqttPort = 1883; + +bool huePollingEnabled = false; //poll hue bridge for light state +uint16_t huePollIntervalMs = 2500; //low values (< 1sec) may cause lag but offer quicker response +char hueApiKey[47] = "api"; //key token will be obtained from bridge +byte huePollLightId = 1; //ID of hue lamp to sync to. Find the ID in the hue app ("about" section) +IPAddress hueIP = (0, 0, 0, 0); //IP address of the bridge +bool hueApplyOnOff = true; +bool hueApplyBri = true; +bool hueApplyColor = true; + +//Time CONFIG +bool ntpEnabled = false; //get internet time. Only required if you use clock overlays or time-activated macros +bool useAMPM = false; //12h/24h clock format +byte currentTimezone = 0; //Timezone ID. Refer to timezones array in wled10_ntp.ino +int utcOffsetSecs = 0; //Seconds to offset from UTC before timzone calculation + +byte overlayDefault = 0; //0: no overlay 1: analog clock 2: single-digit clocl 3: cronixie +byte overlayMin = 0, overlayMax = ledCount - 1; //boundaries of overlay mode + +byte analogClock12pixel = 0; //The pixel in your strip where "midnight" would be +bool analogClockSecondsTrail = false; //Display seconds as trail of LEDs instead of a single pixel +bool analogClock5MinuteMarks = false; //Light pixels at every 5-minute position + +char cronixieDisplay[7] = "HHMMSS"; //Cronixie Display mask. See wled13_cronixie.ino +bool cronixieBacklight = true; //Allow digits to be back-illuminated + +bool countdownMode = false; //Clock will count down towards date +byte countdownYear = 20, countdownMonth = 1; //Countdown target date, year is last two digits +byte countdownDay = 1, countdownHour = 0; +byte countdownMin = 0, countdownSec = 0; + +byte macroBoot = 0; //macro loaded after startup +byte macroNl = 0; //after nightlight delay over +byte macroCountdown = 0; +byte macroAlexaOn = 0, macroAlexaOff = 0; +byte macroButton = 0, macroLongPress = 0, macroDoublePress = 0; + +//Security CONFIG +bool otaLock = false; //prevents OTA firmware updates without password. ALWAYS enable if system exposed to any public networks +bool wifiLock = false; //prevents access to WiFi settings when OTA lock is enabled +bool aOtaEnabled = true; //ArduinoOTA allows easy updates directly from the IDE. Careful, it does not auto-disable when OTA lock is on + +uint16_t userVar0 = 0, userVar1 = 0; + +#ifdef WLED_ENABLE_DMX +//dmx CONFIG +byte DMXChannels = 7; // number of channels per fixture +byte DMXFixtureMap[15] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +// assigns the different channels to different functions. See wled21_dmx.ino for more information. +uint16_t DMXGap = 10; // gap between the fixtures. makes addressing easier because you don't have to memorize odd numbers when climbing up onto a rig. +uint16_t DMXStart = 10; // start address of the first fixture +#endif + +//internal global variable declarations +//wifi +bool apActive = false; +bool forceReconnect = false; +uint32_t lastReconnectAttempt = 0; +bool interfacesInited = false; +bool wasConnected = false; + +//color +byte colOld[]{0, 0, 0, 0}; //color before transition +byte colT[]{0, 0, 0, 0}; //color that is currently displayed on the LEDs +byte colIT[]{0, 0, 0, 0}; //color that was last sent to LEDs +byte colSecT[]{0, 0, 0, 0}; +byte colSecOld[]{0, 0, 0, 0}; +byte colSecIT[]{0, 0, 0, 0}; + +byte lastRandomIndex = 0; //used to save last random color so the new one is not the same + +//transitions +bool transitionActive = false; +uint16_t transitionDelayDefault = transitionDelay; +uint16_t transitionDelayTemp = transitionDelay; +unsigned long transitionStartTime; +float tperLast = 0; //crossfade transition progress, 0.0f - 1.0f +bool jsonTransitionOnce = false; + +//nightlight +bool nightlightActive = false; +bool nightlightActiveOld = false; +uint32_t nightlightDelayMs = 10; +uint8_t nightlightDelayMinsDefault = nightlightDelayMins; +unsigned long nightlightStartTime; +byte briNlT = 0; //current nightlight brightness +byte colNlT[]{0, 0, 0, 0}; //current nightlight color + +//brightness +unsigned long lastOnTime = 0; +bool offMode = !turnOnAtBoot; +byte bri = briS; +byte briOld = 0; +byte briT = 0; +byte briIT = 0; +byte briLast = 128; //brightness before turned off. Used for toggle function +byte whiteLast = 128; //white channel before turned off. Used for toggle function + +//button +bool buttonPressedBefore = false; +bool buttonLongPressed = false; +unsigned long buttonPressedTime = 0; +unsigned long buttonWaitTime = 0; + +//notifications +bool notifyDirectDefault = notifyDirect; +bool receiveNotifications = true; +unsigned long notificationSentTime = 0; +byte notificationSentCallMode = NOTIFIER_CALL_MODE_INIT; +bool notificationTwoRequired = false; + +//effects +byte effectCurrent = 0; +byte effectSpeed = 128; +byte effectIntensity = 128; +byte effectPalette = 0; + +//network +bool udpConnected = false, udpRgbConnected = false; + +//ui style +bool showWelcomePage = false; + +//hue +byte hueError = HUE_ERROR_INACTIVE; +//uint16_t hueFailCount = 0; +float hueXLast = 0, hueYLast = 0; +uint16_t hueHueLast = 0, hueCtLast = 0; +byte hueSatLast = 0, hueBriLast = 0; +unsigned long hueLastRequestSent = 0; +bool hueAuthRequired = false; +bool hueReceived = false; +bool hueStoreAllowed = false, hueNewKey = false; + +//overlays +byte overlayCurrent = overlayDefault; +byte overlaySpeed = 200; +unsigned long overlayRefreshMs = 200; +unsigned long overlayRefreshedTime; + +//cronixie +byte dP[]{0, 0, 0, 0, 0, 0}; +bool cronixieInit = false; + +//countdown +unsigned long countdownTime = 1514764800L; +bool countdownOverTriggered = true; + +//timer +byte lastTimerMinute = 0; +byte timerHours[] = {0, 0, 0, 0, 0, 0, 0, 0}; +byte timerMinutes[] = {0, 0, 0, 0, 0, 0, 0, 0}; +byte timerMacro[] = {0, 0, 0, 0, 0, 0, 0, 0}; +byte timerWeekday[] = {255, 255, 255, 255, 255, 255, 255, 255}; //weekdays to activate on +//bit pattern of arr elem: 0b11111111: sun,sat,fri,thu,wed,tue,mon,validity + +//blynk +bool blynkEnabled = false; + +//preset cycling +bool presetCyclingEnabled = false; +byte presetCycleMin = 1, presetCycleMax = 5; +uint16_t presetCycleTime = 1250; +unsigned long presetCycledTime = 0; +byte presetCycCurr = presetCycleMin; +bool presetApplyBri = true; +bool saveCurrPresetCycConf = false; + +//realtime +byte realtimeMode = REALTIME_MODE_INACTIVE; +IPAddress realtimeIP = (0, 0, 0, 0); +unsigned long realtimeTimeout = 0; + +//mqtt +long lastMqttReconnectAttempt = 0; +long lastInterfaceUpdate = 0; +byte interfaceUpdateCallMode = NOTIFIER_CALL_MODE_INIT; +char mqttStatusTopic[40] = ""; //this must be global because of async handlers + +#if AUXPIN >= 0 + //auxiliary debug pin +byte auxTime = 0; +unsigned long auxStartTime = 0; +bool auxActive = false, auxActiveBefore = false; +#endif + +//alexa udp +String escapedMac; +#ifndef WLED_DISABLE_ALEXA +Espalexa espalexa; +EspalexaDevice *espalexaDevice; +#endif + +//dns server +DNSServer dnsServer; + +//network time +bool ntpConnected = false; +time_t local = 0; +unsigned long ntpLastSyncTime = 999000000L; +unsigned long ntpPacketSentTime = 999000000L; +IPAddress ntpServerIP; +uint16_t ntpLocalPort = 2390; +#define NTP_PACKET_SIZE 48 + +//maximum number of LEDs - MAX_LEDS is coming from the JSON response getting too big, MAX_LEDS_DMA will become a timing issue +#define MAX_LEDS 1500 +#define MAX_LEDS_DMA 500 + +//string temp buffer (now stored in stack locally) +#define OMAX 2048 +char *obuf; +uint16_t olen = 0; + +//presets +uint16_t savedPresets = 0; +int8_t currentPreset = -1; +bool isPreset = false; + +byte errorFlag = 0; + +String messageHead, messageSub; +byte optionType; + +bool doReboot = false; //flag to initiate reboot from async handlers +bool doPublishMqtt = false; + +//server library objects +AsyncWebServer server(80); +AsyncClient *hueClient = NULL; +AsyncMqttClient *mqtt = NULL; + +//function prototypes +void colorFromUint32(uint32_t, bool = false); +void serveMessage(AsyncWebServerRequest *, uint16_t, String, String, byte); +void handleE131Packet(e131_packet_t *, IPAddress); +void arlsLock(uint32_t, byte); +void handleOverlayDraw(); + +//udp interface objects +WiFiUDP notifierUdp, rgbUdp; +WiFiUDP ntpUdp; +ESPAsyncE131 e131(handleE131Packet); +bool e131NewData = false; + +//led fx library object +WS2812FX strip = WS2812FX(); + +#define WLED_CONNECTED (WiFi.status() == WL_CONNECTED) +#define WLED_WIFI_CONFIGURED (strlen(clientSSID) >= 1 && strcmp(clientSSID, DEFAULT_CLIENT_SSID) != 0) + +//debug macros +#ifdef WLED_DEBUG +#define DEBUG_PRINT(x) Serial.print(x) +#define DEBUG_PRINTLN(x) Serial.println(x) +#define DEBUG_PRINTF(x) Serial.printf(x) +unsigned long debugTime = 0; +int lastWifiState = 3; +unsigned long wifiStateChangedTime = 0; +int loops = 0; +#else +#define DEBUG_PRINT(x) +#define DEBUG_PRINTLN(x) +#define DEBUG_PRINTF(x) +#endif + + WLED::WLED() { diff --git a/wled00/wled.h b/wled00/wled.h index c04dfa87b..043414466 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -123,305 +123,297 @@ extern "C" //version code in format yymmddb (b = daily build) #define VERSION 2003222 -char versionString[] = "0.9.1"; +extern char versionString[]; //AP and OTA default passwords (for maximum change them!) -char apPass[65] = DEFAULT_AP_PASS; -char otaPass[33] = DEFAULT_OTA_PASS; +extern char apPass[65]; +extern char otaPass[33]; //Hardware CONFIG (only changeble HERE, not at runtime) //LED strip pin, button pin and IR pin changeable in NpbWrapper.h! -byte auxDefaultState = 0; //0: input 1: high 2: low -byte auxTriggeredState = 0; //0: input 1: high 2: low -char ntpServerName[33] = "0.wled.pool.ntp.org"; //NTP server to use +extern byte auxDefaultState; //0: input 1: high 2: low +extern byte auxTriggeredState; //0: input 1: high 2: low +extern char ntpServerName[33]; //NTP server to use //WiFi CONFIG (all these can be changed via web UI, no need to set them here) -char clientSSID[33] = CLIENT_SSID; -char clientPass[65] = CLIENT_PASS; -char cmDNS[33] = "x"; //mDNS address (placeholder, will be replaced by wledXXXXXXXXXXXX.local) -char apSSID[33] = ""; //AP off by default (unless setup) -byte apChannel = 1; //2.4GHz WiFi AP channel (1-13) -byte apHide = 0; //hidden AP SSID -byte apBehavior = AP_BEHAVIOR_BOOT_NO_CONN; //access point opens when no connection after boot by default -IPAddress staticIP(0, 0, 0, 0); //static IP of ESP -IPAddress staticGateway(0, 0, 0, 0); //gateway (router) IP -IPAddress staticSubnet(255, 255, 255, 0); //most common subnet in home networks -bool noWifiSleep = false; //disabling modem sleep modes will increase heat output and power usage, but may help with connection issues +extern char clientSSID[33]; +extern char clientPass[65]; +extern char cmDNS[33]; //mDNS address (placeholder, will be replaced by wledXXXXXXXXXXXX.local) +extern char apSSID[33]; //AP off by default (unless setup) +extern byte apChannel; //2.4GHz WiFi AP channel (1-13) +extern byte apHide; //hidden AP SSID +extern byte apBehavior; //access point opens when no connection after boot by default +extern IPAddress staticIP; //static IP of ESP +extern IPAddress staticGateway; //gateway (router) IP +extern IPAddress staticSubnet; //most common subnet in home networks +extern bool noWifiSleep; //disabling modem sleep modes will increase heat output and power usage, but may help with connection issues //LED CONFIG -uint16_t ledCount = 30; //overcurrent prevented by ABL -bool useRGBW = false; //SK6812 strips can contain an extra White channel +extern uint16_t ledCount; //overcurrent prevented by ABL +extern bool useRGBW; //SK6812 strips can contain an extra White channel #define ABL_MILLIAMPS_DEFAULT 850; //auto lower brightness to stay close to milliampere limit -bool turnOnAtBoot = true; //turn on LEDs at power-up -byte bootPreset = 0; //save preset to load after power-up +extern bool turnOnAtBoot; //turn on LEDs at power-up +extern byte bootPreset; //save preset to load after power-up -byte col[]{255, 160, 0, 0}; //current RGB(W) primary color. col[] should be updated if you want to change the color. -byte colSec[]{0, 0, 0, 0}; //current RGB(W) secondary color -byte briS = 128; //default brightness +extern byte col[]; //current RGB(W) primary color. col[] should be updated if you want to change the color. +extern byte colSec[]; //current RGB(W) secondary color +extern byte briS; //default brightness -byte nightlightTargetBri = 0; //brightness after nightlight is over -byte nightlightDelayMins = 60; -bool nightlightFade = true; //if enabled, light will gradually dim towards the target bri. Otherwise, it will instantly set after delay over -bool nightlightColorFade = false; //if enabled, light will gradually fade color from primary to secondary color. -bool fadeTransition = true; //enable crossfading color transition -uint16_t transitionDelay = 750; //default crossfade duration in ms +extern byte nightlightTargetBri; //brightness after nightlight is over +extern byte nightlightDelayMins; +extern bool nightlightFade; //if enabled, light will gradually dim towards the target bri. Otherwise, it will instantly set after delay over +extern bool nightlightColorFade; //if enabled, light will gradually fade color from primary to secondary color. +extern bool fadeTransition; //enable crossfading color transition +extern uint16_t transitionDelay; //default crossfade duration in ms -bool skipFirstLed = false; //ignore first LED in strip (useful if you need the LED as signal repeater) -byte briMultiplier = 100; //% of brightness to set (to limit power, if you set it to 50 and set bri to 255, actual brightness will be 127) +extern bool skipFirstLed; //ignore first LED in strip (useful if you need the LED as signal repeater) +extern byte briMultiplier; //% of brightness to set (to limit power, if you set it to 50 and set bri to 255, actual brightness will be 127) //User Interface CONFIG -char serverDescription[33] = "WLED"; //Name of module -bool syncToggleReceive = false; //UIs which only have a single button for sync should toggle send+receive if this is true, only send otherwise +extern char serverDescription[33]; //Name of module +extern bool syncToggleReceive; //UIs which only have a single button for sync should toggle send+receive if this is true, only send otherwise //Sync CONFIG -bool buttonEnabled = true; -byte irEnabled = 0; //Infrared receiver +extern bool buttonEnabled; +extern byte irEnabled; //Infrared receiver -uint16_t udpPort = 21324; //WLED notifier default port -uint16_t udpRgbPort = 19446; //Hyperion port +extern uint16_t udpPort; //WLED notifier default port +extern uint16_t udpRgbPort; //Hyperion port -bool receiveNotificationBrightness = true; //apply brightness from incoming notifications -bool receiveNotificationColor = true; //apply color -bool receiveNotificationEffects = true; //apply effects setup -bool notifyDirect = false; //send notification if change via UI or HTTP API -bool notifyButton = false; //send if updated by button or infrared remote -bool notifyAlexa = false; //send notification if updated via Alexa -bool notifyMacro = false; //send notification for macro -bool notifyHue = true; //send notification if Hue light changes -bool notifyTwice = false; //notifications use UDP: enable if devices don't sync reliably +extern bool receiveNotificationBrightness; //apply brightness from incoming notifications +extern bool receiveNotificationColor; //apply color +extern bool receiveNotificationEffects; //apply effects setup +extern bool notifyDirect; //send notification if change via UI or HTTP API +extern bool notifyButton; //send if updated by button or infrared remote +extern bool notifyAlexa; //send notification if updated via Alexa +extern bool notifyMacro; //send notification for macro +extern bool notifyHue; //send notification if Hue light changes +extern bool notifyTwice; //notifications use UDP: enable if devices don't sync reliably -bool alexaEnabled = true; //enable device discovery by Amazon Echo -char alexaInvocationName[33] = "Light"; //speech control name of device. Choose something voice-to-text can understand +extern bool alexaEnabled; //enable device discovery by Amazon Echo +extern char alexaInvocationName[33]; //speech control name of device. Choose something voice-to-text can understand -char blynkApiKey[36] = ""; //Auth token for Blynk server. If empty, no connection will be made +extern char blynkApiKey[36]; //Auth token for Blynk server. If empty, no connection will be made -uint16_t realtimeTimeoutMs = 2500; //ms timeout of realtime mode before returning to normal mode -int arlsOffset = 0; //realtime LED offset -bool receiveDirect = true; //receive UDP realtime -bool arlsDisableGammaCorrection = true; //activate if gamma correction is handled by the source -bool arlsForceMaxBri = false; //enable to force max brightness if source has very dark colors that would be black +extern uint16_t realtimeTimeoutMs; //ms timeout of realtime mode before returning to normal mode +extern int arlsOffset; //realtime LED offset +extern bool receiveDirect; //receive UDP realtime +extern bool arlsDisableGammaCorrection; //activate if gamma correction is handled by the source +extern bool arlsForceMaxBri; //enable to force max brightness if source has very dark colors that would be black #define E131_MAX_UNIVERSE_COUNT 9 -uint16_t e131Universe = 1; //settings for E1.31 (sACN) protocol (only DMX_MODE_MULTIPLE_* can span over consequtive universes) -uint8_t DMXMode = DMX_MODE_MULTIPLE_RGB; //DMX mode (s.a.) -uint16_t DMXAddress = 1; //DMX start address of fixture, a.k.a. first Channel [for E1.31 (sACN) protocol] -uint8_t DMXOldDimmer = 0; //only update brightness on change -uint8_t e131LastSequenceNumber[E131_MAX_UNIVERSE_COUNT]; //to detect packet loss -bool e131Multicast = false; //multicast or unicast -bool e131SkipOutOfSequence = false; //freeze instead of flickering +extern uint16_t e131Universe; //settings for E1.31 (sACN) protocol (only DMX_MODE_MULTIPLE_* can span over consequtive universes) +extern uint8_t DMXMode; //DMX mode (s.a.) +extern uint16_t DMXAddress; //DMX start address of fixture, a.k.a. first Channel [for E1.31 (sACN) protocol] +extern uint8_t DMXOldDimmer; //only update brightness on change +extern uint8_t e131LastSequenceNumber[E131_MAX_UNIVERSE_COUNT]; //to detect packet loss +extern bool e131Multicast; //multicast or unicast +extern bool e131SkipOutOfSequence; //freeze instead of flickering -bool mqttEnabled = false; -char mqttDeviceTopic[33] = ""; //main MQTT topic (individual per device, default is wled/mac) -char mqttGroupTopic[33] = "wled/all"; //second MQTT topic (for example to group devices) -char mqttServer[33] = ""; //both domains and IPs should work (no SSL) -char mqttUser[41] = ""; //optional: username for MQTT auth -char mqttPass[41] = ""; //optional: password for MQTT auth -char mqttClientID[41] = ""; //override the client ID -uint16_t mqttPort = 1883; +extern bool mqttEnabled; +extern char mqttDeviceTopic[33]; //main MQTT topic (individual per device, default is wled/mac) +extern char mqttGroupTopic[33]; //second MQTT topic (for example to group devices) +extern char mqttServer[33]; //both domains and IPs should work (no SSL) +extern char mqttUser[41]; //optional: username for MQTT auth +extern char mqttPass[41]; //optional: password for MQTT auth +extern char mqttClientID[41]; //override the client ID +extern uint16_t mqttPort; -bool huePollingEnabled = false; //poll hue bridge for light state -uint16_t huePollIntervalMs = 2500; //low values (< 1sec) may cause lag but offer quicker response -char hueApiKey[47] = "api"; //key token will be obtained from bridge -byte huePollLightId = 1; //ID of hue lamp to sync to. Find the ID in the hue app ("about" section) -IPAddress hueIP = (0, 0, 0, 0); //IP address of the bridge -bool hueApplyOnOff = true; -bool hueApplyBri = true; -bool hueApplyColor = true; +extern bool huePollingEnabled; //poll hue bridge for light state +extern uint16_t huePollIntervalMs; //low values (< 1sec) may cause lag but offer quicker response +extern char hueApiKey[47]; //key token will be obtained from bridge +extern byte huePollLightId; //ID of hue lamp to sync to. Find the ID in the hue app ("about" section) +extern IPAddress hueIP; //IP address of the bridge +extern bool hueApplyOnOff; +extern bool hueApplyBri; +extern bool hueApplyColor; //Time CONFIG -bool ntpEnabled = false; //get internet time. Only required if you use clock overlays or time-activated macros -bool useAMPM = false; //12h/24h clock format -byte currentTimezone = 0; //Timezone ID. Refer to timezones array in wled10_ntp.ino -int utcOffsetSecs = 0; //Seconds to offset from UTC before timzone calculation +extern bool ntpEnabled; //get internet time. Only required if you use clock overlays or time-activated macros +extern bool useAMPM; //12h/24h clock format +extern byte currentTimezone; //Timezone ID. Refer to timezones array in wled_ntp.cpp +extern int utcOffsetSecs; //Seconds to offset from UTC before timzone calculation -byte overlayDefault = 0; //0: no overlay 1: analog clock 2: single-digit clocl 3: cronixie -byte overlayMin = 0, overlayMax = ledCount - 1; //boundaries of overlay mode +extern byte overlayDefault; //0: no overlay 1: analog clock 2: single-digit clocl 3: cronixie +extern byte overlayMin; //boundaries of overlay mode +extern byte overlayMax; -byte analogClock12pixel = 0; //The pixel in your strip where "midnight" would be -bool analogClockSecondsTrail = false; //Display seconds as trail of LEDs instead of a single pixel -bool analogClock5MinuteMarks = false; //Light pixels at every 5-minute position +extern byte analogClock12pixel; //The pixel in your strip where "midnight" would be +extern bool analogClockSecondsTrail; //Display seconds as trail of LEDs instead of a single pixel +extern bool analogClock5MinuteMarks; //Light pixels at every 5-minute position -char cronixieDisplay[7] = "HHMMSS"; //Cronixie Display mask. See wled13_cronixie.ino -bool cronixieBacklight = true; //Allow digits to be back-illuminated +extern char cronixieDisplay[7]; //Cronixie Display mask. See wled13_cronixie.ino +extern bool cronixieBacklight; //Allow digits to be back-illuminated -bool countdownMode = false; //Clock will count down towards date -byte countdownYear = 20, countdownMonth = 1; //Countdown target date, year is last two digits -byte countdownDay = 1, countdownHour = 0; -byte countdownMin = 0, countdownSec = 0; +extern bool countdownMode; //Clock will count down towards date +extern byte countdownYear, countdownMonth; //Countdown target date, year is last two digits +extern byte countdownDay, countdownHour; +extern byte countdownMin, countdownSec; -byte macroBoot = 0; //macro loaded after startup -byte macroNl = 0; //after nightlight delay over -byte macroCountdown = 0; -byte macroAlexaOn = 0, macroAlexaOff = 0; -byte macroButton = 0, macroLongPress = 0, macroDoublePress = 0; +extern byte macroBoot; //macro loaded after startup +extern byte macroNl; //after nightlight delay over +extern byte macroCountdown; +extern byte macroAlexaOn, macroAlexaOff; +extern byte macroButton, macroLongPress, macroDoublePress; //Security CONFIG -bool otaLock = false; //prevents OTA firmware updates without password. ALWAYS enable if system exposed to any public networks -bool wifiLock = false; //prevents access to WiFi settings when OTA lock is enabled -bool aOtaEnabled = true; //ArduinoOTA allows easy updates directly from the IDE. Careful, it does not auto-disable when OTA lock is on +extern bool otaLock; //prevents OTA firmware updates without password. ALWAYS enable if system exposed to any public networks +extern bool wifiLock; //prevents access to WiFi settings when OTA lock is enabled +extern bool aOtaEnabled; //ArduinoOTA allows easy updates directly from the IDE. Careful, it does not auto-disable when OTA lock is on -uint16_t userVar0 = 0, userVar1 = 0; +extern uint16_t userVar0, userVar1; #ifdef WLED_ENABLE_DMX //dmx CONFIG -byte DMXChannels = 7; // number of channels per fixture -byte DMXFixtureMap[15] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; -// assigns the different channels to different functions. See wled21_dmx.ino for more information. -uint16_t DMXGap = 10; // gap between the fixtures. makes addressing easier because you don't have to memorize odd numbers when climbing up onto a rig. -uint16_t DMXStart = 10; // start address of the first fixture +extern byte DMXChannels; // number of channels per fixture +extern byte DMXFixtureMap[15]; +extern // assigns the different channels to different functions. See wled21_dmx.ino for more information. +extern uint16_t DMXGap; // gap between the fixtures. makes addressing easier because you don't have to memorize odd numbers when climbing up onto a rig. +extern uint16_t DMXStart; // start address of the first fixture #endif //internal global variable declarations //wifi -bool apActive = false; -bool forceReconnect = false; -uint32_t lastReconnectAttempt = 0; -bool interfacesInited = false; -bool wasConnected = false; +extern bool apActive; +extern bool forceReconnect; +extern uint32_t lastReconnectAttempt; +extern bool interfacesInited; +extern bool wasConnected; //color -byte colOld[]{0, 0, 0, 0}; //color before transition -byte colT[]{0, 0, 0, 0}; //color that is currently displayed on the LEDs -byte colIT[]{0, 0, 0, 0}; //color that was last sent to LEDs -byte colSecT[]{0, 0, 0, 0}; -byte colSecOld[]{0, 0, 0, 0}; -byte colSecIT[]{0, 0, 0, 0}; +extern byte colOld[]; //color before transition +extern byte colT[]; //color that is currently displayed on the LEDs +extern byte colIT[]; //color that was last sent to LEDs +extern byte colSecT[]; +extern byte colSecOld[]; +extern byte colSecIT[]; -byte lastRandomIndex = 0; //used to save last random color so the new one is not the same +extern byte lastRandomIndex; //used to save last random color so the new one is not the same //transitions -bool transitionActive = false; -uint16_t transitionDelayDefault = transitionDelay; -uint16_t transitionDelayTemp = transitionDelay; -unsigned long transitionStartTime; -float tperLast = 0; //crossfade transition progress, 0.0f - 1.0f -bool jsonTransitionOnce = false; +extern bool transitionActive; +extern uint16_t transitionDelayDefault; +extern uint16_t transitionDelayTemp; +extern unsigned long transitionStartTime; +extern float tperLast; //crossfade transition progress, 0.0f - 1.0f +extern bool jsonTransitionOnce; //nightlight -bool nightlightActive = false; -bool nightlightActiveOld = false; -uint32_t nightlightDelayMs = 10; -uint8_t nightlightDelayMinsDefault = nightlightDelayMins; -unsigned long nightlightStartTime; -byte briNlT = 0; //current nightlight brightness -byte colNlT[]{0, 0, 0, 0}; //current nightlight color +extern bool nightlightActive; +extern bool nightlightActiveOld; +extern uint32_t nightlightDelayMs; +extern uint8_t nightlightDelayMinsDefault; +extern unsigned long nightlightStartTime; +extern byte briNlT; //current nightlight brightness +extern byte colNlT[]; //current nightlight color -//brightness -unsigned long lastOnTime = 0; -bool offMode = !turnOnAtBoot; -byte bri = briS; -byte briOld = 0; -byte briT = 0; -byte briIT = 0; -byte briLast = 128; //brightness before turned off. Used for toggle function -byte whiteLast = 128; //white channel before turned off. Used for toggle function +extern unsigned long lastOnTime; +extern bool offMode; +extern byte bri; +extern byte briOld; +extern byte briT; +extern byte briIT; +extern byte briLast; //brightness before turned off. Used for toggle function +extern byte whiteLast; //white channel before turned off. Used for toggle function -//button -bool buttonPressedBefore = false; -bool buttonLongPressed = false; -unsigned long buttonPressedTime = 0; -unsigned long buttonWaitTime = 0; +extern bool buttonPressedBefore; +extern bool buttonLongPressed; +extern unsigned long buttonPressedTime; +extern unsigned long buttonWaitTime; -//notifications -bool notifyDirectDefault = notifyDirect; -bool receiveNotifications = true; -unsigned long notificationSentTime = 0; -byte notificationSentCallMode = NOTIFIER_CALL_MODE_INIT; -bool notificationTwoRequired = false; +extern bool notifyDirectDefault; +extern bool receiveNotifications; +extern unsigned long notificationSentTime; +extern byte notificationSentCallMode; +extern bool notificationTwoRequired; -//effects -byte effectCurrent = 0; -byte effectSpeed = 128; -byte effectIntensity = 128; -byte effectPalette = 0; +extern byte effectCurrent; +extern byte effectSpeed; +extern byte effectIntensity; +extern byte effectPalette; -//network -bool udpConnected = false, udpRgbConnected = false; +extern bool udpConnected, udpRgbConnected; -//ui style -bool showWelcomePage = false; +extern bool showWelcomePage; -//hue -byte hueError = HUE_ERROR_INACTIVE; -//uint16_t hueFailCount = 0; -float hueXLast = 0, hueYLast = 0; -uint16_t hueHueLast = 0, hueCtLast = 0; -byte hueSatLast = 0, hueBriLast = 0; -unsigned long hueLastRequestSent = 0; -bool hueAuthRequired = false; -bool hueReceived = false; -bool hueStoreAllowed = false, hueNewKey = false; +extern byte hueError; +//uint16_t hueFailCount; +extern float hueXLast, hueYLast; +extern uint16_t hueHueLast, hueCtLast; +extern byte hueSatLast, hueBriLast; +extern unsigned long hueLastRequestSent; +extern bool hueAuthRequired; +extern bool hueReceived; +extern bool hueStoreAllowed, hueNewKey; -//overlays -byte overlayCurrent = overlayDefault; -byte overlaySpeed = 200; -unsigned long overlayRefreshMs = 200; -unsigned long overlayRefreshedTime; +extern byte overlayCurrent; +extern byte overlaySpeed; +extern unsigned long overlayRefreshMs; +extern unsigned long overlayRefreshedTime; -//cronixie -byte dP[]{0, 0, 0, 0, 0, 0}; -bool cronixieInit = false; +extern byte dP[]; +extern bool cronixieInit; //countdown -unsigned long countdownTime = 1514764800L; -bool countdownOverTriggered = true; +extern unsigned long countdownTime; +extern bool countdownOverTriggered; //timer -byte lastTimerMinute = 0; -byte timerHours[] = {0, 0, 0, 0, 0, 0, 0, 0}; -byte timerMinutes[] = {0, 0, 0, 0, 0, 0, 0, 0}; -byte timerMacro[] = {0, 0, 0, 0, 0, 0, 0, 0}; -byte timerWeekday[] = {255, 255, 255, 255, 255, 255, 255, 255}; //weekdays to activate on +extern byte lastTimerMinute; +extern byte timerHours[]; +extern byte timerMinutes[]; +extern byte timerMacro[]; +extern byte timerWeekday[]; //weekdays to activate on //bit pattern of arr elem: 0b11111111: sun,sat,fri,thu,wed,tue,mon,validity //blynk -bool blynkEnabled = false; +extern bool blynkEnabled; //preset cycling -bool presetCyclingEnabled = false; -byte presetCycleMin = 1, presetCycleMax = 5; -uint16_t presetCycleTime = 1250; -unsigned long presetCycledTime = 0; -byte presetCycCurr = presetCycleMin; -bool presetApplyBri = true; -bool saveCurrPresetCycConf = false; +extern bool presetCyclingEnabled; +extern byte presetCycleMin, presetCycleMax; +extern uint16_t presetCycleTime; +extern unsigned long presetCycledTime; +extern byte presetCycCurr; +extern bool presetApplyBri; +extern bool saveCurrPresetCycConf; //realtime -byte realtimeMode = REALTIME_MODE_INACTIVE; -IPAddress realtimeIP = (0, 0, 0, 0); -unsigned long realtimeTimeout = 0; +extern byte realtimeMode; +extern IPAddress realtimeIP; +extern unsigned long realtimeTimeout; //mqtt -long lastMqttReconnectAttempt = 0; -long lastInterfaceUpdate = 0; -byte interfaceUpdateCallMode = NOTIFIER_CALL_MODE_INIT; -char mqttStatusTopic[40] = ""; //this must be global because of async handlers +extern long lastMqttReconnectAttempt; +extern long lastInterfaceUpdate; +extern byte interfaceUpdateCallMode; +extern char mqttStatusTopic[40]; //this must be global because of async handlers #if AUXPIN >= 0 - //auxiliary debug pin -byte auxTime = 0; -unsigned long auxStartTime = 0; -bool auxActive = false, auxActiveBefore = false; +//auxiliary debug pin +extern byte auxTime; +extern unsigned long auxStartTime; +extern bool auxActive; #endif //alexa udp -String escapedMac; +extern String escapedMac; #ifndef WLED_DISABLE_ALEXA -Espalexa espalexa; -EspalexaDevice *espalexaDevice; +extern Espalexa espalexa; +extern EspalexaDevice *espalexaDevice; #endif //dns server -DNSServer dnsServer; +extern DNSServer dnsServer; //network time -bool ntpConnected = false; -time_t local = 0; -unsigned long ntpLastSyncTime = 999000000L; -unsigned long ntpPacketSentTime = 999000000L; -IPAddress ntpServerIP; -uint16_t ntpLocalPort = 2390; +extern bool ntpConnected; +extern time_t local; +extern unsigned long ntpLastSyncTime; +extern unsigned long ntpPacketSentTime; +extern IPAddress ntpServerIP; +extern uint16_t ntpLocalPort; #define NTP_PACKET_SIZE 48 //maximum number of LEDs - MAX_LEDS is coming from the JSON response getting too big, MAX_LEDS_DMA will become a timing issue @@ -430,42 +422,43 @@ uint16_t ntpLocalPort = 2390; //string temp buffer (now stored in stack locally) #define OMAX 2048 -char *obuf; -uint16_t olen = 0; +extern char *obuf; +extern uint16_t olen; //presets -uint16_t savedPresets = 0; -int8_t currentPreset = -1; -bool isPreset = false; +extern uint16_t savedPresets; +extern int8_t currentPreset; +extern bool isPreset; -byte errorFlag = 0; +extern byte errorFlag; -String messageHead, messageSub; -byte optionType; +extern String messageHead, messageSub; +extern byte optionType; -bool doReboot = false; //flag to initiate reboot from async handlers -bool doPublishMqtt = false; +extern bool doReboot; //flag to initiate reboot from async handlers +extern bool doPublishMqtt; //server library objects -AsyncWebServer server(80); -AsyncClient *hueClient = NULL; -AsyncMqttClient *mqtt = NULL; +extern AsyncWebServer server; +extern AsyncClient *hueClient; +extern AsyncMqttClient *mqtt; //function prototypes -void colorFromUint32(uint32_t, bool = false); -void serveMessage(AsyncWebServerRequest *, uint16_t, String, String, byte); -void handleE131Packet(e131_packet_t *, IPAddress); -void arlsLock(uint32_t, byte); -void handleOverlayDraw(); +extern void colorFromUint32(uint32_t, bool); +extern void serveMessage(AsyncWebServerRequest *, uint16_t, String, String, byte); +extern void handleE131Packet(e131_packet_t *, IPAddress); +extern void arlsLock(uint32_t, byte); +extern void handleOverlayDraw(); //udp interface objects -WiFiUDP notifierUdp, rgbUdp; -WiFiUDP ntpUdp; -ESPAsyncE131 e131(handleE131Packet); -bool e131NewData = false; +extern WiFiUDP notifierUdp, rgbUdp; +extern WiFiUDP ntpUdp; +extern ESPAsyncE131 e131; +extern bool e131NewData; //led fx library object -WS2812FX strip = WS2812FX(); +extern WS2812FX strip; + #define WLED_CONNECTED (WiFi.status() == WL_CONNECTED) #define WLED_WIFI_CONFIGURED (strlen(clientSSID) >= 1 && strcmp(clientSSID, DEFAULT_CLIENT_SSID) != 0) diff --git a/wled00/wled_colors.h b/wled00/wled_colors.h index 2b3ff32c5..917311e78 100644 --- a/wled00/wled_colors.h +++ b/wled00/wled_colors.h @@ -5,7 +5,7 @@ * Color conversion methods */ -void colorFromUint32(uint32_t in, bool secondary); +void colorFromUint32(uint32_t in, bool secondary = false); void colorFromUint24(uint32_t in, bool secondary = false); void relativeChangeWhite(int8_t amount, byte lowerBoundary = 0); void colorHStoRGB(uint16_t hue, byte sat, byte* rgb); //hue, sat to rgb diff --git a/wled00/wled_ntp.cpp b/wled00/wled_ntp.cpp index 82b99f963..d8d9c38a2 100644 --- a/wled00/wled_ntp.cpp +++ b/wled00/wled_ntp.cpp @@ -1,7 +1,63 @@ #include "wled_ntp.h" #include "wled.h" #include "wled_eeprom.h" +TimeChangeRule UTCr = {Last, Sun, Mar, 1, 0}; // UTC +Timezone tzUTC(UTCr, UTCr); +TimeChangeRule BST = {Last, Sun, Mar, 1, 60}; // British Summer Time +TimeChangeRule GMT = {Last, Sun, Oct, 2, 0}; // Standard Time +Timezone tzUK(BST, GMT); + +TimeChangeRule CEST = {Last, Sun, Mar, 2, 120}; //Central European Summer Time +TimeChangeRule CET = {Last, Sun, Oct, 3, 60}; //Central European Standard Time +Timezone tzEUCentral(CEST, CET); + +TimeChangeRule EEST = {Last, Sun, Mar, 3, 180}; //Central European Summer Time +TimeChangeRule EET = {Last, Sun, Oct, 4, 120}; //Central European Standard Time +Timezone tzEUEastern(EEST, EET); + +TimeChangeRule EDT = {Second, Sun, Mar, 2, -240 }; //Daylight time = UTC - 4 hours +TimeChangeRule EST = {First, Sun, Nov, 2, -300 }; //Standard time = UTC - 5 hours +Timezone tzUSEastern(EDT, EST); + +TimeChangeRule CDT = {Second, Sun, Mar, 2, -300 }; //Daylight time = UTC - 5 hours +TimeChangeRule CST = {First, Sun, Nov, 2, -360 }; //Standard time = UTC - 6 hours +Timezone tzUSCentral(CDT, CST); + +Timezone tzCASaskatchewan(CST, CST); //Central without DST + +TimeChangeRule MDT = {Second, Sun, Mar, 2, -360 }; //Daylight time = UTC - 6 hours +TimeChangeRule MST = {First, Sun, Nov, 2, -420 }; //Standard time = UTC - 7 hours +Timezone tzUSMountain(MDT, MST); + +Timezone tzUSArizona(MST, MST); //Mountain without DST + +TimeChangeRule PDT = {Second, Sun, Mar, 2, -420 }; //Daylight time = UTC - 7 hours +TimeChangeRule PST = {First, Sun, Nov, 2, -480 }; //Standard time = UTC - 8 hours +Timezone tzUSPacific(PDT, PST); + +TimeChangeRule ChST = {Last, Sun, Mar, 1, 480}; // China Standard Time = UTC + 8 hours +Timezone tzChina(ChST, ChST); + +TimeChangeRule JST = {Last, Sun, Mar, 1, 540}; // Japan Standard Time = UTC + 9 hours +Timezone tzJapan(JST, JST); + +TimeChangeRule AEDT = {Second, Sun, Oct, 2, 660 }; //Daylight time = UTC + 11 hours +TimeChangeRule AEST = {First, Sun, Apr, 3, 600 }; //Standard time = UTC + 10 hours +Timezone tzAUEastern(AEDT, AEST); + +TimeChangeRule NZDT = {Second, Sun, Sep, 2, 780 }; //Daylight time = UTC + 13 hours +TimeChangeRule NZST = {First, Sun, Apr, 3, 720 }; //Standard time = UTC + 12 hours +Timezone tzNZ(NZDT, NZST); + +TimeChangeRule NKST = {Last, Sun, Mar, 1, 510}; //Pyongyang Time = UTC + 8.5 hours +Timezone tzNK(NKST, NKST); + +TimeChangeRule IST = {Last, Sun, Mar, 1, 330}; // India Standard Time = UTC + 5.5 hours +Timezone tzIndia(IST, IST); + +// Pick your timezone from here. +Timezone* timezones[] = {&tzUTC, &tzUK, &tzEUCentral, &tzEUEastern, &tzUSEastern, &tzUSCentral, &tzUSMountain, &tzUSArizona, &tzUSPacific, &tzChina, &tzJapan, &tzAUEastern, &tzNZ, &tzNK, &tzIndia, &tzCASaskatchewan}; void handleNetworkTime() { diff --git a/wled00/wled_ntp.h b/wled00/wled_ntp.h index 142933421..be683d143 100644 --- a/wled00/wled_ntp.h +++ b/wled00/wled_ntp.h @@ -2,66 +2,9 @@ #define WLED_NTP_H #include #include "timezone/Timezone.h" - /* * Acquires time from NTP server */ -TimeChangeRule UTCr = {Last, Sun, Mar, 1, 0}; // UTC -Timezone tzUTC(UTCr, UTCr); - -TimeChangeRule BST = {Last, Sun, Mar, 1, 60}; // British Summer Time -TimeChangeRule GMT = {Last, Sun, Oct, 2, 0}; // Standard Time -Timezone tzUK(BST, GMT); - -TimeChangeRule CEST = {Last, Sun, Mar, 2, 120}; //Central European Summer Time -TimeChangeRule CET = {Last, Sun, Oct, 3, 60}; //Central European Standard Time -Timezone tzEUCentral(CEST, CET); - -TimeChangeRule EEST = {Last, Sun, Mar, 3, 180}; //Central European Summer Time -TimeChangeRule EET = {Last, Sun, Oct, 4, 120}; //Central European Standard Time -Timezone tzEUEastern(EEST, EET); - -TimeChangeRule EDT = {Second, Sun, Mar, 2, -240 }; //Daylight time = UTC - 4 hours -TimeChangeRule EST = {First, Sun, Nov, 2, -300 }; //Standard time = UTC - 5 hours -Timezone tzUSEastern(EDT, EST); - -TimeChangeRule CDT = {Second, Sun, Mar, 2, -300 }; //Daylight time = UTC - 5 hours -TimeChangeRule CST = {First, Sun, Nov, 2, -360 }; //Standard time = UTC - 6 hours -Timezone tzUSCentral(CDT, CST); - -Timezone tzCASaskatchewan(CST, CST); //Central without DST - -TimeChangeRule MDT = {Second, Sun, Mar, 2, -360 }; //Daylight time = UTC - 6 hours -TimeChangeRule MST = {First, Sun, Nov, 2, -420 }; //Standard time = UTC - 7 hours -Timezone tzUSMountain(MDT, MST); - -Timezone tzUSArizona(MST, MST); //Mountain without DST - -TimeChangeRule PDT = {Second, Sun, Mar, 2, -420 }; //Daylight time = UTC - 7 hours -TimeChangeRule PST = {First, Sun, Nov, 2, -480 }; //Standard time = UTC - 8 hours -Timezone tzUSPacific(PDT, PST); - -TimeChangeRule ChST = {Last, Sun, Mar, 1, 480}; // China Standard Time = UTC + 8 hours -Timezone tzChina(ChST, ChST); - -TimeChangeRule JST = {Last, Sun, Mar, 1, 540}; // Japan Standard Time = UTC + 9 hours -Timezone tzJapan(JST, JST); - -TimeChangeRule AEDT = {Second, Sun, Oct, 2, 660 }; //Daylight time = UTC + 11 hours -TimeChangeRule AEST = {First, Sun, Apr, 3, 600 }; //Standard time = UTC + 10 hours -Timezone tzAUEastern(AEDT, AEST); - -TimeChangeRule NZDT = {Second, Sun, Sep, 2, 780 }; //Daylight time = UTC + 13 hours -TimeChangeRule NZST = {First, Sun, Apr, 3, 720 }; //Standard time = UTC + 12 hours -Timezone tzNZ(NZDT, NZST); - -TimeChangeRule NKST = {Last, Sun, Mar, 1, 510}; //Pyongyang Time = UTC + 8.5 hours -Timezone tzNK(NKST, NKST); - -TimeChangeRule IST = {Last, Sun, Mar, 1, 330}; // India Standard Time = UTC + 5.5 hours -Timezone tzIndia(IST, IST); - -Timezone* timezones[] = {&tzUTC, &tzUK, &tzEUCentral, &tzEUEastern, &tzUSEastern, &tzUSCentral, &tzUSMountain, &tzUSArizona, &tzUSPacific, &tzChina, &tzJapan, &tzAUEastern, &tzNZ, &tzNK, &tzIndia, &tzCASaskatchewan}; void handleNetworkTime(); void sendNTPPacket(); From c6ea2dff8a87dd4f3fedfa754218f23a1bd13016 Mon Sep 17 00:00:00 2001 From: Travis J Dean Date: Thu, 26 Mar 2020 04:49:35 -0400 Subject: [PATCH 57/90] Add further includes. --- wled00/wled.cpp | 1 + wled00/wled_blynk.cpp | 3 +++ wled00/wled_ir.cpp | 5 ++++- wled00/wled_ir.h | 15 +++++++++++++++ 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/wled00/wled.cpp b/wled00/wled.cpp index a720cb0b4..3b3d6936d 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -1,4 +1,5 @@ #include "wled.h" +#include #include "wled_led.h" #include "wled_ir.h" #include "wled_notify.h" diff --git a/wled00/wled_blynk.cpp b/wled00/wled_blynk.cpp index c6754a231..2e3d9f7bb 100644 --- a/wled00/wled_blynk.cpp +++ b/wled00/wled_blynk.cpp @@ -1,6 +1,9 @@ #include "wled_blynk.h" +#include "const.h" #include "wled.h" #include "src/dependencies/blynk/Blynk/BlynkHandlers.h" +#include "wled_led.h" +#include "wled_colors.h" uint16_t blHue = 0; byte blSat = 255; diff --git a/wled00/wled_ir.cpp b/wled00/wled_ir.cpp index 53f0d23f9..f625148eb 100644 --- a/wled00/wled_ir.cpp +++ b/wled00/wled_ir.cpp @@ -1,5 +1,8 @@ #include "wled_ir.h" #include "wled.h" +#include "wled_led.h" +#include "wled_colors.h" +#include "wled_eeprom.h" #if defined(WLED_DISABLE_INFRARED) void handleIR(){} @@ -34,7 +37,7 @@ bool decodeIRCustom(uint32_t code) //relatively change brightness, minumum A=5 -void relativeChange(byte* property, int8_t amount, byte lowerBoundary = 0, byte higherBoundary = 0xFF) +void relativeChange(byte* property, int8_t amount, byte lowerBoundary, byte higherBoundary) { int16_t new_val = (int16_t) *property + amount; if (new_val > higherBoundary) new_val = higherBoundary; diff --git a/wled00/wled_ir.h b/wled00/wled_ir.h index 3479f410b..3f819da01 100644 --- a/wled00/wled_ir.h +++ b/wled00/wled_ir.h @@ -1,9 +1,24 @@ #ifndef WLED_IR_H #define WLED_IR_H +#include /* * Infrared sensor support for generic 24/40/44 key RGB remotes */ +bool decodeIRCustom(uint32_t code); +void relativeChange(byte* property, int8_t amount, byte lowerBoundary = 0, byte higherBoundary = 0xFF); +void changeEffectSpeed(int8_t amount); +void changeEffectIntensity(int8_t amount); +void decodeIR(uint32_t code); +void decodeIR24(uint32_t code); +void decodeIR24OLD(uint32_t code); +void decodeIR24CT(uint32_t code); +void decodeIR40(uint32_t code); +void decodeIR44(uint32_t code); +void decodeIR21(uint32_t code); +void decodeIR6(uint32_t code); + +void initIR(); void handleIR(); #endif //WLED_IR_H \ No newline at end of file From 7bf1c35dcfac940c9b048c67537a2d7fb5b35f83 Mon Sep 17 00:00:00 2001 From: Travis J Dean Date: Thu, 26 Mar 2020 04:56:19 -0400 Subject: [PATCH 58/90] Change interface of BlynkSimpleEsp. --- wled00/src/dependencies/blynk/BlynkSimpleEsp.cpp | 5 +++++ wled00/src/dependencies/blynk/BlynkSimpleEsp.h | 4 +--- 2 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 wled00/src/dependencies/blynk/BlynkSimpleEsp.cpp diff --git a/wled00/src/dependencies/blynk/BlynkSimpleEsp.cpp b/wled00/src/dependencies/blynk/BlynkSimpleEsp.cpp new file mode 100644 index 000000000..a15d2c1bb --- /dev/null +++ b/wled00/src/dependencies/blynk/BlynkSimpleEsp.cpp @@ -0,0 +1,5 @@ +#include "BlynkSimpleEsp.h" + +WiFiClient _blynkWifiClient; +BlynkArduinoClient _blynkTransport(_blynkWifiClient); +BlynkWifi Blynk(_blynkTransport); \ No newline at end of file diff --git a/wled00/src/dependencies/blynk/BlynkSimpleEsp.h b/wled00/src/dependencies/blynk/BlynkSimpleEsp.h index f6b2a0f1c..6697686fa 100644 --- a/wled00/src/dependencies/blynk/BlynkSimpleEsp.h +++ b/wled00/src/dependencies/blynk/BlynkSimpleEsp.h @@ -89,8 +89,6 @@ public: }; -static WiFiClient _blynkWifiClient; -static BlynkArduinoClient _blynkTransport(_blynkWifiClient); -BlynkWifi Blynk(_blynkTransport); +extern BlynkWifi Blynk; #endif From 30e3bbd0e8e226bc707c9e0e4b0777dcd87b0727 Mon Sep 17 00:00:00 2001 From: Travis J Dean Date: Thu, 26 Mar 2020 05:18:19 -0400 Subject: [PATCH 59/90] Renamed min/max macros to fix potential std::min/max conflict (depending on include order). --- wled00/FX.cpp | 66 +++++++++++++++++++++++------------------------ wled00/FX.h | 4 +-- wled00/FX_fcn.cpp | 10 +++---- wled00/wled.h | 8 +++--- 4 files changed, 44 insertions(+), 44 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index 022b4b232..a57e9ddac 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -268,7 +268,7 @@ uint16_t WS2812FX::mode_breath(void) { counter = (counter >> 2) + (counter >> 4); //0-16384 + 0-2048 if (counter < 16384) { if (counter > 8192) counter = 8192 - (counter - 8192); - var = sin16(counter) / 103; //close to parabolic in range 0-8192, max val. 23170 + var = sin16(counter) / 103; //close to parabolic in range 0-8192, MAX val. 23170 } uint8_t lum = 30 + var; @@ -475,8 +475,8 @@ uint16_t WS2812FX::mode_twinkle(void) { uint32_t it = now / cycleTime; if (it != SEGENV.step) { - uint16_t maxOn = map(SEGMENT.intensity, 0, 255, 1, SEGLEN); // make sure at least one LED is on - if (SEGENV.aux0 >= maxOn) + uint16_t MAXOn = map(SEGMENT.intensity, 0, 255, 1, SEGLEN); // make sure at least one LED is on + if (SEGENV.aux0 >= MAXOn) { SEGENV.aux0 = 0; SEGENV.aux1 = random16(); //new seed for our PRNG @@ -601,7 +601,7 @@ uint16_t WS2812FX::mode_hyper_sparkle(void) { } if(random8(5) < 2) { - for(uint16_t i = 0; i < max(1, SEGLEN/3); i++) { + for(uint16_t i = 0; i < MAX(1, SEGLEN/3); i++) { setPixelColor(random16(SEGLEN), SEGCOLOR(1)); } return 20; @@ -1115,7 +1115,7 @@ uint16_t WS2812FX::mode_fireworks() { if (valid1) setPixelColor(SEGENV.aux0 , sv1); if (valid2) setPixelColor(SEGENV.aux1, sv2); - for(uint16_t i=0; i> 1)) == 0) { uint16_t index = random(SEGLEN); setPixelColor(index, color_from_palette(random8(), false, false, 0)); @@ -1162,12 +1162,12 @@ uint16_t WS2812FX::mode_fire_flicker(void) { byte r = (SEGCOLOR(0) >> 16) & 0xFF; byte g = (SEGCOLOR(0) >> 8) & 0xFF; byte b = (SEGCOLOR(0) & 0xFF); - byte lum = (SEGMENT.palette == 0) ? max(w, max(r, max(g, b))) : 255; + byte lum = (SEGMENT.palette == 0) ? MAX(w, MAX(r, MAX(g, b))) : 255; lum /= (((256-SEGMENT.intensity)/16)+1); for(uint16_t i = 0; i < SEGLEN; i++) { byte flicker = random8(lum); if (SEGMENT.palette == 0) { - setPixelColor(i, max(r - flicker, 0), max(g - flicker, 0), max(b - flicker, 0), max(w - flicker, 0)); + setPixelColor(i, MAX(r - flicker, 0), MAX(g - flicker, 0), MAX(b - flicker, 0), MAX(w - flicker, 0)); } else { setPixelColor(i, color_from_palette(i, true, PALETTE_SOLID_WRAP, 0, 255 - flicker)); } @@ -1197,7 +1197,7 @@ uint16_t WS2812FX::gradient_base(bool loading) { { val = abs(((i>pp) ? p2:pp) -i); } else { - val = min(abs(pp-i),min(abs(p1-i),abs(p2-i))); + val = MIN(abs(pp-i),MIN(abs(p1-i),abs(p2-i))); } val = (brd > val) ? val/brd * 255 : 255; setPixelColor(i, color_blend(SEGCOLOR(0), color_from_palette(i, true, PALETTE_SOLID_WRAP, 1), val)); @@ -1597,8 +1597,8 @@ uint16_t WS2812FX::mode_oscillate(void) uint16_t WS2812FX::mode_lightning(void) { - uint16_t ledstart = random16(SEGLEN); // Determine starting location of flash - uint16_t ledlen = random16(SEGLEN -1 -ledstart); // Determine length of flash (not to go beyond NUM_LEDS-1) + uint16_t ledstart = random16(SEGLEN); // DeterMINe starting location of flash + uint16_t ledlen = random16(SEGLEN -1 -ledstart); // DeterMINe length of flash (not to go beyond NUM_LEDS-1) uint8_t bri = 255/random8(1, 3); if (SEGENV.step == 0) @@ -1778,7 +1778,7 @@ uint16_t WS2812FX::mode_fire_2012() // Step 4. Map from heat cells to LED colors for (uint16_t j = 0; j < SEGLEN; j++) { - CRGB color = ColorFromPalette(currentPalette, min(heat[j],240), 255, LINEARBLEND); + CRGB color = ColorFromPalette(currentPalette, MIN(heat[j],240), 255, LINEARBLEND); setPixelColor(j, color.red, color.green, color.blue); } return FRAMETIME; @@ -2153,9 +2153,9 @@ typedef struct Ripple { uint16_t WS2812FX::ripple_base(bool rainbow) { - uint16_t maxRipples = 1 + (SEGLEN >> 2); - if (maxRipples > 100) maxRipples = 100; - uint16_t dataSize = sizeof(ripple) * maxRipples; + uint16_t MAXRipples = 1 + (SEGLEN >> 2); + if (MAXRipples > 100) MAXRipples = 100; + uint16_t dataSize = sizeof(ripple) * MAXRipples; if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed @@ -2181,7 +2181,7 @@ uint16_t WS2812FX::ripple_base(bool rainbow) } //draw wave - for (uint16_t i = 0; i < maxRipples; i++) + for (uint16_t i = 0; i < MAXRipples; i++) { uint16_t ripplestate = ripples[i].state; if (ripplestate) @@ -2480,8 +2480,8 @@ uint16_t WS2812FX::spots_base(uint16_t threshold) { fill(SEGCOLOR(1)); - uint16_t maxZones = SEGLEN >> 2; - uint16_t zones = 1 + ((SEGMENT.intensity * maxZones) >> 8); + uint16_t MAXZones = SEGLEN >> 2; + uint16_t zones = 1 + ((SEGMENT.intensity * MAXZones) >> 8); uint16_t zoneLen = SEGLEN / zones; uint16_t offset = (SEGLEN - zones * zoneLen) >> 1; @@ -2533,15 +2533,15 @@ typedef struct Ball { */ uint16_t WS2812FX::mode_bouncing_balls(void) { //allocate segment data - uint16_t maxNumBalls = 16; - uint16_t dataSize = sizeof(ball) * maxNumBalls; + uint16_t MAXNumBalls = 16; + uint16_t dataSize = sizeof(ball) * MAXNumBalls; if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed Ball* balls = reinterpret_cast(SEGENV.data); - // number of balls based on intensity setting to max of 7 (cycles colors) + // number of balls based on intensity setting to MAX of 7 (cycles colors) // non-chosen color is a random color - uint8_t numBalls = int(((SEGMENT.intensity * (maxNumBalls - 0.8f)) / 255) + 1); + uint8_t numBalls = int(((SEGMENT.intensity * (MAXNumBalls - 0.8f)) / 255) + 1); float gravity = -9.81; // standard value of gravity float impactVelocityStart = sqrt( -2 * gravity); @@ -2549,7 +2549,7 @@ uint16_t WS2812FX::mode_bouncing_balls(void) { unsigned long time = millis(); if (SEGENV.call == 0) { - for (uint8_t i = 0; i < maxNumBalls; i++) balls[i].lastBounceTime = time; + for (uint8_t i = 0; i < MAXNumBalls; i++) balls[i].lastBounceTime = time; } bool hasCol2 = SEGCOLOR(2); @@ -2573,7 +2573,7 @@ uint16_t WS2812FX::mode_bouncing_balls(void) { uint32_t color = SEGCOLOR(0); if (SEGMENT.palette) { - color = color_wheel(i*(256/max(numBalls, 8))); + color = color_wheel(i*(256/MAX(numBalls, 8))); } else if (hasCol2) { color = SEGCOLOR(i % NUM_COLORS); } @@ -2665,8 +2665,8 @@ typedef struct Spark { */ uint16_t WS2812FX::mode_popcorn(void) { //allocate segment data - uint16_t maxNumPopcorn = 24; - uint16_t dataSize = sizeof(spark) * maxNumPopcorn; + uint16_t MAXNumPopcorn = 24; + uint16_t dataSize = sizeof(spark) * MAXNumPopcorn; if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed Spark* popcorn = reinterpret_cast(SEGENV.data); @@ -2677,7 +2677,7 @@ uint16_t WS2812FX::mode_popcorn(void) { bool hasCol2 = SEGCOLOR(2); fill(hasCol2 ? BLACK : SEGCOLOR(1)); - uint8_t numPopcorn = SEGMENT.intensity*maxNumPopcorn/255; + uint8_t numPopcorn = SEGMENT.intensity*MAXNumPopcorn/255; if (numPopcorn == 0) numPopcorn = 1; for(uint8_t i = 0; i < numPopcorn; i++) { @@ -2792,13 +2792,13 @@ uint16_t WS2812FX::mode_starburst(void) { star* stars = reinterpret_cast(SEGENV.data); - float maxSpeed = 375.0f; // Max velocity + float MAXSpeed = 375.0f; // Max velocity float particleIgnition = 250.0f; // How long to "flash" float particleFadeTime = 1500.0f; // Fade out time for (int j = 0; j < numStars; j++) { - // speed to adjust chance of a burst, max is nearly always. + // speed to adjust chance of a burst, MAX is nearly always. if (random8((144-(SEGMENT.speed >> 1))) == 0 && stars[j].birth == 0) { // Pick a random color and location. @@ -2807,7 +2807,7 @@ uint16_t WS2812FX::mode_starburst(void) { stars[j].color = col_to_crgb(color_wheel(random8())); stars[j].pos = startPos; - stars[j].vel = maxSpeed * (float)(random8())/255.0 * multiplier; + stars[j].vel = MAXSpeed * (float)(random8())/255.0 * multiplier; stars[j].birth = it; stars[j].last = it; // more fragments means larger burst effect @@ -3026,7 +3026,7 @@ uint16_t WS2812FX::mode_drip(void) drops[j].pos = SEGLEN-1; // start at end drops[j].vel = 0; // speed drops[j].col = sourcedrop; // brightness - drops[j].colIndex = 1; // drop state (0 init, 1 forming, 2 falling, 5 bouncing) + drops[j].colIndex = 1; // drop state (0 init, 1 forMINg, 2 falling, 5 bouncing) } setPixelColor(SEGLEN-1,color_blend(BLACK,SEGCOLOR(0), sourcedrop));// water source @@ -3047,7 +3047,7 @@ uint16_t WS2812FX::mode_drip(void) if (drops[j].pos < 0) drops[j].pos = 0; drops[j].vel += gravity; - for (int i=1;i<7-drops[j].colIndex;i++) { // some minor math so we don't expand bouncing droplets + for (int i=1;i<7-drops[j].colIndex;i++) { // some MINor math so we don't expand bouncing droplets setPixelColor(int(drops[j].pos)+i,color_blend(BLACK,SEGCOLOR(0),drops[j].col/i)); //spread pixel with fade while falling } @@ -3055,7 +3055,7 @@ uint16_t WS2812FX::mode_drip(void) setPixelColor(0,color_blend(SEGCOLOR(0),BLACK,drops[j].col)); } } else { // we hit bottom - if (drops[j].colIndex > 2) { // already hit once, so back to forming + if (drops[j].colIndex > 2) { // already hit once, so back to forMINg drops[j].colIndex = 0; drops[j].col = sourcedrop; @@ -3100,7 +3100,7 @@ uint16_t WS2812FX::mode_plasma(void) { */ uint16_t WS2812FX::mode_percent(void) { - uint8_t percent = max(0, min(200, SEGMENT.intensity)); + uint8_t percent = MAX(0, MIN(200, SEGMENT.intensity)); uint16_t active_leds = (percent < 100) ? SEGLEN * percent / 100.0 : SEGLEN * (200 - percent) / 100.0; diff --git a/wled00/FX.h b/wled00/FX.h index d24250f9b..210039ef4 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -38,8 +38,8 @@ #define DEFAULT_SPEED (uint8_t)128 #define DEFAULT_COLOR (uint32_t)0xFFAA00 -#define min(a,b) ((a)<(b)?(a):(b)) -#define max(a,b) ((a)>(b)?(a):(b)) +#define MIN(a,b) ((a)<(b)?(a):(b)) +#define MAX(a,b) ((a)>(b)?(a):(b)) /* Not used in all effects yet */ #define WLED_FPS 42 diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 055686237..75ec65101 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -224,7 +224,7 @@ void WS2812FX::show(void) { if(useWackyWS2815PowerModel) { // ignore white component on WS2815 power calculation - powerSum += (max(max(c.R,c.G),c.B)) * 3; + powerSum += (MAX(MAX(c.R,c.G),c.B)) * 3; } else { @@ -638,7 +638,7 @@ uint32_t WS2812FX::color_wheel(uint8_t pos) { } /* - * Returns a new, random wheel index with a minimum distance of 42 from pos. + * Returns a new, random wheel index with a MINimum distance of 42 from pos. */ uint8_t WS2812FX::get_random_wheel_index(uint8_t pos) { uint8_t r = 0, x = 0, y = 0, d = 0; @@ -647,7 +647,7 @@ uint8_t WS2812FX::get_random_wheel_index(uint8_t pos) { r = random8(); x = abs(pos - r); y = 255 - x; - d = min(x, y); + d = MIN(x, y); } return r; } @@ -730,8 +730,8 @@ void WS2812FX::handle_palette(void) CHSV prim_hsv = rgb2hsv_approximate(prim); targetPalette = CRGBPalette16( CHSV(prim_hsv.h, prim_hsv.s, prim_hsv.v), //color itself - CHSV(prim_hsv.h, max(prim_hsv.s - 50,0), prim_hsv.v), //less saturated - CHSV(prim_hsv.h, prim_hsv.s, max(prim_hsv.v - 50,0)), //darker + CHSV(prim_hsv.h, MAX(prim_hsv.s - 50,0), prim_hsv.v), //less saturated + CHSV(prim_hsv.h, prim_hsv.s, MAX(prim_hsv.v - 50,0)), //darker CHSV(prim_hsv.h, prim_hsv.s, prim_hsv.v)); //color itself break;} case 4: {//primary + secondary diff --git a/wled00/wled.h b/wled00/wled.h index 043414466..3b744bcd4 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -468,10 +468,10 @@ extern WS2812FX strip; #define DEBUG_PRINT(x) Serial.print(x) #define DEBUG_PRINTLN(x) Serial.println(x) #define DEBUG_PRINTF(x) Serial.printf(x) -unsigned long debugTime = 0; -int lastWifiState = 3; -unsigned long wifiStateChangedTime = 0; -int loops = 0; +extern unsigned long debugTime; +extern int lastWifiState; +extern unsigned long wifiStateChangedTime; +extern int loops; #else #define DEBUG_PRINT(x) #define DEBUG_PRINTLN(x) From 77ce67d68588a2e31e811b6219ad7fcf591c293f Mon Sep 17 00:00:00 2001 From: feindsender Date: Fri, 27 Mar 2020 10:03:23 +0100 Subject: [PATCH 60/90] Add P9813 LED driver support Adds support for the P9813 LED driver --- wled00/NpbWrapper.h | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/wled00/NpbWrapper.h b/wled00/NpbWrapper.h index 673d82492..6e58b7197 100644 --- a/wled00/NpbWrapper.h +++ b/wled00/NpbWrapper.h @@ -9,6 +9,7 @@ //#define USE_APA102 // Uncomment for using APA102 LEDs. //#define USE_WS2801 // Uncomment for using WS2801 LEDs (make sure you have NeoPixelBus v2.5.6 or newer) //#define USE_LPD8806 // Uncomment for using LPD8806 +//#define USE_P9813 // Uncomment for using P9813 LEDs (make sure you have NeoPixelBus v2.5.8 or newer) //#define WLED_USE_ANALOG_LEDS //Uncomment for using "dumb" PWM controlled LEDs (see pins below, default R: gpio5, G: 12, B: 15, W: 13) //#define WLED_USE_H801 //H801 controller. Please uncomment #define WLED_USE_ANALOG_LEDS as well //#define WLED_USE_5CH_LEDS //5 Channel H801 for cold and warm white @@ -35,7 +36,7 @@ //END CONFIGURATION -#if defined(USE_APA102) || defined(USE_WS2801) || defined(USE_LPD8806) +#if defined(USE_APA102) || defined(USE_WS2801) || defined(USE_LPD8806) || defined(USE_P9813) #define CLKPIN 0 #define DATAPIN 2 #if BTNPIN == CLKPIN || BTNPIN == DATAPIN @@ -73,6 +74,8 @@ #define PIXELMETHOD NeoWs2801Method #elif defined(USE_LPD8806) #define PIXELMETHOD Lpd8806Method + #elif defined(USE_P9813) + #define PIXELMETHOD P9813Method #else #define PIXELMETHOD NeoEsp32Rmt0Ws2812xMethod #endif @@ -84,6 +87,8 @@ #define PIXELMETHOD NeoWs2801Method #elif defined(USE_LPD8806) #define PIXELMETHOD Lpd8806Method + #elif defined(USE_P9813) + #define PIXELMETHOD P9813Method #elif LEDPIN == 2 #define PIXELMETHOD NeoEsp8266Uart1Ws2813Method //if you get an error here, try to change to NeoEsp8266UartWs2813Method or update Neopixelbus #elif LEDPIN == 3 @@ -101,7 +106,10 @@ #define PIXELFEATURE4 DotStarLbgrFeature #elif defined(USE_LPD8806) #define PIXELFEATURE3 Lpd8806GrbFeature - #define PIXELFEATURE4 Lpd8806GrbFeature + #define PIXELFEATURE4 Lpd8806GrbFeature +#elif defined(USE_P9813) + #define PIXELFEATURE3 P9813BgrFeature + #define PIXELFEATURE4 NeoGrbwFeature #else #define PIXELFEATURE3 NeoGrbFeature #define PIXELFEATURE4 NeoGrbwFeature @@ -143,7 +151,7 @@ public: switch (_type) { case NeoPixelType_Grb: - #if defined(USE_APA102) || defined(USE_WS2801) || defined(USE_LPD8806) + #if defined(USE_APA102) || defined(USE_WS2801) || defined(USE_LPD8806) || defined(USE_P9813) _pGrb = new NeoPixelBrightnessBus(countPixels, CLKPIN, DATAPIN); #else _pGrb = new NeoPixelBrightnessBus(countPixels, LEDPIN); @@ -152,7 +160,7 @@ public: break; case NeoPixelType_Grbw: - #if defined(USE_APA102) || defined(USE_WS2801) || defined(USE_LPD8806) + #if defined(USE_APA102) || defined(USE_WS2801) || defined(USE_LPD8806) || defined(USE_P9813) _pGrbw = new NeoPixelBrightnessBus(countPixels, CLKPIN, DATAPIN); #else _pGrbw = new NeoPixelBrightnessBus(countPixels, LEDPIN); From b8342f1c9cde82c15bfed4728130451a5487d6f3 Mon Sep 17 00:00:00 2001 From: Travis J Dean Date: Sat, 28 Mar 2020 07:32:02 -0400 Subject: [PATCH 61/90] actually call the setup function. --- wled00/wled00.ino | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/wled00/wled00.ino b/wled00/wled00.ino index ac993bf62..522447d0a 100644 --- a/wled00/wled00.ino +++ b/wled00/wled00.ino @@ -3,9 +3,10 @@ */ #include "wled.h" -WLED wled; +WLED& wled; void setup() { - wled.instance(); // Force creation of static instance + wled = WLED::instance(); + wled.setup(); } void loop() { From f35ab125eccb3fca86d27fb4dd7728bb5812e65d Mon Sep 17 00:00:00 2001 From: Travis J Dean Date: Sat, 28 Mar 2020 08:30:51 -0400 Subject: [PATCH 62/90] Rename files to remove wled_ --- wled00/{wled_alexa.cpp => alexa.cpp} | 8 +- wled00/{wled_alexa.h => alexa.h} | 32 +- wled00/{wled_blynk.cpp => blynk.cpp} | 6 +- wled00/{wled_blynk.h => blynk.h} | 24 +- wled00/{wled_button.cpp => button.cpp} | 8 +- wled00/{wled_button.h => button.h} | 22 +- wled00/{wled_colors.cpp => colors.cpp} | 2 +- wled00/{wled_colors.h => colors.h} | 38 +- wled00/{wled_cronixie.cpp => cronixie.cpp} | 2 +- wled00/{wled_cronixie.h => cronixie.h} | 24 +- wled00/{wled_dmx.cpp => dmx.cpp} | 2 +- wled00/{wled_dmx.h => dmx.h} | 20 +- wled00/{wled_eeprom.cpp => eeprom.cpp} | 10 +- wled00/{wled_eeprom.h => eeprom.h} | 46 +- wled00/{wled_file.cpp => file.cpp} | 6 +- wled00/{wled_file.h => file.h} | 22 +- wled00/{wled_hue.cpp => hue.cpp} | 10 +- wled00/{wled_hue.h => hue.h} | 30 +- wled00/{wled_ir.cpp => ir.cpp} | 8 +- wled00/{wled_ir.h => ir.h} | 46 +- wled00/{wled_json.cpp => json.cpp} | 6 +- wled00/{wled_json.h => json.h} | 40 +- wled00/{wled_led.cpp => led.cpp} | 12 +- wled00/{wled_led.h => led.h} | 36 +- wled00/{wled_mqtt.cpp => mqtt.cpp} | 12 +- wled00/{wled_mqtt.h => mqtt.h} | 16 +- wled00/{wled_notify.cpp => notify.cpp} | 4 +- wled00/{wled_notify.h => notify.h} | 34 +- wled00/{wled_ntp.cpp => ntp.cpp} | 4 +- wled00/{wled_ntp.h => ntp.h} | 35 +- wled00/{wled_overlay.cpp => overlay.cpp} | 6 +- wled00/{wled_overlay.h => overlay.h} | 26 +- wled00/{wled_server.cpp => server.cpp} | 10 +- wled00/{wled_server.h => server.h} | 40 +- wled00/{wled_set.cpp => set.cpp} | 18 +- wled00/{wled_set.h => set.h} | 30 +- wled00/{wled_usermod.cpp => usermod.cpp} | 0 wled00/{wled_usermod.h => usermod.h} | 14 +- wled00/wled.cpp | 1763 ++++++++++---------- wled00/wled.h | 1038 ++++++------ wled00/{wled_xml.cpp => xml.cpp} | 6 +- wled00/{wled_xml.h => xml.h} | 28 +- 42 files changed, 1772 insertions(+), 1772 deletions(-) rename wled00/{wled_alexa.cpp => alexa.cpp} (95%) rename wled00/{wled_alexa.h => alexa.h} (96%) rename wled00/{wled_blynk.cpp => blynk.cpp} (96%) rename wled00/{wled_blynk.h => blynk.h} (96%) rename wled00/{wled_button.cpp => button.cpp} (96%) rename wled00/{wled_button.h => button.h} (94%) rename wled00/{wled_colors.cpp => colors.cpp} (99%) rename wled00/{wled_colors.h => colors.h} (97%) rename wled00/{wled_cronixie.cpp => cronixie.cpp} (99%) rename wled00/{wled_cronixie.h => cronixie.h} (96%) rename wled00/{wled_dmx.cpp => dmx.cpp} (98%) rename wled00/{wled_dmx.h => dmx.h} (96%) rename wled00/{wled_eeprom.cpp => eeprom.cpp} (99%) rename wled00/{wled_eeprom.h => eeprom.h} (97%) rename wled00/{wled_file.cpp => file.cpp} (98%) rename wled00/{wled_file.h => file.h} (95%) rename wled00/{wled_hue.cpp => hue.cpp} (97%) rename wled00/{wled_hue.h => hue.h} (96%) rename wled00/{wled_ir.cpp => ir.cpp} (99%) rename wled00/{wled_ir.h => ir.h} (96%) rename wled00/{wled_json.cpp => json.cpp} (99%) rename wled00/{wled_json.h => json.h} (97%) rename wled00/{wled_led.cpp => led.cpp} (97%) rename wled00/{wled_led.h => led.h} (95%) rename wled00/{wled_mqtt.cpp => mqtt.cpp} (96%) rename wled00/{wled_mqtt.h => mqtt.h} (95%) rename wled00/{wled_notify.cpp => notify.cpp} (99%) rename wled00/{wled_notify.h => notify.h} (96%) rename wled00/{wled_ntp.cpp => ntp.cpp} (99%) rename wled00/{wled_ntp.h => ntp.h} (87%) rename wled00/{wled_overlay.cpp => overlay.cpp} (98%) rename wled00/{wled_overlay.h => overlay.h} (95%) rename wled00/{wled_server.cpp => server.cpp} (99%) rename wled00/{wled_server.h => server.h} (96%) rename wled00/{wled_set.cpp => set.cpp} (98%) rename wled00/{wled_set.h => set.h} (97%) rename wled00/{wled_usermod.cpp => usermod.cpp} (100%) rename wled00/{wled_usermod.h => usermod.h} (94%) rename wled00/{wled_xml.cpp => xml.cpp} (99%) rename wled00/{wled_xml.h => xml.h} (96%) diff --git a/wled00/wled_alexa.cpp b/wled00/alexa.cpp similarity index 95% rename from wled00/wled_alexa.cpp rename to wled00/alexa.cpp index 1eb33e71d..621480d0c 100644 --- a/wled00/wled_alexa.cpp +++ b/wled00/alexa.cpp @@ -1,9 +1,9 @@ -#include "wled_alexa.h" +#include "alexa.h" #include "wled.h" #include "const.h" -#include "wled_led.h" -#include "wled_eeprom.h" -#include "wled_colors.h" +#include "led.h" +#include "eeprom.h" +#include "colors.h" #ifndef WLED_DISABLE_ALEXA void onAlexaChange(EspalexaDevice* dev); diff --git a/wled00/wled_alexa.h b/wled00/alexa.h similarity index 96% rename from wled00/wled_alexa.h rename to wled00/alexa.h index 94ad0342b..c2adfebe3 100644 --- a/wled00/wled_alexa.h +++ b/wled00/alexa.h @@ -1,17 +1,17 @@ -#ifndef WLED_ALEXA_H -#define WLED_ALEXA_H -/* - * Alexa Voice On/Off/Brightness Control. Emulates a Philips Hue bridge to Alexa. - * - * This was put together from these two excellent projects: - * https://github.com/kakopappa/arduino-esp8266-alexa-wemo-switch - * https://github.com/probonopd/ESP8266HueEmulator - */ -#include "src/dependencies/espalexa/EspalexaDevice.h" - -void onAlexaChange(EspalexaDevice* dev); -void alexaInit(); -void handleAlexa(); -void onAlexaChange(EspalexaDevice* dev); - +#ifndef WLED_ALEXA_H +#define WLED_ALEXA_H +/* + * Alexa Voice On/Off/Brightness Control. Emulates a Philips Hue bridge to Alexa. + * + * This was put together from these two excellent projects: + * https://github.com/kakopappa/arduino-esp8266-alexa-wemo-switch + * https://github.com/probonopd/ESP8266HueEmulator + */ +#include "src/dependencies/espalexa/EspalexaDevice.h" + +void onAlexaChange(EspalexaDevice* dev); +void alexaInit(); +void handleAlexa(); +void onAlexaChange(EspalexaDevice* dev); + #endif // WLED_ALEXA_H \ No newline at end of file diff --git a/wled00/wled_blynk.cpp b/wled00/blynk.cpp similarity index 96% rename from wled00/wled_blynk.cpp rename to wled00/blynk.cpp index 2e3d9f7bb..2307a6942 100644 --- a/wled00/wled_blynk.cpp +++ b/wled00/blynk.cpp @@ -1,9 +1,9 @@ -#include "wled_blynk.h" +#include "blynk.h" #include "const.h" #include "wled.h" #include "src/dependencies/blynk/Blynk/BlynkHandlers.h" -#include "wled_led.h" -#include "wled_colors.h" +#include "led.h" +#include "colors.h" uint16_t blHue = 0; byte blSat = 255; diff --git a/wled00/wled_blynk.h b/wled00/blynk.h similarity index 96% rename from wled00/wled_blynk.h rename to wled00/blynk.h index e8c8341b5..cf3a2e225 100644 --- a/wled00/wled_blynk.h +++ b/wled00/blynk.h @@ -1,13 +1,13 @@ -#ifndef WLED_BLYNK_H -#define WLED_BLYNK_H -#include -/* - * Remote light control with the free Blynk app - */ - -void initBlynk(const char* auth); -void handleBlynk(); -void updateBlynk(); -// Unsure if the macro expansions need to accessed through the declaration... TODO - +#ifndef WLED_BLYNK_H +#define WLED_BLYNK_H +#include +/* + * Remote light control with the free Blynk app + */ + +void initBlynk(const char* auth); +void handleBlynk(); +void updateBlynk(); +// Unsure if the macro expansions need to accessed through the declaration... TODO + #endif //WLED_BLYNK_H \ No newline at end of file diff --git a/wled00/wled_button.cpp b/wled00/button.cpp similarity index 96% rename from wled00/wled_button.cpp rename to wled00/button.cpp index 3825cc846..1afb54d57 100644 --- a/wled00/wled_button.cpp +++ b/wled00/button.cpp @@ -1,8 +1,8 @@ -#include "wled_button.h" +#include "button.h" #include "wled.h" -#include "wled_led.h" -#include "wled_eeprom.h" -#include "wled_set.h" +#include "led.h" +#include "eeprom.h" +#include "set.h" /* * Physical IO diff --git a/wled00/wled_button.h b/wled00/button.h similarity index 94% rename from wled00/wled_button.h rename to wled00/button.h index 5f8944773..f974888e7 100644 --- a/wled00/wled_button.h +++ b/wled00/button.h @@ -1,12 +1,12 @@ -#ifndef WLED_BUTTON_H -#define WLED_BUTTON_H -#include -/* - * Physical IO - */ - -void shortPressAction(); -void handleButton(); -void handleIO(); - +#ifndef WLED_BUTTON_H +#define WLED_BUTTON_H +#include +/* + * Physical IO + */ + +void shortPressAction(); +void handleButton(); +void handleIO(); + #endif // WLED_BUTTON_H \ No newline at end of file diff --git a/wled00/wled_colors.cpp b/wled00/colors.cpp similarity index 99% rename from wled00/wled_colors.cpp rename to wled00/colors.cpp index dcd32e574..6f2477574 100644 --- a/wled00/wled_colors.cpp +++ b/wled00/colors.cpp @@ -1,4 +1,4 @@ -#include "wled_colors.h" +#include "colors.h" #include "wled.h" void colorFromUint32(uint32_t in, bool secondary) diff --git a/wled00/wled_colors.h b/wled00/colors.h similarity index 97% rename from wled00/wled_colors.h rename to wled00/colors.h index 917311e78..85e0adb50 100644 --- a/wled00/wled_colors.h +++ b/wled00/colors.h @@ -1,20 +1,20 @@ -#ifndef WLED_COLORS_H -#define WLED_COLORS_H -#include -/* - * Color conversion methods - */ - -void colorFromUint32(uint32_t in, bool secondary = false); -void colorFromUint24(uint32_t in, bool secondary = false); -void relativeChangeWhite(int8_t amount, byte lowerBoundary = 0); -void colorHStoRGB(uint16_t hue, byte sat, byte* rgb); //hue, sat to rgb -void colorCTtoRGB(uint16_t mired, byte* rgb); //white spectrum to rgb - -void colorXYtoRGB(float x, float y, byte* rgb); // only defined if huesync disabled TODO -void colorRGBtoXY(byte* rgb, float* xy); // only defined if huesync disabled TODO - -void colorFromDecOrHexString(byte* rgb, char* in); -void colorRGBtoRGBW(byte* rgb); //rgb to rgbw (http://codewelt.com/rgbw). (RGBW_MODE_LEGACY) - +#ifndef WLED_COLORS_H +#define WLED_COLORS_H +#include +/* + * Color conversion methods + */ + +void colorFromUint32(uint32_t in, bool secondary = false); +void colorFromUint24(uint32_t in, bool secondary = false); +void relativeChangeWhite(int8_t amount, byte lowerBoundary = 0); +void colorHStoRGB(uint16_t hue, byte sat, byte* rgb); //hue, sat to rgb +void colorCTtoRGB(uint16_t mired, byte* rgb); //white spectrum to rgb + +void colorXYtoRGB(float x, float y, byte* rgb); // only defined if huesync disabled TODO +void colorRGBtoXY(byte* rgb, float* xy); // only defined if huesync disabled TODO + +void colorFromDecOrHexString(byte* rgb, char* in); +void colorRGBtoRGBW(byte* rgb); //rgb to rgbw (http://codewelt.com/rgbw). (RGBW_MODE_LEGACY) + #endif //WLED_COLORS_H \ No newline at end of file diff --git a/wled00/wled_cronixie.cpp b/wled00/cronixie.cpp similarity index 99% rename from wled00/wled_cronixie.cpp rename to wled00/cronixie.cpp index 107af752a..e3b951b86 100644 --- a/wled00/wled_cronixie.cpp +++ b/wled00/cronixie.cpp @@ -1,4 +1,4 @@ -#include "wled_cronixie.h" +#include "cronixie.h" #include "wled.h" #ifndef WLED_DISABLE_CRONIXIE diff --git a/wled00/wled_cronixie.h b/wled00/cronixie.h similarity index 96% rename from wled00/wled_cronixie.h rename to wled00/cronixie.h index 9dea69b33..f6eea6b43 100644 --- a/wled00/wled_cronixie.h +++ b/wled00/cronixie.h @@ -1,13 +1,13 @@ -#ifndef WLED_CRONIXIE_H -#define WLED_CRONIXIE_H -#include -/* - * Support for the Cronixie clock - */ - -byte getSameCodeLength(char code, int index, char const cronixieDisplay[]); -void setCronixie(); -void _overlayCronixie(); -void _drawOverlayCronixie(); - +#ifndef WLED_CRONIXIE_H +#define WLED_CRONIXIE_H +#include +/* + * Support for the Cronixie clock + */ + +byte getSameCodeLength(char code, int index, char const cronixieDisplay[]); +void setCronixie(); +void _overlayCronixie(); +void _drawOverlayCronixie(); + #endif // WLED_CRONIXIE_H \ No newline at end of file diff --git a/wled00/wled_dmx.cpp b/wled00/dmx.cpp similarity index 98% rename from wled00/wled_dmx.cpp rename to wled00/dmx.cpp index f620f7779..d024ac713 100644 --- a/wled00/wled_dmx.cpp +++ b/wled00/dmx.cpp @@ -1,4 +1,4 @@ -#include "wled_dmx.h" +#include "dmx.h" #include "wled.h" #ifdef WLED_ENABLE_DMX diff --git a/wled00/wled_dmx.h b/wled00/dmx.h similarity index 96% rename from wled00/wled_dmx.h rename to wled00/dmx.h index 94cbb0d25..707dec915 100644 --- a/wled00/wled_dmx.h +++ b/wled00/dmx.h @@ -1,11 +1,11 @@ -#ifndef WLED_DMX_H -#define WLED_DMX_H -/* - * Support for DMX via MAX485. - * Needs the espdmx library. You might have to change the output pin within the library. Sketchy, i know. - * https://github.com/Rickgg/ESP-Dmx - */ - -void handleDMX(); - +#ifndef WLED_DMX_H +#define WLED_DMX_H +/* + * Support for DMX via MAX485. + * Needs the espdmx library. You might have to change the output pin within the library. Sketchy, i know. + * https://github.com/Rickgg/ESP-Dmx + */ + +void handleDMX(); + #endif //WLED_DMX_H \ No newline at end of file diff --git a/wled00/wled_eeprom.cpp b/wled00/eeprom.cpp similarity index 99% rename from wled00/wled_eeprom.cpp rename to wled00/eeprom.cpp index 04b86289a..68b8c35f9 100644 --- a/wled00/wled_eeprom.cpp +++ b/wled00/eeprom.cpp @@ -1,9 +1,9 @@ -#include "wled_eeprom.h" +#include "eeprom.h" #include "wled.h" -#include "wled_cronixie.h" -#include "wled_ntp.h" -#include "wled_set.h" -#include "wled_led.h" +#include "cronixie.h" +#include "ntp.h" +#include "set.h" +#include "led.h" //eeprom Version code, enables default settings instead of 0 init on update diff --git a/wled00/wled_eeprom.h b/wled00/eeprom.h similarity index 97% rename from wled00/wled_eeprom.h rename to wled00/eeprom.h index 19f61925a..eae52b128 100644 --- a/wled00/wled_eeprom.h +++ b/wled00/eeprom.h @@ -1,23 +1,23 @@ -#ifndef WLED_EPPROM_H -#define WLED_EPPROM_H -#include -/* - * Methods to handle saving and loading to non-volatile memory - * EEPROM Map: https://github.com/Aircoookie/WLED/wiki/EEPROM-Map - */ -#define EEPSIZE 2560 //Maximum is 4096 - -void commit(); -void clearEEPROM(); -void writeStringToEEPROM(uint16_t pos, char* str, uint16_t len); -void readStringFromEEPROM(uint16_t pos, char* str, uint16_t len); -void saveSettingsToEEPROM(); -void loadSettingsFromEEPROM(bool first); -void savedToPresets(); -bool applyPreset(byte index, bool loadBri = true); -void savePreset(byte index, bool persist = true); -void loadMacro(byte index, char* m); -void applyMacro(byte index); -void saveMacro(byte index, String mc, bool persist = true); //only commit on single save, not in settings - -#endif //WLED_EPPROM_H +#ifndef WLED_EPPROM_H +#define WLED_EPPROM_H +#include +/* + * Methods to handle saving and loading to non-volatile memory + * EEPROM Map: https://github.com/Aircoookie/WLED/wiki/EEPROM-Map + */ +#define EEPSIZE 2560 //Maximum is 4096 + +void commit(); +void clearEEPROM(); +void writeStringToEEPROM(uint16_t pos, char* str, uint16_t len); +void readStringFromEEPROM(uint16_t pos, char* str, uint16_t len); +void saveSettingsToEEPROM(); +void loadSettingsFromEEPROM(bool first); +void savedToPresets(); +bool applyPreset(byte index, bool loadBri = true); +void savePreset(byte index, bool persist = true); +void loadMacro(byte index, char* m); +void applyMacro(byte index); +void saveMacro(byte index, String mc, bool persist = true); //only commit on single save, not in settings + +#endif //WLED_EPPROM_H diff --git a/wled00/wled_file.cpp b/wled00/file.cpp similarity index 98% rename from wled00/wled_file.cpp rename to wled00/file.cpp index 81b86d6d3..dba27d4fe 100644 --- a/wled00/wled_file.cpp +++ b/wled00/file.cpp @@ -1,7 +1,7 @@ -#include "wled_file.h" +#include "file.h" #include "wled.h" -#include "wled_led.h" -#include "wled_notify.h" +#include "led.h" +#include "notify.h" //filesystem #ifndef WLED_DISABLE_FILESYSTEM diff --git a/wled00/wled_file.h b/wled00/file.h similarity index 95% rename from wled00/wled_file.h rename to wled00/file.h index 7457ec4cd..90731db67 100644 --- a/wled00/wled_file.h +++ b/wled00/file.h @@ -1,12 +1,12 @@ -#ifndef WLED_FILE_H -#define WLED_FILE_H -#include -#include -/* - * Utility for SPIFFS filesystem & Serial console - */ - -void handleSerial(); -bool handleFileRead(AsyncWebServerRequest*, String path); - +#ifndef WLED_FILE_H +#define WLED_FILE_H +#include +#include +/* + * Utility for SPIFFS filesystem & Serial console + */ + +void handleSerial(); +bool handleFileRead(AsyncWebServerRequest*, String path); + #endif // WLED_FILE_H \ No newline at end of file diff --git a/wled00/wled_hue.cpp b/wled00/hue.cpp similarity index 97% rename from wled00/wled_hue.cpp rename to wled00/hue.cpp index 61756ec0e..484f2bd84 100644 --- a/wled00/wled_hue.cpp +++ b/wled00/hue.cpp @@ -1,9 +1,9 @@ -#include "wled_hue.h" +#include "hue.h" #include "wled.h" -#include "wled_colors.h" -#include "wled_eeprom.h" -#include "wled_notify.h" -#include "wled_led.h" +#include "colors.h" +#include "eeprom.h" +#include "notify.h" +#include "led.h" #ifndef WLED_DISABLE_HUESYNC diff --git a/wled00/wled_hue.h b/wled00/hue.h similarity index 96% rename from wled00/wled_hue.h rename to wled00/hue.h index 8b26f8b6f..7ce629fbe 100644 --- a/wled00/wled_hue.h +++ b/wled00/hue.h @@ -1,16 +1,16 @@ -#ifndef WLED_HUE_H -#define WLED_HUE_H -/* - * Sync to Philips hue lights - */ -#include -class AsyncClient; - -void handleHue(); -void reconnectHue(); -void onHueError(void* arg, AsyncClient* client, int8_t error); -void onHueConnect(void* arg, AsyncClient* client); -void sendHuePoll(); -void onHueData(void* arg, AsyncClient* client, void *data, size_t len); - +#ifndef WLED_HUE_H +#define WLED_HUE_H +/* + * Sync to Philips hue lights + */ +#include +class AsyncClient; + +void handleHue(); +void reconnectHue(); +void onHueError(void* arg, AsyncClient* client, int8_t error); +void onHueConnect(void* arg, AsyncClient* client); +void sendHuePoll(); +void onHueData(void* arg, AsyncClient* client, void *data, size_t len); + #endif //WLED_HUE_H \ No newline at end of file diff --git a/wled00/wled_ir.cpp b/wled00/ir.cpp similarity index 99% rename from wled00/wled_ir.cpp rename to wled00/ir.cpp index f625148eb..fcc0f5dda 100644 --- a/wled00/wled_ir.cpp +++ b/wled00/ir.cpp @@ -1,8 +1,8 @@ -#include "wled_ir.h" +#include "ir.h" #include "wled.h" -#include "wled_led.h" -#include "wled_colors.h" -#include "wled_eeprom.h" +#include "led.h" +#include "colors.h" +#include "eeprom.h" #if defined(WLED_DISABLE_INFRARED) void handleIR(){} diff --git a/wled00/wled_ir.h b/wled00/ir.h similarity index 96% rename from wled00/wled_ir.h rename to wled00/ir.h index 3f819da01..e6ecb9668 100644 --- a/wled00/wled_ir.h +++ b/wled00/ir.h @@ -1,24 +1,24 @@ -#ifndef WLED_IR_H -#define WLED_IR_H -#include -/* - * Infrared sensor support for generic 24/40/44 key RGB remotes - */ - -bool decodeIRCustom(uint32_t code); -void relativeChange(byte* property, int8_t amount, byte lowerBoundary = 0, byte higherBoundary = 0xFF); -void changeEffectSpeed(int8_t amount); -void changeEffectIntensity(int8_t amount); -void decodeIR(uint32_t code); -void decodeIR24(uint32_t code); -void decodeIR24OLD(uint32_t code); -void decodeIR24CT(uint32_t code); -void decodeIR40(uint32_t code); -void decodeIR44(uint32_t code); -void decodeIR21(uint32_t code); -void decodeIR6(uint32_t code); - -void initIR(); -void handleIR(); - +#ifndef WLED_IR_H +#define WLED_IR_H +#include +/* + * Infrared sensor support for generic 24/40/44 key RGB remotes + */ + +bool decodeIRCustom(uint32_t code); +void relativeChange(byte* property, int8_t amount, byte lowerBoundary = 0, byte higherBoundary = 0xFF); +void changeEffectSpeed(int8_t amount); +void changeEffectIntensity(int8_t amount); +void decodeIR(uint32_t code); +void decodeIR24(uint32_t code); +void decodeIR24OLD(uint32_t code); +void decodeIR24CT(uint32_t code); +void decodeIR40(uint32_t code); +void decodeIR44(uint32_t code); +void decodeIR21(uint32_t code); +void decodeIR6(uint32_t code); + +void initIR(); +void handleIR(); + #endif //WLED_IR_H \ No newline at end of file diff --git a/wled00/wled_json.cpp b/wled00/json.cpp similarity index 99% rename from wled00/wled_json.cpp rename to wled00/json.cpp index 17359462d..73659e7ed 100644 --- a/wled00/wled_json.cpp +++ b/wled00/json.cpp @@ -1,7 +1,7 @@ -#include "wled_json.h" +#include "json.h" #include "wled.h" -#include "wled_eeprom.h" -#include "wled_led.h" +#include "eeprom.h" +#include "led.h" void deserializeSegment(JsonObject elem, byte it) { diff --git a/wled00/wled_json.h b/wled00/json.h similarity index 97% rename from wled00/wled_json.h rename to wled00/json.h index a7c2473aa..77d1b616f 100644 --- a/wled00/wled_json.h +++ b/wled00/json.h @@ -1,21 +1,21 @@ -#ifndef WLED_JSON_H -#define WLED_JSON_H -/* - * JSON API (De)serialization - */ -#include -#include "ESPAsyncWebServer.h" -#include "src/dependencies/json/ArduinoJson-v6.h" -#include "src/dependencies/json/AsyncJson-v6.h" -#include "fx.h" -// TODO: AsynicWebServerRequest conflict? - -void deserializeSegment(JsonObject elem, byte it); -bool deserializeState(JsonObject root); -void serializeSegment(JsonObject& root, WS2812FX::Segment& seg, byte id); -void serializeState(JsonObject root); -void serializeInfo(JsonObject root); -void serveJson(AsyncWebServerRequest* request); -void serveLiveLeds(AsyncWebServerRequest* request); - +#ifndef WLED_JSON_H +#define WLED_JSON_H +/* + * JSON API (De)serialization + */ +#include +#include "ESPAsyncWebServer.h" +#include "src/dependencies/json/ArduinoJson-v6.h" +#include "src/dependencies/json/AsyncJson-v6.h" +#include "fx.h" +// TODO: AsynicWebServerRequest conflict? + +void deserializeSegment(JsonObject elem, byte it); +bool deserializeState(JsonObject root); +void serializeSegment(JsonObject& root, WS2812FX::Segment& seg, byte id); +void serializeState(JsonObject root); +void serializeInfo(JsonObject root); +void serveJson(AsyncWebServerRequest* request); +void serveLiveLeds(AsyncWebServerRequest* request); + #endif //WLED_JSON_H \ No newline at end of file diff --git a/wled00/wled_led.cpp b/wled00/led.cpp similarity index 97% rename from wled00/wled_led.cpp rename to wled00/led.cpp index bd657a53b..4a16dcff8 100644 --- a/wled00/wled_led.cpp +++ b/wled00/led.cpp @@ -1,10 +1,10 @@ -#include "wled_led.h" +#include "led.h" #include "wled.h" -#include "wled_notify.h" -#include "wled_blynk.h" -#include "wled_eeprom.h" -#include "wled_mqtt.h" -#include "wled_colors.h" +#include "notify.h" +#include "blynk.h" +#include "eeprom.h" +#include "mqtt.h" +#include "colors.h" void setValuesFromMainSeg() { diff --git a/wled00/wled_led.h b/wled00/led.h similarity index 95% rename from wled00/wled_led.h rename to wled00/led.h index aebe95e48..d5eb97a8d 100644 --- a/wled00/wled_led.h +++ b/wled00/led.h @@ -1,19 +1,19 @@ -#ifndef WLED_LED_H -#define WLED_LED_H -#include -/* - * LED methods - */ - -void setValuesFromMainSeg(); -void resetTimebase(); -void toggleOnOff(); -void setAllLeds(); -void setLedsStandard(bool justColors = false); -bool colorChanged(); -void colorUpdated(int callMode); -void updateInterfaces(uint8_t callMode); -void handleTransitions(); -void handleNightlight(); - +#ifndef WLED_LED_H +#define WLED_LED_H +#include +/* + * LED methods + */ + +void setValuesFromMainSeg(); +void resetTimebase(); +void toggleOnOff(); +void setAllLeds(); +void setLedsStandard(bool justColors = false); +bool colorChanged(); +void colorUpdated(int callMode); +void updateInterfaces(uint8_t callMode); +void handleTransitions(); +void handleNightlight(); + #endif \ No newline at end of file diff --git a/wled00/wled_mqtt.cpp b/wled00/mqtt.cpp similarity index 96% rename from wled00/wled_mqtt.cpp rename to wled00/mqtt.cpp index 5e96a18f2..b84184af0 100644 --- a/wled00/wled_mqtt.cpp +++ b/wled00/mqtt.cpp @@ -1,10 +1,10 @@ -#include "wled_mqtt.h" +#include "mqtt.h" #include "wled.h" -#include "wled_notify.h" -#include "wled_led.h" -#include "wled_colors.h" -#include "wled_xml.h" -#include "wled_set.h" +#include "notify.h" +#include "led.h" +#include "colors.h" +#include "xml.h" +#include "set.h" #ifdef WLED_ENABLE_MQTT #define MQTT_KEEP_ALIVE_TIME 60 // contact the MQTT broker every 60 seconds diff --git a/wled00/wled_mqtt.h b/wled00/mqtt.h similarity index 95% rename from wled00/wled_mqtt.h rename to wled00/mqtt.h index f1c7160e5..de602b981 100644 --- a/wled00/wled_mqtt.h +++ b/wled00/mqtt.h @@ -1,9 +1,9 @@ -#ifndef WLED_MQTT_H -#define WLED_MQTT_H -/* - * MQTT communication protocol for home automation - */ -bool initMqtt(); -void publishMqtt(); - +#ifndef WLED_MQTT_H +#define WLED_MQTT_H +/* + * MQTT communication protocol for home automation + */ +bool initMqtt(); +void publishMqtt(); + #endif //WLED_MQTT_H \ No newline at end of file diff --git a/wled00/wled_notify.cpp b/wled00/notify.cpp similarity index 99% rename from wled00/wled_notify.cpp rename to wled00/notify.cpp index 7913b4525..30adfee01 100644 --- a/wled00/wled_notify.cpp +++ b/wled00/notify.cpp @@ -1,7 +1,7 @@ -#include "wled_notify.h" +#include "notify.h" #include "wled.h" #include "src/dependencies/e131/ESPAsyncE131.h" -#include "wled_led.h" +#include "led.h" #define WLEDPACKETSIZE 29 #define UDP_IN_MAXSIZE 1472 diff --git a/wled00/wled_notify.h b/wled00/notify.h similarity index 96% rename from wled00/wled_notify.h rename to wled00/notify.h index 04c65aa43..a0fbfde2c 100644 --- a/wled00/wled_notify.h +++ b/wled00/notify.h @@ -1,18 +1,18 @@ -#ifndef WLED_NOTIFY_H -#define WLED_NOTIFY_H -#include -#include "src/dependencies/e131/ESPAsyncE131.h" -#include "const.h" -/* - * UDP notifier - */ -//union e131_packet_t; // Will this compile? -class IPAddress; - -void notify(byte callMode, bool followUp=false); -void arlsLock(uint32_t timeoutMs, byte md = REALTIME_MODE_GENERIC); -void handleE131Packet(e131_packet_t* p, IPAddress clientIP); -void handleNotifications(); -void setRealtimePixel(uint16_t i, byte r, byte g, byte b, byte w); - +#ifndef WLED_NOTIFY_H +#define WLED_NOTIFY_H +#include +#include "src/dependencies/e131/ESPAsyncE131.h" +#include "const.h" +/* + * UDP notifier + */ +//union e131_packet_t; // Will this compile? +class IPAddress; + +void notify(byte callMode, bool followUp=false); +void arlsLock(uint32_t timeoutMs, byte md = REALTIME_MODE_GENERIC); +void handleE131Packet(e131_packet_t* p, IPAddress clientIP); +void handleNotifications(); +void setRealtimePixel(uint16_t i, byte r, byte g, byte b, byte w); + #endif // WLED_NOTIFY_H \ No newline at end of file diff --git a/wled00/wled_ntp.cpp b/wled00/ntp.cpp similarity index 99% rename from wled00/wled_ntp.cpp rename to wled00/ntp.cpp index d8d9c38a2..3435760b7 100644 --- a/wled00/wled_ntp.cpp +++ b/wled00/ntp.cpp @@ -1,6 +1,6 @@ -#include "wled_ntp.h" +#include "ntp.h" #include "wled.h" -#include "wled_eeprom.h" +#include "eeprom.h" TimeChangeRule UTCr = {Last, Sun, Mar, 1, 0}; // UTC Timezone tzUTC(UTCr, UTCr); diff --git a/wled00/wled_ntp.h b/wled00/ntp.h similarity index 87% rename from wled00/wled_ntp.h rename to wled00/ntp.h index be683d143..9029661dd 100644 --- a/wled00/wled_ntp.h +++ b/wled00/ntp.h @@ -1,19 +1,18 @@ -#ifndef WLED_NTP_H -#define WLED_NTP_H -#include -#include "timezone/Timezone.h" -/* - * Acquires time from NTP server - */ - -void handleNetworkTime(); -void sendNTPPacket(); -bool checkNTPResponse(); -void updateLocalTime(); -void getTimeString(char* out); -bool checkCountdown(); -void setCountdown(); -byte weekdayMondayFirst(); -void checkTimers(); - +#ifndef WLED_NTP_H +#define WLED_NTP_H +#include +/* + * Acquires time from NTP server + */ + +void handleNetworkTime(); +void sendNTPPacket(); +bool checkNTPResponse(); +void updateLocalTime(); +void getTimeString(char* out); +bool checkCountdown(); +void setCountdown(); +byte weekdayMondayFirst(); +void checkTimers(); + #endif // WLED_NTP_H \ No newline at end of file diff --git a/wled00/wled_overlay.cpp b/wled00/overlay.cpp similarity index 98% rename from wled00/wled_overlay.cpp rename to wled00/overlay.cpp index 8cb707866..ccdd3d0cd 100644 --- a/wled00/wled_overlay.cpp +++ b/wled00/overlay.cpp @@ -1,7 +1,7 @@ -#include "wled_overlay.h" +#include "overlay.h" #include "wled.h" -#include "wled_cronixie.h" -#include "wled_ntp.h" +#include "cronixie.h" +#include "ntp.h" void initCronixie() { diff --git a/wled00/wled_overlay.h b/wled00/overlay.h similarity index 95% rename from wled00/wled_overlay.h rename to wled00/overlay.h index ec18f45b4..b403a6e23 100644 --- a/wled00/wled_overlay.h +++ b/wled00/overlay.h @@ -1,14 +1,14 @@ -#ifndef WLED_OVERLAYS_H -#define WLED_OVERLAYS_H -#include -/* - * Used to draw clock overlays over the strip - */ - -void initCronixie(); -void handleOverlays(); -void handleOverlayDraw(); -void _overlayAnalogCountdown(); -void _overlayAnalogClock(); - +#ifndef WLED_OVERLAYS_H +#define WLED_OVERLAYS_H +#include +/* + * Used to draw clock overlays over the strip + */ + +void initCronixie(); +void handleOverlays(); +void handleOverlayDraw(); +void _overlayAnalogCountdown(); +void _overlayAnalogClock(); + #endif // WLED_OVERLAY_H \ No newline at end of file diff --git a/wled00/wled_server.cpp b/wled00/server.cpp similarity index 99% rename from wled00/wled_server.cpp rename to wled00/server.cpp index 542abea4c..3249466cf 100644 --- a/wled00/wled_server.cpp +++ b/wled00/server.cpp @@ -1,9 +1,9 @@ -#include "wled_server.h" +#include "server.h" #include "wled.h" -#include "wled_file.h" -#include "wled_set.h" -#include "wled_json.h" -#include "wled_xml.h" +#include "file.h" +#include "set.h" +#include "json.h" +#include "xml.h" //Is this an IP? diff --git a/wled00/wled_server.h b/wled00/server.h similarity index 96% rename from wled00/wled_server.h rename to wled00/server.h index 46adc9629..ff7133d63 100644 --- a/wled00/wled_server.h +++ b/wled00/server.h @@ -1,21 +1,21 @@ -#ifndef WLED_SERVER_H -#define WLED_SERVER_H -#include -/* - * Server page declarations - */ -class AsyncWebServerRequest; - - -bool isIp(String str); -bool captivePortal(AsyncWebServerRequest *request); -void initServer(); -void serveIndexOrWelcome(AsyncWebServerRequest *request); -void serveIndex(AsyncWebServerRequest* request); -String msgProcessor(const String& var); -void serveMessage(AsyncWebServerRequest* request, uint16_t code, String headl, String subl="", byte optionT=255); -String settingsProcessor(const String& var); -String dmxProcessor(const String& var); -void serveSettings(AsyncWebServerRequest* request); - +#ifndef WLED_SERVER_H +#define WLED_SERVER_H +#include +/* + * Server page declarations + */ +class AsyncWebServerRequest; + + +bool isIp(String str); +bool captivePortal(AsyncWebServerRequest *request); +void initServer(); +void serveIndexOrWelcome(AsyncWebServerRequest *request); +void serveIndex(AsyncWebServerRequest* request); +String msgProcessor(const String& var); +void serveMessage(AsyncWebServerRequest* request, uint16_t code, String headl, String subl="", byte optionT=255); +String settingsProcessor(const String& var); +String dmxProcessor(const String& var); +void serveSettings(AsyncWebServerRequest* request); + #endif //WLED_SERVER_H \ No newline at end of file diff --git a/wled00/wled_set.cpp b/wled00/set.cpp similarity index 98% rename from wled00/wled_set.cpp rename to wled00/set.cpp index 66d9ff1ec..1dfeb19d0 100644 --- a/wled00/wled_set.cpp +++ b/wled00/set.cpp @@ -1,13 +1,13 @@ -#include "wled_set.h" +#include "set.h" #include "wled.h" -#include "wled_colors.h" -#include "wled_hue.h" -#include "wled_led.h" -#include "wled_blynk.h" -#include "wled_eeprom.h" -#include "wled_alexa.h" -#include "wled_cronixie.h" -#include "wled_xml.h" +#include "colors.h" +#include "hue.h" +#include "led.h" +#include "blynk.h" +#include "eeprom.h" +#include "alexa.h" +#include "cronixie.h" +#include "xml.h" void _setRandomColor(bool _sec,bool fromButton) { diff --git a/wled00/wled_set.h b/wled00/set.h similarity index 97% rename from wled00/wled_set.h rename to wled00/set.h index ebee8c928..6bc7dbd6c 100644 --- a/wled00/wled_set.h +++ b/wled00/set.h @@ -1,16 +1,16 @@ -#ifndef WLED_SET_H -#define WLED_SET_H -#include -#include -/* - * Receives client input - */ - -void _setRandomColor(bool _sec,bool fromButton=false); -bool isAsterisksOnly(const char* str, byte maxLen); -void handleSettingsSet(AsyncWebServerRequest *request, byte subPage); -bool handleSet(AsyncWebServerRequest *request, const String& req); -int getNumVal(const String* req, uint16_t pos); -bool updateVal(const String* req, const char* key, byte* val, byte minv=0, byte maxv=255); - +#ifndef WLED_SET_H +#define WLED_SET_H +#include +#include +/* + * Receives client input + */ + +void _setRandomColor(bool _sec,bool fromButton=false); +bool isAsterisksOnly(const char* str, byte maxLen); +void handleSettingsSet(AsyncWebServerRequest *request, byte subPage); +bool handleSet(AsyncWebServerRequest *request, const String& req); +int getNumVal(const String* req, uint16_t pos); +bool updateVal(const String* req, const char* key, byte* val, byte minv=0, byte maxv=255); + #endif // WLED_SET_H \ No newline at end of file diff --git a/wled00/wled_usermod.cpp b/wled00/usermod.cpp similarity index 100% rename from wled00/wled_usermod.cpp rename to wled00/usermod.cpp diff --git a/wled00/wled_usermod.h b/wled00/usermod.h similarity index 94% rename from wled00/wled_usermod.h rename to wled00/usermod.h index 61ef6b785..8269c6e92 100644 --- a/wled00/wled_usermod.h +++ b/wled00/usermod.h @@ -1,8 +1,8 @@ -#ifndef WLED_USERMOD_H -#define WLED_USERMOD_H - -void userSetup(); -void userConnected(); -void userLoop(); - +#ifndef WLED_USERMOD_H +#define WLED_USERMOD_H + +void userSetup(); +void userConnected(); +void userLoop(); + #endif // WLED_USERMOD_H \ No newline at end of file diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 3b3d6936d..106f503b2 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -1,882 +1,883 @@ -#include "wled.h" -#include -#include "wled_led.h" -#include "wled_ir.h" -#include "wled_notify.h" -#include "wled_alexa.h" -#include "wled_overlay.h" -#include "wled_file.h" -#include "wled_button.h" -#include "wled_ntp.h" -#include "wled_usermod.h" -#include "wled_blynk.h" -#include "wled_hue.h" -#include "wled_mqtt.h" -#include "wled_eeprom.h" -#include "wled_server.h" -// Global Variable definitions -char versionString[] = "0.9.1"; - -//AP and OTA default passwords (for maximum change them!) -char apPass[65] = DEFAULT_AP_PASS; -char otaPass[33] = DEFAULT_OTA_PASS; - -//Hardware CONFIG (only changeble HERE, not at runtime) -//LED strip pin, button pin and IR pin changeable in NpbWrapper.h! - -byte auxDefaultState = 0; //0: input 1: high 2: low -byte auxTriggeredState = 0; //0: input 1: high 2: low -char ntpServerName[33] = "0.wled.pool.ntp.org"; //NTP server to use - -//WiFi CONFIG (all these can be changed via web UI, no need to set them here) -char clientSSID[33] = CLIENT_SSID; -char clientPass[65] = CLIENT_PASS; -char cmDNS[33] = "x"; //mDNS address (placeholder, will be replaced by wledXXXXXXXXXXXX.local) -char apSSID[33] = ""; //AP off by default (unless setup) -byte apChannel = 1; //2.4GHz WiFi AP channel (1-13) -byte apHide = 0; //hidden AP SSID -byte apBehavior = AP_BEHAVIOR_BOOT_NO_CONN; //access point opens when no connection after boot by default -IPAddress staticIP(0, 0, 0, 0); //static IP of ESP -IPAddress staticGateway(0, 0, 0, 0); //gateway (router) IP -IPAddress staticSubnet(255, 255, 255, 0); //most common subnet in home networks -bool noWifiSleep = false; //disabling modem sleep modes will increase heat output and power usage, but may help with connection issues - -//LED CONFIG -uint16_t ledCount = 30; //overcurrent prevented by ABL -bool useRGBW = false; //SK6812 strips can contain an extra White channel -#define ABL_MILLIAMPS_DEFAULT 850; //auto lower brightness to stay close to milliampere limit -bool turnOnAtBoot = true; //turn on LEDs at power-up -byte bootPreset = 0; //save preset to load after power-up - -byte col[]{255, 160, 0, 0}; //current RGB(W) primary color. col[] should be updated if you want to change the color. -byte colSec[]{0, 0, 0, 0}; //current RGB(W) secondary color -byte briS = 128; //default brightness - -byte nightlightTargetBri = 0; //brightness after nightlight is over -byte nightlightDelayMins = 60; -bool nightlightFade = true; //if enabled, light will gradually dim towards the target bri. Otherwise, it will instantly set after delay over -bool nightlightColorFade = false; //if enabled, light will gradually fade color from primary to secondary color. -bool fadeTransition = true; //enable crossfading color transition -uint16_t transitionDelay = 750; //default crossfade duration in ms - -bool skipFirstLed = false; //ignore first LED in strip (useful if you need the LED as signal repeater) -byte briMultiplier = 100; //% of brightness to set (to limit power, if you set it to 50 and set bri to 255, actual brightness will be 127) - -//User Interface CONFIG -char serverDescription[33] = "WLED"; //Name of module -bool syncToggleReceive = false; //UIs which only have a single button for sync should toggle send+receive if this is true, only send otherwise - -//Sync CONFIG -bool buttonEnabled = true; -byte irEnabled = 0; //Infrared receiver - -uint16_t udpPort = 21324; //WLED notifier default port -uint16_t udpRgbPort = 19446; //Hyperion port - -bool receiveNotificationBrightness = true; //apply brightness from incoming notifications -bool receiveNotificationColor = true; //apply color -bool receiveNotificationEffects = true; //apply effects setup -bool notifyDirect = false; //send notification if change via UI or HTTP API -bool notifyButton = false; //send if updated by button or infrared remote -bool notifyAlexa = false; //send notification if updated via Alexa -bool notifyMacro = false; //send notification for macro -bool notifyHue = true; //send notification if Hue light changes -bool notifyTwice = false; //notifications use UDP: enable if devices don't sync reliably - -bool alexaEnabled = true; //enable device discovery by Amazon Echo -char alexaInvocationName[33] = "Light"; //speech control name of device. Choose something voice-to-text can understand - -char blynkApiKey[36] = ""; //Auth token for Blynk server. If empty, no connection will be made - -uint16_t realtimeTimeoutMs = 2500; //ms timeout of realtime mode before returning to normal mode -int arlsOffset = 0; //realtime LED offset -bool receiveDirect = true; //receive UDP realtime -bool arlsDisableGammaCorrection = true; //activate if gamma correction is handled by the source -bool arlsForceMaxBri = false; //enable to force max brightness if source has very dark colors that would be black - -#define E131_MAX_UNIVERSE_COUNT 9 -uint16_t e131Universe = 1; //settings for E1.31 (sACN) protocol (only DMX_MODE_MULTIPLE_* can span over consequtive universes) -uint8_t DMXMode = DMX_MODE_MULTIPLE_RGB; //DMX mode (s.a.) -uint16_t DMXAddress = 1; //DMX start address of fixture, a.k.a. first Channel [for E1.31 (sACN) protocol] -uint8_t DMXOldDimmer = 0; //only update brightness on change -uint8_t e131LastSequenceNumber[E131_MAX_UNIVERSE_COUNT]; //to detect packet loss -bool e131Multicast = false; //multicast or unicast -bool e131SkipOutOfSequence = false; //freeze instead of flickering - -bool mqttEnabled = false; -char mqttDeviceTopic[33] = ""; //main MQTT topic (individual per device, default is wled/mac) -char mqttGroupTopic[33] = "wled/all"; //second MQTT topic (for example to group devices) -char mqttServer[33] = ""; //both domains and IPs should work (no SSL) -char mqttUser[41] = ""; //optional: username for MQTT auth -char mqttPass[41] = ""; //optional: password for MQTT auth -char mqttClientID[41] = ""; //override the client ID -uint16_t mqttPort = 1883; - -bool huePollingEnabled = false; //poll hue bridge for light state -uint16_t huePollIntervalMs = 2500; //low values (< 1sec) may cause lag but offer quicker response -char hueApiKey[47] = "api"; //key token will be obtained from bridge -byte huePollLightId = 1; //ID of hue lamp to sync to. Find the ID in the hue app ("about" section) -IPAddress hueIP = (0, 0, 0, 0); //IP address of the bridge -bool hueApplyOnOff = true; -bool hueApplyBri = true; -bool hueApplyColor = true; - -//Time CONFIG -bool ntpEnabled = false; //get internet time. Only required if you use clock overlays or time-activated macros -bool useAMPM = false; //12h/24h clock format -byte currentTimezone = 0; //Timezone ID. Refer to timezones array in wled10_ntp.ino -int utcOffsetSecs = 0; //Seconds to offset from UTC before timzone calculation - -byte overlayDefault = 0; //0: no overlay 1: analog clock 2: single-digit clocl 3: cronixie -byte overlayMin = 0, overlayMax = ledCount - 1; //boundaries of overlay mode - -byte analogClock12pixel = 0; //The pixel in your strip where "midnight" would be -bool analogClockSecondsTrail = false; //Display seconds as trail of LEDs instead of a single pixel -bool analogClock5MinuteMarks = false; //Light pixels at every 5-minute position - -char cronixieDisplay[7] = "HHMMSS"; //Cronixie Display mask. See wled13_cronixie.ino -bool cronixieBacklight = true; //Allow digits to be back-illuminated - -bool countdownMode = false; //Clock will count down towards date -byte countdownYear = 20, countdownMonth = 1; //Countdown target date, year is last two digits -byte countdownDay = 1, countdownHour = 0; -byte countdownMin = 0, countdownSec = 0; - -byte macroBoot = 0; //macro loaded after startup -byte macroNl = 0; //after nightlight delay over -byte macroCountdown = 0; -byte macroAlexaOn = 0, macroAlexaOff = 0; -byte macroButton = 0, macroLongPress = 0, macroDoublePress = 0; - -//Security CONFIG -bool otaLock = false; //prevents OTA firmware updates without password. ALWAYS enable if system exposed to any public networks -bool wifiLock = false; //prevents access to WiFi settings when OTA lock is enabled -bool aOtaEnabled = true; //ArduinoOTA allows easy updates directly from the IDE. Careful, it does not auto-disable when OTA lock is on - -uint16_t userVar0 = 0, userVar1 = 0; - -#ifdef WLED_ENABLE_DMX -//dmx CONFIG -byte DMXChannels = 7; // number of channels per fixture -byte DMXFixtureMap[15] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; -// assigns the different channels to different functions. See wled21_dmx.ino for more information. -uint16_t DMXGap = 10; // gap between the fixtures. makes addressing easier because you don't have to memorize odd numbers when climbing up onto a rig. -uint16_t DMXStart = 10; // start address of the first fixture -#endif - -//internal global variable declarations -//wifi -bool apActive = false; -bool forceReconnect = false; -uint32_t lastReconnectAttempt = 0; -bool interfacesInited = false; -bool wasConnected = false; - -//color -byte colOld[]{0, 0, 0, 0}; //color before transition -byte colT[]{0, 0, 0, 0}; //color that is currently displayed on the LEDs -byte colIT[]{0, 0, 0, 0}; //color that was last sent to LEDs -byte colSecT[]{0, 0, 0, 0}; -byte colSecOld[]{0, 0, 0, 0}; -byte colSecIT[]{0, 0, 0, 0}; - -byte lastRandomIndex = 0; //used to save last random color so the new one is not the same - -//transitions -bool transitionActive = false; -uint16_t transitionDelayDefault = transitionDelay; -uint16_t transitionDelayTemp = transitionDelay; -unsigned long transitionStartTime; -float tperLast = 0; //crossfade transition progress, 0.0f - 1.0f -bool jsonTransitionOnce = false; - -//nightlight -bool nightlightActive = false; -bool nightlightActiveOld = false; -uint32_t nightlightDelayMs = 10; -uint8_t nightlightDelayMinsDefault = nightlightDelayMins; -unsigned long nightlightStartTime; -byte briNlT = 0; //current nightlight brightness -byte colNlT[]{0, 0, 0, 0}; //current nightlight color - -//brightness -unsigned long lastOnTime = 0; -bool offMode = !turnOnAtBoot; -byte bri = briS; -byte briOld = 0; -byte briT = 0; -byte briIT = 0; -byte briLast = 128; //brightness before turned off. Used for toggle function -byte whiteLast = 128; //white channel before turned off. Used for toggle function - -//button -bool buttonPressedBefore = false; -bool buttonLongPressed = false; -unsigned long buttonPressedTime = 0; -unsigned long buttonWaitTime = 0; - -//notifications -bool notifyDirectDefault = notifyDirect; -bool receiveNotifications = true; -unsigned long notificationSentTime = 0; -byte notificationSentCallMode = NOTIFIER_CALL_MODE_INIT; -bool notificationTwoRequired = false; - -//effects -byte effectCurrent = 0; -byte effectSpeed = 128; -byte effectIntensity = 128; -byte effectPalette = 0; - -//network -bool udpConnected = false, udpRgbConnected = false; - -//ui style -bool showWelcomePage = false; - -//hue -byte hueError = HUE_ERROR_INACTIVE; -//uint16_t hueFailCount = 0; -float hueXLast = 0, hueYLast = 0; -uint16_t hueHueLast = 0, hueCtLast = 0; -byte hueSatLast = 0, hueBriLast = 0; -unsigned long hueLastRequestSent = 0; -bool hueAuthRequired = false; -bool hueReceived = false; -bool hueStoreAllowed = false, hueNewKey = false; - -//overlays -byte overlayCurrent = overlayDefault; -byte overlaySpeed = 200; -unsigned long overlayRefreshMs = 200; -unsigned long overlayRefreshedTime; - -//cronixie -byte dP[]{0, 0, 0, 0, 0, 0}; -bool cronixieInit = false; - -//countdown -unsigned long countdownTime = 1514764800L; -bool countdownOverTriggered = true; - -//timer -byte lastTimerMinute = 0; -byte timerHours[] = {0, 0, 0, 0, 0, 0, 0, 0}; -byte timerMinutes[] = {0, 0, 0, 0, 0, 0, 0, 0}; -byte timerMacro[] = {0, 0, 0, 0, 0, 0, 0, 0}; -byte timerWeekday[] = {255, 255, 255, 255, 255, 255, 255, 255}; //weekdays to activate on -//bit pattern of arr elem: 0b11111111: sun,sat,fri,thu,wed,tue,mon,validity - -//blynk -bool blynkEnabled = false; - -//preset cycling -bool presetCyclingEnabled = false; -byte presetCycleMin = 1, presetCycleMax = 5; -uint16_t presetCycleTime = 1250; -unsigned long presetCycledTime = 0; -byte presetCycCurr = presetCycleMin; -bool presetApplyBri = true; -bool saveCurrPresetCycConf = false; - -//realtime -byte realtimeMode = REALTIME_MODE_INACTIVE; -IPAddress realtimeIP = (0, 0, 0, 0); -unsigned long realtimeTimeout = 0; - -//mqtt -long lastMqttReconnectAttempt = 0; -long lastInterfaceUpdate = 0; -byte interfaceUpdateCallMode = NOTIFIER_CALL_MODE_INIT; -char mqttStatusTopic[40] = ""; //this must be global because of async handlers - -#if AUXPIN >= 0 - //auxiliary debug pin -byte auxTime = 0; -unsigned long auxStartTime = 0; -bool auxActive = false, auxActiveBefore = false; -#endif - -//alexa udp -String escapedMac; -#ifndef WLED_DISABLE_ALEXA -Espalexa espalexa; -EspalexaDevice *espalexaDevice; -#endif - -//dns server -DNSServer dnsServer; - -//network time -bool ntpConnected = false; -time_t local = 0; -unsigned long ntpLastSyncTime = 999000000L; -unsigned long ntpPacketSentTime = 999000000L; -IPAddress ntpServerIP; -uint16_t ntpLocalPort = 2390; -#define NTP_PACKET_SIZE 48 - -//maximum number of LEDs - MAX_LEDS is coming from the JSON response getting too big, MAX_LEDS_DMA will become a timing issue -#define MAX_LEDS 1500 -#define MAX_LEDS_DMA 500 - -//string temp buffer (now stored in stack locally) -#define OMAX 2048 -char *obuf; -uint16_t olen = 0; - -//presets -uint16_t savedPresets = 0; -int8_t currentPreset = -1; -bool isPreset = false; - -byte errorFlag = 0; - -String messageHead, messageSub; -byte optionType; - -bool doReboot = false; //flag to initiate reboot from async handlers -bool doPublishMqtt = false; - -//server library objects -AsyncWebServer server(80); -AsyncClient *hueClient = NULL; -AsyncMqttClient *mqtt = NULL; - -//function prototypes -void colorFromUint32(uint32_t, bool = false); -void serveMessage(AsyncWebServerRequest *, uint16_t, String, String, byte); -void handleE131Packet(e131_packet_t *, IPAddress); -void arlsLock(uint32_t, byte); -void handleOverlayDraw(); - -//udp interface objects -WiFiUDP notifierUdp, rgbUdp; -WiFiUDP ntpUdp; -ESPAsyncE131 e131(handleE131Packet); -bool e131NewData = false; - -//led fx library object -WS2812FX strip = WS2812FX(); - -#define WLED_CONNECTED (WiFi.status() == WL_CONNECTED) -#define WLED_WIFI_CONFIGURED (strlen(clientSSID) >= 1 && strcmp(clientSSID, DEFAULT_CLIENT_SSID) != 0) - -//debug macros -#ifdef WLED_DEBUG -#define DEBUG_PRINT(x) Serial.print(x) -#define DEBUG_PRINTLN(x) Serial.println(x) -#define DEBUG_PRINTF(x) Serial.printf(x) -unsigned long debugTime = 0; -int lastWifiState = 3; -unsigned long wifiStateChangedTime = 0; -int loops = 0; -#else -#define DEBUG_PRINT(x) -#define DEBUG_PRINTLN(x) -#define DEBUG_PRINTF(x) -#endif - - - -WLED::WLED() { - -} - -//turns all LEDs off and restarts ESP -void WLED::reset() -{ - briT = 0; - long dly = millis(); - while (millis() - dly < 250) - { - yield(); //enough time to send response to client - } - setAllLeds(); - DEBUG_PRINTLN("MODULE RESET"); - ESP.restart(); -} - -bool oappendi(int i) -{ - char s[11]; - sprintf(s, "%ld", i); - return oappend(s); -} - -bool oappend(const char *txt) -{ - uint16_t len = strlen(txt); - if (olen + len >= OMAX) - return false; //buffer full - strcpy(obuf + olen, txt); - olen += len; - return true; -} - -void WLED::loop() -{ - handleIR(); //2nd call to function needed for ESP32 to return valid results -- should be good for ESP8266, too - handleConnection(); - handleSerial(); - handleNotifications(); - handleTransitions(); -#ifdef WLED_ENABLE_DMX - handleDMX(); -#endif - userLoop(); - - yield(); - handleIO(); - handleIR(); - handleNetworkTime(); - handleAlexa(); - - handleOverlays(); - yield(); -#ifdef WLED_USE_ANALOG_LEDS - strip.setRgbwPwm(); -#endif - - if (doReboot) - reset(); - - if (!realtimeMode) //block stuff if WARLS/Adalight is enabled - { - if (apActive) - dnsServer.processNextRequest(); -#ifndef WLED_DISABLE_OTA - if (WLED_CONNECTED && aOtaEnabled) - ArduinoOTA.handle(); -#endif - handleNightlight(); - yield(); - - handleHue(); - handleBlynk(); - - yield(); - if (!offMode) - strip.service(); - } - yield(); -#ifdef ESP8266 - MDNS.update(); -#endif - if (millis() - lastMqttReconnectAttempt > 30000) - initMqtt(); - -//DEBUG serial logging -#ifdef WLED_DEBUG - if (millis() - debugTime > 9999) - { - DEBUG_PRINTLN("---DEBUG INFO---"); - DEBUG_PRINT("Runtime: "); - DEBUG_PRINTLN(millis()); - DEBUG_PRINT("Unix time: "); - DEBUG_PRINTLN(now()); - DEBUG_PRINT("Free heap: "); - DEBUG_PRINTLN(ESP.getFreeHeap()); - DEBUG_PRINT("Wifi state: "); - DEBUG_PRINTLN(WiFi.status()); - if (WiFi.status() != lastWifiState) - { - wifiStateChangedTime = millis(); - } - lastWifiState = WiFi.status(); - DEBUG_PRINT("State time: "); - DEBUG_PRINTLN(wifiStateChangedTime); - DEBUG_PRINT("NTP last sync: "); - DEBUG_PRINTLN(ntpLastSyncTime); - DEBUG_PRINT("Client IP: "); - DEBUG_PRINTLN(WiFi.localIP()); - DEBUG_PRINT("Loops/sec: "); - DEBUG_PRINTLN(loops / 10); - loops = 0; - debugTime = millis(); - } - loops++; -#endif // WLED_DEBU -} - -void WLED::wledInit() -{ - EEPROM.begin(EEPSIZE); - ledCount = EEPROM.read(229) + ((EEPROM.read(398) << 8) & 0xFF00); - if (ledCount > MAX_LEDS || ledCount == 0) - ledCount = 30; - -#ifdef ESP8266 -#if LEDPIN == 3 - if (ledCount > MAX_LEDS_DMA) - ledCount = MAX_LEDS_DMA; //DMA method uses too much ram -#endif -#endif - Serial.begin(115200); - Serial.setTimeout(50); - DEBUG_PRINTLN(); - DEBUG_PRINT("---WLED "); - DEBUG_PRINT(versionString); - DEBUG_PRINT(" "); - DEBUG_PRINT(VERSION); - DEBUG_PRINTLN(" INIT---"); -#ifdef ARDUINO_ARCH_ESP32 - DEBUG_PRINT("esp32 "); - DEBUG_PRINTLN(ESP.getSdkVersion()); -#else - DEBUG_PRINT("esp8266 "); - DEBUG_PRINTLN(ESP.getCoreVersion()); -#endif - int heapPreAlloc = ESP.getFreeHeap(); - DEBUG_PRINT("heap "); - DEBUG_PRINTLN(ESP.getFreeHeap()); - - strip.init(EEPROM.read(372), ledCount, EEPROM.read(2204)); //init LEDs quickly - strip.setBrightness(0); - - DEBUG_PRINT("LEDs inited. heap usage ~"); - DEBUG_PRINTLN(heapPreAlloc - ESP.getFreeHeap()); - -#ifndef WLED_DISABLE_FILESYSTEM -#ifdef ARDUINO_ARCH_ESP32 - SPIFFS.begin(true); -#endif - SPIFFS.begin(); -#endif - - DEBUG_PRINTLN("Load EEPROM"); - loadSettingsFromEEPROM(true); - beginStrip(); - userSetup(); - if (strcmp(clientSSID, DEFAULT_CLIENT_SSID) == 0) - showWelcomePage = true; - WiFi.persistent(false); - - if (macroBoot > 0) - applyMacro(macroBoot); - Serial.println("Ada"); - - //generate module IDs - escapedMac = WiFi.macAddress(); - escapedMac.replace(":", ""); - escapedMac.toLowerCase(); - if (strcmp(cmDNS, "x") == 0) //fill in unique mdns default - { - strcpy(cmDNS, "wled-"); - sprintf(cmDNS + 5, "%*s", 6, escapedMac.c_str() + 6); - } - if (mqttDeviceTopic[0] == 0) - { - strcpy(mqttDeviceTopic, "wled/"); - sprintf(mqttDeviceTopic + 5, "%*s", 6, escapedMac.c_str() + 6); - } - if (mqttClientID[0] == 0) - { - strcpy(mqttClientID, "WLED-"); - sprintf(mqttClientID + 5, "%*s", 6, escapedMac.c_str() + 6); - } - - strip.service(); - -#ifndef WLED_DISABLE_OTA - if (aOtaEnabled) - { - ArduinoOTA.onStart([]() { -#ifdef ESP8266 - wifi_set_sleep_type(NONE_SLEEP_T); -#endif - DEBUG_PRINTLN("Start ArduinoOTA"); - }); - if (strlen(cmDNS) > 0) - ArduinoOTA.setHostname(cmDNS); - } -#endif -#ifdef WLED_ENABLE_DMX - dmx.init(512); // initialize with bus length -#endif - //HTTP server page init - initServer(); -} - -void WLED::beginStrip() -{ - // Initialize NeoPixel Strip and button - strip.setShowCallback(handleOverlayDraw); - -#ifdef BTNPIN - pinMode(BTNPIN, INPUT_PULLUP); -#endif - - if (bootPreset > 0) - applyPreset(bootPreset, turnOnAtBoot); - colorUpdated(NOTIFIER_CALL_MODE_INIT); - -//init relay pin -#if RLYPIN >= 0 - pinMode(RLYPIN, OUTPUT); -#if RLYMDE - digitalWrite(RLYPIN, bri); -#else - digitalWrite(RLYPIN, !bri); -#endif -#endif - - //disable button if it is "pressed" unintentionally -#ifdef BTNPIN - if (digitalRead(BTNPIN) == LOW) - buttonEnabled = false; -#else - buttonEnabled = false; -#endif -} - -void WLED::initAP(bool resetAP) -{ - if (apBehavior == AP_BEHAVIOR_BUTTON_ONLY && !resetAP) - return; - - if (!apSSID[0] || resetAP) - strcpy(apSSID, "WLED-AP"); - if (resetAP) - strcpy(apPass, DEFAULT_AP_PASS); - DEBUG_PRINT("Opening access point "); - DEBUG_PRINTLN(apSSID); - WiFi.softAPConfig(IPAddress(4, 3, 2, 1), IPAddress(4, 3, 2, 1), IPAddress(255, 255, 255, 0)); - WiFi.softAP(apSSID, apPass, apChannel, apHide); - - if (!apActive) //start captive portal if AP active - { - DEBUG_PRINTLN("Init AP interfaces"); - server.begin(); - if (udpPort > 0 && udpPort != ntpLocalPort) - { - udpConnected = notifierUdp.begin(udpPort); - } - if (udpRgbPort > 0 && udpRgbPort != ntpLocalPort && udpRgbPort != udpPort) - { - udpRgbConnected = rgbUdp.begin(udpRgbPort); - } - - dnsServer.setErrorReplyCode(DNSReplyCode::NoError); - dnsServer.start(53, "*", WiFi.softAPIP()); - } - apActive = true; -} - -void WLED::initConnection() -{ - WiFi.disconnect(); //close old connections -#ifdef ESP8266 - WiFi.setPhyMode(WIFI_PHY_MODE_11N); -#endif - - if (staticIP[0] != 0 && staticGateway[0] != 0) - { - WiFi.config(staticIP, staticGateway, staticSubnet, IPAddress(8, 8, 8, 8)); - } - else - { - WiFi.config(0U, 0U, 0U); - } - - lastReconnectAttempt = millis(); - - if (!WLED_WIFI_CONFIGURED) - { - DEBUG_PRINT("No connection configured. "); - if (!apActive) - initAP(); //instantly go to ap mode - return; - } - else if (!apActive) - { - if (apBehavior == AP_BEHAVIOR_ALWAYS) - { - initAP(); - } - else - { - DEBUG_PRINTLN("Access point disabled."); - WiFi.softAPdisconnect(true); - } - } - showWelcomePage = false; - - DEBUG_PRINT("Connecting to "); - DEBUG_PRINT(clientSSID); - DEBUG_PRINTLN("..."); - -#ifdef ESP8266 - WiFi.hostname(serverDescription); -#endif - - WiFi.begin(clientSSID, clientPass); - -#ifdef ARDUINO_ARCH_ESP32 - WiFi.setSleep(!noWifiSleep); - WiFi.setHostname(serverDescription); -#else - wifi_set_sleep_type((noWifiSleep) ? NONE_SLEEP_T : MODEM_SLEEP_T); -#endif -} - -void WLED::initInterfaces() -{ - DEBUG_PRINTLN("Init STA interfaces"); - - if (hueIP[0] == 0) - { - hueIP[0] = WiFi.localIP()[0]; - hueIP[1] = WiFi.localIP()[1]; - hueIP[2] = WiFi.localIP()[2]; - } - - //init Alexa hue emulation - if (alexaEnabled) - alexaInit(); - -#ifndef WLED_DISABLE_OTA - if (aOtaEnabled) - ArduinoOTA.begin(); -#endif - - strip.service(); - // Set up mDNS responder: - if (strlen(cmDNS) > 0) - { - if (!aOtaEnabled) - MDNS.begin(cmDNS); - - DEBUG_PRINTLN("mDNS started"); - MDNS.addService("http", "tcp", 80); - MDNS.addService("wled", "tcp", 80); - MDNS.addServiceTxt("wled", "tcp", "mac", escapedMac.c_str()); - } - server.begin(); - - if (udpPort > 0 && udpPort != ntpLocalPort) - { - udpConnected = notifierUdp.begin(udpPort); - if (udpConnected && udpRgbPort != udpPort) - udpRgbConnected = rgbUdp.begin(udpRgbPort); - } - if (ntpEnabled) - ntpConnected = ntpUdp.begin(ntpLocalPort); - - initBlynk(blynkApiKey); - e131.begin((e131Multicast) ? E131_MULTICAST : E131_UNICAST, e131Universe, E131_MAX_UNIVERSE_COUNT); - reconnectHue(); - initMqtt(); - interfacesInited = true; - wasConnected = true; -} - -byte stacO = 0; -uint32_t lastHeap; -unsigned long heapTime = 0; - -void WLED::handleConnection() -{ - if (millis() < 2000 && (!WLED_WIFI_CONFIGURED || apBehavior == AP_BEHAVIOR_ALWAYS)) - return; - if (lastReconnectAttempt == 0) - initConnection(); - - //reconnect WiFi to clear stale allocations if heap gets too low - if (millis() - heapTime > 5000) - { - uint32_t heap = ESP.getFreeHeap(); - if (heap < 9000 && lastHeap < 9000) - { - DEBUG_PRINT("Heap too low! "); - DEBUG_PRINTLN(heap); - forceReconnect = true; - } - lastHeap = heap; - heapTime = millis(); - } - - byte stac = 0; - if (apActive) - { -#ifdef ESP8266 - stac = wifi_softap_get_station_num(); -#else - wifi_sta_list_t stationList; - esp_wifi_ap_get_sta_list(&stationList); - stac = stationList.num; -#endif - if (stac != stacO) - { - stacO = stac; - DEBUG_PRINT("Connected AP clients: "); - DEBUG_PRINTLN(stac); - if (!WLED_CONNECTED && WLED_WIFI_CONFIGURED) - { //trying to connect, but not connected - if (stac) - WiFi.disconnect(); //disable search so that AP can work - else - initConnection(); //restart search - } - } - } - if (forceReconnect) - { - DEBUG_PRINTLN("Forcing reconnect."); - initConnection(); - interfacesInited = false; - forceReconnect = false; - wasConnected = false; - return; - } - if (!WLED_CONNECTED) - { - if (interfacesInited) - { - DEBUG_PRINTLN("Disconnected!"); - interfacesInited = false; - initConnection(); - } - if (millis() - lastReconnectAttempt > ((stac) ? 300000 : 20000) && WLED_WIFI_CONFIGURED) - initConnection(); - if (!apActive && millis() - lastReconnectAttempt > 12000 && (!wasConnected || apBehavior == AP_BEHAVIOR_NO_CONN)) - initAP(); - } - else if (!interfacesInited) - { //newly connected - DEBUG_PRINTLN(""); - DEBUG_PRINT("Connected! IP address: "); - DEBUG_PRINTLN(WiFi.localIP()); - initInterfaces(); - userConnected(); - - //shut down AP - if (apBehavior != AP_BEHAVIOR_ALWAYS && apActive) - { - dnsServer.stop(); - WiFi.softAPdisconnect(true); - apActive = false; - DEBUG_PRINTLN("Access point disabled."); - } - } -} - -//by https://github.com/tzapu/WiFiManager/blob/master/WiFiManager.cpp -int getSignalQuality(int rssi) -{ - int quality = 0; - - if (rssi <= -100) - { - quality = 0; - } - else if (rssi >= -50) - { - quality = 100; - } - else - { - quality = 2 * (rssi + 100); - } - return quality; +#include "wled.h" +#include +#include "led.h" +#include "ir.h" +#include "notify.h" +#include "alexa.h" +#include "overlay.h" +#include "file.h" +#include "button.h" +#include "ntp.h" +#include "usermod.h" +#include "blynk.h" +#include "hue.h" +#include "mqtt.h" +#include "eeprom.h" +#include "server.h" +// replaced +// Global Variable definitions +char versionString[] = "0.9.1"; + +//AP and OTA default passwords (for maximum change them!) +char apPass[65] = DEFAULT_AP_PASS; +char otaPass[33] = DEFAULT_OTA_PASS; + +//Hardware CONFIG (only changeble HERE, not at runtime) +//LED strip pin, button pin and IR pin changeable in NpbWrapper.h! + +byte auxDefaultState = 0; //0: input 1: high 2: low +byte auxTriggeredState = 0; //0: input 1: high 2: low +char ntpServerName[33] = "0.wled.pool.ntp.org"; //NTP server to use + +//WiFi CONFIG (all these can be changed via web UI, no need to set them here) +char clientSSID[33] = CLIENT_SSID; +char clientPass[65] = CLIENT_PASS; +char cmDNS[33] = "x"; //mDNS address (placeholder, will be replaced by wledXXXXXXXXXXXX.local) +char apSSID[33] = ""; //AP off by default (unless setup) +byte apChannel = 1; //2.4GHz WiFi AP channel (1-13) +byte apHide = 0; //hidden AP SSID +byte apBehavior = AP_BEHAVIOR_BOOT_NO_CONN; //access point opens when no connection after boot by default +IPAddress staticIP(0, 0, 0, 0); //static IP of ESP +IPAddress staticGateway(0, 0, 0, 0); //gateway (router) IP +IPAddress staticSubnet(255, 255, 255, 0); //most common subnet in home networks +bool noWifiSleep = false; //disabling modem sleep modes will increase heat output and power usage, but may help with connection issues + +//LED CONFIG +uint16_t ledCount = 30; //overcurrent prevented by ABL +bool useRGBW = false; //SK6812 strips can contain an extra White channel +#define ABL_MILLIAMPS_DEFAULT 850; //auto lower brightness to stay close to milliampere limit +bool turnOnAtBoot = true; //turn on LEDs at power-up +byte bootPreset = 0; //save preset to load after power-up + +byte col[]{255, 160, 0, 0}; //current RGB(W) primary color. col[] should be updated if you want to change the color. +byte colSec[]{0, 0, 0, 0}; //current RGB(W) secondary color +byte briS = 128; //default brightness + +byte nightlightTargetBri = 0; //brightness after nightlight is over +byte nightlightDelayMins = 60; +bool nightlightFade = true; //if enabled, light will gradually dim towards the target bri. Otherwise, it will instantly set after delay over +bool nightlightColorFade = false; //if enabled, light will gradually fade color from primary to secondary color. +bool fadeTransition = true; //enable crossfading color transition +uint16_t transitionDelay = 750; //default crossfade duration in ms + +bool skipFirstLed = false; //ignore first LED in strip (useful if you need the LED as signal repeater) +byte briMultiplier = 100; //% of brightness to set (to limit power, if you set it to 50 and set bri to 255, actual brightness will be 127) + +//User Interface CONFIG +char serverDescription[33] = "WLED"; //Name of module +bool syncToggleReceive = false; //UIs which only have a single button for sync should toggle send+receive if this is true, only send otherwise + +//Sync CONFIG +bool buttonEnabled = true; +byte irEnabled = 0; //Infrared receiver + +uint16_t udpPort = 21324; //WLED notifier default port +uint16_t udpRgbPort = 19446; //Hyperion port + +bool receiveNotificationBrightness = true; //apply brightness from incoming notifications +bool receiveNotificationColor = true; //apply color +bool receiveNotificationEffects = true; //apply effects setup +bool notifyDirect = false; //send notification if change via UI or HTTP API +bool notifyButton = false; //send if updated by button or infrared remote +bool notifyAlexa = false; //send notification if updated via Alexa +bool notifyMacro = false; //send notification for macro +bool notifyHue = true; //send notification if Hue light changes +bool notifyTwice = false; //notifications use UDP: enable if devices don't sync reliably + +bool alexaEnabled = true; //enable device discovery by Amazon Echo +char alexaInvocationName[33] = "Light"; //speech control name of device. Choose something voice-to-text can understand + +char blynkApiKey[36] = ""; //Auth token for Blynk server. If empty, no connection will be made + +uint16_t realtimeTimeoutMs = 2500; //ms timeout of realtime mode before returning to normal mode +int arlsOffset = 0; //realtime LED offset +bool receiveDirect = true; //receive UDP realtime +bool arlsDisableGammaCorrection = true; //activate if gamma correction is handled by the source +bool arlsForceMaxBri = false; //enable to force max brightness if source has very dark colors that would be black + +#define E131_MAX_UNIVERSE_COUNT 9 +uint16_t e131Universe = 1; //settings for E1.31 (sACN) protocol (only DMX_MODE_MULTIPLE_* can span over consequtive universes) +uint8_t DMXMode = DMX_MODE_MULTIPLE_RGB; //DMX mode (s.a.) +uint16_t DMXAddress = 1; //DMX start address of fixture, a.k.a. first Channel [for E1.31 (sACN) protocol] +uint8_t DMXOldDimmer = 0; //only update brightness on change +uint8_t e131LastSequenceNumber[E131_MAX_UNIVERSE_COUNT]; //to detect packet loss +bool e131Multicast = false; //multicast or unicast +bool e131SkipOutOfSequence = false; //freeze instead of flickering + +bool mqttEnabled = false; +char mqttDeviceTopic[33] = ""; //main MQTT topic (individual per device, default is wled/mac) +char mqttGroupTopic[33] = "wled/all"; //second MQTT topic (for example to group devices) +char mqttServer[33] = ""; //both domains and IPs should work (no SSL) +char mqttUser[41] = ""; //optional: username for MQTT auth +char mqttPass[41] = ""; //optional: password for MQTT auth +char mqttClientID[41] = ""; //override the client ID +uint16_t mqttPort = 1883; + +bool huePollingEnabled = false; //poll hue bridge for light state +uint16_t huePollIntervalMs = 2500; //low values (< 1sec) may cause lag but offer quicker response +char hueApiKey[47] = "api"; //key token will be obtained from bridge +byte huePollLightId = 1; //ID of hue lamp to sync to. Find the ID in the hue app ("about" section) +IPAddress hueIP = (0, 0, 0, 0); //IP address of the bridge +bool hueApplyOnOff = true; +bool hueApplyBri = true; +bool hueApplyColor = true; + +//Time CONFIG +bool ntpEnabled = false; //get internet time. Only required if you use clock overlays or time-activated macros +bool useAMPM = false; //12h/24h clock format +byte currentTimezone = 0; //Timezone ID. Refer to timezones array in wled10_ntp.ino +int utcOffsetSecs = 0; //Seconds to offset from UTC before timzone calculation + +byte overlayDefault = 0; //0: no overlay 1: analog clock 2: single-digit clocl 3: cronixie +byte overlayMin = 0, overlayMax = ledCount - 1; //boundaries of overlay mode + +byte analogClock12pixel = 0; //The pixel in your strip where "midnight" would be +bool analogClockSecondsTrail = false; //Display seconds as trail of LEDs instead of a single pixel +bool analogClock5MinuteMarks = false; //Light pixels at every 5-minute position + +char cronixieDisplay[7] = "HHMMSS"; //Cronixie Display mask. See wled13_cronixie.ino +bool cronixieBacklight = true; //Allow digits to be back-illuminated + +bool countdownMode = false; //Clock will count down towards date +byte countdownYear = 20, countdownMonth = 1; //Countdown target date, year is last two digits +byte countdownDay = 1, countdownHour = 0; +byte countdownMin = 0, countdownSec = 0; + +byte macroBoot = 0; //macro loaded after startup +byte macroNl = 0; //after nightlight delay over +byte macroCountdown = 0; +byte macroAlexaOn = 0, macroAlexaOff = 0; +byte macroButton = 0, macroLongPress = 0, macroDoublePress = 0; + +//Security CONFIG +bool otaLock = false; //prevents OTA firmware updates without password. ALWAYS enable if system exposed to any public networks +bool wifiLock = false; //prevents access to WiFi settings when OTA lock is enabled +bool aOtaEnabled = true; //ArduinoOTA allows easy updates directly from the IDE. Careful, it does not auto-disable when OTA lock is on + +uint16_t userVar0 = 0, userVar1 = 0; + +#ifdef WLED_ENABLE_DMX +//dmx CONFIG +byte DMXChannels = 7; // number of channels per fixture +byte DMXFixtureMap[15] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +// assigns the different channels to different functions. See wled21_dmx.ino for more information. +uint16_t DMXGap = 10; // gap between the fixtures. makes addressing easier because you don't have to memorize odd numbers when climbing up onto a rig. +uint16_t DMXStart = 10; // start address of the first fixture +#endif + +//internal global variable declarations +//wifi +bool apActive = false; +bool forceReconnect = false; +uint32_t lastReconnectAttempt = 0; +bool interfacesInited = false; +bool wasConnected = false; + +//color +byte colOld[]{0, 0, 0, 0}; //color before transition +byte colT[]{0, 0, 0, 0}; //color that is currently displayed on the LEDs +byte colIT[]{0, 0, 0, 0}; //color that was last sent to LEDs +byte colSecT[]{0, 0, 0, 0}; +byte colSecOld[]{0, 0, 0, 0}; +byte colSecIT[]{0, 0, 0, 0}; + +byte lastRandomIndex = 0; //used to save last random color so the new one is not the same + +//transitions +bool transitionActive = false; +uint16_t transitionDelayDefault = transitionDelay; +uint16_t transitionDelayTemp = transitionDelay; +unsigned long transitionStartTime; +float tperLast = 0; //crossfade transition progress, 0.0f - 1.0f +bool jsonTransitionOnce = false; + +//nightlight +bool nightlightActive = false; +bool nightlightActiveOld = false; +uint32_t nightlightDelayMs = 10; +uint8_t nightlightDelayMinsDefault = nightlightDelayMins; +unsigned long nightlightStartTime; +byte briNlT = 0; //current nightlight brightness +byte colNlT[]{0, 0, 0, 0}; //current nightlight color + +//brightness +unsigned long lastOnTime = 0; +bool offMode = !turnOnAtBoot; +byte bri = briS; +byte briOld = 0; +byte briT = 0; +byte briIT = 0; +byte briLast = 128; //brightness before turned off. Used for toggle function +byte whiteLast = 128; //white channel before turned off. Used for toggle function + +//button +bool buttonPressedBefore = false; +bool buttonLongPressed = false; +unsigned long buttonPressedTime = 0; +unsigned long buttonWaitTime = 0; + +//notifications +bool notifyDirectDefault = notifyDirect; +bool receiveNotifications = true; +unsigned long notificationSentTime = 0; +byte notificationSentCallMode = NOTIFIER_CALL_MODE_INIT; +bool notificationTwoRequired = false; + +//effects +byte effectCurrent = 0; +byte effectSpeed = 128; +byte effectIntensity = 128; +byte effectPalette = 0; + +//network +bool udpConnected = false, udpRgbConnected = false; + +//ui style +bool showWelcomePage = false; + +//hue +byte hueError = HUE_ERROR_INACTIVE; +//uint16_t hueFailCount = 0; +float hueXLast = 0, hueYLast = 0; +uint16_t hueHueLast = 0, hueCtLast = 0; +byte hueSatLast = 0, hueBriLast = 0; +unsigned long hueLastRequestSent = 0; +bool hueAuthRequired = false; +bool hueReceived = false; +bool hueStoreAllowed = false, hueNewKey = false; + +//overlays +byte overlayCurrent = overlayDefault; +byte overlaySpeed = 200; +unsigned long overlayRefreshMs = 200; +unsigned long overlayRefreshedTime; + +//cronixie +byte dP[]{0, 0, 0, 0, 0, 0}; +bool cronixieInit = false; + +//countdown +unsigned long countdownTime = 1514764800L; +bool countdownOverTriggered = true; + +//timer +byte lastTimerMinute = 0; +byte timerHours[] = {0, 0, 0, 0, 0, 0, 0, 0}; +byte timerMinutes[] = {0, 0, 0, 0, 0, 0, 0, 0}; +byte timerMacro[] = {0, 0, 0, 0, 0, 0, 0, 0}; +byte timerWeekday[] = {255, 255, 255, 255, 255, 255, 255, 255}; //weekdays to activate on +//bit pattern of arr elem: 0b11111111: sun,sat,fri,thu,wed,tue,mon,validity + +//blynk +bool blynkEnabled = false; + +//preset cycling +bool presetCyclingEnabled = false; +byte presetCycleMin = 1, presetCycleMax = 5; +uint16_t presetCycleTime = 1250; +unsigned long presetCycledTime = 0; +byte presetCycCurr = presetCycleMin; +bool presetApplyBri = true; +bool saveCurrPresetCycConf = false; + +//realtime +byte realtimeMode = REALTIME_MODE_INACTIVE; +IPAddress realtimeIP = (0, 0, 0, 0); +unsigned long realtimeTimeout = 0; + +//mqtt +long lastMqttReconnectAttempt = 0; +long lastInterfaceUpdate = 0; +byte interfaceUpdateCallMode = NOTIFIER_CALL_MODE_INIT; +char mqttStatusTopic[40] = ""; //this must be global because of async handlers + +#if AUXPIN >= 0 + //auxiliary debug pin +byte auxTime = 0; +unsigned long auxStartTime = 0; +bool auxActive = false, auxActiveBefore = false; +#endif + +//alexa udp +String escapedMac; +#ifndef WLED_DISABLE_ALEXA +Espalexa espalexa; +EspalexaDevice *espalexaDevice; +#endif + +//dns server +DNSServer dnsServer; + +//network time +bool ntpConnected = false; +time_t local = 0; +unsigned long ntpLastSyncTime = 999000000L; +unsigned long ntpPacketSentTime = 999000000L; +IPAddress ntpServerIP; +uint16_t ntpLocalPort = 2390; +#define NTP_PACKET_SIZE 48 + +//maximum number of LEDs - MAX_LEDS is coming from the JSON response getting too big, MAX_LEDS_DMA will become a timing issue +#define MAX_LEDS 1500 +#define MAX_LEDS_DMA 500 + +//string temp buffer (now stored in stack locally) +#define OMAX 2048 +char *obuf; +uint16_t olen = 0; + +//presets +uint16_t savedPresets = 0; +int8_t currentPreset = -1; +bool isPreset = false; + +byte errorFlag = 0; + +String messageHead, messageSub; +byte optionType; + +bool doReboot = false; //flag to initiate reboot from async handlers +bool doPublishMqtt = false; + +//server library objects +AsyncWebServer server(80); +AsyncClient *hueClient = NULL; +AsyncMqttClient *mqtt = NULL; + +//function prototypes +void colorFromUint32(uint32_t, bool = false); +void serveMessage(AsyncWebServerRequest *, uint16_t, String, String, byte); +void handleE131Packet(e131_packet_t *, IPAddress); +void arlsLock(uint32_t, byte); +void handleOverlayDraw(); + +//udp interface objects +WiFiUDP notifierUdp, rgbUdp; +WiFiUDP ntpUdp; +ESPAsyncE131 e131(handleE131Packet); +bool e131NewData = false; + +//led fx library object +WS2812FX strip = WS2812FX(); + +#define WLED_CONNECTED (WiFi.status() == WL_CONNECTED) +#define WLED_WIFI_CONFIGURED (strlen(clientSSID) >= 1 && strcmp(clientSSID, DEFAULT_CLIENT_SSID) != 0) + +//debug macros +#ifdef WLED_DEBUG +#define DEBUG_PRINT(x) Serial.print(x) +#define DEBUG_PRINTLN(x) Serial.println(x) +#define DEBUG_PRINTF(x) Serial.printf(x) +unsigned long debugTime = 0; +int lastWifiState = 3; +unsigned long wifiStateChangedTime = 0; +int loops = 0; +#else +#define DEBUG_PRINT(x) +#define DEBUG_PRINTLN(x) +#define DEBUG_PRINTF(x) +#endif + + + +WLED::WLED() { + +} + +//turns all LEDs off and restarts ESP +void WLED::reset() +{ + briT = 0; + long dly = millis(); + while (millis() - dly < 250) + { + yield(); //enough time to send response to client + } + setAllLeds(); + DEBUG_PRINTLN("MODULE RESET"); + ESP.restart(); +} + +bool oappendi(int i) +{ + char s[11]; + sprintf(s, "%ld", i); + return oappend(s); +} + +bool oappend(const char *txt) +{ + uint16_t len = strlen(txt); + if (olen + len >= OMAX) + return false; //buffer full + strcpy(obuf + olen, txt); + olen += len; + return true; +} + +void WLED::loop() +{ + handleIR(); //2nd call to function needed for ESP32 to return valid results -- should be good for ESP8266, too + handleConnection(); + handleSerial(); + handleNotifications(); + handleTransitions(); +#ifdef WLED_ENABLE_DMX + handleDMX(); +#endif + userLoop(); + + yield(); + handleIO(); + handleIR(); + handleNetworkTime(); + handleAlexa(); + + handleOverlays(); + yield(); +#ifdef WLED_USE_ANALOG_LEDS + strip.setRgbwPwm(); +#endif + + if (doReboot) + reset(); + + if (!realtimeMode) //block stuff if WARLS/Adalight is enabled + { + if (apActive) + dnsServer.processNextRequest(); +#ifndef WLED_DISABLE_OTA + if (WLED_CONNECTED && aOtaEnabled) + ArduinoOTA.handle(); +#endif + handleNightlight(); + yield(); + + handleHue(); + handleBlynk(); + + yield(); + if (!offMode) + strip.service(); + } + yield(); +#ifdef ESP8266 + MDNS.update(); +#endif + if (millis() - lastMqttReconnectAttempt > 30000) + initMqtt(); + +//DEBUG serial logging +#ifdef WLED_DEBUG + if (millis() - debugTime > 9999) + { + DEBUG_PRINTLN("---DEBUG INFO---"); + DEBUG_PRINT("Runtime: "); + DEBUG_PRINTLN(millis()); + DEBUG_PRINT("Unix time: "); + DEBUG_PRINTLN(now()); + DEBUG_PRINT("Free heap: "); + DEBUG_PRINTLN(ESP.getFreeHeap()); + DEBUG_PRINT("Wifi state: "); + DEBUG_PRINTLN(WiFi.status()); + if (WiFi.status() != lastWifiState) + { + wifiStateChangedTime = millis(); + } + lastWifiState = WiFi.status(); + DEBUG_PRINT("State time: "); + DEBUG_PRINTLN(wifiStateChangedTime); + DEBUG_PRINT("NTP last sync: "); + DEBUG_PRINTLN(ntpLastSyncTime); + DEBUG_PRINT("Client IP: "); + DEBUG_PRINTLN(WiFi.localIP()); + DEBUG_PRINT("Loops/sec: "); + DEBUG_PRINTLN(loops / 10); + loops = 0; + debugTime = millis(); + } + loops++; +#endif // WLED_DEBU +} + +void WLED::wledInit() +{ + EEPROM.begin(EEPSIZE); + ledCount = EEPROM.read(229) + ((EEPROM.read(398) << 8) & 0xFF00); + if (ledCount > MAX_LEDS || ledCount == 0) + ledCount = 30; + +#ifdef ESP8266 +#if LEDPIN == 3 + if (ledCount > MAX_LEDS_DMA) + ledCount = MAX_LEDS_DMA; //DMA method uses too much ram +#endif +#endif + Serial.begin(115200); + Serial.setTimeout(50); + DEBUG_PRINTLN(); + DEBUG_PRINT("---WLED "); + DEBUG_PRINT(versionString); + DEBUG_PRINT(" "); + DEBUG_PRINT(VERSION); + DEBUG_PRINTLN(" INIT---"); +#ifdef ARDUINO_ARCH_ESP32 + DEBUG_PRINT("esp32 "); + DEBUG_PRINTLN(ESP.getSdkVersion()); +#else + DEBUG_PRINT("esp8266 "); + DEBUG_PRINTLN(ESP.getCoreVersion()); +#endif + int heapPreAlloc = ESP.getFreeHeap(); + DEBUG_PRINT("heap "); + DEBUG_PRINTLN(ESP.getFreeHeap()); + + strip.init(EEPROM.read(372), ledCount, EEPROM.read(2204)); //init LEDs quickly + strip.setBrightness(0); + + DEBUG_PRINT("LEDs inited. heap usage ~"); + DEBUG_PRINTLN(heapPreAlloc - ESP.getFreeHeap()); + +#ifndef WLED_DISABLE_FILESYSTEM +#ifdef ARDUINO_ARCH_ESP32 + SPIFFS.begin(true); +#endif + SPIFFS.begin(); +#endif + + DEBUG_PRINTLN("Load EEPROM"); + loadSettingsFromEEPROM(true); + beginStrip(); + userSetup(); + if (strcmp(clientSSID, DEFAULT_CLIENT_SSID) == 0) + showWelcomePage = true; + WiFi.persistent(false); + + if (macroBoot > 0) + applyMacro(macroBoot); + Serial.println("Ada"); + + //generate module IDs + escapedMac = WiFi.macAddress(); + escapedMac.replace(":", ""); + escapedMac.toLowerCase(); + if (strcmp(cmDNS, "x") == 0) //fill in unique mdns default + { + strcpy(cmDNS, "wled-"); + sprintf(cmDNS + 5, "%*s", 6, escapedMac.c_str() + 6); + } + if (mqttDeviceTopic[0] == 0) + { + strcpy(mqttDeviceTopic, "wled/"); + sprintf(mqttDeviceTopic + 5, "%*s", 6, escapedMac.c_str() + 6); + } + if (mqttClientID[0] == 0) + { + strcpy(mqttClientID, "WLED-"); + sprintf(mqttClientID + 5, "%*s", 6, escapedMac.c_str() + 6); + } + + strip.service(); + +#ifndef WLED_DISABLE_OTA + if (aOtaEnabled) + { + ArduinoOTA.onStart([]() { +#ifdef ESP8266 + wifi_set_sleep_type(NONE_SLEEP_T); +#endif + DEBUG_PRINTLN("Start ArduinoOTA"); + }); + if (strlen(cmDNS) > 0) + ArduinoOTA.setHostname(cmDNS); + } +#endif +#ifdef WLED_ENABLE_DMX + dmx.init(512); // initialize with bus length +#endif + //HTTP server page init + initServer(); +} + +void WLED::beginStrip() +{ + // Initialize NeoPixel Strip and button + strip.setShowCallback(handleOverlayDraw); + +#ifdef BTNPIN + pinMode(BTNPIN, INPUT_PULLUP); +#endif + + if (bootPreset > 0) + applyPreset(bootPreset, turnOnAtBoot); + colorUpdated(NOTIFIER_CALL_MODE_INIT); + +//init relay pin +#if RLYPIN >= 0 + pinMode(RLYPIN, OUTPUT); +#if RLYMDE + digitalWrite(RLYPIN, bri); +#else + digitalWrite(RLYPIN, !bri); +#endif +#endif + + //disable button if it is "pressed" unintentionally +#ifdef BTNPIN + if (digitalRead(BTNPIN) == LOW) + buttonEnabled = false; +#else + buttonEnabled = false; +#endif +} + +void WLED::initAP(bool resetAP) +{ + if (apBehavior == AP_BEHAVIOR_BUTTON_ONLY && !resetAP) + return; + + if (!apSSID[0] || resetAP) + strcpy(apSSID, "WLED-AP"); + if (resetAP) + strcpy(apPass, DEFAULT_AP_PASS); + DEBUG_PRINT("Opening access point "); + DEBUG_PRINTLN(apSSID); + WiFi.softAPConfig(IPAddress(4, 3, 2, 1), IPAddress(4, 3, 2, 1), IPAddress(255, 255, 255, 0)); + WiFi.softAP(apSSID, apPass, apChannel, apHide); + + if (!apActive) //start captive portal if AP active + { + DEBUG_PRINTLN("Init AP interfaces"); + server.begin(); + if (udpPort > 0 && udpPort != ntpLocalPort) + { + udpConnected = notifierUdp.begin(udpPort); + } + if (udpRgbPort > 0 && udpRgbPort != ntpLocalPort && udpRgbPort != udpPort) + { + udpRgbConnected = rgbUdp.begin(udpRgbPort); + } + + dnsServer.setErrorReplyCode(DNSReplyCode::NoError); + dnsServer.start(53, "*", WiFi.softAPIP()); + } + apActive = true; +} + +void WLED::initConnection() +{ + WiFi.disconnect(); //close old connections +#ifdef ESP8266 + WiFi.setPhyMode(WIFI_PHY_MODE_11N); +#endif + + if (staticIP[0] != 0 && staticGateway[0] != 0) + { + WiFi.config(staticIP, staticGateway, staticSubnet, IPAddress(8, 8, 8, 8)); + } + else + { + WiFi.config(0U, 0U, 0U); + } + + lastReconnectAttempt = millis(); + + if (!WLED_WIFI_CONFIGURED) + { + DEBUG_PRINT("No connection configured. "); + if (!apActive) + initAP(); //instantly go to ap mode + return; + } + else if (!apActive) + { + if (apBehavior == AP_BEHAVIOR_ALWAYS) + { + initAP(); + } + else + { + DEBUG_PRINTLN("Access point disabled."); + WiFi.softAPdisconnect(true); + } + } + showWelcomePage = false; + + DEBUG_PRINT("Connecting to "); + DEBUG_PRINT(clientSSID); + DEBUG_PRINTLN("..."); + +#ifdef ESP8266 + WiFi.hostname(serverDescription); +#endif + + WiFi.begin(clientSSID, clientPass); + +#ifdef ARDUINO_ARCH_ESP32 + WiFi.setSleep(!noWifiSleep); + WiFi.setHostname(serverDescription); +#else + wifi_set_sleep_type((noWifiSleep) ? NONE_SLEEP_T : MODEM_SLEEP_T); +#endif +} + +void WLED::initInterfaces() +{ + DEBUG_PRINTLN("Init STA interfaces"); + + if (hueIP[0] == 0) + { + hueIP[0] = WiFi.localIP()[0]; + hueIP[1] = WiFi.localIP()[1]; + hueIP[2] = WiFi.localIP()[2]; + } + + //init Alexa hue emulation + if (alexaEnabled) + alexaInit(); + +#ifndef WLED_DISABLE_OTA + if (aOtaEnabled) + ArduinoOTA.begin(); +#endif + + strip.service(); + // Set up mDNS responder: + if (strlen(cmDNS) > 0) + { + if (!aOtaEnabled) + MDNS.begin(cmDNS); + + DEBUG_PRINTLN("mDNS started"); + MDNS.addService("http", "tcp", 80); + MDNS.addService("wled", "tcp", 80); + MDNS.addServiceTxt("wled", "tcp", "mac", escapedMac.c_str()); + } + server.begin(); + + if (udpPort > 0 && udpPort != ntpLocalPort) + { + udpConnected = notifierUdp.begin(udpPort); + if (udpConnected && udpRgbPort != udpPort) + udpRgbConnected = rgbUdp.begin(udpRgbPort); + } + if (ntpEnabled) + ntpConnected = ntpUdp.begin(ntpLocalPort); + + initBlynk(blynkApiKey); + e131.begin((e131Multicast) ? E131_MULTICAST : E131_UNICAST, e131Universe, E131_MAX_UNIVERSE_COUNT); + reconnectHue(); + initMqtt(); + interfacesInited = true; + wasConnected = true; +} + +byte stacO = 0; +uint32_t lastHeap; +unsigned long heapTime = 0; + +void WLED::handleConnection() +{ + if (millis() < 2000 && (!WLED_WIFI_CONFIGURED || apBehavior == AP_BEHAVIOR_ALWAYS)) + return; + if (lastReconnectAttempt == 0) + initConnection(); + + //reconnect WiFi to clear stale allocations if heap gets too low + if (millis() - heapTime > 5000) + { + uint32_t heap = ESP.getFreeHeap(); + if (heap < 9000 && lastHeap < 9000) + { + DEBUG_PRINT("Heap too low! "); + DEBUG_PRINTLN(heap); + forceReconnect = true; + } + lastHeap = heap; + heapTime = millis(); + } + + byte stac = 0; + if (apActive) + { +#ifdef ESP8266 + stac = wifi_softap_get_station_num(); +#else + wifi_sta_list_t stationList; + esp_wifi_ap_get_sta_list(&stationList); + stac = stationList.num; +#endif + if (stac != stacO) + { + stacO = stac; + DEBUG_PRINT("Connected AP clients: "); + DEBUG_PRINTLN(stac); + if (!WLED_CONNECTED && WLED_WIFI_CONFIGURED) + { //trying to connect, but not connected + if (stac) + WiFi.disconnect(); //disable search so that AP can work + else + initConnection(); //restart search + } + } + } + if (forceReconnect) + { + DEBUG_PRINTLN("Forcing reconnect."); + initConnection(); + interfacesInited = false; + forceReconnect = false; + wasConnected = false; + return; + } + if (!WLED_CONNECTED) + { + if (interfacesInited) + { + DEBUG_PRINTLN("Disconnected!"); + interfacesInited = false; + initConnection(); + } + if (millis() - lastReconnectAttempt > ((stac) ? 300000 : 20000) && WLED_WIFI_CONFIGURED) + initConnection(); + if (!apActive && millis() - lastReconnectAttempt > 12000 && (!wasConnected || apBehavior == AP_BEHAVIOR_NO_CONN)) + initAP(); + } + else if (!interfacesInited) + { //newly connected + DEBUG_PRINTLN(""); + DEBUG_PRINT("Connected! IP address: "); + DEBUG_PRINTLN(WiFi.localIP()); + initInterfaces(); + userConnected(); + + //shut down AP + if (apBehavior != AP_BEHAVIOR_ALWAYS && apActive) + { + dnsServer.stop(); + WiFi.softAPdisconnect(true); + apActive = false; + DEBUG_PRINTLN("Access point disabled."); + } + } +} + +//by https://github.com/tzapu/WiFiManager/blob/master/WiFiManager.cpp +int getSignalQuality(int rssi) +{ + int quality = 0; + + if (rssi <= -100) + { + quality = 0; + } + else if (rssi >= -50) + { + quality = 100; + } + else + { + quality = 2 * (rssi + 100); + } + return quality; } \ No newline at end of file diff --git a/wled00/wled.h b/wled00/wled.h index 3b744bcd4..c6f791cd4 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -1,519 +1,519 @@ -#ifndef WLED_H -#define WLED_H - -/* - Main sketch, global variable declarations -*/ -/* - * @title WLED project sketch - * @version 0.9.1 - * @author Christian Schwinne - */ - -//ESP8266-01 (blue) got too little storage space to work with all features of WLED. To use it, you must use ESP8266 Arduino Core v2.4.2 and the setting 512K(No SPIFFS). - -//ESP8266-01 (black) has 1MB flash and can thus fit the whole program. Use 1M(64K SPIFFS). -//Uncomment some of the following lines to disable features to compile for ESP8266-01 (max flash size 434kB): - -//You are required to disable over-the-air updates: -//#define WLED_DISABLE_OTA //saves 14kb - -//You need to choose some of these features to disable: -//#define WLED_DISABLE_ALEXA //saves 11kb -//#define WLED_DISABLE_BLYNK //saves 6kb -//#define WLED_DISABLE_CRONIXIE //saves 3kb -//#define WLED_DISABLE_HUESYNC //saves 4kb -//#define WLED_DISABLE_INFRARED //there is no pin left for this on ESP8266-01, saves 12kb -#define WLED_ENABLE_MQTT //saves 12kb -#define WLED_ENABLE_ADALIGHT //saves 500b only -//#define WLED_ENABLE_DMX //uses 3.5kb - -#define WLED_DISABLE_FILESYSTEM //SPIFFS is not used by any WLED feature yet -//#define WLED_ENABLE_FS_SERVING //Enable sending html file from SPIFFS before serving progmem version -//#define WLED_ENABLE_FS_EDITOR //enable /edit page for editing SPIFFS content. Will also be disabled with OTA lock - -//to toggle usb serial debug (un)comment the following line -//#define WLED_DEBUG - -//library inclusions -#include -#ifdef WLED_ENABLE_DMX -#include -DMXESPSerial dmx; -#endif -#ifdef ESP8266 -#include -#include -#include -extern "C" -{ -#include -} -#else //ESP32 -#include -#include "esp_wifi.h" -#include -#include -#include "SPIFFS.h" -#endif - -#include -#include -#include -#include -#ifndef WLED_DISABLE_OTA -#include -#endif -#include -#include "src/dependencies/time/TimeLib.h" -#include "src/dependencies/timezone/Timezone.h" -#ifndef WLED_DISABLE_ALEXA -#define ESPALEXA_ASYNC -#define ESPALEXA_NO_SUBPAGE -#define ESPALEXA_MAXDEVICES 1 -// #define ESPALEXA_DEBUG -#include "src/dependencies/espalexa/Espalexa.h" -#endif -#ifndef WLED_DISABLE_BLYNK -#include "src/dependencies/blynk/BlynkSimpleEsp.h" -#endif -#include "src/dependencies/e131/ESPAsyncE131.h" -#include "src/dependencies/async-mqtt-client/AsyncMqttClient.h" -#include "src/dependencies/json/AsyncJson-v6.h" -#include "src/dependencies/json/ArduinoJson-v6.h" -#include "html_ui.h" -#include "html_settings.h" -#include "html_other.h" -#include "FX.h" -#include "ir_codes.h" -#include "const.h" - -#ifndef CLIENT_SSID -#define CLIENT_SSID DEFAULT_CLIENT_SSID -#endif - -#ifndef CLIENT_PASS -#define CLIENT_PASS "" -#endif - -#if IR_PIN < 0 -#ifndef WLED_DISABLE_INFRARED -#define WLED_DISABLE_INFRARED -#endif -#endif - -#ifndef WLED_DISABLE_INFRARED -#include -#include -#include -#endif - -// remove flicker because PWM signal of RGB channels can become out of phase -#if defined(WLED_USE_ANALOG_LEDS) && defined(ESP8266) -#include "src/dependencies/arduino/core_esp8266_waveform.h" -#endif - -// enable additional debug output -#ifdef WLED_DEBUG -#ifndef ESP8266 -#include -#endif -#endif - -//version code in format yymmddb (b = daily build) -#define VERSION 2003222 - -extern char versionString[]; - -//AP and OTA default passwords (for maximum change them!) -extern char apPass[65]; -extern char otaPass[33]; - -//Hardware CONFIG (only changeble HERE, not at runtime) -//LED strip pin, button pin and IR pin changeable in NpbWrapper.h! - -extern byte auxDefaultState; //0: input 1: high 2: low -extern byte auxTriggeredState; //0: input 1: high 2: low -extern char ntpServerName[33]; //NTP server to use - -//WiFi CONFIG (all these can be changed via web UI, no need to set them here) -extern char clientSSID[33]; -extern char clientPass[65]; -extern char cmDNS[33]; //mDNS address (placeholder, will be replaced by wledXXXXXXXXXXXX.local) -extern char apSSID[33]; //AP off by default (unless setup) -extern byte apChannel; //2.4GHz WiFi AP channel (1-13) -extern byte apHide; //hidden AP SSID -extern byte apBehavior; //access point opens when no connection after boot by default -extern IPAddress staticIP; //static IP of ESP -extern IPAddress staticGateway; //gateway (router) IP -extern IPAddress staticSubnet; //most common subnet in home networks -extern bool noWifiSleep; //disabling modem sleep modes will increase heat output and power usage, but may help with connection issues - -//LED CONFIG -extern uint16_t ledCount; //overcurrent prevented by ABL -extern bool useRGBW; //SK6812 strips can contain an extra White channel -#define ABL_MILLIAMPS_DEFAULT 850; //auto lower brightness to stay close to milliampere limit -extern bool turnOnAtBoot; //turn on LEDs at power-up -extern byte bootPreset; //save preset to load after power-up - -extern byte col[]; //current RGB(W) primary color. col[] should be updated if you want to change the color. -extern byte colSec[]; //current RGB(W) secondary color -extern byte briS; //default brightness - -extern byte nightlightTargetBri; //brightness after nightlight is over -extern byte nightlightDelayMins; -extern bool nightlightFade; //if enabled, light will gradually dim towards the target bri. Otherwise, it will instantly set after delay over -extern bool nightlightColorFade; //if enabled, light will gradually fade color from primary to secondary color. -extern bool fadeTransition; //enable crossfading color transition -extern uint16_t transitionDelay; //default crossfade duration in ms - -extern bool skipFirstLed; //ignore first LED in strip (useful if you need the LED as signal repeater) -extern byte briMultiplier; //% of brightness to set (to limit power, if you set it to 50 and set bri to 255, actual brightness will be 127) - -//User Interface CONFIG -extern char serverDescription[33]; //Name of module -extern bool syncToggleReceive; //UIs which only have a single button for sync should toggle send+receive if this is true, only send otherwise - -//Sync CONFIG -extern bool buttonEnabled; -extern byte irEnabled; //Infrared receiver - -extern uint16_t udpPort; //WLED notifier default port -extern uint16_t udpRgbPort; //Hyperion port - -extern bool receiveNotificationBrightness; //apply brightness from incoming notifications -extern bool receiveNotificationColor; //apply color -extern bool receiveNotificationEffects; //apply effects setup -extern bool notifyDirect; //send notification if change via UI or HTTP API -extern bool notifyButton; //send if updated by button or infrared remote -extern bool notifyAlexa; //send notification if updated via Alexa -extern bool notifyMacro; //send notification for macro -extern bool notifyHue; //send notification if Hue light changes -extern bool notifyTwice; //notifications use UDP: enable if devices don't sync reliably - -extern bool alexaEnabled; //enable device discovery by Amazon Echo -extern char alexaInvocationName[33]; //speech control name of device. Choose something voice-to-text can understand - -extern char blynkApiKey[36]; //Auth token for Blynk server. If empty, no connection will be made - -extern uint16_t realtimeTimeoutMs; //ms timeout of realtime mode before returning to normal mode -extern int arlsOffset; //realtime LED offset -extern bool receiveDirect; //receive UDP realtime -extern bool arlsDisableGammaCorrection; //activate if gamma correction is handled by the source -extern bool arlsForceMaxBri; //enable to force max brightness if source has very dark colors that would be black - -#define E131_MAX_UNIVERSE_COUNT 9 -extern uint16_t e131Universe; //settings for E1.31 (sACN) protocol (only DMX_MODE_MULTIPLE_* can span over consequtive universes) -extern uint8_t DMXMode; //DMX mode (s.a.) -extern uint16_t DMXAddress; //DMX start address of fixture, a.k.a. first Channel [for E1.31 (sACN) protocol] -extern uint8_t DMXOldDimmer; //only update brightness on change -extern uint8_t e131LastSequenceNumber[E131_MAX_UNIVERSE_COUNT]; //to detect packet loss -extern bool e131Multicast; //multicast or unicast -extern bool e131SkipOutOfSequence; //freeze instead of flickering - -extern bool mqttEnabled; -extern char mqttDeviceTopic[33]; //main MQTT topic (individual per device, default is wled/mac) -extern char mqttGroupTopic[33]; //second MQTT topic (for example to group devices) -extern char mqttServer[33]; //both domains and IPs should work (no SSL) -extern char mqttUser[41]; //optional: username for MQTT auth -extern char mqttPass[41]; //optional: password for MQTT auth -extern char mqttClientID[41]; //override the client ID -extern uint16_t mqttPort; - -extern bool huePollingEnabled; //poll hue bridge for light state -extern uint16_t huePollIntervalMs; //low values (< 1sec) may cause lag but offer quicker response -extern char hueApiKey[47]; //key token will be obtained from bridge -extern byte huePollLightId; //ID of hue lamp to sync to. Find the ID in the hue app ("about" section) -extern IPAddress hueIP; //IP address of the bridge -extern bool hueApplyOnOff; -extern bool hueApplyBri; -extern bool hueApplyColor; - -//Time CONFIG -extern bool ntpEnabled; //get internet time. Only required if you use clock overlays or time-activated macros -extern bool useAMPM; //12h/24h clock format -extern byte currentTimezone; //Timezone ID. Refer to timezones array in wled_ntp.cpp -extern int utcOffsetSecs; //Seconds to offset from UTC before timzone calculation - -extern byte overlayDefault; //0: no overlay 1: analog clock 2: single-digit clocl 3: cronixie -extern byte overlayMin; //boundaries of overlay mode -extern byte overlayMax; - -extern byte analogClock12pixel; //The pixel in your strip where "midnight" would be -extern bool analogClockSecondsTrail; //Display seconds as trail of LEDs instead of a single pixel -extern bool analogClock5MinuteMarks; //Light pixels at every 5-minute position - -extern char cronixieDisplay[7]; //Cronixie Display mask. See wled13_cronixie.ino -extern bool cronixieBacklight; //Allow digits to be back-illuminated - -extern bool countdownMode; //Clock will count down towards date -extern byte countdownYear, countdownMonth; //Countdown target date, year is last two digits -extern byte countdownDay, countdownHour; -extern byte countdownMin, countdownSec; - -extern byte macroBoot; //macro loaded after startup -extern byte macroNl; //after nightlight delay over -extern byte macroCountdown; -extern byte macroAlexaOn, macroAlexaOff; -extern byte macroButton, macroLongPress, macroDoublePress; - -//Security CONFIG -extern bool otaLock; //prevents OTA firmware updates without password. ALWAYS enable if system exposed to any public networks -extern bool wifiLock; //prevents access to WiFi settings when OTA lock is enabled -extern bool aOtaEnabled; //ArduinoOTA allows easy updates directly from the IDE. Careful, it does not auto-disable when OTA lock is on - -extern uint16_t userVar0, userVar1; - -#ifdef WLED_ENABLE_DMX -//dmx CONFIG -extern byte DMXChannels; // number of channels per fixture -extern byte DMXFixtureMap[15]; -extern // assigns the different channels to different functions. See wled21_dmx.ino for more information. -extern uint16_t DMXGap; // gap between the fixtures. makes addressing easier because you don't have to memorize odd numbers when climbing up onto a rig. -extern uint16_t DMXStart; // start address of the first fixture -#endif - -//internal global variable declarations -//wifi -extern bool apActive; -extern bool forceReconnect; -extern uint32_t lastReconnectAttempt; -extern bool interfacesInited; -extern bool wasConnected; - -//color -extern byte colOld[]; //color before transition -extern byte colT[]; //color that is currently displayed on the LEDs -extern byte colIT[]; //color that was last sent to LEDs -extern byte colSecT[]; -extern byte colSecOld[]; -extern byte colSecIT[]; - -extern byte lastRandomIndex; //used to save last random color so the new one is not the same - -//transitions -extern bool transitionActive; -extern uint16_t transitionDelayDefault; -extern uint16_t transitionDelayTemp; -extern unsigned long transitionStartTime; -extern float tperLast; //crossfade transition progress, 0.0f - 1.0f -extern bool jsonTransitionOnce; - -//nightlight -extern bool nightlightActive; -extern bool nightlightActiveOld; -extern uint32_t nightlightDelayMs; -extern uint8_t nightlightDelayMinsDefault; -extern unsigned long nightlightStartTime; -extern byte briNlT; //current nightlight brightness -extern byte colNlT[]; //current nightlight color - -extern unsigned long lastOnTime; -extern bool offMode; -extern byte bri; -extern byte briOld; -extern byte briT; -extern byte briIT; -extern byte briLast; //brightness before turned off. Used for toggle function -extern byte whiteLast; //white channel before turned off. Used for toggle function - -extern bool buttonPressedBefore; -extern bool buttonLongPressed; -extern unsigned long buttonPressedTime; -extern unsigned long buttonWaitTime; - -extern bool notifyDirectDefault; -extern bool receiveNotifications; -extern unsigned long notificationSentTime; -extern byte notificationSentCallMode; -extern bool notificationTwoRequired; - -extern byte effectCurrent; -extern byte effectSpeed; -extern byte effectIntensity; -extern byte effectPalette; - -extern bool udpConnected, udpRgbConnected; - -extern bool showWelcomePage; - -extern byte hueError; -//uint16_t hueFailCount; -extern float hueXLast, hueYLast; -extern uint16_t hueHueLast, hueCtLast; -extern byte hueSatLast, hueBriLast; -extern unsigned long hueLastRequestSent; -extern bool hueAuthRequired; -extern bool hueReceived; -extern bool hueStoreAllowed, hueNewKey; - -extern byte overlayCurrent; -extern byte overlaySpeed; -extern unsigned long overlayRefreshMs; -extern unsigned long overlayRefreshedTime; - -extern byte dP[]; -extern bool cronixieInit; - -//countdown -extern unsigned long countdownTime; -extern bool countdownOverTriggered; - -//timer -extern byte lastTimerMinute; -extern byte timerHours[]; -extern byte timerMinutes[]; -extern byte timerMacro[]; -extern byte timerWeekday[]; //weekdays to activate on -//bit pattern of arr elem: 0b11111111: sun,sat,fri,thu,wed,tue,mon,validity - -//blynk -extern bool blynkEnabled; - -//preset cycling -extern bool presetCyclingEnabled; -extern byte presetCycleMin, presetCycleMax; -extern uint16_t presetCycleTime; -extern unsigned long presetCycledTime; -extern byte presetCycCurr; -extern bool presetApplyBri; -extern bool saveCurrPresetCycConf; - -//realtime -extern byte realtimeMode; -extern IPAddress realtimeIP; -extern unsigned long realtimeTimeout; - -//mqtt -extern long lastMqttReconnectAttempt; -extern long lastInterfaceUpdate; -extern byte interfaceUpdateCallMode; -extern char mqttStatusTopic[40]; //this must be global because of async handlers - -#if AUXPIN >= 0 -//auxiliary debug pin -extern byte auxTime; -extern unsigned long auxStartTime; -extern bool auxActive; -#endif - -//alexa udp -extern String escapedMac; -#ifndef WLED_DISABLE_ALEXA -extern Espalexa espalexa; -extern EspalexaDevice *espalexaDevice; -#endif - -//dns server -extern DNSServer dnsServer; - -//network time -extern bool ntpConnected; -extern time_t local; -extern unsigned long ntpLastSyncTime; -extern unsigned long ntpPacketSentTime; -extern IPAddress ntpServerIP; -extern uint16_t ntpLocalPort; -#define NTP_PACKET_SIZE 48 - -//maximum number of LEDs - MAX_LEDS is coming from the JSON response getting too big, MAX_LEDS_DMA will become a timing issue -#define MAX_LEDS 1500 -#define MAX_LEDS_DMA 500 - -//string temp buffer (now stored in stack locally) -#define OMAX 2048 -extern char *obuf; -extern uint16_t olen; - -//presets -extern uint16_t savedPresets; -extern int8_t currentPreset; -extern bool isPreset; - -extern byte errorFlag; - -extern String messageHead, messageSub; -extern byte optionType; - -extern bool doReboot; //flag to initiate reboot from async handlers -extern bool doPublishMqtt; - -//server library objects -extern AsyncWebServer server; -extern AsyncClient *hueClient; -extern AsyncMqttClient *mqtt; - -//function prototypes -extern void colorFromUint32(uint32_t, bool); -extern void serveMessage(AsyncWebServerRequest *, uint16_t, String, String, byte); -extern void handleE131Packet(e131_packet_t *, IPAddress); -extern void arlsLock(uint32_t, byte); -extern void handleOverlayDraw(); - -//udp interface objects -extern WiFiUDP notifierUdp, rgbUdp; -extern WiFiUDP ntpUdp; -extern ESPAsyncE131 e131; -extern bool e131NewData; - -//led fx library object -extern WS2812FX strip; - - -#define WLED_CONNECTED (WiFi.status() == WL_CONNECTED) -#define WLED_WIFI_CONFIGURED (strlen(clientSSID) >= 1 && strcmp(clientSSID, DEFAULT_CLIENT_SSID) != 0) - -//debug macros -#ifdef WLED_DEBUG -#define DEBUG_PRINT(x) Serial.print(x) -#define DEBUG_PRINTLN(x) Serial.println(x) -#define DEBUG_PRINTF(x) Serial.printf(x) -extern unsigned long debugTime; -extern int lastWifiState; -extern unsigned long wifiStateChangedTime; -extern int loops; -#else -#define DEBUG_PRINT(x) -#define DEBUG_PRINTLN(x) -#define DEBUG_PRINTF(x) -#endif - - -// TODO: Inline? -//append new c string to temp buffer efficiently -bool oappend(const char *txt); -//append new number to temp buffer efficiently -bool oappendi(int i); -int getSignalQuality(int rssi); - -class WLED -{ -public: - static WLED &instance() - { - static WLED instance; - return instance; - } - - WLED(); - - void reset(); - void loop(); - - - //boot starts here - void setup() - { - wledInit(); - } - -public: // TODO: privacy - void wledInit(); - void beginStrip(); - - void handleConnection(); - void initAP(bool resetAP = false); - void initConnection(); - void initInterfaces(); -}; -#endif // WLED_H +#ifndef WLED_H +#define WLED_H + +/* + Main sketch, global variable declarations +*/ +/* + * @title WLED project sketch + * @version 0.9.1 + * @author Christian Schwinne + */ + +//ESP8266-01 (blue) got too little storage space to work with all features of WLED. To use it, you must use ESP8266 Arduino Core v2.4.2 and the setting 512K(No SPIFFS). + +//ESP8266-01 (black) has 1MB flash and can thus fit the whole program. Use 1M(64K SPIFFS). +//Uncomment some of the following lines to disable features to compile for ESP8266-01 (max flash size 434kB): + +//You are required to disable over-the-air updates: +//#define WLED_DISABLE_OTA //saves 14kb + +//You need to choose some of these features to disable: +//#define WLED_DISABLE_ALEXA //saves 11kb +//#define WLED_DISABLE_BLYNK //saves 6kb +//#define WLED_DISABLE_CRONIXIE //saves 3kb +//#define WLED_DISABLE_HUESYNC //saves 4kb +//#define WLED_DISABLE_INFRARED //there is no pin left for this on ESP8266-01, saves 12kb +#define WLED_ENABLE_MQTT //saves 12kb +#define WLED_ENABLE_ADALIGHT //saves 500b only +//#define WLED_ENABLE_DMX //uses 3.5kb + +#define WLED_DISABLE_FILESYSTEM //SPIFFS is not used by any WLED feature yet +//#define WLED_ENABLE_FS_SERVING //Enable sending html file from SPIFFS before serving progmem version +//#define WLED_ENABLE_FS_EDITOR //enable /edit page for editing SPIFFS content. Will also be disabled with OTA lock + +//to toggle usb serial debug (un)comment the following line +//#define WLED_DEBUG + +//library inclusions +#include +#ifdef WLED_ENABLE_DMX +#include +DMXESPSerial dmx; +#endif +#ifdef ESP8266 +#include +#include +#include +extern "C" +{ +#include +} +#else //ESP32 +#include +#include "esp_wifi.h" +#include +#include +#include "SPIFFS.h" +#endif + +#include +#include +#include +#include +#ifndef WLED_DISABLE_OTA +#include +#endif +#include +#include "src/dependencies/time/TimeLib.h" +#include "src/dependencies/timezone/Timezone.h" +#ifndef WLED_DISABLE_ALEXA +#define ESPALEXA_ASYNC +#define ESPALEXA_NO_SUBPAGE +#define ESPALEXA_MAXDEVICES 1 +// #define ESPALEXA_DEBUG +#include "src/dependencies/espalexa/Espalexa.h" +#endif +#ifndef WLED_DISABLE_BLYNK +#include "src/dependencies/blynk/BlynkSimpleEsp.h" +#endif +#include "src/dependencies/e131/ESPAsyncE131.h" +#include "src/dependencies/async-mqtt-client/AsyncMqttClient.h" +#include "src/dependencies/json/AsyncJson-v6.h" +#include "src/dependencies/json/ArduinoJson-v6.h" +#include "html_ui.h" +#include "html_settings.h" +#include "html_other.h" +#include "FX.h" +#include "ir_codes.h" +#include "const.h" + +#ifndef CLIENT_SSID +#define CLIENT_SSID DEFAULT_CLIENT_SSID +#endif + +#ifndef CLIENT_PASS +#define CLIENT_PASS "" +#endif + +#if IR_PIN < 0 +#ifndef WLED_DISABLE_INFRARED +#define WLED_DISABLE_INFRARED +#endif +#endif + +#ifndef WLED_DISABLE_INFRARED +#include +#include +#include +#endif + +// remove flicker because PWM signal of RGB channels can become out of phase +#if defined(WLED_USE_ANALOG_LEDS) && defined(ESP8266) +#include "src/dependencies/arduino/core_esp8266_waveform.h" +#endif + +// enable additional debug output +#ifdef WLED_DEBUG +#ifndef ESP8266 +#include +#endif +#endif + +//version code in format yymmddb (b = daily build) +#define VERSION 2003222 + +extern char versionString[]; + +//AP and OTA default passwords (for maximum change them!) +extern char apPass[65]; +extern char otaPass[33]; + +//Hardware CONFIG (only changeble HERE, not at runtime) +//LED strip pin, button pin and IR pin changeable in NpbWrapper.h! + +extern byte auxDefaultState; //0: input 1: high 2: low +extern byte auxTriggeredState; //0: input 1: high 2: low +extern char ntpServerName[33]; //NTP server to use + +//WiFi CONFIG (all these can be changed via web UI, no need to set them here) +extern char clientSSID[33]; +extern char clientPass[65]; +extern char cmDNS[33]; //mDNS address (placeholder, will be replaced by wledXXXXXXXXXXXX.local) +extern char apSSID[33]; //AP off by default (unless setup) +extern byte apChannel; //2.4GHz WiFi AP channel (1-13) +extern byte apHide; //hidden AP SSID +extern byte apBehavior; //access point opens when no connection after boot by default +extern IPAddress staticIP; //static IP of ESP +extern IPAddress staticGateway; //gateway (router) IP +extern IPAddress staticSubnet; //most common subnet in home networks +extern bool noWifiSleep; //disabling modem sleep modes will increase heat output and power usage, but may help with connection issues + +//LED CONFIG +extern uint16_t ledCount; //overcurrent prevented by ABL +extern bool useRGBW; //SK6812 strips can contain an extra White channel +#define ABL_MILLIAMPS_DEFAULT 850; //auto lower brightness to stay close to milliampere limit +extern bool turnOnAtBoot; //turn on LEDs at power-up +extern byte bootPreset; //save preset to load after power-up + +extern byte col[]; //current RGB(W) primary color. col[] should be updated if you want to change the color. +extern byte colSec[]; //current RGB(W) secondary color +extern byte briS; //default brightness + +extern byte nightlightTargetBri; //brightness after nightlight is over +extern byte nightlightDelayMins; +extern bool nightlightFade; //if enabled, light will gradually dim towards the target bri. Otherwise, it will instantly set after delay over +extern bool nightlightColorFade; //if enabled, light will gradually fade color from primary to secondary color. +extern bool fadeTransition; //enable crossfading color transition +extern uint16_t transitionDelay; //default crossfade duration in ms + +extern bool skipFirstLed; //ignore first LED in strip (useful if you need the LED as signal repeater) +extern byte briMultiplier; //% of brightness to set (to limit power, if you set it to 50 and set bri to 255, actual brightness will be 127) + +//User Interface CONFIG +extern char serverDescription[33]; //Name of module +extern bool syncToggleReceive; //UIs which only have a single button for sync should toggle send+receive if this is true, only send otherwise + +//Sync CONFIG +extern bool buttonEnabled; +extern byte irEnabled; //Infrared receiver + +extern uint16_t udpPort; //WLED notifier default port +extern uint16_t udpRgbPort; //Hyperion port + +extern bool receiveNotificationBrightness; //apply brightness from incoming notifications +extern bool receiveNotificationColor; //apply color +extern bool receiveNotificationEffects; //apply effects setup +extern bool notifyDirect; //send notification if change via UI or HTTP API +extern bool notifyButton; //send if updated by button or infrared remote +extern bool notifyAlexa; //send notification if updated via Alexa +extern bool notifyMacro; //send notification for macro +extern bool notifyHue; //send notification if Hue light changes +extern bool notifyTwice; //notifications use UDP: enable if devices don't sync reliably + +extern bool alexaEnabled; //enable device discovery by Amazon Echo +extern char alexaInvocationName[33]; //speech control name of device. Choose something voice-to-text can understand + +extern char blynkApiKey[36]; //Auth token for Blynk server. If empty, no connection will be made + +extern uint16_t realtimeTimeoutMs; //ms timeout of realtime mode before returning to normal mode +extern int arlsOffset; //realtime LED offset +extern bool receiveDirect; //receive UDP realtime +extern bool arlsDisableGammaCorrection; //activate if gamma correction is handled by the source +extern bool arlsForceMaxBri; //enable to force max brightness if source has very dark colors that would be black + +#define E131_MAX_UNIVERSE_COUNT 9 +extern uint16_t e131Universe; //settings for E1.31 (sACN) protocol (only DMX_MODE_MULTIPLE_* can span over consequtive universes) +extern uint8_t DMXMode; //DMX mode (s.a.) +extern uint16_t DMXAddress; //DMX start address of fixture, a.k.a. first Channel [for E1.31 (sACN) protocol] +extern uint8_t DMXOldDimmer; //only update brightness on change +extern uint8_t e131LastSequenceNumber[E131_MAX_UNIVERSE_COUNT]; //to detect packet loss +extern bool e131Multicast; //multicast or unicast +extern bool e131SkipOutOfSequence; //freeze instead of flickering + +extern bool mqttEnabled; +extern char mqttDeviceTopic[33]; //main MQTT topic (individual per device, default is wled/mac) +extern char mqttGroupTopic[33]; //second MQTT topic (for example to group devices) +extern char mqttServer[33]; //both domains and IPs should work (no SSL) +extern char mqttUser[41]; //optional: username for MQTT auth +extern char mqttPass[41]; //optional: password for MQTT auth +extern char mqttClientID[41]; //override the client ID +extern uint16_t mqttPort; + +extern bool huePollingEnabled; //poll hue bridge for light state +extern uint16_t huePollIntervalMs; //low values (< 1sec) may cause lag but offer quicker response +extern char hueApiKey[47]; //key token will be obtained from bridge +extern byte huePollLightId; //ID of hue lamp to sync to. Find the ID in the hue app ("about" section) +extern IPAddress hueIP; //IP address of the bridge +extern bool hueApplyOnOff; +extern bool hueApplyBri; +extern bool hueApplyColor; + +//Time CONFIG +extern bool ntpEnabled; //get internet time. Only required if you use clock overlays or time-activated macros +extern bool useAMPM; //12h/24h clock format +extern byte currentTimezone; //Timezone ID. Refer to timezones array in wled_ntp.cpp +extern int utcOffsetSecs; //Seconds to offset from UTC before timzone calculation + +extern byte overlayDefault; //0: no overlay 1: analog clock 2: single-digit clocl 3: cronixie +extern byte overlayMin; //boundaries of overlay mode +extern byte overlayMax; + +extern byte analogClock12pixel; //The pixel in your strip where "midnight" would be +extern bool analogClockSecondsTrail; //Display seconds as trail of LEDs instead of a single pixel +extern bool analogClock5MinuteMarks; //Light pixels at every 5-minute position + +extern char cronixieDisplay[7]; //Cronixie Display mask. See wled13_cronixie.ino +extern bool cronixieBacklight; //Allow digits to be back-illuminated + +extern bool countdownMode; //Clock will count down towards date +extern byte countdownYear, countdownMonth; //Countdown target date, year is last two digits +extern byte countdownDay, countdownHour; +extern byte countdownMin, countdownSec; + +extern byte macroBoot; //macro loaded after startup +extern byte macroNl; //after nightlight delay over +extern byte macroCountdown; +extern byte macroAlexaOn, macroAlexaOff; +extern byte macroButton, macroLongPress, macroDoublePress; + +//Security CONFIG +extern bool otaLock; //prevents OTA firmware updates without password. ALWAYS enable if system exposed to any public networks +extern bool wifiLock; //prevents access to WiFi settings when OTA lock is enabled +extern bool aOtaEnabled; //ArduinoOTA allows easy updates directly from the IDE. Careful, it does not auto-disable when OTA lock is on + +extern uint16_t userVar0, userVar1; + +#ifdef WLED_ENABLE_DMX +//dmx CONFIG +extern byte DMXChannels; // number of channels per fixture +extern byte DMXFixtureMap[15]; +extern // assigns the different channels to different functions. See wled21_dmx.ino for more information. +extern uint16_t DMXGap; // gap between the fixtures. makes addressing easier because you don't have to memorize odd numbers when climbing up onto a rig. +extern uint16_t DMXStart; // start address of the first fixture +#endif + +//internal global variable declarations +//wifi +extern bool apActive; +extern bool forceReconnect; +extern uint32_t lastReconnectAttempt; +extern bool interfacesInited; +extern bool wasConnected; + +//color +extern byte colOld[]; //color before transition +extern byte colT[]; //color that is currently displayed on the LEDs +extern byte colIT[]; //color that was last sent to LEDs +extern byte colSecT[]; +extern byte colSecOld[]; +extern byte colSecIT[]; + +extern byte lastRandomIndex; //used to save last random color so the new one is not the same + +//transitions +extern bool transitionActive; +extern uint16_t transitionDelayDefault; +extern uint16_t transitionDelayTemp; +extern unsigned long transitionStartTime; +extern float tperLast; //crossfade transition progress, 0.0f - 1.0f +extern bool jsonTransitionOnce; + +//nightlight +extern bool nightlightActive; +extern bool nightlightActiveOld; +extern uint32_t nightlightDelayMs; +extern uint8_t nightlightDelayMinsDefault; +extern unsigned long nightlightStartTime; +extern byte briNlT; //current nightlight brightness +extern byte colNlT[]; //current nightlight color + +extern unsigned long lastOnTime; +extern bool offMode; +extern byte bri; +extern byte briOld; +extern byte briT; +extern byte briIT; +extern byte briLast; //brightness before turned off. Used for toggle function +extern byte whiteLast; //white channel before turned off. Used for toggle function + +extern bool buttonPressedBefore; +extern bool buttonLongPressed; +extern unsigned long buttonPressedTime; +extern unsigned long buttonWaitTime; + +extern bool notifyDirectDefault; +extern bool receiveNotifications; +extern unsigned long notificationSentTime; +extern byte notificationSentCallMode; +extern bool notificationTwoRequired; + +extern byte effectCurrent; +extern byte effectSpeed; +extern byte effectIntensity; +extern byte effectPalette; + +extern bool udpConnected, udpRgbConnected; + +extern bool showWelcomePage; + +extern byte hueError; +//uint16_t hueFailCount; +extern float hueXLast, hueYLast; +extern uint16_t hueHueLast, hueCtLast; +extern byte hueSatLast, hueBriLast; +extern unsigned long hueLastRequestSent; +extern bool hueAuthRequired; +extern bool hueReceived; +extern bool hueStoreAllowed, hueNewKey; + +extern byte overlayCurrent; +extern byte overlaySpeed; +extern unsigned long overlayRefreshMs; +extern unsigned long overlayRefreshedTime; + +extern byte dP[]; +extern bool cronixieInit; + +//countdown +extern unsigned long countdownTime; +extern bool countdownOverTriggered; + +//timer +extern byte lastTimerMinute; +extern byte timerHours[]; +extern byte timerMinutes[]; +extern byte timerMacro[]; +extern byte timerWeekday[]; //weekdays to activate on +//bit pattern of arr elem: 0b11111111: sun,sat,fri,thu,wed,tue,mon,validity + +//blynk +extern bool blynkEnabled; + +//preset cycling +extern bool presetCyclingEnabled; +extern byte presetCycleMin, presetCycleMax; +extern uint16_t presetCycleTime; +extern unsigned long presetCycledTime; +extern byte presetCycCurr; +extern bool presetApplyBri; +extern bool saveCurrPresetCycConf; + +//realtime +extern byte realtimeMode; +extern IPAddress realtimeIP; +extern unsigned long realtimeTimeout; + +//mqtt +extern long lastMqttReconnectAttempt; +extern long lastInterfaceUpdate; +extern byte interfaceUpdateCallMode; +extern char mqttStatusTopic[40]; //this must be global because of async handlers + +#if AUXPIN >= 0 +//auxiliary debug pin +extern byte auxTime; +extern unsigned long auxStartTime; +extern bool auxActive; +#endif + +//alexa udp +extern String escapedMac; +#ifndef WLED_DISABLE_ALEXA +extern Espalexa espalexa; +extern EspalexaDevice *espalexaDevice; +#endif + +//dns server +extern DNSServer dnsServer; + +//network time +extern bool ntpConnected; +extern time_t local; +extern unsigned long ntpLastSyncTime; +extern unsigned long ntpPacketSentTime; +extern IPAddress ntpServerIP; +extern uint16_t ntpLocalPort; +#define NTP_PACKET_SIZE 48 + +//maximum number of LEDs - MAX_LEDS is coming from the JSON response getting too big, MAX_LEDS_DMA will become a timing issue +#define MAX_LEDS 1500 +#define MAX_LEDS_DMA 500 + +//string temp buffer (now stored in stack locally) +#define OMAX 2048 +extern char *obuf; +extern uint16_t olen; + +//presets +extern uint16_t savedPresets; +extern int8_t currentPreset; +extern bool isPreset; + +extern byte errorFlag; + +extern String messageHead, messageSub; +extern byte optionType; + +extern bool doReboot; //flag to initiate reboot from async handlers +extern bool doPublishMqtt; + +//server library objects +extern AsyncWebServer server; +extern AsyncClient *hueClient; +extern AsyncMqttClient *mqtt; + +//function prototypes +extern void colorFromUint32(uint32_t, bool); +extern void serveMessage(AsyncWebServerRequest *, uint16_t, String, String, byte); +extern void handleE131Packet(e131_packet_t *, IPAddress); +extern void arlsLock(uint32_t, byte); +extern void handleOverlayDraw(); + +//udp interface objects +extern WiFiUDP notifierUdp, rgbUdp; +extern WiFiUDP ntpUdp; +extern ESPAsyncE131 e131; +extern bool e131NewData; + +//led fx library object +extern WS2812FX strip; + + +#define WLED_CONNECTED (WiFi.status() == WL_CONNECTED) +#define WLED_WIFI_CONFIGURED (strlen(clientSSID) >= 1 && strcmp(clientSSID, DEFAULT_CLIENT_SSID) != 0) + +//debug macros +#ifdef WLED_DEBUG +#define DEBUG_PRINT(x) Serial.print(x) +#define DEBUG_PRINTLN(x) Serial.println(x) +#define DEBUG_PRINTF(x) Serial.printf(x) +extern unsigned long debugTime; +extern int lastWifiState; +extern unsigned long wifiStateChangedTime; +extern int loops; +#else +#define DEBUG_PRINT(x) +#define DEBUG_PRINTLN(x) +#define DEBUG_PRINTF(x) +#endif + + +// TODO: Inline? +//append new c string to temp buffer efficiently +bool oappend(const char *txt); +//append new number to temp buffer efficiently +bool oappendi(int i); +int getSignalQuality(int rssi); + +class WLED +{ +public: + static WLED &instance() + { + static WLED instance; + return instance; + } + + WLED(); + + void reset(); + void loop(); + + + //boot starts here + void setup() + { + wledInit(); + } + +public: // TODO: privacy + void wledInit(); + void beginStrip(); + + void handleConnection(); + void initAP(bool resetAP = false); + void initConnection(); + void initInterfaces(); +}; +#endif // WLED_H diff --git a/wled00/wled_xml.cpp b/wled00/xml.cpp similarity index 99% rename from wled00/wled_xml.cpp rename to wled00/xml.cpp index 4f2b3f45b..930ab4b0d 100644 --- a/wled00/wled_xml.cpp +++ b/wled00/xml.cpp @@ -1,7 +1,7 @@ -#include "wled_xml.h" +#include "xml.h" #include "wled.h" -#include "wled_eeprom.h" -#include "wled_ntp.h" +#include "eeprom.h" +#include "ntp.h" //build XML response to HTTP /win API request diff --git a/wled00/wled_xml.h b/wled00/xml.h similarity index 96% rename from wled00/wled_xml.h rename to wled00/xml.h index 5cfb1dbb7..022982959 100644 --- a/wled00/wled_xml.h +++ b/wled00/xml.h @@ -1,15 +1,15 @@ -#ifndef WLED_XML_H -#define WLED_XML_H -#include -#include - -/* - * Sending XML status files to client - */ -char* XML_response(AsyncWebServerRequest *request, char* dest = nullptr); -char* URL_response(AsyncWebServerRequest *request); -void sappend(char stype, const char* key, int val); -void sappends(char stype, const char* key, char* val); -void getSettingsJS(byte subPage, char* dest); - +#ifndef WLED_XML_H +#define WLED_XML_H +#include +#include + +/* + * Sending XML status files to client + */ +char* XML_response(AsyncWebServerRequest *request, char* dest = nullptr); +char* URL_response(AsyncWebServerRequest *request); +void sappend(char stype, const char* key, int val); +void sappends(char stype, const char* key, char* val); +void getSettingsJS(byte subPage, char* dest); + #endif // WLED_XML_H \ No newline at end of file From f99f13a090581ea037850b167edbe15d3d0daf98 Mon Sep 17 00:00:00 2001 From: Travis J Dean Date: Sat, 28 Mar 2020 08:45:20 -0400 Subject: [PATCH 63/90] Avoid name collision. Fix wled instance access in ino. --- usermods/blynk_relay_control/wled06_usermod.ino | 2 +- usermods/stairway_wipe_basic/wled06_usermod.ino | 2 +- wled00/alexa.cpp | 2 +- wled00/button.cpp | 2 +- wled00/hue.cpp | 2 +- wled00/ir.cpp | 2 +- wled00/json.cpp | 2 +- wled00/led.cpp | 2 +- wled00/ntp.cpp | 4 +++- wled00/set.cpp | 2 +- wled00/usermod.cpp | 2 +- wled00/wled.cpp | 3 +-- wled00/wled00.ino | 6 ++---- wled00/{eeprom.cpp => wled_eeprom.cpp} | 3 ++- wled00/{eeprom.h => wled_eeprom.h} | 0 wled00/xml.cpp | 2 +- 16 files changed, 19 insertions(+), 19 deletions(-) rename wled00/{eeprom.cpp => wled_eeprom.cpp} (99%) rename wled00/{eeprom.h => wled_eeprom.h} (100%) diff --git a/usermods/blynk_relay_control/wled06_usermod.ino b/usermods/blynk_relay_control/wled06_usermod.ino index 1004b1ed3..d4028ea5d 100644 --- a/usermods/blynk_relay_control/wled06_usermod.ino +++ b/usermods/blynk_relay_control/wled06_usermod.ino @@ -1,7 +1,7 @@ /* * This file allows you to add own functionality to WLED more easily * See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality - * EEPROM bytes 2750+ are reserved for your custom use case. (if you extend #define EEPSIZE in wled01_eeprom.h) + * EEPROM bytes 2750+ are reserved for your custom use case. (if you extend #define EEPSIZE in wled_eeprom.h) * bytes 2400+ are currently ununsed, but might be used for future wled features */ diff --git a/usermods/stairway_wipe_basic/wled06_usermod.ino b/usermods/stairway_wipe_basic/wled06_usermod.ino index 3d493cc1d..0cc85df74 100644 --- a/usermods/stairway_wipe_basic/wled06_usermod.ino +++ b/usermods/stairway_wipe_basic/wled06_usermod.ino @@ -1,7 +1,7 @@ /* * This file allows you to add own functionality to WLED more easily * See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality - * EEPROM bytes 2750+ are reserved for your custom use case. (if you extend #define EEPSIZE in wled01_eeprom.h) + * EEPROM bytes 2750+ are reserved for your custom use case. (if you extend #define EEPSIZE in wled_eeprom.h) * bytes 2400+ are currently ununsed, but might be used for future wled features */ diff --git a/wled00/alexa.cpp b/wled00/alexa.cpp index 621480d0c..e35f5b358 100644 --- a/wled00/alexa.cpp +++ b/wled00/alexa.cpp @@ -2,7 +2,7 @@ #include "wled.h" #include "const.h" #include "led.h" -#include "eeprom.h" +#include "wled_eeprom.h" #include "colors.h" #ifndef WLED_DISABLE_ALEXA diff --git a/wled00/button.cpp b/wled00/button.cpp index 1afb54d57..a80061eeb 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -1,7 +1,7 @@ #include "button.h" #include "wled.h" #include "led.h" -#include "eeprom.h" +#include "wled_eeprom.h" #include "set.h" /* diff --git a/wled00/hue.cpp b/wled00/hue.cpp index 484f2bd84..a186a3144 100644 --- a/wled00/hue.cpp +++ b/wled00/hue.cpp @@ -1,7 +1,7 @@ #include "hue.h" #include "wled.h" #include "colors.h" -#include "eeprom.h" +#include "wled_eeprom.h" #include "notify.h" #include "led.h" diff --git a/wled00/ir.cpp b/wled00/ir.cpp index fcc0f5dda..32b0ee937 100644 --- a/wled00/ir.cpp +++ b/wled00/ir.cpp @@ -2,7 +2,7 @@ #include "wled.h" #include "led.h" #include "colors.h" -#include "eeprom.h" +#include "wled_eeprom.h" #if defined(WLED_DISABLE_INFRARED) void handleIR(){} diff --git a/wled00/json.cpp b/wled00/json.cpp index 73659e7ed..6d71e4141 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -1,6 +1,6 @@ #include "json.h" #include "wled.h" -#include "eeprom.h" +#include "wled_eeprom.h" #include "led.h" void deserializeSegment(JsonObject elem, byte it) diff --git a/wled00/led.cpp b/wled00/led.cpp index 4a16dcff8..7f4d63095 100644 --- a/wled00/led.cpp +++ b/wled00/led.cpp @@ -2,7 +2,7 @@ #include "wled.h" #include "notify.h" #include "blynk.h" -#include "eeprom.h" +#include "wled_eeprom.h" #include "mqtt.h" #include "colors.h" diff --git a/wled00/ntp.cpp b/wled00/ntp.cpp index 3435760b7..206631097 100644 --- a/wled00/ntp.cpp +++ b/wled00/ntp.cpp @@ -1,6 +1,8 @@ #include "ntp.h" +#include "src/dependencies/timezone/Timezone.h" #include "wled.h" -#include "eeprom.h" +#include "wled_eeprom.h" + TimeChangeRule UTCr = {Last, Sun, Mar, 1, 0}; // UTC Timezone tzUTC(UTCr, UTCr); diff --git a/wled00/set.cpp b/wled00/set.cpp index 1dfeb19d0..0a7a4ff4f 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -4,7 +4,7 @@ #include "hue.h" #include "led.h" #include "blynk.h" -#include "eeprom.h" +#include "wled_eeprom.h" #include "alexa.h" #include "cronixie.h" #include "xml.h" diff --git a/wled00/usermod.cpp b/wled00/usermod.cpp index c780ffeeb..6589c07c8 100644 --- a/wled00/usermod.cpp +++ b/wled00/usermod.cpp @@ -2,7 +2,7 @@ /* * This file allows you to add own functionality to WLED more easily * See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality - * EEPROM bytes 2750+ are reserved for your custom use case. (if you extend #define EEPSIZE in wled01_eeprom.h) + * EEPROM bytes 2750+ are reserved for your custom use case. (if you extend #define EEPSIZE in wled01_wled_eeprom.h) * bytes 2400+ are currently ununsed, but might be used for future wled features */ diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 106f503b2..2d474eb3b 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -12,9 +12,8 @@ #include "blynk.h" #include "hue.h" #include "mqtt.h" -#include "eeprom.h" +#include "wled_eeprom.h" #include "server.h" -// replaced // Global Variable definitions char versionString[] = "0.9.1"; diff --git a/wled00/wled00.ino b/wled00/wled00.ino index 522447d0a..e33348379 100644 --- a/wled00/wled00.ino +++ b/wled00/wled00.ino @@ -3,12 +3,10 @@ */ #include "wled.h" -WLED& wled; void setup() { - wled = WLED::instance(); - wled.setup(); + WLED::instance().setup(); } void loop() { - wled.loop(); + WLED::instance().loop(); } \ No newline at end of file diff --git a/wled00/eeprom.cpp b/wled00/wled_eeprom.cpp similarity index 99% rename from wled00/eeprom.cpp rename to wled00/wled_eeprom.cpp index 68b8c35f9..0087641aa 100644 --- a/wled00/eeprom.cpp +++ b/wled00/wled_eeprom.cpp @@ -1,4 +1,5 @@ -#include "eeprom.h" +#include "wled_eeprom.h" +#include #include "wled.h" #include "cronixie.h" #include "ntp.h" diff --git a/wled00/eeprom.h b/wled00/wled_eeprom.h similarity index 100% rename from wled00/eeprom.h rename to wled00/wled_eeprom.h diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 930ab4b0d..09e42011d 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -1,6 +1,6 @@ #include "xml.h" #include "wled.h" -#include "eeprom.h" +#include "wled_eeprom.h" #include "ntp.h" From cc2de04f6b3c912291bbf57d15a06f40ccf99620 Mon Sep 17 00:00:00 2001 From: Travis J Dean Date: Sat, 28 Mar 2020 09:24:07 -0400 Subject: [PATCH 64/90] Avoid name collision. --- wled00/wled.cpp | 2 +- wled00/{server.cpp => wled_server.cpp} | 2 +- wled00/{server.h => wled_server.h} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename wled00/{server.cpp => wled_server.cpp} (99%) rename wled00/{server.h => wled_server.h} (100%) diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 2d474eb3b..4945ffb78 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -13,7 +13,7 @@ #include "hue.h" #include "mqtt.h" #include "wled_eeprom.h" -#include "server.h" +#include "wled_server.h" // Global Variable definitions char versionString[] = "0.9.1"; diff --git a/wled00/server.cpp b/wled00/wled_server.cpp similarity index 99% rename from wled00/server.cpp rename to wled00/wled_server.cpp index 3249466cf..d5b4eebce 100644 --- a/wled00/server.cpp +++ b/wled00/wled_server.cpp @@ -1,4 +1,4 @@ -#include "server.h" +#include "wled_server.h" #include "wled.h" #include "file.h" #include "set.h" diff --git a/wled00/server.h b/wled00/wled_server.h similarity index 100% rename from wled00/server.h rename to wled00/wled_server.h From 8264ca4e9e283f71a95ea5c1e84e03d9d1717d49 Mon Sep 17 00:00:00 2001 From: Aircoookie Date: Sat, 28 Mar 2020 21:30:32 +0100 Subject: [PATCH 65/90] Create stale.yml (to reduce older open issues) --- .github/stale.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/stale.yml diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 000000000..811db619a --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,20 @@ +# Number of days of inactivity before an issue becomes stale +daysUntilStale: 120 +# Number of days of inactivity before a stale issue is closed +daysUntilClose: 7 +# Issues with these labels will never be considered stale +exemptLabels: + - pinned + - keep + - enhancement + - confirmed +# Label to use when marking an issue as stale +staleLabel: stale +# Comment to post when marking an issue as stale. Set to `false` to disable +markComment: > + Hey! This issue has been open for quite some time without any new comments now. + It will be closed automatically in a week if no further activity occurs. + + Thank you for using WLED! +# Comment to post when closing a stale issue. Set to `false` to disable +closeComment: false From d2f55e1064a1188f0b831979c4e9ddde96c29a53 Mon Sep 17 00:00:00 2001 From: Travis J Dean Date: Mon, 30 Mar 2020 03:55:44 -0400 Subject: [PATCH 66/90] Make dmx.h header only. --- wled00/dmx.cpp | 52 ----------------------------------------------- wled00/dmx.h | 55 +++++++++++++++++++++++++++++++++++++++++++++++++- wled00/wled.h | 4 ---- 3 files changed, 54 insertions(+), 57 deletions(-) delete mode 100644 wled00/dmx.cpp diff --git a/wled00/dmx.cpp b/wled00/dmx.cpp deleted file mode 100644 index d024ac713..000000000 --- a/wled00/dmx.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include "dmx.h" -#include "wled.h" - -#ifdef WLED_ENABLE_DMX -void handleDMX() { - // TODO: calculate brightness manually if no shutter channel is set - - uint8_t brightness = strip.getBrightness(); - - for (int i = 0; i < ledCount; i++) { // uses the amount of LEDs as fixture count - - uint32_t in = strip.getPixelColor(i); // time to get the colors for the individual fixtures as suggested by AirCookie at issue #462 - byte w = in >> 24 & 0xFF; - byte r = in >> 16 & 0xFF; - byte g = in >> 8 & 0xFF; - byte b = in & 0xFF; - - int DMXFixtureStart = DMXStart + (DMXGap * i); - for (int j = 0; j < DMXChannels; j++) { - int DMXAddr = DMXFixtureStart + j; - switch (DMXFixtureMap[j]) { - case 0: // Set this channel to 0. Good way to tell strobe- and fade-functions to fuck right off. - dmx.write(DMXAddr, 0); - break; - case 1: // Red - dmx.write(DMXAddr, r); - break; - case 2: // Green - dmx.write(DMXAddr, g); - break; - case 3: // Blue - dmx.write(DMXAddr, b); - break; - case 4: // White - dmx.write(DMXAddr, w); - break; - case 5: // Shutter channel. Controls the brightness. - dmx.write(DMXAddr, brightness); - break; - case 6:// Sets this channel to 255. Like 0, but more wholesome. - dmx.write(DMXAddr, 255); - break; - } - } - } - - dmx.update(); // update the DMX bus -} - -#else -void handleDMX() {} -#endif diff --git a/wled00/dmx.h b/wled00/dmx.h index 707dec915..76aab4849 100644 --- a/wled00/dmx.h +++ b/wled00/dmx.h @@ -1,11 +1,64 @@ #ifndef WLED_DMX_H #define WLED_DMX_H +#include "wled.h" /* * Support for DMX via MAX485. * Needs the espdmx library. You might have to change the output pin within the library. Sketchy, i know. * https://github.com/Rickgg/ESP-Dmx */ -void handleDMX(); +#ifdef WLED_ENABLE_DMX +#include +DMXESPSerial dmx; + +#ifdef WLED_ENABLE_DMX +void handleDMX() { + // TODO: calculate brightness manually if no shutter channel is set + + uint8_t brightness = strip.getBrightness(); + + for (int i = 0; i < ledCount; i++) { // uses the amount of LEDs as fixture count + + uint32_t in = strip.getPixelColor(i); // time to get the colors for the individual fixtures as suggested by AirCookie at issue #462 + byte w = in >> 24 & 0xFF; + byte r = in >> 16 & 0xFF; + byte g = in >> 8 & 0xFF; + byte b = in & 0xFF; + + int DMXFixtureStart = DMXStart + (DMXGap * i); + for (int j = 0; j < DMXChannels; j++) { + int DMXAddr = DMXFixtureStart + j; + switch (DMXFixtureMap[j]) { + case 0: // Set this channel to 0. Good way to tell strobe- and fade-functions to fuck right off. + dmx.write(DMXAddr, 0); + break; + case 1: // Red + dmx.write(DMXAddr, r); + break; + case 2: // Green + dmx.write(DMXAddr, g); + break; + case 3: // Blue + dmx.write(DMXAddr, b); + break; + case 4: // White + dmx.write(DMXAddr, w); + break; + case 5: // Shutter channel. Controls the brightness. + dmx.write(DMXAddr, brightness); + break; + case 6:// Sets this channel to 255. Like 0, but more wholesome. + dmx.write(DMXAddr, 255); + break; + } + } + } + + dmx.update(); // update the DMX bus +} + +#else +void handleDMX() {} +#endif #endif //WLED_DMX_H \ No newline at end of file diff --git a/wled00/wled.h b/wled00/wled.h index c6f791cd4..e5c4f493b 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -37,10 +37,6 @@ //library inclusions #include -#ifdef WLED_ENABLE_DMX -#include -DMXESPSerial dmx; -#endif #ifdef ESP8266 #include #include From 3e1eb02f54b674cbe74064f81b4eca987394e97f Mon Sep 17 00:00:00 2001 From: Travis J Dean Date: Mon, 30 Mar 2020 04:21:47 -0400 Subject: [PATCH 67/90] Comment cleanup and line reduction. --- wled00/wled.h | 345 ++++++++++++++++++-------------------------------- 1 file changed, 124 insertions(+), 221 deletions(-) diff --git a/wled00/wled.h b/wled00/wled.h index e5c4f493b..9e373216f 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -1,41 +1,39 @@ #ifndef WLED_H #define WLED_H - /* Main sketch, global variable declarations -*/ -/* - * @title WLED project sketch - * @version 0.9.1 - * @author Christian Schwinne + @title WLED project sketch + @version 0.9.1 + @author Christian Schwinne */ -//ESP8266-01 (blue) got too little storage space to work with all features of WLED. To use it, you must use ESP8266 Arduino Core v2.4.2 and the setting 512K(No SPIFFS). +// ESP8266-01 (blue) got too little storage space to work with all features of WLED. To use it, you must use ESP8266 Arduino Core v2.4.2 and the setting 512K(No SPIFFS). -//ESP8266-01 (black) has 1MB flash and can thus fit the whole program. Use 1M(64K SPIFFS). -//Uncomment some of the following lines to disable features to compile for ESP8266-01 (max flash size 434kB): +// ESP8266-01 (black) has 1MB flash and can thus fit the whole program. Use 1M(64K SPIFFS). +// Uncomment some of the following lines to disable features to compile for ESP8266-01 (max flash size 434kB): +// Alternatively, with platformio pass your chosen flags to your custom build target in platformio.ini.override -//You are required to disable over-the-air updates: +// You are required to disable over-the-air updates: //#define WLED_DISABLE_OTA //saves 14kb -//You need to choose some of these features to disable: +// You need to choose some of these features to disable: //#define WLED_DISABLE_ALEXA //saves 11kb //#define WLED_DISABLE_BLYNK //saves 6kb //#define WLED_DISABLE_CRONIXIE //saves 3kb //#define WLED_DISABLE_HUESYNC //saves 4kb //#define WLED_DISABLE_INFRARED //there is no pin left for this on ESP8266-01, saves 12kb -#define WLED_ENABLE_MQTT //saves 12kb -#define WLED_ENABLE_ADALIGHT //saves 500b only +#define WLED_ENABLE_MQTT //saves 12kb +#define WLED_ENABLE_ADALIGHT //saves 500b only //#define WLED_ENABLE_DMX //uses 3.5kb #define WLED_DISABLE_FILESYSTEM //SPIFFS is not used by any WLED feature yet //#define WLED_ENABLE_FS_SERVING //Enable sending html file from SPIFFS before serving progmem version //#define WLED_ENABLE_FS_EDITOR //enable /edit page for editing SPIFFS content. Will also be disabled with OTA lock -//to toggle usb serial debug (un)comment the following line +// to toggle usb serial debug (un)comment the following line //#define WLED_DEBUG -//library inclusions +// Library inclusions. #include #ifdef ESP8266 #include @@ -116,225 +114,171 @@ extern "C" #endif #endif -//version code in format yymmddb (b = daily build) +// version code in format yymmddb (b = daily build) #define VERSION 2003222 +// Global external variable declaration. See wled.cpp for definitions and comments. extern char versionString[]; - -//AP and OTA default passwords (for maximum change them!) extern char apPass[65]; extern char otaPass[33]; - -//Hardware CONFIG (only changeble HERE, not at runtime) -//LED strip pin, button pin and IR pin changeable in NpbWrapper.h! - -extern byte auxDefaultState; //0: input 1: high 2: low -extern byte auxTriggeredState; //0: input 1: high 2: low -extern char ntpServerName[33]; //NTP server to use - -//WiFi CONFIG (all these can be changed via web UI, no need to set them here) +extern byte auxDefaultState; +extern byte auxTriggeredState; +extern char ntpServerName[33]; extern char clientSSID[33]; extern char clientPass[65]; -extern char cmDNS[33]; //mDNS address (placeholder, will be replaced by wledXXXXXXXXXXXX.local) -extern char apSSID[33]; //AP off by default (unless setup) -extern byte apChannel; //2.4GHz WiFi AP channel (1-13) -extern byte apHide; //hidden AP SSID -extern byte apBehavior; //access point opens when no connection after boot by default -extern IPAddress staticIP; //static IP of ESP -extern IPAddress staticGateway; //gateway (router) IP -extern IPAddress staticSubnet; //most common subnet in home networks -extern bool noWifiSleep; //disabling modem sleep modes will increase heat output and power usage, but may help with connection issues - -//LED CONFIG -extern uint16_t ledCount; //overcurrent prevented by ABL -extern bool useRGBW; //SK6812 strips can contain an extra White channel -#define ABL_MILLIAMPS_DEFAULT 850; //auto lower brightness to stay close to milliampere limit -extern bool turnOnAtBoot; //turn on LEDs at power-up -extern byte bootPreset; //save preset to load after power-up - -extern byte col[]; //current RGB(W) primary color. col[] should be updated if you want to change the color. -extern byte colSec[]; //current RGB(W) secondary color -extern byte briS; //default brightness - -extern byte nightlightTargetBri; //brightness after nightlight is over +extern char cmDNS[33]; +extern char apSSID[33]; +extern byte apChannel; +extern byte apHide; +extern byte apBehavior; +extern IPAddress staticIP; +extern IPAddress staticGateway; +extern IPAddress staticSubnet; +extern bool noWifiSleep; +extern uint16_t ledCount; +extern bool useRGBW; +#define ABL_MILLIAMPS_DEFAULT 850; +extern bool turnOnAtBoot; +extern byte bootPreset; +extern byte col[]; +extern byte colSec[]; +extern byte briS; +extern byte nightlightTargetBri; extern byte nightlightDelayMins; -extern bool nightlightFade; //if enabled, light will gradually dim towards the target bri. Otherwise, it will instantly set after delay over -extern bool nightlightColorFade; //if enabled, light will gradually fade color from primary to secondary color. -extern bool fadeTransition; //enable crossfading color transition -extern uint16_t transitionDelay; //default crossfade duration in ms - -extern bool skipFirstLed; //ignore first LED in strip (useful if you need the LED as signal repeater) -extern byte briMultiplier; //% of brightness to set (to limit power, if you set it to 50 and set bri to 255, actual brightness will be 127) - -//User Interface CONFIG -extern char serverDescription[33]; //Name of module -extern bool syncToggleReceive; //UIs which only have a single button for sync should toggle send+receive if this is true, only send otherwise - -//Sync CONFIG +extern bool nightlightFade; +extern bool nightlightColorFade; +extern bool fadeTransition; +extern uint16_t transitionDelay; +extern bool skipFirstLed; +extern byte briMultiplier; +extern char serverDescription[33]; +extern bool syncToggleReceive; extern bool buttonEnabled; -extern byte irEnabled; //Infrared receiver - -extern uint16_t udpPort; //WLED notifier default port -extern uint16_t udpRgbPort; //Hyperion port - -extern bool receiveNotificationBrightness; //apply brightness from incoming notifications -extern bool receiveNotificationColor; //apply color -extern bool receiveNotificationEffects; //apply effects setup -extern bool notifyDirect; //send notification if change via UI or HTTP API -extern bool notifyButton; //send if updated by button or infrared remote -extern bool notifyAlexa; //send notification if updated via Alexa -extern bool notifyMacro; //send notification for macro -extern bool notifyHue; //send notification if Hue light changes -extern bool notifyTwice; //notifications use UDP: enable if devices don't sync reliably - -extern bool alexaEnabled; //enable device discovery by Amazon Echo -extern char alexaInvocationName[33]; //speech control name of device. Choose something voice-to-text can understand - -extern char blynkApiKey[36]; //Auth token for Blynk server. If empty, no connection will be made - -extern uint16_t realtimeTimeoutMs; //ms timeout of realtime mode before returning to normal mode -extern int arlsOffset; //realtime LED offset -extern bool receiveDirect; //receive UDP realtime -extern bool arlsDisableGammaCorrection; //activate if gamma correction is handled by the source -extern bool arlsForceMaxBri; //enable to force max brightness if source has very dark colors that would be black - +extern byte irEnabled; +extern uint16_t udpPort; +extern uint16_t udpRgbPort; +extern bool receiveNotificationBrightness; +extern bool receiveNotificationColor; +extern bool receiveNotificationEffects; +extern bool notifyDirect; +extern bool notifyButton; +extern bool notifyAlexa; +extern bool notifyMacro; +extern bool notifyHue; +extern bool notifyTwice; +extern bool alexaEnabled; +extern char alexaInvocationName[33]; +extern char blynkApiKey[36]; +extern uint16_t realtimeTimeoutMs; +extern int arlsOffset; +extern bool receiveDirect; +extern bool arlsDisableGammaCorrection; +extern bool arlsForceMaxBri; #define E131_MAX_UNIVERSE_COUNT 9 -extern uint16_t e131Universe; //settings for E1.31 (sACN) protocol (only DMX_MODE_MULTIPLE_* can span over consequtive universes) -extern uint8_t DMXMode; //DMX mode (s.a.) -extern uint16_t DMXAddress; //DMX start address of fixture, a.k.a. first Channel [for E1.31 (sACN) protocol] -extern uint8_t DMXOldDimmer; //only update brightness on change -extern uint8_t e131LastSequenceNumber[E131_MAX_UNIVERSE_COUNT]; //to detect packet loss -extern bool e131Multicast; //multicast or unicast -extern bool e131SkipOutOfSequence; //freeze instead of flickering - +extern uint16_t e131Universe; +extern uint8_t DMXMode; +extern uint16_t DMXAddress; +extern uint8_t DMXOldDimmer; +extern uint8_t e131LastSequenceNumber[E131_MAX_UNIVERSE_COUNT]; +extern bool e131Multicast; +extern bool e131SkipOutOfSequence; extern bool mqttEnabled; -extern char mqttDeviceTopic[33]; //main MQTT topic (individual per device, default is wled/mac) -extern char mqttGroupTopic[33]; //second MQTT topic (for example to group devices) -extern char mqttServer[33]; //both domains and IPs should work (no SSL) -extern char mqttUser[41]; //optional: username for MQTT auth -extern char mqttPass[41]; //optional: password for MQTT auth -extern char mqttClientID[41]; //override the client ID +extern char mqttDeviceTopic[33]; +extern char mqttGroupTopic[33]; +extern char mqttServer[33]; +extern char mqttUser[41]; +extern char mqttPass[41]; +extern char mqttClientID[41]; extern uint16_t mqttPort; - -extern bool huePollingEnabled; //poll hue bridge for light state -extern uint16_t huePollIntervalMs; //low values (< 1sec) may cause lag but offer quicker response -extern char hueApiKey[47]; //key token will be obtained from bridge -extern byte huePollLightId; //ID of hue lamp to sync to. Find the ID in the hue app ("about" section) -extern IPAddress hueIP; //IP address of the bridge +extern bool huePollingEnabled; +extern uint16_t huePollIntervalMs; +extern char hueApiKey[47]; +extern byte huePollLightId; +extern IPAddress hueIP; extern bool hueApplyOnOff; extern bool hueApplyBri; extern bool hueApplyColor; - -//Time CONFIG -extern bool ntpEnabled; //get internet time. Only required if you use clock overlays or time-activated macros -extern bool useAMPM; //12h/24h clock format -extern byte currentTimezone; //Timezone ID. Refer to timezones array in wled_ntp.cpp -extern int utcOffsetSecs; //Seconds to offset from UTC before timzone calculation - -extern byte overlayDefault; //0: no overlay 1: analog clock 2: single-digit clocl 3: cronixie -extern byte overlayMin; //boundaries of overlay mode +extern bool ntpEnabled; +extern bool useAMPM; +extern byte currentTimezone; +extern int utcOffsetSecs; +extern byte overlayDefault; +extern byte overlayMin; extern byte overlayMax; - -extern byte analogClock12pixel; //The pixel in your strip where "midnight" would be -extern bool analogClockSecondsTrail; //Display seconds as trail of LEDs instead of a single pixel -extern bool analogClock5MinuteMarks; //Light pixels at every 5-minute position - -extern char cronixieDisplay[7]; //Cronixie Display mask. See wled13_cronixie.ino -extern bool cronixieBacklight; //Allow digits to be back-illuminated - -extern bool countdownMode; //Clock will count down towards date -extern byte countdownYear, countdownMonth; //Countdown target date, year is last two digits +extern byte analogClock12pixel; +extern bool analogClockSecondsTrail; +extern bool analogClock5MinuteMarks; +extern char cronixieDisplay[7]; +extern bool cronixieBacklight; +extern bool countdownMode; +extern byte countdownYear, countdownMonth; extern byte countdownDay, countdownHour; extern byte countdownMin, countdownSec; - -extern byte macroBoot; //macro loaded after startup -extern byte macroNl; //after nightlight delay over +extern byte macroBoot; +extern byte macroNl; extern byte macroCountdown; extern byte macroAlexaOn, macroAlexaOff; extern byte macroButton, macroLongPress, macroDoublePress; - -//Security CONFIG -extern bool otaLock; //prevents OTA firmware updates without password. ALWAYS enable if system exposed to any public networks -extern bool wifiLock; //prevents access to WiFi settings when OTA lock is enabled -extern bool aOtaEnabled; //ArduinoOTA allows easy updates directly from the IDE. Careful, it does not auto-disable when OTA lock is on - +extern bool otaLock; +extern bool wifiLock; +extern bool aOtaEnabled; extern uint16_t userVar0, userVar1; - #ifdef WLED_ENABLE_DMX -//dmx CONFIG -extern byte DMXChannels; // number of channels per fixture +extern byte DMXChannels; extern byte DMXFixtureMap[15]; -extern // assigns the different channels to different functions. See wled21_dmx.ino for more information. -extern uint16_t DMXGap; // gap between the fixtures. makes addressing easier because you don't have to memorize odd numbers when climbing up onto a rig. -extern uint16_t DMXStart; // start address of the first fixture +extern +extern uint16_t DMXGap; +extern uint16_t DMXStart; #endif - -//internal global variable declarations -//wifi extern bool apActive; extern bool forceReconnect; extern uint32_t lastReconnectAttempt; extern bool interfacesInited; extern bool wasConnected; - -//color -extern byte colOld[]; //color before transition -extern byte colT[]; //color that is currently displayed on the LEDs -extern byte colIT[]; //color that was last sent to LEDs +extern byte colOld[]; +extern byte colT[]; +extern byte colIT[]; extern byte colSecT[]; extern byte colSecOld[]; extern byte colSecIT[]; - -extern byte lastRandomIndex; //used to save last random color so the new one is not the same - -//transitions +extern byte lastRandomIndex; extern bool transitionActive; extern uint16_t transitionDelayDefault; extern uint16_t transitionDelayTemp; extern unsigned long transitionStartTime; -extern float tperLast; //crossfade transition progress, 0.0f - 1.0f +extern float tperLast; extern bool jsonTransitionOnce; - -//nightlight extern bool nightlightActive; extern bool nightlightActiveOld; extern uint32_t nightlightDelayMs; extern uint8_t nightlightDelayMinsDefault; extern unsigned long nightlightStartTime; -extern byte briNlT; //current nightlight brightness -extern byte colNlT[]; //current nightlight color - +extern byte briNlT; +extern byte colNlT[]; extern unsigned long lastOnTime; extern bool offMode; extern byte bri; extern byte briOld; extern byte briT; extern byte briIT; -extern byte briLast; //brightness before turned off. Used for toggle function -extern byte whiteLast; //white channel before turned off. Used for toggle function - +extern byte briLast; +extern byte whiteLast; extern bool buttonPressedBefore; extern bool buttonLongPressed; extern unsigned long buttonPressedTime; extern unsigned long buttonWaitTime; - extern bool notifyDirectDefault; extern bool receiveNotifications; extern unsigned long notificationSentTime; extern byte notificationSentCallMode; extern bool notificationTwoRequired; - extern byte effectCurrent; extern byte effectSpeed; extern byte effectIntensity; extern byte effectPalette; - extern bool udpConnected, udpRgbConnected; - extern bool showWelcomePage; - extern byte hueError; -//uint16_t hueFailCount; extern float hueXLast, hueYLast; extern uint16_t hueHueLast, hueCtLast; extern byte hueSatLast, hueBriLast; @@ -342,31 +286,20 @@ extern unsigned long hueLastRequestSent; extern bool hueAuthRequired; extern bool hueReceived; extern bool hueStoreAllowed, hueNewKey; - extern byte overlayCurrent; extern byte overlaySpeed; extern unsigned long overlayRefreshMs; extern unsigned long overlayRefreshedTime; - extern byte dP[]; extern bool cronixieInit; - -//countdown extern unsigned long countdownTime; extern bool countdownOverTriggered; - -//timer extern byte lastTimerMinute; extern byte timerHours[]; extern byte timerMinutes[]; extern byte timerMacro[]; -extern byte timerWeekday[]; //weekdays to activate on -//bit pattern of arr elem: 0b11111111: sun,sat,fri,thu,wed,tue,mon,validity - -//blynk +extern byte timerWeekday[]; extern bool blynkEnabled; - -//preset cycling extern bool presetCyclingEnabled; extern byte presetCycleMin, presetCycleMax; extern uint16_t presetCycleTime; @@ -374,36 +307,24 @@ extern unsigned long presetCycledTime; extern byte presetCycCurr; extern bool presetApplyBri; extern bool saveCurrPresetCycConf; - -//realtime extern byte realtimeMode; extern IPAddress realtimeIP; extern unsigned long realtimeTimeout; - -//mqtt extern long lastMqttReconnectAttempt; extern long lastInterfaceUpdate; extern byte interfaceUpdateCallMode; -extern char mqttStatusTopic[40]; //this must be global because of async handlers - +extern char mqttStatusTopic[40]; #if AUXPIN >= 0 -//auxiliary debug pin extern byte auxTime; extern unsigned long auxStartTime; extern bool auxActive; #endif - -//alexa udp extern String escapedMac; #ifndef WLED_DISABLE_ALEXA extern Espalexa espalexa; extern EspalexaDevice *espalexaDevice; #endif - -//dns server extern DNSServer dnsServer; - -//network time extern bool ntpConnected; extern time_t local; extern unsigned long ntpLastSyncTime; @@ -411,51 +332,35 @@ extern unsigned long ntpPacketSentTime; extern IPAddress ntpServerIP; extern uint16_t ntpLocalPort; #define NTP_PACKET_SIZE 48 - -//maximum number of LEDs - MAX_LEDS is coming from the JSON response getting too big, MAX_LEDS_DMA will become a timing issue #define MAX_LEDS 1500 #define MAX_LEDS_DMA 500 - -//string temp buffer (now stored in stack locally) #define OMAX 2048 extern char *obuf; extern uint16_t olen; - -//presets extern uint16_t savedPresets; extern int8_t currentPreset; extern bool isPreset; - extern byte errorFlag; - extern String messageHead, messageSub; extern byte optionType; - -extern bool doReboot; //flag to initiate reboot from async handlers +extern bool doReboot; extern bool doPublishMqtt; - -//server library objects extern AsyncWebServer server; extern AsyncClient *hueClient; extern AsyncMqttClient *mqtt; +extern WiFiUDP notifierUdp, rgbUdp; +extern WiFiUDP ntpUdp; +extern ESPAsyncE131 e131; +extern bool e131NewData; +extern WS2812FX strip; -//function prototypes +// Function prototypes extern void colorFromUint32(uint32_t, bool); extern void serveMessage(AsyncWebServerRequest *, uint16_t, String, String, byte); extern void handleE131Packet(e131_packet_t *, IPAddress); extern void arlsLock(uint32_t, byte); extern void handleOverlayDraw(); -//udp interface objects -extern WiFiUDP notifierUdp, rgbUdp; -extern WiFiUDP ntpUdp; -extern ESPAsyncE131 e131; -extern bool e131NewData; - -//led fx library object -extern WS2812FX strip; - - #define WLED_CONNECTED (WiFi.status() == WL_CONNECTED) #define WLED_WIFI_CONFIGURED (strlen(clientSSID) >= 1 && strcmp(clientSSID, DEFAULT_CLIENT_SSID) != 0) @@ -474,11 +379,9 @@ extern int loops; #define DEBUG_PRINTF(x) #endif - -// TODO: Inline? -//append new c string to temp buffer efficiently +// append new c string to temp buffer efficiently bool oappend(const char *txt); -//append new number to temp buffer efficiently +// append new number to temp buffer efficiently bool oappendi(int i); int getSignalQuality(int rssi); From 408d63825a5176f370fd04b6ea4662c6a3d27286 Mon Sep 17 00:00:00 2001 From: Travis J Dean Date: Mon, 30 Mar 2020 04:26:41 -0400 Subject: [PATCH 68/90] Function prototype cleanup. --- wled00/set.cpp | 1 + wled00/wled.cpp | 8 +------- wled00/wled.h | 7 ------- 3 files changed, 2 insertions(+), 14 deletions(-) diff --git a/wled00/set.cpp b/wled00/set.cpp index 0a7a4ff4f..f26795c8e 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -8,6 +8,7 @@ #include "alexa.h" #include "cronixie.h" #include "xml.h" +#include "wled_server.h" void _setRandomColor(bool _sec,bool fromButton) { diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 4945ffb78..c6dac0d22 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -14,6 +14,7 @@ #include "mqtt.h" #include "wled_eeprom.h" #include "wled_server.h" + // Global Variable definitions char versionString[] = "0.9.1"; @@ -343,13 +344,6 @@ AsyncWebServer server(80); AsyncClient *hueClient = NULL; AsyncMqttClient *mqtt = NULL; -//function prototypes -void colorFromUint32(uint32_t, bool = false); -void serveMessage(AsyncWebServerRequest *, uint16_t, String, String, byte); -void handleE131Packet(e131_packet_t *, IPAddress); -void arlsLock(uint32_t, byte); -void handleOverlayDraw(); - //udp interface objects WiFiUDP notifierUdp, rgbUdp; WiFiUDP ntpUdp; diff --git a/wled00/wled.h b/wled00/wled.h index 9e373216f..e825668e6 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -354,13 +354,6 @@ extern ESPAsyncE131 e131; extern bool e131NewData; extern WS2812FX strip; -// Function prototypes -extern void colorFromUint32(uint32_t, bool); -extern void serveMessage(AsyncWebServerRequest *, uint16_t, String, String, byte); -extern void handleE131Packet(e131_packet_t *, IPAddress); -extern void arlsLock(uint32_t, byte); -extern void handleOverlayDraw(); - #define WLED_CONNECTED (WiFi.status() == WL_CONNECTED) #define WLED_WIFI_CONFIGURED (strlen(clientSSID) >= 1 && strcmp(clientSSID, DEFAULT_CLIENT_SSID) != 0) From de63bdac3951707f781691afd788d8e8a4319bc3 Mon Sep 17 00:00:00 2001 From: Travis J Dean Date: Mon, 30 Mar 2020 04:35:52 -0400 Subject: [PATCH 69/90] Code reorg. --- wled00/wled.cpp | 11 +---------- wled00/wled.h | 11 +++-------- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/wled00/wled.cpp b/wled00/wled.cpp index c6dac0d22..6edd58e2b 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -356,23 +356,14 @@ WS2812FX strip = WS2812FX(); #define WLED_CONNECTED (WiFi.status() == WL_CONNECTED) #define WLED_WIFI_CONFIGURED (strlen(clientSSID) >= 1 && strcmp(clientSSID, DEFAULT_CLIENT_SSID) != 0) -//debug macros +//debug macro variable definitions #ifdef WLED_DEBUG -#define DEBUG_PRINT(x) Serial.print(x) -#define DEBUG_PRINTLN(x) Serial.println(x) -#define DEBUG_PRINTF(x) Serial.printf(x) unsigned long debugTime = 0; int lastWifiState = 3; unsigned long wifiStateChangedTime = 0; int loops = 0; -#else -#define DEBUG_PRINT(x) -#define DEBUG_PRINTLN(x) -#define DEBUG_PRINTF(x) #endif - - WLED::WLED() { } diff --git a/wled00/wled.h b/wled00/wled.h index e825668e6..abcffac16 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -381,28 +381,23 @@ int getSignalQuality(int rssi); class WLED { public: + WLED(); static WLED &instance() { static WLED instance; return instance; } - WLED(); - - void reset(); - void loop(); - - //boot starts here void setup() { wledInit(); } + void loop(); + void reset(); -public: // TODO: privacy void wledInit(); void beginStrip(); - void handleConnection(); void initAP(bool resetAP = false); void initConnection(); From 40ac760285425a6c1d7f6c2b8c9d0bee49f7e342 Mon Sep 17 00:00:00 2001 From: Travis J Dean Date: Mon, 30 Mar 2020 04:41:42 -0400 Subject: [PATCH 70/90] Move getSignalQuality to json.cpp --- wled00/json.cpp | 20 ++++++++++++++++++++ wled00/wled.cpp | 20 -------------------- wled00/wled.h | 1 - 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/wled00/json.cpp b/wled00/json.cpp index 6d71e4141..782dc8f68 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -249,6 +249,26 @@ void serializeState(JsonObject root) } } +//by https://github.com/tzapu/WiFiManager/blob/master/WiFiManager.cpp +int getSignalQuality(int rssi) +{ + int quality = 0; + + if (rssi <= -100) + { + quality = 0; + } + else if (rssi >= -50) + { + quality = 100; + } + else + { + quality = 2 * (rssi + 100); + } + return quality; +} + void serializeInfo(JsonObject root) { root["ver"] = versionString; diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 6edd58e2b..18bc4fca5 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -845,23 +845,3 @@ void WLED::handleConnection() } } } - -//by https://github.com/tzapu/WiFiManager/blob/master/WiFiManager.cpp -int getSignalQuality(int rssi) -{ - int quality = 0; - - if (rssi <= -100) - { - quality = 0; - } - else if (rssi >= -50) - { - quality = 100; - } - else - { - quality = 2 * (rssi + 100); - } - return quality; -} \ No newline at end of file diff --git a/wled00/wled.h b/wled00/wled.h index abcffac16..076066769 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -376,7 +376,6 @@ extern int loops; bool oappend(const char *txt); // append new number to temp buffer efficiently bool oappendi(int i); -int getSignalQuality(int rssi); class WLED { From 7e21955211282daa581ee912da9d426c7f46aba8 Mon Sep 17 00:00:00 2001 From: Travis J Dean Date: Mon, 30 Mar 2020 04:43:37 -0400 Subject: [PATCH 71/90] wledInit -> setup --- wled00/wled.cpp | 2 +- wled00/wled.h | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 18bc4fca5..fe3d7bd99 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -484,7 +484,7 @@ void WLED::loop() #endif // WLED_DEBU } -void WLED::wledInit() +void WLED::setup() { EEPROM.begin(EEPSIZE); ledCount = EEPROM.read(229) + ((EEPROM.read(398) << 8) & 0xFF00); diff --git a/wled00/wled.h b/wled00/wled.h index 076066769..9262c6f17 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -388,14 +388,11 @@ public: } //boot starts here - void setup() - { - wledInit(); - } + void setup(); + void loop(); void reset(); - void wledInit(); void beginStrip(); void handleConnection(); void initAP(bool resetAP = false); From c54092c932ee84ca3a385f38ceb28bbc40b83967 Mon Sep 17 00:00:00 2001 From: Travis J Dean Date: Mon, 30 Mar 2020 05:04:20 -0400 Subject: [PATCH 72/90] Remove redundant defines. --- wled00/wled.cpp | 13 +------------ wled00/wled.h | 7 ++++++- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/wled00/wled.cpp b/wled00/wled.cpp index fe3d7bd99..670868e61 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -45,7 +45,6 @@ bool noWifiSleep = false; //disabling modem sleep modes will i //LED CONFIG uint16_t ledCount = 30; //overcurrent prevented by ABL bool useRGBW = false; //SK6812 strips can contain an extra White channel -#define ABL_MILLIAMPS_DEFAULT 850; //auto lower brightness to stay close to milliampere limit bool turnOnAtBoot = true; //turn on LEDs at power-up byte bootPreset = 0; //save preset to load after power-up @@ -95,7 +94,6 @@ bool receiveDirect = true; //receive UDP realtime bool arlsDisableGammaCorrection = true; //activate if gamma correction is handled by the source bool arlsForceMaxBri = false; //enable to force max brightness if source has very dark colors that would be black -#define E131_MAX_UNIVERSE_COUNT 9 uint16_t e131Universe = 1; //settings for E1.31 (sACN) protocol (only DMX_MODE_MULTIPLE_* can span over consequtive universes) uint8_t DMXMode = DMX_MODE_MULTIPLE_RGB; //DMX mode (s.a.) uint16_t DMXAddress = 1; //DMX start address of fixture, a.k.a. first Channel [for E1.31 (sACN) protocol] @@ -315,14 +313,8 @@ unsigned long ntpLastSyncTime = 999000000L; unsigned long ntpPacketSentTime = 999000000L; IPAddress ntpServerIP; uint16_t ntpLocalPort = 2390; -#define NTP_PACKET_SIZE 48 -//maximum number of LEDs - MAX_LEDS is coming from the JSON response getting too big, MAX_LEDS_DMA will become a timing issue -#define MAX_LEDS 1500 -#define MAX_LEDS_DMA 500 - -//string temp buffer (now stored in stack locally) -#define OMAX 2048 +// Temp buffer char *obuf; uint16_t olen = 0; @@ -353,9 +345,6 @@ bool e131NewData = false; //led fx library object WS2812FX strip = WS2812FX(); -#define WLED_CONNECTED (WiFi.status() == WL_CONNECTED) -#define WLED_WIFI_CONFIGURED (strlen(clientSSID) >= 1 && strcmp(clientSSID, DEFAULT_CLIENT_SSID) != 0) - //debug macro variable definitions #ifdef WLED_DEBUG unsigned long debugTime = 0; diff --git a/wled00/wled.h b/wled00/wled.h index 4eebead2f..3e6c11be1 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -137,7 +137,7 @@ extern IPAddress staticSubnet; extern bool noWifiSleep; extern uint16_t ledCount; extern bool useRGBW; -#define ABL_MILLIAMPS_DEFAULT 850; +#define ABL_MILLIAMPS_DEFAULT 850; //auto lower brightness to stay close to milliampere limit extern bool turnOnAtBoot; extern byte bootPreset; extern byte col[]; @@ -332,11 +332,16 @@ extern unsigned long ntpPacketSentTime; extern IPAddress ntpServerIP; extern uint16_t ntpLocalPort; #define NTP_PACKET_SIZE 48 + +//maximum number of LEDs - MAX_LEDS is coming from the JSON response getting too big, MAX_LEDS_DMA will become a timing issue #define MAX_LEDS 1500 #define MAX_LEDS_DMA 500 + +//string temp buffer (now stored in stack locally) #define OMAX 2048 extern char *obuf; extern uint16_t olen; + extern uint16_t savedPresets; extern int8_t currentPreset; extern bool isPreset; From a47d48c9735eaef7bd96d84a15c83cafc2c1e5bc Mon Sep 17 00:00:00 2001 From: Travis J Dean Date: Mon, 30 Mar 2020 05:19:09 -0400 Subject: [PATCH 73/90] Add clang format. --- .clang-format | 4 + wled00/wled.cpp | 829 +++++++++++++++++++++++------------------------- 2 files changed, 403 insertions(+), 430 deletions(-) create mode 100644 .clang-format diff --git a/.clang-format b/.clang-format new file mode 100644 index 000000000..56df2b76f --- /dev/null +++ b/.clang-format @@ -0,0 +1,4 @@ +--- + BasedOnStyle: Webkit + IndentWidth: 2 + \ No newline at end of file diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 670868e61..fd47592f7 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -1,19 +1,19 @@ #include "wled.h" -#include -#include "led.h" -#include "ir.h" -#include "notify.h" #include "alexa.h" -#include "overlay.h" -#include "file.h" -#include "button.h" -#include "ntp.h" -#include "usermod.h" #include "blynk.h" +#include "button.h" +#include "file.h" #include "hue.h" +#include "ir.h" +#include "led.h" #include "mqtt.h" +#include "notify.h" +#include "ntp.h" +#include "overlay.h" +#include "usermod.h" #include "wled_eeprom.h" #include "wled_server.h" +#include // Global Variable definitions char versionString[] = "0.9.1"; @@ -25,131 +25,131 @@ char otaPass[33] = DEFAULT_OTA_PASS; //Hardware CONFIG (only changeble HERE, not at runtime) //LED strip pin, button pin and IR pin changeable in NpbWrapper.h! -byte auxDefaultState = 0; //0: input 1: high 2: low -byte auxTriggeredState = 0; //0: input 1: high 2: low +byte auxDefaultState = 0; //0: input 1: high 2: low +byte auxTriggeredState = 0; //0: input 1: high 2: low char ntpServerName[33] = "0.wled.pool.ntp.org"; //NTP server to use //WiFi CONFIG (all these can be changed via web UI, no need to set them here) char clientSSID[33] = CLIENT_SSID; char clientPass[65] = CLIENT_PASS; -char cmDNS[33] = "x"; //mDNS address (placeholder, will be replaced by wledXXXXXXXXXXXX.local) -char apSSID[33] = ""; //AP off by default (unless setup) -byte apChannel = 1; //2.4GHz WiFi AP channel (1-13) -byte apHide = 0; //hidden AP SSID +char cmDNS[33] = "x"; //mDNS address (placeholder, will be replaced by wledXXXXXXXXXXXX.local) +char apSSID[33] = ""; //AP off by default (unless setup) +byte apChannel = 1; //2.4GHz WiFi AP channel (1-13) +byte apHide = 0; //hidden AP SSID byte apBehavior = AP_BEHAVIOR_BOOT_NO_CONN; //access point opens when no connection after boot by default -IPAddress staticIP(0, 0, 0, 0); //static IP of ESP -IPAddress staticGateway(0, 0, 0, 0); //gateway (router) IP -IPAddress staticSubnet(255, 255, 255, 0); //most common subnet in home networks -bool noWifiSleep = false; //disabling modem sleep modes will increase heat output and power usage, but may help with connection issues +IPAddress staticIP(0, 0, 0, 0); //static IP of ESP +IPAddress staticGateway(0, 0, 0, 0); //gateway (router) IP +IPAddress staticSubnet(255, 255, 255, 0); //most common subnet in home networks +bool noWifiSleep = false; //disabling modem sleep modes will increase heat output and power usage, but may help with connection issues //LED CONFIG -uint16_t ledCount = 30; //overcurrent prevented by ABL -bool useRGBW = false; //SK6812 strips can contain an extra White channel -bool turnOnAtBoot = true; //turn on LEDs at power-up -byte bootPreset = 0; //save preset to load after power-up +uint16_t ledCount = 30; //overcurrent prevented by ABL +bool useRGBW = false; //SK6812 strips can contain an extra White channel +bool turnOnAtBoot = true; //turn on LEDs at power-up +byte bootPreset = 0; //save preset to load after power-up -byte col[]{255, 160, 0, 0}; //current RGB(W) primary color. col[] should be updated if you want to change the color. -byte colSec[]{0, 0, 0, 0}; //current RGB(W) secondary color -byte briS = 128; //default brightness +byte col[] { 255, 160, 0, 0 }; //current RGB(W) primary color. col[] should be updated if you want to change the color. +byte colSec[] { 0, 0, 0, 0 }; //current RGB(W) secondary color +byte briS = 128; //default brightness byte nightlightTargetBri = 0; //brightness after nightlight is over byte nightlightDelayMins = 60; -bool nightlightFade = true; //if enabled, light will gradually dim towards the target bri. Otherwise, it will instantly set after delay over +bool nightlightFade = true; //if enabled, light will gradually dim towards the target bri. Otherwise, it will instantly set after delay over bool nightlightColorFade = false; //if enabled, light will gradually fade color from primary to secondary color. -bool fadeTransition = true; //enable crossfading color transition -uint16_t transitionDelay = 750; //default crossfade duration in ms +bool fadeTransition = true; //enable crossfading color transition +uint16_t transitionDelay = 750; //default crossfade duration in ms bool skipFirstLed = false; //ignore first LED in strip (useful if you need the LED as signal repeater) -byte briMultiplier = 100; //% of brightness to set (to limit power, if you set it to 50 and set bri to 255, actual brightness will be 127) +byte briMultiplier = 100; //% of brightness to set (to limit power, if you set it to 50 and set bri to 255, actual brightness will be 127) //User Interface CONFIG char serverDescription[33] = "WLED"; //Name of module -bool syncToggleReceive = false; //UIs which only have a single button for sync should toggle send+receive if this is true, only send otherwise +bool syncToggleReceive = false; //UIs which only have a single button for sync should toggle send+receive if this is true, only send otherwise //Sync CONFIG bool buttonEnabled = true; byte irEnabled = 0; //Infrared receiver -uint16_t udpPort = 21324; //WLED notifier default port +uint16_t udpPort = 21324; //WLED notifier default port uint16_t udpRgbPort = 19446; //Hyperion port bool receiveNotificationBrightness = true; //apply brightness from incoming notifications -bool receiveNotificationColor = true; //apply color -bool receiveNotificationEffects = true; //apply effects setup -bool notifyDirect = false; //send notification if change via UI or HTTP API -bool notifyButton = false; //send if updated by button or infrared remote -bool notifyAlexa = false; //send notification if updated via Alexa -bool notifyMacro = false; //send notification for macro -bool notifyHue = true; //send notification if Hue light changes -bool notifyTwice = false; //notifications use UDP: enable if devices don't sync reliably +bool receiveNotificationColor = true; //apply color +bool receiveNotificationEffects = true; //apply effects setup +bool notifyDirect = false; //send notification if change via UI or HTTP API +bool notifyButton = false; //send if updated by button or infrared remote +bool notifyAlexa = false; //send notification if updated via Alexa +bool notifyMacro = false; //send notification for macro +bool notifyHue = true; //send notification if Hue light changes +bool notifyTwice = false; //notifications use UDP: enable if devices don't sync reliably -bool alexaEnabled = true; //enable device discovery by Amazon Echo +bool alexaEnabled = true; //enable device discovery by Amazon Echo char alexaInvocationName[33] = "Light"; //speech control name of device. Choose something voice-to-text can understand char blynkApiKey[36] = ""; //Auth token for Blynk server. If empty, no connection will be made -uint16_t realtimeTimeoutMs = 2500; //ms timeout of realtime mode before returning to normal mode -int arlsOffset = 0; //realtime LED offset -bool receiveDirect = true; //receive UDP realtime +uint16_t realtimeTimeoutMs = 2500; //ms timeout of realtime mode before returning to normal mode +int arlsOffset = 0; //realtime LED offset +bool receiveDirect = true; //receive UDP realtime bool arlsDisableGammaCorrection = true; //activate if gamma correction is handled by the source -bool arlsForceMaxBri = false; //enable to force max brightness if source has very dark colors that would be black +bool arlsForceMaxBri = false; //enable to force max brightness if source has very dark colors that would be black -uint16_t e131Universe = 1; //settings for E1.31 (sACN) protocol (only DMX_MODE_MULTIPLE_* can span over consequtive universes) -uint8_t DMXMode = DMX_MODE_MULTIPLE_RGB; //DMX mode (s.a.) -uint16_t DMXAddress = 1; //DMX start address of fixture, a.k.a. first Channel [for E1.31 (sACN) protocol] -uint8_t DMXOldDimmer = 0; //only update brightness on change +uint16_t e131Universe = 1; //settings for E1.31 (sACN) protocol (only DMX_MODE_MULTIPLE_* can span over consequtive universes) +uint8_t DMXMode = DMX_MODE_MULTIPLE_RGB; //DMX mode (s.a.) +uint16_t DMXAddress = 1; //DMX start address of fixture, a.k.a. first Channel [for E1.31 (sACN) protocol] +uint8_t DMXOldDimmer = 0; //only update brightness on change uint8_t e131LastSequenceNumber[E131_MAX_UNIVERSE_COUNT]; //to detect packet loss -bool e131Multicast = false; //multicast or unicast -bool e131SkipOutOfSequence = false; //freeze instead of flickering +bool e131Multicast = false; //multicast or unicast +bool e131SkipOutOfSequence = false; //freeze instead of flickering bool mqttEnabled = false; -char mqttDeviceTopic[33] = ""; //main MQTT topic (individual per device, default is wled/mac) +char mqttDeviceTopic[33] = ""; //main MQTT topic (individual per device, default is wled/mac) char mqttGroupTopic[33] = "wled/all"; //second MQTT topic (for example to group devices) -char mqttServer[33] = ""; //both domains and IPs should work (no SSL) -char mqttUser[41] = ""; //optional: username for MQTT auth -char mqttPass[41] = ""; //optional: password for MQTT auth -char mqttClientID[41] = ""; //override the client ID +char mqttServer[33] = ""; //both domains and IPs should work (no SSL) +char mqttUser[41] = ""; //optional: username for MQTT auth +char mqttPass[41] = ""; //optional: password for MQTT auth +char mqttClientID[41] = ""; //override the client ID uint16_t mqttPort = 1883; -bool huePollingEnabled = false; //poll hue bridge for light state +bool huePollingEnabled = false; //poll hue bridge for light state uint16_t huePollIntervalMs = 2500; //low values (< 1sec) may cause lag but offer quicker response -char hueApiKey[47] = "api"; //key token will be obtained from bridge -byte huePollLightId = 1; //ID of hue lamp to sync to. Find the ID in the hue app ("about" section) -IPAddress hueIP = (0, 0, 0, 0); //IP address of the bridge +char hueApiKey[47] = "api"; //key token will be obtained from bridge +byte huePollLightId = 1; //ID of hue lamp to sync to. Find the ID in the hue app ("about" section) +IPAddress hueIP = (0, 0, 0, 0); //IP address of the bridge bool hueApplyOnOff = true; bool hueApplyBri = true; bool hueApplyColor = true; //Time CONFIG -bool ntpEnabled = false; //get internet time. Only required if you use clock overlays or time-activated macros -bool useAMPM = false; //12h/24h clock format +bool ntpEnabled = false; //get internet time. Only required if you use clock overlays or time-activated macros +bool useAMPM = false; //12h/24h clock format byte currentTimezone = 0; //Timezone ID. Refer to timezones array in wled10_ntp.ino -int utcOffsetSecs = 0; //Seconds to offset from UTC before timzone calculation +int utcOffsetSecs = 0; //Seconds to offset from UTC before timzone calculation -byte overlayDefault = 0; //0: no overlay 1: analog clock 2: single-digit clocl 3: cronixie +byte overlayDefault = 0; //0: no overlay 1: analog clock 2: single-digit clocl 3: cronixie byte overlayMin = 0, overlayMax = ledCount - 1; //boundaries of overlay mode -byte analogClock12pixel = 0; //The pixel in your strip where "midnight" would be +byte analogClock12pixel = 0; //The pixel in your strip where "midnight" would be bool analogClockSecondsTrail = false; //Display seconds as trail of LEDs instead of a single pixel bool analogClock5MinuteMarks = false; //Light pixels at every 5-minute position char cronixieDisplay[7] = "HHMMSS"; //Cronixie Display mask. See wled13_cronixie.ino -bool cronixieBacklight = true; //Allow digits to be back-illuminated +bool cronixieBacklight = true; //Allow digits to be back-illuminated -bool countdownMode = false; //Clock will count down towards date +bool countdownMode = false; //Clock will count down towards date byte countdownYear = 20, countdownMonth = 1; //Countdown target date, year is last two digits byte countdownDay = 1, countdownHour = 0; byte countdownMin = 0, countdownSec = 0; byte macroBoot = 0; //macro loaded after startup -byte macroNl = 0; //after nightlight delay over +byte macroNl = 0; //after nightlight delay over byte macroCountdown = 0; byte macroAlexaOn = 0, macroAlexaOff = 0; byte macroButton = 0, macroLongPress = 0, macroDoublePress = 0; //Security CONFIG -bool otaLock = false; //prevents OTA firmware updates without password. ALWAYS enable if system exposed to any public networks -bool wifiLock = false; //prevents access to WiFi settings when OTA lock is enabled +bool otaLock = false; //prevents OTA firmware updates without password. ALWAYS enable if system exposed to any public networks +bool wifiLock = false; //prevents access to WiFi settings when OTA lock is enabled bool aOtaEnabled = true; //ArduinoOTA allows easy updates directly from the IDE. Careful, it does not auto-disable when OTA lock is on uint16_t userVar0 = 0, userVar1 = 0; @@ -157,9 +157,9 @@ uint16_t userVar0 = 0, userVar1 = 0; #ifdef WLED_ENABLE_DMX //dmx CONFIG byte DMXChannels = 7; // number of channels per fixture -byte DMXFixtureMap[15] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +byte DMXFixtureMap[15] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; // assigns the different channels to different functions. See wled21_dmx.ino for more information. -uint16_t DMXGap = 10; // gap between the fixtures. makes addressing easier because you don't have to memorize odd numbers when climbing up onto a rig. +uint16_t DMXGap = 10; // gap between the fixtures. makes addressing easier because you don't have to memorize odd numbers when climbing up onto a rig. uint16_t DMXStart = 10; // start address of the first fixture #endif @@ -172,12 +172,12 @@ bool interfacesInited = false; bool wasConnected = false; //color -byte colOld[]{0, 0, 0, 0}; //color before transition -byte colT[]{0, 0, 0, 0}; //color that is currently displayed on the LEDs -byte colIT[]{0, 0, 0, 0}; //color that was last sent to LEDs -byte colSecT[]{0, 0, 0, 0}; -byte colSecOld[]{0, 0, 0, 0}; -byte colSecIT[]{0, 0, 0, 0}; +byte colOld[] { 0, 0, 0, 0 }; //color before transition +byte colT[] { 0, 0, 0, 0 }; //color that is currently displayed on the LEDs +byte colIT[] { 0, 0, 0, 0 }; //color that was last sent to LEDs +byte colSecT[] { 0, 0, 0, 0 }; +byte colSecOld[] { 0, 0, 0, 0 }; +byte colSecIT[] { 0, 0, 0, 0 }; byte lastRandomIndex = 0; //used to save last random color so the new one is not the same @@ -195,8 +195,8 @@ bool nightlightActiveOld = false; uint32_t nightlightDelayMs = 10; uint8_t nightlightDelayMinsDefault = nightlightDelayMins; unsigned long nightlightStartTime; -byte briNlT = 0; //current nightlight brightness -byte colNlT[]{0, 0, 0, 0}; //current nightlight color +byte briNlT = 0; //current nightlight brightness +byte colNlT[] { 0, 0, 0, 0 }; //current nightlight color //brightness unsigned long lastOnTime = 0; @@ -205,7 +205,7 @@ byte bri = briS; byte briOld = 0; byte briT = 0; byte briIT = 0; -byte briLast = 128; //brightness before turned off. Used for toggle function +byte briLast = 128; //brightness before turned off. Used for toggle function byte whiteLast = 128; //white channel before turned off. Used for toggle function //button @@ -251,7 +251,7 @@ unsigned long overlayRefreshMs = 200; unsigned long overlayRefreshedTime; //cronixie -byte dP[]{0, 0, 0, 0, 0, 0}; +byte dP[] { 0, 0, 0, 0, 0, 0 }; bool cronixieInit = false; //countdown @@ -260,10 +260,10 @@ bool countdownOverTriggered = true; //timer byte lastTimerMinute = 0; -byte timerHours[] = {0, 0, 0, 0, 0, 0, 0, 0}; -byte timerMinutes[] = {0, 0, 0, 0, 0, 0, 0, 0}; -byte timerMacro[] = {0, 0, 0, 0, 0, 0, 0, 0}; -byte timerWeekday[] = {255, 255, 255, 255, 255, 255, 255, 255}; //weekdays to activate on +byte timerHours[] = { 0, 0, 0, 0, 0, 0, 0, 0 }; +byte timerMinutes[] = { 0, 0, 0, 0, 0, 0, 0, 0 }; +byte timerMacro[] = { 0, 0, 0, 0, 0, 0, 0, 0 }; +byte timerWeekday[] = { 255, 255, 255, 255, 255, 255, 255, 255 }; //weekdays to activate on //bit pattern of arr elem: 0b11111111: sun,sat,fri,thu,wed,tue,mon,validity //blynk @@ -300,7 +300,7 @@ bool auxActive = false, auxActiveBefore = false; String escapedMac; #ifndef WLED_DISABLE_ALEXA Espalexa espalexa; -EspalexaDevice *espalexaDevice; +EspalexaDevice* espalexaDevice; #endif //dns server @@ -315,7 +315,7 @@ IPAddress ntpServerIP; uint16_t ntpLocalPort = 2390; // Temp buffer -char *obuf; +char* obuf; uint16_t olen = 0; //presets @@ -333,8 +333,8 @@ bool doPublishMqtt = false; //server library objects AsyncWebServer server(80); -AsyncClient *hueClient = NULL; -AsyncMqttClient *mqtt = NULL; +AsyncClient* hueClient = NULL; +AsyncMqttClient* mqtt = NULL; //udp interface objects WiFiUDP notifierUdp, rgbUdp; @@ -353,396 +353,376 @@ unsigned long wifiStateChangedTime = 0; int loops = 0; #endif -WLED::WLED() { - +WLED::WLED() +{ } //turns all LEDs off and restarts ESP void WLED::reset() { - briT = 0; - long dly = millis(); - while (millis() - dly < 250) - { - yield(); //enough time to send response to client - } - setAllLeds(); - DEBUG_PRINTLN("MODULE RESET"); - ESP.restart(); + briT = 0; + long dly = millis(); + while (millis() - dly < 250) { + yield(); //enough time to send response to client + } + setAllLeds(); + DEBUG_PRINTLN("MODULE RESET"); + ESP.restart(); } bool oappendi(int i) { - char s[11]; - sprintf(s, "%ld", i); - return oappend(s); + char s[11]; + sprintf(s, "%ld", i); + return oappend(s); } -bool oappend(const char *txt) +bool oappend(const char* txt) { - uint16_t len = strlen(txt); - if (olen + len >= OMAX) - return false; //buffer full - strcpy(obuf + olen, txt); - olen += len; - return true; + uint16_t len = strlen(txt); + if (olen + len >= OMAX) + return false; //buffer full + strcpy(obuf + olen, txt); + olen += len; + return true; } void WLED::loop() { - handleIR(); //2nd call to function needed for ESP32 to return valid results -- should be good for ESP8266, too - handleConnection(); - handleSerial(); - handleNotifications(); - handleTransitions(); + handleIR(); //2nd call to function needed for ESP32 to return valid results -- should be good for ESP8266, too + handleConnection(); + handleSerial(); + handleNotifications(); + handleTransitions(); #ifdef WLED_ENABLE_DMX - handleDMX(); + handleDMX(); #endif - userLoop(); + userLoop(); - yield(); - handleIO(); - handleIR(); - handleNetworkTime(); - handleAlexa(); + yield(); + handleIO(); + handleIR(); + handleNetworkTime(); + handleAlexa(); - handleOverlays(); - yield(); + handleOverlays(); + yield(); #ifdef WLED_USE_ANALOG_LEDS - strip.setRgbwPwm(); + strip.setRgbwPwm(); #endif - if (doReboot) - reset(); + if (doReboot) + reset(); - if (!realtimeMode) //block stuff if WARLS/Adalight is enabled - { - if (apActive) - dnsServer.processNextRequest(); + if (!realtimeMode) //block stuff if WARLS/Adalight is enabled + { + if (apActive) + dnsServer.processNextRequest(); #ifndef WLED_DISABLE_OTA - if (WLED_CONNECTED && aOtaEnabled) - ArduinoOTA.handle(); + if (WLED_CONNECTED && aOtaEnabled) + ArduinoOTA.handle(); #endif - handleNightlight(); - yield(); - - handleHue(); - handleBlynk(); - - yield(); - if (!offMode) - strip.service(); - } + handleNightlight(); yield(); + + handleHue(); + handleBlynk(); + + yield(); + if (!offMode) + strip.service(); + } + yield(); #ifdef ESP8266 - MDNS.update(); + MDNS.update(); #endif - if (millis() - lastMqttReconnectAttempt > 30000) - initMqtt(); + if (millis() - lastMqttReconnectAttempt > 30000) + initMqtt(); //DEBUG serial logging #ifdef WLED_DEBUG - if (millis() - debugTime > 9999) - { - DEBUG_PRINTLN("---DEBUG INFO---"); - DEBUG_PRINT("Runtime: "); - DEBUG_PRINTLN(millis()); - DEBUG_PRINT("Unix time: "); - DEBUG_PRINTLN(now()); - DEBUG_PRINT("Free heap: "); - DEBUG_PRINTLN(ESP.getFreeHeap()); - DEBUG_PRINT("Wifi state: "); - DEBUG_PRINTLN(WiFi.status()); - if (WiFi.status() != lastWifiState) - { - wifiStateChangedTime = millis(); - } - lastWifiState = WiFi.status(); - DEBUG_PRINT("State time: "); - DEBUG_PRINTLN(wifiStateChangedTime); - DEBUG_PRINT("NTP last sync: "); - DEBUG_PRINTLN(ntpLastSyncTime); - DEBUG_PRINT("Client IP: "); - DEBUG_PRINTLN(WiFi.localIP()); - DEBUG_PRINT("Loops/sec: "); - DEBUG_PRINTLN(loops / 10); - loops = 0; - debugTime = millis(); + if (millis() - debugTime > 9999) { + DEBUG_PRINTLN("---DEBUG INFO---"); + DEBUG_PRINT("Runtime: "); + DEBUG_PRINTLN(millis()); + DEBUG_PRINT("Unix time: "); + DEBUG_PRINTLN(now()); + DEBUG_PRINT("Free heap: "); + DEBUG_PRINTLN(ESP.getFreeHeap()); + DEBUG_PRINT("Wifi state: "); + DEBUG_PRINTLN(WiFi.status()); + if (WiFi.status() != lastWifiState) { + wifiStateChangedTime = millis(); } - loops++; + lastWifiState = WiFi.status(); + DEBUG_PRINT("State time: "); + DEBUG_PRINTLN(wifiStateChangedTime); + DEBUG_PRINT("NTP last sync: "); + DEBUG_PRINTLN(ntpLastSyncTime); + DEBUG_PRINT("Client IP: "); + DEBUG_PRINTLN(WiFi.localIP()); + DEBUG_PRINT("Loops/sec: "); + DEBUG_PRINTLN(loops / 10); + loops = 0; + debugTime = millis(); + } + loops++; #endif // WLED_DEBU } void WLED::setup() { - EEPROM.begin(EEPSIZE); - ledCount = EEPROM.read(229) + ((EEPROM.read(398) << 8) & 0xFF00); - if (ledCount > MAX_LEDS || ledCount == 0) - ledCount = 30; + EEPROM.begin(EEPSIZE); + ledCount = EEPROM.read(229) + ((EEPROM.read(398) << 8) & 0xFF00); + if (ledCount > MAX_LEDS || ledCount == 0) + ledCount = 30; #ifdef ESP8266 #if LEDPIN == 3 - if (ledCount > MAX_LEDS_DMA) - ledCount = MAX_LEDS_DMA; //DMA method uses too much ram + if (ledCount > MAX_LEDS_DMA) + ledCount = MAX_LEDS_DMA; //DMA method uses too much ram #endif #endif - Serial.begin(115200); - Serial.setTimeout(50); - DEBUG_PRINTLN(); - DEBUG_PRINT("---WLED "); - DEBUG_PRINT(versionString); - DEBUG_PRINT(" "); - DEBUG_PRINT(VERSION); - DEBUG_PRINTLN(" INIT---"); + Serial.begin(115200); + Serial.setTimeout(50); + DEBUG_PRINTLN(); + DEBUG_PRINT("---WLED "); + DEBUG_PRINT(versionString); + DEBUG_PRINT(" "); + DEBUG_PRINT(VERSION); + DEBUG_PRINTLN(" INIT---"); #ifdef ARDUINO_ARCH_ESP32 - DEBUG_PRINT("esp32 "); - DEBUG_PRINTLN(ESP.getSdkVersion()); + DEBUG_PRINT("esp32 "); + DEBUG_PRINTLN(ESP.getSdkVersion()); #else - DEBUG_PRINT("esp8266 "); - DEBUG_PRINTLN(ESP.getCoreVersion()); + DEBUG_PRINT("esp8266 "); + DEBUG_PRINTLN(ESP.getCoreVersion()); #endif - int heapPreAlloc = ESP.getFreeHeap(); - DEBUG_PRINT("heap "); - DEBUG_PRINTLN(ESP.getFreeHeap()); + int heapPreAlloc = ESP.getFreeHeap(); + DEBUG_PRINT("heap "); + DEBUG_PRINTLN(ESP.getFreeHeap()); - strip.init(EEPROM.read(372), ledCount, EEPROM.read(2204)); //init LEDs quickly - strip.setBrightness(0); + strip.init(EEPROM.read(372), ledCount, EEPROM.read(2204)); //init LEDs quickly + strip.setBrightness(0); - DEBUG_PRINT("LEDs inited. heap usage ~"); - DEBUG_PRINTLN(heapPreAlloc - ESP.getFreeHeap()); + DEBUG_PRINT("LEDs inited. heap usage ~"); + DEBUG_PRINTLN(heapPreAlloc - ESP.getFreeHeap()); #ifndef WLED_DISABLE_FILESYSTEM #ifdef ARDUINO_ARCH_ESP32 - SPIFFS.begin(true); + SPIFFS.begin(true); #endif - SPIFFS.begin(); + SPIFFS.begin(); #endif - DEBUG_PRINTLN("Load EEPROM"); - loadSettingsFromEEPROM(true); - beginStrip(); - userSetup(); - if (strcmp(clientSSID, DEFAULT_CLIENT_SSID) == 0) - showWelcomePage = true; - WiFi.persistent(false); + DEBUG_PRINTLN("Load EEPROM"); + loadSettingsFromEEPROM(true); + beginStrip(); + userSetup(); + if (strcmp(clientSSID, DEFAULT_CLIENT_SSID) == 0) + showWelcomePage = true; + WiFi.persistent(false); - if (macroBoot > 0) - applyMacro(macroBoot); - Serial.println("Ada"); + if (macroBoot > 0) + applyMacro(macroBoot); + Serial.println("Ada"); - //generate module IDs - escapedMac = WiFi.macAddress(); - escapedMac.replace(":", ""); - escapedMac.toLowerCase(); - if (strcmp(cmDNS, "x") == 0) //fill in unique mdns default - { - strcpy(cmDNS, "wled-"); - sprintf(cmDNS + 5, "%*s", 6, escapedMac.c_str() + 6); - } - if (mqttDeviceTopic[0] == 0) - { - strcpy(mqttDeviceTopic, "wled/"); - sprintf(mqttDeviceTopic + 5, "%*s", 6, escapedMac.c_str() + 6); - } - if (mqttClientID[0] == 0) - { - strcpy(mqttClientID, "WLED-"); - sprintf(mqttClientID + 5, "%*s", 6, escapedMac.c_str() + 6); - } + //generate module IDs + escapedMac = WiFi.macAddress(); + escapedMac.replace(":", ""); + escapedMac.toLowerCase(); + if (strcmp(cmDNS, "x") == 0) //fill in unique mdns default + { + strcpy(cmDNS, "wled-"); + sprintf(cmDNS + 5, "%*s", 6, escapedMac.c_str() + 6); + } + if (mqttDeviceTopic[0] == 0) { + strcpy(mqttDeviceTopic, "wled/"); + sprintf(mqttDeviceTopic + 5, "%*s", 6, escapedMac.c_str() + 6); + } + if (mqttClientID[0] == 0) { + strcpy(mqttClientID, "WLED-"); + sprintf(mqttClientID + 5, "%*s", 6, escapedMac.c_str() + 6); + } - strip.service(); + strip.service(); #ifndef WLED_DISABLE_OTA - if (aOtaEnabled) - { - ArduinoOTA.onStart([]() { + if (aOtaEnabled) { + ArduinoOTA.onStart([]() { #ifdef ESP8266 - wifi_set_sleep_type(NONE_SLEEP_T); + wifi_set_sleep_type(NONE_SLEEP_T); #endif - DEBUG_PRINTLN("Start ArduinoOTA"); - }); - if (strlen(cmDNS) > 0) - ArduinoOTA.setHostname(cmDNS); - } + DEBUG_PRINTLN("Start ArduinoOTA"); + }); + if (strlen(cmDNS) > 0) + ArduinoOTA.setHostname(cmDNS); + } #endif #ifdef WLED_ENABLE_DMX - dmx.init(512); // initialize with bus length + dmx.init(512); // initialize with bus length #endif - //HTTP server page init - initServer(); + //HTTP server page init + initServer(); } void WLED::beginStrip() { - // Initialize NeoPixel Strip and button - strip.setShowCallback(handleOverlayDraw); + // Initialize NeoPixel Strip and button + strip.setShowCallback(handleOverlayDraw); #ifdef BTNPIN - pinMode(BTNPIN, INPUT_PULLUP); + pinMode(BTNPIN, INPUT_PULLUP); #endif - if (bootPreset > 0) - applyPreset(bootPreset, turnOnAtBoot); - colorUpdated(NOTIFIER_CALL_MODE_INIT); + if (bootPreset > 0) + applyPreset(bootPreset, turnOnAtBoot); + colorUpdated(NOTIFIER_CALL_MODE_INIT); //init relay pin #if RLYPIN >= 0 - pinMode(RLYPIN, OUTPUT); + pinMode(RLYPIN, OUTPUT); #if RLYMDE - digitalWrite(RLYPIN, bri); + digitalWrite(RLYPIN, bri); #else - digitalWrite(RLYPIN, !bri); + digitalWrite(RLYPIN, !bri); #endif #endif - //disable button if it is "pressed" unintentionally + //disable button if it is "pressed" unintentionally #ifdef BTNPIN - if (digitalRead(BTNPIN) == LOW) - buttonEnabled = false; -#else + if (digitalRead(BTNPIN) == LOW) buttonEnabled = false; +#else + buttonEnabled = false; #endif } void WLED::initAP(bool resetAP) { - if (apBehavior == AP_BEHAVIOR_BUTTON_ONLY && !resetAP) - return; + if (apBehavior == AP_BEHAVIOR_BUTTON_ONLY && !resetAP) + return; - if (!apSSID[0] || resetAP) - strcpy(apSSID, "WLED-AP"); - if (resetAP) - strcpy(apPass, DEFAULT_AP_PASS); - DEBUG_PRINT("Opening access point "); - DEBUG_PRINTLN(apSSID); - WiFi.softAPConfig(IPAddress(4, 3, 2, 1), IPAddress(4, 3, 2, 1), IPAddress(255, 255, 255, 0)); - WiFi.softAP(apSSID, apPass, apChannel, apHide); + if (!apSSID[0] || resetAP) + strcpy(apSSID, "WLED-AP"); + if (resetAP) + strcpy(apPass, DEFAULT_AP_PASS); + DEBUG_PRINT("Opening access point "); + DEBUG_PRINTLN(apSSID); + WiFi.softAPConfig(IPAddress(4, 3, 2, 1), IPAddress(4, 3, 2, 1), IPAddress(255, 255, 255, 0)); + WiFi.softAP(apSSID, apPass, apChannel, apHide); - if (!apActive) //start captive portal if AP active - { - DEBUG_PRINTLN("Init AP interfaces"); - server.begin(); - if (udpPort > 0 && udpPort != ntpLocalPort) - { - udpConnected = notifierUdp.begin(udpPort); - } - if (udpRgbPort > 0 && udpRgbPort != ntpLocalPort && udpRgbPort != udpPort) - { - udpRgbConnected = rgbUdp.begin(udpRgbPort); - } - - dnsServer.setErrorReplyCode(DNSReplyCode::NoError); - dnsServer.start(53, "*", WiFi.softAPIP()); + if (!apActive) //start captive portal if AP active + { + DEBUG_PRINTLN("Init AP interfaces"); + server.begin(); + if (udpPort > 0 && udpPort != ntpLocalPort) { + udpConnected = notifierUdp.begin(udpPort); } - apActive = true; + if (udpRgbPort > 0 && udpRgbPort != ntpLocalPort && udpRgbPort != udpPort) { + udpRgbConnected = rgbUdp.begin(udpRgbPort); + } + + dnsServer.setErrorReplyCode(DNSReplyCode::NoError); + dnsServer.start(53, "*", WiFi.softAPIP()); + } + apActive = true; } void WLED::initConnection() { - WiFi.disconnect(); //close old connections + WiFi.disconnect(); //close old connections #ifdef ESP8266 - WiFi.setPhyMode(WIFI_PHY_MODE_11N); + WiFi.setPhyMode(WIFI_PHY_MODE_11N); #endif - if (staticIP[0] != 0 && staticGateway[0] != 0) - { - WiFi.config(staticIP, staticGateway, staticSubnet, IPAddress(8, 8, 8, 8)); - } - else - { - WiFi.config(0U, 0U, 0U); - } + if (staticIP[0] != 0 && staticGateway[0] != 0) { + WiFi.config(staticIP, staticGateway, staticSubnet, IPAddress(8, 8, 8, 8)); + } else { + WiFi.config(0U, 0U, 0U); + } - lastReconnectAttempt = millis(); + lastReconnectAttempt = millis(); - if (!WLED_WIFI_CONFIGURED) - { - DEBUG_PRINT("No connection configured. "); - if (!apActive) - initAP(); //instantly go to ap mode - return; + if (!WLED_WIFI_CONFIGURED) { + DEBUG_PRINT("No connection configured. "); + if (!apActive) + initAP(); //instantly go to ap mode + return; + } else if (!apActive) { + if (apBehavior == AP_BEHAVIOR_ALWAYS) { + initAP(); + } else { + DEBUG_PRINTLN("Access point disabled."); + WiFi.softAPdisconnect(true); } - else if (!apActive) - { - if (apBehavior == AP_BEHAVIOR_ALWAYS) - { - initAP(); - } - else - { - DEBUG_PRINTLN("Access point disabled."); - WiFi.softAPdisconnect(true); - } - } - showWelcomePage = false; + } + showWelcomePage = false; - DEBUG_PRINT("Connecting to "); - DEBUG_PRINT(clientSSID); - DEBUG_PRINTLN("..."); + DEBUG_PRINT("Connecting to "); + DEBUG_PRINT(clientSSID); + DEBUG_PRINTLN("..."); #ifdef ESP8266 - WiFi.hostname(serverDescription); + WiFi.hostname(serverDescription); #endif - WiFi.begin(clientSSID, clientPass); + WiFi.begin(clientSSID, clientPass); #ifdef ARDUINO_ARCH_ESP32 - WiFi.setSleep(!noWifiSleep); - WiFi.setHostname(serverDescription); + WiFi.setSleep(!noWifiSleep); + WiFi.setHostname(serverDescription); #else - wifi_set_sleep_type((noWifiSleep) ? NONE_SLEEP_T : MODEM_SLEEP_T); + wifi_set_sleep_type((noWifiSleep) ? NONE_SLEEP_T : MODEM_SLEEP_T); #endif } void WLED::initInterfaces() { - DEBUG_PRINTLN("Init STA interfaces"); + DEBUG_PRINTLN("Init STA interfaces"); - if (hueIP[0] == 0) - { - hueIP[0] = WiFi.localIP()[0]; - hueIP[1] = WiFi.localIP()[1]; - hueIP[2] = WiFi.localIP()[2]; - } + if (hueIP[0] == 0) { + hueIP[0] = WiFi.localIP()[0]; + hueIP[1] = WiFi.localIP()[1]; + hueIP[2] = WiFi.localIP()[2]; + } - //init Alexa hue emulation - if (alexaEnabled) - alexaInit(); + //init Alexa hue emulation + if (alexaEnabled) + alexaInit(); #ifndef WLED_DISABLE_OTA - if (aOtaEnabled) - ArduinoOTA.begin(); + if (aOtaEnabled) + ArduinoOTA.begin(); #endif - strip.service(); - // Set up mDNS responder: - if (strlen(cmDNS) > 0) - { - if (!aOtaEnabled) - MDNS.begin(cmDNS); + strip.service(); + // Set up mDNS responder: + if (strlen(cmDNS) > 0) { + if (!aOtaEnabled) + MDNS.begin(cmDNS); - DEBUG_PRINTLN("mDNS started"); - MDNS.addService("http", "tcp", 80); - MDNS.addService("wled", "tcp", 80); - MDNS.addServiceTxt("wled", "tcp", "mac", escapedMac.c_str()); - } - server.begin(); + DEBUG_PRINTLN("mDNS started"); + MDNS.addService("http", "tcp", 80); + MDNS.addService("wled", "tcp", 80); + MDNS.addServiceTxt("wled", "tcp", "mac", escapedMac.c_str()); + } + server.begin(); - if (udpPort > 0 && udpPort != ntpLocalPort) - { - udpConnected = notifierUdp.begin(udpPort); - if (udpConnected && udpRgbPort != udpPort) - udpRgbConnected = rgbUdp.begin(udpRgbPort); - } - if (ntpEnabled) - ntpConnected = ntpUdp.begin(ntpLocalPort); + if (udpPort > 0 && udpPort != ntpLocalPort) { + udpConnected = notifierUdp.begin(udpPort); + if (udpConnected && udpRgbPort != udpPort) + udpRgbConnected = rgbUdp.begin(udpRgbPort); + } + if (ntpEnabled) + ntpConnected = ntpUdp.begin(ntpLocalPort); - initBlynk(blynkApiKey); - e131.begin((e131Multicast) ? E131_MULTICAST : E131_UNICAST, e131Universe, E131_MAX_UNIVERSE_COUNT); - reconnectHue(); - initMqtt(); - interfacesInited = true; - wasConnected = true; + initBlynk(blynkApiKey); + e131.begin((e131Multicast) ? E131_MULTICAST : E131_UNICAST, e131Universe, E131_MAX_UNIVERSE_COUNT); + reconnectHue(); + initMqtt(); + interfacesInited = true; + wasConnected = true; } byte stacO = 0; @@ -751,86 +731,75 @@ unsigned long heapTime = 0; void WLED::handleConnection() { - if (millis() < 2000 && (!WLED_WIFI_CONFIGURED || apBehavior == AP_BEHAVIOR_ALWAYS)) - return; - if (lastReconnectAttempt == 0) - initConnection(); + if (millis() < 2000 && (!WLED_WIFI_CONFIGURED || apBehavior == AP_BEHAVIOR_ALWAYS)) + return; + if (lastReconnectAttempt == 0) + initConnection(); - //reconnect WiFi to clear stale allocations if heap gets too low - if (millis() - heapTime > 5000) - { - uint32_t heap = ESP.getFreeHeap(); - if (heap < 9000 && lastHeap < 9000) - { - DEBUG_PRINT("Heap too low! "); - DEBUG_PRINTLN(heap); - forceReconnect = true; - } - lastHeap = heap; - heapTime = millis(); + //reconnect WiFi to clear stale allocations if heap gets too low + if (millis() - heapTime > 5000) { + uint32_t heap = ESP.getFreeHeap(); + if (heap < 9000 && lastHeap < 9000) { + DEBUG_PRINT("Heap too low! "); + DEBUG_PRINTLN(heap); + forceReconnect = true; } + lastHeap = heap; + heapTime = millis(); + } - byte stac = 0; - if (apActive) - { + byte stac = 0; + if (apActive) { #ifdef ESP8266 - stac = wifi_softap_get_station_num(); + stac = wifi_softap_get_station_num(); #else - wifi_sta_list_t stationList; - esp_wifi_ap_get_sta_list(&stationList); - stac = stationList.num; + wifi_sta_list_t stationList; + esp_wifi_ap_get_sta_list(&stationList); + stac = stationList.num; #endif - if (stac != stacO) - { - stacO = stac; - DEBUG_PRINT("Connected AP clients: "); - DEBUG_PRINTLN(stac); - if (!WLED_CONNECTED && WLED_WIFI_CONFIGURED) - { //trying to connect, but not connected - if (stac) - WiFi.disconnect(); //disable search so that AP can work - else - initConnection(); //restart search - } - } + if (stac != stacO) { + stacO = stac; + DEBUG_PRINT("Connected AP clients: "); + DEBUG_PRINTLN(stac); + if (!WLED_CONNECTED && WLED_WIFI_CONFIGURED) { //trying to connect, but not connected + if (stac) + WiFi.disconnect(); //disable search so that AP can work + else + initConnection(); //restart search + } } - if (forceReconnect) - { - DEBUG_PRINTLN("Forcing reconnect."); - initConnection(); - interfacesInited = false; - forceReconnect = false; - wasConnected = false; - return; + } + if (forceReconnect) { + DEBUG_PRINTLN("Forcing reconnect."); + initConnection(); + interfacesInited = false; + forceReconnect = false; + wasConnected = false; + return; + } + if (!WLED_CONNECTED) { + if (interfacesInited) { + DEBUG_PRINTLN("Disconnected!"); + interfacesInited = false; + initConnection(); } - if (!WLED_CONNECTED) - { - if (interfacesInited) - { - DEBUG_PRINTLN("Disconnected!"); - interfacesInited = false; - initConnection(); - } - if (millis() - lastReconnectAttempt > ((stac) ? 300000 : 20000) && WLED_WIFI_CONFIGURED) - initConnection(); - if (!apActive && millis() - lastReconnectAttempt > 12000 && (!wasConnected || apBehavior == AP_BEHAVIOR_NO_CONN)) - initAP(); - } - else if (!interfacesInited) - { //newly connected - DEBUG_PRINTLN(""); - DEBUG_PRINT("Connected! IP address: "); - DEBUG_PRINTLN(WiFi.localIP()); - initInterfaces(); - userConnected(); + if (millis() - lastReconnectAttempt > ((stac) ? 300000 : 20000) && WLED_WIFI_CONFIGURED) + initConnection(); + if (!apActive && millis() - lastReconnectAttempt > 12000 && (!wasConnected || apBehavior == AP_BEHAVIOR_NO_CONN)) + initAP(); + } else if (!interfacesInited) { //newly connected + DEBUG_PRINTLN(""); + DEBUG_PRINT("Connected! IP address: "); + DEBUG_PRINTLN(WiFi.localIP()); + initInterfaces(); + userConnected(); - //shut down AP - if (apBehavior != AP_BEHAVIOR_ALWAYS && apActive) - { - dnsServer.stop(); - WiFi.softAPdisconnect(true); - apActive = false; - DEBUG_PRINTLN("Access point disabled."); - } + //shut down AP + if (apBehavior != AP_BEHAVIOR_ALWAYS && apActive) { + dnsServer.stop(); + WiFi.softAPdisconnect(true); + apActive = false; + DEBUG_PRINTLN("Access point disabled."); } + } } From ffbedbc1e62dd25c4a4e798182a831666fe3e3d2 Mon Sep 17 00:00:00 2001 From: Travis J Dean Date: Mon, 30 Mar 2020 06:27:36 -0400 Subject: [PATCH 74/90] Add clang format to git ignore. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index ea49cb51f..5e61e1565 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ /wled00/Release /wled00/extLibs /platformio_override.ini +.clang-format From 8d75c06852e44e3b87b3b2002a40cee4796af7d0 Mon Sep 17 00:00:00 2001 From: Travis J Dean Date: Mon, 30 Mar 2020 06:42:21 -0400 Subject: [PATCH 75/90] Format changes. --- .clang-format | 10 +- wled00/wled.cpp | 366 ++++++++++++++++++++++++------------------------ wled00/wled.h | 188 +++++++++++++------------ 3 files changed, 287 insertions(+), 277 deletions(-) diff --git a/.clang-format b/.clang-format index 56df2b76f..aedd2246f 100644 --- a/.clang-format +++ b/.clang-format @@ -1,4 +1,12 @@ --- BasedOnStyle: Webkit IndentWidth: 2 - \ No newline at end of file + AlignTrailingComments: true + SpacesBeforeTrailingComments: 8 + AllowShortIfStatementsOnASingleLine: Always + AllowShortLoopsOnASingleLine: true + AllowShortLambdasOnASingleLine: true + AllowShortCaseLabelsOnASingleLine: true + AllowShortFunctionsOnASingleLine: All + AllowShortBlocksOnASingleLine: true + IndentCaseLabels: true \ No newline at end of file diff --git a/wled00/wled.cpp b/wled00/wled.cpp index fd47592f7..48231599d 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -18,224 +18,224 @@ // Global Variable definitions char versionString[] = "0.9.1"; -//AP and OTA default passwords (for maximum change them!) +// AP and OTA default passwords (for maximum change them!) char apPass[65] = DEFAULT_AP_PASS; char otaPass[33] = DEFAULT_OTA_PASS; -//Hardware CONFIG (only changeble HERE, not at runtime) -//LED strip pin, button pin and IR pin changeable in NpbWrapper.h! +// Hardware CONFIG (only changeble HERE, not at runtime) +// LED strip pin, button pin and IR pin changeable in NpbWrapper.h! -byte auxDefaultState = 0; //0: input 1: high 2: low -byte auxTriggeredState = 0; //0: input 1: high 2: low -char ntpServerName[33] = "0.wled.pool.ntp.org"; //NTP server to use +byte auxDefaultState = 0; // 0: input 1: high 2: low +byte auxTriggeredState = 0; // 0: input 1: high 2: low +char ntpServerName[33] = "0.wled.pool.ntp.org"; // NTP server to use -//WiFi CONFIG (all these can be changed via web UI, no need to set them here) +// WiFi CONFIG (all these can be changed via web UI, no need to set them here) char clientSSID[33] = CLIENT_SSID; char clientPass[65] = CLIENT_PASS; -char cmDNS[33] = "x"; //mDNS address (placeholder, will be replaced by wledXXXXXXXXXXXX.local) -char apSSID[33] = ""; //AP off by default (unless setup) -byte apChannel = 1; //2.4GHz WiFi AP channel (1-13) -byte apHide = 0; //hidden AP SSID -byte apBehavior = AP_BEHAVIOR_BOOT_NO_CONN; //access point opens when no connection after boot by default -IPAddress staticIP(0, 0, 0, 0); //static IP of ESP -IPAddress staticGateway(0, 0, 0, 0); //gateway (router) IP -IPAddress staticSubnet(255, 255, 255, 0); //most common subnet in home networks -bool noWifiSleep = false; //disabling modem sleep modes will increase heat output and power usage, but may help with connection issues +char cmDNS[33] = "x"; // mDNS address (placeholder, will be replaced by wledXXXXXXXXXXXX.local) +char apSSID[33] = ""; // AP off by default (unless setup) +byte apChannel = 1; // 2.4GHz WiFi AP channel (1-13) +byte apHide = 0; // hidden AP SSID +byte apBehavior = AP_BEHAVIOR_BOOT_NO_CONN; // access point opens when no connection after boot by default +IPAddress staticIP(0, 0, 0, 0); // static IP of ESP +IPAddress staticGateway(0, 0, 0, 0); // gateway (router) IP +IPAddress staticSubnet(255, 255, 255, 0); // most common subnet in home networks +bool noWifiSleep = false; // disabling modem sleep modes will increase heat output and power usage, but may help with connection issues -//LED CONFIG -uint16_t ledCount = 30; //overcurrent prevented by ABL -bool useRGBW = false; //SK6812 strips can contain an extra White channel -bool turnOnAtBoot = true; //turn on LEDs at power-up -byte bootPreset = 0; //save preset to load after power-up +// LED CONFIG +uint16_t ledCount = 30; // overcurrent prevented by ABL +bool useRGBW = false; // SK6812 strips can contain an extra White channel +bool turnOnAtBoot = true; // turn on LEDs at power-up +byte bootPreset = 0; // save preset to load after power-up -byte col[] { 255, 160, 0, 0 }; //current RGB(W) primary color. col[] should be updated if you want to change the color. -byte colSec[] { 0, 0, 0, 0 }; //current RGB(W) secondary color -byte briS = 128; //default brightness +byte col[] { 255, 160, 0, 0 }; // current RGB(W) primary color. col[] should be updated if you want to change the color. +byte colSec[] { 0, 0, 0, 0 }; // current RGB(W) secondary color +byte briS = 128; // default brightness -byte nightlightTargetBri = 0; //brightness after nightlight is over +byte nightlightTargetBri = 0; // brightness after nightlight is over byte nightlightDelayMins = 60; -bool nightlightFade = true; //if enabled, light will gradually dim towards the target bri. Otherwise, it will instantly set after delay over -bool nightlightColorFade = false; //if enabled, light will gradually fade color from primary to secondary color. -bool fadeTransition = true; //enable crossfading color transition -uint16_t transitionDelay = 750; //default crossfade duration in ms +bool nightlightFade = true; // if enabled, light will gradually dim towards the target bri. Otherwise, it will instantly set after delay over +bool nightlightColorFade = false; // if enabled, light will gradually fade color from primary to secondary color. +bool fadeTransition = true; // enable crossfading color transition +uint16_t transitionDelay = 750; // default crossfade duration in ms -bool skipFirstLed = false; //ignore first LED in strip (useful if you need the LED as signal repeater) -byte briMultiplier = 100; //% of brightness to set (to limit power, if you set it to 50 and set bri to 255, actual brightness will be 127) +bool skipFirstLed = false; // ignore first LED in strip (useful if you need the LED as signal repeater) +byte briMultiplier = 100; // % of brightness to set (to limit power, if you set it to 50 and set bri to 255, actual brightness will be 127) -//User Interface CONFIG -char serverDescription[33] = "WLED"; //Name of module -bool syncToggleReceive = false; //UIs which only have a single button for sync should toggle send+receive if this is true, only send otherwise +// User Interface CONFIG +char serverDescription[33] = "WLED"; // Name of module +bool syncToggleReceive = false; // UIs which only have a single button for sync should toggle send+receive if this is true, only send otherwise -//Sync CONFIG +// Sync CONFIG bool buttonEnabled = true; -byte irEnabled = 0; //Infrared receiver +byte irEnabled = 0; // Infrared receiver -uint16_t udpPort = 21324; //WLED notifier default port -uint16_t udpRgbPort = 19446; //Hyperion port +uint16_t udpPort = 21324; // WLED notifier default port +uint16_t udpRgbPort = 19446; // Hyperion port -bool receiveNotificationBrightness = true; //apply brightness from incoming notifications -bool receiveNotificationColor = true; //apply color -bool receiveNotificationEffects = true; //apply effects setup -bool notifyDirect = false; //send notification if change via UI or HTTP API -bool notifyButton = false; //send if updated by button or infrared remote -bool notifyAlexa = false; //send notification if updated via Alexa -bool notifyMacro = false; //send notification for macro -bool notifyHue = true; //send notification if Hue light changes -bool notifyTwice = false; //notifications use UDP: enable if devices don't sync reliably +bool receiveNotificationBrightness = true; // apply brightness from incoming notifications +bool receiveNotificationColor = true; // apply color +bool receiveNotificationEffects = true; // apply effects setup +bool notifyDirect = false; // send notification if change via UI or HTTP API +bool notifyButton = false; // send if updated by button or infrared remote +bool notifyAlexa = false; // send notification if updated via Alexa +bool notifyMacro = false; // send notification for macro +bool notifyHue = true; // send notification if Hue light changes +bool notifyTwice = false; // notifications use UDP: enable if devices don't sync reliably -bool alexaEnabled = true; //enable device discovery by Amazon Echo -char alexaInvocationName[33] = "Light"; //speech control name of device. Choose something voice-to-text can understand +bool alexaEnabled = true; // enable device discovery by Amazon Echo +char alexaInvocationName[33] = "Light"; // speech control name of device. Choose something voice-to-text can understand -char blynkApiKey[36] = ""; //Auth token for Blynk server. If empty, no connection will be made +char blynkApiKey[36] = ""; // Auth token for Blynk server. If empty, no connection will be made -uint16_t realtimeTimeoutMs = 2500; //ms timeout of realtime mode before returning to normal mode -int arlsOffset = 0; //realtime LED offset -bool receiveDirect = true; //receive UDP realtime -bool arlsDisableGammaCorrection = true; //activate if gamma correction is handled by the source -bool arlsForceMaxBri = false; //enable to force max brightness if source has very dark colors that would be black +uint16_t realtimeTimeoutMs = 2500; // ms timeout of realtime mode before returning to normal mode +int arlsOffset = 0; // realtime LED offset +bool receiveDirect = true; // receive UDP realtime +bool arlsDisableGammaCorrection = true; // activate if gamma correction is handled by the source +bool arlsForceMaxBri = false; // enable to force max brightness if source has very dark colors that would be black -uint16_t e131Universe = 1; //settings for E1.31 (sACN) protocol (only DMX_MODE_MULTIPLE_* can span over consequtive universes) -uint8_t DMXMode = DMX_MODE_MULTIPLE_RGB; //DMX mode (s.a.) -uint16_t DMXAddress = 1; //DMX start address of fixture, a.k.a. first Channel [for E1.31 (sACN) protocol] -uint8_t DMXOldDimmer = 0; //only update brightness on change -uint8_t e131LastSequenceNumber[E131_MAX_UNIVERSE_COUNT]; //to detect packet loss -bool e131Multicast = false; //multicast or unicast -bool e131SkipOutOfSequence = false; //freeze instead of flickering +uint16_t e131Universe = 1; // settings for E1.31 (sACN) protocol (only DMX_MODE_MULTIPLE_* can span over consequtive universes) +uint8_t DMXMode = DMX_MODE_MULTIPLE_RGB; // DMX mode (s.a.) +uint16_t DMXAddress = 1; // DMX start address of fixture, a.k.a. first Channel [for E1.31 (sACN) protocol] +uint8_t DMXOldDimmer = 0; // only update brightness on change +uint8_t e131LastSequenceNumber[E131_MAX_UNIVERSE_COUNT]; // to detect packet loss +bool e131Multicast = false; // multicast or unicast +bool e131SkipOutOfSequence = false; // freeze instead of flickering bool mqttEnabled = false; -char mqttDeviceTopic[33] = ""; //main MQTT topic (individual per device, default is wled/mac) -char mqttGroupTopic[33] = "wled/all"; //second MQTT topic (for example to group devices) -char mqttServer[33] = ""; //both domains and IPs should work (no SSL) -char mqttUser[41] = ""; //optional: username for MQTT auth -char mqttPass[41] = ""; //optional: password for MQTT auth -char mqttClientID[41] = ""; //override the client ID +char mqttDeviceTopic[33] = ""; // main MQTT topic (individual per device, default is wled/mac) +char mqttGroupTopic[33] = "wled/all"; // second MQTT topic (for example to group devices) +char mqttServer[33] = ""; // both domains and IPs should work (no SSL) +char mqttUser[41] = ""; // optional: username for MQTT auth +char mqttPass[41] = ""; // optional: password for MQTT auth +char mqttClientID[41] = ""; // override the client ID uint16_t mqttPort = 1883; -bool huePollingEnabled = false; //poll hue bridge for light state -uint16_t huePollIntervalMs = 2500; //low values (< 1sec) may cause lag but offer quicker response -char hueApiKey[47] = "api"; //key token will be obtained from bridge -byte huePollLightId = 1; //ID of hue lamp to sync to. Find the ID in the hue app ("about" section) -IPAddress hueIP = (0, 0, 0, 0); //IP address of the bridge +bool huePollingEnabled = false; // poll hue bridge for light state +uint16_t huePollIntervalMs = 2500; // low values (< 1sec) may cause lag but offer quicker response +char hueApiKey[47] = "api"; // key token will be obtained from bridge +byte huePollLightId = 1; // ID of hue lamp to sync to. Find the ID in the hue app ("about" section) +IPAddress hueIP = (0, 0, 0, 0); // IP address of the bridge bool hueApplyOnOff = true; bool hueApplyBri = true; bool hueApplyColor = true; -//Time CONFIG -bool ntpEnabled = false; //get internet time. Only required if you use clock overlays or time-activated macros -bool useAMPM = false; //12h/24h clock format -byte currentTimezone = 0; //Timezone ID. Refer to timezones array in wled10_ntp.ino -int utcOffsetSecs = 0; //Seconds to offset from UTC before timzone calculation +// Time CONFIG +bool ntpEnabled = false; // get internet time. Only required if you use clock overlays or time-activated macros +bool useAMPM = false; // 12h/24h clock format +byte currentTimezone = 0; // Timezone ID. Refer to timezones array in wled10_ntp.ino +int utcOffsetSecs = 0; // Seconds to offset from UTC before timzone calculation -byte overlayDefault = 0; //0: no overlay 1: analog clock 2: single-digit clocl 3: cronixie -byte overlayMin = 0, overlayMax = ledCount - 1; //boundaries of overlay mode +byte overlayDefault = 0; // 0: no overlay 1: analog clock 2: single-digit clocl 3: cronixie +byte overlayMin = 0, overlayMax = ledCount - 1; // boundaries of overlay mode -byte analogClock12pixel = 0; //The pixel in your strip where "midnight" would be -bool analogClockSecondsTrail = false; //Display seconds as trail of LEDs instead of a single pixel -bool analogClock5MinuteMarks = false; //Light pixels at every 5-minute position +byte analogClock12pixel = 0; // The pixel in your strip where "midnight" would be +bool analogClockSecondsTrail = false; // Display seconds as trail of LEDs instead of a single pixel +bool analogClock5MinuteMarks = false; // Light pixels at every 5-minute position -char cronixieDisplay[7] = "HHMMSS"; //Cronixie Display mask. See wled13_cronixie.ino -bool cronixieBacklight = true; //Allow digits to be back-illuminated +char cronixieDisplay[7] = "HHMMSS"; // Cronixie Display mask. See wled13_cronixie.ino +bool cronixieBacklight = true; // Allow digits to be back-illuminated -bool countdownMode = false; //Clock will count down towards date -byte countdownYear = 20, countdownMonth = 1; //Countdown target date, year is last two digits +bool countdownMode = false; // Clock will count down towards date +byte countdownYear = 20, countdownMonth = 1; // Countdown target date, year is last two digits byte countdownDay = 1, countdownHour = 0; byte countdownMin = 0, countdownSec = 0; -byte macroBoot = 0; //macro loaded after startup -byte macroNl = 0; //after nightlight delay over +byte macroBoot = 0; // macro loaded after startup +byte macroNl = 0; // after nightlight delay over byte macroCountdown = 0; byte macroAlexaOn = 0, macroAlexaOff = 0; byte macroButton = 0, macroLongPress = 0, macroDoublePress = 0; -//Security CONFIG -bool otaLock = false; //prevents OTA firmware updates without password. ALWAYS enable if system exposed to any public networks -bool wifiLock = false; //prevents access to WiFi settings when OTA lock is enabled -bool aOtaEnabled = true; //ArduinoOTA allows easy updates directly from the IDE. Careful, it does not auto-disable when OTA lock is on +// Security CONFIG +bool otaLock = false; // prevents OTA firmware updates without password. ALWAYS enable if system exposed to any public networks +bool wifiLock = false; // prevents access to WiFi settings when OTA lock is enabled +bool aOtaEnabled = true; // ArduinoOTA allows easy updates directly from the IDE. Careful, it does not auto-disable when OTA lock is on uint16_t userVar0 = 0, userVar1 = 0; #ifdef WLED_ENABLE_DMX -//dmx CONFIG -byte DMXChannels = 7; // number of channels per fixture -byte DMXFixtureMap[15] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; -// assigns the different channels to different functions. See wled21_dmx.ino for more information. -uint16_t DMXGap = 10; // gap between the fixtures. makes addressing easier because you don't have to memorize odd numbers when climbing up onto a rig. -uint16_t DMXStart = 10; // start address of the first fixture + // dmx CONFIG + byte DMXChannels = 7; // number of channels per fixture + byte DMXFixtureMap[15] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + // assigns the different channels to different functions. See wled21_dmx.ino for more information. + uint16_t DMXGap = 10; // gap between the fixtures. makes addressing easier because you don't have to memorize odd numbers when climbing up onto a rig. + uint16_t DMXStart = 10; // start address of the first fixture #endif -//internal global variable declarations -//wifi +// internal global variable declarations +// wifi bool apActive = false; bool forceReconnect = false; uint32_t lastReconnectAttempt = 0; bool interfacesInited = false; bool wasConnected = false; -//color -byte colOld[] { 0, 0, 0, 0 }; //color before transition -byte colT[] { 0, 0, 0, 0 }; //color that is currently displayed on the LEDs -byte colIT[] { 0, 0, 0, 0 }; //color that was last sent to LEDs +// color +byte colOld[] { 0, 0, 0, 0 }; // color before transition +byte colT[] { 0, 0, 0, 0 }; // color that is currently displayed on the LEDs +byte colIT[] { 0, 0, 0, 0 }; // color that was last sent to LEDs byte colSecT[] { 0, 0, 0, 0 }; byte colSecOld[] { 0, 0, 0, 0 }; byte colSecIT[] { 0, 0, 0, 0 }; -byte lastRandomIndex = 0; //used to save last random color so the new one is not the same +byte lastRandomIndex = 0; // used to save last random color so the new one is not the same -//transitions +// transitions bool transitionActive = false; uint16_t transitionDelayDefault = transitionDelay; uint16_t transitionDelayTemp = transitionDelay; unsigned long transitionStartTime; -float tperLast = 0; //crossfade transition progress, 0.0f - 1.0f +float tperLast = 0; // crossfade transition progress, 0.0f - 1.0f bool jsonTransitionOnce = false; -//nightlight +// nightlight bool nightlightActive = false; bool nightlightActiveOld = false; uint32_t nightlightDelayMs = 10; uint8_t nightlightDelayMinsDefault = nightlightDelayMins; unsigned long nightlightStartTime; -byte briNlT = 0; //current nightlight brightness -byte colNlT[] { 0, 0, 0, 0 }; //current nightlight color +byte briNlT = 0; // current nightlight brightness +byte colNlT[] { 0, 0, 0, 0 }; // current nightlight color -//brightness +// brightness unsigned long lastOnTime = 0; bool offMode = !turnOnAtBoot; byte bri = briS; byte briOld = 0; byte briT = 0; byte briIT = 0; -byte briLast = 128; //brightness before turned off. Used for toggle function -byte whiteLast = 128; //white channel before turned off. Used for toggle function +byte briLast = 128; // brightness before turned off. Used for toggle function +byte whiteLast = 128; // white channel before turned off. Used for toggle function -//button +// button bool buttonPressedBefore = false; bool buttonLongPressed = false; unsigned long buttonPressedTime = 0; unsigned long buttonWaitTime = 0; -//notifications +// notifications bool notifyDirectDefault = notifyDirect; bool receiveNotifications = true; unsigned long notificationSentTime = 0; byte notificationSentCallMode = NOTIFIER_CALL_MODE_INIT; bool notificationTwoRequired = false; -//effects +// effects byte effectCurrent = 0; byte effectSpeed = 128; byte effectIntensity = 128; byte effectPalette = 0; -//network +// network bool udpConnected = false, udpRgbConnected = false; -//ui style +// ui style bool showWelcomePage = false; -//hue +// hue byte hueError = HUE_ERROR_INACTIVE; -//uint16_t hueFailCount = 0; +// uint16_t hueFailCount = 0; float hueXLast = 0, hueYLast = 0; uint16_t hueHueLast = 0, hueCtLast = 0; byte hueSatLast = 0, hueBriLast = 0; @@ -244,32 +244,32 @@ bool hueAuthRequired = false; bool hueReceived = false; bool hueStoreAllowed = false, hueNewKey = false; -//overlays +// overlays byte overlayCurrent = overlayDefault; byte overlaySpeed = 200; unsigned long overlayRefreshMs = 200; unsigned long overlayRefreshedTime; -//cronixie +// cronixie byte dP[] { 0, 0, 0, 0, 0, 0 }; bool cronixieInit = false; -//countdown +// countdown unsigned long countdownTime = 1514764800L; bool countdownOverTriggered = true; -//timer +// timer byte lastTimerMinute = 0; byte timerHours[] = { 0, 0, 0, 0, 0, 0, 0, 0 }; byte timerMinutes[] = { 0, 0, 0, 0, 0, 0, 0, 0 }; byte timerMacro[] = { 0, 0, 0, 0, 0, 0, 0, 0 }; -byte timerWeekday[] = { 255, 255, 255, 255, 255, 255, 255, 255 }; //weekdays to activate on -//bit pattern of arr elem: 0b11111111: sun,sat,fri,thu,wed,tue,mon,validity +byte timerWeekday[] = { 255, 255, 255, 255, 255, 255, 255, 255 }; // weekdays to activate on +// bit pattern of arr elem: 0b11111111: sun,sat,fri,thu,wed,tue,mon,validity -//blynk +// blynk bool blynkEnabled = false; -//preset cycling +// preset cycling bool presetCyclingEnabled = false; byte presetCycleMin = 1, presetCycleMax = 5; uint16_t presetCycleTime = 1250; @@ -278,35 +278,35 @@ byte presetCycCurr = presetCycleMin; bool presetApplyBri = true; bool saveCurrPresetCycConf = false; -//realtime +// realtime byte realtimeMode = REALTIME_MODE_INACTIVE; IPAddress realtimeIP = (0, 0, 0, 0); unsigned long realtimeTimeout = 0; -//mqtt +// mqtt long lastMqttReconnectAttempt = 0; long lastInterfaceUpdate = 0; byte interfaceUpdateCallMode = NOTIFIER_CALL_MODE_INIT; -char mqttStatusTopic[40] = ""; //this must be global because of async handlers +char mqttStatusTopic[40] = ""; // this must be global because of async handlers #if AUXPIN >= 0 - //auxiliary debug pin -byte auxTime = 0; -unsigned long auxStartTime = 0; -bool auxActive = false, auxActiveBefore = false; + // auxiliary debug pin + byte auxTime = 0; + unsigned long auxStartTime = 0; + bool auxActive = false, auxActiveBefore = false; #endif -//alexa udp +// alexa udp String escapedMac; #ifndef WLED_DISABLE_ALEXA -Espalexa espalexa; -EspalexaDevice* espalexaDevice; + Espalexa espalexa; + EspalexaDevice* espalexaDevice; #endif -//dns server +// dns server DNSServer dnsServer; -//network time +// network time bool ntpConnected = false; time_t local = 0; unsigned long ntpLastSyncTime = 999000000L; @@ -318,7 +318,7 @@ uint16_t ntpLocalPort = 2390; char* obuf; uint16_t olen = 0; -//presets +// presets uint16_t savedPresets = 0; int8_t currentPreset = -1; bool isPreset = false; @@ -328,42 +328,42 @@ byte errorFlag = 0; String messageHead, messageSub; byte optionType; -bool doReboot = false; //flag to initiate reboot from async handlers +bool doReboot = false; // flag to initiate reboot from async handlers bool doPublishMqtt = false; -//server library objects +// server library objects AsyncWebServer server(80); AsyncClient* hueClient = NULL; AsyncMqttClient* mqtt = NULL; -//udp interface objects +// udp interface objects WiFiUDP notifierUdp, rgbUdp; WiFiUDP ntpUdp; ESPAsyncE131 e131(handleE131Packet); bool e131NewData = false; -//led fx library object +// led fx library object WS2812FX strip = WS2812FX(); -//debug macro variable definitions +// debug macro variable definitions #ifdef WLED_DEBUG -unsigned long debugTime = 0; -int lastWifiState = 3; -unsigned long wifiStateChangedTime = 0; -int loops = 0; + unsigned long debugTime = 0; + int lastWifiState = 3; + unsigned long wifiStateChangedTime = 0; + int loops = 0; #endif WLED::WLED() { } -//turns all LEDs off and restarts ESP +// turns all LEDs off and restarts ESP void WLED::reset() { briT = 0; long dly = millis(); while (millis() - dly < 250) { - yield(); //enough time to send response to client + yield(); // enough time to send response to client } setAllLeds(); DEBUG_PRINTLN("MODULE RESET"); @@ -381,7 +381,7 @@ bool oappend(const char* txt) { uint16_t len = strlen(txt); if (olen + len >= OMAX) - return false; //buffer full + return false; // buffer full strcpy(obuf + olen, txt); olen += len; return true; @@ -389,7 +389,7 @@ bool oappend(const char* txt) void WLED::loop() { - handleIR(); //2nd call to function needed for ESP32 to return valid results -- should be good for ESP8266, too + handleIR(); // 2nd call to function needed for ESP32 to return valid results -- should be good for ESP8266, too handleConnection(); handleSerial(); handleNotifications(); @@ -414,7 +414,7 @@ void WLED::loop() if (doReboot) reset(); - if (!realtimeMode) //block stuff if WARLS/Adalight is enabled + if (!realtimeMode) // block stuff if WARLS/Adalight is enabled { if (apActive) dnsServer.processNextRequest(); @@ -439,7 +439,7 @@ void WLED::loop() if (millis() - lastMqttReconnectAttempt > 30000) initMqtt(); -//DEBUG serial logging +// DEBUG serial logging #ifdef WLED_DEBUG if (millis() - debugTime > 9999) { DEBUG_PRINTLN("---DEBUG INFO---"); @@ -467,7 +467,7 @@ void WLED::loop() debugTime = millis(); } loops++; -#endif // WLED_DEBU +#endif // WLED_DEBU } void WLED::setup() @@ -478,10 +478,10 @@ void WLED::setup() ledCount = 30; #ifdef ESP8266 -#if LEDPIN == 3 - if (ledCount > MAX_LEDS_DMA) - ledCount = MAX_LEDS_DMA; //DMA method uses too much ram -#endif + #if LEDPIN == 3 + if (ledCount > MAX_LEDS_DMA) + ledCount = MAX_LEDS_DMA; // DMA method uses too much ram + #endif #endif Serial.begin(115200); Serial.setTimeout(50); @@ -502,17 +502,17 @@ void WLED::setup() DEBUG_PRINT("heap "); DEBUG_PRINTLN(ESP.getFreeHeap()); - strip.init(EEPROM.read(372), ledCount, EEPROM.read(2204)); //init LEDs quickly + strip.init(EEPROM.read(372), ledCount, EEPROM.read(2204)); // init LEDs quickly strip.setBrightness(0); DEBUG_PRINT("LEDs inited. heap usage ~"); DEBUG_PRINTLN(heapPreAlloc - ESP.getFreeHeap()); #ifndef WLED_DISABLE_FILESYSTEM -#ifdef ARDUINO_ARCH_ESP32 - SPIFFS.begin(true); -#endif - SPIFFS.begin(); + #ifdef ARDUINO_ARCH_ESP32 + SPIFFS.begin(true); + #endif + SPIFFS.begin(); #endif DEBUG_PRINTLN("Load EEPROM"); @@ -527,11 +527,11 @@ void WLED::setup() applyMacro(macroBoot); Serial.println("Ada"); - //generate module IDs + // generate module IDs escapedMac = WiFi.macAddress(); escapedMac.replace(":", ""); escapedMac.toLowerCase(); - if (strcmp(cmDNS, "x") == 0) //fill in unique mdns default + if (strcmp(cmDNS, "x") == 0) // fill in unique mdns default { strcpy(cmDNS, "wled-"); sprintf(cmDNS + 5, "%*s", 6, escapedMac.c_str() + 6); @@ -560,9 +560,9 @@ void WLED::setup() } #endif #ifdef WLED_ENABLE_DMX - dmx.init(512); // initialize with bus length + dmx.init(512); // initialize with bus length #endif - //HTTP server page init + // HTTP server page init initServer(); } @@ -579,7 +579,7 @@ void WLED::beginStrip() applyPreset(bootPreset, turnOnAtBoot); colorUpdated(NOTIFIER_CALL_MODE_INIT); -//init relay pin +// init relay pin #if RLYPIN >= 0 pinMode(RLYPIN, OUTPUT); #if RLYMDE @@ -589,7 +589,7 @@ void WLED::beginStrip() #endif #endif - //disable button if it is "pressed" unintentionally + // disable button if it is "pressed" unintentionally #ifdef BTNPIN if (digitalRead(BTNPIN) == LOW) buttonEnabled = false; @@ -612,7 +612,7 @@ void WLED::initAP(bool resetAP) WiFi.softAPConfig(IPAddress(4, 3, 2, 1), IPAddress(4, 3, 2, 1), IPAddress(255, 255, 255, 0)); WiFi.softAP(apSSID, apPass, apChannel, apHide); - if (!apActive) //start captive portal if AP active + if (!apActive) // start captive portal if AP active { DEBUG_PRINTLN("Init AP interfaces"); server.begin(); @@ -631,7 +631,7 @@ void WLED::initAP(bool resetAP) void WLED::initConnection() { - WiFi.disconnect(); //close old connections + WiFi.disconnect(); // close old connections #ifdef ESP8266 WiFi.setPhyMode(WIFI_PHY_MODE_11N); #endif @@ -647,7 +647,7 @@ void WLED::initConnection() if (!WLED_WIFI_CONFIGURED) { DEBUG_PRINT("No connection configured. "); if (!apActive) - initAP(); //instantly go to ap mode + initAP(); // instantly go to ap mode return; } else if (!apActive) { if (apBehavior == AP_BEHAVIOR_ALWAYS) { @@ -687,7 +687,7 @@ void WLED::initInterfaces() hueIP[2] = WiFi.localIP()[2]; } - //init Alexa hue emulation + // init Alexa hue emulation if (alexaEnabled) alexaInit(); @@ -736,7 +736,7 @@ void WLED::handleConnection() if (lastReconnectAttempt == 0) initConnection(); - //reconnect WiFi to clear stale allocations if heap gets too low + // reconnect WiFi to clear stale allocations if heap gets too low if (millis() - heapTime > 5000) { uint32_t heap = ESP.getFreeHeap(); if (heap < 9000 && lastHeap < 9000) { @@ -761,11 +761,11 @@ void WLED::handleConnection() stacO = stac; DEBUG_PRINT("Connected AP clients: "); DEBUG_PRINTLN(stac); - if (!WLED_CONNECTED && WLED_WIFI_CONFIGURED) { //trying to connect, but not connected + if (!WLED_CONNECTED && WLED_WIFI_CONFIGURED) { // trying to connect, but not connected if (stac) - WiFi.disconnect(); //disable search so that AP can work + WiFi.disconnect(); // disable search so that AP can work else - initConnection(); //restart search + initConnection(); // restart search } } } @@ -787,14 +787,14 @@ void WLED::handleConnection() initConnection(); if (!apActive && millis() - lastReconnectAttempt > 12000 && (!wasConnected || apBehavior == AP_BEHAVIOR_NO_CONN)) initAP(); - } else if (!interfacesInited) { //newly connected + } else if (!interfacesInited) { // newly connected DEBUG_PRINTLN(""); DEBUG_PRINT("Connected! IP address: "); DEBUG_PRINTLN(WiFi.localIP()); initInterfaces(); userConnected(); - //shut down AP + // shut down AP if (apBehavior != AP_BEHAVIOR_ALWAYS && apActive) { dnsServer.stop(); WiFi.softAPdisconnect(true); diff --git a/wled00/wled.h b/wled00/wled.h index 3e6c11be1..72686a000 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -14,41 +14,41 @@ // Alternatively, with platformio pass your chosen flags to your custom build target in platformio.ini.override // You are required to disable over-the-air updates: -//#define WLED_DISABLE_OTA //saves 14kb +//#define WLED_DISABLE_OTA // saves 14kb // You need to choose some of these features to disable: -//#define WLED_DISABLE_ALEXA //saves 11kb -//#define WLED_DISABLE_BLYNK //saves 6kb -//#define WLED_DISABLE_CRONIXIE //saves 3kb -//#define WLED_DISABLE_HUESYNC //saves 4kb -//#define WLED_DISABLE_INFRARED //there is no pin left for this on ESP8266-01, saves 12kb -#define WLED_ENABLE_MQTT //saves 12kb -#define WLED_ENABLE_ADALIGHT //saves 500b only -//#define WLED_ENABLE_DMX //uses 3.5kb +//#define WLED_DISABLE_ALEXA // saves 11kb +//#define WLED_DISABLE_BLYNK // saves 6kb +//#define WLED_DISABLE_CRONIXIE // saves 3kb +//#define WLED_DISABLE_HUESYNC // saves 4kb +//#define WLED_DISABLE_INFRARED // there is no pin left for this on ESP8266-01, saves 12kb +#define WLED_ENABLE_MQTT // saves 12kb +#define WLED_ENABLE_ADALIGHT // saves 500b only +//#define WLED_ENABLE_DMX // uses 3.5kb -#define WLED_DISABLE_FILESYSTEM //SPIFFS is not used by any WLED feature yet -//#define WLED_ENABLE_FS_SERVING //Enable sending html file from SPIFFS before serving progmem version -//#define WLED_ENABLE_FS_EDITOR //enable /edit page for editing SPIFFS content. Will also be disabled with OTA lock +#define WLED_DISABLE_FILESYSTEM // SPIFFS is not used by any WLED feature yet +//#define WLED_ENABLE_FS_SERVING // Enable sending html file from SPIFFS before serving progmem version +//#define WLED_ENABLE_FS_EDITOR // enable /edit page for editing SPIFFS content. Will also be disabled with OTA lock // to toggle usb serial debug (un)comment the following line //#define WLED_DEBUG // Library inclusions. #include -#ifdef ESP8266 -#include -#include -#include -extern "C" -{ -#include -} -#else //ESP32 -#include -#include "esp_wifi.h" -#include -#include -#include "SPIFFS.h" + #ifdef ESP8266 + #include + #include + #include + extern "C" + { + #include + } +#else // ESP32 + #include + #include "esp_wifi.h" + #include + #include + #include "SPIFFS.h" #endif #include @@ -56,25 +56,28 @@ extern "C" #include #include #ifndef WLED_DISABLE_OTA -#include + #include #endif #include #include "src/dependencies/time/TimeLib.h" #include "src/dependencies/timezone/Timezone.h" + #ifndef WLED_DISABLE_ALEXA -#define ESPALEXA_ASYNC -#define ESPALEXA_NO_SUBPAGE -#define ESPALEXA_MAXDEVICES 1 -// #define ESPALEXA_DEBUG -#include "src/dependencies/espalexa/Espalexa.h" + #define ESPALEXA_ASYNC + #define ESPALEXA_NO_SUBPAGE + #define ESPALEXA_MAXDEVICES 1 + // #define ESPALEXA_DEBUG + #include "src/dependencies/espalexa/Espalexa.h" #endif #ifndef WLED_DISABLE_BLYNK -#include "src/dependencies/blynk/BlynkSimpleEsp.h" + #include "src/dependencies/blynk/BlynkSimpleEsp.h" #endif + #include "src/dependencies/e131/ESPAsyncE131.h" #include "src/dependencies/async-mqtt-client/AsyncMqttClient.h" #include "src/dependencies/json/AsyncJson-v6.h" #include "src/dependencies/json/ArduinoJson-v6.h" + #include "html_ui.h" #include "html_settings.h" #include "html_other.h" @@ -83,35 +86,35 @@ extern "C" #include "const.h" #ifndef CLIENT_SSID -#define CLIENT_SSID DEFAULT_CLIENT_SSID + #define CLIENT_SSID DEFAULT_CLIENT_SSID #endif #ifndef CLIENT_PASS -#define CLIENT_PASS "" + #define CLIENT_PASS "" #endif #if IR_PIN < 0 -#ifndef WLED_DISABLE_INFRARED -#define WLED_DISABLE_INFRARED -#endif + #ifndef WLED_DISABLE_INFRARED + #define WLED_DISABLE_INFRARED + #endif #endif #ifndef WLED_DISABLE_INFRARED -#include -#include -#include + #include + #include + #include #endif // remove flicker because PWM signal of RGB channels can become out of phase #if defined(WLED_USE_ANALOG_LEDS) && defined(ESP8266) -#include "src/dependencies/arduino/core_esp8266_waveform.h" + #include "src/dependencies/arduino/core_esp8266_waveform.h" #endif // enable additional debug output #ifdef WLED_DEBUG -#ifndef ESP8266 -#include -#endif + #ifndef ESP8266 + #include + #endif #endif // version code in format yymmddb (b = daily build) @@ -137,7 +140,7 @@ extern IPAddress staticSubnet; extern bool noWifiSleep; extern uint16_t ledCount; extern bool useRGBW; -#define ABL_MILLIAMPS_DEFAULT 850; //auto lower brightness to stay close to milliampere limit +#define ABL_MILLIAMPS_DEFAULT 850; // auto lower brightness to stay close to milliampere limit extern bool turnOnAtBoot; extern byte bootPreset; extern byte col[]; @@ -224,11 +227,10 @@ extern bool wifiLock; extern bool aOtaEnabled; extern uint16_t userVar0, userVar1; #ifdef WLED_ENABLE_DMX -extern byte DMXChannels; -extern byte DMXFixtureMap[15]; -extern -extern uint16_t DMXGap; -extern uint16_t DMXStart; + extern byte DMXChannels; + extern byte DMXFixtureMap[15]; + extern uint16_t DMXGap; + extern uint16_t DMXStart; #endif extern bool apActive; extern bool forceReconnect; @@ -315,15 +317,17 @@ extern long lastInterfaceUpdate; extern byte interfaceUpdateCallMode; extern char mqttStatusTopic[40]; #if AUXPIN >= 0 -extern byte auxTime; -extern unsigned long auxStartTime; -extern bool auxActive; + extern byte auxTime; + extern unsigned long auxStartTime; + extern bool auxActive; #endif extern String escapedMac; #ifndef WLED_DISABLE_ALEXA -extern Espalexa espalexa; -extern EspalexaDevice *espalexaDevice; + extern Espalexa espalexa; + extern EspalexaDevice *espalexaDevice; #endif + +#define NTP_PACKET_SIZE 48 extern DNSServer dnsServer; extern bool ntpConnected; extern time_t local; @@ -331,15 +335,14 @@ extern unsigned long ntpLastSyncTime; extern unsigned long ntpPacketSentTime; extern IPAddress ntpServerIP; extern uint16_t ntpLocalPort; -#define NTP_PACKET_SIZE 48 -//maximum number of LEDs - MAX_LEDS is coming from the JSON response getting too big, MAX_LEDS_DMA will become a timing issue +// maximum number of LEDs - MAX_LEDS is coming from the JSON response getting too big, MAX_LEDS_DMA will become a timing issue #define MAX_LEDS 1500 #define MAX_LEDS_DMA 500 -//string temp buffer (now stored in stack locally) +// string temp buffer (now stored in stack locally) #define OMAX 2048 -extern char *obuf; +extern char* obuf; extern uint16_t olen; extern uint16_t savedPresets; @@ -348,11 +351,11 @@ extern bool isPreset; extern byte errorFlag; extern String messageHead, messageSub; extern byte optionType; -extern bool doReboot; +extern bool doReboot; extern bool doPublishMqtt; extern AsyncWebServer server; -extern AsyncClient *hueClient; -extern AsyncMqttClient *mqtt; +extern AsyncClient* hueClient; +extern AsyncMqttClient* mqtt; extern WiFiUDP notifierUdp, rgbUdp; extern WiFiUDP ntpUdp; extern ESPAsyncE131 e131; @@ -362,46 +365,45 @@ extern WS2812FX strip; #define WLED_CONNECTED (WiFi.status() == WL_CONNECTED) #define WLED_WIFI_CONFIGURED (strlen(clientSSID) >= 1 && strcmp(clientSSID, DEFAULT_CLIENT_SSID) != 0) -//debug macros +// debug macros #ifdef WLED_DEBUG -#define DEBUG_PRINT(x) Serial.print(x) -#define DEBUG_PRINTLN(x) Serial.println(x) -#define DEBUG_PRINTF(x) Serial.printf(x) -extern unsigned long debugTime; -extern int lastWifiState; -extern unsigned long wifiStateChangedTime; -extern int loops; + #define DEBUG_PRINT(x) Serial.print(x) + #define DEBUG_PRINTLN(x) Serial.println(x) + #define DEBUG_PRINTF(x) Serial.printf(x) + extern unsigned long debugTime; + extern int lastWifiState; + extern unsigned long wifiStateChangedTime; + extern int loops; #else -#define DEBUG_PRINT(x) -#define DEBUG_PRINTLN(x) -#define DEBUG_PRINTF(x) + #define DEBUG_PRINT(x) + #define DEBUG_PRINTLN(x) + #define DEBUG_PRINTF(x) #endif // append new c string to temp buffer efficiently -bool oappend(const char *txt); +bool oappend(const char* txt); // append new number to temp buffer efficiently bool oappendi(int i); -class WLED -{ +class WLED { public: - WLED(); - static WLED &instance() - { - static WLED instance; - return instance; - } + WLED(); + static WLED& instance() + { + static WLED instance; + return instance; + } - //boot starts here - void setup(); + // boot starts here + void setup(); - void loop(); - void reset(); + void loop(); + void reset(); - void beginStrip(); - void handleConnection(); - void initAP(bool resetAP = false); - void initConnection(); - void initInterfaces(); + void beginStrip(); + void handleConnection(); + void initAP(bool resetAP = false); + void initConnection(); + void initInterfaces(); }; -#endif // WLED_H +#endif // WLED_H From aea07f42d1a55155d79269ec775959a41c8e38fd Mon Sep 17 00:00:00 2001 From: Travis J Dean Date: Mon, 30 Mar 2020 06:56:51 -0400 Subject: [PATCH 76/90] Fix mistaken substitute. --- wled00/FX.cpp | 50 +++++++++++++++++++++++------------------------ wled00/FX_fcn.cpp | 2 +- wled00/blynk.h | 2 +- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/wled00/FX.cpp b/wled00/FX.cpp index e5408a424..7230a9b3e 100644 --- a/wled00/FX.cpp +++ b/wled00/FX.cpp @@ -268,7 +268,7 @@ uint16_t WS2812FX::mode_breath(void) { counter = (counter >> 2) + (counter >> 4); //0-16384 + 0-2048 if (counter < 16384) { if (counter > 8192) counter = 8192 - (counter - 8192); - var = sin16(counter) / 103; //close to parabolic in range 0-8192, MAX val. 23170 + var = sin16(counter) / 103; //close to parabolic in range 0-8192, max val. 23170 } uint8_t lum = 30 + var; @@ -475,8 +475,8 @@ uint16_t WS2812FX::mode_twinkle(void) { uint32_t it = now / cycleTime; if (it != SEGENV.step) { - uint16_t MAXOn = map(SEGMENT.intensity, 0, 255, 1, SEGLEN); // make sure at least one LED is on - if (SEGENV.aux0 >= MAXOn) + uint16_t maxOn = map(SEGMENT.intensity, 0, 255, 1, SEGLEN); // make sure at least one LED is on + if (SEGENV.aux0 >= maxOn) { SEGENV.aux0 = 0; SEGENV.aux1 = random16(); //new seed for our PRNG @@ -1597,8 +1597,8 @@ uint16_t WS2812FX::mode_oscillate(void) uint16_t WS2812FX::mode_lightning(void) { - uint16_t ledstart = random16(SEGLEN); // DeterMINe starting location of flash - uint16_t ledlen = random16(SEGLEN -1 -ledstart); // DeterMINe length of flash (not to go beyond NUM_LEDS-1) + uint16_t ledstart = random16(SEGLEN); // Determine starting location of flash + uint16_t ledlen = random16(SEGLEN -1 -ledstart); // Determine length of flash (not to go beyond NUM_LEDS-1) uint8_t bri = 255/random8(1, 3); if (SEGENV.step == 0) @@ -2153,9 +2153,9 @@ typedef struct Ripple { uint16_t WS2812FX::ripple_base(bool rainbow) { - uint16_t MAXRipples = 1 + (SEGLEN >> 2); - if (MAXRipples > 100) MAXRipples = 100; - uint16_t dataSize = sizeof(ripple) * MAXRipples; + uint16_t maxRipples = 1 + (SEGLEN >> 2); + if (maxRipples > 100) maxRipples = 100; + uint16_t dataSize = sizeof(ripple) * maxRipples; if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed @@ -2181,7 +2181,7 @@ uint16_t WS2812FX::ripple_base(bool rainbow) } //draw wave - for (uint16_t i = 0; i < MAXRipples; i++) + for (uint16_t i = 0; i < maxRipples; i++) { uint16_t ripplestate = ripples[i].state; if (ripplestate) @@ -2480,8 +2480,8 @@ uint16_t WS2812FX::spots_base(uint16_t threshold) { fill(SEGCOLOR(1)); - uint16_t MAXZones = SEGLEN >> 2; - uint16_t zones = 1 + ((SEGMENT.intensity * MAXZones) >> 8); + uint16_t maxZones = SEGLEN >> 2; + uint16_t zones = 1 + ((SEGMENT.intensity * maxZones) >> 8); uint16_t zoneLen = SEGLEN / zones; uint16_t offset = (SEGLEN - zones * zoneLen) >> 1; @@ -2533,15 +2533,15 @@ typedef struct Ball { */ uint16_t WS2812FX::mode_bouncing_balls(void) { //allocate segment data - uint16_t MAXNumBalls = 16; - uint16_t dataSize = sizeof(ball) * MAXNumBalls; + uint16_t maxNumBalls = 16; + uint16_t dataSize = sizeof(ball) * maxNumBalls; if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed Ball* balls = reinterpret_cast(SEGENV.data); - // number of balls based on intensity setting to MAX of 7 (cycles colors) + // number of balls based on intensity setting to max of 7 (cycles colors) // non-chosen color is a random color - uint8_t numBalls = int(((SEGMENT.intensity * (MAXNumBalls - 0.8f)) / 255) + 1); + uint8_t numBalls = int(((SEGMENT.intensity * (maxNumBalls - 0.8f)) / 255) + 1); float gravity = -9.81; // standard value of gravity float impactVelocityStart = sqrt( -2 * gravity); @@ -2549,7 +2549,7 @@ uint16_t WS2812FX::mode_bouncing_balls(void) { unsigned long time = millis(); if (SEGENV.call == 0) { - for (uint8_t i = 0; i < MAXNumBalls; i++) balls[i].lastBounceTime = time; + for (uint8_t i = 0; i < maxNumBalls; i++) balls[i].lastBounceTime = time; } bool hasCol2 = SEGCOLOR(2); @@ -2665,8 +2665,8 @@ typedef struct Spark { */ uint16_t WS2812FX::mode_popcorn(void) { //allocate segment data - uint16_t MAXNumPopcorn = 24; - uint16_t dataSize = sizeof(spark) * MAXNumPopcorn; + uint16_t maxNumPopcorn = 24; + uint16_t dataSize = sizeof(spark) * maxNumPopcorn; if (!SEGENV.allocateData(dataSize)) return mode_static(); //allocation failed Spark* popcorn = reinterpret_cast(SEGENV.data); @@ -2677,7 +2677,7 @@ uint16_t WS2812FX::mode_popcorn(void) { bool hasCol2 = SEGCOLOR(2); fill(hasCol2 ? BLACK : SEGCOLOR(1)); - uint8_t numPopcorn = SEGMENT.intensity*MAXNumPopcorn/255; + uint8_t numPopcorn = SEGMENT.intensity*maxNumPopcorn/255; if (numPopcorn == 0) numPopcorn = 1; for(uint8_t i = 0; i < numPopcorn; i++) { @@ -2792,13 +2792,13 @@ uint16_t WS2812FX::mode_starburst(void) { star* stars = reinterpret_cast(SEGENV.data); - float MAXSpeed = 375.0f; // Max velocity + float maxSpeed = 375.0f; // Max velocity float particleIgnition = 250.0f; // How long to "flash" float particleFadeTime = 1500.0f; // Fade out time for (int j = 0; j < numStars; j++) { - // speed to adjust chance of a burst, MAX is nearly always. + // speed to adjust chance of a burst, max is nearly always. if (random8((144-(SEGMENT.speed >> 1))) == 0 && stars[j].birth == 0) { // Pick a random color and location. @@ -2807,7 +2807,7 @@ uint16_t WS2812FX::mode_starburst(void) { stars[j].color = col_to_crgb(color_wheel(random8())); stars[j].pos = startPos; - stars[j].vel = MAXSpeed * (float)(random8())/255.0 * multiplier; + stars[j].vel = maxSpeed * (float)(random8())/255.0 * multiplier; stars[j].birth = it; stars[j].last = it; // more fragments means larger burst effect @@ -3026,7 +3026,7 @@ uint16_t WS2812FX::mode_drip(void) drops[j].pos = SEGLEN-1; // start at end drops[j].vel = 0; // speed drops[j].col = sourcedrop; // brightness - drops[j].colIndex = 1; // drop state (0 init, 1 forMINg, 2 falling, 5 bouncing) + drops[j].colIndex = 1; // drop state (0 init, 1 forming, 2 falling, 5 bouncing) } setPixelColor(SEGLEN-1,color_blend(BLACK,SEGCOLOR(0), sourcedrop));// water source @@ -3047,7 +3047,7 @@ uint16_t WS2812FX::mode_drip(void) if (drops[j].pos < 0) drops[j].pos = 0; drops[j].vel += gravity; - for (int i=1;i<7-drops[j].colIndex;i++) { // some MINor math so we don't expand bouncing droplets + for (int i=1;i<7-drops[j].colIndex;i++) { // some minor math so we don't expand bouncing droplets setPixelColor(int(drops[j].pos)+i,color_blend(BLACK,SEGCOLOR(0),drops[j].col/i)); //spread pixel with fade while falling } @@ -3055,7 +3055,7 @@ uint16_t WS2812FX::mode_drip(void) setPixelColor(0,color_blend(SEGCOLOR(0),BLACK,drops[j].col)); } } else { // we hit bottom - if (drops[j].colIndex > 2) { // already hit once, so back to forMINg + if (drops[j].colIndex > 2) { // already hit once, so back to forming drops[j].colIndex = 0; drops[j].col = sourcedrop; diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index 75ec65101..de89a430e 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -638,7 +638,7 @@ uint32_t WS2812FX::color_wheel(uint8_t pos) { } /* - * Returns a new, random wheel index with a MINimum distance of 42 from pos. + * Returns a new, random wheel index with a minimum distance of 42 from pos. */ uint8_t WS2812FX::get_random_wheel_index(uint8_t pos) { uint8_t r = 0, x = 0, y = 0, d = 0; diff --git a/wled00/blynk.h b/wled00/blynk.h index cf3a2e225..f17f774a0 100644 --- a/wled00/blynk.h +++ b/wled00/blynk.h @@ -8,6 +8,6 @@ void initBlynk(const char* auth); void handleBlynk(); void updateBlynk(); -// Unsure if the macro expansions need to accessed through the declaration... TODO +// TODO: Make sure that the macro expansions are handled correctly. #endif //WLED_BLYNK_H \ No newline at end of file From 8ff4f50f79aa0f663f25408c621d235225734272 Mon Sep 17 00:00:00 2001 From: Travis J Dean Date: Mon, 30 Mar 2020 07:24:45 -0400 Subject: [PATCH 77/90] Fix visual studio project files and organize. --- wled00/wled00.vcxproj | 317 +++++----------- wled00/wled00.vcxproj.filters | 657 ++++++++++++++++++++-------------- 2 files changed, 483 insertions(+), 491 deletions(-) diff --git a/wled00/wled00.vcxproj b/wled00/wled00.vcxproj index 2183ad262..e095b8f76 100644 --- a/wled00/wled00.vcxproj +++ b/wled00/wled00.vcxproj @@ -104,231 +104,103 @@ CppCode - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - - - CppCode - + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -350,6 +222,11 @@ + + + + + VisualMicroDebugger @@ -362,4 +239,4 @@ - \ No newline at end of file + diff --git a/wled00/wled00.vcxproj.filters b/wled00/wled00.vcxproj.filters index e2c2b219c..ef1f47bdf 100644 --- a/wled00/wled00.vcxproj.filters +++ b/wled00/wled00.vcxproj.filters @@ -1,272 +1,387 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - - - - - - - - - - - - - - - - - - - - - - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {72fe60da-ba26-45b4-82c1-bdff809975da} + + + {8880888d-efea-4189-a25a-834b7b3bb756} + + + + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files\Dependencies + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files\Dependencies + + + Source Files\Dependencies + + + Source Files\Dependencies + + + Source Files\Dependencies + + + Source Files\Dependencies + + + Source Files\Dependencies + + + Source Files\Dependencies + + + Source Files\Dependencies + + + Source Files\Dependencies + + + Source Files\Dependencies + + + Source Files\Dependencies + + + Source Files\Dependencies + + + Source Files\Dependencies + + + Source Files\Dependencies + + + Source Files\Dependencies + + + Source Files\Dependencies + + + Source Files\Dependencies + + + Source Files\Dependencies + + + Source Files\Dependencies + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + \ No newline at end of file From a9a7720a1158dc5b7a2c95c430bcebbc14c4ae3f Mon Sep 17 00:00:00 2001 From: Travis J Dean Date: Mon, 30 Mar 2020 07:45:33 -0400 Subject: [PATCH 78/90] Import dmx output library into project. --- wled00/dmx.h | 39 ++++----- wled00/src/dependencies/dmx/ESPDMX.cpp | 106 +++++++++++++++++++++++++ wled00/src/dependencies/dmx/ESPDMX.h | 31 ++++++++ wled00/wled.cpp | 1 + 4 files changed, 158 insertions(+), 19 deletions(-) create mode 100644 wled00/src/dependencies/dmx/ESPDMX.cpp create mode 100644 wled00/src/dependencies/dmx/ESPDMX.h diff --git a/wled00/dmx.h b/wled00/dmx.h index 76aab4849..0ab2a4a20 100644 --- a/wled00/dmx.h +++ b/wled00/dmx.h @@ -1,64 +1,65 @@ #ifndef WLED_DMX_H #define WLED_DMX_H -#include "wled.h" +#include "wled.h" /* * Support for DMX via MAX485. - * Needs the espdmx library. You might have to change the output pin within the library. Sketchy, i know. + * Change the output pin in src/dependencies/ESPDMX.cpp if needed. + * Library from: * https://github.com/Rickgg/ESP-Dmx */ #ifdef WLED_ENABLE_DMX -#include +#include "src/dependencies/dmx/ESPDMX.h" DMXESPSerial dmx; -#ifdef WLED_ENABLE_DMX -void handleDMX() { +void handleDMX() +{ // TODO: calculate brightness manually if no shutter channel is set - + uint8_t brightness = strip.getBrightness(); - for (int i = 0; i < ledCount; i++) { // uses the amount of LEDs as fixture count + for (int i = 0; i < ledCount; i++) { // uses the amount of LEDs as fixture count - uint32_t in = strip.getPixelColor(i); // time to get the colors for the individual fixtures as suggested by AirCookie at issue #462 + uint32_t in = strip.getPixelColor(i); // time to get the colors for the individual fixtures as suggested by AirCookie at issue #462 byte w = in >> 24 & 0xFF; byte r = in >> 16 & 0xFF; - byte g = in >> 8 & 0xFF; - byte b = in & 0xFF; + byte g = in >> 8 & 0xFF; + byte b = in & 0xFF; int DMXFixtureStart = DMXStart + (DMXGap * i); for (int j = 0; j < DMXChannels; j++) { int DMXAddr = DMXFixtureStart + j; switch (DMXFixtureMap[j]) { - case 0: // Set this channel to 0. Good way to tell strobe- and fade-functions to fuck right off. + case 0: // Set this channel to 0. Good way to tell strobe- and fade-functions to fuck right off. dmx.write(DMXAddr, 0); break; - case 1: // Red + case 1: // Red dmx.write(DMXAddr, r); break; - case 2: // Green + case 2: // Green dmx.write(DMXAddr, g); break; - case 3: // Blue + case 3: // Blue dmx.write(DMXAddr, b); break; - case 4: // White + case 4: // White dmx.write(DMXAddr, w); break; - case 5: // Shutter channel. Controls the brightness. + case 5: // Shutter channel. Controls the brightness. dmx.write(DMXAddr, brightness); break; - case 6:// Sets this channel to 255. Like 0, but more wholesome. + case 6: // Sets this channel to 255. Like 0, but more wholesome. dmx.write(DMXAddr, 255); break; } } } - dmx.update(); // update the DMX bus + dmx.update(); // update the DMX bus } #else void handleDMX() {} #endif -#endif //WLED_DMX_H \ No newline at end of file +#endif //WLED_DMX_H \ No newline at end of file diff --git a/wled00/src/dependencies/dmx/ESPDMX.cpp b/wled00/src/dependencies/dmx/ESPDMX.cpp new file mode 100644 index 000000000..f3ece1c8e --- /dev/null +++ b/wled00/src/dependencies/dmx/ESPDMX.cpp @@ -0,0 +1,106 @@ +// - - - - - +// ESPDMX - A Arduino library for sending and receiving DMX using the builtin serial hardware port. +// ESPDMX.cpp: Library implementation file +// +// Copyright (C) 2015 Rick +// This work is licensed under a GNU style license. +// +// Last change: Marcel Seerig +// +// Documentation and samples are available at https://github.com/Rickgg/ESP-Dmx +// - - - - - + +/* ----- LIBRARIES ----- */ +#include + +#include "ESPDMX.h" + + + +#define dmxMaxChannel 512 +#define defaultMax 32 + +#define DMXSPEED 250000 +#define DMXFORMAT SERIAL_8N2 +#define BREAKSPEED 83333 +#define BREAKFORMAT SERIAL_8N1 + +bool dmxStarted = false; +int sendPin = 2; //dafault on ESP8266 + +//DMX value array and size. Entry 0 will hold startbyte +uint8_t dmxData[dmxMaxChannel] = {}; +int chanSize; + + +void DMXESPSerial::init() { + chanSize = defaultMax; + + Serial1.begin(DMXSPEED); + pinMode(sendPin, OUTPUT); + dmxStarted = true; +} + +// Set up the DMX-Protocol +void DMXESPSerial::init(int chanQuant) { + + if (chanQuant > dmxMaxChannel || chanQuant <= 0) { + chanQuant = defaultMax; + } + + chanSize = chanQuant; + + Serial1.begin(DMXSPEED); + pinMode(sendPin, OUTPUT); + dmxStarted = true; +} + +// Function to read DMX data +uint8_t DMXESPSerial::read(int Channel) { + if (dmxStarted == false) init(); + + if (Channel < 1) Channel = 1; + if (Channel > dmxMaxChannel) Channel = dmxMaxChannel; + return(dmxData[Channel]); +} + +// Function to send DMX data +void DMXESPSerial::write(int Channel, uint8_t value) { + if (dmxStarted == false) init(); + + if (Channel < 1) Channel = 1; + if (Channel > chanSize) Channel = chanSize; + if (value < 0) value = 0; + if (value > 255) value = 255; + + dmxData[Channel] = value; +} + +void DMXESPSerial::end() { + delete dmxData; + chanSize = 0; + Serial1.end(); + dmxStarted == false; +} + +void DMXESPSerial::update() { + if (dmxStarted == false) init(); + + //Send break + digitalWrite(sendPin, HIGH); + Serial1.begin(BREAKSPEED, BREAKFORMAT); + Serial1.write(0); + Serial1.flush(); + delay(1); + Serial1.end(); + + //send data + Serial1.begin(DMXSPEED, DMXFORMAT); + digitalWrite(sendPin, LOW); + Serial1.write(dmxData, chanSize); + Serial1.flush(); + delay(1); + Serial1.end(); +} + +// Function to update the DMX bus diff --git a/wled00/src/dependencies/dmx/ESPDMX.h b/wled00/src/dependencies/dmx/ESPDMX.h new file mode 100644 index 000000000..4585bdd26 --- /dev/null +++ b/wled00/src/dependencies/dmx/ESPDMX.h @@ -0,0 +1,31 @@ +// - - - - - +// ESPDMX - A Arduino library for sending and receiving DMX using the builtin serial hardware port. +// ESPDMX.cpp: Library implementation file +// +// Copyright (C) 2015 Rick +// This work is licensed under a GNU style license. +// +// Last change: Marcel Seerig +// +// Documentation and samples are available at https://github.com/Rickgg/ESP-Dmx +// - - - - - + +#include + + +#ifndef ESPDMX_h +#define ESPDMX_h + +// ---- Methods ---- + +class DMXESPSerial { +public: + void init(); + void init(int MaxChan); + uint8_t read(int Channel); + void write(int channel, uint8_t value); + void update(); + void end(); +}; + +#endif diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 48231599d..fdb607765 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -2,6 +2,7 @@ #include "alexa.h" #include "blynk.h" #include "button.h" +#include "dmx.h" #include "file.h" #include "hue.h" #include "ir.h" From ad70fcba7fc2dbda16ec78b27a1ad443378a5ef4 Mon Sep 17 00:00:00 2001 From: Travis J Dean Date: Mon, 30 Mar 2020 08:00:58 -0400 Subject: [PATCH 79/90] Typo fix. --- wled00/overlay.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wled00/overlay.h b/wled00/overlay.h index b403a6e23..f7b54d5db 100644 --- a/wled00/overlay.h +++ b/wled00/overlay.h @@ -1,5 +1,5 @@ -#ifndef WLED_OVERLAYS_H -#define WLED_OVERLAYS_H +#ifndef WLED_OVERLAY_H +#define WLED_OVERLAY_H #include /* * Used to draw clock overlays over the strip From cf29ddc8d0d25a240648f044d798eb4ebd59f710 Mon Sep 17 00:00:00 2001 From: Travis J Dean Date: Mon, 30 Mar 2020 08:03:55 -0400 Subject: [PATCH 80/90] Typo fix. --- wled00/usermod.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/usermod.cpp b/wled00/usermod.cpp index 6589c07c8..96c1f8a6c 100644 --- a/wled00/usermod.cpp +++ b/wled00/usermod.cpp @@ -2,7 +2,7 @@ /* * This file allows you to add own functionality to WLED more easily * See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality - * EEPROM bytes 2750+ are reserved for your custom use case. (if you extend #define EEPSIZE in wled01_wled_eeprom.h) + * EEPROM bytes 2750+ are reserved for your custom use case. (if you extend #define EEPSIZE in wled_eeprom.h) * bytes 2400+ are currently ununsed, but might be used for future wled features */ From 9875fbef0aa2c159eec8496dfd77fda80e8cd930 Mon Sep 17 00:00:00 2001 From: Travis J Dean Date: Mon, 30 Mar 2020 08:34:20 -0400 Subject: [PATCH 81/90] Layout changes. --- wled00/wled.h | 2 +- wled00/wled00.vcxproj.filters | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/wled00/wled.h b/wled00/wled.h index 72686a000..70b197c8e 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -35,7 +35,7 @@ // Library inclusions. #include - #ifdef ESP8266 +#ifdef ESP8266 #include #include #include diff --git a/wled00/wled00.vcxproj.filters b/wled00/wled00.vcxproj.filters index ef1f47bdf..39fe509e9 100644 --- a/wled00/wled00.vcxproj.filters +++ b/wled00/wled00.vcxproj.filters @@ -135,9 +135,6 @@ Header Files\Dependencies - - Header Files\Dependencies - Header Files\Dependencies @@ -147,9 +144,6 @@ Header Files\Dependencies - - Header Files\Dependencies - Header Files\Dependencies @@ -258,6 +252,12 @@ Header Files + + Header Files + + + Header Files + From a88ae2ca56a277d070906832a534960f1eecc26c Mon Sep 17 00:00:00 2001 From: Travis Dean Date: Mon, 30 Mar 2020 08:48:01 -0400 Subject: [PATCH 82/90] Delete .clang-format --- .clang-format | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 .clang-format diff --git a/.clang-format b/.clang-format deleted file mode 100644 index aedd2246f..000000000 --- a/.clang-format +++ /dev/null @@ -1,12 +0,0 @@ ---- - BasedOnStyle: Webkit - IndentWidth: 2 - AlignTrailingComments: true - SpacesBeforeTrailingComments: 8 - AllowShortIfStatementsOnASingleLine: Always - AllowShortLoopsOnASingleLine: true - AllowShortLambdasOnASingleLine: true - AllowShortCaseLabelsOnASingleLine: true - AllowShortFunctionsOnASingleLine: All - AllowShortBlocksOnASingleLine: true - IndentCaseLabels: true \ No newline at end of file From 6268cadc950db2e58d1837361ebe2e64db9f27f5 Mon Sep 17 00:00:00 2001 From: cschwinne Date: Tue, 31 Mar 2020 02:38:08 +0200 Subject: [PATCH 83/90] Function definitions in func_declare.h Significantly reducing number of header files --- CHANGELOG.md | 36 ++--- wled00/alexa.cpp | 14 +- wled00/alexa.h | 17 --- wled00/blynk.cpp | 8 +- wled00/blynk.h | 13 -- wled00/button.cpp | 4 - wled00/button.h | 12 -- wled00/colors.cpp | 5 +- wled00/colors.h | 20 --- wled00/const.h | 7 +- wled00/cronixie.cpp | 243 ------------------------------- wled00/cronixie.h | 13 -- wled00/{dmx.h => dmx.cpp} | 5 +- wled00/e131.cpp | 144 +++++++++++++++++++ wled00/file.cpp | 86 +---------- wled00/file.h | 12 -- wled00/func_declare.h | 183 ++++++++++++++++++++++++ wled00/hue.cpp | 9 +- wled00/hue.h | 16 --- wled00/ir.cpp | 8 +- wled00/ir.h | 24 ---- wled00/json.cpp | 7 +- wled00/json.h | 21 --- wled00/led.cpp | 10 +- wled00/led.h | 19 --- wled00/mqtt.cpp | 10 +- wled00/mqtt.h | 9 -- wled00/notify.h | 18 --- wled00/ntp.cpp | 6 +- wled00/ntp.h | 18 --- wled00/overlay.cpp | 253 ++++++++++++++++++++++++++++++++- wled00/overlay.h | 14 -- wled00/set.cpp | 14 +- wled00/set.h | 16 --- wled00/{notify.cpp => udp.cpp} | 148 +------------------ wled00/usermod.cpp | 2 +- wled00/usermod.h | 8 -- wled00/wled.cpp | 17 +-- wled00/wled.h | 9 +- wled00/wled00.ino | 6 +- wled00/wled_eeprom.cpp | 9 +- wled00/wled_eeprom.h | 23 --- wled00/wled_serial.cpp | 83 +++++++++++ wled00/wled_server.cpp | 8 +- wled00/wled_server.h | 21 --- wled00/xml.cpp | 6 +- wled00/xml.h | 15 -- 47 files changed, 759 insertions(+), 890 deletions(-) delete mode 100644 wled00/alexa.h delete mode 100644 wled00/blynk.h delete mode 100644 wled00/button.h delete mode 100644 wled00/colors.h delete mode 100644 wled00/cronixie.cpp delete mode 100644 wled00/cronixie.h rename wled00/{dmx.h => dmx.cpp} (96%) create mode 100644 wled00/e131.cpp delete mode 100644 wled00/file.h create mode 100644 wled00/func_declare.h delete mode 100644 wled00/hue.h delete mode 100644 wled00/ir.h delete mode 100644 wled00/json.h delete mode 100644 wled00/led.h delete mode 100644 wled00/mqtt.h delete mode 100644 wled00/notify.h delete mode 100644 wled00/ntp.h delete mode 100644 wled00/overlay.h delete mode 100644 wled00/set.h rename wled00/{notify.cpp => udp.cpp} (55%) delete mode 100644 wled00/usermod.h delete mode 100644 wled00/wled_eeprom.h create mode 100644 wled00/wled_serial.cpp delete mode 100644 wled00/wled_server.h delete mode 100644 wled00/xml.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d3a466c4..a9804b1f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,43 +2,47 @@ ### Development versions after 0.9.1 release +#### Build 2003300 + +- Major change of project structure from .ino to .cpp and func_declare.h + #### Build 2003262 - - Fixed compilation for Analog LEDs - - Fixed sync settings network port fields too small +- Fixed compilation for Analog LEDs +- Fixed sync settings network port fields too small #### Build 2003261 - - Fixed live preview not displaying whole light if over 255 LEDs +- Fixed live preview not displaying whole light if over 255 LEDs #### Build 2003251 - - Added Pacifica effect (tentative, doesn't yet support other colors) - - Added Atlantica palette - - Fixed ESP32 build of Espalexa +- Added Pacifica effect (tentative, doesn't yet support other colors) +- Added Atlantica palette +- Fixed ESP32 build of Espalexa #### Build 2003222 - - Fixed Alexa Whites on non-RGBW lights (bump Espalexa to 2.4.5) +- Fixed Alexa Whites on non-RGBW lights (bump Espalexa to 2.4.5) #### Build 2003221 - - Moved Cronixie driver from FX library to drawOverlay handler +- Moved Cronixie driver from FX library to drawOverlay handler #### Build 2003211 - - Added custom mapping compile define to FX_fcn.h - - Merged pull request #784 by @TravisDean: Fixed initialization bug when toggling skip first - - Added link to youtube videos by Room31 to readme +- Added custom mapping compile define to FX_fcn.h +- Merged pull request #784 by @TravisDean: Fixed initialization bug when toggling skip first +- Added link to youtube videos by Room31 to readme #### Build 2003141 - - Fixed color of main segment returned in JSON API during transition not being target color (closes #765) - - Fixed arlsLock() being called after pixels set in E1.31 (closes #772) - - Fixed HTTP API calls not having an effect if no segment selected (now applies to main segment) +- Fixed color of main segment returned in JSON API during transition not being target color (closes #765) +- Fixed arlsLock() being called after pixels set in E1.31 (closes #772) +- Fixed HTTP API calls not having an effect if no segment selected (now applies to main segment) #### Build 2003121 - - Created changelog.md - make tracking changes to code easier - - Merged pull request #766 by @pille: Fix E1.31 out-of sequence detection +- Created changelog.md - make tracking changes to code easier +- Merged pull request #766 by @pille: Fix E1.31 out-of sequence detection diff --git a/wled00/alexa.cpp b/wled00/alexa.cpp index e35f5b358..c9f61ff23 100644 --- a/wled00/alexa.cpp +++ b/wled00/alexa.cpp @@ -1,9 +1,13 @@ -#include "alexa.h" #include "wled.h" -#include "const.h" -#include "led.h" -#include "wled_eeprom.h" -#include "colors.h" + +/* + * Alexa Voice On/Off/Brightness/Color Control. Emulates a Philips Hue bridge to Alexa. + * + * This was put together from these two excellent projects: + * https://github.com/kakopappa/arduino-esp8266-alexa-wemo-switch + * https://github.com/probonopd/ESP8266HueEmulator + */ +#include "src/dependencies/espalexa/EspalexaDevice.h" #ifndef WLED_DISABLE_ALEXA void onAlexaChange(EspalexaDevice* dev); diff --git a/wled00/alexa.h b/wled00/alexa.h deleted file mode 100644 index c2adfebe3..000000000 --- a/wled00/alexa.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef WLED_ALEXA_H -#define WLED_ALEXA_H -/* - * Alexa Voice On/Off/Brightness Control. Emulates a Philips Hue bridge to Alexa. - * - * This was put together from these two excellent projects: - * https://github.com/kakopappa/arduino-esp8266-alexa-wemo-switch - * https://github.com/probonopd/ESP8266HueEmulator - */ -#include "src/dependencies/espalexa/EspalexaDevice.h" - -void onAlexaChange(EspalexaDevice* dev); -void alexaInit(); -void handleAlexa(); -void onAlexaChange(EspalexaDevice* dev); - -#endif // WLED_ALEXA_H \ No newline at end of file diff --git a/wled00/blynk.cpp b/wled00/blynk.cpp index 2307a6942..39b43ba80 100644 --- a/wled00/blynk.cpp +++ b/wled00/blynk.cpp @@ -1,9 +1,9 @@ -#include "blynk.h" -#include "const.h" #include "wled.h" #include "src/dependencies/blynk/Blynk/BlynkHandlers.h" -#include "led.h" -#include "colors.h" + +/* + * Remote light control with the free Blynk app + */ uint16_t blHue = 0; byte blSat = 255; diff --git a/wled00/blynk.h b/wled00/blynk.h deleted file mode 100644 index f17f774a0..000000000 --- a/wled00/blynk.h +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef WLED_BLYNK_H -#define WLED_BLYNK_H -#include -/* - * Remote light control with the free Blynk app - */ - -void initBlynk(const char* auth); -void handleBlynk(); -void updateBlynk(); -// TODO: Make sure that the macro expansions are handled correctly. - -#endif //WLED_BLYNK_H \ No newline at end of file diff --git a/wled00/button.cpp b/wled00/button.cpp index a80061eeb..e71cce965 100644 --- a/wled00/button.cpp +++ b/wled00/button.cpp @@ -1,8 +1,4 @@ -#include "button.h" #include "wled.h" -#include "led.h" -#include "wled_eeprom.h" -#include "set.h" /* * Physical IO diff --git a/wled00/button.h b/wled00/button.h deleted file mode 100644 index f974888e7..000000000 --- a/wled00/button.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef WLED_BUTTON_H -#define WLED_BUTTON_H -#include -/* - * Physical IO - */ - -void shortPressAction(); -void handleButton(); -void handleIO(); - -#endif // WLED_BUTTON_H \ No newline at end of file diff --git a/wled00/colors.cpp b/wled00/colors.cpp index 6f2477574..f76499a01 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -1,6 +1,9 @@ -#include "colors.h" #include "wled.h" +/* + * Color conversion methods + */ + void colorFromUint32(uint32_t in, bool secondary) { if (secondary) { diff --git a/wled00/colors.h b/wled00/colors.h deleted file mode 100644 index 85e0adb50..000000000 --- a/wled00/colors.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef WLED_COLORS_H -#define WLED_COLORS_H -#include -/* - * Color conversion methods - */ - -void colorFromUint32(uint32_t in, bool secondary = false); -void colorFromUint24(uint32_t in, bool secondary = false); -void relativeChangeWhite(int8_t amount, byte lowerBoundary = 0); -void colorHStoRGB(uint16_t hue, byte sat, byte* rgb); //hue, sat to rgb -void colorCTtoRGB(uint16_t mired, byte* rgb); //white spectrum to rgb - -void colorXYtoRGB(float x, float y, byte* rgb); // only defined if huesync disabled TODO -void colorRGBtoXY(byte* rgb, float* xy); // only defined if huesync disabled TODO - -void colorFromDecOrHexString(byte* rgb, char* in); -void colorRGBtoRGBW(byte* rgb); //rgb to rgbw (http://codewelt.com/rgbw). (RGBW_MODE_LEGACY) - -#endif //WLED_COLORS_H \ No newline at end of file diff --git a/wled00/const.h b/wled00/const.h index 21be19536..535a59870 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -1,5 +1,5 @@ -#ifndef wled_const_h -#define wled_const_h +#ifndef WLED_CONST_H +#define WLED_CONST_H //Defaults #define DEFAULT_CLIENT_SSID "Your_Network" @@ -72,4 +72,7 @@ #define HUE_ERROR_TIMEOUT 251 #define HUE_ERROR_ACTIVE 255 +//EEPROM size +#define EEPSIZE 2560 //Maximum is 4096 + #endif diff --git a/wled00/cronixie.cpp b/wled00/cronixie.cpp deleted file mode 100644 index e3b951b86..000000000 --- a/wled00/cronixie.cpp +++ /dev/null @@ -1,243 +0,0 @@ -#include "cronixie.h" -#include "wled.h" - -#ifndef WLED_DISABLE_CRONIXIE -byte _digitOut[6] = {10,10,10,10,10,10}; - -byte getSameCodeLength(char code, int index, char const cronixieDisplay[]) -{ - byte counter = 0; - - for (int i = index+1; i < 6; i++) - { - if (cronixieDisplay[i] == code) - { - counter++; - } else { - return counter; - } - } - return counter; -} - -void setCronixie() -{ - /* - * digit purpose index - * 0-9 | 0-9 (incl. random) - * 10 | blank - * 11 | blank, bg off - * 12 | test upw. - * 13 | test dnw. - * 14 | binary AM/PM - * 15 | BB upper +50 for no trailing 0 - * 16 | BBB - * 17 | BBBB - * 18 | BBBBB - * 19 | BBBBBB - * 20 | H - * 21 | HH - * 22 | HHH - * 23 | HHHH - * 24 | M - * 25 | MM - * 26 | MMM - * 27 | MMMM - * 28 | MMMMM - * 29 | MMMMMM - * 30 | S - * 31 | SS - * 32 | SSS - * 33 | SSSS - * 34 | SSSSS - * 35 | SSSSSS - * 36 | Y - * 37 | YY - * 38 | YYYY - * 39 | I - * 40 | II - * 41 | W - * 42 | WW - * 43 | D - * 44 | DD - * 45 | DDD - * 46 | V - * 47 | VV - * 48 | VVV - * 49 | VVVV - * 50 | VVVVV - * 51 | VVVVVV - * 52 | v - * 53 | vv - * 54 | vvv - * 55 | vvvv - * 56 | vvvvv - * 57 | vvvvvv - */ - - //H HourLower | HH - Hour 24. | AH - Hour 12. | HHH Hour of Month | HHHH Hour of Year - //M MinuteUpper | MM Minute of Hour | MMM Minute of 12h | MMMM Minute of Day | MMMMM Minute of Month | MMMMMM Minute of Year - //S SecondUpper | SS Second of Minute | SSS Second of 10 Minute | SSSS Second of Hour | SSSSS Second of Day | SSSSSS Second of Week - //B AM/PM | BB 0-6/6-12/12-18/18-24 | BBB 0-3... | BBBB 0-1.5... | BBBBB 0-1 | BBBBBB 0-0.5 - - //Y YearLower | YY - Year LU | YYYY - Std. - //I MonthLower | II - Month of Year - //W Week of Month | WW Week of Year - //D Day of Week | DD Day Of Month | DDD Day Of Year - - DEBUG_PRINT("cset "); - DEBUG_PRINTLN(cronixieDisplay); - - overlayRefreshMs = 1997; //Only refresh every 2secs if no seconds are displayed - - for (int i = 0; i < 6; i++) - { - dP[i] = 10; - switch (cronixieDisplay[i]) - { - case '_': dP[i] = 10; break; - case '-': dP[i] = 11; break; - case 'r': dP[i] = random(1,7); break; //random btw. 1-6 - case 'R': dP[i] = random(0,10); break; //random btw. 0-9 - //case 't': break; //Test upw. - //case 'T': break; //Test dnw. - case 'b': dP[i] = 14 + getSameCodeLength('b',i,cronixieDisplay); i = i+dP[i]-14; break; - case 'B': dP[i] = 14 + getSameCodeLength('B',i,cronixieDisplay); i = i+dP[i]-14; break; - case 'h': dP[i] = 70 + getSameCodeLength('h',i,cronixieDisplay); i = i+dP[i]-70; break; - case 'H': dP[i] = 20 + getSameCodeLength('H',i,cronixieDisplay); i = i+dP[i]-20; break; - case 'A': dP[i] = 108; i++; break; - case 'a': dP[i] = 58; i++; break; - case 'm': dP[i] = 74 + getSameCodeLength('m',i,cronixieDisplay); i = i+dP[i]-74; break; - case 'M': dP[i] = 24 + getSameCodeLength('M',i,cronixieDisplay); i = i+dP[i]-24; break; - case 's': dP[i] = 80 + getSameCodeLength('s',i,cronixieDisplay); i = i+dP[i]-80; overlayRefreshMs = 497; break; //refresh more often bc. of secs - case 'S': dP[i] = 30 + getSameCodeLength('S',i,cronixieDisplay); i = i+dP[i]-30; overlayRefreshMs = 497; break; - case 'Y': dP[i] = 36 + getSameCodeLength('Y',i,cronixieDisplay); i = i+dP[i]-36; break; - case 'y': dP[i] = 86 + getSameCodeLength('y',i,cronixieDisplay); i = i+dP[i]-86; break; - case 'I': dP[i] = 39 + getSameCodeLength('I',i,cronixieDisplay); i = i+dP[i]-39; break; //Month. Don't ask me why month and minute both start with M. - case 'i': dP[i] = 89 + getSameCodeLength('i',i,cronixieDisplay); i = i+dP[i]-89; break; - //case 'W': break; - //case 'w': break; - case 'D': dP[i] = 43 + getSameCodeLength('D',i,cronixieDisplay); i = i+dP[i]-43; break; - case 'd': dP[i] = 93 + getSameCodeLength('d',i,cronixieDisplay); i = i+dP[i]-93; break; - case '0': dP[i] = 0; break; - case '1': dP[i] = 1; break; - case '2': dP[i] = 2; break; - case '3': dP[i] = 3; break; - case '4': dP[i] = 4; break; - case '5': dP[i] = 5; break; - case '6': dP[i] = 6; break; - case '7': dP[i] = 7; break; - case '8': dP[i] = 8; break; - case '9': dP[i] = 9; break; - //case 'V': break; //user var0 - //case 'v': break; //user var1 - } - } - DEBUG_PRINT("result "); - for (int i = 0; i < 5; i++) - { - DEBUG_PRINT((int)dP[i]); - DEBUG_PRINT(" "); - } - DEBUG_PRINTLN((int)dP[5]); - - _overlayCronixie(); //refresh -} - -void _overlayCronixie() -{ - byte h = hour(local); - byte h0 = h; - byte m = minute(local); - byte s = second(local); - byte d = day(local); - byte mi = month(local); - int y = year(local); - //this has to be changed in time for 22nd century - y -= 2000; if (y<0) y += 30; //makes countdown work - - if (useAMPM && !countdownMode) - { - if (h>12) h-=12; - else if (h==0) h+=12; - } - for (int i = 0; i < 6; i++) - { - if (dP[i] < 12) _digitOut[i] = dP[i]; - else { - if (dP[i] < 65) - { - switch(dP[i]) - { - case 21: _digitOut[i] = h/10; _digitOut[i+1] = h- _digitOut[i]*10; i++; break; //HH - case 25: _digitOut[i] = m/10; _digitOut[i+1] = m- _digitOut[i]*10; i++; break; //MM - case 31: _digitOut[i] = s/10; _digitOut[i+1] = s- _digitOut[i]*10; i++; break; //SS - - case 20: _digitOut[i] = h- (h/10)*10; break; //H - case 24: _digitOut[i] = m/10; break; //M - case 30: _digitOut[i] = s/10; break; //S - - case 43: _digitOut[i] = weekday(local); _digitOut[i]--; if (_digitOut[i]<1) _digitOut[i]= 7; break; //D - case 44: _digitOut[i] = d/10; _digitOut[i+1] = d- _digitOut[i]*10; i++; break; //DD - case 40: _digitOut[i] = mi/10; _digitOut[i+1] = mi- _digitOut[i]*10; i++; break; //II - case 37: _digitOut[i] = y/10; _digitOut[i+1] = y- _digitOut[i]*10; i++; break; //YY - case 39: _digitOut[i] = 2; _digitOut[i+1] = 0; _digitOut[i+2] = y/10; _digitOut[i+3] = y- _digitOut[i+2]*10; i+=3; break; //YYYY - - //case 16: _digitOut[i+2] = ((h0/3)&1)?1:0; i++; //BBB (BBBB NI) - //case 15: _digitOut[i+1] = (h0>17 || (h0>5 && h0<12))?1:0; i++; //BB - case 14: _digitOut[i] = (h0>11)?1:0; break; //B - } - } else - { - switch(dP[i]) - { - case 71: _digitOut[i] = h/10; _digitOut[i+1] = h- _digitOut[i]*10; if(_digitOut[i] == 0) _digitOut[i]=10; i++; break; //hh - case 75: _digitOut[i] = m/10; _digitOut[i+1] = m- _digitOut[i]*10; if(_digitOut[i] == 0) _digitOut[i]=10; i++; break; //mm - case 81: _digitOut[i] = s/10; _digitOut[i+1] = s- _digitOut[i]*10; if(_digitOut[i] == 0) _digitOut[i]=10; i++; break; //ss - //case 66: _digitOut[i+2] = ((h0/3)&1)?1:10; i++; //bbb (bbbb NI) - //case 65: _digitOut[i+1] = (h0>17 || (h0>5 && h0<12))?1:10; i++; //bb - case 64: _digitOut[i] = (h0>11)?1:10; break; //b - - case 93: _digitOut[i] = weekday(local); _digitOut[i]--; if (_digitOut[i]<1) _digitOut[i]= 7; break; //d - case 94: _digitOut[i] = d/10; _digitOut[i+1] = d- _digitOut[i]*10; if(_digitOut[i] == 0) _digitOut[i]=10; i++; break; //dd - case 90: _digitOut[i] = mi/10; _digitOut[i+1] = mi- _digitOut[i]*10; if(_digitOut[i] == 0) _digitOut[i]=10; i++; break; //ii - case 87: _digitOut[i] = y/10; _digitOut[i+1] = y- _digitOut[i]*10; i++; break; //yy - case 89: _digitOut[i] = 2; _digitOut[i+1] = 0; _digitOut[i+2] = y/10; _digitOut[i+3] = y- _digitOut[i+2]*10; i+=3; break; //yyyy - } - } - } - } -} - -void _drawOverlayCronixie() -{ - byte offsets[] = {5, 0, 6, 1, 7, 2, 8, 3, 9, 4}; - - for (uint16_t i = 0; i < 6; i++) - { - byte o = 10*i; - byte excl = 10; - if(_digitOut[i] < 10) excl = offsets[_digitOut[i]]; - excl += o; - - if (cronixieBacklight && _digitOut[i] <11) - { - uint32_t col = strip.gamma32(strip.getSegment(0).colors[1]); - for (uint16_t j=o; j< o+10; j++) { - if (j != excl) strip.setPixelColor(j, col); - } - } else - { - for (uint16_t j=o; j< o+10; j++) { - if (j != excl) strip.setPixelColor(j, 0); - } - } - } -} - -#else // WLED_DISABLE_CRONIXIE -byte getSameCodeLength(char code, int index, char const cronixieDisplay[]) {} -void setCronixie() {} -void _overlayCronixie() {} -void _drawOverlayCronixie() {} -#endif diff --git a/wled00/cronixie.h b/wled00/cronixie.h deleted file mode 100644 index f6eea6b43..000000000 --- a/wled00/cronixie.h +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef WLED_CRONIXIE_H -#define WLED_CRONIXIE_H -#include -/* - * Support for the Cronixie clock - */ - -byte getSameCodeLength(char code, int index, char const cronixieDisplay[]); -void setCronixie(); -void _overlayCronixie(); -void _drawOverlayCronixie(); - -#endif // WLED_CRONIXIE_H \ No newline at end of file diff --git a/wled00/dmx.h b/wled00/dmx.cpp similarity index 96% rename from wled00/dmx.h rename to wled00/dmx.cpp index 0ab2a4a20..596624a6e 100644 --- a/wled00/dmx.h +++ b/wled00/dmx.cpp @@ -1,6 +1,5 @@ -#ifndef WLED_DMX_H -#define WLED_DMX_H #include "wled.h" + /* * Support for DMX via MAX485. * Change the output pin in src/dependencies/ESPDMX.cpp if needed. @@ -61,5 +60,3 @@ void handleDMX() #else void handleDMX() {} #endif - -#endif //WLED_DMX_H \ No newline at end of file diff --git a/wled00/e131.cpp b/wled00/e131.cpp new file mode 100644 index 000000000..2034f84d9 --- /dev/null +++ b/wled00/e131.cpp @@ -0,0 +1,144 @@ +#include "wled.h" + +/* + * E1.31 handler + */ + +void handleE131Packet(e131_packet_t* p, IPAddress clientIP){ + //E1.31 protocol support + + uint16_t uni = htons(p->universe); + uint8_t previousUniverses = uni - e131Universe; + uint16_t possibleLEDsInCurrentUniverse; + uint16_t dmxChannels = htons(p->property_value_count) -1; + + // only listen for universes we're handling & allocated memory + if (uni >= (e131Universe + E131_MAX_UNIVERSE_COUNT)) return; + + if (e131SkipOutOfSequence) + if (p->sequence_number < e131LastSequenceNumber[uni-e131Universe] && p->sequence_number > 20 && e131LastSequenceNumber[uni-e131Universe] < 250){ + DEBUG_PRINT("skipping E1.31 frame (last seq="); + DEBUG_PRINT(e131LastSequenceNumber[uni-e131Universe]); + DEBUG_PRINT(", current seq="); + DEBUG_PRINT(p->sequence_number); + DEBUG_PRINT(", universe="); + DEBUG_PRINT(uni); + DEBUG_PRINTLN(")"); + return; + } + e131LastSequenceNumber[uni-e131Universe] = p->sequence_number; + + // update status info + realtimeIP = clientIP; + + switch (DMXMode) { + case DMX_MODE_DISABLED: + return; // nothing to do + break; + + case DMX_MODE_SINGLE_RGB: + if (uni != e131Universe) return; + if (dmxChannels-DMXAddress+1 < 3) return; + arlsLock(realtimeTimeoutMs, REALTIME_MODE_E131); + for (uint16_t i = 0; i < ledCount; i++) + setRealtimePixel(i, p->property_values[DMXAddress+0], p->property_values[DMXAddress+1], p->property_values[DMXAddress+2], 0); + break; + + case DMX_MODE_SINGLE_DRGB: + if (uni != e131Universe) return; + if (dmxChannels-DMXAddress+1 < 4) return; + arlsLock(realtimeTimeoutMs, REALTIME_MODE_E131); + if (DMXOldDimmer != p->property_values[DMXAddress+0]) { + DMXOldDimmer = p->property_values[DMXAddress+0]; + bri = p->property_values[DMXAddress+0]; + strip.setBrightness(bri); + } + for (uint16_t i = 0; i < ledCount; i++) + setRealtimePixel(i, p->property_values[DMXAddress+1], p->property_values[DMXAddress+2], p->property_values[DMXAddress+3], 0); + break; + + case DMX_MODE_EFFECT: + if (uni != e131Universe) return; + if (dmxChannels-DMXAddress+1 < 11) return; + if (DMXOldDimmer != p->property_values[DMXAddress+0]) { + DMXOldDimmer = p->property_values[DMXAddress+0]; + bri = p->property_values[DMXAddress+0]; + } + if (p->property_values[DMXAddress+1] < MODE_COUNT) + effectCurrent = p->property_values[DMXAddress+ 1]; + effectSpeed = p->property_values[DMXAddress+ 2]; // flickers + effectIntensity = p->property_values[DMXAddress+ 3]; + effectPalette = p->property_values[DMXAddress+ 4]; + col[0] = p->property_values[DMXAddress+ 5]; + col[1] = p->property_values[DMXAddress+ 6]; + col[2] = p->property_values[DMXAddress+ 7]; + colSec[0] = p->property_values[DMXAddress+ 8]; + colSec[1] = p->property_values[DMXAddress+ 9]; + colSec[2] = p->property_values[DMXAddress+10]; + if (dmxChannels-DMXAddress+1 > 11) + { + col[3] = p->property_values[DMXAddress+11]; //white + colSec[3] = p->property_values[DMXAddress+12]; + } + transitionDelayTemp = 0; // act fast + colorUpdated(NOTIFIER_CALL_MODE_NOTIFICATION); // don't send UDP + return; // don't activate realtime live mode + break; + + case DMX_MODE_MULTIPLE_RGB: + arlsLock(realtimeTimeoutMs, REALTIME_MODE_E131); + if (previousUniverses == 0) { + // first universe of this fixture + possibleLEDsInCurrentUniverse = (dmxChannels - DMXAddress + 1) / 3; + for (uint16_t i = 0; i < ledCount; i++) { + if (i >= possibleLEDsInCurrentUniverse) break; // more LEDs will follow in next universe(s) + setRealtimePixel(i, p->property_values[DMXAddress+i*3+0], p->property_values[DMXAddress+i*3+1], p->property_values[DMXAddress+i*3+2], 0); + } + } else if (previousUniverses > 0 && uni < (e131Universe + E131_MAX_UNIVERSE_COUNT)) { + // additional universe(s) of this fixture + uint16_t numberOfLEDsInPreviousUniverses = ((512 - DMXAddress + 1) / 3); // first universe + if (previousUniverses > 1) numberOfLEDsInPreviousUniverses += (512 / 3) * (previousUniverses - 1); // extended universe(s) before current + possibleLEDsInCurrentUniverse = dmxChannels / 3; + for (uint16_t i = numberOfLEDsInPreviousUniverses; i < ledCount; i++) { + uint8_t j = i - numberOfLEDsInPreviousUniverses; + if (j >= possibleLEDsInCurrentUniverse) break; // more LEDs will follow in next universe(s) + setRealtimePixel(i, p->property_values[j*3+1], p->property_values[j*3+2], p->property_values[j*3+3], 0); + } + } + break; + + case DMX_MODE_MULTIPLE_DRGB: + arlsLock(realtimeTimeoutMs, REALTIME_MODE_E131); + if (previousUniverses == 0) { + // first universe of this fixture + if (DMXOldDimmer != p->property_values[DMXAddress+0]) { + DMXOldDimmer = p->property_values[DMXAddress+0]; + bri = p->property_values[DMXAddress+0]; + strip.setBrightness(bri); + } + possibleLEDsInCurrentUniverse = (dmxChannels - DMXAddress) / 3; + for (uint16_t i = 0; i < ledCount; i++) { + if (i >= possibleLEDsInCurrentUniverse) break; // more LEDs will follow in next universe(s) + setRealtimePixel(i, p->property_values[DMXAddress+i*3+1], p->property_values[DMXAddress+i*3+2], p->property_values[DMXAddress+i*3+3], 0); + } + } else if (previousUniverses > 0 && uni < (e131Universe + E131_MAX_UNIVERSE_COUNT)) { + // additional universe(s) of this fixture + uint16_t numberOfLEDsInPreviousUniverses = ((512 - DMXAddress + 1) / 3); // first universe + if (previousUniverses > 1) numberOfLEDsInPreviousUniverses += (512 / 3) * (previousUniverses - 1); // extended universe(s) before current + possibleLEDsInCurrentUniverse = dmxChannels / 3; + for (uint16_t i = numberOfLEDsInPreviousUniverses; i < ledCount; i++) { + uint8_t j = i - numberOfLEDsInPreviousUniverses; + if (j >= possibleLEDsInCurrentUniverse) break; // more LEDs will follow in next universe(s) + setRealtimePixel(i, p->property_values[j*3+1], p->property_values[j*3+2], p->property_values[j*3+3], 0); + } + } + break; + + default: + DEBUG_PRINTLN("unknown E1.31 DMX mode"); + return; // nothing to do + break; + } + + e131NewData = true; +} diff --git a/wled00/file.cpp b/wled00/file.cpp index dba27d4fe..eb0dcb931 100644 --- a/wled00/file.cpp +++ b/wled00/file.cpp @@ -1,7 +1,8 @@ -#include "file.h" #include "wled.h" -#include "led.h" -#include "notify.h" + +/* + * Utility for SPIFFS filesystem + */ //filesystem #ifndef WLED_DISABLE_FILESYSTEM @@ -12,85 +13,6 @@ #include "SPIFFSEditor.h" #endif -enum class AdaState { - Header_A, - Header_d, - Header_a, - Header_CountHi, - Header_CountLo, - Header_CountCheck, - Data_Red, - Data_Green, - Data_Blue -}; - -// Maybe Adalight should not be in filehandling? TODO -void handleSerial() -{ - #ifdef WLED_ENABLE_ADALIGHT - static auto state = AdaState::Header_A; - static uint16_t count = 0; - static uint16_t pixel = 0; - static byte check = 0x00; - static byte red = 0x00; - static byte green = 0x00; - - while (Serial.available() > 0) - { - yield(); - byte next = Serial.read(); - switch (state) { - case AdaState::Header_A: - if (next == 'A') state = AdaState::Header_d; - break; - case AdaState::Header_d: - if (next == 'd') state = AdaState::Header_a; - else state = AdaState::Header_A; - break; - case AdaState::Header_a: - if (next == 'a') state = AdaState::Header_CountHi; - else state = AdaState::Header_A; - break; - case AdaState::Header_CountHi: - pixel = 0; - count = next * 0x100; - check = next; - state = AdaState::Header_CountLo; - break; - case AdaState::Header_CountLo: - count += next + 1; - check = check ^ next ^ 0x55; - state = AdaState::Header_CountCheck; - break; - case AdaState::Header_CountCheck: - if (check == next) state = AdaState::Data_Red; - else state = AdaState::Header_A; - break; - case AdaState::Data_Red: - red = next; - state = AdaState::Data_Green; - break; - case AdaState::Data_Green: - green = next; - state = AdaState::Data_Blue; - break; - case AdaState::Data_Blue: - byte blue = next; - setRealtimePixel(pixel++, red, green, blue, 0); - if (--count > 0) state = AdaState::Data_Red; - else { - if (!realtimeMode && bri == 0) strip.setBrightness(briLast); - arlsLock(realtimeTimeoutMs, REALTIME_MODE_ADALIGHT); - - strip.show(); - state = AdaState::Header_A; - } - break; - } - } - #endif -} - #if !defined WLED_DISABLE_FILESYSTEM && defined WLED_ENABLE_FS_SERVING //Un-comment any file types you need diff --git a/wled00/file.h b/wled00/file.h deleted file mode 100644 index 90731db67..000000000 --- a/wled00/file.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef WLED_FILE_H -#define WLED_FILE_H -#include -#include -/* - * Utility for SPIFFS filesystem & Serial console - */ - -void handleSerial(); -bool handleFileRead(AsyncWebServerRequest*, String path); - -#endif // WLED_FILE_H \ No newline at end of file diff --git a/wled00/func_declare.h b/wled00/func_declare.h new file mode 100644 index 000000000..db3cb4bc4 --- /dev/null +++ b/wled00/func_declare.h @@ -0,0 +1,183 @@ +#ifndef WLED_FUNC_DECLARE_H +#define WLED_FUNC_DECLARE_H +#include +#include "src/dependencies/espalexa/EspalexaDevice.h" +#include "src/dependencies/e131/ESPAsyncE131.h" + +/* + * All globally accessible functions are declared here + */ + +//alexa.cpp +void onAlexaChange(EspalexaDevice* dev); +void alexaInit(); +void handleAlexa(); +void onAlexaChange(EspalexaDevice* dev); + +//blynk.cpp +void initBlynk(const char* auth); +void handleBlynk(); +void updateBlynk(); + +//button.cpp +void shortPressAction(); +void handleButton(); +void handleIO(); + +//colors.cpp +void colorFromUint32(uint32_t in, bool secondary = false); +void colorFromUint24(uint32_t in, bool secondary = false); +void relativeChangeWhite(int8_t amount, byte lowerBoundary = 0); +void colorHStoRGB(uint16_t hue, byte sat, byte* rgb); //hue, sat to rgb +void colorCTtoRGB(uint16_t mired, byte* rgb); //white spectrum to rgb + +void colorXYtoRGB(float x, float y, byte* rgb); // only defined if huesync disabled TODO +void colorRGBtoXY(byte* rgb, float* xy); // only defined if huesync disabled TODO + +void colorFromDecOrHexString(byte* rgb, char* in); +void colorRGBtoRGBW(byte* rgb); //rgb to rgbw (http://codewelt.com/rgbw). (RGBW_MODE_LEGACY) + +//e131.cpp +void handleE131Packet(e131_packet_t* p, IPAddress clientIP); + +//file.cpp +bool handleFileRead(AsyncWebServerRequest*, String path); + +//dmx.cpp +void handleDMX(); + +//hue.cpp +void handleHue(); +void reconnectHue(); +void onHueError(void* arg, AsyncClient* client, int8_t error); +void onHueConnect(void* arg, AsyncClient* client); +void sendHuePoll(); +void onHueData(void* arg, AsyncClient* client, void *data, size_t len); + +//ir.cpp +bool decodeIRCustom(uint32_t code); +void relativeChange(byte* property, int8_t amount, byte lowerBoundary = 0, byte higherBoundary = 0xFF); +void changeEffectSpeed(int8_t amount); +void changeEffectIntensity(int8_t amount); +void decodeIR(uint32_t code); +void decodeIR24(uint32_t code); +void decodeIR24OLD(uint32_t code); +void decodeIR24CT(uint32_t code); +void decodeIR40(uint32_t code); +void decodeIR44(uint32_t code); +void decodeIR21(uint32_t code); +void decodeIR6(uint32_t code); + +void initIR(); +void handleIR(); + +//json.cpp +#include "ESPAsyncWebServer.h" +#include "src/dependencies/json/ArduinoJson-v6.h" +#include "src/dependencies/json/AsyncJson-v6.h" +#include "FX.h" +// TODO: AsynicWebServerRequest conflict? + +void deserializeSegment(JsonObject elem, byte it); +bool deserializeState(JsonObject root); +void serializeSegment(JsonObject& root, WS2812FX::Segment& seg, byte id); +void serializeState(JsonObject root); +void serializeInfo(JsonObject root); +void serveJson(AsyncWebServerRequest* request); +void serveLiveLeds(AsyncWebServerRequest* request); + +//led.cpp +void setValuesFromMainSeg(); +void resetTimebase(); +void toggleOnOff(); +void setAllLeds(); +void setLedsStandard(bool justColors = false); +bool colorChanged(); +void colorUpdated(int callMode); +void updateInterfaces(uint8_t callMode); +void handleTransitions(); +void handleNightlight(); + +//mqtt.cpp +bool initMqtt(); +void publishMqtt(); + +//notify.cpp +void notify(byte callMode, bool followUp=false); +void arlsLock(uint32_t timeoutMs, byte md = REALTIME_MODE_GENERIC); +void handleNotifications(); +void setRealtimePixel(uint16_t i, byte r, byte g, byte b, byte w); + +//ntp.cpp +void handleNetworkTime(); +void sendNTPPacket(); +bool checkNTPResponse(); +void updateLocalTime(); +void getTimeString(char* out); +bool checkCountdown(); +void setCountdown(); +byte weekdayMondayFirst(); +void checkTimers(); + +//overlay.cpp +void initCronixie(); +void handleOverlays(); +void handleOverlayDraw(); +void _overlayAnalogCountdown(); +void _overlayAnalogClock(); + +byte getSameCodeLength(char code, int index, char const cronixieDisplay[]); +void setCronixie(); +void _overlayCronixie(); +void _drawOverlayCronixie(); + +//set.cpp +void _setRandomColor(bool _sec,bool fromButton=false); +bool isAsterisksOnly(const char* str, byte maxLen); +void handleSettingsSet(AsyncWebServerRequest *request, byte subPage); +bool handleSet(AsyncWebServerRequest *request, const String& req); +int getNumVal(const String* req, uint16_t pos); +bool updateVal(const String* req, const char* key, byte* val, byte minv=0, byte maxv=255); + +//usermod.cpp +void userSetup(); +void userConnected(); +void userLoop(); + +//wled_eeprom.cpp +void commit(); +void clearEEPROM(); +void writeStringToEEPROM(uint16_t pos, char* str, uint16_t len); +void readStringFromEEPROM(uint16_t pos, char* str, uint16_t len); +void saveSettingsToEEPROM(); +void loadSettingsFromEEPROM(bool first); +void savedToPresets(); +bool applyPreset(byte index, bool loadBri = true); +void savePreset(byte index, bool persist = true); +void loadMacro(byte index, char* m); +void applyMacro(byte index); +void saveMacro(byte index, String mc, bool persist = true); //only commit on single save, not in settings + +//wled_serial.cpp +void handleSerial(); + +//wled_server.cpp +bool isIp(String str); +bool captivePortal(AsyncWebServerRequest *request); +void initServer(); +void serveIndexOrWelcome(AsyncWebServerRequest *request); +void serveIndex(AsyncWebServerRequest* request); +String msgProcessor(const String& var); +void serveMessage(AsyncWebServerRequest* request, uint16_t code, String headl, String subl="", byte optionT=255); +String settingsProcessor(const String& var); +String dmxProcessor(const String& var); +void serveSettings(AsyncWebServerRequest* request); + +//xml.cpp +char* XML_response(AsyncWebServerRequest *request, char* dest = nullptr); +char* URL_response(AsyncWebServerRequest *request); +void sappend(char stype, const char* key, int val); +void sappends(char stype, const char* key, char* val); +void getSettingsJS(byte subPage, char* dest); + +#endif diff --git a/wled00/hue.cpp b/wled00/hue.cpp index a186a3144..77091f63e 100644 --- a/wled00/hue.cpp +++ b/wled00/hue.cpp @@ -1,9 +1,8 @@ -#include "hue.h" #include "wled.h" -#include "colors.h" -#include "wled_eeprom.h" -#include "notify.h" -#include "led.h" + +/* + * Sync to Philips hue lights + */ #ifndef WLED_DISABLE_HUESYNC diff --git a/wled00/hue.h b/wled00/hue.h deleted file mode 100644 index 7ce629fbe..000000000 --- a/wled00/hue.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef WLED_HUE_H -#define WLED_HUE_H -/* - * Sync to Philips hue lights - */ -#include -class AsyncClient; - -void handleHue(); -void reconnectHue(); -void onHueError(void* arg, AsyncClient* client, int8_t error); -void onHueConnect(void* arg, AsyncClient* client); -void sendHuePoll(); -void onHueData(void* arg, AsyncClient* client, void *data, size_t len); - -#endif //WLED_HUE_H \ No newline at end of file diff --git a/wled00/ir.cpp b/wled00/ir.cpp index 32b0ee937..046991db5 100644 --- a/wled00/ir.cpp +++ b/wled00/ir.cpp @@ -1,8 +1,8 @@ -#include "ir.h" #include "wled.h" -#include "led.h" -#include "colors.h" -#include "wled_eeprom.h" + +/* + * Infrared sensor support for generic 24/40/44 key RGB remotes + */ #if defined(WLED_DISABLE_INFRARED) void handleIR(){} diff --git a/wled00/ir.h b/wled00/ir.h deleted file mode 100644 index e6ecb9668..000000000 --- a/wled00/ir.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef WLED_IR_H -#define WLED_IR_H -#include -/* - * Infrared sensor support for generic 24/40/44 key RGB remotes - */ - -bool decodeIRCustom(uint32_t code); -void relativeChange(byte* property, int8_t amount, byte lowerBoundary = 0, byte higherBoundary = 0xFF); -void changeEffectSpeed(int8_t amount); -void changeEffectIntensity(int8_t amount); -void decodeIR(uint32_t code); -void decodeIR24(uint32_t code); -void decodeIR24OLD(uint32_t code); -void decodeIR24CT(uint32_t code); -void decodeIR40(uint32_t code); -void decodeIR44(uint32_t code); -void decodeIR21(uint32_t code); -void decodeIR6(uint32_t code); - -void initIR(); -void handleIR(); - -#endif //WLED_IR_H \ No newline at end of file diff --git a/wled00/json.cpp b/wled00/json.cpp index e79737c43..31208bf5b 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -1,7 +1,8 @@ -#include "json.h" #include "wled.h" -#include "wled_eeprom.h" -#include "led.h" + +/* + * JSON API (De)serialization + */ void deserializeSegment(JsonObject elem, byte it) { diff --git a/wled00/json.h b/wled00/json.h deleted file mode 100644 index 77d1b616f..000000000 --- a/wled00/json.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef WLED_JSON_H -#define WLED_JSON_H -/* - * JSON API (De)serialization - */ -#include -#include "ESPAsyncWebServer.h" -#include "src/dependencies/json/ArduinoJson-v6.h" -#include "src/dependencies/json/AsyncJson-v6.h" -#include "fx.h" -// TODO: AsynicWebServerRequest conflict? - -void deserializeSegment(JsonObject elem, byte it); -bool deserializeState(JsonObject root); -void serializeSegment(JsonObject& root, WS2812FX::Segment& seg, byte id); -void serializeState(JsonObject root); -void serializeInfo(JsonObject root); -void serveJson(AsyncWebServerRequest* request); -void serveLiveLeds(AsyncWebServerRequest* request); - -#endif //WLED_JSON_H \ No newline at end of file diff --git a/wled00/led.cpp b/wled00/led.cpp index 7f4d63095..c3f50cc22 100644 --- a/wled00/led.cpp +++ b/wled00/led.cpp @@ -1,10 +1,8 @@ -#include "led.h" #include "wled.h" -#include "notify.h" -#include "blynk.h" -#include "wled_eeprom.h" -#include "mqtt.h" -#include "colors.h" + +/* + * LED methods + */ void setValuesFromMainSeg() { diff --git a/wled00/led.h b/wled00/led.h deleted file mode 100644 index d5eb97a8d..000000000 --- a/wled00/led.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef WLED_LED_H -#define WLED_LED_H -#include -/* - * LED methods - */ - -void setValuesFromMainSeg(); -void resetTimebase(); -void toggleOnOff(); -void setAllLeds(); -void setLedsStandard(bool justColors = false); -bool colorChanged(); -void colorUpdated(int callMode); -void updateInterfaces(uint8_t callMode); -void handleTransitions(); -void handleNightlight(); - -#endif \ No newline at end of file diff --git a/wled00/mqtt.cpp b/wled00/mqtt.cpp index b84184af0..9c15f5bb3 100644 --- a/wled00/mqtt.cpp +++ b/wled00/mqtt.cpp @@ -1,10 +1,8 @@ -#include "mqtt.h" #include "wled.h" -#include "notify.h" -#include "led.h" -#include "colors.h" -#include "xml.h" -#include "set.h" + +/* + * MQTT communication protocol for home automation + */ #ifdef WLED_ENABLE_MQTT #define MQTT_KEEP_ALIVE_TIME 60 // contact the MQTT broker every 60 seconds diff --git a/wled00/mqtt.h b/wled00/mqtt.h deleted file mode 100644 index de602b981..000000000 --- a/wled00/mqtt.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef WLED_MQTT_H -#define WLED_MQTT_H -/* - * MQTT communication protocol for home automation - */ -bool initMqtt(); -void publishMqtt(); - -#endif //WLED_MQTT_H \ No newline at end of file diff --git a/wled00/notify.h b/wled00/notify.h deleted file mode 100644 index a0fbfde2c..000000000 --- a/wled00/notify.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef WLED_NOTIFY_H -#define WLED_NOTIFY_H -#include -#include "src/dependencies/e131/ESPAsyncE131.h" -#include "const.h" -/* - * UDP notifier - */ -//union e131_packet_t; // Will this compile? -class IPAddress; - -void notify(byte callMode, bool followUp=false); -void arlsLock(uint32_t timeoutMs, byte md = REALTIME_MODE_GENERIC); -void handleE131Packet(e131_packet_t* p, IPAddress clientIP); -void handleNotifications(); -void setRealtimePixel(uint16_t i, byte r, byte g, byte b, byte w); - -#endif // WLED_NOTIFY_H \ No newline at end of file diff --git a/wled00/ntp.cpp b/wled00/ntp.cpp index 206631097..d6332fd2c 100644 --- a/wled00/ntp.cpp +++ b/wled00/ntp.cpp @@ -1,7 +1,9 @@ -#include "ntp.h" #include "src/dependencies/timezone/Timezone.h" #include "wled.h" -#include "wled_eeprom.h" + +/* + * Acquires time from NTP server + */ TimeChangeRule UTCr = {Last, Sun, Mar, 1, 0}; // UTC Timezone tzUTC(UTCr, UTCr); diff --git a/wled00/ntp.h b/wled00/ntp.h deleted file mode 100644 index 9029661dd..000000000 --- a/wled00/ntp.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef WLED_NTP_H -#define WLED_NTP_H -#include -/* - * Acquires time from NTP server - */ - -void handleNetworkTime(); -void sendNTPPacket(); -bool checkNTPResponse(); -void updateLocalTime(); -void getTimeString(char* out); -bool checkCountdown(); -void setCountdown(); -byte weekdayMondayFirst(); -void checkTimers(); - -#endif // WLED_NTP_H \ No newline at end of file diff --git a/wled00/overlay.cpp b/wled00/overlay.cpp index ccdd3d0cd..ceb6fc209 100644 --- a/wled00/overlay.cpp +++ b/wled00/overlay.cpp @@ -1,7 +1,8 @@ -#include "overlay.h" #include "wled.h" -#include "cronixie.h" -#include "ntp.h" + +/* + * Used to draw clock overlays over the strip + */ void initCronixie() { @@ -127,3 +128,249 @@ void handleOverlayDraw() { case 3: _drawOverlayCronixie(); break; } } + + +/* + * Support for the Cronixie clock + */ + +#ifndef WLED_DISABLE_CRONIXIE +byte _digitOut[6] = {10,10,10,10,10,10}; + +byte getSameCodeLength(char code, int index, char const cronixieDisplay[]) +{ + byte counter = 0; + + for (int i = index+1; i < 6; i++) + { + if (cronixieDisplay[i] == code) + { + counter++; + } else { + return counter; + } + } + return counter; +} + +void setCronixie() +{ + /* + * digit purpose index + * 0-9 | 0-9 (incl. random) + * 10 | blank + * 11 | blank, bg off + * 12 | test upw. + * 13 | test dnw. + * 14 | binary AM/PM + * 15 | BB upper +50 for no trailing 0 + * 16 | BBB + * 17 | BBBB + * 18 | BBBBB + * 19 | BBBBBB + * 20 | H + * 21 | HH + * 22 | HHH + * 23 | HHHH + * 24 | M + * 25 | MM + * 26 | MMM + * 27 | MMMM + * 28 | MMMMM + * 29 | MMMMMM + * 30 | S + * 31 | SS + * 32 | SSS + * 33 | SSSS + * 34 | SSSSS + * 35 | SSSSSS + * 36 | Y + * 37 | YY + * 38 | YYYY + * 39 | I + * 40 | II + * 41 | W + * 42 | WW + * 43 | D + * 44 | DD + * 45 | DDD + * 46 | V + * 47 | VV + * 48 | VVV + * 49 | VVVV + * 50 | VVVVV + * 51 | VVVVVV + * 52 | v + * 53 | vv + * 54 | vvv + * 55 | vvvv + * 56 | vvvvv + * 57 | vvvvvv + */ + + //H HourLower | HH - Hour 24. | AH - Hour 12. | HHH Hour of Month | HHHH Hour of Year + //M MinuteUpper | MM Minute of Hour | MMM Minute of 12h | MMMM Minute of Day | MMMMM Minute of Month | MMMMMM Minute of Year + //S SecondUpper | SS Second of Minute | SSS Second of 10 Minute | SSSS Second of Hour | SSSSS Second of Day | SSSSSS Second of Week + //B AM/PM | BB 0-6/6-12/12-18/18-24 | BBB 0-3... | BBBB 0-1.5... | BBBBB 0-1 | BBBBBB 0-0.5 + + //Y YearLower | YY - Year LU | YYYY - Std. + //I MonthLower | II - Month of Year + //W Week of Month | WW Week of Year + //D Day of Week | DD Day Of Month | DDD Day Of Year + + DEBUG_PRINT("cset "); + DEBUG_PRINTLN(cronixieDisplay); + + overlayRefreshMs = 1997; //Only refresh every 2secs if no seconds are displayed + + for (int i = 0; i < 6; i++) + { + dP[i] = 10; + switch (cronixieDisplay[i]) + { + case '_': dP[i] = 10; break; + case '-': dP[i] = 11; break; + case 'r': dP[i] = random(1,7); break; //random btw. 1-6 + case 'R': dP[i] = random(0,10); break; //random btw. 0-9 + //case 't': break; //Test upw. + //case 'T': break; //Test dnw. + case 'b': dP[i] = 14 + getSameCodeLength('b',i,cronixieDisplay); i = i+dP[i]-14; break; + case 'B': dP[i] = 14 + getSameCodeLength('B',i,cronixieDisplay); i = i+dP[i]-14; break; + case 'h': dP[i] = 70 + getSameCodeLength('h',i,cronixieDisplay); i = i+dP[i]-70; break; + case 'H': dP[i] = 20 + getSameCodeLength('H',i,cronixieDisplay); i = i+dP[i]-20; break; + case 'A': dP[i] = 108; i++; break; + case 'a': dP[i] = 58; i++; break; + case 'm': dP[i] = 74 + getSameCodeLength('m',i,cronixieDisplay); i = i+dP[i]-74; break; + case 'M': dP[i] = 24 + getSameCodeLength('M',i,cronixieDisplay); i = i+dP[i]-24; break; + case 's': dP[i] = 80 + getSameCodeLength('s',i,cronixieDisplay); i = i+dP[i]-80; overlayRefreshMs = 497; break; //refresh more often bc. of secs + case 'S': dP[i] = 30 + getSameCodeLength('S',i,cronixieDisplay); i = i+dP[i]-30; overlayRefreshMs = 497; break; + case 'Y': dP[i] = 36 + getSameCodeLength('Y',i,cronixieDisplay); i = i+dP[i]-36; break; + case 'y': dP[i] = 86 + getSameCodeLength('y',i,cronixieDisplay); i = i+dP[i]-86; break; + case 'I': dP[i] = 39 + getSameCodeLength('I',i,cronixieDisplay); i = i+dP[i]-39; break; //Month. Don't ask me why month and minute both start with M. + case 'i': dP[i] = 89 + getSameCodeLength('i',i,cronixieDisplay); i = i+dP[i]-89; break; + //case 'W': break; + //case 'w': break; + case 'D': dP[i] = 43 + getSameCodeLength('D',i,cronixieDisplay); i = i+dP[i]-43; break; + case 'd': dP[i] = 93 + getSameCodeLength('d',i,cronixieDisplay); i = i+dP[i]-93; break; + case '0': dP[i] = 0; break; + case '1': dP[i] = 1; break; + case '2': dP[i] = 2; break; + case '3': dP[i] = 3; break; + case '4': dP[i] = 4; break; + case '5': dP[i] = 5; break; + case '6': dP[i] = 6; break; + case '7': dP[i] = 7; break; + case '8': dP[i] = 8; break; + case '9': dP[i] = 9; break; + //case 'V': break; //user var0 + //case 'v': break; //user var1 + } + } + DEBUG_PRINT("result "); + for (int i = 0; i < 5; i++) + { + DEBUG_PRINT((int)dP[i]); + DEBUG_PRINT(" "); + } + DEBUG_PRINTLN((int)dP[5]); + + _overlayCronixie(); //refresh +} + +void _overlayCronixie() +{ + byte h = hour(local); + byte h0 = h; + byte m = minute(local); + byte s = second(local); + byte d = day(local); + byte mi = month(local); + int y = year(local); + //this has to be changed in time for 22nd century + y -= 2000; if (y<0) y += 30; //makes countdown work + + if (useAMPM && !countdownMode) + { + if (h>12) h-=12; + else if (h==0) h+=12; + } + for (int i = 0; i < 6; i++) + { + if (dP[i] < 12) _digitOut[i] = dP[i]; + else { + if (dP[i] < 65) + { + switch(dP[i]) + { + case 21: _digitOut[i] = h/10; _digitOut[i+1] = h- _digitOut[i]*10; i++; break; //HH + case 25: _digitOut[i] = m/10; _digitOut[i+1] = m- _digitOut[i]*10; i++; break; //MM + case 31: _digitOut[i] = s/10; _digitOut[i+1] = s- _digitOut[i]*10; i++; break; //SS + + case 20: _digitOut[i] = h- (h/10)*10; break; //H + case 24: _digitOut[i] = m/10; break; //M + case 30: _digitOut[i] = s/10; break; //S + + case 43: _digitOut[i] = weekday(local); _digitOut[i]--; if (_digitOut[i]<1) _digitOut[i]= 7; break; //D + case 44: _digitOut[i] = d/10; _digitOut[i+1] = d- _digitOut[i]*10; i++; break; //DD + case 40: _digitOut[i] = mi/10; _digitOut[i+1] = mi- _digitOut[i]*10; i++; break; //II + case 37: _digitOut[i] = y/10; _digitOut[i+1] = y- _digitOut[i]*10; i++; break; //YY + case 39: _digitOut[i] = 2; _digitOut[i+1] = 0; _digitOut[i+2] = y/10; _digitOut[i+3] = y- _digitOut[i+2]*10; i+=3; break; //YYYY + + //case 16: _digitOut[i+2] = ((h0/3)&1)?1:0; i++; //BBB (BBBB NI) + //case 15: _digitOut[i+1] = (h0>17 || (h0>5 && h0<12))?1:0; i++; //BB + case 14: _digitOut[i] = (h0>11)?1:0; break; //B + } + } else + { + switch(dP[i]) + { + case 71: _digitOut[i] = h/10; _digitOut[i+1] = h- _digitOut[i]*10; if(_digitOut[i] == 0) _digitOut[i]=10; i++; break; //hh + case 75: _digitOut[i] = m/10; _digitOut[i+1] = m- _digitOut[i]*10; if(_digitOut[i] == 0) _digitOut[i]=10; i++; break; //mm + case 81: _digitOut[i] = s/10; _digitOut[i+1] = s- _digitOut[i]*10; if(_digitOut[i] == 0) _digitOut[i]=10; i++; break; //ss + //case 66: _digitOut[i+2] = ((h0/3)&1)?1:10; i++; //bbb (bbbb NI) + //case 65: _digitOut[i+1] = (h0>17 || (h0>5 && h0<12))?1:10; i++; //bb + case 64: _digitOut[i] = (h0>11)?1:10; break; //b + + case 93: _digitOut[i] = weekday(local); _digitOut[i]--; if (_digitOut[i]<1) _digitOut[i]= 7; break; //d + case 94: _digitOut[i] = d/10; _digitOut[i+1] = d- _digitOut[i]*10; if(_digitOut[i] == 0) _digitOut[i]=10; i++; break; //dd + case 90: _digitOut[i] = mi/10; _digitOut[i+1] = mi- _digitOut[i]*10; if(_digitOut[i] == 0) _digitOut[i]=10; i++; break; //ii + case 87: _digitOut[i] = y/10; _digitOut[i+1] = y- _digitOut[i]*10; i++; break; //yy + case 89: _digitOut[i] = 2; _digitOut[i+1] = 0; _digitOut[i+2] = y/10; _digitOut[i+3] = y- _digitOut[i+2]*10; i+=3; break; //yyyy + } + } + } + } +} + +void _drawOverlayCronixie() +{ + byte offsets[] = {5, 0, 6, 1, 7, 2, 8, 3, 9, 4}; + + for (uint16_t i = 0; i < 6; i++) + { + byte o = 10*i; + byte excl = 10; + if(_digitOut[i] < 10) excl = offsets[_digitOut[i]]; + excl += o; + + if (cronixieBacklight && _digitOut[i] <11) + { + uint32_t col = strip.gamma32(strip.getSegment(0).colors[1]); + for (uint16_t j=o; j< o+10; j++) { + if (j != excl) strip.setPixelColor(j, col); + } + } else + { + for (uint16_t j=o; j< o+10; j++) { + if (j != excl) strip.setPixelColor(j, 0); + } + } + } +} + +#else // WLED_DISABLE_CRONIXIE +byte getSameCodeLength(char code, int index, char const cronixieDisplay[]) {} +void setCronixie() {} +void _overlayCronixie() {} +void _drawOverlayCronixie() {} +#endif diff --git a/wled00/overlay.h b/wled00/overlay.h deleted file mode 100644 index f7b54d5db..000000000 --- a/wled00/overlay.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef WLED_OVERLAY_H -#define WLED_OVERLAY_H -#include -/* - * Used to draw clock overlays over the strip - */ - -void initCronixie(); -void handleOverlays(); -void handleOverlayDraw(); -void _overlayAnalogCountdown(); -void _overlayAnalogClock(); - -#endif // WLED_OVERLAY_H \ No newline at end of file diff --git a/wled00/set.cpp b/wled00/set.cpp index f26795c8e..7fa2a3276 100644 --- a/wled00/set.cpp +++ b/wled00/set.cpp @@ -1,14 +1,8 @@ -#include "set.h" #include "wled.h" -#include "colors.h" -#include "hue.h" -#include "led.h" -#include "blynk.h" -#include "wled_eeprom.h" -#include "alexa.h" -#include "cronixie.h" -#include "xml.h" -#include "wled_server.h" + +/* + * Receives client input + */ void _setRandomColor(bool _sec,bool fromButton) { diff --git a/wled00/set.h b/wled00/set.h deleted file mode 100644 index 6bc7dbd6c..000000000 --- a/wled00/set.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef WLED_SET_H -#define WLED_SET_H -#include -#include -/* - * Receives client input - */ - -void _setRandomColor(bool _sec,bool fromButton=false); -bool isAsterisksOnly(const char* str, byte maxLen); -void handleSettingsSet(AsyncWebServerRequest *request, byte subPage); -bool handleSet(AsyncWebServerRequest *request, const String& req); -int getNumVal(const String* req, uint16_t pos); -bool updateVal(const String* req, const char* key, byte* val, byte minv=0, byte maxv=255); - -#endif // WLED_SET_H \ No newline at end of file diff --git a/wled00/notify.cpp b/wled00/udp.cpp similarity index 55% rename from wled00/notify.cpp rename to wled00/udp.cpp index 30adfee01..528798034 100644 --- a/wled00/notify.cpp +++ b/wled00/udp.cpp @@ -1,12 +1,12 @@ -#include "notify.h" #include "wled.h" -#include "src/dependencies/e131/ESPAsyncE131.h" -#include "led.h" + +/* + * UDP sync notifier + */ #define WLEDPACKETSIZE 29 #define UDP_IN_MAXSIZE 1472 - void notify(byte callMode, bool followUp) { if (!udpConnected) return; @@ -87,146 +87,6 @@ void arlsLock(uint32_t timeoutMs, byte md) } -void handleE131Packet(e131_packet_t* p, IPAddress clientIP){ - //E1.31 protocol support - - uint16_t uni = htons(p->universe); - uint8_t previousUniverses = uni - e131Universe; - uint16_t possibleLEDsInCurrentUniverse; - uint16_t dmxChannels = htons(p->property_value_count) -1; - - // only listen for universes we're handling & allocated memory - if (uni >= (e131Universe + E131_MAX_UNIVERSE_COUNT)) return; - - if (e131SkipOutOfSequence) - if (p->sequence_number < e131LastSequenceNumber[uni-e131Universe] && p->sequence_number > 20 && e131LastSequenceNumber[uni-e131Universe] < 250){ - DEBUG_PRINT("skipping E1.31 frame (last seq="); - DEBUG_PRINT(e131LastSequenceNumber[uni-e131Universe]); - DEBUG_PRINT(", current seq="); - DEBUG_PRINT(p->sequence_number); - DEBUG_PRINT(", universe="); - DEBUG_PRINT(uni); - DEBUG_PRINTLN(")"); - return; - } - e131LastSequenceNumber[uni-e131Universe] = p->sequence_number; - - // update status info - realtimeIP = clientIP; - - switch (DMXMode) { - case DMX_MODE_DISABLED: - return; // nothing to do - break; - - case DMX_MODE_SINGLE_RGB: - if (uni != e131Universe) return; - if (dmxChannels-DMXAddress+1 < 3) return; - arlsLock(realtimeTimeoutMs, REALTIME_MODE_E131); - for (uint16_t i = 0; i < ledCount; i++) - setRealtimePixel(i, p->property_values[DMXAddress+0], p->property_values[DMXAddress+1], p->property_values[DMXAddress+2], 0); - break; - - case DMX_MODE_SINGLE_DRGB: - if (uni != e131Universe) return; - if (dmxChannels-DMXAddress+1 < 4) return; - arlsLock(realtimeTimeoutMs, REALTIME_MODE_E131); - if (DMXOldDimmer != p->property_values[DMXAddress+0]) { - DMXOldDimmer = p->property_values[DMXAddress+0]; - bri = p->property_values[DMXAddress+0]; - strip.setBrightness(bri); - } - for (uint16_t i = 0; i < ledCount; i++) - setRealtimePixel(i, p->property_values[DMXAddress+1], p->property_values[DMXAddress+2], p->property_values[DMXAddress+3], 0); - break; - - case DMX_MODE_EFFECT: - if (uni != e131Universe) return; - if (dmxChannels-DMXAddress+1 < 11) return; - if (DMXOldDimmer != p->property_values[DMXAddress+0]) { - DMXOldDimmer = p->property_values[DMXAddress+0]; - bri = p->property_values[DMXAddress+0]; - } - if (p->property_values[DMXAddress+1] < MODE_COUNT) - effectCurrent = p->property_values[DMXAddress+ 1]; - effectSpeed = p->property_values[DMXAddress+ 2]; // flickers - effectIntensity = p->property_values[DMXAddress+ 3]; - effectPalette = p->property_values[DMXAddress+ 4]; - col[0] = p->property_values[DMXAddress+ 5]; - col[1] = p->property_values[DMXAddress+ 6]; - col[2] = p->property_values[DMXAddress+ 7]; - colSec[0] = p->property_values[DMXAddress+ 8]; - colSec[1] = p->property_values[DMXAddress+ 9]; - colSec[2] = p->property_values[DMXAddress+10]; - if (dmxChannels-DMXAddress+1 > 11) - { - col[3] = p->property_values[DMXAddress+11]; //white - colSec[3] = p->property_values[DMXAddress+12]; - } - transitionDelayTemp = 0; // act fast - colorUpdated(NOTIFIER_CALL_MODE_NOTIFICATION); // don't send UDP - return; // don't activate realtime live mode - break; - - case DMX_MODE_MULTIPLE_RGB: - arlsLock(realtimeTimeoutMs, REALTIME_MODE_E131); - if (previousUniverses == 0) { - // first universe of this fixture - possibleLEDsInCurrentUniverse = (dmxChannels - DMXAddress + 1) / 3; - for (uint16_t i = 0; i < ledCount; i++) { - if (i >= possibleLEDsInCurrentUniverse) break; // more LEDs will follow in next universe(s) - setRealtimePixel(i, p->property_values[DMXAddress+i*3+0], p->property_values[DMXAddress+i*3+1], p->property_values[DMXAddress+i*3+2], 0); - } - } else if (previousUniverses > 0 && uni < (e131Universe + E131_MAX_UNIVERSE_COUNT)) { - // additional universe(s) of this fixture - uint16_t numberOfLEDsInPreviousUniverses = ((512 - DMXAddress + 1) / 3); // first universe - if (previousUniverses > 1) numberOfLEDsInPreviousUniverses += (512 / 3) * (previousUniverses - 1); // extended universe(s) before current - possibleLEDsInCurrentUniverse = dmxChannels / 3; - for (uint16_t i = numberOfLEDsInPreviousUniverses; i < ledCount; i++) { - uint8_t j = i - numberOfLEDsInPreviousUniverses; - if (j >= possibleLEDsInCurrentUniverse) break; // more LEDs will follow in next universe(s) - setRealtimePixel(i, p->property_values[j*3+1], p->property_values[j*3+2], p->property_values[j*3+3], 0); - } - } - break; - - case DMX_MODE_MULTIPLE_DRGB: - arlsLock(realtimeTimeoutMs, REALTIME_MODE_E131); - if (previousUniverses == 0) { - // first universe of this fixture - if (DMXOldDimmer != p->property_values[DMXAddress+0]) { - DMXOldDimmer = p->property_values[DMXAddress+0]; - bri = p->property_values[DMXAddress+0]; - strip.setBrightness(bri); - } - possibleLEDsInCurrentUniverse = (dmxChannels - DMXAddress) / 3; - for (uint16_t i = 0; i < ledCount; i++) { - if (i >= possibleLEDsInCurrentUniverse) break; // more LEDs will follow in next universe(s) - setRealtimePixel(i, p->property_values[DMXAddress+i*3+1], p->property_values[DMXAddress+i*3+2], p->property_values[DMXAddress+i*3+3], 0); - } - } else if (previousUniverses > 0 && uni < (e131Universe + E131_MAX_UNIVERSE_COUNT)) { - // additional universe(s) of this fixture - uint16_t numberOfLEDsInPreviousUniverses = ((512 - DMXAddress + 1) / 3); // first universe - if (previousUniverses > 1) numberOfLEDsInPreviousUniverses += (512 / 3) * (previousUniverses - 1); // extended universe(s) before current - possibleLEDsInCurrentUniverse = dmxChannels / 3; - for (uint16_t i = numberOfLEDsInPreviousUniverses; i < ledCount; i++) { - uint8_t j = i - numberOfLEDsInPreviousUniverses; - if (j >= possibleLEDsInCurrentUniverse) break; // more LEDs will follow in next universe(s) - setRealtimePixel(i, p->property_values[j*3+1], p->property_values[j*3+2], p->property_values[j*3+3], 0); - } - } - break; - - default: - DEBUG_PRINTLN("unknown E1.31 DMX mode"); - return; // nothing to do - break; - } - - e131NewData = true; -} - - void handleNotifications() { //send second notification if enabled diff --git a/wled00/usermod.cpp b/wled00/usermod.cpp index 96c1f8a6c..2c7c881f5 100644 --- a/wled00/usermod.cpp +++ b/wled00/usermod.cpp @@ -2,7 +2,7 @@ /* * This file allows you to add own functionality to WLED more easily * See: https://github.com/Aircoookie/WLED/wiki/Add-own-functionality - * EEPROM bytes 2750+ are reserved for your custom use case. (if you extend #define EEPSIZE in wled_eeprom.h) + * EEPROM bytes 2750+ are reserved for your custom use case. (if you extend #define EEPSIZE in const.h) * bytes 2400+ are currently ununsed, but might be used for future wled features */ diff --git a/wled00/usermod.h b/wled00/usermod.h deleted file mode 100644 index 8269c6e92..000000000 --- a/wled00/usermod.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef WLED_USERMOD_H -#define WLED_USERMOD_H - -void userSetup(); -void userConnected(); -void userLoop(); - -#endif // WLED_USERMOD_H \ No newline at end of file diff --git a/wled00/wled.cpp b/wled00/wled.cpp index fdb607765..7579a9c5e 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -1,23 +1,8 @@ #include "wled.h" -#include "alexa.h" -#include "blynk.h" -#include "button.h" -#include "dmx.h" -#include "file.h" -#include "hue.h" -#include "ir.h" -#include "led.h" -#include "mqtt.h" -#include "notify.h" -#include "ntp.h" -#include "overlay.h" -#include "usermod.h" -#include "wled_eeprom.h" -#include "wled_server.h" #include // Global Variable definitions -char versionString[] = "0.9.1"; +char versionString[] = "0.9.1n"; // AP and OTA default passwords (for maximum change them!) char apPass[65] = DEFAULT_AP_PASS; diff --git a/wled00/wled.h b/wled00/wled.h index 70b197c8e..926fa0c81 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -3,10 +3,13 @@ /* Main sketch, global variable declarations @title WLED project sketch - @version 0.9.1 + @version 0.9.1n @author Christian Schwinne */ +// version code in format yymmddb (b = daily build) +#define VERSION 2003300 + // ESP8266-01 (blue) got too little storage space to work with all features of WLED. To use it, you must use ESP8266 Arduino Core v2.4.2 and the setting 512K(No SPIFFS). // ESP8266-01 (black) has 1MB flash and can thus fit the whole program. Use 1M(64K SPIFFS). @@ -78,6 +81,7 @@ #include "src/dependencies/json/AsyncJson-v6.h" #include "src/dependencies/json/ArduinoJson-v6.h" +#include "func_declare.h" #include "html_ui.h" #include "html_settings.h" #include "html_other.h" @@ -117,9 +121,6 @@ #endif #endif -// version code in format yymmddb (b = daily build) -#define VERSION 2003301 - // Global external variable declaration. See wled.cpp for definitions and comments. extern char versionString[]; extern char apPass[65]; diff --git a/wled00/wled00.ino b/wled00/wled00.ino index e33348379..f806e5b34 100644 --- a/wled00/wled00.ino +++ b/wled00/wled00.ino @@ -1,6 +1,6 @@ /* - Arduino Studio support file. -*/ + * Arduino IDE compatibility file. + */ #include "wled.h" void setup() { @@ -9,4 +9,4 @@ void setup() { void loop() { WLED::instance().loop(); -} \ No newline at end of file +} diff --git a/wled00/wled_eeprom.cpp b/wled00/wled_eeprom.cpp index 0087641aa..ccb16ff56 100644 --- a/wled00/wled_eeprom.cpp +++ b/wled00/wled_eeprom.cpp @@ -1,11 +1,10 @@ -#include "wled_eeprom.h" #include #include "wled.h" -#include "cronixie.h" -#include "ntp.h" -#include "set.h" -#include "led.h" +/* + * Methods to handle saving and loading to non-volatile memory + * EEPROM Map: https://github.com/Aircoookie/WLED/wiki/EEPROM-Map + */ //eeprom Version code, enables default settings instead of 0 init on update #define EEPVER 18 diff --git a/wled00/wled_eeprom.h b/wled00/wled_eeprom.h deleted file mode 100644 index eae52b128..000000000 --- a/wled00/wled_eeprom.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef WLED_EPPROM_H -#define WLED_EPPROM_H -#include -/* - * Methods to handle saving and loading to non-volatile memory - * EEPROM Map: https://github.com/Aircoookie/WLED/wiki/EEPROM-Map - */ -#define EEPSIZE 2560 //Maximum is 4096 - -void commit(); -void clearEEPROM(); -void writeStringToEEPROM(uint16_t pos, char* str, uint16_t len); -void readStringFromEEPROM(uint16_t pos, char* str, uint16_t len); -void saveSettingsToEEPROM(); -void loadSettingsFromEEPROM(bool first); -void savedToPresets(); -bool applyPreset(byte index, bool loadBri = true); -void savePreset(byte index, bool persist = true); -void loadMacro(byte index, char* m); -void applyMacro(byte index); -void saveMacro(byte index, String mc, bool persist = true); //only commit on single save, not in settings - -#endif //WLED_EPPROM_H diff --git a/wled00/wled_serial.cpp b/wled00/wled_serial.cpp new file mode 100644 index 000000000..a472729d1 --- /dev/null +++ b/wled00/wled_serial.cpp @@ -0,0 +1,83 @@ +#include "wled.h" + +/* + * Adalight handler + */ + +enum class AdaState { + Header_A, + Header_d, + Header_a, + Header_CountHi, + Header_CountLo, + Header_CountCheck, + Data_Red, + Data_Green, + Data_Blue +}; + +void handleSerial() +{ + #ifdef WLED_ENABLE_ADALIGHT + static auto state = AdaState::Header_A; + static uint16_t count = 0; + static uint16_t pixel = 0; + static byte check = 0x00; + static byte red = 0x00; + static byte green = 0x00; + + while (Serial.available() > 0) + { + yield(); + byte next = Serial.read(); + switch (state) { + case AdaState::Header_A: + if (next == 'A') state = AdaState::Header_d; + break; + case AdaState::Header_d: + if (next == 'd') state = AdaState::Header_a; + else state = AdaState::Header_A; + break; + case AdaState::Header_a: + if (next == 'a') state = AdaState::Header_CountHi; + else state = AdaState::Header_A; + break; + case AdaState::Header_CountHi: + pixel = 0; + count = next * 0x100; + check = next; + state = AdaState::Header_CountLo; + break; + case AdaState::Header_CountLo: + count += next + 1; + check = check ^ next ^ 0x55; + state = AdaState::Header_CountCheck; + break; + case AdaState::Header_CountCheck: + if (check == next) state = AdaState::Data_Red; + else state = AdaState::Header_A; + break; + case AdaState::Data_Red: + red = next; + state = AdaState::Data_Green; + break; + case AdaState::Data_Green: + green = next; + state = AdaState::Data_Blue; + break; + case AdaState::Data_Blue: + byte blue = next; + setRealtimePixel(pixel++, red, green, blue, 0); + if (--count > 0) state = AdaState::Data_Red; + else { + if (!realtimeMode && bri == 0) strip.setBrightness(briLast); + arlsLock(realtimeTimeoutMs, REALTIME_MODE_ADALIGHT); + + strip.show(); + state = AdaState::Header_A; + } + break; + } + } + #endif +} diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index d5b4eebce..cd623bc49 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -1,10 +1,8 @@ -#include "wled_server.h" #include "wled.h" -#include "file.h" -#include "set.h" -#include "json.h" -#include "xml.h" +/* + * Integrated HTTP web server page declarations + */ //Is this an IP? bool isIp(String str) { diff --git a/wled00/wled_server.h b/wled00/wled_server.h deleted file mode 100644 index ff7133d63..000000000 --- a/wled00/wled_server.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef WLED_SERVER_H -#define WLED_SERVER_H -#include -/* - * Server page declarations - */ -class AsyncWebServerRequest; - - -bool isIp(String str); -bool captivePortal(AsyncWebServerRequest *request); -void initServer(); -void serveIndexOrWelcome(AsyncWebServerRequest *request); -void serveIndex(AsyncWebServerRequest* request); -String msgProcessor(const String& var); -void serveMessage(AsyncWebServerRequest* request, uint16_t code, String headl, String subl="", byte optionT=255); -String settingsProcessor(const String& var); -String dmxProcessor(const String& var); -void serveSettings(AsyncWebServerRequest* request); - -#endif //WLED_SERVER_H \ No newline at end of file diff --git a/wled00/xml.cpp b/wled00/xml.cpp index 09e42011d..3af1e54ce 100644 --- a/wled00/xml.cpp +++ b/wled00/xml.cpp @@ -1,8 +1,8 @@ -#include "xml.h" #include "wled.h" -#include "wled_eeprom.h" -#include "ntp.h" +/* + * Sending XML status files to client + */ //build XML response to HTTP /win API request char* XML_response(AsyncWebServerRequest *request, char* dest) diff --git a/wled00/xml.h b/wled00/xml.h deleted file mode 100644 index 022982959..000000000 --- a/wled00/xml.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef WLED_XML_H -#define WLED_XML_H -#include -#include - -/* - * Sending XML status files to client - */ -char* XML_response(AsyncWebServerRequest *request, char* dest = nullptr); -char* URL_response(AsyncWebServerRequest *request); -void sappend(char stype, const char* key, int val); -void sappends(char stype, const char* key, char* val); -void getSettingsJS(byte subPage, char* dest); - -#endif // WLED_XML_H \ No newline at end of file From 5cb2a39746e922fecc996a80617c79bf16bb766d Mon Sep 17 00:00:00 2001 From: cschwinne Date: Mon, 6 Apr 2020 02:25:17 +0200 Subject: [PATCH 84/90] Consolidated global variables in wled.h --- CHANGELOG.md | 4 + wled00/const.h | 13 ++ wled00/dmx.cpp | 2 +- wled00/wled.cpp | 339 +-------------------------- wled00/wled.h | 610 ++++++++++++++++++++++++++++-------------------- 5 files changed, 375 insertions(+), 593 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9804b1f4..8df63ff8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ### Development versions after 0.9.1 release +#### Build 2004060 + +- Consolidated global variables in wled.h + #### Build 2003300 - Major change of project structure from .ino to .cpp and func_declare.h diff --git a/wled00/const.h b/wled00/const.h index 535a59870..ef19e51be 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -75,4 +75,17 @@ //EEPROM size #define EEPSIZE 2560 //Maximum is 4096 +#define NTP_PACKET_SIZE 48 + +// maximum number of LEDs - MAX_LEDS is coming from the JSON response getting too big, MAX_LEDS_DMA will become a timing issue +#define MAX_LEDS 1500 +#define MAX_LEDS_DMA 500 + +// string temp buffer (now stored in stack locally) +#define OMAX 2048 + +#define E131_MAX_UNIVERSE_COUNT 9 + +#define ABL_MILLIAMPS_DEFAULT 850; // auto lower brightness to stay close to milliampere limit + #endif diff --git a/wled00/dmx.cpp b/wled00/dmx.cpp index 596624a6e..ea0871241 100644 --- a/wled00/dmx.cpp +++ b/wled00/dmx.cpp @@ -19,7 +19,7 @@ void handleDMX() for (int i = 0; i < ledCount; i++) { // uses the amount of LEDs as fixture count - uint32_t in = strip.getPixelColor(i); // time to get the colors for the individual fixtures as suggested by AirCookie at issue #462 + uint32_t in = strip.getPixelColor(i); // get the colors for the individual fixtures as suggested by Aircoookie in issue #462 byte w = in >> 24 & 0xFF; byte r = in >> 16 & 0xFF; byte g = in >> 8 & 0xFF; diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 7579a9c5e..701b98e31 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -1,344 +1,7 @@ +#define WLED_DEFINE_GLOBAL_VARS //only in one source file, wled.cpp! #include "wled.h" #include -// Global Variable definitions -char versionString[] = "0.9.1n"; - -// AP and OTA default passwords (for maximum change them!) -char apPass[65] = DEFAULT_AP_PASS; -char otaPass[33] = DEFAULT_OTA_PASS; - -// Hardware CONFIG (only changeble HERE, not at runtime) -// LED strip pin, button pin and IR pin changeable in NpbWrapper.h! - -byte auxDefaultState = 0; // 0: input 1: high 2: low -byte auxTriggeredState = 0; // 0: input 1: high 2: low -char ntpServerName[33] = "0.wled.pool.ntp.org"; // NTP server to use - -// WiFi CONFIG (all these can be changed via web UI, no need to set them here) -char clientSSID[33] = CLIENT_SSID; -char clientPass[65] = CLIENT_PASS; -char cmDNS[33] = "x"; // mDNS address (placeholder, will be replaced by wledXXXXXXXXXXXX.local) -char apSSID[33] = ""; // AP off by default (unless setup) -byte apChannel = 1; // 2.4GHz WiFi AP channel (1-13) -byte apHide = 0; // hidden AP SSID -byte apBehavior = AP_BEHAVIOR_BOOT_NO_CONN; // access point opens when no connection after boot by default -IPAddress staticIP(0, 0, 0, 0); // static IP of ESP -IPAddress staticGateway(0, 0, 0, 0); // gateway (router) IP -IPAddress staticSubnet(255, 255, 255, 0); // most common subnet in home networks -bool noWifiSleep = false; // disabling modem sleep modes will increase heat output and power usage, but may help with connection issues - -// LED CONFIG -uint16_t ledCount = 30; // overcurrent prevented by ABL -bool useRGBW = false; // SK6812 strips can contain an extra White channel -bool turnOnAtBoot = true; // turn on LEDs at power-up -byte bootPreset = 0; // save preset to load after power-up - -byte col[] { 255, 160, 0, 0 }; // current RGB(W) primary color. col[] should be updated if you want to change the color. -byte colSec[] { 0, 0, 0, 0 }; // current RGB(W) secondary color -byte briS = 128; // default brightness - -byte nightlightTargetBri = 0; // brightness after nightlight is over -byte nightlightDelayMins = 60; -bool nightlightFade = true; // if enabled, light will gradually dim towards the target bri. Otherwise, it will instantly set after delay over -bool nightlightColorFade = false; // if enabled, light will gradually fade color from primary to secondary color. -bool fadeTransition = true; // enable crossfading color transition -uint16_t transitionDelay = 750; // default crossfade duration in ms - -bool skipFirstLed = false; // ignore first LED in strip (useful if you need the LED as signal repeater) -byte briMultiplier = 100; // % of brightness to set (to limit power, if you set it to 50 and set bri to 255, actual brightness will be 127) - -// User Interface CONFIG -char serverDescription[33] = "WLED"; // Name of module -bool syncToggleReceive = false; // UIs which only have a single button for sync should toggle send+receive if this is true, only send otherwise - -// Sync CONFIG -bool buttonEnabled = true; -byte irEnabled = 0; // Infrared receiver - -uint16_t udpPort = 21324; // WLED notifier default port -uint16_t udpRgbPort = 19446; // Hyperion port - -bool receiveNotificationBrightness = true; // apply brightness from incoming notifications -bool receiveNotificationColor = true; // apply color -bool receiveNotificationEffects = true; // apply effects setup -bool notifyDirect = false; // send notification if change via UI or HTTP API -bool notifyButton = false; // send if updated by button or infrared remote -bool notifyAlexa = false; // send notification if updated via Alexa -bool notifyMacro = false; // send notification for macro -bool notifyHue = true; // send notification if Hue light changes -bool notifyTwice = false; // notifications use UDP: enable if devices don't sync reliably - -bool alexaEnabled = true; // enable device discovery by Amazon Echo -char alexaInvocationName[33] = "Light"; // speech control name of device. Choose something voice-to-text can understand - -char blynkApiKey[36] = ""; // Auth token for Blynk server. If empty, no connection will be made - -uint16_t realtimeTimeoutMs = 2500; // ms timeout of realtime mode before returning to normal mode -int arlsOffset = 0; // realtime LED offset -bool receiveDirect = true; // receive UDP realtime -bool arlsDisableGammaCorrection = true; // activate if gamma correction is handled by the source -bool arlsForceMaxBri = false; // enable to force max brightness if source has very dark colors that would be black - -uint16_t e131Universe = 1; // settings for E1.31 (sACN) protocol (only DMX_MODE_MULTIPLE_* can span over consequtive universes) -uint8_t DMXMode = DMX_MODE_MULTIPLE_RGB; // DMX mode (s.a.) -uint16_t DMXAddress = 1; // DMX start address of fixture, a.k.a. first Channel [for E1.31 (sACN) protocol] -uint8_t DMXOldDimmer = 0; // only update brightness on change -uint8_t e131LastSequenceNumber[E131_MAX_UNIVERSE_COUNT]; // to detect packet loss -bool e131Multicast = false; // multicast or unicast -bool e131SkipOutOfSequence = false; // freeze instead of flickering - -bool mqttEnabled = false; -char mqttDeviceTopic[33] = ""; // main MQTT topic (individual per device, default is wled/mac) -char mqttGroupTopic[33] = "wled/all"; // second MQTT topic (for example to group devices) -char mqttServer[33] = ""; // both domains and IPs should work (no SSL) -char mqttUser[41] = ""; // optional: username for MQTT auth -char mqttPass[41] = ""; // optional: password for MQTT auth -char mqttClientID[41] = ""; // override the client ID -uint16_t mqttPort = 1883; - -bool huePollingEnabled = false; // poll hue bridge for light state -uint16_t huePollIntervalMs = 2500; // low values (< 1sec) may cause lag but offer quicker response -char hueApiKey[47] = "api"; // key token will be obtained from bridge -byte huePollLightId = 1; // ID of hue lamp to sync to. Find the ID in the hue app ("about" section) -IPAddress hueIP = (0, 0, 0, 0); // IP address of the bridge -bool hueApplyOnOff = true; -bool hueApplyBri = true; -bool hueApplyColor = true; - -// Time CONFIG -bool ntpEnabled = false; // get internet time. Only required if you use clock overlays or time-activated macros -bool useAMPM = false; // 12h/24h clock format -byte currentTimezone = 0; // Timezone ID. Refer to timezones array in wled10_ntp.ino -int utcOffsetSecs = 0; // Seconds to offset from UTC before timzone calculation - -byte overlayDefault = 0; // 0: no overlay 1: analog clock 2: single-digit clocl 3: cronixie -byte overlayMin = 0, overlayMax = ledCount - 1; // boundaries of overlay mode - -byte analogClock12pixel = 0; // The pixel in your strip where "midnight" would be -bool analogClockSecondsTrail = false; // Display seconds as trail of LEDs instead of a single pixel -bool analogClock5MinuteMarks = false; // Light pixels at every 5-minute position - -char cronixieDisplay[7] = "HHMMSS"; // Cronixie Display mask. See wled13_cronixie.ino -bool cronixieBacklight = true; // Allow digits to be back-illuminated - -bool countdownMode = false; // Clock will count down towards date -byte countdownYear = 20, countdownMonth = 1; // Countdown target date, year is last two digits -byte countdownDay = 1, countdownHour = 0; -byte countdownMin = 0, countdownSec = 0; - -byte macroBoot = 0; // macro loaded after startup -byte macroNl = 0; // after nightlight delay over -byte macroCountdown = 0; -byte macroAlexaOn = 0, macroAlexaOff = 0; -byte macroButton = 0, macroLongPress = 0, macroDoublePress = 0; - -// Security CONFIG -bool otaLock = false; // prevents OTA firmware updates without password. ALWAYS enable if system exposed to any public networks -bool wifiLock = false; // prevents access to WiFi settings when OTA lock is enabled -bool aOtaEnabled = true; // ArduinoOTA allows easy updates directly from the IDE. Careful, it does not auto-disable when OTA lock is on - -uint16_t userVar0 = 0, userVar1 = 0; - -#ifdef WLED_ENABLE_DMX - // dmx CONFIG - byte DMXChannels = 7; // number of channels per fixture - byte DMXFixtureMap[15] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - // assigns the different channels to different functions. See wled21_dmx.ino for more information. - uint16_t DMXGap = 10; // gap between the fixtures. makes addressing easier because you don't have to memorize odd numbers when climbing up onto a rig. - uint16_t DMXStart = 10; // start address of the first fixture -#endif - -// internal global variable declarations -// wifi -bool apActive = false; -bool forceReconnect = false; -uint32_t lastReconnectAttempt = 0; -bool interfacesInited = false; -bool wasConnected = false; - -// color -byte colOld[] { 0, 0, 0, 0 }; // color before transition -byte colT[] { 0, 0, 0, 0 }; // color that is currently displayed on the LEDs -byte colIT[] { 0, 0, 0, 0 }; // color that was last sent to LEDs -byte colSecT[] { 0, 0, 0, 0 }; -byte colSecOld[] { 0, 0, 0, 0 }; -byte colSecIT[] { 0, 0, 0, 0 }; - -byte lastRandomIndex = 0; // used to save last random color so the new one is not the same - -// transitions -bool transitionActive = false; -uint16_t transitionDelayDefault = transitionDelay; -uint16_t transitionDelayTemp = transitionDelay; -unsigned long transitionStartTime; -float tperLast = 0; // crossfade transition progress, 0.0f - 1.0f -bool jsonTransitionOnce = false; - -// nightlight -bool nightlightActive = false; -bool nightlightActiveOld = false; -uint32_t nightlightDelayMs = 10; -uint8_t nightlightDelayMinsDefault = nightlightDelayMins; -unsigned long nightlightStartTime; -byte briNlT = 0; // current nightlight brightness -byte colNlT[] { 0, 0, 0, 0 }; // current nightlight color - -// brightness -unsigned long lastOnTime = 0; -bool offMode = !turnOnAtBoot; -byte bri = briS; -byte briOld = 0; -byte briT = 0; -byte briIT = 0; -byte briLast = 128; // brightness before turned off. Used for toggle function -byte whiteLast = 128; // white channel before turned off. Used for toggle function - -// button -bool buttonPressedBefore = false; -bool buttonLongPressed = false; -unsigned long buttonPressedTime = 0; -unsigned long buttonWaitTime = 0; - -// notifications -bool notifyDirectDefault = notifyDirect; -bool receiveNotifications = true; -unsigned long notificationSentTime = 0; -byte notificationSentCallMode = NOTIFIER_CALL_MODE_INIT; -bool notificationTwoRequired = false; - -// effects -byte effectCurrent = 0; -byte effectSpeed = 128; -byte effectIntensity = 128; -byte effectPalette = 0; - -// network -bool udpConnected = false, udpRgbConnected = false; - -// ui style -bool showWelcomePage = false; - -// hue -byte hueError = HUE_ERROR_INACTIVE; -// uint16_t hueFailCount = 0; -float hueXLast = 0, hueYLast = 0; -uint16_t hueHueLast = 0, hueCtLast = 0; -byte hueSatLast = 0, hueBriLast = 0; -unsigned long hueLastRequestSent = 0; -bool hueAuthRequired = false; -bool hueReceived = false; -bool hueStoreAllowed = false, hueNewKey = false; - -// overlays -byte overlayCurrent = overlayDefault; -byte overlaySpeed = 200; -unsigned long overlayRefreshMs = 200; -unsigned long overlayRefreshedTime; - -// cronixie -byte dP[] { 0, 0, 0, 0, 0, 0 }; -bool cronixieInit = false; - -// countdown -unsigned long countdownTime = 1514764800L; -bool countdownOverTriggered = true; - -// timer -byte lastTimerMinute = 0; -byte timerHours[] = { 0, 0, 0, 0, 0, 0, 0, 0 }; -byte timerMinutes[] = { 0, 0, 0, 0, 0, 0, 0, 0 }; -byte timerMacro[] = { 0, 0, 0, 0, 0, 0, 0, 0 }; -byte timerWeekday[] = { 255, 255, 255, 255, 255, 255, 255, 255 }; // weekdays to activate on -// bit pattern of arr elem: 0b11111111: sun,sat,fri,thu,wed,tue,mon,validity - -// blynk -bool blynkEnabled = false; - -// preset cycling -bool presetCyclingEnabled = false; -byte presetCycleMin = 1, presetCycleMax = 5; -uint16_t presetCycleTime = 1250; -unsigned long presetCycledTime = 0; -byte presetCycCurr = presetCycleMin; -bool presetApplyBri = true; -bool saveCurrPresetCycConf = false; - -// realtime -byte realtimeMode = REALTIME_MODE_INACTIVE; -IPAddress realtimeIP = (0, 0, 0, 0); -unsigned long realtimeTimeout = 0; - -// mqtt -long lastMqttReconnectAttempt = 0; -long lastInterfaceUpdate = 0; -byte interfaceUpdateCallMode = NOTIFIER_CALL_MODE_INIT; -char mqttStatusTopic[40] = ""; // this must be global because of async handlers - -#if AUXPIN >= 0 - // auxiliary debug pin - byte auxTime = 0; - unsigned long auxStartTime = 0; - bool auxActive = false, auxActiveBefore = false; -#endif - -// alexa udp -String escapedMac; -#ifndef WLED_DISABLE_ALEXA - Espalexa espalexa; - EspalexaDevice* espalexaDevice; -#endif - -// dns server -DNSServer dnsServer; - -// network time -bool ntpConnected = false; -time_t local = 0; -unsigned long ntpLastSyncTime = 999000000L; -unsigned long ntpPacketSentTime = 999000000L; -IPAddress ntpServerIP; -uint16_t ntpLocalPort = 2390; - -// Temp buffer -char* obuf; -uint16_t olen = 0; - -// presets -uint16_t savedPresets = 0; -int8_t currentPreset = -1; -bool isPreset = false; - -byte errorFlag = 0; - -String messageHead, messageSub; -byte optionType; - -bool doReboot = false; // flag to initiate reboot from async handlers -bool doPublishMqtt = false; - -// server library objects -AsyncWebServer server(80); -AsyncClient* hueClient = NULL; -AsyncMqttClient* mqtt = NULL; - -// udp interface objects -WiFiUDP notifierUdp, rgbUdp; -WiFiUDP ntpUdp; -ESPAsyncE131 e131(handleE131Packet); -bool e131NewData = false; - -// led fx library object -WS2812FX strip = WS2812FX(); - -// debug macro variable definitions -#ifdef WLED_DEBUG - unsigned long debugTime = 0; - int lastWifiState = 3; - unsigned long wifiStateChangedTime = 0; - int loops = 0; -#endif - WLED::WLED() { } diff --git a/wled00/wled.h b/wled00/wled.h index 926fa0c81..0c6d31147 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -8,7 +8,7 @@ */ // version code in format yymmddb (b = daily build) -#define VERSION 2003300 +#define VERSION 2004060 // ESP8266-01 (blue) got too little storage space to work with all features of WLED. To use it, you must use ESP8266 Arduino Core v2.4.2 and the setting 512K(No SPIFFS). @@ -119,268 +119,370 @@ #ifndef ESP8266 #include #endif -#endif - -// Global external variable declaration. See wled.cpp for definitions and comments. -extern char versionString[]; -extern char apPass[65]; -extern char otaPass[33]; -extern byte auxDefaultState; -extern byte auxTriggeredState; -extern char ntpServerName[33]; -extern char clientSSID[33]; -extern char clientPass[65]; -extern char cmDNS[33]; -extern char apSSID[33]; -extern byte apChannel; -extern byte apHide; -extern byte apBehavior; -extern IPAddress staticIP; -extern IPAddress staticGateway; -extern IPAddress staticSubnet; -extern bool noWifiSleep; -extern uint16_t ledCount; -extern bool useRGBW; -#define ABL_MILLIAMPS_DEFAULT 850; // auto lower brightness to stay close to milliampere limit -extern bool turnOnAtBoot; -extern byte bootPreset; -extern byte col[]; -extern byte colSec[]; -extern byte briS; -extern byte nightlightTargetBri; -extern byte nightlightDelayMins; -extern bool nightlightFade; -extern bool nightlightColorFade; -extern bool fadeTransition; -extern uint16_t transitionDelay; -extern bool skipFirstLed; -extern byte briMultiplier; -extern char serverDescription[33]; -extern bool syncToggleReceive; -extern bool buttonEnabled; -extern byte irEnabled; -extern uint16_t udpPort; -extern uint16_t udpRgbPort; -extern bool receiveNotificationBrightness; -extern bool receiveNotificationColor; -extern bool receiveNotificationEffects; -extern bool notifyDirect; -extern bool notifyButton; -extern bool notifyAlexa; -extern bool notifyMacro; -extern bool notifyHue; -extern bool notifyTwice; -extern bool alexaEnabled; -extern char alexaInvocationName[33]; -extern char blynkApiKey[36]; -extern uint16_t realtimeTimeoutMs; -extern int arlsOffset; -extern bool receiveDirect; -extern bool arlsDisableGammaCorrection; -extern bool arlsForceMaxBri; -#define E131_MAX_UNIVERSE_COUNT 9 -extern uint16_t e131Universe; -extern uint8_t DMXMode; -extern uint16_t DMXAddress; -extern uint8_t DMXOldDimmer; -extern uint8_t e131LastSequenceNumber[E131_MAX_UNIVERSE_COUNT]; -extern bool e131Multicast; -extern bool e131SkipOutOfSequence; -extern bool mqttEnabled; -extern char mqttDeviceTopic[33]; -extern char mqttGroupTopic[33]; -extern char mqttServer[33]; -extern char mqttUser[41]; -extern char mqttPass[41]; -extern char mqttClientID[41]; -extern uint16_t mqttPort; -extern bool huePollingEnabled; -extern uint16_t huePollIntervalMs; -extern char hueApiKey[47]; -extern byte huePollLightId; -extern IPAddress hueIP; -extern bool hueApplyOnOff; -extern bool hueApplyBri; -extern bool hueApplyColor; -extern bool ntpEnabled; -extern bool useAMPM; -extern byte currentTimezone; -extern int utcOffsetSecs; -extern byte overlayDefault; -extern byte overlayMin; -extern byte overlayMax; -extern byte analogClock12pixel; -extern bool analogClockSecondsTrail; -extern bool analogClock5MinuteMarks; -extern char cronixieDisplay[7]; -extern bool cronixieBacklight; -extern bool countdownMode; -extern byte countdownYear, countdownMonth; -extern byte countdownDay, countdownHour; -extern byte countdownMin, countdownSec; -extern byte macroBoot; -extern byte macroNl; -extern byte macroCountdown; -extern byte macroAlexaOn, macroAlexaOff; -extern byte macroButton, macroLongPress, macroDoublePress; -extern bool otaLock; -extern bool wifiLock; -extern bool aOtaEnabled; -extern uint16_t userVar0, userVar1; -#ifdef WLED_ENABLE_DMX - extern byte DMXChannels; - extern byte DMXFixtureMap[15]; - extern uint16_t DMXGap; - extern uint16_t DMXStart; -#endif -extern bool apActive; -extern bool forceReconnect; -extern uint32_t lastReconnectAttempt; -extern bool interfacesInited; -extern bool wasConnected; -extern byte colOld[]; -extern byte colT[]; -extern byte colIT[]; -extern byte colSecT[]; -extern byte colSecOld[]; -extern byte colSecIT[]; -extern byte lastRandomIndex; -extern bool transitionActive; -extern uint16_t transitionDelayDefault; -extern uint16_t transitionDelayTemp; -extern unsigned long transitionStartTime; -extern float tperLast; -extern bool jsonTransitionOnce; -extern bool nightlightActive; -extern bool nightlightActiveOld; -extern uint32_t nightlightDelayMs; -extern uint8_t nightlightDelayMinsDefault; -extern unsigned long nightlightStartTime; -extern byte briNlT; -extern byte colNlT[]; -extern unsigned long lastOnTime; -extern bool offMode; -extern byte bri; -extern byte briOld; -extern byte briT; -extern byte briIT; -extern byte briLast; -extern byte whiteLast; -extern bool buttonPressedBefore; -extern bool buttonLongPressed; -extern unsigned long buttonPressedTime; -extern unsigned long buttonWaitTime; -extern bool notifyDirectDefault; -extern bool receiveNotifications; -extern unsigned long notificationSentTime; -extern byte notificationSentCallMode; -extern bool notificationTwoRequired; -extern byte effectCurrent; -extern byte effectSpeed; -extern byte effectIntensity; -extern byte effectPalette; -extern bool udpConnected, udpRgbConnected; -extern bool showWelcomePage; -extern byte hueError; -extern float hueXLast, hueYLast; -extern uint16_t hueHueLast, hueCtLast; -extern byte hueSatLast, hueBriLast; -extern unsigned long hueLastRequestSent; -extern bool hueAuthRequired; -extern bool hueReceived; -extern bool hueStoreAllowed, hueNewKey; -extern byte overlayCurrent; -extern byte overlaySpeed; -extern unsigned long overlayRefreshMs; -extern unsigned long overlayRefreshedTime; -extern byte dP[]; -extern bool cronixieInit; -extern unsigned long countdownTime; -extern bool countdownOverTriggered; -extern byte lastTimerMinute; -extern byte timerHours[]; -extern byte timerMinutes[]; -extern byte timerMacro[]; -extern byte timerWeekday[]; -extern bool blynkEnabled; -extern bool presetCyclingEnabled; -extern byte presetCycleMin, presetCycleMax; -extern uint16_t presetCycleTime; -extern unsigned long presetCycledTime; -extern byte presetCycCurr; -extern bool presetApplyBri; -extern bool saveCurrPresetCycConf; -extern byte realtimeMode; -extern IPAddress realtimeIP; -extern unsigned long realtimeTimeout; -extern long lastMqttReconnectAttempt; -extern long lastInterfaceUpdate; -extern byte interfaceUpdateCallMode; -extern char mqttStatusTopic[40]; -#if AUXPIN >= 0 - extern byte auxTime; - extern unsigned long auxStartTime; - extern bool auxActive; -#endif -extern String escapedMac; -#ifndef WLED_DISABLE_ALEXA - extern Espalexa espalexa; - extern EspalexaDevice *espalexaDevice; -#endif - -#define NTP_PACKET_SIZE 48 -extern DNSServer dnsServer; -extern bool ntpConnected; -extern time_t local; -extern unsigned long ntpLastSyncTime; -extern unsigned long ntpPacketSentTime; -extern IPAddress ntpServerIP; -extern uint16_t ntpLocalPort; - -// maximum number of LEDs - MAX_LEDS is coming from the JSON response getting too big, MAX_LEDS_DMA will become a timing issue -#define MAX_LEDS 1500 -#define MAX_LEDS_DMA 500 - -// string temp buffer (now stored in stack locally) -#define OMAX 2048 -extern char* obuf; -extern uint16_t olen; - -extern uint16_t savedPresets; -extern int8_t currentPreset; -extern bool isPreset; -extern byte errorFlag; -extern String messageHead, messageSub; -extern byte optionType; -extern bool doReboot; -extern bool doPublishMqtt; -extern AsyncWebServer server; -extern AsyncClient* hueClient; -extern AsyncMqttClient* mqtt; -extern WiFiUDP notifierUdp, rgbUdp; -extern WiFiUDP ntpUdp; -extern ESPAsyncE131 e131; -extern bool e131NewData; -extern WS2812FX strip; - -#define WLED_CONNECTED (WiFi.status() == WL_CONNECTED) -#define WLED_WIFI_CONFIGURED (strlen(clientSSID) >= 1 && strcmp(clientSSID, DEFAULT_CLIENT_SSID) != 0) - -// debug macros -#ifdef WLED_DEBUG #define DEBUG_PRINT(x) Serial.print(x) #define DEBUG_PRINTLN(x) Serial.println(x) #define DEBUG_PRINTF(x) Serial.printf(x) - extern unsigned long debugTime; - extern int lastWifiState; - extern unsigned long wifiStateChangedTime; - extern int loops; #else #define DEBUG_PRINT(x) #define DEBUG_PRINTLN(x) #define DEBUG_PRINTF(x) #endif +#ifndef WLED_DEFINE_GLOBAL_VARS +# define WLED_GLOBAL extern +# define _INIT(x) +# define _INIT_N(x) +#else +# define WLED_GLOBAL +# define _INIT(x) = x + +//needed to ignore commas in array definitions +#define UNPACK( ... ) __VA_ARGS__ +# define _INIT_N(x) UNPACK x +#endif + +// Global Variable definitions +WLED_GLOBAL char versionString[] _INIT("0.9.1n"); + +// AP and OTA default passwords (for maximum security change them!) +WLED_GLOBAL char apPass[65] _INIT(DEFAULT_AP_PASS); +WLED_GLOBAL char otaPass[33] _INIT(DEFAULT_OTA_PASS); + +// Hardware CONFIG (only changeble HERE, not at runtime) +// LED strip pin, button pin and IR pin changeable in NpbWrapper.h! + +WLED_GLOBAL byte auxDefaultState _INIT(0); // 0: input 1: high 2: low +WLED_GLOBAL byte auxTriggeredState _INIT(0); // 0: input 1: high 2: low +WLED_GLOBAL char ntpServerName[33] _INIT("0.wled.pool.ntp.org"); // NTP server to use + +// WiFi CONFIG (all these can be changed via web UI, no need to set them here) +WLED_GLOBAL char clientSSID[33] _INIT(CLIENT_SSID); +WLED_GLOBAL char clientPass[65] _INIT(CLIENT_PASS); +WLED_GLOBAL char cmDNS[33] _INIT("x"); // mDNS address (placeholder, is replaced by wledXXXXXX.local) +WLED_GLOBAL char apSSID[33] _INIT(""); // AP off by default (unless setup) +WLED_GLOBAL byte apChannel _INIT(1); // 2.4GHz WiFi AP channel (1-13) +WLED_GLOBAL byte apHide _INIT(0); // hidden AP SSID +WLED_GLOBAL byte apBehavior _INIT(AP_BEHAVIOR_BOOT_NO_CONN); // access point opens when no connection after boot by default +WLED_GLOBAL IPAddress staticIP _INIT_N((( 0, 0, 0, 0))); // static IP of ESP +WLED_GLOBAL IPAddress staticGateway _INIT_N((( 0, 0, 0, 0))); // gateway (router) IP +WLED_GLOBAL IPAddress staticSubnet _INIT_N(((255, 255, 255, 0))); // most common subnet in home networks +WLED_GLOBAL bool noWifiSleep _INIT(false); // disabling modem sleep modes will increase heat output and power usage, but may help with connection issues + +// LED CONFIG +WLED_GLOBAL uint16_t ledCount _INIT(30); // overcurrent prevented by ABL +WLED_GLOBAL bool useRGBW _INIT(false); // SK6812 strips can contain an extra White channel +WLED_GLOBAL bool turnOnAtBoot _INIT(true); // turn on LEDs at power-up +WLED_GLOBAL byte bootPreset _INIT(0); // save preset to load after power-up + +WLED_GLOBAL byte col[] _INIT_N(({ 255, 160, 0, 0 })); // current RGB(W) primary color. col[] should be updated if you want to change the color. +WLED_GLOBAL byte colSec[] _INIT_N(({ 0, 0, 0, 0 })); // current RGB(W) secondary color +WLED_GLOBAL byte briS _INIT(128); // default brightness + +WLED_GLOBAL byte nightlightTargetBri _INIT(0); // brightness after nightlight is over +WLED_GLOBAL byte nightlightDelayMins _INIT(60); +WLED_GLOBAL bool nightlightFade _INIT(true); // if enabled, light will gradually dim towards the target bri. Otherwise, it will instantly set after delay over +WLED_GLOBAL bool nightlightColorFade _INIT(false); // if enabled, light will gradually fade color from primary to secondary color. +WLED_GLOBAL bool fadeTransition _INIT(true); // enable crossfading color transition +WLED_GLOBAL uint16_t transitionDelay _INIT(750); // default crossfade duration in ms + +WLED_GLOBAL bool skipFirstLed _INIT(false); // ignore first LED in strip (useful if you need the LED as signal repeater) +WLED_GLOBAL byte briMultiplier _INIT(100); // % of brightness to set (to limit power, if you set it to 50 and set bri to 255, actual brightness will be 127) + +// User Interface CONFIG +WLED_GLOBAL char serverDescription[33] _INIT("WLED"); // Name of module +WLED_GLOBAL bool syncToggleReceive _INIT(false); // UIs which only have a single button for sync should toggle send+receive if this is true, only send otherwise + +// Sync CONFIG +WLED_GLOBAL bool buttonEnabled _INIT(true); +WLED_GLOBAL byte irEnabled _INIT(0); // Infrared receiver + +WLED_GLOBAL uint16_t udpPort _INIT(21324); // WLED notifier default port +WLED_GLOBAL uint16_t udpRgbPort _INIT(19446); // Hyperion port + +WLED_GLOBAL bool receiveNotificationBrightness _INIT(true); // apply brightness from incoming notifications +WLED_GLOBAL bool receiveNotificationColor _INIT(true); // apply color +WLED_GLOBAL bool receiveNotificationEffects _INIT(true); // apply effects setup +WLED_GLOBAL bool notifyDirect _INIT(false); // send notification if change via UI or HTTP API +WLED_GLOBAL bool notifyButton _INIT(false); // send if updated by button or infrared remote +WLED_GLOBAL bool notifyAlexa _INIT(false); // send notification if updated via Alexa +WLED_GLOBAL bool notifyMacro _INIT(false); // send notification for macro +WLED_GLOBAL bool notifyHue _INIT(true); // send notification if Hue light changes +WLED_GLOBAL bool notifyTwice _INIT(false); // notifications use UDP: enable if devices don't sync reliably + +WLED_GLOBAL bool alexaEnabled _INIT(true); // enable device discovery by Amazon Echo +WLED_GLOBAL char alexaInvocationName[33] _INIT("Light"); // speech control name of device. Choose something voice-to-text can understand + +WLED_GLOBAL char blynkApiKey[36] _INIT(""); // Auth token for Blynk server. If empty, no connection will be made + +WLED_GLOBAL uint16_t realtimeTimeoutMs _INIT(2500); // ms timeout of realtime mode before returning to normal mode +WLED_GLOBAL int arlsOffset _INIT(0); // realtime LED offset +WLED_GLOBAL bool receiveDirect _INIT(true); // receive UDP realtime +WLED_GLOBAL bool arlsDisableGammaCorrection _INIT(true); // activate if gamma correction is handled by the source +WLED_GLOBAL bool arlsForceMaxBri _INIT(false); // enable to force max brightness if source has very dark colors that would be black + +WLED_GLOBAL uint16_t e131Universe _INIT(1); // settings for E1.31 (sACN) protocol (only DMX_MODE_MULTIPLE_* can span over consequtive universes) +WLED_GLOBAL byte DMXMode _INIT(DMX_MODE_MULTIPLE_RGB); // DMX mode (s.a.) +WLED_GLOBAL uint16_t DMXAddress _INIT(1); // DMX start address of fixture, a.k.a. first Channel [for E1.31 (sACN) protocol] +WLED_GLOBAL byte DMXOldDimmer _INIT(0); // only update brightness on change +WLED_GLOBAL byte e131LastSequenceNumber[E131_MAX_UNIVERSE_COUNT]; // to detect packet loss +WLED_GLOBAL bool e131Multicast _INIT(false); // multicast or unicast +WLED_GLOBAL bool e131SkipOutOfSequence _INIT(false); // freeze instead of flickering + +WLED_GLOBAL bool mqttEnabled _INIT(false); +WLED_GLOBAL char mqttDeviceTopic[33] _INIT(""); // main MQTT topic (individual per device, default is wled/mac) +WLED_GLOBAL char mqttGroupTopic[33] _INIT("wled/all"); // second MQTT topic (for example to group devices) +WLED_GLOBAL char mqttServer[33] _INIT(""); // both domains and IPs should work (no SSL) +WLED_GLOBAL char mqttUser[41] _INIT(""); // optional: username for MQTT auth +WLED_GLOBAL char mqttPass[41] _INIT(""); // optional: password for MQTT auth +WLED_GLOBAL char mqttClientID[41] _INIT(""); // override the client ID +WLED_GLOBAL uint16_t mqttPort _INIT(1883); + +WLED_GLOBAL bool huePollingEnabled _INIT(false); // poll hue bridge for light state +WLED_GLOBAL uint16_t huePollIntervalMs _INIT(2500); // low values (< 1sec) may cause lag but offer quicker response +WLED_GLOBAL char hueApiKey[47] _INIT("api"); // key token will be obtained from bridge +WLED_GLOBAL byte huePollLightId _INIT(1); // ID of hue lamp to sync to. Find the ID in the hue app ("about" section) +WLED_GLOBAL IPAddress hueIP _INIT((0, 0, 0, 0)); // IP address of the bridge +WLED_GLOBAL bool hueApplyOnOff _INIT(true); +WLED_GLOBAL bool hueApplyBri _INIT(true); +WLED_GLOBAL bool hueApplyColor _INIT(true); + +// Time CONFIG +WLED_GLOBAL bool ntpEnabled _INIT(false); // get internet time. Only required if you use clock overlays or time-activated macros +WLED_GLOBAL bool useAMPM _INIT(false); // 12h/24h clock format +WLED_GLOBAL byte currentTimezone _INIT(0); // Timezone ID. Refer to timezones array in wled10_ntp.ino +WLED_GLOBAL int utcOffsetSecs _INIT(0); // Seconds to offset from UTC before timzone calculation + +WLED_GLOBAL byte overlayDefault _INIT(0); // 0: no overlay 1: analog clock 2: single-digit clocl 3: cronixie +WLED_GLOBAL byte overlayMin _INIT(0), overlayMax _INIT(ledCount - 1); // boundaries of overlay mode + +WLED_GLOBAL byte analogClock12pixel _INIT(0); // The pixel in your strip where "midnight" would be +WLED_GLOBAL bool analogClockSecondsTrail _INIT(false); // Display seconds as trail of LEDs instead of a single pixel +WLED_GLOBAL bool analogClock5MinuteMarks _INIT(false); // Light pixels at every 5-minute position + +WLED_GLOBAL char cronixieDisplay[7] _INIT("HHMMSS"); // Cronixie Display mask. See wled13_cronixie.ino +WLED_GLOBAL bool cronixieBacklight _INIT(true); // Allow digits to be back-illuminated + +WLED_GLOBAL bool countdownMode _INIT(false); // Clock will count down towards date +WLED_GLOBAL byte countdownYear _INIT(20), countdownMonth _INIT(1); // Countdown target date, year is last two digits +WLED_GLOBAL byte countdownDay _INIT(1), countdownHour _INIT(0); +WLED_GLOBAL byte countdownMin _INIT(0), countdownSec _INIT(0); + +WLED_GLOBAL byte macroBoot _INIT(0); // macro loaded after startup +WLED_GLOBAL byte macroNl _INIT(0); // after nightlight delay over +WLED_GLOBAL byte macroCountdown _INIT(0); +WLED_GLOBAL byte macroAlexaOn _INIT(0), macroAlexaOff _INIT(0); +WLED_GLOBAL byte macroButton _INIT(0), macroLongPress _INIT(0), macroDoublePress _INIT(0); + +// Security CONFIG +WLED_GLOBAL bool otaLock _INIT(false); // prevents OTA firmware updates without password. ALWAYS enable if system exposed to any public networks +WLED_GLOBAL bool wifiLock _INIT(false); // prevents access to WiFi settings when OTA lock is enabled +WLED_GLOBAL bool aOtaEnabled _INIT(true); // ArduinoOTA allows easy updates directly from the IDE. Careful, it does not auto-disable when OTA lock is on + +WLED_GLOBAL uint16_t userVar0 _INIT(0), userVar1 _INIT(0); + +#ifdef WLED_ENABLE_DMX + // dmx CONFIG + WLED_GLOBAL byte DMXChannels _INIT(7); // number of channels per fixture + WLED_GLOBAL byte DMXFixtureMap[15] _INIT_N(({ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 })); + // assigns the different channels to different functions. See wled21_dmx.ino for more information. + WLED_GLOBAL uint16_t DMXGap _INIT(10); // gap between the fixtures. makes addressing easier because you don't have to memorize odd numbers when climbing up onto a rig. + WLED_GLOBAL uint16_t DMXStart _INIT(10); // start address of the first fixture +#endif + +// internal global variable declarations +// wifi +WLED_GLOBAL bool apActive _INIT(false); +WLED_GLOBAL bool forceReconnect _INIT(false); +WLED_GLOBAL uint32_t lastReconnectAttempt _INIT(0); +WLED_GLOBAL bool interfacesInited _INIT(false); +WLED_GLOBAL bool wasConnected _INIT(false); + +// color +WLED_GLOBAL byte colOld[] _INIT_N(({ 0, 0, 0, 0 })); // color before transition +WLED_GLOBAL byte colT[] _INIT_N(({ 0, 0, 0, 0 })); // color that is currently displayed on the LEDs +WLED_GLOBAL byte colIT[] _INIT_N(({ 0, 0, 0, 0 })); // color that was last sent to LEDs +WLED_GLOBAL byte colSecT[] _INIT_N(({ 0, 0, 0, 0 })); +WLED_GLOBAL byte colSecOld[] _INIT_N(({ 0, 0, 0, 0 })); +WLED_GLOBAL byte colSecIT[] _INIT_N(({ 0, 0, 0, 0 })); + +WLED_GLOBAL byte lastRandomIndex _INIT(0); // used to save last random color so the new one is not the same + +// transitions +WLED_GLOBAL bool transitionActive _INIT(false); +WLED_GLOBAL uint16_t transitionDelayDefault _INIT(transitionDelay); +WLED_GLOBAL uint16_t transitionDelayTemp _INIT(transitionDelay); +WLED_GLOBAL unsigned long transitionStartTime; +WLED_GLOBAL float tperLast _INIT(0); // crossfade transition progress, 0.0f - 1.0f +WLED_GLOBAL bool jsonTransitionOnce _INIT(false); + +// nightlight +WLED_GLOBAL bool nightlightActive _INIT(false); +WLED_GLOBAL bool nightlightActiveOld _INIT(false); +WLED_GLOBAL uint32_t nightlightDelayMs _INIT(10); +WLED_GLOBAL byte nightlightDelayMinsDefault _INIT(nightlightDelayMins); +WLED_GLOBAL unsigned long nightlightStartTime; +WLED_GLOBAL byte briNlT _INIT(0); // current nightlight brightness +WLED_GLOBAL byte colNlT[] _INIT_N(({ 0, 0, 0, 0 })); // current nightlight color + +// brightness +WLED_GLOBAL unsigned long lastOnTime _INIT(0); +WLED_GLOBAL bool offMode _INIT(!turnOnAtBoot); +WLED_GLOBAL byte bri _INIT(briS); +WLED_GLOBAL byte briOld _INIT(0); +WLED_GLOBAL byte briT _INIT(0); +WLED_GLOBAL byte briIT _INIT(0); +WLED_GLOBAL byte briLast _INIT(128); // brightness before turned off. Used for toggle function +WLED_GLOBAL byte whiteLast _INIT(128); // white channel before turned off. Used for toggle function + +// button +WLED_GLOBAL bool buttonPressedBefore _INIT(false); +WLED_GLOBAL bool buttonLongPressed _INIT(false); +WLED_GLOBAL unsigned long buttonPressedTime _INIT(0); +WLED_GLOBAL unsigned long buttonWaitTime _INIT(0); + +// notifications +WLED_GLOBAL bool notifyDirectDefault _INIT(notifyDirect); +WLED_GLOBAL bool receiveNotifications _INIT(true); +WLED_GLOBAL unsigned long notificationSentTime _INIT(0); +WLED_GLOBAL byte notificationSentCallMode _INIT(NOTIFIER_CALL_MODE_INIT); +WLED_GLOBAL bool notificationTwoRequired _INIT(false); + +// effects +WLED_GLOBAL byte effectCurrent _INIT(0); +WLED_GLOBAL byte effectSpeed _INIT(128); +WLED_GLOBAL byte effectIntensity _INIT(128); +WLED_GLOBAL byte effectPalette _INIT(0); + +// network +WLED_GLOBAL bool udpConnected _INIT(false), udpRgbConnected _INIT(false); + +// ui style +WLED_GLOBAL bool showWelcomePage _INIT(false); + +// hue +WLED_GLOBAL byte hueError _INIT(HUE_ERROR_INACTIVE); +// WLED_GLOBAL uint16_t hueFailCount _INIT(0); +WLED_GLOBAL float hueXLast _INIT(0), hueYLast _INIT(0); +WLED_GLOBAL uint16_t hueHueLast _INIT(0), hueCtLast _INIT(0); +WLED_GLOBAL byte hueSatLast _INIT(0), hueBriLast _INIT(0); +WLED_GLOBAL unsigned long hueLastRequestSent _INIT(0); +WLED_GLOBAL bool hueAuthRequired _INIT(false); +WLED_GLOBAL bool hueReceived _INIT(false); +WLED_GLOBAL bool hueStoreAllowed _INIT(false), hueNewKey _INIT(false); + +// overlays +WLED_GLOBAL byte overlayCurrent _INIT(overlayDefault); +WLED_GLOBAL byte overlaySpeed _INIT(200); +WLED_GLOBAL unsigned long overlayRefreshMs _INIT(200); +WLED_GLOBAL unsigned long overlayRefreshedTime; + +// cronixie +WLED_GLOBAL byte dP[] _INIT_N(({ 0, 0, 0, 0, 0, 0 })); +WLED_GLOBAL bool cronixieInit _INIT(false); + +// countdown +WLED_GLOBAL unsigned long countdownTime _INIT(1514764800L); +WLED_GLOBAL bool countdownOverTriggered _INIT(true); + +// timer +WLED_GLOBAL byte lastTimerMinute _INIT(0); +WLED_GLOBAL byte timerHours[] _INIT_N(({ 0, 0, 0, 0, 0, 0, 0, 0 })); +WLED_GLOBAL byte timerMinutes[] _INIT_N(({ 0, 0, 0, 0, 0, 0, 0, 0 })); +WLED_GLOBAL byte timerMacro[] _INIT_N(({ 0, 0, 0, 0, 0, 0, 0, 0 })); +WLED_GLOBAL byte timerWeekday[] _INIT_N(({ 255, 255, 255, 255, 255, 255, 255, 255 })); // weekdays to activate on +// bit pattern of arr elem: 0b11111111: sun,sat,fri,thu,wed,tue,mon,validity + +// blynk +WLED_GLOBAL bool blynkEnabled _INIT(false); + +// preset cycling +WLED_GLOBAL bool presetCyclingEnabled _INIT(false); +WLED_GLOBAL byte presetCycleMin _INIT(1), presetCycleMax _INIT(5); +WLED_GLOBAL uint16_t presetCycleTime _INIT(1250); +WLED_GLOBAL unsigned long presetCycledTime _INIT(0); +WLED_GLOBAL byte presetCycCurr _INIT(presetCycleMin); +WLED_GLOBAL bool presetApplyBri _INIT(true); +WLED_GLOBAL bool saveCurrPresetCycConf _INIT(false); + +// realtime +WLED_GLOBAL byte realtimeMode _INIT(REALTIME_MODE_INACTIVE); +WLED_GLOBAL IPAddress realtimeIP _INIT((0, 0, 0, 0)); +WLED_GLOBAL unsigned long realtimeTimeout _INIT(0); + +// mqtt +WLED_GLOBAL long lastMqttReconnectAttempt _INIT(0); +WLED_GLOBAL long lastInterfaceUpdate _INIT(0); +WLED_GLOBAL byte interfaceUpdateCallMode _INIT(NOTIFIER_CALL_MODE_INIT); +WLED_GLOBAL char mqttStatusTopic[40] _INIT(""); // this must be global because of async handlers + +#if AUXPIN >= 0 + // auxiliary debug pin + WLED_GLOBAL byte auxTime _INIT(0); + WLED_GLOBAL unsigned long auxStartTime _INIT(0); + WLED_GLOBAL bool auxActive _INIT(false, auxActiveBefore _INIT(false); +#endif + +// alexa udp +WLED_GLOBAL String escapedMac; +#ifndef WLED_DISABLE_ALEXA + WLED_GLOBAL Espalexa espalexa; + WLED_GLOBAL EspalexaDevice* espalexaDevice; +#endif + +// dns server +WLED_GLOBAL DNSServer dnsServer; + +// network time +WLED_GLOBAL bool ntpConnected _INIT(false); +WLED_GLOBAL time_t local _INIT(0); +WLED_GLOBAL unsigned long ntpLastSyncTime _INIT(999000000L); +WLED_GLOBAL unsigned long ntpPacketSentTime _INIT(999000000L); +WLED_GLOBAL IPAddress ntpServerIP; +WLED_GLOBAL uint16_t ntpLocalPort _INIT(2390); + +// Temp buffer +WLED_GLOBAL char* obuf; +WLED_GLOBAL uint16_t olen _INIT(0); + +// presets +WLED_GLOBAL uint16_t savedPresets _INIT(0); +WLED_GLOBAL int8_t currentPreset _INIT(-1); +WLED_GLOBAL bool isPreset _INIT(false); + +WLED_GLOBAL byte errorFlag _INIT(0); + +WLED_GLOBAL String messageHead, messageSub; +WLED_GLOBAL byte optionType; + +WLED_GLOBAL bool doReboot _INIT(false); // flag to initiate reboot from async handlers +WLED_GLOBAL bool doPublishMqtt _INIT(false); + +// server library objects +WLED_GLOBAL AsyncWebServer server _INIT_N(((80))); +WLED_GLOBAL AsyncClient* hueClient _INIT(NULL); +WLED_GLOBAL AsyncMqttClient* mqtt _INIT(NULL); + +// udp interface objects +WLED_GLOBAL WiFiUDP notifierUdp, rgbUdp; +WLED_GLOBAL WiFiUDP ntpUdp; +WLED_GLOBAL ESPAsyncE131 e131 _INIT_N(((handleE131Packet))); +WLED_GLOBAL bool e131NewData _INIT(false); + +// led fx library object +WLED_GLOBAL WS2812FX strip _INIT(WS2812FX()); + +// debug macro variable definitions +#ifdef WLED_DEBUG + WLED_GLOBAL unsigned long debugTime _INIT(0); + WLED_GLOBAL int lastWifiState _INIT(3); + WLED_GLOBAL unsigned long wifiStateChangedTime _INIT(0); + WLED_GLOBAL int loops _INIT(0); +#endif + + +#define WLED_CONNECTED (WiFi.status() == WL_CONNECTED) +#define WLED_WIFI_CONFIGURED (strlen(clientSSID) >= 1 && strcmp(clientSSID, DEFAULT_CLIENT_SSID) != 0) + // append new c string to temp buffer efficiently bool oappend(const char* txt); // append new number to temp buffer efficiently From 8da985b6d03a7a77b2bc6932dbe95fcb9fb36bd2 Mon Sep 17 00:00:00 2001 From: cschwinne Date: Tue, 7 Apr 2020 00:04:09 +0200 Subject: [PATCH 85/90] Fixed RBG and BGR getPixelColor (#825) Improved formatting --- CHANGELOG.md | 5 + wled00/FX_fcn.cpp | 4 +- wled00/const.h | 4 + wled00/{func_declare.h => fcn_declare.h} | 4 +- wled00/html_other.h | 2 +- wled00/html_settings.h | 4 +- wled00/wled.cpp | 4 + wled00/wled.h | 114 ++++++++++++----------- wled00/wled00.ino | 11 ++- 9 files changed, 90 insertions(+), 62 deletions(-) rename wled00/{func_declare.h => fcn_declare.h} (99%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8df63ff8e..d92a1966a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ### Development versions after 0.9.1 release +#### Build 2004061 + +- Fixed RBG and BGR getPixelColor (#825) +- Improved formatting + #### Build 2004060 - Consolidated global variables in wled.h diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp index de89a430e..69b8c88a1 100644 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -412,8 +412,8 @@ uint32_t WS2812FX::getPixelColor(uint16_t i) case 0: return ((col.W << 24) | (col.G << 8) | (col.R << 16) | (col.B)); //0 = GRB, default case 1: return ((col.W << 24) | (col.R << 8) | (col.G << 16) | (col.B)); //1 = RGB, common for WS2811 case 2: return ((col.W << 24) | (col.B << 8) | (col.R << 16) | (col.G)); //2 = BRG - case 3: return ((col.W << 24) | (col.R << 8) | (col.B << 16) | (col.G)); //3 = RBG - case 4: return ((col.W << 24) | (col.B << 8) | (col.G << 16) | (col.R)); //4 = BGR + case 3: return ((col.W << 24) | (col.B << 8) | (col.G << 16) | (col.R)); //3 = RBG + case 4: return ((col.W << 24) | (col.R << 8) | (col.B << 16) | (col.G)); //4 = BGR case 5: return ((col.W << 24) | (col.G << 8) | (col.B << 16) | (col.R)); //5 = GBR } return 0; diff --git a/wled00/const.h b/wled00/const.h index ef19e51be..173d7e7d5 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -1,6 +1,10 @@ #ifndef WLED_CONST_H #define WLED_CONST_H +/* + * Readability defines and their associated numerical values + compile-time constants + */ + //Defaults #define DEFAULT_CLIENT_SSID "Your_Network" #define DEFAULT_AP_PASS "wled1234" diff --git a/wled00/func_declare.h b/wled00/fcn_declare.h similarity index 99% rename from wled00/func_declare.h rename to wled00/fcn_declare.h index db3cb4bc4..c0826d6aa 100644 --- a/wled00/func_declare.h +++ b/wled00/fcn_declare.h @@ -1,5 +1,5 @@ -#ifndef WLED_FUNC_DECLARE_H -#define WLED_FUNC_DECLARE_H +#ifndef WLED_FCN_DECLARE_H +#define WLED_FCN_DECLARE_H #include #include "src/dependencies/espalexa/EspalexaDevice.h" #include "src/dependencies/e131/ESPAsyncE131.h" diff --git a/wled00/html_other.h b/wled00/html_other.h index e27c64dde..ce36cff48 100644 --- a/wled00/html_other.h +++ b/wled00/html_other.h @@ -1,5 +1,5 @@ /* - * Various pages + * Various web pages */ //USER HTML HERE (/u subpage) diff --git a/wled00/html_settings.h b/wled00/html_settings.h index 284d9b30f..e2d94f973 100644 --- a/wled00/html_settings.h +++ b/wled00/html_settings.h @@ -1,6 +1,6 @@ /* - Settings html -*/ + * Settings html + */ //common CSS of settings pages const char PAGE_settingsCss[] PROGMEM = R"=====()====="; diff --git a/wled00/wled.cpp b/wled00/wled.cpp index 701b98e31..b68723bb0 100644 --- a/wled00/wled.cpp +++ b/wled00/wled.cpp @@ -2,6 +2,10 @@ #include "wled.h" #include +/* + * Main WLED class implementation. Mostly initialization and connection logic + */ + WLED::WLED() { } diff --git a/wled00/wled.h b/wled00/wled.h index 0c6d31147..6f4b2cb00 100644 --- a/wled00/wled.h +++ b/wled00/wled.h @@ -8,7 +8,7 @@ */ // version code in format yymmddb (b = daily build) -#define VERSION 2004060 +#define VERSION 2004061 // ESP8266-01 (blue) got too little storage space to work with all features of WLED. To use it, you must use ESP8266 Arduino Core v2.4.2 and the setting 512K(No SPIFFS). @@ -81,7 +81,7 @@ #include "src/dependencies/json/AsyncJson-v6.h" #include "src/dependencies/json/ArduinoJson-v6.h" -#include "func_declare.h" +#include "fcn_declare.h" #include "html_ui.h" #include "html_settings.h" #include "html_other.h" @@ -128,6 +128,12 @@ #define DEBUG_PRINTF(x) #endif +// GLOBAL VARIABLES +// both declared and defined in header (solution from http://www.keil.com/support/docs/1868.htm) +// +//e.g. byte test = 2 becomes WLED_GLOBAL byte test _INIT(2); +// int arr[]{0,1,2} becomes WLED_GLOBAL int arr[] _INIT_N(({0,1,2})); + #ifndef WLED_DEFINE_GLOBAL_VARS # define WLED_GLOBAL extern # define _INIT(x) @@ -170,13 +176,13 @@ WLED_GLOBAL bool noWifiSleep _INIT(false); // disabling // LED CONFIG WLED_GLOBAL uint16_t ledCount _INIT(30); // overcurrent prevented by ABL -WLED_GLOBAL bool useRGBW _INIT(false); // SK6812 strips can contain an extra White channel +WLED_GLOBAL bool useRGBW _INIT(false); // SK6812 strips can contain an extra White channel WLED_GLOBAL bool turnOnAtBoot _INIT(true); // turn on LEDs at power-up -WLED_GLOBAL byte bootPreset _INIT(0); // save preset to load after power-up +WLED_GLOBAL byte bootPreset _INIT(0); // save preset to load after power-up -WLED_GLOBAL byte col[] _INIT_N(({ 255, 160, 0, 0 })); // current RGB(W) primary color. col[] should be updated if you want to change the color. -WLED_GLOBAL byte colSec[] _INIT_N(({ 0, 0, 0, 0 })); // current RGB(W) secondary color -WLED_GLOBAL byte briS _INIT(128); // default brightness +WLED_GLOBAL byte col[] _INIT_N(({ 255, 160, 0, 0 })); // current RGB(W) primary color. col[] should be updated if you want to change the color. +WLED_GLOBAL byte colSec[] _INIT_N(({ 0, 0, 0, 0 })); // current RGB(W) secondary color +WLED_GLOBAL byte briS _INIT(128); // default brightness WLED_GLOBAL byte nightlightTargetBri _INIT(0); // brightness after nightlight is over WLED_GLOBAL byte nightlightDelayMins _INIT(60); @@ -189,52 +195,52 @@ WLED_GLOBAL bool skipFirstLed _INIT(false); // ignore first LED in strip WLED_GLOBAL byte briMultiplier _INIT(100); // % of brightness to set (to limit power, if you set it to 50 and set bri to 255, actual brightness will be 127) // User Interface CONFIG -WLED_GLOBAL char serverDescription[33] _INIT("WLED"); // Name of module -WLED_GLOBAL bool syncToggleReceive _INIT(false); // UIs which only have a single button for sync should toggle send+receive if this is true, only send otherwise +WLED_GLOBAL char serverDescription[33] _INIT("WLED"); // Name of module +WLED_GLOBAL bool syncToggleReceive _INIT(false); // UIs which only have a single button for sync should toggle send+receive if this is true, only send otherwise // Sync CONFIG -WLED_GLOBAL bool buttonEnabled _INIT(true); -WLED_GLOBAL byte irEnabled _INIT(0); // Infrared receiver +WLED_GLOBAL bool buttonEnabled _INIT(true); +WLED_GLOBAL byte irEnabled _INIT(0); // Infrared receiver -WLED_GLOBAL uint16_t udpPort _INIT(21324); // WLED notifier default port -WLED_GLOBAL uint16_t udpRgbPort _INIT(19446); // Hyperion port +WLED_GLOBAL uint16_t udpPort _INIT(21324); // WLED notifier default port +WLED_GLOBAL uint16_t udpRgbPort _INIT(19446); // Hyperion port -WLED_GLOBAL bool receiveNotificationBrightness _INIT(true); // apply brightness from incoming notifications -WLED_GLOBAL bool receiveNotificationColor _INIT(true); // apply color -WLED_GLOBAL bool receiveNotificationEffects _INIT(true); // apply effects setup -WLED_GLOBAL bool notifyDirect _INIT(false); // send notification if change via UI or HTTP API -WLED_GLOBAL bool notifyButton _INIT(false); // send if updated by button or infrared remote -WLED_GLOBAL bool notifyAlexa _INIT(false); // send notification if updated via Alexa -WLED_GLOBAL bool notifyMacro _INIT(false); // send notification for macro -WLED_GLOBAL bool notifyHue _INIT(true); // send notification if Hue light changes -WLED_GLOBAL bool notifyTwice _INIT(false); // notifications use UDP: enable if devices don't sync reliably +WLED_GLOBAL bool receiveNotificationBrightness _INIT(true); // apply brightness from incoming notifications +WLED_GLOBAL bool receiveNotificationColor _INIT(true); // apply color +WLED_GLOBAL bool receiveNotificationEffects _INIT(true); // apply effects setup +WLED_GLOBAL bool notifyDirect _INIT(false); // send notification if change via UI or HTTP API +WLED_GLOBAL bool notifyButton _INIT(false); // send if updated by button or infrared remote +WLED_GLOBAL bool notifyAlexa _INIT(false); // send notification if updated via Alexa +WLED_GLOBAL bool notifyMacro _INIT(false); // send notification for macro +WLED_GLOBAL bool notifyHue _INIT(true); // send notification if Hue light changes +WLED_GLOBAL bool notifyTwice _INIT(false); // notifications use UDP: enable if devices don't sync reliably -WLED_GLOBAL bool alexaEnabled _INIT(true); // enable device discovery by Amazon Echo -WLED_GLOBAL char alexaInvocationName[33] _INIT("Light"); // speech control name of device. Choose something voice-to-text can understand +WLED_GLOBAL bool alexaEnabled _INIT(true); // enable device discovery by Amazon Echo +WLED_GLOBAL char alexaInvocationName[33] _INIT("Light"); // speech control name of device. Choose something voice-to-text can understand -WLED_GLOBAL char blynkApiKey[36] _INIT(""); // Auth token for Blynk server. If empty, no connection will be made +WLED_GLOBAL char blynkApiKey[36] _INIT(""); // Auth token for Blynk server. If empty, no connection will be made -WLED_GLOBAL uint16_t realtimeTimeoutMs _INIT(2500); // ms timeout of realtime mode before returning to normal mode -WLED_GLOBAL int arlsOffset _INIT(0); // realtime LED offset -WLED_GLOBAL bool receiveDirect _INIT(true); // receive UDP realtime -WLED_GLOBAL bool arlsDisableGammaCorrection _INIT(true); // activate if gamma correction is handled by the source -WLED_GLOBAL bool arlsForceMaxBri _INIT(false); // enable to force max brightness if source has very dark colors that would be black +WLED_GLOBAL uint16_t realtimeTimeoutMs _INIT(2500); // ms timeout of realtime mode before returning to normal mode +WLED_GLOBAL int arlsOffset _INIT(0); // realtime LED offset +WLED_GLOBAL bool receiveDirect _INIT(true); // receive UDP realtime +WLED_GLOBAL bool arlsDisableGammaCorrection _INIT(true); // activate if gamma correction is handled by the source +WLED_GLOBAL bool arlsForceMaxBri _INIT(false); // enable to force max brightness if source has very dark colors that would be black -WLED_GLOBAL uint16_t e131Universe _INIT(1); // settings for E1.31 (sACN) protocol (only DMX_MODE_MULTIPLE_* can span over consequtive universes) -WLED_GLOBAL byte DMXMode _INIT(DMX_MODE_MULTIPLE_RGB); // DMX mode (s.a.) -WLED_GLOBAL uint16_t DMXAddress _INIT(1); // DMX start address of fixture, a.k.a. first Channel [for E1.31 (sACN) protocol] -WLED_GLOBAL byte DMXOldDimmer _INIT(0); // only update brightness on change -WLED_GLOBAL byte e131LastSequenceNumber[E131_MAX_UNIVERSE_COUNT]; // to detect packet loss -WLED_GLOBAL bool e131Multicast _INIT(false); // multicast or unicast -WLED_GLOBAL bool e131SkipOutOfSequence _INIT(false); // freeze instead of flickering +WLED_GLOBAL uint16_t e131Universe _INIT(1); // settings for E1.31 (sACN) protocol (only DMX_MODE_MULTIPLE_* can span over consequtive universes) +WLED_GLOBAL byte DMXMode _INIT(DMX_MODE_MULTIPLE_RGB); // DMX mode (s.a.) +WLED_GLOBAL uint16_t DMXAddress _INIT(1); // DMX start address of fixture, a.k.a. first Channel [for E1.31 (sACN) protocol] +WLED_GLOBAL byte DMXOldDimmer _INIT(0); // only update brightness on change +WLED_GLOBAL byte e131LastSequenceNumber[E131_MAX_UNIVERSE_COUNT]; // to detect packet loss +WLED_GLOBAL bool e131Multicast _INIT(false); // multicast or unicast +WLED_GLOBAL bool e131SkipOutOfSequence _INIT(false); // freeze instead of flickering WLED_GLOBAL bool mqttEnabled _INIT(false); -WLED_GLOBAL char mqttDeviceTopic[33] _INIT(""); // main MQTT topic (individual per device, default is wled/mac) -WLED_GLOBAL char mqttGroupTopic[33] _INIT("wled/all"); // second MQTT topic (for example to group devices) -WLED_GLOBAL char mqttServer[33] _INIT(""); // both domains and IPs should work (no SSL) -WLED_GLOBAL char mqttUser[41] _INIT(""); // optional: username for MQTT auth -WLED_GLOBAL char mqttPass[41] _INIT(""); // optional: password for MQTT auth -WLED_GLOBAL char mqttClientID[41] _INIT(""); // override the client ID +WLED_GLOBAL char mqttDeviceTopic[33] _INIT(""); // main MQTT topic (individual per device, default is wled/mac) +WLED_GLOBAL char mqttGroupTopic[33] _INIT("wled/all"); // second MQTT topic (for example to group devices) +WLED_GLOBAL char mqttServer[33] _INIT(""); // both domains and IPs should work (no SSL) +WLED_GLOBAL char mqttUser[41] _INIT(""); // optional: username for MQTT auth +WLED_GLOBAL char mqttPass[41] _INIT(""); // optional: password for MQTT auth +WLED_GLOBAL char mqttClientID[41] _INIT(""); // override the client ID WLED_GLOBAL uint16_t mqttPort _INIT(1883); WLED_GLOBAL bool huePollingEnabled _INIT(false); // poll hue bridge for light state @@ -255,30 +261,30 @@ WLED_GLOBAL int utcOffsetSecs _INIT(0); // Seconds to offset from UTC WLED_GLOBAL byte overlayDefault _INIT(0); // 0: no overlay 1: analog clock 2: single-digit clocl 3: cronixie WLED_GLOBAL byte overlayMin _INIT(0), overlayMax _INIT(ledCount - 1); // boundaries of overlay mode -WLED_GLOBAL byte analogClock12pixel _INIT(0); // The pixel in your strip where "midnight" would be -WLED_GLOBAL bool analogClockSecondsTrail _INIT(false); // Display seconds as trail of LEDs instead of a single pixel -WLED_GLOBAL bool analogClock5MinuteMarks _INIT(false); // Light pixels at every 5-minute position +WLED_GLOBAL byte analogClock12pixel _INIT(0); // The pixel in your strip where "midnight" would be +WLED_GLOBAL bool analogClockSecondsTrail _INIT(false); // Display seconds as trail of LEDs instead of a single pixel +WLED_GLOBAL bool analogClock5MinuteMarks _INIT(false); // Light pixels at every 5-minute position WLED_GLOBAL char cronixieDisplay[7] _INIT("HHMMSS"); // Cronixie Display mask. See wled13_cronixie.ino WLED_GLOBAL bool cronixieBacklight _INIT(true); // Allow digits to be back-illuminated WLED_GLOBAL bool countdownMode _INIT(false); // Clock will count down towards date -WLED_GLOBAL byte countdownYear _INIT(20), countdownMonth _INIT(1); // Countdown target date, year is last two digits -WLED_GLOBAL byte countdownDay _INIT(1), countdownHour _INIT(0); -WLED_GLOBAL byte countdownMin _INIT(0), countdownSec _INIT(0); +WLED_GLOBAL byte countdownYear _INIT(20), countdownMonth _INIT(1); // Countdown target date, year is last two digits +WLED_GLOBAL byte countdownDay _INIT(1) , countdownHour _INIT(0); +WLED_GLOBAL byte countdownMin _INIT(0) , countdownSec _INIT(0); WLED_GLOBAL byte macroBoot _INIT(0); // macro loaded after startup -WLED_GLOBAL byte macroNl _INIT(0); // after nightlight delay over +WLED_GLOBAL byte macroNl _INIT(0); // after nightlight delay over WLED_GLOBAL byte macroCountdown _INIT(0); WLED_GLOBAL byte macroAlexaOn _INIT(0), macroAlexaOff _INIT(0); WLED_GLOBAL byte macroButton _INIT(0), macroLongPress _INIT(0), macroDoublePress _INIT(0); // Security CONFIG -WLED_GLOBAL bool otaLock _INIT(false); // prevents OTA firmware updates without password. ALWAYS enable if system exposed to any public networks -WLED_GLOBAL bool wifiLock _INIT(false); // prevents access to WiFi settings when OTA lock is enabled -WLED_GLOBAL bool aOtaEnabled _INIT(true); // ArduinoOTA allows easy updates directly from the IDE. Careful, it does not auto-disable when OTA lock is on +WLED_GLOBAL bool otaLock _INIT(false); // prevents OTA firmware updates without password. ALWAYS enable if system exposed to any public networks +WLED_GLOBAL bool wifiLock _INIT(false); // prevents access to WiFi settings when OTA lock is enabled +WLED_GLOBAL bool aOtaEnabled _INIT(true); // ArduinoOTA allows easy updates directly from the IDE. Careful, it does not auto-disable when OTA lock is on -WLED_GLOBAL uint16_t userVar0 _INIT(0), userVar1 _INIT(0); +WLED_GLOBAL uint16_t userVar0 _INIT(0), userVar1 _INIT(0); //available for use in usermod #ifdef WLED_ENABLE_DMX // dmx CONFIG diff --git a/wled00/wled00.ino b/wled00/wled00.ino index f806e5b34..866543ab9 100644 --- a/wled00/wled00.ino +++ b/wled00/wled00.ino @@ -1,5 +1,14 @@ /* - * Arduino IDE compatibility file. + * WLED Arduino IDE compatibility file. + * + * Where has everything gone? + * + * In April 2020, the project's structure underwent a major change. + * Global variables are now found in file "wled.h" + * Global function declarations are found in "fcn_declare.h" + * + * Usermod compatibility: Existing wled06_usermod.ino mods should continue to work. Delete usermod.cpp. + * New usermods should use usermod.cpp instead. */ #include "wled.h" From a31da3186f4327c37a0baf23d38774cb40cb89c4 Mon Sep 17 00:00:00 2001 From: Bukovina Date: Wed, 8 Apr 2020 08:35:32 -0400 Subject: [PATCH 86/90] UserMod files update --- usermods/Enclosure_with_OLED_temp_ESP07/readme.md | 2 ++ .../{wled06_usermod.ino => usermod.cpp} | 2 ++ usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md | 1 + .../{wled06_usermod.ino => usermod.cpp} | 2 ++ 4 files changed, 7 insertions(+) rename usermods/Enclosure_with_OLED_temp_ESP07/{wled06_usermod.ino => usermod.cpp} (99%) rename usermods/Wemos_D1_mini+Wemos32_mini_shield/{wled06_usermod.ino => usermod.cpp} (99%) diff --git a/usermods/Enclosure_with_OLED_temp_ESP07/readme.md b/usermods/Enclosure_with_OLED_temp_ESP07/readme.md index 817fb7e4a..f39cece6d 100644 --- a/usermods/Enclosure_with_OLED_temp_ESP07/readme.md +++ b/usermods/Enclosure_with_OLED_temp_ESP07/readme.md @@ -1,5 +1,7 @@ # Almost universal controller board for outdoor applications This usermod is using ideas from @mrVanboy and @400killer + +Installation of file: Copy and replace file in wled00 directory ## Project repository - [Original repository](https://github.com/srg74/Controller-for-WLED-firmware) - Main controller repository ## Features diff --git a/usermods/Enclosure_with_OLED_temp_ESP07/wled06_usermod.ino b/usermods/Enclosure_with_OLED_temp_ESP07/usermod.cpp similarity index 99% rename from usermods/Enclosure_with_OLED_temp_ESP07/wled06_usermod.ino rename to usermods/Enclosure_with_OLED_temp_ESP07/usermod.cpp index 99d155ca6..9724a1b7b 100644 --- a/usermods/Enclosure_with_OLED_temp_ESP07/wled06_usermod.ino +++ b/usermods/Enclosure_with_OLED_temp_ESP07/usermod.cpp @@ -1,3 +1,5 @@ +#include "wled.h" +#include #include // from https://github.com/olikraus/u8g2/ #include //Dallastemperature sensor //The SCL and SDA pins are defined here. diff --git a/usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md b/usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md index 765e56831..39bc8148b 100644 --- a/usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md +++ b/usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md @@ -1,4 +1,5 @@ # Wemos D1 mini and Wemos32 mini shield +- Installation of file: Copy and replace file in wled00 directory - Added third choice of controller Heltec WiFi-Kit-8. Totally DIY but with OLED display. ## Project repository - [Original repository](https://github.com/srg74/WLED-wemos-shield) - WLED Wemos shield repository diff --git a/usermods/Wemos_D1_mini+Wemos32_mini_shield/wled06_usermod.ino b/usermods/Wemos_D1_mini+Wemos32_mini_shield/usermod.cpp similarity index 99% rename from usermods/Wemos_D1_mini+Wemos32_mini_shield/wled06_usermod.ino rename to usermods/Wemos_D1_mini+Wemos32_mini_shield/usermod.cpp index d75cf0a74..579fbfe49 100644 --- a/usermods/Wemos_D1_mini+Wemos32_mini_shield/wled06_usermod.ino +++ b/usermods/Wemos_D1_mini+Wemos32_mini_shield/usermod.cpp @@ -1,3 +1,5 @@ +#include "wled.h" +#include #include // from https://github.com/olikraus/u8g2/ #include //Dallastemperature sensor #ifdef ARDUINO_ARCH_ESP32 //ESP32 boards From 88b67b9541d9b67f886f005cb406080f3a695893 Mon Sep 17 00:00:00 2001 From: Bukovina Date: Wed, 8 Apr 2020 08:40:44 -0400 Subject: [PATCH 87/90] Usermod changes --- .../Enclosure_with_OLED_temp_ESP07/readme.md | 5 +++++ .../readme.md | 22 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/usermods/Enclosure_with_OLED_temp_ESP07/readme.md b/usermods/Enclosure_with_OLED_temp_ESP07/readme.md index f39cece6d..b90beefec 100644 --- a/usermods/Enclosure_with_OLED_temp_ESP07/readme.md +++ b/usermods/Enclosure_with_OLED_temp_ESP07/readme.md @@ -27,6 +27,11 @@ Uncomment `U8g2@~2.27.3`,`DallasTemperature@~3.8.0`,`OneWire@~2.3.5 under` `[com ```ini # platformio.ini ... +[platformio] +... +; default_envs = esp01_1m_full +default_envs = esp07 +... [common] ... lib_deps_external = diff --git a/usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md b/usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md index 39bc8148b..53405a5e7 100644 --- a/usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md +++ b/usermods/Wemos_D1_mini+Wemos32_mini_shield/readme.md @@ -23,3 +23,25 @@ - SSD1306 128x32 I2C OLED display - DS18B20 (temperature sensor) - Push button (N.O. momentary switch) + +### Platformio requirements +Uncomment `U8g2@~2.27.3`,`DallasTemperature@~3.8.0`,`OneWire@~2.3.5 under` `[common]` section in `platformio.ini`: +```ini +# platformio.ini +... +[platformio] +... +; default_envs = esp07 +default_envs = d1_mini +... +[common] +... +lib_deps_external = + ... + #For use SSD1306 OLED display uncomment following + U8g2@~2.27.3 + #For Dallas sensor uncomment following 2 lines + DallasTemperature@~3.8.0 + OneWire@~2.3.5 +... +``` From 5be88dd1880df13c3e4a990724963fc18266b325 Mon Sep 17 00:00:00 2001 From: Bukovina Date: Wed, 8 Apr 2020 09:18:26 -0400 Subject: [PATCH 88/90] QuinLED mod update --- platformio.ini | 2 +- .../{readme.txt => readme.md} | 33 +++++++++++++++++-- .../{wled06_usermod.ino => usermod.cpp} | 2 ++ 3 files changed, 33 insertions(+), 4 deletions(-) rename usermods/QuinLED_Dig_Uno_Temp_MQTT/{readme.txt => readme.md} (55%) rename usermods/QuinLED_Dig_Uno_Temp_MQTT/{wled06_usermod.ino => usermod.cpp} (97%) diff --git a/platformio.ini b/platformio.ini index 91bdd8675..5530f9a28 100644 --- a/platformio.ini +++ b/platformio.ini @@ -30,7 +30,7 @@ default_envs = d1_mini, esp01, esp01_1m_ota, esp32dev ; default_envs = d1_mini ; default_envs = heltec_wifi_kit_8 ; default_envs = d1_mini_debug -; default_envs = esp32dev +default_envs = esp32dev ; default_envs = esp8285_4CH_MagicHome ; default_envs = esp8285_4CH_H801 ; default_envs = esp8285_5CH_H801 diff --git a/usermods/QuinLED_Dig_Uno_Temp_MQTT/readme.txt b/usermods/QuinLED_Dig_Uno_Temp_MQTT/readme.md similarity index 55% rename from usermods/QuinLED_Dig_Uno_Temp_MQTT/readme.txt rename to usermods/QuinLED_Dig_Uno_Temp_MQTT/readme.md index eb8da7ee1..60fc31f73 100644 --- a/usermods/QuinLED_Dig_Uno_Temp_MQTT/readme.txt +++ b/usermods/QuinLED_Dig_Uno_Temp_MQTT/readme.md @@ -1,7 +1,34 @@ -These files allow WLED 0.9.1 to report the temp sensor on the Quinled board to MQTT. I use it to report the board temp to Home Assistant via MQTT, so it will send notifications if something happens and the board start to heat up. +# QuinLED Dig Uno board +These files allow WLED 0.9.1 to report the temp sensor on the Quinled board to MQTT. I use it to report the board temp to Home Assistant via MQTT, so it will send notifications if something happens and the board start to heat up. This code uses Aircookie's WLED software. It has a premade file for user modifications. I use it to publish the temperature from the dallas temperature sensor on the Quinled board. The entries for the top of the WLED00 file, initializes the required libraries, and variables for the sensor. The .ino file waits for 60 seconds, and checks to see if the MQTT server is connected (thanks Aircoookie). It then poles the sensor, and published it using the MQTT service already running, using the main topic programmed in the WLED UI. -To install: +Installation of file: Copy and replace file in wled00 directory -Replace the WLED06_usermod.ino file in Aircoookies WLED folder. +## Project link + +* [QuinLED-Dig-Uno](https://quinled.info/2018/09/15/quinled-dig-uno/) - Project link + +### Platformio requirements + +Uncomment `DallasTemperature@~3.8.0`,`OneWire@~2.3.5 under` `[common]` section in `platformio.ini`: + +```ini +# platformio.ini +... +[platformio] +... +; default_envs = esp07 +default_envs = d1_mini +... +[common] +... +lib_deps_external = + ... + #For use SSD1306 OLED display uncomment following + U8g2@~2.27.3 + #For Dallas sensor uncomment following 2 lines + DallasTemperature@~3.8.0 + OneWire@~2.3.5 +... +``` diff --git a/usermods/QuinLED_Dig_Uno_Temp_MQTT/wled06_usermod.ino b/usermods/QuinLED_Dig_Uno_Temp_MQTT/usermod.cpp similarity index 97% rename from usermods/QuinLED_Dig_Uno_Temp_MQTT/wled06_usermod.ino rename to usermods/QuinLED_Dig_Uno_Temp_MQTT/usermod.cpp index d4b77ccc1..5b4e2e5c7 100644 --- a/usermods/QuinLED_Dig_Uno_Temp_MQTT/wled06_usermod.ino +++ b/usermods/QuinLED_Dig_Uno_Temp_MQTT/usermod.cpp @@ -1,3 +1,5 @@ +#include +#include "wled.h" //Intiating code for QuinLED Dig-Uno temp sensor //Uncomment Celsius if that is your prefered temperature scale #include //Dallastemperature sensor From b5b1dbfe85bcc9b95fa379b56ec4dfc3ccb6d1a9 Mon Sep 17 00:00:00 2001 From: Bukovina Date: Wed, 8 Apr 2020 09:30:49 -0400 Subject: [PATCH 89/90] Fix platformio.ini --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 5530f9a28..91bdd8675 100644 --- a/platformio.ini +++ b/platformio.ini @@ -30,7 +30,7 @@ default_envs = d1_mini, esp01, esp01_1m_ota, esp32dev ; default_envs = d1_mini ; default_envs = heltec_wifi_kit_8 ; default_envs = d1_mini_debug -default_envs = esp32dev +; default_envs = esp32dev ; default_envs = esp8285_4CH_MagicHome ; default_envs = esp8285_4CH_H801 ; default_envs = esp8285_5CH_H801 From 3a84292f82acaa66bb0e1b1565d192fe581cdffc Mon Sep 17 00:00:00 2001 From: srg74 <28492985+srg74@users.noreply.github.com> Date: Thu, 9 Apr 2020 09:06:44 -0400 Subject: [PATCH 90/90] Update readme.md --- usermods/readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/usermods/readme.md b/usermods/readme.md index 350212fb8..34ddef0ff 100644 --- a/usermods/readme.md +++ b/usermods/readme.md @@ -1,13 +1,13 @@ ### Usermods -This folder serves as a repository for usermods (custom `wled06_usermod.ino` files)! +This folder serves as a repository for usermods (custom `usermod.cpp` files)! If you have created an usermod that you believe is useful (for example to support a particular sensor, display, feature...), feel free to contribute by opening a pull request! In order for other people to be able to have fun with your usermod, please keep these points in mind: - Create a folder in this folder with a descriptive name (for example `usermod_ds18b20_temp_sensor_mqtt`) -- Include your custom `wled06_usermod.ino` file +- Include your custom `usermod.cpp` file - If your usermod requires changes to other WLED files, please write a `readme.md` outlining the steps one has to take to use the usermod - Create a pull request! - If your feature is useful for the majority of WLED users, I will consider adding it to the base code!