// Project 11: ESP32 + MQTT + Ubidots (cloud dashboard and cloud control)
// -----------------------------------------------------------------------------
// This is the "real IoT" step. The ESP32:
//   1. reads a DHT11 (temperature + humidity) and PUBLISHES it to the cloud
//      over MQTT, so you can watch it on a Ubidots dashboard from anywhere.
//   2. SUBSCRIBES to a cloud variable ("led") and switches an LED (or a relay)
//      when you flip a switch widget on that dashboard.
//
// It reuses two things you already built:
//   - the DHT11 from Project 9 (data on GPIO 4)
//   - an output on GPIO 26, like the LED/relay from Projects 5 and 7
//
// Libraries (install from the Arduino Library Manager):
//   - "Ubidots ESP32 MQTT"      (this pulls in PubSubClient)
//   - "DHT sensor library"      (Adafruit)
//   - "Adafruit Unified Sensor" (dependency of the DHT library)
// -----------------------------------------------------------------------------

#include "UbidotsEsp32Mqtt.h"
#include <Adafruit_Sensor.h>
#include <DHT.h>

// ============================ EDIT THIS BLOCK ================================
// Everything platform-specific lives here. To move to another MQTT platform
// later (for example Adafruit IO), you only change this section and the small
// publish/subscribe calls, not the whole sketch.

const char *WIFI_SSID = "REPLACE_WITH_YOUR_SSID";
const char *WIFI_PASS = "REPLACE_WITH_YOUR_PASSWORD";

// From Ubidots: Devices menu -> API Credentials -> "Default token".
const char *UBIDOTS_TOKEN = "REPLACE_WITH_YOUR_UBIDOTS_TOKEN";

// A name for THIS board in Ubidots. Give each student a unique label so
// everyone's data does not land on the same device.
const char *DEVICE_LABEL = "esp32-workshop";

// Variable names as they will appear in Ubidots.
const char *VAR_TEMPERATURE = "temperature";
const char *VAR_HUMIDITY    = "humidity";
const char *VAR_LED         = "led";   // the control variable we subscribe to

const int   PUBLISH_EVERY_MS = 5000;   // send readings every 5 seconds
// ============================================================================

#define DHTPIN   4       // DHT11 data pin (same as Project 9)
#define DHTTYPE  DHT11
#define LED_PIN  26      // output to switch (LED, or a relay IN pin from Project 7)

DHT dht(DHTPIN, DHTTYPE);
Ubidots ubidots(UBIDOTS_TOKEN);

unsigned long lastPublish = 0;

// Called automatically whenever the cloud pushes a new value of a variable we
// subscribed to (here: "led"). The payload is the value as text, e.g. "1".
void callback(char *topic, byte *payload, unsigned int length) {
  char value[8] = {0};
  unsigned int n = (length < sizeof(value) - 1) ? length : sizeof(value) - 1;
  memcpy(value, payload, n);
  float v = atof(value);

  bool on = (v >= 0.5);          // treat anything from ~1 as "on"
  digitalWrite(LED_PIN, on ? HIGH : LOW);
  Serial.printf("[CLOUD] led -> %s (raw \"%s\")\n", on ? "ON" : "OFF", value);
}

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

  Serial.println();
  Serial.println("==============================================");
  Serial.println(" Project 11: ESP32 + MQTT + Ubidots");
  Serial.println("==============================================");
  Serial.printf ("DHT11 data -> GPIO %d\n", DHTPIN);
  Serial.printf ("Output      -> GPIO %d\n", LED_PIN);

  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);
  dht.begin();

  // Connect Wi-Fi and the MQTT broker, then subscribe to the control variable.
  ubidots.setDebug(true);                        // prints connection details
  ubidots.connectToWifi(WIFI_SSID, WIFI_PASS);
  ubidots.setCallback(callback);
  ubidots.setup();
  ubidots.reconnect();
  ubidots.subscribeLastValue(DEVICE_LABEL, VAR_LED);

  Serial.println("Connected. Publishing readings and listening for commands.");
  Serial.println("----------------------------------------------");
}

void loop() {
  // Keep the MQTT connection alive; re-subscribe if we dropped and reconnected.
  if (!ubidots.connected()) {
    ubidots.reconnect();
    ubidots.subscribeLastValue(DEVICE_LABEL, VAR_LED);
  }

  // Publish the sensor readings on a timer, without blocking (Day 1 idea:
  // millis() instead of delay(), so the MQTT client stays responsive).
  if (millis() - lastPublish > PUBLISH_EVERY_MS) {
    float t = dht.readTemperature();
    float h = dht.readHumidity();

    if (isnan(t) || isnan(h)) {
      Serial.println("[DHT] Failed to read, skipping this publish");
    } else {
      ubidots.add(VAR_TEMPERATURE, t);
      ubidots.add(VAR_HUMIDITY, h);
      ubidots.publish(DEVICE_LABEL);
      Serial.printf("[PUB] %s: %.1f C, %s: %.1f %%\n",
                    VAR_TEMPERATURE, t, VAR_HUMIDITY, h);
    }
    lastPublish = millis();
  }

  ubidots.loop();   // lets the library process incoming cloud messages
}
