In questo articolo, IoE con Cisco Packet Tracer e HTTP Real, realizziamo una rete con Cisco Packet Tracer costituita da sensori ed attuatori. Utilizzando le funzioni Real offerte da Cisco possiamo inviare i dati che riceve l’MCU ad un server reale e possiamo comandare attuatori, tutto attraverso richieste HTTP

Indice dei contenuti

In questo progetto, IoE con Cisco Packet Tracer e HTTP Real, realizziamo una rete con Cisco Packet Tracer costituita da sensori ed attuatori. Utilizzando le funzioni Real offerte da Cisco possiamo inviare i dati che riceve l’MCU ad un server reale e possiamo comandare attuatori, tutto attraverso richieste HTTP

Descrizione del Progetto

Realizzare un progetto con Cisco Packet Tracer utilizzando un MCU, un potenziometro, un display ed un LED. Il funzionamento è il seguente. Il valore letto dal potenziometro deve essere riportato sul display e, attraverso una richiesta HTTP tali valori verranno memorizzati in una tabella di un Database realizzato con MySQL. Realizzare una dashboard all’interno della quale sia possibile:
– visualizzare, in un grafico, l’andamento dei valori nel tempo
– comandare (e visualizzare) lo stato del led

Capitolo 1: Creazione del Database MySQL

Per iniziare, creiamo un database denominato IoT_Database e una tabella dati per memorizzare i dati provenienti dal dispositivo IoT.

Far partire XAMPP, cliccare su start in corrispondenza di APACHE (per far partire il WEB Server) e in corrispondenza di MySQL (per far partire il Database Server). In corrispondenza di MySQL cliccare su Admin. Verrete reindirizzati a localhost/phpmyadmin l’interfaccia di gestione del DBMS. Cliccare sul TAB “SQL” e incollare il seguente codice

CREATE DATABASE IoT_database;

USE IoT_database;

CREATE TABLE sensor_data (
    id INT AUTO_INCREMENT PRIMARY KEY,
    valore INT(5),  -- Valore del potenziometro
    led_status VARCHAR(10),  -- Stato del LED (acceso/spento)
    timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP  -- Data e ora della registrazione
);

Il codice precedente crea il database “IoT_DB” e la tabella “dati” con chiave primaria “id” e tre campi:

  • valore_Pot che conterrà i valori del potenziometro letti dall’MCU. Ricordiamo che l’ADC di Arduino utilizza un registro a 10 bit per cui avrà 210 = 1024 possibili livelli (da 0 a 1023)
  • stato_LED che conterrà lo stato del LED (acceso o spento)

Capitolo 2: Scrittura delle API REST in PHP

2.1 API per la ricezione dati dall’MCU (iot_data.php)

Questa API riceve i valori dal potenziometro e lo stato del LED, salvandoli nel database.

Sarà necessario instaurare la connessione al database appena creato e ricevere i dati. Il modo più semplice per la ricezione dei dati è in formato JSON per cui avremo bisogno di funzioni PHP che consentano di farlo. Qui di seguito c’è il codice della API iot_data.php. Attraverso un IDE o un semplice file creare lo script e memorizzarlo nella cartella htdocs, magari in una subdirectory “test/IoT”. Il percorso potrebbe essere:

C:\xampp\htdocs\test\IoT

<?php
$servername = "localhost";
$username = "root";
$password = "";
$dbname = "iot_database";

// Connessione al database
$conn = new mysqli($servername, $username, $password, $dbname);

// Controllo connessione
if ($conn->connect_error) {
    die("Connessione fallita: " . $conn->connect_error);
}

// Ricezione del valore dal dispositivo MCU in formato JSON
if ($_SERVER["REQUEST_METHOD"] == "POST") {
    // Legge il JSON inviato dal client e lo decodifica
    $json = file_get_contents("php://input");
    $data = json_decode($json, true);

    // Controlla se il valore del potenziometro e lo stato del LED sono presenti
    if (isset($data["valore"]) && isset($data["led_status"])) {
        $statoPot = $data["valore"];
        $led_status = $data["led_status"];

        // Query per inserire il valore del potenziometro e lo stato del LED nel database
        $sql = "INSERT INTO sensor_data (valore, led_status) VALUES ('$statoPot', '$led_status')";

        if ($conn->query($sql) === TRUE) {
            echo "Dati salvati con successo";
        } else {
            echo "Errore: " . $sql . "<br>" . $conn->error;
        }
    } else {
        echo "Errore: valore o stato del LED non ricevuti";
    }
}

