Introduction: Motorized Pulldown Curtain (Phone & Radio Controlled)
I pull my curtain up and down a couple of times a day. If I watch a movie I pull it down and then, I pull it up to get better lighting. I also pull it down when I go to sleep and I pull it up again when I wake up. I want this process to be automatic, if my curtain rises before I wake up, it will be easier for me to wake up when the clock goes off. Just like those night lights that lights up before you wake up this can do the same but with the sun as a source.
If you are like me and would like to have your home automated, you might also have considered buying motorized Curtains. But if you have, then you have also realized that they cost a considerable amount.
Therefore, I have decided to make my own motorized curtain. This project will be about modding an existing IKEA curtain, adding a motor to it and writing some code. Without further ado, let's begin!
Step 1: How It Should Work
These two pictures should give you an idea about how I wanted it to work. What they do not tell you is that the manual-control for up and down can be used at any time (even in the initial phase). And the external control sends "requests", you might not always be able to see the curtain, or you might have made it so the phone sends requests automatically.
For example you push on the phone that you want it to go up, it will move up if it is in the down position and it will do nothing if it is already up. This makes it so you won't need to think about witch button you push and you can also make it so it sends a request to move up at 8:30 in the morning for example.
In this case the external control is a NodeMCU controlled by a phone. But you could choose to use another Arduino with an SMS-shield instead if that would be a better solution for you.
The curtain will work with only a remote controller so if you do not need the "phone control" feature you could skip it, the code that I provide will still work.
Step 2: Tools & Supplies
For this project we will need some tools.
Tools
- Drill-bit
- Screwdriver
- Computer
- Soldering Iron
- Crimp tool
- Wire Strippers
- Glue gun
- Electrical tape
Step 3: Parts
Parts
1: Pulldown Curtain (that fits the window, IKEA is one supplier of these).
1: Motor(The one I use is a servo-motor, with the servo
functionality removed. It is supplied with good plastic mounts, this makes it easy to mount it to the curtain).2: Relays (You could also use power transistors for this if you want).
1: 5V power supply. (Do not buy a "fake" power supply of ebay
[or any other seller], instead wait for a sale in one of your local stores).1: Arduino
- 1: NodeMCU
- 1: Reflective IR Sensor
1: 433MHz Reciever(will be used to take commands from remote).
1: Radio remote. (I recommend "Livolo Mini Remote Controller"
only because it is the one I will be using in this project and I will provide code that support this remote).1: Project Box
1: Switch (For manual control).
2: Zip Ties (Used to hold the motor onto the L-shaped mounts)
Step 4: Code for the Curtain
You do not have to change anything in the code for the Arduino (RullgardinV3.3English) but if you want to be able to use your phone to control the curtain and set timers for when it should rise in the morning and roll down in the evening. Then you will need to change at least two things in the code for the NodeMCU board (ESP8266_CurtainCompanion) I have marked these two rows and one of them is the authentication token for your project in the Blynk app on your phone. The other one is the name and password of your Wi-Fi connection.
The code for this project is quite long, please download them if you want to take a look.
/** * This code is for controlling a curtain with a 433MHz radio controller and optionally, * also with the help of an external requesting source. * * The communication with requests will use pin 5 & 6 on the micro controller, * remember to connect both devices grounds together, or the requests won’t work. */ //---------------------Buzzer------------------ //These defines sets the notes, with its corresponding frequencies. // NB: ALL NOTES DEFINED WITH STANDARD ENGLISH NAMES, EXCEPT FROM "A" //THAT IS CALLED WITH THE ITALIAN NAME "LA" BECAUSE A0,A1...ARE THE ANALOG PINS ON ARDUINO. // (Ab IS CALLED Ab AND NOT LAb) #define C0 16.35 #define Db0 17.32 #define D0 18.35 #define Eb0 19.45 #define E0 20.60 #define F0 21.83 #define Gb0 23.12 #define G0 24.50 #define Ab0 25.96 #define LA0 27.50 #define Bb0 29.14 #define B0 30.87 #define C1 32.70 #define Db1 34.65 #define D1 36.71 #define Eb1 38.89 #define E1 41.20 #define F1 43.65 #define Gb1 46.25 #define G1 49.00 #define Ab1 51.91 #define LA1 55.00 #define Bb1 58.27 #define B1 61.74 #define C2 65.41 #define Db2 69.30 #define D2 73.42 #define Eb2 77.78 #define E2 82.41 #define F2 87.31 #define Gb2 92.50 #define G2 98.00 #define Ab2 103.83 #define LA2 110.00 #define Bb2 116.54 #define B2 123.47 #define C3 130.81 #define Db3 138.59 #define D3 146.83 #define Eb3 155.56 #define E3 164.81 #define F3 174.61 #define Gb3 185.00 #define G3 196.00 #define Ab3 207.65 #define LA3 220.00 #define Bb3 233.08 #define B3 246.94 #define C4 261.63 #define Db4 277.18 #define D4 293.66 #define Eb4 311.13 #define E4 329.63 #define F4 349.23 #define Gb4 369.99 #define G4 392.00 #define Ab4 415.30 #define LA4 440.00 #define Bb4 466.16 #define B4 493.88 #define C5 523.25 #define Db5 554.37 #define D5 587.33 #define Eb5 622.25 #define E5 659.26 #define F5 698.46 #define Gb5 739.99 #define G5 783.99 #define Ab5 830.61 #define LA5 880.00 #define Bb5 932.33 #define B5 987.77 #define C6 1046.50 #define Db6 1108.73 #define D6 1174.66 #define Eb6 1244.51 #define E6 1318.51 #define F6 1396.91 #define Gb6 1479.98 #define G6 1567.98 #define Ab6 1661.22 #define LA6 1760.00 #define Bb6 1864.66 #define B6 1975.53 #define C7 2093.00 #define Db7 2217.46 #define D7 2349.32 #define Eb7 2489.02 #define E7 2637.02 #define F7 2793.83 #define Gb7 2959.96 #define G7 3135.96 #define Ab7 3322.44 #define LA7 3520.01 #define Bb7 3729.31 #define B7 3951.07 #define C8 4186.01 #define Db8 4434.92 #define D8 4698.64 #define Eb8 4978.03 // DURATION OF THE NOTES #define BPM 120 // you can change this value changing all the others #define H 2*Q //half 2/4 #define Q 60000/BPM //quarter 1/4 #define E Q/2 //eighth 1/8 #define S Q/4 // sixteenth 1/16 #define W 4*Q // whole 4/4 //---------------------Buzzer End----------------- //---------------------For Radio------------------ #define SIGNAL_IN 0 // INTERRUPT 0 = DIGITAL PIN 2 - use the interrupt number in attachInterrupt volatile byte impulse = 0; // kolejny puls volatile int bufor[53]; volatile boolean header = false; volatile unsigned long StartPeriod = 0; // set in the interrupt volatile boolean stop_ints = false; //---------------------For Radio End--------------- // The pins for the relays, controlls the motors int relay1 = 9; int relay2 = 10; // The IR sensor, registers when the curtain is up/down. int sensor = 8; // The buzzer, it plays sounds when buttons on the radio is pushed, // it also playes the imperial march from star wars. int Buzzer = 11; // Variables to keep track of the buzzer. int ShouldBuzzerPlay = 0; int CButtonUsed =0; // This is for the manual-control, activates when pulled low. int relay1Activate = 3; int relay2Activate = 4; // These is for the external requesting source, // It could be an SMS-shield or an NodeMCU for example. int SmsUp = 5; //want to go up int SmsDown = 6; //want to go down // These are more variables to help keep track of // what is happening and what has hapened. int UpOrDown = 0; int ChooseUpOrDown = 0; int CButtonArmed = 0; int CButtonPushedTimes = 0; long previousMillis = 0; // This variable will stop the motor if it takes // to long time to reach the sensor. long interval = 12000; int BreakIfNotOne = 1; /** * Set inputs and outputs. */ void setup() { pinMode(Buzzer, OUTPUT); pinMode(12, OUTPUT); digitalWrite(12,LOW); attachInterrupt(SIGNAL_IN, calcInput, CHANGE); pinMode(13, OUTPUT); pinMode(relay1, OUTPUT); pinMode(relay2, OUTPUT); pinMode(7, OUTPUT); pinMode(sensor, INPUT); pinMode(SmsUp, INPUT); pinMode(SmsDown, INPUT); pinMode(relay1Activate, INPUT); pinMode(relay2Activate, INPUT); Serial.begin(9600); digitalWrite(relay1,HIGH); digitalWrite(relay2,HIGH); digitalWrite(relay1Activate,HIGH); digitalWrite(relay2Activate,HIGH); digitalWrite(7,HIGH); digitalWrite(SmsUp,HIGH); digitalWrite(SmsDown,HIGH); Serial.print("Starting up"); } /** * The main loop */ void loop() { //Listen for requests from external source, sms or phone. if(digitalRead(SmsDown) == LOW){ while(digitalRead(SmsDown) == LOW); Serial.print("Got a 1 in buffer"); if(UpOrDown == 1){ //If curtain is up, go down. Serial.print("Changing position"); changePosition(0); } } if(digitalRead(SmsUp) == LOW){ while(digitalRead(SmsUp) == LOW); Serial.print("Got a 0 in buffer"); if(UpOrDown == 2){ //If curtain is down, go up. Serial.print("Changing position"); changePosition(0); } } // Code for manual control. if(digitalRead(relay1Activate) == LOW){// Relay1 digitalWrite(relay1,LOW); } else{ digitalWrite(relay1,HIGH); } if(digitalRead(relay2Activate) == LOW){// Relay2 digitalWrite(relay2,LOW); } else{ digitalWrite(relay2,HIGH); } // Code for handeling the radio controller. if (stop_ints) //data in buffer { unsigned long binary = 1; //byte i = 0; for (byte j = 0; j < 46; j++) { //Serial.print(binary); if ((bufor[j] > 220) && (bufor[j] < 400)) { binary <<= 1; //binary |= 1; //i++; bitSet(binary,0); } else if ((bufor[j] > 90) && (bufor[j] < 220) && (bufor[j + 1] > 90) && (bufor[j + 1] < 220)) { binary <<= 1; j++; } else if ((bufor[j] > 90) && (bufor[j] < 220) && (bufor[j + 1] > 220) && (bufor[j + 1] < 400)) { binary <<= 1; bitSet(binary,0); //i += 2; j++; } else break; } //Serial.println(bitRead(binary,4)); For debugging. if (bitRead(binary,23)) { bitClear(binary,23); Serial.print("remoteID:"); Serial.print((binary / 128) & 65535); Serial.print(" - "); Serial.print("key code:"); Serial.println(binary & 127); //A Button if(!((binary & 127)-8)){ Serial.println("A Button"); changePosition(0); } //A Button End //B Button if(!((binary & 127)-16)){ Serial.println("B Button"); if(CButtonArmed == 1 && CButtonUsed == 0){ UpOrDown = 1; ChooseUpOrDown = 1; CButtonArmed = 0; CButtonUsed = 1; digitalWrite(13,LOW); //sound rising tone(Buzzer,C5,Q); delay(1+S); tone(Buzzer,D5,Q); delay(1+S); tone(Buzzer,E5,Q); delay(1+S); } } //B Button End //C Button if(!((binary & 127)-56)){ if(CButtonUsed == 0){ Serial.println("C Button"); if(CButtonPushedTimes == 0){ CButtonPushedTimes++; tone(Buzzer,G4,S+S); } else if(CButtonPushedTimes == 1){ if(ChooseUpOrDown == 0){ CButtonArmed = 1; digitalWrite(13,HIGH); tone(Buzzer,G4,S); delay(2+S); tone(Buzzer,G4,S); delay(2+S); tone(Buzzer,G4,S); delay(2+S); } CButtonPushedTimes = 0; } } else{ PlayBuzzer(); } } //C Button End //D Button if(!((binary & 127)-42)){ Serial.println("D Button"); if(CButtonArmed == 1 && CButtonUsed == 0){ UpOrDown = 2; ChooseUpOrDown = 1; CButtonArmed = 0; CButtonUsed = 1; digitalWrite(13,LOW); //sound falling tone(Buzzer,E5,Q); delay(1+S); tone(Buzzer,D5,Q); delay(1+S); tone(Buzzer,C5,Q); delay(1+S); } } //D Button End } else { Serial.println("wrong code "); Serial.println(binary, BIN); } delay (1000); header = false; impulse = 0; stop_ints = false; } } // interrupt below... /** * This function will calculate * the input of the radio remote. */ void calcInput() { // get the time using micros unsigned int duration = (int)(micros() - StartPeriod); // save pulse length to bufor StartPeriod = micros(); //begin next impulse //Serial.println(StartPeriod); if (stop_ints) return; if ((duration < 90) || (duration > 600)) goto reset; //impulse not right bufor[impulse++] = duration; if (duration < 415) return; if (!header) { header = true; impulse = 0; return; } else { if ((impulse < 23) || (impulse > 52)) goto reset; //too long or too short info stop_ints = true; return; } reset: header = false; impulse = 0; return; } /** * This function will change the position of the curtain. * If the input is 0 then the function will change the * state right away. If the input is 1,2 or 3. it will * wait some time before moving the curtain. * (1,2&3 will mostly be usefull if you do not use an external input source.) */ //Change position function void changePosition(int TimeIsMoney){ unsigned long currentMillis = millis(); if(UpOrDown != 0){ if(TimeIsMoney == 1){ delay(120000); } if(TimeIsMoney == 2){ delay(7200000); } if(TimeIsMoney == 3){ delay(10800000); } } // Roll Down------------------------------------ if(UpOrDown == 1){ digitalWrite(relay1, LOW); // Starts the motor delay(700); currentMillis = millis(); previousMillis = currentMillis; digitalWrite(13,HIGH); while(BreakIfNotOne == 1) { currentMillis = millis(); if(currentMillis - previousMillis > interval) { BreakIfNotOne = 0; } if(digitalRead(sensor)== HIGH) { BreakIfNotOne = 0; } } digitalWrite(13,LOW); BreakIfNotOne = 1; UpOrDown = 2; digitalWrite(relay1, HIGH); delay(10); digitalWrite(relay1, LOW); delay(100); digitalWrite(relay1, HIGH); } // Roll Up------------------------------------ else if(UpOrDown == 2){ Serial.println("Reverse"); digitalWrite(relay2, LOW); // Starts the motor delay(700); currentMillis = millis(); previousMillis = currentMillis; digitalWrite(13,HIGH); while(BreakIfNotOne == 1) { currentMillis = millis(); if(currentMillis - previousMillis > interval) { BreakIfNotOne = 0; } if(digitalRead(sensor)== HIGH) { BreakIfNotOne = 0; } } digitalWrite(13,LOW); BreakIfNotOne = 1; UpOrDown = 1; digitalWrite(relay2, HIGH); // We start the relay1 to stop the motors momentum // If we use small motors they might not have enough // internal friction to make the curtain stop quickly enough. digitalWrite(relay1, LOW); delay(100); digitalWrite(relay1, HIGH); } return; } /** * This function plays the song imperial march from starwars * on the piezzo buzzer. The function calles the function * tone(pin, note, duration), to make each individual sound. */ void PlayBuzzer() { tone(Buzzer,LA3,Q); delay(1+Q); //delay duration should always be 1 ms more than the note in order to separate them. tone(Buzzer,LA3,Q); delay(1+Q); tone(Buzzer,LA3,Q); delay(1+Q); tone(Buzzer,F3,E+S); delay(1+E+S); tone(Buzzer,C4,S); delay(1+S); tone(Buzzer,LA3,Q); delay(1+Q); tone(Buzzer,F3,E+S); delay(1+E+S); tone(Buzzer,C4,S); delay(1+S); tone(Buzzer,LA3,H); delay(1+H); tone(Buzzer,E4,Q); delay(1+Q); tone(Buzzer,E4,Q); delay(1+Q); tone(Buzzer,E4,Q); delay(1+Q); tone(Buzzer,F4,E+S); delay(1+E+S); tone(Buzzer,C4,S); delay(1+S); tone(Buzzer,Ab3,Q); delay(1+Q); tone(Buzzer,F3,E+S); delay(1+E+S); tone(Buzzer,C4,S); delay(1+S); tone(Buzzer,LA3,H); delay(1+H); tone(Buzzer,LA4,Q); delay(1+Q); tone(Buzzer,LA3,E+S); delay(1+E+S); tone(Buzzer,LA3,S); delay(1+S); tone(Buzzer,LA4,Q); delay(1+Q); tone(Buzzer,Ab4,E+S); delay(1+E+S); tone(Buzzer,G4,S); delay(1+S); tone(Buzzer,Gb4,S); delay(1+S); tone(Buzzer,E4,S); delay(1+S); tone(Buzzer,F4,E); delay(1+E); delay(1+E);//PAUSE tone(Buzzer,Bb3,E); delay(1+E); tone(Buzzer,Eb4,Q); delay(1+Q); tone(Buzzer,D4,E+S); delay(1+E+S); tone(Buzzer,Db4,S); delay(1+S); tone(Buzzer,C4,S); delay(1+S); tone(Buzzer,B3,S); delay(1+S); tone(Buzzer,C4,E); delay(1+E); delay(1+E);//PAUSE QUASI FINE RIGA tone(Buzzer,F3,E); delay(1+E); tone(Buzzer,Ab3,Q); delay(1+Q); tone(Buzzer,F3,E+S); delay(1+E+S); tone(Buzzer,LA3,S); delay(1+S); tone(Buzzer,C4,Q); delay(1+Q); tone(Buzzer,LA3,E+S); delay(1+E+S); tone(Buzzer,C4,S); delay(1+S); tone(Buzzer,E4,H); delay(1+H); tone(Buzzer,LA4,Q); delay(1+Q); tone(Buzzer,LA3,E+S); delay(1+E+S); tone(Buzzer,LA3,S); delay(1+S); tone(Buzzer,LA4,Q); delay(1+Q); tone(Buzzer,Ab4,E+S); delay(1+E+S); tone(Buzzer,G4,S); delay(1+S); tone(Buzzer,Gb4,S); delay(1+S); tone(Buzzer,E4,S); delay(1+S); tone(Buzzer,F4,E); delay(1+E); delay(1+E);//PAUSE tone(Buzzer,Bb3,E); delay(1+E); tone(Buzzer,Eb4,Q); delay(1+Q); tone(Buzzer,D4,E+S); delay(1+E+S); tone(Buzzer,Db4,S); delay(1+S); tone(Buzzer,C4,S); delay(1+S); tone(Buzzer,B3,S); delay(1+S); tone(Buzzer,C4,E); delay(1+E); delay(1+E);//PAUSE QUASI FINE RIGA tone(Buzzer,F3,E); delay(1+E); tone(Buzzer,Ab3,Q); delay(1+Q); tone(Buzzer,F3,E+S); delay(1+E+S); tone(Buzzer,C4,S); delay(1+S); tone(Buzzer,LA3,Q); delay(1+Q); tone(Buzzer,F3,E+S); delay(1+E+S); tone(Buzzer,C4,S); delay(1+S); tone(Buzzer,LA3,H); delay(1+H); delay(2*H); return; }
Step 5: Schematics
This is the schematics that the build is based upon. Use this when reproducing the project.
Step 6: Building the Curtain
Now it’s time to attach the motor to the curtain. We will do this by
removing the “plug” on the side of the curtain, the rope that one would pull the curtain up or down were attached to this “plug” before it was removed. If you have a 3D-Printer, print this part instead, it would make the construction easier.
It is possible to break down the plastic side into smaller parts, we will only need the biggest part (the part that has contact with the curtain).
As a mount we use L shaped plastic mounts, and hold them in place with zip ties. It should be noted that this was not my first attempt. I pulled the curtain down from the wall once before making this decision.
The zip ties does not grip the motor that hard it just holds it in place. This allows the motor to wiggle a bit and this helps with the problems I had, with creaking noise. When I had the motor firmly mounted it was not completely straight and it bent the metal rod in the curtain ever so slightly to make an irritating creaking noise.
Step 7: Testing the Motor
Servo motors are usually high torque motors and works well for the purpose we will use it for. These motors got an internal feedback controller inside of the housing that we do not really need. It is a good thing to open up the enclosure and remove the board, then we can solder the cables directly to the motor instead.
Step 8: Mounting the Curtain on the Wall
Insert the motor into the curtain and screw it back to the wall. As you can see it does stand out quite a lot on these pictures when compared to the white background. As fortunate as I am, it is impossible to see the motor when you are in the room and from the outside (if you do not move your head, so you face the window). If you have smaller
drapes then me or none at all you would want to make the motor prettier.
Step 9: Adding the Sensor to the Curtain
The sensor that I choose to use is a reflective infrared sensor, there is an infrared emitter next to a receiver. The receiver will measure how much information is lost. If the object is black the loss is great and if the item is white, there is almost no loss. We will use the digital output and trim the potentiometer so that it triggers nicely on the black electrical tape that we will put on the curtain at the top and bottom.
Step 10: Adding Fast Connectors to Sensor & Motor.
I wanted to have the sensor cable easily removable. For a fast connector I decided to use a USB-connector. They are widely available and are usually very cheap. USB-connectors is also made for 5volts and that is the voltage the sensor uses.
Step 11: Setting Up the Phone With Blynk
First of all download the Blynk app to your phone and make an account. I have provided a QR icon in the pictures that you can scan from the app, that way you do not have to setup the app more than edit your own authentication code into the code for the NodeMCU that you will program.
Step 12: Thoughts on Choosing Enclosure.
If you think that my choice of enclosure is weird than you might be right. I chose that box because it will look good in the environment that I am in. If you decide to build something like this then you might what to choose some other enclosure entirely. You might even want to have the enclosure up on the wall next to the motor. The second picture that I am showing here is an old version of this curtain. It does have another enclosure though, it might give you some ideas.
Step 13: Make the Necessary Holes for the Connectors.
The USB-connector is for the IR-sensor and the "black and red" cable is for the motor. The jack in the middle is for an external power source.
Step 14: Thoughts on Cable Management.
To fit all the electronics in the enclosure I decided to make my own cables. If cable management is if great importance to you, then you can always choose a bigger enclosure to help with the cable management. Or If you like to etch your own pcb's than that is a nice thing to do.
Step 15: Fit the Electronics in the Enclosure.
Trying to fit all the electronics in a small space is a nice feeling. You might ask why I use relays that takes up a lot of space when I could have used power-transistors instead. I did actually use power transistors before I went with relays, I did have a problem with too much of a voltage drop over the transistors and I did not want to use a boost converter to accumulate for the drop. The motor is somewhat sensitive to changes in voltage and does get much weaker if the voltage drops.
Step 16: Base-Station Done
This is how the base station came to look in the end. It is not necessary to have it easily removable in a permanent solution but it helps with cable management and just makes your life easier.
Step 17: Power Consumption
These pictures speaks for them self's. If you are interested in the power consumption of this device please take a look.
Step 18: It's Alive!
What is a project that is supposed to move, if you could not see it actually move? This is a 2.5 min clip showing the build of the curtain and the curtain moving, (with input from the radio control and from the phone).
Thanks for reading my instructable!