// 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/
// 
// 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 <FS.h>
#include <DNSServer.h> 
#ifndef ESP32
 #include <ESP8266mDNS.h>
#endif
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
//#include <AddrList.h>
// EspSoftwareSerial (or plain SoftwareSerial)
#ifndef ESP32
 #include <SoftwareSerial.h>
#endif
// MQTT
#include <PubSubClient.h>
// WiFiManager
#include <WiFiManager.h>
#include <ArduinoJson.h> // Version 5 ONLY (TODO upgrade this)

#ifdef ESP32
  #include <SPIFFS.h>
#endif /* ESP32 */

#include "ws3_defs.h"
#include "ws3_vars.h"

#ifndef ESP32
SoftwareSerial WS3(RXD2,TXD2);
#else
#define WS3 Serial2
#endif

WiFiClient espClient;
PubSubClient client(espClient);

// ws3_wifi.ino
void setupSpiffs();
void saveConfigCallback();

// ws3_ota.ino
void setup_ota();


// Setup the stuff.
void setup() {  
  #ifndef ESP32
  Serial.begin(57600);
  #else
  Serial.begin(115200);
  #endif
  while (!Serial) ; // wait for Arduino Serial Monitor to open
  Serial.println("\n\n");
  setupSpiffs();
  Serial.println("Weather Station 3 Adapter by Kiwi");
  #ifndef ESP32
  Serial.println(ESP.getFullVersion());
  #endif

  WiFiManager wm;

  wm.setSaveConfigCallback(saveConfigCallback);

  // Setup custom parameters
  WiFiManagerParameter custom_idx_windir ("idx1",   "Domoticz Index for Wind Virtual Module",          idx_windir,      4);
  WiFiManagerParameter custom_idx_temp   ("idx2",   "Domoticz Index for Temp/Hum/Baro Virtual Module", idx_temp,        4);
  WiFiManagerParameter custom_idx_rain   ("idx3",   "Domoticz Index for Rain Virtual Module",          idx_rain,        4);

  // Add all parameters
  wm.addParameter(&custom_idx_windir);
  wm.addParameter(&custom_idx_temp);
  wm.addParameter(&custom_idx_rain);

  // Automatic connect using saved stuff otherwise start as an AP to configure it
  if (!wm.autoConnect("ESP-Weather-Station")) {
    Serial.println("Failer to connect and hit timeout");
    delay(3000);
    // if we still have not connected restard and try again
    ESP.restart();
    delay(5000);
  }

  // Always start configportal for a little while (2 m)
  wm.setConfigPortalTimeout(30);
//  wm.startConfigPortal("ESP-Weather-Station","ESP");
  wm.startConfigPortal("ESP-Weather-Station");

  // If we get here we are connected !
  Serial.println("Connected to WiFi !");

  // Read the updated parameters
  strcpy(idx_windir,      custom_idx_windir.getValue());
  strcpy(idx_temp,        custom_idx_temp.getValue());
  strcpy(idx_rain,        custom_idx_rain.getValue());

  // Save the custom parameters to FS
  if (shouldSaveConfig) {
    Serial.println("Saving config to FS...");
    DynamicJsonBuffer jsonBuffer;

    JsonObject& json = jsonBuffer.createObject();
    json["idx_windir"]  = idx_windir;
    json["idx_temp"]    = idx_temp;
    json["idx_rain"]    = idx_rain;
    
    File configFile = SPIFFS.open("/config.json", "w");
    if (!configFile) {
      Serial.println("failed to open config file for writing");
    }

    json.prettyPrintTo(Serial);
    json.printTo(configFile);
    configFile.close();
    //end save
    shouldSaveConfig = false;
  }

  // Launch OTA stuff
  setup_ota();
  
  #if LWIP_IPV6
  Serial.printf("IPV6 is enabled\n");
  #else
  Serial.printf("IPV6 is not enabled\n");
  #endif
  Serial.print("My IP address: ");
  Serial.print(WiFi.localIP());
  Serial.print("/");
  Serial.print(WiFi.subnetMask());
  Serial.print(" GW:");
  Serial.println(WiFi.gatewayIP());

  // Start the Software Serial for WS3
  #ifndef ESP32
  WS3.begin(WS3_BAUD);
  #else
  // We use Serial2 on ESP32 (hardware)
  WS3.begin(WS3_BAUD, SERIAL_8N1, RXD2, TXD2);
  while (!WS3) ; // wait for Arduino Serial Monitor to open
  #endif
  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");
}

// 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() {
  // 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); 
 }
 ArduinoOTA.handle();
}
