Smart Home System

by Rayaan Ali in Circuits > Arduino

20 Views, 0 Favorites, 0 Comments

Smart Home System

IMG_3221.jpg

This project is meant to function as a multi functional home system built using an Arduino UNO R3. The main objective is to provide key safety and monitoring functions found in a real home, all combined into one integrated circuit with data being displayed through an I2C OLED screen. Specifically, it uses motion detection, gas monitoring, temperature sensing, and IR remote control into a single interactive OLED I2C display. This instructible is a intermediate friendly and fully documented at each step, to ensure a full replication of the circuit.

Supplies

0.jpg

Project Idea & Purpose

Many home safety projects often use a single sensor at a time. This project combines multiple sensors into one interactive system that is easy to interpret and simulates real world automation concepts. The system monitors:

  1. Burglary / Intrusion using a PIR motion Sensor
  2. Temperature (Thermostat) using a Temperature Sensor
  3. Carbon monoxide detection using a MQ-series gas sensor

An IR remote and a IR receiver are used in order to switch between display modes on the OLED display, allowing the user to view different sensor readings on demand.

Learning Outcomes

  1. Reading digital and analog sensors
  2. Clear input > processing > output logic
  3. Communication using libraries
  4. Displaying output on I2C oled display

Circuit Overview

Inputs: PIR motion sensor, MQ-2 gas sensor, DHT11 temperature sensor, IR receiver

Outputs: OLED i2c display


System Logic

  1. Sensors continuously feed their readings (data) into the Arduino
  2. Arduino processes readings and displays them on the OLED screen
  3. IR remote buttons switch display modes or toggle system ON/OFF

Testing Concept

  1. Each sensor will be tested individually before combining into a full system
  2. IR HEX codes are logged for proper remote functionality

Sensor Explanation

PIR Motion Sensor

  1. Detects changes in infrared radiation caused by movements
  2. Outputs HIGH digital signal when motion is being detected
  3. Used for: burglary or intrusion alerts

MQ-2 Gas Sensor

  1. Detects gases such as smoke, propane, methane, and carbon monoxide
  2. Analog voltage output proportional to gas concentration
  3. Used for: gas leak detection

Temperature Sensor (DHT11)

  1. Outputs digital readings for temperature (C) and humidity (%)
  2. Simple library functions allow for easy coding
  3. Used for: monitoring room temperature

IR Receiver

  1. Detects infrared signals from the remote
  2. Each button has a unique HEX code
  3. Used for: Switching OLED display modes and toggling the system ON/OFF

OLED Display (i2C)

IMG_3201.jpg
IMG_3209.jpg
IMG_3202.jpg
IMG_3206.jpg
  1. This step is to verify that the OLED screen is wired correctly and is able to display text before adding any sensors

Pin Connections

OLED -> Breadboard

VCC -> 5V (power rail)

GND -> ground rail

SDA -> A4

SCL -> A5

  1. Ensure library "Adafruit_GFX.h", "Adafruit_SSD1306.h", are both included in the library within the Arduino files

Code reasoning

  1. #include <Adafruit_GFX.h> and #include <Adafruit_SSD1306.h> are required libraries to draw text and graphics on the screen
  2. display.begin(SSD1306_SWITCHCAPVCC, 0x3C); starts the OLED at address 0x3C, which is the most common address for 128x64 OLEDs
  3. display.clearDisplay() clears any random pixels left in memory
  4. setTextSize, setCursor, and println control how and where the text appears
  5. display.display() sends everything to the screen, without this code nothing will show up

Use this code to test:

#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64

// I2C OLED, no reset pin
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

void setup() {
Wire.begin();

// Most SSD1306 OLEDs use 0x3C
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
while (true); // stop if OLED not found
}

display.clearDisplay();
display.set
TextSize(1);
display.setTextColor(SSD1306_WHITE);

display.setCursor(0, 0);
display.println("OLED CHECK");

display.setCursor(0, 16);
display.println("I2C is Working");

display.setCursor(0, 32);
display.println("Arduino is working as well");

display.display();
}

void loop() {
// nothing needed
}

PIR Motion Sensor

IMG_3204.jpg
IMG_3205.jpg
IMG_3210.jpg
  1. This step tests the motion detection using a PIR sensor and confirms the Arduino can read a digital HIGH/LOW signal.

Pin Connections

PIR -> Breadboard

VCC -> 5V (power rail)

GND -> ground rail

OUT -> digital pin 2

Code reasoning

  1. pinMode(pir, INPUT); tells the arduino the PIR sensor is sending (INPUT) data into the board
  2. digitalRead(pir); reads either HIGH (motion) or LOW (no motion).
  3. The if statement checks the sensor state and prints the result to the LED screen
  4. delay(500); slows the output so messages are readable
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>


