---
header-includes:
- '`\usepackage{natbib}`{=latex}'
- '`\usepackage{longtable,booktabs}`{=latex}'
- '`\usepackage{amsmath,amssymb}`{=latex}'
- '`\usepackage{lmodern}`{=latex}'
- '`\usepackage{minted}`{=latex}'
- '`\usepackage{parskip}`{=latex}'
- '`\title{Memòria Projecte Techlab - Grup E}`{=latex}'
- '`\assignatura{Integració de Sistemes}`{=latex}'
- '`\numpract{Memòria Projecte Techlab - Grup E}`{=latex}'
- '`\autor{Artur Blaya, Llorenç Garcia \& Andreu Villaró}`{=latex}'
- '`\usepackage{fvextra}`{=latex}'
- '`\DefineVerbatimEnvironment{Highlighting}{Verbatim}{breaklines,commandchars=\\\{\}}`{=latex}'
numbersections: true
---
###### tags: `IS`
# Front End
A continuació s'explica com s'ha desenvolupat la part de l'aplicació que correspon al front-end, considerant front-end com l'aplicació amb la qual acaba interactuant l'usuari.
## Decisions de disseny
### Selecció Framework
S'ha decidit realitzar aquest aplicatiu amb el framework de javascript React JS. S'ha triat aquest framework ja que és el més utilitzat en l'actualitat, i volíem desenvolupar l'aplicació al voltant de tecnologies punteres.
{height=200px}
### Comunicació Backend
La comunicació del frontend, amb el backend, es fa mitjançant sol·licituds https. S'ha escollit aquest tipus de comunicació gràcies als avantatges d'integració que comporta i la seguretat de les comunicacions que inclou.
### Desplegament
El frontend està desplegat en un servidor d'heroku. Hem decidit contractar aquest servidor, per la facilitat de desplegament, i el rendiment que ofereix.
## Estructura
El projecte s'ha dut a terme amb el framework de javascript ***React JS***, per la qual cosa des del primer moment hem muntat el codi sobre una estructura inicial.
### Estructura inicial
L'**estructura inicial** consta de:
* /public - Conté l'index.html sobre el qual, el framework insereix el contingut html que es genera automàticament.
* /node_modules - Conté els packets externs (dependències)
* package.json - Conté una llista de totes les dependències del projecte
### Estructura final
A l'estructura inicial li hem afegit els següents components:
* src - Conté el codi font
* /Animations - Conté les animacions que utilitzen alguns components
* /components - conté la part visual dels components ( estructura JSX )
* /context - Conté els components que defineixen els estats globals del projecte
* /hooks - Conté els hooks personalitzats del projecte. La part lògica dels components
* /Img - Conté les imatges del projecte
* /pages - Conté les pàgines del projecte.
* /scss - Conté la configuració global de l'estil
* App - Conté l'encaminador
* index - Munta el component app
* .env - Conté variables d'entorn
{height=200px}
#### Components
Cada component està format per:
* Un archiu *jsx*, el qual defineix l'estructura del component
* Un archiu *scss*, el qual defineix l'estil del component.
{height=50px}
A continuació hi ha un exemple:
**Banner/Banner.jsx**
```
import React from 'react'
import styles from './Banner.module.scss'
import banner from './banner.jpg'
export default function Banner() {
return (
<div className={styles['container']}>
<img src={banner} alt="baner" />
<div className={styles['container__div']}>
<h1 className={styles['container__div--h1']}>TECHLAB UPC</h1>
<p className={styles['container__div--p']}>SISTEMA DE RESERVES</p>
</div>
</div>
)
}
```
**Banner/Banner.module.scss**
```css
.container {
font-family: var(--font-montserrat);
font-weight: 300;
width: 100%;
position: relative;
&__div {
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 100%;
display: flex;
&--h1 {
color: white;
font-size: clamp(1em, 4vw, 4em);
align-self: center;
text-align: center;
width: 50%;
}
&--p {
font-size: clamp(0.5em, 2vw, 1.5em);
color: white;
width: 50%;
text-align: end;
align-self: flex-end;
padding-bottom: 1em;
padding-right: 2em;
}
}
}
```
La idea és que els components es puguin reutilitzar en diverses *Pàgines*, és a dir, són importables en altres components, un nombre de cops il·limitats, sense mantenir cap mena de relació.
Per tal d'usar el component de l'exemple anterior, només caldria importar-lo de la següent manera:
```
<Banner/>
```
#### Hooks
Els Components en react disposen d'un o més estats. Els estats són variables que react utilitza per poder detectar canvis en els components, i poder-los rerenderitzar. Quan un dels estats d'un component canvia, el component es renderitza de nou.
Els components importen la part lògica de la carpeta *hooks*. La lògica vindria a ser el conjunt de funcions que fan canviar l'estat del component.
Se separa la part lògica de l'estilística per tal d'aconseguir un codi més escalable.
#### Context
Els Context són un tipus de components que s'utilitzen per fer accessibles els estats globals. Tots els components que es troben dins d'aquest tipus de component, tenen accés als estats que es defineixin en aquest.
En aquest projecte s'ha fet el següent muntatge:
1. Es defineix un Context en la carpeta de /context
2. S'importa el context definit, al component /context/GlobalContext
3. Es crea un hook dins de la carpeta hooks/context/ que facilita la importació de l'estat que comparteix el context
4. s'usa el hook de context en els components que necessitin accés a l'estat global
El component */context/GlobalContext* és un Context que importa tots els contexts.
```js
export default function GlobalContextProvider({ children }) {
return (
<LoadingContextProvider>
<LoginContextProvider>
<LateralContextProvider>
<ErrorContextProvider>
{children}
</ErrorContextProvider>
</LateralContextProvider>
</LoginContextProvider>
</LoadingContextProvider>
)
}
```
Tots els components que estan dins d'aquest ({children}), tindran accés a tots els estats globals. Per tal que tota l'aplicació tingui aquests estats, el component principal **App**, importa aquest component:
```js
function App() {
return (<div
className={styles['main']}
>
<GlobalContextProvider>
...
</GlobalContextProvider>
</div >
)
}
export default App
```
#### Pages
Les pages es diferencien amb els components, pel fet que aquestes no poden ser incloses en altres pages. Un exemple de page seria la pàgina de lading, és a dir la pàgina d'inici.
**Landing/Landing.jsx**
```js
import React from 'react'
import Historics from '../../components/Historics/Historics'
import Machines from '../../components/Machines/Machines'
import styles from './Landing.module.scss'
export default function Landing() {
return (
<div className={styles['container']}>
<Machines />
<Historics />
</div>
)
}
```
{height=200px}
Com es pot observar, la pàgina *Landing* mostra els components Machines i Històrics. Si ens fixem en la pàgina d'inici de la web, podem veure que apareixen els components de les màquines i els històrics.
#### App
En l'archiu App.jsx hi ha l'encaminador de l'aplicació.
```js
function App() {
return (<div
className={styles['main']}
>
<GlobalContextProvider>
<BrowserRouter>
<Navbar />
<Banner />
<Error />
<Lateral />
<Routes>
<Route path="/" element={<Landing />} />
<Route path="/machine/:name/:terminal_id" element={<Machine />} />
<Route path="/login" element={<LoginRedirect />} />
<Route path="/reservations/future" element={<FutureReservations />} />
<Route path="/reservations/now" element={<PresentReservations />} />
<Route path="/reservations/past" element={<PastReservations />} />
<Route path="/admin" element={<Admin />} />
<Route path="*" element={<Landing />} />
</Routes>
<Footer />
</BrowserRouter>
</GlobalContextProvider>
</div >
)
}
export default App
```
Com es pot observar, dins del component encaminador *BrowserRouter*, hi ha diversos components, i també hi ha rutes. Pel que fa als components, són aquells components que es renderitzaran sempre, sense dependre de la ruta en la qual es trobi el client. Pel que fa a les rutes, defineixen quina pàgina es renderitzarà segons la ruta en la qual es trobi el client.
Per defecte, la pàgina que es renderitza sempre és la Landing.
##### Scss
La carpeta scss conté la configuració global de l'estil. Aquesta carpeta està formada per dues subcarpetes:
* abstracts
* _mixins.scss - Conté funcions scss
* _variables.scss - Conté variables css
* base
* _base.scss - Conté configuracions dels elements body i html
* _resets.scss - reinicia l'estil dels elements html per tal d'aconseguir que l'estil de la web es vegi igual en tots els navegadors
## Components
A continuació s'explica la funció de ser de cada component.
{height=200px}
### Banner
És el banner de la pàgina. Apareix en totes les rutes de l'aplicació. La seva funció és estètica.
### Footer
Defineix el footer de la pàgina. Apareix en totes les rutes de l'aplicació. La seva funció és estètica.
### Navbar
Defineix el navbar de la pàgina. Apareix en totes les rutes de l'aplicació. Conté els components de Logo, i Login, i té accés a l'estat del menú lateral.
### Logo
Component que representa el logo de l'epsem. Quan és clicat et redirigeix a la ruta d'inici.
### Login
Té accés a l'estat global de login, i username. Quan l'estat login és true, mostra el username, juntament amb un botó per fer logout.
Quan l'estat de login és fals, mostra un botó per fer login.
### Lateral
És el menú lateral. Té un llistat de botons que representen enllaços per realitzar accions. Té accés a l'estat global de la barra lateral. Quan aquest estat és true, es mostra la barra lateral. Del contrari, el component no es renderitza.
### Section
Defineix l'estructura i l'estil de les seccions que apareixen en la pàgina Landing.
{height=200px}
### Box
Són el component que fa de subsecció en el component secció.
{height=200px}
### Històrics
Agrupa dins d'un component secció tres components Box que et porten a les pàgines dels històrics.
### Màquines
Agrupa dins d'un component secció tres components Box que et porten a les pàgines de les Màquines.
### Error
Tots els errors de l'aplicació es gestionen de la mateixa manera. El component error té un estat global que quan és diferent a null, provoca que es mostri per pantalla una secció d'error. D'aquesta manera, quan volem fer saltar un error, posem aquest estat al text que volem que surti l'error.
{height=200px}
Per tornar a deixar l'estat a null, i que no es mostri l'error, cal prémer la creueta.
## Pages
A continuació s'expliquen les diverses pàgines.
### Landing
Mostra Els components Màquines (Machines) i Històrics (Històrics)
{height=200px}
### Machine
És la pàgina que s'utilitza per reservar una terminal. Consta del nom de la màquina, d'un calendari per poder seleccionar una data (posterior a l'actual), i les hores disponibles, i un botó per efectuar la reserva.
{height=200px}
Disposa d'un estat que es diu reservation. Aquest estat és modificat un hook anomenat useReservations, que s'encarrega de ser la petició de la reserva i retornar la informació de la reserva mitjançant aquest estat.
Quan l'estat reservation no és null, es mostra la reserva.
{height=200px}
### LoginRedirect
Quan es fa la sol·licitud de login, si tot va bé, el backend ens redirigeix a una ruta on es troba aquesta pàgina. Aquesta pàgina només té la funció enviar un missatge entre finestres, per tal que en la finestra des de la qual es crida el login, es rebi aquest missatge i es canviï l'estat global de login a true. Aquestes accions es fan des del hook de useLogin.
### PastReservations
Mostra les reserves passades.
{height=200px}
### PresentReservations
Mostra les reserves actuals. Hi ha l'opció d'activar reserva.
{height=200px}
### FutureReservations
Mostra les reserves futures. Hi ha l'opció de cancel·lar reserva.
{height=200px}
### Admin
Mostra totes les opcions que té l'admin per tal de modificar la base de dades. Fa peticions al backend mitjançant crides a funcions d'un hook anomenat useAdmin.
{height=200px}
## Context
Defineixen els components que proveeixen estats globals.
A continuació hi ha un exemple:
**/context/ErrorContext.jsx**
```js
import React, { useState } from 'react'
export const ErrorContext = React.createContext()
export default function ErrorContextProvider({ children }) {
const [error, setError] = useState(false)
return (
<ErrorContext.Provider value={{ error, setError }}>
{children}
</ErrorContext.Provider>
)
}
```
La constant ErrorContext s'utilitzarà per accedir als elements que estiguin dins del paràmetre value, del context provider. Aquests elements seran accessibles des dels components que es trobin dins del component provider.
{height=100px}
Tots els contexts tenen la mateixa estructura, el que els diferencia són els elements que proporcionen (estats).
A continuació s'explicarà quins estats es creen en cada Context.
### ErrorContext
Proporciona l'estat global *error*, utilitzat per mostrar errors per pantalla.
### LateralContext
Proporciona l'estat global *bool*, usat per mostrar o ocultar el menú lateral.
### LoadingContext
Proporciona l'estat *loading*. Aquest estat està preparat per ser usat, però no s'ha implementat. Està pensat per una possible ampliació del projecte.
La idea és poder posar aquest estat a true quan es faci una crida a una funció asíncrona, i posar-lo a false quan hi hagi resultat. D'aquesta manera es podria expressar que algo està carregant-se de la mateixa manera sempre.
### LoginContext
Proporciona els estats:
* Login - representa si l'usuari està logejat
* lvl - representa el nivell de l'usuari
* username - representa l'username de l'usuari
### GlobalContext
Importa tots els contexts, i retorna un provider amb el qual es pot accedir a tots els estats de cada context.
## Hooks
### Context
S'utilitzen per poder importar fàcilment els estats que proveeixen els contextos.
El hook useErrorContext obté l'estat error mitjançant l'ErrorContext. Retorna aquest estat. Fent això aconseguim que tots els components que vulguin importar aquest estat, no hagin d'importar en la funció useContext de react, ni el component ErrorContext.
```js
import { useContext } from 'react'
import { ErrorContext } from '../../context/ErrorContext'
function useErrorContext() {
const { error, setError } = useContext(ErrorContext)
return (
{ error, setError }
)
}
export default useErrorContext
```
Per tal d'importar l'estat error, usant useErrorContext, simplement cal:
```jsx
import useErrorContext from './context/useErrorContext'
...
const { error, setError } = useErrorContext()
```
Cada context te el seu *use+NomContext+Context.js*.
### UseAdmin
Conté les funcions relacionades amb la pàgina d'Admin.
Té accés als estats:
* error
* login
* lvl
Té accés a les funcions:
* oauthGoogle de useLogin()
Conté les funcions:
* redirectAdmin - Si l'usuari està logejat, i te el nivell d'admin (lvl = 10), redirigeix a /admin. En cas que l'usuari no estigui logejat, li obra la finestra per logejar-se amb google.
* addMachine - Fa una petició per tal d'afegir una màquina a la base de dades
* deleteMachine - Fa una petició per tal d'eliminar una màquina a la base de dades
* addTerminal - Fa una petició per tal d'afegir un terminal a la base de dades
* deleteTerminal - Fa una petició per tal eliminar un terminal a la base de dades
* bind - Fa una petició per tal d'unir una màquina amb un terminal a la base de dades
* addUser - Fa una petició per tal d'afegir un usuari a la base de dades
* deleteUser - Fa una petició per tal d'eliminar un usuari a la base de dades
* updateUser - Fa una petició per tal d'actualitzar un usuari a la base de dades
* terminalSetPermission - Fa una petició per tal de canviar el nivell de permís d'un terminal a la base de dades
### useCalendar
Conté les funcions relacionades amb el calendari de la pàgina de Machine.
Conté les funcions:
* getMachineDate - Obté el dia en format ISO de l'input *machineCalendar*.
* resetMachineDate - reinicia la data de l'input *machineCalendar*.
### useHistorics
Conté les funcions relacionades amb les pàgines dels històrics
Té accés als estats:
* error
* login
* lvl
Té accés a les funcions:
* oauthGoogle de useLogin()
Conté els estats:
* reservations - conté les reserves futures / presents / pasades, depenent de la pàgina en la que es trobi l'usuari
Conté les funcions:
* redirect - Si l'usuari està logejat, redirigeix a /reservation + time, on time és el parametre de la funció, i pot ser future, past o present. En cas que l'usuari no estigui logejat, li obre la finestra per logejar-se amb google.
* getPastReservations - actualitza asíncronament l'estat de reservations.
* getFutureReservations - actualitza asíncronament l'estat de reservations.
* getCurrentReservations - actualitza asíncronament l'estat de reservations.
* deleteReservation - Fa una petició al backend per tal d'eliminar una reserva, a partir del reservation_id
* setCode - Fa una petició al backend perquè el terminal mostri per pantalla el codi d'activació. Es necessita el reservation_id
* activate - Fa una petició al backend per enviar-li el codi de reserva que apareix al terminal, i poder activar la reserva.
### useHours
Conté les funcions relacionades amb les hores de la pàgina de Machine.
Té accés als estats:
* error
Conté els estats:
* hours - conté les hores disponibles d'un terminal en una data en concret.
Conté les funcions:
* getAvaliableHours - actualitza l'estat hours amb les hores disponibles. Depèn d'una data en format ISO i d'un terminal_id.
### useLogin
Conté les funcions relacionades amb el login.
Té accés als estats:
* login
Conté les funcions:
* oauthGoogle - afegeix un eventListener amb la funció loginACK. Obra una finestra amb un get a l'enllaç *urlBackend/oauth/google*
* loginAck - Espera la rebuda d'un missatge entre finestres de tipus {login: true}. Quan el rep, desactiva l'eventListener i posar l'estat login a true.
* logout - Fa una petició al backend per fer logout.
Aquest mòdul crida les funcions useUsername() i useLvl(). Aquestes funcions tenen un useEffect, amb la dependència del login, per tal que quan aquest estat canviï, s'executi el codi del useEffect. La crida a aquestes funcions està per tal d'inicialitzar els useEffects.
### useLvl
Es crida des del useLogin per tal de ser inicialitzat. Té accés a l'estat login. Quan aquest estat canvia a true, és a dir, l'usuari ja està logejat, es fa una petició a *urlBackend/user/permission*, per tal d'actualitzar l'estat global lvl.
### useUsername
Es crida des del useLogin per tal de ser inicialitzat. Té accés a l'estat login. Quan aquest estat canvia a true, és a dir, l'usuari ja està logejat, es fa una petició a *urlBackend/user/username*, per tal d'actualitzar l'estat global username.
### useMachines
Conté les funcions relacionades amb les màquines de la pàgina Landing. Aquest hook té un useEffect amb la dependencia lvl, és a dir, a l'inici i quan l'estat lvl canvia, s'executa un codi. Aquest codi és el que carrega les màquines disponibles en l'estat machines.
Té accés als estats:
* error
* lvl
* login
Té accés a les funcions:
* oauthGoogle de useLogin()
Conté els estats:
* machines - conté les màquines disponibles. Les màquines són objectes que tenen l'estructura de màquina de la base de dades, amb un atribut afegit que és el terminal_id.
Conté les funcions:
* redirect - Si l'usuari està logejat, redirigeix a /machine/machine_name/terminal_id, on machine_name i terminal_id són els paràmetres de la funció. En cas que l'usuari no estigui logejat, li obre la finestra per logejar-se amb google.
### useReservations
Conté les funcions relacionades amb la selecció d'hores disponibles + la realització de reserves de la pàgina Machine.
Té accés als estats:
* error
Conté els estats:
* Reservation - conté les dades de la reserva en el cas que s'hagi efectuat correctament
* SelectedHours - conté les hores seleccionades
Conté les funcions:
* makeReservation - donada una data en format ISO i un terminal_id, fa una reserva des de l'hora més petita que s'han seleccionat, fins a la més gran.
* selectHour - En cas que l'hora que s'ha clicat no estigui en l'estat a l'estat de selectedHours, l'Afegeix. En cas contrari la treu. Sempre es fa un toggle de la classe css *selectHour*, per tal de mostrar quines hores estan seleccionades i quines no.
## Proves de funcionament
Per tal de poder comprovar el funcionament de l'aplicació client, s'ha donat accés al repositori del backend, i s'ha desenvolupat paral·lelament, per tal de poder comprovar els canvis en local. El resultat de les proves s'han obtingut provant l'aplicatiu i en alguns casos accedint a la base de dades.
\clearpage
# Back End
## Decisions de disseny
### Selecció Framework
S'ha decidit realitzar aquest aplicatiu amb el framework de javascript Express JS. S'ha triat aquest framework ja que és el més utilitzat en l'actualitat, i volíem desenvolupar l'aplicació al voltant de tecnologies punteres.
### Comunicació FrontEnd
La comunicació del backend, amb el frontend, es fa mitjançant sol·licituds https. S'ha escollit aquest tipus de comunicació gràcies als avantatges d'integració que comporta i la seguretat de les comunicacions que inclou.
### Comunicació Firmware
La comunicació del backend, amb el firmware, es fa mitjançant sol·licituds MQTT Authenticated. S'ha escollit aquest tipus de comunicació gràcies als avantatges que comporta de cara a gestionar una gran quantitat de dispositius.
### Desplegament
El backend està desplegat en un servidor d'heroku. Hem decidit contractar aquest servidor, per la facilitat de desplegament, i el rendiment que ofereix.
## Estructura
Per la part del Front-end o de servidor el projecte s'ha desenvolupat utilitzant NodeJs.El projecte està estructurat en les carpetes següents:
- Models: Conté els mòduls per a establir models de documents de la base de dades. Cada document en la base de dades es cada instància d'un objecte que es guarda.
- Controllers: Conté els els mòduls per a realitzar operacions en la base de dades. Aquests utilitzen els models definits en la anterioir carpeta.
- Routes: Conté els diferents routers. En cada router es defineixen una serie de endpoints en els quals el client pot fer peticions. Un cop el client fa les peticions es realitzen les validacions necessàries i es realitzen les operacions en la base de dades utilitzant el mòdul dels controllers.
- Config: englova els diferents mòduls per a realitzar la configuració del projecte. En aquest concretament hi ha el mòdul per a connectar-se a la base de dades, el mòdul per exportar les variables d'entorn i un arxiu .env per a definir les variables d'entorn. (Aquest arxiu és privat i es guarda les claus per a l'API de google, per a connectar-se a la base de dades etc.)
- Utils: Conté altres funcions que s'útillitzen en el projecte. En aquest projecte hi ha un mòdul per a establir la connexió mqtt, i un mòdul per a generar codis aleatoris. Si s'hagués de tenir mòduls per a fer peticions externes a altres Apis també es col·locarien
- en aquesta carpeta.
A més a més a fora d'aquestes carpetes també hi ha el arxiu app.js que es el punt de partida del projecte, es a dir per arrencar el projecte s'executa aquest fitxer. En aquest arxiu s'importen tots els routers i tots els middlewares ( tot i que també podria haver middlewares només per a un router concret) i es llança el servidor utilitzant express. A més també s'estableix la connexio amb la base de dades i la communicació Mqtt en aquest mòdul.
## Models
Ara continuació s'explicarà en detall quins mòduls hi ha en cada carpeta d'aquest projecte.
La carpeta de Models disposa dels mòduls següents:
- user.js
Model per a crear instàncies d'usuari. A cada usuari se li asocia el camp de email (identificador), username, rfid i permission. Quan algu fa un login amb google s'obte l'email i el username de google i es crea un nou usuari automàticament. A part, des de l'aplicatiu l'admin pot modificar els camps de rfid i permission d'usuaris existents, i també pot esborrar usuaris i afegir-ne de nous. Un usuari admin es el que té un nivell de permís de nivell 10. Al iniciar l'aplicació s'inicialitzaria un compte pactat com a admin i amb aquest es podria afegir més admins.
- machine.js
Model per a crear instàncies de màquina. Cada màquina consta d'un nom (identificador) i una descripció. Es crearà una màquina per a cada tips de màquina diferent, es a dir que si hi ha dos màquines iguals en el laboratori només s'haurà de crear una màquina.
- terminal.js
Model per a crear instancies de terminal. Una terminal fà referència al aparell físic que conte el microcontrolador i activa el hardware. Conté els camps de id (identificador ), machine name, i permission. Es crearà una instància de terminal per a cada màquina que es tingui operativa i conectada a una terminal en el laboratori. Els camp machine name fa referència a la màquina a la que està asociada, i permission al nivell mínim que haurà de tenir un usuari per a poder reservar la terminal.
- reservation.js
Model per a crear una instància de reserva. Conté els camps de id (identificador), email, terminal_id, start, end, code. L'email fà referènccia a l'usuari que ha realitzat la reserva, el terminal_id a la terminal que s'ha reservat, start i end son els temps d'inici i fí de la reserva, i code es un codi de 5 digits que serveix per activar la reserva des de l'aplicatiu (explicat en més detall posteriorment).
## Controllers
La carpeta Controllers disposa dels mòduls següents, cadaun amb les seves funcions per operar amb la base de dades:
- user.js
- userFindOrCreate(email, username, rfid, permission)
Busca si hi ha algun usuari existent amb el email especificat. Si existeix obté les dades, si no el crea.
- getUserByRfid(rfid)
Obté l'usuari amb el rfid especificat.
- updateUser(email, rfid, permission)
Actualitza els camps de rfid i permission de l'usuari amb l'email especificat. Si només es vol actualitzar un camp laltre es deixa sense especificar.
- deleteUser(email)
Esborra l'usuari amb l'email especificat
- machine.js
- addMachine(name, description)
Crea una nova instància de màquina
- getMachine
Obté les dades de la màquina amb el nom especificat
- getAllMachines()
Obté una llista de totes les instàncies de maquina
- deleteMachine(name)
Esborra a màquina amb el nom especificat
- terminal.js
- addTerminal(machine_name, permission)
Crea una nova instància de terminal
- deleteTerminal(terminal_id)
Esborra a la terminal especificada
- bindTerminal(terminal_id, machine_name)
Modifica el camp de machine_name de la terminal especificada
- setPermission(terminal_id, permission)
Modifica el camp de permission
- getAllTerminals
Obté una llista amb tots els terminals
- getTerminal(termina_id)
Retorna la terminal especificada
- reservation.js
- checkReservation(terminal_id, email)
Comprova si hi ha una reserva en el moment actual per a la terminal i l'usuari especificats.
- checkReservationById(reservation_id)
Comprova si la reserva especificada es vàlida en el moment actual.
- addReservation(email, terminal_id, start, end)
Crea una nova reserva amb les dades especificades
- deleteReservation (reservation_id)
Esborra la reserva especificada
- getReservationById (reservation_id)
Obté la reserva especificada
- getReservationByCode(code, email)
Busca si e el moment actual hi ha una reserva de l'usuari i amb codi especificats, i si hi es la retorna.
- getReservations(from, to, terminal_id, email)
Retorna una llista de les reserves de l'usuari i terminal especificats, des de la data de from fins a to. No s'especifica algun camp aquest no actua com a filtre de forma que es pot filtrar només pels camps que siguin convenients.
## Routes
La carpeta Routes disposa dels mòduls següents, cadaun disposa d'uns endpoints que s'utilitzen des del client:
- google_oauth.js (/oauth/google)
- get /oauth/google/
redirigeix a l'usuari a la api de google perque pugui realitzar el login
- get /oauth/google/callback
ruta de retorn de la api de google amb la informació de l'usuari. Des d'aquí es torna a redirigir a l'usuari a l'aplicació del client
- user.js(/user)
- get /user
retorna la informació de l'usuari. Aquesta informació s'obté perquè en fer el login es guarda una sessió en el client.
- get /user/username
Retorna el username de l'usuari
- get /user/permission
Retorna el nivell de permís de l'usuari
- get /userlogout
Destrueix la sessió
- post /user/update
- Body: email, rfid, permission
Comprova que l'usuari que fa la petició és admin i modifica l'usuari amb els paràmetres especificats en el body.
- /delete
- Body: email
Comprova que l'usuari que fa la petició és admin i esborra l'usuari especificat en el body.
- machine.js( /machines)
- get /machines
Retorna una llista de totes les màquines
- get /machines/find/:name
Retorna la informacio de la màquina especificada en el paràmetre de la url
- post /machines/add
- Body: name, description
Afegeix una màquina amb els camps especificats en el body ( es necessita nivell de permís d'admin)
- post /delete
- Boddy: name
Esborra la màquina amb el nom especificat ( es necessita nivell de permís d'admin)
- terminal.js( /terminals)
- get /terminals
retorna una llista de tots els terminals
- get /terminals/find/:terminal_id
Retorna el terminal especificat en la url
- post /terminals/add
- Body: machine_name, permission
Afegeix un terminal nou amb el permís especificat, després de comprovar que la màquina existeix
- post /terminals/delete
- Body: terminal_id
Esborra un terminal existent
- post /terminals/bind
- Body: terminal_id, machine_name
Asocia el terminal especificat amb la màquina especificada
- post /terminals/set-permission
- terminal_id, permission
Modifica el nivell de permís de la terminal especificada
- reservation.js( /reservations)
- get /reservations
- Paràmetres de búsqueda: from, to, terminal_id
Retorna totes les reserves filtrades per els filtres especificats. From i to fan referència a les reserves que comensin mes tard o igual que from, i acabin abans o igual que to.
- get /reservations/find/:id
Retorna la reserva que coincideix amb el id especificat en la url
- get /reservations/user
- Paràmetres de búsqueda: from, to, terminal_id
Funciona com el endpoint de get /reservations pero filtrant també les reserves que siguin de l'usuari que ha fet el login
- get /reservations/check
- Paràmetres de búsqueda: terminal_id
Comprova si en el terminal especificat hi ha una reserva actual (el moment actual esta entre start i end).
- get reservations/code/set'
- Paràmetres de búsqueda: reservation_id
Si la reserva especificada és actual envia un missatge mqtt a la placa pe tal de que mostri el codi de reserva, el qual es pot utilitzar per activar la reserva des de l'aplicatiu.
- get reservations/code/check
- Paràmetres de búsqueda: code
Si el codi especificat coincideix amb una reserva actual de l'usuari que ha iniciat sessió, aquesta reserva s'activa sense necessitat de passar la targeta. Això serveix perquè un usuari pugui accedir a una reserva encara que no disposi de la targeta.
- post reservations/add
- Body: terminal_id, start, end
Comprova que l'usuari tingui el permís suficient, el terminal existeixi, i no hi hagi cap reserva que coincideixi en el període: Si tot està bé crea una reserva amb les dades especificades. Retorna el id de la reserva creada.
- post reservations/delete
- Body: reservation_id
Comprova que l'usuari sigui el propietari de la reserva especificada, i l'esborra.
## Comunicació Mqtt
Finalment, cal ressaltar l'arxiu mqttHandler.js que es troba en /utis/mqtt.
Aquest arxiu inicialitza la connexió mqtt amb les diferents terminals (físiques) i principalment te dues funcionalitats
- La primera és que exporta dues funcions per a ser cridades en el moment necessari. Les funcions són les següents:
- activateReservation(terminal_id, end)
Aquesta funció es crida després de comprovar que hi ha una reserva actual i tot esta correcte. Quan es crida envia un missatge mqtt a la terminal física en la qual s'ha efectual la reserva per tal que aquesta s'activi. En aquest missatge s'incorpora el temps en segons per a que la reserva finalitzi de forma que des de la placa es podrà aturar la reserva un cop acabada. A més, per major seguretat, s'envia un altre missatge de finalització en el moment en que s'acaba la reserva.
- setCode(terminal_id, code)
Aquesta funció envia un missatge a la terminal especificada amb el codi especificat perqué el mostri per la pantalla. Aquesta funció es crida quan l'usuari vol activar una reserva per l'aplicatiu. (Per si no té la targeta)
- La segona és escoltar els missatges que arriben des de les terminals físiques. Les terminals enviaran missatges només quan un usuari passi la targeta. Llavors al accedir a la targeta es troba l'usuari i es comprova si realment té una reserva en la màquina activada. Si es així es crida a la funció activateReservation esmentada anteriorment i d'aquesta forma la placa reb un missatge de que tot es correcte i activa la reserva.
\clearpage
# Firmware
A continuació s'explica com s'ha desenvolupat la part de firmware, considerant aquest el codi que interactua directament amb hardware. A més d'interactuar amb el hardware, també es comunica amb el backend a través del protocol MQTT.
Tot el firmware s'ha desenvolupat sobre el llenguatge de programació C++, amb les llibreries oficials d'Arduino i altres explicades a continuació. A més per simplificar el procés de compilació i carrega a la placa, s'ha utilitzat un Makefile que depèn de l'eina arduino-cli, la qual permet compilar i carregar codi de manera simple i ràpida.
## Estructura
Per desenvolupar el firmware de manera estructurada, modular i a prova de fallades, s'han aplicat dues grans característiques.
Per una banda, s'ha prioritzat crear una llibreria independent per a cada mòdul hardware, és a dir una llibreria pel mòdul PN532, una llibreria diferent pel LED, etc. D'aquesta manera en cas que el hardware es modifiqui, el programa principal no varia.
Per altra banda, tot el programa principal està basat en una màquina d'estats. Això permet, a més de poder tenir un control total en tot moment, ajuda a veure error de disseny i evita comportament inesperat.
## Hardware
### SSD1306
El SSD1306 és un mòdul de pantalla OLED, de baix consum i bona qualitat d'imatge. La comunicació amb aquest es fa a través del protocol SPI.
Aquest mòdul és l'únic que no està encapsulat en una llibreria, sinó que està directament implementada al codi principal. Això és com que en intentar crear una llibreria amb les dependències d'aquest mòdul, el compilador retorna diversos codis, que no hem sigut capaços de solucionar.
Per inicialitzar el mòdul, el primer que fem és definir l'objecte display i a continuació cridar la funció SSD1306_Setup. Aquesta última funció s'encarrega de deixar tot a punt per a la utilització del mòdul.
```cpp
Adafruit_SSD1306 display(128, 64, &Wire, 2);
void setup() {
SSD1306_Setup();
}
```
A més de la funció de setup, tenim dues funcions més per interactuar amb el mòdul. La primera d'elles és SSD1306_PrintText, la qual ens permet imprimir un text per pantalla amb una mida de lletra concreta.
```cpp
void SSD1306_PrintText(String payload, int size);
```
La segona funció, s'anomena SSD1306_ClearScreen. Tal com indica el seu nom, neteja la pantalla, és a dir la deixa en blanc.
```cpp
void SSD1306_ClearScreen();
```
### PN532
El PN532 és un mòdul RFID/NFC, el qual ens permet llegir i escriure targetes o tags amb aquestes tecnologies. En el nostre cas, la targeta UPC compta amb la tecnologia RFID.
Per inicialitzar la llibreria, primer cal importar-la, crear un objecte PN532 i cridar la funció PN532_setup.
```cpp
#include "PN532.h"
PN532 PN532(Serial);
void setup() {
PN532.PN532_setup();
}
```
Un cop la llibreria està inicialitzada, només disposem d'una funció anomenada getuid, aquesta funció ens permet llegir del lector. En cas de detectar una targeta, ens retorna el contingut d'aquesta. En canvi, si no detecta cap targeta, simplement ens retorna un String buit.
```cpp
String getuid(int timeout);
```
### LED
El LED és un semiconductor que en activar-se emet llum. En el nostre cas de diversos colors.
És una llibreria molt simple, però necessària per tenir un codi modular. Per inicialitzar aquesta, primer cal importar-la, crear un objecte LED i cridar la funció Led_setup.
```cpp
#include "LED.h"
Led Led(25, 26, 27);
void setup() {
Led.Led_setup();
}
```
Un cop la llibreria està inicialitzada, disposem de dues funcions. La primera és Led_ON, la qual ens permet encendre el LED en diferents colors. Disposa de tres paràmetres booleans: r (red, vermell), g (green, verd) i b (blue, blau).
```cpp
void Led_ON(bool r, bool g, bool b);
```
I la segona és Led_OFF, la qual apaga el LED.
```cpp
void Led_OFF();
```
### Relé
El Relé és un dispositiu electromagnètic, el qual funciona com un interruptor electrònic.
La seva llibreria, de la mateixa manera que el LED, és molt simple, però un cop més necessari per tenir un codi modular. Per inicialitzar aquesta, primer cal importar-la, crear un objecte Relé i cridar la funció Rele_setup.
```cpp
#include "Rele.h"
Rele Rele(32);
void setup() {
Rele.Rele_setup();
}
```
Un cop la llibreria està inicialitzada disposem de dues funcions. La primera és Rele_open, la qual ens permet obrir el relé, o seguit l'analogia de l'interruptor, apagar el circuit.
```cpp
void Rele_open();
```
I la segona és Rele_close, la qual ens permet tancar el relé, o seguint l'analogia de l'interruptor, encendre el circuit.
```cpp
void Rele_close();
```
### Buzzer
El buzzer és un transductor electroacústic que produeix un so continu en un mateix to (generalment agut).
La seva llibreria, de la mateixa manera que el LED i el relé, és molt simple, però un cop més necessari per tenir un codi modular. Per inicialitzar aquesta, primer cal importar-la, crear un objecte Buzzer i cridar la funció Buzzer_setup.
```cpp
#include "Buzzer.h"
Buzzer Buzzer(33);
void setup() {
Buzzer.Buzzer_setup();
}
```
Un cop la llibreria està inicialitzada disposem de dues funcions. La primera és Buzzer_on, la qual ens permet activar el buzzer, i per tant generar un senyal acústic.
```cpp
void Buzzer_on();
```
I la segona és Buzzer_off, la qual ens permet apagar el buzzer, i per tant deixar de generar aquest senyal acústic.
```cpp
void Buzzer_off();
```
## MQTT
El protocol MQTT és un protocol de xarxa lleuger, de publicació i subscripció, de màquina a màquina, de missatges.
Aquest funciona amb topics, els quals són canals de comunicació, en els quals es pot publicar (enviar informació) o subscriure a ells (rebre informació).
### Topics
En el nostre cas els topics que tenim són els següents:
* **rfid** (El terminal publica, el servidor se subscriu)
En aquest topic, cada terminal fa una publicació cada cop que llegeix una targeta RFID. El format del missatge publicat és: *{terminal-id}*/*{rfid-readed}*.
* **{terminal-id}/reserva** (El terminal se subscriu, el servidor publica)
D'aquest topic, hi ha un per cada terminal, ja que el començament d'aquest és l'id del terminal en qüestió. El servidor pot publicar 3 tipus de missatges:
* *"1735689600"*
Per començar una reserva. Aquests números representen el timestamp de finalització de la reserva en format UNIX Epoch.
* *"-1"*
Per indicar que no hi ha cap reserva. S'utilitza per quan es fa una sol·licitud d'activar reserva.
* *"-2"*
Per finalitzar una reserva.
* **{terminal-id}/code** (El terminal se subscriu, el servidor publica)
De la mateixa manera que el topic reserva, d'aquest també hi ha una per cada terminal, pels mateixos motius. Aquest topic es fa servir per mostrar un codi a la pantalla del terminal. El format del missatge és un número de 5 dígits, per exemple *"12345"*.
### Seguretat
Per tal de garantir la seguretat i privacitat dels missatges, hem implementat el protocol MQTT Authenticated. El qual mitjançant un usuari i una contrasenya encripta el missatge.

### Implementació
Per implementar el protocol MQTT, s'ha fet servir una llibreria anomenada EspMQTTClient, la qual a més de gestionar aquest protocol, també gestiona la connexió WiFi. Aquestes funcionalitats, sumat a la seva senzillesa, fa que sigui una opció molt interessant.
Per inicialitzar aquesta, primer cal importar-la, crear un objecte client i cridar la funció client.loop en cada loop. Dintre de l'objecte client tenim diversos paràmetres, els quals es gestionen amb un arxiu de paràmetres el qual s'explica en més detall en el següent punt.
```cpp
#include <EspMQTTClient.h>
EspMQTTClient client(
WiFi_Name,
WiFi_Password,
MQTT_Broker,
MQTT_User,
MQTT_Password,
MQTT_Client,
MQTT_Port
);
void loop() {
client.loop();
}
```
Un cop tenim la llibreria inicialitzada, cal subscriure's als topics reserva i code. Això es fa dins de la funció onConnectionEstablished, la qual es crida un cop la connexió MQTT ha estat exitosa. A més per fer aquestes subscripcions, a part del nom del topic, necessitem definir una funció a cridar quan es rebi un missatge en aquell topic. En el cas de reserva, tenim la funció reserva_received i en el cas de code tenim la funció code_received.
```cpp
void onConnectionEstablished() {
client.subscribe(String(Terminal_ID) + "/reserva", reserva_received);
client.subscribe(String(Terminal_ID) + "/code", code_received);
}
```
La funció reserva_received, analitza el missatge rebut, per veure si es tracta d'activar una reserva, cancel·lar-la o el missatge que no hi ha cap reserva.
```cpp
void reserva_received(const String &payload);
```
La funció code_received, analitza el codi rebut i posteriorment l'emmagatzema en la variable global sm_s_showing_code.
```cpp
void code_received(const String &payload);
```
Per altra banda, a part de subscripcions, també hem de publicar. En aquest cas existeix la funció send_id_detected, la qual rep com a paràmetre l'id llegit i el publica al topic corresponent.
```cpp
void send_id_detected(String data);
```
## Gestió de paràmetres
Per gestionar els diferents paràmetres, tant de la connexió WiFi, com del broker MQTT, com de l'identificador del terminal, existeix el fitxer parameters.h, el qual s'encarrega de gestionar tots aquests paràmetres de manera simple en temps de compilació.
### Paràmetres WiFi
En aquest apartat tenim el nom de la xarxa WiFi i la contrasenya d'aquesta.
```cpp
#define WiFi_Name "My_Wifi"
#define WiFi_Password "123456789abc"
```
### Paràmetres MQTT
En aquest apartat tenim els diferents paràmetres del broker MQTT. En aquest cas, en l'exemple, hi ha la informació d'un broker públic.
```cpp
#define MQTT_Broker "test.mosquitto.org"
#define MQTT_User "rw"
#define MQTT_Password "readwrite"
#define MQTT_Client "Terminal"
#define MQTT_Port 1884
```
### Paràmetres terminal
En aquest apartat tenim els paràmetres relacionats amb el terminal, en aquest cas només tenim un, l'id.
```cpp
#define Terminal_ID "63ab33581ea11dbfdd6a40e9"
```
## Maquina estats
Tota la lògica de funcionament del programa principal és causada per aquesta màquina d'estats. Ja que de manera global és difícil comprendre com funciona, primer veurem una breu descripció de cada estat, i a continuació, analitzarem en profunditat els estats de manera individual i com interacciona amb la resta.
* **read_id:** A l'espera de llegir una targeta RFID o rebre algun missatge per part del servidor.
* **show_code:** Mostra el codi de 5 dígits per pantalla.
* **reserve_active:** La reserva es troba activa i la maquina encesa.
* **reserve_standby:** La reserva es troba activa, però la màquina es troba parada temporalment.

### Read_id
Aquest és un estat d'espera, on no hi ha cap reserva, ni s'està mostrant cap codi. En cas de llegir un ID, aquest s'envia al servidor a través de MQTT. Hi ha dues possibles maneres de sortir d'aquest estat:
* **show_code:** Es rep un missatge de mostrar un codi, i per tant es fa una transició a l'estat show_code. En aquest cas es mostra el codi en la pantalla del terminal i s'activa un temporitzador.
* **reserve_received:** Es rep un missatge d'una reserva rebuda, i en conseqüència es fa una transició a l'estat reserve_active. En aquest cas s'activa el relé i també s'activa un temporitzador.
### Show_code
En aquest estat el codi rebut s'està mostrant a la pantalla. En cas de tornar a rebre el missatge de show_code, es reinicialitza el temporitzador. Hi ha dues possibles maneres de sortir d'aquest estat:
* **code_timeout:** El temporitzador arriba a 0, i per tant s'ha de tornar a l'estat inicial. En aquest cas es neteja pantalla i es desactiva el temporitzador.
* **reserve_received:** Es rep un missatge d'una reserva rebuda, i en conseqüència es fa una transició a l'estat reserve_active. En aquest cas s'activa el relé i també es reinicialitza al temporitzador.
### Reserve_active
En aquest estat la reserva rebuda està activa, i el relé tancat, és a dir que la màquina està funcionant. En cas de tornar a rebre el missatge de reserva_active, es reinicialitza el temporitzador amb el temps de la nova reserva. Hi ha tres possibles maneres de sortir d'aquest estat:
* **reserve_timeout:** El temporitzador arriba a 0, i per tant s'ha de tornar a l'estat inicial. Aquest esdeveniment està pensat com a mesura de seguretat, en cas de no rebre el missatge de finalització per part del servidor, no deixar la màquina encesa sense control. En aquest cas s'obre el relé i es desactiva el temporitzador.
* **reserve_ended:** Es rep un missatge de reserva acabada, i en conseqüència es fa una transició a l'estat de read_id. En aquest cas s'obre el relé i es desactiva el temporitzador.
* **reserve_standby:** Quan el lector RFID detecta la targeta de l'ID associat a la reserva, envia un esdeveniment d'id_readed i en conseqüència es fa una transició a l'estat de reserve_standby.
### Reserve_standby
En aquest estat la reserva rebuda està activa, però el relé es troba obert, és a dir, la màquina no està funcionant. Aquest estat està pensat per poder parar la màquina en moment concrets, sense haver de cancel·lar la reserva completa. Hi ha dues possibles maneres de sortir d'aquest estat:
* **reserve_timeout:** El temporitzador arriba a 0, i per tant s'ha de tornar a l'estat inicial. En aquest cas s'obre el relé i es desactiva el temporitzador.
* * **reserve_ended:** Es rep un missatge de reserva acabada, i en conseqüència es fa una transició a l'estat de read_id. En aquest cas s'obre el relé i es desactiva el temporitzador.
* **reserve_standby:** Quan el lector RFID detecta la targeta de l'ID associat a la reserva, envia un esdeveniment d'id_readed i en conseqüència es fa una transició a l'estat de reserve_active.
\clearpage
# Organització
Les tasques a realitzar es poden dividir en 3 grans grups. Per una banda, hi ha les tasques relacionades el *front-end*. D'altra banda, hi ha les tasques que impliquen el *backend*. Finalment, hi ha les tasques del *firmware*.
Per tal de gestionar les tasques a escala temporal, i en l'àmbit de prioritats, s'usarà l'eina *Trello*.
En la figura \ref{gantt}, s'observa un *diagrama de Gantt*, que preveu com s'organitzarà el grup en temps relacionat amb les tasques amb el número de setmana de l'any.
La organització que hem seguit per a realitzar aquest projecte ha estat la següent:
- **Artur Blaya:** Implementació de la part del client amb ReactJS. Això implica disenyar la interfície d'usuari per a que sigui simple i intuitiva, implementar tota la lògica dels components per a que la pàgina sigui reactiva, i finalment disenyar els estils per a que la pàgina sigui agradable a la vista.
- **Andreu Villaró:** Implementació de la part del servidor amb NodeJs utilitzant la llibreria expres. Inclou crear totes les rutes per a que l'aplicació del client pugui realitzar les petiicions, establir un protocol de comunicació mqtt amb la part del hardware per tal de donar ordres i rebre informació de les plàques físiques, i operar en la base de dades per tal de guardar les dades d'una forma eficient.
- **Llorenç Garcia:** Implementació del firmware amb C++, utilitzant les llibreries d'Arduino. A més construir un Makefile, per tal de compilar i carregar el codi a la placa de manera senzilla i eficaç. Per altra banda, també he dissenyat la caixa que encapsula el terminal.


\clearpage
# Posada en marcha
Per a executar el projecte s'han de següir les següents instruccions:
En la carpeta del servidor: npm start.
En la carpeta del client: npm run dev.
Per compliar el hardware executar make i make upload en la carpeta de firmware.