Project tutorial
How to Build a DIY Arduino-Based Smart Home Hub with 1Sheeld

How to Build a DIY Arduino-Based Smart Home Hub with 1Sheeld © CERN-OHL

Arduino-based smart home hub allows you to control your devices remotely from outside and monitor your home environment from your phone.

  • 8,716 views
  • 1 comment
  • 38 respects

Components and supplies

Necessary tools and machines

siccors
glue gun
tape
cutter knife
Ruller

Apps and online services

About this project

I have been always fascinated by home automation devices and ever wanted to get one of these security home camera, home temperature monitor or even lights and air conditioner.

Even more, I dreamed of remotely control things in my home from outside by using my phone. That takes me to create my own customized home automation project that integrates IoT technology, into only one device... an IoT home automation device!

So, let's get started with the idea...

Idea

The device is placed near the home door and is always connected to the internet via my wifi router. And you can monitor your home environment and control things by using just an app on your smartphone, called IoT MQTT Panel.

The device has 3 sensors to monitor:

  • Door lock status using ultrasonic sensor
  • Temperature
  • Gas level

And allows you to control 3 things:

How It Works

Once you are outside home you can monitor your home light and control it from the app by using a relay.

Also, you can monitor gas level inside your home with the gas sensor and once it gets a dangerous level, an email will be sent to you automatically from the device so you can take a quick action upon it and also the alarm buzzer will be turned on and will stay alarming until someone notice it and press it down.

And even take multiple pictures from different angles and they will be sent to your email.

And when you are about to leave the office back to your home then you can cool things by checking the home temperature and turn on the AC with an auto-optimization mode.

This is done by using an IR sensor directed to the AC and the auto-optimization mode will increase the AC temperature set point automatically each 2 minutes for 2 times so that once you get arrived your home is cooled nicely, not frozen!

Once you arrive home and open the door, the ultrasonic sensor will detect your door movement and the device will tell you (with a text-to-speech) to enter your pre-saved fingerprint so that the device knows that you are an authorized person.

What if he wasn't an authorized one? Then the device will wait for 30 seconds and if the fingerprint hasn't been entered and matched, then it will take a photo of that stranger and send it back to the owner's email.

In addition, once you press the speak button on your app, the device will activate the voice-commands and listen to what you want like playing music or turning lights on/off. Here, you can get relaxed reading your favourite book and listening to your favorite music after a heavy working day!

How to Make It

I am going to use Arduino with the IoT shield existed in 1Sheeld. 1Sheeld is basically an Arduino shield that collects 40+ shields into only one shield!

Just a perfect and cheap board that empowers me to use the required shields in this project:

  • IoT Shield for making the communication with the CloudMQTT online server.
  • Email Shield to get emails of photos taken by the phone's camera and gas alet's emails.

And going to use 2 phones:

  • An iPhone to be connected with the 1Sheeld so that Arduino can connect to the internet (and to the IoT online server, indeed) through the IoT shield in the iPhone's 1Sheeld app.
  • An Android phone to use it as my personal phone that has the IoT MQTT Panel App. And these of course in addition to the listed components and fabrication materials.

So, I will go with you step by step to build this project from scratch but please be patient since there is quite steps, components and coding here.

Before You Start Building It

I recommend you go for this 1Sheeld getting started tutorial to get familiar with 1Sheeld.

Also, check this simple IoT tutorial with Arduino+1Sheeld to get familiar with the IoT shield of 1Sheeld and how to setup the IoT MQTT Panel App and the cloud MQTT server.

Step 1: Prepare the Materials and Cut the Box Faces

First, get the cardboard foam material and cut it into the below described dimensions so you get 6 faces of the box.

Don't forget to make the holes for the components like ultrasonic, gas sensor, etc.

Finally, you should get the box faces ready like that:

Note: the size of the 3D parts and the servo motor arm is up to you according to your servo motor size and your iPhone. But in general, you can use any alike plastic/wood pieces around you. Just make it easy for yourself :)

Step 2: Build the Box

Now comes the assembly part where you glue the 5 faces of the box using the glue gun.

And the 6th face which is the box's door you should install it with a tape from its bottom side to act like a door to be able to open and close it freely, like this:

Note: the box door is fixed to its closing position by using 2 small magnets placed on the box door and 2 metal pieces placed on the correct faced closing place.

Step 3: Connect the Components

You better start with plugging the 1Sheeld over the Arduino and install it inside the box.

You should do it without messing things up inside the box by:

1 - Connect the jumper wires to the components: DHT sensor, ultrasonic, servo, LEDs, buttons, etc.

2 - Glue each component to its hole over its place in the box using the glue gun.

3 - Connect the other jumper wires terminal to the Arduino or power.

Step 4: Box Polishing

I have designed a sticker profile to cover the box so that it gets a visual polish. The sticker is divided into 6 faces, indeed where you can download them from here. Or you can use your own customized sticker.

After all, it will look like this:

Step 5: Adjust Box to the Wall

Then it's time to adjust the device near your home door with a helping 3D printed 4 parts (2 over the box itself and 2 over the wall). You will find the (.gcode) for it in the CAD part of the project so that your can print them directly with the 3S printer.

Finally, connect the 2 relay output wires to the wall light switch.

And tape the IR to be pointed to face your air conditioner.

Step 6: IR Code for the Air Conditioner remote

Before uploading the code to your Arduino mega you should edit this part which contains the Air Conditioner IR remote codes to your own in-home Air Conditioner IR code:

