├── README.txt ├── distance-screen.py ├── gpioservice.cfg ├── gpioservice.py ├── mcp3008.py ├── raspi-adc-pot.py └── screen.py /README.txt: -------------------------------------------------------------------------------- 1 | For all information go to: 2 | 3 | http://jeremyblythe.blogspot.com -------------------------------------------------------------------------------- /distance-screen.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Created on 21 Jul 2012 3 | 4 | @author: Jeremy Blythe 5 | 6 | screen - Manages the Textstar 16x2 4 button display 7 | 8 | Read the blog entry at http://jeremyblythe.blogspot.com for more information 9 | ''' 10 | import serial 11 | import datetime 12 | import time 13 | import subprocess 14 | import twitter 15 | import urllib2 16 | import json 17 | import mcp3008 18 | 19 | CLEAR = chr(12) 20 | ESC = chr(254) 21 | BLOCK = chr(154) 22 | 23 | POLL_TICKS = 15 24 | REFRESH_TICKS = 300 25 | 26 | class Display(): 27 | """ Manages the 16x2 4 button display: 28 | on_tick called every 0.1 seconds as part of the main loop after the button read 29 | on_poll called every 1.5 seconds 30 | on_page called when a new page has been selected 31 | on_refresh called every 30 seconds""" 32 | def __init__(self,on_page=None,on_poll=None,on_tick=None,on_refresh=None): 33 | # Start the serial port 34 | self.ser = serial.Serial('/dev/ttyAMA0',115200,timeout=0.1) 35 | # Callbacks 36 | self.on_page = on_page 37 | self.on_poll = on_poll 38 | self.on_tick = on_tick 39 | self.on_refresh = on_refresh 40 | 41 | self.page = 'a' 42 | self.poll = POLL_TICKS 43 | self.refresh = REFRESH_TICKS 44 | 45 | def position_cursor(self,line,column): 46 | self.ser.write(ESC+'P'+chr(line)+chr(column)) 47 | 48 | def scroll_down(self): 49 | self.ser.write(ESC+'O'+chr(0)) 50 | 51 | def window_home(self): 52 | self.ser.write(ESC+'G'+chr(1)) 53 | 54 | def capped_bar(self, length, percent): 55 | self.ser.write(ESC+'b'+chr(length)+chr(percent)) 56 | 57 | def clear(self): 58 | self.ser.write(CLEAR) 59 | 60 | def run(self): 61 | #show initial page 62 | display.ser.write(' Starting.... ') 63 | if self.on_page != None: 64 | self.on_page() 65 | #main loop 66 | while True: 67 | key = str(self.ser.read(1)) 68 | if key != '' and key in 'abcd': 69 | self.page = key 70 | self.refresh = REFRESH_TICKS 71 | self.poll = POLL_TICKS 72 | if self.on_page != None: 73 | self.on_page() 74 | else: 75 | self.refresh-=1 76 | if self.refresh == 0: 77 | self.refresh = REFRESH_TICKS 78 | if self.on_refresh != None: 79 | self.on_refresh() 80 | 81 | self.poll-=1 82 | if self.poll == 0: 83 | self.poll = POLL_TICKS 84 | if self.on_poll != None: 85 | self.on_poll() 86 | 87 | if self.on_tick != None: 88 | self.on_tick() 89 | 90 | 91 | display = None 92 | # Start twitter 93 | twitter_api = twitter.Api() 94 | 95 | def write_datetime(): 96 | display.position_cursor(1, 1) 97 | dt=str(datetime.datetime.now()) 98 | display.ser.write(' '+dt[:10]+' '+' '+dt[11:19]+' ') 99 | 100 | def get_addr(interface): 101 | try: 102 | s = subprocess.check_output(["ip","addr","show",interface]) 103 | return s.split('\n')[2].strip().split(' ')[1].split('/')[0] 104 | except: 105 | return '?.?.?.?' 106 | 107 | def write_ip_addresses(): 108 | display.position_cursor(1, 1) 109 | display.ser.write('e'+get_addr('eth0').rjust(15)+'w'+get_addr('wlan0').rjust(15)) 110 | 111 | def write_twitter(): 112 | display.position_cursor(1, 1) 113 | try: 114 | statuses = twitter_api.GetUserTimeline('Raspberry_Pi') 115 | twitter_out = BLOCK 116 | for s in statuses: 117 | twitter_out+=s.text.encode('ascii','ignore')+BLOCK 118 | display.ser.write(twitter_out[:256]) 119 | except: 120 | display.ser.write('twitter failed'.ljust(256)) 121 | 122 | def write_recent_numbers(): 123 | display.position_cursor(1, 1) 124 | try: 125 | result = urllib2.urlopen("http://jerbly.uk.to/get_recent_visitors").read() 126 | j = json.loads(result) 127 | if len(j) > 0: 128 | entry = str(j[0]['numbers'][-1:])+' '+j[0]['countryName'] 129 | display.ser.write(entry.ljust(32)) 130 | else: 131 | display.ser.write('No entries found'.ljust(32)) 132 | except: 133 | display.ser.write('jerbly.uk.to failed'.ljust(32)) 134 | 135 | def write_pots(): 136 | display.position_cursor(1, 1) 137 | val = mcp3008.readadc(0) 138 | percent = int(val/10.23) 139 | display.ser.write(str(val).ljust(16)) 140 | display.capped_bar(16, percent) 141 | 142 | def write_distance(): 143 | display.position_cursor(1, 1) 144 | r = [] 145 | for i in range (0,10): 146 | r.append(mcp3008.readadc(1)) 147 | a = sum(r)/10.0 148 | v = (a/1023.0)*3.3 149 | d = 16.2537 * v**4 - 129.893 * v**3 + 382.268 * v**2 - 512.611 * v + 306.439 150 | cm = int(round(d)) 151 | val = '%d cm' % cm 152 | percent = int(cm/1.5) 153 | display.ser.write(str(val).ljust(16)) 154 | display.capped_bar(16, percent) 155 | 156 | # Callbacks 157 | def on_page(): 158 | display.clear() 159 | display.window_home() 160 | if display.page == 'a': 161 | write_datetime() 162 | elif display.page == 'b': 163 | write_distance() 164 | elif display.page == 'c': 165 | write_twitter() 166 | elif display.page == 'd': 167 | write_ip_addresses() 168 | 169 | def on_poll(): 170 | if display.page == 'c': 171 | display.scroll_down() 172 | 173 | def on_tick(): 174 | if display.page == 'a': 175 | write_datetime() 176 | elif display.page == 'b': 177 | write_distance() 178 | 179 | def on_refresh(): 180 | if display.page == 'c': 181 | write_twitter() 182 | elif display.page == 'd': 183 | write_ip_addresses() 184 | 185 | display = Display(on_page, on_poll, on_tick, on_refresh) 186 | display.run() 187 | 188 | -------------------------------------------------------------------------------- /gpioservice.cfg: -------------------------------------------------------------------------------- 1 | [gpos] 2 | fifo = /tmp/gpopipe 3 | 4 | [gpo_pins] 5 | # name = pin, initial_mode {high/low/flash} 6 | red = 11, high 7 | yellow = 12, high 8 | green = 13, high 9 | 10 | [gpi_pins] 11 | # name = pin, command 12 | green_flash = 7, echo "green flash" >> /tmp/gpopipe 13 | motion_snapshot = 22, curl http://localhost:8080/0/action/snapshot 14 | 15 | [options] 16 | debug = False 17 | -------------------------------------------------------------------------------- /gpioservice.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python2 2 | ''' 3 | Created on 18 Jun 2012 4 | 5 | @author: Jeremy Blythe 6 | 7 | GPIO service - provides simple management of Raspberry Pi GPIOs 8 | 9 | Read the blog entry at http://jeremyblythe.blogspot.com for more information 10 | ''' 11 | import threading 12 | import time 13 | import RPi.GPIO as GPIO 14 | import ConfigParser 15 | import sys 16 | import os 17 | import signal 18 | from subprocess import call 19 | 20 | HIGH = 'high' 21 | LOW = 'low' 22 | FLASH = 'flash' 23 | LOWSHOT = 'lowshot' 24 | HIGHSHOT = 'highshot' 25 | 26 | modes = [LOW,HIGH,FLASH,LOWSHOT,HIGHSHOT] 27 | flash_state = HIGH 28 | 29 | gpo_lock = threading.Lock() 30 | gpos = {} #Used inside gpo_lock 31 | gpo_keep_running = True #Used inside gpo_lock 32 | 33 | gpi_lock = threading.Lock() 34 | gpis = {} #Used inside gpi_lock 35 | gpi_keep_running = True #Used inside gpi_lock 36 | 37 | fifo = None 38 | debug = False 39 | 40 | def print_debug(msg): 41 | if debug == True: 42 | print msg 43 | 44 | def update_flash_state(): 45 | global flash_state 46 | if (flash_state == LOW): 47 | flash_state = HIGH 48 | else: 49 | flash_state = LOW 50 | 51 | 52 | class Gpo(): 53 | """Represents a GPO. Action is called repeatedly to update the state if required (mostly for flashing).""" 54 | def __init__(self,pin,mode): 55 | self.pin = pin 56 | self.mode = mode 57 | self.state = None 58 | 59 | GPIO.setup(self.pin, GPIO.OUT) 60 | 61 | def __repr__(self): 62 | return "Pin=%s, Mode=%s, State=%s" % (self.pin, self.mode, self.state) 63 | 64 | def action(self): 65 | if (self.mode != self.state): 66 | if (self.mode == FLASH): 67 | self.switch(flash_state) 68 | elif (self.mode == LOWSHOT): 69 | self.switch(LOW) 70 | self.mode = HIGH 71 | elif (self.mode == HIGHSHOT): 72 | self.switch(HIGH) 73 | self.mode = LOW 74 | else: 75 | self.switch(self.mode) 76 | 77 | def switch(self, new_state): 78 | print_debug("Switching GPO %s %s" % (self.pin, new_state)) 79 | GPIO.output(self.pin, new_state == HIGH) 80 | self.state = new_state 81 | 82 | 83 | class Gpi(): 84 | """Represents a GPI. Action is called repeatedly to poll the state and run the command if required""" 85 | def __init__(self,pin,command): 86 | self.pin = pin 87 | self.command = command 88 | self.state = None 89 | 90 | GPIO.setup(self.pin, GPIO.IN) 91 | 92 | def __repr__(self): 93 | return "Pin=%s, Command=%s, State=%s" % (self.pin, self.command, self.state) 94 | 95 | def action(self): 96 | new_state = GPIO.input(self.pin) 97 | if (new_state != self.state): 98 | self.state = new_state 99 | if self.state == True: 100 | call(self.command, shell=True) 101 | 102 | 103 | class GpoThread(threading.Thread): 104 | """Thread to continuously update the state of the GPOs (if required). Most useful for flashing.""" 105 | def run(self): 106 | while True: 107 | update_flash_state() 108 | with gpo_lock: 109 | if gpo_keep_running == False: 110 | break 111 | print_debug(gpos) 112 | for gpo in gpos.values(): 113 | gpo.action() 114 | time.sleep(0.25) 115 | print_debug('GPO thread stopped') 116 | 117 | 118 | class GpiThread(threading.Thread): 119 | """Thread to continuously read the state of each gpi in turn""" 120 | def run(self): 121 | while True: 122 | with gpi_lock: 123 | if gpi_keep_running == False: 124 | break 125 | for gpi in gpis.values(): 126 | gpi.action() 127 | time.sleep(0.1) 128 | print_debug('GPI thread stopped') 129 | 130 | 131 | def setup(config_file_path): 132 | """Setup from the config file""" 133 | # Load config 134 | config = ConfigParser.ConfigParser() 135 | config.read(config_file_path) 136 | global debug 137 | debug = config.getboolean('options', 'debug') 138 | global fifo 139 | fifo = config.get('gpos', 'fifo') 140 | for name,cfg in config.items('gpo_pins'): 141 | tokens = cfg.split(',',1) 142 | pin = tokens[0].strip() 143 | initial_mode = tokens[1].strip().lower() 144 | if not initial_mode in modes: 145 | exit('%s is not a valid mode' % initial_mode) 146 | gpos[name] = Gpo(int(pin),initial_mode) 147 | for name,cfg in config.items('gpi_pins'): 148 | tokens = cfg.split(',',1) 149 | pin = tokens[0].strip() 150 | cmd = tokens[1].strip() 151 | if cmd[-1] != '&': 152 | cmd += ' &' 153 | gpis[name] = Gpi(int(pin),cmd) 154 | 155 | def read_fifo(): 156 | """Blocking fifo reader""" 157 | #Make the fifo 158 | if os.path.exists(fifo): 159 | os.remove(fifo) 160 | os.mkfifo(fifo) 161 | os.chmod(fifo,0666) 162 | f = open(fifo,'r+') 163 | s = '' 164 | while s != 'STOP': 165 | s = f.readline().strip() 166 | tokens = s.split() 167 | if (len(tokens) > 1 and tokens[0] in gpos and tokens[1] in modes): 168 | print_debug('Accepted: %s' % tokens) 169 | with gpo_lock: 170 | gpos[tokens[0]].mode = tokens[1] 171 | else: 172 | print_debug('Rejected: %s' % tokens) 173 | f.close() 174 | #Delete the fifo 175 | os.remove(fifo) 176 | print_debug('FIFO reader stopped') 177 | 178 | def terminate_threads(): 179 | """Graceful thread termination""" 180 | global gpo_keep_running 181 | global gpi_keep_running 182 | with gpo_lock: 183 | gpo_keep_running = False 184 | with gpi_lock: 185 | gpi_keep_running = False 186 | 187 | def signal_handler(signal, frame): 188 | """Signal handler to send the poison pill to the fifo""" 189 | with open(fifo,'w') as f: 190 | f.write('STOP\n') 191 | 192 | if __name__ == '__main__': 193 | try: 194 | if len(sys.argv) < 2: 195 | exit('GPIO service - provides simple management of Raspberry Pi GPIOs\n by Jeremy Blythe (http://jeremyblythe.blogspot.com)\n\n Usage: gpioservice.py {config-file-path}') 196 | cfg_path = sys.argv[1] 197 | if not os.path.exists(cfg_path): 198 | exit('Config file does not exist [%s]' % cfg_path) 199 | setup(cfg_path) 200 | # Trap SIGINT (Ctrl-C) and exit cleanly 201 | signal.signal(signal.SIGINT, signal_handler) 202 | # Start the Gpo management thread to control flashing etc. 203 | GpoThread().start() 204 | # Start the Gpi management thread to run commands 205 | GpiThread().start() 206 | # Start the fifo blocking read loop 207 | read_fifo() 208 | # Signal graceful thread termination 209 | terminate_threads() 210 | except Exception as e: 211 | terminate_threads() 212 | -------------------------------------------------------------------------------- /mcp3008.py: -------------------------------------------------------------------------------- 1 | import spidev 2 | 3 | spi = spidev.SpiDev() 4 | spi.open(0,0) 5 | 6 | # read SPI data from MCP3008 chip, 8 possible adc's (0 thru 7) 7 | def readadc(adcnum): 8 | if ((adcnum > 7) or (adcnum < 0)): 9 | return -1 10 | r = spi.xfer2([1,(8+adcnum)<<4,0]) 11 | adcout = ((r[1]&3) << 8) + r[2] 12 | return adcout 13 | -------------------------------------------------------------------------------- /raspi-adc-pot.py: -------------------------------------------------------------------------------- 1 | import spidev 2 | import time 3 | import os 4 | 5 | DEBUG = 0 6 | 7 | spi = spidev.SpiDev() 8 | spi.open(0,0) 9 | 10 | # read SPI data from MCP3008 chip, 8 possible adc's (0 thru 7) 11 | def readadc(adcnum): 12 | if ((adcnum > 7) or (adcnum < 0)): 13 | return -1 14 | r = spi.xfer2([1,(8+adcnum)<<4,0]) 15 | adcout = ((r[1]&3) << 8) + r[2] 16 | return adcout 17 | 18 | # 10k trim pot connected to adc #0 19 | potentiometer_adc = 0; 20 | 21 | last_read = 0 # this keeps track of the last potentiometer value 22 | tolerance = 5 # to keep from being jittery we'll only change 23 | # volume when the pot has moved more than 5 'counts' 24 | 25 | while True: 26 | # we'll assume that the pot didn't move 27 | trim_pot_changed = False 28 | 29 | # read the analog pin 30 | trim_pot = readadc(potentiometer_adc) 31 | # how much has it changed since the last read? 32 | pot_adjust = abs(trim_pot - last_read) 33 | 34 | if DEBUG: 35 | print "trim_pot:", trim_pot 36 | print "pot_adjust:", pot_adjust 37 | print "last_read", last_read 38 | 39 | if ( pot_adjust > tolerance ): 40 | trim_pot_changed = True 41 | 42 | if DEBUG: 43 | print "trim_pot_changed", trim_pot_changed 44 | 45 | if ( trim_pot_changed ): 46 | set_volume = trim_pot / 10.24 # convert 10bit adc0 (0-1024) trim pot read into 0-100 volume level 47 | set_volume = round(set_volume) # round out decimal value 48 | set_volume = int(set_volume) # cast volume as integer 49 | 50 | print 'Volume = {volume}%' .format(volume = set_volume) 51 | set_vol_cmd = 'sudo amixer cset numid=1 -- {volume}% > /dev/null' .format(volume = set_volume) 52 | os.system(set_vol_cmd) # set volume 53 | 54 | 55 | if DEBUG: 56 | print "set_volume", set_volume 57 | print "tri_pot_changed", set_volume 58 | 59 | # save the potentiometer reading for the next loop 60 | last_read = trim_pot 61 | # hang out and do nothing for a half second 62 | time.sleep(0.5) 63 | -------------------------------------------------------------------------------- /screen.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Created on 21 Jul 2012 3 | 4 | @author: Jeremy Blythe 5 | 6 | screen - Manages the Textstar 16x2 4 button display 7 | 8 | Read the blog entry at http://jeremyblythe.blogspot.com for more information 9 | ''' 10 | import serial 11 | import datetime 12 | import time 13 | import subprocess 14 | import twitter 15 | import urllib2 16 | import json 17 | 18 | CLEAR = chr(12) 19 | ESC = chr(254) 20 | BLOCK = chr(154) 21 | 22 | POLL_TICKS = 15 23 | REFRESH_TICKS = 300 24 | 25 | class Display(): 26 | """ Manages the 16x2 4 button display: 27 | on_tick called every 0.1 seconds as part of the main loop after the button read 28 | on_poll called every 1.5 seconds 29 | on_page called when a new page has been selected 30 | on_refresh called every 30 seconds""" 31 | def __init__(self,on_page=None,on_poll=None,on_tick=None,on_refresh=None): 32 | # Start the serial port 33 | self.ser = serial.Serial('/dev/ttyAMA0',9600,timeout=0.1) 34 | # Callbacks 35 | self.on_page = on_page 36 | self.on_poll = on_poll 37 | self.on_tick = on_tick 38 | self.on_refresh = on_refresh 39 | 40 | self.page = 'a' 41 | self.poll = POLL_TICKS 42 | self.refresh = REFRESH_TICKS 43 | 44 | def position_cursor(self,line,column): 45 | self.ser.write(ESC+'P'+chr(line)+chr(column)) 46 | 47 | def scroll_down(self): 48 | self.ser.write(ESC+'O'+chr(0)) 49 | 50 | def window_home(self): 51 | self.ser.write(ESC+'G'+chr(1)) 52 | 53 | def clear(self): 54 | self.ser.write(CLEAR) 55 | 56 | def run(self): 57 | #show initial page 58 | display.ser.write(' Starting.... ') 59 | if self.on_page != None: 60 | self.on_page() 61 | #main loop 62 | while True: 63 | key = str(self.ser.read(1)) 64 | if key != '' and key in 'abcd': 65 | self.page = key 66 | self.refresh = REFRESH_TICKS 67 | self.poll = POLL_TICKS 68 | if self.on_page != None: 69 | self.on_page() 70 | else: 71 | self.refresh-=1 72 | if self.refresh == 0: 73 | self.refresh = REFRESH_TICKS 74 | if self.on_refresh != None: 75 | self.on_refresh() 76 | 77 | self.poll-=1 78 | if self.poll == 0: 79 | self.poll = POLL_TICKS 80 | if self.on_poll != None: 81 | self.on_poll() 82 | 83 | if self.on_tick != None: 84 | self.on_tick() 85 | 86 | 87 | display = None 88 | # Start twitter 89 | twitter_api = twitter.Api() 90 | 91 | def write_datetime(): 92 | display.position_cursor(1, 1) 93 | dt=str(datetime.datetime.now()) 94 | display.ser.write(' '+dt[:10]+' '+' '+dt[11:19]+' ') 95 | 96 | def get_addr(interface): 97 | try: 98 | s = subprocess.check_output(["ip","addr","show",interface]) 99 | return s.split('\n')[2].strip().split(' ')[1].split('/')[0] 100 | except: 101 | return '?.?.?.?' 102 | 103 | def write_ip_addresses(): 104 | display.position_cursor(1, 1) 105 | display.ser.write('e'+get_addr('eth0').rjust(15)+'w'+get_addr('wlan0').rjust(15)) 106 | 107 | def write_twitter(): 108 | display.position_cursor(1, 1) 109 | try: 110 | statuses = twitter_api.GetUserTimeline('Raspberry_Pi') 111 | twitter_out = BLOCK 112 | for s in statuses: 113 | twitter_out+=s.text.encode('ascii','ignore')+BLOCK 114 | display.ser.write(twitter_out[:256]) 115 | except: 116 | display.ser.write('twitter failed'.ljust(256)) 117 | 118 | def write_recent_numbers(): 119 | display.position_cursor(1, 1) 120 | try: 121 | result = urllib2.urlopen("http://jerbly.uk.to/get_recent_visitors").read() 122 | j = json.loads(result) 123 | if len(j) > 0: 124 | entry = str(j[0]['numbers'][-1:])+' '+j[0]['countryName'] 125 | display.ser.write(entry.ljust(32)) 126 | else: 127 | display.ser.write('No entries found'.ljust(32)) 128 | except: 129 | display.ser.write('jerbly.uk.to failed'.ljust(32)) 130 | 131 | # Callbacks 132 | def on_page(): 133 | display.clear() 134 | display.window_home() 135 | if display.page == 'a': 136 | write_datetime() 137 | elif display.page == 'b': 138 | write_recent_numbers() 139 | elif display.page == 'c': 140 | write_twitter() 141 | elif display.page == 'd': 142 | write_ip_addresses() 143 | 144 | def on_poll(): 145 | if display.page == 'c': 146 | display.scroll_down() 147 | 148 | def on_tick(): 149 | if display.page == 'a': 150 | write_datetime() 151 | 152 | def on_refresh(): 153 | if display.page == 'b': 154 | write_recent_numbers() 155 | elif display.page == 'c': 156 | write_twitter() 157 | elif display.page == 'd': 158 | write_ip_addresses() 159 | 160 | display = Display(on_page, on_poll, on_tick, on_refresh) 161 | display.run() 162 | 163 | --------------------------------------------------------------------------------