Aller au contenu

Intégration Chirpstack v4

Le problème

publisher.py simule un serveur de réseau Chirpstack en publiant directement des messages MQTT avec la structure JSON appropriée. Mais simule n'est pas prouver : on ne sait pas si le backend peut vraiment traiter les messages d'un vrai Chirpstack v4 en production, avec sa base de données propriétaire, son authentification gRPC, et ses broker MQTT réels.

Le défi : démontrer que l'intégration fonctionne end-to-end en démarrant Chirpstack comme service Docker avec profil Compose, en provisionner les appareils via l'API gRPC, puis en recevant les uplinks MQTT sans modifier le code du subscriber.

Ce que j'ai appris

Docker Compose profiles

Docker Compose permet de regrouper les services en profils, et de n'en démarrer que certains avec le flag --profile :

# docker-compose.yml
services:
  postgres:
    image: postgres:15
    # Service de base, toujours inclus

  chirpstack:
    image: chirpstack/chirpstack:4
    profiles: ["chirpstack"]  # Optionnel — inclus seulement avec --profile chirpstack
    depends_on:
      - postgres
      - redis

  mosquitto:
    image: eclipse-mosquitto:2
    # Service de base, toujours inclus

Démarrage :

# Démarrer sans Chirpstack (mode simulation avec publisher.py)
docker-compose up

# Démarrer avec Chirpstack
docker-compose --profile chirpstack up

Architecture Chirpstack v4

Chirpstack v4 repose sur trois composants interconnectés :

