Unfortunately ControlMyPi will be shutting down on September 1st due to Google removing support for XMPP :(
Since I first wrote ControlMyPi many well supported IoT platforms have come about. Most of these use MQTT for the messaging where ControlMyPi was using XMPP.
I would suggest you take a look at the IoT systems provided by Adafruit and AWS. Here’s a tutorial I wrote for Adafruit:
Monitor PiCam and temperature on a PiTFT via adafruit.io
There are many many more on the Adafruit learning system.
Head of Development, Software Architect and Developer by day. Data Science and Raspberry Pi dabbler by night.
Showing posts with label control my pi. Show all posts
Showing posts with label control my pi. Show all posts
2017-05-06
2013-09-07
Build a Raspberry Pi Moisture Sensor to Monitor Your Plants
*** NOTE: ControlMyPi shutting down ***
Here's a snippet from a detailed tutorial I've written for Tuts+
....You will be able to monitor the sensor locally on the LCD or remotely, via ControlMyPi.com, and receive daily emails if the moisture drops below a specified level.
Along the way I will:
Read the whole tutorial here: Build a Raspberry Pi Moisture Sensor to Monitor Your Plants
Here's a snippet from a detailed tutorial I've written for Tuts+
....You will be able to monitor the sensor locally on the LCD or remotely, via ControlMyPi.com, and receive daily emails if the moisture drops below a specified level.
Along the way I will:
- wire up and read a value from an analog sensor over SPI using a breadboard
- format the sensor reading nicely in the console
- display the sensor reading on an RGB LCD display
- have the Raspberry Pi send an email with the sensor reading
- easily monitor the sensor and some historic readings on the web
Read the whole tutorial here: Build a Raspberry Pi Moisture Sensor to Monitor Your Plants
Labels:
control my pi,
lcd,
python,
raspberry pi,
spi
2013-03-11
Raspberry Pi system monitor embedded on your own site
*** NOTE: ControlMyPi shutting down ***
Above is an embedded ControlMyPi panel showing some system stats from my Raspberry Pi.
If you want to run one of these yourself set up your Raspberry Pi for ControlMyPi by following the instructions on the site and then run the script below (after changing it to use your account and password).
To embed it on your site use an iframe using the instructions on the ControlMyPi FAQ.
If you want this to run automatically every time you boot up just add a line to /etc/rc.local e.g. python /path/to/script/pimonitor.py &
''' Created on 10 Mar 2013 ControlMyPi Raspberry Pi system monitor. See www.controlmypi.com. @author: Jeremy Blythe ''' import subprocess import logging import time from controlmypi import ControlMyPi def get_ip_address(interface): "Returns the IP address for the given interface e.g. eth0" try: s = subprocess.check_output(["ip","addr","show",interface]) return s.split('\n')[2].strip().split(' ')[1].split('/')[0] except: return '?.?.?.?' def get_ram(): "Returns a tuple (total ram, available ram) in megabytes. See www.linuxatemyram.com" try: s = subprocess.check_output(["free","-m"]) lines = s.split('\n') return ( int(lines[1].split()[1]), int(lines[2].split()[3]) ) except: return 0 def get_process_count(): "Returns the number of processes" try: s = subprocess.check_output(["ps","-e"]) return len(s.split('\n')) except: return 0 def get_up_stats(): "Returns a tuple (uptime, 5 min load average)" try: s = subprocess.check_output(["uptime"]) load_split = s.split('load average: ') load_five = float(load_split[1].split(',')[1]) up = load_split[0] up_pos = up.rfind(',',0,len(up)-4) up = up[:up_pos].split('up ')[1] return ( up , load_five ) except: return ( '' , 0 ) def get_connections(): "Returns the number of network connections" try: s = subprocess.check_output(["netstat","-tun"]) return len([x for x in s.split() if x == 'ESTABLISHED']) except: return 0 def get_temperature(): "Returns the temperature in degrees C" try: s = subprocess.check_output(["/opt/vc/bin/vcgencmd","measure_temp"]) return float(s.split('=')[1][:-3]) except: return 0 def on_msg(conn, key, value): pass if __name__ == '__main__': logging.basicConfig(level=logging.ERROR) total_ram = get_ram()[0] p = [ [ ['O'] ], [ ['L','Up time'],['S','up',''] ], [ ['L','Processes'],['S','pcount',''] ], [ ['L','Connections'],['S','ncount',''] ], [ ['C'] ], [ ['O'] ], [ ['G','ram','free Mb',0,0,total_ram],['G','load','load',0,0,4],['G','temp',u'\xB0C',0,0,80] ], [ ['C'] ] ] conn = ControlMyPi('you@yours.com', 'password', 'pimonitor', 'Pi system monitor', p, on_msg) if conn.start_control(): try: status = {'ram':0, 'temp':0, 'load':0, 'pcount':0, 'ncount':0, 'up':''} while True: to_send = {} to_send['ram'] = get_ram()[1] to_send['temp'] = get_temperature() up, load = get_up_stats() to_send['load'] = load to_send['pcount'] = get_process_count() to_send['ncount'] = get_connections() to_send['up'] = up for k,v in to_send.items(): if status[k] == v: del to_send[k] if len(to_send) > 0: status.update(to_send) conn.update_status(to_send) time.sleep(30) finally: conn.stop_control()
2013-02-24
Live Web Bicycle Dashboard - the code
*** NOTE: ControlMyPi shutting down ***
This post is a walkthrough of the code running on the Raspberry Pi as seen in the previous post: Live Web Bicycle Dashboard using ControlMyPi. This file, and the required mcp3008.py, are available in the examples from ControlMyPi. See "How to connect your pi".
Firstly at the top of the file are a few constants to use with ControlMyPi. The PANEL_FORM defines what ControlMyPi will render on the web site. Each update-able widget has a name so we can push changes up to ControlMyPi as new data is read from the attached devices. For example the 'P','streetview' widget defines a Picture widget. Whenever we want to display a new streetview image we can push the URL up to ControlMyPi and it in turn will push this change out to any browser currently viewing the page.
The last line of the panel defines two buttons and a status text widget. These are used to start and stop recording the telemetry to a file. More about this at the end of this post.
For information about all the available widgets in ControlMyPi go to here: ControlMyPi docs
The last few constants in this section define the URLs used for Google Maps Image APIs. %s substitutions are defined in these strings for us to apply longitude, latitude and heading later on. Also an API_KEY constant is defined here. You can comment this out initially to test but you will need to get a key eventually as the quota for image fetches is quite low without a key. With a key you get 25000 per day for free.
Every time the read method is called on this class a single line is read from the GPS unit. If an RMC or GGA message is found then the info is decoded and the member variables are updated. As you'll see later the design of this whole application hinges around the GPS. The program basically runs as fast as the GPS produces output, since the unit is in 10Hz mode we collect all the data for an update and then there's a short delay until the next set of data. All this happens 10 times per second. During the "data gaps" the serial port 0.01 second time out comes in to play to stop the main loop from freezing allowing us to do things like read key presses from the TextStar display.
The constructor opens the serial port through the USB and throws the first few inputs away. Something I noticed during testing is that when the TextStar starts up it spews out a few characters which could spoil the key reading code later. So, the start up routine reads up to 16 characters after a 3 second delay from opening the port. Also, I set up the "on_rec_button" event here. This is the method to call if the record toggle button is pressed.
As well as updating the display with the GPS and Accelerometer info this class displays the currently assigned wired ethernet address and ppp address. This is really useful as it allows you to plug in to a network and easily find the address you've been assigned so you can then ssh in. Secondly it's a confidence check that the 3g is working as you'll see the ppp address.
The key reading routine uses the 'a' button to rotate through the pages of info and the 'c' button to call the 'on_rec_button' event which is used to toggle recording.
The recorder has a reference to the ControlMyPi connection and uses this to push an update showing the recording state. This is either "Recording" or "Stopped" and when it's stopped the generated file name is shown.
The updaters are the LCD, ControlMyPi and the Recorder. The Recorder writes to the file every change of time stamp provided it's in record mode. The LCD and ControlMyPi write less frequently, a simple counter is used to action every n'th update.
You'll notice there is no explicit yield in the main loop. It's quite normal to put a time.sleep(n) into the main loop to stop tight-looping. In this case we're using the blocking serial port reads with their timeouts to yield.
ControlMyPi only needs to be updated with new information, if the GPS is not locked (active) then we don't bother to send the old information again. Instead we send a "NOT LOCKED" message. The status dict is simply filled with the widget names that we want to update and the new values. This dict is then sent to ControlMyPi with the call to update_status.
That's it! If you're brave enough to try this yourself there are a few points where I've left some commented-out print statements for debug. This and some simpler projects are available in a zip on ControlMyPi here: How to connect your pi.
Have fun.
This post is a walkthrough of the code running on the Raspberry Pi as seen in the previous post: Live Web Bicycle Dashboard using ControlMyPi. This file, and the required mcp3008.py, are available in the examples from ControlMyPi. See "How to connect your pi".
Firstly at the top of the file are a few constants to use with ControlMyPi. The PANEL_FORM defines what ControlMyPi will render on the web site. Each update-able widget has a name so we can push changes up to ControlMyPi as new data is read from the attached devices. For example the 'P','streetview' widget defines a Picture widget. Whenever we want to display a new streetview image we can push the URL up to ControlMyPi and it in turn will push this change out to any browser currently viewing the page.
The last line of the panel defines two buttons and a status text widget. These are used to start and stop recording the telemetry to a file. More about this at the end of this post.
For information about all the available widgets in ControlMyPi go to here: ControlMyPi docs
The last few constants in this section define the URLs used for Google Maps Image APIs. %s substitutions are defined in these strings for us to apply longitude, latitude and heading later on. Also an API_KEY constant is defined here. You can comment this out initially to test but you will need to get a key eventually as the quota for image fetches is quite low without a key. With a key you get 25000 per day for free.
''' Created on 6 Nov 2012 Bicycle telemetry recorder with Live web dashboard through ControlMyPi.com See: http://jeremyblythe.blogspot.com http://www.controlmypi.com Follow me on Twitter for updates: @jerbly @author: Jeremy Blythe ''' import serial import subprocess import mcp3008 import time from controlmypi import ControlMyPi JABBER_ID = 'you@your.jabber.host' JABBER_PASSWORD = 'yourpassword' SHORT_ID = 'bicycle' FRIENDLY_NAME = 'Bicycle telemetry system' PANEL_FORM = [ [ ['S','locked',''] ], [ ['O'] ], [ ['P','streetview',''],['P','map',''] ], [ ['C'] ], [ ['O'] ], [ ['L','Speed'],['G','speed','mph',0,0,50], ['L','Height'],['S','height',''] ], [ ['C'] ], [ ['L','Accelerations'] ], [ ['G','accx','X',0,-3,3], ['G','accy','Y',0,-3,3], ['G','accz','Z',1,-3,3] ], [ ['L','Trace file'],['B','start_button','Start'],['B','stop_button','Stop'],['S','recording_state','-'] ] ] API_KEY = '&key=YOUR_API_KEY' STREET_VIEW_URL = 'http://maps.googleapis.com/maps/api/streetview?size=360x300&location=%s,%s&fov=60&heading=%s&pitch=0&sensor=true'+API_KEY MAP_URL = 'http://maps.googleapis.com/maps/api/staticmap?center=%s,%s&zoom=15&size=360x300&sensor=true&markers=%s,%s'+API_KEY
The GPS class.
The constructor goes through a process of setting the GPS unit into 38400 baud and 10Hz update mode. During testing I noticed that if you don't increase the baud rate to 38400 (up from 9600) then the unit won't go into 10Hz mode. Presumably this is because there's too much data to get out per second at 9600 baud so it's incompatible. Finally I set the unit to only produce RMC and GGA messages - everything but the height is available in the RMC message.Every time the read method is called on this class a single line is read from the GPS unit. If an RMC or GGA message is found then the info is decoded and the member variables are updated. As you'll see later the design of this whole application hinges around the GPS. The program basically runs as fast as the GPS produces output, since the unit is in 10Hz mode we collect all the data for an update and then there's a short delay until the next set of data. All this happens 10 times per second. During the "data gaps" the serial port 0.01 second time out comes in to play to stop the main loop from freezing allowing us to do things like read key presses from the TextStar display.
class GPS: def __init__(self): self.height = '0' self.time_stamp = '' self.active = False self.lat = None self.lat_dir = None self.lon = None self.lon_dir = None self.speed = None self.heading = None self.date = '' # Connect to GPS at default 9600 baud self.ser = serial.Serial('/dev/ttyAMA0',9600,timeout=0.01) # Switch GPS to faster baud self.send_and_get_ack('251',',38400') # Assume success - close and re-open serial port at new speed self.ser.close() self.ser = serial.Serial('/dev/ttyAMA0',38400,timeout=0.01) # Set GPS into RMC and GGA only mode self.send_and_get_ack('314',',0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0') # Set GPS into 10Hz mode self.send_and_get_ack('220',',100') def checksum(self,cmd): calc_cksum = 0 for s in cmd: calc_cksum ^= ord(s) return '$'+cmd+'*'+hex(calc_cksum)[2:] def send_and_get_ack(self,cmdno,cmdstr): '''Send the cmd and wait for the ack''' #$PMTK001,604,3*32 #PMTK001,Cmd,Flag #Cmd: The command / packet type the acknowledge responds. #Flag: .0. = Invalid command / packet. #.1. = Unsupported command / packet type #.2. = Valid command / packet, but action failed #.3. = Valid command / packet, and action succeeded cmd = 'PMTK%s%s' % (cmdno,cmdstr) msg = self.checksum(cmd)+chr(13)+chr(10) #print '>>>%s' % cmd self.ser.write(msg) ack = False timeout = 300 while (not ack) and timeout > 0: line = str(self.ser.readline()) if line.startswith('$PMTK001'): tokens = line.split(',') ack = tokens[2][0] == '3' #print '<<<%s success=%s' % (line,ack) timeout -= 1 return ack def read(self): '''Read the GPS''' line = str(self.ser.readline()) #print line if line.startswith('$GPGGA'): # $GPGGA,210612.300,5128.5791,N,00058.5165,W,1,8,1.18,41.9,M,47.3,M,,*79 # 9 = Height in metres tokens = line.split(',') if len(tokens) < 15: return try: self.height = tokens[9] except ValueError as e: print e elif line.startswith('$GPRMC'): # $GPRMC,105215.000,A,5128.5775,N,00058.5070,W,0.12,103.43,211012,,,A*78 # 1 = Time # 2 = (A)ctive or (V)oid # 3 = Latitude # 5 = Longitude # 7 = Speed in knots # 8 = Compass heading # 9 = Date #Divide minutes by 60 and add to degrees. West and South = negative #Multiply knots my 1.15078 to get mph. tokens = line.split(',') if len(tokens) < 10: return try: self.time_stamp = tokens[1] self.active = tokens[2] == 'A' self.lat = tokens[3] self.lat_dir = tokens[4] self.lon = tokens[5] self.lon_dir = tokens[6] self.speed = tokens[7] self.heading = tokens[8] self.date = tokens[9] if self.active: self.lat = float(self.lat[:2]) + float(self.lat[2:])/60.0 if self.lat_dir == 'S': self.lat = -self.lat self.lon = float(self.lon[:3]) + float(self.lon[3:])/60.0 if self.lon_dir == 'W': self.lon = -self.lon self.speed = float(self.speed) * 1.15078 except ValueError as e: print e
The TextStar class.
The TextStar serial LCD is a great little handy device not just for the 16x2 display but also the 4 buttons around the edge of the display for input. Normally I connect this straight into the serial pins on the Raspberry Pi, but in this case I have the GPS connected there. So, I'm using a USB to TTL serial converter. If you have a standard USB to RS232 converter you can use that too, just be sure to set the TextStar into RS232 mode.The constructor opens the serial port through the USB and throws the first few inputs away. Something I noticed during testing is that when the TextStar starts up it spews out a few characters which could spoil the key reading code later. So, the start up routine reads up to 16 characters after a 3 second delay from opening the port. Also, I set up the "on_rec_button" event here. This is the method to call if the record toggle button is pressed.
As well as updating the display with the GPS and Accelerometer info this class displays the currently assigned wired ethernet address and ppp address. This is really useful as it allows you to plug in to a network and easily find the address you've been assigned so you can then ssh in. Secondly it's a confidence check that the 3g is working as you'll see the ppp address.
The key reading routine uses the 'a' button to rotate through the pages of info and the 'c' button to call the 'on_rec_button' event which is used to toggle recording.
class TextStar: def __init__(self, on_rec_button): self.LCD_UPDATE_DELAY = 5 self.lcd_update = 0 self.page = 0 self.ser = serial.Serial('/dev/ttyUSB0',115200,timeout=0.01) # Throw away first few key presses after waiting for the screen to start up time.sleep(3) self.ser.read(16) self.on_rec_button = on_rec_button def get_addr(self,interface): try: s = subprocess.check_output(["ip","addr","show",interface]) return s.split('\n')[2].strip().split(' ')[1].split('/')[0] except: return '?.?.?.?' def write_ip_addresses(self): self.ser.write(chr(254)+'P'+chr(1)+chr(1)) self.ser.write('e'+self.get_addr('eth0').rjust(15)+'p'+self.get_addr('ppp0').rjust(15)) def update(self,gps,acc,rec): self.lcd_update += 1 if self.lcd_update > self.LCD_UPDATE_DELAY: self.lcd_update = 0 self.ser.write(chr(254)+'P'+chr(1)+chr(1)) if not gps.active and (self.page == 0 or self.page == 1): self.ser.write('NO FIX: '+gps.date+' '+rec.recording) self.ser.write(gps.time_stamp.ljust(16)) elif self.page == 0: self.ser.write(('%.8f' % gps.lat).rjust(14)+" "+rec.recording) self.ser.write(('%.8f' % gps.lon).rjust(14)+" ") elif self.page == 1: #0.069 223.03 48.9 -0.010 0.010 0.980 self.ser.write('{: .3f}{:>9} '.format(gps.speed,gps.height)) if acc: self.ser.write('{: .2f}{: .2f}{: .2f}'.format(*acc)) else: self.ser.write(' '*16) def read_key(self): key = str(self.ser.read(1)) if key != '' and key in 'abcd': self.lcd_update = self.LCD_UPDATE_DELAY if key == 'c': self.on_rec_button() elif key == 'a': self.page += 1 if self.page > 2: self.page = 0 elif self.page == 2: self.write_ip_addresses()
The Recorder class
The current status is logged to file every time there's a new reading from the GPS. So that's 10 times per second. If the Accelerometer is not used then dashes replace the X,Y and Z readings. Likewise if there is no GPS lock only the date and time is logged and dashes replace the rest of the data.The recorder has a reference to the ControlMyPi connection and uses this to push an update showing the recording state. This is either "Recording" or "Stopped" and when it's stopped the generated file name is shown.
class Recorder: def __init__(self,gps,cmp): self.gps = gps self.cmp = cmp self.recording = 's' self.rec_file = None def start(self): if self.recording == 's': self.recording = 'r' self.rec_file = open("/home/pi/gps-"+self.gps.date+self.gps.time_stamp+".log", "a") self.cmp.update_status( {'recording_state':'Recording'} ) def stop(self): if self.recording == 'r': self.recording = 's' self.rec_file.close() self.cmp.update_status( {'recording_state':'Stopped - [%s]' % self.rec_file.name} ) def update(self,acc): if self.recording == 'r': if acc: acc_str = '%.2f %.2f %.2f' % acc else: acc_str = '- - -' if self.gps.active: self.rec_file.write('%s %s %.8f %.8f %.3f %s %s %s\n' % (self.gps.date, self.gps.time_stamp, self.gps.lat, self.gps.lon, self.gps.speed, self.gps.heading, self.gps.height, acc_str)) else: self.rec_file.write('%s %s - - - - - %s\n' % (self.gps.date, self.gps.time_stamp, acc_str))
The Accelerometer class
The MCP3008 is used to read the three voltages from the 3 axis accelerometer, convert them to digital readings and retrieve them over SPI. Details of this technique are written up here: Raspberry Pi hardware SPI analog inputs using the MCP3008. As the comment in the code states there's a little tuning to be done to get good readings.class Accelerometer: def read_accelerometer(self): '''Read the 3 axis accelerometer using the MCP3008. Each axis is tuned to show +1g when oriented towards the ground, this will be different for everyone and dependent on physical factors - mostly how flat it's mounted. The result is rounded to 2 decimal places as there is too much noise to be more accurate than this. Returns a tuple (X,Y,Z).''' x = mcp3008.readadc(0) y = mcp3008.readadc(1) z = mcp3008.readadc(2) return ( round((x-504)/102.0,2) , round((y-507)/105.0,2) , round((z-515)/102.0,2) )
Construction and Events
In this section the objects are created and a couple of call-back events are assigned. If you don't have an Accelerometer set acc to None. Also, if you don't have a TextStar LCD set lcd to None. Notably the two call-backs are to handle incoming button events from either ControlMyPi or the TextStar keys.# Start the GPS gps = GPS() # Create the Accelerometer object. Change to acc=None if you don't have an accelerometer. acc = Accelerometer() # Control My Pi def on_control_message(conn, key, value): if key == 'start_button': rec.start() elif key == 'stop_button': rec.stop() conn = ControlMyPi(JABBER_ID, JABBER_PASSWORD, SHORT_ID, FRIENDLY_NAME, PANEL_FORM, on_control_message) # Recording rec = Recorder(gps, conn) def on_rec_button(): if rec.recording == 's': rec.start() else: rec.stop() # Start the TextStar LCD. Change to lcd=None if you don't have a TextStar LCD. lcd = TextStar(on_rec_button)
The main loop
Finally, after connecting to ControlMyPi, the main loop starts. Here we call the readers and the updaters. We read from the GPS, Accelerometer and TextStar keypad every loop. We keep track of the time stamp we're on and when we move to the next 10th of a second we call the updaters.The updaters are the LCD, ControlMyPi and the Recorder. The Recorder writes to the file every change of time stamp provided it's in record mode. The LCD and ControlMyPi write less frequently, a simple counter is used to action every n'th update.
You'll notice there is no explicit yield in the main loop. It's quite normal to put a time.sleep(n) into the main loop to stop tight-looping. In this case we're using the blocking serial port reads with their timeouts to yield.
ControlMyPi only needs to be updated with new information, if the GPS is not locked (active) then we don't bother to send the old information again. Instead we send a "NOT LOCKED" message. The status dict is simply filled with the widget names that we want to update and the new values. This dict is then sent to ControlMyPi with the call to update_status.
if conn.start_control(): try: conn.update_status( {'recording_state':'Stopped'} ) # Start main loop old_time_stamp = 'old' CMP_UPDATE_DELAY = 50 cmp_update = 0 while True: #Read the 3 axis accelerometer if acc: xyz = acc.read_accelerometer() else: xyz = None #Read GPS gps.read() #Update ControlMyPi, LCD and Recorder if we have a new reading if gps.time_stamp != old_time_stamp: if lcd: lcd.update(gps, xyz, rec) # Don't update ControlMyPi every tick, it'll be too much - approx. 5 seconds is # about right as it gives the browser time to fetch the streetview and map cmp_update += 1 if cmp_update > CMP_UPDATE_DELAY: cmp_update = 0 status = {} if xyz: status['accx'] = xyz[0] status['accy'] = xyz[1] status['accz'] = xyz[2] if gps.active: status['locked'] = 'GPS locked' slat = str(gps.lat) slon = str(gps.lon) status['streetview'] = STREET_VIEW_URL % (slat,slon,gps.heading) status['map'] = MAP_URL % (slat,slon,slat,slon) status['speed'] = int(round(gps.speed)) status['height'] = '{:>9}'.format(gps.height) conn.update_status(status) else: status['locked'] = 'GPS NOT LOCKED' conn.update_status(status) #Update recorder every tick rec.update(xyz) old_time_stamp = gps.time_stamp #Read keypad if lcd: lcd.read_key() finally: conn.stop_control() else: print("FAILED TO CONNECT")
That's it! If you're brave enough to try this yourself there are a few points where I've left some commented-out print statements for debug. This and some simpler projects are available in a zip on ControlMyPi here: How to connect your pi.
Have fun.
Labels:
3g,
control my pi,
gps,
lcd,
python,
raspberry pi,
serial,
spi
2013-02-19
Live Web Bicycle Dashboard using ControlMyPi
*** NOTE: ControlMyPi shutting down ***
This post shows how I set up a Live Web Dashboard from a Raspberry Pi as seen in the video above. In case you haven't worked it out, what's going on here is the Raspberry Pi is using 3G to send GPS and accelerometer data up to ControlMyPi. Users can then log in to ControlMyPi and watch the Live data displayed on the dashboard. In this case I'm using Google StreetView and Maps to show the current position and heading. Gauges are used to show speed and X,Y and Z accelerations.
(Special thanks to Rasathus for making this video for me!)
Click here to watch the "as live" replay of bicycle telemetry!
The diagram below shows the data flow:
There's a lot going on here so in this first post I'll explain how it all fits together and in the following post (or maybe posts) I'll go through the code.
3G dongle
There are quite a few guides out there for setting up 3G dongles on Raspberry Pis. Some use what now seems to be an unsupported script called sakis3g - I tried this first but was uneasy about using something that the author appears to have taken down. In fact all I had to do was install usb-modeswitch and wvdial. usb-modeswitch automatically detects your dongle and switches it into TTY mode. Simply use apt-get to install it.
wvdial is used to make the PPP connection. After using apt-get again to install it I followed the instructions on Linux Forums to get my connection up and running.
Start-up sequence: There's probably a better Linuxy way of doing this (maybe someone can comment to let me know) but I needed to be in control of the order that things started up to help with TTY discovery and smooth networking. I'm using a TextStar serial LCD through a USB to serial converter and my code expects to find it on /dev/ttyUSB0 so I really don't want the 3G dongle to appear on USB0. Also, I found that if my code starts trying to use the network before the PPP link is up it doesn't work and you have to stop and start the program. This is no good if you're out on your bicycle somewhere. So I wrote a small boot up script which is run from /etc/rc.local it guides the user through this sequence:
- Remove 3G dongle and power up Raspberry Pi
- Raspberry Pi boots up and runs rc.local
- LCD shows "Insert dongle and press 'A'"
- User plugs in dongle and waits for Blue LED before pressing the 'A' button on the TextStar
- LCD shows "Starting 3G please wait"
- wvdial is started
- Few seconds delay to wait for the network to come good
- Start up Bicycle Telemetry app
GPS
I'm using the Adafruit Ultimate GPS Breakout - 66 channel w/10 Hz updates - Version 3. I chose this one because having a 10Hz update fitted well with the code design. As you'll see when I publish the code, the main loop is timed off the updates from the GPS. Although I'm not updating the Web Dashboard as quickly as this I am logging this information to file 10 times a second.
The breakout board is simply connected up to the TTL serial pins on the Raspberry Pi and then it appears on /dev/ttyAMA0. When I wrote the code I didn't realise that gpsd existed so I have some code which decodes the serial stream directly, it's pretty simple though. Also I can't see how you can send the settings commands through to the GPS module from gpsd - I'm sending commands to switch the baud rate to 38400 and put it in 10Hz update mode (see PMTK_SET_NMEA_UPDATERATE in this pdf). Without these settings it defaults to 9600 baud and 1Hz updates.
3 Axis Accelerometer
Another Adafruit board is used here - the ADXL335 this senses up to 3g in X,Y and Z directions. I've then used an MCP3008 to convert the analog voltage outputs to 3 digital readings available over SPI. This uses the same technique (and code) from my article: Raspberry Pi hardware SPI analog inputs using the MCP3008.
ControlMyPi
I have created the cloud app ControlMyPi to make projects like this not only possible, but easy. A client library is used on the Raspberry Pi and all communication between it and ControlMyPi are over XMPP (also known as Jabber) protocol. This is the instant messaging protocol used by Google Talk as well. By using XMPP you're not hampered by firewalls and such but you do have near real-time messaging.
ControlMyPi also makes it simple to serve your live data to multiple web clients using "push". Updates from your Pi are routed through ControlMyPi and "pushed" to the web browser of anyone viewing the dashboard without refreshing pages.
More information about ControlMyPi is in my previous article: Control My Pi - Easy web remote control for your Raspberry Pi projects. Just as a teaser for the next post here is the panel definition code for the Bicycle Dashboard:
[ [ ['S','locked',''] ], [ ['O'] ], [ ['P','streetview',''],['P','map',''] ], [ ['C'] ], [ ['O'] ], [ ['L','Speed'],['G','speed','mph',0,0,50], ['L','Height'],['S','height',''] ], [ ['C'] ], [ ['L','Accelerations'] ], [ ['G','accx','X',0,-3,3], ['G','accy','Y',0,-3,3], ['G','accz','Z',1,-3,3] ], [ ['L','Trace file'],['B','start_button','Start'],['B','stop_button','Stop'],['S','recording_state','-'] ] ]
Here I'm using Picture widgets (P) for 'streetview' and 'map'. ControlMyPi allows you to push an update to an image by sending a url from your script, so the Google Maps Image APIs work very well. I'll show exactly how this is done in code in the next post.
Since I can't be riding around 24x7 I have set up a script which plays back recorded data from a few journeys. This script is running on a Raspberry Pi and sending out the data to ControlMyPi at the correct speed so it's a pretty good simulation. I've set it up as a public panel so you can access it from the front page - or this link: Replay of bicycle telemetry.
The next post will guide you through the code for all of this, coming soon...
Labels:
3g,
control my pi,
google app engine,
gps,
lcd,
python,
raspberry pi
2013-02-03
Control My Pi - Easy web remote control for your Raspberry Pi projects
*** NOTE: ControlMyPi shutting down ***
I'm launching Control My Pi Beta today. In just a few lines of Python code you can create a control panel for your project accessible over the Internet. No firewall changes to make, no web servers to set up. Pick up your Raspberry Pi, take it to a friends house or work, school, a club, a coffee shop - connect it to 3G and carry it around or put it in your car - it doesn't matter where it is or what network you're on you'll be able to access your control panel on Control My Pi. It's no ordinary control panel either, every viewer establishes a "push" connection to the server so any update sent from your Raspberry Pi is pushed to every open control panel out there in the world.
If you're really proud of your control panel, and you don't mind anyone pressing buttons, you can make your panel public. It will appear as a link on the front page. (I may need to do something about this if this feature becomes very popular!) If you just want to share your panel with a few people send them a copy of your panel URL.
Instructions, FAQ and documentation are available on the web site plus some examples to get started. More write-ups of Control My Pi projects coming up soon from simple GPIO LED projects to 3G GPS bicycle telemetry systems!
I've got a couple of public examples up and running for you to take a look at. Remember to click the "Push status" button on public panels to start the connection if you want to interact with it. (This is done for you automatically on non-public panels).
Here's a video showing a control panel in action. This is taken from the easycontrol.py example available in the download at Control My Pi. In the background is a Nexus 7 tablet showing a zoom in on the control panel at Control My Pi. In the foreground you can see my Raspberry Pi connected to a breadboard with 4 GPIOs. Two for inputs from the buttons and two for outputs to the LEDs. It's a bit hard to see because of my jumper wires but when I push the hardware button the LED's state changes and this is pushed to the web page. Likewise, when I press a software button the same thing happens.
And here's the small Python script to make this happen: hardware inputs, outputs and multi-user, push status, Internet control panel!
Finally, here's a snapshot of the live dashboard of my 3G GPS bicycle telemetry system. My current position, heading, speed, height and X,Y and Z accelerations are pushed to Control My Pi and then out to anyone watching!
I'm launching Control My Pi Beta today. In just a few lines of Python code you can create a control panel for your project accessible over the Internet. No firewall changes to make, no web servers to set up. Pick up your Raspberry Pi, take it to a friends house or work, school, a club, a coffee shop - connect it to 3G and carry it around or put it in your car - it doesn't matter where it is or what network you're on you'll be able to access your control panel on Control My Pi. It's no ordinary control panel either, every viewer establishes a "push" connection to the server so any update sent from your Raspberry Pi is pushed to every open control panel out there in the world.
If you're really proud of your control panel, and you don't mind anyone pressing buttons, you can make your panel public. It will appear as a link on the front page. (I may need to do something about this if this feature becomes very popular!) If you just want to share your panel with a few people send them a copy of your panel URL.
Instructions, FAQ and documentation are available on the web site plus some examples to get started. More write-ups of Control My Pi projects coming up soon from simple GPIO LED projects to 3G GPS bicycle telemetry systems!
I've got a couple of public examples up and running for you to take a look at. Remember to click the "Push status" button on public panels to start the connection if you want to interact with it. (This is done for you automatically on non-public panels).
Here's a video showing a control panel in action. This is taken from the easycontrol.py example available in the download at Control My Pi. In the background is a Nexus 7 tablet showing a zoom in on the control panel at Control My Pi. In the foreground you can see my Raspberry Pi connected to a breadboard with 4 GPIOs. Two for inputs from the buttons and two for outputs to the LEDs. It's a bit hard to see because of my jumper wires but when I push the hardware button the LED's state changes and this is pushed to the web page. Likewise, when I press a software button the same thing happens.
And here's the small Python script to make this happen: hardware inputs, outputs and multi-user, push status, Internet control panel!
from controlmypi import ControlMyPi import json import RPi.GPIO as GPIO import time JABBER_ID = 'me@my.jabber.domain' JABBER_PASSWORD = 'password' SHORT_ID = 'easy1' FRIENDLY_NAME = 'Control my red and yellow LEDs' PANEL_FORM = [ [ ['L','Remote control Raspberry Pi LEDs - status pushed back to this page!'] ], [ ['O'] ], [ ['L','Yellow'],['B','18 on','on'],['B','18 off','off'],['S','GPIO18','-'] ], [ ['L','Red'],['B','23 on','on'],['B','23 off','off'],['S','GPIO23','-'] ], [ ['C'] ], ] GPIO.setmode(GPIO.BCM) GPIO.setup(18, GPIO.OUT) GPIO.setup(23, GPIO.OUT) GPIO.setup(24, GPIO.IN) GPIO.setup(25, GPIO.IN) status = {18:False, 23:False} def switch_led(n, state): if status[n] != state: GPIO.output(n, not state) #Low to glow! conn.update_status({'GPIO'+str(n): 'on' if state else 'off'}) status[n] = state def on_control_message(conn, key, value): tokens = key.split(' ') number = int(tokens[0]) state = tokens[1] if number in [18,23] and state in ['on','off']: switch_led(number, state == 'on') def main_loop(): switch_led(18,True) switch_led(23,True) debounced_one = True debounced_two = True while True: state_one = GPIO.input(24) state_two = GPIO.input(25) if state_one and debounced_one: switch_led(18, not status[18]) debounced_one = False elif not state_one: debounced_one = True if state_two and debounced_two: switch_led(23, not status[23]) debounced_two = False elif not state_two: debounced_two = True time.sleep(0.1) conn = ControlMyPi(JABBER_ID, JABBER_PASSWORD, SHORT_ID, FRIENDLY_NAME, PANEL_FORM, on_control_message) if conn.start_control(): try: main_loop() finally: conn.stop_control() else: print("FAILED TO CONNECT")
Finally, here's a snapshot of the live dashboard of my 3G GPS bicycle telemetry system. My current position, heading, speed, height and X,Y and Z accelerations are pushed to Control My Pi and then out to anyone watching!
Labels:
control my pi,
gpio,
python,
raspberry pi
Subscribe to:
Posts (Atom)