Hackster is hosting Hackster Holidays, Ep. 6: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Monday!Stream Hackster Holidays, Ep. 6 on Monday!
Kenneth Yang
Published © GPL3+

EnLight (Sunset) v1

Control your blinds to collect as much sunset light, and eventually closing the blinds for you.

BeginnerFull instructions provided1 hour3,093

Things used in this project

Hardware components

Arduino 101
Arduino 101
×1
Female/Female Jumper Wires
Female/Female Jumper Wires
I won't specify how many you need, because everyone's window has different dimensions.
×1
Jumper wires (generic)
Jumper wires (generic)
I won't specify how many you need, because everyone's window has different dimensions.
×1
SparkFun Serial Enabled 16x2 LCD - White on Black 3.3V
SparkFun Serial Enabled 16x2 LCD - White on Black 3.3V
Use the one in the Inventor's kit (Arduino 101)
×1
SparkFun Motor Driver - Dual TB6612FNG (1A)
SparkFun Motor Driver - Dual TB6612FNG (1A)
Use the one in the Inventor's kit (Arduino 101)
×1
Photo resistor
Photo resistor
×5
Pushbutton switch 12mm
SparkFun Pushbutton switch 12mm
Requires the two pin version depicted in the Fritzing design
×1
DC motor (generic)
Use the one in the Inventor's kit (Arduino 101)
×1
Resistor 10k ohm
Resistor 10k ohm
×6

Software apps and online services

Arduino IDE
Arduino IDE
Fusion
Autodesk Fusion
This is optional. Only use it to modify the CAD files (if you want)

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)

Story

Read more

Custom parts and enclosures

Photoresistor placeholder

use these placeholders to help guide the angle o set of the photoresistors if the opening is too large you can edit the file to make it smaller, or use tape to connect the photoresistor.

Photoresistor placeholder -f3d

Fusion 360 archive version of the Photoresistor placeholder file

Pully Controller

The wheel that controls the blinds

Pully Controller -f3d

Fusion 360 archive version of Pully Controller

Schematics

Main

The main bread board design. It's a little difficult to see because of the wires, but all resistors used are 10K and the photoresistors are connected to power and the pin between the power-bus and the resistors (that are next to the analog input wires).

Main - LCD

Same file as Main except without the LCD so you can see where the wires go underneath.

Code

Enlight

Arduino
The main code file that deals with everything.
NOTE: the header files "constants.h" and "voids.h" are both required.
/*
   EnLight v1

   Developer: Microbob 
   Contact: microbob@is88.com
*/
#include <LiquidCrystal.h>
#include <CurieTime.h>
#include "constants.h"

LiquidCrystal lcd(12, 11, 5, 4, 3, 2); //LCD init

unsigned long togg;
unsigned int in;
String instr;

unsigned int temp;
String tempstr;
unsigned long prevMil = millis();

unsigned int timeData[6];
bool dirLeft = true;
unsigned long rotDur;

unsigned int calibP = 0;
unsigned int maxBri = 0;
unsigned int minBri = 1023;

unsigned int unHour, unMin, unSec;

unsigned int pos;
unsigned int turn = 0;

