Mahesh Yadav
Published © CC BY-NC-SA

Smart Home Control Tablet using CrowPanel Advance 7" Display

Build a wireless, portable smart-home control tablet

AdvancedFull instructions provided2 days91

Things used in this project

Story

Read more

Custom parts and enclosures

Smart Home Control Tablet using CrowPanel Advance 7" Display

Schematics

SmartHome Tablet project - RoomHub Device ESP32-C6

Smart Home Control Tablet using CrowPanel Advance 7" Display

Code

SmartHome Tablet (CrowPanel HMI DIsplay ESP32-S3 code)

C/C++
//=============================================================================//
// Project/Tutorial       - SmartHome Control Tablet using CrowPanel Advance 7" Display
// Author                 - https://www.hackster.io/maheshyadav216
// Hardware               - Elecrow CrowPanel Advance 7" HMI AI Display    
// Sensors                - NA
// Software               - Visual Studio Code, ESP-IDF Extension
// GitHub Repo of Project - https://github.com/maheshyadav216/Project-SmartHome-Control-Tablet-using-CrowPanel-Advance-AI-Display 
// Code last Modified on  - 24/12/2025
// Code/Content license   - (CC BY-NC-SA 4.0) https://creativecommons.org/licenses/by-nc-sa/4.0/
//============================================================================//

#include "waveshare_rgb_lcd_port.h"
#include "ui.h"
#include "driver/gpio.h"
#include "driver/i2c.h"
#include <stdio.h>
#include <time.h>
#include <string.h>
#include "wifi_manager.h"
#include "mqtt_manager.h"
#include "ui_mqtt_bridge.h"
#include "esp_sntp.h"
#include "esp_timer.h"

// ---------------------------------------------------------------------
// I2C Config
// ---------------------------------------------------------------------
#define I2C_MASTER_SDA_IO          15
#define I2C_MASTER_SCL_IO          16
#define I2C_MASTER_TX_BUF_DISABLE  0
#define I2C_MASTER_RX_BUF_DISABLE  0
#define I2C_MASTER_NUM             I2C_NUM_0
#define I2C_MASTER_FREQ_HZ         100000

// Device addresses
#define DEVICE_ADDR_BACKLIGHT  0x30
#define DEVICE_ADDR_TOUCH      0x5D
#define DEVICE_ADDR_RTC        0x51

// PCF8563 registers
#define PCF8563_REG_CTRL1    0x00
#define PCF8563_REG_CTRL2    0x01
#define PCF8563_REG_SEC      0x02
#define PCF8563_REG_MIN      0x03
#define PCF8563_REG_HOUR     0x04
#define PCF8563_REG_DAY      0x05
#define PCF8563_REG_WDAY     0x06
#define PCF8563_REG_MONTH    0x07
#define PCF8563_REG_YEAR     0x08

// ---------------------------------------------------------------------
// BCD helpers
// ---------------------------------------------------------------------
static uint8_t dec2bcd(uint8_t dec) { return ((dec / 10) << 4) | (dec % 10); }
static uint8_t bcd2dec(uint8_t bcd) { return ((bcd >> 4) * 10) + (bcd & 0x0F); }

// ---------------------------------------------------------------------
// I2C INIT
// ---------------------------------------------------------------------
void i2c_master_init()
{
    i2c_config_t conf = {
        .mode = I2C_MODE_MASTER,
        .sda_io_num = I2C_MASTER_SDA_IO,
        .scl_io_num = I2C_MASTER_SCL_IO,
        .sda_pullup_en = GPIO_PULLUP_ENABLE,
        .scl_pullup_en = GPIO_PULLUP_ENABLE,
        .master.clk_speed = I2C_MASTER_FREQ_HZ
    };
    ESP_ERROR_CHECK(i2c_param_config(I2C_MASTER_NUM, &conf));
    ESP_ERROR_CHECK(i2c_driver_install(I2C_MASTER_NUM, conf.mode,
                                       I2C_MASTER_RX_BUF_DISABLE,
                                       I2C_MASTER_TX_BUF_DISABLE, 0));
}

