Ací es mostren les diferències entre la revisió seleccionada i la versió actual de la pàgina.
Ambdós costats versió prèvia Revisió prèvia Següent revisió | Revisió prèvia | ||
iot_django_aproximacio_un_dashboard_sobre_raspberry_pi [2023/06/24 15:21] jordi_gual_purti |
iot_django_aproximacio_un_dashboard_sobre_raspberry_pi [2023/06/25 06:36] (actual) jordi_gual_purti |
||
---|---|---|---|
Línia 44: | Línia 44: | ||
Els sistema operatiu [[https:// | Els sistema operatiu [[https:// | ||
- | #1# | + | |
- | | + | |
- | | + | |
- | < | + | |
- | | + | |
- | < | + | |
$ cd dev/ | $ cd dev/ | ||
$ python3 -m venv env | $ python3 -m venv env | ||
Línia 58: | Línia 56: | ||
$ pip freeze > requirements.txt | $ pip freeze > requirements.txt | ||
$ ./manage.py startapp temperature</ | $ ./manage.py startapp temperature</ | ||
- | | + | |
- | < | + | |
- | ## | + | Amb això, ja tindríem a punt l' |
- | Amb això, ja tindríem a punt | + | |
+ | ==== Creació del model ==== | ||
+ | |||
+ | El model del nostre projecte estarà format per dues classes: | ||
+ | |||
+ | * La classe '' | ||
+ | * La classe '' | ||
+ | |||
+ | Aquestes dues classes mantenen una relació 1:N entre elles: 1 sensor pot tenir N mostres associades, 1 mostra pot estar associada a 1 únic sensor. A continuació es pot observar el codi detallat que defineix el model de dades de l' | ||
+ | <code python> | ||
+ | import locale | ||
+ | from django.db import models | ||
+ | |||
+ | # Definició de la classe que representarà els sensors que tinguem, permetent registrar | ||
+ | # el fabricant i model de sensor, una descripció de la seva ubicació i les coordenades | ||
+ | # geogràfiques de la seva ubicació. | ||
+ | class TemperatureSensor(models.Model): | ||
+ | manuf_model = models.CharField(max_length=128) | ||
+ | location_desc = models.CharField(max_length=128) | ||
+ | location_geo_lat = models.FloatField() | ||
+ | location_geo_lng = models.FloatField() | ||
+ | |||
+ | def __str__(self): | ||
+ | return self.location_desc + " (" + self.manuf_model + " | ||
+ | |||
+ | # Definició de la classe que representarà les mesures obtingudes pels sensors. Permet | ||
+ | # registrar el valor de la mesura, la data/hora en què ha estat presa i, també, permet | ||
+ | # mantenir l' | ||
+ | class TemperatureSample(models.Model): | ||
+ | sensor = models.ForeignKey(TemperatureSensor, | ||
+ | value = models.DecimalField(max_digits=6, | ||
+ | timestamp = models.DateTimeField(" | ||
+ | |||
+ | def __str__(self): | ||
+ | locale.setlocale(locale.LC_ALL, | ||
+ | valor = locale.format(' | ||
+ | return self.sensor.location_desc + " - " + valor + "ºC - " + self.timestamp.strftime(" | ||
+ | </ | ||
+ | |||
+ | <WRAP info> | ||
+ | Cal dir que l' | ||
+ | </ | ||
+ | |||
+ | ==== Instal·lació de la biblioteca FusionCharts ===== | ||
+ | Abans de continuar veient com implementar la resta de components, cal dur a terme la instal·lació de la llibreria [[https:// | ||
+ | |||
+ | [[https:// | ||
+ | |||
+ | El nivell de detall d' | ||
+ | |||
+ | ==== Creació del dashboard senzill ==== | ||
+ | |||
+ | L' | ||
+ | |||
+ | - **Pàgina principal**: | ||
+ | - **Formulari selecció de data**: on escollirem la data sobre la qual volem generar la gràfica de les mesures registrades, | ||
+ | - **Visualització de la gràfica**: pàgina que visualitzarà les mesures que tenim registrades del sensor escollit i de la data indicada en forma de gràfica temporal. Disposa d'un botó per tornar a la pàgina principal. | ||
+ | |||
+ | <WRAP info> | ||
+ | En aquesta documentació, | ||
+ | </ | ||
+ | |||
+ | Aquests tres components són vistes de Django que estan implementades com es pot veure en el codi font que teniu a continuació: | ||
+ | <code python> | ||
+ | from django.shortcuts import render, get_object_or_404 | ||
+ | from django.http import HttpResponse, | ||
+ | from django import forms | ||
+ | from .models import TemperatureSample, | ||
+ | from .fusioncharts import FusionCharts, | ||
+ | from django.template import loader | ||
+ | from django.urls import reverse | ||
+ | |||
+ | # Vista inicial (root). | ||
+ | def index(request): | ||
+ | latest_sensor_list = TemperatureSensor.objects.order_by(" | ||
+ | template = loader.get_template(" | ||
+ | context = { | ||
+ | " | ||
+ | } | ||
+ | return HttpResponse(template.render(context, | ||
+ | |||
+ | |||
+ | # Vista formulari per configurar la gràfica que volem visualitzar (escollir data). | ||
+ | class GraphForm(forms.Form): | ||
+ | data = forms.DateField(label=" | ||
+ | |||
+ | def graph_form(request, | ||
+ | gf = GraphForm() | ||
+ | return render(request, | ||
+ | |||
+ | |||
+ | # Vista de la gràfica que recull la data del formulari i genera la gràfica d' | ||
+ | def graph_view(request, | ||
+ | if request.method==" | ||
+ | |||
+ | form = GraphForm(request.POST) | ||
+ | |||
+ | if form.is_valid(): | ||
+ | # Preparem les dades i configuracions generals de la gràfica. | ||
+ | sensor = get_object_or_404(TemperatureSensor, | ||
+ | data = form.cleaned_data[" | ||
+ | |||
+ | start = str(data) + ' 00: | ||
+ | end = str(data) + ' 23: | ||
+ | |||
+ | # Preparem l' | ||
+ | # una gràfica de línia, amb proporcionalitat temporal. | ||
+ | schema = [{ | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, { | ||
+ | " | ||
+ | " | ||
+ | }] | ||
+ | dades_temp = [] | ||
+ | |||
+ | # Les dades, les afegim consultant-les de la BD. | ||
+ | for key in TemperatureSample.objects.filter(timestamp__range=(start, | ||
+ | mostra = [] | ||
+ | mostra.append(key.timestamp.strftime(' | ||
+ | mostra.append(float(key.value)) | ||
+ | dades_temp.append(mostra) | ||
+ | |||
+ | # Creem els objectes que necessita el constructor de la gràfica. | ||
+ | fusionTable = FusionTable(schema, | ||
+ | timeSeries = TimeSeries(fusionTable) | ||
+ | |||
+ | # Creem un objecte per a la gràfica utilitzant el constructor de FusionCharts: | ||
+ | # El cinquè paràmetre, ha de coincidir amb el nom del <div> de la template HTML on | ||
+ | # volem que es visualitzi la gràfica. | ||
+ | grafica = FusionCharts(" | ||
+ | |||
+ | # " | ||
+ | return render(request, | ||
+ | |||
+ | return HttpResponseRedirect(reverse(" | ||
+ | |||
+ | </ | ||
+ | |||
+ | Les templates HTML que s' | ||
+ | |||
+ | '' | ||
+ | <code html> | ||
+ | < | ||
+ | {% if latest_sensor_list %} | ||
+ | < | ||
+ | {% for s in latest_sensor_list %} | ||
+ | < | ||
+ | {% endfor %} | ||
+ | </ | ||
+ | {% else %} | ||
+ | <p>No hi ha sensors disponibles</ | ||
+ | {% endif %} | ||
+ | </ | ||
+ | |||
+ | '' | ||
+ | <code html> | ||
+ | < | ||
+ | <form action="/ | ||
+ | {% csrf_token %} | ||
+ | {{form.as_p}} | ||
+ | <input type=" | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | '' | ||
+ | <code html> | ||
+ | < | ||
+ | < | ||
+ | < | ||
+ | < | ||
+ | {% load static %} | ||
+ | <script type=" | ||
+ | <script type=" | ||
+ | <script type=" | ||
+ | </ | ||
+ | < | ||
+ | <div id=" | ||
+ | <div id=" | ||
+ | <a href="/ | ||
+ | < | ||
+ | </ | ||
+ | </ | ||
+ | </ | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | |||
+ | ==== Automatització de la captura de dades ==== | ||
+ | |||
+ | La captura de dades s'ha de fer partint de la base que disposem d'un sensor. El muntatge que hem fet per a la implementació d' | ||
+ | {{ : | ||
+ | |||
+ | Tenint això, cal preparar un petit programa **python** que s' | ||
+ | <code python> | ||
+ | # | ||
+ | from django.core.wsgi import get_wsgi_application | ||
+ | import os | ||
+ | |||
+ | # Establim l' | ||
+ | os.environ.setdefault(" | ||
+ | |||
+ | # Instanciem un web service WSGI per tenir-lo com a passarel·la per al nostre django. | ||
+ | application = get_wsgi_application() | ||
+ | |||
+ | from temperature.models import TemperatureSensor, | ||
+ | import glob | ||
+ | from datetime import datetime, timedelta | ||
+ | import time | ||
+ | |||
+ | # Carreguem els drivers del sensor de temperatura. | ||
+ | os.system(' | ||
+ | os.system(' | ||
+ | |||
+ | # Configurem l' | ||
+ | # el funcionament del sensor Dallas DS18B20. | ||
+ | base_dir = '/ | ||
+ | device_folder = glob.glob(base_dir + ' | ||
+ | device_file = device_folder + '/ | ||
+ | |||
+ | # Aquestes dues funcions implementen la lectura de mostres de temperatura | ||
+ | # del sensor que tenim connectat al RPI. | ||
+ | def read_temp_raw(): | ||
+ | f = open(device_file, | ||
+ | lines = f.readlines() | ||
+ | f.close() | ||
+ | return lines | ||
+ | |||
+ | def read_temp(): | ||
+ | lines = read_temp_raw() | ||
+ | while lines[0].strip()[-3: | ||
+ | time.sleep(0.2) | ||
+ | lines = read_temp_raw() | ||
+ | equals_pos = lines[1].find(' | ||
+ | if equals_pos != -1: | ||
+ | temp_string = lines[1][equals_pos+2: | ||
+ | temp_c = float(temp_string) / 1000.0 | ||
+ | return temp_c | ||
+ | |||
+ | # Cada cop que executem aquest programa, agafem una mostra de la temperatura del | ||
+ | # sensor i la guardem a la BD a través del model de dades de l' | ||
+ | ts = datetime.now() + timedelta(hours=2) | ||
+ | mostra = TemperatureSample(sensor_id=1, | ||
+ | mostra.save() | ||
+ | </ | ||
+ | |||
+ | Per automatitzar la presa de mesures de temperatura només falta configurar el servei **cron** a través d'un arxiu **crontab**. Per programar una lectura de la temperatura cada 2 minuts tindríem el seguent arxiu '' | ||
+ | < | ||
+ | */2 * | ||
+ | |||
+ | </ | ||
+ | Per introduir aquesta configuració a la planificació de tasques del servei **cron** del sistema haurem d' | ||
+ | < | ||
+ | $ crontab crontab.cfg | ||
+ | </ | ||
+ | |||
+ | ==== Algunes imatges ==== | ||
+ | |||
+ | Per tenir una idea més detallada dels resultats obtinguts es faciliten algunes imatges del muntatge del hardware i de la visualització de la gràfica com a mostra del que hem d' | ||
+ | |||
+ | '' | ||
+ | {{ : | ||
+ | |||
+ | '' | ||
+ | {{ : | ||
+ | |||
+ | '' | ||
+ | {{ : | ||
+ | |||
+ | ===== Consideracions finals ===== | ||
+ | |||
+ | Per acabar, es fan constar algunes consideracions importants que cal tenir presents sobre aquest material, tenint en compte la seva naturalesa didàctica: | ||
+ | |||
+ | * Es tracta, com hem dit, d'un material que té un objectiu didàctic. Per tant, no contempla molts dels aspectes que caldria tenir presents si es tractés d'un projecte orientat a la posada en producció real. | ||
+ | * No s'ha dedicat cap esforç a fer-lo bonic aplicant, per exemple, estils CSS. | ||
+ | * Els mecanismes de control d' | ||
+ | * No es toca la qüestió de la posada en producció del sistema. | ||
+ | |||
+ | |||
+ | Aquest projecte s'ha implementat, | ||
+ | * Tenir els sensors associats a un hardware més senzill i barat (Arduino, ESP32, etc.), amb capacitat de connexió a xarxa. | ||
+ | * Tenir la base de dades en un sistema més potent que SQLite: MySQL, PostgresQL, Oracle, etc. | ||
+ | * Tenir el servidor Django en un hardware més potent que no pas un Raspberry Pi. | ||
+ | * Establir les passarel·les adients d' | ||
+ | |||
+ | {{tag> #FpInfor #DamMp09 #DawMp07 django iot raspberry fusioncharts }} |