From b5914dd492fa4716737bbe43f4abcad5258b6396 Mon Sep 17 00:00:00 2001 From: Tim Redfern Date: Fri, 4 May 2012 17:59:13 +0100 Subject: working with image maps --- gps.py | 313 ++++++++++++++++++++++++++ gps_serial.py~ | 80 +++++++ gpspoller.py | 88 ++++++++ gpspoller.pyc | Bin 0 -> 2696 bytes gpstest.py | 14 ++ indexmapV1.gif | Bin 0 -> 3235 bytes latLng.pyc | Bin 0 -> 1095 bytes layers.py | 29 ++- layers.pyc | Bin 0 -> 3862 bytes testreceive.pd | 18 +- tomorrowtheground.py | 35 ++- tomorrowthegroundGUI/data/indexmapV1.gif | Bin 0 -> 3233 bytes tomorrowthegroundGUI/tomorrowthegroundGUI.pde | 24 +- ttg01.xml | 8 +- xml2obj.pyc | Bin 0 -> 3905 bytes 15 files changed, 589 insertions(+), 20 deletions(-) create mode 100644 gps.py create mode 100644 gps_serial.py~ create mode 100644 gpspoller.py create mode 100644 gpspoller.pyc create mode 100644 gpstest.py create mode 100644 indexmapV1.gif create mode 100644 latLng.pyc create mode 100644 layers.pyc create mode 100644 tomorrowthegroundGUI/data/indexmapV1.gif create mode 100644 xml2obj.pyc diff --git a/gps.py b/gps.py new file mode 100644 index 0000000..336615c --- /dev/null +++ b/gps.py @@ -0,0 +1,313 @@ +# ******!*********!*********!*********!*********!*********!*********!********* +# +# Copyright (c) IBM Corporation, 2006. All Rights Reserved. +# Author: Simon Johnston (skjohn@us.ibm.com) +# +# Simple class which can interface to a serially connected GPS device that +# implements the NMEA standard. The reference I used was found at: +# http://www.gpsinformation.org/dale/nmea.htm +# The class currently decodes the following sentences: +# GLL, GSA, GSV, RMC +# +# Classes: +# GPSError - exception for GPSDevice (also wraps serial errors). +# GPSDevice - GPS device interface (serial) class. +# +# Functions: +# format_date - convert date from NMEA to ISO format. +# format_time - convert time from NMEA to ISO format. +# format_latlong - convert date from NMEA to standard decimal format. +# +# ******!*********!*********!*********!*********!*********!*********!********* + +import datetime + +import serial + +class GPSError(Exception): + """ Signal errors in the GPS communication, both NMEA sentence errors + as well as wrapping up underlying serial I/O errors. + """ + pass + +class GPSDevice(object): + """ General GPS Device interface for connecting to serial port GPS devices + using the default communication params specified by the National Marine + Electronics Association (NMEA) specifications. + """ + def __init__(self, commport): + """ GPSDevice(port) + Connects to the serial port specified, as the port numbers are + zero-based on windows the actual device string would be "COM" + + port+1. + """ + self.commport = commport + self.port = None + + def open(self): + """ open() + open the GPS device port, the NMEA default serial I/O parameters are + defined as 4800,8,N,1. + """ + nmea_params = { + 'port': self.commport, + 'baudrate': 4800, + 'bytesize': serial.EIGHTBITS, + 'parity': serial.PARITY_NONE, + 'stopbits': serial.STOPBITS_ONE + } + if self.port: + raise GPSError, 'Device port is already open' + try: + self.port = serial.Serial(**nmea_params) + self.port.open() + except serial.SerialException: + raise GPSError, 'Caught serial error opening port, is device connected?' + + def read(self): + """ read() -> dict + rRad a single NMEA sentence from the device returning the data as a + dictionary. The 'sentence' key will identify the sentence type itself + with other parameters extracted and nicely formatted where possible. + """ + sentence = 'error' + line = self._read_raw() + if line: + parsed = self._validate(line) + if parsed: + if _decode_func.has_key(parsed[0]): + return _decode_func[parsed[0]](parsed) + else: + sentence = parsed[0] + return { + 'sentence': sentence + } + + def read_all(self): + """ read_all() -> dict + A generator allowing the user to read data from the device in a for loop + rather than having to craft their own looping method. + """ + while 1: + try: + record = self.read() + except IOError: + raise StopIteration + yield record + + def close(self): + """ close() + Close the port, note you can no longer read from the device until you + re-open it. + """ + if not self.port: + raise GPSError, 'Device port not open, cannot close' + self.port.close() + self.port = None + + def _read_raw(self): + """ _read_raw() -> str + Internal method which reads a line from the device (line ends in \r\n). + """ + if not self.port: + raise GPSError, 'Device port not open, cannot read' + return self.port.readline() + + def _checksum(self, data): + """ _checksum(data) -> str + Internal method which calculates the XOR checksum over the sentence (as + a string, not including the leading '$' or the final 3 characters, the + ',' and checksum itself). + """ + checksum = 0 + for character in data: + checksum = checksum ^ ord(character) + hex_checksum = "%02x" % checksum + return hex_checksum.upper() + + def _validate(self, sentence): + """ _validate(sentence) -> str + Internal method. + """ + sentence.strip() + if sentence.endswith('\r\n'): + sentence = sentence[:len(sentence)-2] + if not sentence.startswith('$GP'): + # + # Note that sentences that start with '$P' are proprietary + # formats and are described as $P where MID is the + # manufacturer identified (Magellan is MGN etc.) and then the + # SID is the manufacturers sentence identifier. + # + return None + star = sentence.rfind('*') + if star >= 0: + check = sentence[star+1:] + sentence = sentence[1:star] + sum = self._checksum(sentence) + if sum <> check: + return None + sentence = sentence[2:] + return sentence.split(',') + +# +# The internal decoder functions start here. +# +def format_date(datestr): + """ format_date(datestr) -> str + Internal function. Turn GPS DDMMYY into DD/MM/YY + """ + year = int(datestr[4:]) + now = datetime.date.today() + if year + 2000 > now.year: + year = year + 1900 + else: + year = yeat + 2000 + the_date = datetime.date(year, int(datestr[2:4]), int(datestr[:2])) + return the_date.isoformat() + +def format_time(timestr): + """ format_time(timestr) -> str + Internal function. Turn GPS HHMMSS into HH:MM:SS UTC + """ + utc_str = '+00:00' + the_time = datetime.time(int(timestr[:2]), int(timestr[2:4]), int(timestr[4:])) + return the_time.strftime('%H:%M:%S') + utc_str + +def format_latlong(data, direction): + """ format_latlong(data, direction) -> str + Internal function. Turn GPS HHMM.nnnn into standard HH.ddddd + """ + # Check to see if it's HMM.nnnn or HHMM.nnnn or HHHMM.nnnn + dot = data.find('.') + if (dot > 5) or (dot < 3): + raise ValueError, 'Incorrect formatting of "%s"' % data + hours = data[0:dot-2] + mins = float(data[dot-2:]) + if hours[0] == '0': + hours = hours[1:] + if direction in ['S', 'W']: + hours = '-' + hours + decimal = mins / 60.0 * 100.0 + decimal = decimal * 10000.0 + return '%s.%06d' % (hours, decimal) + +def _convert(v, f, d): + """ convert(v, f, d) -> value + Internal function. + """ + try: + return f(v) + except: + return d + +def _decode_gll(data): + """ decode_gll(date) -> dict + Internal function. + """ + return { + 'sentence': data[0], + 'latitude': '%s' % format_latlong(data[1], data[2]), + 'longitude': '%s' % format_latlong(data[3], data[4]), + 'time': format_time(data[5]), + 'active': data[6] + } + +def _decode_gga(data): + """ decode_gga(date) -> dict + Internal function. + """ + quality = ['invalid', 'GPS', 'DGPS', 'PPS', 'Real TIme', 'Float RTK', + 'Estimated', 'Manual', 'Simulation'] + qindex = _convert(data[6], int, '') + if qindex >= len(quality): + qstring = str(qindex) + else: + qstring = quality[qindex] + return { + 'sentence': data[0], + 'time': format_time(data[1]), + 'latitude': '%s' % format_latlong(data[2], data[3]), + 'longitude': '%s' % format_latlong(data[4], data[5]), + 'quality': qstring, + 'tracked': _convert(data[7], int, ''), + 'dilution': _convert(data[8], float, ''), + 'altitude': '%s,%s' % (data[9], data[10]), + 'geoid_height': '%s,%s' % (data[11], data[12]) + } + +def _decode_gsa(data): + """ decode_gsa(date) -> dict + Internal function. + """ + return { + 'sentence': data[0], + 'selection': data[1], + '3dfix': _convert(data[2], int, ''), + 'prns': data[3:14], + 'pdop': convert(data[15], float, ''), + 'horizontal_dilution': _convert(data[16], float, ''), + 'vertical_dilution': _convert(data[17], float, ''), + } + +def _decode_gsv(data): + """ decode_gsv(date) -> dict + Internal function. + """ + return { + 'sentence': data[0], + 'satelite': _convert(data[2], int, ''), + 'inuse': _convert(data[1], int, ''), + 'inview': _convert(data[3], int, ''), + 'prn': _convert(data[4], int, ''), + 'elevation': _convert(data[5], float, ''), + 'azimuth': _convert(data[6], float, ''), + 'snr': _convert(data[7], int, '') + } + +def _decode_rmc(data): + """ decode_rmc(date) -> dict + Internal function. + """ + return { + 'sentence': data[0], + 'time': format_time(data[1]), + 'active': data[2], + 'latitude': '%s' % format_latlong(data[3], data[4]), + 'longitude': '%s' % format_latlong(data[5], data[6]), + 'speed': _convert(data[7], float, ''), + 'angle': _convert(data[8], float, ''), + 'date': format_date(data[9]), + 'variation': '%s,%s' % (data[10], data[11]) + } + +# +# Simple dictionary mapping the sentence types to their +# corresponding decoder functions. +# +_decode_func = { + 'GLL': _decode_gll, + 'GSA': _decode_gsa, + 'GSV': _decode_gsv, + 'RMC': _decode_rmc, +} + +# +# Simple test case, this can be used to run indefinitely (formatting and printing +# each record) or run until it gets a GLL response and print the machines location. +# +if __name__ == '__main__': + import sys + port = 4 + gps = GPSDevice(port) + gps.open() + for record in gps.read_all(): + if sys.argv[0] == 'forever': + print record + else: + if record['sentence'] == 'GLL': + print 'I was at long %s, lat %s at %s' % ( + record['longitude'], + record['latitude'], + record['time']) + break diff --git a/gps_serial.py~ b/gps_serial.py~ new file mode 100644 index 0000000..c25581b --- /dev/null +++ b/gps_serial.py~ @@ -0,0 +1,80 @@ +#!/usr/bin/python + +# Copyright (C) 2007 by Jaroslaw Zachwieja +# Published under the terms of GNU General Public License v2 or later. +# License text available at http://www.gnu.org/licenses/licenses.html#GPL + +import serial +import string + +gps = serial.Serial('/dev/ttyUSB0', 4800, timeout=1) +file = '/tmp/nmea.kml' + +print "Serving data" + +latitude = 0 +longitude = 0 +speed = 0 +heading = 0 +altitude = 0 +range = 1000 +tilt = 30 + +while 1: + line = gps.readline() + datablock = line.split(',') + + if line[0:6] == '$GPRMC': + latitude_in = string.atof(datablock[3]) + longitude_in = string.atof(datablock[5]) + altitude = string.atof(datablock[8]) + speed_in = string.atof(datablock[7]) + heading = string.atof(datablock[8]) + + if datablock[4] == 'S': + latitude_in = -latitude_in + if datablock[6] == 'W': + longitude_in = -longitude_in + + latitude_degrees = int(latitude_in/100) + latitude_minutes = latitude_in - latitude_degrees*100 + + longitude_degrees = int(longitude_in/100) + longitude_minutes = longitude_in - longitude_degrees*100 + + latitude = latitude_degrees + (latitude_minutes/60) + longitude = longitude_degrees + (longitude_minutes/60) + + speed = int(speed_in * 1.852) + range = ( ( speed / 100 ) * 350 ) + 650 + tilt = ( ( speed / 120 ) * 43 ) + 30 + + if speed < 10: + range = 200 + tilt = 30 + heading = 0 + + output = """ + + + %s km/h + ^ + + %s + %s + %s + %s + %s + + + %s,%s,%s + + +""" % (speed,longitude,latitude,range,tilt,heading,longitude,latitude,altitude) + + f=open(file, 'w') + f.write(output) + f.close() + +ser.close() + diff --git a/gpspoller.py b/gpspoller.py new file mode 100644 index 0000000..6f7c19c --- /dev/null +++ b/gpspoller.py @@ -0,0 +1,88 @@ +#!/usr/bin/python + +# Copyright (C) 2007 by Jaroslaw Zachwieja +# Modified (C) 2012 by Tim Redfern +# Published under the terms of GNU General Public License v2 or later. +# License text available at http://www.gnu.org/licenses/licenses.html#GPL + +import serial,string,threading,time,sys,random + +class GpsPoller(threading.Thread): + + latitude = 0 + longitude = 0 + changed = False + + def __init__(self,port,test=False): + self.test=test + self.port=port + self.gps = serial.Serial(port, 9600, timeout=1) + threading.Thread.__init__(self) + + def check(self): + if self.changed: + self.changed=False + return (self.latitude,self.longitude) + else: + return False + + def run(self): + if self.test: + print "GpsPoller: serving random data" + else: + print "GpsPoller: serving data from",self.port + try: + while True: + if self.test: + self.latitude=random.random()*90 + self.longitude=random.random()*90 + self.changed=True + time.sleep(0.5) + else: + line = self.gps.read(1) + line = line+self.gps.readline() + datablock = line.split(',') + + if line[0:6] == '$GPRMC': + latitude_in = string.atof(datablock[3]) + longitude_in = string.atof(datablock[5]) + altitude = string.atof(datablock[8]) + speed_in = string.atof(datablock[7]) + heading = string.atof(datablock[8]) + + if datablock[4] == 'S': + latitude_in = -latitude_in + if datablock[6] == 'W': + longitude_in = -longitude_in + + latitude_degrees = int(latitude_in/100) + latitude_minutes = latitude_in - latitude_degrees*100 + + longitude_degrees = int(longitude_in/100) + longitude_minutes = longitude_in - longitude_degrees*100 + + latitude = latitude_degrees + (latitude_minutes/60) + longitude = longitude_degrees + (longitude_minutes/60) + + if latitude!=self.latitude or longitude!=self.longitude: + self.latitude=latitude + self.longitude=longitude + self.changed=True + + + except StopIteration: + pass + + def __del__(): + self.gps.close() + +if __name__ == '__main__': + + gpsp = GpsPoller(sys.argv[1]) + gpsp.start() + while 1: + # In the main thread, every 5 seconds print the current value + time.sleep(0.1) + check=gpsp.check() + if check!=False: + print "changed:",check[0],check[1] diff --git a/gpspoller.pyc b/gpspoller.pyc new file mode 100644 index 0000000..e8cda43 Binary files /dev/null and b/gpspoller.pyc differ diff --git a/gpstest.py b/gpstest.py new file mode 100644 index 0000000..88c5198 --- /dev/null +++ b/gpstest.py @@ -0,0 +1,14 @@ +import sys,gps +port = 6 +gps = GPSDevice(port) +gps.open() +for record in gps.read_all(): + if sys.argv[0] == 'forever': + print record + else: + if record['sentence'] == 'GLL': + print 'I am hanging out at long %s, lat %s at %s' % ( + record['longitude'], + record['latitude'], + record['time']) + break \ No newline at end of file diff --git a/indexmapV1.gif b/indexmapV1.gif new file mode 100644 index 0000000..63363bb Binary files /dev/null and b/indexmapV1.gif differ diff --git a/latLng.pyc b/latLng.pyc new file mode 100644 index 0000000..11ac9e8 Binary files /dev/null and b/latLng.pyc differ diff --git a/layers.py b/layers.py index 38aeba4..6c1d33f 100644 --- a/layers.py +++ b/layers.py @@ -18,7 +18,7 @@ class layer: except: print "gps layer: failed to parse", file def findpixel(self,pos): - return latLng(int((pos.lng-self.tl.lng)/self.pixsize.lng),int((pos.lat-self.br.lat)/self.pixsize.lat)) + return (int((pos.lng-self.tl.lng)/self.pixsize.lng),int((pos.lat-self.br.lat)/self.pixsize.lat)) def checkcoord(self,pos): p=self.findpixel(pos) if p!=self.pixel: @@ -27,7 +27,10 @@ class layer: else: return None def setcoord(self,pos): - return str(1.0) + """constructs a list of messages when values change" + returns None otherwise""" + result=None + return r class trigger(): """a generic trigger - @@ -43,6 +46,26 @@ class trigger(): return None class indexlayer(layer): + """generates gps triggers from an index colour image + triggers when colour changes""" triggers=[] + colour=-1 def setcoord(self,pos): - return str(1) \ No newline at end of file + result=None + #210 35 5 185 + c=self.image.getpixel(pos) + if c!=self.colour: + self.colour=c + print "indexlayer: new colour",c + for t in self.triggers: + if c==t.id: + result=t.command + return result + +class scalelayer(layer): + """generates a varying signal based on interpolating a greyscale image + uses sub pixel position""" + def findpixel(self,pos): + return (int((pos.lng-self.tl.lng)/self.pixsize.lng),int((pos.lat-self.br.lat)/self.pixsize.lat)) + def setcoord(self,pos): + result=None \ No newline at end of file diff --git a/layers.pyc b/layers.pyc new file mode 100644 index 0000000..e95e372 Binary files /dev/null and b/layers.pyc differ diff --git a/testreceive.pd b/testreceive.pd index c375170..d53a273 100644 --- a/testreceive.pd +++ b/testreceive.pd @@ -1,4 +1,14 @@ -#N canvas 796 273 450 300 10; -#X obj 126 54 netreceive 5401 1; -#X obj 126 96 print udp; -#X connect 0 0 1 0; +#N canvas 915 509 450 300 10; +#X obj 169 92 print udp; +#X msg 29 61 /pd 1; +#X msg 82 26 /other 1; +#X obj 169 50 netreceive 5401 1; +#X obj 148 141 OSCroute /play; +#X obj 145 210 print play; +#X obj 230 204 print other; +#X connect 1 0 4 0; +#X connect 2 0 4 0; +#X connect 3 0 0 0; +#X connect 3 0 4 0; +#X connect 4 0 5 0; +#X connect 4 1 6 0; diff --git a/tomorrowtheground.py b/tomorrowtheground.py index 2ffdf7e..5553b42 100755 --- a/tomorrowtheground.py +++ b/tomorrowtheground.py @@ -1,6 +1,21 @@ #!/usr/bin/python #UDP listener +import signal,sys + +def signal_handler(signal, frame): + insock.close() + print "tomorrowtheground: interrupted" + sys.exit(0) + +signal.signal(signal.SIGINT, signal_handler) + +import gpspoller +gpsp="" +if len(sys.argv)>1: + gpsp = GpsPoller(sys.argv[1]) + gpsp.start() + from latLng import * from layers import * from xml2obj import * @@ -8,7 +23,7 @@ from xml2obj import * doc=xml2obj(open("ttg01.xml")) gpslayers=[] for i in doc.gps.index: - #should catch invalid xml + #catch invalid xml g=indexlayer(i.file,i.ll1,i.ll2) for t in i.trigger: g.triggers.append(trigger(int(t.id),t.command,t.param)) @@ -20,7 +35,7 @@ GUI_IP="127.0.0.1" GUI_PORT=5400 insock = socket.socket( socket.AF_INET, socket.SOCK_DGRAM ) insock.bind( (GUI_IP,GUI_PORT) ) -insock.settimeout(0.01) +insock.settimeout(0.01) #non blocking PD_IP="127.0.0.1" PD_PORT=5401 outsock = socket.socket( socket.AF_INET,socket.SOCK_DGRAM ) @@ -35,11 +50,17 @@ while True: pos.parse(data) posChanged=True except: - nothing=None + nothing=None + if gpsp!="": + check=gpsp.check() + if check!=False: + pos=latLng(check[0],check[1]) + posChanged=True if posChanged: - print "received message:", data + posChanged=False for layer in gpslayers: - r=layer.checkcoord(pos) - if layer.checkcoord!=None: - outsock.sendto( r, (PD_IP, PD_PORT) ) + r=layer.checkcoord(pos) #returns a message or None + if r!=None: + #pd needs \n at end of message + outsock.sendto( str(r[0])+' '+str(r[1])+'\n', (PD_IP, PD_PORT) ) \ No newline at end of file diff --git a/tomorrowthegroundGUI/data/indexmapV1.gif b/tomorrowthegroundGUI/data/indexmapV1.gif new file mode 100644 index 0000000..3fcc16f Binary files /dev/null and b/tomorrowthegroundGUI/data/indexmapV1.gif differ diff --git a/tomorrowthegroundGUI/tomorrowthegroundGUI.pde b/tomorrowthegroundGUI/tomorrowthegroundGUI.pde index 0fe919e..b359409 100644 --- a/tomorrowthegroundGUI/tomorrowthegroundGUI.pde +++ b/tomorrowthegroundGUI/tomorrowthegroundGUI.pde @@ -1,15 +1,19 @@ import hypermedia.net.*; -PImage bgmap; +PImage[] bgmaps; +int usemap; UDP udp; int x,y; float lat1,lng1,lat2,lng2,fw,fh; void setup() { - bgmap = loadImage("gentmap.png"); + bgmaps=new PImage[2]; + bgmaps[0] = loadImage("gentmap.png"); + bgmaps[1] = loadImage("indexmapV1.gif"); + usemap=0; size(bgmap.width,bgmap.height); - frameRate(30); + frameRate(15); udp = new UDP(this); x=width/2; y=height/2; @@ -23,7 +27,7 @@ void setup() void draw() { - background(bgmap); + background(bgmaps[usemap]); fill(255); stroke(255,0,0); ellipse(x,y,5,5); @@ -39,3 +43,15 @@ void mouseDragged() udp.send(((fy*fh)+lat2)+","+((fx*fw)+lng1)+"\n","127.0.0.1",5400); } } + +void keyPressed() { + switch(key) { + case '0': + usemap=0; + break; + case '1': + usemap=1; + break; + } +} + diff --git a/ttg01.xml b/ttg01.xml index 3b3daa0..7b49bda 100644 --- a/ttg01.xml +++ b/ttg01.xml @@ -1,10 +1,14 @@ - + - + + + + + diff --git a/xml2obj.pyc b/xml2obj.pyc new file mode 100644 index 0000000..180a0bb Binary files /dev/null and b/xml2obj.pyc differ -- cgit v1.2.3