Let the Sun Shine

Tell us the story of your project: 

This project did not start with the goal of an automated window blind.  Rather, the journey started when I experimented with a new light sensor. I learned how to extract light intensity data in lux from the light sensor by scaling the gain on the sensor based on an initial lux reading. As it turns out, this was needed to robustly collect intensity data across a range of light conditions from moonlight to direct sunlight for the automated window blind project.  But, I didn't know that at the time. 

I was showing my light sensor and data to other members of my family. Unfortunately, none of them really appreciated the beauty of the project and the light data like I did! Over the past several years of raspberry pi projects, they had grown mostly numb to me showing them my latest project. It was at this point that my wife asked if there wasn't some way I could make this light sensor useful by using it to control window blinds. We like to open the blinds on cold, but sunny, winter days to let the sunshine in and help heat the house. But, our house has a set of windows that are in an entry-way that puts the window blinds out of reach of most people. My wife was looking for a more convenient way to automatically open these blinds. We didn't want to spend the money for fancy automatic blinds and this option didn't even solve the problem on days when neither of us are home. 

After some initial attempts at scoping out the project on these hard to reach windows, my wife had second thoughts about her suggestion. Understandably, she didn't want me working on a project in the front hall in full view of anyone coming to our house. 

She quickly suggested I move the prototyping to a location that was out of the way and easy to reach. This turned out to be our daughter's bedroom. But, that raised the next problem: my daughter was home from college for winter break and didn't want the project in her room either!  So, I needed to wait until she went back to college after winter break. It was at that time the project began to take shape.

Having solved the non-Maker related aspects of this project, it was time for the fun. I believe Makers are lifelong learners and this is the secret to life. This project provided me a chance continue my learning journey while building from a foundation of previous raspberry pi enabled home automation projects. I knew I'd have new challenges in learning to use the light sensor data, adapting what I had learned from a robotic car project, and finding a way to connect the DC motor to the window blind in a robust way. It was the connection to the motor that I was the most concerned about. It turned out my wife, who is more mechanically inclined than I am, was a key resource for this part.

Each of these elements provided plenty of opportunity to learn. Once I had the light sensor working in its new location, I worked on connecting the DC motor that I had re-purposed from a previous robotic car project to the window blind pole. My initial attempts here to directly connect the DC motor shaft to the pole proved I had a lot to learn about the torque output of the small DC motors. The blind shaft required too much force to rotate by the motor. I solved this by re-purposing not just the DC motor, but the remote controlled car chassis that contained the gearing to increase the torque output of the DC motor. With these elements solved, next I needed a way to connect the window blind pole to the DC motor shaft. My wife and I talked about a range of options. Her suggestion was to use a complex mechanical device that sounded like it would work but sounded too complex for a simple build. After some research it turned out this complex device was commercially available as a Universal Joint. This became the final mechanical piece to the puzzle.

The next step was to build the logic in python that connected the light intensity output to a threshold value that triggers turning on the motor in forward or reverse to open or close the blind. In this stage I recognized I could extend the project to enable my learning in another area of interest: data analysis and interpretation.

I decided to plot the light intensity data over time. I used this as an opportunity to begin the journey of learning Pandas as a technique to work with data. This example shows the light intensity over a one day period of time starting from mid-night, extending to sun rise at just after the 400 data point mark, sunset just after 1100 data point mark, and finally just before the start of the next day as the final data point.

From here, I was able to select an appropriate threshold value to active the DC motor to open the blind. The value I chose was 1000 lux. Here's an example. It shows the blind opened at about the 450 data point mark when the graph moves from 0 to 1. It stayed open the entire day until sometime after the 1000 data point mark. This corresponds to the light data in the above graph where the lux values were greater than 1000 lux.

I've also always been interested in cyclic patterns in data. And, living in a Northern climate in January I was looking for something to help me look forward to the longer days of Spring. To this end, I plotted the length of each day as measured by the time during the day that light intensity was greater than 10 lux. Here's an example showing the days lengthening from January through the end of February in Ohio. Note the line is not perfectly straight as I expected. I've noticed that the local weather conditions impact slightly the length of the day as measured by light intensity. Cloud cover tends to shorten the length of the day as measured this way slightly.

