New Technology Design

SchreinCloudParser

Table des Matières

  1. Introduction
  2. Architecture et Concepts
  3. Installation et Configuration
  4. API Référence
  5. Cas d’Utilisation DIY
  6. Dépannage
  7. Bonnes Pratiques

1. Introduction

Qu’est-ce que SchreinCloudParser ?

SchreinCloudParser est une bibliothèque C++ optimisée pour les microcontrôleurs ESP8266 et ESP32 permettant la communication bidirectionnelle avec le cloud Schrein. Elle gère automatiquement les connexions WiFi, le parsing des commandes, l’envoi de télémétrie et inclut des fonctionnalités avancées d’économie d’énergie.

Installation de la Bibliothèque

  1. Téléchargez les fichiers .h et .cpp
  2. Créez un dossier SchreinCloudParser dans votre dossier libraries/Arduino
  3. Copiez les fichiers dans ce dossier
  4. Redémarrez l’IDE Arduino

Caractéristiques Principales

  • Communication SSE (Server-Sent Events) pour la réception de commandes
  • Envoi de télémétrie sécurisé avec authentification Bearer
  • Gestion automatique WiFi avec reconnexion intelligente
  • Mode économie d’énergie avec deep sleep intégré
  • Timeout adaptatif selon les conditions réseau
  • Système de checksum pour l’intégrité des données
  • Callbacks extensibles pour une intégration flexible

2. Architecture et Concepts

Diagramme de Flux

Format des Trames

//Envoi-From Desktop Application to ESP

// Format sécurisé
<[control|key|value]&checksum>

// Exemple concret
<[temperature|sensor1|23.5]&4F>

// Format no sécurisé
<[control|key|value]>

// Exemple concret
<[temperature|sensor1|23.5]>

//Envoi-From ESP to desktop application

// Format sécurisé
controlName=property:value;&checksum

// Exemple concret
SchreinLabel1=Text:23.5;*4F

// Format no sécurisé
controlName=property:value;

// Exemple concret
SchreinLabel1=Text:23.5;


États du Parser

enum class ParserState {
    WAITING_START,    // En attente du début de trame
    READING_FRAME    // Lecture en cours
};


3. Installation et Configuration

Installation

// Dans PlatformIO, ajouter au platformio.ini
lib_deps = 
    https://github.com/SchreikenLab/SchreinCloudParser.git

Inclusion dans le projet

#include <SchreinCloudParser.h>

Configuration Initiale

// Définir les informations de connexion
const String SERVER = "votre-serveur.schrein.cloud";
const String TOKEN = "votre-token-bearer";
const String DEVICE_ID = "votre-device-id";

// Initialiser le parser
SchreinCloudParser parser(SERVER, TOKEN, DEVICE_ID);

Configuration WiFi

void setup() {
    Serial.begin(115200);
    
    // Connexion WiFi
    if (parser.begin("SSID_WIFI", "PASSWORD_WIFI")) {
        Serial.println("✅ Device prêt!");
    } else {
        Serial.println("❌ Échec connexion WiFi");
    }
    
    // Configurer les callbacks
    setupCallbacks();
}


4. API Référence

Méthodes Principales

Initialise la connexion WiFi.

bool success = parser.begin("MonWiFi", "MonPassword");

Boucle principale à appeler régulièrement.

void loop() {
    parser.loop();
    
    if (parser.isFrameAvailable()) {
        traiterCommande();
    }
}

Envoie une trame de télémétrie.

String trame = "temperature=value:23.5;";
parser.sendFrame(trame);

Récupère une valeur parsée.

String valeur = parser.getValue("relay", "state");

Gestion des Commandes

Génère une commande formatée.

String cmd = parser.command("pump", "duration", "5000");
// Résultat: "pump=duration:5000;"

Envoie multiple trames.

String trames[] = {
    parser.command("sensor1", "temp", "22.1"),
    parser.command("sensor2", "humidity", "45")
};
parser.sendFrames(trames, 2);

Gestion Énergétique

Active/désactive l’économie d’énergie.