// Chiudi connessione
$conn->close();
?>

Commentiamo il codice tralasciando la parte di connessione. Nel nostro MCU (che in questo caso si comporta da client) creeremo un endpoint di questo tipo:

request = client.post("http://127.0.0.1/test/IoT/iot_data.php", json_data, "application/json")

Il concetto di endpoint nelle API REST si riferisce a un URL specifico attraverso il quale è possibile accedere a una risorsa esposta da un’applicazione server. In altre parole, un endpoint è un punto di accesso a una funzionalità dell’API, dove il client può inviare richieste HTTP per ottenere informazioni, aggiungere dati, modificarli o eliminarli.

Ogni endpoint corrisponde generalmente a una risorsa (ad esempio, un oggetto o un’entità) che può essere manipolata. Gli endpoint sono definiti dall’URL e dal tipo di operazione HTTP che viene eseguita su di essi.

Con il nostro endpoint accederemo alle funzionalità della API iot_data.php passando i dati JSON che verranno acquisiti dall’MCU attraverso una richiesta HTTP POST e nella nostra API decodificheremo i dati in formati JSON e li memorizzeremo nella tabella del database. La seguente riga di codice:

$json = file_get_contents("php://input");

ha lo scopo di leggere i dati inviati a un server tramite una richiesta HTTP, in particolare i dati del corpo (body) della richiesta, che spesso sono in formato JSON.

Ecco una spiegazione dettagliata di ciascun componente:

  1. file_get_contents(): È una funzione di PHP che legge il contenuto di un file o di una risorsa. In questo caso, viene utilizzata per leggere dati provenienti da una risorsa non convenzionale (il corpo di una richiesta HTTP).
  2. "php://input": È uno stream wrapper di PHP che permette di accedere al corpo della richiesta HTTP. A differenza di $_POST, che è specifico per i dati inviati tramite il metodo POST, php://input consente di leggere il corpo di una richiesta senza preoccuparsi del tipo di contenuto (ad esempio, JSON o XML), rendendolo utile per richieste con contenuti diversi o formati non strutturati.
  3. Il risultato ($json): La funzione file_get_contents("php://input") restituisce i dati letti, che nel contesto di una richiesta HTTP tipica, sono il corpo del messaggio. In questo caso, si suppone che i dati siano in formato JSON, quindi vengono memorizzati nella variabile $json.

Come funziona:

  • Quando un client invia una richiesta HTTP (ad esempio, con il metodo POST), i dati del corpo della richiesta (ad esempio, JSON) vengono letti da PHP tramite php://input.
  • La funzione file_get_contents() legge il contenuto di questa “risorsa” e lo salva nella variabile $json.
  • Quindi, $json conterrà una stringa JSON, che può essere successivamente decodificata in un array o oggetto PHP utilizzando la funzione json_decode().

infatti la seguente riga di codice è proprio la decodifica dei dati JSON. Le righe di codice successive non fanno altro che memorizzare i dati decodificati nella tabella attraverso la query SQL.

2.2 API per il controllo del LED (control_led.php)

Adesso abbiamo bisogno di una API che acquisisca il comando inviato dall’utente attraverso la dashboard e invii una richiesta HTTP GET con il valore del comando (0 = spento e 1 = acceso) all’MCU. La logica all’interno dell’MCU (che in questo caso si comporterà da server), ricevuta la richiesta accenderà o spegnerà il LED.

Questa di seguito è l’API che invia il comando all’MCU per accendere o spegnere il LED.

<?php
$servername = "localhost";
$username = "root";
$password = "";
$dbname = "iot_database";

// Connessione al database
$conn = new mysqli($servername, $username, $password, $dbname);

