donutsorelse
Published © GPL3+

How to Make Minimalistic Smart Curtains

By using a simple setup, we can accomplish everything we'd want in a Smart Curtain while keeping things cheap and easy. Wins all around.

BeginnerFull instructions provided3 hours533
How to Make Minimalistic Smart Curtains

Things used in this project

Hardware components

NodeMCU ESP8266 Breakout Board
NodeMCU ESP8266 Breakout Board
×1
SparkFun Full-Bridge Motor Driver Breakout - L298N
SparkFun Full-Bridge Motor Driver Breakout - L298N
×1
DC Motor, 12 V
DC Motor, 12 V
A motor that is capable of moving your curtains.
×1

Software apps and online services

Arduino IDE
Arduino IDE

Story

Read more

Schematics

Schematics

Code

Smart Curtain Code

Arduino
Fill in your information in the generified fields at the top and it should just work! There's a thorough rundown of the code in the code section of the Story.
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ArduinoJson.h>
#include <ESP8266HTTPClient.h>
#include "AdafruitIO_WiFi.h"

// Replace with your Wi-Fi credentials
const char *ssid = "YOUR_SSID";
const char *password = "YOUR_WIFI_PASSWORD";

// Replace with your Adafruit IO credentials
#define IO_USERNAME "YOUR_IO_USERNAME"
#define IO_KEY "YOUR_IO_KEY"

// Replace with your OpenWeatherMap API key
const char *apiKey = "YOUR_OPENWEATHERMAP_API_KEY";

// Replace with your location (latitude and longitude)
const float latitude = YOUR_LATITUDE;
const float longitude = YOUR_LONGITUDE;

// Motor control pins
const int motorPin1 = YOUR_MOTOR_PIN_1;  // (GPIO5)
const int motorPin2 = YOUR_MOTOR_PIN_2;  // (GPIO4)

long sunrise = 0;
long sunset = 0;
long currentTime = 0;
unsigned long currentMillis;
unsigned long estimatedCurrentTime;

bool isOpen = false;

// Create an instance of the server
ESP8266WebServer server(80);

AdafruitIO_WiFi io(IO_USERNAME, IO_KEY, ssid, password);
AdafruitIO_Feed *blindsFeed = io.feed("blinds");
AdafruitIO_Feed *closeFeed = io.feed("close");

unsigned long previousMillis = 0;
const long interval = 60 * 60 * 1000 * 24;  // Interval to check for sunrise/sunset (once a day)

void setup() {
  // Start the serial communication
  Serial.begin(9600);

  // Connect to Wi-Fi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to Wi-Fi...");
  }
  Serial.print("Connected to Wi-Fi with IP: ");
  Serial.println(WiFi.localIP());

  delay(2000);  // Add a 2-second delay

  // Set up time
  // Change the first parameter to -14400 for EDT (-4 hours) and -18000 for EST (-5 hours)
  configTime(-14400, 0, "pool.ntp.org");
  while (!time(nullptr)) {
    Serial.println("Waiting for time to be set...");
    delay(1000);
  }
  Serial.println("Time has been set.");


  // Set motor control pins as outputs
  pinMode(motorPin1, OUTPUT);
  pinMode(motorPin2, OUTPUT);

  // Set up server routes
  server.on("/blinds", HTTP_GET, []() {
    openCloseBlinds();
    server.send(200, "text/plain", "Blinds toggled");
  });

  // Start the server
  server.begin();

  // Connect to Adafruit IO
  io.connect();
  while (io.status() < AIO_CONNECTED) {
    Serial.print(".");
    delay(500);
  }
  Serial.println();
  Serial.println(io.statusText());

  blindsFeed->onMessage(handleBlindsMessage);
  closeFeed->onMessage(handleCloseBlindsMessage);
}

void loop() {
  server.handleClient();
  io.run();
  // openCloseBlinds();

  currentMillis = millis();
  estimatedCurrentTime = currentTime + ((currentMillis) / 1000);


  if (sunrise == 0 || currentMillis - previousMillis >= interval) {
    getSunriseSunset();
    previousMillis = currentMillis;
  }
  
  if (estimatedCurrentTime > (sunrise - 600)) {
    if (!isOpen) {
      Serial.println("Opening blinds for sunrise");
      openCloseBlinds();
    } else {
      Serial.println("Blinds are already open for sunrise");
    }
    Serial.println("Updating sunrise time for tomorrow");
    sunrise += 86400;
    displayTimes();
  }
  if (estimatedCurrentTime > sunset) {
    if (isOpen) {
      Serial.println("Closing blinds for sunset");
      openCloseBlinds();
    } else {
      Serial.println("Blinds are already closed for sunset");
    }
    Serial.println("Updating sunset time for tomorrow");
    sunset += 86400;
    displayTimes();
  }
}

