Temps réel & IoT
Supervision d’une flotte de capteurs
Section intitulée « Supervision d’une flotte de capteurs »Les capteurs publient leurs mesures en MQTT sur EMQX. Un collecteur Telegraf s’abonne au broker et écrit chaque mesure dans une base PostgreSQL ; Grafana l’affiche, avec sa source de données et un tableau de bord déjà provisionnés. Aucun code à écrire ni à héberger : quatre images publiques et deux fichiers de configuration.
apiVersion: cassiope.eu/v1alpha2kind: Blueprintproject: slug: flotte-capteurs name: Flotte de capteurs environments: - slug: production name: Production addons: - slug: mesures name: Mesures templateKey: postgresql version: "17" resources: { cpuMillicores: 500, ramMb: 1024, diskGb: 20 } backupEnabled: true services: - slug: emqx name: Broker MQTT type: web source: imageRef: emqx/emqx:5.10.5 portsExposes: "8083" publiclyExposed: true healthcheckCommand: curl -s -o /dev/null --max-time 3 http://127.0.0.1:18083/status resources: { cpuMillicores: 1000, ramMb: 1024 } env: - key: EMQX_AUTHENTICATION__1__BACKEND value: built_in_database - key: EMQX_AUTHENTICATION__1__MECHANISM value: password_based - key: EMQX_AUTHENTICATION__1__USER_ID_TYPE value: username - key: EMQX_AUTHENTICATION__1__BOOTSTRAP_TYPE value: plain - key: EMQX_AUTHENTICATION__1__BOOTSTRAP_FILE value: /opt/emqx/etc/auth-built-in-db-bootstrap.csv - key: EMQX_DASHBOARD__DEFAULT_PASSWORD fromSecret: true - key: CAPTEURS_PASSWORD fromSecret: true - key: INGESTION_PASSWORD fromSecret: true files: - mountPath: /opt/emqx/etc/auth-built-in-db-bootstrap.csv content: | user_id,password,is_superuser capteurs,${CAPTEURS_PASSWORD},false ingestion,${INGESTION_PASSWORD},false exposure: { port: 1883, protocol: tcp } - slug: ingestion name: Collecteur MQTT vers PostgreSQL type: worker dependsOn: [emqx] source: imageRef: telegraf:1.40.2 resources: { cpuMillicores: 250, ramMb: 256 } bindings: - addon: mesures envVarKey: MESURES_URL files: - mountPath: /etc/telegraf/telegraf.conf content: | [agent] interval = "10s" flush_interval = "10s" omit_hostname = true
[[inputs.mqtt_consumer]] servers = ["tcp://${MQTT_HOST}:${MQTT_PORT}"] topics = ["capteurs/+/mesures"] username = "ingestion" password = "${INGESTION_PASSWORD}" data_format = "json" name_override = "mesures"
[[inputs.mqtt_consumer.topic_parsing]] topic = "capteurs/+/mesures" tags = "_/capteur/_"
[[outputs.postgresql]] connection = "${MESURES_URL}" - slug: supervision name: Supervision type: web source: imageRef: grafana/grafana:13.2.4 portsExposes: "3000" publiclyExposed: true resources: { cpuMillicores: 500, ramMb: 512 } env: - key: GF_SECURITY_ADMIN_PASSWORD fromSecret: true - key: GF_USERS_ALLOW_SIGN_UP value: "false" bindings: - addon: mesures envVarKey: MESURES derive: split-postgres files: - mountPath: /etc/grafana/provisioning/datasources/mesures.yaml content: | apiVersion: 1 datasources: - name: Mesures uid: mesures type: grafana-postgresql-datasource url: ${MESURES_HOST}:${MESURES_PORT} user: ${MESURES_USER} isDefault: true jsonData: database: ${MESURES_DATABASE} sslmode: disable postgresVersion: 1700 secureJsonData: password: ${MESURES_PASSWORD} - mountPath: /etc/grafana/provisioning/dashboards/flotte.yaml content: | apiVersion: 1 providers: - name: Flotte type: file options: path: /etc/grafana/dashboards - mountPath: /etc/grafana/dashboards/flotte.json content: | { "title": "Flotte de capteurs", "uid": "flotte-capteurs", "schemaVersion": 39, "time": { "from": "now-6h", "to": "now" }, "refresh": "30s", "panels": [ { "id": 1, "type": "timeseries", "title": "Température", "gridPos": { "h": 9, "w": 12, "x": 0, "y": 0 }, "datasource": { "type": "grafana-postgresql-datasource", "uid": "mesures" }, "targets": [{ "refId": "A", "format": "time_series", "editorMode": "code", "rawQuery": true, "datasource": { "type": "grafana-postgresql-datasource", "uid": "mesures" }, "rawSql": "SELECT time, capteur AS metric, temperature FROM mesures WHERE $__timeFilter(time) ORDER BY 1" }] }, { "id": 2, "type": "timeseries", "title": "Humidité", "gridPos": { "h": 9, "w": 12, "x": 12, "y": 0 }, "datasource": { "type": "grafana-postgresql-datasource", "uid": "mesures" }, "targets": [{ "refId": "A", "format": "time_series", "editorMode": "code", "rawQuery": true, "datasource": { "type": "grafana-postgresql-datasource", "uid": "mesures" }, "rawSql": "SELECT time, capteur AS metric, humidite FROM mesures WHERE $__timeFilter(time) ORDER BY 1" }] }, { "id": 3, "type": "table", "title": "Dernière mesure par capteur", "gridPos": { "h": 9, "w": 24, "x": 0, "y": 9 }, "datasource": { "type": "grafana-postgresql-datasource", "uid": "mesures" }, "targets": [{ "refId": "A", "format": "table", "editorMode": "code", "rawQuery": true, "datasource": { "type": "grafana-postgresql-datasource", "uid": "mesures" }, "rawSql": "SELECT DISTINCT ON (capteur) capteur, time, temperature, humidite FROM mesures ORDER BY capteur, time DESC" }] } ] } links: - from: ingestion to: emqx envKeyPrefix: MQTT envCopies: - from: INGESTION_PASSWORDÀ l’import, trois secrets sont demandés : le mot de passe du tableau de bord EMQX
(EMQX_DASHBOARD__DEFAULT_PASSWORD), celui des capteurs (CAPTEURS_PASSWORD) et le mot de passe
d’administration de Grafana (GF_SECURITY_ADMIN_PASSWORD). Le mot de passe du collecteur
(INGESTION_PASSWORD) est saisi une seule fois sur le broker : le lien le recopie chez le
collecteur. Le broker et Grafana reçoivent chacun une adresse en cassiope.eu (visible dans l’onglet Domaines du service) ; pour utiliser votre propre nom, ajoutez-le ensuite dans cet onglet et faites-le pointer vers la plateforme.
{ "values": { "production/emqx": { "EMQX_DASHBOARD__DEFAULT_PASSWORD": "à générer", "CAPTEURS_PASSWORD": "à générer", "INGESTION_PASSWORD": "à générer" }, "production/supervision": { "GF_SECURITY_ADMIN_PASSWORD": "à générer" } }}Brancher les capteurs
Section intitulée « Brancher les capteurs »Un capteur publie sur le sujet capteurs/<identifiant>/mesures un JSON à plat, par exemple
{"temperature": 21.4, "humidite": 48}. Le segment du milieu devient la colonne capteur de la
table mesures, chaque clé numérique devient une colonne ; Telegraf crée la table et les colonnes
à la première mesure reçue, les panneaux Grafana restent donc en erreur jusque-là. Pour des
grandeurs autres que temperature et humidite, adaptez les requêtes du tableau de bord.