Project showcase

Homotica - a simple, cost-effective home control system © GPL3+

Create a simple home automation system using Arduino and an Android phone.

  • 35,608 views
  • 7 comments
  • 108 respects

Components and supplies

A000066 iso both
Arduino UNO & Genuino UNO
Any other Arduino board compatible with the Ethernet or wifi shield will be ok
×1
M6hlqueeutbzfkhid4ph
Arduino Ethernet Shield 2
If your Arduino already provides internet connectivity, you don't need this
×1
Relay (generic)
Any relay/relay board will be fine
×1

Apps and online services

About this project

Why this project?

Everybody loves automation. Telling your home to turn the light on or enter your garage tapping on the screen of your phone are amazing stuff. At least, I think so.

That said, I decided to try to make my own system to control my home devices from my smartphone, without buying expensive third-party wifi adapter, smart bulbs or anythings like that; so, it had to be cheap, easy to use and had to be controlled from local and mobile internet connection. I know there are tons of excellent working projects out there (just to name some, Cayenne or the one by Anurag Vasanwala); anyway, I decided to make my own to fit perfectly my requirements. I focused my work on two main parts: the hardware setup and the development of a decent Android app.

Hardware setup

The “hardware” is composed by three main components: Arduino, an Ethernet adapter and a relay board. All the devices I need to control - which at the moment are some lights, the car gate, the main door and the garage door - are hooked up to the relay board; the board is controlled by the Arduino, which is then connected via an Ethernet shield to my router. Additionally, I found a 2€ RFID reader, and decided to give it a try: it's now connected to the Arduino and allows me to open/close the gate using a RFID tag; unnecessary, but nice to have.

Here is how the finished setup looks like; pretty clean I think!

Communication protocol

All the instructions provided by the user inside the app are sent to the specified Arduino IP using the UDP protocol; the received string is then parsed and the desired device activated. The Arduino private IP and the router public IP are stored inside the app database; it's up to the application to decide which one to use, depending on the currently connected network. Once the Arduino has received the input, it sends back to the phone a confirmation code, to let the user know if the command has been run properly. The app also allows the user to input the router public IP in the form of an URL string: you can generate a unique URL using a dynamic dns service (such as no-ip.com) and register it in the router; this way you'll have a (sort of) static IP address, useful in the case your internet service provider doesn't provide you one - like mine.

In defining the communication protocol between the app and Arduino, the following requirements had to be taken into accounts: it had to tell Arduino which action to perform (turn on/off/trigger/simulate button pressure...) and on which pin and had to carry a unique id string to prevent malicious users to gain control of the house. The syntax I came up with is the following:

?MT=codecode!pin!mode!del!+!pin!mode!del!+

Here ‘MT’ identifies a multiple action - or scene - and ‘codecode’ the 8 chars unique code. After the 8 chars code the first command string is sent; in case of a multiple action, more strings are sent using the ‘+’ char as a divider.

Surely somebody out there could do this way better, so if you have any suggestions about this feel free to post them, I’d be more than happy to improve the code.

User interface - Android app

Designing the app was another important point: I wanted it to be fully customizable, aesthetically pleasant and intuitive, for my family was going to use it. After designing and coding it, this is what it looks like:

Certainly not a professional one, but I’m quite happy with it!

The app provides:

  • Single devices creation, like lights or door;
  • Scenes (or “group actions”) which will run a list of predefined actions, based on existing devices;
  • Customizable vocal commands;
  • Multiple Arduinos management;

Arduino code

The Arduino sketch code is pretty simple:

  • in checkUDP() the connection is checked; if any data is available it looks for the auth code, and once it has found it, the remaining string is sent to the corresponding processing method, depending if it is a single action, a multiple action (or “scene”), a connection check request or a sensor reading request.
  • in checkRFID() Arduino checks the RFID reader; if any card is found, the read code is compared to the authorized ones and, if it is found between them, the corresponding action is launched.
  • homotica.refresh() tells the homotica library to check if enough time has passed for a certain pin to be pulled high or low; this is set when the user send a “push” command; the library is available on Github (link at the bottom of the page).

Here is the sketch code; as I said, suggestions are the welcome!

Future development

I'm planning to keep the development going, at least on the app side, for I'm quite pleased with how this project came out. I’d like to add, in the near future, the following features:

  • Time & location-based triggers
  • Sensor readings & data processing
  • Real time Arduino-to-app notifications