Future project enhancements will include repurposing a Wii remote connected via Bluetooth to the Raspberry pi to enable a remote controlled option to open/close the blind independently of the light intensity.

 

How-to: 

On the mechanical side, start by connecting the Adafruit Stepper Motor to the pi.  Then, connect the DC motor from the re-purposed remote controlled car to the Stepper Motor inputs.  Detailed instructions for this can be found here.  Then, connect the light sensor to the Steper Motor GPIO pins.  Detailed instructions can be found here.   Next, mount the light sensor on a small section of plywood using screws or nails and mount re-purposed RC car to a larger piece of plywood using long bolts.  Use 1 or 2 plastic spacers on the bolts to help with a tight fit between the bolts and the RC car. Finally, attach the extra window blind pole to the RC car axel that has the motor you've connected to the Stepper Motor.  Using a Dremel to reduce the size of the axel to fit inside the Univeral Joint can be helpful. Connect the extra window blind pole to the pole from the window blind using the binder clips.

On the software side, the python scripts are below.  This summarizes the functions of the various scripts.  Note the collect lux script and the power blind script are setup in two differnt scrips because the lux sensor libraries from Adafruit work with python 3 and the Stepper Motor libraries build on python 2.  As a bridge, files are used to write data and pass between the two scripts.

  • collect_light_data.py activates the light sensor and records the lux reading in a file named light_output_data.txt
  • power_blind.py checks to see if the blind should open or close based on the most recent lux reading in the above light_output_data.txt file.
  • generate_light_graph.py makes the lux vs. time image named Lux_(current date).jpg
  • log_summary_light.py records the mean lux values, lenght of the day, and median lux values over the previous day's worth of data.  It geneates a file called light_summary.txt
  • generate_blind_graph.py generates the graph that shows when the blind was open or closed using 1 or 0 on the axis.  It generates a file named Blind.jpg
  • initilize_blind_state.py is used daily to set the initial blind state to closed and is needed by the power_blind.py script to function properly.

The execution of these scripts is done via crontab.  A full descripation of cron tab can be found here.

collect_light_data.py

import time as t
import datetime
import pandas as pd
import numpy as np
import scipy

import board
import busio

import adafruit_tsl2591
import matplotlib.pyplot as plt

from datetime import date
from datetime import time


# Initialize the I2C bus.
i2c = busio.I2C(board.SCL, board.SDA)

# Initialize the sensor.
sensor = adafruit_tsl2591.TSL2591(i2c)

# You can optionally change the gain and integration time:
#sensor.gain = adafruit_tsl2591.GAIN_LOW # (1x gain...for bright light)
#sensor.gain = adafruit_tsl2591.GAIN_MED #(25x gain, the default)
#sensor.gain = adafruit_tsl2591.GAIN_HIGH #(428x gain....for low light)
#sensor.gain = adafruit_tsl2591.GAIN_MAX #(9876x gain)
#sensor.integration_time = adafruit_tsl2591.INTEGRATIONTIME_100MS #(100ms, default)
#sensor.integration_time = adafruit_tsl2591.INTEGRATIONTIME_200MS #(200ms)
#sensor.integration_time = adafruit_tsl2591.INTEGRATIONTIME_300MS #(300ms)
#sensor.integration_time = adafruit_tsl2591.INTEGRAT IONTIME_400MS #(400ms)
#sensor.integration_time = adafruit_tsl2591.INTEGRATIONTIME_500MS #(500ms)
#sensor.integration_time = adafruit_tsl2591.INTEGRATIONTIME_600MS #(600ms)

 

# BE SURE TO SET LOGIC BASED ON LUX and NOT on visible or infrared.  Using visible is not stable.  Weird stuff.
# BE SURE TO GET AT LEAST 3 Reads before get acutal data.  If change the sensorinputs takes at least 3 to stabilize.

 

counter=0
while counter <5 :
    sensor.gain = adafruit_tsl2591.GAIN_LOW
    sensor.integration_time = adafruit_tsl2591.INTEGRATIONTIME_100MS