#include "voids.h"
void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  lcd.begin(16, 2);

  pinMode(bri, OUTPUT);
  pinMode(func, INPUT);


  lcd.print("Please Open a");
  lcd.setCursor(0, 1);
  lcd.print("Serial Session");
  Serial.println("Type in anything and press <Enter> to continue");
  unsigned int briLev = 0, increment = 5;
  while (Serial.available() == 0) {
    brightness(briLev);
    briLev += increment;
    if (briLev == 255 || briLev < 5)
      increment *= -1;
    delay(25);
  }
  brightness(0);
  lcd.clear();
  serialFlush();

  initHeaders("Introduction");
  Serial.println("Welcome to your EnLight!\n");
  Serial.println("This setup process may run (autonomously) for a max. of 24 hours");
  Serial.println("The user part won't take you more than a few minutes,");
  Serial.println("the rest will be the system calibrating itself to the sun\n");
  Serial.println("Type in \"r\" and press <Enter> when you are ready to continue.");
  Serial.println("Type in anything else and press <Enter> if you want to quit");
  Serial.println("NOTE: Always press <Enter> after you type something in to submit it");
  Serial.println("\nPress the RESET button (if accessible),");
  Serial.println("unplug and replug power to the device,");
  Serial.println("or reopen a serial connection to completely restart/reset at any time");
  while (true) {
    input(false);
    if (in == 114)
      break;
    else if (in == 0)
      continue;
    else {
      Serial.println("\nPress the RESET button (if accessible) to restart,");
      Serial.println("Or unplug and replug power to the device to restart");
      Serial.println("END");
      Serial.flush();
      exit(0);
    }
  }

  calTime();
  calDir();
  calDur();

  while (true) {
    Serial.println("\n\n\n");
    Serial.println("+++++++++++++++++++++++++++++++++++");
    Serial.println("+++++  Manual setup complete  +++++");
    Serial.println("+++++++++++++++++++++++++++++++++++");
    Serial.println("\nHere's your last chance to (re)calibrate anything.");
    Serial.println("1) Time");
    Serial.println("2) Rotation Direction");
    Serial.println("3) Length of Direction");
    Serial.println("\nType in the corresponding number to recalibrate,");
    Serial.println("or type in [y] to continue.");
    input(false);
    if (in == 49)
      calTime();
    else if (in == 50)
      calDir();
    else if (in == 51)
      calDur();
    else if (in == 114)
      break;
  }

  Serial.println("\n\n\n");
  Serial.println("The rest will the device calibrating itself to the sun.");
  Serial.println("This can take up to 24 hours.");
  Serial.println("This is because the system needs to wait for it to be 12:00 PM");
  Serial.println("and start collecting data about the brightness at different");
  Serial.println("points of the suns set.");
  Serial.println("\nPLEASE KEEP POWER TO THIS DEVICE!!!!!");
  Serial.println("The LCD will show the status of the device.");
  Serial.println("\nEnjoy your device and save some power!");
  Serial.println("END");


  brightness(100);
  calibState("Starting...");

  bool from12 = false;
  if (hour() == 12 && minute() == 0 && second() == 0)
    from12 = true;
  else if (hour() < 12) {
    temp = map(hour() * 60 + minute(), 0, 720, 0, 100);
    while (hour() != 12 && minute() != 0 && second() != 0) {
      if (temp > map(hour() * 60 + minute(), 0, 720, 0, 100)) {
        temp = map(hour() * 60 + minute(), 0, 720, 0, 100);
        calibState("Waiting...");
      }
    }
    from12 = true;
  }

  if (from12) {
    while (hour() != 0) {
      if (millis() - prevMil >= 1000) {
        temp = briIn();
        prevMil = millis();
        maxBri = max(temp, maxBri);
        minBri = min(temp, minBri);

        temp = map(hour() * 60 + minute(), 720, 1439, 0, 100);
        if (calibP != temp)
          calibState("Collecting...");
      }
    }
  }
  else {
    unHour = hour();
    unMin = minute();
    unSec = second();

    while (hour() != 0) {
      if (millis() - prevMil >= 1000) {
        temp = briIn();
        prevMil = millis();
        maxBri = max(temp, maxBri);
        minBri = min(temp, minBri);

        temp = map(hour() * 60 + minute(), unHour * 60 + unMin, 1439, 0, 33);
        if (calibP != temp)
          calibState("Collecting...");
      }
    }
    temp = map(hour() * 60 + minute(), 0, 720, 33, 66);
    while (hour() == 12 && minute() == 0 && second() == 0) {
      if (temp > map(hour() * 60 + minute(), 0, 720, 0, 100)) {
        temp = map(hour() * 60 + minute(), 0, 720, 33, 66);
        calibState("Waiting...");
      }
    }
    while (hour() != unHour && minute() != unMin && second() != unSec) {
      if (millis() - prevMil >= 1000) {
        temp = briIn();
        prevMil = millis();
        maxBri = max(temp, maxBri);
        minBri = min(temp, minBri);

        temp = map(hour() * 60 + minute(), 720, 1439, 66, 100);
        if (calibP != temp)
          calibState("Collecting...");
      }
    }
  }

  temp = 100;
  calibState("Complete!");
  delay(1000);

  lcd.clear();
  lcd.print("Starting...");
  delay(1000);

  pos = map(briIn(), 200, 1439, 1, 5);
  drive();
  delay(rotDur / 5 * pos);
  sdrive();
  turn = pos;
}

