From a33dfcb96eb7e4fd160ca0b6b0b428be526a30d3 Mon Sep 17 00:00:00 2001 From: ske087 Date: Wed, 12 Mar 2025 15:30:04 +0200 Subject: [PATCH] updates for 12/03/2025 --- ESP_api/ESP_api.ino | 18 +++++++- server_api/app.py | 70 +++++++++++++++++++++++++++-- server_api/instance/logs.db | Bin 86016 -> 94208 bytes server_api/templates/board.html | 20 +++++++-- server_api/templates/index.html | 3 -- server_api/templates/settings.html | 37 ++++++++------- 6 files changed, 121 insertions(+), 27 deletions(-) diff --git a/ESP_api/ESP_api.ino b/ESP_api/ESP_api.ino index caac68b..556a338 100644 --- a/ESP_api/ESP_api.ino +++ b/ESP_api/ESP_api.ino @@ -3,7 +3,7 @@ #include #include #include "esp_mac.h" -//ver 1.03 +//ver 1.04 // Constants for Access Point mode const char* ap_ssid = "ESP32-AP"; const char* ap_password = "12345678"; @@ -25,6 +25,9 @@ bool isAPMode = false; // Variable to keep track of the last log time unsigned long lastLogTime = 0; +// Array to keep track of the previous state of each input pin +bool previousInputState[4] = {HIGH, HIGH, HIGH, HIGH}; + // Function to send logs to the log server void sendLog(String message) { if (WiFi.status() == WL_CONNECTED && logServerIP.length() > 0 && logServerPort.length() > 0) { @@ -184,6 +187,19 @@ void loop() { sendLog("Board is functioning"); lastLogTime = millis(); } + + // Check the state of each input pin and log changes + for (int i = 0; i < 4; i++) { + bool currentState = digitalRead(inputPins[i]); + if (currentState != previousInputState[i]) { + if (currentState == LOW) { + sendLog("Input " + String(i + 1) + " pressed"); + } else { + sendLog("Input " + String(i + 1) + " released"); + } + previousInputState[i] = currentState; + } + } } void startAPMode() { diff --git a/server_api/app.py b/server_api/app.py index fe87f12..8a8efa5 100644 --- a/server_api/app.py +++ b/server_api/app.py @@ -4,6 +4,7 @@ from datetime import datetime, timedelta import os import threading import requests +import base64 app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///logs.db' @@ -25,6 +26,10 @@ class Log(db.Model): input2_status = db.Column(db.String(3), default='off') input3_status = db.Column(db.String(3), default='off') input4_status = db.Column(db.String(3), default='off') + mission1 = db.Column(db.String(100), default='') + mission2 = db.Column(db.String(100), default='') + mission3 = db.Column(db.String(100), default='') + mission4 = db.Column(db.String(100), default='') # Create the database if it does not exist if not os.path.exists('instance/logs.db'): @@ -58,6 +63,10 @@ def log_message(): input2_status = latest_log.input2_status if latest_log else 'off' input3_status = latest_log.input3_status if latest_log else 'off' input4_status = latest_log.input4_status if latest_log else 'off' + mission1 = latest_log.mission1 if latest_log else '' + mission2 = latest_log.mission2 if latest_log else '' + mission3 = latest_log.mission3 if latest_log else '' + mission4 = latest_log.mission4 if latest_log else '' if hostname and ip_address and message: # Parse the message to update relay status @@ -81,19 +90,28 @@ def log_message(): action = 'on' if "pressed" in message else 'off' if input_index == 1: input1_status = action + if action == 'on': + execute_mission(mission1) elif input_index == 2: input2_status = action + if action == 'on': + execute_mission(mission2) elif input_index == 3: input3_status = action + if action == 'on': + execute_mission(mission3) elif input_index == 4: input4_status = action + if action == 'on': + execute_mission(mission4) new_log = Log( hostname=hostname, ip_address=ip_address, message=message, relay1_status=relay1_status, relay2_status=relay2_status, relay3_status=relay3_status, relay4_status=relay4_status, input1_status=input1_status, input2_status=input2_status, - input3_status=input3_status, input4_status=input4_status + input3_status=input3_status, input4_status=input4_status, + mission1=mission1, mission2=mission2, mission3=mission3, mission4=mission4 ) db.session.add(new_log) db.session.commit() @@ -162,7 +180,8 @@ def view_board(hostname): def delete_board(hostname): Log.query.filter_by(hostname=hostname).delete() db.session.commit() - return redirect(url_for('index')) + flash(f'Board {hostname} and its settings have been deleted.', 'success') + return redirect(url_for('settings')) @app.route('/board//logs', methods=['GET']) def get_board_logs(hostname): @@ -205,7 +224,11 @@ def settings(): 'input1': latest_log.input1_status if latest_log else 'off', 'input2': latest_log.input2_status if latest_log else 'off', 'input3': latest_log.input3_status if latest_log else 'off', - 'input4': latest_log.input4_status if latest_log else 'off' + 'input4': latest_log.input4_status if latest_log else 'off', + 'mission1': latest_log.mission1 if latest_log else '', + 'mission2': latest_log.mission2 if latest_log else '', + 'mission3': latest_log.mission3 if latest_log else '', + 'mission4': latest_log.mission4 if latest_log else '' }) return render_template('settings.html', cleanup_time=cleanup_time, mir_ip=mir_ip, mir_user=mir_user, mir_password=mir_password, boards=board_settings) @@ -239,13 +262,21 @@ def set_board_settings(hostname): input2 = request.form.get('input2') input3 = request.form.get('input3') input4 = request.form.get('input4') - if input1 and input2 and input3 and input4: + mission1 = request.form.get('mission1') + mission2 = request.form.get('mission2') + mission3 = request.form.get('mission3') + mission4 = request.form.get('mission4') + if input1 and input2 and input3 and input4 and mission1 and mission2 and mission3 and mission4: latest_log = Log.query.filter_by(hostname=hostname).order_by(Log.timestamp.desc()).first() if latest_log: latest_log.input1_status = input1 latest_log.input2_status = input2 latest_log.input3_status = input3 latest_log.input4_status = input4 + latest_log.mission1 = mission1 + latest_log.mission2 = mission2 + latest_log.mission3 = mission3 + latest_log.mission4 = mission4 db.session.commit() flash('Board settings updated successfully!', 'success') else: @@ -263,6 +294,19 @@ def run_cleanup_now(): flash('Cleanup executed successfully!', 'success') return redirect(url_for('settings')) +@app.route('/board//input_status', methods=['GET']) +def get_input_status(hostname): + log = Log.query.filter_by(hostname=hostname).order_by(Log.timestamp.desc()).first() + if log: + input_status = [ + log.input1_status, + log.input2_status, + log.input3_status, + log.input4_status + ] + return jsonify({'input_status': input_status}) + return jsonify({'input_status': ['off', 'off', 'off', 'off']}) + def post_action_to_server(hostname, action): url = "http://your-server-url.com/action" payload = { @@ -278,6 +322,24 @@ def post_action_to_server(hostname, action): except Exception as e: print(f"Error posting action: {e}") +def execute_mission(mission_id): + mir_ip = app.config.get('MIR_IP') + mir_user = app.config.get('MIR_USER') + mir_password = app.config.get('MIR_PASSWORD') + url = f"http://{mir_ip}/api/v2.0.0/missions/{mission_id}/dispatch" + headers = { + 'Content-Type': 'application/json', + 'Authorization': f'Basic {base64.b64encode(f"{mir_user}:{mir_password}".encode()).decode()}' + } + try: + response = requests.post(url, headers=headers) + if response.status_code == 200: + print(f"Mission {mission_id} executed successfully.") + else: + print(f"Failed to execute mission {mission_id}: {response.text}") + except Exception as e: + print(f"Error executing mission {mission_id}: {e}") + def schedule_cleanup(): with app.app_context(): cutoff_date = datetime.utcnow() - timedelta(hours=app.config.get('CLEANUP_TIME', 24)) diff --git a/server_api/instance/logs.db b/server_api/instance/logs.db index fcaa639e1a5b2fb2fd04080aa0529d95b3222d13..2aaf7b0092b2ba2811937f8eb2053c5b8473d7be 100644 GIT binary patch literal 94208 zcmeI5d#q;HS>C^Mzsxyvd`aRs4fyynBnEug_sa=+o2Z!u07d&bEg&t-LH zoRG$e`M$ls7PZ_W8ucO~1+}3-1!+p*k5r{fTN2S0(w4ReQ8iUjMB7x978R&Kw7<2E z$8%;p@0z6ZBvQ~BIVXw7n#a$6*IxU5*LvRPefYZ{?9LzGeD=)QQ%BEl=A-LIGc%*_ z*xVeAW`BD$8r{TyF8rF|f6QO_Km51<|5y3NpRx7K%sT(n%=&*H@z>t3y(h5u1ooc5 z-V@k+0((zj?+NTZfxRcN_XPHyz}^%1Dm;PV`kAGhZoF~k2UgA>efs3_lV{$|U$gfd zK6LQNq0J))?|$&mCVyk|jjJo&vCaD*I&$dVLx(pX`pzSp4?XhWgWtTly7Hbg=gyx# zdg}P*qX!S)bKk+kZ%mWq&P)D@&b{mCv14bCpF3y&GpG37qwl`>hm@E9A?G{(k@H7S zxy`rmZ+7JVw;%H0XOEve`u_A?{Kom`FaGKIoiG1h7WutA@_WUVf3G|3o$rB@fP%d+9ea>o;=iH;PyM#&;dQ|Lq44Ke741p(i%q*d4ob z_09#>-K#eKieG&3jW2F|VdL`~zq#@28^5~o*^N(c{KJi3*!aZ8PjCFK zjgM~pwT%z&{KH>`<+=y4_XPHyz}^$sdjfk;VDAa+J%PO^u=fP^p1|G{*n0wdPvFn! z6L`(^9+z|Kdcn5%uZ*AP=koYrelCquelCvp^K-%f0rU6qW9|-rH-E?I*oeCRw*5c9|1InP zZvEM{KV18PwVU_-%)U3R{>#;;R(^No#g*%pe_}aX`lm}r7Qe9g-o>SbzrJwi{HNw0 znEMZNr)K|j_D5znXFfS|_vqL8gRb!F)EzgBZaGjMyz!-9>-;)?&OLqR=-Fd=+Gbm6 z)odkOd3Cq{JDc6P&1auK{mgj|L%P%N&XTOWE6MLlv(2R4PLu7j+A6XpZHhC`K6}N# zPTj7A%c`xst&_aIA{Kx1)NL|22*NH)tGX&pxLcKQuuGe)E-DjlQwP_0c4^VJxea%V z63(-OUYVNgzFr4cdv-}vWr@x1>y&V?OUk0G5|iEQb#PT+m*#ELns9$k373^yZ3DgB zgnOM1uG}txE7K~^Ot_nsaGo7ITWZ6-RtHyjc1_dfi5^I^V52bTwS zS(Uc-YyN5_9PH}4%_8sJt8{RgXIB?>QrS!8MkSnQ#~$6-yUGnZxYV<&_y9F_?5Fbvih572wi5sqGhGLkS1FvLQFJbCvxfI6md%D!>(O9(nK9m2jS2TDPs8 z8m;NzAXsh{O`R3?!`!EYgI!T3S#7ghmBA%^5y-dsye9U_Tv5Vhz?FHD+HlJ{IKBwL zWoeq&v0GBY!7gi(tg=6^i#j;I2*Bla8~Fe&DB(Q2q-Y|^!@LfTFG4~+ShR_~Mb9bW zV3+2kwRX}ztApcAKlyI<1lPs30Z|mU57+Ma( z_$Js(rB%YgF3AcqFmq)#Iyf?hW~)idSRGO;;j(6{O4=;7mr5mrOQ|87t%5BqQX!X0 zIM}6a*_QTFDRgkuklbseMbg?2Ggrc8HD{)2mDr!xOb17iGM*1?gh z0LPY@*@@pbDd9Xje~o5;&A(9xN3K$B`J6rS-rcE$gI!frbz-NrZ`8q&s{ofKRiq?& zgAxvQW!Ywtg8UnFaO5h$C1qLIo7e#*9PElJN$lfwU;p1eI(Omz|KR%P)}LDY;@bPy zmiB#Q-|efPT>bWyUtM`}`M)l|Z+T(q$Chqg{KVp$7yiY<6Z5|_|NQ*i+=u6Gnf=+> zH_iOY%wwZ3@W*^9zua>-j1Cxbfb3Lvc9$ZczPciyWT(!yiaKu^^KEx0#E2P)MV95} zcinYTMD}YCQx5%Xj_(m8!jY?NPHn9;*S%Yc$gZ7o_CZ2nz8mgYF=Cc(HEcDRdHCm! zOA*<-Q!WqF#;#S|F)<<>>#T}=>D@C@M0W6Wt7^;4K6Y_WixJ^il@&)z=2|@}MPwfb z5r(B%1iJ4PBf^pI6i3x2$9G8)lWmV!AaJq$UKBjodCl-IHQO&oSXj#eS;qkRnnUrdxSkXRZBqJRwGeW6t5EEn9GpOA*OB zKt#A0*~uRhBf^pU;ik5~$d5`9$vQyfrX`YfJR(MfBULx2+U72IM2Z-)j+CR{$PVzZ z7}0Y?uN2{USc(|3j-)Lj;sW>GVnjG{tQXr?ze|c3vJS4`kuI4l=R3uST&^OAtn7Pu z_mC7Z+>N(IlNI(3@OCL;xUX(nqy~0Q{-79Cb(R+VWv$Xv_sih8EQej1A~ZE$ik!+!)oF6wH&N|Qpul9S@X zJ}Gu%lxadpYQ+NCWJ>#yy%nHNlS0BWEgGb#HY5r)Atcpe!M08YZeB3B@kk5_OYZR5 z*G-l$N)ag+bGFzrjeNU4Bu30Y%$uUHUy%<=5h)jQHdvA=Gr4guh!Nq4ia8>Mbsvx- zQZ9m+R%Ik1{tIG6I95p$D^TArMI`CTea~ZKD_q?7i4oz5Iw_(tcJGrSlJewRS(_Dh zJDYp27!i)tr+MU%`gtiL2@i-_o>lgx(Rnc<98(gkNQ!k%ib%SXpH6DbY|JoV0T)K2uGR}(M6gZ zPe~C;bU3WQq!`h2Owml4ryD!C0k|IyKJ zkKD!L-}+x}j3!K%J!Z{zQuFCRo?>ruV{~Cc$a*_RiH3?Wauqj58zzN>WrilkZsc%d z^kG8CDzHSXA33HTqY;xrdX`C5MsAMB=){DOrDy5CShn!njnRrpAz?`)eRRd57ZXAj zfh7e`YMukSF`6+cBrK7S(~mutK{QH&mBLu%R7ey^en*94wc*c>xnUbF9!~M zQ-Hf3znS+R&P;FdhLv#`Bz5KWC`z|4T3vJ{iyvP6`h~x{(9Hkx{NcIJ&z+q8KeK;v_Qsiy&D_Nw@>Tui z#yVXVpS+^nPJACJnRR60>x5YzlDdrM&Z3}9vKPKinY9C%G~Aq)O&a++=%iU5lDZ5? z?lEH(i%y%h0|`s66lun{AFNKCeMxUkoIsoZea^)7$+Ki5d_ z4|8L@Riu!d@X_Xz#Dr@2s|X>(nKW{At~BiBgU5;#5|*?Ua>Zan;v*q7 zAz@iHBqEVK7XK9?B)JceB|Ia`2M-o0q-V*E#$_jPmn0(ium~Z^eQH|pa3zW)m3Xm8 zAz{f&38uaK;Kw3_B=-R_r(rCzV)0~=LVA|StZlCiH^!Gm2kDjZHGZ44Hbf3qaxo+< zixls_$d@FOLX!J%IbP8iYi4*~%k{rdE=7@%ZF9#=9*kCJB$2!SW8X#d=LeqBdbz_}oipm(37A(u0p0`Lb zt`kk|K*ACQV-x9l8|ze49qu+(Sm$K2 z0}0Ed!V%j1?7Fee%EUt!+XZct96uJ3mqh1fvICiNLsV!g~`ouP?`B=-T52EbS> zb&e)Gke($nxQOI;tg|%nkmNoz{BqtItBiG?COeR@%(%X_EvwyFXKDf@#x0&@ihp4w zb=J9>>_EaYp=ON9Q+2i`9+GOxTOfPUfxY|ad`)&Bxs$B$%g!U&y3W|dLvoUg6>`J& z6FKG3Ih*W2!m`ZDHdg=ZtW7*5C&?{e5~`BON%AAg+y_Wl7PvOX6e~xhkenp9>^^L< zCd;qk`hOx6F{Rt+rqY}pB{q4&O@tyQAgQM4UaGxSlDVTygd(N@nF2|JR$(p)HxY`M zfTWs20_R>Xty6fp%zSTfZBw+Q>e3PnsnQcVF#TSz1Wp9n=v0TPx>BEv4lW+@ag z0ZBClBvMp+K&6`qMN9$Gv!qIm)jL!3qsW0wm|UUmJ@kNgbp785|(~cU#zqd zI((#lU@R;-uWBO~xD%nnM}UN7g4JJbr%)52!v~O5Q@H)1u(HQ z)M8C`V<67+_SU%)FcsMfFAeP4l>kk=Aj#+`^8Y^-cKM4jPGDvbCE_+)P2a%80 z&B_c9_Z?W`bKKgK4c+9mN=UA>JxkgU?IVY;<@#T!DDqyZel}xX6-mS-Z6Z_@0m+U+ zUxgb;&1xQ$WgYU?(`$VWH0@90$ z7++X$(P*boLPb%4Oo7DwHnyV(6-5M+9mQM0HEFE0d04rL0SU{d#jYu`d=5(?siuG= zAC3(7nh1S4c(6@i>2Hc6*I`0m4*ybmmNhctSY<5qz>Hqt3Vk_%q?!VzF z?y>lC#U>3;guWa=lKYe-l{wBF_Je(sGN}X-mb6{R{N*OwQb=+iAd{Ll2aBbz|L+(b z_Wpmj?*F;{cdvhL{h77jTN~D{-}mu-+3IIjA71&LmGdjh%O6?3bLkhC-nRG~i_b0m z(ZUZd+&urY^V@U3HuvQ0|CoJY_PUvm&ZMJX;tv{i6ZA|s9k}$U=FmBZ^EBIIFlp!!Jw|IY1=f0$ZX%Q}L64FcdS%NL5eu@LAYD?>+xf8PEe(tomYWDQ zOM#w`9-V#mC$F2JS<=w+pI6+yVhVVnSP6P`OHy=mg=yD@-Q;- zbT2}8j{r&Zn16uDmGwls3GN;mdPI*|tR!I;mYWD|J)p<ihhRGX)jKpXGfq%^h#8b_Hjfv!Lvg{&qt4(#eS4-^0e-=4n2(Ip~pFd-M80G zFzZmz3vyu`Gb4}EO@uBTfgaH-@H~h`?_DZ-6v&tzBr!GgJEhUXARl@RbBt{*Pig1{ z8MHUfia1tGzDG>YM=wL+X@1ss>ip=tM~o%zH0TKuRgZ&#g*?{SzG=a z%Xck(YU!cH-&#Dg@FxpDy6|=LpPc`;xqmbFuG!z8{r=fk&HThnIr=A~M|Va5bUn(g zYhLo+kNN15eyOm(D}x9jmSQP2xz7Gryn+E^>a^+E%aK#%Cg!QPA7boHXPuk*!5&SrX{^=y4itOC!1-%MuMeqE~Yc z-A3L$p;rm?D6_)Mn-{V!Hpck|>Wa+3O?8PZGDGBt59_O`*ov3#`CM6nrM6Y5O zvmfQjKDd)y`_NISQDQL{32}fuR_jc^%bDa_hga@EQZ)G8^U?rboVnQb7?UU-=8huC z_mN);wW~q@L?0YUu6d+tAeMUr3ySee58o~lbG^`uBp|Xd83J0^cd%VQr+SutVJ3KX zN4Aw&X~u%)+n1LN0KfEs?h0Sm&uHlF$hvCWM;@ymiY)F+9@K-{ch!Q2a@Pv^v&p7Yr%9qGBgc6qKEXpiPio>{Tb1tdW2q{ zw)V8}u19~SphxxSk5O>Di9}B*JPY)E^b$msz-Ed z!M(>?FkO#Riz0edkDN2qG<}*$iR*mw>N)6T%$P7=1lJ2~SOPtw*U*P$+dXtWHY^%?L@(o6#!4)q z{|fX%iN%a!2ItvZ3;rt#dX!k<7$uG6oI>N3K+i`HVNpbs*<-w-q35H=ZEnQXx)(aG zKraZQIc1L)L~qd03*FFZ&8Un>QuYnf&pIWap9o5Og_-7d{Q(s{qL;FZMJxyVJEZgw za6k`5h26ZiQ~y7*`~LrG`u`IPf4uM`3$LI5d-HFe``2^toc&+3FV5aL^Rby?^y$$f z;r_qt6=FJqL+>vIE2F>dn!xo!F&&UpSq!5{FdUEM3JNh@z$&2Uea0eR8=;skKxR8r zDD3R3>lI?UfJO8eL{r<3()B_y9gtL6OuXQ5J0e5w6=J%8Mf5mE=XlNfvbkO;rVEfn zj}CyC9IID|=>nFIUWTcd`69SpD5e9FDhqlE6AjGwq3ac5x`0LW7}r4NWk*jarVEfn zk1?3hTONg&E?^No8eN%4VbMF;2Ql53bIX%$o;`l@_|bF6k7W#%xhu)1YQ4thF;Km^CIBt zXh-jD((fLAhtR|9;4(qQCHMdLtLXXYacdE=Tk7wV(nIUbb#{x=$9x~Ip#Q&G{lC-y zzd8T&^9RxYKa2kVFQNbcDfIupazX#^29$IMF7p7ea=oit0bojzFoid#LD9}n1_X72 zx6E5ZP$7GZbGtAYP|^wMF>@1oSm8$Y$N@o}hF%K2ghRFnJ(P4(dPL9r8AW=e1_X5q zdR(5hU{U#)E5!{c>4fx{B?>)+95!S(AgI&OBYI#(+*wWzK~1M9>zIj=V1!SOe9}n9&WdG--ONwmUKG64YH&A;|!aGGWkPnkTP#8D0YG1_X7A z$Whf1Igspar5iL#I%u&<5W-aUrGy)#N;)738+W=?S=Pt98#GEfKTyQiJ2(KmB5D^t z+-{I6>42ofYU!J`%bu=(QL|DCUm&+_8NDF?(5{yb*XP(-%9rrcQDQ-_Kv@*2AU~*~ z7fP&_aWJ-w$@MRY>G|lTMUk4BW!GzbZx~4R~H-6X*YIUq4>=(iHQs0}L5(|1IuU_Qc)A-(i zMf5lnrZ3FerCl%ey#bQwRS3xKZ&BB4d~d+=(W4bVR$>W#Z!$kbhv-orqp7u?XxA%z zZvqz4Ba4m}$U@(n0O_NLV#K!l==w974+E}6&|}zveF*LP)5;G6_sc{NKS%OSJ9?)i z^!y+qE_+*Fnpjw_KPje1^caze#@vE@S^xi6@&Ee;{Qv$v{(pZ+|Nm?8|7-F8dk6l1 zAHe_b$MOIBNBIB!NBsZ(FaCd<`2QWm|Li(IXA(k zn#&$$DWiw9NOS{&Izf_-S#+7wMMr0wiRA{AbV7PmkI*ZN(w2pE1A;mYy$pJ&zwA$q z8&J|o>G|k!l@*H~f;t5~s=1U~EZ*lx^ia|X>6JcuRm)TuvlQtD1a%sESwcUIzX-8j z1UI0hlhPx4RfPqB`9!+`L7jpgmvq$PIGEd5Za_&Vq*sLKA#rFUMJs|j4LzbqYa5rW zcJxrvN$C+ifBbt{ALNB1nSYONKv1WkM+M2T4U5yh#c~5mIw8H>M-P+qSkXElsMF9R zdNk<9H2K5rVtPJ$=mqVI>pXr}PS4Yx|^kz7I#cUZg2x*IlC^oXAK zw2mk$20U+si<92jl-He`m&neE^^Ib2Qq^(1!C{JE_=?FOuG zH1vpGlU0n+u)lkF-bm?j7Tq>w(nLP%SlCXNK0A8X zspt_sKI(O5z6f85`X42oZn@)+nq-jR&eJ5(4G8K4%iX(qX4L<5ghY~blypLRRFKfC zaD|9ekO=BD^fKsiPY|&j7*Ntl>G|m88TQ2Xaz{|7phpFXJxaxSmzi_90VSP~9u*{z z_`llzl-+=!PD79Ap$o39#MC8&J{-=}|#4 zk%a0$_U<95)6gS&SpUUl3Jxggr1Xd$_LQs`J9-G}6!fSd!J>jp&69LDprjMhqk{C1 zOk^@Q>ux|$r=jPgmt-7IS&z~U_loKH=&@DD=)F}#k2}cB`_OUrAGw`WW}7a zNc7&KqDS<6)3aU9xdGoB;o?kv*5Xgv?N;P&xLZSyJ4mnyU&Ie%!1qQ-j|wtCM*8Ht z0s9-pyN4-Ly4BV>_n!9R#P>!@kNO`6XuA6>$Zo*?MnI4Gw%l59KaQTs!g2$?H#&Mi zQvXx_HxV_{fc=ez9?`?|wu#mM_})nA`RL)aZhwoq0s9*TJ=%piDQd7zF{=|d;Cmya zM+KSsK}mTmrN;h7LyzboZDjy}y*O(zJ)+kXIKG?rS zm{@K=NhhR71)2K^8#GkdZ!>~A4L!10CKGW4U}3odC7qNW(aRav9;+Y`)G6qN3KF3i zJ^400lypLRRFKePn3bJ4cLRbt4LzdQ&{Y(xAW_mu>G|lTS!TCGb_0Ss1-(!~Hf2VK zyd6E1bV7PmkZjiIN+WI01A;mYJs&+vEc?FG4bO?`5j`yAV~w-J2@SnaL9#ZBh@0uq zN$F8R@-3=knPoS;M@5h5A+e60dt-VdT%3uo|CvOaNBV*Xyl<{~aeDE;um90URuNyY z0n-~5y#Pu5-=ZtEi`H(y`$j{L=#}IU5#j8B>5Y`0k6xD6wiQ`7JguQe1qr>pY;99! zH(+`rq(=qGZr0FKVHU03fcK3edJKi}*LPq=KI@p?Na+zhO7h&6`*Z`|Hwt=GkpB9P z&Z$Vw8H{f(9f)W`6oG-&%^lCe9F-FNh0Gx>JRc4k#)}`knDdjmTZ5LM2^*%u9rV4MzZ%v%8?v z?WPJPUBJq4=|cT)d$GEyP)Qdc6X+F;I5fBaZmLkyfkj?YuorV3Z=c>wg-W^r>7$p@ z)f0)HLP-~}h#q@!7STaYg-W^rN%S(WIEX|~p`-(g3bMd0GNb(giG{m*fp+VHTE~Zs-z)0Ezbod1Z8Q?iVBqoO@G2a>kze;Y&8XbtOF@ zi5{1Nu>yIzrlQBWcMVu%VBJ*cc@v%}(W^L8iAb!c3eOv`s34)|Z|UqCZbVKTyu#978N8{2$bUXS%aGf z#~T?vDoFpSO^b;3W~%VL3G|2_Lt$|ewzn31Z&dYvfFycYKxdJu?$cRK^r#^H1yIGX z9h)A$H$r+;ki{0J;pn<8$Zm@Ljbd>!7{*@!F}2Rba>I*adPI-Wu#KH4b;E}=^w4`l zujFLdj^6M=DLwSwK9i`6)=YD|;RO{vAH9?gQ1i3CQ~y6a+W6s(TlW9`{cm0WjrC(| zf3!ATyLsPF?%Q7d?CO&%|7GQcm6hd>E`Q_FFD*T=`1!??i=%}fTDW!o=jQL8``p|! zv%fbx%w9k9@tJJ&nbE`4|86Rj(&gSkH1XqRNlwf)*i98uIFz6)fKryo+_kt6g(6m6+_6pmd<*V zZYq@01xTV-Rye@goAFd3r3+YD>H9om?mbhXlrBILJw&WbBDbDsH&saKz@i8#eOF?{ zrOQo)Qn~=?qer(-L;x^VNa+F=(PQR0bGocY>880dQ{agbJw^^htk%0}Cdm~1bjp%P z$rW|E!y8t!}C?z5$CO1bU?D zu^LNgd=nsv9$9Q+Lv~Yz@lC+;(W4(Y7CoWyO@JhNI1VzJ$yzYoRAGDr7DY(uPlid; zEi5+`8s7v+4!&vX&g}!SZmKZ830Opr1gWrJ1UD5L-vmewzPVCM?M$MZDvWQyLNW|J zM##sm428xw0g~wXTRgi{^5hibn*#^F924mDinD!o;OJQQ4i4t}?54MCHfnr!OGIzJ zZ_0WXPE2p})OK!`@tcl%-|oy2O0P6WC@@P)l6PE-FGYLlTA6=?lha$j+~;`Z8A&^6 z*pqRTVqd16nQrd-#$KbDc7WtX;8NxakS}rc_5Uri{WaeIU%2((`|rIc@MS-NZmLky zQLIuxVtbBPo$Varra~oMfXo?1P8+LzUery$SF$%k4>wPG8xp%~&rO9&x-L^D-SzEuG3YByLlIZ1_8`$SX-Bh8Z3s{Vr;S961GfFoVD(M0w z(aTe2b(vL9H&rOwL_sX|E?u!x@TZm?Zi-Sn|t z-|nmAC0Fz050FHUiX4r#^+dbrqnfRS0upax6v6fxw3|L6rN=E6^vKZj$l^SrqQ{tL zroz%hZaqpj75d%~JzR?_KeX-&lSMAd`V_u5U{OFqkK1_rg2YXQzBd7q@*n+w7O_2_ zDtvDO7SUs7DpDUidP3iu0O_NbWtSHQ7edFsM>kdY-hf2`$^8!+E!!!=O@+QU0g~u3 zfitr+iEgU!y$M)cT6h^uq%mwN^t}m?M33q}i}?Rd6}~rMp%{i9b73NmVN;>+O@Q># zW7?E`Y28f~zBd7j=v8%9=9gB;yIur075d%;NTOG_#4qw;Q25?}g<{y>-qC#>$y$WI bHvy99)jZPJX^O)4CSdvN-WGP|{m=gc+BWJ= literal 86016 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 Wo;#sEo(3FA0auYc__Tkh{eJ`92lFrh diff --git a/server_api/templates/board.html b/server_api/templates/board.html index c08677f..c8f28bb 100644 --- a/server_api/templates/board.html +++ b/server_api/templates/board.html @@ -107,11 +107,11 @@

