/*
 * WetterStation
 * Version: 0.0
 * Author: Hendrik Langer <hendrik+dev@xd0.de>
 */

#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>
extern "C" {
#include <espnow.h>
}

#include <ESP8266HTTPClient.h>
#include <ArduinoJson.h>

#include <Wire.h>
#include <SPI.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BMP085_U.h>
#include <DHT.h>

#include <SDS011.h>

const char* server = "ingress.opensensemap.org";
//const char* ssid = "Freifunk";
//const char* password = "";

constexpr unsigned int postingInterval = 60000; //Uploadintervall in Millisekunden
constexpr unsigned int powerbankInterval = 10000;
constexpr unsigned int dhcp_interval = 60*60*1000;

//senseBox ID
#define SENSEBOX_ID "5a9e9e38f55bff001a494877"

//Sensor IDs
// Temperature
#define SENSOR1_ID "5a9e9e38f55bff001a49487e"
// Humidity
#define SENSOR2_ID "5a9e9e38f55bff001a49487d"
// Pressure
#define SENSOR3_ID "5a9e9e38f55bff001a49487c"
// PM10
#define SENSOR4_ID "5a9e9e38f55bff001a49487b"
// PM2.5
#define SENSOR5_ID "5a9e9e38f55bff001a49487a"
// Radioactivity
#define SENSOR6_ID "5a9e9e38f55bff001a494879"
// Voltage
#define SENSOR7_ID "5a9e9e38f55bff001a494878"
// RSSI
#define SENSOR8_ID "5a9eddb1f55bff001a51de52"


static constexpr uint8_t BMP_SCL = D4;
static constexpr uint8_t BMP_SDA = D3;

static constexpr uint8_t DHT22_PIN = D7;
static constexpr uint8_t DHTTYPE = DHT22; // DHT 22 (AM2302)

static constexpr uint8_t SDS_TX = D1;
static constexpr uint8_t SDS_RX = D2;

static constexpr uint8_t GEIGER_PIN = D6;
static constexpr float CONV_FACTOR = 0.008120;
static constexpr float OWN_BACKGROUND_CPS = 0;  // documentation says 0.2 (make sure value doesn't get negative if subtracting!)
static constexpr unsigned long logging_period_ms = 60000;

ADC_MODE(ADC_VCC);

ESP8266WiFiMulti wifiMulti;

Adafruit_BMP085_Unified bmp = Adafruit_BMP085_Unified(10085);
DHT dht(DHT22_PIN, DHTTYPE);
SDS011 sds;
volatile unsigned long geiger_counts = 0;
unsigned long geiger_previousMillis;
unsigned long last_wifi_activity = 0;
unsigned long last_dhcp = 0;
IPAddress ip, dns, gateway, subnet;
char ssid[64];
char password[64];

struct __attribute__((packed)) SENSOR_DATA {
  float temperature;
  float humidity;
  float pressure;
  float temp2;
  float p10;
  float p25;
  unsigned long cpm;
  float radioactivity;
  float voltage;
  float rssi;
} sd;


void sendESPNOW() {
//  unsigned long espnowmillis = millis();
  uint8_t remoteMac[] = {0x5C, 0xCF, 0x7F, 0x5, 0xFD, 0xF0};
  #define WIFI_CHANNEL 1

  WiFi.forceSleepWake();
  delay(1);  // yield();
  digitalWrite(LED_BUILTIN, LOW);
  WiFi.persistent(false); // don't load and save credentials to flash
  WiFi.mode(WIFI_STA);
  if (esp_now_init()==0) {
    if(esp_now_set_self_role(ESP_NOW_ROLE_CONTROLLER)==0){

      esp_now_add_peer(remoteMac, ESP_NOW_ROLE_SLAVE, WIFI_CHANNEL, NULL, 0);
      u8 bs[sizeof(sd)];
      memcpy(bs, &sd, sizeof(sd));
      for (int i=0; i<20; i++)
        esp_now_send(NULL, bs, sizeof(sd)); // max ESP_NOW_MAX_DATA_LEN

    } else {
      Serial.println("error configuring ESP NOW");
    }
  } else {
    Serial.println("error initializing ESP NOW");
  }
  last_wifi_activity = millis();
  esp_now_deinit();
  WiFi.mode(WIFI_OFF);
  WiFi.forceSleepBegin();
  delay(1); // yield();
  digitalWrite(LED_BUILTIN, HIGH);
//  Serial.printf("sendESPNOW() took %d ms\n", millis()-espnowmillis);
}


