Hackster is hosting Hackster Holidays, Ep. 7: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Friday!Stream Hackster Holidays, Ep. 7 on Friday!
Alberto Sartori
Published © GPL3+

SMART CUBE: A New Way to Control Your Home

Discover a new way to interact with your smart home thanks to Arduino 101: tilt and shake the cube to control your devices via Bluetooth!

IntermediateFull instructions provided3 hours23,485

Things used in this project

Hardware components

Arduino 101
Arduino 101
×1
LED (generic)
LED (generic)
1 blue - 1 yellow - 1 green - 2 red
×5
Resistor 221 ohm
Resistor 221 ohm
×5
Breadboard (generic)
Breadboard (generic)
Not mandatory
×1
Seeed Studio Base Shield V2
×1
Seeed Studio Grove LCD RGB Backlight
×1
Seeed Studio Grove Sound Sensor
×1
Seeed Studio Grove Buzzer
×1
Seeed Studio Grove Temperature Sensor
×1
Keypad
×1
Jumper wires (generic)
Jumper wires (generic)
×1
9V battery (generic)
9V battery (generic)
×1
9V Battery Clip
9V Battery Clip
×1
Medium density fiber (MDF) 20x30 cm
3mm MDF or other material for laser cut
×1

Software apps and online services

Arduino IDE
Arduino IDE
nRF Connect SDK
Nordic Semiconductor nRF Connect SDK
Not mandatory

Hand tools and fabrication machines

Laser cutter (generic)
Laser cutter (generic)
Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Custom parts and enclosures

SMART CUBE box - Laser Cut

This is the file (.dxf) that you can upload on your laser cutter. I used a medium density fiberboard (MDF) of 3mm. Cut the black lines and engrave red lines. Important: if your material thickness is different you have to modify this sketch.

SMART CUBE box in .pdf

The same file of the box but in a different format: .pdf

Schematics

SMART CUBE - Fritzing schematics

This is a .jpeg of the Fritzing scheme (yellow wires are connected to Grove components)

SMART CUBE - Grove Base Shield connections

This is a scheme of the connections in the Grove Shield

Code

SMART CUBE - complete code with comments

Arduino
This is the code to upload on your Arduino 101. You can use this code as it is or customize based on your needs. Follow the comments to understand how it works.
// Libraries
#include "CurieIMU.h" // Acelerometer & Gyroscope
#include <CurieBLE.h> // Bluetooth Low Energy
#include <Wire.h>
#include "rgb_lcd.h"  // LCD
#include <Keypad.h>   // Keypad

rgb_lcd lcd; // LCD initialization

// Bluetooth initialization:
BLEPeripheral blePeripheral;                     // BLE Peripheral Device (the board you're programming)
// BLE Services
BLEService lightsService("1815");                // BLE Automation IO (1815) - lights informations
BLEService termoService("1815");                 // BLE Automation IO - temperature informations
BLEService TVService("1815");                    // BLE Automation IO - tv informations
// BLE characteristic
BLEUnsignedCharCharacteristic lightsChar("2A56", // BLE characteristic Digital (2A56)  - lights
    BLERead | BLENotify);
BLEUnsignedCharCharacteristic termoChar("2A56",  // BLE characteristic Digital - temperature
    BLERead | BLENotify);
BLEUnsignedCharCharacteristic TVChar("2A58",     // BLE characteristic Analog (2A58) - tv
    BLERead | BLENotify);

// Constant and variables declaration:

// Face orientation and shake functions:
int lastOrientation = - 1;              // previous orientation (for comparison)
unsigned long previousMillis = 0;       // last time update
unsigned long interval = 2000;          // time to wait in up position before the face activation
unsigned long SHAKEpreviousMillis = 0;  // last time update
unsigned long SHAKEinterval = 2000;     // time to wait during shaking for face deactivation
boolean keep = false;                   // this is used to count only one time the face orientation change
int lastFUNCTION = -1;                  // this is used to know what is the previous rientation