int pir = 2;


Adafruit_SSD1306 display(128, 64, &Wire, -1);


void setup() {
pinMode(pir, INPUT);


display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
}


void loop() {
int motion = digitalRead(pir);


display.clearDisplay();
display.setCursor(0,0);
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);


if (motion) {
display.println("MOTION DETECTED");
display.println(" ");
display.println("Intruder Alert");
} else {
display.println("No motion");
}
display.display();
delay(500);
}

Noticeable Observations:

  1. "Motion detected" when movement is in front of PIR sensor
  2. "No motion detected" movement is still near PIR sensor

Gas Sensor

IMG_3213.jpg
IMG_3214.jpg
IMG_3212.jpg
  1. This step is to confirm the MQ-2 gas sensor is providing analog readings to the Arduino

Pin Connections

MQ-2 -> Breadboard

VCC -> 5V (power rail)

GND -> ground rail

A0 -> Analog pin A0

D0 -> no output

Code reasoning

  1. pinMode(pir, INPUT); tells the arduino the PIR sensor is sending (INPUT) data into the board
  2. digitalRead(pir); reads either HIGH (motion) or LOW (no motion).
  3. The if statement checks the sensor state and prints the result to the LED screen
  4. delay(500); slows the output so messages are readable
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define GAS_PIN A0

Adafruit_SSD1306 display(128, 64, &Wire, -1);

void setup() {
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
}

void loop() {
int gas = analogRead(GAS_PIN);

display.clearDisplay();
display.setCursor(0,0);
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);

display.print("Gas Value: ");
display.println(gas);

display.display();
delay(500);
}


Noticeable Observations:

  1. Values changing when gas (carbon monoxide) or breathing near the sensor
  2. Higher number = more detected gas

Key Notes:

When the gas sensor is disconnected, the Arduino will still output values as the analog pin will be left floating. Floating analog inputs can pick up electrical noises, which result in irregular readings.


Sensor Temperature (DHT11)

IMG_3215.jpg
IMG_3216.jpg
  1. This step is to verify temperature readings from the DHT11 sensor using simple integer values

Pin Connections

DHT11 -> Breadboard

VCC -> 5V (power rail)

GND -> ground rail

DATA -> digital pin 4

  1. 10K ohm resistor between DATA and VCC

Code reasoning

  1. #include <dht.h> loads the DHT sensor library
  2. DHT.read11(DHTPIN); tells the sensor to take a new measurement
  3. DHT.temperature;, and DHT.humidity; store the integer value from the sensor
  4. These values are printed to the serial monitor
#include <dht.h>

dht DHT;
int DHTPIN = 4;

void setup() {
Serial.begin(9600);
}

void loop() {
DHT.read11(DHTPIN);

int temp = DHT.temperature;
int hum = DHT.humidity;

Serial.print("Temp: ");
Serial.print(temp);
Serial.print(" C ");

Serial.print("Humidity: ");
Serial.print(hum);
Serial.println(" %");

delay(1000);
}

IR Receiver (Finding HEX Codes)

IMG_3220.jpg
IMG_3218.jpg
  1. Every remote sends different HEX codes, you must record these codes before writing the final program. This step is to identify the HEX codes sent by each button on the IR remote, which are required for ON/OFF and mode switching.

Pin Connections

IR receiver -> Breadboard

VCC -> 5V (power rail)

GND -> ground rail

OUT -> digital pin 7

Code reasoning

  1. #include <IRremote.h> This library allows the Arduino to receive and decode infrared signals from a remote control
  2. int IR_PIN = 7; Stores the pin number where the IR receiver's OUT pin is connected
  3. IRrecv irrecv(IR_PIN); Creates an IR receiver object, tells the library which pin to listen to.
  4. decode_results results; Stores the decoded IR signal data, results.value will contain the HEX code of the button pressed
  5. irrecv.enableIRIn(); Starts the IR receiver
  6. if (irrecv.decode(&results)) Checks if a button was pressed, runs the code inside only when a IR signal is detected
  7. long value = results.value; Saves the received HEX code into a variable, long is used as the IR HEX code numbers are large numbers
  8. display.println(value, HEX); Prints the button's HEX code to the OLED screen
  9. irrecv.resume(); Clears the last signal, allows the receiver to detect the next button press
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <IRremote.h>


int IR_PIN = 7;


Adafruit_SSD1306 display(128, 64, &Wire, -1);
IRrecv irrecv(IR_PIN);
decode_results results;


void setup() {
Serial.begin(9600);
irrecv.enableIRIn(); // Start the receiver
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
}


void loop() {
if (irrecv.decode(&results)) {
long value = results.value;
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0,0);
display.print("Button Code: ");
display.println(value, HEX); // shows button code in HEX
display.display();
irrecv.resume(); // Receive next value
}
}

