Il controllo della temperatura con un Raspberry Pi e Amazon AWS

Il controllo della temperatura può rivelarsi molto utile per esempio nel caso di una non abitata, soprattutto nei mesi invernali, con termosifoni spenti oppure con solo la funzione antigelo attivata. In questo articolo vediamo come si può realizzare in modo abbastanza semplice un sistema che permette di leggere e memorizzare la temperatura interna di una casa.

Per realizzare quanto descritto nell’articolo servono:

  • Raspberry Pi – Il codice eseguito è molto semplice e quindi va bene qualsiasi, meglio ti tipo B con connettore Ethernet;
  • Sensore di temperatura – In questo articolo utilizzerò come esempio il KY-015 (con il sensore di temperatura ed umidità DHT11) montato sulla schedina Sunfounder;
  • Un account Amazon AWS per l’hosting del webservice che verrà chiamato dal codice presente su Raspberry e memorizzerà la temperatura in un database DynamoDB.

In alternativa al DHT11 (datasheet) si può utilizzare anche una altro sensore come per esempio il DHT22 (datasheet), più preciso dell’11 utilizzato in questo articolo, il BMP180 (datasheet) che misura temperatura e pressione o il DS18b20 (datasheet).

Il controllo della temperatura: schema elettrico

Nella seguente immagine sono illustrati i semplici collegamenti elettrici utilizzati. La schedina ha tre pin: Vcc, GND e SIG per l’invio e ricezione dei dati. La schedina con il sensore di temperatura (e umidità) in mio possesso accetta come alimentazione una tensione compresa tra 3 e 5 V. Visto che i PIN di input/output del Raspberry Pi accettano una tensione massima di 3.3 V utilizziamo come alimentazione del sensore i 3.3 V forniti dal Raspberry Pi.

Lo schema dei collegamenti elettrici per il controllo della temperatura con Raspberry Pi

Il pin utilizzato per la comunicazione con la scheda sensore è il GPIO 21 ma ovviamente puoi utilizzarne un altro disponibile.

Il controllo della temperatura: codice Python

Il linguaggio di programmazione comunemente utilizzato con il Raspberry Pi è il Python. Il codice qui utilizzato non è particolarmente complesso e dovrebbe essere abbastanza comprensibile. Ricordo solo una caratteristica molto particolare del Python. A differenza di molti altri linguaggi di programmazione, i blocchi di codice non sono definiti da parentesi graffe o da parole chiave del linguaggio ma dall’indentazione. Questo porta ad un codice che è automaticamente più ordinato ma può causare qualche grattacapo soprattutto quando si fanno delle prove e non si presta troppa attenzione all’ordine.

Ho provato diverse librerie che permettono di ottenere letture dal sensore DHT11 e la più affidabile si è rivelata la Adafruit_Python_DHT. Ho provato anche altre librerie adafruit ma non funzionano troppo bene, spesso non riescono ad effettuare la lettura. Quindi vi consiglio di utilizzare quella indicata.

La versione attuale di Raspberry Pi OS (io normalmente utilizzo la versione Lite senza Desktop), la Bullseye 11, dispone già dell’ambiente Python 3. Per verificare la versione del sistema operativo installato e la versione di Python si possono utilizzare i seguenti comandi:

cat /etc/os-release
python --version

Per utilizzare la libreria Adafruit_Python_DHT è necessario di disporre di pip, il sistema di gestione dei pacchetti Python. Si utilizzerà apt-get per effettuare l’installazione ed è sempre consigliabile prima assicurarsi di aggiornare tutte le librerie presenti nel sistema. I comandi necessari sono di seguito indicati. Come in tutti i sistemi basati su Linux il comando sudo permette di ottenere temporaneamente i permessi di root anche se se è fatto il login con un utente che non li dispone. L’utente creato in fase di installazione di Raspberry Pi OS ha i permessi necessari all’esecuzione del comando sudo.

sudo apt-get update
sudo apt-get upgrade

sudo apt-get install python3-pip

Installazione della libreria Adafruit_Python_DHT ed utilizzo

A questo punto possiamo finalmente installare la libreria necessaria per l’accesso al sensore di temperatura.

sudo pip3 install Adafruit_Python_DHT

Il codice che fornisce la lettura di temperatura ed umidità mediante l’utilizzo della libreria appena installata è qui riportato. Nel caso si utilizzi il sensore DHT22 basta modificare la riga in cui si definisce la costante DHT_SENSOR.