parser.setPowerSaving(true);  // Mode basse consommation

Configure la durée du deep sleep.

parser.setDeepSleepDuration(300);  // 5 minutes

Callbacks Disponibles

// Configuration complète des callbacks
void setupCallbacks() {
    parser.onFrameParsed([](String control, String key, String value) {
        Serial.printf("📨 Commande: %s.%s = %s\n", 
                     control.c_str(), key.c_str(), value.c_str());
    });
    
    parser.onError([](String error) {
        Serial.println("❌ Erreur: " + error);
    });
    
    parser.onConnectionStatusChange([](bool connected) {
        Serial.println(connected ? "🔗 Connecté" : "🔌 Déconnecté");
    });
    
    parser.onWiFiSignalUpdate([](int rssi) {
        Serial.printf("📶 Signal WiFi: %d dBm\n", rssi);
    });
}


5. Cas d’Utilisation DIY

🏠 1. Station Météo Intelligente

Objectif: Mesurer température/humidité et envoyer les données au cloud.

#include <SchreinCloudParser.h>
#include <DHT.h>

#define DHT_PIN 4
#define DHT_TYPE DHT22

SchreinCloudParser parser("serveur.schrein.cloud", "token", "station-meteo");
DHT dht(DHT_PIN, DHT_TYPE);

void setup() {
    Serial.begin(115200);
    dht.begin();
    parser.begin("WiFi_Maison", "password");
    
    parser.onFrameParsed(handleCommand);
}

void loop() {
    parser.loop();
    
    static unsigned long lastRead = 0;
    if (millis() - lastRead > 30000) {  // Toutes les 30s
        envoyerDonneesCapteurs();
        lastRead = millis();
    }
}

void envoyerDonneesCapteurs() {
    float temp = dht.readTemperature();
    float hum = dht.readHumidity();
    
    if (!isnan(temp) && !isnan(hum)) {
        String trameTemp = parser.command("dht22", "temperature", String(temp));
        String trameHum = parser.command("dht22", "humidity", String(hum));
        
        String trames[] = {trameTemp, trameHum};
        parser.sendFrames(trames, 2);
        
        Serial.printf("📊 Envoyé: %.1f°C, %.1f%%\n", temp, hum);
    }
}

void handleCommand(String control, String key, String value) {
    if (control == "display" && key == "refresh") {
        envoyerDonneesCapteurs();  // Rafraîchissement à la demande
    }
}


💡 2. Contrôleur d’Éclairage Domotique

Objectif: Contrôler des relais/LED via le cloud.

#include <SchreinCloudParser.h>

SchreinCloudParser parser("serveur.schrein.cloud", "token", "eclairage-salon");

const int RELAY_PIN = 5;
bool etatRelay = false;

void setup() {
    pinMode(RELAY_PIN, OUTPUT);
    parser.begin("WiFi_Maison", "password");
    
    parser.onFrameParsed(handleLightCommand);
    parser.onConnectionStatusChange(handleConnectionChange);
}

void loop() {
    parser.loop();
}

void handleLightCommand(String control, String key, String value) {
    if (control == "light" && key == "state") {
        etatRelay = (value == "on" || value == "1");
        digitalWrite(RELAY_PIN, etatRelay ? HIGH : LOW);
        
        // Confirmation
        String confirmation = parser.command("light", "state", etatRelay ? "on" : "off");
        parser.sendFrame(confirmation);
        
        Serial.println("💡 Lumière: " + String(etatRelay ? "ON" : "OFF"));
    }
}

void handleConnectionChange(bool connected) {
    // Clignoter la LED en cas de déconnexion
    if (!connected) {
        for (int i = 0; i < 3; i++) {
            digitalWrite(LED_BUILTIN, HIGH);
            delay(200);
            digitalWrite(LED_BUILTIN, LOW);
            delay(200);
        }
    }
}


🌿 3. Système d’Arrosage Automatique

Objectif: Gérer l’arrosage basé sur l’humidité du sol.

#include <SchreinCloudParser.h>