// Controllo connessione
if ($conn->connect_error) {
    die("Connessione fallita: " . $conn->connect_error);
}

// Gestione del comando per accendere/spegnere il motore
if ($_SERVER["REQUEST_METHOD"] == "GET") {
    // Ricezione del valore dell'interruttore (1 per accendere, 0 per spegnere)
    if (isset($_GET['comando'])) {
        $comando = $_GET['comando'];  // Il comando può essere 1 o 0
        // Invio del comando al dispositivo IoT (MCU)
        // Invio di una richiesta HTTP all'MCU 
        $url = "http://127.0.0.1:8765/control_led?comando=$comando";  // Cambiare l'IP se necessario
        file_get_contents($url);  // Invio della richiesta GET al dispositivo MCU
        header("Location: dashboard.php");  // Reindirizzamento alla dashboard
    }
}

// Chiudi connessione
$conn->close();
?>

Capitolo 3: Realizzazione della rete in Packet Tracer

A questo punto possiamo aprire Packet Tracer, posizionare i componenti e collegarli tra loro. opportunamente. Questa sezione è dettagliata nell’articolo iot-e-ioe-con-cisco-packet-tracer per cui invito il lettore a fare riferimento all’articolo indicato.

Dopo aver realizzato la rete possiamo cliccare sull’MCU, e nella scheda “Programming” creare un nuovo progetto. Prima di fare ciò esploriamo i template offerti che utilizzano il protocollo Real HTTP, in particolare Real HTTP Client – Python e Real HTTP Server – Python

real http client
real http client
Real http server
Real http server

Da questi template si evince come programmare l’MCU in modo che possa funzionare da client o da server inviando dati ad un server reale o ricevendo dati con HTTP request

Creiamo quindi il progetto “IoT Real HTTP” scegliendo un template vuoto Python e iniziamo a scrivere il codice nel file main.py

Importiamo tutte le funzioni dai moduli realHttp, gpio, time e json

#import delle funzioni 
from realhttp import *
from gpio import *
from time import *
import json

Eseguiamo il setup dei pin, inizializziamo il client e avviamo il server. Ricordiamo che quando l’MCU invia i dati al server reale si comporta come clientmentre quando riceve richieste HTTP e deve fornire un servizio (in questo caso il controllo del LED) si comporta come server. Inizializziamo lo stato del LED:

# ---------------- SETUP ----------------#
# Configurazione dei pin
pinMode(1, OUT)  # Configura il pin 1 come uscita per il LED
pinMode(2, OUT)  # Configura il pin 2 come uscita per il display

# Inizializzazione del client e del server HTTP
client = RealHTTPClient()

# Avvio del server HTTP
server = RealHTTPServer()
print("Server started: %s" % (server.start(8765)))
print("Server listening: %s" % (server.isListening()))

# Inizializzazione del LED
stato_led = "spento"

#------------------------------------------#

Definiamo la funzione e la route Python che consentono di controllare il LED. Nel momento in cui dalla dashboard premo il pulsante “Accendi” o “Spegni” viene richiamata la API REST che crea l’endpoint attraverso una richiesta HTTP GET pasando il parametro “comando”. La funzione control_led riceve in input la richiesta HTTP, estrae il valore del parametro “comando” e, a seconda che questo sia 1 o 0 accende o spegne il led:

#-------------- CONTROLLO LED -------------#
# Funzione per controllare il LED via HTTP
def control_led(context, request, reply):
    global stato_led  # Permette di modificare la variabile globale

    print("Ricevuto comando per LED:", request.url())

    # Estrazione del parametro 'comando' dalla query string 
    url = request.url()
    comando = None

    if "?" in url:
        query_string = url.split("?")[1]
        params = dict(param.split("=") for param in      query_string.split("&") if "=" in param)
        comando = params.get("comando")  # Ottiene il valore del parametro 'comando', se presente

    # Controllo e gestione del LED
    if comando == "1":
        digitalWrite(1, HIGH)  # Accende il LED (pin 1)
        stato_led = "acceso"
        reply.setContent("LED acceso!")
    elif comando == "0":
        digitalWrite(1, LOW)  # Spegne il LED (pin 1)
        stato_led = "spento"
        reply.setContent("LED spento!")
    else:
        reply.setContent("Comando non valido! Usa 'comando=1' per accendere o 'comando=0' per spegnere.")

    reply.setStatus(200)
    reply.end()