bool i2c_scan_address(uint8_t address)
{
    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, (address << 1) | I2C_MASTER_WRITE, true);
    i2c_master_stop(cmd);
    esp_err_t r = i2c_master_cmd_begin(I2C_MASTER_NUM, cmd, 50 / portTICK_PERIOD_MS);
    i2c_cmd_link_delete(cmd);
    return r == ESP_OK;
}

esp_err_t i2c_write_byte(uint8_t dev, uint8_t data)
{
    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, (dev << 1) | I2C_MASTER_WRITE, true);
    i2c_master_write_byte(cmd, data, true);
    i2c_master_stop(cmd);
    esp_err_t r = i2c_master_cmd_begin(I2C_MASTER_NUM, cmd, 50 / portTICK_PERIOD_MS);
    i2c_cmd_link_delete(cmd);
    return r;
}

// ---------------------------------------------------------------------
// RTC FUNCTIONS
// ---------------------------------------------------------------------
static esp_err_t rtc_write_reg(uint8_t reg, uint8_t data)
{
    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, (DEVICE_ADDR_RTC << 1) | I2C_MASTER_WRITE, true);
    i2c_master_write_byte(cmd, reg, true);
    i2c_master_write_byte(cmd, data, true);
    i2c_master_stop(cmd);
    esp_err_t r = i2c_master_cmd_begin(I2C_MASTER_NUM, cmd, 200 / portTICK_PERIOD_MS);
    i2c_cmd_link_delete(cmd);
    return r;
}

static esp_err_t rtc_read_regs(uint8_t reg, uint8_t *data, size_t len)
{
    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, (DEVICE_ADDR_RTC << 1) | I2C_MASTER_WRITE, true);
    i2c_master_write_byte(cmd, reg, true);
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, (DEVICE_ADDR_RTC << 1) | I2C_MASTER_READ, true);
    i2c_master_read(cmd, data, len, I2C_MASTER_LAST_NACK);
    i2c_master_stop(cmd);
    esp_err_t r = i2c_master_cmd_begin(I2C_MASTER_NUM, cmd, 200 / portTICK_PERIOD_MS);
    i2c_cmd_link_delete(cmd);
    return r;
}

static esp_err_t rtc_init(void)
{
    if (rtc_write_reg(PCF8563_REG_CTRL1, 0x00) != ESP_OK) return ESP_FAIL;
    return rtc_write_reg(PCF8563_REG_CTRL2, 0x00);
}

static esp_err_t rtc_set_time(const struct tm *t)
{
    uint8_t d[7];
    d[0] = dec2bcd(t->tm_sec) & 0x7F;
    d[1] = dec2bcd(t->tm_min) & 0x7F;
    d[2] = dec2bcd(t->tm_hour) & 0x3F;
    d[3] = dec2bcd(t->tm_mday) & 0x3F;
    d[4] = t->tm_wday & 0x07;
    d[5] = dec2bcd(t->tm_mon + 1) & 0x1F;
    d[6] = dec2bcd(t->tm_year % 100);

    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, (DEVICE_ADDR_RTC << 1) | I2C_MASTER_WRITE, true);
    i2c_master_write_byte(cmd, PCF8563_REG_SEC, true);
    i2c_master_write(cmd, d, 7, true);
    i2c_master_stop(cmd);

    esp_err_t r = i2c_master_cmd_begin(I2C_MASTER_NUM, cmd, 200 / portTICK_PERIOD_MS);
    i2c_cmd_link_delete(cmd);
    return r;
}

static esp_err_t rtc_get_time(struct tm *t)
{
    uint8_t d[7];
    if (rtc_read_regs(PCF8563_REG_SEC, d, 7) != ESP_OK) return ESP_FAIL;

    t->tm_sec  = bcd2dec(d[0] & 0x7F);
    t->tm_min  = bcd2dec(d[1] & 0x7F);
    t->tm_hour = bcd2dec(d[2] & 0x3F);
    t->tm_mday = bcd2dec(d[3] & 0x3F);
    t->tm_wday = d[4] & 0x07;
    t->tm_mon  = bcd2dec(d[5] & 0x1F) - 1;
    t->tm_year = bcd2dec(d[6]) + 100; // 2000+
    return ESP_OK;
}