If you would like to take a look to the app, feel free to try it out; I made it available on the play store at this link. However, remember it's the first release,

Thank you for reading, have a good day! ;)

Dave

Schematics

Wiring diagram
Homotica bb t6f3ebqqbp

Code

Arduino sketch codeC/C++
//Sketch for Homotica Android App
#include <Homotica.h>
#include <Ethernet.h>
#include <SPI.h>
#include <RFID.h>
#include <EthernetUdp.h>

#define DEBUG_MODE
#define SKETCH_NAME "HomoticaSketch"
#define SKETCH_VERSION "1.0"
#define ACTIVE_LOW                //delete the line if you're using active_high setup

#ifdef ACTIVE_LOW
#define MHIGH 0x0
#define MLOW  0x1
#else
#define MHIGH 0x1
#define MLOW  0x0
#endif

#ifdef DEBUG_MODE
#define DEBUG_PRINT(x) Serial.print(x)
#define DEBUG_PRINTLN(x) Serial.println(x)
#else
#define DEBUG_PRINT(x)
#define DEBUG_PRINTLN(x)
#endif

#define SDA_PIN 9
#define RESET_PIN 8

IPAddress ip(192, 168, 1, 20);
byte mac[] = {0x90, 0xA2, 0xDA, 0x0E, 0xBD, 0x21};
char Data_RX;
int PIN, del, charsIndex[4], relayMode;
static int startingPIN = 4, finishPIN = 7;
unsigned const int localPort = 80;
String msg, request, recivedCode;
static String code = "abcdefgh";

char packetBuffer[200];
const char wrongAuth[] = "2";
const char positiveResponse[] = "1";
const char negativeResponse[] = "0";
EthernetUDP Udp;

Homotica homotica;

RFID mRFID(SDA_PIN, RESET_PIN);
int lastRFIDAction = 0;
static String allowedCards[] = {"6C69A3CU89", "77698D5R23"};

static String openDoor = "!7!2!1000!";
static String closeDoor = "!6!2!1000!";

void setup() {
  Ethernet.begin(mac, ip);
  Udp.begin(localPort);
  SPI.begin();
  mRFID.init();

  Serial.begin(9600);
  DEBUG_PRINT("UDP is at ");
  DEBUG_PRINTLN(Ethernet.localIP());
  DEBUG_PRINTLN("");

  for (int i = startingPIN; i < finishPIN + 1;  i++) {
    pinMode(i, OUTPUT);
    digitalWrite(i, MLOW);
  }
}

void loop() {
  checkUDP();
  checkRFID();
  homotica.refresh();
}

void checkUDP() {
  msg = "";
  int packetSize = Udp.parsePacket();
  if (packetSize) {
    
    DEBUG_PRINTLN();
    DEBUG_PRINTLN("********************NEW INCOMING PACKET**************************");
    
    Udp.read(packetBuffer, 200);
    msg = packetBuffer;

    recivedCode = msg.substring(msg.indexOf("=") + 1 , msg.indexOf("!"));
    request = msg.substring(msg.indexOf("?") + 1 , msg.indexOf("="));
  
    if (recivedCode == code) {
      Udp.beginPacket(Udp.remoteIP(), Udp.remotePort());
      String myString = msg.substring(msg.indexOf("!"));

      DEBUG_PRINT("Recived: ");
      DEBUG_PRINTLN(msg);
      DEBUG_PRINT("Substring: ");
      DEBUG_PRINTLN(myString);

      if (request == "QU") {
        charsIndex[0] = msg.indexOf("!");
        charsIndex[1] = msg.indexOf("!", charsIndex[0] + 1);
        PIN = msg.substring(charsIndex[0] + 1 , charsIndex[1]).toInt();
        String sensorReading = String(analogRead(PIN));
        char output[sensorReading.length() + 1];
        sensorReading.toCharArray(output, sensorReading.length() + 1);
        Udp.write(output);
      }
      else if (request == "ST") {
        Udp.write(positiveResponse);
        Udp.endPacket();
        processSingleRunnable(myString);
      }
      else if (request == "MT") {
        Udp.write(positiveResponse);
        Udp.endPacket();
        processMultipleRunnable(myString);
      }
      else if (request == "CH") {
        Udp.write(positiveResponse);
        Udp.endPacket();
      }
      else {
        Udp.write(negativeResponse);
        Udp.endPacket();
      }
    }
    else{
      Udp.write(wrongAuth);
      Udp.endPacket();
    }
    for ( int i = 0; i < sizeof(packetBuffer);  ++i ) {
      packetBuffer[i] = (char)0;
    }
  }
  delay(10);
}

