Servidor Raspberry Pi com Flask – Final da Estrutra

Neste artigo, iremos terminar o servidor desenvolvido nos posts anteriores, aconselhamos fortemente a visitar os tutoriais anteriores para que possa estar a par dos conceitos abordados e desenvolvidos neste artigo.

Pode consultar os posts anteriores aqui e aqui.

Estrutura do Ambiente
Preview Servidor

Iremos começar por preparar o nosso ambiente com a seguinte estrutura:

  • Pasta: Servidor
    • Ficheiro: run.py
    • Pasta: servidor
      • Ficheiro: __init__.py
      • Ficheiro: config.py
      • Ficheiro: getCuriosidades.py
      • Ficheiro: getNoticias.py
      • Ficheiro: getTempo.py
      • Ficheiro: routes.py
      • Pasta: Templates:
        • Ficheiro: base.html
        • Ficheiro: home.html
        • Ficheiro: noticias.html
        • Ficheiro: sensores.html
      • Pasta: static:
        • Pasta: styles
          • Ficheiro: style.css
Sobre os Ficheiros – Pasta Servidor

run.py
Responsável por inicializar o nosso servidor.

from servidor import app

if __name__ == "__main__":
    app.run(debug = True, host="0.0.0.0", port=80)
__init__.py 
Os ficheiros __init__.py são por norma uma convecção na linguagem python que são executados em primeiro lugar automaticamente.
from flask import Flask

app = Flask(__name__)

from servidor import routes

config.py

Possui dados para configuração do servidor, apenas possui a variável “user” que deve ser substituída pelo nome do utilizador.
def userData():
    userData.user = "Nome"

getCuriosidades.py
Responsável por mostrar uma curiosidade aleatória sempre que a página principal é aberta.

import randfacts
from googletrans import Translator

def getCuriosidade():
    fact = randfacts.get_fact(filter_enabled=filter)
    translator = Translator()
    fact_pt = translator.translate(fact, dest="pt").text
    fatosDic = {"Fact_PT":fact_pt, "Fact_EN": fact}
    print(fatosDic)
    return fatosDic

getNoticias.py
Responsável por fazer webscraping as noticias mais recentes – A criação e explicação deste ficheiro foi desenvolvida aqui.

import os

try:

from bs4 import BeautifulSoup as sp

except:

print("Necessario Instalar BS4")

print("Execute o seguinte comando:")

print(" python3 -m pip install bs4")

print("E aguarde a instalação do pacote")

import requests

def getNoticias():

arrCategoriasJN = ['nacional','justica', 'mundo', 'economia','desporto', 'inovacao', 'artes', 'opiniao']

arrNoticias = []

firstTime = True

for cat in arrCategoriasJN:

if firstTime == True:
active = "active"
firstTime = False
else:
active = ""

linkNoticia = f"https://www.jn.pt/{cat}.html"
r = requests.get(linkNoticia)
soup = sp(r.content, "html.parser")

mainDivNoticia = soup.find(class_ = "t-grid-1-featured-2")
tituloNoticia = mainDivNoticia.find("h2").text
linkNoticia = "https://www.jn.pt" + str(mainDivNoticia.find( 'h2').find('a')['href'])
linkImagemNoticia = mainDivNoticia.find('figure').find('source')['data-srcset']
descNoticia = mainDivNoticia.find('h4').text

noticia = {"titulo":tituloNoticia, "link":linkNoticia, "desc":descNoticia, "img":linkImagemNoticia, "actv":active} 
arrNoticias.append(noticia)

return arrNoticias

getTempo.py
Responsive por fazer webscrape as previsões meteorológicas dos próximos 3 dias em função da sua localidade.

from bs4 import BeautifulSoup as sp
from datetime import date
import requests
import geocoder

def getMetereologia():
myLocation = geocoder.ip('me').city

myLocationFormatted = myLocation.replace(" ", "")
site = f"https://www.weatheronline.pt/Portugal/{myLocationFormatted}.htm"
r = requests.get(site)
soup = sp(r.content)
arrayPrevisoes = []

table = soup.find("table")

