// Libraries that are used by the code
#include <Adafruit_NeoPixel.h> // For controlling RGB LEDs
#include "DHTesp.h" // For communicating with the temperature and humidity sensor
#include <ESP32Servo.h> // For controlling the servo motor (used for the door)
#include <WiFi.h> // For connecting to WiFi (simulated)
#include <PubSubClient.h> // For communication over MQTT (Message Queuing Telemetry Transport)
//////////////////////////// Defining pins. ///////////////////
#define STATUS_LED_PIN 4 // Pin for the status LED
#define LIGHT_SENSOR_PIN 39 // Pin for the light sensor
#define AMBIENT_LIGHT_LEDS_PIN 18 // Pin for ambient light LEDs
#define AMBIENT_LIGHT_LED_COUNT 21 // Number of ambient light LEDs
#define DHT_PIN 15 // Pin for the DHT22 sensor
#define PIR_LIVING_ROOM_PIN 34 // Pin for the PIR sensor in the living room
#define LED_LIVING_ROOM_PIN 5 // Pin for the living room LED
#define WHEEL_LED_COUNT 16 // Number of LEDs in the living room and entrance wheel
#define PIR_ENTRANCE_PIN 35 // Pin for the PIR sensor in the entrance
#define LED_ENTRANCE_PIN 17 // Pin for the entrance LED
#define PIR_KITCHEN_PIN 32 // Pin for the PIR sensor in the kitchen
#define LED_KITCHEN_PIN 16 // Pin for the kitchen LED
#define PIR_SECURITY_PIN 33 // Pin for the security PIR sensor
#define BUZZER_PIN 25 // Pin for the buzzer
#define DOOR_SERVO_PIN 13 // Pin for the door servo motor
//////////////////////////////////////////////////////////////
///////// LIGHT SENSOR CONSTANTS /////////
const float ANALOG_READ_MAX = 4096.0; // Maximum value for analog read
const float VCC = 5.0; // Supply voltage
const float LDR_GAMMA = 0.7; // Gamma value for the LDR (Light Dependent Resistor)
const float LDR_RL10 = 50.0; // LDR resistance at 10 lux
////////// Type Definitions ////////////
// Enum for room identifiers
typedef enum __attribute__((packed))
{
LIVING_ROOM = 0,
ENTRANCE,
KITCHEN,
OUTDOOR,
ALL,
ROOMS_COUNT
} room_t;
// Enum for device identifiers
typedef enum __attribute__((packed)) {
LIGHT = 0,
ALARM,
SERVO,
PIR,
TEMPERATURE,
HUMIDITY,
LUX,
MODE,
SECURITY,
DEVICES_COUNT
} device_t;
// Enum for LED strip colours
typedef enum __attribute__((packed))
{
WHITE = 0,
RED,
ORANGE,
YELLOW,
GREEN,
BLUE,
INDIGO,
VIOLET,
OFF,
NUM_COLORS
} strip_color_t;
// Enum for system modes
typedef enum __attribute__((packed))
{
MANUAL = 0,
AUTO
} system_mode_t;
// Enum for lock states
typedef enum __attribute__((packed)) {
UNLOCKED = 0,
LOCKED
} lock_state_t;
// Struct for home system states
typedef struct {
bool firstRun; // Indicates if it's the first run
system_mode_t mode; // Current system mode (manual or automatic)
lock_state_t security; // Security lock state
lock_state_t door; // Door lock state
bool alarm_triggered; // Alarm triggered state
} home_system_t;
// Struct for WiFi and MQTT configuration
typedef struct {
const char* ssid;
const char* password;
const char* broker;
int port;
const char* clientId;
const char* mqtt_username;
const char* mqtt_password;
} wifi_mqtt_t;
// Struct for LED strip configuration
typedef struct
{
uint8_t strip_pin; // Pin number for the LED strip
uint16_t pixel_count; // Number of pixels in the LED strip
Adafruit_NeoPixel strip_name; // Instance of the LED strip
} strip_t;
// Struct for PIR sensor states
typedef struct {
int pirState; // State of the PIR sensor
int pinState; // State of the associated pin
} pir_t;
strip_t strips[ROOMS_COUNT]; // Array of LED strips for all rooms
pir_t pirs[ROOMS_COUNT]; // Array of PIR sensors for all rooms
wifi_mqtt_t mqttConnection; // WiFi and MQTT configuration instance
DHTesp dhtSensor; // DHT sensor instance
TempAndHumidity dht22Data; // Variable to store temperature and humidity data
volatile home_system_t homeSystem; // Home system state
// Function to map room string to enum
room_t mapRoomToEnum(const char* room) {
if (strcmp(room, "living_room") == 0) return LIVING_ROOM;
if (strcmp(room, "entrance") == 0) return ENTRANCE;
if (strcmp(room, "kitchen") == 0) return KITCHEN;
if (strcmp(room, "outdoor") == 0) return OUTDOOR;
if (strcmp(room, "all") == 0) return ALL;
return ROOMS_COUNT; // Indicates unknown room
}
// Function to map device string to enum
device_t mapDeviceToEnum(const char* device) {
if (strcmp(device, "light") == 0) return LIGHT;
if (strcmp(device, "alarm") == 0) return ALARM;
if (strcmp(device, "servo") == 0) return SERVO;
if (strcmp(device, "pir") == 0) return PIR;
if (strcmp(device, "temperature") == 0) return TEMPERATURE;
if (strcmp(device, "humidity") == 0) return HUMIDITY;
if (strcmp(device, "lux") == 0) return LUX;
if (strcmp(device, "mode") == 0) return MODE;
if (strcmp(device, "security") == 0) return SECURITY;
return DEVICES_COUNT; // Indicates unknown device
}
// Callback function for MQTT messages
void mqttCallback(char* topic, byte* payload, unsigned int length);
// Global WiFi and MQTT clients
WiFiClient wifiClient;
PubSubClient mqttClient(wifiClient);
Servo servo; // Servo motor instance
// Function to execute the appropriate action based on room and device type enums
void executeAction(room_t room, device_t device, int value) {
char stateTopic[256];
char message[50];
if (device == LIGHT) {
stripSetBrightness(room, value); // Set brightness for the LED strip
}
if (device == ALARM) {
// Code to handle alarm
}
if (device == SERVO) {
openCloseDoor(value); // Open or close the door using the servo motor
}
}
// Function to map a float value from one range to another
float floatMap(float x, float in_min, float in_max, float out_min, float out_max) {
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
// Function to initialise a PIR sensor
void pirInit(room_t room) {
pirs[room] = {0, 0};
}
// Function to initialise an LED strip
void stripInit(room_t room, uint8_t strip_pin, uint16_t pixel_count) {
strips[room] = {strip_pin, pixel_count, Adafruit_NeoPixel(pixel_count, strip_pin, NEO_GRB + NEO_KHZ800)};
strips[room].strip_name.begin();
strips[room].strip_name.setBrightness(255); // Set initial brightness
strips[room].strip_name.show(); // Initialise all LEDs to off
}
// Function to get the light intensity in lux
float getLuxValue() {
int analogValue = analogRead(LIGHT_SENSOR_PIN); // Read analog value from the light sensor
float voltage = analogValue / ANALOG_READ_MAX * VCC; // Calculate voltage
float resistance = 2000.0 * voltage / (1 - voltage / VCC); // Calculate resistance
float lux = pow(LDR_RL10 * 1e3 * pow(10, LDR_GAMMA) / resistance, (1 / LDR_GAMMA)); // Calculate lux value
return lux;
}
// Function to set the colour of an LED strip
void setStripColor(room_t room, strip_color_t color) {
strip_t &strip = strips[room];
uint32_t rgbColor;
switch (color) {
case WHITE:
rgbColor = strip.strip_name.Color(255, 255, 255); // White colour
break;
case OFF:
rgbColor = strip.strip_name.Color(0, 0, 0); // Turn off
break;
case RED:
rgbColor = strip.strip_name.Color(255, 0, 0); // Red colour
break;
case ORANGE:
rgbColor = strip.strip_name.Color(255, 165, 0); // Orange colour
break;
case YELLOW:
rgbColor = strip.strip_name.Color(255, 255, 0); // Yellow colour
break;
case GREEN:
rgbColor = strip.strip_name.Color(0, 128, 0); // Green colour
break;
case BLUE:
rgbColor = strip.strip_name.Color(0, 0, 255); // Blue colour
break;
case INDIGO:
rgbColor = strip.strip_name.Color(75, 0, 130); // Indigo colour
break;
case VIOLET:
rgbColor = strip.strip_name.Color(148, 0, 211); // Violet colour
break;
default:
rgbColor = strip.strip_name.Color(0, 0, 0); // Default to off if an unknown colour is passed
}
for (int i = 0; i < strip.pixel_count; i++) {
strip.strip_name.setPixelColor(i, rgbColor); // Set colour for each pixel
}
strip.strip_name.show(); // Update the strip to display the new colour
}
// Function to set the state of an LED strip (on/off)
void stripState(room_t room, int state) {
strip_t &strip = strips[room];
uint32_t on = strip.strip_name.Color(255, 255, 0); // Colour for "on" state (yellow)
uint32_t off = strip.strip_name.Color(0, 0, 0); // Colour for "off" state
for (int i = 0; i < strip.pixel_count; i++) {
if (state == 0) {
strip.strip_name.setPixelColor(i, off); // Turn off the strip
}
if (state == 1) {
strip.strip_name.setPixelColor(i, on); // Turn on the strip
}
}
strip.strip_name.show(); // Update the strip to display the new state
}
// Function to automatically adjust the brightness of an LED strip based on the lux value
void stripAutoBrightness(room_t room, float lux) {
lux = (float)constrain(lux, 10.0, 10000.0);
float brightness = floatMap(lux, 10., 10000., 255., 1.);
strips[room].strip_name.setBrightness(brightness); // Set brightness based on lux
strips[room].strip_name.show();
}
// Function to set the brightness of an LED strip
void stripSetBrightness(room_t room, int brightness) {
int mapped = constrain(brightness, 0, 100);
mapped = map(brightness, 0, 100, 1, 255);
Serial.print("Brighness: ");
Serial.println(mapped);
strip_t &strip = strips[room];
strip.strip_name.setBrightness(mapped);
strip.strip_name.show();
}
// Function to get the current brightness of an LED strip
int getStripBrightness(room_t room) {
strip_t &strip = strips[room];
return map(strip.strip_name.getBrightness(), 1, 255, 0, 100);
}
// Function to get the current temperature and humidity
void getClimate() {
dht22Data = dhtSensor.getTempAndHumidity(); // Get temperature and humidity from the sensor
}
// Function to open or close the door using the servo motor
void openCloseDoor(int open) {
int pos = 0;
if (open == 0) { // If open is 0, unlock the door
for (pos = 0; pos <= 90; pos += 1) {
servo.write(pos); // Move servo to unlock position
}
} else { // If open is 1, lock the door
for (pos = 90; pos >= 0; pos -= 1) {
servo.write(pos); // Move servo to lock position
}
}
}
// Function to reconnect to MQTT broker
void reconnect() {
while (!mqttClient.connected()) {
Serial.print("Attempting MQTT connection...");
if (mqttClient.connect("ESP32Client", mqttConnection.mqtt_username, mqttConnection.mqtt_password)) {
Serial.println("connected");
// Once connected, subscribe to a topic
mqttClient.subscribe("/noora/#");
mqttClient.publish("/noora/home/status/", "1");
} else {
Serial.print("failed, rc=");
Serial.print(mqttClient.state());
Serial.println(" try again in 5 seconds");
delay(5000);
}
}
}
// Setup function, called once when the program starts
void setup() {
Serial.begin(115200); // Start the serial communication
// Set WiFi and MQTT configuration
mqttConnection.ssid = "Wokwi-GUEST";
mqttConnection.password = "";
mqttConnection.broker = "13.41.227.36";
mqttConnection.port = 1883;
mqttConnection.clientId = "ESP32Client";
mqttConnection.mqtt_username = "noora";
mqttConnection.mqtt_password = "123456789";
// Connect to WiFi
Serial.print("Connecting to WiFi");
WiFi.begin(mqttConnection.ssid, mqttConnection.password, 6);
while (WiFi.status() != WL_CONNECTED) {
delay(100);
Serial.print(".");
}
Serial.println(" Connected!");
pinMode(STATUS_LED_PIN, OUTPUT); // Set status LED pin as output
pinMode(AMBIENT_LIGHT_LEDS_PIN, OUTPUT); // Set ambient light LEDs pin as output
digitalWrite(STATUS_LED_PIN, HIGH); // Turn on the status LED
// Initialise LED strips for different rooms
stripInit(OUTDOOR, AMBIENT_LIGHT_LEDS_PIN, AMBIENT_LIGHT_LED_COUNT);
stripInit(LIVING_ROOM, LED_LIVING_ROOM_PIN, WHEEL_LED_COUNT);
stripInit(ENTRANCE, LED_ENTRANCE_PIN, WHEEL_LED_COUNT);
stripInit(KITCHEN, LED_KITCHEN_PIN, WHEEL_LED_COUNT);
// Set initial state of LED strips
stripState(OUTDOOR, 1);
stripState(LIVING_ROOM, 1);
stripState(ENTRANCE, 1);
stripState(KITCHEN, 1);
// Set initial colours of LED strips
setStripColor(OUTDOOR, WHITE);
setStripColor(LIVING_ROOM, GREEN);
setStripColor(ENTRANCE, ORANGE);
setStripColor(KITCHEN, BLUE);
dhtSensor.setup(DHT_PIN, DHTesp::DHT22); // Initialise the DHT sensor
servo.attach(DOOR_SERVO_PIN, 500, 2400); // Attach the servo to the door pin
// Initialise home system state
homeSystem.mode = MANUAL;
homeSystem.security = LOCKED;
homeSystem.door = LOCKED;
homeSystem.firstRun = 1;
homeSystem.alarm_triggered = 0;
// Set MQTT server and callback
mqttClient.setServer(mqttConnection.broker, mqttConnection.port);
mqttClient.setCallback(mqttCallback);
}
int period = 1000; // Period for the loop (1 second)
unsigned long time_now = 0; // Current time
// Main loop, called repeatedly
void loop() {
char stateTopic[256];
char message[50];
// Reconnect to MQTT broker if not connected
if (!mqttClient.connected()) {
reconnect();
}
mqttClient.loop();
// Initialise home system on first run
if (homeSystem.firstRun == 1) {
Serial.println("First run, setting defaults");
delay(1000);
homeSystem.firstRun = 0;
const char *roomNames[ROOMS_COUNT] = {"living_room", "entrance", "kitchen", "outdoor", "all"};
const char *deviceNames[DEVICES_COUNT] = {"light", "alarm", "servo", "pir", "temperature", "humidity", "lux", "mode", "security"};
// Get the state of each device in each room
for (int roomIdx = 0; roomIdx < ROOMS_COUNT; roomIdx++) {
for (int deviceIdx = 0; deviceIdx < DEVICES_COUNT; deviceIdx++) {
getDeviceState(roomNames[roomIdx], deviceNames[deviceIdx]);
}
}
Serial.println("First run complete");
}
// Example of reading sensor data periodically (every second)
// if (millis() >= time_now + period) {
// time_now += period;
// }
}
// Callback function to receive MQTT messages
void mqttCallback(char* topic, byte * payload, unsigned int length) {
char topicCopy[256];
strncpy(topicCopy, topic, sizeof(topicCopy));
topicCopy[sizeof(topicCopy) - 1] = '\0'; // Ensure null-termination
char* segments[6]; // Array for topic segments
int segmentCount = 0;
char* segment;
segment = strtok(topicCopy, "/");
// Split topic into segments
while (segment != NULL && segmentCount < 6) {
segments[segmentCount++] = segment;
segment = strtok(NULL, "/");
}
// Ensure we have enough segments for "room/device" pattern (at least 6)
if (segmentCount >= 6) {
char* roomStr = segments[3]; // Extract room from topic
char* deviceStr = segments[4]; // Extract device from topic
char* key = segments[5];
room_t room = mapRoomToEnum(roomStr);
device_t device = mapDeviceToEnum(deviceStr);
if (room != ROOMS_COUNT && device != DEVICES_COUNT) {
char message[length + 1];
strncpy(message, (char*)payload, length);
message[length] = '\0'; // Null-terminate the string
int value = atoi(message); // Convert payload to integer
switch (device) {
case LIGHT:
if (strncmp(key, "level", 5) == 0) {
executeAction(room, device, value); // Execute action for light level
}
if (strncmp(key, "state", 5) == 0) {
getDeviceState(roomStr, deviceStr); // Publish state update
}
break;
case SERVO:
if (strncmp(key, "set", 3) == 0) {
homeSystem.door = (lock_state_t)value; // Set door state
executeAction(room, device, homeSystem.door); // Execute action for servo
}
if (strncmp(key, "state", 5) == 0) {
getDeviceState(roomStr, deviceStr); // Publish state update
}
break;
case PIR:
break;
case TEMPERATURE:
if (strncmp(key, "state", 5) == 0) {
getDeviceState(roomStr, deviceStr); // Publish state update
}
break;
case HUMIDITY:
if (strncmp(key, "state", 5) == 0) {
getDeviceState(roomStr, deviceStr); // Publish state update
}
break;
case MODE:
if (strncmp(key, "set", 3) == 0) {
homeSystem.mode = (system_mode_t)value; // Set system mode
}
if (strncmp(key, "state", 5) == 0) {
getDeviceState(roomStr, deviceStr); // Publish state update
}
break;
case LUX:
if (strncmp(key, "state", 5) == 0) {
getDeviceState(roomStr, deviceStr); // Publish state update
}
break;
case SECURITY:
if (strncmp(key, "set", 3) == 0) {
homeSystem.security = (lock_state_t)value; // Set security state
}
if (strncmp(key, "state", 5) == 0) {
getDeviceState(roomStr, deviceStr); // Publish state update
}
break;
default: break;
}
} else {
Serial.print("Unknown action for room [");
Serial.print(roomStr);
Serial.print("] or device type [");
Serial.print(deviceStr);
Serial.println("]");
}
}
}
// Function to get the state of a device in a room
void getDeviceState(const char* roomStr, const char* deviceStr) {
room_t room = mapRoomToEnum(roomStr);
device_t device = mapDeviceToEnum(deviceStr);
char stateTopic[256];
char message[50];
switch (device) {
case LIGHT:
snprintf(stateTopic, sizeof(stateTopic), "/noora/home/room/%s/%s/state/", roomStr, deviceStr);
snprintf(message, sizeof(message), "%d", getStripBrightness(room)); // Get brightness of the LED strip
mqttClient.publish(stateTopic, message, true); // Publish state update with retained flag
break;
case PIR:
break;
case TEMPERATURE:
getClimate(); // Get temperature and humidity
snprintf(stateTopic, sizeof(stateTopic), "/noora/home/room/%s/%s/state/", roomStr, deviceStr);
snprintf(message, sizeof(message), "%.2f", dht22Data.temperature); // Get temperature
mqttClient.publish(stateTopic, message, true); // Publish state update with retained flag
break;
case SERVO:
snprintf(stateTopic, sizeof(stateTopic), "/noora/home/room/%s/%s/state/", roomStr, deviceStr);
snprintf(message, sizeof(message), "%d", homeSystem.door); // Get door state
mqttClient.publish(stateTopic, message, true); // Publish state update with retained flag
break;
case HUMIDITY:
getClimate(); // Get temperature and humidity
snprintf(stateTopic, sizeof(stateTopic), "/noora/home/room/%s/%s/state/", roomStr, deviceStr);
snprintf(message, sizeof(message), "%.2f", dht22Data.humidity); // Get humidity
mqttClient.publish(stateTopic, message, true); // Publish state update with retained flag
break;
case MODE:
snprintf(stateTopic, sizeof(stateTopic), "/noora/home/room/%s/%s/state/", roomStr, deviceStr);
snprintf(message, sizeof(message), "%d", homeSystem.mode); // Get system mode
mqttClient.publish(stateTopic, message, true); // Publish state update with retained flag
break;
case SECURITY:
snprintf(stateTopic, sizeof(stateTopic), "/noora/home/room/%s/%s/state/", roomStr, deviceStr);
snprintf(message, sizeof(message), "%d", homeSystem.security); // Get security state
mqttClient.publish(stateTopic, message, true); // Publish state update with retained flag
break;
case LUX:
snprintf(stateTopic, sizeof(stateTopic), "/noora/home/room/%s/%s/state/", roomStr, deviceStr);
snprintf(message, sizeof(message), "%.2f", getLuxValue()); // Get lux value
mqttClient.publish(stateTopic, message, true); // Publish state update with retained flag
break;
default:
break;
}
}