Neel Nadkarni
Published © MIT

Hardware Based Smart Home Hub

Be able to control a variety of smart home features through simulated clicking of remote buttons.

IntermediateFull instructions provided114
Hardware Based Smart Home Hub

Things used in this project

Hardware components

Argon
Particle Argon
×1
SRD-05VDC-SL-C Relay
Each separate relay controls a separate remote. Amount of relays based on amount of buttons you want to be able to click [CAN BE REPLACED BY A RELAY ON A BREAKOUT BOARD]
×3
1N4007 – High Voltage, High Current Rated Diode
1N4007 – High Voltage, High Current Rated Diode
You need one diode per relay (if you are deciding to manually wire the relay) [CAN BE REPLACED BY A RELAY ON A BREAKOUT BOARD]
×3
General Purpose Transistor NPN
General Purpose Transistor NPN
You need one transistor per relay (if you are deciding to manually wire the relay) [CAN BE REPLACED BY A RELAY ON A BREAKOUT BOARD]
×3
4x20 LCD Screen
×1
Analog joystick (Generic)
Must have click function
×1

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Schematics

Wiring the Joystick and the LCD screen

IMPORTANT: If your joystick is using 5V logic, you will need a voltage divider or logic level shifter as the Particle Argon runs on 3.3V logic.

How to wire a relay manually

Using this diagram you can wire your own relay for this project. You can also find relays already on breakout boards and ignore this step.

Code

Full code for the Particle Argon

C/C++
Change the relay constants to the digital pins you chose on your micro controller. Change the joystick constants as well. Change the time zone to fit your time zone (UTC-X)
#include "Particle.h"
#include <LiquidCrystal_I2C_Spark.h>

SYSTEM_THREAD(ENABLED);

//LCD Info ==
LiquidCrystal_I2C *lcd; // Initialize variable for LCD

const String textToDisplay[] = {"Lights On", "Lights Off", "Toggle Fan", "Schedule Task", "Screen Off", "Power off"};
const int ACTIONSLEN = arraySize(textToDisplay);

int startTime = 0;

// Relays ==
const int RELAY_LIGHTS_ON_PIN = 7; 
const int RELAY_LIGHTS_OFF_PIN = 8; 
const int TOGGLE_FAN_PIN = 6; 

// Joystick ==
const int JOYSTICK_CLICK_PIN = 13;
const int JOYSTICK_Y_PIN = A4;
const int JOYSTICK_X_PIN = A2;

// Joystick offsets ==
const int JOYSTICK_OFFSET = 400;
const int JOYSTICK_CENTER_Y = 1200;
const int JOYSTICK_CENTER_X = 1200;

// Joystick action nums ==
const int JOYSTICK_LOCATION_CENTER = 0;
const int JOYSTICK_LOCATION_LEFT = 1;
const int JOYSTICK_LOCATION_RIGHT = 2;
const int JOYSTICK_LOCATION_UP = 3;
const int JOYSTICK_LOCATION_DOWN = 4;
const int JOYSTICK_ACTION_CLICK = 5;

// Running state
int state = 0;

// Task Scheduler ==
retained int targetHour = 0; // 0 - 12
retained int targetMinute = 0; // 0 - 60
retained bool isAM = true;

retained bool screenOff = false;

int currHour = 0;
int compMinute = 0;

int cursorPos = 0;

retained bool isSCHEDULED = false;
retained int scheduledState = 0;

bool SCHEDULEMODE = false;

String centeredText;
SystemSleepConfiguration config;

int lastMinute = 0;

void setup() {
    System.enableFeature(FEATURE_RETAINED_MEMORY);

    Watchdog.init(WatchdogConfiguration().capabilities(WatchdogCap::DEBUG_RUNNING | WatchdogCap::RESET).timeout(30s));
    Watchdog.start();
    Watchdog.refresh();

    lcd = new LiquidCrystal_I2C(0x27, 20, 4);
    lcd->init();
    lcd->backlight();
    lcd->display();
    
    pinMode(RELAY_LIGHTS_OFF_PIN, OUTPUT);
    pinMode(RELAY_LIGHTS_ON_PIN, OUTPUT);
    pinMode(TOGGLE_FAN_PIN, OUTPUT);

    pinMode(JOYSTICK_CLICK_PIN, INPUT_PULLUP);
    pinMode(JOYSTICK_Y_PIN, INPUT);
    pinMode(JOYSTICK_X_PIN, INPUT);
  
    Time.zone(-5);
    lastMinute = Time.minute();
    
    updateDisplay();
}

void loop(void) {
    if((isAM == Time.isAM()) && (targetMinute == Time.minute()) && (targetHour == Time.hourFormat12()) && isSCHEDULED){
        runTask(scheduledState, true);
        isSCHEDULED = false;
    }

    if (lastMinute != Time.minute()) {
        lastMinute = Time.minute();
        updateDisplay();
    }

    if(screenOff){
        runTask(8, false);
    }

    switch (checkJoystickState()) {
        case JOYSTICK_LOCATION_UP:
            state = (state + 1) % (ACTIONSLEN);
            delay(250);
            updateDisplay();
            break;
        case JOYSTICK_LOCATION_DOWN:
            state = (state + (ACTIONSLEN - 1)) % (ACTIONSLEN);
            delay(250);
            updateDisplay();
            break;
        case JOYSTICK_ACTION_CLICK:
            runTask(state, false);
            break;
        default:
            break;
    } 

    Watchdog.refresh();
}