Observations to be made:

  1. Press each remote button > Write down HEX codes for:
  2. Temperature, gas, reset, on, off

Final Step

IMG_3219.jpg
IMG_3217.jpg

In this step, all sensors, the OLED display, and the IR remote are combined into one complete system. The remote is used to turn the system on/off and switch between temperature, gas, and home screens, while sensors update live data on the OLED


Sensor Pins

//pin setup
int DHTPIN = 4; // DHT11 data pin
dht DHT;
int MQ2PIN = A0; // Analog input for MQ2 gas sensor
int PIRPIN = 2; // PIR motion sensor digital pin
int IR_PIN = 7; // IR receiver pin
  1. int is used because pin numbers are simple whole numbers
  2. Makes wiring easy to change later without rewriting code

Remote button HEX codes

const long BTN_TEMP = 0xFF30CF; // 1
const long BTN_GAS = 0xFF18E7; // 2
const long BTN_RESET = 0xFF7A85; // 3
const long BTN_ON = 0xFFA25D; // ON BUTTON
const long BTN_OFF = 0xFFE21D; // OFF BUTTON
bool systemOn = true; // system starts ON
int screenMode = 0; // 0=default/reset, 1=temp, 2=gas
  1. const prevents accidental changes during runtime

System State Variables

bool systemOn = true; // system starts ON
int screenMode = 0; // 0=default/reset, 1=temp, 2=gas
  1. boolsystemOn, if true than the system will start, if false, OLED will be blank
  2. screenmode, 0 = home, 1 = temperature screen, 2 = gas

PinMode Setup

void setup() {
Serial.begin(9600);
pinMode(PIRPIN, INPUT);
irrecv.enableIRIn(); // start IR receiver
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
}
  1. Starts the serial monitor
  2. Allows the PIR to be input
  3. Starts the OLED and IR receiver


IR remote controlling system

void loop() {
display.clearDisplay(); // bottom right corner removes old pixels
// To READ REMOTE
if(irrecv.decode(&results)){
long value = results.value;
if(value == BTN_TEMP) screenMode = 1;
else if(value == BTN_GAS) screenMode = 2;
else if(value == BTN_RESET) screenMode = 0;
else if(value == BTN_ON) systemOn = true; // turn everything ON
else if(value == BTN_OFF) systemOn = false; // turn everything OFF
irrecv.resume();
}
  1. Reads the HEX code from the remote
  2. Each button changes the system screen
  3. irrecv.resume(); allows the receiver to prepare for the next button press

Sensor Readings

if (systemOn) {
// read the SENSORS
DHT.read11(DHTPIN);
int temp = DHT.temperature; // integer temperature
int hum = DHT.humidity; // integer humidity
int gasRaw = analogRead(MQ2PIN); // raw analog value
// Convert MQ2 raw data to rough CO ppm
int gasPPM = map(gasRaw, 200, 1023, 0, 200);
if(gasPPM < 0) gasPPM = 0;
if(gasPPM > 200) gasPPM = 200;
int motion = digitalRead(PIRPIN);
  1. Uses integers for stability
  2. MQ-2 outputs raw analog values
  3. map() converts this into a rough PPM estimate
  4. Capped between 0 - 200 ppm to prevent false spikes
  5. High = motion detected

Temperature Screen

// display
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);

// Temperature Screen:
if(screenMode == 1){
display.setTextSize(1);
display.setCursor(0,0);
display.println("Temperature");


display.setTextSize(3);
display.setCursor(34, 20);
display.print(temp);


display.setTextSize(1);
display.setCursor(90, 30);
display.print(" C");


display.setTextSize(1);
display.setCursor(0, 55);
display.print("Humidity: ");
display.print(hum);
display.println("%");
}

  1. Large centered temperature
  2. Smaller humidity display
  3. Note: setcursor must always go before .print

Gas screen

// Gas Screen:
else if(screenMode == 2){
display.setTextSize(1);
display.setCursor(0,0);
display.println("Carbon Monoxide");

// Large PPM number
display.setTextSize(3);
display.setCursor(20, 15);
display.print(gasPPM);

// Raw Values
display.setTextSize(1);
display.setCursor(0, 55);
display.print("Raw: ");
display.print(gasRaw);

// Bar graph
int barHeight = map(gasPPM, 0, 200, 0, 40);
display.drawRect(110, 60-40, 15, 40, SSD1306_WHITE);
display.fillRect(111, 60-barHeight, 13, barHeight, SSD1306_WHITE);
}
  1. Large CO ppm value
  2. Raw sensor value shown
  3. Vertical Bar graph for visuals

Home page / Reset (END)

else {
display.setTextSize(1);
display.setCursor(0,0);
display.println("Home System");

if(motion){
display.setTextSize(2);
display.setCursor(10,20);
display.println(" MOTION!");
display.setTextSize(2);
display.print(" DETECTED!");
} else {
display.setTextSize(1);
display.setCursor(10,20);
display.println("No motion detected");
}
}
display.display();
delay(500);
}
else {
display.clearDisplay();
display.display(); // blank screen
}
}
  1. Default screen
  2. Displays motion alert when PIR is triggered
  3. Acts as the systems dashboard