for x in range(1,4):
data = table.find_all("tr")[0].find_all("td")[x].text
data_min = table.find_all("tr")[1].find_all("td")[x].text
data_max = table.find_all("tr")[2].find_all("td")[x].text
#Data 1 Manha
data_manha_img_link = table.find_all("tr")[4].find_all("td")[x].find("img")['src']
data_manha_img_title = table.find_all("tr")[4].find_all("td")[x].find("img")['title'].replace(" ", " ").replace(" , ", ", ").capitalize()
#Data 1 Tarde
data_tarde_img_link = table.find_all("tr")[5].find_all("td")[x].find("img")["src"]
data_tarde_img_title = table.find_all("tr")[5].find_all("td")[x].find("img")["title"].replace(" ", " ").replace(" , ", ", ").capitalize()
#Data 1 Noite
data_noite_link = table.find_all("tr")[6].find_all("td")[x].find("img")["src"]
data_noite_title = table.find_all("tr")[6].find_all("td")[x].find("img")["title"].replace(" ", " ").replace(" , ", ", ").capitalize()

temperatura = {"cidade":myLocation, "data":data, "temp_max":data_max, "temp_min":data_min,"data_manha_img_link":data_manha_img_link, 
"data_manha_img_title":data_manha_img_title, "data_tarde_img_link":data_tarde_img_link, 
"data_tarde_img_title":data_tarde_img_title, "data_noite_link":data_noite_link, 
"data_noite_title":data_noite_title}

arrayPrevisoes.append(temperatura)

return arrayPrevisoes

routes.py
Ficheiro que possui os diretórios de todo o nosso servidor

from flask import render_template, url_for, request
from servidor import app
from servidor.getNoticias import getNoticias
from servidor.sendRequest import sendRequest
from servidor.getCuriosidade import getCuriosidade
from servidor.getTempo import getMetereologia
from servidor.config import userData
import socket

userData()

@app.route("/")
def homepage():
username = userData.user
dadosTempo = getMetereologia()
randomFact = getCuriosidade()
return render_template("home.html", username = username, 
dadosTempo = dadosTempo, randomFact = randomFact)

@app.route("/noticias")
def noticias():
noticias = getNoticias()
return render_template("noticias.html", noticias = noticias)

@app.route("/sensores")
def sensores():
return render_template("sensores.html")


#Interpretar Requests

#Quarto
#Quarto Luz Principal
#Ligar
@app.route("/ligar_Luz", methods=['POST'])
def ligar_LuzQuarto ():
if request.method == "POST":
requestType = "ligar_Luz"
sendRequest("IP", requestType)
return render_template("sensores.html")

@app.route("/desligar_Luz", methods=['POST'])
def desligar_LuzQuarto ():
if request.method == "POST":
requestType = "desligar_Luz"
sendRequest("IP", requestType)
return render_template("sensores.html")

#Quarto Luz Candeeiro
#Ligar
@app.route("/ligar_LuzCandeeiro", methods=['POST'])
def ligar_LuzCandeeiro():
if request.method == "POST":
requestType = "ligar_LuzCandeeiro"
sendRequest("IP", requestType)
return render_template("sensores.html")
#Desligar
@app.route("/desligar_LuzCandeeiro", methods=['POST'])
def desligar_LuzCandeeiro():
if request.method == "POST":
requestType = "desligar_LuzCandeeiro"
sendRequest("IP", requestType)
return render_template("sensores.html")

#Sala
#Luz Sala de Estar
#Ligar
@app.route("/ligar_LuzSaladeEstar", methods=['POST'])
def ligar_LuzSaladeEstar():
if request.method == "POST":
requestType = "ligar_LuzSaladeEstar"
sendRequest("IP", requestType)
return render_template("sensores.html")
#Desligar
@app.route("/desligar_LuzSaladeEstar", methods=['POST'])
def desligar_LuzSaladeEstar():
if request.method == "POST":
requestType = "desligar_LuzSaladeEstar"
sendRequest("IP", requestType)
return render_template("sensores.html")

#Luz Sala de Jantar
#Ligar
@app.route("/ligar_LuzSaladeJantar", methods=['POST'])
def ligar_LuzSaladeJantar():
if request.method == "POST":
requestType = "ligar_LuzSaladeJantar"
sendRequest("IP", requestType)
return render_template("sensores.html")

