Exemple : la page web des mesures du capteur
Un seul fichier index.html : il demande les mesures du groupe au serveur AirCarto et les trace avec Chart.js (particules, température et humidité, CO2), avec un choix de période. À ouvrir d'un double-clic pour tester, puis à publier sur GitHub Pages.
Code de l'exemple du guide IUT-AMU-11 « Une page web pour montrer les mesures de votre capteur », v1.0 du 2026-11-02. Les explications sont dans le PDF.
Mise en route
- Téléchargez le projet (bouton vert) et décompressez-le, ou copiez
index.htmlci-dessous avec le bouton Copier dans un fichier du même nom. - Dans
index.html, remplacezVOTRE_CLEpar la clé du groupe. - Double-cliquez sur le fichier : la page s'ouvre dans le navigateur avec vos mesures. F12, onglet Console, si rien ne s'affiche.
- Publiez-la sur GitHub Pages : dépôt public, Upload files, puis Settings › Pages, branche
main(guide, partie 4).
La clé du groupe sera lisible par tous dans la page publiée : c'est accepté pour ce TP, mais quelqu'un pourrait s'en servir pour envoyer des mesures. Valeurs étranges que votre carte n'a pas envoyées : prévenez l'enseignant, il vous donnera une nouvelle clé. Jamais de mot de passe ENT dans la page.
Le projet
Télécharger le projet IUT-AMU-11-exemple-v1.0.zip<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Notre capteur</title>
<!-- Chart.js : la bibliothèque qui dessine les graphes, chargée depuis un CDN -->
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
<style>
body {
font-family: system-ui, sans-serif; color: #1b2420; background: #f6f7f6;
max-width: 900px; margin: 0 auto; padding: 16px;
}
h1 { margin-bottom: 4px; }
.carte {
background: #fff; border: 1px solid #d6ddd8; border-radius: 8px;
padding: 12px 16px; margin: 16px 0;
}
#valeurs { display: flex; flex-wrap: wrap; gap: 24px; }
.valeur b { display: block; font-size: 1.8rem; }
#etat { color: #5a6a60; }
#etat.erreur { color: #b3261e; }
</style>
</head>
<body>
<h1>Notre capteur</h1>
<p>Mesures envoyées par la carte du groupe au serveur AirCarto.</p>
<p>
Période :
<select id="periode">
<option value="-6h">6 dernières heures</option>
<option value="-24h" selected>24 dernières heures</option>
<option value="-7d">7 derniers jours</option>
</select>
</p>
<p id="etat">Chargement…</p>
<div class="carte">
<h2>Dernière mesure</h2>
<div id="valeurs"></div>
</div>
<div class="carte">
<h2>Particules (µg/m³)</h2>
<canvas id="graphe-pm"></canvas>
</div>
<div class="carte">
<h2>Température (°C) et humidité (%)</h2>
<canvas id="graphe-meteo"></canvas>
</div>
<div class="carte" id="carte-co2">
<h2>CO₂ (ppm)</h2>
<canvas id="graphe-co2"></canvas>
</div>
<script>
// ---------- Réglages : à remplacer ----------
const CLE = "VOTRE_CLE"; // clé du groupe (32 caractères)
const DEVICE_ID = ""; // adresse MAC d'une carte, ou "" pour toutes
// Une moyenne par intervalle selon la période : assez de points pour une
// courbe lisible, pas trop pour rester rapide (7 jours en brut = 60 000 points).
const MOYENNE = { "-6h": "5m", "-24h": "15m", "-7d": "1h" };
// Nom affiché et unité de chaque champ de l'API
const CHAMPS = {
pm1: ["PM1", "µg/m³"], pm25: ["PM2.5", "µg/m³"], pm10: ["PM10", "µg/m³"],
temp: ["Température", "°C"], hum: ["Humidité", "%"], co2: ["CO₂", "ppm"],
};
const graphes = {}; // les graphes déjà dessinés, à remplacer au rechargement
// Demande les mesures au serveur et renvoie la liste des points.
async function chargerMesures(periode) {
const params = new URLSearchParams({ key: CLE, start: periode, freq: MOYENNE[periode] });
if (DEVICE_ID) params.set("device_id", DEVICE_ID);
const reponse = await fetch("https://api.aircarto.fr/iut-amu/data?" + params);
const json = await reponse.json();
if (!json.ok) {
// Le serveur explique toujours l'erreur dans "message"
throw new Error(json.message);
}
return json.points;
}
// Dessine (ou redessine) un graphe avec une courbe par champ.
function dessiner(idCanvas, points, champs) {
const etiquettes = points.map(p => new Date(p.time).toLocaleString("fr-FR",
{ weekday: "short", hour: "2-digit", minute: "2-digit" }));
const courbes = champs
.filter(c => points.some(p => p[c] !== undefined)) // seulement les champs mesurés
.map(c => ({
label: CHAMPS[c][0],
data: points.map(p => p[c] ?? null), // null = trou dans la courbe
pointRadius: 0,
borderWidth: 2,
}));
if (graphes[idCanvas]) graphes[idCanvas].destroy();
graphes[idCanvas] = new Chart(document.getElementById(idCanvas), {
type: "line",
data: { labels: etiquettes, datasets: courbes },
options: { interaction: { mode: "index", intersect: false } },
});
return courbes.length;
}
// Affiche la valeur la plus récente de chaque champ.
function afficherDerniere(points) {
const derniere = points[points.length - 1];
const html = Object.keys(CHAMPS)
.filter(c => derniere[c] !== undefined)
.map(c => {
const [nom, unite] = CHAMPS[c];
const valeur = derniere[c].toFixed(c === "co2" ? 0 : 1); // CO2 sans décimale
return `<div class="valeur">${nom}<b>${valeur} ${unite}</b></div>`;
});
document.getElementById("valeurs").innerHTML = html.join("");
}
async function rafraichir() {
const etat = document.getElementById("etat");
const periode = document.getElementById("periode").value;
try {
const points = await chargerMesures(periode);
if (points.length === 0) {
etat.textContent = "Aucune mesure sur cette période : la carte envoie-t-elle ?";
return;
}
afficherDerniere(points);
dessiner("graphe-pm", points, ["pm1", "pm25", "pm10"]);
dessiner("graphe-meteo", points, ["temp", "hum"]);
const avecCo2 = dessiner("graphe-co2", points, ["co2"]);
// Pas de CO2 sur un NebuleAir : on cache son graphe
document.getElementById("carte-co2").hidden = (avecCo2 === 0);
const date = new Date(points[points.length - 1].time).toLocaleString("fr-FR");
etat.className = "";
etat.textContent = `${points.length} points, moyenne sur ${MOYENNE[periode]}. `
+ `Dernière mesure : ${date}.`;
} catch (erreur) {
etat.className = "erreur";
etat.textContent = "Erreur : " + erreur.message;
}
}
document.getElementById("periode").addEventListener("change", rafraichir);
rafraichir();
setInterval(rafraichir, 5 * 60 * 1000); // mise à jour toutes les 5 minutes
</script>
</body>
</html>