***Edit (Nov 2017): Currently Raspbian Stretch has reverted back to disabling Predictable Network Interface Names by default. To learn how to set a static IP address using the current default configuration, see this tutorial. To enable Predictable Network Interface names, use the command raspi-config to pull up Raspbian's basic configuration menu. From the main menu select option 2 - Network Options, and then from the sub-menu select option N3 - Network Interface Names.***
As we are all aware by now, Raspbian released their latest version named Stretch a few days ago on August 16th, 2017. So this weekend, when I was about to start a new project, I went to the Raspbian download page and downloaded a new image of Raspbian Stretch Lite. While I waited on the download I skimmed the release notes, which unfortunately fail to mention an important change to the new operating system version: Predictable Network Interface Names.
Once I finished downloading and the image was written to the SD card, I proceeded to do the initial set-up of the Pi for this particular project, which in this case included setting a static IP address. As usual, I opened /etc/dhcpcd.conf and edited the file with the IP information for eth0. After rebooting, I was surprised to find that I was still issued the same dhcp address instead of the static ip address I specified. After over an hour of troubleshooting and searching through forums, I finally found my problem: my network interface is no longer named eth0. This should have been evident after my first ifconfig, but since I wasn't expecting a change to the interface name, my eyes just skimmed over that information and went straight to the ip address. I'm hoping that by publishing this I can save someone else the same frustration and waste of time.
Predictable Network Interface Names are not new, but prior to Stretch they were not used by default in Raspbian. The new naming scheme no longer uses the traditional interface names such as eth0, eth1, wlan0, etc, and instead uses hardware address based names to avoid ambiguity and prevent the accidental changing of interface names. On the Raspberry Pi, since the Ethernet interface is connected through the usb bus, the mac address is used to identify the interface. Specifically, the interface names will start with "en" for Ethernet or "wl" for wireless, followed by an "x" to indicate that it will be followed by the mac address, and then predictably the mac address. For example, if an Ethernet port has a mac address of b827eb123456, then the interface name will be enxb827eb123456. It is straight forward enough, but if you weren't aware of the change, it could certainly catch you off guard, and leave you wondering why configuring eth0 has no effect on the configuration of your Ethernet port.
In order to find the new name of your interface, simply use ifconfig and check the available interfaces. If you would rather not use the new naming scheme, you can revert to the old naming scheme by adding net.ifnames=0 to /boot/cmdline.txt. However, this is quickly becoming the new normal, as more and more Linux distributions switch to using predictable network interface names, so I highly recommend sticking with and getting used to using the new naming scheme, as it will soon be ubiquitous.
Here is a link to the forum post where I found the initial information regarding Raspbian Stretch and the change to predictable network interface names.
EDIT: Apparently the interface names are only affected on new installations of Stretch, and not on systems that are upgraded from Jessie to Stretch. I assume the reason for this is so that every system with a static IP that gets upgraded to Stretch won't immediately lose their static IP.
EDIT: As of the latest version of Raspbian Stretch, released on Nov 29th, 2017, Raspbian no longer defaults to using Predictable Network Interface Names. Instead it uses the old naming scheme of eth0, wlan0, etc. They have also added a new option to raspi-config to allow you turn on Predictable Network Interface Names if you would prefer to use the new naming scheme.
Sunday, August 20, 2017
Sunday, August 13, 2017
Reading Temperature Sensors and Displaying Data Using I2C Protocol and the Raspberry Pi
For this project, I am going to be using LM57A temperature sensors to measure the temperature inside my network and server cabinets, and communicate that back to the Raspberry Pi using I2C protocol. I am then going to use that data to control cooling fans inside the cabinet. I am also going to output the data to a 7-segment display, again using I2C protocol, and store the data in a log file so that it can be analyzed later. Lastly I am going to have the Raspberry Pi send me an email if the temperature continues to rise and reaches a second threshold. I am aware that this project is very specific to my own needs, as not too many people have network and server cabinets in their houses, but I believe that the techniques and methods used in this project will be very useful to others trying to do similar things, especially using I2C protocol to send and receive data. I am going to break this project into several segments for easy reference. They are as follows: using the LM57A temperature sensor, setting up and using I2C protocol with the Raspberry Pi, using the Adafruit I2C Backpack with a 7-segment display, controlling fans using the Raspberry Pi, and final Python script.
I2C is a very simple, easy to use, serial communication protocol developed in the 1980s, primarily for use in intra-board communications. It is also commonly referred to as I2C or IIC. So why use I2C protocol? Three words: "Conserve GPIO pins." I2C only uses two wires for serial communication, and can use multiple devices on those same two wires. It does this by using a master and slave configuration, where there is only one master device, and all other devices on the circuit act as slaves. Communication can only be initiated by the master in order to eliminate the possibility of more than one device trying to communicate at a time. It is obvious that this saves us a few pins by consolidating all the temperature sensors onto one serial input, but the real savings comes when using I2C to communicate with the 7-segment LED display. If we used a traditional 4-digit 7-segment display we would be using up a whopping 12 GPIO pins for the display alone! Considering there are only 26 usable GPIO pins on the Pi 2B and 3B, and I have already used up several of them on previous projects, this project would come close to maxing out this Pi's available pins if we did not use I2C. Since we are using I2C, two pins for serial communication and two pins to control the cooling fans is all we will need for this entire project.
Now that I've discussed why I am using I2C, let's get to how to set up I2C on the Raspberry Pi. Adafruit has a nice tutorial (link), but unfortunately it is out of date and doesn't work with the latest versions of Raspbian. So, here is my own updated tutorial on enabling and testing I2C.
By default I2C is not enabled, so first we need to enable it. This can be done the easy way using raspi-config, options 5, then option P5, but I prefer to understand what is happening under the hood, so let's go over how to quickly do this via the CLI. First we need to edit the /etc/modules file to tell the Pi to load the I2C kernel module on boot. Open /etc/modules with a text editor and add the following two lines to the end of the file:
i2c-bcm2708
i2c-dev
Using the LM57A Temperature Sensor
I looked at using several different temperature sensors for this project, and settled on the LM75A Temperature Sensor High-speed 12C Interface Development Board Module. I chose the LM75A for several reasons, including cost, on-board analog-to-digital conversion, I2C interface, and ability to hard-wire the I2C address into the chip. For those of you who would rather not read through my personal experience with the LM57A, here is a link to the data sheet. For less than $3 US, this is an amazing little device, and has way more features than we will be using here. You can actually set temperature thresholds on the chip itself, and use it as a thermostat without even involving the Pi, but enough about features we won't be using. I'll try to focus on the features that make this the perfect temperature sensor for this project.
The most important feature of this sensor, is that it has built-in analog-to-digital conversion, so it can send data directly to the Raspberry Pi, which is limited in only having digital GPIO pins. I was also looking for something with an I2C interface, which I will cover more in-depth in the next section. The nice thing about I2C is that you can connect multiple devices on the same 2-wire serial connection. In order for this to work, each device must have a unique 7-bit address, which is where another feature of the LM57A comes in handy. You can set the last three bits of the address by connecting the solder pads for A0 through A2 on the back of the PCB. The first four bits of the address are '1001', and are hardwired inside the chip, so that gives us the option to use the addresses '1001000' through '1001111', which are the Hex addresses 0x48 through 0x4F. With these 8 potential addresses we can hook up 8 of these sensors on one I2C circuit. In the picture above you can see that the configuration of the pads makes it easy to set the address using a simple solder bridge. The chip on the left is unset. The chip in the middle is set to 1001001 (0x49) and the chip on the left is set to 1001011 (0x4B). These registers must be set, and should not be left floating during use.
One last feature of the LM57A that adds a little bit of convenience to this project is that the input voltage can range from 2.8V to 5.5V. This means that we can power these little units with either the 3.3V or 5V rail on the Pi. If it was just the sensors the 3.3V rail would suffice, but since we are also going to be powering a 7-segment LED from the same source, and the 3.3V on the Pi is limited in the amount of current it can supply, we'll go ahead and use the 5V supply on the Pi, which is wired directly to the USB power source, and is only limited by the power supply that you are using for your Pi.
One last feature of the LM57A that adds a little bit of convenience to this project is that the input voltage can range from 2.8V to 5.5V. This means that we can power these little units with either the 3.3V or 5V rail on the Pi. If it was just the sensors the 3.3V rail would suffice, but since we are also going to be powering a 7-segment LED from the same source, and the 3.3V on the Pi is limited in the amount of current it can supply, we'll go ahead and use the 5V supply on the Pi, which is wired directly to the USB power source, and is only limited by the power supply that you are using for your Pi.
Setting-up and Using I2C Protocol with the Raspberry Pi
The image above is borrowed from https://www.lammertbies.nl/comm/info/I2C-bus.html |
Now that I've discussed why I am using I2C, let's get to how to set up I2C on the Raspberry Pi. Adafruit has a nice tutorial (link), but unfortunately it is out of date and doesn't work with the latest versions of Raspbian. So, here is my own updated tutorial on enabling and testing I2C.
By default I2C is not enabled, so first we need to enable it. This can be done the easy way using raspi-config, options 5, then option P5, but I prefer to understand what is happening under the hood, so let's go over how to quickly do this via the CLI. First we need to edit the /etc/modules file to tell the Pi to load the I2C kernel module on boot. Open /etc/modules with a text editor and add the following two lines to the end of the file:
i2c-bcm2708
i2c-dev
Next we need to edit the /boot/config.txt file. Open /boot/config.txt with a text editor and uncomment the following line by removing the pound sign:
#dtparam=i2c_arm=on
And that's it. I2C is now enabled. Next we want to install the python module and command line utility that will enable us to utilize I2C.
sudo apt-get install python-smbus
sudo apt-get install i2c-tools
OK, we are ready to test this out. Hookup one of the LM57A sensors to the Pi. The I2C-1 SDA and SCL pins are on GPIO pins 3 and 5 respectively, and we will also need to to use a 3.3V and Ground pin to power the sensor. To test to see if the Pi can see the sensor use the command i2cdetect -y 1, where the 1 refers to the specific I2C bus we are using. On older models of the Pi this will be 0 instead of 1.
In the image to the left, you can see that the Pi has detected the temperature sensor with an address of 0x48. On this particular sensor I have set A0, A1, and A2 to 0, so the full address should be 1001000 as explained above. If we convert this to hexadecimal we get the number 48, which means everything is working as expected. Next lets see if we can pull a temperature reading from the sensor. For this we use the command i2cget -y 1 0x48, where 1 is the name of the I2C bus, and 0x48 is the address of the device we are polling. As you can see, the sensor returned a value of 0x1c. At first glance this may not seem like a usable temperature value, but lets think about this for a second. First, this is a hex value, but most of us think in decimal. If we convert 0x1c to decimal we get 28. If you are in the United States like I am, we are also not used to thinking in Celsius, so we need to convert 28 degrees C to Fahrenheit, and we get 82.4 degrees F. It's a warm day and I have the shop door open, so I am willing to accept that as an accurate reading.
So, now we can detect an I2C slave device, and take a reading from it, but the whole point of using I2C is that we can hook up multiple devices to the same two-wire serial bus. Since we only have one set of pins on the Pi to connect to the bus we are going to have to whip up a quick circuit board to connect multiple devices. This can be done by soldering some pin headers on a circuit board and using solder bridges to connect the pins. Be sure to use a multi-meter to make sure all the rows are properly bridged, and that none of the rows are bridged together.
And that's it. I2C is now enabled. Next we want to install the python module and command line utility that will enable us to utilize I2C.
sudo apt-get install python-smbus
sudo apt-get install i2c-tools
OK, we are ready to test this out. Hookup one of the LM57A sensors to the Pi. The I2C-1 SDA and SCL pins are on GPIO pins 3 and 5 respectively, and we will also need to to use a 3.3V and Ground pin to power the sensor. To test to see if the Pi can see the sensor use the command i2cdetect -y 1, where the 1 refers to the specific I2C bus we are using. On older models of the Pi this will be 0 instead of 1.
In the image to the left, you can see that the Pi has detected the temperature sensor with an address of 0x48. On this particular sensor I have set A0, A1, and A2 to 0, so the full address should be 1001000 as explained above. If we convert this to hexadecimal we get the number 48, which means everything is working as expected. Next lets see if we can pull a temperature reading from the sensor. For this we use the command i2cget -y 1 0x48, where 1 is the name of the I2C bus, and 0x48 is the address of the device we are polling. As you can see, the sensor returned a value of 0x1c. At first glance this may not seem like a usable temperature value, but lets think about this for a second. First, this is a hex value, but most of us think in decimal. If we convert 0x1c to decimal we get 28. If you are in the United States like I am, we are also not used to thinking in Celsius, so we need to convert 28 degrees C to Fahrenheit, and we get 82.4 degrees F. It's a warm day and I have the shop door open, so I am willing to accept that as an accurate reading.
So, now we can detect an I2C slave device, and take a reading from it, but the whole point of using I2C is that we can hook up multiple devices to the same two-wire serial bus. Since we only have one set of pins on the Pi to connect to the bus we are going to have to whip up a quick circuit board to connect multiple devices. This can be done by soldering some pin headers on a circuit board and using solder bridges to connect the pins. Be sure to use a multi-meter to make sure all the rows are properly bridged, and that none of the rows are bridged together.
Using the Adafruit I2C Backpack for 7-Segment Display
Adafruit makes a very handy little device called the backpack that takes I2C in and uses that to drive an LCD array. For this project we are using the model HT16K33 that works with a 4-digit 7-segment display. As always, Adafruit provides a nice tutorial (Here and Here) for getting this thing working, so I don't feel the need to go into great detail, but I'll outline the general process and we'll test it out.
First we need to solder the backpack onto the 7-segment LED display. This is pretty self-explanatory, just be sure not to solder it on upside-down. There is a stencil on the backpack that matches the display to help with orientation. Next we need to solder on the pin headers for the two serial wires plus Vcc and GND. With the male pin headers sticking out the back of the backpack, we can stick the backpack directly on the homemade I2C bus we just created.
To get this thing working I downloaded the software recommended by Adafruit:
sudo apt-get install -y git build-essential python-dev python-smbus python-imaging python-pip python-pil
and then:
git clone https://github.com/adafruit/Adafruit_Python_LED_Backpack.git
cd Adafruit_Python_LED_Backpack
sudo python setup.py install
Once that is done, inside the /Adafruit_Python_LED_Backpack folder you just created, there is a folder called /examples which contains well documented code that will give you a thorough understanding of how to use these modules in Python. If only every hardware maker was as thorough as Adafruit we could save so much time. You can run these example scripts to make sure that your Backpack and 7-segment display are working correctly.
One last trick I'd like to mention regarding the 7-segment display being used for this project is that while this unit can only display numbers, it is able to display hexadecimal numbers, which gives it the ability to display the letters a through f. I wanted to be able to display which area each temperature correlated to, so I needed to be able to write the strings "Cab1", "Cab2", and "Base" for basement. Luckily the only letter in those labels that is not able to be represented in hex is "s". Luckily there is no difference between 5 and S on a 7-segment display, so that is how I was able to make it display the necessary strings.
One last trick I'd like to mention regarding the 7-segment display being used for this project is that while this unit can only display numbers, it is able to display hexadecimal numbers, which gives it the ability to display the letters a through f. I wanted to be able to display which area each temperature correlated to, so I needed to be able to write the strings "Cab1", "Cab2", and "Base" for basement. Luckily the only letter in those labels that is not able to be represented in hex is "s". Luckily there is no difference between 5 and S on a 7-segment display, so that is how I was able to make it display the necessary strings.
Controlling Fans Using the Raspberry Pi
Turning on the cooling fans for the network cabinets is the easy part. Since I am using 12V fans they will need to be powered by an external power source and controlled by a relay wired to a GPIO pin. If you are using a relay board similar to the one I am using (JBtek 4 Channel DC 5V Relay Module), then there is no additional circuitry required between the Pi and the board because the relay board already contains a resistor and an opto-isolator to prevent too much current from being drawn from the GPIO pin. Just be sure to power the relay board with one of your 5V pins wired to Vcc on the board.
If you have followed any of my previous projects, you may know that I have already set-up a 12V (30 amp) power supply, a DC fuse panel, and a relay board for use with my automated sprinklers, so there was nothing for me to purchase other than the fans. I wired the 12V power supply, through the fuse panel, to a switch to allow me to turn off the power to both sets of fans. I wired the output of the switch to two relays so that I could control the fans in the two cabinets separately. From the relays I ran 16 awg wire to the top of each cabinet where the fans would be mounted. The fans I purchased had old IDE-style power connectors. I like to keep things modular, so in order to make replacing the fans easy in the future, I cut some power connectors off of an old PSU, and soldered them onto the ends of the 16 awg wires.
If you have followed any of my previous projects, you may know that I have already set-up a 12V (30 amp) power supply, a DC fuse panel, and a relay board for use with my automated sprinklers, so there was nothing for me to purchase other than the fans. I wired the 12V power supply, through the fuse panel, to a switch to allow me to turn off the power to both sets of fans. I wired the output of the switch to two relays so that I could control the fans in the two cabinets separately. From the relays I ran 16 awg wire to the top of each cabinet where the fans would be mounted. The fans I purchased had old IDE-style power connectors. I like to keep things modular, so in order to make replacing the fans easy in the future, I cut some power connectors off of an old PSU, and soldered them onto the ends of the 16 awg wires.
Final Python Script
I glossed over using I2C with Python earlier in this post because I'm doing my best to keep these posts as short as possible while still including as much information as possible. Below I have pasted the entire Python script for this project so that anyone that would like to use the script can copy and paste it. In addition I have done my best to thoroughly comment the script, so that anyone with a basic understanding of Python syntax should be able to follow along and see exactly how I used Python to send and receive data on the I2C bus. If anyone has trouble understanding the code, or if you have problems adapting the code to your own project, feel free to leave questions in the comment section below.
Of course, I've edited out my email addresses and password in the code, so if you are going to copy&paste it, be sure to substitute in your own email addresses and the password for the sending email account.
Of course, I've edited out my email addresses and password in the code, so if you are going to copy&paste it, be sure to substitute in your own email addresses and the password for the sending email account.
#!/usr/bin/env python ############################################################### # # # i2ctemp2-0.py # # written by James Campbell # # July 2017 # # # # Monitor temperatures of network and server cabinets # # Display temperatures and time on 7-segment display # # If temperatures reach preset threshold then turn on fans # # If temperatures reach second threshold then send email # # Store temperatures to log file # # # ############################################################### import time import smbus # lets us access the i2c bus from Adafruit_LED_Backpack import SevenSegment # used to control 7-segment display import RPi.GPIO as GPIO # used to read/write to GPIO pins import datetime import smtplib #used to send email from email.mime.text import MIMEText #used to compose email CAB_1_TEMP = 0x48 # I2C address for Cab 1 temp sensor CAB_2_TEMP = 0x49 # I2C address for Cab 2 temp sensor BASEMENT_TEMP = 0x4B # I2C address for basement temp sensor DISPLAY = 0x70 # I2C address for 7-seg display BUS_NUM = 1 # 1 is the I2C bus (board pins 3 and 5) PAUSE_TIME = 3 # the number of seconds to pause on each display FAN1_PIN = 13 # GPIO Pin to control fan relay for Cab 1 FAN2_PIN = 19 # GPIO Pin to control fan relay for Cab 2 FAN_THRESHOLD = 90 # the temperature threshold (F) for turning on fans EMAIL_THRESHOLD = 95 # the temperature threshold (F) for sending an email GMAIL_USER = 'youremail@gmail.com' # email account used to send email GMAIL_PASS = 'yourpassword' # password for email account used to send email SENT_FROM = GMAIL_USER SEND_TO = 'email1@email.com, email2@email.com' # email recipient for alerts FAN1_RUNNING = False FAN2_RUNNING = False EMAIL_TIMER_1 = 0 EMAIL_TIMER_2 = 0 EMAIL_TIMER_B = 0 # FAN_THRESHOLD = 50 # Uncomment this line to test the fans # EMAIL_THRESHOLD = 50 # Uncomment this line to test the email def CtoF( Ctemp ): return((Ctemp*9.0/5.0)+32) # this line converts the Celcius temperature passed # to the funtion to Fahrenheit and returns it. def GET_TEMPS(): global Ftemp1 global Ftemp2 global FtempB # below is an example of how to read data from an I2C bus # where "bus" has already been defined in the main program # using "bus = smbus.SMBus(BUS_NUM)". This will return 1 byte. Ctemp1 = bus.read_byte(CAB_1_TEMP) Ctemp2 = bus.read_byte(CAB_2_TEMP) CtempB = bus.read_byte(BASEMENT_TEMP) # the lines below use the CtoF() Function defined above to # convert Celcius to Fahrenheit Ftemp1 = CtoF(Ctemp1) Ftemp2 = CtoF(Ctemp2) FtempB = CtoF(CtempB) # the below is just for troubleshooting print ('The follwoing temperatures have been read') print '%d, %d, and %d' % (Ftemp1, Ftemp2, FtempB) def FAN_CHECK(): # This function compares cabinet temps to # thresholds and turns the fan on or off. global FAN1_RUNNING global FAN2_RUNNING if FAN1_RUNNING: if Ftemp1 < (FAN_THRESHOLD - 3): GPIO.output(FAN1_PIN, 1) FAN1_RUNNING = False else: if Ftemp1 > FAN_THRESHOLD: GPIO.output(FAN1_PIN, 0) FAN1_RUNNING = True if FAN2_RUNNING: if Ftemp2 < (FAN_THRESHOLD - 3): GPIO.output (FAN2_PIN, 1) FAN2_RUNNING = False else: if Ftemp2 > FAN_THRESHOLD: GPIO.output (FAN2_PIN, 0) FAN2_TIMER_RUNNING = True print ('fan check') # for troubleshooting only def DISPLAY_TEMP( temp, cab_num ): # in this function we will write data # to the I2C Backpack 7-segment display # The value for "display" has already been # set in the main program to refer to the # bus and address for the 7-seg display display.clear() # This clears the display. If this is not done, any # data that is not overwritten will remain. The display # will not actually clear until .write_display() is called display.set_colon(False) # This uses a boolean value to specify if the # colon is displayed, as in a digital clock display.print_hex(cab_num) # This is how you display a hex number on # the display display.write_display() # Once whatever is going to be displayed is set # This line actually sends it to the display print ('displaying %s' % hex(cab_num)) # for troubleshooting only time.sleep(PAUSE_TIME/2) # This line pauses before changing the display display.clear() # Just like above, this line clears the display display.print_float(temp, decimal_digits=1) # this line displays the value # temp with one digit after # the decimal point display.write_display() # Just like above, this line actually sends the # data to the display print 'displaying temp' # for troubleshooting only time.sleep(PAUSE_TIME) # This line pauses before changing the display def DISPLAY_CLOCK(): # This function will also write to the 7-segment display # but this time instead of cabinet and temperature # it will display the current time. This will also show you # how to write each specific digit seperately # Below is one of the two ways we will use in this script to get the # current time. now = datetime.datetime.now() hour = now.hour minute = now.minute if hour > 12: hour = hour - 12 # This converts 24 hour time to 12 hour time display.clear() # Just like above this will clear the display. # Below we are setting the digits one at a time. They are numbered # 0 through 3, with 0 starting on the left and 3 ending on the right. if (hour / 10) > 0: # These two lines keep 6:00 from showing up as 06:00 display.set_digit(0, int(hour / 10)) display.set_digit(1, hour % 10) # this uses modulo to set the second digit # for hour display.set_digit(2, int(minute / 10)) # this line sets the first digit for # minute by dividing minutes by 10 and # dropping the remainder display.set_digit(3, minute % 10) # this uses modulo to set the second # digit for minute display.set_colon(True) # This line turns on the colon on the display display.write_display() # As before, this line actually sends the data to # the 7-seg display print 'displaying time' # for troubleshooting only time.sleep(PAUSE_TIME) # This line pauses before changing the display again def SEND_EMAIL ( location, temperature ): # This function sends an email timestamp = time.strftime("%m-%d-%y %H:%M:%S") subject = '%s Temperature Too High' % (location) + timestamp body = 'The temperature in %s is %d degrees.' % (location, temperature) msg = MIMEText(body) msg['From'] = SENT_FROM msg['To'] = SEND_TO msg['Subject'] = subject try: server = smtplib.SMTP_SSL('smtp.gmail.com', 465) server.ehlo() server.login(GMAIL_USER, GMAIL_PASS) server.sendmail(SENT_FROM, SEND_TO, msg.as_string()) server.close() print 'email sent' # for troubleshooting only except: print 'email atempted and failed' # for troubleshooting only def EMAIL_CHECK (): # This function decides if an email should be sent, and calls SEND_EMAIL() if so. # I don't want to spam myself with emails, so the two conditions for emailing # are that the temperature is above the threashold, and an email has not been # sent in the last 15 minutes global EMAIL_THRESHOLD global EMAIL_TIMER_1 global EMAIL_TIMER_2 global EMAIL_TIMER_B print 'email check' # for troubleshooting only if Ftemp1 > EMAIL_THRESHOLD and time.time() > (EMAIL_TIMER_1 + 900): SEND_EMAIL ('Network_cabinet_1', Ftemp1) EMAIL_TIMER_1 = time.time() #reset email timer print 'ending email for cab1' # for troubleshooting only if Ftemp2 > EMAIL_THRESHOLD and time.time() > (EMAIL_TIMER_2 + 900): SEND_EMAIL ('Server_cabinet_2', Ftemp2) EMAIL_TIMER_2 = time.time() print 'sending email for cab2' # for troubleshooting only if FtempB > EMAIL_THRESHOLD and time.time() > (EMAIL_TIMER_B + 900): SEND_EMAIL ('Basement', FtempB) EMAIL_TIMER_B = time.time() print 'sending email for basement' # for troubleshooting only def WRITE_TO_FILE(): print 'writing to file' # for troubleshooting only timestamp = time.strftime("%y-%m-%d %H:%M:%S") # the is the second way we # have used in this script # to get the current date/time f=open('/mnt/usb/temperature.txt', 'a') # this opens a file for writing. # the 'a' means append, which will # add anything written to the end # of the file. # The line below writes to the file we are appending f.write('%s cab1 %d cab2 %d basement %d\n' % (timestamp, Ftemp1, Ftemp2, FtempB)) f.close() # This closes the file we just appended. # Begining of Program bus = smbus.SMBus(BUS_NUM) # sets the variable bus to refer to I2C bus 1 # The line below sets the variable display to refer to our specific # 7-segment display by address and bus number. display = SevenSegment.SevenSegment(address=DISPLAY, busnum=BUS_NUM) GPIO.setwarnings(False) # turns off GPIO warnings to terminal GPIO.setmode(GPIO.BCM) # sets mode for GPIO pins to BCM GPIO.setup(FAN1_PIN, GPIO.OUT) # sets relay 1 control pin as output GPIO.setup(FAN2_PIN, GPIO.OUT) # sets relay 2 control pin as output GPIO.output(FAN1_PIN, 1) # sets relay 1 control pin to high (relay open) GPIO.output(FAN2_PIN, 1) # sets relay 2 control pin to high (relay open) display.begin() # initializes 7-segment display while True: # infinite loop which will run the functions listed below # in that order and then repeat GET_TEMPS() FAN_CHECK() EMAIL_CHECK() # For the three lines below, the hex value is how I am displaying the # label strings to the seven segment display for cab1, cab2, and basement. DISPLAY_TEMP( Ftemp1, 0xCAB1 ) DISPLAY_TEMP( Ftemp2, 0xCAB2 ) DISPLAY_TEMP( FtempB, 0xBA5E ) DISPLAY_CLOCK() WRITE_TO_FILE()
Final Result
So we put together the hardware and the software, and we end up with a functional device. Here is the result of the project. When the temperature in the cabinets rises above 90 degrees the fans will turn on. They will stay on until the temperature drops back below 88. If the temperature continues to rise above 95 degrees the device will email me with the current temperatures. All temperatures are recorded in a log file along with a time-stamp so that I can review the temperature data at any time.
Sunday, August 6, 2017
Building Cedar Planters With My Wife
I challenged my wife to come up with a project that she would like to have done that we could do together. Her answer was two large planters for the back yard. I thought this was an excellent suggestion. The project would be just long enough to be worth doing, and just short enough that she wouldn't swear off working in the shop forever.
Cut List
(8) 22" x 1-1/2" x 1-1/2" (legs)
I decided on red cedar for the wood due to it's rot resistance (we are going to be filling these with soil after all.) I chose a very basic design, and based the measurements for this project on the dimensions of the cedar boards that were readily available and reasonably priced. I'll give the measurements for this project as well as a cut list for anyone wanting to replicate it, but I don't see any reason that anyone should feel the need to use the exact same measurements. This project is easy enough to adapt to your specific environment or available materials.
Cut List
(8) 22" x 1-1/2" x 1-1/2" (legs)
(16) 16-3/4" x 3/4" x 2" (top and bottom rail for sides)
(24) 18" x 3/4" x 5-1/2" (side slats)
(6) 16-3/4" x 3/4" x 5-1/2" (bottom slats)
(8) 15-1/4" x 3/4" x 3/4" (ledge for the bottom slats to rest on)
(24) 18" x 3/4" x 5-1/2" (side slats)
(6) 16-3/4" x 3/4" x 5-1/2" (bottom slats)
(8) 15-1/4" x 3/4" x 3/4" (ledge for the bottom slats to rest on)
So here are the measurements. At this point I hadn't decided what type of border I was going to use for the top, so you won't see it in the cut list. I wanted to see what the planters looked like before I made that decision, so I just bought an extra 8ft 1x6 board and set it aside to be cut later.
This is proof that my wife actually helped to build these. She did most of the cutting on the radial arm saw, although she shied away from ripping on the table saw. One step at a time.
So here are the planters assembled sans the top border. This should go without saying, but this should be glued as well as screwed together. For outdoor projects always be sure to use an appropriately waterproof glue like Titebond III, and exterior wood screws.
Now it was time to make the border for the top. I thought that using the router to make any sort of rounded detail would look out of place on this project, so I decided to just cut a 60 degree chamfer on the table saw.
Here is the finished product. We lined the inside of the side panels with garden fabric before filling with soil to keep the soil from dripping out the spaces between the slats in the side panels when watered. We didn't put any garden fabric in the bottom, and actually drilled some holes in the bottom slats to aid in drainage. The bottom pieces are not actually glued or screwed to the rest of the planter, so they are easily replaced if they start to lose their structural integrity.
Wednesday, July 26, 2017
Filming Birds with a Raspberry Pi (Part One)
I had recently purchased a camera module for the Raspberry Pi, mainly just out of curiosity, and a desire to learn how to use it. In the back of my mind I had been working on an idea to build a birdhouse and use a Pi to film the birds, but I was currently working on several other projects, so I was in no hurry to start a new one. One evening I was working at my desk in my shop, and I heard some chirping outside the window. I pulled back the curtain to find a Carolina Wren's nest with baby chicks inside chirping away. How fortuitous. Suddenly my project to film birds with the Pi moved to the front of the list. With little knowledge of how to use the camera, I bent a piece of 12awg copper wire into a makeshift camera stand, hooked it up to the Pi, and set it in the window. A quick Google search told me that I could enable the camera module using the raspi-config menu, and then operate the camera from the command line using the following commands for pictures and video respectively:
raspistill -o birdpic.jpg
The above command takes an 8 megapixel image and outputs it to a file named birdpic.jpg.
raspivid -o birdvid.h264 -t 10000
The above command takes a video and outputs it to a file named birdvid.h264. The -t specifies the length of the video in miliseconds, so -t 10000 takes a 10 second video.
It worked. I sat there for a little while amusing myself taking video of the birds every time they started chirping, because that usually meant that it was feeding time. The video through the window was decent, and I managed to edit together a short video.
Obviously, this was far from an optimal set-up. The video through the window was OK, but it could definitely be better, and the whole thing was dependent on me sitting there listening for the chirping and hitting a button. It was time to take this to the next level. I had some IR motion sensors lying around, and some leftover 1/2" plywood from a previous project, so I whipped up a quick little box with a hinged lid and a removable front panel with holes for the camera and motion detector. I decided to store the video on a 16 GB usb thumb drive. The other options would have been to save it
to the SD card, which would only wear out the card faster, or stream it via wifi to my server. I decided against wifi in case I wanted to use this project for filming something else that was too far away from the house for wifi. The USB drive turned out to be an adequate solution. I made sure to leave plenty of room in the box, and air gaps at the top to deal with the heat produced by the Pi and the USB stick. To mount the motion detector and camera on the front of the box I simply used electrical tape, since this was more of a prototype or proof-of-concept than anything else. I was pleased with my little box, considering I made it on the fly in just a little
over an hour. The IR motion sensor I used was a HC-SR501, which runs off of 5V, but conveniently send a 3.3V signal when motion is detected, so I could hook it up directly to the Pi using only three pins without having to use a level-shifter. With that, the hardware was complete. Now all I needed was a little code to automate the whole process. Back to Google.
It turns out, as to be expected, the Raspberry Pi Camera Python module is located in the Raspbian repositories, so it can be installed with a quick:
sudo apt-get install python-picamera
I don't want to get too in-depth on how to use this module, so I will just point you to Raspberrypi.org's documentation here. The code is simple. All I am going to do is to monitor the GPIO pin that the motion sensor is connected to. When motion is detected I am going to start recording. After 10 seconds, I am going to check the motion sensor again. If there is no motion, then the clip is over. If there is still motion after 10 seconds then it will wait another 10 seconds and check again. This results in a minimum size clip of 10 seconds, with no maximum size as long as motion continues. To prevent video files from overwriting each other, each clip will contain a time-stamp in the file name. I could have gone a little more advanced with the code, but I was in a hurry to get this up and running, so I kept it simple. Here's the code if anyone would like to use it:
As always, we need to make the file executable using sudo chmod +x /home/pi/motion-vid.py. I want to have this script run on start-up, so we need to add this file to the /etc/rc.local file, but before we do that, we need to think about the USB flash drive that we are using to store the video. When a USB drive is inserted into the Raspberry Pi, it is not automatically mounted as it is in many other operating systems, so we need to make sure that the Pi also mounts the USB drive on start-up. To do this, we must first determine the name of the drive, and of the partition on the drive. Typically the first USB drive you insert in the Pi will be sda, but in the interest of being thorough we will double check just to be sure using the command ls /dev/sd*.
As you can see above, it returned two values. sda is the name of the drive itself, and sda1 is the name of the partition on the drive. We want to mount the partition. Next we need to look to see if we have a place to mount the drive. Navigate to /mnt using cd /mnt, and see if you have directories there. If not, create a directory named usb (the name is arbitrary) using the command sudo mkdir ./usb. We can now mount the usb drive using sudo mount /dev/sda1 /mnt/usb.
So now that we know how to do it, let's tell the Pi to mount the USB on boot and run the camera script. Open up the /etc/rc.local file using your text editor du jour (i.e. sudo vim /etc/rc.local)and add the following lines to the file:
# The following line mounts sda1 to /mnt/usb
sudo mount /dev/sda1 /mnt/usb
#The following lines runs the script that takes video based on a motion sensor
cd /mnt/usb
sudo /home/pi/motion-vid.py &
And that's it. I put the box on the windowsill next to the birds nest and plugged it in. The first day I got just under 400 video clips, which took up about 3.5 GB. That means with a 16 GB USB drive I would only need to swap out the drive every four days or so. At any time I could log into the Pi over wifi to make sure it was still working, and view any of the clips I chose. And of course, I had to make another bird montage:
And now for the sad conclusion of this project. After a little less than a week of watching the birds, and admittedly becoming a bit attached to the little guys, I was working in my shop when I heard a commotion at the window. I figured the birds were getting active, so I logged into the Pi and pulled down the last video clip. Horrified I watched as one of the neighbor's cats pounced onto the nest and pulled it, and the baby birds, off of the window ledge. C'est la vie. It is nature after all, and cats will be cats. I can't be happy they keep away the mice and snakes, and then fault them for eating the birds. It wasn't the smartest place for the Wrens to build a nest anyway, 2 feet off the ground. At least I got to watch them for a while, and it gave me a good starting point for this project.
So what did I learn, and what's next. Most importantly, next time I film baby birds they will be in a bird house where they will be better protected from predators. My code could also use a little bit of improvement, and next time I will probably just stream the video to a server instead of using the USB drive for storage. I am already working on a design for a bluebird house that will have an internal camera and an external camera, and will be able to conceal the electronics in a separate compartment from the birds. The biggest hurdle to overcome with that project will be powering two Pi Zeros and two cameras, but I'm sure I can come up with something. Until then, stay safe, and keep building.
raspistill -o birdpic.jpg
The above command takes an 8 megapixel image and outputs it to a file named birdpic.jpg.
raspivid -o birdvid.h264 -t 10000
The above command takes a video and outputs it to a file named birdvid.h264. The -t specifies the length of the video in miliseconds, so -t 10000 takes a 10 second video.
It worked. I sat there for a little while amusing myself taking video of the birds every time they started chirping, because that usually meant that it was feeding time. The video through the window was decent, and I managed to edit together a short video.
Obviously, this was far from an optimal set-up. The video through the window was OK, but it could definitely be better, and the whole thing was dependent on me sitting there listening for the chirping and hitting a button. It was time to take this to the next level. I had some IR motion sensors lying around, and some leftover 1/2" plywood from a previous project, so I whipped up a quick little box with a hinged lid and a removable front panel with holes for the camera and motion detector. I decided to store the video on a 16 GB usb thumb drive. The other options would have been to save it
to the SD card, which would only wear out the card faster, or stream it via wifi to my server. I decided against wifi in case I wanted to use this project for filming something else that was too far away from the house for wifi. The USB drive turned out to be an adequate solution. I made sure to leave plenty of room in the box, and air gaps at the top to deal with the heat produced by the Pi and the USB stick. To mount the motion detector and camera on the front of the box I simply used electrical tape, since this was more of a prototype or proof-of-concept than anything else. I was pleased with my little box, considering I made it on the fly in just a little
over an hour. The IR motion sensor I used was a HC-SR501, which runs off of 5V, but conveniently send a 3.3V signal when motion is detected, so I could hook it up directly to the Pi using only three pins without having to use a level-shifter. With that, the hardware was complete. Now all I needed was a little code to automate the whole process. Back to Google.
It turns out, as to be expected, the Raspberry Pi Camera Python module is located in the Raspbian repositories, so it can be installed with a quick:
sudo apt-get install python-picamera
I don't want to get too in-depth on how to use this module, so I will just point you to Raspberrypi.org's documentation here. The code is simple. All I am going to do is to monitor the GPIO pin that the motion sensor is connected to. When motion is detected I am going to start recording. After 10 seconds, I am going to check the motion sensor again. If there is no motion, then the clip is over. If there is still motion after 10 seconds then it will wait another 10 seconds and check again. This results in a minimum size clip of 10 seconds, with no maximum size as long as motion continues. To prevent video files from overwriting each other, each clip will contain a time-stamp in the file name. I could have gone a little more advanced with the code, but I was in a hurry to get this up and running, so I kept it simple. Here's the code if anyone would like to use it:
#!/usr/bin/env python
import RPi.GPIO as GPIO
import picamera
import time
MOTION_PIN = 4
camera = picamera.PiCamera()
GPIO.setmode(GPIO.BCM)
GPIO.setup(MOTION_PIN, GPIO.IN)
def take_video():
timestamp = time.strftime("%y-%m-%d-%H-%M-%S")
print 'recording started' # for troubleshooting only
camera.start_recording('vid%s.h264' % timestamp)
camera.wait_recording(10)
while GPIO.input(MOTION_PIN):
camera.wait_recording(10)
camera.stop_recording()
print 'recording stopped' # for troubleshooting only
# Begining of Program
time.sleep(2)
while True:
print 'waiting for motion' # for troubleshooting only
GPIO.wait_for_edge(MOTION_PIN, GPIO.RISING)
print 'motion detected' # for troubleshooting only
take_video()
As always, we need to make the file executable using sudo chmod +x /home/pi/motion-vid.py. I want to have this script run on start-up, so we need to add this file to the /etc/rc.local file, but before we do that, we need to think about the USB flash drive that we are using to store the video. When a USB drive is inserted into the Raspberry Pi, it is not automatically mounted as it is in many other operating systems, so we need to make sure that the Pi also mounts the USB drive on start-up. To do this, we must first determine the name of the drive, and of the partition on the drive. Typically the first USB drive you insert in the Pi will be sda, but in the interest of being thorough we will double check just to be sure using the command ls /dev/sd*.
As you can see above, it returned two values. sda is the name of the drive itself, and sda1 is the name of the partition on the drive. We want to mount the partition. Next we need to look to see if we have a place to mount the drive. Navigate to /mnt using cd /mnt, and see if you have directories there. If not, create a directory named usb (the name is arbitrary) using the command sudo mkdir ./usb. We can now mount the usb drive using sudo mount /dev/sda1 /mnt/usb.
So now that we know how to do it, let's tell the Pi to mount the USB on boot and run the camera script. Open up the /etc/rc.local file using your text editor du jour (i.e. sudo vim /etc/rc.local)and add the following lines to the file:
# The following line mounts sda1 to /mnt/usb
sudo mount /dev/sda1 /mnt/usb
#The following lines runs the script that takes video based on a motion sensor
cd /mnt/usb
sudo /home/pi/motion-vid.py &
And that's it. I put the box on the windowsill next to the birds nest and plugged it in. The first day I got just under 400 video clips, which took up about 3.5 GB. That means with a 16 GB USB drive I would only need to swap out the drive every four days or so. At any time I could log into the Pi over wifi to make sure it was still working, and view any of the clips I chose. And of course, I had to make another bird montage:
And now for the sad conclusion of this project. After a little less than a week of watching the birds, and admittedly becoming a bit attached to the little guys, I was working in my shop when I heard a commotion at the window. I figured the birds were getting active, so I logged into the Pi and pulled down the last video clip. Horrified I watched as one of the neighbor's cats pounced onto the nest and pulled it, and the baby birds, off of the window ledge. C'est la vie. It is nature after all, and cats will be cats. I can't be happy they keep away the mice and snakes, and then fault them for eating the birds. It wasn't the smartest place for the Wrens to build a nest anyway, 2 feet off the ground. At least I got to watch them for a while, and it gave me a good starting point for this project.
So what did I learn, and what's next. Most importantly, next time I film baby birds they will be in a bird house where they will be better protected from predators. My code could also use a little bit of improvement, and next time I will probably just stream the video to a server instead of using the USB drive for storage. I am already working on a design for a bluebird house that will have an internal camera and an external camera, and will be able to conceal the electronics in a separate compartment from the birds. The biggest hurdle to overcome with that project will be powering two Pi Zeros and two cameras, but I'm sure I can come up with something. Until then, stay safe, and keep building.
Sunday, July 9, 2017
Watering the Garden with Raspberry Pi
If you've ever planted a garden or grass seed in the spring, you know that watering can be a time consuming task, and going out of town for a weekend can be disastrous for your new plants if the weather is hot and no one is around to water them. More importantly, I just like to build things, so why not scratch that building itch and create an automated hose bib that can be scheduled to turn on and off using a Raspberry Pi?
For the water-on.py script I added in a 20 minute timer before automatically turning the water back off as a fail-safe. As always, once the scripts are created using a text editor, they need to be made executable by using
chmod +x water-on.py
chmod +x water-off.py
I need to make sure that the GPIO pin that controls the relay isn't floating when the Pi reboots, so I need to run the water-off.py script on boot. I am going to do this by adding the following line to the /etc/rc.local file:
sudo /home/pi/water/water-off.py
This time I am also going to make the file executable without having to specify the whole path to the file by copying it to the /usr/bin folder like so:
cp /home/pi/water/water-on.py /usr/bin/water-on
cp /home/pi/water/water-off.py /usr/bin/water-off
Notice I dropped the .py when I added the file to /usr/bin. This is not necessary, but in Linux land file extensions are not required, and it saves me a little bit of typing. So now the water can be turned on with a simple command.
water-on
But the point of this was not to be able to turn on the water by typing a command, we could do that more easily by flipping a switch. The goal is to be able to schedule the water to turn on and off automatically. For this we will use Linux's built in scheduling system called Cron. To add a task to the cron table we use the following command:
crontab -e
The first time you edit the cron table you will be prompted to pick a text editor. I prefer vim, but it's purely a matter of personal taste. When the file is opened for the first time there is nothing but lines of comments explaining how Cron works. Since the directions are built into the cron table, I won't go into too much detail here. To turn the sprinklers on for 20 minutes every morning at 6:00am you would add the following two lines to the end of the cron table:
00 6 * * * water-on &
20 6 * * * water-off
Remember that I added a fail-safe to the water-on script to have it shut off after 20 minutes, so the water-off isn't 100% necessary, but it seems like good practice. If you wanted to run the sprinklers for more than 20 minutes you could either edit the sleep line of the water-on script, or just add another line to the cron table to turn the water back on after 20 minutes.
So that's it. Now every morning my Pi will wake up at 6am sharp and take care of my watering for me. Sure you can buy an off-the-shelf product that will do the same thing, but what's the fun in that? If anyone needs any help adapting this walk-through to your own projects just let me know in the comments and I'll do my best to help.
My original approach to this project involved a watering system located near the garden, but I ran into several complications during the design phase. The first issue arose trying to power the Pi as well as the electronic solenoid valve that would control the water flow. Running wires across the back yard was not an ideal solution, and battery power was going to require frequent recharging. I was also going to have to find a way to protect the electronics from the sun's UV rays as well as moisture, while at the same time allowing enough air-flow to let heat escape. It occurred to me that locating all of the electronics inside the house and using them to control a hose bib on the side of the house solved both of these issues, and also added the flexibility to allow me to change the location of my automatic sprinklers or drip-watering-systems whenever necessary.
With this basic concept in mind, I broke the project into three segments: electronic hardware, plumbing hardware, and control software. For the electronic hardware, I would need to be able to switch on and off a 12V fused power source that would supply an electric solenoid valve that would control the water. This could easily be done by controlling a relay with a GPIO pin on the Pi. For the plumbing hardware, I would need to tap into my water line and run it through the electric solenoid valve, and from there outside to a hose bib. I would also need to be sure to include a check valve to prevent the back-flow of water from the outside, and there would need to be a way to drain any outdoor pipes to prevent freezing in the winter. The control software would be the easiest. All I needed was a short start-up script to set-up the GPIO pin, and then the timing of opening and closing the valve could be done with cron jobs. With a plan in place it was time to get building.
Electronic Hardware
For this project I purchased a 12V / 3amp normally-closed brass solenoid valve to control the water flow. You can buy a plastic one cheaper, but I had reservations about connecting a plastic water valve to my plumbing. Basically all I needed to do was to connect a GPIO pin from the Pi to a relay that could then switch on/off the 12V power supply to the valve. I went with a 4-channel relay module since I already had plans for future projects that would require additional relays. I had a 12V / 30 amp power supply from previous projects on hand, which will provide more than enough current for this project, as well as anything I might need it for in the future. The only thing left to plan was a fuse for the power to the valve. For most people a simple in-line fuse holder would solve this problem, but I just so happened to have an old DC fuse panel lying around looking for a job to do, so I put it to use. Here is the schematic for the final design:
In the above schematic I also drew in the relay board. Obviously if you use the same relay board that I did, you don't need to build any of this, but I wanted to include it so that anyone using a different relay could adapt the circuit to their own needs.
Regarding the switches, I added the first switch to cut the 12V power so I don't have to pull the fuse every time I want to make adjustments to the set-up. This particular switch has an LED that is powered via the same 12V that it switches, so I had to add a resistor to the ground leg on the switch to limit the current for the LED without limiting the current to the valve. This was done by simply cutting the wire and adding the resistor in-line. I also added a second switch to bypass the relay and open the valve without involving the Pi. This is a standing "request" from my wife that anything I automate must still have a manual switch for operation.
One last, but very important note, when I finally had the whole set-up finished, I tested it out. As soon as I sent power to the solenoid, my Pi emailed me that my smoke detector was alerting and gave me an audible alert that it was preparing to shut down. What!?!? These are all separate systems that should have nothing to do with each other except that the same Pi controls them all! Well, since I had run my 12V lines along the same path as the jumpers for all of my GPIO pins, as soon as I switched the solenoid on, the electrical noise on the 12V lines interfered with the signals on the other GPIOs, causing a moment of havoc for the Pi. This could have been solved by rerouting my wires, or by trying to smooth things over with capacitors, but I happened to have some ferrite core noise suppression filters lying around, so I tried one out. After testing several times, I can verify that this does in fact solve the problem. With the noise filter there are no issues, without it the Pi goes a little nuts. So keep this in mind when determining your cable management, or invest in some filters to solve the problem after the fact.
In the above schematic I also drew in the relay board. Obviously if you use the same relay board that I did, you don't need to build any of this, but I wanted to include it so that anyone using a different relay could adapt the circuit to their own needs.
One last, but very important note, when I finally had the whole set-up finished, I tested it out. As soon as I sent power to the solenoid, my Pi emailed me that my smoke detector was alerting and gave me an audible alert that it was preparing to shut down. What!?!? These are all separate systems that should have nothing to do with each other except that the same Pi controls them all! Well, since I had run my 12V lines along the same path as the jumpers for all of my GPIO pins, as soon as I switched the solenoid on, the electrical noise on the 12V lines interfered with the signals on the other GPIOs, causing a moment of havoc for the Pi. This could have been solved by rerouting my wires, or by trying to smooth things over with capacitors, but I happened to have some ferrite core noise suppression filters lying around, so I tried one out. After testing several times, I can verify that this does in fact solve the problem. With the noise filter there are no issues, without it the Pi goes a little nuts. So keep this in mind when determining your cable management, or invest in some filters to solve the problem after the fact.
Plumbing Hardware
I hate plumbing, but it's a handy thing to know how to do. I know just enough about it to make me dangerous, but that probably applies to a lot of things. I won't go into too much detail here, because everyone's plumbing situation is going to be different, so there is no one-size-fits-all solution. I am going to try to focus on the few components that will be universally required, and then just skip to the final result. If you are not completely comfortable with altering the plumbing in your house, then you may want to look into another way to do this project. Plumbers are expensive, so mistakes could be costly, not to mention messy. You've been warned, so I accept no responsibility if you choose to proceed and accidentally flood your house.
Above is the basic set up for the plumbing piece. Starting on the left, the first piece we need is a cut-off valve. If my improvised plumbing leaks or brakes, or my Pi goes all Superman 3 on me, I need to have a way to cut off the water from it's source. Next comes the solenoid valve that will be controlled by the Pi. After that we need to use a check valve to prevent the water from the hose from draining back into my water pipes (this is probably required by code in most places). Finally, I used a T-junction and another cut-off valve to allow for draining the outdoor pipes. This valve will stay closed during normal operation, and will only be opened at the end of the season in order to drain the outdoor pipes so they don't freeze. The outlet for this valve will lead to a sink basin in my shop so that I don't have to mess with sewer lines and traps.
So that is the basic design. To the right is the fully installed product. To tap into the waterline I opted to slice into one of my newer Pex waterlines, which is much easier than trying to mess with 70 year old galvanized water pipe. For the line that continues outside I opted to use galvanized steel water pipe because I wanted something more rigid to support the solenoid valve, and because you can't run Pex outside. If you do something similar and use steel pipe outside, be sure to paint it to keep it from rusting.
Above is the basic set up for the plumbing piece. Starting on the left, the first piece we need is a cut-off valve. If my improvised plumbing leaks or brakes, or my Pi goes all Superman 3 on me, I need to have a way to cut off the water from it's source. Next comes the solenoid valve that will be controlled by the Pi. After that we need to use a check valve to prevent the water from the hose from draining back into my water pipes (this is probably required by code in most places). Finally, I used a T-junction and another cut-off valve to allow for draining the outdoor pipes. This valve will stay closed during normal operation, and will only be opened at the end of the season in order to drain the outdoor pipes so they don't freeze. The outlet for this valve will lead to a sink basin in my shop so that I don't have to mess with sewer lines and traps.
So that is the basic design. To the right is the fully installed product. To tap into the waterline I opted to slice into one of my newer Pex waterlines, which is much easier than trying to mess with 70 year old galvanized water pipe. For the line that continues outside I opted to use galvanized steel water pipe because I wanted something more rigid to support the solenoid valve, and because you can't run Pex outside. If you do something similar and use steel pipe outside, be sure to paint it to keep it from rusting.
Control Software
With everything in place, all I needed to do was to write a couple of short scripts to control the solenoid valve. This is the easy part. Here are the two python scripts called water-on.py and water-off.py:
#!/usr/bin/env python
# water-on.py
import RPi.GPIO as GPIO
from time import sleep
RELAY_PIN = 26
GPIO.setmode(GPIO.BCM)
GPIO.setup(RELAY_PIN, GPIO.OUT)
GPIO.output(RELAY_PIN, 0)
sleep(1200)
GPIO.output(RELAY_PIN, 1)
#!/usr/bin/env python
# water-off.py
import RPi.GPIO as GPIO
RELAY_PIN = 26
GPIO.setmode(GPIO.BCM)
GPIO.setup(RELAY_PIN, GPIO.OUT)
GPIO.output(RELAY_PIN, 1)
For the water-on.py script I added in a 20 minute timer before automatically turning the water back off as a fail-safe. As always, once the scripts are created using a text editor, they need to be made executable by using
chmod +x water-on.py
chmod +x water-off.py
I need to make sure that the GPIO pin that controls the relay isn't floating when the Pi reboots, so I need to run the water-off.py script on boot. I am going to do this by adding the following line to the /etc/rc.local file:
sudo /home/pi/water/water-off.py
This time I am also going to make the file executable without having to specify the whole path to the file by copying it to the /usr/bin folder like so:
cp /home/pi/water/water-on.py /usr/bin/water-on
cp /home/pi/water/water-off.py /usr/bin/water-off
Notice I dropped the .py when I added the file to /usr/bin. This is not necessary, but in Linux land file extensions are not required, and it saves me a little bit of typing. So now the water can be turned on with a simple command.
water-on
But the point of this was not to be able to turn on the water by typing a command, we could do that more easily by flipping a switch. The goal is to be able to schedule the water to turn on and off automatically. For this we will use Linux's built in scheduling system called Cron. To add a task to the cron table we use the following command:
crontab -e
The first time you edit the cron table you will be prompted to pick a text editor. I prefer vim, but it's purely a matter of personal taste. When the file is opened for the first time there is nothing but lines of comments explaining how Cron works. Since the directions are built into the cron table, I won't go into too much detail here. To turn the sprinklers on for 20 minutes every morning at 6:00am you would add the following two lines to the end of the cron table:
00 6 * * * water-on &
20 6 * * * water-off
Remember that I added a fail-safe to the water-on script to have it shut off after 20 minutes, so the water-off isn't 100% necessary, but it seems like good practice. If you wanted to run the sprinklers for more than 20 minutes you could either edit the sleep line of the water-on script, or just add another line to the cron table to turn the water back on after 20 minutes.
So that's it. Now every morning my Pi will wake up at 6am sharp and take care of my watering for me. Sure you can buy an off-the-shelf product that will do the same thing, but what's the fun in that? If anyone needs any help adapting this walk-through to your own projects just let me know in the comments and I'll do my best to help.
Subscribe to:
Posts (Atom)