@app.route("/desligar_LuzSaladeJantar", methods=['POST'])
def desligar_LuzSaladeJantar():
if request.method == "POST":
requestType = "desligar_LuzSaladeJantar"
sendRequest("IP", requestType)
return render_template("sensores.html")

@app.route("/ligar_FichaCarregamento", methods=['POST'])
def ligar_FichaCarregamento():
if request.method == "POST":
requestType = "ligar_FichaCarregamento"
sendRequest("IP", requestType)
return render_template("sensores.html")

@app.route("/desligar_FichadeCarregamento", methods=['POST'])
def desligar_FichadeCarregamento():
if request.method == "POST":
requestType = "desligar_FichadeCarregamento"
sendRequest("IP", requestType)
return render_template("sensores.html")
Sobre os Ficheiros – Pasta Templates

Base.html
Ficheiro template base que será utilizado nas restantes páginas do nosso servidor como estrutura básica

<!DOCTYPE html>
<html charset="utf-8" lang="pt">
<head>
<title>{% block title %}{% endblock %}</title>
<!--Link Documento CSS-->
<link rel="stylesheet" type="text/css" href= "{{ url_for('static',filename='styles/style.css') }}">

<!-- CSS only -->
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

<!--BootStrap 4-->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">

<!--Google Fonts-->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Glory&display=swap" rel="stylesheet">

<link rel="icon" href="https://www.electrofun.pt/img/favicon.ico?1528822022" >
</head>
<body>
<!--Barra de Nav.-->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<a class="navbar-brand" href="{{ url_for('homepage') }}">
<img id="logo" src="https://www.electrofun.pt/img/favicon.ico?1528822022" width="18px" height="20px">
Servidor Local
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item hover-menu">
<a class="nav-link {% block homepageActive %} {% endblock %}" href="{{ url_for('homepage') }}">Home</a>
</li>
<li class="nav-item hover-menu">
<a class="nav-link {% block noticiasActive %} {% endblock %}" href="{{ url_for('noticias') }}">Noticias</a>
</li>
<li class="nav-item hover-menu">
<a class="nav-link {% block sensoresActive %} {% endblock %}" href="{{ url_for('sensores') }}">Casa</a>
</li>
<li class="nav-item hover-menu">
<a class="nav-link" target="_blank" href="https://www.electrofun.pt/">ElectroFun</a>
</li>
<li class="nav-item hover-menu">
<a class="nav-link" target="_blank" href="https://www.electrofun.pt/blog/">+ Projetos</a>
</li>
<li class="nav-item hover-menu">
<a class="nav-link" target="_blank" href="https://www.instagram.com/electrofun.pt/">Instagram</a>
</li>
<li class="nav-item hover-menu">
<a class="nav-link" target="_blank" href="https://www.youtube.com/channel/UCwD7RHUJu6z1WX8PRb99iYQ">Youtube</a>
</li>
</ul>
</div>
</nav>

{% block content %}
{% endblock %}

<style>
body{
background-color:#2a2c2e;
color:rgb(236, 236, 236);
}

.active{
color: #e7b851 !important;
}

#logo{
padding-bottom:1%;
}

a:hover {
color:#ddb867 !important;
}
</style>
</body>
</html>
    <style>
        body{
          background-color:#2a2c2e;
          color:rgb(236, 236, 236);
        }
        .active{
          color: #e7b851 !important;
        }
        #logo{
          padding-bottom:1%;
        }
        a:hover {
          color:#ddb867 !important;
        }
    </style>
  </body>
</html>

home.html

Ficheiro html da página principal.
{% extends "base.html" %}
{% block title %}
HomePage
{% endblock %}
{% block homepageActive %} active {% endblock %}
{% block content %}