SchreinCloudParser parser("serveur.schrein.cloud", "token", "arrosage-jardin");

const int SOIL_SENSOR = A0;
const int WATER_PUMP = 12;
const int SOIL_THRESHOLD = 500;

void setup() {
    pinMode(WATER_PUMP, OUTPUT);
    digitalWrite(WATER_PUMP, LOW);
    
    parser.begin("WiFi_Jardin", "password");
    parser.onFrameParsed(handleWaterCommand);
    parser.setPowerSaving(true);  // Économie d'énergie
}

void loop() {
    parser.loop();
    
    static unsigned long lastCheck = 0;
    if (millis() - lastCheck > 60000) {  // Toutes les minutes
        gererArrosageAutomatique();
        lastCheck = millis();
    }
}

void gererArrosageAutomatique() {
    int humiditeSol = analogRead(SOIL_SENSOR);
    
    // Envoyer l'humidité
    String trameHumidite = parser.command("soil", "moisture", String(humiditeSol));
    parser.sendFrame(trameHumidite);
    
    // Arrosage automatique si trop sec
    if (humiditeSol > SOIL_THRESHOLD) {
        declencherArrosage(5000);  // 5 secondes
    }
}

void handleWaterCommand(String control, String key, String value) {
    if (control == "pump" && key == "duration") {
        int duree = value.toInt();
        declencherArrosage(duree);
    }
}

void declencherArrosage(int dureeMs) {
    digitalWrite(WATER_PUMP, HIGH);
    
    String trame = parser.command("pump", "state", "active");
    parser.sendFrame(trame);
    
    delay(dureeMs);
    digitalWrite(WATER_PUMP, LOW);
    
    trame = parser.command("pump", "state", "inactive");
    parser.sendFrame(trame);
    
    Serial.println("💦 Arrosage: " + String(dureeMs) + "ms");
}


🔒 4. Système de Sécurité

Objectif: Surveillance avec capteurs PIR et alertes.

#include <SchreinCloudParser.h>

SchreinCloudParser parser("serveur.schrein.cloud", "token", "alarme-maison");

const int PIR_PIN = 14;
const int BUZZER_PIN = 15;
const int LED_ALERTE = 13;

bool alarmeActive = false;
unsigned long derniereDetection = 0;

void setup() {
    pinMode(PIR_PIN, INPUT);
    pinMode(BUZZER_PIN, OUTPUT);
    pinMode(LED_ALERTE, OUTPUT);
    
    parser.begin("WiFi_Securite", "password");
    parser.onFrameParsed(handleSecurityCommand);
    
    Serial.println("🚨 Système de sécurité initialisé");
}

void loop() {
    parser.loop();
    
    // Détection mouvement
    if (alarmeActive && digitalRead(PIR_PIN) == HIGH) {
        if (millis() - derniereDetection > 10000) {  // Anti-rebond 10s
            declencherAlerte();
            derniereDetection = millis();
        }
    }
}

void handleSecurityCommand(String control, String key, String value) {
    if (control == "alarm" && key == "state") {
        alarmeActive = (value == "armed");
        
        String statut = parser.command("alarm", "status", alarmeActive ? "armed" : "disarmed");
        parser.sendFrame(statut);
        
        Serial.println("🚨 Alarme: " + String(alarmeActive ? "ACTIVÉE" : "DÉSACTIVÉE"));
    }
}

void declencherAlerte() {
    // Sonner le buzzer
    tone(BUZZER_PIN, 1000, 2000);
    
    // Clignoter LED
    for (int i = 0; i < 10; i++) {
        digitalWrite(LED_ALERTE, HIGH);
        delay(200);
        digitalWrite(LED_ALERTE, LOW);
        delay(200);
    }
    
    // Envoyer alerte
    String alerte = parser.command("security", "alert", "motion_detected");
    parser.sendFrame(alerte);
    
    Serial.println("⚠️  Mouvement détecté! Alerte envoyée.");
}


🔋 5. Moniteur de Batterie Solaire

Objectif: Surveiller une installation solaire autonome.