#   This is to clear the sensor.  Can later try to remove this.  But, at some point seemed to be more robust with this.    
    for x in range (5):
        visible = sensor.visible
        lux =sensor.lux
        infrared=sensor.infrared
        t.sleep(0.1)
    
    if lux <= 1:
        sensor.gain = adafruit_tsl2591.GAIN_HIGH
        sensor.integration_time = adafruit_tsl2591.INTEGRATIONTIME_600MS
        for x in range (5):
            visible = sensor.visible
            lux =sensor.lux
            infrared=sensor.infrared
            t.sleep(0.25)
 
    elif lux <= 10:
        sensor.gain = adafruit_tsl2591.GAIN_HIGH
        sensor.integration_time = adafruit_tsl2591.INTEGRATIONTIME_200MS
        for x in range (5):
            visible = sensor.visible
            lux =sensor.lux
            infrared=sensor.infrared
            t.sleep(0.25)

    elif lux <= 100:
        sensor.gain = adafruit_tsl2591.GAIN_MED
        sensor.integration_time = adafruit_tsl2591.INTEGRATIONTIME_200MS
        for x in range (5):
            visible = sensor.visible
            lux =sensor.lux
            infrared=sensor.infrared
            t.sleep(0.25)

    elif lux <= 1000:
        sensor.gain = adafruit_tsl2591.GAIN_MED
        sensor.integration_time = adafruit_tsl2591.INTEGRATIONTIME_100MS
        for x in range (5):
            visible = sensor.visible
            lux =sensor.lux
            infrared=sensor.infrared
            t.sleep(0.25)

    else:
        sensor.gain = adafruit_tsl2591.GAIN_LOW
        sensor.integration_time = adafruit_tsl2591.INTEGRATIONTIME_100MS
        for x in range (5):
            visible = sensor.visible
            lux =sensor.lux
            infrared=sensor.infrared
            t.sleep(0.25)
    counter = counter+1
    print (counter)

print (infrared)
print('Total light: {0}lux'.format(lux))
print('Infrared light: {0}'.format(infrared))
print('Visible light: {0}'.format(visible))  


x = date.today()


file = open('/home/pi/LIGHT/'+'light_output_'+str(x)+'.txt', "a")
t = datetime.datetime.now()

file.write (str(lux)+","+str(infrared)+","+str(visible)+"\n")


print("end")
file.close()

 

 

power_blind.py

#!/usr/bin/python
from Adafruit_MotorHAT import Adafruit_MotorHAT, Adafruit_DCMotor
import time
import atexit

import pandas as pd
import numpy as np
import scipy
from datetime import date
from datetime import time as t

 

# Note - this is in python2 since the Adafruit_MotorHAT modele is python2


# create a default object, no changes to I2C address or frequency
mh = Adafruit_MotorHAT(addr=0x60)

# recommended for auto-disabling motors on shutdown!
def turnOffMotors():
    mh.getMotor(1).run(Adafruit_MotorHAT.RELEASE)
    mh.getMotor(2).run(Adafruit_MotorHAT.RELEASE)
    mh.getMotor(3).run(Adafruit_MotorHAT.RELEASE)
    mh.getMotor(4).run(Adafruit_MotorHAT.RELEASE)

atexit.register(turnOffMotors)

################################# DC motor test!
myMotor = mh.getMotor(3)

DF=[]
DF2=[]

x = date.today()

DF = pd.read_csv('/home/pi/LIGHT/'+'light_output_'+str(x)+'.txt')
DF.columns=['Lux','Infrared','Visible']
previous_lux = DF['Lux'].iloc[-1]
print previous_lux

DF1 = pd.read_csv('/home/pi/LIGHT/'+'blind_state_'+str(x)+'.txt', header=None)
DF1.columns=['Date','Lux','Opened_or_Closed']
print DF1
previous_state = DF1['Opened_or_Closed'].iloc[-1]

print 'previous_state ',+previous_state


if previous_lux > 1000:
        new_state=1
else:
        new_state=0
        
print 'new state ',+new_state
file = open('/home/pi/LIGHT/'+'blind_state_'+str(x)+'.txt', "a")
file.write (str(x)+","+str(previous_lux)+','+str(new_state)+"\n")
file.close()

if new_state == 1:
        if previous_state ==0:             
