From 4409742ed8d8edaea8c4e83f97ebb7ce53d796af Mon Sep 17 00:00:00 2001 From: Michel Date: Sat, 19 Apr 2025 11:11:19 +0200 Subject: [PATCH 01/31] =?UTF-8?q?=F0=9F=8E=89=20Initial=20commit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | Bin 2663 -> 38 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/README.md b/README.md index 481e80fec79b0d46660adc581601bf1a7f758692..e62cbb7ed3ba51629d0bf2d0cafd5b024f01c756 100644 GIT binary patch literal 38 lcmezWPnki1!Hpr4A(H4~~HrknPwBAfZlWB!{N3R^&=rgXA*%<63|Y?h~|t zivnp52@3Q_w;p=vp*OyXU!Zw}e!CR)lg0*$@?cBU?99$LGv7CB^zO~;-~ayeTRP_2 z2$@mLHEEf-f3ZxZ5ic!1-dtVNA-+D`3W8RiCn91qYpT_>&AGY|x;k!nnio^Wj8$CM zYQ!#hM0-JYjpGDD~H^X%pw;d!)*~yQ?5i7iJT=| z!GpQ52J=(JGMx%NhckxjVhY&Y9cs1C z)6-LLxC#)@@Y@_4xZPk;#Nnl%aDNieG+ad=ZZ!tV0bEt%lqVb@r4-Vml!*lY*R})n$!s8oc2@i4rUtr{tRo^<0~YZ` zIp4kc;V)Ou7P`6k)ds;wUp~4-D<}M`jgc|A-V4Lnrs-IuK&BDNG~8`AFL#>FF!E2> z^v`|6FU_YjFPrl~oAcs4SdMx9jq7MTYY~g)k>}h5!Gw-b0+;2=GodevKTy&C`%Hof z+V}&{Ew4XddFLzmr{1jH@^No*-HDsZ68gm`FOAH?^3G4*e@9|UrD`k(m{2!TA~&vc zP4QDS@`E=3qfw5|@eNZ<4LaI$#?95MvKjTi!<9L3u*JBcN4 znuYyspL~D=oDdm5(=1BZxXfij2Qo=;M|XJ*N?IaR(#S%C%p9R~{h{0Z(@KDpTTsb= z4AAk1?hc^%OAC&0gsL0n()3_Hp+oR2&tjH=h3I5#Mi3VatjM%s$nCwpz9Yh%Z!w@h zy8(Stx+rNqkr4_Q1ifCb&-5r53JiZDK{D0MrstmW_I7r5!xS^clW;~9XQj|Ja3+Z2 ziausM32ofEw;^pwVR!FTaF+k%bJccEM0D zqBUwm;4Z_IMW1&AZ~vlfSN&qi40g{&7R#@pA8S>*?jAtW^;-pNkc+$ml;PQmrSHF^mNg^PR6q+n)6FI3Z9xyfCw@+>5q@FtAvVx#h#{bLOC5Cmi*L;N0&pKvkE;x^fjB8x{ju6p zT#8P7dzkNv3^sDBM+g8h_v7vqb)P{X9Vb+Z-5GbEwrNyM`& Date: Sat, 19 Apr 2025 11:38:20 +0200 Subject: [PATCH 02/31] =?UTF-8?q?Fichier=20readme.md=20retrouv=C3=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | Bin 38 -> 10890 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/README.md b/README.md index e62cbb7ed3ba51629d0bf2d0cafd5b024f01c756..39eaeb0564b46e9ca5f2c8655956a6e946412f93 100644 GIT binary patch literal 10890 zcmds--)t8v6?kKIL9mh$h9m?D78H9y5Ji)*Jx&bvj2YV-g5`JG zm*5?8u}JJ?_91rn`_-A_>gukZ@i@r^$eO9?uBub#?^mZz`G5ai302q#NBTPm`(Y;x z!-?8S80-BfVHA#Q&%1iA^oCx3ABWe%A2j+>UUwrX(6eEQq;4`UD7 zm}mq#*%oI<;?m!_JUpLVnu4#0CXDOD3ocEMiTgdRwJT|wT;B;_hFjrIVa-X=7E`T+ zuFy$aQs>BZ#0WlEa8=)EZy-*F;gNn1X4cu0MjnNJ?LG6+)F`Znp7>+6Nq9f3iT}6s z*$k($%Rkh=uTck)ryV`@!kg;l`KiXAiU#waXcWHH3pZ-qq&52LS%3ELe;M5)jfD2T z_;s({(mOozxq7fNtK+Lq5gAt^Z*N8ZKGy1EX&bp1TaSQkJ)sFa%D&A*pGS(w#~&1T z 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 literal 38 lcmezWPnki1!Hpr4A( Date: Sat, 19 Apr 2025 11:42:01 +0200 Subject: [PATCH 03/31] =?UTF-8?q?=F0=9F=93=9D=20Mise=20=C3=A0=20jour=20com?= =?UTF-8?q?pl=C3=A8te=20du=20README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | Bin 10890 -> 5302 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/README.md b/README.md index 39eaeb0564b46e9ca5f2c8655956a6e946412f93..e6fc8ac078c60fa04ac34fff05c087176dc8123a 100644 GIT binary patch literal 5302 zcmbW5-EJFI5XVn%kr0)*>fND{NLq*kg^LoAN}-`2Qq!iylnaV#H*r2_VjJ5DjnoI? zArKN058;cp6bTUKk_RaKe=~b>b{#tfWNos0cFvsn{Ljq({ci>07 zBP;js=vo(bSI=03vyiN2R>YF?_Dgo12{YQUG@N}FB{kmI5HHuQEsjV#&}n^9b;W1b zD0X$vJM5D<%^RQJ#SS$Rb~ zutK(TvXFbfDV}}3&)z3LkORvR+noP_Sqw|GOD8N1zk42)E9Lk-89gbt;8Pb@GBZkt z7(Nv@Jk>e&5v$8uVGof&EM$$AhJVR3w?$9PZ0RazCq7=(g)U9K+q10TMC99+ZLl*^ zywG2=uPv%xv1?s^Td$n;lOf4SR4IeYcoK~tUhquhbkD^YzAiLM5slY9ay`CU#@Q{A zcuvb!&#_H$AWH}0ga6J&VrV1$Vb5d-dJv=d+VhS)J5xjeSoYttykR)$`;HV_eZ^p- z#Q6v5K>T(j#Uq^$`XB+38^c4b5J6sjTR6LsEcsg1pthlI{+A88@=UCQM2gdUlOe;r!(#a#JD?6lm5q0O95L=#MTiU%S zgimL`6}~(n_sP8fN>S<2vmnjNT0STuw|s6*7HDhTGh@2dek^>{lUJ$;c_1ml*N$Qp z98A(a(fT!MwP9KkxA7G}cC3g;UwgnTFgL~Y6XW}&r~zIH{>|Uhb|jur(YPx4$zh_9 zn$uORHKxdmoXeu7jvN)SnIeE#oK&f)w%f}7NeD1&oUjnP$;z^1Ra`zzF7{2X4wDhV zvu?ajx;}SmL-u7vY_e`?MU@_!ulG_d4FG zl&w!pjictY96}oE^J(bpmC8F9$I7bqM1ogK?ot)+%fbWU$e@xB`8;ahrab38tJkh0 zQSVqrr4yzh%a!4usw>y0WCpdV+7V!rDDGbI64rnk-6B@;!nv5yH1|kzq9Bf=@S6&T ztaJ=*TLsnV5WMn7!<(hyP2nE#($-!$)5UrXr<=K=iypjQ$V11H<&Cl-c3~c%uWuPG z9z^anJ@?GY9woDuWnd_hI9?`st6a|?pLbroa#qnjf}3IU%tt9tJj $1#nofprODPGr#bKpevaKW;WOvpn+>gv&wf>E zsKA08=ku~y>IDyVS1bC$L*vLv^fTVA=XVRzlvt$?_o{!WJN%zBeMa}UjOsvnxL{GW z5$*_Wks|ZDU%}sgxPO9Y&FqRTqfJj=7*{IHx3Y!nMBVjnB>Oa~?oz*Bg%EIX|8d7P zX=Xd~ws&A{S%|JVWne-a@0ole#%7t9_Xk*vyo#DsF6nRTsuHH%5iZ{-POo;2mycK1 z<&84>QuXQQ%P2j)FUWJmZHt<={KB+=OxTEt0G!2(-WxqutiZn^$*0X*jnd3;)i_jV zlW6m0-MZX7L*EkRAk6ACpMvO}j&w%u#3uP*X?Rw+yR4W?o&V$>p=|qQ*^GEc&#Gjp zOLNp6$tFEzI@-^PvZ<(Z1n@qK$pge*(;J@{%bJgSCQsc=Wc!j~&rYA$UY}9WoKAcE ztA*z^@sYFKV={)%jmOC^8c^@*6rT&=%&FvNvJ?I#Gx+cNMw5Yl)vaqviMV3 z92|w4cPq)#YdR&(FJ968ixCyozp=d;QzrYHks<(%@P>CBZX=I@n%3&#NWSo%X_cuF lk5qF7oZzj~(O*+b^Oq0*cH`g9h+>ywBszK#&nvIe{{U*xo?rj~ literal 10890 zcmds--)t8v6?kKIL9mh$h9m?D78H9y5Ji)*Jx&bvj2YV-g5`JG zm*5?8u}JJ?_91rn`_-A_>gukZ@i@r^$eO9?uBub#?^mZz`G5ai302q#NBTPm`(Y;x z!-?8S80-BfVHA#Q&%1iA^oCx3ABWe%A2j+>UUwrX(6eEQq;4`UD7 zm}mq#*%oI<;?m!_JUpLVnu4#0CXDOD3ocEMiTgdRwJT|wT;B;_hFjrIVa-X=7E`T+ zuFy$aQs>BZ#0WlEa8=)EZy-*F;gNn1X4cu0MjnNJ?LG6+)F`Znp7>+6Nq9f3iT}6s z*$k($%Rkh=uTck)ryV`@!kg;l`KiXAiU#waXcWHH3pZ-qq&52LS%3ELe;M5)jfD2T z_;s({(mOozxq7fNtK+Lq5gAt^Z*N8ZKGy1EX&bp1TaSQkJ)sFa%D&A*pGS(w#~&1T z z_fS0Vh~h|EY>&or$NGDs6{q6QS6q=rz6fuJH?#_JUz4`J4IhT@^!ssvj`_(@@|iB2 z&Sun`;Xp5PdLF;wxvAqjaV$~oRx*7)l9k36mhwLH@?5*47!Pv}Z*-jA|RG3m!cg9n5YD;d4gP|Sef@Ji{i+XM zoluAoO|H(hs(HBE7haz>c=bLoF8FdoV9U|$=$sjn}J zc%NHz7`a~$TWU{b38P-udufGLjr>s`C}Tgf#%8!3KGWx8Q6tWMzX309NwaQgk92$? zMS3NKyeAL;T%#Xmwt1*GUx&M*d`}-}ZD@ttTGgrE&|74_CJj`gaZf#ruEIy*8-15~ zF(gAH#+!TIo6?G_KSO8AWg5iq$kGFf`4Ijl;)m#;Xf?+L zr|T_Luw0{k*2&3TX^k5l$Gm-c=(&txb6K%4#WlYSkkZF1GNA5l^oRgM<3 zTz9md$d|UGO$N~)i8bQStpj>)#D0H2$YRcR2A9~|I+9!k14iNr#K1S%8HVu*$v5@Z zumBDd5}d{NWl7F8vI`f=E7fsg`RFIEn|jPO(uDz~n8|1Eh&CAp(O-`GI-WPjmg%#b zkx5iS!unolJ7Hg1wqJ`Q%64%wmSMEqR9wRAfo!;Fw)>8cPdO?`FqVDE4L9VU`=V|c z5gFG<`rsLB%CyQFamwDN706Q!gKy4gSler8f`}tOJeD7pa$W03g9#hRNa5>1yswKp ze#19;C~tb&{~Rlwv#vIA%rD|%zD`^tZ@)B+fCIi8R6^FSoag0BjQ;xJotY#h4|c5? zR$v3m76)p{fXM<~gYcL7$Fc}J-mCY`murd5nS^eMwnXHgbz3YO8K!mT`Z8M2cRO}g zJLg;0pIg8(so`%*?_h#wFV7{NV*``B|z<&}@_Cn)-bALZ4@`c3^yv15vFZPcnby%|GKFv^-8YH;Ieo@gmQXevf#u zq20co2U6PrB_Anf?F?mgrmJ8@()_FOX!m659691F=dX#+d44+7Y4dt5{;%simps;b zZ%K2dQ$cBL{ghz+B^{S`HLuLBOYU+uT;54--l`CJSAVIl+LfjI__TA^a}~q%`715j zZ}a~7v=+r;-fy%@+M~*f&kyA3mRZ?37j&m;k?L&uDUzpVb>&=pn`xRaa}V&nIHk%(J*=-RkrQW54`if*W$`-DnD3Rjry3YIxWcN>uK!XA zOQ)(ca!x=8~_Ji)Z3g7s=Oz{agz*sXvN;Gs-`;;b@JnU zmPrNoZHshU&wa8^t4Z0{?xsS8-#DI#Q{?QT0e|?#w!S%Wa|(sy`3M7YQi-DSHI}9) znn%PLHc-)G#gccrhP~z7yDbh6;%RLC+0VLl*pdYg^s)NfR9J1cGmGXvTwVBCUdea* z&DOKtb;)?S8Exw^w;5xXqs46knFl9Ni^S%Q;GgEkn@iKSJP`uAAIUP6>Z$3;EB^B?ynZ__?FlrFi|BQ6c~meCwOjn7cc1kdprtdedV zeMN8GI$krVub*q$XLy@e<`&;{+zOv6$Jx^N-&%Jc^;%~ARs%VbKF)RHG5_8IZ{EB_ zWhwdYv2?|Lajf{A%rt&qS_5D2hnw-nz_USn>ltA#f2pQfu8(6`EHYoai#ROP&Ut$I z)l&BskNjMFizG4xup4mhTc@*3WU9QvcQmS;h8>Pq?r+W3<07X=tzbKz{8A0J7e09Q zA7v<8(mJ!O5((mhJk%KYPXdcMuyFCpF8?4>Fq-7dT|8Ok1W(?UXZ_N9b?N|?xp#R(8BJVMZmp8u^L&B-I(S_gx)TwD-K@@0J^!uOeUP?|$i8~B9mkk5 zO!I8l@6gC-Y#&H(Y&<83t~-P*M&#Sz% zZm#Ul^;kD9oTuhp_IxjKdfvX+omdmG%cl@utJ&6ND&L&-WYx=Lw@u3II^4Y96ADyu z58BcVxK)I@?f^N1_chDok&M0T98TYD#RFcvoW7mYue5i47XB_On{nN1LKCRg+vO;u zE5na^ww5pp>K0}E>RsR?F}>VMb-0on~uN zU*#-fa^wAe>alagfht@1{?R^>I2SJ}mD?=O2%c#DsWeQbfdB2|boHq`6zkYo#O}@A cmZh^VC!YEq%zvd&Ct#OMC#AMzAQhkg19jvFpa1{> From 05050ff846b685d87f3a26b74a7315ef2be07c94 Mon Sep 17 00:00:00 2001 From: Michel Date: Sat, 19 Apr 2025 18:32:55 +0200 Subject: [PATCH 04/31] Refonte du pdf --- domo91.py | 100 +++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 84 insertions(+), 16 deletions(-) diff --git a/domo91.py b/domo91.py index 2a73efa..efb8231 100644 --- a/domo91.py +++ b/domo91.py @@ -26,10 +26,8 @@ db_config = { "database": "Sondes" } - # --- Fonction de génération PDF --- def generer_pdf(site, date_str): - st.info(f"Génération du rapport PDF pour {site} à la date {date_str}") try: conn = mysql.connector.connect(**db_config) cursor = conn.cursor(dictionary=True) @@ -37,7 +35,23 @@ def generer_pdf(site, date_str): cursor.execute(f"SELECT Sonde, Date, Temperature FROM `{site}` WHERE DATE(Date) = %s ORDER BY Sonde, Date", (date_str,)) rows = cursor.fetchall() df = pd.DataFrame(rows) - df["Heure"] = pd.to_datetime(df["Date"]).dt.strftime("%H:%M") + df["Heure"] = pd.to_datetime(df["Date"]) + + # --- Appliquer les filtres de l'utilisateur --- + sonde_choisie = st.session_state.get("sonde_selectionnee") + tranche = st.session_state.get("tranche_horaire") + if sonde_choisie: + df = df[df["Sonde"] == sonde_choisie] + if tranche: + heure = df["Heure"].dt.hour + if tranche == "Matin (6h-12h)": + df = df[(heure >= 6) & (heure < 12)] + elif tranche == "Après-midi (12h-18h)": + df = df[(heure >= 12) & (heure < 18)] + elif tranche == "Nuit (18h-6h)": + df = df[(heure >= 18) | (heure < 6)] + + df["Heure"] = df["Heure"].dt.strftime("%H:%M") releves = {} for sonde in df["Sonde"].unique(): @@ -67,12 +81,19 @@ def generer_pdf(site, date_str): def releves_section(self, data): self.set_font("Arial", "B", 12) self.cell(0, 10, "Relevés de température", ln=1) + col_width = self.w / 3.3 + row_height = 6 for sonde, mesures in data.items(): + if not mesures: + continue self.set_font("Arial", "B", 11) - self.cell(0, 8, f"Sonde : {sonde}", ln=1) + self.cell(0, 8, f"Sonde : {sonde}", ln=1, fill=True) self.set_font("Arial", "", 10) - for heure, temp in mesures: - self.cell(0, 6, f"{heure} - {temp} °C", ln=1) + for i in range(0, len(mesures), 3): + ligne = mesures[i:i+3] + for heure, temp in ligne: + self.cell(col_width, row_height, f"{heure} : {temp:.1f}°C", border=1) + self.ln() self.ln(2) def alertes_section(self, data): @@ -90,21 +111,67 @@ def generer_pdf(site, date_str): file_name = f"rapport_{site}_{date_str}.pdf" output_dir = "PDF" - os.makedirs(output_dir, exist_ok=True) # 🔧 Crée le dossier si absent + os.makedirs(output_dir, exist_ok=True) output_path = os.path.join(output_dir, file_name) - pdf.output(output_path) - with open(output_path, "rb") as f: - st.download_button( - label="📥 Télécharger le rapport PDF", - data=f, - file_name=file_name, - mime="application/pdf" - ) + return output_path except Exception as e: st.error(f"Erreur lors de la génération du PDF : {e}") + return None + +# Ajout du bouton dans la sidebar pour tous les utilisateurs connectés +if st.session_state.get("authenticated") and st.session_state.get("selected_date"): + site = ( + st.session_state.get("lieu_autorise") + if st.session_state.get("role") != "superviseur" + else st.session_state.get("selected_site") + ) + date_val = st.session_state["selected_date"].strftime("%Y-%m-%d") + st.session_state["site_selectionne"] = site + st.sidebar.markdown("---") + st.sidebar.subheader("📄 Impressions") + + if st.sidebar.button("📥 Générer le PDF maintenant"): + pdf_path = generer_pdf(site, date_val) + if pdf_path: + st.session_state["pdf_path"] = pdf_path + + if "pdf_path" in st.session_state: + with open(st.session_state["pdf_path"], "rb") as f: + st.sidebar.download_button( + label="📄 Télécharger le PDF", + data=f, + file_name=os.path.basename(st.session_state["pdf_path"]), + mime="application/pdf", + key="pdf_download_button" + ) + +# Ajout du bouton dans la sidebar pour tous les utilisateurs connectés +if st.session_state.get("authenticated") and st.session_state.get("selected_date"): + site = ( + st.session_state.get("lieu_autorise") + if st.session_state.get("role") != "superviseur" + else st.session_state.get("selected_site") + ) + date_val = st.session_state["selected_date"].strftime("%Y-%m-%d") + st.session_state["site_selectionne"] = site + st.sidebar.markdown("---") + st.sidebar.subheader("📄 Impressions") + generer_pdf(site, date_val) +# Ajout du bouton dans la sidebar pour tous les utilisateurs connectés +if st.session_state.get("authenticated") and st.session_state.get("selected_date"): + site = ( + st.session_state.get("lieu_autorise") + if st.session_state.get("role") != "superviseur" + else st.session_state.get("selected_site") + ) + date_val = st.session_state["selected_date"].strftime("%Y-%m-%d") + st.sidebar.markdown("---") + st.sidebar.subheader("📄 Impressions") + generer_pdf(site, date_val) + # --- Initialisation des variables de session --- if "authenticated" not in st.session_state: st.session_state["authenticated"] = False @@ -230,6 +297,7 @@ if st.session_state["authenticated"]: df["Date"] = pd.to_datetime(df["Date"]) sondes = sorted(df["Sonde"].unique()) sonde_choisie = st.selectbox("🧪 Choisissez une sonde :", sondes) + st.session_state["sonde_selectionnee"] = sonde_choisie df_sonde = df[df["Sonde"] == sonde_choisie] # Ajouter une colonne Heure pour faciliter les filtres @@ -238,7 +306,7 @@ if st.session_state["authenticated"]: # Filtrage par tranche horaire tranche = st.radio("🕒 Tranche horaire :", ["Toute la journée", "Matin (6h-12h)", "Après-midi (12h-18h)", "Nuit (18h-6h)"]) - + st.session_state["tranche_horaire"] = tranche if tranche == "Matin (6h-12h)": df_sonde = df_sonde[(df_sonde["Heure"] >= 6) & (df_sonde["Heure"] < 12)] elif tranche == "Après-midi (12h-18h)": From c91d5b561262c30f8a3ac3330502cd44a038f7e4 Mon Sep 17 00:00:00 2001 From: Michel Date: Sun, 20 Apr 2025 08:55:53 +0200 Subject: [PATCH 05/31] Fichiers suivis par logs --- Cuisine_meudon.py | 81 +++++++++++++++++++++++++++++++++-------------- Cuisine_saclay.py | 81 +++++++++++++++++++++++++++++++++-------------- 2 files changed, 116 insertions(+), 46 deletions(-) diff --git a/Cuisine_meudon.py b/Cuisine_meudon.py index e8c1b05..90213ac 100644 --- a/Cuisine_meudon.py +++ b/Cuisine_meudon.py @@ -1,30 +1,65 @@ import paho.mqtt.client as mqttClient -client = mqttClient.Client() import mysql.connector import sys -sys.path.insert(0, "/myenv/lib/python3.11.2/site-packages") +import os +import logging +from datetime import datetime -# Configuration de la connexion MySQL -mydb = mysql.connector.connect( - host="54.36.188.119", - user="michel", - password="#SO2&1nf%mZ@jfh", - database="Sondes" +# 📁 Création du dossier de logs s'il n'existe pas +dossier_logs = "/var/log/Cuisine_Meudon" +os.makedirs(dossier_logs, exist_ok=True) + +# 📝 Configuration du logger +logfile = os.path.join(dossier_logs, "Cuisine_Meudon.log") +logging.basicConfig( + filename=logfile, + level=logging.INFO, + format="%(asctime)s - %(levelname)s - %(message)s" ) -# Fonction de callback quand un message est reçu -def on_message(_client, _userdata, msg): - print(f"Message reçu sur {msg.topic}: {msg.payload.decode()}") - cursor = mydb.cursor() - frigo_name = msg.topic.split('/')[-1] # Prend la dernière partie après le "/" - sql = "INSERT INTO Meudon (Sonde, Temperature) VALUES (%s, %s)" - val = (frigo_name, msg.payload.decode()) - cursor.execute(sql, val) - mydb.commit() +# Ajoute aussi un affichage console si utile : +console = logging.StreamHandler() +console.setLevel(logging.INFO) +formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") +console.setFormatter(formatter) +logging.getLogger('').addHandler(console) -# Configuration du client MQTT -client.username_pw_set("Bwps", "scJ5ACj2keRfI^") -client.on_message = on_message -client.connect("54.36.188.119", 1883, 60) -client.subscribe("Meudon/#") # S'abonner à tous les topics commençant par Saclay -client.loop_forever() # Rester connecté en continu pour écouter les messages +# 🔌 Connexion MySQL +try: + sys.path.insert(0, "/myenv/lib/python3.11.2/site-packages") + mydb = mysql.connector.connect( + host="54.36.188.119", + user="michel", + password="#SO2&1nf%mZ@jfh", + database="Sondes" + ) + logging.info("Connexion MySQL réussie.") +except mysql.connector.Error as err: + logging.error(f"Erreur de connexion MySQL : {err}") + sys.exit(1) + +# 📥 Callback MQTT +def on_message(_client, _userdata, msg): + try: + logging.info(f"Message reçu sur {msg.topic}: {msg.payload.decode()}") + cursor = mydb.cursor() + frigo_name = msg.topic.split('/')[-1] + sql = "INSERT INTO Meudon (Sonde, Temperature) VALUES (%s, %s)" + val = (frigo_name, msg.payload.decode()) + cursor.execute(sql, val) + mydb.commit() + logging.info(f"Insertion réussie : {val}") + except Exception as e: + logging.error(f"Erreur lors de l'insertion du message : {e}") + +# 📡 Connexion MQTT +try: + client = mqttClient.Client() + client.username_pw_set("Bwps", "scJ5ACj2keRfI^") + client.on_message = on_message + client.connect("54.36.188.119", 1883, 60) + client.subscribe("Meudon/#") + logging.info("Connexion MQTT réussie et abonnement au topic 'Meudon/#'.") + client.loop_forever() +except Exception as e: + logging.error(f"Erreur MQTT : {e}") diff --git a/Cuisine_saclay.py b/Cuisine_saclay.py index 4e68428..c194121 100644 --- a/Cuisine_saclay.py +++ b/Cuisine_saclay.py @@ -1,30 +1,65 @@ import paho.mqtt.client as mqttClient -client = mqttClient.Client() import mysql.connector import sys -sys.path.insert(0, "/myenv/lib/python3.11.2/site-packages") +import os +import logging +from datetime import datetime -# Configuration de la connexion MySQL -mydb = mysql.connector.connect( - host="54.36.188.119", - user="michel", - password="#SO2&1nf%mZ@jfh", - database="Sondes" +# 📁 Création du dossier de logs s'il n'existe pas +dossier_logs = "/var/log/Cuisine_Saclay" +os.makedirs(dossier_logs, exist_ok=True) + +# 📝 Configuration du logger +logfile = os.path.join(dossier_logs, "Cuisine_Saclay.log") +logging.basicConfig( + filename=logfile, + level=logging.INFO, + format="%(asctime)s - %(levelname)s - %(message)s" ) -# Fonction de callback quand un message est reçu -def on_message(_client, _userdata, msg): - print(f"Message reçu sur {msg.topic}: {msg.payload.decode()}") - cursor = mydb.cursor() - frigo_name = msg.topic.split('/')[-1] # Prend la dernière partie après le "/" - sql = "INSERT INTO Saclay (Sonde, Temperature) VALUES (%s, %s)" - val = (frigo_name, msg.payload.decode()) - cursor.execute(sql, val) - mydb.commit() +# Ajoute aussi l'affichage console +console = logging.StreamHandler() +console.setLevel(logging.INFO) +formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") +console.setFormatter(formatter) +logging.getLogger('').addHandler(console) -# Configuration du client MQTT -client.username_pw_set("Bwps", "scJ5ACj2keRfI^") -client.on_message = on_message -client.connect("54.36.188.119", 1883, 60) -client.subscribe("Saclay/#") # S'abonner à tous les topics commençant par Saclay -client.loop_forever() # Rester connecté en continu pour écouter les messages +# 🔌 Connexion MySQL +try: + sys.path.insert(0, "/myenv/lib/python3.11.2/site-packages") + mydb = mysql.connector.connect( + host="54.36.188.119", + user="michel", + password="#SO2&1nf%mZ@jfh", + database="Sondes" + ) + logging.info("Connexion MySQL réussie.") +except mysql.connector.Error as err: + logging.error(f"Erreur de connexion MySQL : {err}") + sys.exit(1) + +# 📥 Callback MQTT +def on_message(_client, _userdata, msg): + try: + logging.info(f"Message reçu sur {msg.topic}: {msg.payload.decode()}") + cursor = mydb.cursor() + frigo_name = msg.topic.split('/')[-1] + sql = "INSERT INTO Saclay (Sonde, Temperature) VALUES (%s, %s)" + val = (frigo_name, msg.payload.decode()) + cursor.execute(sql, val) + mydb.commit() + logging.info(f"Insertion réussie : {val}") + except Exception as e: + logging.error(f"Erreur lors de l'insertion du message : {e}") + +# 📡 Connexion MQTT +try: + client = mqttClient.Client() + client.username_pw_set("Bwps", "scJ5ACj2keRfI^") + client.on_message = on_message + client.connect("54.36.188.119", 1883, 60) + client.subscribe("Saclay/#") + logging.info("Connexion MQTT réussie et abonnement au topic 'Saclay/#'.") + client.loop_forever() +except Exception as e: + logging.error(f"Erreur MQTT : {e}") From 492e958f0c5124817aa26293279f2bad291dfe68 Mon Sep 17 00:00:00 2001 From: Michel Date: Sun, 20 Apr 2025 10:14:25 +0200 Subject: [PATCH 06/31] =?UTF-8?q?Fichiers=20suivis"=F0=9F=94=A7=20requirem?= =?UTF-8?q?ents.txt=20nettoy=C3=A9=20et=20optimis=C3=A9"=20par=20logs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements.txt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..4ed3ba5 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,18 @@ +colorama==0.4.6 +cryptography==44.0.2 +filelock==3.18.0 +matplotlib==3.10.1 +mysql_connector_repackaged==0.3.1 +paho_mqtt==2.1.0 +pandas==2.2.3 +paramiko==3.5.1 +Pillow==11.2.1 +protobuf==6.30.2 +pyOpenSSL==25.0.0 +python-dotenv==1.1.0 +redis==5.2.1 +schedule==1.2.2 +sphinx==8.2.3 +streamlit==1.44.1 +urllib3_secure_extra==0.1.0 + From ea50d05d1c8eb849e9412bec9c2f5b2e14d213bf Mon Sep 17 00:00:00 2001 From: Michel Date: Sun, 20 Apr 2025 10:18:57 +0200 Subject: [PATCH 07/31] =?UTF-8?q?"=F0=9F=94=A7=20Programme=20de=20nettoyag?= =?UTF-8?q?e=20des=20fichiers=20non=20UTF-8"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- detect_encodage.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 detect_encodage.py diff --git a/detect_encodage.py b/detect_encodage.py new file mode 100644 index 0000000..65a0fa1 --- /dev/null +++ b/detect_encodage.py @@ -0,0 +1,16 @@ +import os + +racine = "." # point de départ = dossier courant + +def detect_fichiers_non_utf8(dossier): + for root, dirs, files in os.walk(dossier): + for file in files: + if file.endswith(".py"): + chemin = os.path.join(root, file) + try: + with open(chemin, encoding="utf-8") as f: + f.read() + except UnicodeDecodeError: + print(f"⚠️ Encodage non UTF-8 : {chemin}") + +detect_fichiers_non_utf8(racine) From afcec3662d3cee739891a46b8f0b025a2de281c5 Mon Sep 17 00:00:00 2001 From: Michel Date: Sun, 20 Apr 2025 10:49:17 +0200 Subject: [PATCH 08/31] =?UTF-8?q?Ajout=20script=20de=20mise=20=C3=A0=20jou?= =?UTF-8?q?r=20product=20depuis=20develop?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Docs/Commandes pratiques terminal | 0 tools/update_product_from_develop.sh | 33 ++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 Docs/Commandes pratiques terminal create mode 100644 tools/update_product_from_develop.sh diff --git a/Docs/Commandes pratiques terminal b/Docs/Commandes pratiques terminal new file mode 100644 index 0000000..e69de29 diff --git a/tools/update_product_from_develop.sh b/tools/update_product_from_develop.sh new file mode 100644 index 0000000..c4d19b2 --- /dev/null +++ b/tools/update_product_from_develop.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +# --------------------------------------------- +# Script : update_product_from_develop.sh +# Objectif : Met à jour les fichiers de 'product' depuis 'develop' +# uniquement pour les fichiers déjà existants dans product +# --------------------------------------------- + +echo "📁 Branche active : $(git branch --show-current)" + +# Vérification qu'on est bien sur 'product' +current_branch=$(git branch --show-current) +if [ "$current_branch" != "product" ]; then + echo "❌ Tu n'es pas sur la branche 'product'. Abandon." + exit 1 +fi + +# Lister les fichiers présents dans 'product' +echo "📄 Création de la liste des fichiers dans 'product'..." +git ls-tree --name-only -r product > product_files.txt + +echo "🔄 Mise à jour des fichiers depuis 'develop'..." + +while IFS= read -r file; do + if git show develop:"$file" > /dev/null 2>&1; then + git checkout develop -- "$file" + echo "✅ Mis à jour : $file" + else + echo "❌ Absent dans develop : $file" + fi +done < product_files.txt + +echo "✅ Mise à jour terminée." From 606dd4d752cd9aa266efc752280c05e5a44cae69 Mon Sep 17 00:00:00 2001 From: Michel Date: Sun, 20 Apr 2025 11:17:41 +0200 Subject: [PATCH 09/31] =?UTF-8?q?"=F0=9F=94=A7=20Programme=20de=20copie=20?= =?UTF-8?q?des=20fichiers=20develop=20sur=20product"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Docs/update_product_from_develop.txt | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 Docs/update_product_from_develop.txt diff --git a/Docs/update_product_from_develop.txt b/Docs/update_product_from_develop.txt new file mode 100644 index 0000000..f07a03c --- /dev/null +++ b/Docs/update_product_from_develop.txt @@ -0,0 +1,20 @@ +📝 Exécution du script update_product_from_develop.sh sous Windows +🎯 Objectif : + Mettre à jour les fichiers déjà présents dans la branche product depuis develop, + sans importer les nouveaux fichiers. + +✅ Étapes à retenir (avec Git Bash) +Ouvrir Git Bash +(depuis Démarrer → Git Bash) + +Naviguer dans ton projet : + cd "/c/Users/miche/PycharmProjects/Gestion sondes/tools" + Rendre le script exécutable une seule fois : + chmod +x update_product_from_develop.sh +Exécuter le script à tout moment : + ./update_product_from_develop.sh + +🔁 Résultat : + 1) Seuls les fichiers déjà présents dans product sont mis à jour + 2) Les fichiers nouveaux de develop sont ignorés + 3) Affichage clair de ce qui est mis à jour ou non \ No newline at end of file From 088620bdf7ff13d6cca692fb060b0e8cc4f9cf17 Mon Sep 17 00:00:00 2001 From: Michel Date: Sun, 20 Apr 2025 11:38:51 +0200 Subject: [PATCH 10/31] =?UTF-8?q?Ajout=20fiche=20d'utilisation=20du=20scri?= =?UTF-8?q?pt=20de=20mise=20=C3=A0=20jour?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tools/MAJ_develop_sur_product.txt | 23 ----------------------- tools/update_product_from_develop.txt | 0 2 files changed, 23 deletions(-) delete mode 100644 tools/MAJ_develop_sur_product.txt create mode 100644 tools/update_product_from_develop.txt diff --git a/tools/MAJ_develop_sur_product.txt b/tools/MAJ_develop_sur_product.txt deleted file mode 100644 index 49ac08a..0000000 --- a/tools/MAJ_develop_sur_product.txt +++ /dev/null @@ -1,23 +0,0 @@ -✅ Objectif : mettre à jour domo91.py de product avec la version de develop -🧭 Étapes simples à suivre : -1. Assure-toi d’être sur la branche product -git checkout product -2. Récupère seulement le fichier domo91.py depuis develop - -git checkout develop -- domo91.py -💡 Cette commande signifie : - -"Depuis la branche develop, prends juste le fichier domo91.py et applique-le sur la branche actuelle (product)". - -3. Vérifie le changement - -git status -Tu devrais voir : -modified: domo91.py -4. Commit et push la mise à jour -git commit -am "MAJ de domo91.py depuis develop" -git push origin product -✅ Résultat : -Seul le fichier domo91.py est mis à jour sur product - -Tu ne touches ni aux autres fichiers, ni à l’historique Git \ No newline at end of file diff --git a/tools/update_product_from_develop.txt b/tools/update_product_from_develop.txt new file mode 100644 index 0000000..e69de29 From 5922c4a47dd8138a552ff5fa3b2eb2af92af9167 Mon Sep 17 00:00:00 2001 From: Michel Date: Mon, 21 Apr 2025 12:45:28 +0000 Subject: [PATCH 11/31] Actualiser domo91.py --- domo91.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/domo91.py b/domo91.py index efb8231..436372a 100644 --- a/domo91.py +++ b/domo91.py @@ -375,7 +375,7 @@ if st.session_state["authenticated"]: except Exception as e: st.error(f"Erreur MySQL : {e}") if st.session_state["role"] == "superviseur": - with st.expander("➕ Ajouter une nouvelle chambre froide", expanded=False): + with st.expander("+ Ajouter une nouvelle chambre froide", expanded=False): with st.form("ajout_sonde"): nouvelle_sonde = st.text_input("Nom de la sonde") temp_max = st.number_input("Température maximale autorisée (°C)", value=4) @@ -432,13 +432,13 @@ if st.session_state["role"] == "superviseur": temp_max = chambre["Temp_Max"] moins, val, plus = st.columns([1, 2, 1]) with moins: - if st.button("➖", key=f"moins_{chambre['Id']}"): + if st.button("-", key=f"moins_{chambre['Id']}"): temp_max -= 1 with val: st.markdown(f"
{temp_max}°C
", unsafe_allow_html=True) with plus: - if st.button("➕", key=f"plus_{chambre['Id']}"): + if st.button("+", key=f"plus_{chambre['Id']}"): temp_max += 1 if new_etat != chambre["Etat"] or temp_max != chambre["Temp_Max"]: From e924b65b82347eb176a38b971309a5ea4684c205 Mon Sep 17 00:00:00 2001 From: Michel Date: Mon, 21 Apr 2025 17:38:14 +0000 Subject: [PATCH 12/31] Actualiser domo91.py --- domo91.py | 108 ++++++++++-------------------------------------------- 1 file changed, 19 insertions(+), 89 deletions(-) diff --git a/domo91.py b/domo91.py index 436372a..9c1c21c 100644 --- a/domo91.py +++ b/domo91.py @@ -26,8 +26,10 @@ db_config = { "database": "Sondes" } + # --- Fonction de génération PDF --- def generer_pdf(site, date_str): + st.info(f"Génération du rapport PDF pour {site} à la date {date_str}") try: conn = mysql.connector.connect(**db_config) cursor = conn.cursor(dictionary=True) @@ -35,23 +37,7 @@ def generer_pdf(site, date_str): cursor.execute(f"SELECT Sonde, Date, Temperature FROM `{site}` WHERE DATE(Date) = %s ORDER BY Sonde, Date", (date_str,)) rows = cursor.fetchall() df = pd.DataFrame(rows) - df["Heure"] = pd.to_datetime(df["Date"]) - - # --- Appliquer les filtres de l'utilisateur --- - sonde_choisie = st.session_state.get("sonde_selectionnee") - tranche = st.session_state.get("tranche_horaire") - if sonde_choisie: - df = df[df["Sonde"] == sonde_choisie] - if tranche: - heure = df["Heure"].dt.hour - if tranche == "Matin (6h-12h)": - df = df[(heure >= 6) & (heure < 12)] - elif tranche == "Après-midi (12h-18h)": - df = df[(heure >= 12) & (heure < 18)] - elif tranche == "Nuit (18h-6h)": - df = df[(heure >= 18) | (heure < 6)] - - df["Heure"] = df["Heure"].dt.strftime("%H:%M") + df["Heure"] = pd.to_datetime(df["Date"]).dt.strftime("%H:%M") releves = {} for sonde in df["Sonde"].unique(): @@ -81,19 +67,12 @@ def generer_pdf(site, date_str): def releves_section(self, data): self.set_font("Arial", "B", 12) self.cell(0, 10, "Relevés de température", ln=1) - col_width = self.w / 3.3 - row_height = 6 for sonde, mesures in data.items(): - if not mesures: - continue self.set_font("Arial", "B", 11) - self.cell(0, 8, f"Sonde : {sonde}", ln=1, fill=True) + self.cell(0, 8, f"Sonde : {sonde}", ln=1) self.set_font("Arial", "", 10) - for i in range(0, len(mesures), 3): - ligne = mesures[i:i+3] - for heure, temp in ligne: - self.cell(col_width, row_height, f"{heure} : {temp:.1f}°C", border=1) - self.ln() + for heure, temp in mesures: + self.cell(0, 6, f"{heure} - {temp} °C", ln=1) self.ln(2) def alertes_section(self, data): @@ -111,67 +90,21 @@ def generer_pdf(site, date_str): file_name = f"rapport_{site}_{date_str}.pdf" output_dir = "PDF" - os.makedirs(output_dir, exist_ok=True) + os.makedirs(output_dir, exist_ok=True) # 🔧 Crée le dossier si absent output_path = os.path.join(output_dir, file_name) + pdf.output(output_path) - return output_path + with open(output_path, "rb") as f: + st.download_button( + label="📥 Télécharger le rapport PDF", + data=f, + file_name=file_name, + mime="application/pdf" + ) except Exception as e: st.error(f"Erreur lors de la génération du PDF : {e}") - return None - -# Ajout du bouton dans la sidebar pour tous les utilisateurs connectés -if st.session_state.get("authenticated") and st.session_state.get("selected_date"): - site = ( - st.session_state.get("lieu_autorise") - if st.session_state.get("role") != "superviseur" - else st.session_state.get("selected_site") - ) - date_val = st.session_state["selected_date"].strftime("%Y-%m-%d") - st.session_state["site_selectionne"] = site - st.sidebar.markdown("---") - st.sidebar.subheader("📄 Impressions") - - if st.sidebar.button("📥 Générer le PDF maintenant"): - pdf_path = generer_pdf(site, date_val) - if pdf_path: - st.session_state["pdf_path"] = pdf_path - - if "pdf_path" in st.session_state: - with open(st.session_state["pdf_path"], "rb") as f: - st.sidebar.download_button( - label="📄 Télécharger le PDF", - data=f, - file_name=os.path.basename(st.session_state["pdf_path"]), - mime="application/pdf", - key="pdf_download_button" - ) - -# Ajout du bouton dans la sidebar pour tous les utilisateurs connectés -if st.session_state.get("authenticated") and st.session_state.get("selected_date"): - site = ( - st.session_state.get("lieu_autorise") - if st.session_state.get("role") != "superviseur" - else st.session_state.get("selected_site") - ) - date_val = st.session_state["selected_date"].strftime("%Y-%m-%d") - st.session_state["site_selectionne"] = site - st.sidebar.markdown("---") - st.sidebar.subheader("📄 Impressions") - generer_pdf(site, date_val) -# Ajout du bouton dans la sidebar pour tous les utilisateurs connectés -if st.session_state.get("authenticated") and st.session_state.get("selected_date"): - site = ( - st.session_state.get("lieu_autorise") - if st.session_state.get("role") != "superviseur" - else st.session_state.get("selected_site") - ) - date_val = st.session_state["selected_date"].strftime("%Y-%m-%d") - st.sidebar.markdown("---") - st.sidebar.subheader("📄 Impressions") - generer_pdf(site, date_val) - # --- Initialisation des variables de session --- if "authenticated" not in st.session_state: st.session_state["authenticated"] = False @@ -212,8 +145,8 @@ else: st.session_state["lieu_autorise"] = None st.rerun() -# 🔔 Forcer une alerte de test dynamique avec choix de la sonde -if st.session_state.get("authenticated"): +# 🔔 Forcer une alerte de test dynamique avec choix de la sonde (réservé aux superviseurs) +if st.session_state.get("authenticated") and st.session_state.get("role") == "superviseur": site_actuel = ( st.session_state.get("lieu_autorise") if st.session_state.get("role") != "superviseur" @@ -297,7 +230,6 @@ if st.session_state["authenticated"]: df["Date"] = pd.to_datetime(df["Date"]) sondes = sorted(df["Sonde"].unique()) sonde_choisie = st.selectbox("🧪 Choisissez une sonde :", sondes) - st.session_state["sonde_selectionnee"] = sonde_choisie df_sonde = df[df["Sonde"] == sonde_choisie] # Ajouter une colonne Heure pour faciliter les filtres @@ -306,7 +238,7 @@ if st.session_state["authenticated"]: # Filtrage par tranche horaire tranche = st.radio("🕒 Tranche horaire :", ["Toute la journée", "Matin (6h-12h)", "Après-midi (12h-18h)", "Nuit (18h-6h)"]) - st.session_state["tranche_horaire"] = tranche + if tranche == "Matin (6h-12h)": df_sonde = df_sonde[(df_sonde["Heure"] >= 6) & (df_sonde["Heure"] < 12)] elif tranche == "Après-midi (12h-18h)": @@ -453,6 +385,4 @@ if st.session_state["role"] == "superviseur": conn_admin.close() except Exception as e: - st.error(f"Erreur SQL (admin) : {e}") - - + st.error(f"Erreur SQL (admin) : {e}") \ No newline at end of file From 09af31f6763e6d7be85e0d7483a103d82900c9ff Mon Sep 17 00:00:00 2001 From: Michel Date: Mon, 21 Apr 2025 20:13:51 +0200 Subject: [PATCH 13/31] =?UTF-8?q?"=F0=9F=94=A7=20Ajout=20des=20fonctions?= =?UTF-8?q?=20gestion=20des=20boutons"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- domo91.py | 132 ++++++++++++++++-------------------------------------- 1 file changed, 39 insertions(+), 93 deletions(-) diff --git a/domo91.py b/domo91.py index efb8231..e26270b 100644 --- a/domo91.py +++ b/domo91.py @@ -26,8 +26,10 @@ db_config = { "database": "Sondes" } + # --- Fonction de génération PDF --- def generer_pdf(site, date_str): + st.info(f"Génération du rapport PDF pour {site} à la date {date_str}") try: conn = mysql.connector.connect(**db_config) cursor = conn.cursor(dictionary=True) @@ -35,23 +37,7 @@ def generer_pdf(site, date_str): cursor.execute(f"SELECT Sonde, Date, Temperature FROM `{site}` WHERE DATE(Date) = %s ORDER BY Sonde, Date", (date_str,)) rows = cursor.fetchall() df = pd.DataFrame(rows) - df["Heure"] = pd.to_datetime(df["Date"]) - - # --- Appliquer les filtres de l'utilisateur --- - sonde_choisie = st.session_state.get("sonde_selectionnee") - tranche = st.session_state.get("tranche_horaire") - if sonde_choisie: - df = df[df["Sonde"] == sonde_choisie] - if tranche: - heure = df["Heure"].dt.hour - if tranche == "Matin (6h-12h)": - df = df[(heure >= 6) & (heure < 12)] - elif tranche == "Après-midi (12h-18h)": - df = df[(heure >= 12) & (heure < 18)] - elif tranche == "Nuit (18h-6h)": - df = df[(heure >= 18) | (heure < 6)] - - df["Heure"] = df["Heure"].dt.strftime("%H:%M") + df["Heure"] = pd.to_datetime(df["Date"]).dt.strftime("%H:%M") releves = {} for sonde in df["Sonde"].unique(): @@ -81,19 +67,12 @@ def generer_pdf(site, date_str): def releves_section(self, data): self.set_font("Arial", "B", 12) self.cell(0, 10, "Relevés de température", ln=1) - col_width = self.w / 3.3 - row_height = 6 for sonde, mesures in data.items(): - if not mesures: - continue self.set_font("Arial", "B", 11) - self.cell(0, 8, f"Sonde : {sonde}", ln=1, fill=True) + self.cell(0, 8, f"Sonde : {sonde}", ln=1) self.set_font("Arial", "", 10) - for i in range(0, len(mesures), 3): - ligne = mesures[i:i+3] - for heure, temp in ligne: - self.cell(col_width, row_height, f"{heure} : {temp:.1f}°C", border=1) - self.ln() + for heure, temp in mesures: + self.cell(0, 6, f"{heure} - {temp} °C", ln=1) self.ln(2) def alertes_section(self, data): @@ -111,66 +90,21 @@ def generer_pdf(site, date_str): file_name = f"rapport_{site}_{date_str}.pdf" output_dir = "PDF" - os.makedirs(output_dir, exist_ok=True) + os.makedirs(output_dir, exist_ok=True) # 🔧 Crée le dossier si absent output_path = os.path.join(output_dir, file_name) + pdf.output(output_path) - return output_path + with open(output_path, "rb") as f: + st.download_button( + label="📥 Télécharger le rapport PDF", + data=f, + file_name=file_name, + mime="application/pdf" + ) except Exception as e: st.error(f"Erreur lors de la génération du PDF : {e}") - return None - -# Ajout du bouton dans la sidebar pour tous les utilisateurs connectés -if st.session_state.get("authenticated") and st.session_state.get("selected_date"): - site = ( - st.session_state.get("lieu_autorise") - if st.session_state.get("role") != "superviseur" - else st.session_state.get("selected_site") - ) - date_val = st.session_state["selected_date"].strftime("%Y-%m-%d") - st.session_state["site_selectionne"] = site - st.sidebar.markdown("---") - st.sidebar.subheader("📄 Impressions") - - if st.sidebar.button("📥 Générer le PDF maintenant"): - pdf_path = generer_pdf(site, date_val) - if pdf_path: - st.session_state["pdf_path"] = pdf_path - - if "pdf_path" in st.session_state: - with open(st.session_state["pdf_path"], "rb") as f: - st.sidebar.download_button( - label="📄 Télécharger le PDF", - data=f, - file_name=os.path.basename(st.session_state["pdf_path"]), - mime="application/pdf", - key="pdf_download_button" - ) - -# Ajout du bouton dans la sidebar pour tous les utilisateurs connectés -if st.session_state.get("authenticated") and st.session_state.get("selected_date"): - site = ( - st.session_state.get("lieu_autorise") - if st.session_state.get("role") != "superviseur" - else st.session_state.get("selected_site") - ) - date_val = st.session_state["selected_date"].strftime("%Y-%m-%d") - st.session_state["site_selectionne"] = site - st.sidebar.markdown("---") - st.sidebar.subheader("📄 Impressions") - generer_pdf(site, date_val) -# Ajout du bouton dans la sidebar pour tous les utilisateurs connectés -if st.session_state.get("authenticated") and st.session_state.get("selected_date"): - site = ( - st.session_state.get("lieu_autorise") - if st.session_state.get("role") != "superviseur" - else st.session_state.get("selected_site") - ) - date_val = st.session_state["selected_date"].strftime("%Y-%m-%d") - st.sidebar.markdown("---") - st.sidebar.subheader("📄 Impressions") - generer_pdf(site, date_val) # --- Initialisation des variables de session --- if "authenticated" not in st.session_state: @@ -180,9 +114,7 @@ if "role" not in st.session_state: if "lieu_autorise" not in st.session_state: st.session_state["lieu_autorise"] = None -# --- Sidebar (connexion + bouton PDF) --- - -# 🔐 Connexion (toujours affichée) +# --- Connexion utilisateur dans la sidebar --- st.sidebar.header("🔐 Connexion") if not st.session_state.get("authenticated"): login = st.sidebar.text_input("Nom d'utilisateur") @@ -198,6 +130,7 @@ if not st.session_state.get("authenticated"): st.session_state["role"] = result["role"] st.session_state["lieu_autorise"] = result["Lieu"] st.success(f"Connecté comme {result['role']} ({result['Lieu']})") + st.rerun() else: st.sidebar.error("Identifiants invalides") cursor.close() @@ -206,14 +139,28 @@ if not st.session_state.get("authenticated"): st.sidebar.error(f"Erreur lors de la connexion à la base : {e}") else: st.sidebar.success(f"Connecté ({st.session_state['role']})") - if st.sidebar.button("🔓 Déconnexion"): + if st.sidebar.button("🔓 Déconnexion", key="logout_sidebar"): st.session_state["authenticated"] = False st.session_state["role"] = None st.session_state["lieu_autorise"] = None st.rerun() -# 🔔 Forcer une alerte de test dynamique avec choix de la sonde -if st.session_state.get("authenticated"): +# 📄 Affichage bouton PDF si une date est choisie +site_pdf = ( + st.session_state.get("lieu_autorise") + if st.session_state.get("role") != "superviseur" + else st.session_state.get("selected_site") +) +date_pdf = st.session_state.get("selected_date") +if site_pdf and date_pdf: + st.sidebar.markdown("---") + st.sidebar.subheader("📄 Rapport PDF") + if st.sidebar.button("📥 Télécharger l’état du jour (PDF)", key="pdf_btn"): + generer_pdf(site_pdf, date_pdf.strftime("%Y-%m-%d")) + + +# 🔔 Forcer une alerte de test dynamique avec choix de la sonde (réservé aux superviseurs) +if st.session_state.get("authenticated") and st.session_state.get("role") == "superviseur": site_actuel = ( st.session_state.get("lieu_autorise") if st.session_state.get("role") != "superviseur" @@ -253,7 +200,7 @@ if st.session_state.get("authenticated"): else: st.success(f"Connecté ({st.session_state['role']})") - if st.button("🔓 Déconnexion"): + if st.button("🔓 Déconnexion", key="logout_main"): st.session_state["authenticated"] = False st.session_state["role"] = None st.session_state["lieu_autorise"] = None @@ -297,7 +244,6 @@ if st.session_state["authenticated"]: df["Date"] = pd.to_datetime(df["Date"]) sondes = sorted(df["Sonde"].unique()) sonde_choisie = st.selectbox("🧪 Choisissez une sonde :", sondes) - st.session_state["sonde_selectionnee"] = sonde_choisie df_sonde = df[df["Sonde"] == sonde_choisie] # Ajouter une colonne Heure pour faciliter les filtres @@ -306,7 +252,7 @@ if st.session_state["authenticated"]: # Filtrage par tranche horaire tranche = st.radio("🕒 Tranche horaire :", ["Toute la journée", "Matin (6h-12h)", "Après-midi (12h-18h)", "Nuit (18h-6h)"]) - st.session_state["tranche_horaire"] = tranche + if tranche == "Matin (6h-12h)": df_sonde = df_sonde[(df_sonde["Heure"] >= 6) & (df_sonde["Heure"] < 12)] elif tranche == "Après-midi (12h-18h)": @@ -375,7 +321,7 @@ if st.session_state["authenticated"]: except Exception as e: st.error(f"Erreur MySQL : {e}") if st.session_state["role"] == "superviseur": - with st.expander("➕ Ajouter une nouvelle chambre froide", expanded=False): + with st.expander("+ Ajouter une nouvelle chambre froide", expanded=False): with st.form("ajout_sonde"): nouvelle_sonde = st.text_input("Nom de la sonde") temp_max = st.number_input("Température maximale autorisée (°C)", value=4) @@ -432,13 +378,13 @@ if st.session_state["role"] == "superviseur": temp_max = chambre["Temp_Max"] moins, val, plus = st.columns([1, 2, 1]) with moins: - if st.button("➖", key=f"moins_{chambre['Id']}"): + if st.button("-", key=f"moins_{chambre['Id']}"): temp_max -= 1 with val: st.markdown(f"
{temp_max}°C
", unsafe_allow_html=True) with plus: - if st.button("➕", key=f"plus_{chambre['Id']}"): + if st.button("+", key=f"plus_{chambre['Id']}"): temp_max += 1 if new_etat != chambre["Etat"] or temp_max != chambre["Temp_Max"]: From d98a9e5d02763faae92b01dcbac62314c7494a80 Mon Sep 17 00:00:00 2001 From: Michel Date: Mon, 21 Apr 2025 20:18:06 +0200 Subject: [PATCH 14/31] =?UTF-8?q?=F0=9F=86=95=20R=C3=A9vision=20de=20domo9?= =?UTF-8?q?1.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements.txt | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 4ed3ba5..0000000 --- a/requirements.txt +++ /dev/null @@ -1,18 +0,0 @@ -colorama==0.4.6 -cryptography==44.0.2 -filelock==3.18.0 -matplotlib==3.10.1 -mysql_connector_repackaged==0.3.1 -paho_mqtt==2.1.0 -pandas==2.2.3 -paramiko==3.5.1 -Pillow==11.2.1 -protobuf==6.30.2 -pyOpenSSL==25.0.0 -python-dotenv==1.1.0 -redis==5.2.1 -schedule==1.2.2 -sphinx==8.2.3 -streamlit==1.44.1 -urllib3_secure_extra==0.1.0 - From 1bf27c54a9972b6af8885b090440e94ee52f0cd2 Mon Sep 17 00:00:00 2001 From: Michel Date: Tue, 22 Apr 2025 07:45:20 +0200 Subject: [PATCH 15/31] =?UTF-8?q?"=F0=9F=94=A7=20Ajout=20des=20fonctions?= =?UTF-8?q?=20gestion=20connexions=20et=20insertion=20dans=20bde?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- domo91.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/domo91.py b/domo91.py index e26270b..5054ac9 100644 --- a/domo91.py +++ b/domo91.py @@ -130,6 +130,16 @@ if not st.session_state.get("authenticated"): st.session_state["role"] = result["role"] st.session_state["lieu_autorise"] = result["Lieu"] st.success(f"Connecté comme {result['role']} ({result['Lieu']})") + # ➕ Enregistrement de la connexion dans Connexion_Log + try: + now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + cursor.execute(""" + INSERT INTO Connexion_Log (Utilisateur, Lieu, Date_Connexion) + VALUES (%s, %s, %s) + """, (login, result["Lieu"], now)) + conn.commit() + except Exception as e: + st.warning(f"⚠️ Connexion enregistrée échouée : {e}") st.rerun() else: st.sidebar.error("Identifiants invalides") From 6fefd432aaec033b2aa95cd8a70d6dbd748cface Mon Sep 17 00:00:00 2001 From: Michel Date: Tue, 22 Apr 2025 08:18:56 +0200 Subject: [PATCH 16/31] 2 modif --- domo91.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/domo91.py b/domo91.py index 5054ac9..6a2ddc8 100644 --- a/domo91.py +++ b/domo91.py @@ -130,7 +130,7 @@ if not st.session_state.get("authenticated"): st.session_state["role"] = result["role"] st.session_state["lieu_autorise"] = result["Lieu"] st.success(f"Connecté comme {result['role']} ({result['Lieu']})") - # ➕ Enregistrement de la connexion dans Connexion_Log + # Enregistrement de la connexion dans Connexion_Log try: now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") cursor.execute(""" @@ -305,7 +305,7 @@ if st.session_state["authenticated"]: ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M')) ax.legend() st.pyplot(fig) -# --- Affichage automatique des alertes non acquittées --- + # --- Affichage automatique des alertes non acquittées --- try: conn = mysql.connector.connect(**db_config) cursor = conn.cursor(dictionary=True) @@ -411,4 +411,3 @@ if st.session_state["role"] == "superviseur": except Exception as e: st.error(f"Erreur SQL (admin) : {e}") - From 0234ac98d3a0d3db2b3c39d64072c63582cbb722 Mon Sep 17 00:00:00 2001 From: Michel Date: Tue, 22 Apr 2025 08:26:52 +0200 Subject: [PATCH 17/31] 3 modification --- domo91.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/domo91.py b/domo91.py index 6a2ddc8..8962c8a 100644 --- a/domo91.py +++ b/domo91.py @@ -388,13 +388,13 @@ if st.session_state["role"] == "superviseur": temp_max = chambre["Temp_Max"] moins, val, plus = st.columns([1, 2, 1]) with moins: - if st.button("-", key=f"moins_{chambre['Id']}"): + if st.button("▼", key=f"moins_{chambre['Id']}"): temp_max -= 1 with val: st.markdown(f"
{temp_max}°C
", unsafe_allow_html=True) with plus: - if st.button("+", key=f"plus_{chambre['Id']}"): + if st.button("▲", key=f"plus_{chambre['Id']}"): temp_max += 1 if new_etat != chambre["Etat"] or temp_max != chambre["Temp_Max"]: From d5fcc473865c8fb93f14930bab7fd0a431f22b00 Mon Sep 17 00:00:00 2001 From: Michel Date: Tue, 22 Apr 2025 08:53:32 +0200 Subject: [PATCH 18/31] 4 modification --- domo91.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/domo91.py b/domo91.py index 8962c8a..642676c 100644 --- a/domo91.py +++ b/domo91.py @@ -228,7 +228,11 @@ if st.session_state.get("authenticated") and st.session_state.get("role") == "su # --- CONTENU PRINCIPAL SI AUTHENTIFIÉ --- if st.session_state["authenticated"]: - st.markdown("## Sélection du site et de la date") + if st.session_state["role"] == "superviseur": + onglet = st.sidebar.radio("📁 Navigation", ["Accueil", "Statistiques"]) + else: + onglet = "Accueil" + st.markdown("## Sélection du site et de la date") try: conn = mysql.connector.connect(**db_config) cursor = conn.cursor(dictionary=True) From 700d8c5a5eca696913019c94d1192041ad129622 Mon Sep 17 00:00:00 2001 From: Michel Date: Tue, 22 Apr 2025 09:40:54 +0200 Subject: [PATCH 19/31] 5 --- domo91.py | 198 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 124 insertions(+), 74 deletions(-) diff --git a/domo91.py b/domo91.py index 642676c..f452fca 100644 --- a/domo91.py +++ b/domo91.py @@ -34,7 +34,8 @@ def generer_pdf(site, date_str): conn = mysql.connector.connect(**db_config) cursor = conn.cursor(dictionary=True) - cursor.execute(f"SELECT Sonde, Date, Temperature FROM `{site}` WHERE DATE(Date) = %s ORDER BY Sonde, Date", (date_str,)) + cursor.execute(f"SELECT Sonde, Date, Temperature FROM `{site}` WHERE DATE(Date) = %s ORDER BY Sonde, Date", + (date_str,)) rows = cursor.fetchall() df = pd.DataFrame(rows) df["Heure"] = pd.to_datetime(df["Date"]).dt.strftime("%H:%M") @@ -45,7 +46,8 @@ def generer_pdf(site, date_str): releves[sonde] = list(zip(df_sonde["Heure"], df_sonde["Temperature"])) table_alertes = f"Alertes_{site}" - cursor.execute(f"SELECT Sonde, Debut_defaut, Status FROM {table_alertes} WHERE DATE(Debut_defaut) = %s", (date_str,)) + cursor.execute(f"SELECT Sonde, Debut_defaut, Status FROM {table_alertes} WHERE DATE(Debut_defaut) = %s", + (date_str,)) alertes = cursor.fetchall() cursor.close() @@ -106,6 +108,7 @@ def generer_pdf(site, date_str): except Exception as e: st.error(f"Erreur lors de la génération du PDF : {e}") + # --- Initialisation des variables de session --- if "authenticated" not in st.session_state: st.session_state["authenticated"] = False @@ -168,7 +171,6 @@ if site_pdf and date_pdf: if st.sidebar.button("📥 Télécharger l’état du jour (PDF)", key="pdf_btn"): generer_pdf(site_pdf, date_pdf.strftime("%Y-%m-%d")) - # 🔔 Forcer une alerte de test dynamique avec choix de la sonde (réservé aux superviseurs) if st.session_state.get("authenticated") and st.session_state.get("role") == "superviseur": site_actuel = ( @@ -230,86 +232,135 @@ if st.session_state.get("authenticated") and st.session_state.get("role") == "su if st.session_state["authenticated"]: if st.session_state["role"] == "superviseur": onglet = st.sidebar.radio("📁 Navigation", ["Accueil", "Statistiques"]) + site_selectionne = st.session_state.get("selected_site", "Saclay") else: onglet = "Accueil" + + if onglet == "Accueil": st.markdown("## Sélection du site et de la date") - try: - conn = mysql.connector.connect(**db_config) - cursor = conn.cursor(dictionary=True) - sites_possibles = ["Saclay", "Meudon"] - if st.session_state["role"] == "superviseur": - site_selectionne = st.selectbox("📍 Choisissez un site :", sites_possibles) - st.session_state["selected_site"] = site_selectionne - else: - site_selectionne = st.session_state["lieu_autorise"] - st.info(f"Site imposé : {site_selectionne}") + try: + conn = mysql.connector.connect(**db_config) + cursor = conn.cursor(dictionary=True) + sites_possibles = ["Saclay", "Meudon"] + if st.session_state["role"] == "superviseur": + site_selectionne = st.selectbox("📍 Choisissez un site :", sites_possibles) + st.session_state["selected_site"] = site_selectionne + else: + site_selectionne = st.session_state["lieu_autorise"] + st.info(f"Site imposé : {site_selectionne}") + + selected_date = st.date_input("📅 Date du relevé", value=date.today()) + st.session_state["selected_date"] = selected_date + + cursor.execute( + f"SELECT * FROM `{site_selectionne}` WHERE DATE(Date) = %s ORDER BY Sonde, Date", + (selected_date.strftime("%Y-%m-%d"),) + ) + rows = cursor.fetchall() + if rows: + df = pd.DataFrame(rows) + df["Date"] = pd.to_datetime(df["Date"]) + sondes = sorted(df["Sonde"].unique()) + sonde_choisie = st.selectbox("🧪 Choisissez une sonde :", sondes, key="selectbox_accueil") + df_sonde = df[df["Sonde"] == sonde_choisie] + + df_sonde.loc[:, "Heure"] = df_sonde["Date"].dt.hour + + tranche = st.radio("🕒 Tranche horaire :", + ["Toute la journée", "Matin (6h-12h)", "Après-midi (12h-18h)", "Nuit (18h-6h)"]) + + if tranche == "Matin (6h-12h)": + df_sonde = df_sonde[(df_sonde["Heure"] >= 6) & (df_sonde["Heure"] < 12)] + elif tranche == "Après-midi (12h-18h)": + df_sonde = df_sonde[(df_sonde["Heure"] >= 12) & (df_sonde["Heure"] < 18)] + elif tranche == "Nuit (18h-6h)": + df_sonde = df_sonde[(df_sonde["Heure"] >= 18) | (df_sonde["Heure"] < 6)] + df_sonde = df_sonde.copy() + + cursor.execute("SELECT Temp_Max FROM Chambres_froides WHERE Lieu = %s AND Sonde = %s", + (site_selectionne, sonde_choisie)) + seuil = cursor.fetchone() + seuil_temp = seuil["Temp_Max"] if seuil else 10 + + st.subheader("📊 Tableau des relevés") + df_filtré = df_sonde.copy() + df_filtré = df_filtré.drop(columns="Id", errors="ignore") - selected_date = st.date_input("📅 Date du relevé", value=date.today()) - st.session_state["selected_date"] = selected_date + def surlignage_temp(val): + try: + if float(val) > seuil_temp: + return "color: red; font-weight: bold" + except: + pass + return "" - cursor.execute( - f"SELECT * FROM `{site_selectionne}` WHERE DATE(Date) = %s ORDER BY Sonde, Date", - (selected_date.strftime("%Y-%m-%d"),) - ) - rows = cursor.fetchall() - if rows: + + styled_df = df_filtré.style.applymap(surlignage_temp, subset=["Temperature"]) + st.dataframe(styled_df, use_container_width=True) + + st.subheader("📈 Évolution de la température") + fig, ax = plt.subplots(figsize=(10, 4)) + ax.plot(df_filtré["Date"], df_filtré["Temperature"], marker='o', label="Température") + ax.axhline(seuil_temp, color='red', linestyle='--', label=f"Seuil {seuil_temp}°C") + ax.set_xlabel("Heure") + ax.set_ylabel("Température (°C)") + ax.set_title(f"{sonde_choisie} - {selected_date.strftime('%d/%m/%Y')}") + ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M')) + ax.legend() + st.pyplot(fig) + + cursor.close() + conn.close() + + except Exception as e: + st.error(f"Erreur MySQL : {e}") + + elif onglet == "Statistiques": + st.markdown("## 📈 Statistiques de température") + try: + conn = mysql.connector.connect(**db_config) + cursor = conn.cursor(dictionary=True) + + site = ( + st.session_state["lieu_autorise"] + if st.session_state["role"] != "superviseur" + else st.session_state.get("selected_site", "Saclay") + ) + + date_val = st.session_state.get("selected_date", date.today()) + + cursor.execute( + f"SELECT * FROM `{site}` WHERE DATE(Date) = %s ORDER BY Sonde, Date", + (date_val.strftime("%Y-%m-%d"),) + ) + rows = cursor.fetchall() df = pd.DataFrame(rows) - df["Date"] = pd.to_datetime(df["Date"]) - sondes = sorted(df["Sonde"].unique()) - sonde_choisie = st.selectbox("🧪 Choisissez une sonde :", sondes) - df_sonde = df[df["Sonde"] == sonde_choisie] -# Ajouter une colonne Heure pour faciliter les filtres - df_sonde.loc[:, "Heure"] = df_sonde["Date"].dt.hour + if df.empty: + st.info("Aucune donnée pour cette date.") + else: + df["Date"] = pd.to_datetime(df["Date"]) + sondes = sorted(df["Sonde"].unique()) + sonde = st.selectbox("Choisir une sonde :", sondes, key="selectbox_stats") + df_sonde = df[df["Sonde"] == sonde] -# Filtrage par tranche horaire - tranche = st.radio("🕒 Tranche horaire :", - ["Toute la journée", "Matin (6h-12h)", "Après-midi (12h-18h)", "Nuit (18h-6h)"]) + st.subheader("Évolution journalière") + fig, ax = plt.subplots(figsize=(10, 4)) + ax.plot(df_sonde["Date"], df_sonde["Temperature"], marker='o') + ax.set_title(f"{sonde} - {date_val.strftime('%d/%m/%Y')}") + ax.set_xlabel("Heure") + ax.set_ylabel("Température (°C)") + ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M')) + st.pyplot(fig) - if tranche == "Matin (6h-12h)": - df_sonde = df_sonde[(df_sonde["Heure"] >= 6) & (df_sonde["Heure"] < 12)] - elif tranche == "Après-midi (12h-18h)": - df_sonde = df_sonde[(df_sonde["Heure"] >= 12) & (df_sonde["Heure"] < 18)] - elif tranche == "Nuit (18h-6h)": - df_sonde = df_sonde[(df_sonde["Heure"] >= 18) | (df_sonde["Heure"] < 6)] - df_sonde = df_sonde.copy() + cursor.close() + conn.close() - cursor.execute("SELECT Temp_Max FROM Chambres_froides WHERE Lieu = %s AND Sonde = %s", (site_selectionne, sonde_choisie)) - seuil = cursor.fetchone() - seuil_temp = seuil["Temp_Max"] if seuil else 10 + except Exception as e: + st.error(f"Erreur chargement statistiques : {e}") - st.subheader("📊 Tableau des relevés") - df_filtré = df_sonde.copy() - df_filtré = df_filtré.drop(columns="Id", errors="ignore") - - - def surlignage_temp(val): - try: - if float(val) > seuil_temp: - return "color: red; font-weight: bold" - except: - pass - return "" - - -# Appliquer le style uniquement à la colonne "Temperature" - - styled_df = df_filtré.style.applymap(surlignage_temp, subset=["Temperature"]) - - st.dataframe(styled_df, use_container_width=True) - - st.subheader("📈 Évolution de la température") - fig, ax = plt.subplots(figsize=(10, 4)) - ax.plot(df_filtré["Date"], df_filtré["Temperature"], marker='o', label="Température") - ax.axhline(seuil_temp, color='red', linestyle='--', label=f"Seuil {seuil_temp}°C") - ax.set_xlabel("Heure") - ax.set_ylabel("Température (°C)") - ax.set_title(f"{sonde_choisie} - {selected_date.strftime('%d/%m/%Y')}") - ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M')) - ax.legend() - st.pyplot(fig) - # --- Affichage automatique des alertes non acquittées --- + # --- Affichage automatique des alertes non acquittées --- try: conn = mysql.connector.connect(**db_config) cursor = conn.cursor(dictionary=True) @@ -332,8 +383,8 @@ if st.session_state["authenticated"]: except Exception as e: st.error(f"Erreur lors de la récupération des alertes : {e}") - except Exception as e: - st.error(f"Erreur MySQL : {e}") + except Exception as e: + st.error(f"Erreur MySQL : {e}") if st.session_state["role"] == "superviseur": with st.expander("+ Ajouter une nouvelle chambre froide", expanded=False): with st.form("ajout_sonde"): @@ -414,4 +465,3 @@ if st.session_state["role"] == "superviseur": except Exception as e: st.error(f"Erreur SQL (admin) : {e}") - From bb013bd12d8fa119f6fb32639e22d342a15b642c Mon Sep 17 00:00:00 2001 From: Michel Date: Tue, 22 Apr 2025 09:52:51 +0200 Subject: [PATCH 20/31] =?UTF-8?q?R=C3=A9vision=206?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- domo91.py | 42 ++++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/domo91.py b/domo91.py index f452fca..484cfe7 100644 --- a/domo91.py +++ b/domo91.py @@ -230,9 +230,38 @@ if st.session_state.get("authenticated") and st.session_state.get("role") == "su # --- CONTENU PRINCIPAL SI AUTHENTIFIÉ --- if st.session_state["authenticated"]: + # --- AFFICHAGE GLOBAL DES ALERTES NON ACQUITTÉES --- + try: + conn = mysql.connector.connect(**db_config) + cursor = conn.cursor(dictionary=True) + + site_selectionne = ( + st.session_state["lieu_autorise"] + if st.session_state["role"] != "superviseur" + else st.session_state.get("selected_site", "Saclay") + ) + + table_alertes = f"Alertes_{site_selectionne}" + cursor.execute( + f"SELECT Sonde, Debut_defaut, Status FROM `{table_alertes}` WHERE Status != 'Acquitté' ORDER BY Debut_defaut DESC" + ) + alertes = cursor.fetchall() + + if alertes: + df_alertes = pd.DataFrame(alertes) + st.subheader("🚨 Alertes non acquittées") + st.dataframe(df_alertes, use_container_width=True) + else: + st.success("✅ Aucune alerte en cours.") + + cursor.close() + conn.close() + except Exception as e: + st.error(f"Erreur lors de la récupération des alertes : {e}") + + # --- NAVIGATION --- if st.session_state["role"] == "superviseur": onglet = st.sidebar.radio("📁 Navigation", ["Accueil", "Statistiques"]) - site_selectionne = st.session_state.get("selected_site", "Saclay") else: onglet = "Accueil" @@ -366,17 +395,6 @@ if st.session_state["authenticated"]: cursor = conn.cursor(dictionary=True) table_alertes = f"Alertes_{site_selectionne}" - cursor.execute( - f"SELECT Sonde, Debut_defaut, Status FROM `{table_alertes}` WHERE Status != 'Acquitté' ORDER BY Debut_defaut DESC" - ) - alertes = cursor.fetchall() - - if alertes: - df_alertes = pd.DataFrame(alertes) - st.subheader("🚨 Alertes non acquittées") - st.dataframe(df_alertes, use_container_width=True) - else: - st.success("✅ Aucune alerte en cours.") cursor.close() conn.close() From a8a38a0f338b3e5983846c5ba27c268b5512ada1 Mon Sep 17 00:00:00 2001 From: Michel Date: Tue, 22 Apr 2025 11:14:21 +0200 Subject: [PATCH 21/31] =?UTF-8?q?R=C3=A9visioS=C3=A9curisation=20.env=20+?= =?UTF-8?q?=20am=C3=A9lioration=20supervisionn=206?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/.env b/.env index 702821a..d6eb113 100644 --- a/.env +++ b/.env @@ -1,3 +1,16 @@ DB_HOST=54.36.188.119 DB_USER=michel -DB_PASSWORD=#SO2&1nf%mZ@jfh \ No newline at end of file +DB_PASSWORD=#SO2&1nf%mZ@jfh +DB_NAME=Sondes + +MQTT_HOST=54.36.188.119 +MQTT_USER=Bwps +MQTT_PASSWORD=scJ5ACj2keRfI^ + +# === EMAIL SMTP ===# === EMAIL SMTP === +SMTP_HOST=smtp.mail.ovh.net +SMTP_PORT=465 +SMTP_USER=alertes_saclay@domo91.fr +SMTP_PASSWORD=Kdpke674y23Feq^H +EMAIL_FROM=alertes_saclay@domo91.fr +EMAIL_DEST=services@domo91.fr From 742baadf99419d47a90377644ee0b3f09e3547a0 Mon Sep 17 00:00:00 2001 From: Michel Date: Tue, 22 Apr 2025 11:31:52 +0200 Subject: [PATCH 22/31] =?UTF-8?q?Mise=20=C3=A0=20jour=20de=20env=20et=20Mo?= =?UTF-8?q?nitor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 4 +++- Monitor.py | 13 ++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/.env b/.env index d6eb113..070422e 100644 --- a/.env +++ b/.env @@ -13,4 +13,6 @@ SMTP_PORT=465 SMTP_USER=alertes_saclay@domo91.fr SMTP_PASSWORD=Kdpke674y23Feq^H EMAIL_FROM=alertes_saclay@domo91.fr -EMAIL_DEST=services@domo91.fr +EMAIL_SACLAY=Cuisine +EMAIL_MEUDON=meudon@domo91.fr +EMAIL_DEFAULT=admin@domo91.fr diff --git a/Monitor.py b/Monitor.py index 60d51e4..dac9a76 100644 --- a/Monitor.py +++ b/Monitor.py @@ -5,6 +5,7 @@ import time import smtplib from email.mime.text import MIMEText import pandas as pd +import os # --- Config MySQL --- config = { @@ -15,17 +16,19 @@ config = { } # --- Destinataires email --- -destinataires = ['services@domo91.fr'] +def get_destinataires(lieu): + return os.getenv(f"EMAIL_{lieu.upper()}", os.getenv("EMAIL_DEFAULT")).split(",") + # --- Fonction d'envoi de mail --- def envoyer_mail(sujet, message, destinataires): msg = MIMEText(message) msg['Subject'] = sujet - msg['From'] = 'alertes_saclay@domo91.fr' + msg['From'] = os.getenv("EMAIL_FROM") msg['To'] = ', '.join(destinataires) try: - with smtplib.SMTP_SSL('smtp.mail.ovh.net', 465) as server: - server.login('alertes_saclay@domo91.fr', 'Kdpke674y23Feq^H') + with smtplib.SMTP_SSL(os.getenv("SMTP_HOST"), int(os.getenv("SMTP_PORT"))) as server: + server.login(os.getenv("SMTP_USER"), os.getenv("SMTP_PASSWORD")) server.sendmail(msg['From'], destinataires, msg.as_string()) print(f"📧 Mail envoyé à {destinataires}", flush=True) except Exception as e: @@ -92,7 +95,7 @@ def surveiller(): f"La sonde '{nom_sonde}' du site '{lieu}' a dépassé le seuil de {seuil}°C " f"depuis plus de 30 minutes.\nHeure : {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" ) - envoyer_mail(sujet, message, destinataires) + envoyer_mail(sujet, message, get_destinataires(lieu)) # Acquittement automatique cursor.execute(f""" From f3aef233fd5acd6ec78f4d3a5716c0573087d4c9 Mon Sep 17 00:00:00 2001 From: Michel Date: Tue, 22 Apr 2025 11:35:01 +0200 Subject: [PATCH 23/31] =?UTF-8?q?S=C3=A9curisation=20.env=20+=20am=C3=A9li?= =?UTF-8?q?oration=20supervision=20R=C3=A9v.7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Monitor.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Monitor.py b/Monitor.py index dac9a76..c63a38b 100644 --- a/Monitor.py +++ b/Monitor.py @@ -6,20 +6,22 @@ import smtplib from email.mime.text import MIMEText import pandas as pd import os +from dotenv import load_dotenv + +load_dotenv() # --- Config MySQL --- config = { - "host": "54.36.188.119", - "user": "michel", - "password": "#SO2&1nf%mZ@jfh", - "database": "Sondes" + "host": os.getenv("DB_HOST"), + "user": os.getenv("DB_USER"), + "password": os.getenv("DB_PASSWORD"), + "database": os.getenv("DB_NAME") } -# --- Destinataires email --- +# --- Fonction de sélection des destinataires selon le site --- def get_destinataires(lieu): return os.getenv(f"EMAIL_{lieu.upper()}", os.getenv("EMAIL_DEFAULT")).split(",") - # --- Fonction d'envoi de mail --- def envoyer_mail(sujet, message, destinataires): msg = MIMEText(message) @@ -125,6 +127,6 @@ def surveiller(): # --- Boucle principale --- while True: - print(f"📡 Vérification à {datetime.now()}", flush=True) + print(f"🛁 Vérification à {datetime.now()}", flush=True) surveiller() time.sleep(300) # 5 minutes From d895c3fd91bae2de99ee5a2828ba067e6456cd55 Mon Sep 17 00:00:00 2001 From: Michel Date: Tue, 22 Apr 2025 14:08:55 +0200 Subject: [PATCH 24/31] =?UTF-8?q?R=C3=A9v.8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Monitor.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Monitor.py b/Monitor.py index c63a38b..0187052 100644 --- a/Monitor.py +++ b/Monitor.py @@ -18,10 +18,12 @@ config = { "database": os.getenv("DB_NAME") } + # --- Fonction de sélection des destinataires selon le site --- def get_destinataires(lieu): return os.getenv(f"EMAIL_{lieu.upper()}", os.getenv("EMAIL_DEFAULT")).split(",") + # --- Fonction d'envoi de mail --- def envoyer_mail(sujet, message, destinataires): msg = MIMEText(message) @@ -36,6 +38,7 @@ def envoyer_mail(sujet, message, destinataires): except Exception as e: print(f"Erreur envoi mail : {e}", flush=True) + # --- Fonction de surveillance --- def surveiller(): log_entries = [] @@ -64,7 +67,6 @@ def surveiller(): """, (nom_sonde,)) relevés = cursor.fetchall() - # Log CSV : tous les relevés analysés for r in relevés: log_entries.append({ "Date": r['Date'], @@ -124,9 +126,3 @@ def surveiller(): except Exception as e: print(f"Erreur : {e}", flush=True) - -# --- Boucle principale --- -while True: - print(f"🛁 Vérification à {datetime.now()}", flush=True) - surveiller() - time.sleep(300) # 5 minutes From 08a94ed41289c2ca81d8d437368e064abba84fd6 Mon Sep 17 00:00:00 2001 From: Michel Date: Tue, 22 Apr 2025 15:37:44 +0200 Subject: [PATCH 25/31] =?UTF-8?q?R=C3=A9v.11?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Monitor.py | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/Monitor.py b/Monitor.py index 0187052..d387c91 100644 --- a/Monitor.py +++ b/Monitor.py @@ -1,13 +1,14 @@ -# Surveillance continue avec envoi d'alertes par email + log CSV + # Surveillance continue avec envoi d'alertes par email + log CSV import mysql.connector from datetime import datetime, timedelta import time import smtplib from email.mime.text import MIMEText import pandas as pd -import os from dotenv import load_dotenv +import os +# Charger les variables d'environnement load_dotenv() # --- Config MySQL --- @@ -18,27 +19,23 @@ config = { "database": os.getenv("DB_NAME") } - -# --- Fonction de sélection des destinataires selon le site --- -def get_destinataires(lieu): - return os.getenv(f"EMAIL_{lieu.upper()}", os.getenv("EMAIL_DEFAULT")).split(",") - +# --- Destinataires email --- +destinataires = ['services@domo91.fr'] # --- Fonction d'envoi de mail --- def envoyer_mail(sujet, message, destinataires): msg = MIMEText(message) msg['Subject'] = sujet - msg['From'] = os.getenv("EMAIL_FROM") + msg['From'] = 'alertes_saclay@domo91.fr' msg['To'] = ', '.join(destinataires) try: - with smtplib.SMTP_SSL(os.getenv("SMTP_HOST"), int(os.getenv("SMTP_PORT"))) as server: - server.login(os.getenv("SMTP_USER"), os.getenv("SMTP_PASSWORD")) + with smtplib.SMTP_SSL('smtp.mail.ovh.net', 465) as server: + server.login('alertes_saclay@domo91.fr', 'Kdpke674y23Feq^H') server.sendmail(msg['From'], destinataires, msg.as_string()) print(f"📧 Mail envoyé à {destinataires}", flush=True) except Exception as e: print(f"Erreur envoi mail : {e}", flush=True) - # --- Fonction de surveillance --- def surveiller(): log_entries = [] @@ -67,6 +64,7 @@ def surveiller(): """, (nom_sonde,)) relevés = cursor.fetchall() + # Log CSV : tous les relevés analysés for r in relevés: log_entries.append({ "Date": r['Date'], @@ -99,7 +97,7 @@ def surveiller(): f"La sonde '{nom_sonde}' du site '{lieu}' a dépassé le seuil de {seuil}°C " f"depuis plus de 30 minutes.\nHeure : {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}" ) - envoyer_mail(sujet, message, get_destinataires(lieu)) + envoyer_mail(sujet, message, destinataires) # Acquittement automatique cursor.execute(f""" @@ -122,7 +120,13 @@ def surveiller(): # Enregistrer le log if log_entries: df_logs = pd.DataFrame(log_entries) - df_logs.to_csv("/home/debian/travail/Logs/monitor.csv", sep=";", index=False) + df_logs.to_csv("/home/debian/travail/logs/monitor.csv", sep=";", index=False) except Exception as e: print(f"Erreur : {e}", flush=True) + +# --- Boucle principale --- +while True: + print(f"📡 Vérification à {datetime.now()}", flush=True) + surveiller() + time.sleep(300) # 5 minutes \ No newline at end of file From 281955d87c77611ce96090beae3d0b7442fc2e46 Mon Sep 17 00:00:00 2001 From: Michel Date: Tue, 22 Apr 2025 15:49:11 +0200 Subject: [PATCH 26/31] =?UTF-8?q?R=C3=A9v.12?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Monitor.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/Monitor.py b/Monitor.py index d387c91..60d51e4 100644 --- a/Monitor.py +++ b/Monitor.py @@ -1,22 +1,17 @@ - # Surveillance continue avec envoi d'alertes par email + log CSV +# Surveillance continue avec envoi d'alertes par email + log CSV import mysql.connector from datetime import datetime, timedelta import time import smtplib from email.mime.text import MIMEText import pandas as pd -from dotenv import load_dotenv -import os - -# Charger les variables d'environnement -load_dotenv() # --- Config MySQL --- config = { - "host": os.getenv("DB_HOST"), - "user": os.getenv("DB_USER"), - "password": os.getenv("DB_PASSWORD"), - "database": os.getenv("DB_NAME") + "host": "54.36.188.119", + "user": "michel", + "password": "#SO2&1nf%mZ@jfh", + "database": "Sondes" } # --- Destinataires email --- @@ -120,7 +115,7 @@ def surveiller(): # Enregistrer le log if log_entries: df_logs = pd.DataFrame(log_entries) - df_logs.to_csv("/home/debian/travail/logs/monitor.csv", sep=";", index=False) + df_logs.to_csv("/home/debian/travail/Logs/monitor.csv", sep=";", index=False) except Exception as e: print(f"Erreur : {e}", flush=True) @@ -129,4 +124,4 @@ def surveiller(): while True: print(f"📡 Vérification à {datetime.now()}", flush=True) surveiller() - time.sleep(300) # 5 minutes \ No newline at end of file + time.sleep(300) # 5 minutes From 9d4374e53c7a2f33ff0f5b3a788e28a812286bd0 Mon Sep 17 00:00:00 2001 From: Michel Date: Tue, 22 Apr 2025 17:18:25 +0200 Subject: [PATCH 27/31] =?UTF-8?q?R=C3=A9v.12?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Monitor.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Monitor.py b/Monitor.py index 60d51e4..99a5aed 100644 --- a/Monitor.py +++ b/Monitor.py @@ -5,13 +5,17 @@ import time import smtplib from email.mime.text import MIMEText import pandas as pd +from dotenv import load_dotenv +import os + +load_dotenv() # --- Config MySQL --- config = { - "host": "54.36.188.119", - "user": "michel", - "password": "#SO2&1nf%mZ@jfh", - "database": "Sondes" + "host": os.getenv("DB_HOST"), + "user": os.getenv("DB_USER"), + "password": os.getenv("DB_PASSWORD"), + "database": os.getenv("DB_NAME") } # --- Destinataires email --- From 1821ce44f8dbaaab73bb440eaa56c3a21e5fc3dd Mon Sep 17 00:00:00 2001 From: Michel Date: Tue, 22 Apr 2025 19:46:21 +0200 Subject: [PATCH 28/31] =?UTF-8?q?R=C3=A9v.13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Monitor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Monitor.py b/Monitor.py index 99a5aed..2bc9325 100644 --- a/Monitor.py +++ b/Monitor.py @@ -19,7 +19,7 @@ config = { } # --- Destinataires email --- -destinataires = ['services@domo91.fr'] +destinataires = ['services@domo91.fr,cuisine@bw-paris-saclay.com>'] # --- Fonction d'envoi de mail --- def envoyer_mail(sujet, message, destinataires): From 7ad495e28e6671724e53e6007815df64a2cb00fc Mon Sep 17 00:00:00 2001 From: Michel Date: Wed, 23 Apr 2025 09:17:12 +0200 Subject: [PATCH 29/31] =?UTF-8?q?=F0=9F=9B=A0=20Mises=20=C3=A0=20jour=20s?= =?UTF-8?q?=C3=A9curisation=20et=20nettoyage=20de=20Monitor,=20domo91,=20s?= =?UTF-8?q?cripts=20MQTT?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cuisine_meudon.py | 13 ++++++++----- Cuisine_saclay.py | 13 ++++++++----- Purge_Alertes_saclay.py | 18 ------------------ Purge_alertes.py | 35 +++++++++++++++++++++++++++++++++++ domo91.py | 25 ++++++++++++++----------- 5 files changed, 65 insertions(+), 39 deletions(-) delete mode 100644 Purge_Alertes_saclay.py create mode 100644 Purge_alertes.py diff --git a/Cuisine_meudon.py b/Cuisine_meudon.py index 90213ac..9b26b4a 100644 --- a/Cuisine_meudon.py +++ b/Cuisine_meudon.py @@ -3,7 +3,10 @@ import mysql.connector import sys import os import logging -from datetime import datetime +from dotenv import load_dotenv + +# Charger les variables d'environnement +load_dotenv() # 📁 Création du dossier de logs s'il n'existe pas dossier_logs = "/var/log/Cuisine_Meudon" @@ -28,10 +31,10 @@ logging.getLogger('').addHandler(console) try: sys.path.insert(0, "/myenv/lib/python3.11.2/site-packages") mydb = mysql.connector.connect( - host="54.36.188.119", - user="michel", - password="#SO2&1nf%mZ@jfh", - database="Sondes" + host=os.getenv("DB_HOST"), + user=os.getenv("DB_USER"), + password=os.getenv("DB_PASSWORD"), + database=os.getenv("DB_NAME") ) logging.info("Connexion MySQL réussie.") except mysql.connector.Error as err: diff --git a/Cuisine_saclay.py b/Cuisine_saclay.py index c194121..20cdbb8 100644 --- a/Cuisine_saclay.py +++ b/Cuisine_saclay.py @@ -3,7 +3,10 @@ import mysql.connector import sys import os import logging -from datetime import datetime +from dotenv import load_dotenv + +# Charger les variables d'environnement +load_dotenv() # 📁 Création du dossier de logs s'il n'existe pas dossier_logs = "/var/log/Cuisine_Saclay" @@ -28,10 +31,10 @@ logging.getLogger('').addHandler(console) try: sys.path.insert(0, "/myenv/lib/python3.11.2/site-packages") mydb = mysql.connector.connect( - host="54.36.188.119", - user="michel", - password="#SO2&1nf%mZ@jfh", - database="Sondes" + host=os.getenv("DB_HOST"), + user=os.getenv("DB_USER"), + password=os.getenv("DB_PASSWORD"), + database=os.getenv("DB_NAME") ) logging.info("Connexion MySQL réussie.") except mysql.connector.Error as err: diff --git a/Purge_Alertes_saclay.py b/Purge_Alertes_saclay.py deleted file mode 100644 index 83f89e2..0000000 --- a/Purge_Alertes_saclay.py +++ /dev/null @@ -1,18 +0,0 @@ -# Purge de la table Sondes.Alertes_Saclay. -import mysql.connector - -config = { - "host": "54.36.188.119", - "user": "michel", - "password": "#SO2&1nf%mZ@jfh", - "database": "Sondes" -} - -conn = mysql.connector.connect(**config) -cursor = conn.cursor() -cursor.execute("DELETE FROM Alertes_Saclay WHERE Debut_defaut < NOW() - INTERVAL 7 DAY") -conn.commit() -cursor.close() -conn.close() - -print("✅ Alertes de plus de 7 jours supprimées.") diff --git a/Purge_alertes.py b/Purge_alertes.py new file mode 100644 index 0000000..54d83d5 --- /dev/null +++ b/Purge_alertes.py @@ -0,0 +1,35 @@ +# Purges des entrées de toutes les tables dans la base Sondes qui commencent +# par Alertes_*** et qui sont agées de plus de sept jours. +import mysql.connector +import os +from dotenv import load_dotenv + +# Charger les variables d'environnement +load_dotenv() + +config = { + "host": os.getenv("DB_HOST"), + "user": os.getenv("DB_USER"), + "password": os.getenv("DB_PASSWORD"), + "database": os.getenv("DB_NAME") +} + +conn = mysql.connector.connect(**config) +cursor = conn.cursor() + +# Récupérer toutes les tables d'alertes +cursor.execute("SHOW TABLES") +tables = [t[0] for t in cursor.fetchall()] +alertes_tables = [t for t in tables if t.startswith("Alertes_")] + +# Appliquer la purge à chaque table +for table in alertes_tables: + query = f"DELETE FROM {table} WHERE Debut_defaut < NOW() - INTERVAL 7 DAY" + cursor.execute(query) + print(f"✅ Table {table} purgée.") + +conn.commit() +cursor.close() +conn.close() + +print("🎉 Purge terminée pour toutes les alertes anciennes.") \ No newline at end of file diff --git a/domo91.py b/domo91.py index 484cfe7..f243e91 100644 --- a/domo91.py +++ b/domo91.py @@ -1,4 +1,5 @@ # Application Gestion de sondes +# -*- coding: utf-8 -*- import streamlit as st import mysql.connector import pandas as pd @@ -9,6 +10,10 @@ from fpdf import FPDF import os import random import datetime +from dotenv import load_dotenv + +# Charger les variables d'environnement +load_dotenv() st.set_page_config(page_title="Domo91 - Surveillance", layout="wide") if "authenticated" not in st.session_state: @@ -18,21 +23,19 @@ if "authenticated" not in st.session_state: st.title("📡 Supervision Températures") -# --- Configuration base de données --- -db_config = { - "host": "54.36.188.119", - "user": "michel", - "password": "#SO2&1nf%mZ@jfh", - "database": "Sondes" +db_config ={ + "host": os.getenv("DB_HOST"), + "user": os.getenv("DB_USER"), + "password": os.getenv("DB_PASSWORD"), + "database": os.getenv("DB_NAME") } - # --- Fonction de génération PDF --- def generer_pdf(site, date_str): st.info(f"Génération du rapport PDF pour {site} à la date {date_str}") try: conn = mysql.connector.connect(**db_config) - cursor = conn.cursor(dictionary=True) + pdf_cursor = conn.cursor(dictionary=True) cursor.execute(f"SELECT Sonde, Date, Temperature FROM `{site}` WHERE DATE(Date) = %s ORDER BY Sonde, Date", (date_str,)) @@ -105,8 +108,8 @@ def generer_pdf(site, date_str): mime="application/pdf" ) - except Exception as e: - st.error(f"Erreur lors de la génération du PDF : {e}") + except Exception as err1: + st.error(f"Erreur lors de la génération du PDF : {err1}") # --- Initialisation des variables de session --- @@ -401,7 +404,7 @@ if st.session_state["authenticated"]: except Exception as e: st.error(f"Erreur lors de la récupération des alertes : {e}") - except Exception as e: + except Exception as err: st.error(f"Erreur MySQL : {e}") if st.session_state["role"] == "superviseur": with st.expander("+ Ajouter une nouvelle chambre froide", expanded=False): From 6aede05b7eb57465d86b9cbfd619f05d0d1cea6d Mon Sep 17 00:00:00 2001 From: Michel Date: Thu, 24 Apr 2025 15:52:55 +0200 Subject: [PATCH 30/31] =?UTF-8?q?R=C3=A9v.1.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- domo91.py | 160 +++++++++++++++++++++++++++--------------------------- 1 file changed, 81 insertions(+), 79 deletions(-) diff --git a/domo91.py b/domo91.py index f243e91..3ec8497 100644 --- a/domo91.py +++ b/domo91.py @@ -1,5 +1,7 @@ # Application Gestion de sondes # -*- coding: utf-8 -*- +from logging import exception + import streamlit as st import mysql.connector import pandas as pd @@ -23,13 +25,14 @@ if "authenticated" not in st.session_state: st.title("📡 Supervision Températures") -db_config ={ +db_config = { "host": os.getenv("DB_HOST"), "user": os.getenv("DB_USER"), "password": os.getenv("DB_PASSWORD"), "database": os.getenv("DB_NAME") } + # --- Fonction de génération PDF --- def generer_pdf(site, date_str): st.info(f"Génération du rapport PDF pour {site} à la date {date_str}") @@ -237,7 +240,11 @@ if st.session_state["authenticated"]: try: conn = mysql.connector.connect(**db_config) cursor = conn.cursor(dictionary=True) + if "role" not in st.session_state: + st.session_state["role"] = None + if "lieu_autorise" not in st.session_state: + st.session_state["lieu_autorise"] = None site_selectionne = ( st.session_state["lieu_autorise"] if st.session_state["role"] != "superviseur" @@ -315,8 +322,8 @@ if st.session_state["authenticated"]: seuil_temp = seuil["Temp_Max"] if seuil else 10 st.subheader("📊 Tableau des relevés") - df_filtré = df_sonde.copy() - df_filtré = df_filtré.drop(columns="Id", errors="ignore") + df_filtre = df_sonde.copy() + df_filtre = df_filtre.drop(columns="Id", errors="ignore") def surlignage_temp(val): @@ -328,12 +335,12 @@ if st.session_state["authenticated"]: return "" - styled_df = df_filtré.style.applymap(surlignage_temp, subset=["Temperature"]) + styled_df = df_filtre.style.applymap(surlignage_temp, subset=["Temperature"]) st.dataframe(styled_df, use_container_width=True) st.subheader("📈 Évolution de la température") fig, ax = plt.subplots(figsize=(10, 4)) - ax.plot(df_filtré["Date"], df_filtré["Temperature"], marker='o', label="Température") + ax.plot(df_filtre["Date"], df_filtre["Temperature"], marker='o', label="Température") ax.axhline(seuil_temp, color='red', linestyle='--', label=f"Seuil {seuil_temp}°C") ax.set_xlabel("Heure") ax.set_ylabel("Température (°C)") @@ -404,85 +411,80 @@ if st.session_state["authenticated"]: except Exception as e: st.error(f"Erreur lors de la récupération des alertes : {e}") - except Exception as err: - st.error(f"Erreur MySQL : {e}") -if st.session_state["role"] == "superviseur": - with st.expander("+ Ajouter une nouvelle chambre froide", expanded=False): - with st.form("ajout_sonde"): - nouvelle_sonde = st.text_input("Nom de la sonde") - temp_max = st.number_input("Température maximale autorisée (°C)", value=4) - etat_on = st.checkbox("État actif (ON)", value=True) + # --- Fonctionnalités administrateur : ajout et gestion des chambres froides --- + if st.session_state["role"] == "superviseur": + with st.expander("+ Ajouter une nouvelle chambre froide", expanded=False): + with st.form("ajout_sonde"): + nouvelle_sonde = st.text_input("Nom de la sonde") + temp_max = st.number_input("Température maximale autorisée (°C)", value=4) + etat_on = st.checkbox("État actif (ON)", value=True) + + submitted = st.form_submit_button("✅ Ajouter") + if submitted: + try: + conn_add = mysql.connector.connect(**db_config) + cursor_add = conn_add.cursor() + cursor_add.execute( + "INSERT INTO Chambres_froides (Lieu, Sonde, Temp_Max, Status) VALUES (%s, %s, %s, %s)", + (site, nouvelle_sonde, temp_max, "ON" if etat_on else "OFF") + ) + conn_add.commit() + cursor_add.close() + conn_add.close() + st.success(f"Sonde '{nouvelle_sonde}' ajoutée avec succès au site {site} 🎉") + st.session_state["refresh_admin"] = random.randint(1000, 9999) + except Exception as e: + st.error(f"Erreur lors de l'ajout : {e}") + + with st.expander("🛠️ Gestion des chambres froides (administrateur)", expanded=True): + if st.button("🔄 Actualiser la liste"): + st.session_state["refresh_admin"] = random.randint(0, 9999) - submitted = st.form_submit_button("✅ Ajouter") - if submitted: try: - conn_add = mysql.connector.connect(**db_config) - cursor_add = conn_add.cursor() + conn_admin = mysql.connector.connect(**db_config) + cursor_admin = conn_admin.cursor(dictionary=True) + cursor_admin.execute("SELECT * FROM Chambres_froides WHERE Lieu = %s", (site,)) + chambres = cursor_admin.fetchall() - cursor_add.execute( - "INSERT INTO Chambres_froides (Lieu, Sonde, Temp_Max, Status) VALUES (%s, %s, %s, %s)", - (site_selectionne, nouvelle_sonde, temp_max, "ON" if etat_on else "OFF") - ) - conn_add.commit() - cursor_add.close() - conn_add.close() + if not chambres: + st.warning("Aucune chambre froide pour ce site.") + else: + for chambre in chambres: + col1, col2, col3 = st.columns([3, 1, 2]) + with col1: + st.markdown(f"**{chambre['Sonde']}**") - st.success(f"Sonde '{nouvelle_sonde}' ajoutée avec succès au site {site_selectionne} 🎉") + with col2: + etat = st.checkbox("ON", value=(chambre["Etat"] == "ON"), + key=f"etat_{chambre['Id']}_{st.session_state.get('refresh_admin', 0)}") + new_etat = "ON" if etat else "OFF" - # Refresh immédiat - st.session_state["refresh_admin"] = random.randint(1000, 9999) + with col3: + temp_max = chambre["Temp_Max"] + moins, val, plus = st.columns([1, 2, 1]) + with moins: + if st.button("▼", key=f"moins_{chambre['Id']}"): + temp_max -= 1 + with val: + st.markdown(f"
{temp_max}°C
", + unsafe_allow_html=True) + with plus: + if st.button("▲", key=f"plus_{chambre['Id']}"): + temp_max += 1 + + if new_etat != chambre["Etat"] or temp_max != chambre["Temp_Max"]: + cursor_admin.execute( + "UPDATE Chambres_froides SET Etat = %s, Temp_Max = %s WHERE Id = %s", + (new_etat, temp_max, chambre["Id"]) + ) + conn_admin.commit() + st.success(f"{chambre['Sonde']} mise à jour") + + cursor_admin.close() + conn_admin.close() except Exception as e: - st.error(f"Erreur lors de l'ajout : {e}") - with st.expander("🛠️ Gestion des chambres froides (administrateur)", expanded=True): + st.error(f"Erreur SQL (admin) : {e}") - # Bouton d'actualisation - if st.button("🔄 Actualiser la liste"): - st.session_state["refresh_admin"] = random.randint(0, 9999) # Force le rerun - - try: - conn_admin = mysql.connector.connect(**db_config) - cursor_admin = conn_admin.cursor(dictionary=True) - - cursor_admin.execute("SELECT * FROM Chambres_froides WHERE Lieu = %s", (site_selectionne,)) - chambres = cursor_admin.fetchall() - - if not chambres: - st.warning("Aucune chambre froide pour ce site.") - else: - for chambre in chambres: - col1, col2, col3 = st.columns([3, 1, 2]) - with col1: - st.markdown(f"**{chambre['Sonde']}**") - - with col2: - etat = st.checkbox("ON", value=(chambre["Etat"] == "ON"), - key=f"etat_{chambre['Id']}_{st.session_state.get('refresh_admin', 0)}") - new_etat = "ON" if etat else "OFF" - - with col3: - temp_max = chambre["Temp_Max"] - moins, val, plus = st.columns([1, 2, 1]) - with moins: - if st.button("▼", key=f"moins_{chambre['Id']}"): - temp_max -= 1 - with val: - st.markdown(f"
{temp_max}°C
", - unsafe_allow_html=True) - with plus: - if st.button("▲", key=f"plus_{chambre['Id']}"): - temp_max += 1 - - if new_etat != chambre["Etat"] or temp_max != chambre["Temp_Max"]: - cursor_admin.execute( - "UPDATE Chambres_froides SET Etat = %s, Temp_Max = %s WHERE Id = %s", - (new_etat, temp_max, chambre["Id"]) - ) - conn_admin.commit() - st.success(f"{chambre['Sonde']} mise à jour") - - cursor_admin.close() - conn_admin.close() - - except Exception as e: - st.error(f"Erreur SQL (admin) : {e}") + except exception as err: + st.error(f"Erreur MySQL : {err}") From 74d734277d83fe4a908ed368ceee2bec481ce544 Mon Sep 17 00:00:00 2001 From: Michel Date: Thu, 24 Apr 2025 16:15:23 +0200 Subject: [PATCH 31/31] =?UTF-8?q?=F0=9F=9B=A0=20Mises=20=C3=A0=20jour=20du?= =?UTF-8?q?=2024/4/25?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- domo91.py | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/domo91.py b/domo91.py index 3ec8497..61f3fc8 100644 --- a/domo91.py +++ b/domo91.py @@ -32,7 +32,6 @@ db_config = { "database": os.getenv("DB_NAME") } - # --- Fonction de génération PDF --- def generer_pdf(site, date_str): st.info(f"Génération du rapport PDF pour {site} à la date {date_str}") @@ -124,7 +123,7 @@ if "lieu_autorise" not in st.session_state: st.session_state["lieu_autorise"] = None # --- Connexion utilisateur dans la sidebar --- -st.sidebar.header("🔐 Connexion") + st.sidebar.header("🔐 Connexion") if not st.session_state.get("authenticated"): login = st.sidebar.text_input("Nom d'utilisateur") password = st.sidebar.text_input("Mot de passe", type="password") @@ -271,10 +270,10 @@ if st.session_state["authenticated"]: # --- NAVIGATION --- if st.session_state["role"] == "superviseur": - onglet = st.sidebar.radio("📁 Navigation", ["Accueil", "Statistiques"]) + onglet = st.sidebar.radio("📁 Navigation", ["Accueil", "Statistiques", "Traffic"]) +# --- ONGLET ACCUEIL --- else: - onglet = "Accueil" - + onglet = "Accueil" if onglet == "Accueil": st.markdown("## Sélection du site et de la date") try: @@ -354,7 +353,7 @@ if st.session_state["authenticated"]: except Exception as e: st.error(f"Erreur MySQL : {e}") - +# ---- ONGLET STATISTIQUES --- elif onglet == "Statistiques": st.markdown("## 📈 Statistiques de température") try: @@ -486,5 +485,25 @@ if st.session_state["authenticated"]: except Exception as e: st.error(f"Erreur SQL (admin) : {e}") - except exception as err: - st.error(f"Erreur MySQL : {err}") +# --- ONGLET TRAFFIC ------ + elif onglet == "Traffic": + st.markdown("## 🚦 Connexions récentes") + try: + conn = mysql.connector.connect(**db_config) + cursor = conn.cursor(dictionary=True) + cursor.execute( + "SELECT Utilisateur, Lieu, Date_Connexion FROM Connexion_Log ORDER BY Date_Connexion DESC LIMIT 200") + connexions = cursor.fetchall() + cursor.close() + conn.close() + + if connexions: + df_connexions = pd.DataFrame(connexions) + st.dataframe(df_connexions, use_container_width=True) + else: + st.info("Aucune connexion enregistrée.") + except Exception as e: + st.error(f"Erreur chargement des connexions : {e}") + + except exception as err: + st.error(f"Erreur MySQL : {err}")