// AC on/off at temp 23
unsigned int power[] = {4712, 2580, 384, 376, 380, 952, 380, 976, 356, 396, 360, 976, 356, 376, 380, 396, 356, 396, 360, 396, 356, 976, 356, 400, 356, 396, 356, 400, 356, 976, 356, 396, 360, 396, 356, 396, 360, 396, 356, 976, 356, 400, 356, 396, 356, 400, 356, 976, 356, 376, 380, 396, 356, 400, 356, 396, 356, 976, 360, 396, 356, 396, 360, 396, 356, 400, 356, 396, 356, 400, 356, 396, 356, 400, 356, 976, 356, 396, 360, 396, 356, 396, 360, 976, 356, 396, 356, 400, 356, 396, 360, 972, 360, 396, 360, 396, 356, 396, 356, 976, 360, 972, 360, 396, 356, 400, 356, 396, 356, 976, 356, 400, 356, 396, 360, 396, 356, 396, 360, 396, 356, 976, 360, 972, 360, 976, 356, 396, 360, 956, 356, 20116, 4676, 6568, 9276, 5012, 384, 396, 356, 396, 360, 396, 356, 400, 356, 396, 360, 396, 356, 396, 356, 400, 356, 396, 360, 396, 356, 396, 360, 396, 356, 976, 356, 976, 360, 396, 356, 396, 360, 396, 356, 396, 360, 396, 356, 396, 360, 396, 356, 400, 356, 976, 356, 396, 356, 400, 356, 396, 360, 396, 356, 396, 360, 396, 356, 400, 356, 396, 356, 396, 360, 396, 356, 400, 356, 396, 356, 400, 356, 396, 360, 396, 356, 396, 356, 400, 356, 396, 360, 396, 356, 400, 356, 396, 356, 396, 360, 396, 360, 396, 356, 396, 356, 400, 356, 396, 356, 400, 356, 396, 360, 396, 356, 396, 360, 396, 356, 396, 360, 396, 356, 396, 360, 396, 356, 396, 360, 976, 356, 976, 356, 976, 356, 380, 360, 20116, 4672};
// AC temp+ from 23 to 24
unsigned int temp24[] = {4716, 2604, 360, 396, 356, 976, 356, 976, 360, 396, 356, 976, 356, 400, 356, 396, 356, 400, 356, 396, 356, 976, 360, 396, 356, 396, 360, 396, 356, 976, 360, 396, 356, 396, 360, 972, 360, 396, 356, 980, 352, 400, 356, 396, 360, 396, 356, 396, 360, 396, 356, 976, 356, 400, 356, 396, 356, 976, 360, 396, 356, 400, 356, 396, 356, 400, 356, 396, 356, 400, 356, 396, 356, 400, 356, 976, 356, 396, 360, 396, 356, 400, 356, 976, 356, 396, 360, 396, 356, 396, 360, 976, 356, 396, 356, 400, 356, 396, 360, 396, 356, 396, 356, 976, 360, 396, 356, 400, 356, 976, 356, 396, 360, 396, 356, 396, 360, 396, 356, 396, 360, 396, 356, 400, 356, 976, 356, 396, 356, 380, 360, 20104, 4684, 6596, 9276, 5012, 356, 396, 360, 396, 356, 396, 360, 396, 356, 400, 356, 396, 356, 400, 356, 400, 356, 396, 356, 396, 356, 400, 356, 396, 360, 976, 356, 976, 356, 396, 360, 396, 356, 396, 360, 396, 356, 396, 360, 396, 356, 400, 356, 396, 356, 976, 356, 400, 356, 396, 356, 400, 356, 396, 360, 396, 356, 400, 356, 396, 356, 400, 356, 396, 356, 400, 356, 396, 356, 400, 356, 396, 356, 400, 356, 396, 356, 400, 356, 396, 356, 400, 356, 400, 356, 396, 356, 400, 352, 400, 356, 400, 356, 396, 356, 400, 356, 396, 356, 400, 356, 396, 356, 400, 356, 400, 352, 400, 356, 400, 352, 400, 356, 400, 356, 396, 356, 400, 352, 400, 356, 980, 352, 980, 352, 980, 356, 380, 356, 20104, 4684};
// AC temp+ from 24 to 25
unsigned int temp25[] = {4716, 2604, 356, 400, 356, 976, 356, 976, 360, 396, 356, 976, 356, 396, 360, 396, 356, 400, 356, 396, 356, 976, 360, 396, 356, 396, 360, 396, 356, 976, 356, 400, 356, 396, 356, 400, 356, 396, 356, 976, 360, 396, 356, 396, 360, 396, 356, 976, 360, 396, 356, 396, 360, 396, 356, 396, 360, 972, 360, 396, 356, 400, 356, 396, 356, 400, 356, 396, 356, 400, 356, 396, 360, 396, 356, 976, 356, 400, 356, 396, 356, 400, 356, 976, 356, 396, 360, 396, 356, 396, 360, 976, 356, 396, 360, 396, 356, 396, 356, 976, 360, 396, 356, 976, 356, 400, 356, 396, 360, 972, 360, 396, 356, 396, 360, 396, 356, 396, 360, 396, 356, 400, 356, 976, 356, 396, 360, 976, 356, 380, 356, 20108, 4684, 6592, 9276, 5012, 356, 396, 360, 396, 360, 396, 356, 396, 360, 396, 356, 396, 360, 396, 356, 396, 360, 396, 356, 400, 356, 396, 356, 396, 360, 976, 356, 976, 356, 396, 360, 396, 356, 396, 360, 396, 356, 400, 356, 396, 356, 400, 356, 396, 360, 976, 356, 396, 356, 396, 360, 396, 356, 400, 356, 396, 360, 396, 356, 396, 360, 396, 356, 396, 360, 396, 356, 396, 360, 396, 356, 396, 360, 396, 356, 400, 356, 396, 356, 400, 356, 396, 356, 400, 356, 396, 356, 400, 356, 396, 360, 396, 356, 396, 360, 396, 356, 396, 360, 396, 356, 396, 360, 396, 356, 396, 360, 396, 360, 396, 356, 396, 356, 400, 356, 396, 356, 400, 356, 396, 360, 972, 360, 976, 356, 976, 356, 380, 356, 20080, 4712};

So, you need to know the IR codes responsible for:

  • Turning your Air Conditioner on/off at a temperature of 23.
  • Increasing your Air Conditioner temperature from 23 to 24.
  • Increasing your Air Conditioner temperature from 24 to 25.

The last 2 codes are required for the Auto-Optimization mode.

For me, this was the most difficult part of the whole code since all IR remote libraries on the internet for Arduino aren't compatible with long range of the codes generated by the Air Conditioner IR remotes since these remotes generates IR codes with much more number of complex bits that popular Arduino IR remote libraries can't recognize.

After a long search and trying combinations of codes I finally got it working! You just need to make these connections between the Arduino and an IR receiver:

Connections:

IR Receiver Arduino

V+ -> +5v

GND -> GND

Signal Out -> Digital Pin 2

Then, upload this code to your Arduino:

/*
Author: AnalysIR
Revision: 1.0
This code is provided to overcome an issue with Arduino IR libraries
It allows you to capture raw timings for signals longer than 255 marks & spaces.
Typical use case is for long Air conditioner signals.
You can use the output to plug back into IRremote, to resend the signal.
This Software was written by AnalysIR.
Usage: Free to use, subject to conditions posted on blog below.
Please credit AnalysIR and provide a link to our website/blog, where possible.
Copyright AnalysIR 2014
Please refer to the blog posting for conditions associated with use.
http://www.analysir.com/blog/2014/03/19/air-conditioners-problems-recording-long-infrared-remote-control-signals-arduino/
Connections:
IR Receiver      Arduino
V+          ->  +5v
GND          ->  GND
Signal Out   ->  Digital Pin 2
(If using a 3V Arduino, you may connect V+ to +3V)
*/
#define LEDPIN 13
//you may increase this value on Arduinos with greater than 2k SRAM
#define maxLen 300
volatile  unsigned int irBuffer[maxLen]; //stores timings - volatile because changed by ISR
volatile unsigned int x = 0; //Pointer thru irBuffer - volatile because changed by ISR
void setup() {
 Serial.begin(115200); //change BAUD rate as required
 attachInterrupt(0, rxIR_Interrupt_Handler, CHANGE);//set up ISR for receiving IR signal
}
void loop() {
 // put your main code here, to run repeatedly:
 Serial.println(F("Press the button on the remote now - once only"));
 delay(5000); // pause 5 secs
 if (x) { //if a signal is captured
   digitalWrite(LEDPIN, HIGH);//visual indicator that signal received
   Serial.println();
   Serial.print(F("Raw: (")); //dump raw header format - for library
   Serial.print((x - 1));
   Serial.print(F(") "));
   detachInterrupt(0);//stop interrupts & capture until finshed here
   for (int i = 1; i < x; i++) { //now dump the times
     if (!(i & 0x1)) Serial.print(F("-"));
     Serial.print(irBuffer[i] - irBuffer[i - 1]);
     Serial.print(F(", "));
   }
   x = 0;
   Serial.println();
   Serial.println();
   digitalWrite(LEDPIN, LOW);//end of visual indicator, for this time
   attachInterrupt(0, rxIR_Interrupt_Handler, CHANGE);//re-enable ISR for receiving IR signal
 }
}
void rxIR_Interrupt_Handler() {
 if (x > maxLen) return; //ignore if irBuffer is already full
 irBuffer[x++] = micros(); //just continually record the time-stamp of signal transitions
}

And open the serial monitor and press the on/off key from the IR remote towards the IR receiver. You will get something like this on the screen:

Copy and paste the code you got in a text file. Code will be something like this:

Raw: (267) 4644, -2604, 384, -416, 312, -1020, 316, -1016, 316, -440, 312, -1020, 312, -440, 312, -444, 312, -440, 312, -444, 312, -1016, 316, -440, 312, -440, 316, -1016, 316, -440, 312, -444, 312, -440, 312, -444, 312, -1016, 316, -1016, 316, -440, 316, -1016, 320, -436, 312, -1016, 316, -440, 316, -1016, 316, -440, 312, -440, 316, -440, 312, -440, 312, -440, 316, -440, 312, -440, 316, -440, 312, -440, 316, -440, 312, -440, 316, -1016, 316, -440, 312, -440, 316, -440, 312, -440, 316, -440, 312, -440, 316, -436, 316, -1016, 316, -440, 316, -436, 316, -440, 316, -436, 316, -440, 316, -1016, 316, -436, 316, -440, 316, -1016, 316, -436, 316, -440, 316, -436, 316, -440, 316, -436, 316, -1016, 316, -440, 312, -1020, 316, -1016, 316, -420, 316, -20132, 4648, -6592, 9272, -5008, 384, -412, 316, -440, 312, -440, 316, -436, 316, -440, 316, -436, 316, -440, 312, -440, 316, -440, 312, -440, 316, -440, 312, -440, 316, -440, 312, -440, 316, -436, 316, -440, 316, -436, 316, -440, 316, -436, 316, -440, 312, -440, 316, -440, 312, -1020, 312, -440, 312, -444, 312, -440, 312, -444, 312, -440, 312, -444, 312, -440, 312, -440, 316, -440, 312, -440, 316, -440, 312, -440, 312, -444, 312, -440, 316, -440, 312, -440, 312, -444, 312, -440, 312, -440, 316, -440, 312, -440, 316, -440, 312, -440, 312, -444, 312, -440, 312, -444, 312, -440, 312, -444, 312, -440, 312, -440, 316, -440, 312, -440, 316, -440, 312, -440, 316, -440, 312, -440, 316, -440, 312, -440, 312, -444, 312, -1016, 316, -424, 312, -20136, 4648,

If you noticed, the IR code for on/off switch has negative numbers. Many of them, actually. Here, you must remove all these (-) signs and copy the whole code with all numbers are positive to your project code.

Going to do all this manually! What's the hill!! Surely not, however, I did it manually to test a new code I have made to take the IR remote code with its negative numbers, removes all negative signs (-) and generates the required clean IR code for you. All automatically! Yeah, here is it:

// ir received array
int irArray[] = {4716, -2604, 360, -396, 356, -976, 356, -976, 360, -396, 356, -976, 356, -400, 356, -396, 356, -400, 356, -396, 356, -976, 360, -396, 356, -396, 360, -396, 356, -976, 360, -396, 356, -396, 360, -972, 360, -396, 356, -980, 352, -400, 356, -396, 360, -396, 356, -396, 360, -396, 356, -976, 356, -400, 356, -396, 356, -976, 360, -396, 356, -400, 356, -396, 356, -400, 356, -396, 356, -400, 356, -396, 356, -400, 356, -976, 356, -396, 360, -396, 356, -400, 356, -976, 356, -396, 360, -396, 356, -396, 360, -976, 356, -396, 356, -400, 356, -396, 360, -396, 356, -396, 356, -976, 360, -396, 356, -400, 356, -976, 356, -396, 360, -396, 356, -396, 360, -396, 356, -396, 360, -396, 356, -400, 356, -976, 356, -396, 356, -380, 360, -20104, 4684, -6596, 9276, -5012, 356, -396, 360, -396, 356, -396, 360, -396, 356, -400, 356, -396, 356, -400, 356, -400, 356, -396, 356, -396, 356, -400, 356, -396, 360, -976, 356, -976, 356, -396, 360, -396, 356, -396, 360, -396, 356, -396, 360, -396, 356, -400, 356, -396, 356, -976, 356, -400, 356, -396, 356, -400, 356, -396, 360, -396, 356, -400, 356, -396, 356, -400, 356, -396, 356, -400, 356, -396, 356, -400, 356, -396, 356, -400, 356, -396, 356, -400, 356, -396, 356, -400, 356, -400, 356, -396, 356, -400, 352, -400, 356, -400, 356, -396, 356, -400, 356, -396, 356, -400, 356, -396, 356, -400, 356, -400, 352, -400, 356, -400, 352, -400, 356, -400, 356, -396, 356, -400, 352, -400, 356, -980, 352, -980, 352, -980, 356, -380, 356, -20104, 4684};
signed int irArraySize= sizeof(irArray) / sizeof(irArray[0]);
int i;
void setup() {
 Serial.begin(9600);
 delay(2000);
 Serial.println("Wait .. I will convert your signed IR array now ...");
 delay(1000);
 Serial.println("");
 /* print the old signed array */
 Serial.println("Your original IR array with negative numbers:");
 Serial.print("{");
 for(i=0; i<irArraySize; i++)
 {
   Serial.print(irArray[i]);
   Serial.print(", ");
 }
 Serial.print("}");
 Serial.println("");
 Serial.println("");
 /* remove '-' sign from the original array and over right elements */
 for(i=0; i<irArraySize; i++)
 {
   if(irArray[i] < 0 )
   {
     irArray[i] = irArray[i] * ( (signed int)(-1) );
   }
 }
 /* print the new unsigned array */
 Serial.println("Your new all positive numbers IR array:");
 Serial.print("{");
 for(i=0; i<irArraySize; i++)
 {
   Serial.print(irArray[i]);
   Serial.print(", ");
 }
 Serial.print("}");
 Serial.println("");
 Serial.println("");
 Serial.println("All finished .. ready to be copied");
}
void loop() {
 // put your main code here, to run repeatedly:
}

Just replace the (irArray[]) content with the code you got which contains the negative (-) numbers and upload the code to the Arduino and you will get your new positive numbers IR code ready to be copied to the project code.

And repeat that with the temp (+) button when Air Conditioner is turned on at 23 c and temp (+) button again when Air Conditioner is on at 24 c.

Finally, you will get 3 IR array codes for the 3 positions we mentioned before:

  • Turning your Air Conditioner on/off at a temperature of 23.
  • Increasing your Air Conditioner temperature from 23 to 24.
  • Increasing your Air Conditioner temperature from 24 to 25.

Copy these arrays to replace them with those in the project code.

Step 7: Arduino Code

  • Connect the Arduino via your PC using Arduino USB cable.
  • Switch the 1Sheeld power to operate on 5v (Not the 3.3v):
  • 1Sheeld have 2 modes: Uploading mode and Operating mode. You can switch between them using the switch close to the Digital pins and is called “UART SWITCH” on 1Sheeld and “SERIAL SWITCH” on 1Sheeld+.

Firstly, you slide the switch towards the “SWITCH” notation which turns the 1Sheeld board into the Uploading mode to let you upload the Arduino code.

Note: You have to add these attached code files [dht.cpp - dht.h - TimerOne.cpp - TimerOne.h] in the same folder of the Arduino code file as they are libraries I used in the Arduino code.

Secondly, after you finish uploading the code, slide the switch towards the “UART” notation (or “SERIAL” at 1Sheeld+ board) which turns the 1Sheeld board into the Operating mode to communicate with your smartphone 1Sheeld app.

Finally, open the 1Sheeld app and connect it to the 1Sheeld board via Bluetooth.

Step 8: Configuring the App

Now move on to download the IoT MQTT Panel App. You need to configure the app with MQTT server instance credentials you have gotten from the CloudMQTT online server.

I have made it with the help of this IoT tutorial which will show you how to add just the light button widget and use it with the app:

And this IoT Temperature sensor tutorial which will show you how to add a sensor reading widget and use it with the app.

After that, you will be able to add all other widgets easily like AC button, Auto-Optimization button, Speak, Camera Capture button, Camera Servo slider, gas level reading widget, Door Status widget. You should finish it with a full panel like this:

Release the IoT Home Automation Monster!

Once finished all the steps, insert the batteries:

Then turn the device on from the left side switch:

And connect your iPhone with the 1Sheeld then press the reset button on the device left side.

Then open the IoT MQTT Panel App and you are ready to go and control and monitor your home anytime, anywhere with just your own smartphone!

Hope you enjoyed it, guys!

If you have any comment or feedback please let me know in the comments below.

Code

Project codeArduino
/*
  Arduino IoT Quality Project using IoT Shield from 1Sheeld

  OPTIONAL:
  To reduce the library compiled size and limit its memory usage, you
  can specify which shields you want to include in your sketch by
  defining CUSTOM_SETTINGS and the shields respective INCLUDE_ define.
*/

/* Include all required shields */
#define CUSTOM_SETTINGS
#define INCLUDE_IOT_SHIELD
#define INCLUDE_TERMINAL_SHIELD
#define INCLUDE_EMAIL_SHIELD
#define INCLUDE_CAMERA_SHIELD
#define INCLUDE_BUZZER_SHIELD
#define INCLUDE_FINGERPRINT_SCANNER_SHIELD
#define INCLUDE_TEXT_TO_SPEECH_SHIELD
#define INCLUDE_MUSIC_PLAYER_SHIELD
#define INCLUDE_VOICE_RECOGNIZER_SHIELD

/* Define the devices signal pin */
#define camServoPin             3
#define light_pin               8
#define gas_switch_pin          21
#define gas_switch_led_pin      11
#define test_led                7
#define trigPin                 4
#define echoPin                 5
#define ir_led                  9

/* Include 1Sheeld library. */
#include <OneSheeld.h>

/* Include IR library */
#include <IRremote.h>

/* Include servo motor library */
#include <Servo.h>

/* Include the dht11 sensor library */
#include "DHT.h"

/* Define the sensors signal pins */
#define TEMP_PIN  A0
#define GAS_PIN  A1

/* Initia servo objects */
Servo CamServo;

/* Initia IR object */
IRsend irsend;

/* Crea an instance of the dht11 */
dht DHT;

/* door status variables to prevent multiple publication */
bool published_door_status_closed = false;
bool published_door_status_opened = false;

/* Variable to save the measured distance from ultrasonic */
int distance;

/* 38kHz carrier frequency for the NEC protocol */
int khz = 38;

