From 5d37ec2396f3fa3453c8a89f4dc32d3b8b3d6b36 Mon Sep 17 00:00:00 2001 From: ferdzo Date: Thu, 10 Oct 2024 00:09:59 +0200 Subject: [PATCH 1/3] Big refactor --- __pycache__/proba.cpython-311.pyc | Bin 0 -> 2823 bytes .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 178 bytes iotDashboard/__pycache__/apps.cpython-311.pyc | Bin 0 -> 561 bytes iotDashboard/__pycache__/forms.cpython-311.pyc | Bin 0 -> 862 bytes .../getInfoFromDevices.cpython-311.pyc | Bin 0 -> 933 bytes iotDashboard/__pycache__/models.cpython-311.pyc | Bin 0 -> 1011 bytes .../__pycache__/settings.cpython-311.pyc | Bin 0 -> 3380 bytes iotDashboard/__pycache__/tasks.cpython-311.pyc | Bin 0 -> 2586 bytes iotDashboard/__pycache__/urls.cpython-311.pyc | Bin 0 -> 1847 bytes iotDashboard/__pycache__/views.cpython-311.pyc | Bin 0 -> 5381 bytes iotDashboard/__pycache__/wsgi.cpython-311.pyc | Bin 0 -> 710 bytes iotDashboard/db_create.py | 0 iotDashboard/gpt.py | 0 .../__pycache__/0001_initial.cpython-311.pyc | Bin 0 -> 1258 bytes .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 189 bytes 15 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 __pycache__/proba.cpython-311.pyc create mode 100644 iotDashboard/__pycache__/__init__.cpython-311.pyc create mode 100644 iotDashboard/__pycache__/apps.cpython-311.pyc create mode 100644 iotDashboard/__pycache__/forms.cpython-311.pyc create mode 100644 iotDashboard/__pycache__/getInfoFromDevices.cpython-311.pyc create mode 100644 iotDashboard/__pycache__/models.cpython-311.pyc create mode 100644 iotDashboard/__pycache__/settings.cpython-311.pyc create mode 100644 iotDashboard/__pycache__/tasks.cpython-311.pyc create mode 100644 iotDashboard/__pycache__/urls.cpython-311.pyc create mode 100644 iotDashboard/__pycache__/views.cpython-311.pyc create mode 100644 iotDashboard/__pycache__/wsgi.cpython-311.pyc create mode 100644 iotDashboard/db_create.py create mode 100644 iotDashboard/gpt.py create mode 100644 iotDashboard/migrations/__pycache__/0001_initial.cpython-311.pyc create mode 100644 iotDashboard/migrations/__pycache__/__init__.cpython-311.pyc diff --git a/__pycache__/proba.cpython-311.pyc b/__pycache__/proba.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7f163fb5343764efff76715de4623758c193c89f GIT binary patch literal 2823 zcmb_d-EY%Y6u;MYVmEI3m3{$ZOQ8)-i_(?Pff&<93v?8>(1Ns3RE5~3?&7#yyImWl zl0it6snW{CL^mNV4;y0w?Rk&;50*@p7Fp`kw1+)R3Sv(@?OdlxW4ecF+O>Ur@44rk zd(OGPbMJS-U;sh;bLr<9$&b))bWmx&tQEalA=xxQpV= zawE_d#g_|JG;04~i(u^aPZiRTbl{GM+%u&>xf|MX|#gw^HyE(b8!kG`nmKQ*E7+uRTWyZ1^t@78EaOu! zl||5FXwT#;k_gRlmA=fHE@I2$EZW}J46tO5Qg8NGm@Jb6Z0~Gd4`koAE%3qrpMBu} zHy;EyBaj)7XN+g$_l$(?(F_L&tCJj#;>q#L%A%T7GW@J*&Yv6@$b<$AC2i)1DGcqObiN)a5((x=$VsKR}6(1Q!@&YGy2r{a&k67 zv~i-(DM{0qs)h2@Jkf6?V)M&R`;1~HXQ!#P=^3JH)8*C38(@|u!uSD*g*L^;`^VOf zSwowlJr7SkIQ{UnHMH7(d$icL2Rfa%rA?t}t$zKZZ#$m0fAdzM?d|;G^M$ta1!2S% zMy&UXyZ3w@`6}{d@8jOwaeH?^=E%V$+jF_dnu z#_G1TcWsbk|4lh!WKyhbHJR~7O0N)rH!Ec(N2H12%ah>|X>u%_)YE`aRZkn?UJCnz z;W8o)hHDUgFkHoN`0D$kSB59Ty*Ej|9C(s;!A>X;NtmkEt7*WvISmCumJOOKzq##CZF=`DJgUz2^`}ErV>ucAovzwvzhsR3{uG{y5 zWn2A!At2UhNkBgFo^(fAKb31Oa1on}<he+Z6YgEObK&;IyBDpCMJ{k}^v>vQ>8@l+ zn_S&$q|CQqj_ie1Gi=|$AlxT@O-)0jNnlkQb=`&J5Of|PvpRv$5W3#6f@)8Evj#v} z^g3wPI^B)L5}XYkV8;!eM4%VNZvG64=dB!ZCY&s!9~g(=Gh#q2^n1&$62iWHE?M<& z3ZXBCHX;R~%NDxw?ptCj({5zZHaHF4`_mETjpd|1fAf&Lx9%}U+Q6#A&BC=yXq?1P z3B8LPo?e<M^#&0ri(W%caO*mC&rQP#Y+tBwX9EP_@N$DG$41X3H$W!{$E&Kt**R?{ zjE~)rzDKTiE*f45r!-w&NGYeuQP80&FfIdyNH8v<pyfVA)M&X+5q0LhZxQXyd*2%2 z?mYU3w}{@%d*7XA0a!wdmDxCv=X(l#kIna3T(O~LRnCVZg@yxm!vRYyBGD2zL|CJM zx@~u*Vz6uVsvYdg^%sIMI~dCkd|(I9Sb>rs37yuG&39}B3jBVX-*0iitMAP3>dOcE zExsfO_!2Im8rm7aEhSVXyAk966^DMoq4jg?=W<8$t<gej)CP+CY}{937!1e&@`6Q7 LzlY<p-JJgoY&mV7 literal 0 HcmV?d00001 diff --git a/iotDashboard/__pycache__/__init__.cpython-311.pyc b/iotDashboard/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..72f2c2c0a3d0c20eea99a3fa2ec99d87f96089f7 GIT binary patch literal 178 zcmZ3^%ge<81pM<jQbF`%5CH>>P{wCAAY(d13PUi1CZpd<h9V{)|1(JPm#ec?OlWax zQE^OKYEep6eoR1Raz<iNZa`6fR%&udaZF}@iA!Q}MpAxaQ3@(2K0Y%qvm`!Vub}c5 khfQvNN@-52T@fqLRFJ*J{6OLZGb1D82L>2X#0(Sz04#wla{vGU literal 0 HcmV?d00001 diff --git a/iotDashboard/__pycache__/apps.cpython-311.pyc b/iotDashboard/__pycache__/apps.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2bd9865e07d33ca0f824b5265abdd0af5b803eb2 GIT binary patch literal 561 zcmZut&r8EF6n<$t(ESkEVNk(?$6k6C5n*m9B8-7}DT9zL>3-0yHEj<Y2s`*c%(DkY z{A1j4^dxxlHkqe+F=;#5(3j-p`|{rVlDwxvAq&F3Z(r>PvA;{QvP=pVrT`P5K#>D| zWFZ6!oC8(vfT|2VssJxyC1xp9y8<KgA*q3)?Xu@p+=1O~x6+JwLn&F51egE=3sDfY z$Wp0dX;c*&T2#DngDUYmO_y+*sEqb$j||$bNt<ThrOffo({8&Q2JTsxIW)?5*Gg(e zCfdSyK>7^hD2uVE2px&@7~h1%Nh3wdY!W&FCPD|>v5+QX&#;gWU6w0HjccEAzhN^@ zM{c7&Y;_3l*SXtct-x=r-?~YN=lP~L<g%Nw(**E!;l{sXrXNnV(k!Qst8(zdDz|2+ zQ7-+k6ymbaiBw*qIB`dy<<@cXw-dfAsBEu)DcoE|2))6<baT!%1?|n@ue;Yj<>}Wy Hq4=^Nlq`~R literal 0 HcmV?d00001 diff --git a/iotDashboard/__pycache__/forms.cpython-311.pyc b/iotDashboard/__pycache__/forms.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..48a2e6256dcec675dd5adc775a5358189d3864d7 GIT binary patch literal 862 zcmZ`%zi-n(6uuv}n+B@35-q4mu;QhE076L6D4kkWLM&D!%lIyhBfA&w9HbzI4h$U` zkh(JzA^sLb#^_e2ZmH5K6Yu#jm8JIG-RJl2z5Cw1cmBS%)+QjYKK<_hg6|(RYVrRm ziw-E~L=Zu9QqYJ}BADPgD|o~yVPu~O?=um;q(iShe8CC%3Cl_3i{K3jS1!>EsrCCx z7v_>WE9l4%St?T(>#!zTJOlfjND@&&B331%kc?e0NC3-8#07^0o^3m>F7P+_>d3`T zgHQ6vJBGr(QMe)l2EJUsVc0#8WfJ<<J4p&@SvIn*kyfQjRc`N<vKUF7lw&PzYcMV{ zk(HCfFtDvS#`rk4?Km!!80V<pkK^~_BzGQ-y|q3#Wp0ChCUaqakOkJVe7*O)_tr>l zdVQ(IN7Xx;q=Q5kM_LVKTAE&_%1&Yi?^L2i)2O<-JDOMzFRoXC4ilLt0H^=QY-9V( zpKW%h)jSJu=q(7*4*?ECPp_k~fv{O`>j$V+QQN4k)PN6Vx&Su`=8XCKbWU!^t7`xH z4zEjr?AXdc43m>%rSV6yLDeqfdbfIjy~AgAS7}~v*?Qp`|587Oqxheh3}DVFr8Dwq e+MF}8Gi}bf$0?R@JAT>v`t+*uUEdK=mGKt=Y{@eK literal 0 HcmV?d00001 diff --git a/iotDashboard/__pycache__/getInfoFromDevices.cpython-311.pyc b/iotDashboard/__pycache__/getInfoFromDevices.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a37e53b85644ea40462c19a5cc135203aa2c0a94 GIT binary patch literal 933 zcmd5*&ubGw6rS1LBx{n|loCX!P`pSl2~iAbDPn5sk4r762SY9~Gi^3xGx5zNl29=R zFG|7Q>OrBWN+|v>3FJ@~1jUoLKyEqtCQY`AdiO2+?Kj_>H*fZv-A{9KVS@4Pv&~m2 zA>Z|27RHo}t|B=kj4)aydBO~4Iy6t2^_t`j7C<x^eMDA*KXk#C7p^)@r?v}1xb0|a z^?D+e&LmQao9PmE@FRfC#Cz01I3((qT*c>%Dht!o*dv!`l(dmCL*p^dKJhDF(mi^l z*Zm`oQ91EdW}dHwd$+d6p|Vo-wPdorn3TLG1tb-B>l})z0o)5Ti`542TAmI3d4o$O zJ#&{UFT{A$DRJpp6)9?6!wa4X9tEilTwWeOyt7t#B00#y4hPm2h3!_UTm*L;M1_}> zEI2~tin9Dn6oLIdzzz>;JK`<~H+R-2QEz!6lpo{XAchUJkMagWhkUVP`_Y4^A730b zKfLP46TM`vAJ6se&4Iny$(}}{@6vD6Z!-ItgL{L>QqNi%VHx?aPX+YrwPUjrDK}h) zIjRLglp&-ciZFE==8=05bm#tT-Xba&^o)Nwl<mi}eS2eIZ~WCESis^H3v7IV{#7QP zgI=~vCdo<DvAymJ)~NDZ5XY4M8s%+-p-Cw{CG(xh^)+(o{l#ARS|>EL40>fqe6W3X Ju4y*u@DsjT#x(!{ literal 0 HcmV?d00001 diff --git a/iotDashboard/__pycache__/models.cpython-311.pyc b/iotDashboard/__pycache__/models.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9c4ab4737734c4d60e73dda2c549677ca6f09cf1 GIT binary patch literal 1011 zcmah|y=&V*6u*<dB+H44TS`p=DS?zYgCQX;rIZdyNCPD%w6udU9E$H8+enssl0zL@ z(2&7HhHhCir7p=I(5e4IWzZnBcJk)pPMP}7iUUasd6M48z2Ez~JAIp)N+X~jUw&1~ z7@^<7m{8(U89fK(7*RyAflOS&7)j_gqS7IvF@_t`B;YiL&>4IYm0~ph4&@UAVdim| zG%d;uyK?CsiwSu26U<}8Pzh61k|e}pjrase@ws#g&7Hv)Q7Lf)5xa#*-ib(!5%MG_ zjh`oOR8j6kq-pXHl~kI7LFF*H!S?GFHduk2FmT$`KM{j9_ng|`0W598T8gr&-Zq@d z)hyr;y4Z=)Jz&S^Gird02=d}0#KoJs$%M3-QME<D<BRq67v=Xh<94~qI6bh+JDo~R z=jIN#8m!{j<+|l;=yq+_(mB0uL`P9*b;1-Ow!;ZIn2wq+T-pkhu*KbQQJ~#F>Ui>_ zk!Y!y&xA4|q8&oQG$D|xZ3sO>$j7#BL>?~Y36*u|XS2=>%2i=Y^;RgixaC+C%Lp@T zmSr%#IR=EP!^{@ry3^(?lxuCXPU}vGPea^y#5E9nN-%LN$4p#1`v{nO^#tln_nEKG zoN!NF>Z?n>y5w#IO0N6RS8^wBJ!P@4Ec(i#yB273-F;u1>#cZNp|2Hut>A72*@dr9 z{Om%HdD+!|cGb_Wx^Dt)ULfatPd#n9uPyu9vb!~m<3w(VZnLOS(PJ?m4;9+bn|oG) z?ne8O*cQJ)G(t|yaW&S3{4(0eS9}&6;(+aKV8a;3I6$-B^>--A80z0<-493QKVtpw Jf1KlT{{Xu3?JocT literal 0 HcmV?d00001 diff --git a/iotDashboard/__pycache__/settings.cpython-311.pyc b/iotDashboard/__pycache__/settings.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ed36fdda41d2b8c9991885b1532fa4335f732b71 GIT binary patch literal 3380 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 literal 0 HcmV?d00001 diff --git a/iotDashboard/__pycache__/tasks.cpython-311.pyc b/iotDashboard/__pycache__/tasks.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3e86615a21f52c9de441c620af899c79deb19241 GIT binary patch literal 2586 zcmb_d%}*Og6rb5$+iUz4AP|Csx=9JeltNQAsYD17328(X85&5XxKzk^7g*Q!y0dG* z$Vg6=1BsCGAtY#;s6M5sQbj#R>ZR>JP`g^G)=HeJ9&&S(9-^H3W{i!ODpD(TcINGy z_c0%DesAVmpRWl)`QfeN{5=Okf6z{?K%bQt_u#jMWF+GRid6qNf}zhPxFjFpF_rNN zA?b)X7%wE8NoT~#ct^sO6eA+=PT7@kCz~Qoz>C*W#3Q?bdO4&xpz(6k_nwCsp~ujw z#om*>*U>{zJg%ux#3$n^6!JZzl5hxHt{Bl$MsyBXUs@rmCabYoBdUK60#S|{ilHVI z%Ik_@sHp}08J!CR&M{?4jVZDE)F7tY=b^p$5~N$mXwcc0v4#z5w_D;|jk8<ju#AU6 zl|%8mHOM$BGu$;FgyOZZLrB5aS<`%tWZ?mC56K#r;|i{tMdRN-x9m7vSHhgm|1hWP zf0|Qddxsd<R>IkOPFqjh>&**r%<ixy(DSpLGCQnQ*P>yhhXw~%TL*O|rE6r+P?Bk) zM2!qlEXPtbkx?uUl_`YDRu9w+7c)s!R*kGB*0_*p34j)Z(C}M)N?W#sR1{_tiSk88 z(GA_=7Zk(d)U+kUbuAU*iHDjBfUBs5iB3;dlB8GX&JWF8(G{Z4%qv7*)n+EMvBfA! zP7*Dy#0-5#)r_&IzBs2viEQsssOkghtkpiR7_r4!I`r&3(UP;(EfX3Nny~s4AbGUo z3EVifaVkHy(;D0yzB95pk{?@dzjmqI7To+4*p6%A9r3`1x7c=Od+B}_e$`qMM@(@f zKU(e#e$#WW=d0fPy@e0V&bPs(t0WxS^%<)A3>DjGEs3X1@ih1x>eyPkyL4w|bEV)i z5A}jiXGu6%_8z$1zHw>0dwb>nz@zR*Yi8e>r^ZhonP(@8p2?yx`2tpqYzao8%@VBT z9|@L2`+)wM6f^4S0f{5f8ECQlo<uB7>S<NAsZx8Itno5l6LNf^VbneAoRGs0A-Nyd z6C9&|mwxOXggbJ-w~VIHo9G16g=KDqzlxSIMpqGrk7`0(cy*}>5LGYZ;i(Ikr={`m z^n?`CQUG98OX*TC&5C}h3Xy)P0r34&9p1KqJ{i4q<-(NIdtC3A_FAZJ6A~HHEHSNT zV_JIQJ-Au5R0_~bXlShF^Aq9lh4a(n6XDsZ>C5Bci<UE%A&`QWOIcB383VF3rX`cA z5pu9JA??&pprepCDC<>IkR!${<QB>5^l;cVNe5MWc9j(Wq#Gn{(DWaHP|P1K^prdy z(-Q*RyPHk-;Rn5D=ldo1Nz;9@f?SUFrw7Z;ZCk;+!8=`>UF*&Y$9H(wy%iC;1GoG) z{kL<4>5|xIihV_~uPl0Rd2V`c_%?j&zDfYR*$|98r-S`P$)4=B+{>3QU#nvZy#?0) zWE77M^FI#@=aB0c9s^zF`vy<YCn%|=AZ{@*suzlAmLYLq;0dZL0PXeXs8GjagL>af zxdzY982YRko~nlD0`Lr@?7g=bZL~1t;c!SGA`EA_v^jQZS$s5+U`G++ELRn3Iy+gN zPA^H-gA4Azx4>)n?$Svg7#@YDp9PXffBFw@q<;5Hzxkz-|Cs4NmcLkTIb1>5afFd| z=Z@HV`@@nLG{s<%)e2uzl1>;S<R?eqM`&OzVKJj*35}N3O4$LxnnBdL0XfbzG?vxN z-6O}NsRfN1%vbU3Olq={(Akz5^4U(=g1<v-Dz?2>;xHIy63Pg97e=Eep#KU4mW6Q{ z1@i1`U^<G(R?Fy}qP><;SJ7U}NGjTE8Fd!zwc-@<$GCzTXuBH&O6#Npae;i-`tthn hwo(jqmjc};P<-6P#~~dsL>S0(9&tT32@|so{|&;uJ7fR= literal 0 HcmV?d00001 diff --git a/iotDashboard/__pycache__/urls.cpython-311.pyc b/iotDashboard/__pycache__/urls.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fc30403e764e4308bddfdf95c361540cbda32dc5 GIT binary patch literal 1847 zcmb7E&u<$=7@c*-j<a@>CT&UrrA!qC$4c;ussZHGMOB-s5r`x}E>@7c@r>gQ`@@<U zm&6w;ap{3W4w2|F2ULpSz_EYETG~UjS5Dj__0|*L?2pJPMuPF~dghzAZ{GK2_WNag zTu0D;Ui4d~EJA;(LE6TX&Z`3rp`Q>%bwo9qu{9@C&uFSYVvjhuj-x)dvrev_(@+L6 zOtUPrKbjKl=hNQv3_`!b9k&RXW4ZmYG%{p<6y_t13Pbb1k3Am&4!GkMap-_4q!C&e ziYWr7c#at#ikSdRnl+-6Lot^CGjYx{H54-qn8|a@<)N4>fVp&znRyelH&&mbSHD8# zx6B{FFn#;W&q>pBTh?Beo6_=J((*X5JbB*~?Oo60lytba&ziEL>tDASdDP{0$CQ$B zS3DxNB_!cqS296>N)YLh#}@leRLBG1aXik5<pQM>lXq-p3PuEDRh>X<OW6@s!=PSM zROr6x?s@5QP0ulyYuu{bG^E$Dn!-?E1C|$tzII?bz$L2sgRa|*bVtiZq#stuT}p}R z63gj$TviEbanB*nVZ-cnl3*+|!rx5rDg%|kK!)%tB->srm6nsd+MdIfiEBEnR#N>^ zS-)qSLVU0biO~Vpe^cSfHseZ2B~qNqc?ewpM~%W)Owmvzi{&>It-DRTOD%T~Oo-M~ zsi|#F{aKHUAs_{|M`77wnY0ostFc}2c2tz%smKJo9GUW;)$ZEfo&g4?E0t6r;w_cS z`d{j>mbEZzQpa+kgO%ed5c$)~MR6lUz3CQ{&31#b$5xXys3}b&occ#hF{th7<(h4i zOC!ui|Gj+to-o2f!k`Xsub75SN{vv5XR<)=YC6NHC5_vbE32Jv@4)M7oPuUk5M?%# zYydVMM^fM@<P6`55$58~7v<@2w9$Zr-)MvriPx}2l3Tl6l_8KBeGidQ9IHri@Vn$t zS5b(CC0U5oCWTqcrR;#K`p8x3<Ep-KRlvBaP+XOuun;dFotv;QaMXWAuV5a%zI(5_ zvn?1GJ1xd(&)eBNR0cYmaTyRhgCZ^tl!{8{@OA#S<I%3o?(lbj5vn{A*Wh_JqG{S$ zW>UjvNIeMBZup1{UZ?1qfAKm+*ZqsvSq^2#p4@*rd$e*~J^r+h=L0-{it`Yfe!BMK zXMH>y;MpkPkG=b>8@%_CU)%KYRv&K#cq>NE9xWU%oqX)$r9NH?@KPLo`)E6udH=;X zK3?tP)c~)?;p@+4g1Pb^MIYDtxEA1ALVn|9C78eUvgYIUK3)&-dJ>;M*$x&ydHKDM nH~M%Zz#B2m+|gQaW#Pp`AFuTBN`P0+F6)z-Ck6H6bKuh7%aIlM literal 0 HcmV?d00001 diff --git a/iotDashboard/__pycache__/views.cpython-311.pyc b/iotDashboard/__pycache__/views.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fa574a583dfd7698683f32e48206d8ea2c72645c GIT binary patch literal 5381 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 literal 0 HcmV?d00001 diff --git a/iotDashboard/__pycache__/wsgi.cpython-311.pyc b/iotDashboard/__pycache__/wsgi.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b5c238e9aa2515c3087f33ebad2afdcd7f1c5512 GIT binary patch literal 710 zcmY*X&ubGw6n?XtHg#*Gi0OGKy##bu#ZwWXn3_a1Eu=*Wfz5R1CA(pFXW5yiHm4r^ zAH0cPD$;)@m4dK%2zc@q=&dJb8w2T^pKoT~_kHg%-`CbG(01?33VtI1e{yGTh5wr? zUvEAGhc<A+jgYj7Ud_;m%(h7Y$zc8xzAnv!7gt{3OJ@eaxgIkkU;vtD+Bu&eL3^1S zEvPJyep~NbwFdP>+za}&CsG;+Rb|pY6fEU5NyPv?Wm{H5QT&()iIOTG=`4zcAv+9_ zGD#VY1V0M#afm|<>5!$t%~i}I<h0vmNfHJgQ-O%PU5)ozQ$-?08pN74y1Aet*5HAp zy&%L#R3ciwQi<Gh94<U*^8t(dVy?*(k%O`G+<xZx;#diXV-kv!2xH|O%YNWkfAf5s z6_P#dqjK{zcR@zBUVXFOtnIq3o&Ehrv(|Fo?N$%o?u^QdC9owb6~uj6DP~32)jC`^ zGlb0U&(zqSPQuQyQ&Y2LMU00*D&ox2fgF46C{)?%0__jD%4J<|zuM{?NK9p?hbbS4 z&fbaVvozY9m0WffRor%RHaj`FUU@OA`OCDV5%re>`9V)plMpgBili`we0l)HI=z#( x2|OL&?h`ORS-+tC6UyUtd;Fw!0rg9$kD)#(ZH!AB7p2Y1(&p)E-qO1o{2z{l(=Gr2 literal 0 HcmV?d00001 diff --git a/iotDashboard/db_create.py b/iotDashboard/db_create.py new file mode 100644 index 0000000..e69de29 diff --git a/iotDashboard/gpt.py b/iotDashboard/gpt.py new file mode 100644 index 0000000..e69de29 diff --git a/iotDashboard/migrations/__pycache__/0001_initial.cpython-311.pyc b/iotDashboard/migrations/__pycache__/0001_initial.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..018d62798f3e4eb9bf76a60a19fb0b928253e09e GIT binary patch literal 1258 zcma)5&2JM&6rWkI*Y<i7>=0PFm8k_0ut*>SQb;S)v`z{t0)+TFtSqfIp0TrLKg{ep z!W^m`df>>d9D7Ptkpsv43B7eJ?ZKK;q@HqH6i%F&S;wg$NbuJ4{NBv(x9`n+Z~vN^ zQ301Hzx~?*836th&Q!7o#(skve*gp!bb$v==t-sofdp;>B>fIh2E!ibPGpN)uMa&l zgX9Om$nJ?lLt?7ubcr1~fgdu(3lMh0_5m^d(+-^dZ=eW?fe8^XB?Qe(7nrgHaJHvR z2}nX2zLV?a4|gDfNJiOhWlQF1@HLT+gmNjeFx^4<?ZOt1@tUtgjQR<t_!*4$6_}Y6 zlNMII8_C<VM`9iOGN!9+m$oFHBVQBgLbHg<yr6TeaA#V{hfG<;8%`TD$w4=m^y{i2 zGtC}Efz>A1jxb`yK5;yo46P@4$O<7Qj_o?nG1E3MSqnmJ`L>6dEcD(L9?F2cQ}#2< z#eO$h=kM+B&GT)R?~@=3+JVc85%&6+w{k!*N9Pdk*aJ6W`SpS4ASW6c5}6egrQ={1 zg?9~&<*hW1Wii#Vc=HD?=C)>8PY1RuVskI&_)f&LV;aJJ>?7>A9UKxdP^`F;UgwS& z8zOqkw5HSjnP2pVh`|ag>o%E~S~Cb-Z2J>W4boJ_)cp<iIT5^nxbj2m5idm8>R^JN z2d#U<HfMYHNYKOWC~P@Fv}%XzYk^JB$IkyF+^W~>m#u@0FZG8+jAp8Y5$<xedHC$k z&TZaIWH_&E-cItM@ZAeF&Yv2WE4$?rRKM_YBi1kM$p0)*)1p41dN|U<SPy9_8kcoi zF<zaFE5@7qw6Zj+EX9>2TKax0nCArZ+?y*@UmWR+vA#%4*T&^<Y4zf($8q)I&K#{a zM%6}KZP3#7arqRjUHwyuYgc!Ep|$3y){JXST3SgNb-}3boS@a^QFS@4F4NLa$xLp3 zHp!|5^)2A)olr?en^QJ#iHDq-UimJcx&rFiemA&;*3yopO_~(tw1B191#Z31Hzxi6 tk(}kO_y)trTqPL@VWI$NP;ma}FHw#|B>_j(OEoSnycGxUK69GJ{QxMeOauS` literal 0 HcmV?d00001 diff --git a/iotDashboard/migrations/__pycache__/__init__.cpython-311.pyc b/iotDashboard/migrations/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..24f7a7a0774fd4b70cd4dad6b73a18de6c4dffe4 GIT binary patch literal 189 zcmZ3^%ge<81le`x(?IlN5CH>>P{wCAAY(d13PUi1CZpd<h9V{)|1(JPSAersOlWax zQE^OKYEep6eoR1Raz<iNZa`6fR%&udaZF}@iA!Q}MpAxaQ3@(2H#5B`u_QA;uQ(<? vJ~J<~BtBlRpz;@oO>TZlX-=wL5i8IFkQ0jefy4)9Mn=XD3^1aI87Kw-Uyw2z literal 0 HcmV?d00001 From ed7d33e87f85be25692e4e905abafc27c207e2fa Mon Sep 17 00:00:00 2001 From: ferdzo <ferdzo@users.noreply.github.com> Date: Thu, 10 Oct 2024 00:10:21 +0200 Subject: [PATCH 2/3] Update chart.html, experimenting with MQTT. --- iotDashboard/db_create.py | 54 +++++ iotDashboard/forms.py | 69 +++++- iotDashboard/gpt.py | 41 ++++ iotDashboard/models.py | 30 ++- iotDashboard/mqtt_service.py | 99 +++++--- iotDashboard/tasks.py | 179 +++++++++----- iotDashboard/templates/chart.html | 310 +++++++++--------------- iotDashboard/templates/device_form.html | 21 +- iotDashboard/templates/device_list.html | 42 ++-- iotDashboard/urls.py | 5 +- iotDashboard/views.py | 119 +++++++-- 11 files changed, 620 insertions(+), 349 deletions(-) diff --git a/iotDashboard/db_create.py b/iotDashboard/db_create.py index e69de29..d76df4c 100644 --- a/iotDashboard/db_create.py +++ b/iotDashboard/db_create.py @@ -0,0 +1,54 @@ +import psycopg2 +from psycopg2 import sql +import os +from dotenv import load_dotenv + +# Load environment variables +load_dotenv() + +# Define your database connection parameters +DATABASE_NAME = os.getenv('DB_NAME', 'example') +USER = os.getenv('DB_USER', 'postgres') +PASSWORD = os.getenv('DB_PASSWORD', 'coolermaster') +HOST = os.getenv('DB_HOST', '10.10.0.1') +PORT = os.getenv('DB_PORT', '5555') + +def create_sensor_readings_table(): + """Create the sensor_readings table if it does not exist.""" + try: + # Establish connection to the database + conn = psycopg2.connect( + dbname=DATABASE_NAME, + user=USER, + password=PASSWORD, + host=HOST, + port=PORT + ) + + with conn.cursor() as cursor: + # SQL command to create the sensor_readings table + create_table_query = """ + CREATE TABLE IF NOT EXISTS sensor_readings ( + time TIMESTAMPTZ NOT NULL, + device_name VARCHAR(255) NOT NULL, -- Use device_name as a string + metric VARCHAR(50) NOT NULL, -- Type of sensor + value DOUBLE PRECISION NOT NULL, -- The sensor's value + PRIMARY KEY (time, device_name, metric) -- Composite primary key + ); + """ + cursor.execute(create_table_query) + print("Table 'sensor_readings' created or already exists.") + + # Commit changes + conn.commit() + + except Exception as e: + print(f"Error during database operations: {e}") + + finally: + if conn: + conn.close() + print("Database connection closed.") + +if __name__ == "__main__": + create_sensor_readings_table() \ No newline at end of file diff --git a/iotDashboard/forms.py b/iotDashboard/forms.py index 315987a..8c09f2e 100644 --- a/iotDashboard/forms.py +++ b/iotDashboard/forms.py @@ -1,7 +1,72 @@ from django import forms -from .models import Device +from .models import Device, Sensor, SensorType class DeviceForm(forms.ModelForm): + # Optionally include sensors as choices in the form if relevant + sensors = forms.ModelMultipleChoiceField( + queryset=Sensor.objects.all(), + required=False, + widget=forms.CheckboxSelectMultiple, + label='Sensors' + ) + class Meta: model = Device - fields = ['name', 'ip', 'protocol', 'temperature', 'humidity'] + fields = ['name', 'ip', 'protocol'] + + def __init__(self, *args, **kwargs): + # Optionally pass initial sensors for editing an existing device + if 'instance' in kwargs: + initial_sensors = kwargs['instance'].sensors.all() if kwargs['instance'] else None + initial = kwargs.get('initial', {}) + initial['sensors'] = initial_sensors + kwargs['initial'] = initial + super(DeviceForm, self).__init__(*args, **kwargs) + + def save(self, commit=True): + # Save the device instance + device = super(DeviceForm, self).save(commit=False) + + if commit: + device.save() + self.save_m2m() # Ensure M2M save happens + + return device +class SensorWithTypeForm(forms.ModelForm): + # Add fields for SensorType directly in the form + type_name = forms.CharField(max_length=50, label="Sensor Type Name") + unit = forms.CharField(max_length=20, label="Unit", required=False) + protocol = forms.ChoiceField( + choices=[('mqtt', 'MQTT'), ('http', 'HTTP')], + label="Protocol" + ) + topic = forms.CharField(max_length=100, label="Topic", required=False) + endpoint = forms.CharField(max_length=100, label="Endpoint", required=False) + + class Meta: + model = Sensor + fields = ['device', 'enabled'] + + def save(self, commit=True): + # Create or get the SensorType + try: + sensor_type = SensorType.objects.get(name=self.cleaned_data['type_name']) + except SensorType.DoesNotExist: + sensor_type = SensorType( + name=self.cleaned_data['type_name'], + unit=self.cleaned_data['unit'], + protocol=self.cleaned_data['protocol'], + topic=self.cleaned_data['topic'], + endpoint=self.cleaned_data['endpoint'] + ) + if commit: + sensor_type.save() + + # Create Sensor with the SensorType found or created + sensor = super(SensorWithTypeForm, self).save(commit=False) + sensor.type = sensor_type + + if commit: + sensor.save() + + return sensor \ No newline at end of file diff --git a/iotDashboard/gpt.py b/iotDashboard/gpt.py index e69de29..9654bd9 100644 --- a/iotDashboard/gpt.py +++ b/iotDashboard/gpt.py @@ -0,0 +1,41 @@ +import json + +import redis +from dotenv import load_dotenv +from openai import OpenAI + +load_dotenv() +client = OpenAI() + +redis_client = redis.StrictRedis(host='10.10.0.1', port=6379, db=0) + +data = redis_client.get("last5").decode("utf-8") + + +def analysis(environment_data): + completion = client.chat.completions.create( + model="gpt-4o-mini", + messages=[ + {"role": "system", + "content": "You are an assistant that analyzes environmental data for an office working space and provides " + "concise numerical insights."}, + { + "role": "user", + "content": f"Analyze the following environmental data. The goal is maintaining optimal working " + f"conditions in the office and peak working brain. Focus on any outliers or necessary adjustments. The data is as following: {environment_data}." + f"The output should be only the recommendations in numerical form with postitive and negative " + f"numbers and also provide small summary in a sentence or two of the current conditions and " + f"easily computable in json format. Be consistent with the + and - signs and the summary" + } + ], + response_format={"type": "json_object"} + + ) + output = completion.choices[0].message.content + + return output + +output = analysis(data) +redis_client.set("gpt",json.dumps(output)) + +print(output) diff --git a/iotDashboard/models.py b/iotDashboard/models.py index 5e39517..34249c1 100644 --- a/iotDashboard/models.py +++ b/iotDashboard/models.py @@ -1,10 +1,26 @@ from django.db import models -class Device(models.Model): - name = models.CharField(max_length=50) - ip = models.CharField(max_length=20) - protocol = models.CharField(max_length=20) - temperature = models.BooleanField(default=False) - humidity = models.BooleanField(default=False) + +class SensorType(models.Model): + name = models.CharField(max_length=50, unique=True) # Sensor name, e.g., "CO2", "Noise", etc. + unit = models.CharField(max_length=20) # Unit of measurement, e.g., "ppm", "dB", "lux" + protocol = models.CharField(max_length=20, choices=[('mqtt', 'MQTT'), ('http', 'HTTP')]) # Protocol for communication + topic = models.CharField(max_length=100, null=True, blank=True) # Topic for MQTT communication + endpoint = models.CharField(max_length=100, null=True, blank=True) # Endpoint for HTTP communication def __str__(self): - return self.name \ No newline at end of file + return f"{self.name} ({self.unit})" + +class Device(models.Model): + name = models.CharField(max_length=50) # Device name + ip = models.CharField(max_length=20) # Device IP address + protocol = models.CharField(max_length=20, choices=[('mqtt', 'MQTT'), ('http', 'HTTP')]) + + def __str__(self): + return self.name + +class Sensor(models.Model): + device = models.ForeignKey(Device, related_name='sensors', on_delete=models.CASCADE) + type = models.ForeignKey(SensorType, on_delete=models.CASCADE) + enabled = models.BooleanField(default=True) + def __str__(self): + return f"{self.type.name} Sensor on {self.device.name}" diff --git a/iotDashboard/mqtt_service.py b/iotDashboard/mqtt_service.py index caa959b..b4319eb 100644 --- a/iotDashboard/mqtt_service.py +++ b/iotDashboard/mqtt_service.py @@ -6,83 +6,104 @@ import paho.mqtt.client as mqtt import redis from dotenv import load_dotenv +# Load environment variables load_dotenv() +# Set up Redis client REDIS_HOST = os.getenv('REDIS_HOST', 'localhost') redis_client = redis.StrictRedis(host=REDIS_HOST, port=6379, db=0) - print("Connected to Redis Server") +# MQTT broker address MQTT_BROKER = os.getenv('MQTT_BROKER') - mqtt_data = {} -def get_devices(): - """" Simple method to get all devices """ - # Get devices from Redis - devices_json = redis_client.get('devices') +def get_mqtt_devices(): + """Retrieve MQTT devices and sensor details from Redis.""" + devices_json = redis_client.get('mqtt_devices') if devices_json: return json.loads(devices_json) return [] +def build_device_map(): + """Build a mapping of device endpoints to friendly names.""" + devices = get_mqtt_devices() + return {device['topic'].split('/')[0]: device['device_name'] for device in + devices} # Assuming topic starts with device name + + def on_message(client, userdata, msg): - """" Simple method to handle messages """ - topic = msg.topic.split('/') - device_name = topic[0] - sensor = topic[-2] + """Handle incoming messages from MQTT broker.""" + try: + # Parse the incoming message topic + topic_parts = msg.topic.split('/') + device_endpoint = topic_parts[0] # This is the actual endpoint name + sensor_type = topic_parts[2] # Assuming sensor type is in the third part - if device_name not in mqtt_data: - mqtt_data[device_name] = {"time": datetime.now(), - "device": device_name, - "temperature": None, - "humidity": None} + sensor_value = float(msg.payload.decode()) + print(f"Received message from {device_endpoint}, sensor {sensor_type}: {sensor_value}") - if sensor == "tempreature": - mqtt_data[device_name]["temperature"] = float(msg.payload.decode()) - elif sensor == "humidity": - mqtt_data[device_name]["humidity"] = float(msg.payload.decode()) + # Build the device map to get the friendly device name + device_map = build_device_map() + device_name = device_map.get(device_endpoint, device_endpoint) # Fallback to endpoint if not found + + if device_name not in mqtt_data: + mqtt_data[device_name] = { + "time": datetime.utcnow().isoformat(), + "device": device_name, + "sensors": {} + } + + # Update the sensor value in the mqtt_data dictionary + mqtt_data[device_name]["sensors"][sensor_type] = sensor_value + mqtt_data[device_name]["time"] = datetime.utcnow().isoformat() + + # Store the updated data structure in Redis + redis_client.set(device_name, json.dumps(mqtt_data[device_name])) + print(f"Updated data for {device_name}: {mqtt_data[device_name]}") + + except ValueError as e: + print(f"Error processing message payload: {e}") - mqtt_data[device_name]["time"] = str(datetime.now()) - redis_client.set(device_name, json.dumps(mqtt_data)) - print(f"Updated data for {device_name}: {mqtt_data[device_name]}") def on_connect(client, userdata, flags, rc): - """Handle successful connection.""" - print(f"Connected with result code {rc}") - devices = get_devices() - for device in devices: - client.subscribe(f"{device['name']}/sensor/+/state") + """Handle successful MQTT connection.""" + if rc == 0: + print("Connected to MQTT Broker") + devices = get_mqtt_devices() + for device in devices: + client.subscribe(device['topic']) # Subscribing to each device's topic + print(f"Subscribed to topic: {device['topic']}") + else: + print(f"Failed to connect, return code {rc}") + def on_disconnect(client, userdata, rc): - """Handle disconnection.""" - if rc != 0: - print(f"Unexpected disconnection. Result code: {rc}") + """Handle disconnection from MQTT broker.""" + print(f"Disconnected with result code: {rc}") + def start_mqtt_client(): - """ Start the MQTT client """ - devices = get_devices() - + """Start the MQTT client to begin listening to topics.""" client = mqtt.Client() - client.on_message = on_message client.on_connect = on_connect client.on_disconnect = on_disconnect + client.on_message = on_message client.connect(MQTT_BROKER) + client.loop_start() print("MQTT Client Started") - for device in devices: - client.subscribe(f"{device['name']}/sensor/+/state") - # Keep the script running try: while True: time.sleep(10) # Sleep to prevent high CPU usage except KeyboardInterrupt: print("Script interrupted by user") finally: - client.loop_stop() # Stop the loop when exiting + client.loop_stop() if __name__ == "__main__": - start_mqtt_client() \ No newline at end of file + start_mqtt_client() diff --git a/iotDashboard/tasks.py b/iotDashboard/tasks.py index 2ea6aef..459d904 100644 --- a/iotDashboard/tasks.py +++ b/iotDashboard/tasks.py @@ -2,91 +2,154 @@ import json import datetime import requests import psycopg2 +import redis from django.conf import settings from huey import crontab from huey.contrib.djhuey import periodic_task -from .models import Device -import redis - +from .models import Device, Sensor, SensorType # Initialize Redis client redis_client = redis.StrictRedis(host='10.10.0.1', port=6379, db=0) - def devices_to_redis(): - """Fetch all devices from Django and store them in Redis.""" + """Fetch devices and their sensors' topics from Django and store them in Redis.""" devices = Device.objects.all() - devices_list = [ - { - 'id': device.id, - 'name': device.name, - 'protocol': device.protocol, - 'ip': device.ip, - } - for device in devices - ] - redis_client.set('devices', json.dumps(devices_list)) + devices_list = [] + for device in devices: + for sensor in device.sensors.all(): + sensor_data = { + 'device_name': device.name, + 'sensor_name': sensor.type.name, + 'topic': sensor.type.topic # Assuming the topic is stored in SensorType + } + devices_list.append(sensor_data) + redis_client.set('mqtt_devices', json.dumps(devices_list)) + print("Devices with sensors stored in Redis.") - -def fetch_data_http(device): - """Fetch temperature and humidity data from an HTTP sensor.""" - data = { - "time": datetime.datetime.now().isoformat(), - "device": device.name, - } +def fetch_data_http(device, sensor): + """Fetch data from an HTTP sensor.""" + sensor_type_name = sensor.type.name.lower() try: - temperature_response = requests.get(f"http://{device.ip}/sensor/tempreature") - humidity_response = requests.get(f"http://{device.ip}/sensor/humidity") - data["temperature"] = temperature_response.json().get('value') - data["humidity"] = humidity_response.json().get('value') + # Make the request to the device's HTTP endpoint + response = requests.get(f"http://{device.ip}/sensor/{sensor_type_name}", timeout=5) + response.raise_for_status() # Raise an exception for any non-200 status codes + sensor_value = response.json().get('value') # Assuming the JSON response structure + if sensor_value is not None: + return { + "time": datetime.datetime.utcnow().isoformat(), + "device": device.name, + "sensor": sensor_type_name, + "sensor_value": sensor_value + } + else: + print(f"No value returned from {device.name} for {sensor_type_name}") except requests.RequestException as e: - print(f"HTTP request failed: {e}") - return data + print(f"HTTP request failed for {device.name}: {e}") + return None - -def fetch_data_mqtt(device_name): - """Fetch data from Redis for a specific MQTT device.""" - data = redis_client.get(device_name) +def fetch_data_mqtt(device, sensor): + """Fetch data from Redis for a specific MQTT device and sensor.""" + # Get the data for the specific device from Redis + data = redis_client.get(device.name) # Assumes device.name is the Redis key if data: - data = json.loads(data.decode('utf-8')).get(device_name) - if data and datetime.datetime.fromisoformat(data["time"]) > datetime.datetime.now() - datetime.timedelta( - minutes=2): - return data + data = json.loads(data.decode('utf-8')) + + # Normalize the sensor name to lowercase for lookup + sensor_name = sensor.type.name.lower() + sensor_value = data['sensors'].get(sensor_name) + + if sensor_value is not None and is_recent_data(data['time']): + return { + "time": data['time'], + "device": device.name, + "sensor_value": sensor_value + } + + print(data) return None -def insert_data(data): - """Insert data into the PostgreSQL database.""" - with psycopg2.connect(settings.CONNECTION_STRING) as conn: - with conn.cursor() as cursor: - insert_query = """ - INSERT INTO conditions (time, device, temperature, humidity) - VALUES (%s, %s, %s, %s) - """ - cursor.execute(insert_query, (data["time"], data["device"], data["temperature"], data["humidity"])) - conn.commit() +def is_recent_data(timestamp): + """Check if data is within a 2-minute freshness window.""" + data_time = datetime.datetime.fromisoformat(timestamp) + return data_time > datetime.datetime.utcnow() - datetime.timedelta(minutes=2) +def insert_data(data, sensor_type): + """Insert parsed data into the PostgreSQL database.""" + if 'sensor_value' not in data: + print(f"Missing 'sensor_value' in data: {data}. Skipping insertion.") + return + + insert_data_dict = { + "time": data['time'], + "device": data['device'], + "metric": sensor_type.lower(), + "value": data['sensor_value'], + } + + try: + with psycopg2.connect(settings.CONNECTION_STRING) as conn: + with conn.cursor() as cursor: + insert_query = """ + INSERT INTO sensor_readings (time, device_name, metric, value) + VALUES (%s, %s, %s, %s); + """ + cursor.execute(insert_query, ( + insert_data_dict["time"], + insert_data_dict["device"], + insert_data_dict["metric"], + insert_data_dict["value"] + )) + conn.commit() + print(f"Data inserted successfully for {insert_data_dict['device']}: {insert_data_dict}") + except Exception as e: + print(f"Failed to insert data: {e}") @periodic_task(crontab(minute='*/1')) def fetch_data_from_all_devices(): """Fetch and insert data for all devices based on their protocol.""" devices = Device.objects.all() for device in devices: - data = None - if device.protocol == 'http': - data = fetch_data_http(device) - elif device.protocol == 'mqtt': - data = fetch_data_mqtt(device.name) + for sensor in device.sensors.all(): + data = None - if data: - data_time = datetime.datetime.fromisoformat(data["time"]) - if data_time > datetime.datetime.now() - datetime.timedelta(minutes=1): - insert_data(data) + if device.protocol == 'http': + data = fetch_data_http(device, sensor) + elif device.protocol == 'mqtt': + data = fetch_data_mqtt(device, sensor) + + if data and is_recent_data(data['time']): + insert_data(data, sensor.type.name) else: - print(f"No recent data available for {device.name}. Skipping insertion.") - else: - print(f"No data available for {device.name}. Skipping insertion.") + print(f"No recent or valid data for {device.name}. Skipping.") +@periodic_task(crontab(minute='*/5')) +def last_5_minutes(): + """Fetch the last 5 readings from TimescaleDB and store them in Redis.""" + try: + with psycopg2.connect(settings.CONNECTION_STRING) as conn: + with conn.cursor() as cursor: + cursor.execute(""" + SELECT time, device_name, metric, value + FROM sensor_readings + ORDER BY time DESC + LIMIT 5; + """) + results = cursor.fetchall() + + data = [ + { + "time": reading[0].isoformat(), + "device": reading[1], + "metric": reading[2], + "value": reading[3] + } + for reading in results + ] + redis_client.set("last5", json.dumps(data)) + print("Last 5 readings:", data) + except Exception as e: + print(f"Error fetching or storing the last 5 readings: {e}") # Initialize device data in Redis devices_to_redis() diff --git a/iotDashboard/templates/chart.html b/iotDashboard/templates/chart.html index 09b3707..2079096 100644 --- a/iotDashboard/templates/chart.html +++ b/iotDashboard/templates/chart.html @@ -1,222 +1,144 @@ <!DOCTYPE html> <html lang="en"> - <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title>Conditions Chart with Chart.js</title> - <!-- Bootstrap CSS --> - <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> + <title>IoT Sensor Dashboard</title> + <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css" rel="stylesheet"> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <style> - .chart-container { - position: relative; - width: 100%; - height: 400px; - background-color: #f9f9f9; - padding: 20px; - border-radius: 10px; - box-shadow: 0px 0px 20px rgba(0, 0, 0, 0.1); - } - - #conditionsChart { - width: 100% !important; - height: 100% !important; - } - - .current-conditions { - text-align: center; - padding: 20px; + .device-panel { margin-bottom: 20px; - background-color: #ffffff; - border-radius: 10px; - box-shadow: 0px 0px 15px rgba(0, 0, 0, 0.1); - } - - .current-conditions h2 { - font-size: 36px; - margin: 10px 0; - } - - .current-conditions .value { - font-size: 48px; - font-weight: bold; - margin-bottom: 10px; } </style> </head> +<body class="container my-4"> + <!-- GPT Summary --> + <h4 class="text-center mb-4">{{ gpt }}</h4> -<body class="bg-light"> - <nav class="navbar navbar-expand-lg navbar-light bg-light"> - <div class="container-fluid"> - <a class="navbar-brand" href="#">IoT Dashboard</a> - <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"> - <span class="navbar-toggler-icon"></span> - </button> - <div class="collapse navbar-collapse" id="navbarNav"> - <ul class="navbar-nav me-auto mb-2 mb-lg-0"> - <li class="nav-item"> - <a class="nav-link" href="{% url 'chart' %}">Chart</a> - </li> - <li class="nav-item"> - <a class="nav-link" href="{% url 'device_list' %}">Devices</a> - </li> - </ul> - <ul class="navbar-nav"> - {% if user.is_authenticated %} - <li class="nav-item"> - <a class="nav-link" href="{% url 'logout' %}">Logout</a> - </li> - {% else %} - <li class="nav-item"> - <a class="nav-link" href="{% url 'login' %}">Login</a> - </li> - {% endif %} - </ul> - </div> - </div> - </nav> + <h1 class="text-center mb-4">IoT Sensor Data Dashboard</h1> - <div class="container mt-5"> - <h1 class="text-center mb-4">Temperature and Humidity Over Time</h1> + <!-- Button to add a new "island" (device panel) --> + <div class="text-center mb-4"> + <button class="btn btn-success" onclick="addDevicePanel()">Add Device Panel</button> + </div> - <!-- Current Conditions --> - <div class="current-conditions"> - <h2>Current Conditions</h2> - <div class="value" id="current-temperature">Loading...</div> - <div class="value" id="current-humidity">Loading...</div> - </div> - - <!-- Device Selector Dropdown --> - <div class="row mb-4"> - <div class="col-md-4 offset-md-4"> - <select id="deviceSelector" class="form-select" onchange="fetchDeviceData()"> - {% for device in devices %} - <option value="{{ device.name }}">{{ device.name }} ({{ device.ip }})</option> - {% endfor %} - </select> - </div> - </div> - - <!-- Date Pickers for Time Window --> - <div class="row mb-4"> - <div class="col-md-6 offset-md-3 d-flex justify-content-between"> - <div> - <label for="startDate" class="form-label">Start Date:</label> - <input type="datetime-local" id="startDate" class="form-control" onchange="fetchDeviceData()"> - </div> - <div> - <label for="endDate" class="form-label">End Date:</label> - <input type="datetime-local" id="endDate" class="form-control" onchange="fetchDeviceData()"> - </div> - </div> - </div> - - <!-- Chart Container --> - <div class="row"> - <div class="col-md-8 offset-md-2"> - <div class="chart-container"> - <canvas id="conditionsChart"></canvas> - </div> - </div> - </div> + <!-- Container for dynamic device panels ("islands") --> + <div id="device-panels" class="row"> + <!-- Dynamic panels will be added here --> </div> <script> - var ctx = document.getElementById('conditionsChart').getContext('2d'); - var conditionsChart; + const devices = {{ devices_json|safe }}; + let panelCount = 0; + const charts = {}; - function fetchDeviceData() { - var device = document.getElementById('deviceSelector').value; - var startDate = document.getElementById('startDate').value; - var endDate = document.getElementById('endDate').value; + // Function to add a new device panel + function addDevicePanel() { + const panelId = `device-panel-${panelCount}`; + const chartId = `sensorChart-${panelCount}`; + const panelHtml = ` + <div class="col-md-6 device-panel" id="${panelId}"> + <div class="card"> + <div class="card-header d-flex justify-content-between"> + <span>Device Panel</span> + <button class="btn btn-danger btn-sm" onclick="removeDevicePanel('${panelId}')">Remove</button> + </div> + <div class="card-body"> + <div class="mb-3"> + <label for="device-select-${panelId}" class="form-label">Choose a device:</label> + <select id="device-select-${panelId}" class="form-select" onchange="fetchSensorData('${panelId}', '${chartId}')"> + <!-- Dynamically populated options --> + </select> + </div> - fetch(`/fetch_device_data/?device=${device}&start_date=${startDate}&end_date=${endDate}`) + <!-- Date and Time Selection --> + <div class="mb-3"> + <label for="start-date-${panelId}" class="form-label">Start Date and Time:</label> + <input type="datetime-local" id="start-date-${panelId}" class="form-control"> + </div> + + <div class="mb-3"> + <label for="end-date-${panelId}" class="form-label">End Date and Time:</label> + <input type="datetime-local" id="end-date-${panelId}" class="form-control"> + </div> + + <button class="btn btn-primary w-100" onclick="fetchSensorData('${panelId}', '${chartId}')">Fetch Data</button> + + <canvas id="${chartId}" class="mt-4"></canvas> + </div> + </div> + </div>`; + + document.getElementById('device-panels').insertAdjacentHTML('beforeend', panelHtml); + populateDeviceSelect(`device-select-${panelId}`); + + const ctx = document.getElementById(chartId).getContext('2d'); + charts[chartId] = new Chart(ctx, { + type: 'line', + data: { labels: [], datasets: [] }, + options: { responsive: true } + }); + + panelCount++; // Increment panelCount after using it + } + + // Function to populate device dropdown + function populateDeviceSelect(selectId) { + const deviceSelect = document.getElementById(selectId); + deviceSelect.innerHTML = ""; // Clear previous options + devices.forEach(device => { + const option = document.createElement('option'); + option.value = device.name; + option.text = `${device.name} (${device.sensors__type__name})`; + deviceSelect.appendChild(option); + }); + } + + // Function to remove a device panel + function removeDevicePanel(panelId) { + const panel = document.getElementById(panelId); + panel.remove(); + } + + // Function to fetch sensor data and update the chart for a specific panel + function fetchSensorData(panelId, chartId) { + const device = document.getElementById(`device-select-${panelId}`).value; + const startDate = document.getElementById(`start-date-${panelId}`).value; + const endDate = document.getElementById(`end-date-${panelId}`).value; + + if (!device || !startDate || !endDate) { + alert('Please select a device and both start and end date/time.'); + return; + } + + fetch(`/fetch_device_data?device=${device}&start_date=${startDate}&end_date=${endDate}`) .then(response => response.json()) .then(data => { - if (conditionsChart) { - conditionsChart.destroy(); // Destroy the old chart - } + const times = Object.values(data)[0].times; + const datasets = Object.keys(data).map(metric => ({ + label: metric, + data: data[metric].values, + borderColor: getRandomColor(), + fill: false + })); - conditionsChart = new Chart(ctx, { - type: 'line', - data: { - labels: data.times, - datasets: [ - { - label: 'Temperature (°C)', - data: data.temperatures, - borderColor: 'red', - fill: false, - }, - { - label: 'Humidity (%)', - data: data.humidities, - borderColor: 'blue', - fill: false, - } - ] - }, - options: { - responsive: true, - maintainAspectRatio: false, - scales: { - x: { - title: { - display: true, - text: 'Time' - }, - ticks: { - autoSkip: true, - maxRotation: 45, - minRotation: 45, - } - }, - y: { - title: { - display: true, - text: 'Values' - }, - beginAtZero: true - } - }, - plugins: { - legend: { - display: true, - position: 'top', - labels: { - boxWidth: 20, - padding: 20, - } - }, - tooltip: { - enabled: true, - } - } - } - }); - - // Check if the last recorded data is within the last 10 minutes - const lastRecordedTime = new Date(data.times[data.times.length - 1]); - const now = new Date(); - const tenMinutesAgo = new Date(now.getTime() - 10 * 60000); - - if (lastRecordedTime > tenMinutesAgo) { - document.getElementById('current-temperature').textContent = `Temperature: ${data.temperatures[data.temperatures.length - 1]}°C`; - document.getElementById('current-humidity').textContent = `Humidity: ${data.humidities[data.humidities.length - 1]}%`; - } else { - document.getElementById('current-temperature').textContent = `Temperature: -`; - document.getElementById('current-humidity').textContent = `Humidity: -`; - } + charts[chartId].data.labels = times; + charts[chartId].data.datasets = datasets; + charts[chartId].update(); }); } - // Initial load for the default device and time range - fetchDeviceData(); + // Function to generate random color for chart lines + function getRandomColor() { + const letters = '0123456789ABCDEF'; + let color = '#'; + for (let i = 0; i < 6; i++) color += letters[Math.floor(Math.random() * 16)]; + return color; + } + + // Initialize with one device panel + addDevicePanel(); </script> - - <!-- Bootstrap JS and dependencies (Optional) --> - <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> </body> - </html> diff --git a/iotDashboard/templates/device_form.html b/iotDashboard/templates/device_form.html index ff9c424..e296695 100644 --- a/iotDashboard/templates/device_form.html +++ b/iotDashboard/templates/device_form.html @@ -5,9 +5,11 @@ <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>{% if form.instance.pk %}Edit{% else %}Add{% endif %} Device</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> + <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> </head> + <body class="bg-light"> - <nav class="navbar navbar-expand-lg navbar-light bg-light"> + <nav class="navbar navbar-expand-lg navbar-light bg-light"> <div class="container-fluid"> <a class="navbar-brand" href="#">IoT Dashboard</a> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"> @@ -16,7 +18,7 @@ <div class="collapse navbar-collapse" id="navbarNav"> <ul class="navbar-nav me-auto mb-2 mb-lg-0"> <li class="nav-item"> - <a class="nav-link" href="{% url 'chart' %}">Chart</a> + <a class="nav-link" href="{% url 'index' %}">Chart</a> </li> <li class="nav-item"> <a class="nav-link" href="{% url 'device_list' %}">Devices</a> @@ -36,14 +38,29 @@ </div> </div> </nav> + <div class="container mt-5"> <h1 class="text-center mb-4">{% if form.instance.pk %}Edit{% else %}Add{% endif %} Device</h1> <form method="post"> {% csrf_token %} <div class="mb-3"> + <!-- Display the form fields --> {{ form.as_p }} + + <!-- If there are errors, display them --> + {% if form.errors %} + <div class="alert alert-danger"> + <ul> + {% for field, errors in form.errors.items %} + <li>{{ field }}: {{ errors|join:", " }}</li> + {% endfor %} + </ul> + </div> + {% endif %} </div> + + <!-- Submit and Cancel buttons --> <button type="submit" class="btn btn-success">Save</button> <a href="{% url 'device_list' %}" class="btn btn-secondary">Cancel</a> </form> diff --git a/iotDashboard/templates/device_list.html b/iotDashboard/templates/device_list.html index ad23c91..bfb8c57 100644 --- a/iotDashboard/templates/device_list.html +++ b/iotDashboard/templates/device_list.html @@ -7,7 +7,8 @@ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> </head> <body class="bg-light"> - <nav class="navbar navbar-expand-lg navbar-light bg-light"> + <!-- Navbar --> + <nav class="navbar navbar-expand-lg navbar-light bg-light"> <div class="container-fluid"> <a class="navbar-brand" href="#">IoT Dashboard</a> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"> @@ -15,30 +16,23 @@ </button> <div class="collapse navbar-collapse" id="navbarNav"> <ul class="navbar-nav me-auto mb-2 mb-lg-0"> - <li class="nav-item"> - <a class="nav-link" href="{% url 'chart' %}">Chart</a> - </li> - <li class="nav-item"> - <a class="nav-link" href="{% url 'device_list' %}">Devices</a> - </li> + <li class="nav-item"><a class="nav-link" href="{% url 'index' %}">Chart</a></li> + <li class="nav-item"><a class="nav-link" href="{% url 'device_list' %}">Devices</a></li> </ul> <ul class="navbar-nav"> {% if user.is_authenticated %} - <li class="nav-item"> - <a class="nav-link" href="{% url 'logout' %}">Logout</a> - </li> + <li class="nav-item"><a class="nav-link" href="{% url 'logout' %}">Logout</a></li> {% else %} - <li class="nav-item"> - <a class="nav-link" href="{% url 'login' %}">Login</a> - </li> + <li class="nav-item"><a class="nav-link" href="{% url 'login' %}">Login</a></li> {% endif %} </ul> </div> </div> </nav> + + <!-- Device Management --> <div class="container mt-5"> <h1 class="text-center mb-4">Manage Devices</h1> - <a href="{% url 'add_device' %}" class="btn btn-primary mb-3">Add Device</a> <table class="table table-striped"> @@ -47,8 +41,7 @@ <th>Name</th> <th>IP Address</th> <th>Protocol</th> - <th>Temperature Monitoring</th> - <th>Humidity Monitoring</th> + <th>Sensor Types</th> <!-- Adjusted to Sensor Types --> <th>Actions</th> </tr> </thead> @@ -58,16 +51,27 @@ <td>{{ device.name }}</td> <td>{{ device.ip }}</td> <td>{{ device.protocol }}</td> - <td>{{ device.temperature|yesno:"Yes,No" }}</td> - <td>{{ device.humidity|yesno:"Yes,No" }}</td> + <!-- Collect and display sensor types --> + <td> + {% for sensor in device.sensors.all %} + {{ sensor.type.name }}{% if not forloop.last %}, {% endif %} + {% empty %} + No sensors + {% endfor %} + </td> + <!-- You may further expand other sensor-related data --> <td> <a href="{% url 'edit_device' device.pk %}" class="btn btn-warning btn-sm">Edit</a> <a href="{% url 'delete_device' device.pk %}" class="btn btn-danger btn-sm">Delete</a> </td> </tr> + {% empty %} + <tr> + <td colspan="5" class="text-center">No devices found.</td> + </tr> {% endfor %} </tbody> </table> </div> </body> -</html> +</html> \ No newline at end of file diff --git a/iotDashboard/urls.py b/iotDashboard/urls.py index 597f8c0..2c4e263 100644 --- a/iotDashboard/urls.py +++ b/iotDashboard/urls.py @@ -20,12 +20,13 @@ from iotDashboard import views urlpatterns = [ path('admin/', admin.site.urls), - path('',views.index), + path('devices_api/',views.devices_api), + path('',views.chart,name="index"), path('fetch_device_data/', views.fetch_device_data, name='fetch_device_data'), - path('chart/',views.chart,name='chart'), path('devices/', views.device_list, name='device_list'), path('devices/add/', views.add_device, name='add_device'), path('devices/edit/<int:pk>/', views.edit_device, name='edit_device'), path('devices/delete/<int:pk>/', views.delete_device, name='delete_device'), path('logout/', views.logout_view, name='logout'), + path('sensor/add/',views.add_sensor_with_type,name="add_sensor_with_type") ] diff --git a/iotDashboard/views.py b/iotDashboard/views.py index 1264901..11ae9a6 100644 --- a/iotDashboard/views.py +++ b/iotDashboard/views.py @@ -1,57 +1,105 @@ -from django.http import HttpResponse, request, JsonResponse +import json + +from django.core.serializers.json import DjangoJSONEncoder +from django.http import JsonResponse, HttpResponse from django.db import connections from django.shortcuts import render, redirect, get_object_or_404 -from .models import Device -from .forms import DeviceForm +from django.views.decorators.http import require_GET + +from .models import Device, Sensor, SensorType +from .forms import DeviceForm, SensorWithTypeForm +import redis + +redis_client = redis.StrictRedis(host='10.10.0.1', port=6379, db=0) +def fetch_gpt_data(): + return redis_client.get("gpt").decode("utf-8").strip('b"').replace('\\"', '"').replace("\\n", "").replace("\\", "") + +def chart(request): + # Fetch devices and their related sensors + devices = list(Device.objects.all().values('name', 'sensors__type__name')) + + # Serialize data to JSON format + devices_json = json.dumps(devices, cls=DjangoJSONEncoder) + + # Pass devices data to the context + gpt = fetch_gpt_data() + gpt = json.loads(gpt) + context = {'devices_json': devices_json, 'gpt': gpt["summary"]} + + return render(request, 'chart.html', context) + +# Fetch sensor data (AJAX) def fetch_device_data(request): - device = request.GET.get('device', 'livingroom') + device_name = request.GET.get('device', 'Livingroom') start_date = request.GET.get('start_date') end_date = request.GET.get('end_date') - query = """ - SELECT time, temperature, humidity - FROM conditions - WHERE device = %s - """ - params = [device] + # Log the parameters to ensure they are correct + print("Device Name:", device_name) + print("Start Date:", start_date) + print("End Date:", end_date) + # Get the specific device by name + device = get_object_or_404(Device, name=device_name) + + # Initialize the results dictionary to store sensor data + results = {} + + # Prepare SQL query and parameters for the specific sensor type + query = """ + SELECT time, metric, value + FROM sensor_readings + WHERE device_name = %s + """ + params = [device.name] + + # Add time filtering to the query if start_date: - query += " AND time >= %s" + query += " AND time >= %s::timestamptz" params.append(start_date) if end_date: - query += " AND time <= %s" + query += " AND time <= %s::timestamptz" params.append(end_date) + # Log the final query and params + print("Final Query:", query) + print("Params Before Execution:", params) + + # Fetch data from the database with connections["data"].cursor() as cursor: cursor.execute(query, params) rows = cursor.fetchall() - times = [row[0].strftime('%Y-%m-%d %H:%M:%S') for row in rows] - temperatures = [row[1] for row in rows] - humidities = [row[2] for row in rows] + # Process the results and group them by sensor type (metric) + for row in rows: + time, metric, value = row + formatted_time = time.strftime('%Y-%m-%d %H:%M:%S') - return JsonResponse({ - 'times': times, - 'temperatures': temperatures, - 'humidities': humidities, - }) -def chart(request): - devices = Device.objects.all() - context = {'devices': devices} - return render(request, 'chart.html', context) + if metric not in results: + results[metric] = { + 'times': [], + 'values': [] + } + results[metric]['times'].append(formatted_time) + results[metric]['values'].append(value) + + return JsonResponse(results) def index(request): if request.user.is_authenticated: return redirect("/chart/") return HttpResponse("NOT AUTHENTICATED!!!") + + def device_list(request): devices = Device.objects.all() return render(request, 'device_list.html', {'devices': devices}) + def add_device(request): if request.method == 'POST': form = DeviceForm(request.POST) @@ -62,6 +110,7 @@ def add_device(request): form = DeviceForm() return render(request, 'device_form.html', {'form': form}) + def edit_device(request, pk): device = get_object_or_404(Device, pk=pk) if request.method == 'POST': @@ -73,6 +122,7 @@ def edit_device(request, pk): form = DeviceForm(instance=device) return render(request, 'device_form.html', {'form': form}) + def delete_device(request, pk): device = get_object_or_404(Device, pk=pk) if request.method == 'POST': @@ -80,5 +130,22 @@ def delete_device(request, pk): return redirect('device_list') return render(request, 'device_confirm_delete.html', {'device': device}) -def logout_view(): - redirect("/admin") \ No newline at end of file +def add_sensor_with_type(request): + if request.method == 'POST': + form = SensorWithTypeForm(request.POST) + if form.is_valid(): + form.save() # This will save both Sensor and SensorType as needed + return redirect('device_list') # Adjust this to your specific URL name + else: + form = SensorWithTypeForm() + + context = {'form': form} + return render(request, 'sensor_form.html', context) + +def logout_view(request): + return redirect("/admin") + +def devices_api(request): + devices = list(Device.objects.all().values('name', 'sensors__type__name')) + return JsonResponse(devices, safe=False) + From 61512ffc14166d69b5607db26298e09570c85f6b Mon Sep 17 00:00:00 2001 From: ferdzo <ferdzo@users.noreply.github.com> Date: Thu, 10 Oct 2024 00:10:44 +0200 Subject: [PATCH 3/3] Update small --- ...sortype_remove_device_humidity_and_more.py | 47 +++++++++++++++++++ iotDashboard/templates/sensor_form.html | 41 ++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 iotDashboard/migrations/0002_sensortype_remove_device_humidity_and_more.py create mode 100644 iotDashboard/templates/sensor_form.html diff --git a/iotDashboard/migrations/0002_sensortype_remove_device_humidity_and_more.py b/iotDashboard/migrations/0002_sensortype_remove_device_humidity_and_more.py new file mode 100644 index 0000000..6944faf --- /dev/null +++ b/iotDashboard/migrations/0002_sensortype_remove_device_humidity_and_more.py @@ -0,0 +1,47 @@ +# Generated by Django 4.2.5 on 2024-10-08 10:51 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('iotDashboard', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='SensorType', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=50, unique=True)), + ('unit', models.CharField(max_length=20)), + ('protocol', models.CharField(choices=[('mqtt', 'MQTT'), ('http', 'HTTP')], max_length=20)), + ('topic', models.CharField(blank=True, max_length=100, null=True)), + ('endpoint', models.CharField(blank=True, max_length=100, null=True)), + ], + ), + migrations.RemoveField( + model_name='device', + name='humidity', + ), + migrations.RemoveField( + model_name='device', + name='temperature', + ), + migrations.AlterField( + model_name='device', + name='protocol', + field=models.CharField(choices=[('mqtt', 'MQTT'), ('http', 'HTTP')], max_length=20), + ), + migrations.CreateModel( + name='Sensor', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('enabled', models.BooleanField(default=True)), + ('device', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sensors', to='iotDashboard.device')), + ('type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='iotDashboard.sensortype')), + ], + ), + ] diff --git a/iotDashboard/templates/sensor_form.html b/iotDashboard/templates/sensor_form.html new file mode 100644 index 0000000..572d018 --- /dev/null +++ b/iotDashboard/templates/sensor_form.html @@ -0,0 +1,41 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Add Sensor and Type</title> + <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> +</head> +<body class="bg-light"> + <nav class="navbar navbar-expand-lg navbar-light bg-light"> + <div class="container-fluid"> + <a class="navbar-brand" href="#">IoT Dashboard</a> + <div class="collapse navbar-collapse" id="navbarNav"> + <ul class="navbar-nav me-auto mb-2 mb-lg-0"> + <li class="nav-item"><a class="nav-link" href="{% url 'device_list' %}">Devices</a></li> + </ul> + </div> + </div> + </nav> + + <div class="container mt-5"> + <h1 class="text-center mb-4">Add Sensor and Sensor Type</h1> + <form method="post"> + {% csrf_token %} + {{ form.as_p }} <!-- Renders the inputs for all fields you added in your form --> + <button type="submit" class="btn btn-success">Save</button> + <a href="{% url 'device_list' %}" class="btn btn-secondary">Cancel</a> + </form> + + {% if form.errors %} + <div class="alert alert-danger"> + <ul> + {% for field, errors in form.errors.items %} + <li>{{ field }}: {{ errors|join:', ' }}</li> + {% endfor %} + </ul> + </div> + {% endif %} + </div> +</body> +</html> \ No newline at end of file