void displayFinishedTask(String text){
    clearLine(1);
    lcd->setCursor(0,1);
    lcd->print(padText(text));
    delay(1500);
    updateDisplay();
}

void relayToggle(int pin){
    digitalWrite(pin, HIGH);
    delay(300);
    digitalWrite(pin, LOW);
}

void runTask(int state, bool wasScheduled){
    switch(state){
            case 0:
                relayToggle(RELAY_LIGHTS_ON_PIN);
                displayFinishedTask("Light turned on!");
            break;
            case 1:
                relayToggle(RELAY_LIGHTS_OFF_PIN);
                displayFinishedTask("Light turned off!");
            break;
            case 2:
                relayToggle(TOGGLE_FAN_PIN);
                displayFinishedTask("Fan Toggled!");
            break;
            case 3:
                delay(300);
                enterScheduleMode();
            break;
            case 4:
                lcd->clear();
                print4L("Powering off...", "Goodbye!", "Click Joystick to", "Power On!");
                delay(2000);
                lcd->noBacklight();
                lcd->noDisplay();
                screenOff = true;
                delay(2000);

                while(checkJoystickState() != JOYSTICK_ACTION_CLICK){
                    Watchdog.refresh();

                    if((isAM == Time.isAM()) && (targetMinute == Time.minute()) && (targetHour == Time.hourFormat12()) && isSCHEDULED){
                        runTask(scheduledState, true);
                        isSCHEDULED = false;
                    }
                }

                screenOff = false;

                delay(200);

                lcd->display();
                lcd->backlight();

                state = 0;
                updateDisplay();
            break;
            case 5:
                lcd->clear();
                print4L("Powering off...", "Goodbye!", "Click Joystick to", "Power On!");
                delay(2000);
                lcd->noBacklight();
                lcd->noDisplay();
                delay(2000);
    
                config.mode(SystemSleepMode::HIBERNATE)
                    .gpio(JOYSTICK_CLICK_PIN, FALLING);
                System.sleep(config);
            break;
          default:
            clearLine(1);
            lcd->setCursor(0,1);
            lcd->print("*****ERROR*****");
            break;
        }

    String data = "[\"" + textToDisplay[state] + "\", \"" + String(wasScheduled) + "\"]";
    Particle.publish("pushToSheet", data, PRIVATE);
}

void print4L(String line1, String line2, String line3, String line4){
    lcd->clear();
    lcd->setCursor(0, 0);
    lcd->print(line1);
    lcd->setCursor(0, 1);
    lcd->print(line2);
    lcd->setCursor(0, 2);
    lcd->print(line3);
    lcd->setCursor(0, 3);
    lcd->print(line4);
}

String padText(String text) {
    uint16_t displayWidth = 20;
    int textLength = text.length();

    if (textLength >= displayWidth) {
    return text.substring(0, displayWidth);
    }

    int padding = (displayWidth - textLength) / 2;
    centeredText = "";

    for (int i = 0; i < padding; i++) {
        centeredText += ' ';
    }

    centeredText += text;

    while (centeredText.length() < displayWidth) {
        centeredText += ' ';
    }

    return centeredText;
}


void updateDisplay(){
    String currTime = (String(Time.hourFormat12()) + ":" + (String(Time.minute()).length() < 2 ? ("0" + String(Time.minute())) : String(Time.minute()))  + " " + (Time.isAM() ? "AM" : "PM"));
    String schedhTime = isSCHEDULED ? (String(targetHour) + ":" + (String(targetMinute).length() < 2 ? ("0" + String(targetMinute)) : String(targetMinute)) + " " + (isAM ? "AM" : "PM")) : "Pending";
    int schedhTimeLen = schedhTime.length();

    int state2 = (state + 1) % (ACTIONSLEN);

    print4L
    (
        (String(Time.hourFormat12()) + ":" + (String(Time.minute()).length() < 2 ? ("0" + String(Time.minute())) : String(Time.minute()))  + " " + (Time.isAM() ? "AM" : "PM")), 
        "--------------------", 
        padText(textToDisplay[state]), 
        padText(textToDisplay[state2])
    );

    lcd->setCursor(20 - schedhTimeLen, 0);
    lcd->print(schedhTime);

    lcd->setCursor(0, 2);
    lcd->print(">");
    lcd->setCursor(19, 2);
    lcd->print("<");
}

void clearLine(int line) {
    lcd->setCursor(0, line);
    lcd->print("                    ");
}

void updateDisplaySchedule() {
    uint8_t minLen = String(targetMinute).length();
    uint8_t minHour = String(targetHour).length();
    
    String targMinStr = minLen < 2 ? "0" + String(targetMinute) : String(targetMinute);
    String targHrStr = minHour < 2 ? "0" + String(targetHour): String(targetHour);
    
    lcd->clear();
    lcd->setCursor(4, 0);
    lcd->print(targHrStr + ":" + targMinStr + " ");
    lcd->print(isAM ? "AM" : "PM");
    lcd->setCursor((cursorPos * 3) + 4, 1);
    lcd->print("^");
}