<div class = "container">
<div class = "row">
<div class="col-lg">
<h2 style = "padding-top: 2%;">Olá <span style="color:#ddb867;">{{username}}</span></h2>
</div>
</div>
</div> 
<div class = "container">
<div class = "row">
{%for prev in dadosTempo%}
<div class = "col-lg" style="width: 25%; padding-top:2%;">
<div class="card bg-dark text-white" style="border-radius: 5%;">
<div class="card-header text-center" style="color:#e7b851;">
<strong>Previsão {{prev.data}}, {{prev.cidade}}</strong>
</div>
<div class="card-body text-left">
<p class="card-title">Temperatura Máxima: {{prev.temp_max}} </p>
<p class="card-title">Temperatura Mínima: {{prev.temp_min}} </p>
<p class="card-text" style="padding-top:2%;">
<table class="table text-center" >
<thead class = "thead-dark" >
<tr>
<th scope="col">Manhã</th>
<th scope="col">Tarde</th>
<th scope="col">Noite</th>
</tr>
</thead>
<tbody>
<tr>
<td><img id = "tempoImagem" src="{{prev.data_manha_img_link}}" alt="{{prev.data_manha_img_title}}" title = "{{prev.data_manha_img_title}}"></td>
<td><img id = "tempoImagem" src="{{prev.data_tarde_img_link}}" alt="{{prev.data_tarde_img_title}}" title = "{{prev.data_tarde_img_title}}"></td>
<td><img id = "tempoImagem" src="{{prev.data_noite_link}}" alt="{{prev.data_noite_title}}" title = "{{prev.data_noite_title}}"></td>
</tr>
</tbody>
</table>
</p>
</div>
</div>
</div>
{%endfor%}
</div>
</div>
<div class = "container">
<div class = "row">
<div class="col-lg">
<div class="card bg-dark text-white text-center " style="margin-top:3%; border-radius:5%; max-width: 100%; min-width: 100%;">
<div class="card-header" style="color:#e7b851">
<strong>Curiosidade</strong>
</div>
<div class="card-body">
<h5 class="card-title">{{randomFact.Fact_PT}}</h5>
<p class="card-text" style="color:rgb(167, 167, 167)">{{randomFact.Fact_EN}}</p>
</div>
</div>
</div>
</div>
</div>
<style>
#tempoImagem{
margin-top:20%;
filter: brightness(0.80);
}
</style>

{% endblock %}

noticias.html
Template que constitui um carrousel fornecido pelo Bootstrap com as noticias.

{% extends "base.html" %}

{% block title %}
Noticias
{% endblock %}

{% block noticiasActive %} active {% endblock %}

{% block content %}
<section id="news">
<h2 id="tituloSection">Últimas Notícias </h2>
<div id="carouselExampleControls" class="carousel slide" data-ride="carousel">
<div class="carousel-inner">

{% for item in noticias %}
<div class="carousel-item {{item.actv}}">
<img class="news-img rounded-circle news-img" src="{{item.img}}" alt="noticia">
<h2 id="tituloNoticia">{{item.titulo}}</h2>
<p id="descricaoNoticia">{{item.desc}}</p>
<a class="linkContinuarLer" href="{{item.link}}">Continuar a Ler</a>
</div>
{% endfor %}

<a class="carousel-control-prev" href="#carouselExampleControls" role="button" data-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="sr-only">Previous</span>
</a>
<a class="carousel-control-next" href="#carouselExampleControls" role="button" data-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="sr-only">Next</span>
</a>
</div>
</section>

{% endblock %}

sensores.html

Página com vários formulários que num futuro tutorial iram efetuar a comunicação com NodeMCU/Esp8266-Esp-01 de forma a poder controlar os equipamentos da sua casa.

{% extends "base.html" %}

{% block title %}
Sensores Locais
{% endblock %}

{% block sensoresActive %} active {% endblock %}

{% block content %}
<div style ="padding-top:2%;color:#e7b851; padding-left:5%;">
<h3>Quarto Principal</h3>
</div>
<div class="row" style ="padding-top:2%; padding-left:5%; padding-right:5%;">
<div class="col-sm-3">
<div class="card border-0">
<div class="card-body">
<form class="text-center" method = "post">
<h5 class="card-title">Luz Principal</h5>
<input type = "submit" class="btn btn-secondary" value = "Ligar" formaction="/ligar_Luz">
<input type = "submit" class="btn btn-secondary" value = "Desligar" formaction="/desligar_Luz">
</form>
</div>
</div>
</div>
<div class="col-sm-3">
<div class="card border-0">
<div class="card-body">
<form class="text-center" method = "post">
<h5 class="card-title">Luz Candeeiro</h5>
<input type = "submit" class="btn btn-secondary" value = "Ligar" formaction="/ligar_LuzCandeeiro">
<input type = "submit" class="btn btn-secondary" value = "Desligar" formaction="/desligar_LuzCandeeiro">
</form>
</div>
</div>
</div>
</div>