#!/usr/bin/python

import Adafruit_DHT

DHT_SENSOR = Adafruit_DHT.DHT11
DHT_PIN = 21

humidity, temperature = Adafruit_DHT.read_retry(DHT_SENSOR, DHT_PIN)

if humidity is not None and temperature is not None:
    print("Temperatura: {0:0.1f}°C  Umidità: {1:0.1f}%".format(temperature, humidity))
else:
    print("Si è verificato un errore nella lettura.")

Questo un esempio di lettura con DHT11:

Temperatura: 22.0°C  Umidità: 35.0%

Questo un esempio di lettura con DHT22:

Temperatura: 21.8°C  Umidità: 50.1%

Come si vede la lettura di umidità si discosta molto ed è da considerare più precisa quella fornita dal DHT22. Se interessa misurare temperature che possono andare sotto lo zero il DHT11 non è adatto allo scopo. Si può usare il DHT22 oppure, se si vuole una misura della temperatura più precisa, è consigliabile l’utilizzo di altri sensori come per esempio il DS18b20.

Nota sull’utilizzo del sensore DS18b20

Nel caso si opti per l’ottimo sensore di temperatura DS18b20 ricorda che questo sensore utilizza il protocollo One-Wire che è supportato da Raspberry Pi OS ma va abilitato nella sezione “Interface Options” di raspi-config (comando che richiede i permessi di root quindi va eseguito con sudo). Può essere utilizzato qualsiasi pin di IO ma di default One-Wire su Raspberry utilizza il GPIO 4.

Questi i comandi da dare dopo aver abilitato One-Wire ed effettuato il remoto:

sudo modprobe w1-gpio
sudo modprobe w1-therm
cd /sys/bus/w1/devices
ls

La cartella dovrebbe contenere una cartella il cui nome inizia con “28-“. Se si entra nella cartella e si da il comando “cat w1_slave” si può verificare la lettura di temperatura. Verificato in questo modo la correttezza dei collegamenti ed il funzionamento del sensore è possibile utilizzarlo da codice. In questo articolo non vedremo i dettagli di utilizzo di questo sensore ma volevo dare alcune indicazioni nel caso tu lo possieda o lo voglia usare.

Web service su AWS

Realizziamo ora il web service in Node.js che verrà eseguito su Amazon AWS. E’ possibile utilizzare la tecnologia preferita e l’hosting preferito per realizzare il web service. Visto che nello sviluppo delle skill di Alexa utilizzo di solito AWS, lo utilizzerò anche per il web service.

Il web service verrà eseguito su una AWS Lambda ed utilizzerà DynamoDB come database per memorizzare le misure ricevute.

Per il deploy su AWS utilizzerò il framework Serverless di cui ho già parlato in questo articolo. Qui ipotizzerò che tu abbia seguito le indicazioni presenti nell’articolo per configurare quanto necessario per il deploy con serverless.

Codice javascript

Questo il codice di app.js per l’implementazione del webservice che verrà chiamato dal codice Python eseguito dal Raspberry Pi.

const sls = require('serverless-http');
const express = require('express');
const AWS = require('aws-sdk');
const moment = require('moment-timezone');

const dynamoDb = new AWS.DynamoDB();

const app = express();

app.get('/', async (req, res) => {
  const date = new Date();
  const dateISO = moment(date.getTime()).tz("Europe/Rome").format("YYYYMMDD-HHmmss");

  const temperature = req.query.temperature;
  const humidity = req.query.humidity;

  const item = {
    TableName: process.env.tablename,
    Item: {
      'id': { S: 'sample' },
      'temperature': { N: temperature },
      'humidity': { N: humidity },
      'timestamp': { S: dateISO },
    },
  };

  try {
    await dynamoDb.putItem(item).promise();
  } catch (e) {
    console.log(e);
  }
  
  res.send(`Temperature: ${temperature}, Humidity: ${humidity}`);
});

module.exports.handler = sls(app);

Si tratta di codice molto semplice che utilizza express per l’implementazione del web service con Node.js per rispondere alla seguente URL:

https://<endpoint>?temperature=<temp>&humidity=<hum>