void loop() {
  // put your main code here, to run repeatedly:
  do {
    if (millis() - prevMil >= 60000) {
      temp = map(briIn(), 200, 1439, 1, 5);
      if (temp != pos) {
        drive();
        delay(rotDur / 5 * (temp - turn));
        sdrive();
        turn = temp - turn;
      }


      lcd.clear();
      lcd.print("Operating...");
      lcd.setCursor(0,1);
      lcd.print(String(map(hour() * 60 + minute(), 720, 1439, 0, 100))+"%");
    }
  } while (turn != 5 || hour() < 22);

  turn = 0;
  while (hour() != 7 && minute() != 30 && second() != 0) {
    if (millis() - prevMil >= 1000) {
      temp = map(hour() * 60 + minute(), 720, 1439, 0, 50);
      if (calibP != temp)
        calibState("Waiting...");
    }
  }
  odrive();

  temp = map(hour() * 60 + minute(), 0, 720, 0, 100);
  while (hour() != 12 && minute() != 0 && second() != 0) {
    if (temp > map(hour() * 60 + minute(), 0, 720, 0, 100)) {
      temp = map(hour() * 60 + minute(), 0, 720, 50, 100);
      calibState("Waiting...");
    }
  }

  pos = map(briIn(), 200, 1439, 1, 5);
  drive();
  delay(rotDur / 5 * pos);
  sdrive();
  turn = pos;
}

constants

Plain text
Just a header file to hold different constants and variables that won't change, like alt names for pins.
#define topPin A0
#define topMidPin A1
#define midPin A2
#define bottomMidPin A3
#define bottomPin A4

#define bri 10

#define dirA 7
#define dirB 8
#define spd 6

#define func A5

const char* setTimeHeader[] = {"Month (MM)", "Day (DD)", "Year (YYYY)", "Hour (24-HH)", "Minute (MM)"};

voids

Plain text
The file that holds all of the functions to the program, like time calibration.
// I/O
void brightness(const unsigned int b) {
  analogWrite(bri, b);
}
void off() {
  digitalWrite(spd, LOW);
}
void sped(const unsigned int s) {
  analogWrite(spd, s);
}

int briIn() {
  unsigned int top = analogRead(topPin);
  unsigned int topMid = analogRead(topMidPin);
  unsigned int mid = analogRead(midPin);
  unsigned int bottomMid = analogRead(bottomMidPin);
  unsigned int bottom = analogRead(bottomPin);

  return (top + topMid + mid + bottomMid + bottom) / 5;
}
// Motor dir
void drive() {
  if (dirLeft) {
    digitalWrite(dirA, HIGH);
    digitalWrite(dirB, LOW);
  } else {
    digitalWrite(dirA, LOW);
    digitalWrite(dirB, HIGH);
  }

  analogWrite(spd, 125);
}
void sdrive() {
  digitalWrite(spd, LOW);
}
void odrive() {
  if (!dirLeft) {
    digitalWrite(dirA, HIGH);
    digitalWrite(dirB, LOW);
  } else {
    digitalWrite(dirA, LOW);
    digitalWrite(dirB, HIGH);
  }

  analogWrite(spd, 125);
  delay(rotDur);
  sdrive();
}

