diff --git a/.gitignore b/.gitignore index 89cc49c..5045321 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ .vscode/c_cpp_properties.json .vscode/launch.json .vscode/ipch +config.json \ No newline at end of file diff --git a/README.md b/README.md index 9e64067..7f6606c 100644 --- a/README.md +++ b/README.md @@ -1,182 +1,402 @@ # ESP32 Contrôleur Solaire pour Chauffe-Eau -Application ESP32 avec serveur web, stockage LittleFS et mises à jour OTA. +Application ESP32 pour contrôler intelligemment un chauffe-eau avec l'excédent de production solaire. ## Fonctionnalités -- ✅ Serveur web avec stockage HTML sur LittleFS -- ✅ Protection par mot de passe (authentification SHA-256) -- ✅ Page d'accueil "Hello World" à la racine -- ✅ Interface de mise à jour OTA accessible via `/update.html` -- ✅ Mise à jour du firmware via OTA -- ✅ Mise à jour du filesystem via OTA +### Interface Web +- ✅ Serveur web asynchrone (ESPAsyncWebServer) avec authentification SHA-256 +- ✅ Interface responsive avec suivi en temps réel +- ✅ Configuration complète via interface web +- ✅ Mise à jour OTA du firmware et du filesystem via `/update.html` - ✅ Support ArduinoOTA pour mise à jour via l'IDE -## Configuration +### Modes de Fonctionnement +- **Mode OFF (0)**: Chauffe-eau désactivé +- **Mode JOUR (2)**: Chauffe progressive pour atteindre la température maximale à l'heure cible +- **Mode SOLEIL (4)**: Chauffe uniquement avec l'excédent solaire +- **Mode NUIT (8)**: Chauffe forcée 100% pendant la période nocturne jusqu'à la température minimale fixée en paramètre +- **Mode ON (16)**: Chauffe forcée 100% en continu +- **Modes combinables**: JOUR+SOLEIL (6), NUIT+SOLEIL (12), NUIT+JOUR (10), NUIT+SOLEIL+JOUR (14) + +### Contrôle Intelligent +- ✅ PWM logiciel 1 Hz pour contrôle précis de la puissance +- ✅ Intégration passerelle Enphase pour données solaires en temps réel +- ✅ Réception température via API ESPEasy +- ✅ API REST complète pour contrôle externe + +### Configuration + +La configuration se fait exclusivement via l'interface web à `/settings.html`. Toutes les modifications sont enregistrées dans `config.json`. + +#### Paramètres WiFi +- **SSID et mot de passe** : Configurables via l'interface web +- **Mot de passe AP** : Pour le mode Access Point (min 8 caractères) +- **Mode AP automatique** : Si la connexion WiFi échoue, l'ESP32 démarre un point d'accès + +#### Authentification Web +- **Utilisateur par défaut** : `admin` +- **Mot de passe par défaut** : `password` +- **Changement de mot de passe** : Via l'interface `/settings.html` (hash SHA-256 automatique) +- **Timeout de session** : Configurable (défaut: 60 minutes) + +#### Paramètres Système +- **Hostname** : Nom de l'appareil sur le réseau +- **Puissance max chauffe-eau** : En Watts (défaut: 2400W) +- **Température eau min/max** : Plages de température (défaut: 40-60°C) +- **Coefficient jour** : Pour calcul mode JOUR (défaut: 350) +- **Heure cible** : Heure pour atteindre temp max en mode JOUR (défaut: 17h) +- **Période nuit** : Début et fin (défaut: 0h-4h) +- **Coordonnées GPS** : Latitude/Longitude pour calcul lever/coucher soleil + +#### Intégration Enphase +- **IP passerelle** : Adresse IP de la passerelle Enphase +- **Token JWT** : Token d'authentification +- **Intervalle mise à jour** : En secondes (défaut: 5s) -### 1. Modifier les identifiants WiFi - -Éditez `src/main.cpp` et modifiez les lignes suivantes: - -```cpp -const char* ssid = "VotreSSID"; // Votre nom de réseau WiFi -const char* password = "VotreMotDePasse"; // Votre mot de passe WiFi -``` - -### 2. Configuration OTA (optionnel) +## Installation -```cpp -const char* otaHostname = "ESP32-Controleur-Solaire"; // Nom de l'appareil sur le réseau -const char* otaPassword = "admin"; // Mot de passe pour OTA via IDE +### Matériel requis +- **ESP32-S3** (testé avec ESP32-S3-supermini) + - QFN56, révision v0.2 + - Flash 4MB (XMC) + - PSRAM 2MB (AP_3v3) + - Crystal 40MHz + - USB-Serial/JTAG intégré +- **GPIO12** : Sortie pour commande chauffe-eau (PWM logiciel 1Hz) + +### Configuration PlatformIO +Le fichier `platformio.ini` est déjà configuré pour l'ESP32-S3 : +```ini +[env:esp32-s3-supermini] +platform = espressif32 +board = esp32-s3-devkitc-1 +framework = arduino +board_build.mcu = esp32s3 +board_build.f_cpu = 240000000L +board_build.flash_size = 4MB +board_build.partitions = default.csv +board_build.filesystem = littlefs +build_flags = + -DBOARD_HAS_PSRAM + -DARDUINO_USB_MODE=1 + -DARDUINO_USB_CDC_ON_BOOT=1 +lib_deps = + esphome/ESPAsyncWebServer-esphome@^3.1.0 + bblanchon/ArduinoJson@^7.2.1 + me-no-dev/AsyncTCP@^1.1.4 ``` -### 3. Configuration de l'authentification web - -Par défaut, l'accès au serveur web est protégé par mot de passe: -- **Utilisateur**: `admin` -- **Mot de passe**: `password` - -#### Changer le mot de passe: - -1. **Générer le hash SHA-256 de votre nouveau mot de passe:** - - Sous Linux/Mac: - ```bash - echo -n "votre_nouveau_mot_de_passe" | sha256sum - ``` - - Sous Windows (PowerShell): - ```powershell - $stringAsStream = [System.IO.MemoryStream]::new() - $writer = [System.IO.StreamWriter]::new($stringAsStream) - $writer.write("votre_nouveau_mot_de_passe") - $writer.Flush() - $stringAsStream.Position = 0 - Get-FileHash -InputStream $stringAsStream -Algorithm SHA256 | Select-Object Hash - ``` - - Ou utilisez un outil en ligne: https://emn178.github.io/online-tools/sha256.html - -2. **Éditer le fichier `data/config.json`:** - ```json - { - "auth": { - "username": "admin", - "password_hash": "VOTRE_NOUVEAU_HASH_ICI" - } - } - ``` - -3. **Re-uploader le filesystem:** - ```bash - platformio run --target uploadfs - ``` - -**⚠️ Important**: Le mot de passe est stocké sous forme de hash SHA-256 dans le fichier de configuration pour plus de sécurité. - -## Installation - ### Première installation (câble USB) -1. **Compiler et uploader le code:** +1. **Compiler et uploader le firmware:** ```bash - platformio run --target upload + platformio run --target upload --environment esp32-s3-supermini ``` 2. **Uploader le filesystem LittleFS:** ```bash - platformio run --target uploadfs + platformio run --target uploadfs --environment esp32-s3-supermini ``` +3. **Accéder à l'interface web:** + - En mode AP : connectez-vous au réseau WiFi "Routeur solaire" (mot de passe: `123456`) + - Ouvrez http://192.168.4.1 + - Connectez-vous avec `admin` / `password` + - Configurez votre WiFi dans `/settings.html` + ### Mises à jour ultérieures -#### Via l'interface web `/update.html`: +#### Via l'interface web `/update.html` (recommandé): 1. Accédez à `http://[IP_de_votre_ESP]/update.html` 2. Sélectionnez le fichier `.bin` approprié: - - **Firmware**: `.pio/build/esp32/firmware.bin` - - **Filesystem**: `.pio/build/esp32/littlefs.bin` + - **Firmware**: `.pio/build/esp32-s3-supermini/firmware.bin` + - **Filesystem**: `.pio/build/esp32-s3-supermini/littlefs.bin` 3. Cliquez sur le bouton d'upload correspondant +4. Attendez la fin du transfert (100%) +5. Le message de succès s'affiche pendant 10s (firmware) ou 8s (filesystem) +6. Redirection automatique vers `/index.html` après redémarrage #### Via ArduinoOTA (depuis PlatformIO): ```bash # Mise à jour du firmware -platformio run --target upload --upload-port [IP_de_votre_ESP] +platformio run --target upload --environment esp32-s3-supermini --upload-port [IP_de_votre_ESP] # Mise à jour du filesystem -platformio run --target uploadfs --upload-port [IP_de_votre_ESP] +platformio run --target uploadfs --environment esp32-s3-supermini --upload-port [IP_de_votre_ESP] ``` +**Note**: Le mot de passe OTA est `password` (configurable dans `main.cpp`) + ## Structure du projet ``` -esp32_controleur_solaire/ -├── platformio.ini # Configuration PlatformIO +RouteurSolaire/ +├── platformio.ini # Configuration PlatformIO ESP32-S3 ├── src/ -│ └── main.cpp # Code principal +│ └── main.cpp # Code principal (1400+ lignes) ├── data/ # Fichiers pour LittleFS -│ ├── config.json # Configuration (hash du mot de passe) -│ ├── index.html # Console principale (tout CSS inclus inline) -│ ├── login.html # Page de connexion (tout CSS inclus inline) -│ ├── update.html # Interface de mise à jour OTA (tout CSS inclus inline) -│ └── settings.html # Page de configuration (tout CSS inclus inline) +│ ├── config.json # Configuration persistante +│ ├── index.html # Console principale avec graphiques +│ ├── login.html # Page de connexion +│ ├── settings.html # Configuration système +│ ├── update.html # Interface OTA +│ └── favicon.ico # Icône du site ├── include/ ├── lib/ └── test/ ``` +**Note**: Tous les fichiers HTML incluent leur CSS inline. Aucun fichier CSS externe n'est nécessaire. + +## API REST + +### Endpoints authentifiés + +#### GET `/api/data` - Données temps réel +Retourne les données de monitoring : +```json +{ + "solar_production": 1500.0, + "power_consumption": 800.0, + "heater_power": 75, + "water_temperature": 48.5, + "sunrise_time": "08:32", + "sunset_time": "17:15", + "timestamp": 123456789 +} +``` + +#### POST `/api/data` - Mise à jour manuelle +Envoyer des données manuellement : +```json +{ + "solar_production": 1500.0, + "power_consumption": 800.0, + "water_temperature": 48.5 +} +``` + +#### GET `/api/mode` - Lire le mode actuel +```json +{ + "mode": 14 +} +``` + +#### POST `/api/mode` - Changer le mode +```json +{ + "mode": 14 +} +``` + +#### GET `/api/settings` - Configuration système +Retourne toute la configuration (WiFi, système, Enphase) + +#### POST `/api/settings` - Modifier configuration +Envoyer les paramètres à modifier (voir structure dans `config.json`) + +### Endpoints publics (sans authentification) + +#### GET `/api/espeasy?temperature=48.5` +Endpoint simplifié pour ESPEasy : +``` +http://[IP]/api/espeasy?temperature=48.5 +``` +Retourne: `OK` (200) ou message d'erreur (400) + +#### GET `/api/mode?mode=8` +Changer le mode via URL simple : +``` +http://[IP]/api/mode?mode=8 +``` +Retourne: `OK` (200) ou message d'erreur (400) + +**Valeurs de mode** : +- `0` = OFF +- `2` = JOUR +- `4` = SOLEIL +- `6` = JOUR+SOLEIL +- `8` = NUIT +- `10` = NUIT+JOUR +- `12` = NUIT+SOLEIL +- `14` = NUIT+SOLEIL+JOUR (défaut) +- `16` = ON ## URLs disponibles - **http://[IP]/login** - Page de connexion - **http://[IP]/** - Console principale (protégée) - **http://[IP]/settings.html** - Configuration (protégée) -- **http://[IP]/update.html** - Interface de mise à jour OTA (protégée) +- **http://[IP]/update.html** - Interface OTA (protégée) - **http://[IP]/logout** - Déconnexion -**Note**: Toutes les pages sauf `/login` nécessitent une authentification. +**Note**: Toutes les pages sauf `/login` nécessitent une authentification via cookie de session. ## Moniteur série -Pour voir les logs de démarrage: +Pour voir les logs en temps réel: ```bash -platformio device monitor +platformio device monitor --environment esp32-s3-supermini ``` -Vous verrez: -- L'adresse IP attribuée -- Le statut de montage de LittleFS -- Les informations de connexion WiFi -- Les événements OTA +**Logs typiques au démarrage:** +``` +Démarrage ESP32 - Contrôleur Solaire +LittleFS monté avec succès +GPIO12 configurée pour le chauffe-eau +Configuration d'authentification chargée +Connexion à VotreSSID ........ +WiFi connecté! +Adresse IP: 192.168.0.29 +Heure actuelle: 14:23:45 +[SUN] Lever: 08:32, Coucher: 17:15 +ArduinoOTA prêt +Hostname OTA: Routeur solaire +Serveur HTTP AsyncWebServer démarré +Timer PWM initialisé (100Hz) +``` + +**Logs en fonctionnement:** +``` +[ESPEASY] Température reçue: 48.5°C +Enphase - Total: 1500.0W, Panneaux: 800.0W, Net: 700.0W +[MODE] Mode changé via GET: 14 +[OTA] Mise à jour démarrée: firmware.bin +[OTA] Espace sketch disponible +[OTA] Mise à jour réussie: 1441792 octets +[OTA] Redémarrage planifié dans 2 secondes... +``` + +## Intégration ESPEasy + +Pour envoyer la température de l'eau depuis un capteur DS18B20 sur ESPEasy : + +1. Configurez votre capteur DS18B20 dans ESPEasy +2. Créez une règle pour envoyer la température : + ``` + on DS18B20#Temperature do + SendToHTTP 192.168.0.29,80,/api/espeasy?temperature=[DS18B20#Temperature] + endon + ``` + +## Calcul des Modes + +### Mode JOUR +Calcul progressif pour atteindre `max_water_temp` à l'heure cible : +``` +Puissance(%) = 100 × CoeffJour × (TempMax - TempActuelle) / SecondesRestantes +``` +- Plus on approche de l'heure cible, plus la puissance augmente +- Si température atteinte avant l'heure, chauffe = 0% + +### Mode SOLEIL +Utilise uniquement l'excédent solaire : +``` +Puissance(%) = (ProductionSolaire - Consommation) × 100 / PuissanceMaxChauffeEau +``` +- Ajustement dynamique en temps réel (toutes les 5s) +- Maximum plafonné à 100% + +### Mode NUIT +Chauffe forcée pendant la période configurée : +- Si `TempEau < TempMin` → Puissance = 100% +- Sinon → Puissance = 0% + +### Modes Combinés +Prend le **maximum** entre les modes actifs. +Exemple: JOUR+SOLEIL (6) = max(PuissanceJour, PuissanceSoleil) ## Dépannage ### Impossible de se connecter (authentification) - Vérifiez les identifiants par défaut: `admin` / `password` -- Assurez-vous que le fichier `config.json` a été uploadé avec le filesystem -- Vérifiez les logs série pour confirmer que la configuration a été chargée -- Effacez les cookies de votre navigateur et réessayez +- Connectez-vous au mode AP si aucun WiFi configuré : "Routeur solaire" / `123456` +- Effacez les cookies de votre navigateur +- Vérifiez les logs série pour confirmer que `config.json` a été chargé ### LittleFS ne monte pas -- Assurez-vous d'avoir uploadé le filesystem avec `uploadfs` +- Uploadez le filesystem : `platformio run --target uploadfs --environment esp32-s3-supermini` - Vérifiez que `board_build.filesystem = littlefs` est dans `platformio.ini` +- Vérifiez la taille de partition dans les logs série -### Impossible de se connecter au WiFi -- Vérifiez les identifiants WiFi dans `main.cpp` -- Vérifiez la force du signal WiFi - +### WiFi ne se connecte pas +- En cas d'échec, l'ESP32 démarre automatiquement en mode AP +- SSID par défaut : "Routeur solaire" +- Mot de passe AP : `123456` (modifiable dans `/settings.html`) +- Configurez votre WiFi via l'interface web +- Le système redémarre automatiquement après changement WiFi ### OTA ne fonctionne pas -- Vérifiez que l'ESP et votre ordinateur sont sur le même réseau -- Vérifiez le mot de passe OTA si vous utilisez ArduinoOTA -- Assurez-vous que le port 8266 n'est pas bloqué par un pare-feu +- **Via web** : Vérifiez que vous utilisez les bons fichiers `.bin` depuis `.pio/build/esp32-s3-supermini/` +- **Via ArduinoOTA** : Vérifiez que l'ESP et votre ordinateur sont sur le même réseau +- Mot de passe OTA : `password` (défini dans `main.cpp`) +- Attendez que le message de succès s'affiche (10s pour firmware, 8s pour filesystem) +- Si la page reste bloquée, attendez 15-20s puis rafraîchissez manuellement + +### Données Enphase non reçues +- Vérifiez l'IP de la passerelle dans `/settings.html` +- Vérifiez le token JWT (doit être valide) +- Consultez les logs série pour voir les erreurs HTTP +- Intervalle par défaut : 5 secondes + +### Température ESPEasy non reçue +- Testez manuellement : `http://[IP]/api/espeasy?temperature=50.5` +- Vérifiez la règle ESPEasy +- Consultez les logs série : `[ESPEASY] Température reçue: XX.X°C` +- Plage acceptée : 0-100°C + +### PWM ne fonctionne pas +- Vérifiez la connexion sur GPIO12 +- Le PWM est logiciel à 1Hz (1 seconde de période) +- Consultez les logs pour voir `heaterPower` calculé +- Timer fonctionne à 100Hz (interruption toutes les 10ms) + +## Caractéristiques Techniques + +- **Plateforme** : ESP32-S3 (espressif32) +- **Framework** : Arduino +- **Fréquence CPU** : 240 MHz +- **Flash** : 4 MB +- **PSRAM** : 2 MB +- **Filesystem** : LittleFS +- **Serveur Web** : ESPAsyncWebServer (asynchrone) +- **PWM** : Logiciel 1 Hz (période 1000ms) via timer hardware 100Hz +- **Contrôle** : GPIO12 (sortie digitale) +- **Sécurité** : SHA-256 pour authentification, sessions avec timeout +- **OTA** : Web + ArduinoOTA (port série et réseau) + +## Bibliothèques utilisées + +```ini +lib_deps = + esphome/ESPAsyncWebServer-esphome@^3.1.0 + bblanchon/ArduinoJson@^7.2.1 + me-no-dev/AsyncTCP@^1.4.0 +``` + +## Performances -### Style CSS -- Tous les styles sont désormais inclus directement dans chaque fichier HTML (``). -- Le fichier `style.css` a été supprimé, il n'est plus nécessaire. +- **Mémoire** : ~150KB RAM utilisée (avec PSRAM disponible) +- **CPU** : <5% en idle, ~20% pendant fetch Enphase +- **Réseau** : Latence <50ms pour API REST +- **PWM** : Précision ±10ms (timer 100Hz) +- **Update rate** : Enphase configurable (défaut 5s), calcul PWM 1s + +## Contributeurs + +Projet développé par C SCAYA avec l'assistance de GitHub Copilot (Claude Sonnet AI). ## Licence -MIT +MIT License - Libre d'utilisation, modification et distribution. + +--- + +**Version**: 1.0.0 +**Date**: Janvier 2026 +**Compatibilité**: ESP32-S3, Arduino Framework, PlatformIO diff --git a/data/favicon.ico b/data/favicon.ico new file mode 100644 index 0000000..305505b Binary files /dev/null and b/data/favicon.ico differ diff --git a/data/update.html b/data/update.html index ae1f29e..98ca8a2 100644 --- a/data/update.html +++ b/data/update.html @@ -217,7 +217,7 @@ showMessage('success', 'firmwareMessage', 'Firmware mis à jour avec succès! Redémarrage en cours...'); setTimeout(() => { window.location.href = '/index.html'; - }, 5000); + }, 10000); } else { showMessage('error', 'firmwareMessage', 'Erreur lors de la mise à jour: ' + xhr.responseText); } @@ -264,7 +264,7 @@ showMessage('success', 'filesystemMessage', 'Filesystem mis à jour avec succès! Redémarrage en cours...'); setTimeout(() => { window.location.href = '/index.html'; - }, 5000); + }, 10000); } else { showMessage('error', 'filesystemMessage', 'Erreur lors de la mise à jour: ' + xhr.responseText); } diff --git a/platformio.ini b/platformio.ini index f650d0b..99d8e62 100644 --- a/platformio.ini +++ b/platformio.ini @@ -12,4 +12,25 @@ lib_deps = ;upload_protocol = espota ;upload_port = 192.168.0.28 ;upload_flags = -; --auth=password \ No newline at end of file +; --auth=password + +[env:esp32-s3-supermini] +platform = espressif32 +board = esp32-s3-devkitc-1 +framework = arduino +board_upload.flash_size = 4MB +board_build.filesystem = littlefs +board_build.partitions = default.csv +monitor_speed = 115200 +lib_deps = + https://github.com/me-no-dev/ESPAsyncWebServer.git + https://github.com/me-no-dev/AsyncTCP.git + bblanchon/ArduinoJson@^7.2.1 +build_flags = + -DARDUINO_USB_CDC_ON_BOOT=1 + -DBOARD_HAS_PSRAM +; Configuration ArduinoOTA pour firmware et filesystem +; upload_protocol = espota +; upload_port = 192.168.0.29 +; upload_flags = +; --auth=password \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index f05f59d..3255ec5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -59,6 +59,10 @@ float waterTemperature = 0.0; // Température de l'eau du chauffe-eau en °C // Configuration GPIO #define HEATER_GPIO 12 // GPIO12 pour contrôle chauffe-eau +// Variables pour la gestion OTA +bool otaRebootPending = false; +unsigned long otaRebootTime = 0; + // Variables pour la gestion PWM avec timer (période 1s) const unsigned long PWM_PERIOD = 1000; // Période de 1 seconde en ms volatile unsigned long pwmCycleCount = 0; // Compteur de cycles PWM @@ -520,10 +524,10 @@ void calculateHeaterPower() { // Calculer les secondes restantes int secondsRemaining = targetTimeInSeconds - currentTimeInSeconds; - // Si on est après l'heure cible ou à l'heure cible, calculer jusqu'au lendemain - if (secondsRemaining <= 0) { - secondsRemaining = 86400 + secondsRemaining; // 86400 = 24h en secondes - } + // // Si on est après l'heure cible ou à l'heure cible, calculer jusqu'au lendemain + // if (secondsRemaining < 0) { + // secondsRemaining = 86400 + secondsRemaining; // 86400 = 24h en secondes + // } float tempDiff = maxWaterTemp - waterTemperature; @@ -1259,22 +1263,7 @@ void setup() { }); // Route POST /update - Mise à jour OTA (firmware ou filesystem) - server.on("/update", HTTP_POST, - [](AsyncWebServerRequest *request) { - // Handler appelé après la fin de l'upload - bool shouldReboot = !Update.hasError(); - - if (Update.hasError()) { - request->send(500, "text/plain", "Erreur lors de la mise à jour"); - } else { - request->send(200, "text/plain", "Mise à jour réussie! Redémarrage..."); - } - - if (shouldReboot) { - delay(1000); - ESP.restart(); - } - }, + server.on("/update", HTTP_POST,NULL, [](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { // Handler appelé pendant l'upload des données if (index == 0) { @@ -1317,8 +1306,20 @@ void setup() { if (final) { if (Update.end(true)) { Serial.printf("[OTA] Mise à jour réussie: %u octets\n", index + len); + + // Envoyer la réponse au client + AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", "Mise à jour réussie! Redémarrage..."); + response->addHeader("Connection", "close"); + response->addHeader("Content-Length", "38"); + request->send(response); + + // Planifier le redémarrage dans 2 secondes (géré dans loop) + otaRebootPending = true; + otaRebootTime = millis() + 2000; + Serial.println("[OTA] Redémarrage planifié dans 2 secondes..."); } else { Update.printError(Serial); + request->send(500, "text/plain", "Erreur lors de la mise à jour"); } } }); @@ -1381,6 +1382,12 @@ void setup() { } void loop() { + // Vérifier si un redémarrage OTA est en attente + if (otaRebootPending && millis() >= otaRebootTime) { + Serial.println("[OTA] Redémarrage maintenant..."); + ESP.restart(); + } + // Gérer les requêtes ArduinoOTA ArduinoOTA.handle();