// ---------------------------------------------------------------------
// SNTP SYNC (silent, timeout 30s, update RTC once) - TZ fixed (IST)
// ---------------------------------------------------------------------
static bool sntp_sync_rtc_once(void)
{
    // Set timezone to IST (UTC+5:30). POSIX form "IST-5:30" works on ESP-IDF.
    setenv("TZ", "IST-5:30", 1);
    tzset();

    // Configure SNTP (silent, polling)
    esp_sntp_setoperatingmode(SNTP_OPMODE_POLL);
    esp_sntp_setservername(0, "pool.ntp.org");
    esp_sntp_init();

    const int64_t start = esp_timer_get_time() / 1000; // ms
    const int64_t timeout_ms = 30000;

    while ((esp_timer_get_time() / 1000 - start) < timeout_ms)
    {
        time_t now = 0;
        struct tm timeinfo = {0};
        time(&now);
        localtime_r(&now, &timeinfo);

        // If year looks valid ( > 1970 ), break and use it
        if (timeinfo.tm_year > (1970 - 1900)) {
            // write the local time (IST) to RTC once
            rtc_set_time(&timeinfo);
            // Stop SNTP quietly
            esp_sntp_stop();
            return true;
        }

        vTaskDelay(pdMS_TO_TICKS(500));
    }

    // Timeout — stop SNTP and keep existing RTC
    esp_sntp_stop();
    return false;
}