#------------ ROUTE CONTROLLO LED ----------#
# Route per controllare il LED
server.route("/control_led", ["GET"], control_led)

#-------------------------------------------#

Definiamo il ciclo principale. Packet Tracer non supporta il threading per cui esegue in modo sequenziale le istruzioni non permettendo l’esecuzione parallela. Nel ciclo principale leggiamo il valore del potenziometro, lo riportiamo sul display, creiamo l’ oggetto JSON contenente valore del potenziometro e stato attuale del LED.

Non ci resta che creare l’endpoint che rappresentano il corpo- della richiesta HTTP POST che l’MCU fa al server reale. Il Server elaborerà la richiesta e memorizzerà i dati nella tabella del database. Attendiamo poi due secondi prima dell’invio della richiesta successiva

#----- Ciclo principale senza threading-----#
while True:
    # Legge il valore del potenziometro
    valPot = analogRead(A0)

    # Scrive il valore del potenziometro sul display
    customWrite(2, valPot)

    # Creazione dell'oggetto JSON con il valore del potenziometro e lo stato del LED
    data = {"valore": valPot, "led_status": stato_led}  # Corretto il nome della chiave
    json_data = json.dumps(data)

    # Creazione della richiesta HTTP POST per inviare i dati
    try:
        request = client.post("http://127.0.0.1/test/IoT/iot_data.php", json_data, "application/json")
        print("Dati inviati:", json_data)
    except Exception as e:
        print("Errore nell'invio dei dati:", e)

    # Attesa prima di inviare nuovamente i dati
    sleep(2)

Capitolo 4: Sviluppo della Dashboard

La dashboard è sviluppata in PHP e utilizza Bootstrap per il layout e Chart.js per la rappresentazione grafica dei dati. Dobbiamo connetterci al database e recuperare i dati e valorizzare con essi le porzioni di codice che ci consentono di visualizzare i valori

4.1 Connessione al Database

$servername = "localhost";
$username = "root";
$password = "";
$dbname = "iot_database";

$conn = new mysqli($servername, $username, $password, $dbname);
if ($conn->connect_error) {
    die("Connessione fallita: " . $conn->connect_error);
}

4.2 Recupero dati dal database

Occorre recuperare i dati dal database limitandosi, ad esempio agli ultimi 10 valori e selezionare l’ultimo stato del LED

$sql = "SELECT * FROM sensor_data ORDER BY id DESC LIMIT 10";
$result = $conn->query($sql);
$data = [];
if ($result->num_rows > 0) {
    while ($row = $result->fetch_assoc()) {
        $data[] = $row;
    }
}

// Ottieni lo stato più recente del LED
$sql_led_status = "SELECT led_status FROM sensor_data ORDER BY id DESC LIMIT 1";
$result_led_status = $conn->query($sql_led_status);
$led_status = ($result_led_status->num_rows > 0) ? $result_led_status->fetch_assoc()['led_status'] : 'spento';

$conn->close();
?>

4.3 Struttura della Dashboard

La dashboard presenta:

  • Un pannello di controllo per accendere o spegnere il LED.
  • Una tabella con gli ultimi 10 valori ricevuti.
  • Un grafico interattivo con l’andamento del potenziometro nel tempo.

Di seguito la sezione HEAD. Si nota la funzione jJavaScript per il refresh automatico ogni 2 secondi

<!DOCTYPE html>
<html lang="it">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Dashboard IoT</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <script>
        function refreshPage() {
            location.reload();
        }
        setInterval(refreshPage, 2000);
    </script>
</head>

Qui in basso invece c’è la sezione del controllo e visualizzazione LED. Si noti la riga di codice <?= $led_status == ‘acceso’ ? ‘yellow’ : ‘#ccc’; ?> che riempie il cerchio, che rappresenta il LED, in giallo se è acceso io in grigio se spento

