---
# System prepended metadata

title: 'Tutorial: How to build automated blinds'

---

# Tutorial: How to build automated blinds based on sunlight

##### Author: Jakob Karlstrand (jk224jb)

---

### Project overview

After this tutorial you'll have automated blinds that go down when the evening starts and will go up in the morning the sun starts to rise. This is doable by using a microcontroller which utilizes a servo motor and a light sensor. 
Total darkness during the night, and waking up by the sunlight.

To follow along this tutorial should take about **1-2 hours** if you already have all the equipment.

A video demonstration of the automated blinds is available at the bottom of the document


### Objective

I chose to build this device because I wanted to build something using a servo controller. I wasn't sure what to build until one morning when I woke up by the alarm and my room was pitch black (due to the blinds being down).
That's where I got the idea to build automated blinds which will go up in the morning but stay down during the night.

I much more prefer waking up by the light than the alarm sound.

The are two types of data collected:
- The inital data collection to log the light resistance values. These will be different depending on where you live and where you place the sensor.
- The continious data collection which logs every time the blinds go up and down. (i.e. every time the light sensor thinks it's morning and night) **(optional)**

What we will probably realize is that it would be more wise to set the blinds to go up and down based on a time of day instead of when the sun rises and sets (since this changes drastically over the year).

But we are required to collect data, and why not do it this way? ;)


### Material

The table below shows list of hardware used in the project, specification and price.

