Sunday 9 June 2013

Allowing for USB-serial disconnects and reconnects

The sat modem has the ID_VENDOR_FROM_DATABASE Future Technology Devices International, Ltd

The GPS Dongle has  ID_VENDOR_FROM_DATABASE Prolific Technology inc

so we loop through the attached usb-serial devices with for DEV in /dev/ttyUSB*;

for each of them we ask info about them do udevadm info -q env -n $DEV /dev/ttyUSB | grep -w 'DEVNAME\|ID_VENDOR_FROM_DATABASE' ; done

for DEV in /dev/ttyUSB*; do udevadm info -q env -n $DEV /dev/ttyUSB | grep -w 'DEVNAME\|ID_VENDOR_FROM_DATABASE' ; done

Giving an output of  : 

DEVNAME=/dev/ttyUSB0
ID_VENDOR_FROM_DATABASE=Future Technology Devices International, Ltd
DEVNAME=/dev/ttyUSB1

ID_VENDOR_FROM_DATABASE=Prolific Technology, Inc.

Now we know which /dev/ttyUSB? is connected to which manufacturers device.

Saturday 16 March 2013

USB (serial) GPS Reader with pyserial, Redis, Ubuntu 12.10 on Beaglebone

Requirements for this script:
Make available: An easy to discover and understand, new-enough, and accurate-enough GPS location to other software

Process:

For this exercise we will write the scripts needed directly to the beaglebone using vim.  That way we may run and debug the scripts directly.  Backing up work is very simple and can be done when needed using SCP. 

Hardware:
  • Beaglebone rev A6, setup using this tutorial
  • Desktop PC running Ubuntu 11.10
  • USB GPS dongle
  • Linksys Nano WiFi Router
  • 4 port USB hub, model unknown
Software on Beaglebone:
  • Ubuntu 12.10
  • Redis
  • easy-install, pip, pyserial
Step One - log into Beaglebone

$ screen /dev/ttyUSB1 115200

Wait a few seconds then press 'enter' 
Login using your credentials ie: 'ubuntu' , 'temppwd'

Step Two - set-up the Beaglebone working environment

$ sudo apt-get install vim

Configure vim to be use-able.  Either SCP your current .vimrc file over to the beaglebone, or create a new one.  I only need a couple of small changes to vim so will create a new one.

cd ~/
mdkir tmp
vim .vimrc

Add these lines to the file.

set tabstop=2
set softtabstop=2
set number
set expandtab
et autoindent
set smarttab
set noswapfile
colorscheme pablo

These settings will shrink the tabs to two spaces.  Give you line numbers.  Make the tabs actually spaces (to avoid indentation errors)  Stop vim from annoying you with .swp files and give you a colorscheme that works OK with python.  Save the file and quit vim.

Step Three - set-up USB Devices

Plug in USB hub. 
Then into the hub plug in the GPS dongle and Router.
***My USB hub had issues (Unable to enumerate USB device) - fixed here.  Then restart Beaglebone

Step Four - use python to access GPS data

We need to find out the serial/USB port that the data will be coming into.

$ ls /dev | less

This give us a readable output of the /dev directory, press 'enter' until we find a result that looks  promising like 'ttyUSB0' then press 'q'.  Depending on your hardware set-up this may be different.

Create a .py file to hold our script, named with it's intended use in mind.

$ vim read_and_store_gps_data.py

Lets create the skeletion of this file, add the following lines:

#!/usr/bin/python
import sys
import serial

print 'hello world'

Save the file and quit vim ':wq!' Now lets test the file to see if everything might work.

$ ./read_and_store_gps_data.py

Great, time to read data from the GPS

We will want to make this script useful for different types of GPS units using different serial ports and baud rates.  Let's leave something to remind us how to use the script.  Replace the line print 'hello world' with:

print 'example usage: ./read_and_store_gps_data.py -baud 4800 -port /dev/ttyUSB0'  

Now that we can use the system args, we will check them and use them to connect to the GPS.
Edit the file to look like this:

  #!/usr/bin/python
  import sys
  import serial
  
  def read_line_from_serial():
    reading = serial_port.readline()
    if reading[:6] == '$GPGGA':    
      print reading
      
  def start_serial_connection(port,baud,timeout):
    try:
      serial_port = serial.Serial(port, baud, timeout=timeout)
    except Exception, e:
      print e
      sys.exit()
      
  def is_a_number(thing):
    try:
      return float(thing)
    except Exception, e:
      print e
      return None
  def start_gps_reader():
    baud = None
    port = None
    timeout = None
  
    args_length = len(sys.argv)
  
   for i in range(0,args_length-1):
      if (i+1) <= args_length:
        if sys.argv[i] == '-baud':
          baud = is_a_number(sys.argv[i+1])
        elif sys.argv[i] == '-timeout':
          timeout = is_a_number(sys.argv[i+1])
        elif sys.argv[i] == '-port':
          port = sys.argv[i+1]
  
    if baud and port and timeout:
      start_serial_connection(baud=baud,port=port,timeout=timeout)
    else:
      print 'example usage: ./read_and_store_gps_data.py -baud 4800 -port /dev
  /ttyUSB0 -timeout 0.5'
  
  start_gps_reader()
  