#include <SchreinCloudParser.h>

SchreinCloudParser parser("serveur.schrein.cloud", "token", "solaire-maison");

const int VOLTAGE_PIN = A0;
const int CURRENT_PIN = A1;
const int CHARGE_RELAY = 16;

float tensionBatterie = 0;
float courantCharge = 0;

void setup() {
    pinMode(CHARGE_RELAY, OUTPUT);
    parser.begin("WiFi_Solaire", "password");
    parser.setPowerSaving(true);
    parser.setDeepSleepDuration(300);  // Deep sleep 5min entre mesures
    
    parser.onFrameParsed(handleSolarCommand);
}

void loop() {
    parser.loop();
    
    static unsigned long lastMeasurement = 0;
    if (millis() - lastMeasurement > 30000) {  // Mesure toutes les 30s
        mesurerParametres();
        envoyerDonneesSolaire();
        lastMeasurement = millis();
        
        // Deep sleep en mode très basse consommation
        if (parser.isPowerSavingEnabled() && tensionBatterie < 11.5) {
            Serial.println("🔋 Batterie faible - Deep sleep activé");
            parser.enterDeepSleep();  // La librairie gère le wakeup automatique
        }
    }
}

void mesurerParametres() {
    // Lecture tension (diviseur de tension)
    int rawVoltage = analogRead(VOLTAGE_PIN);
    tensionBatterie = (rawVoltage / 1023.0) * 5.0 * 3;  // Conversion selon circuit
    
    // Lecture courant (capteur ACS712)
    int rawCurrent = analogRead(CURRENT_PIN);
    courantCharge = ((rawCurrent - 512) / 1023.0) * 5.0;  // Ajuster selon calibration
}

void envoyerDonneesSolaire() {
    String trames[] = {
        parser.command("battery", "voltage", String(tensionBatterie, 2)),
        parser.command("solar", "current", String(courantCharge, 2)),
        parser.command("system", "power_mode", parser.isPowerSavingEnabled() ? "low_power" : "normal")
    };
    
    parser.sendFrames(trames, 3);
    
    Serial.printf("☀️  Solaire: %.2fV, %.2fA\n", tensionBatterie, courantCharge);
}

void handleSolarCommand(String control, String key, String value) {
    if (control == "charge" && key == "enable") {
        bool enableCharge = (value == "true" || value == "1");
        digitalWrite(CHARGE_RELAY, enableCharge ? HIGH : LOW);
        
        String statut = parser.command("charge", "status", enableCharge ? "enabled" : "disabled");
        parser.sendFrame(statut);
    }
}


6. Dépannage

Diagnostic Complet

void diagnosticComplet() {
    Serial.println("\n=== DIAGNOSTIC SCHREIN CLOUD ===");
    
    // Statut connexion
    Serial.println("📡 " + parser.getConnectionStatus());
    
    // Timeout actuel
    Serial.println("⏱  Timeout: " + String(parser.getCurrentTimeout()) + "ms");
    
    // Statistiques d'erreur
    Serial.println("❌ Erreurs consécutives: " + String(parser.getConsecutiveErrors()));
    Serial.println("⏰ Timeouts consécutifs: " + String(parser.getConsecutiveTimeouts()));
    
    // Dernière commande
    Serial.println("🕐 Âge dernière commande: " + String(parser.getLastCommandAge()) + "ms");
    
    // Mode économie
    Serial.println("🔋 Mode économie: " + String(parser.isPowerSavingEnabled() ? "ACTIF" : "INACTIF"));
}

// Appeler cette fonction quand besoin
diagnosticComplet();

Codes d’Erreur Courants

void handleErrors(ErrorCode code, String message) {
    switch(code) {
        case ErrorCode::TIMEOUT:
            Serial.println("⏰ Timeout - Vérifiez la connexion réseau");
            break;
        case ErrorCode::INVALID_FRAME:
            Serial.println("📦 Format de trame invalide");
            break;
        case ErrorCode::CHECKSUM_ERROR:
            Serial.println("🔍 Erreur checksum - Données corrompues");
            break;
        case ErrorCode::BUFFER_OVERFLOW:
            Serial.println("💥 Buffer overflow - Trame trop longue");
            break;
        default:
            Serial.println("❌ Erreur inconnue: " + message);
    }
}

