Surveiller les conditions environnementales permet de combiner l’IoT avec les outils d’observabilité cloud-native. Dans cet article, nous construisons une configuration pour surveiller la température et l’humidité à l’aide d’un Raspberry Pi et d’un capteur DHT22 (AM2302).
Au programme :
- câblage du capteur sur le Raspberry Pi
- collecte des données du capteur
- exposition des mesures via un exporter Prometheus personnalisé écrit en Go
- visualisation dans Grafana
Présentation du capteur : AM2302 (DHT22)

L’AM2302 est une version filaire du DHT22. Peu coûteux et simple à intégrer avec les GPIO du Raspberry Pi.
Caractéristiques principales :
- Tension : 3V à 5V (logique et alimentation)
- Consommation : max 2,5 mA lors des mesures
- Plage d’humidité : 0–100% HR (±2–5%)
- Plage de température : -40°C à +80°C (±0,5°C)
- Fréquence d’échantillonnage : max une lecture toutes les 2 secondes (0,5 Hz)
- Dimensions : 15,1 mm × 25 mm × 7,7 mm
- Interface : header 4 broches (espacement 0,1”)
Il est suffisamment précis pour une utilisation en intérieur et facile à intégrer avec les GPIO d’un Raspberry Pi.
Câblage du capteur sur le Pi
Le câblage de l’AM2302 est simple :
| Broche capteur | Fonction | Broche Raspberry Pi |
|---|---|---|
| 1 | VCC (Alim.) | 3,3V (Pin 1) |
| 2 | Data | GPIO 4 (Pin 7) |
| 3 | Non utilisée | - |
| 4 | GND | Ground (Pin 6) |
Tester le capteur
Avant d’écrire un exporter Prometheus, testez le capteur avec un script Python basique.
Installer les dépendances :
$ sudo apt-get update
$ sudo apt-get install python3 python3-pip
$ python3 -m pip install --upgrade pip
$ python3 -m pip install Adafruit-DHT
Le script de test :
import Adafruit_DHT
sensor = Adafruit_DHT.DHT22
pin = 4
humidity, temperature = Adafruit_DHT.read_retry(sensor, pin)
print(f'Temp: {temperature:.1f}°C, Humidity: {humidity:.1f}%')
Rendre le script exécutable et le lancer :
$ chmod +x am2302_tester.py
$ python3 am2302_tester.py
Temp=19.2*C Humidity=55.0%
Tout fonctionne correctement. On peut maintenant passer au développement de l’exporter Prometheus.
Écriture d’un exporter Prometheus en Go
Configuration
La configuration est un fichier YAML dont l’emplacement suit cet ordre de priorité :
/etc/dht-prometheus-exporter.yml$HOME/dht-prometheus-exporter.yml$PWD/dht-prometheus-exporter.yml
Le package viper gère le fichier de configuration et stocke les valeurs dans une struct. Les informations récurrentes sont ainsi accessibles aux autres parties du projet.
package main
import (
"fmt"
"github.com/spf13/viper"
)
type Config struct {
path string
name string
gpio string
maxRetries int
listenPort int
defaultLogLevel string
temperatureUnit string
}
func ReadConfig() *Config {
viper.SetConfigName("dht-prometheus-exporter")
viper.SetConfigType("yaml")
viper.AddConfigPath("/etc")
viper.AddConfigPath("$HOME")
viper.AddConfigPath(".")
err := viper.ReadInConfig()
if err != nil {
panic(fmt.Sprintf("Error when reading config file: %v", err))
}
config := &Config{
path: viper.ConfigFileUsed(),
name: viper.GetString("name"),
gpio: fmt.Sprintf("GPIO%d", viper.GetInt("gpio_pin")),
maxRetries: viper.GetInt("max_retries"),
listenPort: viper.GetInt("listen_port"),
defaultLogLevel: viper.GetString("log_level"),
temperatureUnit: viper.GetString("temperature_unit"),
}
return config
}
Logging
Le package logrus gère les logs et réutilise le niveau défini dans la configuration :
package main
import (
log "github.com/sirupsen/logrus"
"os"
"sync"
)
const defaultLogLevel = log.InfoLevel
var (
once sync.Once
instance log.Logger
)
func LogLevel(levelName string) log.Level {
var logLevel log.Level
switch levelName {
case "debug":
logLevel = log.DebugLevel
case "info":
logLevel = log.InfoLevel
case "warn":
logLevel = log.WarnLevel
case "error":
logLevel = log.ErrorLevel
case "fatal":
logLevel = log.FatalLevel
case "panic":
logLevel = log.PanicLevel
default:
logLevel = defaultLogLevel
}
return logLevel
}
func getLogger(config *Config) log.Logger {
once.Do(func() {
instance = log.Logger{
Out: os.Stderr,
Level: LogLevel(config.defaultLogLevel),
Formatter: &log.TextFormatter{
DisableColors: true,
FullTimestamp: true,
},
}
})
return instance
}
Capteur
Le package go-dht fait l’interface avec le capteur. Il fournit déjà une fonction pour lire les données depuis la broche GPIO.
Une struct contient les métriques collectées :
package main
import (
"fmt"
"github.com/MichaelS11/go-dht"
)
type Sensor struct {
config *Config
temperatureSymbol string
client *dht.DHT
}
const CelsiusSymbol = "C"
const FahrenheitSymbol = "F"
func newSensor(config *Config) *Sensor {
var err error
var client *dht.DHT
var temperatureSymbol string
lg.Info("Initializing the DHT22/AM2302 sensor on the host")
err = dht.HostInit()
if err != nil {
lg.Panic("Failed to initialized DHT22/AM2302 sensor on the host: ", err)
}
if config.temperatureUnit == "celsius" {
client, err = dht.NewDHT(config.gpio, dht.Celsius, "")
temperatureSymbol = CelsiusSymbol
} else {
client, err = dht.NewDHT(config.gpio, dht.Fahrenheit, "")
temperatureSymbol = FahrenheitSymbol
}
if err != nil {
lg.Panic("Failed to create new DHT client: ", err)
}
return &Sensor{
config: config,
temperatureSymbol: temperatureSymbol,
client: client,
}
}
func (s *Sensor) readRetry() (humidity float64, temperature float64, err error) {
humidity, temperature, err = s.client.ReadRetry(s.config.maxRetries)
if err != nil {
lg.Error("Cannot retrieve humidity and temperature from the sensor: ", err)
}
lg.Info(fmt.Sprintf("Retrieved humidity=%.2f%%, temperature=%.2f°%s from the sensor",
humidity, temperature, s.temperatureSymbol))
return humidity, temperature, err
}
Collector Prometheus
Le package Prometheus fournit le collector. Il liste les différentes métriques à collecter et les expose. Les métriques d’humidité et de température sont dans le même collector. Les métriques ont des labels pour donner plus de contexte :
dht_name: nom du capteur pour l’identifier facilement (en cas de capteurs multiples)hostnameunit: l’unité de température en Fahrenheit ou Celsius
Les fonctions Describe et Collect sont déjà implémentées dans le package Prometheus. Nous devons les redéfinir pour les surcharger avec nos métriques :
package main
import (
"github.com/prometheus/client_golang/prometheus"
"os"
)
type Collector struct {
sensor *Sensor
temperatureMetric *prometheus.Desc
humidityMetric *prometheus.Desc
}
func newCollector(s *Sensor) *Collector {
lg.Debug("Creating a new prometheus collector for the sensor")
return &Collector{
sensor: s,
temperatureMetric: prometheus.NewDesc("dht_temperature_degree",
"Temperature degree measured by the sensor",
[]string{"dht_name", "hostname", "unit"}, nil,
),
humidityMetric: prometheus.NewDesc("dht_humidity_percent",
"Humidity percent measured by the sensor",
[]string{"dht_name", "hostname"}, nil,
),
}
}
func (c *Collector) Describe(ch chan<- *prometheus.Desc) {
ch <- c.temperatureMetric
ch <- c.humidityMetric
}
func (c *Collector) Collect(ch chan<- prometheus.Metric) {
humidity, temperature, _ := c.sensor.readRetry()
hostname, err := os.Hostname()
temperatureUnit := c.sensor.config.temperatureUnit
dhtName := c.sensor.config.name
if err != nil {
lg.Error("Failed to get hostname")
}
ch <- prometheus.MustNewConstMetric(c.temperatureMetric, prometheus.CounterValue, temperature, dhtName, hostname, temperatureUnit)
ch <- prometheus.MustNewConstMetric(c.humidityMetric, prometheus.CounterValue, humidity, dhtName, hostname)
}
Fichier principal
Le fichier principal assemble les fonctions des fichiers précédents. Il crée le client chargé de lire les données depuis la broche GPIO, instancie le collector avec ce client et l’enregistre.
Il configure ensuite le serveur promhttp avec le logging et le démarre. Les métriques sont accessibles sur le chemin /metrics :
package main
import (
"fmt"
"github.com/coreos/go-systemd/daemon"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
log "github.com/sirupsen/logrus"
stdlibLog "log"
"net/http"
)
var lg log.Logger
func main() {
config := ReadConfig()
lg = getLogger(config)
w := lg.Writer()
defer w.Close()
sensor := newSensor(config)
collector := newCollector(sensor)
lg.Debug("Registering the prometheus collector")
prometheus.MustRegister(collector)
http.Handle("/metrics", promhttp.HandlerFor(prometheus.DefaultGatherer, promhttp.HandlerOpts{
ErrorLog: stdlibLog.New(w, "", 0),
}))
lg.Info(fmt.Sprintf("Starting http server on TCP/%d port", config.listenPort))
lg.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", config.listenPort), nil))
daemon.SdNotify(false, daemon.SdNotifyReady)
}
Une fois démarré, le binaire expose les métriques collectées sur requête :
$ pi@raspberrypi:~/go/src/github.com/guivin/dht-prometheus-exporter $ ./dht-exporter
time="2021-02-17T20:19:20Z" level=info msg="Initializing the DHT22/AM2302 sensor on the host"
time="2021-02-17T20:19:20Z" level=info msg="Starting http server on TCP/8080 port"
time="2021-02-17T20:19:36Z" level=info msg="Retrieved humidity=62.70%, temperature=68.90°F from the sensor"
La requête se fait sur le port TCP/8080.
Déployer l’exporter Prometheus sur le Raspberry
Installer golang et make pour compiler le code source :
sudo apt install golang make
Définir les variables d’environnement pour Golang :
export GOPATH=$HOME/go
export GOBIN=$GOPATH/bin
export PATH=$PATH:$GOBIN
Télécharger le package dep :
go get -u github.com/golang/dep/cmd/dep
Cloner le dépôt git et compiler les sources :
go get -u github.com/guivin/dht-prometheus-exporter.git
cd $GOPATH/src/guivin/dht-prometheus-exporter
git checkout tags/v0.1
make all
Installer le service systemd :
sudo cp dht-prometheus-exporter.service /etc/systemd/system
Déployer le fichier de configuration :
sudo cp dht-prometheus-exporter.yml /etc
sudo chown dht-prometheus-exporter:dht-prometheus-exporter /etc/dht-prometheus-exporter.yml
sudo chmod 0640 /etc/dht-prometheus-exporter.yml
Créer un utilisateur et groupe système dht-prometheus-exporter pour l’exporter. Cet utilisateur appartient aussi au groupe gpio en tant que groupe secondaire pour lire les broches GPIO :
sudo useradd --user-group --groups gpio --no-create-home --system --shell /usr/sbin/nologin dht-prometheus-exporter
Démarrer le service systemd :
sudo systemctl daemon-reload
sudo systemctl start dht-prometheus-exporter
Vérifier les logs du service systemd :
$ sudo journalctl -u dht-prometheus-exporter -f
Feb 18 07:14:25 raspberrypi dht-prometheus-exporter[4031]: time="2021-02-18T07:14:25Z" level=info msg="Initializing the DHT22/AM2302 sensor on the host"
Feb 18 07:14:25 raspberrypi dht-prometheus-exporter[4031]: time="2021-02-18T07:14:25Z" level=info msg="Starting http server on TCP/8080 port"
Enregistrer l’exporter dans Prometheus
Créer l’utilisateur et le groupe système prometheus :
useradd --home-dir /opt/prometheus --user-group --shell /usr/sbin/nologin --system prometheus
Télécharger le binaire prometheus et adapter selon l’architecture de votre Raspberry Pi (ici armv7) :
cd /tmp
wget https://github.com/prometheus/prometheus/releases/download/v2.25.0/prometheus-2.25.0.linux-armv7.tar.gz
tar xzf prometheus-2.25.0.linux-armv7.tar.gz
Créer le répertoire home pour l’utilisateur prometheus dans /opt/prometheus :
sudo cp -r prometheus-2.25.0.linux-armv7 /opt/prometheus
sudo chown prometheus:prometheus /opt/prometheus
sudo chmod 0740 -R /opt/prometheus
Créer le répertoire /var/lib/prometheus avec les bonnes permissions pour le stockage tsdb :
sudo mkdir /var/lib/prometheus
sudo chown -R prometheus:prometheus /var/lib/prometheus
sudo chmod -R 0740 /var/lib/prometheus
Créer un lien symbolique depuis la configuration dans /opt/prometheus vers /etc :
sudo ln -s /opt/prometheus/prometheus.yml /etc/prometheus.yml
Mettre à jour /etc/prometheus.yml et ajouter un nouveau job dans le bloc scrape_configs :
- job_name: 'dht-prometheus-exporter'
static_configs:
- targets: ['localhost:8080']
Créer un service systemd pour prometheus dans /etc/systemd/system/prometheus.service :
[Service]
Type=simple
User=prometheus
Group=prometheus
ExecReload=/bin/kill -HUP $MAINPID
ExecStart=/opt/prometheus/prometheus \
--config.file=/etc/prometheus/prometheus.yml \
--storage.tsdb.path=/var/lib/prometheus \
--storage.tsdb.retention.time=30d \
--storage.tsdb.retention.size=0 \
--web.console.libraries=/opt/prometheus/console_libraries \
--web.console.templates=/opt/prometheus/consoles \
--web.listen-address=0.0.0.0:9090 \
--web.external-url=
SyslogIdentifier=prometheus
Restart=always
[Install]
WantedBy=multi-user.target
Démarrer et activer le service prometheus :
sudo systemctl daemon-reload
sudo systemctl start prometheus
sudo systemctl enable prometheus
Vérifier que prometheus fonctionne :
sudo systemctl status prometheus --no-pager
Notez l’IP privée de votre Raspberry Pi avec la commande ip a.
Ouvrez votre navigateur et connectez-vous via le port TCP/8080 avec le schéma HTTP (ex. http://<IP_RASPBERRY>:8080).
L’exporter est bien enregistré dans le menu “Targets” :

Sur la page d’accueil de l’interface Prometheus, nous saisissons les noms des deux métriques de l’exporter. Elles sont présentes et affichent les labels définis précédemment :


On peut également afficher un graphe des métriques de l’exporter :

Grafana
Installation
Pour Grafana, vous pouvez suivre les instructions disponibles ici : https://grafana.com/tutorials/install-grafana-on-raspberry-pi
Création du dashboard
On accède à l’interface Grafana et on crée un nouveau dashboard. Sur ce dashboard, on ajoute un graphe séparé pour chaque métrique : température et humidité.
Pour les deux graphes, on utilise la fonction PromQL avg_over_time avec un vecteur de plage d’1 minute. Cette approche lisse les données et évite les trous ou discontinuités dans les graphes.

Conclusion
À travers ces étapes, nous avons construit un système de monitoring complet à partir d’un capteur et d’un Raspberry Pi. Nous avons câblé le capteur, validé l’acquisition des données et mesuré avec succès la température et l’humidité ambiantes.
Pour exposer ces métriques à Prometheus, nous avons développé un exporter personnalisé en Go qui sert les données via un endpoint HTTP. Nous avons aussi ajouté des labels à nos métriques, ce qui est essentiel pour suivre plusieurs capteurs et les distinguer.
Ensuite, nous avons installé et configuré Prometheus sur le Raspberry Pi pour scraper l’exporter et stocker les métriques. Via l’interface de Prometheus, on peut interroger et analyser les données efficacement.
Alors que Prometheus offre une visualisation basique, nous avons enrichi l’expérience en ajoutant Grafana, créant un dashboard riche et interactif affichant les tendances de température et d’humidité.
Ce projet couvre l’ensemble du pipeline : collecte, stockage et visualisation des données depuis un capteur physique.