diff --git a/ESP32-PoE-Test3/ESP32-PoE-Test3.ino b/ESP32-PoE-Test3/ESP32-PoE-Test3.ino new file mode 100644 index 0000000..68bf784 --- /dev/null +++ b/ESP32-PoE-Test3/ESP32-PoE-Test3.ino @@ -0,0 +1,35 @@ +/* + This sketch is combined demo of: 1) SD(esp_32) -- > SD_Test; 2) WiFi --> ETH_LAN8720 + It displays info about the card on the terminal and initializes LAN after that. You can ping the IP address shown on the terminal. + +*/ +#define ETH_CLK_MODE ETH_CLOCK_GPIO17_OUT +#define ETH_PHY_POWER 12 + +#include + +static bool eth_connected = false; + +void WiFiEvent(WiFiEvent_t event); +void testClient(const char * host, uint16_t port); + +#define BUTTON_PRESSED() (!digitalRead (34)) +void setup() +{ + Serial.begin(115200); + WiFi.onEvent(WiFiEvent); + ETH.begin(); + pinMode (34, INPUT); // Button +} + + +void loop() +{ + if (BUTTON_PRESSED()) { + if (eth_connected) { + Serial.println("Button pressed"); + testClient("google.com", 80); + } + delay(1000); + } +} diff --git a/ESP32-PoE-Test3/esp_eth.ino b/ESP32-PoE-Test3/esp_eth.ino new file mode 100644 index 0000000..09fc662 --- /dev/null +++ b/ESP32-PoE-Test3/esp_eth.ino @@ -0,0 +1,42 @@ +#include + +void WiFiEvent(WiFiEvent_t event) +{ + switch (event) { + case SYSTEM_EVENT_ETH_START: + Serial.println("ETH Started"); + //set eth hostname here + ETH.setHostname("esp32-ethernet"); + break; + case SYSTEM_EVENT_ETH_CONNECTED: + Serial.println("ETH Connected"); + break; + case SYSTEM_EVENT_ETH_GOT_IP: + Serial.print("ETH MAC: "); + Serial.print(ETH.macAddress()); + Serial.print(", IPv4: "); + Serial.print(ETH.localIP()); + if (ETH.fullDuplex()) { + Serial.print(", FULL_DUPLEX"); + } + Serial.print(", "); + Serial.print(ETH.linkSpeed()); + Serial.println("Mbps"); + if (ETH.enableIpV6()) { + Serial.print(" IPv6 enabled : "); + Serial.println(ETH.localIPv6()); + } + eth_connected = true; + break; + case SYSTEM_EVENT_ETH_DISCONNECTED: + Serial.println("ETH Disconnected"); + eth_connected = false; + break; + case SYSTEM_EVENT_ETH_STOP: + Serial.println("ETH Stopped"); + eth_connected = false; + break; + default: + break; + } +} diff --git a/ESP32-PoE-Test3/testclient.ino b/ESP32-PoE-Test3/testclient.ino new file mode 100644 index 0000000..ee40a9a --- /dev/null +++ b/ESP32-PoE-Test3/testclient.ino @@ -0,0 +1,19 @@ +void testClient(const char * host, uint16_t port) +{ + Serial.print("\nconnecting to "); + Serial.println(host); + + WiFiClient client; + if (!client.connect(host, port)) { + Serial.println("connection failed"); + return; + } + client.printf("GET / HTTP/1.1\r\nHost: %s\r\n\r\n", host); + while (client.connected() && !client.available()); + while (client.available()) { + Serial.write(client.read()); + } + + Serial.println("closing connection\n"); + client.stop(); +} diff --git a/Fil_Pilot/Fil_Pilot/FP_OTA.ino b/Fil_Pilot/Fil_Pilot/FP_OTA.ino new file mode 100644 index 0000000..1481bd5 --- /dev/null +++ b/Fil_Pilot/Fil_Pilot/FP_OTA.ino @@ -0,0 +1,37 @@ +// OTA Stuff to push the Code using Arduino OTA +#include "Fil_Pilot.h" + +// Setup OTA +void setup_ota() { + //Port defaults to 8266 + //ArduinoOTA.setPort(8266); + + // Hostname defaults to projet name + ArduinoOTA.setHostname(Mqtt_clientid); + + // No auth per default + ArduinoOTA.setPassword((const char *)OTAPASSWORD); + + ArduinoOTA.onStart([]() { + Serial.println("OTA Update is Starting !"); + }); + + ArduinoOTA.onEnd([]() { + Serial.println("\nOTA is done. Rebooting..."); + }); + + ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { + Serial.printf("Progress: %u%%\r", (progress / (total / 100))); + }); + + ArduinoOTA.onError([](ota_error_t error) { + Serial.printf("OTA Error[%u]: ", error); + if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed"); + else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed"); + else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed"); + else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed"); + else if (error == OTA_END_ERROR) Serial.println("End Failed"); + }); + + ArduinoOTA.begin(); +} diff --git a/Fil_Pilot/Fil_Pilot/FP_WMN.ino b/Fil_Pilot/Fil_Pilot/FP_WMN.ino new file mode 100644 index 0000000..193abf4 --- /dev/null +++ b/Fil_Pilot/Fil_Pilot/FP_WMN.ino @@ -0,0 +1,147 @@ +// Wifi Manager +// Thanks to https://github.com/tzapu/WiFiManager/blob/b22ba24283ad3f22f8c8dfb2a52ed0583c0222d7/examples/AutoConnectWithFSParameters/AutoConnectWithFSParameters.ino + +//callback notifying us of the need to save config +void saveConfigCallback () { + Serial.println("Should save config"); + shouldSaveConfig = true; +} + +void setup_WMN() { +//read configuration from FS json + Serial.println("mounting FS..."); + + if (SPIFFS.begin()) { + Serial.println("mounted file system"); + if (SPIFFS.exists("/config.json")) { + //file exists, reading and loading + Serial.println("reading config file"); + File configFile = SPIFFS.open("/config.json", "r"); + if (configFile) { + Serial.println("opened config file"); + size_t size = configFile.size(); + // Allocate a buffer to store contents of the file. + std::unique_ptr buf(new char[size]); + + configFile.readBytes(buf.get(), size); + + #ifdef ARDUINOJSON_VERSION_MAJOR >= 6 + DynamicJsonDocument json(1024); + DeserializationError deserializeError = deserializeJson(json, buf.get()); + serializeJsonPretty(json, Serial); + if (!deserializeError) { + #else + DynamicJsonBuffer jsonBuffer; + JsonObject& json = jsonBuffer.parseObject(buf.get()); + json.printTo(Serial); + if (json.success()) { + #endif + + Serial.println("\nparsed json"); + strcpy(mqtt_server, json["mqtt_server"]); + strcpy(mqtt_port, json["mqtt_port"]); + strcpy(blynk_token, json["blynk_token"]); + } else { + Serial.println("failed to load json config"); + } + configFile.close(); + } + } + } else { + Serial.println("failed to mount FS"); + } + + + // The extra parameters to be configured (can be either global or just in the setup) + // After connecting, parameter.getValue() will get you the configured value + // id/name placeholder/prompt default length + WiFiManagerParameter custom_mqtt_server("server", "mqtt server", mqtt_server, 40); + WiFiManagerParameter custom_mqtt_port("port", "mqtt port", mqtt_port, 6); + WiFiManagerParameter custom_blynk_token("blynk", "blynk token", blynk_token, 32); + + //WiFiManager + //Local intialization. Once its business is done, there is no need to keep it around + WiFiManager wifiManager; + + //set config save notify callback + wifiManager.setSaveConfigCallback(saveConfigCallback); + + //set static ip + wifiManager.setSTAStaticIPConfig(IPAddress(10, 0, 1, 99), IPAddress(10, 0, 1, 1), IPAddress(255, 255, 255, 0)); + + //add all your parameters here + wifiManager.addParameter(&custom_mqtt_server); + wifiManager.addParameter(&custom_mqtt_port); + wifiManager.addParameter(&custom_blynk_token); + + //reset settings - for testing + //wifiManager.resetSettings(); + + //set minimu quality of signal so it ignores AP's under that quality + //defaults to 8% + //wifiManager.setMinimumSignalQuality(); + + //sets timeout until configuration portal gets turned off + //useful to make it all retry or go to sleep + //in seconds + //wifiManager.setTimeout(120); + + //fetches ssid and pass and tries to connect + //if it does not connect it starts an access point with the specified name + //here "AutoConnectAP" + //and goes into a blocking loop awaiting configuration + if (!wifiManager.autoConnect("AutoConnectAP", "password")) { + Serial.println("failed to connect and hit timeout"); + delay(3000); + //reset and try again, or maybe put it to deep sleep + ESP.reset(); + delay(5000); + } + + //if you get here you have connected to the WiFi + Serial.println("connected...yeey :)"); + + //read updated parameters + strcpy(mqtt_server, custom_mqtt_server.getValue()); + strcpy(mqtt_port, custom_mqtt_port.getValue()); + strcpy(blynk_token, custom_blynk_token.getValue()); + Serial.println("The values in the file are: "); + Serial.println("\tmqtt_server : " + String(mqtt_server)); + Serial.println("\tmqtt_port : " + String(mqtt_port)); + Serial.println("\tblynk_token : " + String(blynk_token)); + + //save the custom parameters to FS + if (shouldSaveConfig) { + Serial.println("saving config"); + + #ifdef ARDUINOJSON_VERSION_MAJOR >= 6 + DynamicJsonDocument json(1024); + #else + DynamicJsonBuffer jsonBuffer; + JsonObject& json = jsonBuffer.createObject(); + #endif + + json["mqtt_server"] = mqtt_server; + json["mqtt_port"] = mqtt_port; + json["blynk_token"] = blynk_token; + + File configFile = SPIFFS.open("/config.json", "w"); + if (!configFile) { + Serial.println("failed to open config file for writing"); + } + + + #ifdef ARDUINOJSON_VERSION_MAJOR >= 6 + serializeJsonPretty(json, Serial); + serializeJson(json, configFile); + #else + json.printTo(Serial); + json.printTo(configFile); + #endif + + configFile.close(); + //end save + } + + Serial.println("local ip"); + Serial.println(WiFi.localIP()); diff --git a/Fil_Pilot/Fil_Pilot/FP_const.h b/Fil_Pilot/Fil_Pilot/FP_const.h new file mode 100644 index 0000000..446bb26 --- /dev/null +++ b/Fil_Pilot/Fil_Pilot/FP_const.h @@ -0,0 +1,8 @@ +// Some constants and variables for this project. + +const char* Mqtt_clientid = "ESP-FP"; +const char* dom_in = "domoticz/in"; +const char* dom_out = "domoticz/out"; + +// Flag for saving data +bool shouldSaveConfig = false; diff --git a/Fil_Pilot/Fil_Pilot/Fil_Pilot.h b/Fil_Pilot/Fil_Pilot/Fil_Pilot.h new file mode 100644 index 0000000..02f302d --- /dev/null +++ b/Fil_Pilot/Fil_Pilot/Fil_Pilot.h @@ -0,0 +1,5 @@ +// Personnal Settings + + +// OTA Password for Arduino +#define OTAPASSWORD "123" diff --git a/Fil_Pilot/Fil_Pilot/Fil_Pilot.ino b/Fil_Pilot/Fil_Pilot/Fil_Pilot.ino new file mode 100644 index 0000000..821ed8e --- /dev/null +++ b/Fil_Pilot/Fil_Pilot/Fil_Pilot.ino @@ -0,0 +1,28 @@ +// Fil Pilot code +// +// Hande low level code for Xavier Beaudouin's Fil Pilot Board +// +#include +#include +#include +#include +#include +#include +#include + +#include "Fil_Pilot.h" // Defines and some configurations +#include "FP_const.h" // Constants variables. + +void setup() { + Serial.begin (115200); + while (!Serial); // Wait for Arduino + Serial.println("\n\n"); + + // Launch OTA stuff + setup_ota(); +} + +void loop() { + // End + ArduinoOTA.handle(); +} diff --git a/Fil_Pilot/Fp_2/Fp_2.ino b/Fil_Pilot/Fp_2/Fp_2.ino new file mode 100644 index 0000000..0822936 --- /dev/null +++ b/Fil_Pilot/Fp_2/Fp_2.ino @@ -0,0 +1,248 @@ +#include +#include +#include +#include +#include + +#ifndef STASSID +#define STASSID "Kiwi" +#define STAPSK "ZeKiwi127" +#endif + +// FP status info LittleFS +#define FP_STATUS "/fp_status.txt" + +const char* ssid = STASSID; +const char* password = STAPSK; +File Status_File; + +// Configuration des IO +const uint16_t ALT_POS = 13; // Broche pilotant l'alternance positive +const uint16_t ALT_NEG = 14; // Broche pilotant l'alternance négative + +const uint8_t DEFAULT_PILOTE_STATUS = 1; // Mode par défaut Eco de manière à ce que la LED passe en orange après entrée d'une configuration valide + + +unsigned int fp = 0; + +// Cette fonction pilote le changement d'état des sorties, de la LED bicolore d'état et le mémorise dans le fichier d'état +void Pilote (int Status) { + switch (Status) { + case 0 : // aucune alternance en sortie, LED allumée en rouge : Confort + Serial.println("Confort"); + digitalWrite(ALT_NEG,LOW); + digitalWrite(ALT_POS,LOW); + break; + case 1 : // pleine alternance en sortie, LED allumée en orange (rouge+vert) : Eco + Serial.println("Eco"); + digitalWrite(ALT_NEG,HIGH); + digitalWrite(ALT_POS,HIGH); + break; + case 2 : // demie alternance négative en sortie, LED allumée en vert : Hors Gel + Serial.println("Hors Gel"); + digitalWrite(ALT_NEG,HIGH); + digitalWrite(ALT_POS,LOW); + break; + case 3 : // demie alternance positive en sortie, LED éteinte : Arrêt + Serial.println("Arrêt"); + digitalWrite(ALT_NEG,LOW); + digitalWrite(ALT_POS,HIGH); + break; + } + // Mémorisation dans le fichier + Serial.print(F("Status écrit : ")); + Serial.println(Status); + Status_File = LittleFS.open(FP_STATUS, "w+"); + Status_File.write(Status); + Status_File.seek(0, SeekSet); + Serial.print(F("Status relu : ")); + Serial.println(Status_File.read()); + Status_File.close(); +} + +// Setup OTA Stuff +void setupOTA() { + // Port defaults to 8266 + // ArduinoOTA.setPort(8266); + + // Hostname defaults to esp8266-[ChipID] + //ArduinoOTA.setHostname("myesp8266"); + + // No authentication by default + ArduinoOTA.setPassword("12345"); + + // Password can be set with it's md5 value as well + // MD5(admin) = 21232f297a57a5a743894a0e4a801fc3 + // ArduinoOTA.setPasswordHash("21232f297a57a5a743894a0e4a801fc3"); + + ArduinoOTA.onStart([]() { + String type; + if (ArduinoOTA.getCommand() == U_FLASH) { + type = "sketch"; + } else { // U_FS + type = "filesystem"; + } + + // NOTE: if updating FS this would be the place to unmount FS using FS.end() + Serial.println("Start updating " + type); + }); + ArduinoOTA.onEnd([]() { + Serial.println("\nEnd"); + }); + ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { + Serial.printf("Progress: %u%%\r", (progress / (total / 100))); + }); + ArduinoOTA.onError([](ota_error_t error) { + Serial.printf("Error[%u]: ", error); + if (error == OTA_AUTH_ERROR) { + Serial.println("Auth Failed"); + } else if (error == OTA_BEGIN_ERROR) { + Serial.println("Begin Failed"); + } else if (error == OTA_CONNECT_ERROR) { + Serial.println("Connect Failed"); + } else if (error == OTA_RECEIVE_ERROR) { + Serial.println("Receive Failed"); + } else if (error == OTA_END_ERROR) { + Serial.println("End Failed"); + } + }); + ArduinoOTA.begin(); +} + +// Setup LittleFS +void setupLFS() { + Serial.println(F("Initializing FS...")); + if (LittleFS.begin()) { + Serial.println(F("LittleFS system mounted with success")); + } else { + Serial.println(F("An Error has occurred while mounting LittleFS")); + } + + // Get all information about LittleFS + FSInfo fsInfo; + LittleFS.info(fsInfo); + + Serial.println("------------------------------"); + Serial.println("File system info"); + Serial.println("------------------------------"); + + // Taille de la zone de fichier + Serial.print("Total space: "); + Serial.print(fsInfo.totalBytes); + Serial.println(" byte"); + + // Espace total utilise + Serial.print("Total space used: "); + Serial.print(fsInfo.usedBytes); + Serial.println(" byte"); + + // Taille d un bloc et page + Serial.print("Block size: "); + Serial.print(fsInfo.blockSize); + Serial.println(" byte"); + + Serial.print("Page size: "); + Serial.print(fsInfo.totalBytes); + Serial.println(" byte"); + + Serial.print("Max open files: "); + Serial.println(fsInfo.maxOpenFiles); + + // Taille max. d un chemin + Serial.print("Max path lenght: "); + Serial.println(fsInfo.maxPathLength); + + Serial.println(); + + Serial.println("------------------------------"); + Serial.println("List files"); + Serial.println("------------------------------"); + // Ouvre le dossier racine | Open folder + Dir dir = LittleFS.openDir("/"); + // Affiche le contenu du dossier racine | Print dir the content + while (dir.next()) { + // recupere le nom du fichier | get filename + Serial.print(dir.fileName()); + Serial.print(" - "); + // et sa taille | and the size + if (dir.fileSize()) { + File file = dir.openFile("r"); + Serial.print(file.size()); + Serial.println(" byte"); + file.close(); + } else { + File file = dir.openFile("r"); + if ( file.isDirectory() ) { + Serial.println("this is a folder"); + } else { + Serial.println("file is empty"); + } + file.close(); + } + } +} + +void fp_from_fs() { + unsigned int Num_Cde; + + if (!LittleFS.exists(FP_STATUS)) { + Serial.println("Fil Pilot status file does not exists. Create it"); + Status_File = LittleFS.open(FP_STATUS, "w"); + Status_File.write(DEFAULT_PILOTE_STATUS); + Status_File.close(); + Pilote(DEFAULT_PILOTE_STATUS); + return; + } + // File should exist so read it + Serial.println(F("Status FP from FS : ")); + Status_File = LittleFS.open(FP_STATUS, "r"); + Num_Cde = Status_File.read(); + Status_File.close(); + Serial.print(F("Status is : ")); + Serial.println(Num_Cde); + + Pilote(Num_Cde); +} + +void setup() { + Serial.begin(115200); + Serial.println("Booting"); + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + while (WiFi.waitForConnectResult() != WL_CONNECTED) { + Serial.println("Connection Failed! Rebooting..."); + delay(5000); + ESP.restart(); + } + + // Setup OTA + setupOTA(); + + // Setup LittleFS + setupLFS(); + + // Alternance - + pinMode(ALT_NEG, OUTPUT); + // Alternance + + pinMode(ALT_POS, OUTPUT); + + // Now retreive FP status from FS + fp_from_fs(); + + // We are ready + Serial.println("Ready"); + Serial.print("IP address: "); + Serial.println(WiFi.localIP()); + + +} + +void loop() { + Pilote(fp); + fp = fp + 1; + if (fp >= 4) { + fp = 0; + } + delay(30000); + ArduinoOTA.handle(); +} diff --git a/Fil_Pilot/WiFi_Heater_Pilot_v.2.8/WiFi_Heater_Pilot_v.2.8.ino b/Fil_Pilot/WiFi_Heater_Pilot_v.2.8/WiFi_Heater_Pilot_v.2.8.ino new file mode 100644 index 0000000..c5ae99e --- /dev/null +++ b/Fil_Pilot/WiFi_Heater_Pilot_v.2.8/WiFi_Heater_Pilot_v.2.8.ino @@ -0,0 +1,1175 @@ +#include +#include +#include +#include +#include + +// Structures de données +struct ParVal +{ + String param; + String value; +}; + +struct Conf_WHP { + char devname[16] = ""; + char ip_ipx[16] = ""; + char api_key[16] = ""; +}; + +// Version du firmware +const String FIRMWARE_VERSION = "PatLeHibou's WiFi Heater Pilot v.2"; +String FilePath = __FILE__; // Récupère le chemin et le nom du fichier source +String FileName; // Recevra le nom du fichier seul, sans extension, permettant d'avoir la version précise de la release + +// Suivi de version +// v.1.0 : première version complètement opérationnelle basée sur la version v.0.6d de développement +// l'état du fil pilote, la luminosité, la présence ou non du capteur DHT ainsi que le nom du device sont conservés dans l'EEPROM et rechargé en cas de reboot (après coupure par ex.) +// V.2.0 : deuxième version intégrant les remontées d'informations vers l'IPX +// 2.0a : prend en charge les paramètres de connexion à l'IPX et pour la surveillance de la T° et l'hygrométrie, et envoie les requêtes HTTP vers l'IPX +// 2.1a : version faisant appel à SPIFFS pour solliciter moins l'EEPROM lors des changements d'état +// 2.1b : utilisation de la librairie DHTesp +// 2.2b : modification de la structure du JSON pour avoir une arborescence des paramètres de th_control et introduction d'un sous-paramètre "status" (même modif qu'en 2.1a et 2.2a) +// 2.2c : si la valeur de DeviceConfig lue au démarrage est 0xFF (valeur usuelle avec une carte WeMos neuve) DeviceConfig est forcé à 0 +// 2.5 : évolution majeure dans la gestion du Wifi en tirant parti de la mémorisation du SSID et du Password directement par l'ESP +// suppression de ces informations dans la structure de données rebaptisée Conf_WHP (pour WiFi Heater Pilot) +// suppression de la mémorisation d'un accès dans le byte DeviceConfig réduit à gérer la présence du capteur DHT +// suppression du byte qui jusqu'à la version 2.0a servait à mémoriser l'état du Fil Pilote +// nettoyage du code (suppression des commentaires inutiles, ajouts, corrections de commentaires...) +// 2.5a : ajout de la surveillance de la connexion WiFi (extinction LED Link si perte du réseau) +// avec auto-reconnexion en cas de perte du réseau +// 2.6 : remplacement de la procédure d'initialisation via requêtes HTTP par celle de WiFiManager +// 2.6a : modification du réglage de luminosité des LEDs pour éviter le scintillement +// luminosité fixée à 1000 si valeur demandée supérieure +// ajout d'un Wipe de l'EEPROM si bouton Hard Reset enfoncé au démarrage (les LEDs Link et Rouge cligntotent alternativement). Attention : luminosité fixée à 0 après +// mise en commentaire de la ligne "if (Etat.substring(0, 3).equalsIgnoreCase("EP=")) {Val=9;}" pour désactiver les fonctions de Dump, Wipe et Fill sur l'EEPROM +// 2.7 : ajout du retour de la température et de l'humidité dans des entrées virtuelles analogiques de l'IPX +// 2.7a : correction d'un bug empêchant de désactiver le capteur DHT (oubli de modifier un test suite au passage en 2.6) et d'un autre dans la réponse en JSON lors de l'entrée de la configuration de l'IPX (virgule manquante) +// 2.7b : modification de la gestion de la luminosité des LEDs : gestion individuelle des 3 valeurs R, V et B et comptage pour 256 tours de boucle au lieu de 1000 (et modifications relatives) +// +//-------------------------------------------------------// +// LISTE DES COMMANDES VALIDES POUR LE PILOTAGE VIA HTTP // +//-------------------------------------------------------// +// CF : Mode 0 fait passer le Fil Pilote en mode Confort (pas de tension) +// EC : Mode 1 fait passer le Fil Pilote en mode Eco (alternance complète) +// HG : Mode 2 fait passer le Fil Pilote en mode Hors-Gel (1/2 alternance négative) +// AR : Mode 3 fait passer le Fil Pilote en mode Arrêt ou Délestage (1/2 alternance positive) +// VE : retourne la version du Firmware et celle du fichier source +// LUM : suivi de '?' et un ou trois paramètres sous la forme +// - soit RVB=Valeur : luminosité identique pour les 3 LEDs, valeur entre 0 (extinction complète) et 255 (luminosité max) +// - soit R=Valeur_R&V=Valeur_V&B=Valeur_B : luminosité différenciée pour les 3 LEDs, valeurs entre 0 (extinction complète) et 255 (luminosité max) +// NM : suivi de '=' et un mot, renomme le dispositif (éviter les espaces qui seront traduits en %22) +// TH : suivi de '=' et ON pour activer le capteur DHT s'il est présent, OFF (ou n'importe quoi d'autre) pour le désactiver +// A partir de v.2.0 +// EP : suivi de '=' et DUMP pour voir le contenu de l'EEPROM en Hexa et en ASCII, WIPE pour vider l'EEPROM (mise à 0 de toutes les cellules) +// IPX_CFG : suivi de '?' et une suite de paramètres sous la forme IP_IPX=AddrIP_IPX&APIKey=apikey, si APIKey est omis elle est fixée à "" et l'APIkey n'est pas utilisée +// CTRL_TH : suivi de '?' et une suite de paramètres sous la forme PERIOD=Périodicité_en_Minutes&T_MINI=Val_T°_mini&T_MAXI=Val_T°_maxi&SV_TE=Num_SV_T°&H_MINI=Val_Hum_mini&H_MAXI=Val_Hum_maxi&SV_HU=Num_SV_Hum +// - Si PERIOD=0, inhibe la surveillance et les autres paramètres peuvent être omis (ne sont pas évalués) +// - PERIOD doit être inférieure à 1440 soit une journée +// - T_MINI doit être compris entre 0 et 20 +// - T_MAXI doit être compris entre 0 et 30 +// - T_MINI doit être inférieur à T_MAXI +// - H_MINI doit être compris entre 0 et 99 +// - H_MAXI doit être compris entre 0 et 99 +// - H_MINI doit être inférieur à H_MAXI +// - SV_TE et SV_HU doivent être comprises entre 1 et 128, si 0, n'est pas utilisée, les 2 peuvent être égales pour conduire au même comportement +// RTRN_TH : suivi de '?' et une suite de paramètres sous la forme PERIOD=Périodicité_en_Minutes&VA_TE=Num_VA_T°&VA_HU=Num_VA_Hum +// - Si PERIOD=0, inhibe la surveillance et les autres paramètres peuvent être omis (ne sont pas évalués) +// - PERIOD doit être inférieure à 1440 soit une journée +// - VA_TE et VA_HU doivent être comprises entre 1 et 32, si 0, n'est pas utilisée, les 2 ne peuvent être égales (conflit de remontées) + +// toutes ces commandes retournent une réponse en JSON +// toute autre valeur retournera un message "status" : "error" + +// Constantes + +// Structure du contenu de l'EEPROM +const uint16_t EEPROM_SIZE=512; // Taille utilisée de l'EEPROM +const uint8_t FLAG_DEVICECONFIG=0; // Byte 0 Configuration du device 1er bit = DHT actif ou non +const uint8_t LUM_VALUE=1; // Byte 1, 2 et 3 Valeur des luminosité Rouge, Verte et Bleue (3 x uint8_t : 3 x 1 octet) +const uint8_t PERIODE_CTRL=4; // Byte 4 et 5 Valeur de la périodicité de contrôle de franchissement des seuils de T° et d'Humidité +const uint8_t T_MIN=6; // Byte 6 Valeur de T° mini +const uint8_t T_MAX=7; // Byte 7 Valeur de T° maxi +const uint8_t SV_TE=8; // Byte 8 N° de la Sortie Virtuelle de l'IPX recevant les changements d'état liés à la T° (1 si T° < seuil mini, 0 si T° > seuil maxi) +const uint8_t H_MIN=9; // Byte 9 Valeur d'humidité mini +const uint8_t H_MAX=10; // Byte 10 Valeur d'humidité maxi +const uint8_t SV_HU=11; // Byte 11 N° de la Sortie Virtuelle de l'IPX recevant les changements d'état liés à l'humidité (0 si Humi < seuil mini, 1 si Humi > seuil maxi) +const uint8_t PERIODE_RTRN=12; // Byte 12 et 13 Valeur de la périodicité de mesure de la T° et de l'Humidité +const uint8_t VA_TE=14; // Byte 14 N° de l'Entrée Virtuelle Analogique de l'IPX recevant la température (doit être différente de VA_HU) +const uint8_t VA_HU=15; // Byte 15 N° de l'Entrée Virtuelle Analogique de l'IPX recevant la température (doit être différente de VA_TE) + +// Les bytes 16 à 31 sont réservés pour un usage ultérieur +const uint8_t POS_DEVICECONFIG=32; // Byte 32 et au-delà : Structure de donnée mémorisant la configuration du device (Nom, Infos IPX...) + +// Nom du fichier contenant le statut du radiateur +const String STATUS_FILENAME = "/status.dat"; + +// Commandes de configuration du boitier +// Nb max de paramètres dans les commandes +const uint8_t MAX_PARAMS = 10; // Nb de paramètres maximum pour dimensionnement du tableau (7 utilisés pour CTRL_TH) + +// Statut par défaut du fil pilote à l'initialisation de la connexion au WiFi (0 = Confort, 1 = Eco, 2 = Hors Gel, 3 = Arrêt) +const uint8_t DEFAULT_PILOTE_STATUS = 1; // Mode par défaut Eco de manière à ce que la LED passe en orange après entrée d'une configuration valide + // Cycle à l'initialisation du dispositif : + // Clignotement lent rouge : attend qu'une station se connecte sur l'adresse IP par défaut + // Clignotement lent vert : une station est connecté sur l'AP, attend une commande de configuration + // Vert fixe : commande reçue en cours d'interprétation (la LED bleue clignote pour indiquer les tentatives d'accès au WiFi, 20 clignotements rapides = réseau rejoint) + // Rouge de courte durée : le réseau n'a pu être rejoint, réinitialisation du dispositif + // Orange (et bleue) fixe : l'appareil est connecté au réseau WiFi et peut être piloté + +// Constantes d'affectation des broches aux fonctions (à modifier en cas de changement d'utilisation des broches) +const uint16_t ALT_POS = D13; // Broche pilotant l'alternance positive +const uint16_t ALT_NEG = D14; // Broche pilotant l'alternance négative +const uint16_t LED_LINK = D4; // Broche pilotant la LED Link WiFi bleue +const uint16_t MODE_BUTTON = D6; // Broche recevant le bouton de changement de mode +const uint16_t HARD_RESET = D5; // Broche recevant le bouton de Hard Reset +const uint16_t SENSOR_PIN = D3; // Broche sur laquelle le capteur de T° et d'humidité est branché (s'il est présent) + +// Autres constantes de base +const uint16_t LUMIN_BASE = 10; // Valeur entre 0 et 1000 spécifiant le ‰ de temps que les LEDs doivent être allumées par boucle loop (si 0 : complètement éteintes, mode discret) +const uint16_t NB_TENTATIVES = 20; // Nombre de tentatives de lecture de la T° et de l'hygrométrie +const String SEPARATEUR= "------------------------------------------------------------------------------------------------------------"; + +// Paramètres du périphérique +// MAC adress *** ATTENTION la WiFi.softAPmacAddress() n'a pas la même valeur que celle de connexion au réseau *** +const String MACaddress = WiFi.macAddress(); + +// Nom du réseau Ad Hoc de configuration : Pilote_ les 6 derniers octets de l'adresse Mac de la carte en hexa +const String AP_Network = "Pilote_" + MACaddress.substring(9, 11) + MACaddress.substring(12, 14) + MACaddress.substring(15, 17); + +// Port d'écoute du serveur Web +WiFiServer server(80); + +// Variables de gestion du WiFi et des échanges HTTP +boolean WiFiConnected; // Booléen mémorisant l'état de la conexion WiFi +char ssid[32] = ""; // SSID +char pass[64] = ""; // Mot de passe d'accès au WiFi +char devname[16] = ""; // Nom donné au device +char ip_ipx[16] = ""; // Adresse Ip de l'IPX +char api_key[16] = ""; // APIkey de l'IPX +String ClientRequest; +String RequeteAnalyse; +String Method; +String Command; +ParVal Params[MAX_PARAMS]; +boolean CdeOK; +uint16_t DeviceConfig = 0; // 1er bit : Flag pour indiquer la présence ou non du capteur DHT +Conf_WHP ConfWHP; // Infos de config du boitier en EEPROM +File Status_File; +String Nom_Dispositif; +String Commande; +short Num_Cde, Num_Cde_Recue; +String HttpResponse; + +// Variables pour le capteur DHT +boolean Capteur_DHT; // Vrai si actif (suppose qu'il soit présent) +float Temp; +float Humi; +uint16_t Num_Tentative; // Numéro de la tentative de lecture + +// Variables pour la gestion de la luminosité des LEDs +uint8_t Luminosite_R = 4; // Valeur de luminosité rouge en cours +uint8_t Luminosite_V = 4; // Valeur de luminosité verte en cours +uint8_t Luminosite_B = 4; // Valeur de luminosité bleue en cours +uint16_t Val_Lum_Dde; // Valeur de luminosité demandée +uint16_t Cpt_Lum; // Compteur de boucle pour la luminosité des LEDs + +// Variables intermédiaires +char Char_Tab[16]; // Tableau de char pour conversion String to int + +// Variables pour la connexion à un IPX800 +String IP_IPX = ""; +String API_Key = ""; +String IPX_API_Prefix = ""; +String IPX_API_Command = ""; + +// Variables pour la surveillance de la T° et de l'Humidité +int T_min = 10; +int T_Max = 12; +int H_min = 70; +int H_Max = 80; +int SV_Te = 0; +int SV_Hu = 0; +short Periode_Ctrl = 0; + +// Variables pour la remontée périodique de la T° et de l'Humidité +int VA_Te = 0; +int VA_Hu = 0; +short Periode_Rtrn = 0; +short CAN_Te, CAN_Hu; // Variable recevant la conversion de la T° et de l'humidité mesurée sur 16 bits + // CAN_Te = (Temp+40)/120*65535 + // CAN_Hu = Humi/100*65535 + +// Variables recevant les nouvelles valeurs demandées +int New_T_min, New_T_Max, New_H_min, New_H_Max, New_SV_Te, New_SV_Hu, New_VA_Te, New_VA_Hu; +int New_Periode_Ctrl, New_Periode_Rtrn; // Plantage du module lors du sscanf dans le case CTRL_TH si short au lieu de int !!! + +// Fonction de requête HTTP GET +String GetWithAnswer(String IP_Addr, String Commande) { + String httpurl; + String ClientAnswer; + HTTPClient http; + httpurl = "http://"; + httpurl += IP_Addr; +// httpurl+="/"; supprimé car le massage contient un "/" en début de chaîne + httpurl += Commande; + Serial.println(httpurl); + http.begin(httpurl); + http.GET(); + ClientAnswer = (http.getString()); + http.end(); + return ClientAnswer; +} + +// Cette fonction extrait les caractères d'une chaine jusqu'à un symbole de séparation spécifié, une espace ou la fin de la chaine +String ExtractStr(String Entree, char Separateur) { + uint16_t i = 0; + boolean finChaine = false; + String Sortie = ""; + while (Entree[i] != Separateur && Entree[i] != ' ' && i != Entree.length()) { + Sortie += Entree[i]; + i++; + } + return Sortie; +} + +// Cette fonction extrait les couples (Paramètre, Valeur) présents dans une chaîne de la forme param1=value1¶m2&value2&... à concurrence de NB_PARAMS +//void ExtractParamsValues (String Requete, ParVal ParsVals[NB_PARAMS]) { +void ExtractParamsValues (String Requete, ParVal ParsVals[], uint8_t Nb_Params) { + uint8_t i; + // Initialisation à vide du tableau des paramètres + for (i = 0; i < Nb_Params ; i++) { + ParsVals[i].param = ""; + ParsVals[i].value = ""; + } + // Extrait les paramètres et les valeurs séparés par = et & + String ChAnalyse = Requete; + i = 0; + while ((ChAnalyse != "") && (i < Nb_Params)) { + ParsVals[i].param = ExtractStr (ChAnalyse, '='); + ChAnalyse = ChAnalyse.substring(ParsVals[i].param.length() + 1); + ParsVals[i].value = ExtractStr (ChAnalyse, '&'); + ChAnalyse = ChAnalyse.substring(ParsVals[i].value.length() + 1); + // Suppression de la " éventuelle en début de chaîne + if (ParsVals[i].value[0] == '"') { + ParsVals[i].value = ParsVals[i].value.substring(1); + } + // Suppression du %22 (") éventuel en début de chaîne + if (ParsVals[i].value.substring(0, 3) == "%22") { + ParsVals[i].value = ParsVals[i].value.substring(3); + } + // Suppression de la " éventuelle en fin de chaîne + if (ParsVals[i].value[ParsVals[i].value.length() - 1] == '"') { + ParsVals[i].value = ParsVals[i].value.substring(0, ParsVals[i].value.length() - 1); + } + // Suppression du %22 (") éventuel en fin de chaîne + if (ParsVals[i].value.substring(ParsVals[i].value.length() - 3) == "%22") { + ParsVals[i].value = ParsVals[i].value.substring(0, ParsVals[i].value.length() - 3); + } + i++; + } +} + +// Cette fonction teste l'appui sur le bouton Hard Reset +void Test_Hard_Reset () { + if (digitalRead(HARD_RESET) == 0) { + Serial.println(); + Serial.println(F("**************************************")); + Serial.println(F("* HARD RESET, REINITIALISATION EN AP *")); + Serial.println(F("**************************************")); + // Lors d'un Hard Reset, déconnecte d'un éventuel réseau WiFi et efface un SSID précédemment mémorisé + Serial.print(F("Lecture du SSID mémorisé : ")); + Serial.println(WiFi.SSID()); + WiFi.disconnect(); + // Réinitialisation des paramètres + DeviceConfig = 0; // Pas de capteur DHT + Luminosite_R = 4; // Luminosité rouge à 4/256 + Luminosite_V = 4; // Luminosité verte à 4/256 + Luminosite_B = 4; // Luminosité bleue à 4/256 + Nom_Dispositif = AP_Network; // Nom du dispositif au nom par défaut du boitier + Nom_Dispositif.toCharArray(devname, 16); + for (uint8_t i = 0; i < 16 ; i++) ConfWHP.devname[i] = devname[i]; + IP_IPX = ""; // Mise à "" de l'adresse IP de l'IPX + IP_IPX.toCharArray(ip_ipx, 16); + for (uint8_t i = 0; i < 16 ; i++) ConfWHP.ip_ipx[i] = ip_ipx[i]; + API_Key = ""; // Mise à "" de l'APIkey de l'IPX + API_Key.toCharArray(api_key, 16); + for (uint8_t i = 0; i < 16 ; i++) ConfWHP.api_key[i] = api_key[i]; + Periode_Ctrl = 0; // Mise à 0 de la périodicité de contrôle du climat (la désactive) + Periode_Rtrn = 0; // Mise à 0 de la périodicité de remontée du climat (la désactive) + + // Mémorisation dans l'EEPROM + EEPROM.write(FLAG_DEVICECONFIG,DeviceConfig); + EEPROM.write(LUM_VALUE,Luminosite_R); + EEPROM.write(LUM_VALUE+1,Luminosite_V); + EEPROM.write(LUM_VALUE+2,Luminosite_B); + EEPROM.put(POS_DEVICECONFIG,ConfWHP); + EEPROM.put(PERIODE_CTRL, Periode_Ctrl); + EEPROM.put(PERIODE_RTRN, Periode_Rtrn); + EEPROM.end(); + + delay(1000); // délai pour la mémorisation de l'effacement + Serial.print(F("Lecture du SSID après réinitialisation : ")); + Serial.println(WiFi.SSID()); + // Fait rebooter l'équipement + ESP.restart(); + } +} + +// Cette fonction fait clignoter la led additionnelle sur la GPIO 2 (broche D4) et la led intégrée à l'inverse l'une de l'autre. +// La durée d'allumage et d'extinction est donnée par delai en 1000ème de seconde +void ClignoteLED(uint8_t nbre, long delai) { + uint8_t i; + nbre = abs (nbre); + for (i = 1; i <= nbre ; i++) { + digitalWrite(LED_LINK, LOW); + delay(delai); + digitalWrite(LED_LINK, HIGH); + delay(delai); + } +} + +// ************************************************************** +// ******** Fonctions propres au pilotage d'un radiateur ******** +// ************************************************************** + +// Cette fonction donne le n° correspondant à l'état demandé : 0=Confort, 1=Eco, 2=Hors-Gel, 3=Arrêt +// ou à la commande passée : 4=Status, 5=Version de Firmware, 6=Réglage de la luminosité, 7=Renommage du device, +// 8=activation/désactivation DHT, 9=Dump/Wipe, 10=Configuration IPX, 11=Paramètres de surveillance de la T° et de l'hygrométrie +int NumEtat(String Etat) { + int Val = -1; + if (Etat.equalsIgnoreCase("CF")) {Val=0;} + if (Etat.equalsIgnoreCase("EC")) {Val=1;} + if (Etat.equalsIgnoreCase("HG")) {Val=2;} + if (Etat.equalsIgnoreCase("AR")) {Val=3;} + if (Etat.equalsIgnoreCase("?")) {Val=4;} + if (Etat.equalsIgnoreCase("VE")) {Val=5;} + if (Etat.substring(0, 4).equalsIgnoreCase("LUM?")) {Val=6;} + if (Etat.substring(0, 3).equalsIgnoreCase("NM=")) {Val=7;} + if (Etat.substring(0, 3).equalsIgnoreCase("TH=")) {Val=8;} +// if (Etat.substring(0, 3).equalsIgnoreCase("EP=")) {Val=9;} + if (Etat.substring(0, 8).equalsIgnoreCase("IPX_CFG?")) {Val=10;} + if (Etat.substring(0, 8).equalsIgnoreCase("CTRL_TH?")) {Val=11;} + if (Etat.substring(0, 8).equalsIgnoreCase("RTRN_TH?")) {Val=12;} + + return Val; +} + +// Cette fonction pilote le changement d'état des sorties, de la LED bicolore d'état et le mémorise dans le fichier d'état +void Pilote (int Status) { + switch (Status) { + case 0 : // aucune alternance en sortie, LED allumée en rouge : Confort + digitalWrite(ALT_NEG,LOW); + digitalWrite(ALT_POS,LOW); + break; + case 1 : // pleine alternance en sortie, LED allumée en orange (rouge+vert) : Eco + digitalWrite(ALT_NEG,HIGH); + digitalWrite(ALT_POS,HIGH); + break; + case 2 : // demie alternance négative en sortie, LED allumée en vert : Hors Gel + digitalWrite(ALT_NEG,HIGH); + digitalWrite(ALT_POS,LOW); + break; + case 3 : // demie alternance positive en sortie, LED éteinte : Arrêt + digitalWrite(ALT_NEG,LOW); + digitalWrite(ALT_POS,HIGH); + break; + } + // Mémorisation dans le fichier + Serial.print(F("Status écrit : ")); + Serial.println(Status); + Status_File = SPIFFS.open(STATUS_FILENAME, "w+"); + Status_File.write(Status); + Status_File.seek(0, SeekSet); + Serial.print(F("Status relu : ")); + Serial.println(Status_File.read()); + Status_File.close(); +} + +//******************************************************************************************************************// +// Setup // +//******************************************************************************************************************// + +void setup() +{ + // Affiche les communications sur le port série + Serial.begin(921600); + + Serial.println(); + Serial.println(F("Initialisation du système")); + + // Initialisation du WiFiManager + WiFiManager wifiManager; + + // Extraction du nom du fichier source en tant que version de Firmware + //reverse(FilePath.begin(),FilePath.end()); + FileName = ExtractStr(FilePath.substring(4),'/'); + //reverse(FileName.begin(),FileName.end()); + + // Déclaration des broches utilisées + // LED Link + pinMode(LED_LINK, OUTPUT); + // Alternance - + pinMode(ALT_NEG, OUTPUT); + // Alternance + + pinMode(ALT_POS, OUTPUT); + // Bouton de changement de mode + pinMode(MODE_BUTTON, INPUT); + // Bouton Hard Reset + pinMode(HARD_RESET, INPUT); + + // Charge l'EEPROM en RAM et lit le premier octet pour savoir si un capteur DHT est déclaré présent + EEPROM.begin(EEPROM_SIZE); + + + // lit le premier octet pour savoir si un capteur DHT est déclaré présent + DeviceConfig = EEPROM.read(FLAG_DEVICECONFIG); + Serial.print(F("Lecture du DeviceConfig dans l'EEPROM : ")); + Serial.println(DeviceConfig); + + // Lecture du SSID mémorisé par l'ESP8266 + Serial.print(F("Lecture du SSID mémorisé : ")); + Serial.println(WiFi.SSID()); + + //Démarrage du File System + SPIFFS.begin(); + Serial.println(F("Démarrage du File System")); + Serial.println(SEPARATEUR); + + // Initialisation de la connexion si pas de SSID connu + if (WiFi.SSID() == "") { + AP_Network.toCharArray(Char_Tab,14); + wifiManager.autoConnect(Char_Tab); + } + + //------------------------------------------------------------------------// + // Une configuration valide a été entrée et l'accès au WiFi a été vérifié // + //------------------------------------------------------------------------// + + // Récupère les infos de luminosité et de configuration du boitier + Luminosite_R = EEPROM.read(LUM_VALUE); + Luminosite_V = EEPROM.read(LUM_VALUE+1); + Luminosite_B = EEPROM.read(LUM_VALUE+2); + EEPROM.get(POS_DEVICECONFIG, ConfWHP); + Serial.print(F("Device Config : ")); + Serial.println(EEPROM.read(FLAG_DEVICECONFIG)); + // Initialisation dans le mode précédemment mémorisé + // Vérification de l'existence du fichier status.dat + if (!SPIFFS.exists(STATUS_FILENAME)) { + // Le crée et l'initialise avec la valeur par défaut s'il n'existe pas + Status_File = SPIFFS.open(STATUS_FILENAME, "w"); + Status_File.write(DEFAULT_PILOTE_STATUS); + Status_File.close(); + } + Status_File = SPIFFS.open(STATUS_FILENAME, "r"); + Num_Cde = Status_File.read(); + Status_File.close(); + Serial.print(F("Statut du radiateur (valeur lue dans le fichier Status) : ")); + Serial.println(Num_Cde); + + // Bascule du radiateur dans son état antérieur + Pilote(Num_Cde); + + // Nomme le périphérique + Nom_Dispositif = ConfWHP.devname; + if (Nom_Dispositif != "") WiFi.hostname(Nom_Dispositif); + Nom_Dispositif = WiFi.hostname(); + + // Connecte au WiFi + Serial.print(F("Recherche réseau WiFi : ")); + Serial.println(WiFi.SSID()); + WiFi.begin(); + while ((!(WiFi.status() == WL_CONNECTED))) { + delay(300); + Serial.print(F(".")); + ClignoteLED(2, 30); + digitalWrite(LED_LINK, LOW); + Test_Hard_Reset(); + } + WiFiConnected = true; // Passe le booléen à vrai + WiFi.setAutoReconnect(true); // Permet la reconnexion automatique en cas de perte du réseau + Serial.println(); + Serial.print(F("Connecté au WIFI : ")); + Serial.println(WiFi.SSID()); + Serial.print(F("Puissance du signal ")); + Serial.print(WiFi.RSSI()); + Serial.println(F(" dBm")); + ClignoteLED(5, 250); + + // Démarrage du Serveur Web + server.begin(); + + // Affichage des informations sur le port série + Serial.println(F("Serveur Web démarré")); + Serial.print(F("L\'adresse IP du dispositif est ")); + Serial.println((WiFi.localIP().toString())); + Serial.print(F("Le nom du dispositif est ")); + Serial.println((WiFi.hostname())); + Serial.print(F("La version du firmware est ")); + Serial.println(FIRMWARE_VERSION); + Serial.print(F("Le fichier source est ")); + Serial.println(FileName); + Serial.print(F("La luminosité RVB est ")); + Serial.print(Luminosite_R); + Serial.print(F(", ")); + Serial.print(Luminosite_V); + Serial.print(F(", ")); + Serial.println(Luminosite_B); + + //Serial.print(F("Le capteur DHT est à ")); + //Serial.println(Capteur_DHT); + + // Charge et affiche les paramètres de surveillance de la T° et de l'humidité + IP_IPX = ConfWHP.ip_ipx; + if (IP_IPX != "") { + Serial.print(F("IP IPX800 : ")); + Serial.println(IP_IPX); + IPX_API_Prefix = "/api/xdevices.json?"; + if (ConfWHP.api_key != "") { + IPX_API_Prefix = IPX_API_Prefix+"key="+ConfWHP.api_key+"&"; + } + Serial.print(F("Préfixe des requêtes HTTP pour IPX : http://")); + Serial.println(IP_IPX+IPX_API_Prefix); + } + T_min = EEPROM.read(T_MIN); + T_Max = EEPROM.read(T_MAX); + H_min = EEPROM.read(H_MIN); + H_Max = EEPROM.read(H_MAX); + SV_Te = EEPROM.read(SV_TE); + SV_Hu = EEPROM.read(SV_HU); + Periode_Ctrl = EEPROM.read(PERIODE_CTRL); + if (Periode_Ctrl == 0) { + Serial.println(F("Surveillance T° et Hygrométrie désactivée, périodicité à 0")); + } else { + Serial.println(F("Surveillance T° et Hygrométrie activée")); + Serial.print(F("Périodicité : ")); + Serial.print(Periode_Ctrl); + Serial.println(F(" minutes")); + Serial.print(F("T° mini : ")); + Serial.print(T_min); + Serial.println(F(" °C")); + Serial.print(F("T° Maxi : ")); + Serial.print(T_Max); + Serial.println(F(" °C")); + Serial.print(F("La sortie virtuelle ")); + Serial.print(SV_Te); + Serial.println(F(" est sélectionnée pour la surveillance de la T°")); + Serial.print(F("Hygro mini : ")); + Serial.print(H_min); + Serial.println(F(" %")); + Serial.print(F("Hygro Maxi : ")); + Serial.print(H_Max); + Serial.println(F(" %")); + Serial.print(F("La sortie virtuelle ")); + Serial.print(SV_Hu); + Serial.println(F(" est sélectionnée pour la surveillance de l'hygrométrie")); + } + VA_Te = EEPROM.read(VA_TE); + VA_Hu = EEPROM.read(VA_HU); + Periode_Rtrn = EEPROM.read(PERIODE_RTRN); + if (Periode_Ctrl == 0) { + Serial.println(F("Remontée T° et Hygrométrie désactivée, périodicité à 0")); + } else { + Serial.println(F("Remontée T° et Hygrométrie activée")); + Serial.print(F("Périodicité : ")); + Serial.print(Periode_Rtrn); + Serial.println(F(" minutes")); + Serial.print(F("L'entrée analogique virtuelle ")); + Serial.print(VA_Te); + Serial.println(F(" est sélectionnée pour la remontée de la T°")); + Serial.print(F("L'entrée analogique virtuelle ")); + Serial.print(VA_Hu); + Serial.println(F(" est sélectionnée pour la remontée de l'hygrométrie")); + } + Serial.println(F("Pilote correctement démarré, en attente de commande...")); + Serial.println(SEPARATEUR); +} + +//******************************************************************************************************************// +//******************************************************************************************************************// +// Loop // +//******************************************************************************************************************// +//******************************************************************************************************************// + +void loop() +{ + // Test du bouton Hard Reset + Test_Hard_Reset(); + + // Test du bouton de changement d'état + if (digitalRead(MODE_BUTTON) == 0) { + delay(10);// Attend 10 ms que l'état du bouton soit stabilisé (pour éviter les commandes multiples en cas de rebond du poussoir) + Serial.println(); + Serial.print(F("Passage d'état ")); + Serial.print(Num_Cde); + Num_Cde = (Num_Cde+1)%4; + Serial.print(F(" à ")); + Serial.println(Num_Cde); + Pilote(Num_Cde); + while (digitalRead(MODE_BUTTON) == 0) { // Attend que le bouton soit relâché + delay(1); + } + delay(10);// Attend 10 ms que l'état du bouton soit stabilisé (pour éviter les commandes multiples en cas de rebond du poussoir lors de sa réouverture) + } + + // Teste une fois toutes les 10 secondes si le WiFi est bien connecté + if ((millis()%10000) == 0) { + WiFiConnected = WiFi.isConnected(); + } + + // Clignotement des LEDs pour réduire l'intensité + if ((Luminosite_R+Luminosite_V+Luminosite_B) != 0) { // Vérifie si toutes les luminosités ne sont pas à 0 + Cpt_Lum=(Cpt_Lum+1)%256; + // Luminosité de la LED Link (bleue) + if (Luminosite_B != 0) { + if (((Cpt_Lum%(256/Luminosite_B)) == 0) && (WiFiConnected)) { + digitalWrite(LED_LINK,HIGH); // Allume la LED Link uniquement si le WiFi est connecté + } else { + digitalWrite(LED_LINK,LOW); + } + } else { + digitalWrite(LED_LINK,LOW); + } + + // Attente d'une connexion sur le serveur Web + WiFiClient client = server.available(); + if (!client) { return; } + Serial.println(F("Client connecté sur le serveur web embarqué")); + Serial.print(F("Adresse IP du client :")); + Serial.println((client.remoteIP())); + ClientRequest = (client.readStringUntil('\r')); + Serial.println(ClientRequest); + Commande=ClientRequest.substring(5,ClientRequest.length()-9); + Serial.print("Commande \""+Commande+"\" : "); + Num_Cde_Recue=NumEtat(Commande); + Serial.println(Num_Cde_Recue); + if (Num_Cde_Recue >= 4) { + Serial.print(F("Status : ")); + Serial.println(Num_Cde); + } + client.println(F("HTTP/1.1 200 OK")); + client.println(F("Cache-Control: no-cache")); + client.println(F("Connection: close")); + client.println(F("Content-Type: application/json")); + client.println(F("access-control-allow-origin: *")); + client.println(F("")); + client.println(F("{")); + client.println(" \"device\" : \""+Nom_Dispositif+"\","); + + if (Num_Cde_Recue == -1) { + ClignoteLED(5, 60); + client.println(F(" \"status\" : \"error\"")); + } else { + ClignoteLED(1, 60); + client.println(" \"command\" : \""+Commande+"\","); + switch (Num_Cde_Recue) { + case 4 : // Lecture de l'état + client.print(F(" \"status\" : \"")); + client.print((digitalRead(ALT_POS)*3+(digitalRead(ALT_NEG)*2))%4); + client.println(F("\",")); + client.print(F(" \"IP_IPX\" : \"")); + client.print(IP_IPX); + client.println(F("\",")); + client.print(F(" \"IPX_API_Prefix\" : \"")); + client.print(IPX_API_Prefix); + client.println(F("\",")); + client.println(F(" \"th_control\" : {")); + if (Periode_Ctrl == 0) { + client.println(F(" \"status\" : \"Off\"")); + } else { + client.println(F(" \"status\" : \"On\",")); + client.print(F(" \"periodicity\" : \"")); + client.print(Periode_Ctrl); + client.println(F("\",")); + client.print(F(" \"t_min\" : \"")); + client.print(T_min); + client.println(F("\",")); + client.print(F(" \"t_max\" : \"")); + client.print(T_Max); + client.println(F("\",")); + client.print(F(" \"sv_te\" : \"")); + client.print(SV_Te); + client.println(F("\",")); + client.print(F(" \"h_min\" : \"")); + client.print(H_min); + client.println(F("\",")); + client.print(F(" \"h_max\" : \"")); + client.print(H_Max); + client.println(F("\",")); + client.print(F(" \"sv_hu\" : \"")); + client.print(SV_Hu); + client.println(F("\"")); + } + client.println(F(" },")); + client.println(F(" \"th_return\" : {")); + if (Periode_Rtrn == 0) { + client.println(F(" \"status\" : \"Off\"")); + } else { + client.println(F(" \"status\" : \"On\",")); + client.print(F(" \"periodicity\" : \"")); + client.print(Periode_Rtrn); + client.println(F("\",")); + client.print(F(" \"va_te\" : \"")); + client.print(VA_Te); + client.println(F("\",")); + client.print(F(" \"va_hu\" : \"")); + client.print(VA_Hu); + client.println(F("\"")); + } + client.println(F(" },")); + client.print(F(" \"blue_luminosity\" : \"")); + client.print(Luminosite_B); + client.println(F("\",")); + client.print(F(" \"red_luminosity\" : \"")); + client.print(Luminosite_R); + client.println(F("\",")); + client.print(F(" \"green_luminosity\" : \"")); + client.print(Luminosite_V); + client.println(F("\",")); + client.print(F(" \"RSSI\" : \"")); + client.print(WiFi.RSSI()); + break; + case 5 : // Lecture de la version du firmware + client.println(" \"version\" : \""+FIRMWARE_VERSION+"\","); + client.print(" \"source\" : \""+FileName); + break; + case 6 : // Changement de luminosité des LEDs + ExtractParamsValues(Commande.substring(4), Params, 3); // Extrait les 3 paramètres attendus : RVB ou R, V et B + CdeOK = false; + if (Params[0].param.equalsIgnoreCase("RVB")) { // Le paramètre est RVB : on fixe la même valeur pour les 3 luminosités + Params[0].value.toCharArray(Char_Tab,16); + int Value; + sscanf(Char_Tab, "%i", &Value); + Val_Lum_Dde=Value; + Serial.print(F("Luminosité RVB demandée : ")); + Serial.println(Val_Lum_Dde); + if (Val_Lum_Dde <= 255) { + Luminosite_R = Val_Lum_Dde; + Luminosite_V = Val_Lum_Dde; + Luminosite_B = Val_Lum_Dde; + } else { + Luminosite_R = 255; + Luminosite_V = 255; + Luminosite_B = 255; + } + CdeOK=true; + } + + if ((Params[0].param.equalsIgnoreCase("R")) && (Params[1].param.equalsIgnoreCase("V")) && (Params[2].param.equalsIgnoreCase("B"))) { // Les 3 paramètres sont R, V et B (dans l'ordre) : on fixe des valeurs différenciées pour les 3 luminosités + // Valeur de luminosité rouge + Params[0].value.toCharArray(Char_Tab,16); + int Value; + sscanf(Char_Tab, "%i", &Value); + Val_Lum_Dde=Value; + Serial.print(F("Luminosité R demandée : ")); + Serial.println(Val_Lum_Dde); + if (Val_Lum_Dde <= 255) { + Luminosite_R = Val_Lum_Dde; + } else { + Luminosite_R = 255; + } + // Valeur de luminosité verte + Params[1].value.toCharArray(Char_Tab,16); + sscanf(Char_Tab, "%i", &Value); + Val_Lum_Dde=Value; + Serial.print(F("Luminosité V demandée : ")); + Serial.println(Val_Lum_Dde); + if (Val_Lum_Dde <= 255) { + Luminosite_V = Val_Lum_Dde; + } else { + Luminosite_V = 255; + } + // Valeur de luminosité bleue + Params[2].value.toCharArray(Char_Tab,16); + sscanf(Char_Tab, "%i", &Value); + Val_Lum_Dde=Value; + Serial.print(F("Luminosité B demandée : ")); + Serial.println(Val_Lum_Dde); + if (Val_Lum_Dde <= 255) { + Luminosite_B = Val_Lum_Dde; + } else { + Luminosite_B = 255; + } + CdeOK=true; + } + + // Indique le résultat + if (CdeOK) { + // Mémorisation des luminosités dans l'EEPROM + EEPROM.write(LUM_VALUE,Luminosite_R); + EEPROM.write(LUM_VALUE+1,Luminosite_V); + EEPROM.write(LUM_VALUE+2,Luminosite_B); + EEPROM.commit(); + client.print(F(" \"blue_luminosity\" : \"")); + client.print(Luminosite_B); + client.println(F("\",")); + client.print(F(" \"red_luminosity\" : \"")); + client.print(Luminosite_R); + client.println(F("\",")); + client.print(F(" \"green_luminosity\" : \"")); + client.print(Luminosite_V); + } else { + client.print(F(" \"status\" : \"LUMI Command error, incorrect or missing parameter(s)")); + } + break; + case 7 : // Renommage du device + Nom_Dispositif = Commande.substring(3,18); + //---------------------------------------------------- + Nom_Dispositif.toCharArray(devname, 16); + for (uint8_t i = 0; i < 16 ; i++) ConfWHP.devname[i] = devname[i]; + // Mémorisation du nouveau nom dans l'EEPROM + EEPROM.put(POS_DEVICECONFIG,ConfWHP); + EEPROM.commit(); + Serial.print(F("Nouveau nom du device : ")); + Serial.println(Nom_Dispositif); + client.print(F(" \"newname\" : \"")); + client.print(Nom_Dispositif); + break; + Serial.print(F("Config écrite : ")); + Serial.println(DeviceConfig); + EEPROM.write(FLAG_DEVICECONFIG,DeviceConfig); + Serial.print(F("Config relue : ")); + Serial.println(EEPROM.read(FLAG_DEVICECONFIG)); + EEPROM.commit(); + break; + case 9 : // Dump, Wipe (remplissage avec des 0) ou Fill (remplissage avec des 0xFF) de l'EEPROM + if (Commande.substring(3).equalsIgnoreCase("WIPE")) { + Serial.println(F("Wipe de l'EEPROM")); + for (uint16_t x = 0; x < EEPROM_SIZE; x++) { //Loop end of EEPROM address + if (!(EEPROM.read(x) == 0)) { + EEPROM.write(x, 0); // if not 0 write 0 to clear, it takes 3.3mS ? + } + yield(); + } + EEPROM.commit(); + Serial.println(F("Wipe terminé, vérification...")); + Commande="EP=DUMP"; // Force un Dump de l'EEPROM après Wipe pour contrôle + } + if (Commande.substring(3).equalsIgnoreCase("FILL")) { + Serial.println(F("Fill de l'EEPROM")); + for (uint16_t x = 0; x < EEPROM_SIZE; x++) { //Loop end of EEPROM address + if (!(EEPROM.read(x) == 0xFF)) { + EEPROM.write(x, 0xFF); // if not 0 write 0 to clear, it takes 3.3mS ? + } + yield(); + } + DeviceConfig = 0; // Force le device Config à 0 + EEPROM.write(FLAG_DEVICECONFIG,DeviceConfig); + EEPROM.commit(); + Serial.println(F("Fill terminé, vérification...")); + Commande="EP=DUMP"; // Force un Dump de l'EEPROM après Fill pour contrôle + } + if (Commande.substring(3).equalsIgnoreCase("DUMP")) { + // Dump en Hexa et en ASCII + Serial.println(F("Dump de l'EEPROM")); + for (uint16_t i = 0 ; i < (EEPROM_SIZE/16) ; i++) { + for (uint8_t j = 0; j < 16 ; j++) { + Serial.print(EEPROM.read(i*16+j) < 0x10 ? "0" : ""); + Serial.print(EEPROM.read(i*16+j), HEX); + Serial.print(F(" ")); + } + for (uint8_t j = 0; j < 16 ; j++) { + Serial.print((EEPROM.read(i*16+j) < 0x20) || (EEPROM.read(i*16+j) > 0x7F) ? '^' : char(EEPROM.read(i*16+j))); + } + Serial.println(F("")); + yield(); + } + client.print(F(" \"status\" : \"Successfull")); + break; + } + Serial.println(F("Commande non reconnue")); + client.print(F(" \"status\" : \"EP Command error")); + break; + case 10 : // Paramètres de l'IPX + Serial.println(F("Configuration des paramètres de l'IPX")); + ExtractParamsValues(Commande.substring(8), Params, 2); // Extrait les 2 paramètres attendus : Ip et, éventuellement, APIkey de l'IPX + CdeOK = true; + // Teste si le 1er paramètre est bien IP_IPX + if (!Params[0].param.equalsIgnoreCase("IP_IPX")) { + CdeOK = false; + Serial.println(F("Le premier paramètre n'est pas IP_IPX")); + Serial.println(F("Configuration non modifiée")); + client.print(F(" \"status\" : \"IPX_CFG Command error, 1st param not IP_IPX")); + Params[1].param = ""; // Efface l'éventuel deuxième paramètre pour ne pas rentrer dans le test suivant + } else { + IP_IPX = Params[0].value; + } + // Teste si le 2ème paramètre existe et est bien APIkey + //API_Key = ""; + if (Params[1].param != "") { + if (!Params[1].param.equalsIgnoreCase("APIkey")) { + CdeOK = false; + Serial.println(F("Une second paramètre existe mais n'est pas APIkey")); + Serial.println(F("Configuration non modifiée")); + client.print(F(" \"status\" : \"IPX_CFG Command error, 2nd param present but not APIkey")); + IP_IPX = ConfWHP.ip_ipx; // Restaure l'ancienne valeur de l'IP de l'IPX + } else { + API_Key = Params[1].value; + } + } + IPX_API_Prefix = ""; + if (IP_IPX != "") { + Serial.print(F("IP IPX800 : ")); + Serial.println(IP_IPX); + IPX_API_Prefix = "/api/xdevices.json?"; + if (API_Key != "") { + IPX_API_Prefix = IPX_API_Prefix+"key="+API_Key+"&"; + } + } + Serial.print(F("Préfixe des requêtes HTTP pour IPX : ")); + Serial.println((IPX_API_Prefix=="") ? "Effacée" : "http://"+IP_IPX+IPX_API_Prefix); + // Mémorisation des paramètres dans l'EEPROM + if (CdeOK) { + client.println(F(" \"status\" : \"IPX_CFG Success\",")); + client.print(F(" \"IP_IPX\" : \"")); + client.print(IP_IPX); + client.println(F("\",")); + client.print(F(" \"IPX_API_Prefix\" : \"")); + client.print(IPX_API_Prefix); + IP_IPX.toCharArray(ip_ipx, 16); + for (uint8_t i = 0; i < 16 ; i++) ConfWHP.ip_ipx[i] = ip_ipx[i]; + API_Key.toCharArray(api_key, 16); + for (uint8_t i = 0; i < 16 ; i++) ConfWHP.api_key[i] = api_key[i]; + EEPROM.put(POS_DEVICECONFIG,ConfWHP); + EEPROM.commit(); + } + break; + case 11 : // Paramètres de surveillance du climat + Serial.println(F("Configuration des paramètres pour la surveillance du climat")); + ExtractParamsValues(Commande.substring(8), Params, 7); // Extrait les 7 paramètres attendus : PERIOD, T_MINI, T_MAXI, SV_TE et optionnellement H_MINI, H_MAXI et SV_HU + CdeOK = true; + if (!Params[0].param.equalsIgnoreCase("PERIOD")) { + CdeOK = false; + Serial.println(F("Le premier paramètre n'est pas PERIOD")); + Serial.println(F("Configuration non modifiée")); + client.print(F(" \"status\" : \"CTRL_TH Command error, 1st param not PERIOD")); + } else { + Serial.println(F("Le premier paramètre est bien PERIOD")); + // Duplication des anciennes valeurs + New_T_min = T_min; + New_T_Max = T_Max; + New_H_min = H_min; + New_H_Max = H_Max; + New_SV_Te = SV_Te; + New_SV_Hu = SV_Hu; + New_Periode_Ctrl = Periode_Ctrl; + // Evaluation de la périodicité demandée + Params[0].value.toCharArray(Char_Tab,16); + sscanf(Char_Tab, "%i", &New_Periode_Ctrl); + if (New_Periode_Ctrl >= 1440) { // Périodicité supérieure à 24h refusée + CdeOK = false; + Serial.println(F("La périodicité demandée est supérieure à 24h")); + Serial.println(F("Configuration non modifiée")); + client.print(F(" \"status\" : \"CTRL_TH Command error, PERIOD more than 24h")); + } else { + if (New_Periode_Ctrl == 0) { // La périodicité est fixée à 0, inutile d'évaluer le reste + Serial.println(F("Périodicité à 0, fonction de surveillance désactivée")); + } else { + Serial.println(F("La périodicité est comprise entre 0 et 24h")); + if ((!Params[1].param.equalsIgnoreCase("T_MINI")) || (!Params[2].param.equalsIgnoreCase("T_MAXI")) || (!Params[3].param.equalsIgnoreCase("SV_TE"))) { + CdeOK = false; + Serial.println(F("Un des paramètres pour la T° est faux ou manquant")); + Serial.println(F("Configuration non modifiée")); + client.print(F(" \"status\" : \"CTRL_TH Command error, incorrect or missing Temperature parameter")); + } else { + Serial.println(F("Les paramètres de T° sont bien tous présents")); + // Evaluation des paramètres de T° + Params[1].value.toCharArray(Char_Tab,16); + sscanf(Char_Tab, "%i", &New_T_min); + Serial.print(F("T min = ")); + Serial.println(New_T_min); + Params[2].value.toCharArray(Char_Tab,16); + sscanf(Char_Tab, "%i", &New_T_Max); + Serial.print(F("T Max = ")); + Serial.println(New_T_Max); + if ((New_T_min <0) || (New_T_min > 20) || (New_T_Max <0) || (New_T_Max > 30) || (New_T_min >= New_T_Max)) { + CdeOK = false; + Serial.println(F("Les valeurs de T° min et Max sont incorrectes, vérifiez la consigne demandée")); + Serial.println(F("Configuration non modifiée")); + client.print(F(" \"status\" : \"CTRL_TH Command error, incorrect Temperature value(s)")); + } else { + Serial.println(F("Les températures mini et Maxi sont bien cohérentes")); + Params[3].value.toCharArray(Char_Tab,16); + sscanf(Char_Tab, "%i", &New_SV_Te); + if (New_SV_Te > 128) { + CdeOK = false; + Serial.println(F("La sortie virtuelle demandée pour la température est au delà de 128")); + Serial.println(F("Configuration non modifiée")); + client.print(F(" \"status\" : \"CTRL_TH Command error, incorrect temperature VO value")); + } else { + Serial.print(F("La sortie virtuelle ")); + Serial.print(New_SV_Te); + Serial.println(F(" est sélectionnée pour la surveillance de la T°")); + if ((Params[4].value != "") && (Params[4].param.equalsIgnoreCase("H_MINI")) && (Params[5].param.equalsIgnoreCase("H_MAXI")) && (Params[6].param.equalsIgnoreCase("SV_HU"))) { + // Des paramètres d'hygrométrie sont présents on vérifie leur cohérence + Serial.println(F("Les paramètres d'hygrométrie sont bien tous présents")); + // Evaluation des paramètres d'hygrométrie + Params[4].value.toCharArray(Char_Tab,16); + sscanf(Char_Tab, "%i", &New_H_min); + Serial.print(F("H min = ")); + Serial.println(New_H_min); + Params[5].value.toCharArray(Char_Tab,16); + sscanf(Char_Tab, "%i", &New_H_Max); + Serial.print(F("H Max = ")); + Serial.println(New_H_Max); + if ((New_H_min <0) || (New_H_min > 100) || (New_H_Max <0) || (New_H_Max > 100) || (New_H_min >= New_H_Max)) { + CdeOK = false; + Serial.println(F("Les valeurs d'humidité min et Max sont incorrectes, vérifiez la consigne demandée")); + Serial.println(F("Configuration non modifiée")); + client.print(F(" \"status\" : \"CTRL_TH Command error, incorrect Hygrometry value(s)")); + } else { + Serial.println(F("Les pourcentages d'hygrométrie mini et Maxi sont bien cohérents")); + Params[6].value.toCharArray(Char_Tab,16); + sscanf(Char_Tab, "%i", &New_SV_Hu); + if (New_SV_Hu > 128) { + CdeOK = false; + Serial.println(F("La sortie virtuelle demandée pour l'hygrométrie est au delà de 128")); + Serial.println(F("Configuration non modifiée")); + client.print(F(" \"status\" : \"CTRL_TH Command error, incorrect hygrometry VO value")); + } else { + Serial.print(F("La sortie virtuelle ")); + Serial.print(New_SV_Hu); + Serial.println(F(" est sélectionnée pour la surveillance de l'hygrométrie")); + } // endif Vérification SV Hygrométrie <=128 + } // endif Contrôle des paramètres d'hygrométrie + } else { + // Réinitialisation des paramètres d'hygrométrie + New_H_min = 0; + New_H_Max = 100; + New_SV_Hu = 0; + } // endif présence des paramètres d'hygrométrie + } // endif Vérification SV Température <=128 + } // endif Contrôle des T° demandées + } // endif Contrôle des paramètres de T° + } // endif PERIOD à 0, désactivation + } // endif PERIOD > 24h + } // endif Premier paramètre non égal à PERIOD + // Mémorisation des paramètres dans l'EEPROM + if (CdeOK) { // La commande est Ok + Serial.println(F("Configuration OK")); + // Recopie des nouvelles valeurs demandées dans les variables correspondantes + T_min = New_T_min; + T_Max = New_T_Max; + H_min = New_H_min; + H_Max = New_H_Max; + SV_Te = New_SV_Te; + SV_Hu = New_SV_Hu; + Periode_Ctrl = New_Periode_Ctrl; + // Mémorisation dans l'EEPROM + EEPROM.put(PERIODE_CTRL, Periode_Ctrl); + if (Periode_Ctrl == 0) { + client.print(F(" \"status\" : \"CTRL_TH Successfully deactivated")); + } else { + client.print(F(" \"status\" : \"CTRL_TH Successfull")); + EEPROM.write(T_MIN, T_min); + EEPROM.write(T_MAX, T_Max); + EEPROM.write(SV_TE, SV_Te); + EEPROM.write(H_MIN, H_min); + EEPROM.write(H_MAX, H_Max); + EEPROM.write(SV_HU, SV_Hu); + } + EEPROM.commit(); + } + break; + case 12 : // Paramètres de remontée du climat + Serial.println(F("Configuration des paramètres pour la remontée du climat vers l'IPX")); + ExtractParamsValues(Commande.substring(8), Params, 3); // Extrait les 3 paramètres attendus : PERIOD, VA_TE et optionnellement VA_HU + CdeOK = true; + if (!Params[0].param.equalsIgnoreCase("PERIOD")) { + CdeOK = false; + Serial.println(F("Le premier paramètre n'est pas PERIOD")); + Serial.println(F("Configuration non modifiée")); + client.print(F(" \"status\" : \"RTRN_TH Command error, 1st param not PERIOD")); + } else { + Serial.println(F("Le premier paramètre est bien PERIOD")); + // Evaluation de la périodicité demandée + Params[0].value.toCharArray(Char_Tab,16); + sscanf(Char_Tab, "%i", &New_Periode_Rtrn); + if (New_Periode_Rtrn >= 1440) { // Périodicité supérieure à 24h refusée + CdeOK = false; + Serial.println(F("La périodicité demandée est supérieure à 24h")); + Serial.println(F("Configuration non modifiée")); + client.print(F(" \"status\" : \"RTRN_TH Command error, PERIOD more than 24h")); + } else { + if (New_Periode_Rtrn == 0) { // La périodicité est fixée à 0, inutile d'évaluer le reste + Serial.println(F("Périodicité à 0, fonction de remontée désactivée")); + } else { + Serial.println(F("La périodicité est comprise entre 0 et 24h")); + if (!Params[1].param.equalsIgnoreCase("VA_TE")) { + CdeOK = false; + Serial.println(F("Le paramètre du n° d'entrée virtuelle analogique est manquant")); + Serial.println(F("Configuration non modifiée")); + client.print(F(" \"status\" : \"RTRN_TH Command error, incorrect or missing temperature VA argument")); + } else { + Serial.println(F("Le paramètre du n° d'entrée virtuelle analogique est bien présent")); + // Evaluation des paramètres de T° + Params[1].value.toCharArray(Char_Tab,16); + sscanf(Char_Tab, "%i", &New_VA_Te); + if (New_VA_Te > 32) { + CdeOK = false; + Serial.println(F("L'entrée virtuelle analogique demandée pour la température est au delà de 32")); + Serial.println(F("Configuration non modifiée")); + client.print(F(" \"status\" : \"RTRN_TH Command error, incorrect temperature VA number (max 32)")); + } else { + Serial.print(F("L'entrée virtuelle analogique ")); + Serial.print(New_VA_Te); + Serial.println(F(" est sélectionnée pour la remontée de la T°")); + if (Params[2].param.equalsIgnoreCase("VA_HU")) { + // Une entrée virtuelle analogique est indiquée pour l'humidité + Params[2].value.toCharArray(Char_Tab,16); + sscanf(Char_Tab, "%i", &New_VA_Hu); + if ((New_VA_Hu > 32) || (New_VA_Hu == New_VA_Te)) { + CdeOK = false; + Serial.println(F("L'entrée virtuelle analogique demandée pour l'hygrométrie est au delà de 32 ou égale à celle de la température")); + Serial.println(F("Configuration non modifiée")); + if (New_VA_Hu > 32) { + client.print(F(" \"status\" : \"RTRN_TH Command error, incorrect hygrometry VA number (max 32)")); + } else { + client.print(F(" \"status\" : \"RTRN_TH Command error, hygrometry VA number same as temperature (must be different)")); + } // endif Vérification VA Hygrométrie <=32 pour message + } else { + Serial.print(F("L'entrée virtuelle analogique ")); + Serial.print(New_VA_Hu); + Serial.println(F(" est sélectionnée pour la remontée de l'hygrométrie")); + } // endif Vérification VA Hygrométrie <=32 et != VA T° + } else { // Pas de troisième paramètre ou il n'est pas égal à VA_HU, mise à 0 + New_VA_Hu = 0; + } // endif Contrôle de la présence de VA d'hygrométrie + } // endif Vérification VA Température <=32 + } // endif Contrôle de la VA de T° + } // endif PERIOD à 0, désactivation + } // endif PERIOD > 24h + } // endif Premier paramètre non égal à PERIOD + // Mémorisation des paramètres dans l'EEPROM + if (CdeOK) { // La commande est Ok + Serial.println(F("Configuration OK")); + // Recopie des nouvelles valeurs demandées dans les variables correspondantes + VA_Te = New_VA_Te; + VA_Hu = New_VA_Hu; + Periode_Rtrn = New_Periode_Rtrn; + // Mémorisation dans l'EEPROM + EEPROM.put(PERIODE_RTRN, Periode_Rtrn); + if (Periode_Rtrn == 0) { + client.print(F(" \"status\" : \"RTRN_TH Successfully deactivated")); + } else { + client.print(F(" \"status\" : \"RTRN_TH Successfull")); + EEPROM.write(VA_TE, VA_Te); + EEPROM.write(VA_HU, VA_Hu); + } + EEPROM.commit(); + } + break; + default : // Gestion d'un changement d'état + Num_Cde=Num_Cde_Recue; + client.print(F(" \"status\" : \"")); + client.print(Num_Cde); + Pilote(Num_Cde); + } + client.println(F("\"")); + } + client.println(F("}")); + client.flush(); + Serial.println(SEPARATEUR); + +} +} diff --git a/WS3/WS3-ESP32-v2/WS3-ESP32-v2.ino b/WS3/WS3-ESP32-v2/WS3-ESP32-v2.ino new file mode 100644 index 0000000..30a2a4e --- /dev/null +++ b/WS3/WS3-ESP32-v2/WS3-ESP32-v2.ino @@ -0,0 +1,713 @@ +// Weather Station 3 from AliExpress / TaoBao +// +// ESP32-PoE-ISO version. +// This version uses ONLY the ethernet interface. +// +// Highly inspired by https://github.com/kquinsland/ws3-to-esphome-bridge/ +// +// Maybe the code is not optimal but it works. +// +// Configuration of WS3 : +// +// JP1 toggle betwen ARS inch mode and professional metric mode +// short : inch +// open : metric +// +// JP2 baud rate +// short : 2400 +// open : 9600 +// +// Configuration used for this code : +// JP1 : open +// JP2 : closed +// +// Data is in the form : +// +// A4095B000C0000D0000E0000F0000G0000H0000I0000J0000K0000L0218M515N09654O.....*52 +// +#include +#include +#include +// MQTT +#include +#include + +// Domoticz MQTT configuration has to be coded here +const char *mqtt_server = "portbuild.home.oav.net"; +#define mqtt_port 1883 + +// configuration values +const char idx_windir[4]="48"; +const char idx_temp[4] ="52"; +const char idx_rain[4] ="51"; + +// To handle Gust +volatile unsigned int loopcount=0; // number of loops (300 = 10 minutes, since every Serial loops is 2s) +volatile unsigned int WindGust=0; // wind gust data values to keep track of the strongest gust in the last 10 minutes + +// Definitions +const char* Mqtt_clientid = "ESP-Weather-Station"; +const char* dom_in = "domoticz/in"; +#define MQTT_MAX_PACKET_SIZE 128 +char msgToPublish[MQTT_MAX_PACKET_SIZE + 1]; + +// In case of too much MQTT Failure +volatile unsigned int mqttfail = 0; +#define MAX_MQTT_FAIL 30; + +// Defines +#define OTAPASSWORD "123" // Password for OTA upload thru Arduino IDE + +// Debug +#define DEBUG 1 + +#ifdef DEBUG + #define debug(x) Serial.print(x) + #define debugln(x) Serial.println(x) +#else /* DEBUG */ + #define debug(x) + #define debugln(x) +#endif /* DEBUG */ +// Baud used to read see JP2 +#define WS3_BAUD 2400 +// Toggle support for PM2.5 sensor +//#define SUPPORT_PM25_SENSOR + +// Define the length of data +#ifdef SUPPORT_PM25_SENSOR + // There is 88 bytes per packets + #define WS3_PKT_LEN 78 + // And the checksum is the last 2 bytes + #define WS3_CHK_LEN 2 + #define CHK_SUM_DELINEATOR 75 +#else /* SUPPORT_PM25_SENSOR */ + // There is 88 bytes per packets + #define WS3_PKT_LEN 78 + // And the checksum is the last 2 bytes + #define WS3_CHK_LEN 2 + #define CHK_SUM_DELINEATOR 75 +#endif /* SUPPRT_PM25_SENSOR */ + +// Seems the Metric format does not have a correct checksum +// In this case we should not test the checksum, just see +// if we have a correct dataline +#define DONT_CHKSUM 1 + +// Define ALTITUDE if you need to get pression corrected +// Set this to 0 if you don't care +#define ALTITUDE 340 // Longwy is at 340m + +// Ethernet stuff +#define ETH_CLK_MODE ETH_CLOCK_GPIO17_OUT +#define ETH_PHY_POWER 12 + +static bool eth_connected = false; + +// Place holder for the packet received +String pkt_str = ""; + +// Flag for packet OK +volatile byte pkt_ok = false; + +//#define WS3 Serial2 +#define WS3 Serial1 + +//WiFiClient espClient; // FIXME +WiFiClient espClient; +PubSubClient client(espClient); + +// After parsing the string of bytes, we'll have an easier to use struct +// TODO: this should be it's own file? +struct WS3Packet { + + // The 1st field is "A0000" - Wind direction AD value in real time (0-4096) + unsigned int wind_dir; + // The 2nd field is "B000" - Wind direct angle value (16 direction) + unsigned int wind_angle; // new + // The 3rd field is "C0000" - Real time wind speed frequency 1Hz + unsigned int wind_freq; // New + // The 4th field is "D0000" - Real time wind speed, unit: 0.1 m/s + unsigned int wind_speed; + // The 5th field is "E0000" - Avg wind speed in the previous minute, unit: 0.1m/s + unsigned int wind_speed_1m; + // The 6th field is "F0000" - the highest wind speed in the last 5 minutes, unit: 0.1m/s + unsigned int wind_speed_5m; + // The 7th field is "G0000" - Real time rain bucket (0-9999), loop-count + int rain_bucket_cnt; // New + // The 8th field is "H0000" - Number of rain bucket in the last minute, (0-9999) + int rt_rain_bucket; + // The 9th field is "I0000" - Rain fall in 1 minute, unit: 0.1mm + unsigned int rain_1m; + // The 10th field is "J0000" - the previous hour's rainfall ( 0.1 mm) + unsigned int rain_1h; + // The 11th field is "K0000" - rainfall during the first 24 hours ( 0.1 mm) + unsigned int rain_24h; + // The 12th field is "L0000" - temperature, unit: degree C (unit 0.1 Degree) + float temp_f; + // The 13th field is "M000" - humidity ( 00 % - 99 %), unit 0.1% + float humidity; + // The 14th field is "M10020" - air pressure ( 0.1 hpa ) + float air_pressure; +}; + +// Return the index according to Wind Angle +String str_windir(unsigned int WinVal){ + if(WinVal >= 360) return "N"; //N + if(WinVal >= 0 && WinVal < 22) return "N"; //N + if(WinVal >= 22 && WinVal < 45) return "NNE"; //NNE + if(WinVal >= 45 && WinVal < 67) return "NE"; //NE + if(WinVal >= 67 && WinVal < 90) return "ENE"; //ENE + if(WinVal >= 90 && WinVal < 112) return "E"; //E + if(WinVal >= 112 && WinVal < 135) return "ESE"; //ESE + if(WinVal >= 135 && WinVal < 157) return "SE"; //SE + if(WinVal >= 157 && WinVal < 180) return "S"; //S + if(WinVal >= 180 && WinVal < 202) return "S"; //S + if(WinVal >= 202 && WinVal < 225) return "SSW"; //SSW + if(WinVal >= 225 && WinVal < 247) return "SW"; //SW + if(WinVal >= 247 && WinVal < 270) return "WSW"; //WSW + if(WinVal >= 270 && WinVal < 292) return "W"; //W + if(WinVal >= 292 && WinVal < 315) return "WNW"; //WNW + if(WinVal >= 315 && WinVal < 337) return "NW"; //NW + if(WinVal >= 337 && WinVal < 359) return "NNW"; //NNW +} + +// Setup OTA stuff +void setup_ota() { + //Port defaults to 8266 + //ArduinoOTA.setPort(8266); + + // Hostname defaults to projet name + ArduinoOTA.setHostname(Mqtt_clientid); + + // No auth per default + ArduinoOTA.setPassword((const char *)OTAPASSWORD); + + ArduinoOTA.onStart([]() { + Serial.println("OTA Update is Starting !"); + }); + + ArduinoOTA.onEnd([]() { + Serial.println("\nOTA is done. Rebooting..."); + }); + + ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { + Serial.printf("Progress: %u%%\r", (progress / (total / 100))); + }); + + ArduinoOTA.onError([](ota_error_t error) { + Serial.printf("OTA Error[%u]: ", error); + if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed"); + else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed"); + else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed"); + else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed"); + else if (error == OTA_END_ERROR) Serial.println("End Failed"); + }); + + ArduinoOTA.begin(); +} + +void WiFiEvent(WiFiEvent_t event) +{ + switch (event) { + case SYSTEM_EVENT_ETH_START: + Serial.println("ETH Started"); + //set eth hostname here + ETH.setHostname("esp32-ethernet"); + //ETH.setHostname(Mqtt_clientid); + break; + case SYSTEM_EVENT_ETH_CONNECTED: + Serial.println("ETH Connected"); + break; + case SYSTEM_EVENT_ETH_GOT_IP: + Serial.print("ETH MAC: "); + Serial.print(ETH.macAddress()); + Serial.print(", IPv4: "); + Serial.print(ETH.localIP()); + if (ETH.fullDuplex()) { + Serial.print(", FULL_DUPLEX"); + } + Serial.print(", "); + Serial.print(ETH.linkSpeed()); + Serial.println("Mbps"); + eth_connected = true; + break; + case SYSTEM_EVENT_ETH_DISCONNECTED: + Serial.println("ETH Disconnected"); + eth_connected = false; + break; + case SYSTEM_EVENT_ETH_STOP: + Serial.println("ETH Stopped"); + eth_connected = false; + break; + default: + break; + } +} + +// Setup the stuff. +void setup() { + Serial.begin(115200); + while (!Serial) ; // wait for Arduino Serial Monitor to open + Serial.println("\n\n"); + Serial.println("Weather Station 3 Adapter by Kiwi"); + + // Enable Ethernet + // Why this is "WiFi" instead of Eth???? + WiFi.onEvent(WiFiEvent); + ETH.begin(); + + //while(!eth_connected); + // Wait 30 seconds to allow Cisco switch to be ready + Serial.println("Wait for link up and IP address to be ready - 10 sec ..."); + delay(10*1000); + + // Launch OTA stuff + setup_ota(); + + // Start the Software Serial for WS3 +// WS3.begin(WS3_BAUD,SERIAL_8N1, 16,17); + // GPIO 17 is used by Ethernet, so use unused IO -> 10 + //WS3.begin(WS3_BAUD,SERIAL_8N1, 16,10); + WS3.begin(WS3_BAUD,SERIAL_8N1, 36,4); + + + while (!WS3) ; // wait for Arduino Serial Monitor to open + debugln("WS3 UART is ready..."); + + // Allocate memory for packet + pkt_str.reserve(WS3_PKT_LEN); + debugln(" -> Packet memory allocated!"); + + client.setServer(mqtt_server, mqtt_port); + //client.setCallback(callback); + debugln("MQTT started"); +} + +#ifdef DONT_CHKSUM +// Validate packet using the checksum. +// Work only APRS data on this 51W3 board. +// Maybe the code on the board does not make the correct checksum ? +bool validate_packet(String pay, unsigned long chk) { + // Print the payload and the checksum we want + debugln("validate_packet:"); + debug(pay); + debug(" * "); + debugln(chk); + + // TEST DATA (actual packets) + // c000s000g000t075r000p019h43b09940*32 + // String pay = "c000s000g000t075r000p019h43b09940"; + // byte chk = 0x32; // this will be in HEX) + + // c000s000g000t075r000p019h42b09940*33 + // String pay = "c000s000g000t075r000p019h42b09940"; + // byte chk = 0x33; // this will be in HEX) + + // c000s000g000t075r000p019h42b09939*3D + // String pay = "c000s000g000t075r000p019h42b09939"; + // byte chk = 0x3D; // this will be in HEX) + + // SUPER grateful for the helpful https://toolslick.com/math/bitwise/xor-calculator to validate my + // code! + + // Current byte + byte i1=0; + + // the intermediate checksum + byte tmp = 0; + + // starting from the second character, we begin XORing + for (int x = 0; x < pay.length() ; x++) { + + i1=pay[x]; + + // Do the xOR + tmp = tmp^i1; + + } + + // do the check + if(tmp == chk){ + return true; + } else { + debugln("INVALID!"); + debug("calculated:"); + debugln(tmp); + return false; + } +} +#endif /* DONT_CHKSUM */ + + +// Parse the packet and fill the structure with data +void parse_packet(String payload, WS3Packet* p) { + + // E.G.: A4095 B000 C0000 D0000 E0000 F0000 G0000 H0000 I0000 J0000 K0000 L0237 M502 N09810 O..... + + // Parse in order, starting with A0000 (wind dir real time, 0-4096) + int wind_dir_idx = payload.indexOf('A'); + p->wind_dir = payload.substring(wind_dir_idx+1, wind_dir_idx+5).toInt(); + + // Then move on to B000 - wind direction angle (16 direction) + int wind_angle_idx = payload.indexOf('B'); + p->wind_angle = payload.substring(wind_angle_idx+1, wind_angle_idx+4).toInt(); + + // Then move on to C0000 - wind speed frequency (1 Hz) + int wind_freq_idx = payload.indexOf('C'); + p->wind_freq = payload.substring(wind_freq_idx+1, wind_freq_idx+5).toInt(); + + // Then move on to D0000 - wind speed real time (unit 0.1 m/s) + int wind_speed_idx = payload.indexOf('D'); + p->wind_speed = payload.substring(wind_speed_idx+1, wind_speed_idx+5).toInt(); + + // Then move on to E0000 - wind speed avg in the last minute (unit 0.1 m/s) + int wind_speed_1m_idx = payload.indexOf('D'); + p->wind_speed_1m = payload.substring(wind_speed_1m_idx+1, wind_speed_1m_idx+5).toInt(); + + // Then move on to F0000 - wind speed over the last 5 min + int wind_speed_5_idx = payload.indexOf('F'); + p->wind_speed_5m = payload.substring(wind_speed_5_idx+1, wind_speed_5_idx+5).toInt(); + + // Then move on to G0000 - Rain in Realtime (0-9999 counter) bucket + int rain_bucket_cnt_idx = payload.indexOf('G'); + p->rain_bucket_cnt = payload.substring(rain_bucket_cnt_idx+1, rain_bucket_cnt_idx+5).toInt(); + + // Then move on to H0000 - Rain bucket in the last 1 minute (0-9999 counter) + int rt_rain_bucket_idx = payload.indexOf('H'); + p->rt_rain_bucket = payload.substring(rt_rain_bucket_idx+1, rt_rain_bucket_idx+5).toInt(); + + // Then move on to I0000 - rain last minute (0.1mm) + int rain1m_idx = payload.indexOf('I'); + p->rain_1m = payload.substring(rain1m_idx+1, rain1m_idx+5).toInt(); + + // Then move on to J0000 - rain last hour (0.1mm) + int rain1h_idx = payload.indexOf('J'); + p->rain_1h = payload.substring(rain1h_idx+1, rain1h_idx+5).toInt(); + + // Then move on to K0000 - rain last 24h (0.1mm) + int rain24h_idx = payload.indexOf('K'); + p->rain_24h = payload.substring(rain24h_idx+1, rain24h_idx+5).toInt(); + + // Then move on to L0200 - temp (0.1°C) + int temp_idx = payload.indexOf('L'); + p->temp_f = payload.substring(temp_idx+1, temp_idx+5).toInt()*.1; + + // Then move on to M611 - Humidity + int humidity_idx = payload.indexOf('M'); + p->humidity = payload.substring(humidity_idx+1, humidity_idx+3).toInt(); + + // Then move on to N10020 - air pressure + int pressure_idx = payload.indexOf('N'); + p->air_pressure = payload.substring(pressure_idx+1, pressure_idx+6).toInt()*.1; + + // Handle Gust + if(p->wind_speed > WindGust) { + WindGust = p->wind_speed; + debugln("Update WindGust"); + } + + if(loopcount >= 300) { + loopcount = 0; + WindGust = p->wind_speed; + debugln("10 min expired -> reset counter"); + } + // Increment loopcount for Gust + loopcount++; +} + +// Clear the packet before working on the next +void clear_pkt(WS3Packet* p) { + p->wind_dir = 0; + p->wind_angle = 0; + p->wind_freq = 0; + p->wind_speed = 0; + p->wind_speed_1m = 0; + p->wind_speed_5m = 0; + p->rain_bucket_cnt = 0; + p->rt_rain_bucket = 0; + p->rain_1m = 0; + p->rain_1h = 0; + p->rain_24h = 0; + p->temp_f = 0; + p->humidity = 0; + p->air_pressure = 0; +} + +#ifdef DEBUG +// Print the data +void print_weather(WS3Packet* p){ + Serial.print("Wind Direction (realtime): "); + Serial.println(p->wind_dir, DEC); + Serial.print("Wind direction angle : "); + Serial.print(p->wind_angle, DEC); + Serial.print(" degree "); + Serial.println(str_windir(p->wind_angle)); + Serial.print("Wind speed Frequency: "); + Serial.print(p->wind_freq, DEC); + Serial.println(" Hz"); + Serial.print("Wind speed: "); + Serial.print(p->wind_speed/10, DEC); + Serial.println(" m/s"); + Serial.print("Wind speed 1m: "); + Serial.print(p->wind_speed_1m/10, DEC); + Serial.println(" m/s"); + Serial.print("Wind speed 5m: "); + Serial.print(p->wind_speed_5m/10, DEC); + Serial.println(" m/s"); + Serial.print("temp_f: "); + Serial.print(p->temp_f, DEC); + Serial.println(" deg. C."); + + Serial.print("Rain buckets / buckets 1m: "); + Serial.print(p->rain_bucket_cnt, DEC); + Serial.print(" / "); + Serial.println(p->rt_rain_bucket, DEC); + + Serial.print("Rain 1m / 1H / 24H: "); + Serial.print(p->rain_1m*0.1, DEC); + Serial.print(" / "); + Serial.print(p->rain_1h*0.1, DEC); + Serial.print(" / "); + Serial.print(p->rain_24h*.1, DEC); + Serial.println(" mm"); + + Serial.print("humidity: "); + Serial.print(p->humidity, DEC); + Serial.println(" %"); + + Serial.print("air_pressure: "); + Serial.print(p->air_pressure, DEC); + Serial.println(" hpa"); +} +#endif /* DEBUG */ + + +// Processing the packet. +bool process_packet(String pkt, WS3Packet* p) { + debugln("[D] process_packet - ALive!"); + debugln(pkt); + + // Allocate bytes for the payload + String payload; + payload.reserve(WS3_PKT_LEN-WS3_CHK_LEN); + + #ifdef DONT_CHKSUM + // everything after the * is checksum (2 char long) + unsigned long chksum; + #endif /* DONT_CHKSUM */ + + // Check if the 75rd character is * + if (pkt.charAt(CHK_SUM_DELINEATOR) != '*') { + debugln("Packed invalid; no * character at position 75!"); + return false; + #ifdef DONT_CHKSUM + } else { + // The character indicating the checksum is coming is in the correct place. Yay. + // Now, we need to pull the two ascii characters that are transmitted to us + // and turn them into a single byte. E.G. Char 3, Char D should convert to 0x3D. + // + // We can do this with the strtoul() function; we indicate that we wante base 16 + + chksum = strtoul(pkt.substring(CHK_SUM_DELINEATOR+1, CHK_SUM_DELINEATOR+2).c_str(),NULL,16); + } + #endif /* DONT_CHKSUM */ + + // We have the checksum, Now we can bother to get the payload + payload = pkt.substring(0, CHK_SUM_DELINEATOR); + + // And try to validate... + #ifndef DONT_CHKSUM + if(!validate_packet(payload, chksum)){ + debugln("invalid packet! :("); + return false; + } else { + debugln("Valid packet!"); + } + #endif /* DONT_CHKSUM */ + parse_packet(payload, p); + return true; +} + +// MQTT Stuff +//void callback(char* topic, byte* payload, unsigned int length) { +// debug("Message arrived ["); +// Serial.print(topic); +// debug("] "); +// for (int i = 0; i < length; i++) { +// Serial.print((char)payload[i]); +// } +// debug(" "); +//} + +void reconnect() { + // Loop until we're reconnected + while (!client.connected()) { + debug("Attempting MQTT connection..."); + // Attempt to connect + if (client.connect(Mqtt_clientid)) { + debugln("connected"); + // ... and resubscribe + client.subscribe(dom_in); + } else { + debug("failed, rc="); + debug(client.state()); + debugln(" try again in 5 seconds"); + // Wait 5 seconds before retrying + for(int i = 0; i<5000; i++){ + delay(1); + } + } + } +} + +// Sends MQTT payload to the Mosquitto server running on a Raspberry Pi. +// Mosquitto server deliveres data to Domoticz server running on a same Raspberry Pi +void sendMQTTPayload(String msgpayload) +{ + // Convert payload to char array + msgpayload.toCharArray(msgToPublish, msgpayload.length()+1); + + //Publish payload to MQTT broker + if (client.publish(dom_in, msgToPublish)) + { + debug("Following data published to MQTT broker: "); + debug(dom_in); + debug(" "); + debugln(msgpayload); + } + else + { + debug("Publishing to MQTT broker failed... "); + debugln(client.state()); + mqttfail = mqttfail + 1; + } +} + + +// Print the data over MQTT +void push_weather(WS3Packet* p) { + String MQPayload; + byte humidity_status = 0; // Domoticz humdity status + byte bar_forecast = 0; // Domoticz baro forcast + float pression = 0.0; // Pression + float wct = 0.0; // Windchill temperature + + // Rain + MQPayload = "{ \"idx\" : "+ String(idx_rain) +",\"nvalue\" : 0, \"svalue\": \"" + String(p->rain_1h*10) + ";" + String(p->rain_1m*0.1) + "\"}"; + sendMQTTPayload(MQPayload); + + // Temperature / Humidity / Baro + + // Humidity stuff + // O = Normal + // 1 = Confortable + // 2 = Dry (<35%) + // 3 = Wet (>70%) + if (p->humidity < 35) { + humidity_status = 2; // Dry + } else if (p->humidity >= 70) { + humidity_status = 3; // Wet + } else { + if ((p->humidity >=35) && (p->humidity <50)) { + humidity_status = 1; // Confortable + } else { + // >=50 -> 70% + humidity_status = 0; // Normal + } + } + // Correct the pression according to altitude + pression = p->air_pressure + (ALTITUDE / 8.3); + // Pression should be adapted as it should be on sea level + + // Prediction + // 0 : No info + // 1 : Sunny + // 2 : Partly cloudy + // 3 : Cloudy + // 4 : Rain + // Maybe have to store values for last 3 hours? + // XXX: fix value with altitude + if (pression < 1000) { + bar_forecast = 4; // Rain + } else if (pression < 1020) { + bar_forecast = 3; // Cloudy + } else if (pression < 1030) { + bar_forecast = 2; // Partly cloudy + } else { + bar_forecast = 1; // Sunny + } + // See : https://github.com/G6EJD/ESP32-Weather-Forecaster + MQPayload = "{ \"idx\" : "+ String(idx_temp) +",\"nvalue\" : 0, \"svalue\": \"" + String(p->temp_f) + ";" + String(p->humidity) + ";"+String(humidity_status)+ ";" + String(pression) +";"+String(bar_forecast)+"\"}"; + + sendMQTTPayload(MQPayload); + // Wind + // Last temperature is WindChill + // Have to compute it https://www.alpiniste.fr/windchill-calculateur/ + //wct = p->temp_f; + + wct = 13.12 + 0.6215*p->temp_f - 11,37*pow((p->wind_speed * 3.6),0.16)+0.3965*p->temp_f*pow((p->wind_speed * 3.6),0.16); + MQPayload = "{ \"idx\" : "+ String(idx_windir) +",\"nvalue\" : 0, \"svalue\": \"" + String(p->wind_angle) + ";" + String(str_windir(p->wind_angle)) + ";" + String(p->wind_speed) + ";" + String(WindGust) +";" + String(p->temp_f) + ";"+String(wct)+"\"}"; + sendMQTTPayload(MQPayload); +} + +void loop() { + if(eth_connected) { + // MQTT + if (!client.connected()) { + reconnect(); + } + + // While data comes in and we don't have a pending packet to process... + while (WS3.available() && pkt_ok !=true) { + + // Pull the bytes off the stream + char inChar = (char)WS3.read(); + + // And build up the packet + pkt_str += inChar; + + // Until we hit the end + if (inChar == '\n') { + pkt_ok = true; + } + } + + // Yay, we now have a packet! + // Now, we attempt to parse out the string into a packet that we can work with + if (pkt_ok) { + debugln("pkt_ok!"); + + // At this point, we have a string of characters that was probably a valid packet + // We set get some memory and attempt to parse the string into the struct + WS3Packet p = {}; + + // Validate the payload, then parse it. + if (process_packet(pkt_str, &p)) { + // print results if parse OK + #ifdef DEBUG + print_weather(&p); + #endif /* DEBUG */ + // Push to MQTT + push_weather(&p); + debugln("processed"); + } else { + debugln("unable to parse packet :("); + } + + + // clear so we can start again + pkt_str = ""; + pkt_ok = false; + clear_pkt(&p); + } + } + if (mqttfail > 5) { + debugln("MQTT Fails more than 5 times, reconnect"); + reconnect(); + } + if (mqttfail > 30) { + debugln("MQTT Fails : reboot"); + ESP.restart(); + } + ArduinoOTA.handle(); +} diff --git a/WS3/WS3-ESP32/WS3-ESP32.ino b/WS3/WS3-ESP32/WS3-ESP32.ino index 6b28a14..841c027 100644 --- a/WS3/WS3-ESP32/WS3-ESP32.ino +++ b/WS3/WS3-ESP32/WS3-ESP32.ino @@ -1,6 +1,8 @@ // Weather Station 3 from AliExpress / TaoBao // // Code for ESP32-PoE-ISO +// +// Notice : this will use ONLY Ethernet card. With DHCP. // // Highly inspired by https://github.com/kquinsland/ws3-to-esphome-bridge/ // @@ -58,60 +60,12 @@ SoftwareSerial WS3(RXD2,TXD2); WiFiClient espClient; PubSubClient client(espClient); -// After parsing the string of bytes, we'll have an easier to use struct -// TODO: this should be it's own file? -struct WS3Packet { +// ws3_wifi.ino +void setupSpiffs(); +void saveConfigCallback(); - // The 1st field is "A0000" - Wind direction AD value in real time (0-4096) - unsigned int wind_dir; - // The 2nd field is "B000" - Wind direct angle value (16 direction) - unsigned int wind_angle; // new - // The 3rd field is "C0000" - Real time wind speed frequency 1Hz - unsigned int wind_freq; // New - // The 4th field is "D0000" - Real time wind speed, unit: 0.1 m/s - unsigned int wind_speed; - // The 5th field is "E0000" - Avg wind speed in the previous minute, unit: 0.1m/s - unsigned int wind_speed_1m; - // The 6th field is "F0000" - the highest wind speed in the last 5 minutes, unit: 0.1m/s - unsigned int wind_speed_5m; - // The 7th field is "G0000" - Real time rain bucket (0-9999), loop-count - int rain_bucket_cnt; // New - // The 8th field is "H0000" - Number of rain bucket in the last minute, (0-9999) - int rt_rain_bucket; - // The 9th field is "I0000" - Rain fall in 1 minute, unit: 0.1mm - unsigned int rain_1m; - // The 10th field is "J0000" - the previous hour's rainfall ( 0.1 mm) - unsigned int rain_1h; - // The 11th field is "K0000" - rainfall during the first 24 hours ( 0.1 mm) - unsigned int rain_24h; - // The 12th field is "L0000" - temperature, unit: degree C (unit 0.1 Degree) - float temp_f; - // The 13th field is "M000" - humidity ( 00 % - 99 %), unit 0.1% - float humidity; - // The 14th field is "M10020" - air pressure ( 0.1 hpa ) - float air_pressure; -}; - -// Return the index according to Wind Angle -String str_windir(unsigned int WinVal){ - if(WinVal >= 360) return "N"; //N - if(WinVal >= 0 && WinVal < 22) return "N"; //N - if(WinVal >= 22 && WinVal < 45) return "NNE"; //NNE - if(WinVal >= 45 && WinVal < 67) return "NE"; //NE - if(WinVal >= 67 && WinVal < 90) return "ENE"; //ENE - if(WinVal >= 90 && WinVal < 112) return "E"; //E - if(WinVal >= 112 && WinVal < 135) return "ESE"; //ESE - if(WinVal >= 135 && WinVal < 157) return "SE"; //SE - if(WinVal >= 157 && WinVal < 180) return "S"; //S - if(WinVal >= 180 && WinVal < 202) return "S"; //S - if(WinVal >= 202 && WinVal < 225) return "SSW"; //SSW - if(WinVal >= 225 && WinVal < 247) return "SW"; //SW - if(WinVal >= 247 && WinVal < 270) return "WSW"; //WSW - if(WinVal >= 270 && WinVal < 292) return "W"; //W - if(WinVal >= 292 && WinVal < 315) return "WNW"; //WNW - if(WinVal >= 315 && WinVal < 337) return "NW"; //NW - if(WinVal >= 337 && WinVal < 359) return "NNW"; //NNW -} +// ws3_ota.ino +void setup_ota(); // Setup the stuff. @@ -221,205 +175,6 @@ void setup() { debugln("MQTT started"); } -#ifdef DONT_CHKSUM -// Validate packet using the checksum. -// Work only APRS data on this 51W3 board. -// Maybe the code on the board does not make the correct checksum ? -bool validate_packet(String pay, unsigned long chk) { - // Print the payload and the checksum we want - debugln("validate_packet:"); - debug(pay); - debug(" * "); - debugln(chk); - - // TEST DATA (actual packets) - // c000s000g000t075r000p019h43b09940*32 - // String pay = "c000s000g000t075r000p019h43b09940"; - // byte chk = 0x32; // this will be in HEX) - - // c000s000g000t075r000p019h42b09940*33 - // String pay = "c000s000g000t075r000p019h42b09940"; - // byte chk = 0x33; // this will be in HEX) - - // c000s000g000t075r000p019h42b09939*3D - // String pay = "c000s000g000t075r000p019h42b09939"; - // byte chk = 0x3D; // this will be in HEX) - - // SUPER grateful for the helpful https://toolslick.com/math/bitwise/xor-calculator to validate my - // code! - - // Current byte - byte i1=0; - - // the intermediate checksum - byte tmp = 0; - - // starting from the second character, we begin XORing - for (int x = 0; x < pay.length() ; x++) { - - i1=pay[x]; - - // Do the xOR - tmp = tmp^i1; - - } - - // do the check - if(tmp == chk){ - return true; - } else { - debugln("INVALID!"); - debug("calculated:"); - debugln(tmp); - return false; - } -} -#endif /* DONT_CHKSUM */ - - -// Parse the packet and fill the structure with data -void parse_packet(String payload, WS3Packet* p) { - - // E.G.: A4095 B000 C0000 D0000 E0000 F0000 G0000 H0000 I0000 J0000 K0000 L0237 M502 N09810 O..... - - // Parse in order, starting with A0000 (wind dir real time, 0-4096) - int wind_dir_idx = payload.indexOf('A'); - p->wind_dir = payload.substring(wind_dir_idx+1, wind_dir_idx+5).toInt(); - - // Then move on to B000 - wind direction angle (16 direction) - int wind_angle_idx = payload.indexOf('B'); - p->wind_angle = payload.substring(wind_angle_idx+1, wind_angle_idx+4).toInt(); - - // Then move on to C0000 - wind speed frequency (1 Hz) - int wind_freq_idx = payload.indexOf('C'); - p->wind_freq = payload.substring(wind_freq_idx+1, wind_freq_idx+5).toInt(); - - // Then move on to D0000 - wind speed real time (unit 0.1 m/s) - int wind_speed_idx = payload.indexOf('D'); - p->wind_speed = payload.substring(wind_speed_idx+1, wind_speed_idx+5).toInt(); - - // Then move on to E0000 - wind speed avg in the last minute (unit 0.1 m/s) - int wind_speed_1m_idx = payload.indexOf('D'); - p->wind_speed_1m = payload.substring(wind_speed_1m_idx+1, wind_speed_1m_idx+5).toInt(); - - // Then move on to F0000 - wind speed over the last 5 min - int wind_speed_5_idx = payload.indexOf('F'); - p->wind_speed_5m = payload.substring(wind_speed_5_idx+1, wind_speed_5_idx+5).toInt(); - - // Then move on to G0000 - Rain in Realtime (0-9999 counter) bucket - int rain_bucket_cnt_idx = payload.indexOf('G'); - p->rain_bucket_cnt = payload.substring(rain_bucket_cnt_idx+1, rain_bucket_cnt_idx+5).toInt(); - - // Then move on to H0000 - Rain bucket in the last 1 minute (0-9999 counter) - int rt_rain_bucket_idx = payload.indexOf('H'); - p->rt_rain_bucket = payload.substring(rt_rain_bucket_idx+1, rt_rain_bucket_idx+5).toInt(); - - // Then move on to I0000 - rain last minute (0.1mm) - int rain1m_idx = payload.indexOf('I'); - p->rain_1m = payload.substring(rain1m_idx+1, rain1m_idx+5).toInt(); - - // Then move on to J0000 - rain last hour (0.1mm) - int rain1h_idx = payload.indexOf('J'); - p->rain_1h = payload.substring(rain1h_idx+1, rain1h_idx+5).toInt(); - - // Then move on to K0000 - rain last 24h (0.1mm) - int rain24h_idx = payload.indexOf('K'); - p->rain_24h = payload.substring(rain24h_idx+1, rain24h_idx+5).toInt(); - - // Then move on to L0200 - temp (0.1°C) - int temp_idx = payload.indexOf('L'); - p->temp_f = payload.substring(temp_idx+1, temp_idx+5).toInt()*.1; - - // Then move on to M611 - Humidity - int humidity_idx = payload.indexOf('M'); - p->humidity = payload.substring(humidity_idx+1, humidity_idx+3).toInt(); - - // Then move on to N10020 - air pressure - int pressure_idx = payload.indexOf('N'); - p->air_pressure = payload.substring(pressure_idx+1, pressure_idx+6).toInt()*.1; - - // Handle Gust - if(p->wind_speed > WindGust) { - WindGust = p->wind_speed; - debugln("Update WindGust"); - } - - if(loopcount >= 300) { - loopcount = 0; - WindGust = p->wind_speed; - debugln("10 min expired -> reset counter"); - } - // Increment loopcount for Gust - loopcount++; -} - -// Clear the packet before working on the next -void clear_pkt(WS3Packet* p) { - p->wind_dir = 0; - p->wind_angle = 0; - p->wind_freq = 0; - p->wind_speed = 0; - p->wind_speed_1m = 0; - p->wind_speed_5m = 0; - p->rain_bucket_cnt = 0; - p->rt_rain_bucket = 0; - p->rain_1m = 0; - p->rain_1h = 0; - p->rain_24h = 0; - p->temp_f = 0; - p->humidity = 0; - p->air_pressure = 0; -} - -#ifdef DEBUG -// Print the data -void print_weather(WS3Packet* p){ - Serial.print("Wind Direction (realtime): "); - Serial.println(p->wind_dir, DEC); - Serial.print("Wind direction angle : "); - Serial.print(p->wind_angle, DEC); - Serial.print(" degree "); - Serial.println(str_windir(p->wind_angle)); - Serial.print("Wind speed Frequency: "); - Serial.print(p->wind_freq, DEC); - Serial.println(" Hz"); - Serial.print("Wind speed: "); - Serial.print(p->wind_speed/10, DEC); - Serial.println(" m/s"); - Serial.print("Wind speed 1m: "); - Serial.print(p->wind_speed_1m/10, DEC); - Serial.println(" m/s"); - Serial.print("Wind speed 5m: "); - Serial.print(p->wind_speed_5m/10, DEC); - Serial.println(" m/s"); - Serial.print("temp_f: "); - Serial.print(p->temp_f, DEC); - Serial.println(" deg. C."); - - Serial.print("Rain buckets / buckets 1m: "); - Serial.print(p->rain_bucket_cnt, DEC); - Serial.print(" / "); - Serial.println(p->rt_rain_bucket, DEC); - - Serial.print("Rain 1m / 1H / 24H: "); - Serial.print(p->rain_1m*0.1, DEC); - Serial.print(" / "); - Serial.print(p->rain_1h*0.1, DEC); - Serial.print(" / "); - Serial.print(p->rain_24h*.1, DEC); - Serial.println(" mm"); - - Serial.print("humidity: "); - Serial.print(p->humidity, DEC); - Serial.println(" %"); - - Serial.print("air_pressure: "); - Serial.print(p->air_pressure, DEC); - Serial.println(" hpa"); -} -#endif /* DEBUG */ - - // Print the data over MQTT void push_weather(WS3Packet* p) { String MQPayload; @@ -486,53 +241,6 @@ void push_weather(WS3Packet* p) { sendMQTTPayload(MQPayload); } - -// Processing the packet. -bool process_packet(String pkt, WS3Packet* p) { - debugln("[D] process_packet - ALive!"); - debugln(pkt); - - // Allocate bytes for the payload - String payload; - payload.reserve(WS3_PKT_LEN-WS3_CHK_LEN); - - #ifdef DONT_CHKSUM - // everything after the * is checksum (2 char long) - unsigned long chksum; - #endif /* DONT_CHKSUM */ - - // Check if the 75rd character is * - if (pkt.charAt(CHK_SUM_DELINEATOR) != '*') { - debugln("Packed invalid; no * character at position 75!"); - return false; - #ifdef DONT_CHKSUM - } else { - // The character indicating the checksum is coming is in the correct place. Yay. - // Now, we need to pull the two ascii characters that are transmitted to us - // and turn them into a single byte. E.G. Char 3, Char D should convert to 0x3D. - // - // We can do this with the strtoul() function; we indicate that we wante base 16 - - chksum = strtoul(pkt.substring(CHK_SUM_DELINEATOR+1, CHK_SUM_DELINEATOR+2).c_str(),NULL,16); - } - #endif /* DONT_CHKSUM */ - - // We have the checksum, Now we can bother to get the payload - payload = pkt.substring(0, CHK_SUM_DELINEATOR); - - // And try to validate... - #ifndef DONT_CHKSUM - if(!validate_packet(payload, chksum)){ - debugln("invalid packet! :("); - return false; - } else { - debugln("Valid packet!"); - } - #endif /* DONT_CHKSUM */ - parse_packet(payload, p); - return true; -} - void loop() { // MQTT if (!client.connected()) { diff --git a/WS3/WS3-ESP32/ws3_stuff.ino b/WS3/WS3-ESP32/ws3_stuff.ino new file mode 100644 index 0000000..9cc4b2e --- /dev/null +++ b/WS3/WS3-ESP32/ws3_stuff.ino @@ -0,0 +1,301 @@ + +// After parsing the string of bytes, we'll have an easier to use struct +// TODO: this should be it's own file? +struct WS3Packet { + + // The 1st field is "A0000" - Wind direction AD value in real time (0-4096) + unsigned int wind_dir; + // The 2nd field is "B000" - Wind direct angle value (16 direction) + unsigned int wind_angle; // new + // The 3rd field is "C0000" - Real time wind speed frequency 1Hz + unsigned int wind_freq; // New + // The 4th field is "D0000" - Real time wind speed, unit: 0.1 m/s + unsigned int wind_speed; + // The 5th field is "E0000" - Avg wind speed in the previous minute, unit: 0.1m/s + unsigned int wind_speed_1m; + // The 6th field is "F0000" - the highest wind speed in the last 5 minutes, unit: 0.1m/s + unsigned int wind_speed_5m; + // The 7th field is "G0000" - Real time rain bucket (0-9999), loop-count + int rain_bucket_cnt; // New + // The 8th field is "H0000" - Number of rain bucket in the last minute, (0-9999) + int rt_rain_bucket; + // The 9th field is "I0000" - Rain fall in 1 minute, unit: 0.1mm + unsigned int rain_1m; + // The 10th field is "J0000" - the previous hour's rainfall ( 0.1 mm) + unsigned int rain_1h; + // The 11th field is "K0000" - rainfall during the first 24 hours ( 0.1 mm) + unsigned int rain_24h; + // The 12th field is "L0000" - temperature, unit: degree C (unit 0.1 Degree) + float temp_f; + // The 13th field is "M000" - humidity ( 00 % - 99 %), unit 0.1% + float humidity; + // The 14th field is "M10020" - air pressure ( 0.1 hpa ) + float air_pressure; +}; + +// Return the index according to Wind Angle +String str_windir(unsigned int WinVal){ + if(WinVal >= 360) return "N"; //N + if(WinVal >= 0 && WinVal < 22) return "N"; //N + if(WinVal >= 22 && WinVal < 45) return "NNE"; //NNE + if(WinVal >= 45 && WinVal < 67) return "NE"; //NE + if(WinVal >= 67 && WinVal < 90) return "ENE"; //ENE + if(WinVal >= 90 && WinVal < 112) return "E"; //E + if(WinVal >= 112 && WinVal < 135) return "ESE"; //ESE + if(WinVal >= 135 && WinVal < 157) return "SE"; //SE + if(WinVal >= 157 && WinVal < 180) return "S"; //S + if(WinVal >= 180 && WinVal < 202) return "S"; //S + if(WinVal >= 202 && WinVal < 225) return "SSW"; //SSW + if(WinVal >= 225 && WinVal < 247) return "SW"; //SW + if(WinVal >= 247 && WinVal < 270) return "WSW"; //WSW + if(WinVal >= 270 && WinVal < 292) return "W"; //W + if(WinVal >= 292 && WinVal < 315) return "WNW"; //WNW + if(WinVal >= 315 && WinVal < 337) return "NW"; //NW + if(WinVal >= 337 && WinVal < 359) return "NNW"; //NNW +} + + +#ifdef DONT_CHKSUM +// Validate packet using the checksum. +// Work only APRS data on this 51W3 board. +// Maybe the code on the board does not make the correct checksum ? +bool validate_packet(String pay, unsigned long chk) { + // Print the payload and the checksum we want + debugln("validate_packet:"); + debug(pay); + debug(" * "); + debugln(chk); + + // TEST DATA (actual packets) + // c000s000g000t075r000p019h43b09940*32 + // String pay = "c000s000g000t075r000p019h43b09940"; + // byte chk = 0x32; // this will be in HEX) + + // c000s000g000t075r000p019h42b09940*33 + // String pay = "c000s000g000t075r000p019h42b09940"; + // byte chk = 0x33; // this will be in HEX) + + // c000s000g000t075r000p019h42b09939*3D + // String pay = "c000s000g000t075r000p019h42b09939"; + // byte chk = 0x3D; // this will be in HEX) + + // SUPER grateful for the helpful https://toolslick.com/math/bitwise/xor-calculator to validate my + // code! + + // Current byte + byte i1=0; + + // the intermediate checksum + byte tmp = 0; + + // starting from the second character, we begin XORing + for (int x = 0; x < pay.length() ; x++) { + + i1=pay[x]; + + // Do the xOR + tmp = tmp^i1; + + } + + // do the check + if(tmp == chk){ + return true; + } else { + debugln("INVALID!"); + debug("calculated:"); + debugln(tmp); + return false; + } +} +#endif /* DONT_CHKSUM */ + + +// Parse the packet and fill the structure with data +void parse_packet(String payload, WS3Packet* p) { + + // E.G.: A4095 B000 C0000 D0000 E0000 F0000 G0000 H0000 I0000 J0000 K0000 L0237 M502 N09810 O..... + + // Parse in order, starting with A0000 (wind dir real time, 0-4096) + int wind_dir_idx = payload.indexOf('A'); + p->wind_dir = payload.substring(wind_dir_idx+1, wind_dir_idx+5).toInt(); + + // Then move on to B000 - wind direction angle (16 direction) + int wind_angle_idx = payload.indexOf('B'); + p->wind_angle = payload.substring(wind_angle_idx+1, wind_angle_idx+4).toInt(); + + // Then move on to C0000 - wind speed frequency (1 Hz) + int wind_freq_idx = payload.indexOf('C'); + p->wind_freq = payload.substring(wind_freq_idx+1, wind_freq_idx+5).toInt(); + + // Then move on to D0000 - wind speed real time (unit 0.1 m/s) + int wind_speed_idx = payload.indexOf('D'); + p->wind_speed = payload.substring(wind_speed_idx+1, wind_speed_idx+5).toInt(); + + // Then move on to E0000 - wind speed avg in the last minute (unit 0.1 m/s) + int wind_speed_1m_idx = payload.indexOf('D'); + p->wind_speed_1m = payload.substring(wind_speed_1m_idx+1, wind_speed_1m_idx+5).toInt(); + + // Then move on to F0000 - wind speed over the last 5 min + int wind_speed_5_idx = payload.indexOf('F'); + p->wind_speed_5m = payload.substring(wind_speed_5_idx+1, wind_speed_5_idx+5).toInt(); + + // Then move on to G0000 - Rain in Realtime (0-9999 counter) bucket + int rain_bucket_cnt_idx = payload.indexOf('G'); + p->rain_bucket_cnt = payload.substring(rain_bucket_cnt_idx+1, rain_bucket_cnt_idx+5).toInt(); + + // Then move on to H0000 - Rain bucket in the last 1 minute (0-9999 counter) + int rt_rain_bucket_idx = payload.indexOf('H'); + p->rt_rain_bucket = payload.substring(rt_rain_bucket_idx+1, rt_rain_bucket_idx+5).toInt(); + + // Then move on to I0000 - rain last minute (0.1mm) + int rain1m_idx = payload.indexOf('I'); + p->rain_1m = payload.substring(rain1m_idx+1, rain1m_idx+5).toInt(); + + // Then move on to J0000 - rain last hour (0.1mm) + int rain1h_idx = payload.indexOf('J'); + p->rain_1h = payload.substring(rain1h_idx+1, rain1h_idx+5).toInt(); + + // Then move on to K0000 - rain last 24h (0.1mm) + int rain24h_idx = payload.indexOf('K'); + p->rain_24h = payload.substring(rain24h_idx+1, rain24h_idx+5).toInt(); + + // Then move on to L0200 - temp (0.1°C) + int temp_idx = payload.indexOf('L'); + p->temp_f = payload.substring(temp_idx+1, temp_idx+5).toInt()*.1; + + // Then move on to M611 - Humidity + int humidity_idx = payload.indexOf('M'); + p->humidity = payload.substring(humidity_idx+1, humidity_idx+3).toInt(); + + // Then move on to N10020 - air pressure + int pressure_idx = payload.indexOf('N'); + p->air_pressure = payload.substring(pressure_idx+1, pressure_idx+6).toInt()*.1; + + // Handle Gust + if(p->wind_speed > WindGust) { + WindGust = p->wind_speed; + debugln("Update WindGust"); + } + + if(loopcount >= 300) { + loopcount = 0; + WindGust = p->wind_speed; + debugln("10 min expired -> reset counter"); + } + // Increment loopcount for Gust + loopcount++; +} + +// Clear the packet before working on the next +void clear_pkt(WS3Packet* p) { + p->wind_dir = 0; + p->wind_angle = 0; + p->wind_freq = 0; + p->wind_speed = 0; + p->wind_speed_1m = 0; + p->wind_speed_5m = 0; + p->rain_bucket_cnt = 0; + p->rt_rain_bucket = 0; + p->rain_1m = 0; + p->rain_1h = 0; + p->rain_24h = 0; + p->temp_f = 0; + p->humidity = 0; + p->air_pressure = 0; +} + +#ifdef DEBUG +// Print the data +void print_weather(WS3Packet* p){ + Serial.print("Wind Direction (realtime): "); + Serial.println(p->wind_dir, DEC); + Serial.print("Wind direction angle : "); + Serial.print(p->wind_angle, DEC); + Serial.print(" degree "); + Serial.println(str_windir(p->wind_angle)); + Serial.print("Wind speed Frequency: "); + Serial.print(p->wind_freq, DEC); + Serial.println(" Hz"); + Serial.print("Wind speed: "); + Serial.print(p->wind_speed/10, DEC); + Serial.println(" m/s"); + Serial.print("Wind speed 1m: "); + Serial.print(p->wind_speed_1m/10, DEC); + Serial.println(" m/s"); + Serial.print("Wind speed 5m: "); + Serial.print(p->wind_speed_5m/10, DEC); + Serial.println(" m/s"); + Serial.print("temp_f: "); + Serial.print(p->temp_f, DEC); + Serial.println(" deg. C."); + + Serial.print("Rain buckets / buckets 1m: "); + Serial.print(p->rain_bucket_cnt, DEC); + Serial.print(" / "); + Serial.println(p->rt_rain_bucket, DEC); + + Serial.print("Rain 1m / 1H / 24H: "); + Serial.print(p->rain_1m*0.1, DEC); + Serial.print(" / "); + Serial.print(p->rain_1h*0.1, DEC); + Serial.print(" / "); + Serial.print(p->rain_24h*.1, DEC); + Serial.println(" mm"); + + Serial.print("humidity: "); + Serial.print(p->humidity, DEC); + Serial.println(" %"); + + Serial.print("air_pressure: "); + Serial.print(p->air_pressure, DEC); + Serial.println(" hpa"); +} +#endif /* DEBUG */ + + +// Processing the packet. +bool process_packet(String pkt, WS3Packet* p) { + debugln("[D] process_packet - ALive!"); + debugln(pkt); + + // Allocate bytes for the payload + String payload; + payload.reserve(WS3_PKT_LEN-WS3_CHK_LEN); + + #ifdef DONT_CHKSUM + // everything after the * is checksum (2 char long) + unsigned long chksum; + #endif /* DONT_CHKSUM */ + + // Check if the 75rd character is * + if (pkt.charAt(CHK_SUM_DELINEATOR) != '*') { + debugln("Packed invalid; no * character at position 75!"); + return false; + #ifdef DONT_CHKSUM + } else { + // The character indicating the checksum is coming is in the correct place. Yay. + // Now, we need to pull the two ascii characters that are transmitted to us + // and turn them into a single byte. E.G. Char 3, Char D should convert to 0x3D. + // + // We can do this with the strtoul() function; we indicate that we wante base 16 + + chksum = strtoul(pkt.substring(CHK_SUM_DELINEATOR+1, CHK_SUM_DELINEATOR+2).c_str(),NULL,16); + } + #endif /* DONT_CHKSUM */ + + // We have the checksum, Now we can bother to get the payload + payload = pkt.substring(0, CHK_SUM_DELINEATOR); + + // And try to validate... + #ifndef DONT_CHKSUM + if(!validate_packet(payload, chksum)){ + debugln("invalid packet! :("); + return false; + } else { + debugln("Valid packet!"); + } + #endif /* DONT_CHKSUM */ + parse_packet(payload, p); + return true; +}