Jezra.net

Annoyatron - Part II: The Code

After explaining the hardware build in Part I, it is finally time to get crackin on a description of the software that runs the Annoyatron. The Linkit Smart 7688 computer that is the brains of the Annoyatron is running OpenWRT Linux and this means that I had my choice of programming languages with which to hack together some code. I opted for Python.

To control the relays via GPIO, it was first necessary to create a class to represent the GPIO pins. The Smart 7688 ships with the MRAA library that provides easy asccess to GPIO functionality. However, for the basic needs of this project, a 3rd party library wasn't needed.

The Class to Control the IO for the Relays

Nothing too fancy here, it is mostly just a bunch of writing to a file.
class relay_pin:
  value_path = ""
  #init the pin with a GPIO number
  def __init__(self,num):
    #convert the number to a string
    num = str(num)
    
    #unexport the pin by writing the pin number to /sys/class/gpio/export
    f = open("/sys/class/gpio/unexport",'w')
    f.write(num)
    f.close()

    #export the pin by writing the pin number to /sys/class/gpio/export
    f = open("/sys/class/gpio/export",'w')
    f.write(num)
    f.close()

    pin_path = "/sys/class/gpio/gpio"+num
    self.value_path = pin_path+"/value"

    #set the direction
    f = open(pin_path+"/direction", "w")
    f.write("out")
    f.close()
    #default to off
    self.off()      

  def set_high(self):
    f = open(self.value_path, "w")
    f.write("1")
    f.close()

  def set_low(self):
    f = open(self.value_path, "w")
    f.write("0")
    f.close()

  #setting 'low' turns on
  def on(self):
    self.set_low()

  def off(self):
    self.set_high()

  #turn off if the class is destroyed
  def __del__(self):
    self.off()

The Main loop of the Code

In order to know when to turn GPIO pins on or off, the main controller for the Annoyatron runs a loop which will:

  1. look for a file containing commands
  2. parse commands from the file
  3. run the commands
  4. delete the file
The commands are in PIN_NUMBER BINARY_VALUE format, and there is one command per line.
#!/usr/bin/env python
import sys, signal, os, time
from relay_pin import relay_pin

#where is this script?
script_dir = os.path.dirname( os.path.realpath(__file__) )
#what is the path to the command file?
cmd_file = os.path.join(script_dir, 'cmd_file')

#function to run when CTRL+c is pressed
def ctrl_c_quit(signal, frame):
  print "so long, buddy!"
  sys.exit(0)

#connect ctrl+c to the quit function
signal.signal(signal.SIGINT, ctrl_c_quit)

#create the relay pins
relay_1 = relay_pin(0)
relay_2 = relay_pin(3)

loop = True
print "starting main loop... \n"
while loop:
 #does the file exist
  if os.path.isfile(cmd_file):
    #open the file for reading
    f = open(cmd_file, 'r')
    #loop through the lines
    for L in f:
      l = L.strip()
      #### get the data
      # data is in `PIN_NUM BINARY_VALUE`
      
      #default to no value for the Pin and Binary val
      p = b = None
      try:
        (p,b) = l.split(" ")
      except:
        # if the line can't be split, the data is bunk
        pass
      #are p and b not None?
      if p !=None and p!=None:
        if p == '1':
          if b=='1':
            print "1 on"
            relay_1.on()
          elif b=='0':
            print "1 off"
            relay_1.off()

        elif p =='2':
          if b=='1':
            print "2 on"
            relay_2.on()
          elif b=='0':
            print "2 off"
            relay_2.off()
              
    #close the file 
    f.close()
    #delete the file
    os.remove(cmd_file)
  
  time.sleep(0.1)

A Web Server/API for Writing Commands to the Command File

What good is the ability to control GPIO from commands in a file if there is no way for a user to create the file? Since Python is already running the main code for the Annoyatron, I decided to use Python again for creating a web API to write the commands file.

Using the [Bottle](http://bottlepy.org/docs/dev/) micro-framework, a rather small bit of code quickly gave me a working API as well as the ability to serve static content.

#!/usr/bin/env python

from bottle import Bottle, run, static_file, redirect

app = Bottle()

def write_cmd_file(string):
  f = open('cmd_file', "w")
  f.write(string)
  f.close()  

@app.route('/')
def root():
  return static_file('index.html', root='public')

@app.route('//')
def action(relay, value):
  #convert value to an int and then a string
  v = str(int(value))
  if relay == "both":
    cmd = "1 %s\n2 %s" % (v,v)
  else:
    r = str(int(relay))
    cmd = "%s %s" % (r,v)
  write_cmd_file(cmd)
  redirect("/")
  
run(app, host='0.0.0.0', port=8080)

A Web UI for the Web Server

Now to make some static HTML and CSS content to load into a web browser.
<html>
        <head>
          <meta content='width=device-width, initial-scale=1' name='viewport'>
          <title>Annoyatron</title>
          <style type='text/css'>
            body {
              color: white;
            }
          
            .btn {
              border-radius: 5px;
              border: 2px solid green;
              padding: 3px 10px;
              color: white;
              text-decoration: none;
              
            }
            h2 {
              margin-bottom: 5px;
              padding: 0px;
            }
            .group {
              margin-bottom: 10px
            }
          </style>
        </head>
        <body bgcolor='black'>
          <div class='group'>
            <h2>Both</h2>
            <a class='btn' href='both/1'>On</a>
            <a class='btn' href= 'both/0'>Off</a>
          </div>
          
          <div class='group'>
            <h2>Lights</h2>
            <a class='btn' href='1/1'>On</a>
            <a class='btn' href= '1/0'>Off</a>
          </div>
          
          <div class='group'>
            <h2>Beep</h2>
            <a class='btn' href='2/1'>On</a>
            <a class='btn' href= '2/0'>Off</a>
          </div>
          
        </body>
      </html>

The Web Interface in Firefox on Android

Simple, and it gets the job done.
Cheers,
Jezra :)