<div style ="padding-top:2%;color:#e7b851; padding-left:5%;">
<h3>Sala</h3>
</div>
<div class="row" style ="padding-top:2%; padding-left:5%; padding-right:5%;">
<div class="col-sm-3">
<div class="card border-0">
<div class="card-body">
<form class="text-center" method = "post">
<h5 class="card-title">Luz Sala de Estar</h5>
<input type = "submit" class="btn btn-secondary" value = "Ligar" formaction="/ligar_LuzSaladeEstar">
<input type = "submit" class="btn btn-secondary" value = "Desligar" formaction="/desligar_LuzSaladeEstar">
</form>
</div>
</div>
</div>
<div class="col-sm-3">
<div class="card border-0">
<div class="card-body">
<form class="text-center" method = "post">
<h5 class="card-title">Luz Sala de Jantar</h5>
<input type = "submit" class="btn btn-secondary " value = "Ligar" formaction="/ligar_LuzSaladeJantar">
<input type = "submit" class="btn btn-secondary" value = "Desligar" formaction="/desligar_LuzSaladeJantar">
</form>
</div>
</div>
</div>
<div class="col-sm-3">
<div class="card border-0">
<div class="card-body">
<form class="text-center" method = "post">
<h5 class="card-title">Ficha Sala</h5>
<input type = "submit" class="btn btn-secondary" value = "Ligar" formaction="/ligar_FichaCarregamento">
<input type = "submit" class="btn btn-secondary" value = "Desligar" formaction="/desligar_FichadeCarregamento">
</form>
</div>
</div>
</div>
</div>
<style>
.card-body{
background-color: #343a40;
color:rgb(236, 236, 236);
}

.btn-secondary:hover{
background-color:#c0a469!important;
}

</style>

{% endblock %}

Pasta Styles

styles.css
Responsável por fazer algumas alterações a nível gráfico do nosso servidor.

/* Estilos NavBar*/
.navbar{
font-family: 'Glory', sans-serif;
}

.nav-item {
padding: 0 18px;
}

.nav-link {
font-size: 1.2rem;
font-family: 'Glory', sans-serif;
}

/*Estilos de noticias.htmk*/
#tituloSection {
padding-top:10px;
color: #ffffff;
font-size: 26px;
font-family: 'Glory', sans-serif;
}

/* Titulo Noticia*/
#tituloNoticia {
color: #e7b851;
text-align: center;
font-size: 24px;
font-weight: bold;
width: 50%;
margin-left: 24%;
font-family: 'Glory', sans-serif;
}

/*Desc. Noticia*/
#descricaoNoticia {
color: #ffffff;
}

.linkContinuarLer {
color: #ececec; 
}

.linkContinuarLer:hover {
color: #ffffff;
font-style: none;
font-weight: bold;
text-decoration: none;
}
/*Containers*/

#news {
position: static;
padding: 0 15% 0 15%;
text-align: center;
}

#news p {
text-align: center;
font-size: 16px;
width: 50%;
margin-left: 24%;
position: static;
}

.news-img {
margin: 15px;
margin-bottom: 30px;
}
Conclusão

Como mencionado nos tutoriais anteriores, aquilo que estamos a ilustrar é apenas uma forma de efetuar este projeto, o HTML pode ser modificado ao seu gosto de forma a ficar mais do seu grado. Pode também adicionar outras páginas ao ficheiro routes.py de forma a criar outras páginas que ache interessantes.

Para mais projetos, percorram o nosso blog, onde podem encontrar vários artigos interessantes relacionados com eletrónica, robótica e muito mais! Visitem também o nosso site, onde encontram tudo para eletrónica e robótica!