// AC on/off at mp 23
unsigned int power[] = {4712, 2580, 384, 376, 380, 952, 380, 976, 356, 396, 360, 976, 356, 376, 380, 396, 356, 396, 360, 396, 356, 976, 356, 400, 356, 396, 356, 400, 356, 976, 356, 396, 360, 396, 356, 396, 360, 396, 356, 976, 356, 400, 356, 396, 356, 400, 356, 976, 356, 376, 380, 396, 356, 400, 356, 396, 356, 976, 360, 396, 356, 396, 360, 396, 356, 400, 356, 396, 356, 400, 356, 396, 356, 400, 356, 976, 356, 396, 360, 396, 356, 396, 360, 976, 356, 396, 356, 400, 356, 396, 360, 972, 360, 396, 360, 396, 356, 396, 356, 976, 360, 972, 360, 396, 356, 400, 356, 396, 356, 976, 356, 400, 356, 396, 360, 396, 356, 396, 360, 396, 356, 976, 360, 972, 360, 976, 356, 396, 360, 956, 356, 20116, 4676, 6568, 9276, 5012, 384, 396, 356, 396, 360, 396, 356, 400, 356, 396, 360, 396, 356, 396, 356, 400, 356, 396, 360, 396, 356, 396, 360, 396, 356, 976, 356, 976, 360, 396, 356, 396, 360, 396, 356, 396, 360, 396, 356, 396, 360, 396, 356, 400, 356, 976, 356, 396, 356, 400, 356, 396, 360, 396, 356, 396, 360, 396, 356, 400, 356, 396, 356, 396, 360, 396, 356, 400, 356, 396, 356, 400, 356, 396, 360, 396, 356, 396, 356, 400, 356, 396, 360, 396, 356, 400, 356, 396, 356, 396, 360, 396, 360, 396, 356, 396, 356, 400, 356, 396, 356, 400, 356, 396, 360, 396, 356, 396, 360, 396, 356, 396, 360, 396, 356, 396, 360, 396, 356, 396, 360, 976, 356, 976, 356, 976, 356, 380, 360, 20116, 4672};

// AC mp+ from 23 to 24
unsigned int temp24[] = {4716, 2604, 360, 396, 356, 976, 356, 976, 360, 396, 356, 976, 356, 400, 356, 396, 356, 400, 356, 396, 356, 976, 360, 396, 356, 396, 360, 396, 356, 976, 360, 396, 356, 396, 360, 972, 360, 396, 356, 980, 352, 400, 356, 396, 360, 396, 356, 396, 360, 396, 356, 976, 356, 400, 356, 396, 356, 976, 360, 396, 356, 400, 356, 396, 356, 400, 356, 396, 356, 400, 356, 396, 356, 400, 356, 976, 356, 396, 360, 396, 356, 400, 356, 976, 356, 396, 360, 396, 356, 396, 360, 976, 356, 396, 356, 400, 356, 396, 360, 396, 356, 396, 356, 976, 360, 396, 356, 400, 356, 976, 356, 396, 360, 396, 356, 396, 360, 396, 356, 396, 360, 396, 356, 400, 356, 976, 356, 396, 356, 380, 360, 20104, 4684, 6596, 9276, 5012, 356, 396, 360, 396, 356, 396, 360, 396, 356, 400, 356, 396, 356, 400, 356, 400, 356, 396, 356, 396, 356, 400, 356, 396, 360, 976, 356, 976, 356, 396, 360, 396, 356, 396, 360, 396, 356, 396, 360, 396, 356, 400, 356, 396, 356, 976, 356, 400, 356, 396, 356, 400, 356, 396, 360, 396, 356, 400, 356, 396, 356, 400, 356, 396, 356, 400, 356, 396, 356, 400, 356, 396, 356, 400, 356, 396, 356, 400, 356, 396, 356, 400, 356, 400, 356, 396, 356, 400, 352, 400, 356, 400, 356, 396, 356, 400, 356, 396, 356, 400, 356, 396, 356, 400, 356, 400, 352, 400, 356, 400, 352, 400, 356, 400, 356, 396, 356, 400, 352, 400, 356, 980, 352, 980, 352, 980, 356, 380, 356, 20104, 4684};

// AC mp+ from 24 to 25
unsigned int temp25[] = {4716, 2604, 356, 400, 356, 976, 356, 976, 360, 396, 356, 976, 356, 396, 360, 396, 356, 400, 356, 396, 356, 976, 360, 396, 356, 396, 360, 396, 356, 976, 356, 400, 356, 396, 356, 400, 356, 396, 356, 976, 360, 396, 356, 396, 360, 396, 356, 976, 360, 396, 356, 396, 360, 396, 356, 396, 360, 972, 360, 396, 356, 400, 356, 396, 356, 400, 356, 396, 356, 400, 356, 396, 360, 396, 356, 976, 356, 400, 356, 396, 356, 400, 356, 976, 356, 396, 360, 396, 356, 396, 360, 976, 356, 396, 360, 396, 356, 396, 356, 976, 360, 396, 356, 976, 356, 400, 356, 396, 360, 972, 360, 396, 356, 396, 360, 396, 356, 396, 360, 396, 356, 400, 356, 976, 356, 396, 360, 976, 356, 380, 356, 20108, 4684, 6592, 9276, 5012, 356, 396, 360, 396, 360, 396, 356, 396, 360, 396, 356, 396, 360, 396, 356, 396, 360, 396, 356, 400, 356, 396, 356, 396, 360, 976, 356, 976, 356, 396, 360, 396, 356, 396, 360, 396, 356, 400, 356, 396, 356, 400, 356, 396, 360, 976, 356, 396, 356, 396, 360, 396, 356, 400, 356, 396, 360, 396, 356, 396, 360, 396, 356, 396, 360, 396, 356, 396, 360, 396, 356, 396, 360, 396, 356, 400, 356, 396, 356, 400, 356, 396, 356, 400, 356, 396, 356, 400, 356, 396, 360, 396, 356, 396, 360, 396, 356, 396, 360, 396, 356, 396, 360, 396, 356, 396, 360, 396, 360, 396, 356, 396, 356, 400, 356, 396, 356, 400, 356, 396, 360, 972, 360, 976, 356, 976, 356, 380, 356, 20080, 4712};

/* auto optimization variable */
bool isAutoOptimize = false;

/* temp24 and temp25 checking variables */
bool temp24_isSent = false;
bool temp25_isSent = false;

