Pentru inceput...
Salutare tuturor! Iata ca am gasit ragazul sa sa mai adaug un articol pe blog.
Daca pana acum comenzile pe care le-am trimis Pi-ului erau preluate de la tastatura prin SSH, am zis sa ridic un pic stacheta si sa programez pentru Raspberry un server web. Zis si facut! Cum Python-ul este obiectivul meu pe termen lung am ales ca server o veche cunostinta: Flask-ul. Server-ul este usor de programat iar documentatie se gaseste garla pe internet. Ce a iesit puteti vedea in filmuletul de mai jos:
Si o poza cu realizarea practica:
Aaa si il puteti vedea live la http://84.117.194.136:64000/
username: guest ; password 1234
Cum functioneaza?
Daca sunteti interesati de fenomenul IOT (internet of things) si ati mai configurat ceva servere, fie cu Arduino fie cu NodeMcu, ati observat, probabil, faptul ca este realtiv greu sa realizati o protectie minimala impotriva "vizitatorilor" nepoftiti. Cat timp rulati serverul in reteaua locala nu este nici o problema, insa cand "iesiti" cu el pe internet va puteti trezi ca aerul conditionat porneste din senin sau lumina se stinge si se aprinde haotic. Cu alte cuvinte cineva se joaca la butoanele voastre!
Ceea ce voi prezenta in continuare este o solutie sigura, deoarece permite autentificarea pe baza de user-name si parola.
Cum se face?
Pentru a putea instala cu succes server-ul trebuie sa urmati tutorialele anterioare, pe care evident, le gasiti pe blog.
Fiindca sunt baiat bun voi face public codul ba mai mult va voi explica in cateva cuvinte cum lucreaza pentru a-l putea adapta la nevoile voastre. Sa incepem:
Pe lanaga Python care, impreuna cu libraria Flask, ruleaza pe Raspberry Pi a mai fost necesar sa scriu codul HTML pentru paginile web (cea de logare si cea de control) si sa folosesc un pic de JavaScript impreuna cu Ajax si jQuery. Pheeewww :) !
Daca insiruirea de mai sus pare impresionanta (mi-a mancat ceva din viata :) ), pentru cei care vor sa foloseasca serverul "as it is" este necesara modificarea doar a catorva linii de cod. Chiar este simplu, credeti-ma!
In prezent serverul are implementat controlul GPIO impreuna cu senzorul de temperatura DS18B20. In viitor daca va fi cazul probabil voi mai adauga si alti senzori. Asadar, pentru a adapta codul necesitiatilor voastre tot ce aveti de facut este sa urmati pasii:
2. Descarcati proiectul de aici
- Deschideti o noua sesiune SSH (recomand cu caldura Moba Xterm) cu Raspberry Pi si instalati libraria Flask:
sudo pip3 install flask
3. Creati un folder nou in ' /home/pi/' pe care il puteti denumi, de exmplu, Server_Login
4. In noul folder desarcati proiectul dezarhivat. Din nou MobaXterm este prietenul vostru.
Haideti sa vedem un pic structura folderelor si continutul acestora:
In folderul radacina al proiectului:
Folderul radacina |
-folderul 'static' contine fisierele necesare pentru pagina web: stiluri css de formatare, codul javascript.
Folderul 'static' |
-folderul 'templates' contine codul paginilor web dinamice. Daca va 'bagati nasul' un pic prin ele veti observa ca acesta difera de codul html traditional. Asta deoarece paginile se genereaza in timp real in functie de user (admin sau guest), de numarul de iesiri si de cel de senzori.
Folderul templates |
Dupa cum spuneam si la inceputul articolului, daca doriti sa folositi serverul asa cum este el, nu aveti de modificat absolut nimic in fisierele rezidente in folderele 'static' si 'templates'.
Folderul radacina mai contine doua fisiere: 'flask_server_login.py' si 'ds18b20.py'. Primul reprezinta serverul propriu-zis, iar cel de-al doilea driverul pentru senzorul de temperatura DS18B20.
Codul pe care va trebui sa-l modificati este cel din fisierul 'flask_server_login.py'. Listingul este prezentat mai jos:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 | #https://pythonspot.com/login-authentication-with-flask/ #https://www.tutorialspoint.com/flask/flask_sessions.htm from flask import Flask from flask import Flask, flash, redirect, render_template, request, session, abort import os import time import threading as th import RPi.GPIO as g import ds18b20 as ts time.sleep(20) app = Flask(__name__) #date pentru tabel users=[{'Name':'admin','Pass':'1234','Rank':'admin'}, #users names, passwords, and ranks {'Name':'User','Pass':'1234','Rank':'admin'}, {'Name':'guest','Pass':'1234','Rank':'guest'}] title="PANOU CONTROL RASPBERRY PI" # title of the table switches=(('Priza AC','p0'),('Bec Intrare','p1'),('Releu centrala','p2'),('Masina de spalat','p3'),('Veioza','p4'))# tupple of switches: switch designation and html id sensors = (('S.T. Dormitor','s1'),) # tupple of sensors: sensor name and html id a='enabled' #template prototipe -> html enabled for switches. Only if rank is admin b='disabled' #template prototipe -> html enabled for switches. If rank is guest x=0 y=0 g.setmode(g.BCM) pins=[26,19,13,6,5] status=[0,0,0,0,0] #status array -> contains the status of switches sensorStatus=['-125'] #status of sensors def readTemp(): #read the temperature ds=ts.ds18b20(1) sensorStatus=ds.readTemp() th.Timer(10,readTemp).start() def checkQuerry(a): # check if a valid request was sent. for i in switches: if i[1]==a: return True return False def setStringChange(): # the return string of change request j=0 a="" for i in switches: a=a+i[1]+'_'+str(status[j])+':' g.output(pins[j],status[j]) j=j+1 return a[0:len(a)-1] def setStatusString(): #the return of a status request a=setStringChange() b="" j=0 #for i in switches: #a=a+i[1]+'_'+str(status[j])+':' #j=j+1 #a=a[0:len(a)-1] #j=0 for i in sensors: b=b+i[1]+'_'+str(sensorStatus[j])+':' j=j+1 b=b[0:len(b)-1] return a+"#"+b # return string has following pattern: p0_1:p1_0:p3_0:p4_0#s1_24.5:s2_19.1 @app.before_first_request #before first request initiate the temperature loop def initTemp(): readTemp() os.system('clear') cnt=0 for i in pins: g.setup(i,g.OUT) g.output(pins[cnt],0) cnt=cnt+1 @app.route('/') # def home(): if not session.get('logged_in'): #if there is no field called 'logged_in' redirect the client to login form print('nelogat_home()') return render_template('login.html', mesaj='Ok') if session['rank']=='admin' and session.get('logged_in'): # if the client has already logged in return the control page for admin return render_template('ctrl.html',title=title,switches=switches,sensor=sensors,st=a) if session['rank']=='guest' and session.get('logged_in'): # if the client has already logged in return the control page for guest return render_template('ctrl.html',title=title,switches=switches,sensor=sensors,st=b) @app.route('/login', methods=['POST']) def do_admin_login(): u=request.form['username'] # read the values of fields 'username' and 'password' p=request.form['password'] for us in users: # check if the user is in the dictionary if us['Name']==u and us['Pass']==p and us['Rank']=='admin': session['logged_in'] = True # asign to the session the filed logged_in session['rank']='admin' # asign to the session the filed rank session['name']=u # asign to the session the name of user if us['Name']==u and us['Pass']==p and us['Rank']=='guest': session['logged_in'] = True session['rank']='guest' session['name']='Guest' return home() @app.route("/logout") def logout(): session['logged_in'] = False return home() @app.route('/change',methods=['GET']) #change request def change(): if not session.get('logged_in'): #if only the client is not logged_in return render_template('login.html',mesaj='Nu esti autentificat !') if session.get('logged_in') == True and session.get('rank')=='admin': #if the client has the rank of admin req = request.args.get('change') # read the value of change request if checkQuerry(req): #check if it's a valid request a=int(req.strip('p')) if status[a]==0: status[a]=1 else: status[a]=0 return setStringChange() #return the current status of switches else: session['logged_in']=False #if change request was written by hand logout the user return render_template('login.html',mesaj='Gotcha...') elif session.get('logged_in')==True and session.get('rank')=='guest': #if a guest wants to change the switch from html logg him out session['logged_in']=False return render_template('login.html',mesaj='Autentifica-te ca admin !') @app.route('/status',methods=['GET']) def getStatus(): if not session.get('logged_in'): return render_template('login.html',mesaj='Nu esti autentificat !') elif session.get('logged_in') == True : return setStatusString() if __name__ == "__main__": app.secret_key = os.urandom(12) app.run(debug=True,host='192.168.0.199', port=6000, threaded=True) |
Asadar sa incepem :) :
Crearea userilor se face modificand array-ul de dicitionare users (linia 17). Fiecare user trebuie sa contina un nume, o parola si un privilegiu (rank). Cei care au voie sa modifice starea switch-urilor vor avea rolul de 'admin', iar cei care pot doar vizualiza starea acestora sunt 'guest'. Puteti adauga oricat de multi useri cu conditia sa respecati tiparul din cod, cu alte cuvinte atentie la sintaxa.
Dupa cum am zis crearea paginilor este dinamica, cu alte cuvinte codul html poate fi modificat direct de aici fara a necesita interventia asupra paginii. Variabila 'titlu' va contine informatia necesara pentru afisarea denumirii panoului de comanda. Eu am ales sa se numeasca "PANOU CONTROL RASPBERRY PI", voi puteti sa modificati.
Tupple-urile 'switches' si 'sensors' contin informatiile despre comutatoare si senzori. Prima intrare defineste denumirea senzorului iar cea de-a doua id-ul sau din pagina html. Puteti adauga sau sterge oricat de multe comutatoare, cu conditia sa fiti atenti la sintaxa. Ca ultim cuvant, in cazul in care aveti un singur switch sau senzor virgula de final este obligatorie (vezi cazul de fata tupple-ul sensors)
Cele doua variabile 'a' si 'b', folosite in functie de 'rank' dau sau nu privilegiul de a schimba sau nu starea comutatoarelor.
Mai departe (in linia 33) definim cum va fi numerotarea pinilor. In cazul de fata s-a ales numerotarea fizica la terminalele microprocesorului. In urmatoarea linie se defineste un array care contine numarul pinilor. Array-ul status (linia 36) contine starea initiala a switch-urilor. El trebuie sa contina tot atatea elemente cate cate comutatoare au fost definite. In cazul de fata sunt utilizate 5 comutatoare, deci 'status' contine tot cinci elemente. Analog si pentru sensorStatus.
Astea sunt toate modificarile pe care le aveti de facut.
Acum haideti sa vedem care este functionarea. Flask foloseste un sistem 'decorator', sistem pe care inca nici eu nu il stapanesc prea bine. Din ceea ce stiu va pot spune doar ca in functie de context sunt apelate functiile specifice. In cazul de fata au fost utilizate urmatoarele 'decoratoare':
@app.before_first_request - inainte de a executa primul request se executa functia asociata:
in cazulde fata - def initTemp():
@app.route('/') - primul request atunci cand user-ul acceseaza adresa- se executa functia def home():
@app.route('/login', methods=['POST']) - atunci cand userul trimite datele de autentificare se executa functia def do_admin_login():
@app.route("/logout") - atunci cand userul doreste sa incheie sesiunea - se executa functia def logout():
@app.route('/change',methods=['GET']) - atunci cand userul doreste sa schimbe starea unui comutator - se executa functia def change():
@app.route('/status',methods=['GET']) - atunci cand userul doreste sa fle starea sistemului se apeleaza functia def getStatus():
Cam atat ar fi de zis. Pentru intrebari va rog sa nu sfiiti sa lasati comentarii.
Sa auzim de bine !
E ceva foarte interesant, pentru mine, ce e descris in acest articol. Saint foarte interesant, ca an un proect aproape identic si am nevoe de ajutor contra cost. Daca e posibil as dori sa am contact direct di Dvs.
RăspundețiȘtergereSalut,cum se adauga un senzor nou?am modificat linia 25 dar degeaba ,nu mai afiseaza nici temp de la primul senzor.
RăspundețiȘtergere