FULL CODE

#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <dht.h>
#include <IRremote.h>

//pin setup
int DHTPIN = 4; // DHT11 data pin
dht DHT;
int MQ2PIN = A0; // Analog input for MQ2 gas sensor
int PIRPIN = 2; // PIR motion sensor digital pin
int IR_PIN = 7; // IR receiver pin


IRrecv irrecv(IR_PIN);
decode_results results;
Adafruit_SSD1306 display(128, 64, &Wire, -1);


// Remote button HEX codes
const long BTN_TEMP = 0xFF30CF; // 1
const long BTN_GAS = 0xFF18E7; // 2
const long BTN_RESET = 0xFF7A85; // 3
const long BTN_ON = 0xFFA25D; // ON BUTTON
const long BTN_OFF = 0xFFE21D; // OFF BUTTON
bool systemOn = true; // system starts ON
int screenMode = 0; // 0=default/reset, 1=temp, 2=gas

void setup() {
Serial.begin(9600);
pinMode(PIRPIN, INPUT);
irrecv.enableIRIn(); // start IR receiver
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
}


void loop() {
display.clearDisplay(); // bottom right corner removes old pixels
// To READ REMOTE
if(irrecv.decode(&results)){
long value = results.value;
if(value == BTN_TEMP) screenMode = 1;
else if(value == BTN_GAS) screenMode = 2;
else if(value == BTN_RESET) screenMode = 0;
else if(value == BTN_ON) systemOn = true; // turn everything ON
else if(value == BTN_OFF) systemOn = false; // turn everything OFF
irrecv.resume();
}
if (systemOn) {
// read the SENSORS
DHT.read11(DHTPIN);
int temp = DHT.temperature; // integer temperature
int hum = DHT.humidity; // integer humidity
int gasRaw = analogRead(MQ2PIN); // raw analog value
// Convert MQ2 raw data to rough CO ppm
int gasPPM = map(gasRaw, 200, 1023, 0, 200);
if(gasPPM < 0) gasPPM = 0;
if(gasPPM > 200) gasPPM = 200;
int motion = digitalRead(PIRPIN);

// display
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);

// Temperature Screen:
if(screenMode == 1){
display.setTextSize(1);
display.setCursor(0,0);
display.println("Temperature");


display.setTextSize(3);
display.setCursor(34, 20);
display.print(temp);


display.setTextSize(1);
display.setCursor(90, 30);
display.print(" C");


display.setTextSize(1);
display.setCursor(0, 55);
display.print("Humidity: ");
display.print(hum);
display.println("%");
}

// Gas Screen:
else if(screenMode == 2){
display.setTextSize(1);
display.setCursor(0,0);
display.println("Carbon Monoxide");

// Large PPM number
display.setTextSize(3);
display.setCursor(20, 15);
display.print(gasPPM);

// Raw Values
display.setTextSize(1);
display.setCursor(0, 55);
display.print("Raw: ");
display.print(gasRaw);

// Bar graph
int barHeight = map(gasPPM, 0, 200, 0, 40);
display.drawRect(110, 60-40, 15, 40, SSD1306_WHITE);
display.fillRect(111, 60-barHeight, 13, barHeight, SSD1306_WHITE);
}

// Reset to home page screen
else {
display.setTextSize(1);
display.setCursor(0,0);
display.println("Home System");

if(motion){
display.setTextSize(2);
display.setCursor(10,20);
display.println(" MOTION!");
display.setTextSize(2);
display.print(" DETECTED!");
} else {
display.setTextSize(1);
display.setCursor(10,20);
display.println("No motion detected");
}
}
display.display();
delay(500);
}
else {
display.clearDisplay();
display.display(); // blank screen
}
}

Conclusion (Reflection)

This project is to show how an Arduino is able to use multiple sensors to monitor home safety and home features. The PIR, DHT11, and MQ-2 sensors are able to provide real time data on motion, temperature, humidity, and carbon monoxide, all displayed on a OLED screen. Using an IR remote to control modes and ON/OFF states shows the importance of assigning the correct HEX codes. Through this project, I personally learnt how to handle analog and digital inputs, process sensor data, and manage a clean display without glitches. I was also able to build my code through testing of the physical (sensor) components, as each test provided the knowledge and understanding of the code for the final assembly.