void ICACHE_FLASH_ATTR sendValues() {
  sd.temperature = dht.readTemperature();
  sd.humidity = dht.readHumidity();

  bmp.getPressure(&(sd.pressure));
  sd.pressure /= 100;
  bmp.getTemperature(&(sd.temp2));

  int sds_error = sds.read(&(sd.p25), &(sd.p10));
  if(!sds_error) {
//    Serial.println("SDS011 updated.");
  } else {
//    Serial.println("SDS011 no new values.");
  }

//  sds.sleep();

  sd.cpm = 0;
  sd.radioactivity = -1;
  if (millis() - geiger_previousMillis > logging_period_ms) {
    sd.cpm = geiger_counts * 60000 / (millis() - geiger_previousMillis);
    unsigned long constexpr own_cpm = OWN_BACKGROUND_CPS * 60;
    geiger_previousMillis = millis();
    geiger_counts = 0;
    sd.radioactivity = (sd.cpm - own_cpm) * CONV_FACTOR;
//    Serial.println(cpm);
  } else {
//    Serial.println("Geiger Counter: no new value");
  }

                  Serial.printf("Temperature  : %6.2f°C    (DHT22)\n", sd.temperature);
                  Serial.printf("Humidity     : %6.2f%%     (DHT22)\n", sd.humidity);
                  Serial.printf("Temperature  : %6.2f°C    (BMP180)\n", sd.temp2);
                  Serial.printf("Pressure     : %6.2fhPa   (BMP180)\n", sd.pressure);
  if (!sds_error) Serial.printf("Particles 10 : %6.2fµg/m³ (SDS011)\n", sd.p10);
  if (!sds_error) Serial.printf("Particles 2.5: %6.2fµg/m³ (SDS011)\n", sd.p25);
  if (sd.cpm > 0)    Serial.printf("Radiation    : %6.2fµSv/h (J305)\n", sd.radioactivity);

  DynamicJsonBuffer jsonBuffer;
  JsonArray& array = jsonBuffer.createArray();

  JsonObject& temperatureObject = array.createNestedObject();
  temperatureObject["sensor"] = SENSOR1_ID;
  temperatureObject["value"] = sd.temperature;

  JsonObject& humidityObject = array.createNestedObject();
  humidityObject["sensor"] = SENSOR2_ID;
  humidityObject["value"] = sd.humidity;

  JsonObject& pressureObject = array.createNestedObject();
  pressureObject["sensor"] = SENSOR3_ID;
  pressureObject["value"] = sd.pressure;

  if (!sds_error) {
    JsonObject& pm10Object = array.createNestedObject();
    pm10Object["sensor"] = SENSOR4_ID;
    pm10Object["value"] = sd.p10;

    JsonObject& pm25Object = array.createNestedObject();
    pm25Object["sensor"] = SENSOR5_ID;
    pm25Object["value"] = sd.p25;

  }

  if (sd.cpm > 0) {
    JsonObject& cpmObject = array.createNestedObject();
    cpmObject["sensor"] = SENSOR6_ID;
    cpmObject["value"] = sd.radioactivity;
  }

  JsonObject& voltageObject = array.createNestedObject();
  voltageObject["sensor"] = SENSOR7_ID;
  voltageObject["value"] = ESP.getVcc()/1024.0;

  JsonObject& rssiObject = array.createNestedObject();
  rssiObject["sensor"] = SENSOR8_ID;
  rssiObject["value"] = WiFi.RSSI();


  char buffer[500];
  array.printTo(buffer, sizeof(buffer));

  WiFi.forceSleepWake();
  delay(1);  // yield();
  WiFi.persistent(false); // don't load and save credentials to flash
  WiFi.mode(WIFI_STA);
  wifiMulti.addAP("nether.net", "password1");
  wifiMulti.addAP("LNet", "password2");
  wifiMulti.addAP("Freifunk", "");

  if ( ip != INADDR_NONE && dns != INADDR_NONE && gateway != INADDR_NONE && subnet != INADDR_NONE
       && ((ip[0] == 192 && ip[1] == 168) || (ip[0] == 172 && ip[1] == 16))
       && strlen(ssid) > 0 && strlen(password) > 0
       && (millis() - last_dhcp < dhcp_interval)
     ) {

    Serial.println("static ip");
    WiFi.config(ip, dns, gateway, subnet);
    WiFi.begin(ssid, password);
    int tries = 0;
    constexpr unsigned int retry_delay = 500;
    while (WiFi.status() != WL_CONNECTED) {
      tries++;
      Serial.print(".");
      if (tries*retry_delay >= powerbankInterval) {
        Serial.println(" [ERROR]");
        Serial.println("Retrying in 10 seconds..");
        sendESPNOW();
        delay(powerbankInterval);
        ESP.restart();
      }
      delay(retry_delay);
    }
    Serial.println(" [CONNECTED, static]");
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());

  } else {
    Serial.println("dhcp");
    int tries = 0;
    constexpr unsigned int retry_delay = 500;
    while (wifiMulti.run() != WL_CONNECTED) {
      tries++;
      Serial.print(".");
      if (tries*retry_delay >= powerbankInterval) {
        Serial.println(" [ERROR]");
        Serial.println("Retrying in 10 seconds..");
        sendESPNOW();
        delay(powerbankInterval);
        ESP.restart();
      }
      delay(retry_delay);
    }
    Serial.println(" [CONNECTED, dhcp]");
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());

    ip = WiFi.localIP();
    dns = WiFi.dnsIP();
    gateway = WiFi.gatewayIP();
    subnet = WiFi.subnetMask();
    strncpy(ssid, WiFi.SSID().c_str(), 64);
    strncpy(password, WiFi.psk().c_str(), 64);
    last_dhcp = millis();
  }


  int httpCode = 0;
  for (int tries=0; tries<3 && httpCode != HTTP_CODE_CREATED; tries++) {

    HTTPClient httpclient;
    char url[100];
    sprintf(url, "http://%s/boxes/%s/data", server, SENSEBOX_ID);

    httpclient.begin(url);
    httpclient.addHeader("Content-Type", "application/json");

    httpCode = httpclient.POST(buffer);

    if (httpCode > 0) {
      if (httpCode == HTTP_CODE_CREATED) {
        httpclient.writeToStream(&Serial);
        Serial.println();
      } else {
        Serial.printf("[HTTP] POST... failed, error: %s\n", httpclient.errorToString(httpCode).c_str());
      }
    }

    httpclient.end();

  }

  last_wifi_activity = millis();
  WiFi.disconnect();
  WiFi.mode(WIFI_OFF);
  WiFi.forceSleepBegin();
  delay(1); // yield();

}


void ICACHE_RAM_ATTR ISR_geiger_impulse() {
  geiger_counts++;
}


void setup() {
  Serial.begin(115200);

  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, HIGH);  // turn OFF board led

  sds.begin(SDS_TX, SDS_RX);
  sds.mode_mon_300();

  Wire.begin(BMP_SDA, BMP_SCL);
  if (!bmp.begin(BMP085_MODE_STANDARD)) {
    Serial.println("No valid BMP085 sensor!");
  }

  dht.begin();

  pinMode(GEIGER_PIN, INPUT);
  attachInterrupt(digitalPinToInterrupt(GEIGER_PIN), ISR_geiger_impulse, FALLING);

  Serial.println("ready."); Serial.flush();
}

void loop() {
  //Serial.println(millis() - last_wifi_activity);
  sendValues();

  /* keep powerbank happy */
  for(int i=0; i<(postingInterval/powerbankInterval); i++) {
    delay(powerbankInterval);
    //Serial.println(millis() - last_wifi_activity);
    sendESPNOW();
  }
  //delay(powerbankInterval);
}