// Enregistrer le callback
parser.onErrorCode(handleErrors);

Optimisation des Performances

void optimiserPerformance() {
    // Réduire timeout si connexion stable
    if (parser.getConsecutiveErrors() == 0) {
        Serial.println("✅ Connexion stable - Optimisation possible");
    }
    
    // Activer économie si batterie
    if (estSurBatterie()) {
        parser.setPowerSaving(true);
        parser.setDeepSleepDuration(600);  // 10 minutes
    }
    
    // Désactiver checksum si besoin de performance
    if (besoinDebitMaximal) {
        parser.enableChecksum(false);
    }
}

💡 Bonnes Pratiques

1. Gestion de la Mémoire

// Éviter les String dynamiques dans les callbacks
void handleFrame(String control, String key, String value) {
    // MAUVAIS: Création de String dynamiques
    // String message = "Commande: " + control + "." + key + " = " + value;
    
    // BON: Utiliser printf ou traitement direct
    Serial.printf("📨 %s.%s = %s\n", control.c_str(), key.c_str(), value.c_str());
    
    // Traitement immédiat sans allocation
    if (control == "relay" && key == "state") {
        digitalWrite(RELAY_PIN, value == "on" ? HIGH : LOW);
    }
}

2. Gestion des Déconnexions

void robustLoop() {
    static unsigned long lastReconnect = 0;
    
    parser.loop();
    
    // Reconnexion forcée périodique
    if (millis() - lastReconnect > 300000) {  // 5 minutes
        if (!parser.isConnectionHealthy()) {
            Serial.println("🔄 Reconnexion proactive...");
            parser.forceReconnect();
        }
        lastReconnect = millis();
    }
}

3. Logging Intelligent

class Logger {
public:
    static void debug(String message) {
        #ifdef DEBUG
        Serial.println("[DEBUG] " + message);
        #endif
    }
    
    static void error(String message) {
        Serial.println("[ERROR] " + message);
        // Envoyer l'erreur au cloud
        // parser.sendFrame(parser.command("system", "error", message));
    }
};

// Utilisation
Logger::debug("Trame reçue: " + trame);
Logger::error("Timeout connexion");

4. Sécurité

void setupSecurite() {
    // Validation des entrées
    parser.onFrameParsed([](String control, String key, String value) {
        if (control.length() > 50 || key.length() > 50 || value.length() > 100) {
            Logger::error("Trame suspecte - taille excessive");
            return;
        }
        
        // Whitelist des commandes autorisées
        String commandesAutorisees[] = {"light", "pump", "sensor", "alarm"};
        bool autorise = false;
        for (auto& cmd : commandesAutorisees) {
            if (control == cmd) {
                autorise = true;
                break;
            }
        }
        
        if (!autorise) {
            Logger::error("Commande non autorisée: " + control);
            return;
        }
        
        traiterCommande(control, key, value);
    });
}


🎯 Conclusion

La bibliothèque SchreinCloudParser offre une solution complète pour connecter vos projets ESP8266/ESP32 au cloud Schrein. Sa conception robuste avec gestion automatique des connexions, économie d’énergie intégrée et système de callbacks extensible la rend adaptée à une large gamme de projets DIY.

Les 5 cas d’utilisation présentés couvrent les scénarios les plus courants et peuvent servir de base à vos propres créations. N’hésitez pas à adapter le code selon vos besoins spécifiques et à explorer les fonctionnalités avancées comme le timeout adaptatif et le système de checksum.

Prochaines Étapes:

  1. Commencez avec le cas d’utilisation le plus proche de votre projet
  2. Testez la connexion avec les outils de diagnostic
  3. Ajoutez progressivement les fonctionnalités avancées
  4. Optimisez selon vos contraintes énergétiques

Bonne création ! 🚀