Sunday, June 30, 2013

Raspberry Pi Garage Door Status Indicator with Ultrasonic GPIO without ‘sudo’

As anybody who has a garage knows, sometimes the door get left open inadvertently. Since the door is usually not visible from inside the house, that is easily done. Most of the time, there is no harm, but, if left overnight, raccoons and other undesirables may enter and create havoc.

Raspberry Pi to the rescue. In short, what I did was mount an ultrasonic sensor (HC-SR04, from eBay, $2.00, free shipping from China) on the ceiling in one of the bays of the garage (our garage is a double with a single 5 m door). When operating, if the sensor finds that is measure less than 20 cm, then the garage door is open. If the distance is between 50 and 150 cm, then the garage door must be closed with a car parked in the spot. If the distance is greater than 150 cm, the door is closed with the car gone. (Total distance from ceiling to floor is 244 cm)

sensorandgarageceiling

My primary Pi does temperature logging every minute (see earlier posts), so I decided to make the the garage door check a part of this process.

Since I used the wiringpi library as part of the temperature logging process, I do not need to invoke ‘sudo’ before starting python. However, the sample ultrasonic sensor script did use sudo, so I was forced to find a way around. In the end, I used Richard Ryniker’s gpio_control C program to export the required GPIO pins. I found it gives great results. Now I can run my Python program without using sudo.

Sometime the ultrasonic sensor gives erroneous results. Therefore, I take 20 readings (only takes less than 200 millisecond the accomplish), then drop the highest 3 and the lowest 3 readings and average the rest. This gives very accurate results. Then I write the output, i.e. the distance and the time to a text file so I can use it when a web request comes in.

Here’s the final code:

   1: def write_once(path, value):
   2:     f = open(path, 'w')
   3:     f.write(value)
   4:     f.close()
   5:     return
   6:  
   7: def checkGarageDoorStatus():
   8:     trigger = '/sys/class/gpio/gpio25/'   #TRIGGER
   9:     write_once(trigger + 'direction', 'out\n')
  10:     ftrigger = open(trigger + 'value', 'w')
  11:     
  12:     #pin_base24a = '/sys/class/gpio/gpio24/'   #RESET ECHO TO LOW FIRST
  13:     #write_once(pin_base24a + 'direction', 'out\n')
  14:     #f24a = open(pin_base24a + 'value', 'w')
  15:     #f24a.write('1')
  16:     #f24a.flush()
  17:     #f24a.close()
  18:     
  19:     echo = '/sys/class/gpio/gpio8/'  #ECHO
  20:     write_once(echo + 'direction', 'in')
  21:     write_once(echo + 'edge', 'both') #'both','rising', 'falling' are the keywords
  22:     #Note: On a longer (40'), thinner cable (24 AWG), I had to change the parameter in the above line to 'falling'.
  23:     #Also needed to subtract a constant of 6 cm from the distance total to arrive at the correct result.
  24:     #print "Ultrasonic Measurement"
  25:     #time.sleep(0.5)
  26:     z = 0
  27:     while z < 20:
  28:         fecho = open(echo + 'value', 'r')
  29:             
  30:         zero_last = fecho.read(1)
  31:         #sys.stdout.write('Initial pin value = {}\n'.format(repr(state_last)))
  32:     
  33:         po = select.poll()
  34:         po.register(fecho, select.POLLPRI)
  35:     
  36:     
  37:         z = z + 1
  38:         ftrigger.write('0')
  39:         ftrigger.flush()
  40:     
  41:         # Allow module to settle
  42:         #time.sleep(0.5)
  43:     
  44:         # Send 10us pulse to trigger
  45:     
  46:         ftrigger.write('1')
  47:         ftrigger.flush()
  48:     
  49:         time.sleep(0.00001)
  50:     
  51:         ftrigger.write('0')
  52:         ftrigger.flush()
  53:     
  54:         events = po.poll(1000)
  55:         t1 = time.time()
  56:         fecho.seek(0)
  57:         first_last = fecho.read(1)
  58:     
  59:     
  60:         events = po.poll(1000)
  61:         t2 = time.time()
  62:         fecho.seek(0)
  63:         second_last = fecho.read(1)
  64:     
  65:         if len(events) == 0:
  66:             #sys.stdout.write('  timeout  delta = {:8.4f} seconds\n'.format(t2 - t1))
  67:             nDistance = 0
  68:         else:
  69:             #sys.stdout.write('value = {}  delta ={:8.4f}\n'.format(state_last, t2 - t1))
  70:             TimeElapsed = t2 - t1
  71:             nDistance = TimeElapsed * 34300/2
  72:             #print zero_last
  73:             #print first_last
  74:             #print second_last
  75:             
  76:         #print ("Distance: ={:8.1f}".format(nDistance)) + " cm"
  77:         fecho.close
  78:         if z == 1:
  79:             lResult = [nDistance]
  80:         else:
  81:             lResult.append(nDistance)
  82:             
  83:     lResult.sort()
  84:     #print lResult
  85:     del lResult[19]
  86:     del lResult[18]
  87:     del lResult[17]
  88:     del lResult[2]
  89:     del lResult[1]
  90:     del lResult[0]
  91:     #print '\n'
  92:     #print lResult
  93:     
  94:     nTotalDistance = 0
  95:     for l in lResult:
  96:         nTotalDistance = nTotalDistance + l
  97:     
  98:     nAverageDistance = nTotalDistance/14
  99:     #print nAverageDistance
 100:     dDateTimeRecorded = datetime.datetime.now()
 101:     if (nAverageDistance > 5 and nAverageDistance  < 20):
 102:         cStatus = "OPEN"
 103:     else:
 104:         if (nAverageDistance > 30 and nAverageDistance  < 150):
 105:             cStatus = "CLOSEDWITHCAR"
 106:         else:
 107:             cStatus = "CLOSEDWITHOUTCAR"
 108:     with open('GarageDoorStatus','w') as file:
 109:         file.write("{}\n".format(cStatus))
 110:         file.write("{}\n".format(dDateTimeRecorded))
 111:         file.close()
 112:     return cStatus    