// Faces initilization: at the beginning every face is false
boolean face0 = false;
boolean face1 = false;
boolean face2 = false;
boolean face3 = false;
boolean face4 = false;
boolean face5 = false;

// LIGHTS face
const int LEDlights =  11;   // pin 11: yellow led

// TEMPERATURE face
const int pinTemp = A0;      // pin A0: temperature sensor
const int LEDhot =  12;      // pin 12: red led
const int LEDcold =  13;     // pin 13: blue led
float temperature;           // temperature value memorization
int B=3975;                  // B value of the thermistor
float resistance;            // resistance value memorization
float tooHot = 26.0;         // temperature at which the air conditioner is activated [SET]
float tooCold = 23.0;        // temperature at which the heater is activated [SET]

// TIMER face
int BUZZER = 3;              // pin 3: buzzer
boolean KEEPtime = false;    // this is used to count only one time the face orientation change (not restart while counting)
int TIMERmillis = 0;         // the following are for the countdown determination
int prevSHOWsecond = 0;
int CountdownInMillis = 0;
int SHOWmillis = 0;          // millis value calculation result
int SHOWminute = 0;          // minutes value to show in the monitor for the countdown
int SHOWseconds = 0;         // seconds value to show in the monitor for the countdown
const int SETminute =  2;    // set 2 minute timer [SET]
const int SETsecond =  30;   // set 30 seconds timer [SET]

// SOUND face
const int soundLED = 9 ;     // pin 9: green led    
const int soundSENSOR = A1;  // pin A0: sound sensor
int brightness = 0;          // green led brightness initialization

// TV face
const byte ROWS = 4;         // four rows keypad
const byte COLS = 3;         // three columns keypad
char keys[ROWS][COLS] = {
  {'1','2','3'},
  {'4','5','6'},
  {'7','8','9'},
  {'*','0','#'}
};                                  // keypad button values
byte rowPins[ROWS] = {10,8,7,6};    // pin 10,8,7,6: connect to the row pinouts of the keypad
byte colPins[COLS] = {5,4,2};       // pin 5,4,2: connect to the column pinouts of the keypad
Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );  // keypad initialization

// OFF face
const int LEDOFF =  A2;      // pin A2: red led


void setup() {

  pinMode(LEDlights, OUTPUT); // every led is set as an output
  pinMode(LEDhot, OUTPUT);
  pinMode(LEDcold, OUTPUT);
  pinMode(soundLED,OUTPUT);
  pinMode(LEDOFF, OUTPUT);
  
  Serial.begin(9600); // initialize Serial communication

  CountdownInMillis = (SETminute*60 + SETsecond)*1000; // this calculates the corrispondent value in millis from the minutes and seconds setting
  lcd.begin(16, 2);     // LCD initialization
  lcd.setRGB(0, 0, 0);  // LCD RGB is OFF at the beginning
  
  // initialize device
  Serial.println("Initializing IMU device...");
  CurieIMU.begin();

  // Set the accelerometer range to 2G
  CurieIMU.setAccelerometerRange(2);

  // Enable Shock Detection 
  CurieIMU.setDetectionThreshold(CURIE_IMU_SHOCK, 7000);  // 7.0g = 7000 mg  (this value set the intensity of the shock)
  CurieIMU.setDetectionDuration(CURIE_IMU_SHOCK, 2000);   // 2000ms (this value set the duration of the shock)
  CurieIMU.interrupts(CURIE_IMU_SHOCK);

  // BLE setup initialization
  blePeripheral.setLocalName("SmartCubeSketch");                 // the name of the project 
  
  blePeripheral.setAdvertisedServiceUuid(lightsService.uuid());  // add the lights service UUID
  blePeripheral.addAttribute(lightsService);                     // add the BLE lights service
  blePeripheral.addAttribute(lightsChar);                        // add the BLE lights characteristic
  lightsChar.setValue(3);                                        // initial value for this characteristic = 3
  //BLE lights value meaning: 0 = lights off, 1 = lights on, 3 = initial state, 4 = not used
  
  blePeripheral.setAdvertisedServiceUuid(termoService.uuid());   // add the temperature service UUID
  blePeripheral.addAttribute(termoService);                      // add the BLE temperature service
  blePeripheral.addAttribute(termoChar);                         // add the BLE temperature characteristic
  termoChar.setValue(0);                                         // initial value is 0: cold off - hot off
  //BLE termo value meaning: 0 = cold off - hot off, 1 = cold on - hot off, 2 = cold off - hot on, 3 = not used

  blePeripheral.setAdvertisedServiceUuid(TVService.uuid());      // add the tv service UUID
  blePeripheral.addAttribute(TVService);                         // add the BLE tv service
  blePeripheral.addAttribute(TVChar);                            // add the tv characteristic
  TVChar.setValue('x');                                          // initial value for this characteristic (x means nothing)
  //BLE TV  value meaning: #number corrispond to the pressed button, C: close TV, O: open TV, x: initial state

  blePeripheral.begin();
  Serial.println("Bluetooth device active, waiting for connections...");
  
} // setup end