/* sensor variabes */
int temp;
int gas_degree;
bool gas_HighLevelWarning = false;
bool gas_emailSent = false;
bool alarm_isPressed = false;
bool interruptPin_isPressed = false;
/* required variables for the publishing function */
char tempChar[4];
char gas_degree_char[4];

/* time relad variables */
bool isAuthorized = true;
unsigned long start_time;
unsigned long time_passed;

unsigned long ac_start_time;
unsigned long ac_time_passed;

/* broker instance security details */
const char * host_name = "m12.cloudmqtt.com";
const char * user = "azlckshz";
const char * password = "aoRcZqKQV0Kf";
const int port = 11911;

/* The topics*/
const char * ultrasonic = "ultrasonic";
const char * cam_dir = "cam/direction";
const char * cam_cap = "cam/capture";
const char * light = "light";
const char * temperature = "temp";
const char * gas = "gas";
const char * speak = "speak";
const char * ac = "ac";
const char * ac_auto = "auto";

byte interruptPin = gas_switch_pin;
void setup()
{
  pinMode(interruptPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin), interruptPin_isr, FALLING);
  // pinMode(gas_switch_pin , INPUT_PULLUP);
  pinMode(gas_switch_led_pin , OUTPUT);
  pinMode(light_pin , OUTPUT);
  pinMode(test_led , OUTPUT);
  pinMode(trigPin, OUTPUT);     // Sets the trigPin as an Output
  pinMode(echoPin, INPUT);      // Sets the echoPin as an Input
  /* Configure Arduino to use this pin as the servo signal pin */
  CamServo.attach(camServoPin);

  /* Start communication. */
  OneSheeld.begin();

  /* Disconnect from broker. */
  IOT.disconnect();

  /* Reset all connection variables to default */
  IOT.resetConnectionParametersToDefaults();

  /* Connect to mosquitto's public broker. */
  IOT.connect(host_name, user, password, port);

  /* Subscribe to new messages. */
  IOT.setOnNewMessage(&newMessage);

  /* Subscribe to connnection status callback. */
  IOT.setOnConnectionStatusChange(&connectionStatus);

  /* Subscribe to error callback. */
  IOT.setOnError(&error);

  /* Some time for app to connect. */
  delay(3000);

  /* Subscribions to topics */
  IOT.subscribe(ultrasonic);
  IOT.subscribe(cam_dir);
  IOT.subscribe(cam_cap);
  IOT.subscribe(light);
  IOT.subscribe(speak);
  IOT.subscribe(ac);
  IOT.subscribe(ac_auto);

  /* publish ready once light is connecd */
  IOT.publish("status", "ready", QOS_0);
  
  /* Start servo at the 90 degree angle */
  CamServo.write(90);

}

void loop()
{
  /************************************************* Ultrasonic Sensor *****************************************************/
  /* Get the current distance from the ultrasonic */
  distance = getUltrasonicDistance();
  //terrminal.println("");
  //Terminal.print("distance:");
  //Terminal.println(distance);
  if ( (distance < 30) && (distance > 0) && (isAuthorized == true) )
  {
    isAuthorized = false;
    start_time = millis();
    TextToSpeech.say("please enter your fingerprint");
    OneSheeld.delay(1400);

    if(published_door_status_opened == false)
    {
      /* Publish door status as opened */
      IOT.publish(ultrasonic, "your door is opened", QOS_0);
      
      /* ensure publisjing one time only */
      published_door_status_opened = true;
    }
  }
  else
  {
    if(published_door_status_closed == false)
    {
      /* Publish door status as opened */
      IOT.publish(ultrasonic, "your door is closed", QOS_0);
      
      /* ensure publisjing one time only */
      published_door_status_closed = true;
    }
  }
  /************************************************* fingerprint security timer *****************************************************/
  if ( isAuthorized == false )
  {
    time_passed = (unsigned long) (millis() - start_time);
    if ( FingerprintScanner.isNewFingerScanned() && FingerprintScanner.isVerified())
    {
      isAuthorized = true;
      TextToSpeech.say("fingerprint matched");
      OneSheeld.delay(1100);
      TextToSpeech.say("welcome home");
      OneSheeld.delay(1200);

      /* allow publishing again */
      published_door_status_closed = false;
      published_door_status_opened = false;
    }

    if ( time_passed >= 30000 )
    {
      isAuthorized = true;
      TextToSpeech.say("you are not authorized");
      OneSheeld.delay(1500);

      /* Take a photo for that stranger using the phone's front camera */
      Camera.frontCapture();

      /* delay for the camera */
      OneSheeld.delay(8000);

      /* Send an email with the last picture in OneSheeld folder. */
      Email.attachLastPicture("amr.mostaafaa@gmail.com", "Security Alert!", "Hi, A stranger has entered your home and I got a photo of him. Check it out", 1);

      /* delay for the email */
      OneSheeld.delay(3000);
    }
  }

  /************************************************* air conditioner timer *****************************************************/
    ac_time_passed = (unsigned long) (millis() - ac_start_time);
    if( isAutoOptimize )
    {
        /* increase the AC temperature from 23 to 24C if the time passed since on is >= 2sec */
        if ( ac_time_passed >= (60000 * 2) && (temp24_isSent == false) )
        {
          /* increase temp to 24 */
          irsend.sendRaw(temp24, sizeof(power) / sizeof(temp24[0]), khz); //No the approach used to automatically calculate the size of the array.
          temp24_isSent = true;
        }

        /* increase the AC temperature from 24 to 25C if the time passed since on is >= 4sec */
        if ( ac_time_passed >= (60000 * 4) && (temp25_isSent == false))
        {
          /* increase temp to 25 */
          irsend.sendRaw(temp25, sizeof(power) / sizeof(temp25[0]), khz); //Note the approach used to automatically calculate the size of the array.
          temp25_isSent = true;

          /* turn off auto-optimization since it's completed */
          isAutoOptimize = false;
          
          /* publish that auto-optimization has completed and is off now */
          IOT.publish(ac_auto, "off", QOS_0);
        }
    }  
  /************************************************* voice recognition *************************************************************/
  //check if 1Sheeld's Arduino Voice Recognition Shield received a new command
  if (VoiceRecognition.isNewCommandReceived())
  {
    //Compare the last command received by the Arduino Voice Recognition Shield with the command "on"
    if (!strcmp("light on", VoiceRecognition.getLastCommand()))
    {
      //Then turn the light on
      digitalWrite(light_pin, HIGH);
    }

    //Compare the last command received by the Arduino Voice Recognition Shield with the command "off"
    else if (!strcmp("light off", VoiceRecognition.getLastCommand()))
    {
      //Then turn the light off
      digitalWrite(light_pin, LOW);
    }

    //Compare the last command received by the Arduino Voice Recognition Shield with the command "play music"
    else if (!strcmp("play music", VoiceRecognition.getLastCommand()))
    {
      // play the music.
      MusicPlayer.play(0);
      // Terminal.println("play music now");
    }

    //Compare the last command received by the Arduino Voice Recognition Shield with the command "pause music"
    else if (!strcmp("pause music", VoiceRecognition.getLastCommand()))
    {
      // pause the music.
      MusicPlayer.pause();
      // Terminal.println("pause music now");
    }
  }

  /********************************* Temperature & Gas sensors *************************************/
  /* Read DHT all data */
  DHT.read11(TEMP_PIN);

  /* Read temperature */
  temp = (int)DHT.temperature;

  /* read gas sensor */
  gas_degree = analogRead(GAS_PIN);

  /* Convert sensors integers to char arrays */
  itoa(temp, tempChar, 10);
  itoa(gas_degree, gas_degree_char, 10);

  /* Publish sensors' data */
  IOT.publish(temperature, tempChar, QOS_0);
  IOT.publish(gas, gas_degree_char, QOS_0);
  
  /************************************************ Gas alarm ****************************************************/
  /* check for gas dangerous level */
  if ( gas_degree > 150 )
  {
     /* blink the button led*/
     digitalWrite(gas_switch_led_pin , HIGH);
     OneSheeld.delay(500);
     digitalWrite(gas_switch_led_pin , LOW);
     gas_HighLevelWarning = true;
  }
  else
  {
    /* turn off button led*/
    digitalWrite(gas_switch_led_pin , LOW);
    
    gas_HighLevelWarning = false;
    alarm_isPressed = false;
    gas_emailSent = false;
  }

  
  if( (gas_HighLevelWarning == true) && ( alarm_isPressed == false ) )
  {
    /* Turn on the buzzer. */
    Buzzer.buzzOn();
    
    if(gas_emailSent == false)
    {
        /* Send an email with the last picture in OneSheeld folder. */
        Email.send("amr.mostaafaa@gmail.com", "Dangerous Gas Alert!", "Hi, There is a dangerous level of gas in your home! Please check your dashboard.");
    
        /* delay for the email */
        OneSheeld.delay(3000);
    
        /* prevent multiple sending*/
        gas_emailSent = true;
    }
  }

  if( (interruptPin_isPressed == true) && (alarm_isPressed == false) )
  {
    /* Turn off the buzzer. */
    Buzzer.buzzOff();
    alarm_isPressed = true;
    interruptPin_isPressed = false;
  }
}