This is how it is called:



   1: def main():
   2:     os.system("gpio export 11 out")
   3:     os.system("gpio export 18 out")
   4:     os.system("gpio_control 25 export")
   5:     os.system("gpio_control 8 export")
   6:     wiringpi.wiringPiSetupSys()
   7:     wiringpi.GPIO(wiringpi.GPIO.WPI_MODE_SYS)
   8:     wiringpi.pinMode(11,wiringpi.OUTPUT)
   9:     wiringpi.pinMode(18,wiringpi.OUTPUT)
  10:     #wiringpi.pinMode(24,wiringpi.OUTPUT)
  11:     loadDrivers()
  12:     nSlaveCount = findNumberOfSensors()
  13:     lSlave = findSlaves(nSlaveCount)
  14:     cAmbientTemperature = "N/A"
  15:     while True:
  16:         wiringpi.digitalWrite(18,1)
  17:         checkTemps(nSlaveCount,lSlave)
  18:         wiringpi.digitalWrite(18,0)
  19:         if (checkGarageDoorStatus() == "OPEN"):
  20:             print "Open"
  21:             wiringpi.digitalWrite(11,1)
  22:         else:
  23:             print "Closed"
  24:             wiringpi.digitalWrite(11,0)
  25:         nCurrentSecond = datetime.datetime.now().second
  26:         time.sleep(60-nCurrentSecond)

Note that when the status is found to be open, GPIO Pin 11 is turned high. This I use to turn on an LED which I mounted next to the furnace thermostat in the living room. Now, in order to make this more attention grabbing, I wanted it to be a flashing LED. Of course the Pi could do this, but in this case that seemed like overkill. So I bought an LED that flashes all on its own, at about 2 Hz. (KLF-336HD-3P).


thermostatwithled


I also made it part of the web page that my Pi can produce (see earlier posts).


garagedoorstatusonwebpage


In case you are wondering, the first 7 lines show temperatures inside, outside and in my generation housing (see earlier posts).


Although it seems a rather trivial project, it was nonetheless very time consuming. Pulling wires from the utility room where the Pi  is located to the garage, as well as to the living room next to the thermostat (doing it decently, that is) takes far more time than you wish it would. Then, finding a workaround for sudoless ultrasonic sensing, fitting the new into the existing Pi setup, testing a flashing LED on the Pi (ya never know what it might do) all took its sweet time. But then, hey, it is all worth it when in the end it works and it is useful.

6 comments:

Michael Elizarof said...

Thanks for great information, it's much helpful for the new professional.
Philadelphia Garage Doors | Garage Doors Services

Rachel Burr said...

Nice to read this article will be very helpful in the future, share more info with us. Good job Garage Door Repair Fontana

Steve Le said...

Thanks for sharing this information! I really enjoy to read all the content is posted on your blog.
American Best Garage Doors | Garage Door Repair

Michael No said...

Good and interesting post for me. Thanks for sharing such useful information.
Philadelphia Garage Door | Garage Door Services

Elizabeth J. Neal said...

If the distance is between 50 and 150 cm, then the garage door must be closed with a car parked in the spot. If the distance is greater than 150 cm, the door is closed with the car gone. (Total distance from ceiling to floor is 244 cm) garage door repair

Jack Men said...

An interesting dialogue is price comment. I feel that it is best to write more on this matter, it may not be a taboo topic however usually individuals are not enough to talk on such topics. To the next. Cheers. Garage door repair