# Open Blind
# set the speed to start, from 0 (off) to 255 (max speed)
                print 'Was closed, needs to open'
                myMotor.setSpeed(0)
                myMotor.run(Adafruit_MotorHAT.FORWARD);
# turn on motor
                myMotor.run(Adafruit_MotorHAT.RELEASE);
# Open Blind to let in light
                myMotor.run(Adafruit_MotorHAT.BACKWARD)
                myMotor.setSpeed(50)
                time.sleep(4)
        else:
                print 'was open, needs to stay open, so dont open again'
else:
        if previous_state == 1:
# Close Blind

                print 'was opened, needs to close'             
                myMotor.run(Adafruit_MotorHAT.FORWARD)
                myMotor.setSpeed(50)
                time.sleep(4.5)
        if previous_state == 0:
                print 'was closed, needs to stay closed'

print 'end'

 

generate_light_graph.py

import time
import datetime
import pandas as pd
import numpy as np
import scipy
from datetime import date
from datetime import time


import board
import busio

import adafruit_tsl2591

import matplotlib as mpl

mpl.use('Agg')
import matplotlib.pyplot as plt


x = date.today()
DF = pd.read_csv('/home/pi/LIGHT/'+'light_output_'+str(x)+'.txt')
DF = DF.astype(float)
DF.columns=['Lux','Infrared','Visible']

DF1=DF[['Lux']]
DF1.columns=['Lux']

DF1.plot()
plt.title("Lux vs. Time")
plt.ylabel("Calibrated Lux")
plt.xlabel("Data Points")
#plt.savefig('/home/pi/LIGHT/Lux.jpg')
plt.savefig ('/home/pi/LIGHT/'+'Lux_'+str(x)+'.jpg')


# Uncomment this to show on the screen    
#plt.show()

 

log_summary_light.py

import time
import datetime
import pandas as pd
import numpy as np
import scipy
from datetime import date
from datetime import time
import board
import busio
import adafruit_tsl2591
import matplotlib as mpl
mpl.use('Agg')
import matplotlib.pyplot as plt

x = date.today()

DF = pd.read_csv('/home/pi/LIGHT/'+'light_output_'+str(x)+'.txt')
DF = DF.astype(float)
DF.columns=['Lux','Infrared','Visible']

DF1=DF[['Lux']]
DF1.columns=['Lux']


Lux_Mean = DF1['Lux'].mean()
print (Lux_Mean)


Lux_Mean_Filter = float(DF1[DF1['Lux']>10].mean())
print (Lux_Mean_Filter)

Lux_Median_Filter = float(DF1[DF1['Lux']>10].median())
print (Lux_Median_Filter)

Lux_Count_Filter = float(DF1[DF1['Lux']>10].count())
print (Lux_Count_Filter)

x = datetime.datetime.now()
x = x.strftime("%d/%m/%y")
print (x)

file = open("/home/pi/LIGHT/light_summary.txt", "a")
file.write(str(x)+","+str(Lux_Mean)+","+str(Lux_Mean_Filter)+","+str(Lux_Median_Filter)+","+str(Lux_Count_Filter)+"\n")


print("end")
file.close()

generate_blind_graph.py

import time
import datetime
import pandas as pd
import numpy as np
import scipy
from datetime import date
from datetime import time


import board
import busio

import adafruit_tsl2591

import matplotlib as mpl

mpl.use('Agg')
import matplotlib.pyplot as plt


x = date.today()
DF = pd.read_csv('/home/pi/LIGHT/'+'blind_state_'+str(x)+'.txt')
DF.columns=['Date','Lux','Opened_or_Closed']

DF1=DF[['Opened_or_Closed']]
DF1.columns=['Opened_or_Closed']

DF1.plot()
plt.title("Status of Window Blind vs. Time")
plt.ylabel("Opened or Closed")
plt.xlabel("Data Points")
plt.ylim(-0.25,1.25)
plt.savefig ('/home/pi/LIGHT/'+'Blind.jpg')

 

initilize_blind_state.py

import pandas as pd
import numpy as np
import time as t
import datetime
from datetime import date
from datetime import time

 

x = date.today()