Quando verrà effettuato il deploy con serverless si otterrà l’endpoint AWS da inserire nella URL. Nel codice javascript vengono letti i parametri di temperatura e pressione inseriti e viene inserito un nuovo elemento nel database DynamoDB. Ricordo che DynamoDB è un database NoSQL quindi non relazionale che è adatto all’inserimento di dati non strutturati come nel nostro caso. Il codice termina inviando al chiamate i valori di temperatura e umidità. Questo non è obbligatorio e nel codice Python non utilizzeremo i dati restituiti, in alternativa potremmo ritornare semplicemente “OK” per indicare che l’operazione è stata eseguita correttamente.

Oltre ad express nel codice vengono utilizzati i seguenti pacchetti Node.js:

  • serverless-http: serve per indicare l’esecutore delle chiamate http ricevute dall’esterno. Nel nostro caso express.
  • express: come già detto si tratta del gestore delle chiamate http
  • aws-sdk: permette l’accesso da codice ai componenti di Amazon AWS. In questo caso è utilizzato per le chiamate al database DynamoDB
  • moment-timezone: serve per le conversioni di date e tempi visto che il server su cui viene eseguito il codice potrebbe non avere lo stesso fuso orario che ci interessa.

Per installare questi pacchetti si utilizza il comando npm con il parametro che permette di aggiornare il file package.json. Questa la sequenza di comandi:

npm i serverless-http --save
npm i express --save
npm i aws-sdk --save
npm i moment-timezone --save 

Per finire il codice da inserire nel file serverless.yml con quanto serve per la definizione della tabella DynamoDB con i relativi permessi di accesso e della lambda function AWS.

service: dht11temp

provider:
  name: aws
  runtime: nodejs16.x
  profile: default
  memorySize: 256
  stage: ${opt:stage}
  region: eu-west-1
  versionFunctions: false
  iam:
    role:
      statements:
        - Effect: Allow
          Action: 
            - dynamodb:PutItem
            - dynamodb:Scan
            - dynamodb:GetItem
            - dynamodb:UpdateItem
          Resource: 
            - arn:aws:dynamodb:${aws:region}:${aws:accountId}:table/DHT11TEMP-${opt:stage}

functions:
  app:
    handler: app.handler
    environment:
      tablename: DHT11TEMP-${opt:stage}

    events: 
      - http: ANY /
      - http: ANY /{proxy+}

resources:
  Resources:
    WeatherStation:
      Type: AWS::DynamoDB::Table
      Properties:
        TableName: DHT11TEMP-${opt:stage}
        BillingMode: PAY_PER_REQUEST
        AttributeDefinitions:
          - AttributeName: id
            AttributeType: S
          - AttributeName: timestamp
            AttributeType: S
        KeySchema:
          - AttributeName: id
            KeyType: HASH
          - AttributeName: timestamp
            KeyType: RANGE

Nel file package.json di solito aggiungo gli script per il deploy in sviluppo e in produzione con serverless:

"scripts": {
    "deploydev": "sls deploy --verbose --stage dev",
    "deploy": "sls deploy --verbose --stage prod",
    ...
}

Con il seguente comando si ottiene il deploy in dev o in prod su AWS:

npm run deploydev
npm run deploy

Codice Python aggiornato

Aggiorniamo ora il codice Python in modo da inviare i dati utilizzando il webservice appena creato. L’indirizzo dell’endpoint da inserire nella URL si ottiene al termine del deploy con serverless.

#!/usr/bin/python

import Adafruit_DHT
import requests

DHT_SENSOR = Adafruit_DHT.DHT11
DHT_PIN = 21

SERVER_URL = <endpoint AWS>

def send_temperature_humidity_data(temperature, humidity):
    payload = {'temperature': temperature, 'humidity': humidity}
    requests.get(SERVER_URL, params=payload)

humidity, temperature = Adafruit_DHT.read_retry(DHT_SENSOR, DHT_PIN)

if humidity is not None and temperature is not None:
    send_temperature_humidity_data(temperature, humidity)

Per eseguire periodicamente questo codice e quindi effettuare la lettura e l’invio della temperatura al database si può per esempio creare un task cron.

Conclusioni

Abbiamo quindi visto come si può, abbastanza semplicemente, creare un sistema che misura periodicamente la temperatura e la invia al server memorizzandola in un database con data ed ora in cui è stata eseguita la misura.

Il passo successivo sarà la realizzazione di una skill per Alexa a cui si può chiedere l’ultimo valore di temperatura disponibile o per esempio la media dell’ultima settimana.

Avatar Paolo Godino