/* ######################################################################################################### */
void newMessage(char * incomingTopic, char * payload, byte qos, bool retained)
{
/*************************************************************************************************************/
  /* check for the speak topic */
  if (!strcmp(speak, incomingTopic))
  {
    /* If message is speak */
    if (!strcmp("speak", payload))
    {
      // this is for testing
      digitalWrite(test_led , HIGH);
      
      /* start listening to commands */
      VoiceRecognition.start();
      
      // this is for testing
      digitalWrite(test_led , LOW);
    }
  }
/*************************************************************************************************************/
  /* check for the light topic */
  if (!strcmp(light, incomingTopic))
  {
    /* If message is on */
    if (!strcmp("on", payload))
    {

      // this is for testing
      digitalWrite(test_led , HIGH);
      
      /* turn on light */
      digitalWrite(light_pin , HIGH);

      // delay to notice the test led light
      OneSheeld.delay(200);
      
      // this is for testing
      digitalWrite(test_led , LOW);
    }

    /* If message is off */
    else if (!strcmp("off", payload))
    {
      // this is for testing
      digitalWrite(test_led , HIGH);
      
      /* turn off light */
      digitalWrite(light_pin , LOW);

      // delay to notice the test led light
      OneSheeld.delay(200);
      
      // this is for testing
      digitalWrite(test_led , LOW);
    }
  }
/*************************************************************************************************************/
  /* check for the cam_cap topic */
  if (!strcmp(cam_cap, incomingTopic))
  {
    /* If message is capture */
    if (!strcmp("capture", payload))
    {
      // this is for testing
      digitalWrite(test_led , HIGH);
      
      /* Take a photo for that stranger using the phone's front camera */
      Camera.frontCapture();

      /* delay for the camera */
      OneSheeld.delay(8000);

      /* Send an email with the last picture in OneSheeld folder. */
      Email.attachLastPicture("amr.mostaafaa@gmail.com", "Home Camera", "Hi, here is a photo of your home", 1);

      /* delay for the email */
      OneSheeld.delay(3000);

      // this is for testing
      digitalWrite(test_led , LOW);
    }
  }
/*************************************************************************************************************/
  /* check for the cam_dir topic */
  if (!strcmp(cam_dir, incomingTopic))
  {
    /* If message is 0 */
    if (!strcmp("0", payload))
    {
      /* Start servo at the 180 degree angle */
      servo_for_loop(CamServo.read() , 180);
    }

    /* If message is 1 */
    else if (!strcmp("1", payload))
    {

      /* Start servo at the 135 degree angle */
      servo_for_loop(CamServo.read() , 135);
    }

    /* If message is 1 */
    else if (!strcmp("2", payload))
    {
      /* Start servo at the 90 degree angle */
      servo_for_loop(CamServo.read() , 90);
    }

    /* If message is 3 */
    else if (!strcmp("3", payload))
    {
      /* Start servo at the 45 degree angle */
      servo_for_loop(CamServo.read() , 45);
    }

    /* If message is 4 */
    else if (!strcmp("4", payload))
    {
      /* Start servo at the 0 degree angle */
      servo_for_loop(CamServo.read() , 0);
    }
  }
/*************************************************************************************************************/
  /* check for the ac topic */
  if (!strcmp(ac, incomingTopic))
  {
    /* If message is on */
    if (!strcmp("on", payload))
    {
      // this is for testing
      digitalWrite(test_led , HIGH);

      /* turn on ac */
      irsend.sendRaw(power, sizeof(power) / sizeof(power[0]), khz); //Note the approach used to automatically calculate the size of the array.

      // this is for testing
      digitalWrite(test_led , LOW);
    }

    /* If message is off */
    else if (!strcmp("off", payload))
    {
      // this is for testing
      digitalWrite(test_led , HIGH);

      /* turn on ac */
      irsend.sendRaw(power, sizeof(power) / sizeof(power[0]), khz); //Note the approach used to automatically calculate the size of the array.

      // this is for testing
      digitalWrite(test_led , LOW);
    }
  }
/*************************************************************************************************************/
  /* check for the auto-optimization ac topic */
  if (!strcmp(ac_auto, incomingTopic))
  {
    /* If message is on */
    if (!strcmp("on", payload))
    {
      // this is for testing
      digitalWrite(test_led , HIGH);

      /* turn on ac */
      ac_start_time = millis();
      
      // turn on auto optimization
      isAutoOptimize = true;
      
      // this is for testing
      digitalWrite(test_led , LOW);
    }

    /* If message is off */
    else if (!strcmp("off", payload))
    {
      // this is for testing
      digitalWrite(test_led , HIGH);

      // turn off auto optimization
      isAutoOptimize = false;

      // this is for testing
      digitalWrite(test_led , LOW);
    }
  }
}
/*************************************************************************************************************/
void connectionStatus(byte statusCode)
{
  /* Check connection code and display. */
  switch (statusCode)
  {
    case CONNECTION_SUCCESSFUL: Terminal.println("CONNECTION_SUCCESSFUL"); break;
    case CONNECTION_FAILED: Terminal.println("CONNECTION_FAILED"); break;
    case CONNECTION_LOST: Terminal.println("CONNECTION_LOST"); break;
    case CONNECTION_LOST_RECONNECTING: Terminal.println("CONNECTION_LOST_RECONNECTING"); break;
    case NOT_CONNECTED_YET:  Terminal.println("NOT_CONNECTED_YET"); break;
    case MISSING_HOST: Terminal.println("MISSING_HOST"); break;
  }
}