void enterScheduleMode() {
    SCHEDULEMODE = true;
    clearLine(0);
    clearLine(1);
    updateDisplaySchedule();
    
    bool loopE = true;
    
    while(loopE){
        Watchdog.refresh();

        if (checkJoystickState() == JOYSTICK_LOCATION_RIGHT) { 
            cursorPos = (cursorPos + 1) % 3; 
            updateDisplaySchedule();
            delay(100);
        }
        
        if (checkJoystickState() == JOYSTICK_LOCATION_LEFT) { 
            cursorPos = (cursorPos + 2) % 3; 
            updateDisplaySchedule();
            delay(100);
        }
        
        if (checkJoystickState() == JOYSTICK_LOCATION_DOWN) {
            if (cursorPos == 0) {
                targetHour = (targetHour + 1) % 13;
            } else if (cursorPos == 1) {
                targetMinute = (targetMinute + 1) % 61;
            } else if (cursorPos == 2) {
                isAM = !isAM;
            }
            updateDisplaySchedule();
            delay(100); 
        }
        
        if (checkJoystickState() == JOYSTICK_LOCATION_UP) {
            if (cursorPos == 0) {
                targetHour = (targetHour + 12) % 13;
            } else if (cursorPos == 1) {
                targetMinute = (targetMinute + 60) % 61;
            } else if (cursorPos == 2) {
                isAM = !isAM;
            }
            updateDisplaySchedule();
            delay(100);
        }
        
        if (checkJoystickState() == JOYSTICK_ACTION_CLICK){
            loopE = false;
            SCHEDULEMODE = false;
        }
    }
    
   lcd->clear();
   
   delay(500);
   
   lcd->setCursor(0,0);
   lcd->print("  Pick a Task!");
   
   delay(2000);
   
   bool loopB = true;
   state = 0;
   updateDisplay();
   while(loopB){
        Watchdog.refresh();

        if(checkJoystickState() == JOYSTICK_LOCATION_UP){
            scheduledState = (scheduledState + 1) % (ACTIONSLEN - 2);
            state = (state + 1) % (ACTIONSLEN - 2);
            delay(250);
            updateDisplay();
        }
        
        if(checkJoystickState() == JOYSTICK_LOCATION_DOWN){
            scheduledState = (scheduledState + (ACTIONSLEN - 3)) % (ACTIONSLEN - 2);
            state = (state + (ACTIONSLEN - 3)) % (ACTIONSLEN - 2);
            delay(250);
            updateDisplay();
        }
        
        if (checkJoystickState() == JOYSTICK_ACTION_CLICK){
            loopB = false;
        }
   }
   
   lcd->clear();
   lcd->setCursor(0, 0);
   lcd->print(" Scheduled for:");
   lcd->setCursor(3, 1);
   
    uint8_t minLen = String(targetMinute).length();
    uint8_t minHour = String(targetHour).length();
    
    String taskArr[3] = {"Lights On", "Lights Off", "Toggle Fan"};
    String taskSelected = taskArr[scheduledState];
    
    String targMinStr = minLen < 2 ? "0" + String(targetMinute) : String(targetMinute);
    String targHrStr = minHour < 2 ? "0" + String(targetHour): String(targetHour);
    
    lcd->print(targHrStr + ":" + targMinStr + " ");
    lcd->print(isAM ? "AM" : "PM");
    isSCHEDULED = true;
    
    delay(2000);
    
    lcd->clear();
    lcd->setCursor(0, 0);
    lcd->print("Scheduled Task:");
    lcd->setCursor(0, 1);
    lcd->print(padText(taskSelected));
    
    delay(2000);
    
    state = 0;
    lcd->clear();

    updateDisplay();
    
}

int checkJoystickState(){
    int yValue = analogRead(JOYSTICK_Y_PIN);
    int xValue = analogRead(JOYSTICK_X_PIN);
    
    if (digitalRead(JOYSTICK_CLICK_PIN) == LOW){
        return JOYSTICK_ACTION_CLICK;
    }
    
    if (yValue < JOYSTICK_CENTER_Y - JOYSTICK_OFFSET) {
        return JOYSTICK_LOCATION_DOWN;
    } 
    else if (yValue > JOYSTICK_CENTER_Y + JOYSTICK_OFFSET) {
        return JOYSTICK_LOCATION_UP;
    } 
    else if (xValue < JOYSTICK_CENTER_X - JOYSTICK_OFFSET) {
        return JOYSTICK_LOCATION_LEFT;
    } 
    else if (xValue > JOYSTICK_CENTER_X + JOYSTICK_OFFSET) {
        return JOYSTICK_LOCATION_RIGHT;
    } 
    else {
        return JOYSTICK_LOCATION_CENTER;
    }
}

Credits

Neel Nadkarni
2 projects • 0 followers
Thanks to Jim Brower.

Comments