Now test the file

 $ ./read_and_store_gps_data.py -baud 4800 -port /dev/ttyUSB0 -timeout 0.5

Test the file using some incorrect details to see what happens.  If all is running thus far - we can now extract the useful data from the GPS.  GPS units output strings called NMEA strings.  An excellent reference to these strings is in this link.  For now we only want the GPGGA string.  

Here is a breakdown of the GPGGA string:

NameExample DataDescription
Sentence Identifier$GPGGAGlobal Positioning System Fix Data
Time17083417:08:34 Z
Latitude4124.8963, N41d 24.8963' N or 41d 24' 54" N
Longitude08151.6838, W81d 51.6838' W or 81d 51' 41" W
Fix Quality:
- 0 = Invalid
- 1 = GPS fix
- 2 = DGPS fix
1Data is from a GPS fix
Number of Satellites055 Satellites are in view
Horizontal Dilution of Precision (HDOP)1.5Relative accuracy of horizontal position
Altitude280.2, M280.2 meters above mean sea level
Height of geoid above WGS84 ellipsoid-34.0, M-34.0 meters
Time since last DGPS updateblankNo last update
DGPS reference station idblankNo station id
Checksum*75Used by program to check for transmission errors

Next we break down the string into its parts.  According to the table above.  The string is separated by commas.  We add a method to parse the GPGGA string, and a class to hold our newly found position.   And refactor a little.

   #!/usr/bin/python
   import sys
   import serial
   
   class Position:
    def __init__(self,time,latitude,lat_cardinal,longitude,
                               lon_cardinal,quality,altitude,precision):
      self.time = time
      self.latitude = latitude
      self.lat_cardinal = lat_cardinal
      self.longitude = longitude
      self.lon_cardinal = lon_cardinal
      self.quality = quality
      self.altitude = altitude
      self.precision = precision
    
  def parse_gpgga_string(gpgga_string):
    position = None
    gpgga_parts = gpgga_string.split(',')
    if not len(gpgga_parts) == 15:
      print 'malformed gpgga string'
      return False
    else:
      position = Position(time = gpgga_parts[1],
                      latitude = gpgga_parts[2],
                      lat_cardinal = gpgga_parts[3],
                      longitude = gpgga_parts[4],
                      lon_cardinal = gpgga_parts[5],
                      quality = gpgga_parts[6],
                      altitude = gpgga_parts[9],
                      precision = gpgga_parts[8])
      return position
  
  def get_gpgga_string_from_serial(serial_port):
    reading = serial_port.readline()
    if reading[:6] == '$GPGGA':
      return str(reading)
    else:
      return False
  
  def start_serial_connection(port,baud,timeout):
    try:
      serial_port = serial.Serial(port, baud, timeout=timeout)
      return serial_port
    except Exception, e:
      print e 
      sys.exit()
  
  def is_a_number(thing):
    try:
      return float(thing)
    except Exception, e:
      print e
      return None
   
  def get_serial_connection_with_system_args():
    baud = None
    port = None
    timeout = None
    serial_port = None
  
    args_length = len(sys.argv)
  
    for i in range(0,args_length-1):
      if (i+1) <= args_length:
        if sys.argv[i] == '-baud':
          baud = is_a_number(sys.argv[i+1])
        elif sys.argv[i] == '-timeout':
          timeout = is_a_number(sys.argv[i+1])
        elif sys.argv[i] == '-port':
          port = sys.argv[i+1]
    if baud and port and timeout:
      serial_port = start_serial_connection(baud=baud,
                                                    port=port,timeout=timeout)
    return serial_port
  
  def start_gps_reader():
    serial_port = get_serial_connection_with_system_args()
    if serial_port:
      while True:
        try:
          response = get_gpgga_string_from_serial(serial_port)
          if response:
            position = parse_gpgga_string(str(response))
            print "latitude %s" %  position.latitude
            print "lat cardinal %s" % position.lat_cardinal
            print "longitude %s" %  position.longitude
            print "lon cardinal %s" % position.lon_cardinal
            print "altitude %s" % position.altitude
            print "precision %s" % position.precision
            print "quality %s" % position.quality
        except Exception, e:
          print e
    else:
      print 'example usage: ./read_and_store_gps_data.py -baud 4800 -port /dev/ttyUSB0 -timeout 0.5'
      sys.exit()
  
 start_gps_reader()

