#include <DHTesp.h>         // DHT for ESP32 library
#include <WiFi.h>           // WiFi control for ESP32
//#include <ThingsBoard.h>    // ThingsBoard SDK
#include "ThingsBoard.h"

#define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x])))))

#define WIFI_AP_NAME                  "Wokwi-GUEST"
#define WIFI_PASSWORD                 ""
#define THINGSBOARD_MQTT_SERVER       "thingsboard.cloud"
#define THINGSBOARD_MQTT_ACESSTOKEN   "M6XCmSnCokxk5cu3N1W"

#define SERIAL_DEBUG_BAUD    115200


WiFiClient espClient;
ThingsBoard tb(espClient);
int status = WL_IDLE_STATUS;

// Array with LEDs that should be controlled from ThingsBoard, one by one
uint8_t leds_control[] = { 26, 33, 25 };

DHTesp dht;
#define DHT_PIN 15

// Main application loop delay
int quant = 20;
// Initial period of LED cycling.
int led_delay = 1000;
// Period of sending a temperature/humidity data.
int send_delay = 2000;

// Time passed after LED was turned ON, milliseconds.
int led_passed = 0;
// Time passed after temperature/humidity data was sent, milliseconds.
int send_passed = 0;

// Set to true if application is subscribed for the RPC messages.
bool subscribed = false;
// LED number that is currenlty ON.
int current_led = 0;

float temperature = 0;
float humidity = 0;

RPC_Response processDelayChange(const RPC_Data &data)
{
  Serial.println("Received the set delay RPC method");

  // Process data

  led_delay = data;

  Serial.print("Set new delay: ");
  Serial.println(led_delay);

  return String(led_delay);
}

RPC_Response processGetDelay(const RPC_Data &data)
{
  Serial.println("Received the get value method");
  return String(led_delay);
}

RPC_Response processSetGpioState(const RPC_Data &data)
{
  Serial.println("Received the set GPIO RPC method");

  int pin = data["pin"]; 
  bool enabled = data["enabled"]; 

  Serial.print("Setting LED ");
  Serial.print(pin);
  Serial.print(" to state ");
  Serial.println(enabled);

  // Red: 26, Green: 33, Blue: 25
  digitalWrite(pin, enabled);

  return String("{\"" + String(pin) + "\": " + String(enabled?"true":"false") + "}");
}

RPC_Response processGetGpioState(const RPC_Data &data)
{
  Serial.println("Received the get GPIO RPC method");
  String respStr = "{";
  
  for (size_t i = 0; i < COUNT_OF(leds_control); ++i) {
    int pin = leds_control[i];
    Serial.print("Getting LED ");
    Serial.print(pin);
    Serial.print(" state ");
    bool ledState = digitalRead(pin);
    Serial.println(ledState);

    respStr += String("\"" + String(pin) + "\": " + String(ledState?"true":"false") + ", ");
  }  
  respStr = respStr.substring(0, respStr.length() - 2);
  respStr += "}";
  return respStr; 
}

// RPC handlers
RPC_Callback callbacks[] = {
  { "setValue",         processDelayChange },
  { "getValue",         processGetDelay },  
  { "setGpioStatus",    processSetGpioState },
  { "getGpioStatus",    processGetGpioState },
};

void setup() {
  Serial.begin(SERIAL_DEBUG_BAUD);
  WiFi.begin(WIFI_AP_NAME, WIFI_PASSWORD);
  InitWiFi();

  for (size_t i = 0; i < COUNT_OF(leds_control); ++i) {
    pinMode(leds_control[i], OUTPUT);
  }

  // Initialize temperature sensor
  dht.setup(DHT_PIN, DHTesp::DHT22);

}

void loop() 
{
  delay(quant);

  led_passed += quant;
  send_passed += quant;

  // Reconnect to WiFi, if needed
  if (WiFi.status() != WL_CONNECTED) {
    reconnect();
    return;
  }

  // Reconnect to ThingsBoard, if needed
  if (!tb.connected()) {
    subscribed = false;

    // Connect to the ThingsBoard
    Serial.print("Connecting MQTT to: ");
    Serial.print(THINGSBOARD_MQTT_SERVER);
    Serial.print(" with access token ");
    Serial.println(THINGSBOARD_MQTT_ACESSTOKEN);
    if (!tb.connect(THINGSBOARD_MQTT_SERVER, THINGSBOARD_MQTT_ACESSTOKEN)) {
      Serial.println("Failed to connect");
      return;
    }
  }

  // Subscribe for RPC, if needed
  if (!subscribed) {
    Serial.println("Subscribing for RPC... ");

    // Perform a subscription. All consequent data processing will happen in
    // callbacks as denoted by callbacks[] array.
    if (!tb.RPC_Subscribe(callbacks, COUNT_OF(callbacks))) {
      Serial.println("Failed to subscribe for RPC");
      return;
    }

    Serial.println("Subscribe done");
    subscribed = true;
  }  

  // Check if it is a time to send DHT22 temperature and humidity
  if (send_passed > send_delay) {
    Serial.println();
    Serial.print("Sending data... ");
    TempAndHumidity lastValues = dht.getTempAndHumidity();    

    if (isnan(lastValues.humidity) || isnan(lastValues.temperature)) {
      Serial.println("Failed to read from DHT sensor!");
    } else {
      temperature = lastValues.temperature;
      Serial.print("temperature: ");
      Serial.print(temperature);
      humidity = lastValues.humidity;
      Serial.print(" humidity: ");
      Serial.print(humidity);
      tb.sendTelemetryFloat("temperature", temperature);
      tb.sendTelemetryFloat("humidity", humidity);
    }
    send_passed = 0;
  }

  tb.loop();
}

void InitWiFi()
{
  Serial.println("Connecting to AP ...");
  // attempt to connect to WiFi network

  WiFi.begin(WIFI_AP_NAME, WIFI_PASSWORD);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println(WiFi.localIP());
  Serial.println("Connected to AP");
}

void reconnect() {
  // Loop until we're reconnected
  status = WiFi.status();
  if ( status != WL_CONNECTED) {
    WiFi.begin(WIFI_AP_NAME, WIFI_PASSWORD);
    while (WiFi.status() != WL_CONNECTED) {
      delay(500);
      Serial.print(".");
    }
    Serial.println(WiFi.localIP());
    Serial.println("Connected to AP");
  }
}