From dc076f96cbac3912206c5ccef1c6cb8e5de406d0 Mon Sep 17 00:00:00 2001 From: Debashish Sahu Date: Wed, 20 Mar 2019 13:06:03 -0400 Subject: [PATCH 01/14] Update platformio.ini --- platformio.ini | 40 ++++++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/platformio.ini b/platformio.ini index e5253ac..c0d544b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -15,30 +15,48 @@ env_default = nodemcuv2 description = The ESP8266 based multi-client lighting gadget [common] +# ------------------------------------------------------------------------------ +# PLATFORM: +# !! DO NOT confuse platformio's ESP8266 development platform with Arduino core for ESP8266 +# We use Arduino Core 2.5.0 (platformIO 2.0.4) as default +# +# arduino core 2.3.0 = platformIO 1.5.0 +# arduino core 2.4.0 = platformIO 1.6.0 +# arduino core 2.4.1 = platformIO 1.7.3 +# arduino core 2.4.2 = platformIO 1.8.0 +# arduino core 2.5.0 = platformIO 2.0.4 +# arduino core stage = platformIO feature#stage +# ------------------------------------------------------------------------------ +arduino_core_2_3_0 = espressif8266@1.5.0 +arduino_core_2_4_0 = espressif8266@1.6.0 +arduino_core_2_4_1 = espressif8266@1.7.3 +arduino_core_2_4_2 = espressif8266@1.8.0 +arduino_core_2_5_0 = espressif8266@2.0.4 +arduino_core_stage = https://github.com/platformio/platform-espressif8266.git#feature/stage + framework = arduino -platform = espressif8266@1.8.0 -; platform = https://github.com/platformio/platform-espressif8266.git -; platform = https://github.com/platformio/platform-espressif8266.git#feature/stage -; platform = https://github.com/platformio/platform-espressif8266.git#develop +platform = ${common.arduino_core_2_4_2} +; platform = ${common.arduino_core_2_5_0} build_flags = -DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH - -DMQTT_MAX_PACKET_SIZE=2048 ; PubSubClient Specific flags + -DMQTT_MAX_PACKET_SIZE=512 ; PubSubClient Specific flags -w ; supress all warnings monitor_speed = 115200 -board_build.flash_mode = dout +board_build.flash_mode = dio upload_speed = 115200 upload_resetmethod = nodemcu lib_deps = WiFiManager@0.14 AsyncMqttClient - ArduinoJson + ArduinoJson@6.9.1 WS2812FX - NeoPixelBus@2.4.1 + NeoPixelBus WebSockets ESPAsyncE131 ESPAsyncUDP https://github.com/FabLab-Luenen/GY33_MCU - Brzo_I2C + Brzo I2C + IRremoteESP8266@2.5.3 [env:esp01_1m] board = esp01_1m @@ -55,7 +73,9 @@ lib_deps = ${common.lib_deps} board = nodemcuv2 framework = ${common.framework} platform = ${common.platform} -build_flags = ${common.build_flags} +build_flags = + ${common.build_flags} + ; -Wl,-Teagle.flash.4m3m.ld ;;;; Required for core > v2.5.0 or staging version monitor_speed = ${common.monitor_speed} upload_speed = ${common.upload_speed} upload_resetmethod = ${common.upload_resetmethod} From 82e1063801ac536945dc565a04f4af2d5316bdf8 Mon Sep 17 00:00:00 2001 From: Debashish Sahu Date: Wed, 20 Mar 2019 13:18:09 -0400 Subject: [PATCH 02/14] Update platformio.ini - Add FastLED --- platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/platformio.ini b/platformio.ini index c0d544b..a7fbbfd 100644 --- a/platformio.ini +++ b/platformio.ini @@ -57,6 +57,7 @@ lib_deps = https://github.com/FabLab-Luenen/GY33_MCU Brzo I2C IRremoteESP8266@2.5.3 + FastLED [env:esp01_1m] board = esp01_1m From fcc1f49cf00b90887baae7d97365704c02d29996 Mon Sep 17 00:00:00 2001 From: bpohvoodoo Date: Wed, 27 Mar 2019 16:51:35 +0100 Subject: [PATCH 03/14] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b56180f..b62e6a9 100644 --- a/README.md +++ b/README.md @@ -68,10 +68,10 @@ I hope I didn't miss any sources and mentioned every author. In case I forgot so - [ ] Additional clients - [ ] If no wifi, at least enable button mode. - [ ] Also enable McLighting in Wifi AP mode. -- [ ] IR remote support [issue](https://github.com/toblum/McLightingUI/issues/3) - [ ] Multiple buttons/GPIO Inputs. [Issue](https://github.com/toblum/McLighting/issues/119) - [ ] Music visualizer / Bring back ArtNet [Issue](https://github.com/toblum/McLighting/issues/111) - [ ] Display version and parameters (Number of LEDs, definition settings, ..) in the web UI [Issue](https://github.com/toblum/McLighting/issues/150) +- [x] IR remote support [issue](https://github.com/toblum/McLightingUI/issues/3) - [x] MQTT support - [x] Save favourite effects? [Issue](https://github.com/toblum/McLighting/issues/35)(https://github.com/toblum/McLighting/issues/101) - [x] OTA update [Issue](https://github.com/toblum/McLighting/issues/92) From 6867d1021b0784f03dd5dcc85036e8c1c1de1bd4 Mon Sep 17 00:00:00 2001 From: bpohvoodoo Date: Wed, 11 Sep 2019 09:10:03 +0200 Subject: [PATCH 04/14] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0c5611a..66ae6a0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # McLighting v2 - The ESP8266 based multi-client lighting gadget -[![Gitter](https://badges.gitter.im/mclighting/Lobby.svg)](https://gitter.im/mclighting/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Build Status](https://travis-ci.com/toblum/McLighting.svg?branch=master)](https://travis-ci.com/toblum/McLighting) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![version](https://img.shields.io/badge/version-v2.2.4-blue.svg)](https://github.com/toblum/McLighting/blob/master/Arduino/McLighting/version.h) +[![Gitter](https://badges.gitter.im/mclighting/Lobby.svg)](https://gitter.im/mclighting/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Build Status](https://travis-ci.com/toblum/McLighting.svg?branch=master)](https://travis-ci.com/toblum/McLighting) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![version](https://img.shields.io/badge/version-v2.2.6-blue.svg)](https://github.com/toblum/McLighting/blob/master/Arduino/McLighting/version.h) McLighting (the multi-client lighting gadget) is a very cheap internet-controllable lighting solution based on the famous ESP8266 microcontroller and WS2811/2812 led strips. It features among other things a web-interface, a REST-API and a websocket connector. @@ -61,8 +61,6 @@ I hope I didn't miss any sources and mentioned every author. In case I forgot so ## Todos - [ ] Support multiple strips and control them separately or together [Issue](https://github.com/toblum/McLighting/issues/118) -- [ ] Make number of pixels, MQTT and PIN configurable via front end [Issue](https://github.com/toblum/McLighting/issues/93) and [Issue](https://github.com/toblum/McLighting/issues/93) -- [ ] Bundle webpages instead of SPIFFS [Issue](https://github.com/toblum/McLighting/issues/93) - [ ] Remove old / wrong EEPROM settings completely [Issue] - [ ] Customer profile to define segments of (in)active areas on the strip [Issue](https://github.com/toblum/McLighting/issues/37) - [ ] Additional clients @@ -71,6 +69,8 @@ I hope I didn't miss any sources and mentioned every author. In case I forgot so - [ ] Multiple buttons/GPIO Inputs. [Issue](https://github.com/toblum/McLighting/issues/119) - [ ] Music visualizer / Bring back ArtNet [Issue](https://github.com/toblum/McLighting/issues/111) - [ ] Display version and parameters (Number of LEDs, definition settings, ..) in the web UI [Issue](https://github.com/toblum/McLighting/issues/150) +- [x] Make number of pixels, MQTT and PIN configurable via front end [Issue](https://github.com/toblum/McLighting/issues/93) and [Issue](https://github.com/toblum/McLighting/issues/93) +- [x] Bundle webpages instead of SPIFFS [Issue](https://github.com/toblum/McLighting/issues/93) - [x] IR remote support [issue](https://github.com/toblum/McLightingUI/issues/3) - [x] MQTT support - [x] Save favourite effects? [Issue](https://github.com/toblum/McLighting/issues/35)(https://github.com/toblum/McLighting/issues/101) From 191251f71b2b13076f2e2f0825345271d0fc751a Mon Sep 17 00:00:00 2001 From: bpohvoodoo Date: Fri, 13 Sep 2019 19:09:09 +0200 Subject: [PATCH 05/14] Bugfix * adressed issue: #32 (again) * adressed issue: #36 --- Arduino/McLighting/request_handlers.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Arduino/McLighting/request_handlers.h b/Arduino/McLighting/request_handlers.h index 9e10a97..b7b0a8a 100644 --- a/Arduino/McLighting/request_handlers.h +++ b/Arduino/McLighting/request_handlers.h @@ -1506,7 +1506,6 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t lenght #endif #endif - // *************************************************************************** // Button management // *************************************************************************** @@ -1726,7 +1725,7 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t lenght checkRGBOrder(tmp_rgbOrder); uint8_t temp_pin; checkPin((uint8_t) root["ws_pin"]); - WS2812FXStripSettings.fxoptions = constrain(root["ws_fxopt"].as(), 0, 255) && 0xFE; + WS2812FXStripSettings.fxoptions = constrain(root["ws_fxopt"].as(), 0, 255) & 0xFE; jsonBuffer.clear(); return true; } else { From f1d638c06459f110e74fd889de84b00685ce8377 Mon Sep 17 00:00:00 2001 From: bpohvoodoo Date: Mon, 23 Sep 2019 15:38:40 +0200 Subject: [PATCH 06/14] Updated Icons added sources of index.htm and edit.htm --- Arduino/McLighting/data/apple-touch-icon.png | Bin 0 -> 4500 bytes Arduino/McLighting/data/edit.htm | 674 +++++ Arduino/McLighting/data/favicon.ico | Bin 1150 -> 34494 bytes Arduino/McLighting/data/index.htm | 2697 +++++++++++++++++- 4 files changed, 3274 insertions(+), 97 deletions(-) create mode 100644 Arduino/McLighting/data/apple-touch-icon.png create mode 100644 Arduino/McLighting/data/edit.htm diff --git a/Arduino/McLighting/data/apple-touch-icon.png b/Arduino/McLighting/data/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..c00fb89de57046547094f4785167e3409afdc8ac GIT binary patch literal 4500 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 m_iVO+_x78a5@6?Iw*L#`C2;+Lpe=y_0000 + + + SPIFFS Editor + + + + + +
+
+
+ + + + diff --git a/Arduino/McLighting/data/favicon.ico b/Arduino/McLighting/data/favicon.ico index 71b25fe6ee6012a4c26602977262d217af885520..1c55beaf3d048b31b786937190d4bdc8a40610ea 100644 GIT binary patch literal 34494 zcmeI51$b0P*MK*MyA%o(r-f3iK%s&ZDJ?|{#frOY1t=7EcMBSU1Sd$42<{FgKuB;e zS{(NL@0raFn>2y?_kYj-U!FHJdw1{5nKNg6=3=wC*gR~hQrU2`+uFL@Y;|llTgHrz z&%+bjY{A^ip55_z5x@H^h0RvDaMb5KHruVNHe1=Uj?a0^*=$oQ*lc+TgP#(j<33KD zm&9>hy?b32Uf&>tt}cZQvgrCTxpwbP9KVct>+1b0()3DGaetUgY*G%|z-U-09uM~M z-8s2>|JRrgMSTjpbx=y*$tbD#wN28%htLwn!v;7fwnxt;)6v^<`0DK#VcfibS=#b! zMSk@TBoKH0=7%OQ1$K$;`F*in5V36&$>e`quHJqrhu@Wk@*}^}*fflH@1>L)D;vnr z1>K?vT)(TXuQ3>MZ@^2 z=Ss2F=K0T{l*ppB*W}W*{<5liP3|=6Vf|e%?}&hBa~GL>V?}f!MP1Y!=8UGHCQw zxpua{$R(S|c{pPeuQ~BQKXT{-NzczxK{6ONi04mviG=cD9r)QKU}SX}J$kgPUAs0~ z{(yi06Mv7IE2R=Y`v{7_s)g6&?k|_*(JVKS@!aTRllv`G$lCd{WXzZ`(fU(e4(>Ke z#;3d|-}19&@Rj=mxqkJoN#d1x4MaL~vksIbjI1eTRkcRqHE6te4W1zZHT>m7%KH+? z*E3-d%xZkuQjgrfeo5{$e8W5|nfTHr844;19bo|+mq)gz@-ts)7;`h;mS3*ikA7Y~ zw~psQZH2k2PJQpE)PcQ44`21G5vEAp-pUbi{ z0a7iZwIsh-L2NJTip|~`hEONWk`x!brA9=ktUMDYFJ8Pb@x%_pIc_6N)SrEq_DJzF zMF=YeC0FabkD(THf}t=E0^uOPxg^EHPs;vFFYV4bEGrHx?nS3(O5S@3Bn@TSMe8$r zCMZT&)p0xE4#)Mz4S_KHcc>rjlJMkRnR_HWT3@ll)z2#kyW(RPDMr}YAqm**DShPWwf4^-OHn?PB}cF0LVuWbR%^9G1LRT8>}bDJ}1L2wFCEDcX@Y!Ohgk z+3}SJZ5PG>^_d(ceCowJgnbfIr+&5th$NhISN5HLsVj{~^Kw)~A?by@s(}W$6`=tB zR4>SZ1BIo{>Ymc1Uq@-$ue)?!?<+ZWQ>#8e_He@2uvfrb{1ZfqjX7iTTxHkr_eCs` z8H6_$20%Ax1m*9!$iSc99ex~7HG@Z8@Z=#{ZZ=FsUy>-P5TYQhYy!2Q>Mt&Ehoh`_J!E` zBEMEEE#VuH8vV`~)jxjxNH$(hB0G>b1hg$#j5}(7b_og!GIgoS-@A8j>C>l=;m03; zly&RY$)G`l%x`ja*ekXsgj*Zgzr?K|lA_!NQwAf?9+mTi7YV1~I2-`qz&7IP>1o2( z@GN_+!aSA_azi%AVnaV+XXVkOM{;FFVzIAB?o~irYo9|N49IHo zZp4TY5*ZmOtbAS992>>hsJlKe3PI z<^a%!yd~7I$%7KE5;l2sbouJirHd$9zO?TU@O_Xhx80R_{9GUJ3tgcV@q{c3*Pu;a zJ$v>{BKyB5_KwKi7^*-?euyIN9&U0dTX9*_qPGla*ii;H>>(T5`^ny{p_s)BIYd~2 z;0=>t2n;F`Zt5J>jiT4Z!`tM3J$I4HjjbCm^ zufu&L?diPqOXv^DCr}ldKvx(9lR;bYKz_4R(x2KSeGcuGTesuxWB(r6RHr_N=S$|Z z=?K>Y)Xf7W;0tI3R{T@((>Ie%prgkKxFv6#2^ZhPM~yxHo;a=Fs11L+yP4#8Vw1Fl zL~oOS$srrO4>~Gt09{}(%z$OE8IHka^zg{^KW!HtOWRGmqx z{T3txhYm8}>fKMFF3@im`t9NgtAM_#gu`vIJ)>{*3&lPH4%?;nir34aIv=*XseDC0 zLsM81@=b7atJPw_9sH~B(`&K1hh&Ev`bt6U9ZfG)rV$F z+oul8(ouuT@B!q3)L{Dkk{Mqys16;0e!uuY2+$|A-*xEy5^)}bZLkg&gXd%M2|XI8 z>TmpVU4A^9MtTy8YPC6N3RMP;EjuIu*PE&2O`rFr`^Mo?_ouefeE1L2e&tNbx^R@Z z-8kkL7hEH*8GE{7hilKZQ8Uk z_l6A{M%r#ktCc&&wj3Ge5+CCV+Yrzus5`V5N!R?8sW&Ylb|3STb;O~gA1%eq%9s*0 z7Iy&jJ6KAzKR0vcOxeAAcf2fLzFcb8t}RcVJdyHK(ZdMh>jT}OEi{D&K!1Ma#+P;w ze7uDqv21}L(6PdD@PP@pT;=q+V`lzk%a$!BeLZ^gFlp9wTj&_*>eZ{JZ>eLVlaUw2 zt=*02afz9ys!H6SLwS)7Lov>%EBIJJwOlz4hhZP=fDjnCIFrfSf&~kT#;<)q?H^yc zawP^!mMk&-(IZEWNWOge%rjXkQzu7_TWqC?_e1yq-WMtHwIhBl^E-|umFvWH6)wSf zh=fz%zpkz9+qX};b?av0*F4a^bL?x|wr!%esPVUI)yh26*nE3KTUdRKvAO|*( z|Cp%@v|T$L?kdk1_dUf16z6fz;Gy-+c&u{e%D+2)wM!ktHEu@Rl#ck)0OKCIbz)2D zz$5huV}lz1nZwLc-KO7n3x0u{z_>^l7Y!Tu=i_hEc(BvBh&fJzbO$#`=zc;pZq@&> zjY-U0qnzf8FmM_-t@CN~hxOmGWlQr+uTE>7#!Vt`67(C84sxV*=wI{k*c$rjd%3w2 zwn7Lnt`f#oOZvPS!~RyRSRqZDHjObCsq>NT+O?B;^X8fSx9qQcmW5J=V06gwJ`?~Q z%V{^h$_Fp)|5#u%kqz8j11o^BoG|W^>9k}+T;y!nAyfWVty*RDwP(*BGd9xkyTy(j zJIvgL+F#$keI@+o^U~P$X7sqL62W~6pFj!d*Y0KfM|UiA#OcKsvtbI11|666pjG?s zLsQm1`|LAWzka=F{PE%I>npu_^^&n=4myp?M7|-Y+E5jAycxLcNOTdpckiA=j=?Y| z@#QEO2tA<#G-51O#U{JEH;{|e??Z?7iUGt0EPDT80w}y zWo$2606d3%sU!#SWp8i{CdDZ43qF&9_FU+p?1ysp&|zZ_akW3^j-8Xur4C|BI(F1? zoykIzr2hE72bIIK=RxPFjsM2AD{{Uo1ys|l6WU*cW*TujkNa-BWD)-iC1qpBVKSgr zTN%};r>tt}DN8d?ky|e3V~qP$1~ny57!O0?hjfv0^9CKGIIe@srpvR2{OBwA3^Z;v z*o+W0E)>Rv5;ZOq#)ZPTP#6~q<3h1rjv5y_im?_amBAn8zy#Y9*)T6=+v_Zb)&;x! z&_Gq-#~Sy$kOh*0Eg@uvBA|^(Q=l6;gq#&f#kXnj3V?T@6}-V_YdVKl~w}ot|s*g0+D=Ja!fi!#3hx zRBewL8@?)T>pPWUce@7iOe?AcU`q^{;5{e!ZeG9ns-XYS zcUngsS-47$H5)9)3bdD_Nt#KxYiH;qN0W|_qXp;7(H4Pn#DA}7fAXKcG=Kh%@ppXl z0{_;1ou|;XqNeb=c?##UYxyl#^&OXA?^*Z%lm5TU9yAR)U%YkKc9|ctL`DV8lW*5d zl<(F~mC?ZqWkE=QY~Q_0e);8>=yd-baa+3kGp??qZQHwDdIS%YLZLM!!I84!`lu#j z)ke@3zJ-1;5~hml15ZhKc)Sz|nJzyBFO^;UnRATF$3N@7%04XpXxP@J{$GFnRi20Ij9ETfUdnEzo`7W0;MBh z9r@{KR7Zom;0Q3M^z4%44qY$)+qV8;+p^O3I`_iG^YYc2FC@jUF5+glp+65u1zF%7 zC<%qGKXi^*^O4XD!-2O$KrZ|AO3Z? z+d$`(!f~&_eRwXmhj!++?2={ePT6*dbtWfS;`3W_$K~oXOSby3cAe0yog$@V0<>;s z9v!n^Y50^C)P_EUO7Jzb1f8u>`NzW?SOVC-u^k=Np99tX6R|x-_B%AyEalqHh$IO- zDa*IMzF(#?#>eulzEbXD0x5;gbqzZ|I0dTvU)d}0g02Nx0NK_5l1_aVfX|zcavR`%sDCB_Q!8aM%Y+?&Yp{uCi^o;V`8WSTBA@ini=x^#t6lz0 zz;ui*9c3B4Q!Zb=5=%SDgJBvlgJ<6a2Otudi+(|wy@#GJ!6`TZ+h8LEfIoPFr(LR#S{n7CvUYa zDQ!Ia$+v#vC2ga!QaN;%B-=JqY)=mJTh;v|huvI7zSF?a)yTd9|4RIeaOZ<=OPa)9 zkYRUaaiF7pu;jH|m0K_EANSt&mYF#pfLuQUJpePCt+h!5P|H?Z(wauC-UZ>n5Pj(J&PHf#yXQXblaaCTKpC zfV^isWX$>zW*ubd(xsyFc^YPH`So7&=FMg5)Tw5zdHni~l4$cu))COL+6?Omwxyu< zG853bZ4?ZKe$WegJd>GzK{4z|^`c?xo|%y$xn({wd7{&4xYT;ubCJRL`{4F~cKDm% z*24W{zlV&RK2&z?+GWtW=u)Lh8GrHO#m!zm-IJIzXHIjkdiCn^!w)~m-o1NGoLdhb zmeju3(>i2ZjE?8wPKB}fhvFK!@%O;r85ml=*LstA){|?Y7}v;*zXtv)@CE+Q>{6s|NVLvW zevNnCDR;9GjDcpO`sO4Xa7}-pKsKF@;eZIjciul^4oGSttb`F-KnPkQv8m z7^?ftTh>eXGZzU*x2GTsbl=qxI0*Y-Cv1TkdlO3Cx^>MyGOcSY`_VO;lqpk2>m@$> zdFP#XOr5IVtNi84l`~;!+LBg4W0lZzc_;%PLrKtmTtz|mauuR2DDXhG@7QhP(ll%e znJU-NEv<@NM#hV{x`#}AC=sCP(iZU0)zntf;Q;?Il!Z7}OJxH*|K^bL-VCpe_P&7GwdP9?b+9 zA%la_53E+Y4A2K!xHFfNm=b|q>J%g#EM_q^XH zzbeN{gR}pY@^|)P9m{UkvD|Q74Q$K8b@0;Pz57e~LpJm``wZnWvg-u(MYsUxKqreM z;0&B%KWpQ3(fR#%*pK#&Ec?lpu|@PcSQ?^B4Jjc7B!^_6Q|?J%3~hj=m*5SP#U75H zk;mt2q? zvO;Fa1R3ECNDp4~9c`GVVN2*bd4AAEu~$WCy2BD z)H*#$k|c8QAp6lzo{SS+8`5?+EG*3EAa?!vagrUZ!`aO`Tv6OYpvw)KrgtDW{+zfu zTp!9dr?K(5bLS*{sk>v1GL#<$Ipj5GEduZ_16}a(qCC%{|IqPknLmHN=)MH2{Zc*X zK874Qa!9sp+2SO7_UvZArRrKQmA_}to+hj|4SP#1=Jp-ycp~MAt~7iEA3#wk0tMmS z1c%LBrG}wl>6mNtv|M6mK}L0KUkS);_OANi&V?B;5k_J|J#v|T`0?Y%O&>t#J!9L+ z@A<6qtNrNNf%fliVjU10g1S%>s(`LZmWQ&SdGjGuE99vAEj_3k?9IB%Qsh%TtIYFp zJ#nYO1pLTtXZ_ON+a}MtFp{0z*T~$tb7xcjZb$72v*fmP;~Yn9za_ud@b#>zK9L`g ztuwSHx~91GLDyDm;C>0ZY*+zzNcT+|youj<__Jekpgf#I8JNS5W`eHujtAs6>z{pa zdq8_=%FInwH`%jswaGi(>ybHgW+_~_@E;;+(xfu%$I&vv<4p8AWM6{+7CJ#2Xa>4Q zTo-D=m#%kY`_?^{*wOi}c^KM1yVxhA<8h$+W|7xUfOZyG?VSO|*c(DksA!WDC6dUV zb*oMPQTNaM-tmhy-kCgPx=eE0%{r!C=E7tc2?L=w^ngy#2AV@7_$Kd0qqErgtO?r0 zUa1!&NTkZ$54R_7cTk--$E^?5@mIht3Gb2*ncZYvzrLnC{NC}S_ElF84wQ}V5ma%G z{me@Uc0T@TFc$w%+9MZ>a>pPSj(mO zX%%P8SBspgauBFZ{QzyUh5lxJ-^vHe4f2~Vk2|{< z5VnHuD_;ga;0bC^V?gtwkIQ{o=^Ywv@0Pr(7w3G?y~}GmmlC@coCe7D6;#G=$*vV} zF79Q3B=~J^$dekhL`SKoOFN>blW(q+b$O5S$0oeGSO;~nYb|?s?LHDLNf==)gjDvZw z0z%;+s131>M;UlX8q|&=;TY_J&7k?P0;VK9BRe+5zfPtJakd}JuWpEsr(7-JF80DG z;T`0qyxP;@PmF7$jM$Q)$2W1`hGO{3;nswv_`Bft1D(v7gS!-HcWli4aDdBhV?LHV zc#e#B;WEK$SvUf_@rU9rOmIOq`22Rx&{@XljEKWy(%|JO=kq2II)ftEyy?ig;hKFT zX>hYbekchQp%yfUuFwxu{@I`tR60Sp6V!&z!!6SBR30PiF9dl3=>Bp>1p7&JMmG66 zWRF9%=vyk|>(DmpP@c46Pleu%-DsIj4jF;@A9H4cGS38umR&vDU}saQ-PRw*z%1ZQ z4SO-{He_PL)6T@Fojoab^56yMT7D%BYDZ^bMTWhy`xQ3hEUzX+KU-n{0l)R^h0s?Lm2*xxVPb{JVov+ zE)T@NLV%n*dp=sHmVB}0alWtL#f9^4)US1G(DWIyBV$fM&`Fx=xZePM zLC*F#OVHfl7u#_bnu4dun~5^VY%;mRDyCZmkr|sMfbt{*~?jG z%kS*Q=hyt$7PwJ@+Vr9SP(x0;e<{yhnxND6$oC!Y02qaT8m^Z-b6G55?sFwLkEd+v zu#|dno9X+-7yj?PufK~6)rWQe_xR7BKQCL>k)FxE65M~b1a+Sdp7?zvXzFs=vOdJ@ zb^Ie?zAj$<-ucq7|C=th2mSx`AJl;E(<%%7;dT48{+sOTzg<=>$2zZ~^|H?G>v>{b zUv;iK{@c^&EWf64)!G2gg^ZIri+f1X#ZBZ*zgm)N<=5h|s*WUB^^K%n*@}DZrMUl( zQh)In8N8Tt`GM<1&sYC9UOLY>e9;&wxu}{X3N0#bzm{jEfxQP(3mSs1n(J!kckmMo z1wF;2tDq7q<6r$yJ z)y7p)#JiZdT~CF)Z$MVa0|ns&C=T?z~7u=D}6_P+2$P76lKNN#fpu0n=Lp^8?9YFKH z9}I^HFblk4DXfDK&=uc9z*@f9m!>O{cf|cnn0!5Fj_4fwzkO+*&ssJ^vWKLWlt_~Z zT)?sKPIpgbfNY>;pa^^f6`(5A1=a&&q<;jTCxR#Fiu?*#4_iQ2y^lgTT!deMRZaVI z_IKJPT|l6C@s0(R>EF1t-D)+Xq2#>gW^y?l(j*79fR{Fq7JnA7%7E4Z<={)G15Kel zSoyE%p9Hf(cZpbKUu|Dk{!hSJxC*+8%X<6^)*c^m=lvy#(X#Nwr(DO`H zW|xqHtm(6(OtQn9kQx%hOB+zD$Ot(=%Rq4`17ARGXaeovTj&Rx{wXjQ{9rW%gWCRK zI1Lv-^Zo&O|APHq;yAN>9hWIEmlKcW)6p~ju{KlNpUMmK8%r*V6z_rNuGVx~BA2ge~2qVnGU)Yv^kbH_e0 zoiaQFC*Y7>ijSVpp2Ew2YxZB_ytpmktPXb5dV^WRGUH2hw$3^u?vI0&b}%KL{e?VhbFW>0P;u1>iehP}Z4(Bi}9 z{cU!j<-GwmuY2RZL=sIw8PF141oA;nu*yL~{Em{K+DH%CAwQIW@=y)Ffi_^Je>DCX z-~%fm2-NnELIh~u-^1>oIqY8dBVUEHa1x@^jz1Uz?eb~A>3_LC&^oo-jC#_D@?IAS zzJe-X=|EFd81h0k$N*Xwy1`2uQ0sUHwA7RZ4WT}?h91x##(=hc3qi|%D5&j+!FkYf z|CqA@c0DS_d%QrWIZwe6F!_#a(vG`|`oQn8v>w6y;$LVNJpCs~iyLmHx27dlrLF~* z4oc(y0184Lcni`(Vt8o-s+C-zn$kKz`@yQsR-ox00OMdb_`^C-+dl+nK=b~-qt4U% zUiXGxgwt>o_Q7`0wCj5lR>ETNrCpeKLFO+(8GqtBapHtD+mhVu)zy|trELlgAhr&Q zbFTnsDl>tWd=GH^O^urkvC7H*!mLx0s zkv^^e4!~*9y#Li<_e^7&G9Q7f)BKwDZJ=pi3pxer2VO86ro$u{XP2-0vd-}b%0WQj zQt5NW&Fpah2?=_^x6lQ&)oKA+gKI8YI#A0l3R)j#gS3zkY|lM7Gm$~k)X5^X>wRtN zB!7SG+(t{zd3wFxc;gN8eo#I0*{N$6Nm}_+Nxpgk?c7aAou_usP}tOWKjZ2-08P6t z+pK}5un^|MESLflU^EQp4F7lGvMFSKb27^wO_YwK9!-C&s11nbh@eU0)(;Jyc8dMJ> zURuJ*Tzz$|na8AJin7OZQ*@k@Q>7{rvL@DKd2)HcuUJ zfV617Zv?6}Q`XhqJz*-0hmkM@20&lv3EyD@Z7<2%KpuR>rDbH$!qTP}HivY~1nrfn z9>&0M7zBMmZJ{e@d1wapp*oa*giTB@FS?fDe3dR;`cI`_!_ab4b>uYcopxXY>CrlV z5vaXec|HdJFc=6{y1&KW4LSi!Ctr5)b{6S3{=F(YgI6n-wXgTg(Lgm%sB=^urwW&9`T1)ASopgpt(svEQ9-4M4v)OjJJr(ig7 zTx$EH{N6Kr%GZ;YwXhmg7fZnpd|*C!f|iR3pmpS6XuZ!}_8r(`Le})_doTX#rTgb{ z<;o>RiWD(tTQ$7nKmRNVyHY0-ys6tX-94Z)=<;AIXa>4tVf+i~$ORZ%)D>6y)h_hRkM6^K9ok-4 zm+mFh{qMSe-)h5qkHaE+K8y1lhrUxaMw+Yfxe9y%mDnQj5qp%-YwT<3av$0M)YbI5 z_mC#-fo%sgZF=KDxT=eFpt{iVp*EpqqgPO(7-P=Z>DTwSYF(gh{_jD{zwY6-E-gPU zE-vOQgWl7&F?nsIP#f30R{O38n$I<$DpUs2Yqn05!_{T{()5Wxxht#J&{>RidHHuY zvn<-{JPL>5Anb>|VCiEE{$SV$s*6=n?wjPI?|ZV)`pwz?D^#c;TK{Xls|~ykeV1;Y zJb9ue^zrdA>DTi>RvfAF(&B2GYXNp`^7wPyieRO;6#h@(Lnr~op_s^!(f-k4Tb_CT zl_Zk1L_j#4hLfNpFlU_{;IpNVZTQ#RVO-Gs8}p7^eb23;_UfGe4?g%n+P80SMfF-& z-%F)^LhG{9ukW<7;%L#b73VoFIkpFAddq;O^&_y-S``2LP#6kA0eFw?XKiApU(3~8 z|J;sq9;As-9cMjo&p{+udO3yP(#aA0)7HDm-Me=!QH>kh{{OD@>;6dVQh(C-shvxb zCe2q(t8+T@^En^ng?9jRVoQ#BpI7mU?71P$)}`xl3;YvBpAET1x_$wA-*N-4!!@`9 zm*FCuhqEwjdU}~LV@3?L-To)?U*Fa199OoYlf?EeY0@;l4VuPWkQ1^)wkWdVz6Dtz zGu!m?`I$9HEB(6uxO!azonbIM!c`td@c@@mSoArXd-(6bZD`iU!}M#kZ)2hL*FTYd z-N$dmp>Cqo-@H6S^CoG_2pOWF_}J1%kq$R)6lpj=k!3}UcfRO+VQ4V>=IFo0fOgI9 zMC`LTdIo3l;`){vquqbQ{x!08o#>e)Ctb;$NQyr;Nx0{PTapzq+AE#k3|;RcY&PSt zZ!GTd*_pe1R;c%s#lBR+v95nD{q2^Tt){Ms;t~s*TH4yUadnNEex2hCT%5r14!!F< zq+E;QD()4~jgZPExCrOr9OIux9;WZE{dS9g(*CVD)J>kKQM~7X*b1uKL8IU2mk>W& z6b)%pt%$KNOzS`Y8407)6n(xak_RH-41~ie(2bNQ;5Zy**0z7z77FfD@6ThXO z(#2x7e=4{C%nFWl9^!|ZPTe@J8?X0(p1IlqTOkyJU<0h9O;}7D(4>BJza*~y&qp79 zB#j$4w$$@lSKo)2DO0BC-^JDcY1XtQXPBh56fa;}b z9lz+XEzjb4448F;vz1ozmW}g{cS~C74dgTDpkyU11wZg+4(QWlqG$drV)uXb{EGJ9 z^$eGe=U#=L$=CS+eLtmjS?SmQuN6n0l(jeq<#^>_dEBz_34912Krtu+Y;QO3vCNPA zF64#b1Am+TrHkjw^HA59>D|Z=*TY%}fE8e+*$4l8@PuisQg`!+F~=O+{ps( zv2SzOLi2h$OoFjMx}y*Ae9w5Xp{wlLzSRV)|96zmcjx(DO` z33@^|Xa@-3S0kI5>(&VLy|da6(6Oxk|1W<9eSfXiAG#l|dp_@|b6~s^HcEuBkc{}f=1dJngEg6 ziDXvokD~k5etv$EB1H-__wi@uKb_}KlP6CuKbD(9wmgqMM>djZDSG6rg3mw>JYyE!z{oO7{@_{ozNz*3J87+v92^4WTw@p~JwQ zmSDd4%a5Y%z>4dy%zx|L+0Y8psfHh!;;GfI7Dp9RMH~tPK*jqG)FdrEw8Y;ORLs^D z7g*xN>#kbjCHIEW4h-i3O}7RdTfti3G*GR8zXB~}TIrUA0@y)L%0m8ZvTN&>=y?9@ z`p5n~2W0ZwK6FK%o8~x>D70GE>R5{>%}?}kSgTm};+~pkSFrTZ*zKwWtXLN*gP%U6}K?~OBV5Pkv{@joWQt+M+cQ;wkyp^0id-e~nf9aTX zK?g4hb>C-(8b1@{Uf2w(18t5M!aUICNQ>!c7zP7Dv#cjrdT9Rs{1{{Y_|mWU0~UMB z&F*e7jY;#II?fo9Rr)`1_N7r-pg>dLC3)pYxVmWht;=VWcb##bdd z#&_Cg&1=VGT4x^Cy1f;sy=$df8>r`Eq`feo-+`=<4ibY4Whm+#o?~*R7(?qaveC7a z%t~KL+PD>xMYVsD#r1|t$HbLnYMPD`=r)3(^=6(s1y|sKIL_fYhMuPh_Au;*P_XPk zb>Ib_Fcq}w8U<<#gE)WnZ57WL^Ib6#8_$Qs%+v0w$E1PQacb}NKpPWH`)6R~dlCHF zsmu-;AUU`}oagvHjz_DHY|bluoI5MdC)Ut&p5~Dc+LF<+>>w!c0vfK z4pxFcc*AU%4py_(DfK4vZlZXS`O4cqlZMHi`s8CB0;~yI$5(_hP!crl`5*_p38^3f zaCX-*F{2YwI_anrlh~74gs6+#61qcw7z3W*57bX)f%g#Z8MqD)C9p3R*h+hTBFqh0;SER$#_l!msWZ%EtLFXNPz2QW zwd{Wdji4QT2LoUpha`fv~Ed+Uc4@GBO^6v}b`lpo>l=p&V3!I?xO{!uO#0KMJP80?;;KErh~e zI1UkT74Ek#^Q+m*L!FS{c*_E2IZtUJ2*{rY-hJoD;UkCtSLL^r-dDM* z3!PV9>g6kb-G+!?#g4KlX9HQ3sD`Xf@QnnywvZsVwz4Kc2U(c7oh-`TOZ+|`A&Yv< pkY&D0Br-Dczb?(M(uaPhI{CkJ{`qzK_)mY&ezgC;{ts*5e*m_xxaI%= literal 1150 zcmd5)T}YZ?6n@3p3@y{hrv0dyWg4`Ks8LiSs1Zp~iZo)Ag)vw}5=AXod1FBkBZ8nH zm^Z-|?V=!w1lNUBASeXo@J`0^VlWvo2dii2d%G#v{l>?6f6jZJbDr~@k1-K_xw(v1 z9ebI0x7Iqot*#Mb_8XCpMW( zReHVNRa#m)qtR%dYqeVa%E}7s=;*k^yt%pgaZOFlCyT}Mt)ikLSyomSAdHVNiTwQh zYlT9AoSdAi?Ck6_l}cqxrBb4{wllAZC#W|h5(!LCPa_ZrfO4a^w-*$T#l=O$<8hpvoWSq* zr+q%3e_~?7OnSW+IMa^f<71qjo??H0AF)^rR;v~3>+77){QNxD*47Y-M377-5sgNF zZ*FeBB8@*378YW1auP>JN7&xpMmQYC!NCEBhljDWw8S}YZEay}Y>emI+uIY~)4^cy z8F70`_|(9_00svKF*G!U(a}+OJRTk|_-LM-)6~=yc6N5SQH{@((@6bGii?Y1E0xNx zGMVg3&{-mpfX*l>DM4Lb9lE=_xgNW_yPVhI;UPky&|8{|m^5NE!wk}(ie_b}IlBq} zMVOVKdwqR9$B&PX3*3JY_m9Nk&GPc{gN==iJO5F2bv2uvofXmS?P|3;LfBta`3zy6F#O-#g&(F`<>gww4@PB>= d7!xdbpRqYH(Gg7acUp7g|4wmf`3*+E{Q*gMMA`rV diff --git a/Arduino/McLighting/data/index.htm b/Arduino/McLighting/data/index.htm index 6aa58cc..52eda48 100644 --- a/Arduino/McLighting/data/index.htm +++ b/Arduino/McLighting/data/index.htm @@ -1,97 +1,2600 @@ - - - - - - ESP Monitor - - - - -
- - - - -
-
-
-
- - \ No newline at end of file + + + + McLighting + + + + + + + + + + + +
+
+ + + + From 589806d0fda737011426754a6d84c88535ded688 Mon Sep 17 00:00:00 2001 From: patricksp Date: Wed, 9 Oct 2019 16:30:41 +0200 Subject: [PATCH 07/14] added output to control external power supply If defined, enable output to control external power supply PIN (12 / D6) --- Arduino/McLighting/McLighting.ino | 13 +++++++++++++ Arduino/McLighting/definitions.h | 1 + Arduino/McLighting/version.h | 2 +- Arduino/McLighting/version_info.ino | 3 +++ 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/Arduino/McLighting/McLighting.ino b/Arduino/McLighting/McLighting.ino index 03df3d6..ee06621 100644 --- a/Arduino/McLighting/McLighting.ino +++ b/Arduino/McLighting/McLighting.ino @@ -427,6 +427,10 @@ void setup() { } #endif +#if defined(POWER_SUPPLY) + pinMode(POWER_SUPPLY, OUTPUT); // output to control external power supply +#endif + // start ticker with 0.5 because we start in AP mode and try to connect ticker.attach(0.5, tick); @@ -776,8 +780,17 @@ void loop() { #if defined(ENABLE_MQTT) if (prevmode != mode) { snprintf(mqtt_buf, sizeof(mqtt_buf), "OK =off", ""); } #endif + #if defined(POWER_SUPPLY) + if (prevmode != mode) {digitalWrite(POWER_SUPPLY, LOW); } // power off -> external power supply + #endif } +#if defined(POWER_SUPPLY) + if (mode != OFF) { + if (prevmode != mode) {digitalWrite(POWER_SUPPLY, HIGH); } // power on -> external power supply + } +#endif + if (mode == AUTO) { if (prevmode != mode) { handleAutoStart(); diff --git a/Arduino/McLighting/definitions.h b/Arduino/McLighting/definitions.h index 91cb4ed..f47c0a8 100644 --- a/Arduino/McLighting/definitions.h +++ b/Arduino/McLighting/definitions.h @@ -12,6 +12,7 @@ #define FX_OPTIONS 48 // ws2812fx Options 48 = SIZE_SMALL + FADE_MEDIUM is configurable just for the start; for WS2812FX setSegment OPTIONS, see: https://github.com/kitesurfer1404/WS2812FX/blob/master/extras/WS2812FX%20Users%20Guide.md //#define LED_TYPE_WS2811 // Uncomment, if LED type uses 400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers) #define LED_BUILTIN 2 // ESP-12F has the built in LED on GPIO2, see https://github.com/esp8266/Arduino/issues/2192 +#define POWER_SUPPLY 12 // PIN (12 / D6) If defined, enable output to control external power supply char HOSTNAME[65] = "McLightingRGBW"; // Friedly hostname is configurable just for the start. Hostname should not contain spaces as this can break Home Assistant discovery if used. diff --git a/Arduino/McLighting/version.h b/Arduino/McLighting/version.h index c1cb01b..c3b43d1 100644 --- a/Arduino/McLighting/version.h +++ b/Arduino/McLighting/version.h @@ -1 +1 @@ -#define SKETCH_VERSION "2.2.6.RU1.rgbw.3c" +#define SKETCH_VERSION "2.2.7.RU1.rgbw.3c" diff --git a/Arduino/McLighting/version_info.ino b/Arduino/McLighting/version_info.ino index a74007e..fbbb069 100644 --- a/Arduino/McLighting/version_info.ino +++ b/Arduino/McLighting/version_info.ino @@ -165,4 +165,7 @@ * adressed issue: #31 * adressed issue: #32 * + * 09 Oktober 2019 + * Version Bump to 2.2.7 rgbw 3colors + * added output to control external power supply */ From 1d2a6f92372dfc8a3b2be5e8860bd27453ae3f29 Mon Sep 17 00:00:00 2001 From: bpohvoodoo Date: Fri, 25 Oct 2019 18:51:56 +0200 Subject: [PATCH 08/14] Update README.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 66ae6a0..667889e 100644 --- a/README.md +++ b/README.md @@ -64,8 +64,9 @@ I hope I didn't miss any sources and mentioned every author. In case I forgot so - [ ] Remove old / wrong EEPROM settings completely [Issue] - [ ] Customer profile to define segments of (in)active areas on the strip [Issue](https://github.com/toblum/McLighting/issues/37) - [ ] Additional clients -- [ ] If no wifi, at least enable button mode. -- [ ] Also enable McLighting in Wifi AP mode. +- [x] If no wifi, at least enable button mode. +- [x] Also enable McLighting in Wifi AP mode. +- [ ] Search an non blocking alternative for WifiManager - [ ] Multiple buttons/GPIO Inputs. [Issue](https://github.com/toblum/McLighting/issues/119) - [ ] Music visualizer / Bring back ArtNet [Issue](https://github.com/toblum/McLighting/issues/111) - [ ] Display version and parameters (Number of LEDs, definition settings, ..) in the web UI [Issue](https://github.com/toblum/McLighting/issues/150) From fa31f97c47759c3664dc4c5d2c40bf52077c34e9 Mon Sep 17 00:00:00 2001 From: bpohvoodoo Date: Sun, 17 Nov 2019 13:34:22 +0100 Subject: [PATCH 09/14] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 667889e..fe688ef 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # McLighting v2 - The ESP8266 based multi-client lighting gadget -[![Gitter](https://badges.gitter.im/mclighting/Lobby.svg)](https://gitter.im/mclighting/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Build Status](https://travis-ci.com/toblum/McLighting.svg?branch=master)](https://travis-ci.com/toblum/McLighting) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![version](https://img.shields.io/badge/version-v2.2.6-blue.svg)](https://github.com/toblum/McLighting/blob/master/Arduino/McLighting/version.h) +[![Gitter](https://badges.gitter.im/mclighting/Lobby.svg)](https://gitter.im/mclighting/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Build Status](https://travis-ci.com/toblum/McLighting.svg?branch=master)](https://travis-ci.com/toblum/McLighting) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![version](https://img.shields.io/badge/version-v2.2.7-blue.svg)](https://github.com/toblum/McLighting/blob/master/Arduino/McLighting/version.h) McLighting (the multi-client lighting gadget) is a very cheap internet-controllable lighting solution based on the famous ESP8266 microcontroller and WS2811/2812 led strips. It features among other things a web-interface, a REST-API and a websocket connector. @@ -64,8 +64,8 @@ I hope I didn't miss any sources and mentioned every author. In case I forgot so - [ ] Remove old / wrong EEPROM settings completely [Issue] - [ ] Customer profile to define segments of (in)active areas on the strip [Issue](https://github.com/toblum/McLighting/issues/37) - [ ] Additional clients -- [x] If no wifi, at least enable button mode. -- [x] Also enable McLighting in Wifi AP mode. +- [ ] If no wifi, at least enable button mode. +- [ ] Also enable McLighting in Wifi AP mode. - [ ] Search an non blocking alternative for WifiManager - [ ] Multiple buttons/GPIO Inputs. [Issue](https://github.com/toblum/McLighting/issues/119) - [ ] Music visualizer / Bring back ArtNet [Issue](https://github.com/toblum/McLighting/issues/111) From a613b8c9782f7c30a715730526ae2c4fde96d3b1 Mon Sep 17 00:00:00 2001 From: ryancasler Date: Sun, 8 Dec 2019 01:00:54 -0500 Subject: [PATCH 10/14] Power Supply Update Moved the pin setting for POWER_SUPPLY to definitions.h instead of the main sketch. --- Arduino/McLighting/McLighting.ino | 4 ++-- Arduino/McLighting/definitions.h | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Arduino/McLighting/McLighting.ino b/Arduino/McLighting/McLighting.ino index ee06621..5909938 100644 --- a/Arduino/McLighting/McLighting.ino +++ b/Arduino/McLighting/McLighting.ino @@ -781,13 +781,13 @@ void loop() { if (prevmode != mode) { snprintf(mqtt_buf, sizeof(mqtt_buf), "OK =off", ""); } #endif #if defined(POWER_SUPPLY) - if (prevmode != mode) {digitalWrite(POWER_SUPPLY, LOW); } // power off -> external power supply + if (prevmode != mode) {digitalWrite(POWER_SUPPLY, !POWER_ON); } // power off -> external power supply #endif } #if defined(POWER_SUPPLY) if (mode != OFF) { - if (prevmode != mode) {digitalWrite(POWER_SUPPLY, HIGH); } // power on -> external power supply + if (prevmode != mode) {digitalWrite(POWER_SUPPLY, POWER_ON); } // power on -> external power supply } #endif diff --git a/Arduino/McLighting/definitions.h b/Arduino/McLighting/definitions.h index f47c0a8..87c155e 100644 --- a/Arduino/McLighting/definitions.h +++ b/Arduino/McLighting/definitions.h @@ -13,6 +13,9 @@ //#define LED_TYPE_WS2811 // Uncomment, if LED type uses 400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers) #define LED_BUILTIN 2 // ESP-12F has the built in LED on GPIO2, see https://github.com/esp8266/Arduino/issues/2192 #define POWER_SUPPLY 12 // PIN (12 / D6) If defined, enable output to control external power supply +#if defined(POWER_SUPPLY) + #define POWER_ON HIGH // Define the output state to turn on the power supply, either HIGH or LOW. Opposite will be uses for power off. +#endif char HOSTNAME[65] = "McLightingRGBW"; // Friedly hostname is configurable just for the start. Hostname should not contain spaces as this can break Home Assistant discovery if used. From 99394f86725eeb0b0403eda808077d0428b4e763 Mon Sep 17 00:00:00 2001 From: sehraf Date: Mon, 16 Dec 2019 18:28:21 +0100 Subject: [PATCH 11/14] fix #if block missing the last line Causes compiling error when ENABLE_LEGACY_ANIMATIONS is not enabled. --- Arduino/McLighting/request_handlers.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Arduino/McLighting/request_handlers.h b/Arduino/McLighting/request_handlers.h index b7b0a8a..1cc3495 100644 --- a/Arduino/McLighting/request_handlers.h +++ b/Arduino/McLighting/request_handlers.h @@ -521,8 +521,8 @@ void handleSetNamedMode(uint8_t * mypayload) { ws2812fx_mode = FX_MODE_THEATER_CHASE_RAINBOW; mode = SET_MODE; } -#endif } +#endif void handleSetWS2812FXMode(uint8_t * mypayload) { if (isDigit(mypayload[1])) { From aa4e7fa5c8c2a94e715fc64eb9012df28d1cef80 Mon Sep 17 00:00:00 2001 From: bpohvoodoo Date: Mon, 16 Dec 2019 21:11:24 +0100 Subject: [PATCH 12/14] Bump to 2.2.8 * Version Bump to 2.2.8 rgbw 3colors * Bugfixes * adressed issue #59 --- Arduino/McLighting/McLighting.ino | 1 + Arduino/McLighting/definitions.h | 8 ++++---- Arduino/McLighting/request_handlers.h | 1 + Arduino/McLighting/version.h | 2 +- Arduino/McLighting/version_info.ino | 5 +++++ 5 files changed, 12 insertions(+), 5 deletions(-) diff --git a/Arduino/McLighting/McLighting.ino b/Arduino/McLighting/McLighting.ino index 5909938..d2b1b98 100644 --- a/Arduino/McLighting/McLighting.ino +++ b/Arduino/McLighting/McLighting.ino @@ -727,6 +727,7 @@ void loop() { WiFi.disconnect(); WiFi.setSleepMode(WIFI_NONE_SLEEP); WiFi.mode(WIFI_STA); + WiFi.hostname(HOSTNAME); WiFi.begin(); } else { if ((strlen(mqtt_host) != 0) && (mqtt_port != 0) && (mqtt_reconnect_retries < MQTT_MAX_RECONNECT_TRIES)) { diff --git a/Arduino/McLighting/definitions.h b/Arduino/McLighting/definitions.h index 87c155e..c2c7705 100644 --- a/Arduino/McLighting/definitions.h +++ b/Arduino/McLighting/definitions.h @@ -171,10 +171,10 @@ bool updateState = false; // Button handling #if defined(ENABLE_BUTTON) -//#define BTN_MODE_SHORT "STA|mo|fxm| h| s| r1| g1| b1| w1| r2| g2| b2| w2| r3| g3| b3| w3" // Example - #define BTN_MODE_SHORT "STA| 5| 0|255|196| 0| 0| 0|255| 0| 0| 0| 0| 0| 0| 0| 0" // Static white - #define BTN_MODE_MEDIUM "STA| 5| 48|200|196|255|102| 0| 0| 0| 0| 0| 0| 0| 0| 0| 0" // Fire flicker - #define BTN_MODE_LONG "STA| 5| 46|200|196|255|102| 0| 0| 0| 0| 0| 0| 0| 0| 0| 0" // Fireworks random +//#define BTN_MODE_SHORT "STA|mo|fxm| s| b| r1| g1| b1| w1| r2| g2| b2| w2| r3| g3| b3| w3" // Example + #define BTN_MODE_SHORT "STA| 5| 0|196|255| 0| 0| 0|255| 0| 0| 0| 0| 0| 0| 0| 0" // Static white + #define BTN_MODE_MEDIUM "STA| 5| 48|196|200|255|102| 0| 0| 0| 0| 0| 0| 0| 0| 0| 0" // Fire flicker + #define BTN_MODE_LONG "STA| 5| 46|196|200|255|102| 0| 0| 0| 0| 0| 0| 0| 0| 0| 0" // Fireworks random unsigned long keyPrevMillis = 0; const unsigned long keySampleIntervalMs = 25; byte longKeyPressCountMax = 80; // 80 * 25 = 2000 ms diff --git a/Arduino/McLighting/request_handlers.h b/Arduino/McLighting/request_handlers.h index 1cc3495..e8431fe 100644 --- a/Arduino/McLighting/request_handlers.h +++ b/Arduino/McLighting/request_handlers.h @@ -1395,6 +1395,7 @@ void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t lenght DBG_OUTPUT_PORT.println("Re-connecting to Wi-Fi..."); WiFi.setSleepMode(WIFI_NONE_SLEEP); WiFi.mode(WIFI_STA); + WiFi.hostname(HOSTNAME); WiFi.begin(); } diff --git a/Arduino/McLighting/version.h b/Arduino/McLighting/version.h index c3b43d1..e51a3f8 100644 --- a/Arduino/McLighting/version.h +++ b/Arduino/McLighting/version.h @@ -1 +1 @@ -#define SKETCH_VERSION "2.2.7.RU1.rgbw.3c" +#define SKETCH_VERSION "2.2.8.RU1.rgbw.3c" diff --git a/Arduino/McLighting/version_info.ino b/Arduino/McLighting/version_info.ino index fbbb069..226b95f 100644 --- a/Arduino/McLighting/version_info.ino +++ b/Arduino/McLighting/version_info.ino @@ -168,4 +168,9 @@ * 09 Oktober 2019 * Version Bump to 2.2.7 rgbw 3colors * added output to control external power supply + * + * 06 December 2019 + * Version Bump to 2.2.8 rgbw 3colors + * Bugfixes + * adressed issue #59 */ From 0eeec51bb0fa610f39d513bab2b6ec12da4a1ad6 Mon Sep 17 00:00:00 2001 From: bpohvoodoo Date: Sun, 19 Jan 2020 11:14:14 +0100 Subject: [PATCH 13/14] Update platformio.ini --- platformio.ini | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 5803450..8d2cfec 100644 --- a/platformio.ini +++ b/platformio.ini @@ -25,6 +25,11 @@ description = The ESP8266 based multi-client lighting gadget # arduino core 2.4.1 = platformIO 1.7.3 # arduino core 2.4.2 = platformIO 1.8.0 # arduino core 2.5.0 = platformIO 2.0.4 +# arduino core 2.5.1 = platformIO 2.1.1 +# arduino core 2.5.2 = platformIO 2.2.3 +# arduino core 2.6.1 = platformIO 2.3.0 +# arduino core 2.6.2 = platformIO 2.3.1 +# arduino core 2.6.3 = platformIO 2.3.2 # arduino core stage = platformIO feature#stage # ------------------------------------------------------------------------------ arduino_core_2_3_0 = espressif8266@1.5.0 @@ -33,7 +38,10 @@ arduino_core_2_4_1 = espressif8266@1.7.3 arduino_core_2_4_2 = espressif8266@1.8.0 arduino_core_2_5_0 = espressif8266@2.0.4 arduino_core_2_5_1 = espressif8266@2.1.1 -arduino_core_2_5_2 = espressif8266@2.2.0 +arduino_core_2_5_2 = espressif8266@2.2.3 +arduino_core_2_6_1 = espressif8266@2.3.0 +arduino_core_2_6_2 = espressif8266@2.3.1 +arduino_core_2_6_3 = espressif8266@2.3.2 arduino_core_stage = https://github.com/platformio/platform-espressif8266.git#feature/stage framework = arduino From ca255317f69213e16688f18c975b59c9316c966a Mon Sep 17 00:00:00 2001 From: bpohvoodoo Date: Sat, 7 Mar 2020 09:42:14 +0100 Subject: [PATCH 14/14] Bump to 3.1.0 --- Arduino/McLighting/McLighting.ino | 643 +- Arduino/McLighting/WS2812FX.cpp | 1647 +++++ Arduino/McLighting/WS2812FX.h | 651 ++ Arduino/McLighting/data/edit.htm | 4 +- Arduino/McLighting/data/edit.htm.gz | Bin 4116 -> 5563 bytes Arduino/McLighting/data/favicon.ico.gz | Bin 0 -> 12171 bytes Arduino/McLighting/data/index.htm | 644 +- Arduino/McLighting/data/index.htm.gz | Bin 0 -> 20878 bytes Arduino/McLighting/data/material.woff2 | Bin 0 -> 60832 bytes Arduino/McLighting/definitions.h | 142 +- Arduino/McLighting/filesystem_functions.h | 262 + Arduino/McLighting/helper_functions.h | 323 + Arduino/McLighting/htm_edit_gz.h | 925 +-- Arduino/McLighting/htm_index_gz.h | 3406 ++++----- Arduino/McLighting/html_material_icons.h | 6472 +++++++++++++++++ Arduino/McLighting/json_functions.h | 260 + .../mode_custom_ws2812fx_animations.h | 312 +- Arduino/McLighting/request_handlers.h | 1936 ++--- Arduino/McLighting/rest_api.h | 904 +-- Arduino/McLighting/spiffs_webserver.h | 3 + Arduino/McLighting/version.h | 2 +- Arduino/McLighting/version_info.ino | 94 +- README.md | 42 +- clients/HomeAssistant/light.yaml | 7 +- clients/homebridge/config.json | 6 +- clients/web/apple-touch-icon.png | Bin 0 -> 4500 bytes clients/web/edit.htm | 676 ++ clients/web/edit.htm.gz | Bin 0 -> 5563 bytes clients/web/favicon.ico | Bin 0 -> 34494 bytes clients/web/favicon.ico.gz | Bin 0 -> 12171 bytes clients/web/graphs.js.gz | Bin 0 -> 1971 bytes clients/web/index.htm | 2696 +++++++ clients/web/index.htm.gz | Bin 0 -> 20878 bytes clients/web/material.woff2 | Bin 0 -> 60832 bytes platformio.ini | 2 +- 35 files changed, 17457 insertions(+), 4602 deletions(-) create mode 100644 Arduino/McLighting/WS2812FX.cpp create mode 100644 Arduino/McLighting/WS2812FX.h create mode 100644 Arduino/McLighting/data/favicon.ico.gz create mode 100644 Arduino/McLighting/data/index.htm.gz create mode 100644 Arduino/McLighting/data/material.woff2 create mode 100644 Arduino/McLighting/filesystem_functions.h create mode 100644 Arduino/McLighting/helper_functions.h create mode 100644 Arduino/McLighting/html_material_icons.h create mode 100644 Arduino/McLighting/json_functions.h create mode 100644 clients/web/apple-touch-icon.png create mode 100644 clients/web/edit.htm create mode 100644 clients/web/edit.htm.gz create mode 100644 clients/web/favicon.ico create mode 100644 clients/web/favicon.ico.gz create mode 100644 clients/web/graphs.js.gz create mode 100644 clients/web/index.htm create mode 100644 clients/web/index.htm.gz create mode 100644 clients/web/material.woff2 diff --git a/Arduino/McLighting/McLighting.ino b/Arduino/McLighting/McLighting.ino index d2b1b98..ad2cd04 100644 --- a/Arduino/McLighting/McLighting.ino +++ b/Arduino/McLighting/McLighting.ino @@ -1,4 +1,8 @@ #include "definitions.h" +// *************************************************************************** +// Load library "ticker" for blinking status led, mqtt send and save state +// *************************************************************************** +#include #include "version.h" // *************************************************************************** @@ -10,16 +14,12 @@ #include #include #include //https://github.com/tzapu/WiFiManager - -#include -#include #include -#if ENABLE_STATE_SAVE == 0 - #include -#endif + +#include #include //https://github.com/Links2004/arduinoWebSockets #include - + #if defined(ENABLE_BUTTON_GY33) // *************************************************************************** // Load libraries for GY33 and initialize color sensor @@ -28,19 +28,18 @@ GY33_MCU tcs; #endif -#include //https://github.com/bblanchon/ArduinoJson - // MQTT #if defined(ENABLE_MQTT) #if ENABLE_MQTT == 0 // *************************************************************************** // Load libraries for PubSubClient // *************************************************************************** + #include #include WiFiClient espClient; PubSubClient * mqtt_client; #endif - + #if ENABLE_MQTT == 1 // *************************************************************************** // Load libraries for Amqtt @@ -50,16 +49,15 @@ AsyncMqttClient * mqtt_client; WiFiEventHandler wifiConnectHandler; WiFiEventHandler wifiDisconnectHandler; + Ticker mqttReconnectTimer; + Ticker wifiReconnectTimer; + #endif + #if defined(ENABLE_HOMEASSISTANT) + Ticker ha_send_data; #endif #endif -#if defined(ARDUINOJSON_VERSION) - #if !(ARDUINOJSON_VERSION_MAJOR == 6 and ARDUINOJSON_VERSION_MINOR >= 8) - #error "Install ArduinoJson v6.8.x or higher" - #endif -#endif - -#if defined(ENABLE_E131) +#if defined(CUSTOM_WS2812FX_ANIMATIONS) // *************************************************************************** // Load libraries for E131 support // *************************************************************************** @@ -78,8 +76,9 @@ #endif #if defined(USE_HTML_MIN_GZ) -#include "htm_index_gz.h" -#include "htm_edit_gz.h" +#include "htm_index_gz.h" +#include "htm_edit_gz.h" +#include "html_material_icons.h" #endif @@ -106,87 +105,72 @@ WebSocketsServer webSocket = WebSocketsServer(81); // *************************************************************************** // Load and instanciate WS2812FX library // *************************************************************************** -#include // https://github.com/kitesurfer1404/WS2812FX +#include "WS2812FX.h" // https://github.com/kitesurfer1404/WS2812FX WS2812FX * strip = NULL; #if defined(USE_WS2812FX_DMA) #include #if USE_WS2812FX_DMA == 0 // Uses GPIO3/RXD0/RX, more info: https://github.com/Makuna/NeoPixelBus/wiki/ESP8266-NeoMethods - #if !defined(LED_TYPE_WS2811) - NeoEsp8266Dma800KbpsMethod * dma = NULL ; //800 KHz bitstream (most NeoPixel products w/WS2812 LEDs) + #if !defined(LED_TYPE_WS2811) + NeoPixelBus * dma = NULL ; //800 KHz bitstream (most NeoPixel products w/WS2812 LEDs) #else - NeoEsp8266Dma400KbpsMethod * dma = NULL; //400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers) + NeoPixelBus * dma = NULL; //400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers) #endif #endif #if USE_WS2812FX_DMA == 1 // Uses UART1: GPIO1/TXD0/TX, more info: https://github.com/Makuna/NeoPixelBus/wiki/ESP8266-NeoMethods - #if !defined(LED_TYPE_WS2811) - NeoEsp8266Uart0800KbpsMethod * dma = NULL; //800 KHz bitstream (most NeoPixel products w/WS2812 LEDs) + #if !defined(LED_TYPE_WS2811) + NeoPixelBus * dma = NULL; //800 KHz bitstream (most NeoPixel products w/WS2812 LEDs) #else - NeoEsp8266Uart0400KbpsMethod * dma = NULL; //400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers) + NeoPixelBus * dma = NULL; //400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers) #endif #endif #if USE_WS2812FX_DMA == 2 // Uses UART2: GPIO2/TXD1/D4, more info: https://github.com/Makuna/NeoPixelBus/wiki/ESP8266-NeoMethods - #if !defined(LED_TYPE_WS2811) - NeoEsp8266Uart1800KbpsMethod * dma = NULL; //800 KHz bitstream (most NeoPixel products w/WS2812 LEDs) + #if !defined(LED_TYPE_WS2811) + NeoPixelBus * dma = NULL; //800 KHz bitstream (most NeoPixel products w/WS2812 LEDs) #else - NeoEsp8266Uart1400KbpsMethod * dma = NULL; //400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers) + NeoPixelBus * dma = NULL; //400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers) #endif #endif - + void initDMA(uint16_t stripSize = NUMLEDS){ if (dma != NULL) { delete(dma); } - uint8_t ledcolors = 3; - if (strstr(WS2812FXStripSettings.RGBOrder, "W") != NULL) { - ledcolors = 4; - } #if USE_WS2812FX_DMA == 0 // Uses GPIO3/RXD0/RX, more info: https://github.com/Makuna/NeoPixelBus/wiki/ESP8266-NeoMethods - #if !defined(LED_TYPE_WS2811) - dma = new NeoEsp8266Dma800KbpsMethod(stripSize, ledcolors); //800 KHz bitstream (most NeoPixel products w/WS2812 LEDs) + #if !defined(LED_TYPE_WS2811) + dma = new NeoPixelBus(stripSize); //800 KHz bitstream (most NeoPixel products w/WS2812 LEDs) #else - dma = new NeoEsp8266Dma400KbpsMethod(stripSize, ledcolors); //400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers) + dma = new NeoPixelBus(stripSize); //400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers) #endif #endif #if USE_WS2812FX_DMA == 1 // Uses UART1: GPIO1/TXD0/TX, more info: https://github.com/Makuna/NeoPixelBus/wiki/ESP8266-NeoMethods - #if !defined(LED_TYPE_WS2811) - dma = new NeoEsp8266Uart0800KbpsMethod(stripSize, ledcolors); //800 KHz bitstream (most NeoPixel products w/WS2812 LEDs) + #if !defined(LED_TYPE_WS2811) + dma = new NeoPixelBus(stripSize); //800 KHz bitstream (most NeoPixel products w/WS2812 LEDs) #else - dma = new NeoEsp8266Uart0400KbpsMethod(stripSize, ledcolors); //400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers) + dma = new NeoPixelBus(stripSize); //400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers) #endif #endif #if USE_WS2812FX_DMA == 2 // Uses UART2: GPIO2/TXD1/D4, more info: https://github.com/Makuna/NeoPixelBus/wiki/ESP8266-NeoMethods - #if !defined(LED_TYPE_WS2811) - dma = new NeoEsp8266Uart1800KbpsMethod(stripSize, ledcolors); //800 KHz bitstream (most NeoPixel products w/WS2812 LEDs) + #if !defined(LED_TYPE_WS2811) + dma = new NeoPixelBus(stripSize); //800 KHz bitstream (most NeoPixel products w/WS2812 LEDs) #else - dma = new NeoEsp8266Uart1400KbpsMethod(stripSize, ledcolors); //400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers) + dma = new NeoPixelBus(stripSize); //400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers) #endif #endif - dma->Initialize(); + dma->Begin(); } void DMA_Show(void) { - if(dma->IsReadyToUpdate()) { - memcpy(dma->getPixels(), strip->getPixels(), dma->getPixelsSize()); - dma->Update(false); + if(dma->CanShow()) { + memcpy(dma->Pixels(), strip->getPixels(), dma->PixelsSize()); + dma->Dirty(); + dma->Show(); } } #endif -// *************************************************************************** -// Load library "ticker" for blinking status led, mqtt send and save state -// *************************************************************************** -#include Ticker ticker; -#if defined(ENABLE_MQTT) - #if ENABLE_MQTT == 1 - Ticker mqttReconnectTimer; - Ticker wifiReconnectTimer; - #endif - #if defined(ENABLE_HOMEASSISTANT) - Ticker ha_send_data; - #endif -#endif + void tick() { //toggle state @@ -199,9 +183,6 @@ void tick() { decode_results results; #endif -Ticker settings_save_state; -Ticker settings_save_conf; - // *************************************************************************** // Saved state handling in WifiManager // *************************************************************************** @@ -248,99 +229,22 @@ void saveConfigCallback () { // *************************************************************************** #include "spiffs_webserver.h" -// *************************************************************************** -// Include: Request handlers -// *************************************************************************** -#include "request_handlers.h" - -#if defined(ENABLE_TV) -// *************************************************************************** -// Include: TV mode -// *************************************************************************** - #include "mode_tv.h" -#endif - -#if defined(CUSTOM_WS2812FX_ANIMATIONS) // *************************************************************************** // Include: Custom animations // *************************************************************************** - #include "mode_custom_ws2812fx_animations.h" // Add animations in this file -#endif +#include "mode_custom_ws2812fx_animations.h" // Add animations in this file -// function to Initialize the strip -void initStrip(uint16_t stripSize = WS2812FXStripSettings.stripSize, char RGBOrder[5] = WS2812FXStripSettings.RGBOrder, uint8_t pin = WS2812FXStripSettings.pin, uint8_t fxoptions = WS2812FXStripSettings.fxoptions ){ - DBG_OUTPUT_PORT.println("Initializing strip!"); -/*#if defined(USE_WS2812FX_DMA) - if (dma != NULL) { - delete(dma); - } -#endif*/ - if (strip != NULL) { - delete(strip); - WS2812FXStripSettings.stripSize = stripSize; - strcpy(WS2812FXStripSettings.RGBOrder, RGBOrder); - WS2812FXStripSettings.pin = pin; - WS2812FXStripSettings.fxoptions = fxoptions; - } -#if defined(ENABLE_E131) +// *************************************************************************** +// Include: helper functions +// *************************************************************************** +#include "helper_functions.h" -#endif - if (ledstates != NULL) { - delete(ledstates); - } - ledstates = new uint8_t [WS2812FXStripSettings.stripSize]; - -#if !defined(LED_TYPE_WS2811) - strip = new WS2812FX(stripSize, pin, checkRGBOrder(RGBOrder) + NEO_KHZ800); -#else - strip = new WS2812FX(stripSize, pin, checkRGBOrder(RGBOrder) + NEO_KHZ400); -#endif - // Parameter 1 = number of pixels in strip - // Parameter 2 = Arduino pin number (most are valid) - // Parameter 3 = pixel type flags, add together as needed: - // NEO_KHZ800 800 KHz bitstream (most NeoPixel products w/WS2812 LEDs) - // NEO_KHZ400 400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers) - // NEO_GRB Pixels are wired for GRB bitstream (most NeoPixel products) - // NEO_RGB Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2) - - // IMPORTANT: To reduce NeoPixel burnout risk, add 1000 uF capacitor across - // pixel power leads, add 300 - 500 Ohm resistor on first pixel's data input - // and minimize distance between Arduino and first pixel. Avoid connecting - // on a live circuit...if you must, connect GND first. - strip->init(); - #if defined(USE_WS2812FX_DMA) - initDMA(stripSize); - strip->setCustomShow(DMA_Show); - #endif - strip->setBrightness(brightness); -//parameters: index, start, stop, mode, color, speed, options - strip->setSegment(0, 0, stripSize - 1, ws2812fx_mode, hex_colors, convertSpeed(ws2812fx_speed), fxoptions); -#if defined(CUSTOM_WS2812FX_ANIMATIONS) - strip->setCustomMode(0, F("Fire 2012"), myCustomEffect0); -//strip->setCustomMode(1, F("CustEffect"), myCustomEffect1); - gReverseDirection = (WS2812FXStripSettings.fxoptions & 128); -#endif -#if defined(ENABLE_E131) - if (e131 != NULL) { delete(e131); } - e131 = new ESPAsyncE131(END_UNIVERSE - START_UNIVERSE + 1); - float universe_leds = 170.0; // a universe has only 512 (0..511) channels: 3*170 or 4*128 <= 512 - if (strstr(WS2812FXStripSettings.RGBOrder, "W") != NULL) { - //universe_leds = 128.0; - } - float float_enduni = stripSize/universe_leds; - uint8_t END_UNIVERSE = stripSize/universe_leds; - if (float_enduni > END_UNIVERSE) { - END_UNIVERSE = END_UNIVERSE +1; - } - - // if (e131.begin(E131_UNICAST)) // Listen via Unicast - if (e131->begin(E131_MULTICAST, START_UNIVERSE, END_UNIVERSE)) {// Listen via Multicast - DBG_OUTPUT_PORT.println(F("Listening for data...")); - } else { - DBG_OUTPUT_PORT.println(F("*** e131.begin failed ***")); - } -#endif -} +// *************************************************************************** +// Include: other functions +// *************************************************************************** +#include "json_functions.h" +#include "filesystem_functions.h" +#include "request_handlers.h" #if defined(ENABLE_MQTT) void initMqtt() { @@ -381,7 +285,7 @@ void initMqtt() { mqtt_client->setServer(mqtt_host, mqtt_port); mqtt_client->setCallback(onMqttMessage); #endif - #if ENABLE_MQTT == 1 + #if ENABLE_MQTT == 1 DBG_OUTPUT_PORT.printf("AMQTT active: %s:%d\r\n", mqtt_host, mqtt_port); mqtt_client->onConnect(onMqttConnect); mqtt_client->onDisconnect(onMqttDisconnect); @@ -406,9 +310,6 @@ void setup() { delay(500); DBG_OUTPUT_PORT.println(""); DBG_OUTPUT_PORT.println("Starting...Main Setup"); -#if ENABLE_STATE_SAVE == 0 - EEPROM.begin(512); -#endif // set builtin led pin as output pinMode(LED_BUILTIN, OUTPUT); // button pin setup @@ -428,7 +329,7 @@ void setup() { #endif #if defined(POWER_SUPPLY) - pinMode(POWER_SUPPLY, OUTPUT); // output to control external power supply + pinMode(POWER_SUPPLY, OUTPUT); // output to control external power supply #endif // start ticker with 0.5 because we start in AP mode and try to connect @@ -460,37 +361,30 @@ void setup() { #if defined(ENABLE_STATE_SAVE) //Strip Config - #if ENABLE_STATE_SAVE == 1 - (readConfigFS()) ? DBG_OUTPUT_PORT.println("WiFiManager config FS read success!"): DBG_OUTPUT_PORT.println("WiFiManager config FS Read failure!"); - delay(250); - (readStateFS()) ? DBG_OUTPUT_PORT.println("Strip state config FS read Success!") : DBG_OUTPUT_PORT.println("Strip state config FS read failure!"); - #endif - #if ENABLE_STATE_SAVE == 0 - (setConfByConfString(readEEPROM(0, 222)))? DBG_OUTPUT_PORT.println("WiFiManager config EEPROM read success!"): DBG_OUTPUT_PORT.println("WiFiManager config EEPROM Read failure!"); - delay(250); - (setModeByStateString(readEEPROM(256, 66)))? DBG_OUTPUT_PORT.println("Strip state config EEPROM read Success!") : DBG_OUTPUT_PORT.println("Strip state config EEPROM read failure!"); - #endif - char tmp_strip_size[6], tmp_fxoptions[5], tmp_rgbOrder[5]; //needed tempararily for WiFiManager Settings + (readConfigFS()) ? DBG_OUTPUT_PORT.println("WiFiManager config FS read success!"): DBG_OUTPUT_PORT.println("WiFiManager config FS Read failure!"); + delay(250); + (readStateFS()) ? DBG_OUTPUT_PORT.println("Strip state config FS read Success!") : DBG_OUTPUT_PORT.println("Strip state config FS read failure!"); + char _stripSize[6], _fx_options[5], _rgbOrder[5]; //needed tempararily for WiFiManager Settings WiFiManagerParameter custom_hostname("hostname", "Hostname", HOSTNAME, 64, " maxlength=64"); #if defined(ENABLE_MQTT) - char tmp_mqtt_port[6]; //needed tempararily for WiFiManager Settings + char _mqtt_port[6]; //needed tempararily for WiFiManager Settings WiFiManagerParameter custom_mqtt_host("host", "MQTT hostname", mqtt_host, 64, " maxlength=64"); - sprintf(tmp_mqtt_port, "%d", mqtt_port); - WiFiManagerParameter custom_mqtt_port("port", "MQTT port", tmp_mqtt_port, 5, " maxlength=5 type=\"number\""); + sprintf(_mqtt_port, "%d", mqtt_port); + WiFiManagerParameter custom_mqtt_port("port", "MQTT port", _mqtt_port, 5, " maxlength=5 type=\"number\""); WiFiManagerParameter custom_mqtt_user("user", "MQTT user", mqtt_user, 32, " maxlength=32"); WiFiManagerParameter custom_mqtt_pass("pass", "MQTT pass", mqtt_pass, 32, " maxlength=32 type=\"password\""); #endif - sprintf(tmp_strip_size, "%d", WS2812FXStripSettings.stripSize); - WiFiManagerParameter custom_strip_size("strip_size", "Number of LEDs", tmp_strip_size, 4, " maxlength=4 type=\"number\""); + sprintf(_stripSize, "%d", Config.stripSize); + WiFiManagerParameter custom_strip_size("strip_size", "Number of LEDs", _stripSize, 4, " maxlength=4 type=\"number\""); #if !defined(USE_WS2812FX_DMA) char tmp_led_pin[3]; - sprintf(tmp_led_pin, "%d", WS2812FXStripSettings.pin); + sprintf(tmp_led_pin, "%d", Config.pin); WiFiManagerParameter custom_led_pin("led_pin", "LED GPIO", tmp_led_pin, 2, " maxlength=2 type=\"number\""); #endif - sprintf(tmp_rgbOrder, "%s", WS2812FXStripSettings.RGBOrder); - WiFiManagerParameter custom_rgbOrder("rgbOrder", "RGBOrder", tmp_rgbOrder, 4, " maxlength=4"); - sprintf(tmp_fxoptions, "%d", WS2812FXStripSettings.fxoptions); - WiFiManagerParameter custom_fxoptions("fxoptions", "fxOptions", tmp_fxoptions, 3, " maxlength=3"); + sprintf(_rgbOrder, "%s", Config.RGBOrder); + WiFiManagerParameter custom_rgbOrder("rgbOrder", "RGBOrder", _rgbOrder, 4, " maxlength=4"); + sprintf(_fx_options, "%d", segState.options); + WiFiManagerParameter custom_fxoptions("fxoptions", "fxOptions", _fx_options, 3, " maxlength=3"); #endif @@ -504,7 +398,7 @@ void setup() { wifiManager.setAPCallback(configModeCallback); //set config save notify callback wifiManager.setSaveConfigCallback(saveConfigCallback); - + wifiManager.addParameter(&custom_hostname); #if defined(ENABLE_MQTT) //add all your parameters here @@ -519,16 +413,16 @@ void setup() { #endif wifiManager.addParameter(&custom_rgbOrder); wifiManager.addParameter(&custom_fxoptions); - + WiFi.setSleepMode(WIFI_NONE_SLEEP); - + // Uncomment if you want to restart ESP8266 if it cannot connect to WiFi. // Value in brackets is in seconds that WiFiManger waits until restart #if defined(WIFIMGR_PORTAL_TIMEOUT) wifiManager.setConfigPortalTimeout(WIFIMGR_PORTAL_TIMEOUT); #endif - // Order is: IP, Gateway and Subnet + // Order is: IP, Gateway and Subnet #if defined(WIFIMGR_SET_MANUAL_IP) wifiManager.setSTAStaticIPConfig(IPAddress(_ip[0], _ip[1], _ip[2], _ip[3]), IPAddress(_gw[0], _gw[1], _gw[2], _gw[3]), IPAddress(_sn[0], _sn[1], _sn[2], _sn[3])); #endif @@ -540,10 +434,10 @@ void setup() { if (!wifiManager.autoConnect(HOSTNAME)) { DBG_OUTPUT_PORT.println("failed to connect and hit timeout"); //reset and try again, or maybe put it to deep sleep - ESP.reset(); //Will be removed when upgrading to standalone offline McLightingUI version - delay(1000); //Will be removed when upgrading to standalone offline McLightingUI version + //ESP.reset(); //Will be removed when upgrading to standalone offline McLightingUI version + //delay(1000); //Will be removed when upgrading to standalone offline McLightingUI version } - + //save the custom parameters to FS/EEPROM #if defined(ENABLE_STATE_SAVE) strcpy(HOSTNAME, custom_hostname.getValue()); @@ -554,34 +448,19 @@ void setup() { strcpy(mqtt_user, custom_mqtt_user.getValue()); strcpy(mqtt_pass, custom_mqtt_pass.getValue()); #endif - strcpy(tmp_strip_size, custom_strip_size.getValue()); - WS2812FXStripSettings.stripSize = constrain(atoi(custom_strip_size.getValue()), 1, MAXLEDS); + strcpy(_stripSize, custom_strip_size.getValue()); + Config.stripSize = constrain(atoi(custom_strip_size.getValue()), 1, MAXLEDS); #if !defined(USE_WS2812FX_DMA) checkPin(atoi(custom_led_pin.getValue())); #endif - strcpy(tmp_rgbOrder, custom_rgbOrder.getValue()); - checkRGBOrder(tmp_rgbOrder); - WS2812FXStripSettings.fxoptions = atoi(custom_fxoptions.getValue()); + strcpy(_rgbOrder, custom_rgbOrder.getValue()); + checkRGBOrder(_rgbOrder); + segState.options = atoi(custom_fxoptions.getValue()); if (updateConfig) { - #if ENABLE_STATE_SAVE == 1 - (writeConfigFS(updateConfig)) ? DBG_OUTPUT_PORT.println("WiFiManager config FS Save success!"): DBG_OUTPUT_PORT.println("WiFiManager config FS Save failure!"); - #endif - #if ENABLE_STATE_SAVE == 0 - char last_conf[223]; - DBG_OUTPUT_PORT.println("Saving WiFiManager config"); - #if defined(ENABLE_MQTT) - snprintf(last_conf, sizeof(last_conf), "CNF|%64s|%64s|%5d|%32s|%32s|%4d|%2d|%4s|%3d", HOSTNAME, mqtt_host, mqtt_port, mqtt_user, mqtt_pass, WS2812FXStripSettings.stripSize, WS2812FXStripSettings.pin, WS2812FXStripSettings.RGBOrder, WS2812FXStripSettings.fxoptions); - #else - snprintf(last_conf, sizeof(last_conf), "CNF|%64s|%64s|%5d|%32s|%32s|%4d|%2d|%4s|%3d", HOSTNAME, "", "", "", "", WS2812FXStripSettings.stripSize, WS2812FXStripSettings.pin, WS2812FXStripSettings.RGBOrder, WS2812FXStripSettings.fxoptions); - #endif - last_conf[sizeof(last_conf)] = 0x00; - writeEEPROM(0, 222, last_conf); - EEPROM.commit(); - updateConfig = false; - #endif + (writeConfigFS(updateConfig)) ? DBG_OUTPUT_PORT.println("WiFiManager config FS Save success!"): DBG_OUTPUT_PORT.println("WiFiManager config FS Save failure!"); } #endif - + //if you get here you have connected to the WiFi DBG_OUTPUT_PORT.println("connected...yeey :)"); ticker.detach(); @@ -605,11 +484,11 @@ void setup() { // No authentication by default // ArduinoOTA.setPassword("admin"); - + // Password can be set with it's md5 value as well // MD5(admin) = 21232f297a57a5a743894a0e4a801fc3 // ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3"); - + ArduinoOTA.onStart([]() { DBG_OUTPUT_PORT.println("Arduino OTA: Start updating"); }); @@ -627,7 +506,7 @@ void setup() { else if (error == OTA_RECEIVE_ERROR) DBG_OUTPUT_PORT.println("Arduino OTA: Receive Failed"); else if (error == OTA_END_ERROR) DBG_OUTPUT_PORT.println("Arduino OTA: End Failed"); }); - + ArduinoOTA.begin(); DBG_OUTPUT_PORT.println(""); #endif @@ -644,7 +523,7 @@ void setup() { wifiConnectHandler = WiFi.onStationModeGotIP(onWifiConnect); wifiDisconnectHandler = WiFi.onStationModeDisconnected(onWifiDisconnect); #endif - + // *************************************************************************** // Setup: MDNS responder // *************************************************************************** @@ -657,11 +536,11 @@ void setup() { DBG_OUTPUT_PORT.print("Use http://"); DBG_OUTPUT_PORT.print(HOSTNAME); DBG_OUTPUT_PORT.println(".local/ when you have Bonjour installed."); - +#if !defined(USE_HTML_MIN_GZ) DBG_OUTPUT_PORT.print("New users: Open http://"); DBG_OUTPUT_PORT.print(WiFi.localIP()); DBG_OUTPUT_PORT.println("/upload to upload the webpages first."); - +#endif DBG_OUTPUT_PORT.println(""); // *************************************************************************** @@ -679,8 +558,6 @@ void setup() { MDNS.addService("http", "tcp", 80); } - prevmode = mode; - #if defined(ENABLE_BUTTON_GY33) tcs.setConfig(MCU_LED_06, MCU_WHITE_ON); // delay(2000); @@ -688,12 +565,12 @@ void setup() { #endif #if defined(ENABLE_REMOTE) irrecv.enableIRIn(); // Start the receiver - snprintf(last_state, sizeof(last_state), "STA|%2d|%3d|%3d|%3d|%3d|%3d|%3d|%3d|%3d|%3d|%3d|%3d|%3d|%3d|%3d|%3d", mode, ws2812fx_mode, ws2812fx_speed, brightness, main_color.red, main_color.green, main_color.blue, main_color.white, back_color.red, back_color.green, back_color.blue, back_color.white, xtra_color.red, xtra_color.green, xtra_color.blue,xtra_color.white); - last_state[sizeof(last_state)]= 0x00; #endif - DBG_OUTPUT_PORT.println("finished Main Setup!"); - + fx_speed = segState.speed[State.segment]; + brightness_trans = State.brightness; initStrip(); + strip->setBrightness(0); + DBG_OUTPUT_PORT.println("finished Main Setup!"); } // *************************************************************************** @@ -706,7 +583,7 @@ void loop() { #if defined(ENABLE_BUTTON_GY33) button_gy33(); - #endif + #endif server.handleClient(); webSocket.loop(); @@ -727,7 +604,7 @@ void loop() { WiFi.disconnect(); WiFi.setSleepMode(WIFI_NONE_SLEEP); WiFi.mode(WIFI_STA); - WiFi.hostname(HOSTNAME); + WiFi.hostname(HOSTNAME); WiFi.begin(); } else { if ((strlen(mqtt_host) != 0) && (mqtt_port != 0) && (mqtt_reconnect_retries < MQTT_MAX_RECONNECT_TRIES)) { @@ -748,148 +625,119 @@ void loop() { if (new_ha_mqtt_msg) sendState(); #endif #endif - + // *************************************************************************** // Simple statemachine that handles the different modes - // *************************************************************************** - if ((mode != OFF) && (mode != TV) && (mode != E131)) { // strip->start() is only needed for modes with WS2812FX functionality - if(!strip->isRunning()) strip->start(); - } + // *************************************************************************** - if ((mode == OFF) || (mode == TV) || (mode == E131)) { + if ((State.mode == OFF) && ((strip->getBrightness() == 0) || !Config.transEffect)) { if(strip->isRunning()) { - strip->strip_off(); // Workaround: to be shure, + strip->strip_off(); // Workaround: to be shure, delay(10); // that strip is really off. Sometimes strip->stop isn't enought - strip->stop(); // should clear memory + strip->stop(); // should clear memory + for (uint8_t i = 0; i < Config.segments; i++) { + autoCount[i] = 0; + autoDelay[i] = 0; + } } else { - if (prevmode != mode) { // Start temporarily to clear strip + if (prevmode != State.mode) { // Start temporarily to clear strip strip->start(); - strip->strip_off(); // Workaround: to be shure, + strip->strip_off(); // Workaround: to be shure, delay(10); // that strip is really off. Sometimes strip->stop isn't enought - strip->stop(); // should clear memory + strip->stop(); // should clear memory + for (uint8_t i = 0; i < Config.segments; i++) { + autoCount[i] = 0; + autoDelay[i] = 0; + } } } } - - if (( mode == AUTO) || (mode == HOLD)) { // strip->service() is only needed for modes with WS2812FX functionality - strip->service(); + + if (State.mode == OFF) { + if (prevmode != State.mode) { + #if defined(ENABLE_MQTT) + snprintf(mqtt_buf, sizeof(mqtt_buf), "OK =off", ""); + #endif + #if defined(POWER_SUPPLY) + digitalWrite(POWER_SUPPLY, !POWER_ON); // power off -> external power supply + #endif + if (Config.transEffect) { + brightness_trans = 0; + } + } } - - if ((prevmode == AUTO) && (mode != AUTO)) { handleAutoStop(); } // stop auto mode - - if (mode == OFF) { - #if defined(ENABLE_MQTT) - if (prevmode != mode) { snprintf(mqtt_buf, sizeof(mqtt_buf), "OK =off", ""); } - #endif - #if defined(POWER_SUPPLY) - if (prevmode != mode) {digitalWrite(POWER_SUPPLY, !POWER_ON); } // power off -> external power supply - #endif - } - #if defined(POWER_SUPPLY) - if (mode != OFF) { - if (prevmode != mode) {digitalWrite(POWER_SUPPLY, POWER_ON); } // power on -> external power supply + if (State.mode != OFF) { + if (prevmode != State.mode) {digitalWrite(POWER_SUPPLY, POWER_ON); } // power on -> external power supply } #endif - if (mode == AUTO) { - if (prevmode != mode) { - handleAutoStart(); + if (State.mode == SET) { + State.mode = HOLD; + // Segment + if (prevsegment != State.segment) { #if defined(ENABLE_MQTT) - snprintf(mqtt_buf, sizeof(mqtt_buf), "OK =auto", ""); + snprintf(mqtt_buf, sizeof(mqtt_buf), "OK Ss%i", State.segment); #endif + //prevsegment = State.segment; } - } - - #if defined(ENABLE_TV) - if (mode == TV) { - handleTV(); + // Mode + if (segState.mode[State.segment] != fx_mode) { + segState.mode[State.segment] = fx_mode; #if defined(ENABLE_MQTT) - if (prevmode != mode) { snprintf(mqtt_buf, sizeof(mqtt_buf), "OK =tv", ""); } + snprintf(mqtt_buf, sizeof(mqtt_buf), "OK /%i", segState.mode[State.segment]); #endif + strip->strip_off(); + autoCount[State.segment] = 0; + autoDelay[State.segment] = 0; + //strip->setSpeed(State.segment, segState.speed[State.segment]); + //strip->setColors(State.segment, segState.colors[State.segment]); + strip->setMode(State.segment, segState.mode[State.segment]); + //strip->trigger; } - #endif - - #if defined(ENABLE_E131) - if (mode == E131) { - handleE131(); + //Color + /*if (memcmp(segmentState.colors[prevsegment)], strip->getColors(prevsegment), sizeof(segmentState.colors[prevsegment)])) != 0) { + convertColors(); + }*/ + // Brightness + if (strip->getBrightness() != State.brightness) { #if defined(ENABLE_MQTT) - if (prevmode != mode) { snprintf(mqtt_buf, sizeof(mqtt_buf), "OK =e131", ""); } + snprintf(mqtt_buf, sizeof(mqtt_buf), "OK %%%i", State.brightness); + #endif + brightness_trans = State.brightness; + } + // Speed + if (fx_speed != segState.speed[State.segment]) { + #if defined(ENABLE_MQTT) + snprintf(mqtt_buf, sizeof(mqtt_buf), "OK ?%i", segState.speed[prevsegment]); #endif } - #endif - - if (mode == INIT_STRIP) { - mode = prevmode; - //ws2812fx_mode = strip->getMode(); - strip->strip_off(); - delay(10); - if(strip->isRunning()) strip->stop(); - initStrip(); - prevmode = INIT_STRIP; + prevmode = SET; } - - if (mode == SET_ALL) { - mode = HOLD; - if ((prevmode == OFF) || (prevmode == AUTO) || (prevmode == TV) || (prevmode == E131)) { setModeByStateString(last_state); } - #if defined(ENABLE_MQTT) - snprintf(mqtt_buf, sizeof(mqtt_buf), "OK /%i", ws2812fx_mode); - #endif - strip->setMode(ws2812fx_mode); - convertColors(); - strip->setColors(0, hex_colors); - strip->setSpeed(convertSpeed(ws2812fx_speed)); - strip->setBrightness(brightness); - prevmode = SET_ALL; - strip->trigger(); - } - - if (mode == SET_MODE) { - mode = HOLD; - #if defined(ENABLE_MQTT) - snprintf(mqtt_buf, sizeof(mqtt_buf), "OK /%i", ws2812fx_mode); - #endif - strip->setMode(ws2812fx_mode); - prevmode = SET_MODE; - strip->trigger(); - } - - if (mode == SET_COLOR) { - convertColors(); - strip->setColors(0, hex_colors); - mode = prevmode; - prevmode = SET_COLOR; - //DBG_OUTPUT_PORT.printf("mode: %d\r\n", mode); - if (mode == HOLD) strip->trigger(); - } - if (mode == SET_SPEED) { - #if defined(ENABLE_MQTT) - snprintf(mqtt_buf, sizeof(mqtt_buf), "OK ?%i", ws2812fx_speed); - #endif - strip->setSpeed(convertSpeed(ws2812fx_speed)); - mode = prevmode; - prevmode = SET_SPEED; - if (mode == HOLD) strip->trigger(); - } - if (mode == SET_BRIGHTNESS) { - #if defined(ENABLE_MQTT) - snprintf(mqtt_buf, sizeof(mqtt_buf), "OK %%%i", brightness); - #endif - strip->setBrightness(brightness); - mode = prevmode; - prevmode = SET_BRIGHTNESS; - //DBG_OUTPUT_PORT.printf("mode: %d\r\n", mode); - if (mode == HOLD) strip->trigger(); - } - - if (prevmode != mode) { - if ((prevmode != AUTO) && (prevmode != INIT_STRIP)) { // do not save if AUTO Mode was set - #if defined(ENABLE_STATE_SAVE) - if(!settings_save_state.active()) settings_save_state.once(3, tickerSaveState); - #endif - snprintf(last_state, sizeof(last_state), "STA|%2d|%3d|%3d|%3d|%3d|%3d|%3d|%3d|%3d|%3d|%3d|%3d|%3d|%3d|%3d|%3d", prevmode, ws2812fx_mode, ws2812fx_speed, brightness, main_color.red, main_color.green, main_color.blue, main_color.white, back_color.red, back_color.green, back_color.blue, back_color.white, xtra_color.red, xtra_color.green, xtra_color.blue, xtra_color.white); - last_state[sizeof(last_state) - 1] = 0x00; + + if ((State.mode == HOLD) || ((State.mode == OFF) && (strip->getBrightness() > 0) && Config.transEffect)) { + if(!strip->isRunning()) strip->start(); + strip->service(); + bool playE131 = false; + for (uint8_t i = 0; i < Config.segments; i++) { + if (segState.mode[i] == FX_MODE_CUSTOM_0) { handleAutoPlay(i); } + if (segState.mode[i] == FX_MODE_CUSTOM_3) { playE131 = true; } } + if (playE131 == true) { handleE131Play(); } + } + + if (prevmode != State.mode) { + if (segState.mode[prevsegment] != FX_MODE_CUSTOM_0) { + convertColors(); + if (memcmp(hexcolors_trans, strip->getColors(prevsegment), sizeof(hexcolors_trans)) != 0) { + DBG_OUTPUT_PORT.println("Color changed!"); + trans_cnt_max = convertColorsFade(prevsegment); + trans_cnt = 1; + memcpy(segState.colors[prevsegment], hexcolors_trans, sizeof(hexcolors_trans)); + } + strip->setSpeed(prevsegment, convertSpeed(fx_speed)); + } + //strip->setBrightness(brightness_actual); #if defined(ENABLE_MQTT) #if ENABLE_MQTT == 0 mqtt_client->publish(mqtt_outtopic, mqtt_buf); @@ -898,44 +746,113 @@ void loop() { mqtt_client->publish(mqtt_outtopic, qospub, false, mqtt_buf); #endif #if defined(ENABLE_HOMEASSISTANT) - if(!ha_send_data.active()) ha_send_data.once(3, tickerSendState); + if(ha_send_data.active()) ha_send_data.detach(); + ha_send_data.once(5, tickerSendState); #endif #endif } + + prevmode = State.mode; + #if defined(ENABLE_STATE_SAVE) if (updateState){ - #if ENABLE_STATE_SAVE == 1 - (writeStateFS(updateState)) ? DBG_OUTPUT_PORT.println(" State FS Save Success!") : DBG_OUTPUT_PORT.println("State FS Save failure!"); - #endif - #if ENABLE_STATE_SAVE == 0 - writeEEPROM(384, 66, last_state); // 384 --> last_state (reserved 66 bytes) - EEPROM.commit(); - updateState = false; - settings_save_state.detach(); - #endif + (writeStateFS(updateState)) ? DBG_OUTPUT_PORT.println("State FS Save Success!") : DBG_OUTPUT_PORT.println("State FS Save failure!"); + } + if (updateSegState) { + (writeSegmentStateFS(updateSegState, prevsegment)) ? DBG_OUTPUT_PORT.println("Segment State FS Save Success!") : DBG_OUTPUT_PORT.println("Segment State FS Save failure!"); } if (updateConfig) { - #if ENABLE_STATE_SAVE == 1 (writeConfigFS(updateConfig)) ? DBG_OUTPUT_PORT.println("Config FS Save success!"): DBG_OUTPUT_PORT.println("Config FS Save failure!"); - #endif - #if ENABLE_STATE_SAVE == 0 - char last_conf[223]; - #if defined(ENABLE_MQTT) - snprintf(last_conf, sizeof(last_conf), "CNF|%64s|%64s|%5d|%32s|%32s|%4d|%2d|%4s|%3d", HOSTNAME, mqtt_host, mqtt_port, mqtt_user, mqtt_pass, WS2812FXStripSettings.stripSize, WS2812FXStripSettings.pin, WS2812FXStripSettings.RGBOrder, WS2812FXStripSettings.fxoptions); - #else - snprintf(last_conf, sizeof(last_conf), "CNF|%64s|%64s|%5d|%32s|%32s|%4d|%2d|%4s|%3d", HOSTNAME, "", "", "", "", WS2812FXStripSettings.stripSize, WS2812FXStripSettings.pin, WS2812FXStripSettings.RGBOrder, WS2812FXStripSettings.fxoptions); - #endif - last_conf[sizeof(last_conf) - 1] = 0x00; - writeEEPROM(0, 222, last_conf); - EEPROM.commit(); - updateConfig = false; - settings_save_conf.detach(); - #endif } #endif - - prevmode = mode; - + + // Async color transition + if ((segState.mode[prevsegment] != FX_MODE_CUSTOM_0) && (memcmp(hexcolors_trans, strip->getColors(prevsegment), sizeof(hexcolors_trans)) != 0)) { + if (Config.transEffect) { + if ((trans_cnt > 0) && (trans_cnt < trans_cnt_max)) { + if (colorFadeDelay <= millis()) { + uint32_t _hexcolors_new[3] = {}; + _hexcolors_new[0] = trans(hexcolors_trans[0], strip->getColors(prevsegment)[0], trans_cnt, trans_cnt_max); + _hexcolors_new[1] = trans(hexcolors_trans[1], strip->getColors(prevsegment)[1], trans_cnt, trans_cnt_max); + _hexcolors_new[2] = trans(hexcolors_trans[2], strip->getColors(prevsegment)[2], trans_cnt, trans_cnt_max); + strip->setColors(prevsegment, _hexcolors_new); + trans_cnt++; + colorFadeDelay = millis() + TRANS_COLOR_DELAY; + if (State.mode == HOLD) strip->trigger(); + } + } else if (trans_cnt >= trans_cnt_max) { + strip->setColors(prevsegment, hexcolors_trans); + if (State.mode == HOLD) strip->trigger(); + DBG_OUTPUT_PORT.println("Color transition finished!"); + trans_cnt = 0; + } + } else { + strip->setColors(prevsegment, hexcolors_trans); + if (State.mode == HOLD) strip->trigger(); + } + } + // Async speed transition + if ((segState.mode[prevsegment] != FX_MODE_CUSTOM_0) && (segState.mode[prevsegment] != FX_MODE_CUSTOM_3) && (fx_speed != segState.speed[prevsegment])) { + if (Config.transEffect) { + //if (true == false) { // disabled for the moment + if (speedFadeDelay <= millis()) { + //DBG_OUTPUT_PORT.print("Speed trans actual: "); + if (fx_speed < segState.speed[prevsegment]) { + fx_speed++; + } + if (fx_speed > segState.speed[prevsegment]) { + fx_speed--; + } + //DBG_OUTPUT_PORT.println(fx_speed); + speedFadeDelay = millis() + TRANS_DELAY; + strip->setSpeed(prevsegment, convertSpeed(fx_speed)); + if (State.mode == HOLD) strip->trigger(); + } + } else { + fx_speed = segState.speed[prevsegment]; + //DBG_OUTPUT_PORT.print("Speed actual: "); + strip->setSpeed(prevsegment, convertSpeed(fx_speed)); + //DBG_OUTPUT_PORT.println(fx_speed); + if (State.mode == HOLD) strip->trigger(); + } + } + + // Async brightness transition + if (strip->getBrightness() != brightness_trans) { + if (Config.transEffect) { + if(brightnessFadeDelay <= millis()) { + if (strip->getBrightness() < brightness_trans) { + strip->increaseBrightness(1); + } + if (strip->getBrightness() > brightness_trans) { + strip->decreaseBrightness(1); + } + brightnessFadeDelay = millis() + TRANS_DELAY; + if (State.mode == HOLD) strip->trigger(); + } + } else { + brightness_trans = State.brightness; + strip->setBrightness(brightness_trans); + } + } + + // Segment change only if color and speed transitions are finished, because they are segment specific + if (prevsegment != State.segment) { + DBG_OUTPUT_PORT.println("Segment not equal"); + //if ((segState.mode[State.segment] == FX_MODE_CUSTOM_0) || (segState.mode[State.segment] == FX_MODE_CUSTOM_2) || (segState.mode[prevsegment] == FX_MODE_CUSTOM_0)) { + if ((segState.mode[State.segment] == FX_MODE_CUSTOM_0) || (segState.mode[prevsegment] == FX_MODE_CUSTOM_0)) { + fx_speed = segState.speed[State.segment]; + DBG_OUTPUT_PORT.printf("Switched segment from: %i to %i", prevsegment, State.segment); + prevsegment = State.segment; + } else if ((memcmp(hexcolors_trans, strip->getColors(prevsegment), sizeof(hexcolors_trans)) == 0) && (fx_speed == segState.speed[prevsegment])) { + memcpy(hexcolors_trans, segState.colors[State.segment], sizeof(hexcolors_trans)); + fx_speed = segState.speed[State.segment]; + DBG_OUTPUT_PORT.printf("Switched segment from: %i to %i\r\n", prevsegment, State.segment); + prevsegment = State.segment; + } + } + + #if defined(ENABLE_REMOTE) handleRemote(); #endif diff --git a/Arduino/McLighting/WS2812FX.cpp b/Arduino/McLighting/WS2812FX.cpp new file mode 100644 index 0000000..dad49ac --- /dev/null +++ b/Arduino/McLighting/WS2812FX.cpp @@ -0,0 +1,1647 @@ +/* + WS2812FX.cpp - Library for WS2812 LED effects. + + Harm Aldick - 2016 + www.aldick.org + + + FEATURES + * A lot of blinken modes and counting + * WS2812FX can be used as drop-in replacement for Adafruit NeoPixel Library + + NOTES + * Uses the Adafruit NeoPixel library. Get it here: + https://github.com/adafruit/Adafruit_NeoPixel + + + + LICENSE + + The MIT License (MIT) + + Copyright (c) 2016 Harm Aldick + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + + + CHANGELOG + + 2016-05-28 Initial beta release + 2016-06-03 Code cleanup, minor improvements, new modes + 2016-06-04 2 new fx, fixed setColor (now also resets _mode_color) + 2017-02-02 added external trigger functionality (e.g. for sound-to-light) + 2017-02-02 removed "blackout" on mode, speed or color-change + 2017-09-26 implemented segment and reverse features + 2017-11-16 changed speed calc, reduced memory footprint + 2018-02-24 added hooks for user created custom effects +*/ + +#include "WS2812FX.h" + +void WS2812FX::init() { + resetSegmentRuntimes(); + Adafruit_NeoPixel::begin(); +} + +// void WS2812FX::timer() { +// for (int j=0; j < 1000; j++) { +// uint16_t delay = (this->*_mode[SEGMENT.mode])(); +// } +// } + +void WS2812FX::service() { + if(_running || _triggered) { + unsigned long now = millis(); // Be aware, millis() rolls over every 49 days + bool doShow = false; + for(uint8_t i=0; i < _num_segments; i++) { + _segment_index = i; + CLR_FRAME; + if(now > SEGMENT_RUNTIME.next_time || _triggered) { + SET_FRAME; + doShow = true; + uint16_t delay = (this->*_mode[SEGMENT.mode])(); + SEGMENT_RUNTIME.next_time = now + max(delay, SPEED_MIN); + SEGMENT_RUNTIME.counter_mode_call++; + } + } + if(doShow) { + delay(1); // for ESP32 (see https://forums.adafruit.com/viewtopic.php?f=47&t=117327) + show(); + } + _triggered = false; + } +} + +// overload setPixelColor() functions so we can use gamma correction +// (see https://learn.adafruit.com/led-tricks-gamma-correction/the-issue) +void WS2812FX::setPixelColor(uint16_t n, uint32_t c) { + if(IS_GAMMA) { + uint8_t w = (c >> 24) & 0xFF; + uint8_t r = (c >> 16) & 0xFF; + uint8_t g = (c >> 8) & 0xFF; + uint8_t b = c & 0xFF; + Adafruit_NeoPixel::setPixelColor(n, gamma8(r), gamma8(g), gamma8(b), gamma8(w)); + } else { + Adafruit_NeoPixel::setPixelColor(n, c); + } +} + +void WS2812FX::setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b) { + if(IS_GAMMA) { + Adafruit_NeoPixel::setPixelColor(n, gamma8(r), gamma8(g), gamma8(b)); + } else { + Adafruit_NeoPixel::setPixelColor(n, r, g, b); + } +} + +void WS2812FX::setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b, uint8_t w) { + if(IS_GAMMA) { + Adafruit_NeoPixel::setPixelColor(n, gamma8(r), gamma8(g), gamma8(b), gamma8(w)); + } else { + Adafruit_NeoPixel::setPixelColor(n, r, g, b, w); + } +} + +void WS2812FX::copyPixels(uint16_t dest, uint16_t src, uint16_t count) { + uint8_t *pixels = getPixels(); + uint8_t bytesPerPixel = getNumBytesPerPixel(); // 3=RGB, 4=RGBW + + memmove(pixels + (dest * bytesPerPixel), pixels + (src * bytesPerPixel), count * bytesPerPixel); +} + +// overload show() functions so we can use custom show() +void WS2812FX::show(void) { + if(customShow == NULL) { + Adafruit_NeoPixel::show(); + } else { + customShow(); + } +} + +void WS2812FX::start() { + resetSegmentRuntimes(); + _running = true; +} + +void WS2812FX::stop() { + _running = false; + strip_off(); +} + +void WS2812FX::pause() { + _running = false; +} + +void WS2812FX::resume() { + _running = true; +} + +void WS2812FX::trigger() { + _triggered = true; +} + +void WS2812FX::setMode(uint8_t m) { + setMode(0, m); +} + +void WS2812FX::setMode(uint8_t seg, uint8_t m) { + resetSegmentRuntime(seg); + _segments[seg].mode = constrain(m, 0, MODE_COUNT - 1); +} + +void WS2812FX::setOptions(uint8_t seg, uint8_t o) { + _segments[seg].options = o; +} + +void WS2812FX::setSpeed(uint16_t s) { + setSpeed(0, s); +} + +void WS2812FX::setSpeed(uint8_t seg, uint16_t s) { +// resetSegmentRuntime(seg); + _segments[seg].speed = constrain(s, SPEED_MIN, SPEED_MAX); +} + +void WS2812FX::increaseSpeed(uint8_t s) { + uint16_t newSpeed = constrain(SEGMENT.speed + s, SPEED_MIN, SPEED_MAX); + setSpeed(newSpeed); +} + +void WS2812FX::decreaseSpeed(uint8_t s) { + uint16_t newSpeed = constrain(SEGMENT.speed - s, SPEED_MIN, SPEED_MAX); + setSpeed(newSpeed); +} + +void WS2812FX::setColor(uint8_t r, uint8_t g, uint8_t b) { + setColor(((uint32_t)r << 16) | ((uint32_t)g << 8) | b); +} + +void WS2812FX::setColor(uint8_t r, uint8_t g, uint8_t b, uint8_t w) { + setColor((((uint32_t)w << 24)| ((uint32_t)r << 16) | ((uint32_t)g << 8)| ((uint32_t)b))); +} + +void WS2812FX::setColor(uint32_t c) { + setColor(0, c); +} + +void WS2812FX::setColor(uint8_t seg, uint32_t c) { +// resetSegmentRuntime(seg); + _segments[seg].colors[0] = c; +} + +void WS2812FX::setColors(uint8_t seg, uint32_t* c) { +// resetSegmentRuntime(seg); + for(uint8_t i=0; i 1); + + _segments[0].start = 0; + _segments[0].stop = Adafruit_NeoPixel::numLEDs - 1; +} + +void WS2812FX::increaseLength(uint16_t s) { + s = _segments[0].stop - _segments[0].start + 1 + s; + setLength(s); +} + +void WS2812FX::decreaseLength(uint16_t s) { + if (s > _segments[0].stop - _segments[0].start + 1) s = 1; + s = _segments[0].stop - _segments[0].start + 1 - s; + + for(uint16_t i=_segments[0].start + s; i <= (_segments[0].stop - _segments[0].start + 1); i++) { + setPixelColor(i, 0); + } + show(); + + setLength(s); +} + +boolean WS2812FX::isRunning() { + return _running; +} + +boolean WS2812FX::isTriggered() { + return _triggered; +} + +boolean WS2812FX::isFrame() { + return isFrame(0); +} + +boolean WS2812FX::isFrame(uint8_t segIndex) { + return (_segment_runtimes[segIndex].aux_param2 & FRAME); +} + +boolean WS2812FX::isCycle() { + return isCycle(0); +} + +boolean WS2812FX::isCycle(uint8_t segIndex) { + return (_segment_runtimes[segIndex].aux_param2 & CYCLE); +} + +uint8_t WS2812FX::getMode(void) { + return getMode(0); +} + +uint8_t WS2812FX::getMode(uint8_t seg) { + return _segments[seg].mode; +} + +uint16_t WS2812FX::getSpeed(void) { + return getSpeed(0); +} + +uint16_t WS2812FX::getSpeed(uint8_t seg) { + return _segments[seg].speed; +} + + +uint8_t WS2812FX::getOptions(uint8_t seg) { + return _segments[seg].options; +} + +uint16_t WS2812FX::getLength(void) { + return numPixels(); +} + +uint16_t WS2812FX::getNumBytes(void) { + return numBytes; +} + +uint8_t WS2812FX::getNumBytesPerPixel(void) { + return (wOffset == rOffset) ? 3 : 4; // 3=RGB, 4=RGBW +} + +uint8_t WS2812FX::getModeCount(void) { + return MODE_COUNT; +} + +uint8_t WS2812FX::getNumSegments(void) { + return _num_segments; +} + +void WS2812FX::setNumSegments(uint8_t n) { + _num_segments = n; +} + +uint32_t WS2812FX::getColor(void) { + return getColor(0); +} + +uint32_t WS2812FX::getColor(uint8_t seg) { + return _segments[seg].colors[0]; +} + +uint32_t* WS2812FX::getColors(uint8_t seg) { + return _segments[seg].colors; +} + +uint8_t WS2812FX::getSegmentIndex(void) { + return _segment_index; +} + +WS2812FX::Segment* WS2812FX::getSegment(void) { + return &_segments[_segment_index]; +} + +WS2812FX::Segment* WS2812FX::getSegment(uint8_t seg) { + return &_segments[seg]; +} + +WS2812FX::Segment* WS2812FX::getSegments(void) { + return _segments; +} + +WS2812FX::Segment_runtime* WS2812FX::getSegmentRuntime(void) { + return &_segment_runtimes[_segment_index]; +} + +WS2812FX::Segment_runtime* WS2812FX::getSegmentRuntime(uint8_t seg) { + return &_segment_runtimes[seg]; +} + +WS2812FX::Segment_runtime* WS2812FX::getSegmentRuntimes(void) { + return _segment_runtimes; +} + +const __FlashStringHelper* WS2812FX::getModeName(uint8_t m) { + if(m < MODE_COUNT) { + return _names[m]; + } else { + return F(""); + } +} + +void WS2812FX::setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t mode, uint32_t color, uint16_t speed, bool reverse) { + uint32_t colors[] = {color, 0, 0}; + setSegment(n, start, stop, mode, colors, speed, reverse); +} + +void WS2812FX::setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t mode, uint32_t color, uint16_t speed, uint8_t options) { + uint32_t colors[] = {color, 0, 0}; + setSegment(n, start, stop, mode, colors, speed, options); +} + +void WS2812FX::setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t mode, const uint32_t colors[], uint16_t speed, bool reverse) { + setSegment(n, start, stop, mode, colors, speed, (uint8_t)(reverse ? REVERSE : NO_OPTIONS)); +} + +void WS2812FX::setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t mode, const uint32_t colors[], uint16_t speed, uint8_t options) { + if(n < (sizeof(_segments) / sizeof(_segments[0]))) { + if(n + 1 > _num_segments) _num_segments = n + 1; + _segments[n].start = start; + _segments[n].stop = stop; + _segments[n].mode = mode; + _segments[n].speed = speed; + _segments[n].options = options; + + for(uint8_t i=0; i g -> b -> back to r + * Inspired by the Adafruit examples. + */ +uint32_t WS2812FX::color_wheel(uint8_t pos) { + pos = 255 - pos; + if(pos < 85) { + return ((uint32_t)(255 - pos * 3) << 16) | ((uint32_t)(0) << 8) | (pos * 3); + } else if(pos < 170) { + pos -= 85; + return ((uint32_t)(0) << 16) | ((uint32_t)(pos * 3) << 8) | (255 - pos * 3); + } else { + pos -= 170; + return ((uint32_t)(pos * 3) << 16) | ((uint32_t)(255 - pos * 3) << 8) | (0); + } +} + + +/* + * 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; + uint8_t x = 0; + uint8_t y = 0; + uint8_t d = 0; + + while(d < 42) { + r = random8(); + x = abs(pos - r); + y = 255 - x; + d = min(x, y); + } + + return r; +} + +// fast 8-bit random number generator shamelessly borrowed from FastLED +uint8_t WS2812FX::random8() { + _rand16seed = (_rand16seed * 2053) + 13849; + return (uint8_t)((_rand16seed + (_rand16seed >> 8)) & 0xFF); +} + +// note random8(lim) generates numbers in the range 0 to (lim -1) +uint8_t WS2812FX::random8(uint8_t lim) { + uint8_t r = random8(); + r = (r * lim) >> 8; + return r; +} + +uint16_t WS2812FX::random16() { + return (uint16_t)random8() * 256 + random8(); +} + +// note random16(lim) generates numbers in the range 0 to (lim - 1) +uint16_t WS2812FX::random16(uint16_t lim) { + uint16_t r = random16(); + r = ((uint32_t)r * lim) >> 16; + return r; +} + +// Return the sum of all LED intensities (can be used for +// rudimentary power calculations) +uint32_t WS2812FX::intensitySum() { + uint8_t *pixels = getPixels(); + uint32_t sum = 0; + for(uint16_t i=0; i 255) lum = 511 - lum; // lum = 15 -> 255 -> 15 + + uint16_t delay; + if(lum == 15) delay = 970; // 970 pause before each breath + else if(lum <= 25) delay = 38; // 19 + else if(lum <= 50) delay = 36; // 18 + else if(lum <= 75) delay = 28; // 14 + else if(lum <= 100) delay = 20; // 10 + else if(lum <= 125) delay = 14; // 7 + else if(lum <= 150) delay = 11; // 5 + else delay = 10; // 4 + + uint32_t color = SEGMENT.colors[0]; + uint8_t w = (color >> 24 & 0xFF) * lum / 256; + uint8_t r = (color >> 16 & 0xFF) * lum / 256; + uint8_t g = (color >> 8 & 0xFF) * lum / 256; + uint8_t b = (color & 0xFF) * lum / 256; + for(uint16_t i=SEGMENT.start; i <= SEGMENT.stop; i++) { + setPixelColor(i, r, g, b, w); + } + + SEGMENT_RUNTIME.counter_mode_step += 2; + if(SEGMENT_RUNTIME.counter_mode_step > (512-15)) SEGMENT_RUNTIME.counter_mode_step = 15; + return delay; +} + + +/* + * Fades the LEDs between two colors + */ +uint16_t WS2812FX::mode_fade(void) { + int lum = SEGMENT_RUNTIME.counter_mode_step; + if(lum > 255) lum = 511 - lum; // lum = 0 -> 255 -> 0 + + uint32_t color = color_blend(SEGMENT.colors[0], SEGMENT.colors[1], lum); + for(uint16_t i=SEGMENT.start; i <= SEGMENT.stop; i++) { + setPixelColor(i, color); + } + + SEGMENT_RUNTIME.counter_mode_step += 4; + if(SEGMENT_RUNTIME.counter_mode_step > 511) SEGMENT_RUNTIME.counter_mode_step = 0; + return (SEGMENT.speed / 128); +} + + +/* + * scan function - runs a block of pixels back and forth. + */ +uint16_t WS2812FX::scan(uint32_t color1, uint32_t color2, bool dual) { + int8_t dir = SEGMENT_RUNTIME.aux_param ? -1 : 1; + uint8_t size = 1 << SIZE_OPTION; + + for(uint16_t i=SEGMENT.start; i <= SEGMENT.stop; i++) { + setPixelColor(i, color2); + } + + for(uint8_t i = 0; i < size; i++) { + if(IS_REVERSE || dual) { + setPixelColor(SEGMENT.stop - SEGMENT_RUNTIME.counter_mode_step - i, color1); + } + if(!IS_REVERSE || dual) { + setPixelColor(SEGMENT.start + SEGMENT_RUNTIME.counter_mode_step + i, color1); + } + } + + SEGMENT_RUNTIME.counter_mode_step += dir; + if(SEGMENT_RUNTIME.counter_mode_step == 0) SEGMENT_RUNTIME.aux_param = 0; + if(SEGMENT_RUNTIME.counter_mode_step >= (uint16_t)(SEGMENT_LENGTH - size)) SEGMENT_RUNTIME.aux_param = 1; + + return (SEGMENT.speed / (SEGMENT_LENGTH * 2)); +} + + +/* + * Runs a block of pixels back and forth. + */ +uint16_t WS2812FX::mode_scan(void) { + return scan(SEGMENT.colors[0], SEGMENT.colors[1], false); +} + + +/* + * Runs two blocks of pixels back and forth in opposite directions. + */ +uint16_t WS2812FX::mode_dual_scan(void) { + return scan(SEGMENT.colors[0], SEGMENT.colors[1], true); +} + + +/* + * Cycles all LEDs at once through a rainbow. + */ +uint16_t WS2812FX::mode_rainbow(void) { + uint32_t color = color_wheel(SEGMENT_RUNTIME.counter_mode_step); + for(uint16_t i=SEGMENT.start; i <= SEGMENT.stop; i++) { + setPixelColor(i, color); + } + + SEGMENT_RUNTIME.counter_mode_step = (SEGMENT_RUNTIME.counter_mode_step + 1) & 0xFF; + return (SEGMENT.speed / 256); +} + + +/* + * Cycles a rainbow over the entire string of LEDs. + */ +uint16_t WS2812FX::mode_rainbow_cycle(void) { + for(uint16_t i=0; i < SEGMENT_LENGTH; i++) { + uint32_t color = color_wheel(((i * 256 / SEGMENT_LENGTH) + SEGMENT_RUNTIME.counter_mode_step) & 0xFF); + setPixelColor(SEGMENT.start + i, color); + } + + SEGMENT_RUNTIME.counter_mode_step = (SEGMENT_RUNTIME.counter_mode_step + 1) & 0xFF; + return (SEGMENT.speed / 256); +} + + +/* + * Theatre-style crawling lights. + * Inspired by the Adafruit examples. + */ +uint16_t WS2812FX::mode_theater_chase(void) { + return tricolor_chase(SEGMENT.colors[0], SEGMENT.colors[1], SEGMENT.colors[1]); +} + + +/* + * Theatre-style crawling lights with rainbow effect. + * Inspired by the Adafruit examples. + */ +uint16_t WS2812FX::mode_theater_chase_rainbow(void) { + SEGMENT_RUNTIME.counter_mode_step = (SEGMENT_RUNTIME.counter_mode_step + 1) & 0xFF; + uint32_t color = color_wheel(SEGMENT_RUNTIME.counter_mode_step); + return tricolor_chase(color, SEGMENT.colors[1], SEGMENT.colors[1]); +} + + +/* + * Running lights effect with smooth sine transition. + */ +uint16_t WS2812FX::mode_running_lights(void) { + uint8_t w = ((SEGMENT.colors[0] >> 24) & 0xFF); + uint8_t r = ((SEGMENT.colors[0] >> 16) & 0xFF); + uint8_t g = ((SEGMENT.colors[0] >> 8) & 0xFF); + uint8_t b = (SEGMENT.colors[0] & 0xFF); + + uint8_t size = 1 << SIZE_OPTION; + uint8_t sineIncr = max(1, (256 / SEGMENT_LENGTH) * size); + for(uint16_t i=0; i < SEGMENT_LENGTH; i++) { + int lum = (int)sine8(((i + SEGMENT_RUNTIME.counter_mode_step) * sineIncr)); + if(IS_REVERSE) { + setPixelColor(SEGMENT.start + i, (r * lum) / 256, (g * lum) / 256, (b * lum) / 256, (w * lum) / 256); + } else { + setPixelColor(SEGMENT.stop - i, (r * lum) / 256, (g * lum) / 256, (b * lum) / 256, (w * lum) / 256); + } + } + SEGMENT_RUNTIME.counter_mode_step = (SEGMENT_RUNTIME.counter_mode_step + 1) % 256; + return (SEGMENT.speed / SEGMENT_LENGTH); +} + + +/* + * twinkle function + */ +uint16_t WS2812FX::twinkle(uint32_t color1, uint32_t color2) { + if(SEGMENT_RUNTIME.counter_mode_step == 0) { + for(uint16_t i=SEGMENT.start; i <= SEGMENT.stop; i++) { + setPixelColor(i, color2); + } + uint16_t min_leds = max(1, SEGMENT_LENGTH / 5); // make sure, at least one LED is on + uint16_t max_leds = max(1, SEGMENT_LENGTH / 2); // make sure, at least one LED is on + SEGMENT_RUNTIME.counter_mode_step = random(min_leds, max_leds); + } + + setPixelColor(SEGMENT.start + random16(SEGMENT_LENGTH), color1); + + SEGMENT_RUNTIME.counter_mode_step--; + return (SEGMENT.speed / SEGMENT_LENGTH); +} + +/* + * Blink several LEDs on, reset, repeat. + * Inspired by www.tweaking4all.com/hardware/arduino/arduino-led-strip-effects/ + */ +uint16_t WS2812FX::mode_twinkle(void) { + return twinkle(SEGMENT.colors[0], SEGMENT.colors[1]); +} + +/* + * Blink several LEDs in random colors on, reset, repeat. + * Inspired by www.tweaking4all.com/hardware/arduino/arduino-led-strip-effects/ + */ +uint16_t WS2812FX::mode_twinkle_random(void) { + return twinkle(color_wheel(random8()), SEGMENT.colors[1]); +} + + +/* + * fade out functions + */ +void WS2812FX::fade_out() { + return fade_out(SEGMENT.colors[1]); +} + +void WS2812FX::fade_out(uint32_t targetColor) { + static const uint8_t rateMapH[] = {0, 1, 1, 1, 2, 3, 4, 6}; + static const uint8_t rateMapL[] = {0, 2, 3, 8, 8, 8, 8, 8}; + + uint8_t rate = FADE_RATE; + uint8_t rateH = rateMapH[rate]; + uint8_t rateL = rateMapL[rate]; + + uint32_t color = targetColor; + int w2 = (color >> 24) & 0xff; + int r2 = (color >> 16) & 0xff; + int g2 = (color >> 8) & 0xff; + int b2 = color & 0xff; + + for(uint16_t i=SEGMENT.start; i <= SEGMENT.stop; i++) { + color = getPixelColor(i); // current color + if(rate == 0) { // old fade-to-black algorithm + setPixelColor(i, (color >> 1) & 0x7F7F7F7F); + } else { // new fade-to-color algorithm + int w1 = (color >> 24) & 0xff; + int r1 = (color >> 16) & 0xff; + int g1 = (color >> 8) & 0xff; + int b1 = color & 0xff; + + // calculate the color differences between the current and target colors + int wdelta = w2 - w1; + int rdelta = r2 - r1; + int gdelta = g2 - g1; + int bdelta = b2 - b1; + + // if the current and target colors are almost the same, jump right to the target + // color, otherwise calculate an intermediate color. (fixes rounding issues) + wdelta = abs(wdelta) < 3 ? wdelta : (wdelta >> rateH) + (wdelta >> rateL); + rdelta = abs(rdelta) < 3 ? rdelta : (rdelta >> rateH) + (rdelta >> rateL); + gdelta = abs(gdelta) < 3 ? gdelta : (gdelta >> rateH) + (gdelta >> rateL); + bdelta = abs(bdelta) < 3 ? bdelta : (bdelta >> rateH) + (bdelta >> rateL); + + setPixelColor(i, r1 + rdelta, g1 + gdelta, b1 + bdelta, w1 + wdelta); + } + } +} + + +/* + * color blend function + */ +uint32_t WS2812FX::color_blend(uint32_t color1, uint32_t color2, uint8_t blend) { + if(blend == 0) return color1; + if(blend == 255) return color2; + + uint8_t w1 = (color1 >> 24) & 0xff; + uint8_t r1 = (color1 >> 16) & 0xff; + uint8_t g1 = (color1 >> 8) & 0xff; + uint8_t b1 = color1 & 0xff; + + uint8_t w2 = (color2 >> 24) & 0xff; + uint8_t r2 = (color2 >> 16) & 0xff; + uint8_t g2 = (color2 >> 8) & 0xff; + uint8_t b2 = color2 & 0xff; + + uint32_t w3 = ((w2 * blend) + (w1 * (255U - blend))) / 256U; + uint32_t r3 = ((r2 * blend) + (r1 * (255U - blend))) / 256U; + uint32_t g3 = ((g2 * blend) + (g1 * (255U - blend))) / 256U; + uint32_t b3 = ((b2 * blend) + (b1 * (255U - blend))) / 256U; + + return ((w3 << 24) | (r3 << 16) | (g3 << 8) | (b3)); +} + + +/* + * twinkle_fade function + */ +uint16_t WS2812FX::twinkle_fade(uint32_t color) { + fade_out(); + + if(random8(3) == 0) { + uint8_t size = 1 << SIZE_OPTION; + uint16_t index = SEGMENT.start + random16(SEGMENT_LENGTH - size); + for(uint8_t i=0; i= (uint16_t)((SEGMENT_LENGTH * 2) - 2)) { + SEGMENT_RUNTIME.counter_mode_step = 0; + } + + return (SEGMENT.speed / (SEGMENT_LENGTH * 2)); +} + + +/* + * Firing comets from one end. + */ +uint16_t WS2812FX::mode_comet(void) { + fade_out(); + + if(IS_REVERSE) { + setPixelColor(SEGMENT.stop - SEGMENT_RUNTIME.counter_mode_step, SEGMENT.colors[0]); + } else { + setPixelColor(SEGMENT.start + SEGMENT_RUNTIME.counter_mode_step, SEGMENT.colors[0]); + } + + SEGMENT_RUNTIME.counter_mode_step = (SEGMENT_RUNTIME.counter_mode_step + 1) % SEGMENT_LENGTH; + return (SEGMENT.speed / SEGMENT_LENGTH); +} + + +/* + * Fireworks function. + */ +uint16_t WS2812FX::fireworks(uint32_t color) { + fade_out(); + +// for better performance, manipulate the Adafruit_NeoPixels pixels[] array directly + uint8_t *pixels = getPixels(); + uint8_t bytesPerPixel = getNumBytesPerPixel(); // 3=RGB, 4=RGBW + uint16_t startPixel = SEGMENT.start * bytesPerPixel + bytesPerPixel; + uint16_t stopPixel = SEGMENT.stop * bytesPerPixel ; + for(uint16_t i=startPixel; i > 2) + + pixels[i] + + (pixels[i + bytesPerPixel] >> 2); + pixels[i] = tmpPixel > 255 ? 255 : tmpPixel; + } + + uint8_t size = 2 << SIZE_OPTION; + if(!_triggered) { + for(uint16_t i=0; i> 24) & 0xFF; + byte r = (SEGMENT.colors[0] >> 16) & 0xFF; + byte g = (SEGMENT.colors[0] >> 8) & 0xFF; + byte b = (SEGMENT.colors[0] & 0xFF); + byte lum = max(w, max(r, max(g, b))) / rev_intensity; + for(uint16_t i=SEGMENT.start; i <= SEGMENT.stop; i++) { + int flicker = random8(lum); + setPixelColor(i, max(r - flicker, 0), max(g - flicker, 0), max(b - flicker, 0), max(w - flicker, 0)); + } + return (SEGMENT.speed / SEGMENT_LENGTH); +} + +/* + * Random flickering. + */ +uint16_t WS2812FX::mode_fire_flicker(void) { + return fire_flicker(3); +} + +/* +* Random flickering, less intensity. +*/ +uint16_t WS2812FX::mode_fire_flicker_soft(void) { + return fire_flicker(6); +} + +/* +* Random flickering, more intensity. +*/ +uint16_t WS2812FX::mode_fire_flicker_intense(void) { + return fire_flicker(1.7); +} + + +/* + * Tricolor chase function + */ +uint16_t WS2812FX::tricolor_chase(uint32_t color1, uint32_t color2, uint32_t color3) { + uint8_t sizeCnt = 1 << SIZE_OPTION; + uint16_t index = SEGMENT_RUNTIME.counter_mode_call % (sizeCnt * 3); + for(uint16_t i=0; i < SEGMENT_LENGTH; i++, index++) { + index = index % (sizeCnt * 3); + + uint32_t color = color3; + if(index < sizeCnt) color = color1; + else if(index < (sizeCnt * 2)) color = color2; + + if(IS_REVERSE) { + setPixelColor(SEGMENT.start + i, color); + } else { + setPixelColor(SEGMENT.stop - i, color); + } + } + + return (SEGMENT.speed / SEGMENT_LENGTH); +} + + +/* + * Tricolor chase mode + */ +uint16_t WS2812FX::mode_tricolor_chase(void) { + return tricolor_chase(SEGMENT.colors[0], SEGMENT.colors[1], SEGMENT.colors[2]); +} + + +/* + * Alternating white/red/black pixels running. + */ +uint16_t WS2812FX::mode_circus_combustus(void) { + return tricolor_chase(RED, WHITE, BLACK); +} + +/* + * ICU mode + */ +uint16_t WS2812FX::mode_icu(void) { + uint16_t dest = SEGMENT_RUNTIME.counter_mode_step & 0xFFFF; + + setPixelColor(SEGMENT.start + dest, SEGMENT.colors[0]); + setPixelColor(SEGMENT.start + dest + SEGMENT_LENGTH/2, SEGMENT.colors[0]); + + if(SEGMENT_RUNTIME.aux_param3 == dest) { // pause between eye movements + if(random8(6) == 0) { // blink once in a while + setPixelColor(SEGMENT.start + dest, BLACK); + setPixelColor(SEGMENT.start + dest + SEGMENT_LENGTH/2, BLACK); + return 200; + } + SEGMENT_RUNTIME.aux_param3 = random16(SEGMENT_LENGTH/2); + return 1000 + random16(2000); + } + + setPixelColor(SEGMENT.start + dest, BLACK); + setPixelColor(SEGMENT.start + dest + SEGMENT_LENGTH/2, BLACK); + + if(SEGMENT_RUNTIME.aux_param3 > SEGMENT_RUNTIME.counter_mode_step) { + SEGMENT_RUNTIME.counter_mode_step++; + dest++; + } else if (SEGMENT_RUNTIME.aux_param3 < SEGMENT_RUNTIME.counter_mode_step) { + SEGMENT_RUNTIME.counter_mode_step--; + dest--; + } + + setPixelColor(SEGMENT.start + dest, SEGMENT.colors[0]); + setPixelColor(SEGMENT.start + dest + SEGMENT_LENGTH/2, SEGMENT.colors[0]); + + return (SEGMENT.speed / SEGMENT_LENGTH); +} + +/* + * Custom modes + */ +uint16_t WS2812FX::mode_custom_0() { + return customModes[0](); +} +uint16_t WS2812FX::mode_custom_1() { + return customModes[1](); +} +uint16_t WS2812FX::mode_custom_2() { + return customModes[2](); +} +uint16_t WS2812FX::mode_custom_3() { + return customModes[3](); +} +uint16_t WS2812FX::mode_custom_4() { + return customModes[4](); +} +uint16_t WS2812FX::mode_custom_5() { + return customModes[5](); +} +uint16_t WS2812FX::mode_custom_6() { + return customModes[6](); +} +uint16_t WS2812FX::mode_custom_7() { + return customModes[7](); +} +uint16_t WS2812FX::mode_custom_8() { + return customModes[8](); +} +uint16_t WS2812FX::mode_custom_9() { + return customModes[9](); +} +/* + * Custom mode helpers + */ +void WS2812FX::setCustomMode(uint16_t (*p)()) { + customModes[0] = p; +} + +uint8_t WS2812FX::setCustomMode(const __FlashStringHelper* name, uint16_t (*p)()) { + static uint8_t custom_mode_index = 0; + return setCustomMode(custom_mode_index++, name, p); +} + +uint8_t WS2812FX::setCustomMode(uint8_t index, const __FlashStringHelper* name, uint16_t (*p)()) { + if((uint8_t)(FX_MODE_CUSTOM_0 + index) < MODE_COUNT) { + _names[FX_MODE_CUSTOM_0 + index] = name; // store the custom mode name + customModes[index] = p; // store the custom mode + + return (FX_MODE_CUSTOM_0 + index); + } + return 0; +} + +/* + * Custom show helper + */ +void WS2812FX::setCustomShow(void (*p)()) { + customShow = p; +} diff --git a/Arduino/McLighting/WS2812FX.h b/Arduino/McLighting/WS2812FX.h new file mode 100644 index 0000000..5405457 --- /dev/null +++ b/Arduino/McLighting/WS2812FX.h @@ -0,0 +1,651 @@ +/* + WS2812FX.h - Library for WS2812 LED effects. + + Harm Aldick - 2016 + www.aldick.org + FEATURES + * A lot of blinken modes and counting + * WS2812FX can be used as drop-in replacement for Adafruit NeoPixel Library + NOTES + * Uses the Adafruit NeoPixel library. Get it here: + https://github.com/adafruit/Adafruit_NeoPixel + LICENSE + The MIT License (MIT) + Copyright (c) 2016 Harm Aldick + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + CHANGELOG + 2016-05-28 Initial beta release + 2016-06-03 Code cleanup, minor improvements, new modes + 2016-06-04 2 new fx, fixed setColor (now also resets _mode_color) + 2017-02-02 added external trigger functionality (e.g. for sound-to-light) +*/ + +#ifndef WS2812FX_h +#define WS2812FX_h + +#define FSH(x) (__FlashStringHelper*)(x) + +#include + +#define DEFAULT_BRIGHTNESS (uint8_t)50 +#define DEFAULT_MODE (uint8_t)0 +#define DEFAULT_SPEED (uint16_t)1000 +#define DEFAULT_COLOR (uint32_t)0xFF0000 + +#if defined(ESP8266) || defined(ESP32) + //#pragma message("Compiling for ESP") + #define SPEED_MIN (uint16_t)2 +#else + //#pragma message("Compiling for Arduino") + #define SPEED_MIN (uint16_t)10 +#endif +#define SPEED_MAX (uint16_t)65535 + +#define BRIGHTNESS_MIN (uint8_t)0 +#define BRIGHTNESS_MAX (uint8_t)255 + +/* each segment uses 36 bytes of SRAM memory, so if you're application fails because of + insufficient memory, decreasing MAX_NUM_SEGMENTS may help */ +#define MAX_NUM_SEGMENTS 10 +#define NUM_COLORS 3 /* number of colors per segment */ +#define MAX_CUSTOM_MODES 10 +#define SEGMENT _segments[_segment_index] +#define SEGMENT_RUNTIME _segment_runtimes[_segment_index] +#define SEGMENT_LENGTH (uint16_t)(SEGMENT.stop - SEGMENT.start + 1) + +// some common colors +#define RED (uint32_t)0xFF0000 +#define GREEN (uint32_t)0x00FF00 +#define BLUE (uint32_t)0x0000FF +#define WHITE (uint32_t)0xFFFFFF +#define BLACK (uint32_t)0x000000 +#define YELLOW (uint32_t)0xFFFF00 +#define CYAN (uint32_t)0x00FFFF +#define MAGENTA (uint32_t)0xFF00FF +#define PURPLE (uint32_t)0x400080 +#define ORANGE (uint32_t)0xFF3000 +#define PINK (uint32_t)0xFF1493 +#define ULTRAWHITE (uint32_t)0xFFFFFFFF +#define DARK(c) (uint32_t)((c >> 4) & 0x0f0f0f0f) + +// segment options +// bit 7: reverse animation +// bits 4-6: fade rate (0-7) +// bit 3: gamma correction +// bits 1-2: size +// bits 0: TBD +#define NO_OPTIONS (uint8_t)B00000000 +#define REVERSE (uint8_t)B10000000 +#define IS_REVERSE ((SEGMENT.options & REVERSE) == REVERSE) +#define FADE_XFAST (uint8_t)B00010000 +#define FADE_FAST (uint8_t)B00100000 +#define FADE_MEDIUM (uint8_t)B00110000 +#define FADE_SLOW (uint8_t)B01000000 +#define FADE_XSLOW (uint8_t)B01010000 +#define FADE_XXSLOW (uint8_t)B01100000 +#define FADE_GLACIAL (uint8_t)B01110000 +#define FADE_RATE ((SEGMENT.options >> 4) & 7) +#define GAMMA (uint8_t)B00001000 +#define IS_GAMMA ((SEGMENT.options & GAMMA) == GAMMA) +#define SIZE_SMALL (uint8_t)B00000000 +#define SIZE_MEDIUM (uint8_t)B00000010 +#define SIZE_LARGE (uint8_t)B00000100 +#define SIZE_XLARGE (uint8_t)B00000110 +#define SIZE_OPTION ((SEGMENT.options >> 1) & 3) + +// segment runtime options (aux_param2) +#define FRAME (uint8_t)B10000000 +#define SET_FRAME (SEGMENT_RUNTIME.aux_param2 |= FRAME) +#define CLR_FRAME (SEGMENT_RUNTIME.aux_param2 &= ~FRAME) +#define CYCLE (uint8_t)B01000000 +#define SET_CYCLE (SEGMENT_RUNTIME.aux_param2 |= CYCLE) +#define CLR_CYCLE (SEGMENT_RUNTIME.aux_param2 &= ~CYCLE) + +#define MODE_COUNT (sizeof(_names)/sizeof(_names[0])) + +#define FX_MODE_STATIC 0 +#define FX_MODE_BLINK 1 +#define FX_MODE_BREATH 2 +#define FX_MODE_COLOR_WIPE 3 +#define FX_MODE_COLOR_WIPE_INV 4 +#define FX_MODE_COLOR_WIPE_REV 5 +#define FX_MODE_COLOR_WIPE_REV_INV 6 +#define FX_MODE_COLOR_WIPE_RANDOM 7 +#define FX_MODE_RANDOM_COLOR 8 +#define FX_MODE_SINGLE_DYNAMIC 9 +#define FX_MODE_MULTI_DYNAMIC 10 +#define FX_MODE_RAINBOW 11 +#define FX_MODE_RAINBOW_CYCLE 12 +#define FX_MODE_SCAN 13 +#define FX_MODE_DUAL_SCAN 14 +#define FX_MODE_FADE 15 +#define FX_MODE_THEATER_CHASE 16 +#define FX_MODE_THEATER_CHASE_RAINBOW 17 +#define FX_MODE_RUNNING_LIGHTS 18 +#define FX_MODE_TWINKLE 19 +#define FX_MODE_TWINKLE_RANDOM 20 +#define FX_MODE_TWINKLE_FADE 21 +#define FX_MODE_TWINKLE_FADE_RANDOM 22 +#define FX_MODE_SPARKLE 23 +#define FX_MODE_FLASH_SPARKLE 24 +#define FX_MODE_HYPER_SPARKLE 25 +#define FX_MODE_STROBE 26 +#define FX_MODE_STROBE_RAINBOW 27 +#define FX_MODE_MULTI_STROBE 28 +#define FX_MODE_BLINK_RAINBOW 29 +#define FX_MODE_CHASE_WHITE 30 +#define FX_MODE_CHASE_COLOR 31 +#define FX_MODE_CHASE_RANDOM 32 +#define FX_MODE_CHASE_RAINBOW 33 +#define FX_MODE_CHASE_FLASH 34 +#define FX_MODE_CHASE_FLASH_RANDOM 35 +#define FX_MODE_CHASE_RAINBOW_WHITE 36 +#define FX_MODE_CHASE_BLACKOUT 37 +#define FX_MODE_CHASE_BLACKOUT_RAINBOW 38 +#define FX_MODE_COLOR_SWEEP_RANDOM 39 +#define FX_MODE_RUNNING_COLOR 40 +#define FX_MODE_RUNNING_RED_BLUE 41 +#define FX_MODE_RUNNING_RANDOM 42 +#define FX_MODE_LARSON_SCANNER 43 +#define FX_MODE_COMET 44 +#define FX_MODE_FIREWORKS 45 +#define FX_MODE_FIREWORKS_RANDOM 46 +#define FX_MODE_MERRY_CHRISTMAS 47 +#define FX_MODE_FIRE_FLICKER 48 +#define FX_MODE_FIRE_FLICKER_SOFT 49 +#define FX_MODE_FIRE_FLICKER_INTENSE 50 +#define FX_MODE_CIRCUS_COMBUSTUS 51 +#define FX_MODE_HALLOWEEN 52 +#define FX_MODE_BICOLOR_CHASE 53 +#define FX_MODE_TRICOLOR_CHASE 54 +#define FX_MODE_ICU 55 +#define FX_MODE_CUSTOM 56 // keep this for backward compatiblity +#define FX_MODE_CUSTOM_0 56 // custom modes need to go at the end +#define FX_MODE_CUSTOM_1 57 +#define FX_MODE_CUSTOM_2 58 +#define FX_MODE_CUSTOM_3 59 +#define FX_MODE_CUSTOM_4 60 +#define FX_MODE_CUSTOM_5 61 +#define FX_MODE_CUSTOM_6 62 +#define FX_MODE_CUSTOM_7 63 +#define FX_MODE_CUSTOM_8 64 +#define FX_MODE_CUSTOM_9 65 + +// create GLOBAL names to allow WS2812FX to compile with sketches and other libs that store strings +// in PROGMEM (get rid of the "section type conflict with __c" errors once and for all. Amen.) +const char name_0[] PROGMEM = "Static"; +const char name_1[] PROGMEM = "Blink"; +const char name_2[] PROGMEM = "Breath"; +const char name_3[] PROGMEM = "Color Wipe"; +const char name_4[] PROGMEM = "Color Wipe Inverse"; +const char name_5[] PROGMEM = "Color Wipe Reverse"; +const char name_6[] PROGMEM = "Color Wipe Reverse Inverse"; +const char name_7[] PROGMEM = "Color Wipe Random"; +const char name_8[] PROGMEM = "Random Color"; +const char name_9[] PROGMEM = "Single Dynamic"; +const char name_10[] PROGMEM = "Multi Dynamic"; +const char name_11[] PROGMEM = "Rainbow"; +const char name_12[] PROGMEM = "Rainbow Cycle"; +const char name_13[] PROGMEM = "Scan"; +const char name_14[] PROGMEM = "Dual Scan"; +const char name_15[] PROGMEM = "Fade"; +const char name_16[] PROGMEM = "Theater Chase"; +const char name_17[] PROGMEM = "Theater Chase Rainbow"; +const char name_18[] PROGMEM = "Running Lights"; +const char name_19[] PROGMEM = "Twinkle"; +const char name_20[] PROGMEM = "Twinkle Random"; +const char name_21[] PROGMEM = "Twinkle Fade"; +const char name_22[] PROGMEM = "Twinkle Fade Random"; +const char name_23[] PROGMEM = "Sparkle"; +const char name_24[] PROGMEM = "Flash Sparkle"; +const char name_25[] PROGMEM = "Hyper Sparkle"; +const char name_26[] PROGMEM = "Strobe"; +const char name_27[] PROGMEM = "Strobe Rainbow"; +const char name_28[] PROGMEM = "Multi Strobe"; +const char name_29[] PROGMEM = "Blink Rainbow"; +const char name_30[] PROGMEM = "Chase White"; +const char name_31[] PROGMEM = "Chase Color"; +const char name_32[] PROGMEM = "Chase Random"; +const char name_33[] PROGMEM = "Chase Rainbow"; +const char name_34[] PROGMEM = "Chase Flash"; +const char name_35[] PROGMEM = "Chase Flash Random"; +const char name_36[] PROGMEM = "Chase Rainbow White"; +const char name_37[] PROGMEM = "Chase Blackout"; +const char name_38[] PROGMEM = "Chase Blackout Rainbow"; +const char name_39[] PROGMEM = "Color Sweep Random"; +const char name_40[] PROGMEM = "Running Color"; +const char name_41[] PROGMEM = "Running Red Blue"; +const char name_42[] PROGMEM = "Running Random"; +const char name_43[] PROGMEM = "Larson Scanner"; +const char name_44[] PROGMEM = "Comet"; +const char name_45[] PROGMEM = "Fireworks"; +const char name_46[] PROGMEM = "Fireworks Random"; +const char name_47[] PROGMEM = "Merry Christmas"; +const char name_48[] PROGMEM = "Fire Flicker"; +const char name_49[] PROGMEM = "Fire Flicker (soft)"; +const char name_50[] PROGMEM = "Fire Flicker (intense)"; +const char name_51[] PROGMEM = "Circus Combustus"; +const char name_52[] PROGMEM = "Halloween"; +const char name_53[] PROGMEM = "Bicolor Chase"; +const char name_54[] PROGMEM = "Tricolor Chase"; +const char name_55[] PROGMEM = "ICU"; +const char name_56[] PROGMEM = "Custom 0"; // custom modes need to go at the end +const char name_57[] PROGMEM = "Custom 1"; +const char name_58[] PROGMEM = "Custom 2"; +const char name_59[] PROGMEM = "Custom 3"; +const char name_60[] PROGMEM = "Custom 4"; +const char name_61[] PROGMEM = "Custom 5"; +const char name_62[] PROGMEM = "Custom 6"; +const char name_63[] PROGMEM = "Custom 7"; +const char name_64[] PROGMEM = "Custom 8"; +const char name_65[] PROGMEM = "Custom 9"; + +static const __FlashStringHelper* _names[] = { + FSH(name_0), + FSH(name_1), + FSH(name_2), + FSH(name_3), + FSH(name_4), + FSH(name_5), + FSH(name_6), + FSH(name_7), + FSH(name_8), + FSH(name_9), + FSH(name_10), + FSH(name_11), + FSH(name_12), + FSH(name_13), + FSH(name_14), + FSH(name_15), + FSH(name_16), + FSH(name_17), + FSH(name_18), + FSH(name_19), + FSH(name_20), + FSH(name_21), + FSH(name_22), + FSH(name_23), + FSH(name_24), + FSH(name_25), + FSH(name_26), + FSH(name_27), + FSH(name_28), + FSH(name_29), + FSH(name_30), + FSH(name_31), + FSH(name_32), + FSH(name_33), + FSH(name_34), + FSH(name_35), + FSH(name_36), + FSH(name_37), + FSH(name_38), + FSH(name_39), + FSH(name_40), + FSH(name_41), + FSH(name_42), + FSH(name_43), + FSH(name_44), + FSH(name_45), + FSH(name_46), + FSH(name_47), + FSH(name_48), + FSH(name_49), + FSH(name_50), + FSH(name_51), + FSH(name_52), + FSH(name_53), + FSH(name_54), + FSH(name_55), + FSH(name_56), + FSH(name_57), + FSH(name_58), + FSH(name_59), + FSH(name_60), + FSH(name_61), + FSH(name_62), + FSH(name_63), + FSH(name_64), + FSH(name_65) +}; + +class WS2812FX : public Adafruit_NeoPixel { + + typedef uint16_t (WS2812FX::*mode_ptr)(void); + + // segment parameters + public: + typedef struct Segment { // 20 bytes + uint16_t start; + uint16_t stop; + uint16_t speed; + uint8_t mode; + uint8_t options; + uint32_t colors[NUM_COLORS]; + } segment; + + // segment runtime parameters + typedef struct Segment_runtime { // 16 bytes + unsigned long next_time; + uint32_t counter_mode_step; + uint32_t counter_mode_call; + uint8_t aux_param; // auxilary param (usually stores a color_wheel index) + uint8_t aux_param2; // auxilary param (usually stores bitwise options) + uint16_t aux_param3; // auxilary param (usually stores a segment index) + } segment_runtime; + + WS2812FX(uint16_t n, uint8_t p, neoPixelType t) : Adafruit_NeoPixel(n, p, t) { + _mode[FX_MODE_STATIC] = &WS2812FX::mode_static; + _mode[FX_MODE_BLINK] = &WS2812FX::mode_blink; + _mode[FX_MODE_COLOR_WIPE] = &WS2812FX::mode_color_wipe; + _mode[FX_MODE_COLOR_WIPE_INV] = &WS2812FX::mode_color_wipe_inv; + _mode[FX_MODE_COLOR_WIPE_REV] = &WS2812FX::mode_color_wipe_rev; + _mode[FX_MODE_COLOR_WIPE_REV_INV] = &WS2812FX::mode_color_wipe_rev_inv; + _mode[FX_MODE_COLOR_WIPE_RANDOM] = &WS2812FX::mode_color_wipe_random; + _mode[FX_MODE_RANDOM_COLOR] = &WS2812FX::mode_random_color; + _mode[FX_MODE_SINGLE_DYNAMIC] = &WS2812FX::mode_single_dynamic; + _mode[FX_MODE_MULTI_DYNAMIC] = &WS2812FX::mode_multi_dynamic; + _mode[FX_MODE_RAINBOW] = &WS2812FX::mode_rainbow; + _mode[FX_MODE_RAINBOW_CYCLE] = &WS2812FX::mode_rainbow_cycle; + _mode[FX_MODE_SCAN] = &WS2812FX::mode_scan; + _mode[FX_MODE_DUAL_SCAN] = &WS2812FX::mode_dual_scan; + _mode[FX_MODE_FADE] = &WS2812FX::mode_fade; + _mode[FX_MODE_THEATER_CHASE] = &WS2812FX::mode_theater_chase; + _mode[FX_MODE_THEATER_CHASE_RAINBOW] = &WS2812FX::mode_theater_chase_rainbow; + _mode[FX_MODE_TWINKLE] = &WS2812FX::mode_twinkle; + _mode[FX_MODE_TWINKLE_RANDOM] = &WS2812FX::mode_twinkle_random; + _mode[FX_MODE_TWINKLE_FADE] = &WS2812FX::mode_twinkle_fade; + _mode[FX_MODE_TWINKLE_FADE_RANDOM] = &WS2812FX::mode_twinkle_fade_random; + _mode[FX_MODE_SPARKLE] = &WS2812FX::mode_sparkle; + _mode[FX_MODE_FLASH_SPARKLE] = &WS2812FX::mode_flash_sparkle; + _mode[FX_MODE_HYPER_SPARKLE] = &WS2812FX::mode_hyper_sparkle; + _mode[FX_MODE_STROBE] = &WS2812FX::mode_strobe; + _mode[FX_MODE_STROBE_RAINBOW] = &WS2812FX::mode_strobe_rainbow; + _mode[FX_MODE_MULTI_STROBE] = &WS2812FX::mode_multi_strobe; + _mode[FX_MODE_BLINK_RAINBOW] = &WS2812FX::mode_blink_rainbow; + _mode[FX_MODE_CHASE_WHITE] = &WS2812FX::mode_chase_white; + _mode[FX_MODE_CHASE_COLOR] = &WS2812FX::mode_chase_color; + _mode[FX_MODE_CHASE_RANDOM] = &WS2812FX::mode_chase_random; + _mode[FX_MODE_CHASE_RAINBOW] = &WS2812FX::mode_chase_rainbow; + _mode[FX_MODE_CHASE_FLASH] = &WS2812FX::mode_chase_flash; + _mode[FX_MODE_CHASE_FLASH_RANDOM] = &WS2812FX::mode_chase_flash_random; + _mode[FX_MODE_CHASE_RAINBOW_WHITE] = &WS2812FX::mode_chase_rainbow_white; + _mode[FX_MODE_CHASE_BLACKOUT] = &WS2812FX::mode_chase_blackout; + _mode[FX_MODE_CHASE_BLACKOUT_RAINBOW] = &WS2812FX::mode_chase_blackout_rainbow; + _mode[FX_MODE_COLOR_SWEEP_RANDOM] = &WS2812FX::mode_color_sweep_random; + _mode[FX_MODE_RUNNING_COLOR] = &WS2812FX::mode_running_color; + _mode[FX_MODE_RUNNING_RED_BLUE] = &WS2812FX::mode_running_red_blue; + _mode[FX_MODE_RUNNING_RANDOM] = &WS2812FX::mode_running_random; + _mode[FX_MODE_LARSON_SCANNER] = &WS2812FX::mode_larson_scanner; + _mode[FX_MODE_COMET] = &WS2812FX::mode_comet; + _mode[FX_MODE_FIREWORKS] = &WS2812FX::mode_fireworks; + _mode[FX_MODE_FIREWORKS_RANDOM] = &WS2812FX::mode_fireworks_random; + _mode[FX_MODE_MERRY_CHRISTMAS] = &WS2812FX::mode_merry_christmas; + _mode[FX_MODE_FIRE_FLICKER] = &WS2812FX::mode_fire_flicker; + _mode[FX_MODE_FIRE_FLICKER_SOFT] = &WS2812FX::mode_fire_flicker_soft; + _mode[FX_MODE_FIRE_FLICKER_INTENSE] = &WS2812FX::mode_fire_flicker_intense; + _mode[FX_MODE_CIRCUS_COMBUSTUS] = &WS2812FX::mode_circus_combustus; + _mode[FX_MODE_HALLOWEEN] = &WS2812FX::mode_halloween; + _mode[FX_MODE_BICOLOR_CHASE] = &WS2812FX::mode_bicolor_chase; + _mode[FX_MODE_TRICOLOR_CHASE] = &WS2812FX::mode_tricolor_chase; +// if flash memory is constrained (I'm looking at you Arduino Nano), replace modes +// that use a lot of flash with mode_static (reduces flash footprint by about 2100 bytes) +#ifdef REDUCED_MODES + _mode[FX_MODE_BREATH] = &WS2812FX::mode_static; + _mode[FX_MODE_RUNNING_LIGHTS] = &WS2812FX::mode_static; + _mode[FX_MODE_ICU] = &WS2812FX::mode_static; +#else + _mode[FX_MODE_BREATH] = &WS2812FX::mode_breath; + _mode[FX_MODE_RUNNING_LIGHTS] = &WS2812FX::mode_running_lights; + _mode[FX_MODE_ICU] = &WS2812FX::mode_icu; +#endif + _mode[FX_MODE_CUSTOM_0] = &WS2812FX::mode_custom_0; + _mode[FX_MODE_CUSTOM_1] = &WS2812FX::mode_custom_1; + _mode[FX_MODE_CUSTOM_2] = &WS2812FX::mode_custom_2; + _mode[FX_MODE_CUSTOM_3] = &WS2812FX::mode_custom_3; + _mode[FX_MODE_CUSTOM_4] = &WS2812FX::mode_custom_4; + _mode[FX_MODE_CUSTOM_5] = &WS2812FX::mode_custom_5; + _mode[FX_MODE_CUSTOM_6] = &WS2812FX::mode_custom_6; + _mode[FX_MODE_CUSTOM_7] = &WS2812FX::mode_custom_7; + _mode[FX_MODE_CUSTOM_8] = &WS2812FX::mode_custom_8; + _mode[FX_MODE_CUSTOM_9] = &WS2812FX::mode_custom_9; + + brightness = DEFAULT_BRIGHTNESS + 1; // Adafruit_NeoPixel internally offsets brightness by 1 + _running = false; + _num_segments = 1; + _segments[0].mode = DEFAULT_MODE; + _segments[0].colors[0] = DEFAULT_COLOR; + _segments[0].start = 0; + _segments[0].stop = n - 1; + _segments[0].speed = DEFAULT_SPEED; + resetSegmentRuntimes(); + } + + void +// timer(void), + init(void), + service(void), + start(void), + stop(void), + pause(void), + resume(void), + strip_off(void), + fade_out(void), + fade_out(uint32_t), + setMode(uint8_t m), + setMode(uint8_t seg, uint8_t m), + setOptions(uint8_t seg, uint8_t o), + setCustomMode(uint16_t (*p)()), + setCustomShow(void (*p)()), + setSpeed(uint16_t s), + setSpeed(uint8_t seg, uint16_t s), + increaseSpeed(uint8_t s), + decreaseSpeed(uint8_t s), + setColor(uint8_t r, uint8_t g, uint8_t b), + setColor(uint8_t r, uint8_t g, uint8_t b, uint8_t w), + setColor(uint32_t c), + setColor(uint8_t seg, uint32_t c), + setColors(uint8_t seg, uint32_t* c), + setBrightness(uint8_t b), + increaseBrightness(uint8_t s), + decreaseBrightness(uint8_t s), + setLength(uint16_t b), + increaseLength(uint16_t s), + decreaseLength(uint16_t s), + trigger(void), + setNumSegments(uint8_t n), + setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t mode, uint32_t color, uint16_t speed, bool reverse), + setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t mode, uint32_t color, uint16_t speed, uint8_t options), + setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t mode, const uint32_t colors[], uint16_t speed, bool reverse), + setSegment(uint8_t n, uint16_t start, uint16_t stop, uint8_t mode, const uint32_t colors[], uint16_t speed, uint8_t options), + resetSegments(), + resetSegmentRuntimes(), + resetSegmentRuntime(uint8_t), + setPixelColor(uint16_t n, uint32_t c), + setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b), + setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b, uint8_t w), + copyPixels(uint16_t d, uint16_t s, uint16_t c), + show(void); + + boolean + isRunning(void), + isTriggered(void), + isFrame(void), + isFrame(uint8_t), + isCycle(void), + isCycle(uint8_t); + + uint8_t + random8(void), + random8(uint8_t), + getMode(void), + getMode(uint8_t), + getModeCount(void), + setCustomMode(const __FlashStringHelper* name, uint16_t (*p)()), + setCustomMode(uint8_t i, const __FlashStringHelper* name, uint16_t (*p)()), + getNumSegments(void), + get_random_wheel_index(uint8_t), + getOptions(uint8_t), + getNumBytesPerPixel(void), + getSegmentIndex(void); + + uint16_t + random16(void), + random16(uint16_t), + getSpeed(void), + getSpeed(uint8_t), + getLength(void), + getNumBytes(void); + + uint32_t + color_wheel(uint8_t), + getColor(void), + getColor(uint8_t), + intensitySum(void); + + uint32_t* getColors(uint8_t); + uint32_t* intensitySums(void); + + const __FlashStringHelper* getModeName(uint8_t m); + + WS2812FX::Segment* getSegment(void); + + WS2812FX::Segment* getSegment(uint8_t); + + WS2812FX::Segment* getSegments(void); + + WS2812FX::Segment_runtime* getSegmentRuntime(void); + + WS2812FX::Segment_runtime* getSegmentRuntime(uint8_t); + + WS2812FX::Segment_runtime* getSegmentRuntimes(void); + + // mode helper functions + uint16_t + blink(uint32_t, uint32_t, bool strobe), + color_wipe(uint32_t, uint32_t, bool), + twinkle(uint32_t, uint32_t), + twinkle_fade(uint32_t), + chase(uint32_t, uint32_t, uint32_t), + running(uint32_t, uint32_t), + fireworks(uint32_t), + fire_flicker(int), + tricolor_chase(uint32_t, uint32_t, uint32_t), + scan(uint32_t, uint32_t, bool); + uint32_t + color_blend(uint32_t, uint32_t, uint8_t); + + // builtin modes + uint16_t + mode_static(void), + mode_blink(void), + mode_blink_rainbow(void), + mode_strobe(void), + mode_strobe_rainbow(void), + mode_color_wipe(void), + mode_color_wipe_inv(void), + mode_color_wipe_rev(void), + mode_color_wipe_rev_inv(void), + mode_color_wipe_random(void), + mode_color_sweep_random(void), + mode_random_color(void), + mode_single_dynamic(void), + mode_multi_dynamic(void), + mode_breath(void), + mode_fade(void), + mode_scan(void), + mode_dual_scan(void), + mode_theater_chase(void), + mode_theater_chase_rainbow(void), + mode_rainbow(void), + mode_rainbow_cycle(void), + mode_running_lights(void), + mode_twinkle(void), + mode_twinkle_random(void), + mode_twinkle_fade(void), + mode_twinkle_fade_random(void), + mode_sparkle(void), + mode_flash_sparkle(void), + mode_hyper_sparkle(void), + mode_multi_strobe(void), + mode_chase_white(void), + mode_chase_color(void), + mode_chase_random(void), + mode_chase_rainbow(void), + mode_chase_flash(void), + mode_chase_flash_random(void), + mode_chase_rainbow_white(void), + mode_chase_blackout(void), + mode_chase_blackout_rainbow(void), + mode_running_color(void), + mode_running_red_blue(void), + mode_running_random(void), + mode_larson_scanner(void), + mode_comet(void), + mode_fireworks(void), + mode_fireworks_random(void), + mode_merry_christmas(void), + mode_halloween(void), + mode_fire_flicker(void), + mode_fire_flicker_soft(void), + mode_fire_flicker_intense(void), + mode_circus_combustus(void), + mode_bicolor_chase(void), + mode_tricolor_chase(void), + mode_icu(void), + mode_custom_0(void), + mode_custom_1(void), + mode_custom_2(void), + mode_custom_3(void), + mode_custom_4(void), + mode_custom_5(void), + mode_custom_6(void), + mode_custom_7(void), + mode_custom_8(void), + mode_custom_9(void); + private: + uint16_t _rand16seed; + uint16_t (*customModes[MAX_CUSTOM_MODES])(void) { + []{ return (uint16_t)1000; }, + []{ return (uint16_t)1000; }, + []{ return (uint16_t)1000; }, + []{ return (uint16_t)1000; }, + []{ return (uint16_t)1000; }, + []{ return (uint16_t)1000; }, + []{ return (uint16_t)1000; }, + []{ return (uint16_t)1000; }, + []{ return (uint16_t)1000; }, + []{ return (uint16_t)1000; } + }; + void (*customShow)(void) = NULL; + + boolean + _running, + _triggered; + + mode_ptr _mode[MODE_COUNT]; // SRAM footprint: 4 bytes per element + + uint8_t _segment_index = 0; + uint8_t _num_segments = 1; + segment _segments[MAX_NUM_SEGMENTS] = { // SRAM footprint: 20 bytes per element + // start, stop, speed, mode, options, color[] + { 0, 7, DEFAULT_SPEED, FX_MODE_STATIC, NO_OPTIONS, {DEFAULT_COLOR}} + }; + segment_runtime _segment_runtimes[MAX_NUM_SEGMENTS]; // SRAM footprint: 16 bytes per element +}; + +#endif diff --git a/Arduino/McLighting/data/edit.htm b/Arduino/McLighting/data/edit.htm index c8e8727..728b705 100644 --- a/Arduino/McLighting/data/edit.htm +++ b/Arduino/McLighting/data/edit.htm @@ -182,7 +182,9 @@ var mkfile = document.createElement("button"); mkfile.innerHTML = 'MkFile'; document.getElementById(element).appendChild(mkfile); - + var para = document.createElement("text"); + para.innerHTML = " Press Ctrl-S for save, Ctrl-Z for undo, Ctrl-Shift-Z for redo"; + document.getElementById(element).appendChild(para); function httpPostProcessRequest(){ if (xmlHttp.readyState == 4){ if(xmlHttp.status != 200) alert("ERROR["+xmlHttp.status+"]: "+xmlHttp.responseText); diff --git a/Arduino/McLighting/data/edit.htm.gz b/Arduino/McLighting/data/edit.htm.gz index 69ce414f47f4b25a70160fc4985b2bd320c9b90a..03cc7007caa1f26277ec996e2fdc86aea5d9c7b8 100644 GIT binary patch literal 5563 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 JpMMQv002^C#<>6h literal 4116 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 - - - + + + + + + + + + + + +
+
+
+ + + + diff --git a/clients/web/edit.htm.gz b/clients/web/edit.htm.gz new file mode 100644 index 0000000000000000000000000000000000000000..03cc7007caa1f26277ec996e2fdc86aea5d9c7b8 GIT binary patch literal 5563 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 JpMMQv002^C#<>6h literal 0 HcmV?d00001 diff --git a/clients/web/favicon.ico b/clients/web/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..1c55beaf3d048b31b786937190d4bdc8a40610ea GIT binary patch literal 34494 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 literal 0 HcmV?d00001 diff --git a/clients/web/favicon.ico.gz b/clients/web/favicon.ico.gz new file mode 100644 index 0000000000000000000000000000000000000000..9e0ba66b97836e527dd9bb05fd28bce58665573d GIT binary patch literal 12171 zcmV;6FLcl!iwFoM+51`q0A^u!X=870E@@+L0PURxT-C`I$1iCs)|$`GwbymkT|3v- z)wR1@QFradPV553qC-?b1tnDs1OyZnm9S8;5j*aAznTBJK?%mb_`Emo^L_K*>%H@z zGjpcT8B{6*m5IvCOvL3>V~tg+-YS){=@<9qG$t5kLC@_$!W zsh*Tksamv9{<~T$m1=7nm8u%jz*q5!@_A-~G6?sRACS|7W3>3bJ2*zp527jQ`7_EY z+)uejL+=eG<5v|)MXkUXUoP+I`OGWWDGmsZ7UyR>hvm*+^ ze|Q5I6?v`dMwXVAbmYhpZT`KyyyWkv_wbO8(RzF^Pemys z^V5;NJ7~?CHL1#QiTP?;U*I{-z}JR>-;7_^g|- z7Oy9}CD!EC-IcBsNFg8m^LDTp>==AkD__ckM0z&xJNa8G2@LY0fEs~`z!qGh*Q$4P z8UG~G*j@Z7J-(NcN(Y}rhtsnkkcO!tzF&qbX)3S*XGpDjLXTBy3c+vhMm))|4WuL{ z(!Kt}>6K9({H7b20jz;Py)urWI|J{~^SgL&7U5YU>c!5Nk`{P?07|mIOv!hjX82vz zSB7e!IH;RNKciNw<@-dO+4H-1?O@PYQn zc~RH+(Uk8_8&ZAjLn`%TuoQj54$6Pqp1Q{ekw;t%ef;<_({wTlVTtFcL0mngl^-p) zBKQUL1e3rrum|{n2z)1zevG|J;fZ(klgLyK=j+?4+H+GXgf?v;`ZIM&@FUXdhIj(v z6^QK-2Z9(pKSTehrW{w3Y4?R#kw&KFa}a5_ePcjBBJFY@H&CeyfEu797!OtfS8x{G zL0P>g)dMxDF008f@HOoYjm@xpF5NjpBcGd4AMQ)*ffB$-_Q~ZyYaSQY01t2x>7!qK zhP1De(#Luc<=B->Vb`@~rLD`=@pWi6@+(T9KjOBa7M{&Mq6W^_r7?$Q(GZJ?G}K}m zO+M;KW7zA2BH=srSGbtb- zfHrN~L{q0umF(KJiw++?Oj|EpC1Zb#FZ+C;QcPdT@CDjn5+Y5Q` zgt!e+{#LhT8%(%yk!~Tq1aKW(0_TCF&ls|?u@UKu^wPE)`Pt5$J8ARg%`{sK+ z?FOJOs0pflpsUwzQhdBGy+wMj!7K0rJO>sF2hy}@)3oWQ3EK20PDGuLu#n@L>N`*b z6b1!}0*_y(_*h>|6jY>s2$ED7G!SFlTQoma`f1xts`Q5sv5@0lZuH|h5$6D=cn%D_ zP6_cBNPP((Tma$VtV)iHM{P%I%14%YQc@B_MkavB*-W0SIQc{?^jaWB3&!Icnx9;FbTbj;~HAHa3QT-yEa1|9W-bV*_eCN zBGpGS$4^Hf-M)ysfj_&(Y3nNPtP80v@!>)+1B?XSRrIoF++`;v19 zuRs0ifGU~x;OpY&^TAXwN|gQnSZ!UsfB&8m7Sts5MC5xg=nNVu1*tYMqR6rh=FggT0X!YYywNc;(D>7&q>wC-H8gOl)lD99hJ1zqN4I5 zR^)^ul=l;DqOmCdm{Nawo&F<(l&rdfUi=s0zMZ7#NuF zq^%L?1O5ctz!7i>?|DWkNHY#!4FQJ?-q5M-*VB$KskpXh3#F7YM!DbzDl?=}6|}_j zK*Y1a7T^KGKq7sB3=i>+^Cr(J9BF4VMy4*S)2ArDRz3W_B&ZA8BmJR>=OEsMcpu_Z zAQsQhDW*;w-8y?G-T3~AAnQEN#e?F09Y=R_cR@O$W1I^%;@O!Jb046%j;APgUyS^2 zhC!F_Pst_QV8VqMiuVf#5tMN8I{Mu=;@`4F^*4uVyw+>+1?#=+1R=w#SFZ$F*2%XB zFJHc-0~e3bxacMH)1|RgIC>zN#EbyucwS5mFYTlW(MQPhVyLW_ZxtRsen_*U=27A6 z)s*=VH340CEjbk|2Ag>l_rZ70P|<6CG%qrQo;-a@Um8T2Iz;WIQa6eqT@xVU%Ag_W z00!y3e=BeWe&9Ta2T$m`xL9(Gy7<}kBXoF1$S^AZRz-!8lCfNa=L2QI4?L6W52k=6 zz#8la$3Zl>i*k4^=bx(EZ)mJvh#XV06U4ixhIXOGykh?c$P1J*D8`?*1bx6{uoTz; zPjD8*f~TZT= zj|7R3{Vl1I@!p%@0(WUXY8vZ$_LFsSC~_x_eWz$vPcG%3xYi2-16dQL0MGkrB3J<& zc;0^%?@m_AJrVDW=J~TXa051P$RX%rCVKvW=H4hov$#};vxIH=ZRJ32VED+4N;v#L z(|lJ@pTEY^@D(#@yvKGb?1=Dwn;_1ng^zC9_)E~5- zrTGg?1EYXu-B1^G(j}rfUA}dhx_9qR(b3UzuC!{^Dmr!Ql>F@KojcU}H1z+n zWzR#SC0GnF)XcMuOuf!dNg=EFg0dE^z&h5d*l9-=no{{&aL6dVqJ`S6VOK z0tw)ns1NV46c!dn=H}+&{h|)!+&OIs4h|N&DBnM7)F}CzDSx3P>*9SiKs8VqR0QRr z12v+>yXClcJ=T!kW8L>oElIWRS+cC3T#t3^*fHDhhhECx3?8;vS@#qI1%VkTpkPw) ziqJv%{};0bsP9)X7-3BB8*FMt1#!AtbkMR+GL21epD<16CbvJ9eqbCaB` zUI#HiZ{6hWFy^z%f8@xK@;9?59nr0uKnd_2C<4kCj@I_Y(T6cl55|MzB&!0b^VlUsK!G@2t z>#p{oC1?T~0*i6F_g_5iq`cD(>;zkYW?eQNquL*hWgBbPt{ojcdQ`kWQyd)~Y4+^d zw6=MKZe0cjfL@@B$ctE8?Z2RX|MTb1DPawiaufb>HCO~@feB!cvTh5R)}QX6e_yt2 zS!VsO=$qxbbZTWwh@zIsEdjH^WH1^G27L-&le(uLq8!bYjo>-DOv}RSpj4o`BFdpf zW4aL;mFf6%Gx{bSZyEtrQY-5^QH3nh0`Mp2!)8`Q`tdjE9^IO1rpP=D{4NSgzB|>r z@2Np)axK<~d?zlag+0g8>Or%}bEFNqm)b&43~r@b_xZ3Y*?{$68JJllfgU}4Oc{lU z{Z{mTpx8_Y?ZmrTVT;Sg%NMb4H6Zy&c zSI9gJP;L0#-*dywBi@|@lmhj@ugYMitQ&!Qs&(UQm0BKQM1xbDhkf|WcIcTb=fa{- z3?GH`YvM;`_;AC}kUP*yyyor-wChUX0s?u`lSChJHt-BTy8)s=Fy8Il^_-mBW+#X| zgpBJ??~8E9smcXPf|{T?_zjHV!F>ZCLLCF=dD59eZ}Cp09^TU7p=YxFZvDE5*ltC) z%nGyY<-rd;nDz$azWZ@G zy6*=j@cY++y*$|q#(SeFw%}Qc_#=!SJ$OXlDu_Pnf|Dmj4_iXfwZ_xM+`}l=a59)j z7xS#7i?#RC#SuPq!8H`~$*j+0zBYWT^|$KZ9IEkI(a>z{Q>57k`mA%HfA{mZ3P?-t z6`wqNiuMNXqt*UH+)|LI@>o>wE@2JDpm5cM$-OomTRP%S@a+Tnjp z>`i4`XTDT_M0wjEx1(|=%3(d82YD&VEBG3`qtFU;1q1l(Z8{M7x8xPD1E0nD^Pb8D zWl!mSB2@}HO0K~tKRZwAY}Aq9_MKbw+u;tB|Ahe=$#X~(U7+x&!Eg=TV&hh02OwZ3A5J>@9Q$L@Ik)>U$ssd?3{;HTJgDRQkvn z3XZ(+)%qj;ecwq3oL%RjT6}U{5|}BwZoKA=aX=VhO$8~ompTHP_FrYQ3tK@CtI6a#tGnmWh} zO0W(!0Bt}|FdR$+i@*l37aRluARJudvVQ?vfsdG4A^%Ar0bBy%IQu(zgASZH^F{h| zBHR!5205{#tejmpMO+_L$8%ZMqZ~ligt{;&kLS9GTPS@H4MS|M;Tv7ef9@48CC;TJwje{S4NpdsPKXfWQ;n|qYDLc4jF zxGL{f<_Cskfd9|SpQPPKH|KrqExeEJ&AJlJ;@m$ok|3aWrcpd*iAQ^6AM`(1$_ zI1duQQ|Jxa>~pav3$B6lAQ<=pFW?I7fQ_2Etv-}3{W%!8o94VSpqXmr6kr?}j^}=e zyRj~bk*cLyD z?_Vo=b01s>5v1h(m|9u(IV0W+ti@6cwjj%s$<60v+I*+mZ=DnT4_eN>##}Iice6+G z$wGHUmr|bt;5_1czgjeM(P--2xwA~GMT-{XGkNIS(C0GV5fT)Wp zU^EyAday1v1l8h9XwA`;@(jY=-CgXbiL}z@Uw$@x_;A{~b*nsUUVqe=avr~ma|G5I zAK=dVvK?#!tHBar0cL~g?`gY>e@6Nv(_9r;iT0|MQ*Q`1Ea`TIs!a11mhw52z;2BGTpm_U+qpAAiS=9T_3__pe`9YzhJc1F1-nBCrRF zm%3YLI_4GAkiYS2WeaAoCeL{8j<_@Efai80!Qm1zwb zKL>e!t88KPVzbVUY#!Pgv;a-P&)6gXF;cAKv}J$%#8HZUZ$Pmq+iM_(?XE6>2oMI& zfD^zvGzazR(?{5miM~ct{_>2bK!E~T>Q2?FRYjj9o@M@9wQ42OlCtG#gA!}6Mt(ta z@C#_fc3kz@ma7iNf?6->)aejy`TGTKr6grUr@P4G9mKZ~-%wT;Qmz=DF}egb*T}7} zKXM=1%*-sybyM2>`}yhmla8Z;L|&EctU7FW_C21fA+Dn4a~i}IuxIEPp#5Dy-~xJq zyrqEWAQ?OZPr+mG2x!ichK(yq_3PJ{)T&iWqd(gGXAv~{*XWNpQ_!YYyy7tAjTGDM zvfv+V4pfS_35$bb3YP2rwdE4vKNa&HRXRTCP|l%%o&6}WuPqRLd)o5$@d`#XnbuQ~kl* zLYrRM;v=-@V1+SHTr<8C(QW;5-NgXV9PS zZ6bb?W&6#p{&4%14E{BUs-tYHfQq0TC<97?lAt*F4ip7;d;N85tsrmu5Mh9W4&+f7 z-8`AhE8 zIEPcqbGZ75>wubg7BW=@mGE2vae2d66s)&4zIpQ|#kw0SXOuy}U*wm4Exf>fmG&I( z!w$?JCjL%)_wE&T2{Qj2q#Z-~^5v;)*|Hf@u3R}`Z>f2&%>S%evt(Lh2F{_Ls&~pc z9%v1ku@A>jLLTrMA8e&Qu2c@E)fUQG zw|W{7Wj-8nKhRB7BHI*yk9sI&MBWzjMSIBh`cceP4=wQ_U*mW>#rDkmknCQ>+rU~; z7l3RsCR!@=kY-%4fOmz>d{dG&xa}i&>{;3VkXYl=gf-U7z6Z zVnuHz8^|(Ok6?Z21v-INT<*0%c~BUb097t&yZiy?u;Hc+De#xkG_^z%+BJASxr|#& zqs@M$z-BusG=~$ulYs9k=dcezEQkOnxgPcd2Vlecv<8T}m}ihe9`-?5?*9nea;Jpd+3&c^4gUMa0E`7|2yd$WK8aCXPQKZUcJbc|79zU_GAg5qpCWJYPrr z2)w5hF(T+X)7T?) zsMtBWb?bJv_763L9P^@EtxZVXg3G-Qs0>Pjg1}VhjR`0O%7NOT8R!BAfl1&munz12 z2SE^s078cz%d_-XtVanT8k_^ixgHLJEji-ov|rd~?>~z;(7}f8H-zoKa5kxm{FVV? za+pix-2mhjWskTD_z};o5cdE>@jL~w1z3ycU5MR*AD%-IUj_GM9eh9@lL4l_vMpQy zAz~VX*va%Z9dkJORp%Fhb~bdYnki8^BwCCovPK%akwWZ)S%Y<_E$9h`v-6Y%5c%H; z+<1fV4C~M>@I=+>*mHs&c}jEbyWnAB#hH?qy<1I0ll z_L*o3I*7IlMuKTz0ayce02gosoCJ{|4kUpW^bvk7FIbOm2)!v5N+EvPJ6rvP{-lyG z0d4w7tETiu+RWs{dm)cppjMZRfj4W!WVe;g_0E5_u=MS)e4WzArJ#UWtg3}<1QVcJWf29L-Y`u@PgZa{s9sSARS=L9ek1zR; znTPp9ce-xei9Q$%L79#h`A57Etj6;;#CG)Fz>Q*zcT+%R8}ggrj(#y%%>AzHLapY!}B`tzR@RR0hE)=-Uo z$rfb8PV4{SUl({^MfA&JZ(sNmi}O|S`F~v-&rT3BdLHqDU&uP@?KYk2yA7cdE`kVg1E`?KD10`a8o17-er{`Mi5vX#eY~ZA^?zd!dx|TZ*H9zpu9P#VJ{i3b zcP4aXFR_7OB%gNv!QNBL*o$c!pT;?pQL+o=^0%a>&TDCnv#s=3F6yBGG#Yso&4zkvZP|3p3cm$UpLx z;>b8Lk{a9XB;Vu5|Lx@<&V0M=?o2su8w-me_BbrWHbWIa4faNC3fi)zOJDX@oWSy1 zuovM5zE#5>xU)@BAfNa~f~#zomLyN{jB($~@4I%%J@$Wf5aqsOzcrN$EJy{Ao1DNv zYj>9)6l34pYJ7vr&!7$H!XA>t(#XFOYy>uJPjwI+6}r!--WSa#J zmk-%_dHu_DL5y3YtOrtsBqLeNML<6GIo9bwVNhDH4K!jK)=q4BHWZ9cBmXA03~|zH z`=|Ki{|dMP?(-=w&f`Dgyzv$0nNP885P6$MZ`mPj>;KLm=J=g=Hl*4()33r7x+OqC zkSmoA6kspp^4ta*a39cty>y4L1=tKd`L}T2@4{_AfOS6#TnD$g?q8zrKN5}=6rbe> zijAGYl{eJV()w>3GsXDR(W!>ir-uv%tC~f09u>{SzMb~40`wDvPAO}!dLRvfahM;vC zJN6lRcI?(^?bx~Bi|4w3E^J~Iy?+MogB#$gzU&}C@!fA}VfEGhf#_4st^3j-wD&$d zd+DrK1`T2!d#qbj$W7MwyqFj~)!e+`d)PTcl`SodSVEw1}F@C{(S zf56+E*T99evI9@_2QF`D^vWGyH7?k=ZlDnljpS@?Fz33bUKupw*-dTk3;x0FAXh3K zD8{X%F82W)c?=xI@-GDIxbJu6wja!6ejL|*iqhx3;x>N=Tn86Hn4aw3?Aqw2*oqo$ zyiI%edD54JD_5@2uoL-cDvwm7c+~2jwhWZsJrg^86K)k{vtft78@TJ+;d9+TQS9*F zqVAvS?C@35db0bo><57>Z~(jI_AC|o)9172NJ3|(?P@D+OJ{N}CvuA&&aJl>=%QB! zKeB}7xyJKK4KU!R5=t9Lszl>49spVbb$eB5c?JtJx1{uh|d zJ=GK*wMK9c{=2RWbbbI_+l4iK;Rh4=OcbL+y-QQCzQ4;p$<@_W?2l>BJiq(yJ8?g# z^!c1LbqeL_*pl)cvc)*}i2VSF{Q$sa5YDpufy3-h>%?xAJJ`i{18)(pfDiv4v9RSk zPT2`!?{>OlEqRy4DpFRai-1NK%)tb14};SB5%8E$g;xM(Atq#QXHE|uJP_yi@|+kU zQ~43ZJp{sDQ`+s!vA3fffhRH6sg?fjicWT&AAz2}TXUVSWqaQxV1eRx+jxR)wshd& zAs5`CVI*fG8~IL;m0&6N)N@!DCUFlw1oY7>gI_?M4;bz3s#8Ri)^Gf?<=2GEm#(|f;tJd-?Lq9ZY*6fSq*W4x5Wz{y+d5YpT?ZJ1{ z?A5FDHDEbdq$l?bzDs42a&*#Ziv1_$kBW+-jnO7@2JXnSvEAv*LGgoNU6`)#2Z4GY zWYt32YURzFS08E5o+C#N`J5|PE;2SY*8H1%&Y3f(c$Tq=iHUqJRH%@!XVJFHE=#O1 zw!Ka72eASyP9yhJK58EeMk+_AIl7*fcG;PQT`u$KFyCvlpJ(FEydvMjGn8#y4wl>| zELaz&2>I=+2zx1MbJVqKS8*reS0U`EiWe^~&R?{8P;eC@zMc0up`ku0}3XR7%#`_BbR{DVa618 zUfMqi`Q^PAUmJw|T*ZnNsb0N$(zjKFOP4QGj;ZJqm&v|O=g%;ij|WGAVPG(uTl8VO z^&X1t`Y!{WGJ2KNi#_K|DVSGYfoxxYjLX7HxnoG{$6+hVf%ReiNA!`l6Gbwa$uIOm z*bE6f%q$V(1qWeEDD2;b{l2#Up0my#dj38`KMvJ^G%|Muo!R`PJxnBi_LJqBHn<;f zpzwEwa@Kv0XJDtmNuG`Sb6I$ES?m`&!EIxvvz4xjWz6)KSRGrW36|3*}BFqbA98=&*M>`R!b%(G{L` zUgTYe2;O-N)hmw^yffj;W#I{04ag_%+tDEUw^Z`CY12mZ|DtY%4rGb2yRBTgviA25 z4h}+o;p3xyhgr3*ip)KQUUMD)nnvEHpb6h2)etlQFo#)c>6%XdJ+AF3LF3oJv(Kv_ zCRLfF_d_}S4C{j71H>J)8ic;4lK-clexmW?$5VC#?x+!CVp{pdol4qwj2MX-SnEWX z2e7=&S=OKRWUbFvfYb%GK`rG%Z2oC(jS)of~OnO(SOwP@T=ss`6c4m6ZFu8nks0GNm$bPzeRrB)tl-)W$SNZKNwI&cVC&#*MupP z+UKNXbr&IXD*w(y-lk%QLGqe;#k>SsKNZv8|O|8o5oVcaCl zO!7gdyn5uxfZVAu%6(ACoN9kF=%@j~*bK^$hUaNWW}YdyIrjbc$v@6rn3_rq(xCbT zhK3sXb$gsXZl=l|dJmW+=Dv!3fYSH@-BSF3#tkwNb9R0HR{ht1PrjUkGW7$>rJ&sB z$CxsO%?=8gQ}utY);TmX>HM1#^n9FR!8JCdyux?>UBs^Kg2Ml{{;y`~&C*o8G@Bk5 zXH(Lm>?>7R&tJ+66ky7iOx6D>e!f-}bn?aM$rueTut9qSU_T@}qZqjRt}_(#v46S# zi@l=ixx5rVB*hGv{g4!s+OpsudSz2WuY8*Qs2Trs!F78ZMb1c;GaQ7ntY^V#Rfc{{ zDQ*}8hV=X7{O9MNf2P5M2UB)~xDT;p$&#O({|p;C5N} z8QR+=-8<5VZDKnb?YTr@0Ku_wd=hM}K?}}{9aw8`cEoCb~W5^!l&d!Qkdb78Vxt*I$2S$?n~|h2KzS z`Y+dqopbw8Kfa@~8|cJk(FU|+AGcr9l*bQ3M~qU))8kNvwhZU3Org_U$DZtLu#aVS z1h#xJqBYnI)+x>`>c!X>SU5tHl-R!#{yuWg=39ic@-Lsd7cM*>GW5ghnx-sTf##s8 z=I=J;3=lSF8Sa0$EXG86e;S$X+1bK|FUQ;j)~0dd`4elSfrfPU)Jc()_}fupzf1Vm zOS_=24f^s&oR6e&CTFE9({w$5wBcY7=*w4e_F%WBj$9UPxh#JB$syDCxLD6poJ;Jb ze(QYhwt|g%axVdY@tvpUV4RWxm+oaXdl%x~Suqa~YgzI4U%m?5Un}I7XS&m>+be!x zz=E?dO|KkA@SUmsL2uTH?%J|Y)4Ju3naUp)7D`uFo9OC#3(LJmBe#5Mrha5?|}s z-2Zl*(@1IC8}#H}$```U6}ry%&5q}n4CGFzlcHen8e+e=)6YT=zS;h_*gIR+#tPl= zYuP*KX;xsS8K-{PN6b;;0GKCwEu!&*4cQr5~NL9E(>e zQ}J`!GDv;(_73i@)@IOTCmP?RBVN9g-5|!SJ>#&MHkteOQLOL%HJ4(ik)8eUQV&8-_y`huP; zdpkYZ>&Z!FHvRb8@h0_fV4lX0Zy%-i<9jOoS>GVJw0?YNKp>Za2j5w3&)1<_=?zp-IoN1P0Q(7KSO08?ZJI~ThLrj_UfQKFR;us_dgqfe7qr5i8m&j@F7GWK9HNn z2RLi^0M`|GgJ2K|;=lu;1JVyLh1?K?|ETW3KY`ly#e2UU6?l=tMdkJ3qF|W$A@#%_2l2K7%;FOp>oK772IJR zcqja_<)1vb$9=&i5Ux^|Y5`mZo?Hfwq6`e)lSTh+pRM;9ox$04tEEh)n`!{SEKhPAV1fA1>THq%=d72R}QGy4^bz-HD7M$3H(6_hz2(l9Z-BhWncIR+ySxN zABKTbY06-qxF4Xw_OJ5&OM5oBzg;Dr>@|3tE5m=Fp@2s3McwDttNSW^|9cZ|`@f~} zV_f)2KSpssykhY5Oz4303xCe-;3l{RE`U(|GQf8h)pU@yy5BNf#oaLbn^#gc7pm|^ zLUA7Ja%p_tP5BRY6yy8XYq0J&=eFOA4=KlIAio`O2R`5=I1ggDAABhL0X1|0<`vp@ zkN{%1KRl;W?yX;5%R$aJ{*EB@z_vwg`dFUlIE7j7jWvB<9B!i`~6mYK-ZfO zEGL3LS^m{*$Y9H3z!B~PLU|mB2ls^zfVXlye8KG?5yYvKxluUU`|^f6t^xxp2Phsf*2PZSFNP81#`3B8|&45DIW7`vhFtn?fEdiKaYKr z)5yPt4Y*vm5A*}4Km@qL{a_ME*6P4ZmGb9rJn{vR(u~r9e8YPldI~!@I`tJ-v z7d-79$f-(QN;WLbV_pr`{a^T!#7^Az4dStXY8v@BgPlAE9^gLkI5@{`;2QUX59pE6 zO|q-GmyWm}`M1>hr`C-g_KxJx=vTU8qUraWa@+61ecuos`^~{@mVXr+u-UL7qdWJ3 zfjk$EkasFXn!?Yg`3~}O_4;P}ihoByoyfoCw-*8%!ep`yEM!aI@XpTRzN;M>CH`Bk93rb1y+*nfgVXr6{*O z+qWk+vpIfdbmRB&GiOD~!8-BI8jWLya*xm0`?Ns*u7@$U5b~-2FAtyY?VsHiJWiul z$g}Ci$m17Tz>sgbNBG!p6`3AIdmW5d-8e)$jYf;JJn?*ncK^L=>v4>q7o#|V#AXxo zd(ilSPVW5&A*9`%(ECk!Y+arKfe)*P?pEpRp~jzoF096?>r=Aoz97T=5)|h&-xQIu zg>l28ew8E08a6CBp`}c_5jC)_GP?DY`3f}-jd!TP&)0@aTI6>ySBJ(C^6Vjg_FCi? z4W)y8K|{N+7xu_b?5Q2tp*_aW9rTMaq?DSsrVu+rd~J}hE1ki{8Q_4DpSuf+7N=y0 z&lbmI*d0+cIw7C&8TouX>b8vuC7%rP5n-@XYJN3AXB2Qkjq~PBh|f&&!8{IOB&X(2 zH9yyb7aQ|uSdryo%OvNrl!(4VCh<(%jlKg>o+p(VLEZu+SU49}D`X?;9hV2J6#b@5xoQzr_H2o5@^1L}hMUELzT^@ZjjKOo0;;PSc-R=y16 zZ?GMY#vgK8*szEP4+cZTEn{%f2k_E z27Gde+C~f)Fg^jqZfsd{i2;6w@jVpRk!@);Z(!Q^0V_`j7DaAW9TdZ@cIt4a4szZaa^^aU29qDr;ABm46G?-=na2}Ferp=!isDL5 zZsRR-8*h`__!TcbYL6zj@e8?){|UK0{(#)d@5!y&&_(q^Zq+tjEH`wq+|b2RliLz5 zFqaQCU2wY8dG2R6eWA(3ua&TB6J>0#%pV>3Z zdyofc0!I`ut`SP$kiJ3WHi#-0<433_` z)YOv0bAAFn?dFnNRNv(ebZf$J9m~5X4;HB}|KgQLUj9wtTU0ot`o1rAx!kOPE}Fw8 zS~Q^pvJt_F1Mf4=n%)ASw`5Ik|GTcX!Dzk1YQ^WUvs*ob65M#*8Pl}n#sj~2z;Kp3 z#wt>!wBemdQe1%Fa@<6G*Tw3BO6(?)=)j$C-mo1S$t$oENe=yhLb-k<8_;yI4OzB} zRnJ39Vd(ZadQJ$E_Fo6+T+OyM?w0=_W4@|s1;9o}-pE(zujhQpQ&}rb!wtIwq}*=X z4FSKo$AySPF)Nk{6YIBX;rZfke?Eikq=c+ENp6^52>r$^5|&2ANW^K%_#LYXv7c8e z=vGa#hyxfcmO0B{UVkpiG+<$zvOo;WqJY`6)K6l6B8Kp~K2ezpCd|@8%tl_2EiSSo zFA^|v1uq>WUSe{a+%Cnisy&!<^nU5?i#QLL2@C+6O z9mvZ6hkhC(4R=nr5iF#GH6&3&)`F6DcbF}Sh6I^7IhzV|9hOnAbbP~0)_aTc?G-T zUOp0`CmZu63}>tmJ`@(Gu)SWMGnfEAsi4IU)bR<5*jr~_#!;-Z^EjQ<&VItC4B8MT z8B`^L%rDim_gH$+=1MruU~n)H8CPxfm(bCv7eR<84hDk()HUc0oPGxaFZ!1Rx^{U0 z&VK*U?e~RBheyXbhhXsb4|_d`A?>3*L0l+t&uz4z32}iM0zT**`+0@&e*in8G#?BP F000|7$0Ps% literal 0 HcmV?d00001 diff --git a/clients/web/index.htm b/clients/web/index.htm new file mode 100644 index 0000000..b56bd4c --- /dev/null +++ b/clients/web/index.htm @@ -0,0 +1,2696 @@ + + + + McLighting + + + + + + + + + + + + + + + +
+ + + + + diff --git a/clients/web/index.htm.gz b/clients/web/index.htm.gz new file mode 100644 index 0000000000000000000000000000000000000000..c85a898181e1a69bd15a16b59f36cc23e5c41ee4 GIT binary patch literal 20878 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 RwK5qQOMU;>`RB?50067%5`_Q& literal 0 HcmV?d00001 diff --git a/platformio.ini b/platformio.ini index 8d2cfec..e986027 100644 --- a/platformio.ini +++ b/platformio.ini @@ -46,7 +46,7 @@ arduino_core_stage = https://github.com/platformio/platform-espressif8266.git#fe framework = arduino ;platform = ${common.arduino_core_2_4_2} -platform = ${common.arduino_core_2_5_2} +platform = ${common.arduino_core_2_6_3} build_flags = -DPIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH -DMQTT_MAX_PACKET_SIZE=512 ; PubSubClient Specific flags