void displayTimes() {
  Serial.println("Sunrise: " + String(sunrise));
  Serial.println("Sunset: " + String(sunset));
  Serial.println("Current time: " + String(estimatedCurrentTime));
}
void getSunriseSunset() {
  // Get sunrise and sunset times from the OpenWeatherMap API
  HTTPClient http;
  WiFiClient client;
  String url = String("http://api.openweathermap.org/data/2.5/weather?lat=") + String(latitude) + "&lon=" + String(longitude) + "&appid=" + apiKey;
  http.begin(client, url);
  int httpCode = http.GET();
  String payload = http.getString();
  if (httpCode == 200) {
    StaticJsonDocument<1024> doc;
    deserializeJson(doc, payload);
    JsonObject sys = doc["sys"];
    sunrise = sys["sunrise"];  // + timezoneOffsetInSeconds;
    sunset = sys["sunset"];    // + timezoneOffsetInSeconds;
    // Get the current time in UNIX format
    currentTime = doc["dt"];

    estimatedCurrentTime = currentTime + ((millis()) / 1000);
    if (estimatedCurrentTime > sunrise) {
      Serial.println("Updating sunrise time for tomorrow in setup");
      sunrise += 86400;
    }
    if (estimatedCurrentTime > sunset) {
      Serial.println("Updating sunset time for tomorrow in setup");
      sunset += 86400;
    }
  } else {
    // Error fetching sunrise and sunset times, use default values
    Serial.println("Error fetching sunrise and sunset times:");
    Serial.println("HTTP status code: " + String(httpCode));
    Serial.println("Response payload: " + payload);

    // Current time as a time_t type
    time_t currentTm = time(nullptr);
    struct tm *ptm;

    ptm = gmtime(&currentTm);
    ptm->tm_hour = 7;
    ptm->tm_min = 0;
    ptm->tm_sec = 0;
    sunrise = mktime(ptm);

    ptm = gmtime(&currentTm);
    ptm->tm_hour = 19;
    ptm->tm_min = 30;
    ptm->tm_sec = 0;
    sunset = mktime(ptm);
  }
  http.end();

  // Display the values in the Serial Monitor
  Serial.println("Sunrise: " + String(sunrise));
  Serial.println("Sunset: " + String(sunset));
  Serial.println("Current time: " + String(currentTime));
  currentMillis = millis();
  estimatedCurrentTime = currentTime + ((currentMillis) / 1000);
  Serial.println("Millis: " + String(currentMillis));
  Serial.println("Est current time: " + String(estimatedCurrentTime));
}

// int value can go here if we want it
void openCloseBlinds() {
  if (!isOpen) {
    Serial.println("Opening blinds");
    digitalWrite(motorPin1, HIGH);
    digitalWrite(motorPin2, LOW);
    delay(100000);  // Adjust delay based on the time required for the blinds to open fully
    digitalWrite(motorPin1, LOW);
    isOpen = true;
  } else {
    Serial.println("Closing blinds");
    digitalWrite(motorPin1, LOW);
    digitalWrite(motorPin2, HIGH);
    delay(60000);  // It's easier for the blinds to close than to open with the taut string setup
    digitalWrite(motorPin2, LOW);
    isOpen = false;
  }
}
// Define a PWM duty cycle for the motor speed. This should be a value between 0 (off) and 1023 (full speed).
// A lower value will make the motor move slower. Experiment to find the best value for your specific motor and power supply.
// const int motorSpeed = 50;  // Half speed

// void openCloseBlinds() {
//   if (!isOpen) {
//     Serial.println("Opening blinds");
//     analogWrite(motorPin1, motorSpeed);  // Use analogWrite to send a PWM signal
//     digitalWrite(motorPin2, LOW);
//     delay(4000);  // Adjust delay based on the time required for the blinds to open fully
//     analogWrite(motorPin1, 0);  // Turn off the motor
//     isOpen = true;
//   } else {
//     Serial.println("Closing blinds");
//     digitalWrite(motorPin1, LOW);
//     analogWrite(motorPin2, motorSpeed);  // Use analogWrite to send a PWM signal
//     delay(4000);  // Adjust delay based on the time required for the blinds to close fully
//     analogWrite(motorPin2, 0);  // Turn off the motor
//     isOpen = false;
//   }
// }


void handleBlindsMessage(AdafruitIO_Data *data) {
  Serial.println("Message received: " + String(data->value()));
  int value = data->toInt();
  openCloseBlinds();
}
void handleCloseBlindsMessage(AdafruitIO_Data *data) {
  Serial.println("Message received from test feed: " + String(data->value()));
  if (!isOpen) {
    Serial.println("Blinds are already closed.");
    return;
  }
  openCloseBlinds();
}

Credits

donutsorelse

donutsorelse

13 projects • 11 followers
I make different stuff every week of all kinds. Usually I make funny yet useful inventions.

Comments