// ---------------------------------------------------------------------
// RTC Task — SAME AS BEFORE
// ---------------------------------------------------------------------
void rtc_display_task(void *arg)
{
    const char *days[] = {
        "Sunday","Monday","Tuesday","Wednesday",
        "Thursday","Friday","Saturday"
    };

    struct tm tm_now;

    while (1)
    {
        if (rtc_get_time(&tm_now) == ESP_OK)
        {
            if (lvgl_port_lock(-1))
            {
                char buf1[16], buf2[16], buf3[32];

                sprintf(buf1, "%02d:%02d:%02d",
                        tm_now.tm_hour, tm_now.tm_min, tm_now.tm_sec);
                sprintf(buf2, "%s", days[tm_now.tm_wday]);
                sprintf(buf3, "%04d-%02d-%02d",
                        tm_now.tm_year + 1900,
                        tm_now.tm_mon + 1,
                        tm_now.tm_mday);

                lv_label_set_text(uic_time, buf1);
                lv_label_set_text(uic_day, buf2);
                lv_label_set_text(uic_date, buf3);

                lvgl_port_unlock();
            }
        }
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

// ---------------------------------------------------------------------
// WiFi callback
// ---------------------------------------------------------------------
static void on_wifi_got_ip(void)
{
    // First SNTP sync → update RTC once
    sntp_sync_rtc_once();

    // MQTT
    mqtt_manager_start("mqtt://192.168.0.154:1883",
                       "mqttuser", "mqttpassword");

    ui_mqtt_bridge_init();
}


// ---------------------------------------------------------------------
// MAIN — minimal changes
// ---------------------------------------------------------------------
void app_main()
{
    ESP_LOGI("MAIN", "Starting System...");

    i2c_master_init();
    vTaskDelay(pdMS_TO_TICKS(50));

    i2c_write_byte(DEVICE_ADDR_BACKLIGHT, 0x18);
    i2c_write_byte(DEVICE_ADDR_BACKLIGHT, 0x10);

    if (rtc_init() == ESP_OK)
        ESP_LOGI("MAIN", "RTC OK");


    waveshare_esp32_s3_rgb_lcd_init();

    if (lvgl_port_lock(-1))
    {
        ui_init();

        wifi_manager_init("xxxx", "xxxx", on_wifi_got_ip);

        lv_label_set_text(uic_time, "00:00:00");
        lv_label_set_text(uic_day,  "Loading...");
        lv_label_set_text(uic_date, "0000-00-00");

        lvgl_port_unlock();
    }

    xTaskCreate(rtc_display_task, "rtc_display_task", 4096, NULL, 5, NULL);

    ESP_LOGI("MAIN", "System ready!");
}

SmartHome Tablet project - RoomHub Device ESP32-C6 Arduino code

C/C++
//=============================================================================//
// Project/Tutorial       - SmartHome Control Tablet using CrowPanel Advance 7" Display
// Device                 - RoomHub
// Author                 - https://www.hackster.io/maheshyadav216
// Hardware               - ESP32-C6,4 Channel Relay module 5V     
// Sensors                - SEN0500 Fermion Multifunctional Environmental Sensor
// Software               - Arduino IDE
// GitHub Repo of Project - https://github.com/maheshyadav216/Project-SmartHome-Control-Tablet-using-CrowPanel-Advance-AI-Display 
// Code last Modified on  - 24/12/2025
// Code/Content license   - (CC BY-NC-SA 4.0) https://creativecommons.org/licenses/by-nc-sa/4.0/
//============================================================================//

/* RoomHub (final + unified auto control) — compact HA discovery + robust MQTT publishes
   ESP32-C6 + SEN0500 + 4-relay (active LOW)
   Mapping (confirmed):
     AC  -> GPIO 3
     Fan -> GPIO 2
     TV  -> GPIO 11
     Bulb-> GPIO 10
   
   AUTO CLIMATE CONTROL:
   - Temperature: AC ON at 27°C, AC OFF at 26°C (hysteresis)
   - Humidity: FAN ON at 54%, FAN OFF at 53% (hysteresis)
   - Light: BULB ON at <1%, BULB OFF at >2% (hysteresis)
   - Single unified control: home/roomhub/auto/all/cmd
*/

#include <WiFi.h>
#include <PubSubClient.h>
#include <Wire.h>
#include "DFRobot_EnvironmentalSensor.h"

// ---------- Edit these ----------
const char* WIFI_SSID = "xxxx";
const char* WIFI_PASS = "xxxx";

const char* MQTT_HOST = "192.168.0.154";
const uint16_t MQTT_PORT = 1883;
const char* MQTT_USER = "mqttuser";
const char* MQTT_PASS = "mqttpassword";
// ---------------------------------

// Climate control thresholds
const float TEMP_HIGH_THRESHOLD = 27.0;  // AC turns ON
const float TEMP_LOW_THRESHOLD  = 26.0;  // AC turns OFF
const float HUMI_HIGH_THRESHOLD = 54.0;  // FAN turns ON
const float HUMI_LOW_THRESHOLD  = 53.0;  // FAN turns OFF
const float LIGHT_LOW_THRESHOLD = 1.0;   // BULB turns ON (below 1%)
const float LIGHT_HIGH_THRESHOLD = 2.0;  // BULB turns OFF (above 2%)

// Single unified auto control flag
bool auto_control_enabled = true;

// Relay pins (active-low)
const uint8_t RELAY_AC   = 3;
const uint8_t RELAY_FAN  = 2;
const uint8_t RELAY_TV   = 11;
const uint8_t RELAY_BULB = 10;

// Sensor
DFRobot_EnvironmentalSensor sensor(0x22, &Wire);

// Topics
const char* T_TEMP  = "home/roomhub/sensor/temperature";
const char* T_HUMI  = "home/roomhub/sensor/humidity";
const char* T_LIGHT = "home/roomhub/sensor/light";

const char* T_AC_CMD   = "home/roomhub/relay/ac/cmd";
const char* T_FAN_CMD  = "home/roomhub/relay/fan/cmd";
const char* T_TV_CMD   = "home/roomhub/relay/tv/cmd";
const char* T_BULB_CMD = "home/roomhub/relay/bulb/cmd";

const char* T_AC_STATE   = "home/roomhub/relay/ac/state";
const char* T_FAN_STATE  = "home/roomhub/relay/fan/state";
const char* T_TV_STATE   = "home/roomhub/relay/tv/state";
const char* T_BULB_STATE = "home/roomhub/relay/bulb/state";

// Unified auto control topic (single switch for all automations)
const char* T_AUTO_ALL_CMD   = "home/roomhub/auto/all/cmd";
const char* T_AUTO_ALL_STATE = "home/roomhub/auto/all/state";

WiFiClient espClient;
PubSubClient mqtt(espClient);

// timing
unsigned long lastSensorMillis = 0;
const unsigned long SENSOR_INTERVAL_MS = 5000;

// last published strings to avoid duplicates
String last_temp_str = "";
String last_humi_str = "";
String last_light_str = "";

// relay state memory
bool relay_ac_on = false;
bool relay_fan_on = false;
bool relay_tv_on = false;
bool relay_bulb_on = false;

// Manual override flags (per device)
bool ac_manual_override = false;
bool fan_manual_override = false;
bool bulb_manual_override = false;

// ---------------- relay helpers ----------------
void setRelayAndPublish(uint8_t pin, bool on, const char* state_topic, bool &state_var, bool is_auto = false) {
  if (on) digitalWrite(pin, LOW);   // active low
  else    digitalWrite(pin, HIGH);
  state_var = on;
  bool ok = mqtt.publish(state_topic, on ? "ON" : "OFF", true);
  
  const char* source = is_auto ? "[AUTO]" : "[MANUAL]";
  Serial.printf("%s publish %s -> %s %s\n", source, state_topic, on ? "ON":"OFF", ok ? "OK":"FAIL");
}

void relayOn(uint8_t pin, const char* state_topic, bool &state_var, bool is_auto = false) {
  setRelayAndPublish(pin, true, state_topic, state_var, is_auto);
}
void relayOff(uint8_t pin, const char* state_topic, bool &state_var, bool is_auto = false) {
  setRelayAndPublish(pin, false, state_topic, state_var, is_auto);
}

// ---------------- helper: robust publish ----------------
bool robustPublish(const char* topic, const char* payload, bool retained = false) {
  bool ok = mqtt.publish(topic, payload, retained);
  mqtt.loop();
  delay(60);
  Serial.printf("PUB %s -> %s (%s)\n", topic, payload, ok ? "OK":"FAIL");
  return ok;
}

// ---------------- publish compact discovery ----------------
void publishDiscovery() {
  Serial.println("🔵 Sending compact Home Assistant discovery...");

  // --- SENSORS ---
  {
    char payload[256];
    snprintf(payload, sizeof(payload),
      "{\"name\":\"roomhub Temp\",\"state_topic\":\"%s\",\"unit_of_measurement\":\"°C\",\"device_class\":\"temperature\",\"unique_id\":\"roomhub_temp\"}",
      T_TEMP);
    robustPublish("homeassistant/sensor/roomhub_temperature/config", payload, true);
  }

  {
    char payload[256];
    snprintf(payload, sizeof(payload),
      "{\"name\":\"roomhub Humi\",\"state_topic\":\"%s\",\"unit_of_measurement\":\"%%\",\"device_class\":\"humidity\",\"unique_id\":\"roomhub_humi\"}",
      T_HUMI);
    robustPublish("homeassistant/sensor/roomhub_humidity/config", payload, true);
  }

  {
    char payload[256];
    snprintf(payload, sizeof(payload),
      "{\"name\":\"roomhub Light\",\"state_topic\":\"%s\",\"unit_of_measurement\":\"%%\",\"unique_id\":\"roomhub_light\"}",
      T_LIGHT);
    robustPublish("homeassistant/sensor/roomhub_light/config", payload, true);
  }

  // --- SWITCHES ---
  auto sendSwitchCompact = [&](const char* name, const char* cmd, const char* state, const char* uid) {
    char payload[256];
    snprintf(payload, sizeof(payload),
      "{\"name\":\"%s\",\"command_topic\":\"%s\",\"state_topic\":\"%s\",\"payload_on\":\"ON\",\"payload_off\":\"OFF\",\"unique_id\":\"%s\"}",
      name, cmd, state, uid);
    char topic[128];
    snprintf(topic, sizeof(topic), "homeassistant/switch/%s/config", uid);
    robustPublish(topic, payload, true);
  };

  sendSwitchCompact("AC",   T_AC_CMD,   T_AC_STATE,   "roomhub_ac");
  sendSwitchCompact("Fan",  T_FAN_CMD,  T_FAN_STATE,  "roomhub_fan");
  sendSwitchCompact("TV",   T_TV_CMD,   T_TV_STATE,   "roomhub_tv");
  sendSwitchCompact("Bulb", T_BULB_CMD, T_BULB_STATE, "roomhub_bulb");

  // Single unified auto control switch
  sendSwitchCompact("Auto Control", T_AUTO_ALL_CMD, T_AUTO_ALL_STATE, "roomhub_auto_all");

  Serial.println("🟢 Compact discovery published.");
}

// ---------------- publish sensor snapshot ----------------
void publishSensorSnapshot() {
  float t = sensor.getTemperature(TEMP_C);
  float h = sensor.getHumidity();
  float lux = sensor.getLuminousIntensity();
  float pct = constrain((lux / 800.0f) * 100.0f, 0.0f, 100.0f);

  char buf[32];

  dtostrf(t, 5, 2, buf);
  robustPublish(T_TEMP, buf, true);
  dtostrf(h, 5, 2, buf);
  robustPublish(T_HUMI, buf, true);
  dtostrf(pct, 5, 2, buf);
  robustPublish(T_LIGHT, buf, true);

  Serial.println("📤 Sensor snapshot published");
}

// ---------------- Auto Climate Control Logic ----------------
void autoClimateControl(float temperature, float humidity, float light_pct) {
  if (!auto_control_enabled) {
    return; // All automations disabled
  }

  // Temperature-based AC control with hysteresis
  if (!ac_manual_override) {
    if (temperature >= TEMP_HIGH_THRESHOLD && !relay_ac_on) {
      Serial.printf("🌡️  TEMP %.2f°C >= %.2f°C → AUTO AC ON\n", temperature, TEMP_HIGH_THRESHOLD);
      relayOn(RELAY_AC, T_AC_STATE, relay_ac_on, true);
    }
    else if (temperature <= TEMP_LOW_THRESHOLD && relay_ac_on) {
      Serial.printf("🌡️  TEMP %.2f°C <= %.2f°C → AUTO AC OFF\n", temperature, TEMP_LOW_THRESHOLD);
      relayOff(RELAY_AC, T_AC_STATE, relay_ac_on, true);
    }
  }

  // Humidity-based FAN control with hysteresis
  if (!fan_manual_override) {
    if (humidity >= HUMI_HIGH_THRESHOLD && !relay_fan_on) {
      Serial.printf("💧 HUMI %.2f%% >= %.2f%% → AUTO FAN ON\n", humidity, HUMI_HIGH_THRESHOLD);
      relayOn(RELAY_FAN, T_FAN_STATE, relay_fan_on, true);
    }
    else if (humidity <= HUMI_LOW_THRESHOLD && relay_fan_on) {
      Serial.printf("💧 HUMI %.2f%% <= %.2f%% → AUTO FAN OFF\n", humidity, HUMI_LOW_THRESHOLD);
      relayOff(RELAY_FAN, T_FAN_STATE, relay_fan_on, true);
    }
  }

  // Light-based BULB control with hysteresis
  if (!bulb_manual_override) {
    if (light_pct < LIGHT_LOW_THRESHOLD && !relay_bulb_on) {
      Serial.printf("💡 LIGHT %.2f%% < %.2f%% → AUTO BULB ON\n", light_pct, LIGHT_LOW_THRESHOLD);
      relayOn(RELAY_BULB, T_BULB_STATE, relay_bulb_on, true);
    }
    else if (light_pct > LIGHT_HIGH_THRESHOLD && relay_bulb_on) {
      Serial.printf("💡 LIGHT %.2f%% > %.2f%% → AUTO BULB OFF\n", light_pct, LIGHT_HIGH_THRESHOLD);
      relayOff(RELAY_BULB, T_BULB_STATE, relay_bulb_on, true);
    }
  }
}

// ---------------- MQTT callback ----------------
void mqttCallback(char* topic, byte* payload, unsigned int length) {
  String msg;
  for (unsigned int i = 0; i < length; i++) msg += (char)payload[i];

  Serial.printf("MQTT RX: %s -> %s\n", topic, msg.c_str());

  // Manual AC control
  if (String(topic) == T_AC_CMD) {
    ac_manual_override = true;  // User took manual control
    if (msg == "ON") relayOn(RELAY_AC, T_AC_STATE, relay_ac_on);
    else relayOff(RELAY_AC, T_AC_STATE, relay_ac_on);
  } 
  // Manual Fan control
  else if (String(topic) == T_FAN_CMD) {
    fan_manual_override = true;  // User took manual control
    if (msg == "ON") relayOn(RELAY_FAN, T_FAN_STATE, relay_fan_on);
    else relayOff(RELAY_FAN, T_FAN_STATE, relay_fan_on);
  } 
  // TV control (always manual)
  else if (String(topic) == T_TV_CMD) {
    if (msg == "ON") relayOn(RELAY_TV, T_TV_STATE, relay_tv_on);
    else relayOff(RELAY_TV, T_TV_STATE, relay_tv_on);
  } 
  // Manual Bulb control
  else if (String(topic) == T_BULB_CMD) {
    bulb_manual_override = true;  // User took manual control
    if (msg == "ON") relayOn(RELAY_BULB, T_BULB_STATE, relay_bulb_on);
    else relayOff(RELAY_BULB, T_BULB_STATE, relay_bulb_on);
  }
  // UNIFIED auto control enable/disable (controls ALL automations)
  else if (String(topic) == T_AUTO_ALL_CMD) {
    if (msg == "ON") {
      auto_control_enabled = true;
      // Clear all manual overrides when re-enabling automation
      ac_manual_override = false;
      fan_manual_override = false;
      bulb_manual_override = false;
      robustPublish(T_AUTO_ALL_STATE, "ON", true);
      Serial.println("✅ ALL AUTOMATIONS ENABLED (Temp/Humi/Light)");
    } else {
      auto_control_enabled = false;
      robustPublish(T_AUTO_ALL_STATE, "OFF", true);
      
      // Turn off AC if it was on
      if (relay_ac_on) relayOff(RELAY_AC, T_AC_STATE, relay_ac_on, true);
      
      // Turn off FAN if it was on
      if (relay_fan_on) relayOff(RELAY_FAN, T_FAN_STATE, relay_fan_on, true);
      
      // Turn off BULB if it was on
      if (relay_bulb_on) relayOff(RELAY_BULB, T_BULB_STATE, relay_bulb_on, true);

      Serial.println("❌ DISABLING AUTOMATIONS AND RESETTING RELAYS");
    }
  }
}

// ---------------- MQTT reconnect ----------------
unsigned long lastReconnectAttempt = 0;
bool mqttReconnect() {
  if (mqtt.connected()) return true;

  unsigned long now = millis();
  if (now - lastReconnectAttempt < 2000) return false;
  lastReconnectAttempt = now;

  String clientId = String("roomhub_") + String((uint64_t)ESP.getEfuseMac(), HEX);

  Serial.print("MQTT connecting... ");
  if (mqtt.connect(clientId.c_str(), MQTT_USER, MQTT_PASS)) {
    Serial.println("connected");

    // Subscribe to all command topics
    mqtt.subscribe(T_AC_CMD);
    mqtt.subscribe(T_FAN_CMD);
    mqtt.subscribe(T_TV_CMD);
    mqtt.subscribe(T_BULB_CMD);
    mqtt.subscribe(T_AUTO_ALL_CMD);  // Single unified auto control topic

    publishDiscovery();
    mqtt.loop();
    delay(200);
    publishSensorSnapshot();

    // Publish current relay states
    mqtt.publish(T_AC_STATE, relay_ac_on ? "ON" : "OFF", true);
    mqtt.loop(); delay(60);
    mqtt.publish(T_FAN_STATE, relay_fan_on ? "ON" : "OFF", true);
    mqtt.loop(); delay(60);
    mqtt.publish(T_TV_STATE, relay_tv_on ? "ON" : "OFF", true);
    mqtt.loop(); delay(60);
    mqtt.publish(T_BULB_STATE, relay_bulb_on ? "ON" : "OFF", true);
    mqtt.loop(); delay(60);

    // Publish unified auto control state
    mqtt.publish(T_AUTO_ALL_STATE, auto_control_enabled ? "ON" : "OFF", true);
    mqtt.loop(); delay(60);

    return true;
  } else {
    Serial.printf("failed, rc=%d\n", mqtt.state());
    return false;
  }
}

// ---------------- WiFi connect ----------------
void wifiConnect() {
  if (WiFi.status() == WL_CONNECTED) return;
  Serial.print("Connecting WiFi...");
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  unsigned long start = millis();
  while (WiFi.status() != WL_CONNECTED) {
    delay(300);
    Serial.print(".");
    if (millis() - start > 20000) {
      Serial.println("\nWiFi connect timeout — restarting.");
      ESP.restart();
    }
  }
  Serial.println("\nWiFi connected: " + WiFi.localIP().toString());
}

// ---------------- setup ----------------
void setup() {
  Serial.begin(115200);
  delay(200);

  pinMode(RELAY_AC, OUTPUT);
  pinMode(RELAY_FAN, OUTPUT);
  pinMode(RELAY_TV, OUTPUT);
  pinMode(RELAY_BULB, OUTPUT);

  // Ensure all relays start OFF
  digitalWrite(RELAY_AC, HIGH);
  digitalWrite(RELAY_FAN, HIGH);
  digitalWrite(RELAY_TV, HIGH);
  digitalWrite(RELAY_BULB, HIGH);

  Wire.begin(6, 7); // SDA=6 SCL=7 for ESP32-C6

  if (sensor.begin() != 0) Serial.println("❌ SEN0500 init FAILED");
  else Serial.println("✅ SEN0500 OK");

  wifiConnect();

  mqtt.setServer(MQTT_HOST, MQTT_PORT);
  mqtt.setCallback(mqttCallback);

  // Attempt to connect immediately
  mqttReconnect();
  
  Serial.println("\n🤖 AUTO CLIMATE CONTROL ACTIVE:");
  Serial.printf("   🌡️  AC: ON at %.1f°C, OFF at %.1f°C\n", TEMP_HIGH_THRESHOLD, TEMP_LOW_THRESHOLD);
  Serial.printf("   💧 FAN: ON at %.1f%%, OFF at %.1f%%\n", HUMI_HIGH_THRESHOLD, HUMI_LOW_THRESHOLD);
  Serial.printf("   💡 BULB: ON at <%.1f%%, OFF at >%.1f%%\n", LIGHT_LOW_THRESHOLD, LIGHT_HIGH_THRESHOLD);
}

// ---------------- loop ----------------
void loop() {
  if (!mqtt.connected()) mqttReconnect();
  else mqtt.loop();

  unsigned long now = millis();
  if (now - lastSensorMillis >= SENSOR_INTERVAL_MS) {
    lastSensorMillis = now;

    float t = sensor.getTemperature(TEMP_C);
    float h = sensor.getHumidity();
    float lux = sensor.getLuminousIntensity();
    float pct = constrain((lux / 800.0f) * 100.0f, 0.0f, 100.0f);

    // Run auto climate control logic (temp, humidity, and light)
    autoClimateControl(t, h, pct);

    char buf[32];

    dtostrf(t, 5, 2, buf); String s_t = String(buf);
    if (s_t != last_temp_str) { robustPublish(T_TEMP, s_t.c_str(), true); last_temp_str = s_t; }

    dtostrf(h, 5, 2, buf); String s_h = String(buf);
    if (s_h != last_humi_str) { robustPublish(T_HUMI, s_h.c_str(), true); last_humi_str = s_h; }

    dtostrf(pct, 5, 2, buf); String s_l = String(buf);
    if (s_l != last_light_str) { robustPublish(T_LIGHT, s_l.c_str(), true); last_light_str = s_l; }

    Serial.printf("SENSORS: T=%.2f°C H=%.2f%% LUX=%.2f PCT=%.2f%%\n", t, h, lux, pct);
  }
}

Smart Home Control Tablet using CrowPanel Advance 7" Display

Credits

Mahesh Yadav
20 projects • 33 followers
Tech Content Creator, Electronics and Embedded Systems Engineer, Maker, Tinkerer, Web Designer, & Tech Explorer

Comments