Input Status

-
    +
      {% for i in range(4) %}
    • Input {{ i + 1 }} - {{ input_status[i] }} + {{ input_status[i] }}
    • {% endfor %}
    @@ -158,7 +158,7 @@ .then(data => { const logList = document.getElementById('log-list'); logList.innerHTML = ''; - data.logs.reverse().forEach(log => { // Reverse the order to show newest first + data.logs.forEach(log => { // Keep the order as newest first const logItem = document.createElement('li'); logItem.className = 'list-group-item'; logItem.innerHTML = `${log.timestamp}: ${log.message}`; @@ -169,6 +169,19 @@ .catch(error => console.error('Error fetching logs:', error)); } + function fetchInputStatus() { + fetch(`/board/{{ hostname }}/input_status`) + .then(response => response.json()) + .then(data => { + for (let i = 0; i < 4; i++) { + const inputStatus = document.getElementById(`input-status-${i + 1}`); + inputStatus.className = `badge badge-${data.input_status[i] === 'on' ? 'success' : 'secondary'}`; + inputStatus.innerText = data.input_status[i]; + } + }) + .catch(error => console.error('Error fetching input status:', error)); + } + function startTimer() { let timer = 5; const timerElement = document.getElementById('timer'); @@ -179,6 +192,7 @@ } else { timer = 5; fetchLogs(); + fetchInputStatus(); } }, 1000); } diff --git a/server_api/templates/index.html b/server_api/templates/index.html index 186214b..048c852 100644 --- a/server_api/templates/index.html +++ b/server_api/templates/index.html @@ -58,9 +58,6 @@

    Status: {{ board.status }}

    Last Seen: {{ board.last_seen }}

    View Details -
    - -
diff --git a/server_api/templates/settings.html b/server_api/templates/settings.html index 592c1c4..4363ad3 100644 --- a/server_api/templates/settings.html +++ b/server_api/templates/settings.html @@ -51,15 +51,15 @@
Set Cleanup Time
-
+
- - + +
- -
-
- + + + +
@@ -91,22 +91,27 @@
Board: {{ board.hostname }}
- - + +
- - + +
- - + +
- - + + +
+
+ + + +
-