|Image |Hardware | Specification |Price|
|---|---|---|---|
|![](https://hackmd.io/_uploads/r1QKKhRd2.jpg)| [Rasberry Pi Pico W](https://www.electrokit.com/produkt/raspberry-pi-pico-w/)  | A microcontroller that is used to collaborate with sensors and connect to different networks | 98kr  |
| ![](https://hackmd.io/_uploads/BJDTF3Cu2.jpg)  | [Large servo (360deg)](https://www.electrokit.com/produkt/servo-stort-360/) |A motor that controlls the wire attached to the blinds|149kr
| ![](https://hackmd.io/_uploads/H1hG53Adn.jpg)|[Breadboard](https://www.electrokit.com/produkt/kopplingsdack-840-anslutningar/)| Used to connect all the wires to from sensors to the Pico W| 62kr
| ![](https://hackmd.io/_uploads/BkfvchAOn.jpg)|[Jumper cables (M2M)](https://www.electrokit.com/produkt/labbsladd-40-pin-30cm-hane-hane/)   | Used to connect sensor via the breadboard|49kr 
| ![](https://hackmd.io/_uploads/HJP9q3RO2.jpg)|[Light sensor](https://www.electrokit.com/produkt/ljussensor/)   |Collect a data value based on the photo resistance   | 39kr
| ![](https://hackmd.io/_uploads/Hy6WjnR_n.jpg)| [Fishing line (5kg)](https://www.jula.se/catalog/fritid/fiske/fisketillbehor/linor/fiskelina-771020/) | Used together with the servo to control the position of the blinds   | 149kr
|![](https://hackmd.io/_uploads/rJL9IRR_n.png)| [Super glue](https://www.clasohlson.com/se/Superlim-Loctite-Precision,-5-g/p/25-1544) | Needed to attach the spinning wheel to the fishing line | 68kr
|![](https://hackmd.io/_uploads/Bk4nIRCdn.png) | [Clamp](https://www.clasohlson.com/se/Skruvtving-Bessey/p/40-8781) | Used to hold the servo in place  | 79kr
| |||**702kr**


### Computer setup

The first things we need to do is to update the MicroPython firmware, setup our IDE and have a way to transfer code to our Pico W.

#### To update the firmware follow these steps:

- Go to https://micropython.org/download/rp2-pico-w/ and download the latest .uf2 firmware
- While conntecting your Pico W to your computer hold down the BOOTSEL button.
- Simply drag and drop the firmware to your device folder and it will install automatically.

You will also need to install Node.js.
You can get it from here: https://nodejs.org/en
Simply follow the steps of the wizard to install.

For this project I used Microsoft Visual Studio Code together with Adafruit to run and save scripts on the hardware.

#### Install Adafruit (ampy)

Ampy let's us transfer python files from our workspace to the controller, and also run the files directly on the controller.

To install ampy run the following snippet in your termnial:

```shell
$ pip install adafruit-ampy
```
``

#### Hello world
Let's create a simple blinking project to see if everything is setup OK.
Open a new python file in Visual Studio Code, name it main.py and place the following code in your main.py:

~~~python=3
from machine import Pin
from time import sleep

led = Pin(6, mode=Pin.OUT)

while True:
    led.value(1)
    sleep(0.5)
    led.value(0)
    sleep(0.5)
~~~

> ⚠️  It's important to name our main program to *main.py* or else it will not automatically run when plugged in to a power source later on.

Now to copy the file over to the hardware and run it, simply save your file and then paste the following lines in your terminal:

```shell
$ ampy --port <PORT_NUMBER> put main.py
```
```shell
$ ampy --port <PORT_NUMBER> run main.py
```



Where `<PORT_NUMBER>` is the number of the COM port your hardware is using. This number is usually **300**. If you want to be certain, open Device Manager on Windows and check under *Ports*

Now your Pico W's LED should be blinking once per second.

Done!


### Putting everything together

#### Hardware
Now it's time to connect the breadboard, jumper cables, sensor and servo!
Connect everything according to the curcuit diagram below:


![](https://hackmd.io/_uploads/Hkx1Qa0dn.png)

This setup will be used now in development and also production.

#### Utility

In order to make the blinds go up and down we need to tie the fishing wire into the blinds and then connect the spool to the servo motor.

>I use a naive way to setup the mechanics of this project. If you have a 3D printer, I would recommend to build a case for your controller.*

In the images below you can see how I connected everything, in order to keep it in place.

I use superglue to attach the servo wheel on the wire spool

Clamp and wire spool  |  Wire tied to the blinds
:-------------------------:|:-------------------------:
![](https://hackmd.io/_uploads/Hk7UWb1K2.jpg) |![](https://hackmd.io/_uploads/BJEUbbyKn.jpg)







### Platform
I used a custom Cloud function on Firebase to store my data. I later visualize the data in a simple React app using Chart.js for React.

I created an API-endpoint on Firebase which can recieve a value, and when added to the database it will also create a timestamp.

Using Reactjs I fetch all database entries and visualize it using Chartjs library.



### The code

This chapter will explain code snippets and core functionality for the project.

#### Connect to wifi
We need a network connection to able to send our data values to the server.
To connect to your wifi follow these steps:
- Create a new python file called network_handler.py
- Paste the code below:

~~~python=3
def connect_to_wifi():
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)

    # Fill in your network name (ssid) and password here:
    ssid = 'YOUR_WIFI_SSID'
    password = 'YOUR_WIFI_PASSWORd'
    wlan.connect(ssid, password)

    max_wait = 10

    while max_wait > 0:
        if wlan.status() < 0 or wlan.status() >= 3:
            break
        max_wait -= 1
        print("Waiting for connection...")
        sleep(1)

    if wlan.status() != 3:
        raise RuntimeError("Network connection failed")
    else:
        print("Connected to ", ssid)
~~~


#### Light sensor

The only thing we need to do here is to sample a value from the light sensor every time the main loop runs:

**Create a reference to the PIN where our light sensor is**

~~~python 
lr = ADC(Pin(26))     # create ADC object on ADC pin
~~~

In the main loop we can access the sample value by:
~~~python 
lr_value = lrr.read_u16() # Get a value based on photo resistance
~~~

This is all we need to collect the inital data. Now we want to send this value to our database every 15 minutes.
~~~python=3
# Imports 
# -----
import ujson
import network
import urequests as requests
from time import sleep
# -----

def send_data_to_server(value):
    MAX_TRIES = 5
    c_try = 1
    while c_try < 5:
        try:
            json_obj = ujson.dumps({'value': value})
            res = requests.post(API_URL, headers = {'content-type': 'application/json'}, data = json_obj).json()
            break
        except Exception as error:
            c_try += 1
          

~~~

The reason for the while loop is that sometimes the request fails, so we try again (max 5 times), but if it succeeds we break the loop.

So now our code should look like this:
~~~python=3
# Imports 
# -----
import utime
from machine import Pin,ADC
# -----


ldr = ADC(Pin(26))

last_time = utime.time()
INTERVAL = 60*15 # 15 mintues
if __name__ == '__main__':
    
    try:
        connect_to_wifi()
        while True:
            if utime.time() - last_time > INTERVAL: # Every 15 minutes
                value = ldr.read_u16()
                send_data_to_server(value)
                last_time = utime.time() # Update the time
                
    except Exception as error:
		print(error)
        
    
    

~~~
Now save your files and put the files on your hardware:

```shell
$ ampy --port <PORT_NUMBER> put main.py
```
```shell
$ ampy --port <PORT_NUMBER> put network_handler.py
```

Place your hardware by the window, connect it to a powersource and let it run for a couple days.

#### Controlling the servo motor
Now we have multiple data values of a timestamp together with a light sensor value.

For me, good threshhold values where `1100` for the blinds to go down, and `150` for the blinds to go up.

So the higher the value, the darker it is.

Let's build our servo controller:
~~~python 
servo = PWM(Pin(8)) # Connect to servo PIN
servo.freq(50) # Set servo frequency
~~~ 


Also set some constants for the pulse widths that are accepted by the servo
~~~python 
servoStop= 1_500_000
servoForward = 900_000
servoReverse = 2_100_000	
~~~ 

Now we can define functions to make the blinds go up and down:
~~~python=3
def blinds_up():
	servo.duty_ns(servoReverse)
	utime.sleep(TIME_FOR_BLINDS_TO_GO_UP)
	servo.duty_ns(servoStop)
    


def blinds_down():
	servo.duty_ns(servoForward)
	utime.sleep(TIME_FOR_BLINDS_TO_GO_DOWN)
	servo.duty_ns(servoStop)
   

~~~ 

What we are doing here is we tell the servo to rotate either forward or backwards, then we make our script sleep for a duration (the time it takes for the blinds to go up or down).

This takes some experimenting to get the duration right.
For me it was 32 seconds for the blinds to go down, and about 5 seconds to up up.


Now in our main file we have the following code:


~~~python=3

# Imports 
# -----
import utime
from machine import PWM,Pin,ADC
from time import sleep
# -----


servo = PWM(Pin(8)) # Connect to servo PIN
servo.freq(50) # Set servo frequency


servoForward = 900_000 # Servo rotates clockwise
servoStop= 1_500_000 # Servo stops
servoReverse = 2_100_000 # Servo rotates counter-clockwise

threshhold_to_go_down = 1100
threshhold_to_go_up = 150

UP = "up"
DOWN = "down"

blinds_status = UP # Start with the blinds up

lr = ADC(Pin(26))

if __name__ == '__main__':
    
    try:
        connect_to_wifi()

        while True:
            lr_value = lr.read_u16()

            if lr_value >= threshhold_to_go_down and blinds_status == UP:
                blinds_down()
                blinds_status = DOWN
                send_data_to_server(DOWN, lr_value)
            elif lr_value <= threshhold_to_go_up and blinds_status == DOWN:
                blinds_up()
                blinds_status = UP
                send_data_to_server(UP,lr_value)
            
            
    except Exception as error:
		print(error)
~~~ 

You can tell we also send data to our server here, but now we also include if the blinds are going up or down.

The function now looks like this:

~~~python=3
def send_data_to_server(upOrDown, value):
    MAX_TRIES = 5
    c_try = 1
    while c_try < 5:
        try:
            json_obj = ujson.dumps({'value': value, 'upOrDown': upOrDown})
            res = requests.post(API_URL, headers = {'content-type': 'application/json'}, data = json_obj).json()
        except Exception as error:
            c_try += 1
            
~~~ 


### Transmitting the data / connectivity

In this projcet, Wifi was used to connect the device to internet. Since Raspberry Pico W has built in support for Wifi this an easy choice.

The data is transmitted via a custom Cloud function using Firebase Functions. It is sent using a regular HTTP POST request with a JSON body. The protocol used is TCP

The payload is then handled on the server and added to the database together with a timestamp.

As mentioned earlier, the data is sent every 15 minutes during the inital setup phase. And during production it's sent everytime the blinds go up or down (hopefully twice a day)

### Presenting the data

To present the data I built a simple dashboard using Reactjs together with the Chart.js library.

Below is a screenshot of the dashboard for the inital data collection.


I chose Firestore (Firebase) as database (NoSQL) as I am familiar with it and it is very easy to setup and get running.


![](https://hackmd.io/_uploads/ryLxAixYn.png)


### Finalizing the design

As I mentioned in the objective section. These blinds would probably operate better with a given timestamp to go up or down. But I am happy with the result! When I go to bed the blinds are down and when I wake up they are sometimes up.

The reason they don't go up sometimes is because the blinds have a built in stopper which it sometimes gets stuck on unfortunately.

Nevertheless I had so much fun building this, and I will keep expanding on it.

Something that I will consider doing is a 3D printed case for the controller, instead of using the awkward clamp to hold it in place. 


### Demo

<a href="https://youtu.be/h_mOgPck2fM">
<img src="https://hackmd.io/_uploads/SyNNpLkK3.png"  width="100%">
</a>