file = open('/home/pi/LIGHT/'+'blind_state_'+str(x)+'.txt', "a")
t = datetime.datetime.now()

file.write (str(x)+","+'0'+","+'0'+"\n")
#file.write (str(x)+","+'0'+","+'0'+"\n")

print("end")
file.close()

 

crontab setup

04 00 * * * sudo python3 /home/pi/LIGHT/initilize_blind_state.py
*/ 1 * * * * sudo python3 /home/pi/LIGHT/collect_light_data.py
*/1 * * * * sudo python /home/pi/LIGHT/PowerBlind.py
49 23 * * * sudo python3 /home/pi/LIGHT/generate_light_graph.py
50 23 * * * sudo python3 /home/pi/LIGHT/log_summary_light.py
51 23 * * * sudo python3 /home/pi/LIGHT/generate_blind_graph.py
52 23 * * * sudo python3 /home/pi/LIGHT/log_summary_light.py
53 23 * * * sudo python3 /home/pi/LIGHT/generate_light_summary_graph.py
56 23 * * * sudo cp /home/pi/LIGHT/light_output*.txt /home/pi/LIGHT/ARCHIVE/.
56 23 * * * sudo mv /home/pi/LIGHT/Lux*.jpg /home/pi/LIGHT/ARCHIVE/.
58 23 * * * sudo reboot

Difficulty: 
Duration: 
Share a "Show & Tell" video.: 
https://youtu.be/D13qOuPT1Ig
Collaborators: 
thedunlop
Number of Forks: 
0
Tools: 
Name: 
Small screwdriver
Quantity: 
1
Name: 
Small allen wrench
Quantity: 
1
Name: 
Dremel
Quantity: 
1
Materials: 
Name: 
Power source for Raspberry Pi 3--AC Adapter, or mobile phone battery back)
Quantity or amount: 
1
Name: 
power source for Adafruit Board
Quantity or amount: 
1
Name: 
electrical wires
Quantity or amount: 
2
Name: 
Existing Mounted Window Blind
Quantity or amount: 
1
Name: 
Binder Clip
Quantity or amount: 
2
Name: 
Extra Window Blind Pole
Quantity or amount: 
1
Name: 
Gears from RC Car, or similar
Quantity or amount: 
1
Name: 
Male/Female Jumper Wires
Quantity or amount: 
4
Name: 
Adafruit TSL2591 High Dynamic Range Digital Light Sensor
Quantity or amount: 
1
Name: 
Universal Joint, 10mm x 10mm
Quantity or amount: 
1
Name: 
DC Motor, If not re-purposing RC Car, 130size or similar
Quantity or amount: 
1
Name: 
12mm plywood, or similar
Quantity or amount: 
1
Name: 
Clamp
Quantity or amount: 
2
Name: 
Small screws
Quantity or amount: 
2
Name: 
Long bolts
Quantity or amount: 
4
Name: 
Plastic spacer
Quantity or amount: 
2
Boards & Kits: 
Name: 
Raspberry Pi 3 Model B
Quantity: 
1
Name: 
Adafruit DC & Stepper Motor HAT for Raspberry Pi - Mini Kit
Quantity: 
1
Team Members: 
Team member name: 
thedunlop
What role did this person play on the project?: 
Project Lead
Public
Sort Order: 
0
Teaser: 
This project opens and closes a window blind automatically based on the amount of light coming in the window. It uses a raspberry pi, light sensor, Adafruit DC Motor controller, universal joint, and a re-purposed RC car for the DC motor and gears.
Aha! moment: 
There were two AHA! moments in this project. On the mechanical side, my early prototypes lacked a robust way to connect the window blind pole to the DC motor. The solution was a universal joint. On the data side, I realized I could take the light intensity data I was collecting and plot it to measure length of each day as seasons changed.
Uh-oh! moment: 
My early prototypes connected a the DC motor directly to the window blind pole. Unfortunately, the small DC motor from the repurposed RC car was not large enough to spin the pole. The solution was to re-use the entire gear assembly of the RC car to get enough torque from the motor to spin the window blind pole.
Show & Tell video as default: 
Creation Date: 
Thursday, February 21, 2019 - 11:34