From f169e20f2f3fbd205a95cc45cab1650c5b6af971 Mon Sep 17 00:00:00 2001 From: Dennis Fleurbaaij Date: Fri, 5 Feb 2021 15:57:45 +0100 Subject: [PATCH] opt-out global KNX, ability to DIY construct knx object, minor cleanups (#121) * Refactored the knx_facade a bit to make the auto-building of the global knx object and the buttonUp() ISR function opt-in. Also added the ability to quite easily attach another serial (in this case tested with Serial2 of an ESP32). * Added visual studio code temp files to .gitignore * Fix for missing build flag for WifiManager in ESP32_ip demo * Added example in which we DIY build the KNX object and use ESP32's second UART to connect so that you can debug with the first one that's connected to USB. * Minor platform cleanups to remove compilation warnings. * ESP32 can't handle the defaulted 8192 byte EPROM. Next to that I needed to begin() the eprom lib in my setup() to get it to work and that requires knowing how much memory KNX will use. This fix makes the default more conservative and moves it's definition to the .h file so that other files can always use it as well. * After comments from thelsing, inverted the logic. The default global knx object is now opt-out by defining KNX_NO_AUTOMATIC_GLOBAL_INSTANCE. See the knx-demo-diy project for an example. Minor syntactic cleanups. * Removed hardwareserial from facade (detail which was not needed) Placed facade constructors together Minor syntactic cleanup * Fixed knx-cc1310 where own button routine was calling now private internal knx facade variable. Renamed files in knx-demo-diy Minor syntactic stuff * Added gitattributes for binary knxprod files --- .gitattributes | 1 + .gitignore | 1 + examples/knx-cc1310/knx_wrapper.cpp | 2 +- examples/knx-demo-diy/.gitignore | 5 + examples/knx-demo-diy/knx-demo-diy-tp.knxprod | Bin 0 -> 41318 bytes examples/knx-demo-diy/knx-demo-diy-tp.xml | 133 +++++++++++++ examples/knx-demo-diy/knx-demo-diy.ino | 138 +++++++++++++ examples/knx-demo-diy/platformio-ci.ini | 24 +++ examples/knx-demo-diy/platformio.ini | 33 ++++ examples/knx-demo/platformio.ini | 1 + src/arduino_platform.cpp | 5 +- src/esp32_platform.cpp | 9 +- src/esp32_platform.h | 2 +- src/esp_platform.cpp | 7 +- src/esp_platform.h | 2 +- src/knx/memory.cpp | 4 - src/knx/memory.h | 4 + src/knx_facade.cpp | 130 ++++++------ src/knx_facade.h | 187 +++++++++++------- 19 files changed, 529 insertions(+), 159 deletions(-) create mode 100644 .gitattributes create mode 100644 examples/knx-demo-diy/.gitignore create mode 100644 examples/knx-demo-diy/knx-demo-diy-tp.knxprod create mode 100644 examples/knx-demo-diy/knx-demo-diy-tp.xml create mode 100644 examples/knx-demo-diy/knx-demo-diy.ino create mode 100644 examples/knx-demo-diy/platformio-ci.ini create mode 100644 examples/knx-demo-diy/platformio.ini diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..163bf16 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.knxprod binary diff --git a/.gitignore b/.gitignore index 3339a25..69f65ed 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,7 @@ _build # Visual Studio 2015 cache/options directory .vs/ +.vscode/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ diff --git a/examples/knx-cc1310/knx_wrapper.cpp b/examples/knx-cc1310/knx_wrapper.cpp index f19a65a..19b616f 100644 --- a/examples/knx-cc1310/knx_wrapper.cpp +++ b/examples/knx-cc1310/knx_wrapper.cpp @@ -11,7 +11,7 @@ void buttonUp() if (millis() - lastpressed > 200) { KnxFacade &knx = *pKnx; - knx._toogleProgMode = true; + knx.toggleProgMode(); lastpressed = millis(); } } diff --git a/examples/knx-demo-diy/.gitignore b/examples/knx-demo-diy/.gitignore new file mode 100644 index 0000000..89cc49c --- /dev/null +++ b/examples/knx-demo-diy/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/examples/knx-demo-diy/knx-demo-diy-tp.knxprod b/examples/knx-demo-diy/knx-demo-diy-tp.knxprod new file mode 100644 index 0000000000000000000000000000000000000000..ccb136353a00735032916bfe4dcfd1f3a3969ad1 GIT binary patch literal 41318 zcmZs?V{~NQ6aO3Aww-kBNhY>4v8{hY6=lF7FhD>+U_fk&Tx44`xa!H`KtRl*p+S&9C_sMNy6M{(IysvDV z!-0@|ksu(TAUGh^`g?>5J+q+qk$ON}%DMmNde(7jdpc1|{lG7{;j7s#yAb-Qg+;5B zH1;RU*S67ArjcQ|2@?VepoC@q269UY3YPxvMMTCFqb|uM z6)RUNRpTB@+xFSLF-kE|YUbji>hf4s`q}k)`uTji*#$5;5Sw$BQvLYwf7gBL2^rDI zkQ_O?6i^$yQ@{V)^?CUD(Di(J_67tMP`&H=OvWF)`NPNOsl4G`wsMBD#;;>_lE=-7 zr1=2-#M1XRkM1lor{zb=y!dlRC-Sogp&*3}hhFaO75P}xlkj_%CG=sbV;=v)AazyW z3GSmkCo!>}KznCmG|=Y!&6OshV8en22Uv=|Sv&ir7=W2SH zQGc5fqvqV%J& zdKI!gzecP5xeI^qNw2~}&V>?%|G>Z^OSoQLT}3vd=)~RHS@bs%Eb$B>EFkUqCP0u& zv%G!Mb7kNEy%84~)SSU?s_=EY%p~IQ6zb?fRVE#d9(RqoKJm$)Ct@Pl>DitpE2wnj zCnNHfoswe65teSNSc0uKDv^nUoMwF$3`~+vFJ;1&pa#RD3CFD+SZ=#af<)+osgQPg z3EToC=yI)0;~8pYKFrE%14$Ku6kl>vGWIDEXmr?U-i{K6i1S`f+8=b3idNAwn&~oA z886iZ6ZO%)P|%bkMSh=*HPZk`^+Zpn$>zyWUTB_&LNTUl=2t{3sQtROpLGec>AgT&A$ zLTiD|+KvJ;+2<8l%Bz26wE+ecqI`uz!qrYUw|1FNVaM9bOJwB~WT-Wx&~8d8amot7 zKY8KH7a_4xOtJaMZ#5)4xuz5+mCoOA zAGWaB*^ky%h8dE=xb_iB^;CQSxw3g`Yh-dBsaB^E!n4;OAV5uj6gEJ^@N`q>E2P0d zmD7`)6^@%QV1!P}y?R7WAsIfv#-OpPC}Piq6`#FTL1~$5Nuw!HHvBT2O5BI$fB<(w z5VhtSMS%JjV7O7p&@-;lyM}gch=Y5rVyq2ubZ8>x=cY!hWuIctZ`+u z)m;+^CbArOJd{p@rAO7YJ6pUphXCqOC&rP0-1m7p{Wf!!2;M$;sPAtBA3x0aeQuTbR=`!A zG|Dk_Q)zch_^TAU*q*kwp!x@gVl=`G3JY+0Wlg-Xyg#8(cePP|Z_mPAD=ap$GB2PDL)NNicWzLC}%2H7?%7!E@?b%|r*O zY4&hnQ`s|ke+b^v5d|A`SwOO%z0Ygt(oV21w}bebwZyq7A#bJZH1|klxAWN65^?Nz zE7dJj{w#L|WzvN-4lYBX)!-3sV(vOx0sRG@sk9LIS|Nk~p>!soZ6 zmh?BY(8GOfm>VJu*jdQ9oo0W<1BY>=s6%jbIZDzo{r7fyUX5H#^e?X zW!dk_1ME;p-KM2whTR|>+vTsa78ghwFKJr0>U9v+_(2faiLK)0nxXed8J93XWO09F z!=>fLzjB1$35&Lj{@j~_QSdSuzg!-|^b=x?c1r=L?;ZB`mf@R3WZHuYi#V?ny6(DF zZdw{vYKjkxG(@UUeTyO@`vO1-Y8M7!5^zbNA6(nqr+v=*wRHX zKkv&MmW(%9qzm|V2jhxo4^6d6?R@~m(XvbrDe?BB5bJE=}=V<3eg>pow> zaHC1mjIX&h0r{aO+-WUNuc=cIb6|_?08SUFHXT^jUJF`_An)cF6WkTwtWioOS#|g| zV*4k;;gd4-Ye_tU&8qD1k9&c5y5+{u#^Y0QrDHp=X{@)_ zZu_$DocY)_6(K8QHN<}I(Wv8T-5uyo@CY3zGAOxlVcWarn=agXi=moOmEuUEptqr^ zN3TPtFFwiUTQd}NbFW2d?mh-tLzMV1>-W+RAFv@)4mgRJhCIMoabLQ6ADqGrh{QeJ#mrqgSr>N@+*mnpNh zwIs2KL21@E0rcwhn)ZISJ*_6~Hd6so5o!TUKd2UP7Phi8Es`W>O@JOq$Iy2f1j_hLKCssS7hj7@t?VR&pbXKSFqj%p#Rm&yYF z_%NKk3i3#~n2hd3jX!B6%(LX?{2XL@@bXZJ(+UEN^1E!&5=3p;WygPlPp@F^)tL4a z(K0|@LHg}bHASpzk16C@((Pg6I4->%_;I3No;SpAOQ*px9@6UYWoSevXUWar@|Y0g zcx^3dk0R5+Alu4kzkRaNes(LGAP(y#pO($|2QVr{bu<}rzh)s#O1VRelOqv$*5Y}z zt#k~$5yaiV%ktDdHJCbBTQCosj;|Q+{~h9ovKOFGJMS*12=B7<<*m%BQ&_`CwBOT@W~pqd zc;t<=l#)@!0!Y+MCxkvvxngHCNpdi{3AyEAz9>Tl0kSbK`rOnQ-7=OIqsXNas6|BB zrWx=NQ+kUcYz3g|_;*Ng+JB$fg`8c)w7iXXu+E^?*vKvSu}RX#d!%k9n(4E>(1l>+ zBTtt1TK+|I$EhRK#>$Cx#Sz@55E^9f^#)V%Nq7aC9?M+-AXd?qkenrwo25bq_P_vz ztte(;7Lvy4CH6$Tg}L$@EMooMSm5VHt%}=xhaZyjZ)i2dEI*z?bdbdQO|TrOX4$5l z%zowTpu}tRCyP%0TGkiFTf&$=CLdfQwA{Is7QG25I>jHGj7L^B3z4EXl3n>NUESl% zPc4}IHw6O&^;WGvnTsBO0d9J|<7-n_v+L>dWAc713 z_vN0nI7SB&@xlNWR^5>IwU``)OI~3L;ycRz`UK{}f;KHlI66spFaKwr%zk@E%KniA zX0<_+;!0b8ZnTAi&5~8mDfT`J5(*Ys{Wo=9K!XO=>xBRK5A0epO=IpYN~# zvwu6ULyWpGgEFjf>{<{>@2}^y-Y$0%(=eQTv_TU0+T6LCO`(BmyU$)wanlW2!N*A%0#QSOLPNf=aK_1KO_3 zn09JXk}umWPSRN7*XJ`SW&_G);~9<+bni8@p|QGAwXeM>q)MO-Mf$(1bGob3aKeIh?tk0&s+z~404AQ zXFE2iBv~l+t5MqksNt`@$9sNO_!*%xAB+gawfWZhQv~O*^+ThDqp+x85*%m+oYEle zt+bQrua}dkmH5?2bCn)o*n2|$v144!58dd!Ag#;XVK(s2PU!*brxa31fP6?m?zftk zt3>HzfM}$lKQ{Q)zRcR_hN|PlLcf%a>us%dLkm4(Zc%%W#cIVHS(`anY~By0PFTb9 zTL#aj_D+R!1r>xC9@T*Vo_7o`CBo=~F5C)WZJ&Zvr*YUaj=Pvz-tPy=Ry{9Q!`;x$ z8Y5$G+fIx9$A;I=qc#o!%eLott(&%eo$;Q6C2dh+BWijjJZ_$E4+LlbNX7EFeXodc zvtJd}NgGohC1v2YU%Xe)_ywd^G?rdGRu1GIIDKX%1#YIaPpw<^<$wWtKu`xxv=Wk8 zgE8y`jZ_FU7SSw)s>m#6#!s{pAry6|tddThk(ni(7qPCs6&tj*U>4CJ@D8#w#r!?0 zZ#|yUy^JtVR#*-;BYu^A4%=sMTBfd4}@H~r`y3od6O|HRGyA=h&=rQbM+Neupl)|f!9CS>~9t6Cr24uMhTo( znMbJeYYuc^rpMx4H#^YR@<8Ejw^Ie|NrEvh`3MH=DylfE;-{i3{Q@9wgd0DA#d_b@ z)Sz{*p*$Hu+TYN0yu-AbJVn$Kpue@p5CHQMY4Xkrw|KGOohdLhs#F&-ltfbal7AU} z?Mb_~l>QVS9))n;e;`Ogg;`h z>wXE+CxnD4t;L-h3qLyg zP9uoC2fbT1ked|#^g2W@$%0UcBLUKGl3{E$n75rcoZO16U2nQHg-3CIK#@Hr&tm0ry_HXw-{tc~KRjh0$LXZsZ7HPto*$@+p$Cn;ZqCxlTHdWMk`B*= z#&Eu<`d8txs}e)g1v%cmI@1cF-MzUsEB0Famz3AU^7gvM@ncu{`bF73|8KweSwHCP zz6E8z=Nx&nZ&MvVsvd3U+XxMisWvE|LrG-DFxDE1Hci&106(h8gZG!R-q7XTm?jcn z?+;D01cIF?JbX=^O0hPlfl7b%9(Y|(o8!E}Z=BRzUJwVrS5qLdIjbGrpS+8&JFOsX zkw@%K@GI%-J-j$-wEVe^Hvs+Ah?vSys={u{|FwNB)3|5kjnRsG;&Q;e;{V`uL?men z<-t-8e=<6%r{8%n16FBZ-Hd;TM8AcP5PN4$5nlrBxm|Hxl?ts8Qms|e=|DH&_MX7f zJ{vw`JVs~? zH%HqjR+ZaWanfMZw_UxhnFqeeg3%O~+EBV!u34mMaF*)Dpc;o8U{|SiWi@ZK9lGCh zFCE&o0(s<7SfK|fRiGj?E$yw@?G_9e7WEs0NQv!cvq9w|0vY4`p;<+S7&TUUy5SPs z6Z`e5`XuZksCW0V)!kbmZn;qmGiyBDrl&TEXco2HMal{8X~^FHnI1B^|F);1$`&So zMQF$6zsx6yS$>t7|III2rNQVaGCrpKia)3TLEv^=N7?`IKDR`~z7r%I4z@qS58IbO z{0P)Z&umPC^0R*Hp2UO7b%!A!wT|gk_S+*389?naumVjpBDf+RKez!2bq7I*NqUUS z?b@OvAvgei!iq@5b)sOH*6n-<%Vw5d>ovK1FAJwV#Dt|XONZwt19Pkr))ZUs&JAK3 zrRFN*gLHYz1QNeLb!xEwMftqe=y^ym2~iEH-jpkn1-wtn#$6!QAEQB1r0Hu^np20i zMbMRk9!L(a2Axup$~5MvO^wSnRC?bgi(sz&Kzffbn!J7IbbpP(1vaRXV({O-C9Dtr zXNvSs8ivuKhj*h5HNqmF=+f0--al4o{e8Z%LSnr_*{R+>$Kc5e{DJSS(-k00t`c36Vr! zFaKI!+D$spKNe{u7tu!x!)`!m<$d{1=7GzwKy9-++Iq?5xi+>Qw=bQ=hD_h5cx^qa z!ek8z6ZKe{M8@CkZ&U~O_ZdLmRZt1?>Qsk%@sa!CT@0fj2nozJlwV>c2v`EcB)Dj= zjS$7~!wE28oYU4ms_gEjkgG8`HMJ*_G(fdWp8;!f>kH1CA&W4nJ`; z5~n&EF_a}{q*t4=b0eeKM*2fLQA}4-#e$MW!P7@v($f7#Wb-3%6MnO*(9y{8)R$9A zFz*ekh_UGptDsq>QPrO1`+=>!n12Kc>PYEiENaf_A0um}iw(aQi*`7VNiSS0FBo(v ziNz+WlnY$Wkn|H^A#G`5D9JtwJ0j^_x?M~BtxrJneckem!_X*gY(M?R7ScoPH z2=M+a2_`9a)=V+&Phle9C*zJRi2j7DnIhYt5<|rB#9Q%05w$9X&nX6XQL;?ORu$0f zvIgAxXf|IEzm>=_il96!{_K3HuN81ii%aoQoILg$;$aF~ONG~vAw5djEdgLd?+%R* z!{T}6*es*Qt+mnkmP@+fNXWj@ryu8xa5=;@kyUg!kYchk|zNVAnx?t7`H|$ zKnAN53gK{u-HINE@PoUrQxuaZ6tknp=otGo+;nW>4x(1RJ4>nxD%y{O_jqN7mY8w) zrcq%0&j%HqF{ai+S0pxW=6iQeEaqX5S~^$b35Oalxhz$xz$0UWTU)bD7dj`Xtw?U@ z(T15R71^A1^Zh}07E#^xs2bbt)O3#nc^VC@EPCJ8kB6FKDi4p?>t;iGwQIm_-w02A z9N{6b7W_|cZJgmcuN%}N4&!(qL5Jfs0lTK#iIF|eTjhEbm4&#j-UQ@CTJMn!HRm4| z)$N?0vbKcZuc+zTN%2zC%%jsv5N772_?5q3v~Ctw`H%pq28n`8iQ(^pv#v>?5=W)!-mpkgJXs?RiL zw#_Du(i6a*-|9=jN}kub4hNxRA|@+yC}d_N9Ls3yB5_qj&A+rkH)`Zw-ybwGG{`cC zztPD2BF*BU_u#CNRfPb9qGjj(`E)2pYf34splQ8$3*^gO>>*?AlQS$dTaqpS2#w&O zwAcpl|A6XCbhwUb&X`uA3~JSG8lreYXJSFmf)yT5v^LpQlkpU-N#UOHT}LZF{_$Lmmw z+_=)5+Lf)G_%_$?CqMm;;^vL^3br=byYjet)xvP^7ZHg%qLp4RXM}|F3$~$Bu`pR)(_OL95n-OTE>ZAj;C` zlR-ywlAg|1(V>be9V5QAjBx2QNp!J*RHxATia{~zqRIVM^txqiB-%8NOPu{H3FOsu`KG_cQ@3_QkugZ;w|in12!}E!M&M6R5`Xb~)ru7x>$KQ34B| zbI}OzL3)g*x#P^EnvT}1?3#)74U5?TAenLR3+UT;UIyt=M_vzY)7he|2 zqz2w=<;8o?Q)u*A$jx<%4%I*yo4|CoS89Lb{F&P4@fh{9FpN)#o^<(wYMn8Yx#y?_ zEP#5)zm@f8w3c(##rn4p96E?rxZxF3%?0*z8BuU)2*-vM+Q++;D9+i=ZM1#3fQs~y zURePw#H3OWXi^zgMZ(%X#99$z&8Y4!9ZhnVIVa1NB*q?PU%?U*A*l*v2}Yq;G*S(6 zie?lbBhM|^yG}3BqNt&=UCZ6VBIB@8VmFOQoJbW!Fg_Xs`VwWEOgDS~EcF3L?h(bq zkZoS@o_)MYpkmws!EX>``We*TjkXXJI{XkCPlD&g(5sg@(Z$T|y@AzA-(r9F*(K~h zWXNX+&7>DoZo%AIWlz-o?En>#fY4IEv5zYyQ0Lat4p8Gi-S)H*Vosp2KA<%mgjWD5`eK(Bxv9G#b7q?SKv6M8Pe+iP% zYitsa^)yUS)bROWB!p(m{oZmpUuVLsY&Q&JjR}`fMT3E%g-&7`w(jq41CG@cRsvmD zC3;2Rb}ho4>G0!Y6+3w105>fGr?gy$u#%5OQsg zah!&-JQzksun*f-IOVbMo$lkz<$XrSdC`jA&Nn;Ts0+KRiM%^j|$fCT}G|TKWkF_&+dVdtgMx zjJWpE#ry(HK^>*|IaialAVNxg3| z;sM>frnATuiyqgBAKF=Rs}(A8h7|=LN`)k0O@j69%+7K57&BO_r=BK#gFjKZS|`OWvkeXJlk?L_v|`vC-})Cc6!Td^gUf#*Jep^Mw8t z_~%F83Xy{?k~;E&mk>`*g*(S$dQftE~%IY9CNrB;zU12W{HJ%4+| zUnDhOE)!d1MN|?i6A4ccC}|YPk)nmhiWw}+0h>bCMrCYP(od~I1MLk|M}7N@t?`A{ zJok|cfiq?1jfxT;0wXs8LT}V-Me&4M2N{$|T7@_o$c#)>)7z@=qRarKh@qWDV!TNn z-(yH;eUS%n*YcOIY%+@?TuhJU+t!J7?!23Sz;~u=g^cX>K}XC!@;nz;Zr*Bs7lMS_ ztLnRoWcevD4&~AE>w2mhks{TjdD)H=lpSFK4Gg_p>W40a5PDCDzxA7&)LC>g$2f=U zc(D7LbP7V!p!i^c&4oAw`dXL_y_g7l&9uFxrzip_K4_9!WMkf#cz1y+>~}|a#tJ+{ zx88qIs0rpj6nvuw)!vd4?0m&fCLG4wkbG-KdKv|u0hAFyrsz-ETQHw1#Gyf^gQqYx zL(^Q(I$VUlGeZV;yA;yQ9wmqmYj24OwkjMTv$}SKKxMr|q zh*IMYmBP*tV^1fs8%#`iBG#>CXov?`oroIiq(e}<8tcU8%Mu!pPCr2UbddLSK&nZB zbE(wr<{4{XVGFq{7I5{gztR6%ngUfGgPBO5gkW1lzX*YgH;@S>>Kf*$Geps>K2iJ1 z>%f|A?O@OQf;jU4L~$)xfW2_&K!2{s&Fezws6*F?bJy*UH&vwI9m z6#p^*f#UIqkcbz0(KHq@iUt_$IC72{>_V`O4aMQFer$v84)+${5WzxLgiu9Or(-M z7zIZDfFcn(p59r~M1t+(80o01y%`BNM@L}9OXANb#e-`J-9d*nNu%<_piGAQXUII~NYk)9F)Yo0UnvD!v|cGOEzfP`AuZQ$ z<>~pAbW0G%S@uW}`dNN?M#nu8sJF35o^vwHm`^k-le9e&(`tnf#4eJBu*5E*%1<0R&3iPS3B3|dh4C2QrML64=)~ZN5rJ_3FTlR6_;hq}AJQuU3n{*xH%%e2o##FB zAB^!m_2Ve}YT6Tr434EJzgzvTO$#g3Zb#rA6xbcF8|5y zIS>M(4y?MGuErjKfl(K%Ah7w>g#)_-r;Tm1+TM)03uhrFD$ZRGCR7=N)$=PTR|m8r z7P1vg%bMM-RD>So7|v2mWc#Zor_x7F6d~{{bqZ&R zdZbRX^i?xerC3itn)DOm1rzR9U_HzoJ;vpKDn&PajJ7DBZ7j$&TnU;F&IzCIr2=0GsR6m zPe=^%USg#%!zuM*eTdq~2(Lf@*2facoc|lUKTG^&)JSJ3S263L2OUAt)q#SBheA zW3K_JoZ~y}-yY#_65I<-V1}e}!iZr$0fBkJis2#I)iK|F^>h(H{FORt3O!hwxvEh1 zY@LnnZ-Gg8&D-SaE8G{pJI)zd%IUV54|=zhxM8#Fxm*}UG7hc(ugTxW88}u>XB;@D zioyDy_f^o`O(V~hx<@0wsF1SbOe>$X<2*}Dh*6Y=EF$`^9RG=dKAlnhYTG#+i8c0B zw|m#Q<fqBoL=tn`efi;ly}VDJ2Qi|`L6 z;;Bm-s9zM{Yy?g=zx`5dou+dnbq0m3%*%Gp_(s$kd!=02?tv1;;h7k5y@vVA&aJC+ zKZg)@b2o2YeL4yvp#q;~-GYWI>B7Q72t%hfZ@U9K7`#Nhm+z^#QFK{kzWqTlGT^s; z2~l{EDJDNiPIy4GY40^m6|3)+tVC=~6O7XcB+wPpVjf1KgBCek(6v2*vB!g@Z({a8n$o!q$ zMAbe}Ine;ZX1LU64rXvC=W_)54$Jo|pTp$`-Q#*fTIgk*tg z;p%pO1=hub3R8%w_nlyhY-;uDWjip$lwDV=2eKcYkBS7L08>Y(y;_NdHC;_o4KiOW z`!PooGX~3dBiKw8M2q0RtDqLdZ{msw?rf>-Po>ik>?g7w$&h4K&=RzWP?#^8tY?U1 zq!gt7oqN=NMZ1=cgx_x(ji(H3h7yMd;!9_>M8M=P15-SN-7NAJqQGr&m~ym-?{7{+$Pw!wIsi z7!olK#C!pf_5?Qn2@*5^6R^LB8GfxqwN0~iD};vYX2rWRO+Mhqa4JW z0uIVHX}+^76HF@Ab}ostypqk`i4&EXn|`zHA!0gUA5nd@ptMCBB^lFebM-!vMV;eV zje`<3Bh@}_vL>8f~I`QXQW zsixV)pWhD#cVo}GIKfrvKRz{GbDjv#dLLFGEmH%^rExr4IRt@=-@lPDdIyIrQC})V z_|Ibe`q(kBvvrD^Ykixa|1CrJOeFQ~k5)&;213o~E2pWsYb|m*u(3eN-&7s)rz^A6 z6IOxs5awN*YW=X=m3TVUfS&wj5i76nyO0b#r2O%Z_+fP#Ks_ykmJcnBQOgunR*;Si z@9;;z;H0z-w?2TVA7ju2VYvp@1iOV(WCgQaajDV*XX~=tU8}ISj0%OwTf{PWms5+d zI)M-z0F6~ffTQ+NqOx66mbRubY{tppNjLOcO{{yH1|WjlsS&{0!768d9VT zVM&5CPQEezV-K=wgk`-hY_%r;+*>Dg7H8>6F?eQX#?bql;z$8!14f{|!fEpYAMb^# z`~bwZLYuBV_BqHP&d0hu_V*+zqcS>>Th9DT;KX(&aji z7}Bh!H97Zl>#W1X%n7w{XzDpYe`kYUOA(#OAaUW^~Kyr+sXHAf6aGRkzkrv=IK z)0*{7U#o;SvQ_+}+{DbAl*bQ_s5y7@vX&yEyd94VJ?`SkFA<`=Z)W5#kEnxfOCaoKld*>))$LW@|H23|M&%O^9Z;kShpo>m-{!+WBNNAQC7G8^e3Q@l?;gCx-CJ#{#Ab<}6- zh4{JZGq!YN_BZjj+SU5bzm=kCE`tzK`!NKsev*(37W-i;r}QQ~xG$-sqYJ;`>SKiG zcwQ<+;I+67?fTQYeEfu;6-KtQ!BcRfn4JUru(b^($_ z33%#4my6_VfJxBI`lSu&&4in|RcZp(n<(l|zGpS?!_;EuBQSE&KB3 z2e`2eeIEzgM*&#HwdX|Jj|YLK+z2*Isw5ZQp{78hqE(xH4P%o0MVbj7tO*|Ej#ho3 z4ZIoW4`1;H+trF#V$oIOWrfW@8&D5p>?(Wl*8EOcMXozpww?#Ywn4E5Bh_p zJ>k5|)fTC*HA~k3&?i$WG>3aj>u_gceAu9s!)n(vVtM~a(HgPC#5z3bLHziF(?!ON?* z^)l~&cX6}|*k(0BXB>EeUar^7xp9W9%s*E6Ct+g`@6ihh|)@z-%(EJN;z!4vQ0p~Xwi;8Aw`lUN7|h5 z!BE`?>|Jhw&B(v4TRj;s@D7J=M<+5qXWYZ1YCN=J)*X4uJJX*hTmyzVzD0FE82sQj zM;je9ID9Dy`(Xp}J#!3EJ|vG*GI~g$q5i{p5nB88?#2m6&q9RA8Q@JU_sYM!eEKF>qijLV=l^*6&>yDr_1=UtaV!G^u5=M*w6v5 zEhW7vr5Uud&FrGIR<@wq&M%3M(JgdRDm3YBJ9viY(`1(tW+bj6v2h?SGfj9>4mKs# z`=sATxC0G(9vEKBQrQ6_Tu23Ykix+cE7+vh_JBoCT!DA_Hr7lo4;^Y>QbCJQ82=H5y2 z^Z3s_&=iQh?({DazlK-fu&!A1#gNh02*V)4XTXGu{(8558S-y%(Ue%ruumiEJ$~&M zD${gQNUjXz%DR7iDPvzJL?Pnv;BTl;cVhDa26fgni}I$Ulr=tCh^E&M)G~>zjn=d{ znSR@gfI@?(C&z39x9f1fZFs!|cTjS=H=bHgl(Dx~ejHcEiCOU8zXPXEzLHN*)a6i3 ztc*H;nkpE6Yio{~)5~nA0l(KJejD|)uqLq8rdMA~K~|&q%g%g^JTardI78fc$V?&m zCnT{ahQ>UXOB;vT#9!HEvoAoQMK;aAWNY8V&%z?Qj#`p=x!*C=_g%IXB56{W0PM;2 zA~3znE2Dzj&L{;*6nD)Vb@AF7BBc=0~w@{@M{in_t+vUi{347o)X>6kh}~(e7z72 zdQE@82llnXZ&Qxry{T;54F6aUe0$*4bMD@(^D)3wdsZc~Why@;DpL=ZjFVhL#~QqDENRs`0>8v4t0Wwf zyirrpB)M=#XCx$559DcfDKUYJC^DHam?U0Mg~Kj@)|3jij3#@(1vu=d_!4tZSB(vX zZ6~UgZ%1ih)}Ya!Fo77uiWlCyJ{*N$red^F7aOed<$_bPD^8D7Y+>DEb(Ph-@*<+^~a_6>9h09SS!S5rji*Wzs_yRy)*0L3H@meKQiFUC?LY^FQz75aLx^T zx2!tdYqkGe1VhgMjR0DacVTB_pmbh#Yk2t7zTSGNmriW_3jI0kp_Js+)wQC2*zv&M z*@{uu`tJRQgSbxX^;yPs^S71ILs5+lX5t~UA747J zwAM0zSK^@4_cih)=gF#oTi(5U8=55W%){0IEU)x&$F`?kYu;oUCd3qtre^$-v6e{ zni}P?2T%eIkdbs5Mt!~4P?zdnub~%n_OeacVgabM@RO?>L_$@*^bovLhHTIrP2~i7 zAdf~fgzq;#a@R4`mw&~60J7k6Oc*<}L|M*|aDjl-4AluLJZd(TI)cVD0C#_#f-$^ms) zin108@(>BDux=XY9D6gXi^UMp%_fr*dq zImyD7Qh3?>5Wz~0YKCOxtFf5WppDYNmlMh#E=~z8TGqnyZ|o2+V2NDwW>7yAitZb= z8)rlN(43>ASjq9PDty;cYiTUjsn8w=QHt+5$&}az8?>y&s!_%8YC(2C#1jI46;cn^ zvSajAQQMP+%eYK5JE9P7Is07pfK2YEQvFT(0WbFCr8z|YY}Tg#_!cj>nsr?}jIC8- z!lh2v(t99HnPyT@ThK{YWx$+b>WKi`j6XEZHFhmvw7|YtRvJviV_hVhBz4S6t4p;` zhutbG{lB1KG~YQTfiZqY6{9sQ?z?8I?8!&wR|xuX4$ZAgH9nk5`c?5VzUh^cu&zU8 z$}*a7jg%yn*Rdb{e!N4ct5XnO=IoFtY2xhQa*3_(M3UjjRCypM_^hil?rZPZ*4%2m zJT;)&`XIchceF$I62Msb)msjV`%52|CfC*ZueU?us*cV+b9k8!5|%nw-qRSC`e&Pd zSMs#ZObWKP!PaC+DT|-BQk14vxFGK>9j;R$JVPOXiKf!&8dH;kRh>Jfi*2f%waa_o z68D$C{LnUavSz}i>1edZYTlJO( zYDwvv3LaKiN6S!q&(G(-@(Ak4Z9si3C^qbyIt=E(bp=-3LQIwe;h;jK3!Q)x&0fBc zDIFmYp@-3^4CgqfOh;-_6kFuEL{KvYOVM=`zw!&i_6+CEOj+TEch!cD)rHH@6)G!wRsJgHJe6bl12VvS?h*McTAk1M*PHrO(fs6m_NmEIb_Us)fhq1F3$_rSA z2zXu!@FK)k6UVQU`cn`tL2rr>j9@ME)-hAv;qeodWyiLJ!$Xt7bQ)#%*mh*UXE)+W zh1GvOZ*7GbF4qzE5~UrF$EB>Pkmi&0yBv=a-Y?FG9JztI#? zo`CyEf?l!p+j#ih-pDZ!)GufgUqLoJ*xIsk~WmimT%q zGvdg4u$xGCEpG;HcKzx5Gdd<}iZO5CtYks~p3}-Uw^qdDJ5?)JZ;cLE6&>Y#hL9QM z97rPk3Md%08bg6Lxu$6r%T)ilD~WIaxwOz^`Tu1ddr)~cJ7l1<8-gu*y(RxnuZZNR zYMkBgy-Oiw2L^Y3G_U_jA^|7}L3?X!BBIo6IginW;Her5-xA&njA;)@bhvBjnUR`=`TGC>Vl1R3 zN}DF!*c00}XJSq?!H#WBY}>ZIW81bfv2EM-$@i~w&ehwf>aL4kUF&`7sp^q3;n_A3 z7)*98XXajKO{}OxEqsBK^1<|P>XUGr2C=Dm`v=@dI$*9#q{M0fzIQ%&yZx+>!5T|8^>>x3@3n}hUgp3Ek5>5qlNH3}rwMeB1W+^dprP`f}*A$=Jm{i1-KV+E(HmXR6? zFuF@Q0&6(}_tJRIZo09^NOwkwx7iQZrkXf!T)%B#%q}p6T)!A#?!{?O^G%ZytP&Tu zC85{0KZG>cvy^8X*PfhXv`Ua*^;ynk-$ zkj-GhRY)8ERUDB+A{llag4`^$4fGJmD18JKpCq#^C5(pdMa*9fLK9eXRCN~a>W^tr zre(53egRMZhgja>nLQ+ax1#_?3fHqeG@jO70%e)y*%_ANAZncp?Ci%@RZc+%%TKXm zd4}!QVRd0h5o=P7V~&kQDHn&KWSyP(tZU2+7sY*AqjB>oN?0`IQ=fLF9w{(qdH=v1 zkv_r{;@VqWxf~ypcs0VcaIA#fa(*mnWtaN9TZ%N<>PdgZrPy0Gnq=Hwrh049gIp`t zIn>t;&wnyY3$=#vKi3vHz|0W}sQXeeY6a&mI}6mcWQ-95Q`P*vloiGq1g|G7g7pb7 z0c2i*w+Zxr^?6*1qFK&jj#LRyUW`h^KCQ#aLh(VfYKLd2jLiU9ydrm{XP)*QV6nA!j7-fuO%m zd#8bCv~JX%<|o(xs!x_CQ>OfMRQK{W+LdMKjY#rA1UUSuc*U;i+u7CTf!9B*epMG0 zPiPpqGY|*DjHE-lRe)uLjoNT43!y3BNR?C4o_V6LTSdy2$RWPdSCc+}k^Kd)jpNZn z%M-$hjGk5~515k7e)bMFb!jR%M`)jB4X-Q%0(?KQN+a9pJo%f$rd)jS&uuQpw(rQg z8ey|nUNPGPv_Kfm89{V+zN6hO)FU9Ch4?>Y2Smr9Kq@1kwsx9|!LKpcaVc^drDz@8;VSZjspILSb!!-IeBH5EgEXh3-3WTEw>kmbP-h^YfazO{utNP!sf z-0=yhCl~4KdM+ND0EisraWyq8m}eSQtXU!gXbvcZE)kz7NJC;yGwD7INI1P6pxFc# zBwJKZk+c}rcM%izTxVYdR6M1ob4>yA`IfSc5o~Z;3v~JoLWNDT4#bqgPj)q(L2l=p zAmPylf_(we#46bZ@@yUB{qPNrSV4CbC`qga=@MHZtE+RFtDJ!9cq}FR^20ia3DtLC5-QX;$^p4O( zOK+=)28;Dz7tJ~Un`)76Un^>3ItnS>n)AQW`h77)MuV`pBCY8}`0q&%yP3oYY@z)B zCjI{#jA-%C{jWm`ttgOgFG$UtZjT~u&w9`&oA!To;=ai@G*~apBLCBX;2)zu$ZJWn z-}zm+vL*U0O?E)KtI4l(LS>Hr3uF~TQTCI%DE(btit8mm)!;VdI~=x;^c^P~AwsM~ zj~581q4eQwP!RvLH7$&N-g$Zi4Z6)iw=U7l3 zcn40`8I;^IRB-7-2?~WgIa};dT)_zx%zY)s*W~^L3p)10^*3K0$3YqVZzWw z-I>0!E&JPO@1r7BJyduwVAzAJmyESq6)a7Q&D*MBXxRH%)-$b=M?qGgalE>0u z1t!{MC?Pg2%mhR3qSh^}k^f{2GQi?jb{Liof2EB`NK1`=9IAyGG&}|V`e@h-*%*rh z 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 literal 0 HcmV?d00001 diff --git a/examples/knx-demo-diy/knx-demo-diy-tp.xml b/examples/knx-demo-diy/knx-demo-diy-tp.xml new file mode 100644 index 0000000..d748668 --- /dev/null +++ b/examples/knx-demo-diy/knx-demo-diy-tp.xml @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/knx-demo-diy/knx-demo-diy.ino b/examples/knx-demo-diy/knx-demo-diy.ino new file mode 100644 index 0000000..00d3c6f --- /dev/null +++ b/examples/knx-demo-diy/knx-demo-diy.ino @@ -0,0 +1,138 @@ +#include +#include + +#ifdef ARDUINO_ARCH_ESP8266 +#include +#endif + +// create named references for easy access to group objects +#define goCurrent knx.getGroupObject(1) +#define goMax knx.getGroupObject(2) +#define goMin knx.getGroupObject(3) +#define goReset knx.getGroupObject(4) + +// If you don't want a global knx object, for example because you want +// to more finely control it's construction, this is an example +// of how to do so. Define KNX_NO_AUTOMATIC_GLOBAL_INSTANCE +// and then you can DIY a knx object as shown below. In this case we use +// the ESP32's secondary UART and late-bind the ISR function in setup(). +Esp32Platform knxPlatform(&Serial2); +Bau07B0 knxBau(knxPlatform); +KnxFacade knx(knxBau); + +ICACHE_RAM_ATTR void myButtonPressed() +{ + // Debounce + static uint32_t lastpressed=0; + if (millis() - lastpressed > 200) + { + knx.toggleProgMode(); + lastpressed = millis(); + } +} + +float currentValue = 0; +float maxValue = 0; +float minValue = RAND_MAX; +long lastsend = 0; + +void measureTemp() +{ + long now = millis(); + if ((now - lastsend) < 2000) + return; + + lastsend = now; + int r = rand(); + currentValue = (r * 1.0) / (RAND_MAX * 1.0); + currentValue *= 100 * 100; + + // write new value to groupobject + goCurrent.value(currentValue); + + if (currentValue > maxValue) + { + maxValue = currentValue; + goMax.value(maxValue); + } + + if (currentValue < minValue) + { + minValue = currentValue; + goMin.value(minValue); + } +} + +// callback from reset-GO +void resetCallback(GroupObject& go) +{ + if (go.value()) + { + maxValue = 0; + minValue = 10000; + } +} + +void setup() +{ + knx.setButtonISRFunction(myButtonPressed); + + Serial.begin(115200); + ArduinoPlatform::SerialDebug = &Serial; + + Serial2.begin(19200); // KNX, pin 16,17 on EPS32 + + randomSeed(millis()); + +#ifdef ARDUINO_ARCH_ESP8266 + WiFiManager wifiManager; + wifiManager.autoConnect("knx-demo"); +#endif + + // read adress table, association table, groupobject table and parameters from eeprom + knx.readMemory(); + + // print values of parameters if device is already configured + if (knx.configured()) + { + // register callback for reset GO + goReset.callback(resetCallback); + goReset.dataPointType(DPT_Trigger); + goCurrent.dataPointType(DPT_Value_Temp); + goMin.dataPointType(DPT_Value_Temp); + goMax.dataPointType(DPT_Value_Temp); + + Serial.print("Timeout: "); + Serial.println(knx.paramByte(0)); + Serial.print("Zykl. senden: "); + Serial.println(knx.paramByte(1)); + Serial.print("Min/Max senden: "); + Serial.println(knx.paramByte(2)); + Serial.print("Aenderung senden: "); + Serial.println(knx.paramByte(3)); + Serial.print("Abgleich: "); + Serial.println(knx.paramByte(4)); + } + + // pin or GPIO the programming led is connected to. Default is LED_BUILTIN + // knx.ledPin(LED_BUILTIN); + // is the led active on HIGH or low? Default is LOW + // knx.ledPinActiveOn(HIGH); + // pin or GPIO programming button is connected to. Default is 0 + // knx.buttonPin(0); + + // start the framework. + knx.start(); +} + +void loop() +{ + // don't delay here to much. Otherwise you might lose packages or mess up the timing with ETS + knx.loop(); + + // only run the application code if the device was configured with ETS + if (!knx.configured()) + return; + + measureTemp(); +} diff --git a/examples/knx-demo-diy/platformio-ci.ini b/examples/knx-demo-diy/platformio-ci.ini new file mode 100644 index 0000000..2071160 --- /dev/null +++ b/examples/knx-demo-diy/platformio-ci.ini @@ -0,0 +1,24 @@ +;PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +; +;--- ESP32 ----------------------------------------------- + +[env:esp32dev_tp] +platform = espressif32 +board = esp32dev +framework = arduino +lib_deps = + knx + +build_flags = + -DMASK_VERSION=0x07B0 + -Wno-unknown-pragmas + -DKNX_NO_AUTOMATIC_GLOBAL_INSTANCE diff --git a/examples/knx-demo-diy/platformio.ini b/examples/knx-demo-diy/platformio.ini new file mode 100644 index 0000000..011e390 --- /dev/null +++ b/examples/knx-demo-diy/platformio.ini @@ -0,0 +1,33 @@ +;PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html +[platformio] +; We have to keep libdeps dir out the project directory otherwise, +; library scanner seems to have issues so compilation fails +libdeps_dir = /tmp/libdeps +src_dir = . + + +;--- ESP32 ----------------------------------------------- + +[env:esp32dev_tp] +platform = espressif32 +board = esp32dev +framework = arduino +; We consider that the this projects is opened within its project directory +; while working with VS Code. +lib_extra_dirs = ../../../ + +lib_deps = + knx + +build_flags = + -DMASK_VERSION=0x07B0 + -Wno-unknown-pragmas + -DKNX_NO_AUTOMATIC_GLOBAL_INSTANCE diff --git a/examples/knx-demo/platformio.ini b/examples/knx-demo/platformio.ini index 0371159..edd9954 100644 --- a/examples/knx-demo/platformio.ini +++ b/examples/knx-demo/platformio.ini @@ -59,6 +59,7 @@ framework = arduino lib_extra_dirs = ../../../ lib_deps = + WifiManager knx build_flags = diff --git a/src/arduino_platform.cpp b/src/arduino_platform.cpp index 44651cb..65ec833 100644 --- a/src/arduino_platform.cpp +++ b/src/arduino_platform.cpp @@ -12,11 +12,12 @@ ArduinoPlatform::ArduinoPlatform(HardwareSerial* knxSerial) : _knxSerial(knxSeri void ArduinoPlatform::fatalError() { - const int period = 200; while (true) { #ifdef LED_BUILTIN - if ((millis() % period) > (period / 2)) + static const long LED_BLINK_PERIOD = 200; + + if ((millis() % LED_BLINK_PERIOD) > (LED_BLINK_PERIOD / 2)) digitalWrite(LED_BUILTIN, HIGH); else digitalWrite(LED_BUILTIN, LOW); diff --git a/src/esp32_platform.cpp b/src/esp32_platform.cpp index 844f446..cc902dd 100644 --- a/src/esp32_platform.cpp +++ b/src/esp32_platform.cpp @@ -10,7 +10,7 @@ Esp32Platform::Esp32Platform() : ArduinoPlatform(&Serial1) { } -Esp32Platform::Esp32Platform( HardwareSerial* s) : ArduinoPlatform(s) +Esp32Platform::Esp32Platform(HardwareSerial* s) : ArduinoPlatform(s) { } @@ -58,10 +58,9 @@ void Esp32Platform::closeMultiCast() bool Esp32Platform::sendBytesMultiCast(uint8_t * buffer, uint16_t len) { //printHex("<- ",buffer, len); - int result = 0; - result = _udp.beginMulticastPacket(); - result = _udp.write(buffer, len); - result = _udp.endPacket(); + _udp.beginMulticastPacket(); + _udp.write(buffer, len); + _udp.endPacket(); return true; } diff --git a/src/esp32_platform.h b/src/esp32_platform.h index 7cc641a..3c006ba 100644 --- a/src/esp32_platform.h +++ b/src/esp32_platform.h @@ -8,7 +8,7 @@ class Esp32Platform : public ArduinoPlatform { public: Esp32Platform(); - Esp32Platform( HardwareSerial* s); + Esp32Platform(HardwareSerial* s); // ip stuff uint32_t currentIpAddress() override; diff --git a/src/esp_platform.cpp b/src/esp_platform.cpp index 11984ae..f453fea 100644 --- a/src/esp_platform.cpp +++ b/src/esp_platform.cpp @@ -61,10 +61,9 @@ void EspPlatform::closeMultiCast() bool EspPlatform::sendBytesMultiCast(uint8_t * buffer, uint16_t len) { //printHex("<- ",buffer, len); - int result = 0; - result = _udp.beginPacketMulticast(_mulitcastAddr, _mulitcastPort, WiFi.localIP()); - result = _udp.write(buffer, len); - result = _udp.endPacket(); + _udp.beginPacketMulticast(_mulitcastAddr, _mulitcastPort, WiFi.localIP()); + _udp.write(buffer, len); + _udp.endPacket(); return true; } diff --git a/src/esp_platform.h b/src/esp_platform.h index 6fe820d..cd026d8 100644 --- a/src/esp_platform.h +++ b/src/esp_platform.h @@ -8,7 +8,7 @@ class EspPlatform : public ArduinoPlatform { public: EspPlatform(); - EspPlatform( HardwareSerial* s); + EspPlatform(HardwareSerial* s); // ip stuff uint32_t currentIpAddress() override; diff --git a/src/knx/memory.cpp b/src/knx/memory.cpp index 9869fdf..a5945cb 100644 --- a/src/knx/memory.cpp +++ b/src/knx/memory.cpp @@ -2,10 +2,6 @@ #include #include "bits.h" -#ifndef KNX_FLASH_SIZE -# define KNX_FLASH_SIZE 8192 -#endif - Memory::Memory(Platform& platform, DeviceObject& deviceObject) : _platform(platform), _deviceObject(deviceObject) { diff --git a/src/knx/memory.h b/src/knx/memory.h index 06215ef..715c2b4 100644 --- a/src/knx/memory.h +++ b/src/knx/memory.h @@ -9,6 +9,10 @@ #define MAXSAVE 5 #define MAXTABLEOBJ 4 +#ifndef KNX_FLASH_SIZE +# define KNX_FLASH_SIZE 1024 +#endif + class MemoryBlock { public: diff --git a/src/knx_facade.cpp b/src/knx_facade.cpp index 86ebea9..ee92547 100644 --- a/src/knx_facade.cpp +++ b/src/knx_facade.cpp @@ -2,72 +2,72 @@ #include "knx/bits.h" -#ifdef ARDUINO_ARCH_SAMD - // predefined global instance for TP or RF or TP/RF coupler - #if MASK_VERSION == 0x07B0 - KnxFacade knx; - #elif MASK_VERSION == 0x27B0 - KnxFacade knx; - #elif MASK_VERSION == 0x2920 - KnxFacade knx; - #else - #error Mask version not supported on ARDUINO_ARCH_SAMD +#ifndef KNX_NO_AUTOMATIC_GLOBAL_INSTANCE + + #if (defined(ARDUINO_ARCH_STM32) || \ + defined(ARDUINO_ARCH_ESP32) || \ + defined(ARDUINO_ARCH_ESP8266) || \ + defined(ARDUINO_ARCH_SAMD)) + + // Only ESP8266 and ESP32 have this define. For all other platforms this is just empty. + #ifndef ICACHE_RAM_ATTR + #define ICACHE_RAM_ATTR + #endif + + ICACHE_RAM_ATTR void buttonUp() + { + static uint32_t lastpressed=0; + if (millis() - lastpressed > 200){ + knx.toggleProgMode(); + lastpressed = millis(); + } + } #endif -#elif defined(ARDUINO_ARCH_ESP8266) - // predefined global instance for TP or IP or TP/IP coupler - #if MASK_VERSION == 0x07B0 - KnxFacade knx; - #elif MASK_VERSION == 0x57B0 - KnxFacade knx; - #elif MASK_VERSION == 0x091A - KnxFacade knx; - #else - #error Mask version not supported on ARDUINO_ARCH_ESP8266 + #ifdef ARDUINO_ARCH_SAMD + // predefined global instance for TP or RF or TP/RF coupler + #if MASK_VERSION == 0x07B0 + KnxFacade knx(buttonUp); + #elif MASK_VERSION == 0x27B0 + KnxFacade knx(buttonUp); + #elif MASK_VERSION == 0x2920 + KnxFacade knx(buttonUp); + #else + #error "Mask version not supported on ARDUINO_ARCH_SAMD" + #endif + + #elif defined(ARDUINO_ARCH_ESP8266) + // predefined global instance for TP or IP or TP/IP coupler + #if MASK_VERSION == 0x07B0 + KnxFacade knx(buttonUp); + #elif MASK_VERSION == 0x57B0 + KnxFacade knx(buttonUp); + #elif MASK_VERSION == 0x091A + KnxFacade knx(buttonUp); + #else + #error "Mask version not supported on ARDUINO_ARCH_ESP8266" + #endif + + #elif defined(ARDUINO_ARCH_ESP32) + // predefined global instance for TP or IP or TP/IP coupler + #if MASK_VERSION == 0x07B0 + KnxFacade knx(buttonUp); + #elif MASK_VERSION == 0x57B0 + KnxFacade knx(buttonUp); + #elif MASK_VERSION == 0x091A + KnxFacade knx(buttonUp); + #else + #error "Mask version not supported on ARDUINO_ARCH_ESP32" + #endif + + #elif defined(ARDUINO_ARCH_STM32) + #if MASK_VERSION == 0x07B0 + KnxFacade knx(buttonUp); + #else + #error "Mask version not supported on ARDUINO_ARCH_STM32" + #endif + #else // Non-Arduino platforms and Linux platform + // no predefined global instance #endif -#elif defined(ARDUINO_ARCH_ESP32) - // predefined global instance for TP or IP or TP/IP coupler - #if MASK_VERSION == 0x07B0 - KnxFacade knx; - #elif MASK_VERSION == 0x57B0 - KnxFacade knx; - #elif MASK_VERSION == 0x091A - KnxFacade knx; - #else - #error Mask version not supported on ARDUINO_ARCH_ESP8266 - #endif - -#elif defined(ARDUINO_ARCH_STM32) - #if MASK_VERSION == 0x07B0 - KnxFacade knx; - #else - #error Mask version not supported on ARDUINO_ARCH_STM32 - #endif -#else // Non-Arduino platforms and Linux platform - // no predefined global instance -#endif - -// Only ESP8266 and ESP32 have this define. For all other platforms this is just empty. -#ifndef ICACHE_RAM_ATTR - #define ICACHE_RAM_ATTR -#endif - -#if (defined(ARDUINO_ARCH_STM32) || \ - defined(ARDUINO_ARCH_ESP32) || \ - defined(ARDUINO_ARCH_ESP8266) || \ - defined(ARDUINO_ARCH_SAMD)) -ICACHE_RAM_ATTR void buttonUp() -{ - static uint32_t lastpressed=0; - if (millis() - lastpressed > 200){ - knx._toogleProgMode = true; - lastpressed = millis(); - } -} -#elif defined(__linux__) -void buttonUp() -{ - // no de-bounce on linux platform, just satisfy the compiler -} -#endif \ No newline at end of file +#endif // KNX_NO_AUTOMATIC_GLOBAL_INSTANCE diff --git a/src/knx_facade.h b/src/knx_facade.h index d499fe4..27e024a 100644 --- a/src/knx_facade.h +++ b/src/knx_facade.h @@ -10,33 +10,41 @@ #ifdef ARDUINO_ARCH_SAMD #include "samd_platform.h" - void buttonUp(); + #ifndef KNX_NO_AUTOMATIC_GLOBAL_INSTANCE + void buttonUp(); + #endif #elif defined(ARDUINO_ARCH_ESP8266) - #include "esp_platform.h" - void buttonUp(); + #include "esp_platform.h" + #ifndef KNX_NO_AUTOMATIC_GLOBAL_INSTANCE + void buttonUp(); + #endif #elif defined(ARDUINO_ARCH_ESP32) - #define LED_BUILTIN 13 - #include "esp32_platform.h" - void buttonUp(); + #define LED_BUILTIN 13 + #include "esp32_platform.h" + #ifndef KNX_NO_AUTOMATIC_GLOBAL_INSTANCE + void buttonUp(); + #endif #elif defined(ARDUINO_ARCH_STM32) - #include "stm32_platform.h" - void buttonUp(); + #include "stm32_platform.h" + #ifndef KNX_NO_AUTOMATIC_GLOBAL_INSTANCE + void buttonUp(); + #endif #elif __linux__ - #define LED_BUILTIN 0 - #include "linux_platform.h" - void buttonUp(); + #define LED_BUILTIN 0 + #include "linux_platform.h" #else - #define LED_BUILTIN 5 // see GPIO_PinConfig gpioPinConfigs[] - #include "cc1310_platform.h" - extern void buttonUp(); + #define LED_BUILTIN 5 // see GPIO_PinConfig gpioPinConfigs[] + #include "cc1310_platform.h" + #ifndef KNX_NO_AUTOMATIC_GLOBAL_INSTANCE + extern void buttonUp(); + #endif #endif typedef uint8_t* (*SaveRestoreCallback)(uint8_t* buffer); +typedef void (*IsrFunctionPtr)(); template class KnxFacade : private SaveRestore { - friend void buttonUp(); - public: KnxFacade() : _platformPtr(new P()), _bauPtr(new B(*_platformPtr)), _bau(*_bauPtr) { @@ -44,6 +52,19 @@ template class KnxFacade : private SaveRestore _bau.addSaveRestore(this); } + KnxFacade(B& bau) : _bau(bau) + { + manufacturerId(0xfa); + _bau.addSaveRestore(this); + } + + KnxFacade(IsrFunctionPtr buttonISRFunction) : _platformPtr(new P()), _bauPtr(new B(*_platformPtr)), _bau(*_bauPtr) + { + manufacturerId(0xfa); + _bau.addSaveRestore(this); + setButtonISRFunction(buttonISRFunction); + } + virtual ~KnxFacade() { if (_bauPtr) @@ -53,12 +74,6 @@ template class KnxFacade : private SaveRestore delete _platformPtr; } - KnxFacade(B& bau) : _bau(bau) - { - manufacturerId(0xfa); - _bau.addSaveRestore(this); - } - P& platform() { return *_platformPtr; @@ -89,6 +104,14 @@ template class KnxFacade : private SaveRestore _bau.deviceObject().progMode(value); } + /** + * To be called by ISR handling on button press. + */ + void toggleProgMode() + { + _toggleProgMode = true; + } + bool configured() { return _bau.configured(); @@ -181,10 +204,10 @@ template class KnxFacade : private SaveRestore digitalWrite(ledPin(), HIGH - _ledPinActiveOn); } } - if (_toogleProgMode) + if (_toggleProgMode) { progMode(!progMode()); - _toogleProgMode = false; + _toggleProgMode = false; } _bau.loop(); } @@ -216,21 +239,30 @@ template class KnxFacade : private SaveRestore void start() { - pinMode(_ledPin, OUTPUT); + pinMode(ledPin(), OUTPUT); - digitalWrite(_ledPin, HIGH - _ledPinActiveOn); + digitalWrite(ledPin(), HIGH - _ledPinActiveOn); - pinMode(_buttonPin, INPUT_PULLUP); + pinMode(buttonPin(), INPUT_PULLUP); + + if (_progButtonISRFuncPtr) + { + // Workaround for https://github.com/arduino/ArduinoCore-samd/issues/587 + #if (ARDUINO_API_VERSION >= 10200) + attachInterrupt(_buttonPin, _progButtonISRFuncPtr, (PinStatus)_buttonPinInterruptOn); + #else + attachInterrupt(_buttonPin, _progButtonISRFuncPtr, _buttonPinInterruptOn); + #endif + } - // Workaround for https://github.com/arduino/ArduinoCore-samd/issues/587 - #if (ARDUINO_API_VERSION >= 10200) - attachInterrupt(_buttonPin, buttonUp, (PinStatus)_buttonPinInterruptOn); - #else - attachInterrupt(_buttonPin, buttonUp, _buttonPinInterruptOn); - #endif enabled(true); } + void setButtonISRFunction(IsrFunctionPtr progButtonISRFuncPtr) + { + _progButtonISRFuncPtr = progButtonISRFuncPtr; + } + void setSaveCallback(SaveRestoreCallback func) { _saveCallback = func; @@ -303,9 +335,10 @@ template class KnxFacade : private SaveRestore uint32_t _buttonPin = 0; SaveRestoreCallback _saveCallback = 0; SaveRestoreCallback _restoreCallback = 0; - bool _toogleProgMode = false; + volatile bool _toggleProgMode = false; bool _progLedState = false; uint16_t _saveSize = 0; + IsrFunctionPtr _progButtonISRFuncPtr = 0; uint8_t* save(uint8_t* buffer) { @@ -334,46 +367,48 @@ template class KnxFacade : private SaveRestore } }; -#ifdef ARDUINO_ARCH_SAMD - // predefined global instance for TP or RF or TP/RF coupler - #if MASK_VERSION == 0x07B0 - extern KnxFacade knx; - #elif MASK_VERSION == 0x27B0 - extern KnxFacade knx; - #elif MASK_VERSION == 0x2920 - extern KnxFacade knx; - #else - #error "Mask version not supported on ARDUINO_ARCH_SAMD" +#ifndef KNX_NO_AUTOMATIC_GLOBAL_INSTANCE + #ifdef ARDUINO_ARCH_SAMD + // predefined global instance for TP or RF or TP/RF coupler + #if MASK_VERSION == 0x07B0 + extern KnxFacade knx; + #elif MASK_VERSION == 0x27B0 + extern KnxFacade knx; + #elif MASK_VERSION == 0x2920 + extern KnxFacade knx; + #else + #error "Mask version not supported on ARDUINO_ARCH_SAMD" + #endif + #elif defined(ARDUINO_ARCH_ESP8266) + // predefined global instance for TP or IP or TP/IP coupler + #if MASK_VERSION == 0x07B0 + extern KnxFacade knx; + #elif MASK_VERSION == 0x57B0 + extern KnxFacade knx; + #elif MASK_VERSION == 0x091A + extern KnxFacade knx; + #else + #error "Mask version not supported on ARDUINO_ARCH_ESP8266" + #endif + #elif defined(ARDUINO_ARCH_ESP32) + // predefined global instance for TP or IP or TP/IP coupler + #if MASK_VERSION == 0x07B0 + extern KnxFacade knx; + #elif MASK_VERSION == 0x57B0 + extern KnxFacade knx; + #elif MASK_VERSION == 0x091A + extern KnxFacade knx; + #else + #error "Mask version not supported on ARDUINO_ARCH_ESP32" + #endif + #elif defined(ARDUINO_ARCH_STM32) + // predefined global instance for TP only + #if MASK_VERSION == 0x07B0 + extern KnxFacade knx; + #else + #error "Mask version not supported on ARDUINO_ARCH_STM32" + #endif + #else // Non-Arduino platforms and Linux platform + // no predefined global instance #endif -#elif defined(ARDUINO_ARCH_ESP8266) - // predefined global instance for TP or IP or TP/IP coupler - #if MASK_VERSION == 0x07B0 - extern KnxFacade knx; - #elif MASK_VERSION == 0x57B0 - extern KnxFacade knx; - #elif MASK_VERSION == 0x091A - extern KnxFacade knx; - #else - #error "Mask version not supported on ARDUINO_ARCH_ESP8266" - #endif -#elif defined(ARDUINO_ARCH_ESP32) - // predefined global instance for TP or IP or TP/IP coupler - #if MASK_VERSION == 0x07B0 - extern KnxFacade knx; - #elif MASK_VERSION == 0x57B0 - extern KnxFacade knx; - #elif MASK_VERSION == 0x091A - extern KnxFacade knx; - #else - #error "Mask version not supported on ARDUINO_ARCH_ESP32" - #endif -#elif defined(ARDUINO_ARCH_STM32) - // predefined global instance for TP only - #if MASK_VERSION == 0x07B0 - extern KnxFacade knx; - #else - #error Mask version not supported on ARDUINO_ARCH_STM32 - #endif -#else // Non-Arduino platforms and Linux platform - // no predefined global instance -#endif +#endif // KNX_NO_AUTOMATIC_GLOBAL_INSTANCE