void checkRFID() {
  if (mRFID.isCard()) {
    mRFID.readCardSerial();
    String code = "";
    for (byte i = 0; i <= 4; i++)
    {
      code += String (mRFID.serNum[i], HEX);
      code.toUpperCase();
    }
    DEBUG_PRINTLN("Read code:");
    DEBUG_PRINTLN(code);
    DEBUG_PRINTLN();
    
    for (String allowedCode : allowedCards) {
      if (allowedCode == code) {
        if (lastRFIDAction == 0) { //checks if the door has been closed or opened the last time
          processSingleRunnable(openDoor);
          lastRFIDAction = 1;
        } else if (lastRFIDAction == 1) {
          processSingleRunnable(closeDoor);
          lastRFIDAction = 0;
        }
      }
    }
    delay(2000);
  }
}

void processSingleRunnable(String msg) {
  charsIndex[0] = msg.indexOf("!");
  charsIndex[1] = msg.indexOf("!", charsIndex[0] + 1);
  charsIndex[2] = msg.indexOf("!", charsIndex[1] + 1);
  charsIndex[3] = msg.indexOf("!", charsIndex[2] + 1);
  
  PIN = msg.substring(charsIndex[0] + 1 , charsIndex[1]).toInt();
  relayMode = msg.substring(charsIndex[1] + 1, charsIndex[2]).toInt();
  del = msg.substring(charsIndex[2] + 1, charsIndex[3]).toInt();

  DEBUG_PRINTLN();
  DEBUG_PRINTLN("- OPENING -");
  DEBUG_PRINT("Pin: ");
  DEBUG_PRINT(String(PIN));
  DEBUG_PRINT("; Mode: ");
  DEBUG_PRINT(String(relayMode));
  DEBUG_PRINT("; Delay: ");
  DEBUG_PRINTLN(msg.substring(charsIndex[2] + 1, charsIndex[3]));
  DEBUG_PRINTLN("(0 --> ON, 1 --> OFF, 2 --> PUSH, 3 --> TOGGLE)");

  if (0 <= relayMode <= 2 && startingPIN <= PIN <= finishPIN)
  {
    if (relayMode == 0) {
      digitalWrite(PIN, MHIGH); //relay on
    }
    else if (relayMode == 1) {
      digitalWrite(PIN, MLOW);
    }
    else if (relayMode == 2) {
      homotica.pushPin(PIN, del, digitalRead(PIN));
    }
    else if (relayMode == 3) {
      digitalWrite(PIN, !digitalRead(PIN));
    }
  }
}

void processMultipleRunnable(String msg) {
  char input[msg.length() + 1];
  msg.toCharArray(input, msg.length() + 1);
  const char* divider = ("+");
  int lastIndex = 0;
  for (int k = 0; k < sizeof(input); k++) {
    if (String(input[k]) == "+") {
      processSingleRunnable(msg.substring(lastIndex, k));
      k += 1;
      lastIndex = k;
    }
  }
}

Comments

Similar projects you might like

Arduino Obstacle Avoidance Robot with Ultrasonic HC-SR04

Project tutorial by Jorge Rancé

  • 640 views
  • 1 comment
  • 10 respects

Using Finite State Machines

by Gustavo Gonnet

  • 6,700 views
  • 2 comments
  • 18 respects

Alexa: "Your Clothes Are Dry"

Project in progress by TNunnster

  • 1,631 views
  • 0 comments
  • 6 respects

How to Configure NeoPixels Using Vixen Lights and Arduino

Project tutorial by Victor Aguilar

  • 364 views
  • 0 comments
  • 3 respects

Temperature Streaming with Arduino + Big Data Tools

Project showcase by Gabriel Rodriguez

  • 1,600 views
  • 1 comment
  • 15 respects

Bluetooth Speaker w/ Music-Reactive LED Matrix

Project tutorial by Modustrial Maker

  • 3,797 views
  • 0 comments
  • 14 respects
Add projectSign up / Login