PostgreSQL (base de données Chirpstack) - Schéma distinct de exploreiot — Chirpstack gère ses propres tables (applications, devices, etc.) - Initialisé via migrations Chirpstack lors du premier démarrage - Port recommandé : 5432 (exposé en 5435 sur l'hôte pour ne pas conflictuer)

Redis (cache et broker de messages internes) - Utilisé par Chirpstack pour le cache et la communication inter-processus - Port standard : 6379 - Pas besoin de persistance disque

gRPC API (provisionnement) - Port standard : 8080 - Utilisée pour créer/modifier les applications et appareils LoRaWAN - Authentification par api_token dans le header

MQTT Broker (publication des uplinks) - Chirpstack publie les uplinks reçus des passerelles LoRa sur des topics MQTT - Port standard : 1883 (partagé avec Mosquitto) - Format de topic : application/{app_id}/device/{device_id}/event/up

Configuration Chirpstack : chirpstack.toml

La clé de configuration détermine le comportement de Chirpstack :

# General
# Regional configuration (defines the LoRaWAN region)
region_name = "EU868"

# gRPC API
[grpc]
bind = "0.0.0.0:8080"

# MQTT broker — connexion à Mosquitto (ou au broker interne)
[mqtt]
broker = "mosquitto:1883"
topic_prefix = ""

# PostgreSQL — pointant vers le service postgres Docker
[postgresql]
dsn = "postgres://chirpstack:chirpstack@postgres:5432/chirpstack?sslmode=disable"

# Redis
[redis]
servers = ["redis:6379"]

# Logging
[logging]
level = "info"

API gRPC pour le provisionnement

Créer une application Chirpstack :

import grpc
from chirpstack_api import api

# Connexion au service gRPC
channel = grpc.insecure_channel("localhost:8080")
stub = api.ApplicationServiceStub(channel)

# Créer une application
application = api.Application(
    name="ExploreIOT Demo",
    description="Application de démonstration"
)

# Envoyer la requête (nécessite l'authentification par token)
metadata = [("authorization", "Bearer YOUR_API_TOKEN")]
response = stub.Create(application, metadata=metadata)
print(f"Created application: {response.id}")

Enregistrer un capteur (device) :

device = api.Device(
    application_id=app_id,
    name="Capteur Bureau 1",
    description="SHT31 LoRa",
    dev_eui="a1b2c3d4e5f60001",  # Clé 16 hex
    device_profile_id=profile_id,
    skip_fcnt_check=False  # Recommandé : vérifier le compteur de trames
)

response = stub.Create(device, metadata=metadata)
print(f"Created device: {response.id}")

Format des topics Chirpstack v4

Chirpstack v4 publie les uplinks sur des topics au format :

application/{app_id}/device/{device_id}/event/up

Le payload JSON contient les données décodées dans le champ object (pré-décodé par le codec Chirpstack) et le payload binaire brut en base64 dans le champ data. Le subscriber utilise object en priorité, avec fallback sur le décodage base64.

Code concret

Extrait docker-compose.yml avec profil Chirpstack

version: '3.8'

services:
  # Base de données pour ExploreIOT
  postgres-exploreiot:
    image: postgres:15
    container_name: postgres-exploreiot
    environment:
      POSTGRES_DB: exploreiot
      POSTGRES_USER: exploreiot
      POSTGRES_PASSWORD: exploreiot_dev
    ports:
      - "5434:5432"
    volumes:
      - postgres-exploreiot-data:/var/lib/postgresql/data

  # Redis global (shared)
  redis:
    image: redis:7-alpine
    container_name: redis
    ports:
      - "6379:6379"

  # Mosquitto MQTT Broker
  mosquitto:
    image: eclipse-mosquitto:2
    container_name: mosquitto
    ports:
      - "1883:1883"
    volumes:
      - ./docker/mosquitto/config:/mosquitto/config
      - mosquitto-data:/mosquitto/data

  # Chirpstack — optionnel, profil chirpstack
  postgres-chirpstack:
    image: postgres:15
    container_name: postgres-chirpstack
    profiles: ["chirpstack"]
    environment:
      POSTGRES_DB: chirpstack
      POSTGRES_USER: chirpstack
      POSTGRES_PASSWORD: chirpstack_dev
    ports:
      - "5435:5432"
    volumes:
      - postgres-chirpstack-data:/var/lib/postgresql/data

  chirpstack:
    image: chirpstack/chirpstack:4
    container_name: chirpstack
    profiles: ["chirpstack"]
    environment:
      CHIRPSTACK_CONFIG_DIR: /etc/chirpstack
    ports:
      - "8080:8080"  # gRPC API
      - "8090:8090"  # HTTP API
    volumes:
      - ./docker/chirpstack/chirpstack.toml:/etc/chirpstack/chirpstack.toml
    depends_on:
      - postgres-chirpstack
      - redis
      - mosquitto

  # Backend FastAPI ExploreIOT
  backend:
    build: ./backend
    container_name: backend
    environment:
      DB_HOST: postgres-exploreiot
      DB_PORT: 5432
      DB_NAME: exploreiot
      DB_USER: exploreiot
      DB_PASSWORD: exploreiot_dev
      MQTT_HOST: mosquitto
      MQTT_PORT: 1883
      MQTT_TOPIC: application/+/device/+/event/up
      CHIRPSTACK_ENABLED: "false"
    ports:
      - "8000:8000"
    depends_on:
      - postgres-exploreiot
      - mosquitto

volumes:
  postgres-exploreiot-data:
  postgres-chirpstack-data:
  mosquitto-data:

Extrait docker/chirpstack/chirpstack.toml

[general]
log_level = "info"
log_format = "json"
region_name = "EU868"

[grpc]
bind = "0.0.0.0:8080"

[api]
bind = "0.0.0.0:8090"

[mqtt]
broker = "mosquitto:1883"
topic_prefix = ""

[postgresql]
dsn = "postgres://chirpstack:chirpstack_dev@postgres-chirpstack:5432/chirpstack?sslmode=disable"
max_open_connections = 16

[redis]
servers = ["redis:6379"]

Vérification du provisionnement (CLI)

Utiliser grpcurl pour tester la connexion au gRPC sans écrire de code :

# Lister les applications (nécessite un token valide — peut être généré dans l'UI Chirpstack)
grpcurl -plaintext \
  -H "authorization: Bearer YOUR_API_TOKEN" \
  localhost:8080 \
  chirpstack.api.ApplicationService/List

# Créer une application
grpcurl -plaintext \
  -H "authorization: Bearer YOUR_API_TOKEN" \
  -d '{"name": "Demo", "description": "Test"}' \
  localhost:8080 \
  chirpstack.api.ApplicationService/Create

Piège à éviter

Chirpstack utilise sa propre base de données

Chirpstack ne peut pas partager la base PostgreSQL d'ExploreIOT. Son schéma est très différent et contient des tables Chirpstack spécifiques (devices, device_profiles, gateways, etc.).

Erreur typique : pointer chirpstack.toml vers postgres-exploreiot:5434 et laisser Chirpstack effectuer les migrations. Cela va polluer la base ExploreIOT et créer des conflits de schéma.

Solution : Utiliser une deuxième instance PostgreSQL dédiée (postgres-chirpstack) et laisser Chirpstack effectuer ses migrations dans sa base propre.

Le topic MQTT doit correspondre exactement

Si le topic_prefix dans chirpstack.toml est vide (ce qui est correct), Chirpstack publie sur :

application/{app_id}/device/{device_id}/event/up

Si le subscriber s'abonne à un mauvais topic, il ne recevra rien.

Solution : S'assurer que MQTT_TOPIC dans .env correspond au format réel de Chirpstack, ou adapter topic_prefix dans chirpstack.toml pour les deux côtés.

gRPC requiert une authentification valide

La plupart des opérations gRPC (Create, Update, Delete) nécessitent un token API valide. Sans token ou avec un token expiré, les requêtes échouent avec une erreur UNAUTHENTICATED.

Solution : Générer un token via l'interface web Chirpstack (Section Users > Tokens) et le passer dans le header authorization: Bearer YOUR_TOKEN.

Ressources

Pour aller plus loin