// LCD Interface
void calibState(String msg) {
  lcd.clear();
  calibP = temp;
  lcd.print("Calibrating: " + String(calibP) + "%");
  lcd.setCursor(0, 1);
  lcd.print(">> " + msg);
}

// Serial Interface
void serialFlush() {
  while (Serial.available())
    Serial.read();
}
void input(bool str) {
  while (true) {
    if (Serial.available() > 0) {
      if (str)
        in = Serial.readString().toInt();
      else
        in = Serial.read();
      break;
    }
  }
}
void initHeaders(const String& header) {
  const unsigned int hh = header.length();
  Serial.println("\n\n\n===== (Initial) Setup Process =====");
  Serial.print(">>>>>");

  for (unsigned int l = 0; l < (25 - hh) / 2 + (hh % 2); l++) {
    Serial.print(" ");
  }
  Serial.print(header);
  for (unsigned int l = 0; l < (25 - hh) / 2; l++) {
    Serial.print(" ");
  }

  Serial.print("<<<<<\n\n");
}
void convert(unsigned int num) { //trailing zero
  if (num >= 0 && num < 10)
    Serial.print("0");
  Serial.print(num);
}

void calTime() {
  initHeaders("Setting the Time");
  for (unsigned int l = 0; l < 4; l++) {
    while (true) {
      Serial.println("Type in the " + String(setTimeHeader[l]) + " number");
      input(true);
      temp = in;
      Serial.println(String(setTimeHeader[l]) + ": " + String(temp));
      Serial.println("Was that what you wanted? [y/n]");
      input(false);
      if (in == 121) {
        timeData[l] = temp;
        Serial.println(String(setTimeHeader[l]) + " has been set to " + String(timeData[l]));
        break;
      }
    }
  }
  while (true) {
    Serial.println("Type in the current Seconds (SS). When you press enter, it will set to what was entered.");
    input(true);
    temp = in + 4;
    setTime(timeData[3], timeData[4], temp, timeData[1], timeData[0], timeData[2]);

    for (unsigned int l = 0; l < 5; l++) {
      convert(hour());
      Serial.print(":");
      convert(minute());
      Serial.print(":");
      convert(second());
      delay(1000);
    }

    Serial.println("\nLooks good? [y/n]");
    input(false);
    if (in == 121) {
      timeData[5] = temp;
      Serial.println("\nThe time has been set!");
      break;
    }
  }
}
void calDir() {
  initHeaders("Rotation Direction");
  Serial.println("For your device and blind's safety,");
  Serial.println("plase make sure the pully controller is removed.");
  Serial.println("Ready to continue? [y]\n");
  while (true) {
    input(false);
    if (in == 121)
      break;
  }

  drive();
  Serial.println("Is this the direction your blind's close if turned? [y/n]");
  input(false);
  if (in == 121)
    Serial.println("Blinds close in oposite direction.\n Direction Set");
  else
    dirLeft = false;
}
void calDur() {
  initHeaders("Length of Rotation");
  while (true) {
    Serial.println("1) Open your blinds to full extent.");
    Serial.println("2) Reinstall the pully controller.");
    Serial.println("3) Locate the function button, it will be used to control the motor.");
    Serial.println("\nWhen you are ready, the motor will start closing your blinds.");
    Serial.println("Press the function button when your blinds fully close.");
    Serial.println("Submit [y] when you are ready to continue.");
    while (true) {
      input(false);
      if (in == 121)
        break;
    }

    prevMil = millis();
    drive();
    while (func != HIGH);
    rotDur = millis() - prevMil;
    sdrive();

    Serial.println("Rotation recorded.");
    delay(1000);
    odrive();
    drive();
    delay(rotDur);
    sdrive();

    Serial.println("Looks good? [y/n]");
    input(false);
    if (in == 121) {
      odrive();
      break;
    }
  }
}

Credits

Kenneth Yang

Kenneth Yang

10 projects • 100 followers
Maker, developer, 3D content creator

Comments