Copy
<body class="bg-light">
    <div class="container mt-5">
        <h2 class="text-center">Dashboard IoT</h2>
        
        <div class="card p-3 mt-4">
        <div class="row">
    <!-- Sezione Controllo LED -->
    <div class="col-6 text-center">
        <h4>Controllo LED</h4>
        <form method="get" action="control_led.php" class="d-inline">
            <input type="hidden" name="comando" value="1">
            <button type="submit" class="btn btn-success mb-2 w-100">Accendi</button>
        </form>
        <form method="get" action="control_led.php" class="d-inline w-100">
            <input type="hidden" name="comando" value="0">
            <button type="submit" class="btn btn-danger w-100">Spegni</button>
        </form>
    </div>

    <!-- Sezione Stato LED -->
    <div class="col-6 text-center">
        <h4>Stato LED</h4>
        <div class="d-flex justify-content-center align-items-center mt-2">
            <div id="ledCircle" class="rounded-circle" 
                 style="width: 50px; height: 50px; background-color: <?= $led_status == 'acceso' ? 'yellow' : '#ccc'; ?>;">
            </div>
        </div>
    </div>
</div>

Segue la sezione della tabella . Si noti la valorizzazione delle righe con i dati recuperati dal database:

Copy
<div class="row">
            <!-- Tabella -->
            <div class="col-md-4">
                <div class="card p-3">
                    <h4 class="text-center">Valori e stato del LED</h4>
                    <table class="table table-striped">
                        <thead>
                            <tr>
                                <th>Timestamp</th>
                                <th>Potenziometro</th>
                                <th>LED</th>
                            </tr>
                        </thead>
                        <tbody>
                            <?php foreach ($data as $row): ?>
                                <tr>
                                    <td><?= $row['timestamp'] ?></td>
                                    <td><?= $row['valore'] ?></td>
                                    <td><?= $row['led_status'] ?></td>
                                </tr>
                            <?php endforeach; ?>
                        </tbody>
                    </table>
                </div>
            </div>

4.4 Grafico dei dati con Chart.js

Infine abbiamo la sezione del grafico ottenuto con chart.js

Copy
 <!-- Grafico -->
            <div class="col-md-8">
                <div class="card p-3">
                    <h4 class="text-center">Trend dei valori</h4>
                    <canvas id="iotChart"></canvas>
                </div>
            </div>
        </div>
    </div>
    
    <script>
        let labels = <?= json_encode(array_column($data, 'timestamp')) ?>.reverse();
        let values = <?= json_encode(array_column($data, 'valore')) ?>.reverse();
        
        const ctx = document.getElementById('iotChart').getContext('2d');
        const chart = new Chart(ctx, {
            type: 'line',
            data: {
                labels: labels,
                datasets: [{
                    label: 'Valore Potenziometro',
                    data: values,
                    borderColor: 'rgb(75, 192, 192)',
                    tension: 0.1
                }]
            },
            options: {
                responsive: true,
                scales: {
                    x: { title: { display: true, text: 'Tempo' } },
                    y: { title: { display: true, text: 'Valore' } }
                }
            }
        });
    </script>
</body>
</html>

Questa configurazione genera un grafico lineare, mostrando l’andamento dei valori nel tempo con un effetto di curvatura morbida tra i punti.


Capitolo 5: Test finale

Dopo aver avviato tutte le componenti (APACHE, MySQL e “run” del codice MCU) , testiamo il flusso completo:

  1. Muovere il cursore del potenziometro nell’MCUVisualizziamo il valore aggiornato sulla dashboard
  2. Cliccare “Accendi” sulla dashboardLED si accende nell’MCU
  3. Verificare i dati nel database

Clicca qui per fare il download del progetto: DOWNLOAD oppure vai alla pagina dei download relativa a tutti i progetti IoT con Packet Tracer

Conclusioni

Come abbiamo visto la versatilità di Cisco Packet tracer ci consente di testare su server reali le reti permettendoci di realizzare progetti completi interdisciplinari

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *