// ============================================================
// GetMeteo.h
// Version    : v2
// Projet     : IoToS Station Meteo — UNO R4 Minima + W5500
//------------------------------------------------------------------------------
// Fonction   : Recuperation des donnees meteo courantes (current)
//              via API Open-Meteo (https://open-meteo.com)
//              Gratuit — sans cle API — non-commercial — < 10 000 req/jour
//------------------------------------------------------------------------------
// Cible      : Arduino UNO R4 Minima + Ethernet Shield V2 (W5500)
// Protocole  : HTTP/1.1  TCP  port 80
//              (pas de HTTPS — W5500 sans TLS natif)
// Serveur    : api.open-meteo.com
// Parsing    : strstr() + atof()/atoi() sur buffer char[]
//              Pas de ArduinoJson — RAM UNO R4 limitee
//------------------------------------------------------------------------------
//
// ════════════════════════════════════════════════════════════════
// DONNEES RECUPEREES — Open-Meteo current=
// ════════════════════════════════════════════════════════════════
//
//  Parametre API              Variable IoToS            Type    Unite
//  ─────────────────────────────────────────────────────────────────────
//  TEMPERATURE
//  temperature_2m             getMeteo_temperature      float   degC
//    Temperature de l air a 2 metres au-dessus du sol
//    Plage typique : -30..+50 degC
//
//  apparent_temperature       getMeteo_temperatureRess  float   degC
//    Temperature ressentie (wind chill + indice chaleur)
//    Combine vent, humidite et rayonnement
//
//  dew_point_2m               getMeteo_rosee            float   degC
//    Point de rosee a 2m — temperature a laquelle la vapeur
//    commence a condenser — indique le confort hygrometrique
//
//  ─────────────────────────────────────────────────────────────────────
//  HUMIDITE
//  relative_humidity_2m       getMeteo_humidite         uint8_t %
//    Humidite relative a 2m  (0..100%)
//
//  ─────────────────────────────────────────────────────────────────────
//  PRESSION
//  surface_pressure           getMeteo_pression         float   hPa
//    Pression atmospherique a la surface du sol
//    Varie avec l altitude du lieu — non corrigee au niveau mer
//    Plage : 900..1100 hPa
//
//  ─────────────────────────────────────────────────────────────────────
//  VENT
//  wind_speed_10m             getMeteo_vent             float   km/h
//    Vitesse du vent a 10m de hauteur (vitesse moyenne)
//
//  wind_direction_10m         getMeteo_dirVent          uint16_t  deg
//    Direction du vent a 10m — angle depuis le Nord sens horaire
//    0=Nord  90=Est  180=Sud  270=Ouest
//    Utilise pour afficher une girouette "meteo exterieure"
//
//  wind_gusts_10m             getMeteo_rafales          float   km/h
//    Vitesse maximale des rafales sur la periode ecoulee
//    Toujours >= wind_speed_10m
//
//  ─────────────────────────────────────────────────────────────────────
//  PRECIPITATIONS
//  precipitation              getMeteo_pluie            float   mm
//    Precipitation totale = pluie + averses + neige fondue
//    Somme sur l heure precedente
//
//  rain                       getMeteo_pluieLiquide     float   mm
//    Pluie seule (hors averses convectives et neige)
//
//  showers                    getMeteo_averses          float   mm
//    Averses convectives (orages, cellules isolees)
//    Separee de la pluie frontale (rain)
//
//  snowfall                   getMeteo_neige            float   cm
//    Chutes de neige en cm (pas en mm — attention a l unite)
//    0 si temperature > 2 degC
//
//  ─────────────────────────────────────────────────────────────────────
//  NUAGES / VISIBILITE / RAYONNEMENT
//  cloud_cover                getMeteo_nuages           uint8_t %
//    Couverture nuageuse totale toutes altitudes (0..100%)
//    0=ciel degage  100=couvert
//
//  visibility                 getMeteo_visibilite       uint16_t  m
//    Visibilite horizontale en metres
//    Brouillard : < 1000m  Faible : 1000..5000m  Bonne : > 10 000m
//    Stockee en metres — diviser par 1000 pour km
//
//  shortwave_radiation        getMeteo_rayonnement      float   W/m2
//    Rayonnement solaire global (GHI) — somme directe + diffuse
//    0 la nuit  max ~1000 W/m2 en ete a midi
//    Utile pour estimer la production solaire ou l UV
//
//  ─────────────────────────────────────────────────────────────────────
//  CODE METEO WMO
//  weather_code               getMeteo_codeWMO          uint8_t  —
//    Code Organisation Meteorologique Mondiale (WMO 4677)
//    Decrit l etat meteo en un chiffre — liste complete :
//    0   = Ciel clair
//    1   = Principalement clair
//    2   = Partiellement nuageux
//    3   = Couvert
//    45  = Brouillard
//    48  = Brouillard givrant
//    51  = Bruine legere
//    53  = Bruine moderee
//    55  = Bruine dense
//    61  = Pluie legere
//    63  = Pluie moderee
//    65  = Pluie forte
//    71  = Chute de neige legere
//    73  = Chute de neige moderee
//    75  = Chute de neige forte
//    80  = Averses legeres
//    81  = Averses moderees
//    82  = Averses violentes
//    85  = Averses de neige legeres
//    86  = Averses de neige fortes
//    95  = Orage (faible ou modere)
//    96  = Orage avec grele legere
//    99  = Orage avec grele forte (*)
//    (*) Disponible seulement en Europe Centrale
//
//  ─────────────────────────────────────────────────────────────────────
//  HORODATAGE
//  time                       getMeteo_lastUpdate       char[20]  —
//    Horodatage ISO 8601 de la mesure : "2026-04-13T14:00"
//    Mis a jour par Open-Meteo toutes les 15 minutes
//
// ════════════════════════════════════════════════════════════════
// REQUETE HTTP GENEREE
// ════════════════════════════════════════════════════════════════
//
//  GET /v1/forecast
//      ?latitude=48.08&longitude=7.36
//      &current=temperature_2m,apparent_temperature,dew_point_2m,
//               relative_humidity_2m,surface_pressure,
//               wind_speed_10m,wind_direction_10m,wind_gusts_10m,
//               precipitation,rain,showers,snowfall,
//               cloud_cover,visibility,shortwave_radiation,
//               weather_code
//      &wind_speed_unit=kmh
//      &timezone=Europe%2FParis
//      HTTP/1.1
//  Host: api.open-meteo.com
//  Connection: close
//
// ════════════════════════════════════════════════════════════════
// REPONSE JSON (extrait current) :
// ════════════════════════════════════════════════════════════════
//
//  {"latitude":48.08,"longitude":7.36,...,
//   "current":{
//     "time":"2026-04-13T14:00",
//     "interval":900,
//     "temperature_2m":18.4,
//     "apparent_temperature":16.2,
//     "dew_point_2m":8.1,
//     "relative_humidity_2m":52,
//     "surface_pressure":1013.2,
//     "wind_speed_10m":12.6,
//     "wind_direction_10m":245,
//     "wind_gusts_10m":22.0,
//     "precipitation":0.0,
//     "rain":0.0,
//     "showers":0.0,
//     "snowfall":0.0,
//     "cloud_cover":25,
//     "visibility":24140,
//     "shortwave_radiation":620.5,
//     "weather_code":2
//   }}
//
// ════════════════════════════════════════════════════════════════
// VARIABLES XML AJAX — endpoint GET /ajax_inputs?meteo_ext
// ════════════════════════════════════════════════════════════════
//
//  Balise XML      Variable source             Description
//  ───────────────────────────────────────────────────────────────
//  <GTEMP>         getMeteo_temperature        Temperature 2m      degC
//  <GTRES>         getMeteo_temperatureRess    Temp. ressentie     degC
//  <GROSEE>        getMeteo_rosee              Point de rosee      degC
//  <GHUM>          getMeteo_humidite           Humidite relative   %
//  <GPRES>         getMeteo_pression           Pression surface    hPa
//  <GVENT>         getMeteo_vent               Vent 10m            km/h
//  <GDIR>          getMeteo_dirVent            Direction vent      deg
//  <GRAFALES>      getMeteo_rafales            Rafales 10m         km/h
//  <GPLUIE>        getMeteo_pluie              Precipitation tot.  mm
//  <GPLUIELIQ>     getMeteo_pluieLiquide       Pluie seule         mm
//  <GAVERSES>      getMeteo_averses            Averses             mm
//  <GNEIGE>        getMeteo_neige              Neige               cm
//  <GNUAGES>       getMeteo_nuages             Couverture nuages   %
//  <GVISI>         getMeteo_visibilite         Visibilite          m
//  <GRAYON>        getMeteo_rayonnement        Rayonnement sol.    W/m2
//  <GWMO>          getMeteo_codeWMO            Code WMO            —
//  <GVALIDE>       getMeteo_valide             Validite donnees    0/1
//  <GUPDATE>       getMeteo_lastUpdate         Horodatage ISO      —
//
// ════════════════════════════════════════════════════════════════
// ARCHITECTURE NON BLOQUANTE — machine a 6 etats
// ════════════════════════════════════════════════════════════════
//
//  GM_IDLE ─────periode────► GM_CONNECT ─────ok────► GM_SEND_REQUEST
//    ▲                          │ timeout                    │
//    │                          └─────────────────► GM_IDLE  │
//    │                                                       ▼
//    │                                           GM_WAIT_RESPONSE
//    │                                              │     │ timeout
//    │                                              │     └──► GM_CLOSE
//    │                                              ▼
//    │                                           GM_PARSE
//    │                                              │
//    └──────────────────────────────────── GM_CLOSE ┘
//
//  Pas de delay() — pas de while() bloquant
//  Une seule passe par appel de GetMeteo_loop()
//  Timeout independant sur chaque etat (GETMETEO_TIMEOUT_MS)
//
// ════════════════════════════════════════════════════════════════
// INTEGRATION DANS LE .ino
// ════════════════════════════════════════════════════════════════
//
//  #include "GetMeteo.h"      // apres Ethernet.h
//
//  setup() : GetMeteo_init();
//  loop()  : GetMeteo_loop();
//
//  ServeurWEB.h (bloc GET) :
//    if (strstr(HTTP_req, "meteo_ext")) {
//        GetMeteo_sendXML(client);
//        return;
//    }
//
// ════════════════════════════════════════════════════════════════
// BUFFER — dimensionnement
// ════════════════════════════════════════════════════════════════
//
//  La reponse HTTP complete (headers + JSON) tient en ~900 octets.
//  GETMETEO_BUF_SIZE doit etre >= 900 pour capturer tout le JSON.
//  Le JSON seul (sans headers HTTP) tient en ~400 octets.
//  Sur UNO R4 (32KB RAM) : 1024 octets est raisonnable.
//  Reduire a 768 si manque de RAM.
//
// ════════════════════════════════════════════════════════════════
// SOCKETS W5500
// ════════════════════════════════════════════════════════════════
//
//  W5500 (Ethernet Shield V2) : 8 sockets independants
//  W5100 (Ethernet Shield V1) : 4 sockets seulement
//  GetMeteo utilise 1 socket pendant ~1s (requete + reception)
//  Ordre d init IoToS recommande :
//    NTP UDP → Telnet → WEB → FTP → GetMeteo (dernier)
//
// ============================================================
#ifndef GETMETEO_H
#define GETMETEO_H

// ============================================================
// PARAMETRES UTILISATEUR — a adapter a votre installation
// ============================================================

#define GETMETEO_LAT_DEFAULT    "48.08"          // Latitude  par defaut (Colmar)
#define GETMETEO_LON_DEFAULT    "7.36"           // Longitude par defaut (Colmar)
// Coordonnees actives : conf.getMeteoLat / conf.getMeteoLon
// Commande shell : GPS <lat> <lon>   ex: GPS 48.5734 7.7521  (Strasbourg)
#define GETMETEO_PERIODE_MS     300000UL         // Rafraichissement : 5 min
#define GETMETEO_TIMEOUT_MS     10000UL          // Timeout connexion/reception
#define GETMETEO_BUF_SIZE       1600             // Buffer JSON (octets)
// NOTE TAILLE BUFFER :
//   Reponse HTTP Open-Meteo avec 16 variables = ~1107 octets (headers + JSON)
//   + chunked encoding = +quelques dizaines d octets
//   1024 = INSUFFISANT → les dernieres valeurs sont tronquees
//   1600 = marge suffisante (UNO R4 : 32KB RAM disponible)

// ============================================================
// SERVEUR OPEN-METEO
// ============================================================

static const char GETMETEO_HOST[] = "api.open-meteo.com";
static const uint16_t GETMETEO_PORT = 80;

// ============================================================
// VARIABLES DE SORTIE — disponibles globalement apres parsing
// ============================================================
// Prefixe getMeteo_ pour eviter tout conflit avec rx_xxx (LoRa)

// ── Temperature ──────────────────────────────────────────────
float    getMeteo_temperature    = 0.0;   // temperature_2m          degC
float    getMeteo_temperatureRess= 0.0;   // apparent_temperature    degC
float    getMeteo_rosee          = 0.0;   // dew_point_2m            degC

// ── Humidite ─────────────────────────────────────────────────
uint8_t  getMeteo_humidite       = 0;     // relative_humidity_2m    %

// ── Pression ─────────────────────────────────────────────────
float    getMeteo_pression       = 0.0;   // surface_pressure        hPa

// ── Vent ─────────────────────────────────────────────────────
float    getMeteo_vent           = 0.0;   // wind_speed_10m          km/h
uint16_t getMeteo_dirVent        = 0;     // wind_direction_10m      deg (0=N)
float    getMeteo_rafales        = 0.0;   // wind_gusts_10m          km/h

// ── Precipitations ───────────────────────────────────────────
float    getMeteo_pluie          = 0.0;   // precipitation           mm (total)
float    getMeteo_pluieLiquide   = 0.0;   // rain                    mm
float    getMeteo_averses        = 0.0;   // showers                 mm
float    getMeteo_neige          = 0.0;   // snowfall                cm

// ── Nuages / Visibilite / Rayonnement ────────────────────────
uint8_t  getMeteo_nuages         = 0;     // cloud_cover             %
uint16_t getMeteo_visibilite     = 0;     // visibility              m (max 65535)
float    getMeteo_rayonnement    = 0.0;   // shortwave_radiation     W/m2

// ── Code meteo WMO ───────────────────────────────────────────
uint8_t  getMeteo_codeWMO        = 0;     // weather_code            WMO 4677

// ── Etat general ─────────────────────────────────────────────
boolean  getMeteo_valide         = false; // true = donnees recues et parsees
char     getMeteo_lastUpdate[20] = {0};   // "2026-04-13T14:00"

// ============================================================
// MACHINE A ETATS — variables internes (static)
// ============================================================

typedef enum {
    GM_IDLE,            // Attente de la prochaine periode
    GM_CONNECT,         // Connexion TCP au serveur
    GM_SEND_REQUEST,    // Envoi de la requete HTTP GET
    GM_WAIT_RESPONSE,   // Lecture du flux HTTP/JSON
    GM_PARSE,           // Extraction des valeurs par strstr()
    GM_CLOSE            // Fermeture socket client.stop()
} GM_State;

static GM_State       _gm_state          = GM_IDLE;
static EthernetClient _gm_client;
static unsigned long  _gm_lastTime        = 0;
static boolean        _gm_premiereRequete = true;   // true = connexion immediate sans attendre la periode
static char           _gm_buf[GETMETEO_BUF_SIZE];
static uint16_t       _gm_bufIdx          = 0;

// ============================================================
// _gm_parseFloat()
//------------------------------------------------------------------------------
// Fonction   : Extrait un float depuis le buffer JSON par strstr()
//------------------------------------------------------------------------------
// Entree     : buf  (const char*)  buffer JSON complet
//              key  (const char*)  cle JSON ex: "temperature_2m":
// Sortie     : float — valeur parsee, 0.0 si cle absente
// ============================================================
static float _gm_parseFloat(const char* buf, const char* key) {
    const char* p = strstr(buf, key);
    if (!p) return 0.0f;
    p += strlen(key);
    while (*p == ':' || *p == ' ') p++;
    return atof(p);
}

// ============================================================
// _gm_parseInt()
//------------------------------------------------------------------------------
// Fonction   : Extrait un entier depuis le buffer JSON par strstr()
//------------------------------------------------------------------------------
// Entree     : buf  (const char*)  buffer JSON complet
//              key  (const char*)  cle JSON ex: "cloud_cover":
// Sortie     : int — valeur parsee, 0 si cle absente
// ============================================================
static int _gm_parseInt(const char* buf, const char* key) {
    const char* p = strstr(buf, key);
    if (!p) return 0;
    p += strlen(key);
    while (*p == ':' || *p == ' ') p++;
    return atoi(p);
}

// ============================================================
// _gm_parseStr()
//------------------------------------------------------------------------------
// Fonction   : Extrait une chaine depuis le buffer JSON (valeur entre "")
//------------------------------------------------------------------------------
// Entree     : buf     (const char*)  buffer JSON complet
//              key     (const char*)  cle JSON ex: "time":
//              dest    (char*)        buffer destination
//              maxLen  (uint8_t)      taille max dest (avec \0)
// Sortie     : aucune — ecrit dans dest, '\0' si cle absente
// ============================================================
static void _gm_parseStr(const char* buf, const char* key,
                          char* dest, uint8_t maxLen) {
    const char* p = strstr(buf, key);
    if (!p) { dest[0] = 0; return; }
    p += strlen(key);
    while (*p == ':' || *p == ' ') p++;
    if (*p == '"') p++;
    uint8_t i = 0;
    while (*p && *p != '"' && i < maxLen - 1) dest[i++] = *p++;
    dest[i] = 0;
}

// ============================================================
// GetMeteo_init()
//------------------------------------------------------------------------------
// Fonction   : Initialisation — a appeler une fois dans setup()
//              APRES Initialisation_Ethernet() et server.begin()
//------------------------------------------------------------------------------
// Entree     : aucune
// Sortie     : aucune
// Var mod    : _gm_state, _gm_lastTime, _gm_premiereRequete, _gm_bufIdx
// ============================================================
void GetMeteo_init(void) {
    _gm_state          = GM_IDLE;
    _gm_lastTime       = millis();
    _gm_premiereRequete = true;     // → connexion immediate au 1er appel loop()
    _gm_bufIdx         = 0;
    memset(_gm_buf, 0, sizeof(_gm_buf));
    Serial.println(F("[GetMeteo] init OK — Open-Meteo HTTP:80"));
    Serial.print(F("[GetMeteo] lat="));  Serial.print(conf.getMeteoLat[0] ? conf.getMeteoLat : GETMETEO_LAT_DEFAULT);
    Serial.print(F(" lon="));            Serial.print(conf.getMeteoLon[0] ? conf.getMeteoLon : GETMETEO_LON_DEFAULT);
    Serial.print(F(" periode="));        Serial.print(GETMETEO_PERIODE_MS / 1000);
    Serial.println(F("s"));
}

// ============================================================
// GetMeteo_resetChrono()
//------------------------------------------------------------------------------
// Fonction   : A appeler depuis setup() apres une connexion manuelle
//              Empeche la double connexion au debut de loop()
//              _gm_premiereRequete = false → loop() attend GETMETEO_PERIODE_MS
//------------------------------------------------------------------------------
// Entree     : aucune
// Sortie     : aucune
// ============================================================
void GetMeteo_resetChrono(void) {
    _gm_premiereRequete = false;
    _gm_lastTime        = millis();
}

// ============================================================
// GetMeteo_loop()
//------------------------------------------------------------------------------
// Fonction   : Machine a etats non bloquante — a appeler dans loop()
//              Une seule passe par appel — jamais bloquant
//------------------------------------------------------------------------------
// Entree     : aucune
// Sortie     : aucune
// Var mod    : toutes les variables getMeteo_xxx, _gm_state, _gm_buf
// ============================================================
void GetMeteo_loop(void) {

    switch (_gm_state) {

    // ────────────────────────────────────────────────────────
    // GM_IDLE — attente de la prochaine periode
    //   _gm_premiereRequete = true  → connexion immediate
    //   Ensuite : condition millis() normale (periode 1 min)
    // ────────────────────────────────────────────────────────
    case GM_IDLE:
        if (_gm_premiereRequete ||
            millis() - _gm_lastTime >= GETMETEO_PERIODE_MS) {
            _gm_premiereRequete = false;
            Serial.println(F("[GetMeteo] → GM_CONNECT"));
            _gm_state    = GM_CONNECT;
            _gm_lastTime = millis();
        }
        break;

    // ────────────────────────────────────────────────────────
    // GM_CONNECT — connexion TCP port 80
    //   client.connect() peut bloquer ~10ms (ARP) : acceptable
    //   Timeout : GETMETEO_TIMEOUT_MS
    // ────────────────────────────────────────────────────────
    case GM_CONNECT:
        if (millis() - _gm_lastTime > GETMETEO_TIMEOUT_MS) {
            Serial.println(F("[GetMeteo] TIMEOUT connect → GM_IDLE"));
            _gm_state    = GM_IDLE;
            _gm_lastTime = millis();
            break;
        }
        if (_gm_client.connect(GETMETEO_HOST, GETMETEO_PORT)) {
            Serial.println(F("[GetMeteo] TCP ok → GM_SEND_REQUEST"));
            _gm_state    = GM_SEND_REQUEST;
            _gm_lastTime = millis();
        }
        break;

    // ────────────────────────────────────────────────────────
    // GM_SEND_REQUEST — envoi requete HTTP GET
    //
    //  Variables demandees (current=) :
    //    temperature_2m         temperature 2m         degC
    //    apparent_temperature   temperature ressentie  degC
    //    dew_point_2m           point de rosee         degC
    //    relative_humidity_2m   humidite relative      %
    //    surface_pressure       pression surface       hPa
    //    wind_speed_10m         vitesse vent 10m       km/h
    //    wind_direction_10m     direction vent 10m     deg
    //    wind_gusts_10m         rafales 10m            km/h
    //    precipitation          precipitation totale   mm
    //    rain                   pluie seule            mm
    //    showers                averses convectives    mm
    //    snowfall               chutes de neige        cm
    //    cloud_cover            couverture nuageuse    %
    //    visibility             visibilite             m
    //    shortwave_radiation    rayonnement solaire    W/m2
    //    weather_code           code WMO               0..99
    //
    //  Options : wind_speed_unit=kmh  timezone=Europe/Paris
    //  HTTP/1.1 + Connection: close (libere le socket W5500)
    // ────────────────────────────────────────────────────────
    case GM_SEND_REQUEST:
        _gm_client.print(F("GET /v1/forecast?latitude="));
        _gm_client.print(conf.getMeteoLat[0] ? conf.getMeteoLat : GETMETEO_LAT_DEFAULT);
        _gm_client.print(F("&longitude="));
        _gm_client.print(conf.getMeteoLon[0] ? conf.getMeteoLon : GETMETEO_LON_DEFAULT);
        _gm_client.print(F("&current=temperature_2m,apparent_temperature,"));
        _gm_client.print(F("dew_point_2m,relative_humidity_2m,"));
        _gm_client.print(F("surface_pressure,wind_speed_10m,"));
        _gm_client.print(F("wind_direction_10m,wind_gusts_10m,"));
        _gm_client.print(F("precipitation,rain,showers,snowfall,"));
        _gm_client.print(F("cloud_cover,visibility,"));
        _gm_client.print(F("shortwave_radiation,weather_code"));
        _gm_client.print(F("&wind_speed_unit=kmh"));
        _gm_client.println(F("&timezone=Europe%2FParis HTTP/1.1"));
        _gm_client.print(F("Host: "));
        _gm_client.println(F(GETMETEO_HOST));
        _gm_client.println(F("Connection: close"));
        _gm_client.println();

        _gm_bufIdx   = 0;
        memset(_gm_buf, 0, sizeof(_gm_buf));
        _gm_lastTime = millis();
        _gm_state    = GM_WAIT_RESPONSE;
        Serial.println(F("[GetMeteo] requete envoyee → GM_WAIT_RESPONSE"));
        break;

    // ────────────────────────────────────────────────────────
    // GM_WAIT_RESPONSE — lecture flux HTTP non bloquante
    //   Lit tous les octets disponibles en une passe
    //   Fin detectee par deconnexion serveur (Connection: close)
    //   Timeout si serveur ne repond pas
    // ────────────────────────────────────────────────────────
    case GM_WAIT_RESPONSE:
        if (millis() - _gm_lastTime > GETMETEO_TIMEOUT_MS) {
            Serial.println(F("[GetMeteo] TIMEOUT reception → GM_CLOSE"));
            _gm_state = GM_CLOSE;
            break;
        }
        while (_gm_client.available()) {
            char c = _gm_client.read();
            if (_gm_bufIdx < GETMETEO_BUF_SIZE - 1)
                _gm_buf[_gm_bufIdx++] = c;
        }
        if (!_gm_client.connected()) {
            _gm_buf[_gm_bufIdx] = 0;
            Serial.print(F("[GetMeteo] "));
            Serial.print(_gm_bufIdx);
            Serial.print(F("/"));
            Serial.print(GETMETEO_BUF_SIZE);
            Serial.println(F(" octets → GM_PARSE"));
            if (_gm_bufIdx >= GETMETEO_BUF_SIZE - 1)
                Serial.println(F("[GetMeteo] ATTENTION : buffer plein — reponse tronquee !"));
            _gm_state = GM_PARSE;
        }
        break;

    // ────────────────────────────────────────────────────────
    // ────────────────────────────────────────────────────────
    // GM_PARSE — extraction JSON par strstr()
    //
    //   IMPORTANT : la reponse contient "current_units":{...}
    //   AVANT "current":{...}
    //   strstr(buf, "\"current\"") trouve "current_units" en premier
    //   → toutes les valeurs parsees depuis les UNITES = 0 ou ""
    //
    //   SOLUTION : chercher "\"current\":{"  (avec accolade)
    //   "current_units" est suivi de ':' mais PAS de '{'
    //   immediatement — "current":{ est unique dans la reponse
    //
    //   Ensuite : tous les strstr() sont faits depuis pJson
    //   (pointeur sur le debut du bloc current)
    //   → impossible de remonter dans current_units
    // ────────────────────────────────────────────────────────
    case GM_PARSE: {

        // Localiser le debut du bloc "current":{
        // "current_units" ne matche pas car suivi de ':' + '"'
        // "current":{ est la seule occurrence avec accolade directe
        const char* pJson = strstr(_gm_buf, "\"current\":{");
        if (!pJson) {
            Serial.println(F("[GetMeteo] PARSE ERR : bloc current:{ absent"));
            // Afficher les 200 premiers octets pour diagnostic
            char tmp[201]; strncpy(tmp, _gm_buf, 200); tmp[200] = 0;
            Serial.println(tmp);
            getMeteo_valide = false;
            _gm_state = GM_CLOSE;
            break;
        }

        // Avancer apres "current":{ pour etre dans le bon bloc
        pJson += 11;   // strlen("\"current\":{") = 11

        // Horodatage — "time": dans le bloc current
        _gm_parseStr(pJson, "\"time\":",
                     getMeteo_lastUpdate, sizeof(getMeteo_lastUpdate));

        // Temperature
        getMeteo_temperature     = _gm_parseFloat(pJson, "\"temperature_2m\":");
        getMeteo_temperatureRess = _gm_parseFloat(pJson, "\"apparent_temperature\":");
        getMeteo_rosee           = _gm_parseFloat(pJson, "\"dew_point_2m\":");

        // Humidite
        getMeteo_humidite  = (uint8_t)_gm_parseInt(pJson, "\"relative_humidity_2m\":");

        // Pression
        getMeteo_pression  = _gm_parseFloat(pJson, "\"surface_pressure\":");

        // Vent
        getMeteo_vent      = _gm_parseFloat(pJson, "\"wind_speed_10m\":");
        getMeteo_dirVent   = (uint16_t)_gm_parseInt(pJson, "\"wind_direction_10m\":");
        getMeteo_rafales   = _gm_parseFloat(pJson, "\"wind_gusts_10m\":");

        // Precipitations
        getMeteo_pluie        = _gm_parseFloat(pJson, "\"precipitation\":");
        getMeteo_pluieLiquide = _gm_parseFloat(pJson, "\"rain\":");
        getMeteo_averses      = _gm_parseFloat(pJson, "\"showers\":");
        getMeteo_neige        = _gm_parseFloat(pJson, "\"snowfall\":");

        // Nuages / Visibilite / Rayonnement
        getMeteo_nuages      = (uint8_t) _gm_parseInt(pJson, "\"cloud_cover\":");
        getMeteo_visibilite  = (uint16_t)_gm_parseInt(pJson, "\"visibility\":");
        getMeteo_rayonnement = _gm_parseFloat(pJson, "\"shortwave_radiation\":");

        // Code WMO
        getMeteo_codeWMO = (uint8_t)_gm_parseInt(pJson, "\"weather_code\":");

        getMeteo_valide = true;

        // Debug serie
        Serial.println(F("[GetMeteo] PARSE OK :"));
        Serial.print(F("  time    : ")); Serial.println(getMeteo_lastUpdate);
        Serial.print(F("  temp    : ")); Serial.print(getMeteo_temperature,1);     Serial.println(F(" C"));
        Serial.print(F("  ressenti: ")); Serial.print(getMeteo_temperatureRess,1); Serial.println(F(" C"));
        Serial.print(F("  rosee   : ")); Serial.print(getMeteo_rosee,1);           Serial.println(F(" C"));
        Serial.print(F("  humidite: ")); Serial.print(getMeteo_humidite);          Serial.println(F(" %"));
        Serial.print(F("  pression: ")); Serial.print(getMeteo_pression,1);        Serial.println(F(" hPa"));
        Serial.print(F("  vent    : ")); Serial.print(getMeteo_vent,1);            Serial.println(F(" km/h"));
        Serial.print(F("  dir     : ")); Serial.print(getMeteo_dirVent);           Serial.println(F(" deg"));
        Serial.print(F("  rafales : ")); Serial.print(getMeteo_rafales,1);         Serial.println(F(" km/h"));
        Serial.print(F("  pluie   : ")); Serial.print(getMeteo_pluie,1);           Serial.println(F(" mm"));
        Serial.print(F("  neige   : ")); Serial.print(getMeteo_neige,1);           Serial.println(F(" cm"));
        Serial.print(F("  nuages  : ")); Serial.print(getMeteo_nuages);            Serial.println(F(" %"));
        Serial.print(F("  visib.  : ")); Serial.print(getMeteo_visibilite);        Serial.println(F(" m"));
        Serial.print(F("  rayon.  : ")); Serial.print(getMeteo_rayonnement,0);     Serial.println(F(" W/m2"));
        Serial.print(F("  WMO     : ")); Serial.println(getMeteo_codeWMO);

        _gm_state = GM_CLOSE;
    } break;

    // ────────────────────────────────────────────────────────
    // GM_CLOSE — fermeture propre du socket TCP
    //   client.stop() libere le socket W5500
    //   Retour en GM_IDLE — chrono repart pour la periode suivante
    // ────────────────────────────────────────────────────────
    case GM_CLOSE:
        _gm_client.stop();
        _gm_lastTime = millis();
        _gm_state    = GM_IDLE;
        Serial.println(F("[GetMeteo] socket ferme → GM_IDLE"));
        break;

    } // fin switch
}

// ============================================================
// GetMeteo_sendXML()
//------------------------------------------------------------------------------
// Fonction   : Genere la reponse HTTP/XML pour le serveur WEB AJAX
//              A appeler dans ServeurWEB.h sur GET /ajax_inputs?meteo_ext
//------------------------------------------------------------------------------
// Entree     : client  (EthernetClient&)  client HTTP connecte
// Sortie     : aucune — ecrit la reponse HTTP complete dans le client
//
// Format XML retourne :
//   HTTP/1.1 200 OK
//   Content-Type: text/xml
//   Connection: close
//
//   <?xml version="1.0"?>
//   <meteo_ext>
//     <GTEMP>18.4</GTEMP>
//     <GTRES>16.2</GTRES>
//     <GROSEE>8.1</GROSEE>
//     <GHUM>52</GHUM>
//     <GPRES>1013.2</GPRES>
//     <GVENT>12.6</GVENT>
//     <GDIR>245</GDIR>
//     <GRAFALES>22.0</GRAFALES>
//     <GPLUIE>0.0</GPLUIE>
//     <GPLUIELIQ>0.0</GPLUIELIQ>
//     <GAVERSES>0.0</GAVERSES>
//     <GNEIGE>0.0</GNEIGE>
//     <GNUAGES>25</GNUAGES>
//     <GVISI>24140</GVISI>
//     <GRAYON>620</GRAYON>
//     <GWMO>2</GWMO>
//     <GVALIDE>1</GVALIDE>
//     <GUPDATE>2026-04-13T14:00</GUPDATE>
//   </meteo_ext>
//
// Integration ServeurWEB.h :
//   if (strstr(HTTP_req, "meteo_ext")) {
//       GetMeteo_sendXML(client);
//       return;
//   }
// ============================================================
void GetMeteo_sendXML(EthernetClient& client) {

    client.println(F("HTTP/1.1 200 OK"));
    client.println(F("Content-Type: text/xml"));
    client.println(F("Connection: close"));
    client.println();
    client.println(F("<?xml version=\"1.0\"?>"));
    client.println(F("<meteo_ext>"));

    // Temperature 2m
    client.print(F("<GTEMP>"));    client.print(getMeteo_temperature,1);     client.println(F("</GTEMP>"));
    // Temperature ressentie
    client.print(F("<GTRES>"));    client.print(getMeteo_temperatureRess,1); client.println(F("</GTRES>"));
    // Point de rosee
    client.print(F("<GROSEE>"));   client.print(getMeteo_rosee,1);           client.println(F("</GROSEE>"));
    // Humidite relative
    client.print(F("<GHUM>"));     client.print(getMeteo_humidite);          client.println(F("</GHUM>"));
    // Pression surface
    client.print(F("<GPRES>"));    client.print(getMeteo_pression,1);        client.println(F("</GPRES>"));
    // Vent vitesse
    client.print(F("<GVENT>"));    client.print(getMeteo_vent,1);            client.println(F("</GVENT>"));
    // Vent direction
    client.print(F("<GDIR>"));     client.print(getMeteo_dirVent);           client.println(F("</GDIR>"));
    // Rafales
    client.print(F("<GRAFALES>")); client.print(getMeteo_rafales,1);         client.println(F("</GRAFALES>"));
    // Precipitation totale
    client.print(F("<GPLUIE>"));   client.print(getMeteo_pluie,1);           client.println(F("</GPLUIE>"));
    // Pluie seule
    client.print(F("<GPLUIELIQ>")); client.print(getMeteo_pluieLiquide,1);   client.println(F("</GPLUIELIQ>"));
    // Averses
    client.print(F("<GAVERSES>")); client.print(getMeteo_averses,1);         client.println(F("</GAVERSES>"));
    // Neige (cm)
    client.print(F("<GNEIGE>"));   client.print(getMeteo_neige,1);           client.println(F("</GNEIGE>"));
    // Couverture nuageuse
    client.print(F("<GNUAGES>"));  client.print(getMeteo_nuages);            client.println(F("</GNUAGES>"));
    // Visibilite (m)
    client.print(F("<GVISI>"));    client.print(getMeteo_visibilite);        client.println(F("</GVISI>"));
    // Rayonnement solaire (W/m2 — 0 decimale suffisante)
    client.print(F("<GRAYON>"));   client.print(getMeteo_rayonnement,0);     client.println(F("</GRAYON>"));
    // Code WMO
    client.print(F("<GWMO>"));     client.print(getMeteo_codeWMO);           client.println(F("</GWMO>"));
    // Validite
    client.print(F("<GVALIDE>")); client.print(getMeteo_valide ? 1 : 0);    client.println(F("</GVALIDE>"));
    // Horodatage
    client.print(F("<GUPDATE>")); client.print(getMeteo_lastUpdate);        client.println(F("</GUPDATE>"));

    client.println(F("</meteo_ext>"));
}

#endif // GETMETEO_H