Test the script, and see we now have position objects with useful attributes.

Step Five - use redis to make the latest position available

Why Redis? I'm doing this anyway - and have been hearing about redis all the time.  This script will only set the latest position.  The other parts of the solution will use the position.  Add to the script at the top:

import redis
import json

Now to use redis to store a position object as json, we only want a very recent position so we will set the time to live to 3 seconds. Directly underneath the start_gps_read() declaration insert the line:

redis_server = redis.Redis("localhost")

Replace the printing of the positions values in start_gps_reader() with the following lines:

redis.set("position:current",json.dumps(position.__dict__))
redis.expire("position:current",3)

Now exit vim and test the scripts' results


$ ./read_and_store_gps_data.py -baud 4800 -port /dev/ttyUSB0 -timeout 0.5 &
$ redis-cli
redis 127.0.0.1:6379> get position:current

$ ps-aux **look for the python process take note of its number
$ kill 1088 **where 1088 is the python processes number

Wait three seconds.

redis-cli
get position:current

Great there is only a 3 seconds or newer position available as position:current,  Thats all for this post, if you are interested to see what comes next then come back.

Thanks! Rimu Boddy.

Installing + testing Ubuntu 12.10, Redis, PySerial on Beaglebone

Step One - Install and login to Ubuntu 12.10 on the BeagleBone

Hardware used:
Beaglebone rev A6
4 GB Micro SD
Ubuntu desktop PC

Do this using an Ubuntu or Debian PC - it was not so easy using a mac

Install Ubuntu 12.10 following instructions in this link http://elinux.org/BeagleBoardUbuntu#Quantal_12.10_armhf

Download the image.

$ wget http://rcn-ee.net/deb/rootfs/quantal/ubuntu-12.10-console-armhf-2013-02-16.tar.xz
Verify sum against 'd452c17a9caff3db56d4d03083552f83'

$ md5sum ubuntu-12.10-console-armhf-2013-02-16.tar.xz

Unpack the image

$ tar xJf ubuntu-12.10-console-armhf-2013-02-16.tar.xz 
$ cd ubuntu-12.10-console-armhf-2013-02-16

Check the location of the sdcard

$ sudo ./setup_sdcard.sh --probe-mmc    
Or
$ sudo fdisk -l

Use the provided install script to install Ubuntu 12.10
Where /dev/sdc is the location of your Micro SD card

$ sudo ./setup_sdcard.sh --mmc /dev/sdc --uboot bone

Once the image was installed the beaglebone did not seem to boot.

The LED that flashes above the mini USB port indicates that all is working OK.  The first time the LED did not flash.

After plugging in the Ethernet cable, the LED began to flash for a short while then stopped.

After plugging the USB cord into the port at the back of the PC rather than the front and having Ethernet attached the Beagle booted OK.  Beaglebones, can stop running when Ethernet is attached and the power to the mini USB is too low.

Powering via the 5V power socket with a 2a power supply is advised.  And never fails in my exp.  Subsequent boot attempts have all been successful since.

To access 12.10 on Beaglebone:

$ screen /dev/ttyUSB0 115200

then waited for a few seconds and press 'enter'.

login: ubuntu
pass : temppwd

Step Two - Install Redis

There is the simpler way to install Redis without using Apt.  The apt route chosen for future convenience.  Install Redis via Apt following instructions on this link .  Exchanging the use of aptitude install redis-server with apt-get install redis-server.

Provide apt with access to the latest Redis version.  Create a file in the /etc/apt/sources.list.d directory

# touch /etc/apt/sources.list.d/dotdeb.org.list

Edit the file adding the following two lines

deb http://packages.dotdeb.org squeeze all
deb-src http://packages.dotdeb.org squeeze all

Authenticate the added repository using their public key.

wget -q -O - http://www.dotdeb.org/dotdeb.gpg | sudo apt-key add -

Update APT cache and install Redis.

$ sudo apt-get update 
$ sudo apt-get install redis-server

Quick test of Redis:

This link provides a quick and simple Redis setup & test.

$ redis-server
$ redis-cli ping
$ redis-cli

redis 127.0.0.1:6379> set foo:bar baz

redis 127.0.0.1:6379> get foo:bar


Install redis python driver via pip

$sudo pip install redis


Redis just worked right out of the box.

Step Three - install pyserial

Install python-setuptools via apt.  (easy_install)

$ apt-get install python-setuptools

Install pip via easy_install

$ sudo easy_install pip 

Install pyserial via pip

$ sudo pip install pyserial

Quick test

# python

>> import serial

And were all done

Next blog we do something with it.