/*************************************************************************************************************/
void error(byte errorCode)
{
  /* Check error code and display. */
  switch (errorCode)
  {
    case CONNECTION_REFUSED                    : Terminal.println("CONNECTION_REFUSED"); break;
    case ILLEGAL_MESSAGE_RECEIVED              : Terminal.println("ILLEGAL_MESSAGE_RECEIVED"); break;
    case DROPPING_OUT_GOING_MESSAGE            : Terminal.println("DROPPING_OUT_GOING_MESSAGE"); break;
    case ENCODER_NOT_READY                     : Terminal.println("ENCODER_NOT_READY"); break;
    case INVALID_CONNACK_RECEIVED              : Terminal.println("INVALID_CONNACK_RECEIVED"); break;
    case NO_CONNACK_RECEIVED                   : Terminal.println("NO_CONNACK_RECEIVED"); break;
    case CONNACK_UNACCEPTABLEP_ROTOCOLVERSION  : Terminal.println("CONNACK_UNACCEPTABLEP_ROTOCOLVERSION"); break;
    case CONNACK_IDENTIFIER_REJECTED           : Terminal.println("CONNACK_IDENTIFIER_REJECTED"); break;
    case CONNACK_SERVER_UNAVAILABLE            : Terminal.println("CONNACK_SERVER_UNAVAILABLE"); break;
    case CONNACK_AUTHENTICATION_FAILED         : Terminal.println("CONNACK_AUTHENTICATION_FAILED"); break;
    case CONNACK_NOT_AUTHORIZED                : Terminal.println("CONNACK_NOT_AUTHORIZED"); break;
    case CONNACK_RESERVED                      : Terminal.println("CONNACK_RESERVED"); break;
  }
}

/*************************************************************************************************************/
/* A function that makes the whole operation of the ultrasonic and returning the detected distance */
int getUltrasonicDistance(void)
{
  /* Variable to save the sound wave travel time in microseconds */
  long duration;

  /* Variable to save the detected distance in cm */
  int distanceReturned;

  /* Clears the trigPin */
  digitalWrite(trigPin, LOW);

  /* delay 2 micro seconds */
  delayMicroseconds(2);

  /* Sets the trigPin on HIGH state for 10 micro seconds */
  digitalWrite(trigPin, HIGH);

  /* delay 10 micro seconds */
  delayMicroseconds(10);

  /* Sets the trigPin on LOW state */
  digitalWrite(trigPin, LOW);

  /* Reads the echoPin, returns the sound wave travel time in microseconds */
  duration = pulseIn(echoPin, HIGH);


  /* Calculating the distance */
  distanceReturned = duration * 0.034 / 2;

  /* Returning the detected distance in cm */
  return distanceReturned;
}

/*************************************************************************************************************/
void servo_for_loop(int start_angle , int stop_angle)
{
  if( start_angle < stop_angle )
  {
    for( ; start_angle <= stop_angle ; start_angle++ )
    {
      CamServo.write(start_angle);
      delay(25);
    }
  }
  else if ( start_angle > stop_angle )
  {
    for( ; start_angle >= stop_angle ; start_angle-- )
    {
      CamServo.write(start_angle);
      delay(25);
    }
  }
}
/*************************************************************************************************************/
void interruptPin_isr()
{
  interruptPin_isPressed = true;
}

Custom parts and enclosures

3D printed 4 parts
3d_gcode_489G4WMLVX.rar

Schematics

Schematic
Schematic rok6p5pfes

Comments

Similar projects you might like

How to Build a DIY Animatronic Halloween Prop Using Arduino

Project tutorial by amrmostaafaa

  • 1,245 views
  • 1 comment
  • 8 respects

Arduino Joystick Servo Control Using 1Sheeld

Project tutorial by amrmostaafaa

  • 1,877 views
  • 0 comments
  • 12 respects

SMS based Home Automation system using 1SHEELD

Project tutorial by Tanishq Jaiswal

  • 27,240 views
  • 11 comments
  • 63 respects

Smart Access [Home/Office Automation]

Project tutorial by Ajmal Muhammad P

  • 4,547 views
  • 6 comments
  • 16 respects

Smart Energy Saver for Your Home

Project tutorial by Dhairya Parikh

  • 8,123 views
  • 14 comments
  • 26 respects

DIY Vending Machine using Arduino & 1Sheeld

Project tutorial by Mahmoud Ahmed

  • 7,676 views
  • 7 comments
  • 33 respects
Add projectSign up / Login