void loop() {

  BLECentral central = blePeripheral.central(); // BLE connection
  
  unsigned long currentMillis = millis();       // current value of time in milliseconds

  // the following code comes from www.arduino.cc/en/Tutorial/Genuino101CurieIMUccelerometerOrientation
  // it is used to detect the orientation of the board
  int orientation = - 1;                        // the board's orientation
  String orientationString;                     // string for printing description of orientation
  
  // read accelerometer:
  int x = CurieIMU.readAccelerometer(X_AXIS);
  int y = CurieIMU.readAccelerometer(Y_AXIS);
  int z = CurieIMU.readAccelerometer(Z_AXIS);

  // calculate the absolute values, to determine the largest
  int absX = abs(x);
  int absY = abs(y);
  int absZ = abs(z);

  if ( (absZ > absX) && (absZ > absY)) {
    // base orientation on Z
    if (z > 0) {
      orientationString = "up";
      orientation = 0;  
    } else {
      orientationString = "down";
      orientation = 1;
    }
  } else if ( (absY > absX) && (absY > absZ)) {
    // base orientation on Y
    if (y > 0) {
      orientationString = "digital pins up";
      orientation = 2;
    } else {
      orientationString = "analog pins up";
      orientation = 3;
    }
  } else {
    // base orientation on X
    if (x < 0) {
      orientationString = "connector up";
      orientation = 4;
    } else {
      orientationString = "connector down";
      orientation = 5;
    }
  }
  // end of the tutorial code.
  // at this point you have the orientation value of the board constantly updated:
  /*
    The orientations of the board:
    0: flat, processor facing up     (TIMER)
    1: flat, processor facing down   (TV)
    2: landscape, analog pins down   (TEMPERATURE) 
    3: landscape, analog pins up     (OFF)
    4: portrait, USB connector up    (LIGHTS)
    5: portrait, USB connector down  (SOUND)
  */

  // for this project you need to know if the face has changed from the previous face function [lastFUNCTION != orientation]
  // but this information is printed only if the face is in the UP position for more than [interval] time
  // and only for one time [keep] (you don't nedd to constantly activate the face, you just need it one time)
  // because the orientation value is constantly updated you need to start counting time when the orientation change [orientation != lastOrientation]
  
  if (orientation != lastOrientation) {  // if the orientation has changed, start to count time
    lastOrientation = orientation;       // memorize the current orientation of the face
    previousMillis = currentMillis;      // memorize the time when the face has changed
    keep = false;                        
  } else if (currentMillis - previousMillis > interval && keep == false && lastFUNCTION != orientation) {
    //this condition print the orientation only if the face is up for an interval
    //and only for one time (keep)
    //and only if the face is different from the previous loop
    Serial.println(orientationString);   // print the orientation
    // the current face [orientation] is set as true (that means that the face function is set as activated)
    if (orientation == 1) {              // TV face
      face1 = true;                      // TV face becomes true
      lastFUNCTION = orientation;        // memorize this activation in [lastFUNCTION]
      Serial.println("TV true");         // print the activated face
      TVChar.setValue('O');              // O: open the tv signal (BLE): tv is open only one time
    }
    if (orientation == 4) {              // LIGHTS face
      face4 = true;                      
      lastFUNCTION = orientation;
      Serial.println("LIGHTS true");
    }
    if (orientation == 3) {              // OFF face
      face3 = true;
      lastFUNCTION = orientation;
      Serial.println("OFF true");
    }
    if (orientation == 5) {              // SOUND face
      face5 = true;
      lastFUNCTION = orientation;
      Serial.println("SOUND true");
    }
    if (orientation == 2) {             // TEMPERATURE face
      face2 = true;
      lastFUNCTION = orientation;
      Serial.println("TEMPERATURE true");
    }
    if (orientation == 0) {             // TIMER face
      face0 = true;
      lastFUNCTION = orientation;
      Serial.println("TIMER true");
      if (KEEPtime == false) {          // timer is activated only if it is the 1st cycle or has been stopped
        TIMERmillis = currentMillis;    // start counting time
      }
    }
    keep = true;  // [keep] change value so that, in the next loop, you can't enter in this condition if the face don't change (avoid to activate another time the same face)
  }

  // this condition is for the shake function: if you shake for more than [SHAKEinterval] time, the face is deactivated
  if (CurieIMU.getInterruptStatus(CURIE_IMU_SHOCK) && currentMillis - SHAKEpreviousMillis > SHAKEinterval) {
    Serial.println("SHAKE");               // print "SHAKE" if shake is detected 
    // the last activated face [lastFUNCTION] is set as false (that means that the face function is deactivated)
     
    //TV deactivation
    if (lastFUNCTION == 1) {                // TV face
      TVChar.setValue('C');                 // C: close the tv BLE signal
      Serial.println("TV false - CLOSE");   // print the closed face
      face1 = false;                        // TV face becomes false
    }
    //LIGHTS deactivation
    if (lastFUNCTION == 4) {                // LIGHTS face
      if (central.connected() == true) {    // if a central is connected to peripheral:
        lightsChar.setValue(0);             // lights OFF BLE signal
        digitalWrite (LEDlights, HIGH);     // open the yellow led to see the cube in the dark
      }
      Serial.println("LIGHTS false - CLOSE");
      face4 = false;                        // LIGHTS face become false
    }
    // OFF 
    if (lastFUNCTION == 3) {                // OFF face
      // OFF face shaked: everything is closed and red led OFF is open
      digitalWrite (LEDOFF, HIGH);          // red led OFF is on when cube is closed
      // now close all the activated functions:
      // CLOSE TV
      TVChar.setValue('C');                 // C: close the tv BLE signal
      Serial.println("TV false - CLOSE");
      face1 = false;
      // CLOSE LIGHTS
      Serial.println("LIGHTS false - CLOSE");
      if (central.connected() == true) {
        lightsChar.setValue(0); 
        digitalWrite (LEDlights, LOW);      // lights led is closed if OFF face is shaked
      }
      face4 = false;
      // CLOSE SOUND 
      analogWrite(soundLED, LOW);           // close the sound led
      Serial.println("SOUND false - CLOSE");
      face5 = false;
      //CLOSE TEMPERATURE
      if (central.connected() == true) {
        digitalWrite(LEDhot, LOW);
        digitalWrite(LEDcold, LOW);
        termoChar.setValue(0);              // temperature BLE signal: 0 = cold off - hot off
      }
      Serial.println("TEMPERATURE false - CLOSE");
      face2 = false;
      // CLOSE TIMER
      Serial.println("TIMER false - CLOSE");
      lcd.setRGB(0, 0, 0);                  // the LCD RGB is closed
      lcd.clear();
      KEEPtime = false;
      face0 = false;
      // The cube is inactive, only OFF led is active
      Serial.println("OFF false - CLOSE");
      face3 = false;                        // OFF face becomes false
    }
    // SOUND deactivation
    if (lastFUNCTION == 5) {                // SOUND face
      analogWrite(soundLED, LOW);           // close the sound led
      Serial.println("SOUND false - CLOSE");
      face5 = false;                        // SOUND face becomes false
    }
    // TEMPERATURE deactivation
    if (lastFUNCTION == 2) {                // TEMPERATURE face
      // if a central is connected to peripheral:
      if (central.connected() == true) {
        digitalWrite(LEDhot, LOW);          // close temperature red led 
        digitalWrite(LEDcold, LOW);         // close temperature blue led
        termoChar.setValue(0);              // temperature BLE signal: 0 = cold off - hot off
      }
      Serial.println("TEMPERATURE false - CLOSE");
      face2 = false;                        // TEMPERATURE face became false
    }
    // TIMER deactivation
    if (lastFUNCTION == 0) {                // TIMER face
      Serial.println("TIMER false - CLOSE");
      face0 = false;                        // TIMER face became false
      // if you shake the cube when the time is running, the LCD became red and show the remaining time to countdown
      lcd.setRGB(180, 40, 0);               // the RGB backlight become red
      lcd.clear(); // lcd is cleared
      lcd.setCursor(0, 0);
      lcd.print("STOP AT ");
      lcd.setCursor(8, 0);
      lcd.print(SHOWminute);                // indicates the minutes when you shake the cube
      lcd.setCursor(9, 0);
      lcd.print(":");
      lcd.setCursor(10, 0);
      lcd.print(SHOWseconds);               // indicates the seconds when you shake the cube
      tone(BUZZER,1000,1000);               // it make a short sound
      delay(2000);
      lcd.clear();                          // clear the LCD
      lcd.setRGB(0, 0, 0);                  // LCD RGB backlight is closed
      KEEPtime = false;                     // TIMER face became false
    }
      
    SHAKEpreviousMillis = currentMillis;    // memorize the value for the [SHAKEinterval] calculation
  }

  // the following instructions are executed in loop only if the face is activated

  if (face1 == true) {                      // TV face
    digitalWrite (LEDOFF, LOW);             // if this face is true the OFF face led is LOW 
    if (central.connected() == true) {      // if the cube is BLE connected 
      char key = keypad.getKey();           // read the value from the keypad
      if (key && orientation == 1){         // if something is pressed and only when the tv face is up (avoid involuntary keypad pression)
        if (key == '0') {                   // if the pressed key is 0
          TVChar.setValue(key);             // send the [key] value via BLE
          Serial.println(key);              // print the pressed button (comment if you don't want to show this information)
        }
        if (key == '1'){
          TVChar.setValue(key);
          Serial.println(key);
        }
        if (key == '2'){
          TVChar.setValue(key);
          Serial.println(key);
        }
        if (key == '3'){
          TVChar.setValue(key);
          Serial.println(key);
        }
        if (key == '4'){
          TVChar.setValue(key);
          Serial.println(key);
        }
        if (key == '5'){
          TVChar.setValue(key);
          Serial.println(key);
        }
        if (key == '6'){
          TVChar.setValue(key);
          Serial.println(key);
        }
        if (key == '7'){
          TVChar.setValue(key);
          Serial.println(key);
        }
        if (key == '8'){
          TVChar.setValue(key);
          Serial.println(key);
        }
        if (key == '9'){
          TVChar.setValue(key);
          Serial.println(key);
        }
        if (key == '*'){
          TVChar.setValue(key);
          Serial.println(key);
        }
        if (key == '#'){
          TVChar.setValue(key);
          Serial.println(key);
        }
      }
    }   
  }
    
  if (face4 == true) {                      // LIGHTS face
    digitalWrite (LEDOFF, LOW);             // if this face is true the OFF face led is LOW 
    if (central.connected() == true) {      // if a central is connected to peripheral:
      lightsChar.setValue(1);               // LIGHTS activated BLE signal
      digitalWrite (LEDlights, LOW);        // yellow led is closed because the home lights are on
    }
  }
    
  if (face3 == true) {                      // OFF face
    // when OFF face is up nothing is done 
    digitalWrite (LEDOFF, LOW);             // led OFF is activated only when the cube is shaked, so now is LOW
  }

    
  if (face5 == true) {                      // SOUND face
    digitalWrite (LEDOFF, LOW);             // if this face is true the OFF face led is LOW 
    // sound sensor is activated, led brightness regulated by the sond
    // this code comes from brightness regulation example
    long sum = 0;
    for (int i=0; i<32; i++) {
      sum += analogRead(soundSENSOR);
    }
    sum >>= 5;
    brightness = (sum*255)/1024;            // calculate the brightness value
    analogWrite(soundLED,brightness);       // green led brightness intensity is regulated by the noise
    delay(50) ;   
    //end brightness example
  }

    
  if (face2 == true) {                      // TEMPERATURE face
    digitalWrite (LEDOFF, LOW);             // if this face is true the OFF face led is LOW
    if (central.connected() == true) {      // if the cube is BLE connected 
      // read temperature value
      int val = analogRead(pinTemp);                               // get analog value
      resistance=(float)(1023-val)*10000/val;                      // get resistance
      temperature=1/(log(resistance/10000)/B+1/298.15)-273.15;     // calculate temperature
      //conditions of activation
      if (temperature > tooHot) {           // activate air conditioning
        digitalWrite(LEDhot, LOW);          // close heating led
        digitalWrite(LEDcold, HIGH);        // open air conditioner led
        termoChar.setValue(1);              // set via BLE the condition 1 = cold on - hot off
      }
      if (temperature < tooCold){           // activate heating
        digitalWrite(LEDhot, HIGH);
        digitalWrite(LEDcold, LOW);
        termoChar.setValue(2);              // 2 = cold off - hot on
      }
      if (temperature > tooCold && temperature < tooHot){  // ideal temperature: nothing is activated
        digitalWrite(LEDhot, LOW);
        digitalWrite(LEDcold, LOW);
        termoChar.setValue(0);              // 0 = cold off - hot off
      }
    }
  }


  if (face0 == true ) {                     // TIMER face activated
    digitalWrite (LEDOFF, LOW);             // if this face is true the OFF face led is LOW
    // this calculate the time to show in the display
    SHOWmillis = (CountdownInMillis - (currentMillis - TIMERmillis))/1000;
    SHOWminute = SHOWmillis/60;             // minutes value to show in the LCD
    SHOWseconds = SHOWmillis%60;            // seconds value to show in the LCD
    lcd.setRGB(50, 100, 70);                // open LCD with RGB blue color 
    if (prevSHOWsecond != SHOWseconds){     // refresh the screen every second (because refresh at every loop is too fast)
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("TIMER  ");
      lcd.setCursor(7, 0);
      lcd.print(SETminute);                 // writes the minutes that you set
      lcd.setCursor(8, 0);
      lcd.print(":");
      lcd.setCursor(9, 0);
      lcd.print(SETsecond);                 // writes the seconds that you set
      lcd.setCursor(0, 1);
      lcd.print(SHOWminute);                // write the current remaining minutes
      lcd.setCursor(1, 1);
      lcd.print(":");
      lcd.setCursor(2, 1);
      lcd.print(SHOWseconds);               // write the current remaining seconds
      KEEPtime = true;
      prevSHOWsecond = SHOWseconds;         // to refresh every second
    }
    if (SHOWminute == 0 && SHOWseconds == 0) {  // when countdown ends 
      lcd.clear();                          // clear the LCD
      lcd.setCursor(4, 0);
      lcd.setRGB(180, 40, 0);               // LCD RGB become red
      lcd.print("TIME OUT");
      tone(BUZZER,1000,500);                // three sounds
      delay (900);
      tone(BUZZER,1000,500);
      delay (900);
      tone(BUZZER,1000,1000);
      delay(1500);
      lcd.clear();                          // clear and close the LCD
      lcd.setRGB(0, 0, 0); 
      face0 = false;                        // TIMER face is now closed (false)
      KEEPtime = false;
    }
  }
  
} // loop end

Credits

Alberto Sartori
1 project • 24 followers
Italian Mechatronic Engineer studying Robot Systems in Denmark. My interests are: Automation, Environment and Innovations.

Comments