Index: pyglider/trunk/pyglider/pyglider.py
===================================================================
--- (revision )
+++ pyglider/trunk/pyglider/pyglider.py (revision 470)
@@ -1,0 +1,522 @@
+"""pyglider.py A module of utilities to run on dockserver for glider operations
+
+ parse_glider_mail
+ generate_track_kml
+
+"""
+
+REAL_RE_STR = '\\s*(-?\\d(\\.\\d+|)[Ee][+\\-]\\d\\d?|-?(\\d+\\.\\d*|\\d*\\.\\d+)|-?\\d+)\\s*'
+
+import sys
+import os
+import re
+
+import time
+import datetime
+
+def load_data(inFile):
+ lines=None
+ if os.path.exists(inFile):
+ f = open(inFile, 'r')
+ lines = f.readlines()
+ f.close()
+ if len(lines)<=0:
+ print 'Empty file: '+ inFile
+ else:
+ print 'File does not exist: '+ inFile
+ return lines
+
+def display_time_diff(diff):
+ """Display time difference in HH:MM and days (D) if necessary"""
+ days = diff.days
+ minutes, seconds = divmod(diff.seconds, 60)
+ hours, minutes = divmod(minutes, 60)
+ if (days<=1):
+ if minutes<1:
+ str = "%02:%02d:%02d" % (hours, minutes,seconds)
+ else:
+ str = "%02d:%02d" % (hours, minutes)
+ elif (days>1):
+ str = "%d Days %02d:%02d" % (days, hours, minutes)
+ else:
+ str = "%02d:%02d" % (hours, minutes)
+ return str
+
+# -------------------------------------------------------------------
+# playground
+# fn = '/var/spool/mail/localuser'
+# lines = load_data(fn)
+# glider = 'ramses'
+# data = parse_glider_mail(lines, glider)
+
+# -------------------------------------------------------------------
+def parse_glider_mail(lines, glider):
+ msg_split_patt = r'From root\@dockserver'
+ msgs = re.split(msg_split_patt, ''.join(lines))
+
+ gms = []
+ # select messages for specific glider based on "Subject:" line
+ for msg in msgs:
+ m = re.search(r'^Subject: Glider: (\w*)', msg, re.MULTILINE)
+ if m:
+ if m.group(1) == glider:
+ gms.append(msg)
+
+ data = []
+ for msg in gms:
+ m = re.search(r'^Subject: Glider: (\w*).*$', msg, re.MULTILINE)
+ if m:
+ subject_string = m.group(0)
+ subject_glider = m.group(1)
+ # print subject_string
+ else:
+ continue
+
+ m = re.search(r'\s*(Event)*: (.*?)', subject_string, re.MULTILINE)
+ if m:
+ subject_event = m.group(2)
+ else:
+ subject_event = None
+
+ m = re.search(r'\s*(Reason)*:\s*(.*)$', subject_string, re.MULTILINE)
+ if m:
+ subject_reason = m.group(2)
+ else:
+ subject_reason = None
+
+ m = re.search(r'^(Vehicle Name:)\s+(\w*)', msg, re.MULTILINE)
+ if m: glidername = m.group(2)
+ else: glidername = None
+
+ m = re.search(r'^(Curr Time:)\s+(.*)\s+MT:', msg, re.MULTILINE)
+ if m:
+ try:
+ t = time.strptime(m.group(2), "%a %b %d %H:%M:%S %Y")
+ # the '*' operator unpacks the tuple, producing the argument list.
+ dt = datetime.datetime(*t[0:6])
+ diff = datetime.datetime.utcnow() - dt
+ hours_ago = display_time_diff(diff)
+ dt_str = datetime.date.strftime(dt, "%Y-%m-%d %H:%M:%S UTC")
+ if (diff.days)>0:
+ # dt_str_short = datetime.date.strftime(dt, "%b-%d")
+ dt_str_short = ""
+ if (diff.days) <= 0:
+ dt_str_short = datetime.date.strftime(dt, "%H:%M")
+ except ValueError, e:
+ dt_str = None
+ dt_str_short = None
+ hours_ago = None
+
+ m = re.search(r'^(GPS Location:)\s+(-?\d{2})(\d{2}\.\d+)\s+([NnSs])'+ \
+ r'\s+(-?\d{2})(\d{2}\.\d+)\s+([EeWw]).*$', msg, re.MULTILINE)
+ if m:
+ #
+ lat_deg = float(m.group(2))
+ lat_min = float(m.group(3))
+ lat_hem = m.group(4).upper()
+ #
+ lon_deg = float(m.group(5))
+ lon_min = float(m.group(6))
+ lon_hem = m.group(7).upper()
+ if lat_deg<0:
+ lat = lat_deg - lat_min/60.
+ else:
+ lat = lat_deg + lat_min/60.
+ if lon_deg<0:
+ lon = lon_deg - lon_min/60.
+ else:
+ lon = lon_deg + lon_min/60.
+ gps_str = m.group(0)
+ m = re.search(r'GPS Location: (.*) measured', gps_str)
+ if m: gps_posn_str = m.group(1)
+ else: gps_posn_str = None
+ m = re.search(r'measured\s*(\d*)\.\d* secs ago', gps_str)
+ if m:
+ gps_secs_ago = int(m.group(1))
+ gps_dt = dt - datetime.timedelta(0,gps_secs_ago,0)
+ gps_dt_str = datetime.date.strftime(gps_dt, "%Y-%m-%d %H:%M:%S UTC")
+ else: gps_dt_str = None
+ else:
+ lat = None
+ lon = None
+
+ m = re.search(r'(MT:)\s+(.*)$', msg, re.MULTILINE)
+ if m: mt = m.group(2)
+ else: mt = None
+
+ m = re.search(r'(sensor:m_battery.*?=)\s*(-?\d+\.\d*)', msg, re.MULTILINE)
+ if m: batt = float(m.group(2))
+ else: batt = None
+
+ m = re.search(r'(sensor:m_leakdetect.*?=)\s*(-?\d+\.\d*)', msg, re.MULTILINE)
+ if m: leak = float(m.group(2))
+ else: leak = None
+
+ m = re.search(r'(sensor:m_vacuum.*?=)\s*(-?\d+\.\d*)', msg, re.MULTILINE)
+ if m: vacuum = float(m.group(2))
+ else: vacuum = None
+
+ m = re.search(r'^(Because:)\s*(.*)', msg, re.MULTILINE)
+ if m: because = m.group(2)
+ else: because = 'Unknown'
+
+ m = re.search(r'^(MissionName:)\s*(.*?)\s+', msg, re.MULTILINE)
+ if m: mission_name = m.group(2)
+ else: mission_name = 'Unknown'
+
+ m = re.search(r'\s+(MissionNum:)(.*)', msg, re.MULTILINE)
+ if m: mission_num = m.group(2)
+ else: mission_num = 'Unknown'
+
+ m = re.search(r'^(Waypoint:)\s+(\(.*\)).*$', msg, re.MULTILINE)
+ if m:
+ wp_str = m.group(0)
+ waypoint_posn = m.group(2)
+ else:
+ wp_str = None
+ waypoint_posn = 'Unknown'
+ waypoint_range = 'Unknown'
+ waypoint_bearing = 'Unknown'
+ waypoint_age = 'Unknown'
+ wlat=None
+ wlon=None
+
+ if wp_str:
+ # parse out the next waypoint for a place mark
+ m = re.search(r'\((-?\d{2})(\d{2}\.\d+),(-?\d{2})(\d{2}\.\d+)\)', waypoint_posn)
+ if m:
+ #
+ lat_deg = float(m.group(1))
+ lat_min = float(m.group(2))
+ #
+ lon_deg = float(m.group(3))
+ lon_min = float(m.group(4))
+ if lat_deg<0:
+ wlat = lat_deg - lat_min/60.
+ else:
+ wlat = lat_deg + lat_min/60.
+ if lon_deg<0:
+ wlon = lon_deg - lon_min/60.
+ else:
+ wlon = lon_deg + lon_min/60.
+ else:
+ wlat = None
+ wlon = None
+ m = re.search(r'(Range:)\s+(.*?),', wp_str)
+ if m: waypoint_range = m.group(2)
+ else: waypoint_range = 'Unknown'
+ m = re.search(r'(Bearing:)\s+(.*?),', wp_str)
+ if m: waypoint_bearing = m.group(2)
+ else: waypoint_bearing = 'Unknown'
+ m = re.search(r'(Age:)\s+(.*?)$', wp_str)
+ if m: waypoint_age = m.group(2)
+ else: waypoint_age = 'Unknown'
+
+ if lat and lon:
+ # generate report table for google earth
+ # using ![CDATA[ {html} ]] inside of KML description tag
+ html_report = [
+ ' ',
+ '
',
+ '
Surface Report
',
+ '
',
+ '',
+ 'Glider: %s | %s |
' % (glider, dt_str,),
+ '',
+ '']
+ html_report.extend(
+ [
+ 'GPS Location: | %s |
' % (gps_posn_str,),
+ 'GPS Time: | %s |
' % (gps_dt_str,)
+ ])
+ html_report.extend(
+ [
+ 'Surface Event: | %s |
' % (subject_event,),
+ 'Surface Reason: | %s |
' % (subject_reason,),
+ 'Surface Because: | %s |
' % (because,),
+ ])
+ html_report.extend(
+ [
+ 'Mission Name: | %s |
' % (mission_name,),
+ 'Mission Number: | %s |
' % (mission_num,),
+ 'Mission Time: | %s |
' % (mt,)
+ ])
+
+ if batt:
+ if batt < 10.:
+ html_report.append('Battery (volts): | %g |
' % (batt,))
+ elif (batt>=10) and (batt<11):
+ html_report.append('Battery (volts): | %g |
' % (batt,))
+ else:
+ html_report.append('Battery (volts): | %f |
' % (batt,))
+ else:
+ html_report.append('Battery (volts): | Unknown |
')
+
+ if vacuum:
+ if (vacuum>=10) or (vacuum<6):
+ html_report.append('Vacuum (inHg): | %g |
' % (vacuum,))
+ else:
+ html_report.append('Vacuum (inHg): | %g |
' % (vacuum,))
+ else:
+ html_report.append('Vacuum (inHg): | Unknown |
')
+ # need tolerances for flagging leak detect in report
+ if leak:
+ html_report.append('Leak Detect (volts): | %f |
' % (leak,))
+ else:
+ html_report.append('Leak Detect (Volts): | Unknown |
')
+ #
+ html_report.extend(
+ [
+ 'Waypoint: | %s |
' % (waypoint_posn,),
+ 'Waypoint Range: | %s |
' % (waypoint_range,),
+ 'Waypoint Bearing: | %s |
' % (waypoint_bearing,),
+ 'Waypoint Age: | %s |
' % (waypoint_age,)
+ ])
+ # close the report tbody table div and CDATA
+ html_report.extend(
+ ['',
+ '
',
+ '
',
+ ' '
+ ])
+ # make a readable CDATA string
+ html_report_str = '\n'.join(html_report)
+ # can create a KML object since extracted lat and lon
+ data.append({'glider': glider,
+ 'dt': dt,
+ 'datetime': dt_str,
+ 'name': dt_str_short,
+ 'hours_ago': hours_ago,
+ 'lat': lat,
+ 'lon': lon,
+ 'description': html_report_str,
+ 'wlat': wlat,
+ 'wlon': wlon,
+ })
+ else:
+ # if no lat lon, can't create a KML object without lat and lon
+ # but want to append info to last known surface report
+ html_report = [
+ '',
+ '
',
+ '
',
+ '',
+ 'Glider: %s | %s |
' % (glider, dt_str,),
+ '',
+ '']
+ html_report.extend(
+ ['Glider Location | -- |
',
+ 'GPS Fix: | %s |
' % (gps_posn_str,),
+ 'Time of Fix: | %s |
' % (gps_dt_str,)
+ ])
+ # close the report tbody table div and CDATA
+ html_report.extend(
+ ['',
+ '
',
+ '
',
+ ' '
+ ])
+
+ # close for-loop of msg in gms:
+ return data
+
+def generate_track_kml(data, glider):
+ """ Use pykml to generate kml file for each glider from parsed mail data
+ A glider track consists of a line and placemarks for each surfacing
+
+ Usage: kml_doc_str = surface_report_kml(data, glider)
+ """
+
+ from pykml.factory import KML_ElementMaker as KML
+ import lxml.etree
+
+ # ***** append LookAt after checking that lat, lon exist in data[-1]
+ d = data[-1]
+ # print '(%f, %f)' % (d['lon'], d['lat'])
+
+ # start a KML file skeleton with track styles
+ doc = KML.kml(
+ KML.Document(
+ KML.Name(glider + "_track"),
+ KML.LookAt(
+ KML.longitude('%f' % d['lon']),
+ KML.latitude('%f' % d['lat']),
+ KML.heading('0'),
+ KML.tilt('0'),
+ KML.range('60000'),
+ KML.altitudeMode('absolute')
+ ),
+ KML.Style(
+ KML.IconStyle(
+ KML.scale(0.7),
+ KML.color("ff00ff00"),
+ KML.Icon(KML.href("icons/square.png"))
+ ),
+ KML.LabelStyle(
+ KML.color("ff00ff00"),
+ KML.scale(0.8)),
+ id="lastPosnIcon")
+ )
+ )
+ doc.Document.append(
+ KML.Style(
+ KML.IconStyle(
+ KML.scale(0.8),
+ KML.color("ff0000ff"),
+ KML.Icon(KML.href("icons/cross-hairs.png"))),
+ KML.LabelStyle(
+ KML.scale(0.8)),
+ id="lastWayPosnIcon")
+ )
+ doc.Document.append(
+ KML.Style(
+ KML.IconStyle(
+ KML.scale(0.7),
+ KML.color("7d00ffff"),
+ KML.Icon(KML.href("icons/donut.png"))),
+ KML.LabelStyle(
+ KML.color("7d00ffff"),
+ KML.scale(0.8)),
+ id="prevPosnIcon")
+ )
+ doc.Document.append(
+ KML.Style(
+ KML.IconStyle(
+ KML.scale(0.9),
+ KML.color("ff00ff00"),
+ KML.Icon(KML.href("icons/donut.png"))),
+ KML.LabelStyle(
+ KML.color("ff00ff00"),
+ KML.scale(0.7)),
+ id="histPosnIcon")
+ )
+ doc.Document.append(
+ KML.Style(
+ KML.IconStyle(
+ KML.scale(0.8),
+ KML.color("7d0000ff"),
+ KML.Icon(KML.href("icons/cross-hairs.png"))),
+ KML.LabelStyle(
+ KML.scale(0.8)),
+ id="histWayPosnIcon")
+ )
+ doc.Document.append(
+ KML.Style(
+ KML.LineStyle(
+ KML.color("7dff0000"),
+ KML.width(2)
+ ),
+ id="transBlueLine")
+ )
+ doc.Document.append(
+ KML.Style(
+ KML.LineStyle(
+ KML.color("7d00ff00"),
+ KML.width(2)
+ ),
+ id="transGreenLine")
+ )
+
+ if glider == 'ramses':
+ linestyle = "#transBlueLine"
+ elif glider == 'pelagia':
+ linestyle = "#transGreenLine"
+ else:
+ linestyle = ""
+
+ coord_str = ""
+ for d in data:
+ if d['lat'] and d['lon']:
+ coord_str = coord_str + "%f,%f,%f\n" % (d['lon'], d['lat'], 0.)
+
+ track_line = KML.Placemark(
+ KML.name(glider),
+ KML.styleUrl(linestyle),
+ KML.LineString(
+ KML.altitudeMode("absolute"),
+ KML.coordinates(coord_str)
+ )
+ )
+ # glider placemarks (pms)
+ pms = []
+ for d in data[:-1]:
+ pms.append(
+ KML.Placemark(
+ # short time stamp
+ KML.name(d['name']),
+ # surface report data
+ KML.description(d['description']),
+ KML.styleUrl("#prevPosnIcon"),
+ KML.Point(
+ KML.altitudeMode("absolute"),
+ KML.coordinates("%f,%f,%f" % (d['lon'], d['lat'], 0.))
+ )
+ )
+ )
+ # glider history placemarks (hpms) using timestamp tag
+ histpms = []
+ for idx, d in enumerate(data[:-1]):
+ dt=d['dt']
+ # YYYY-MM-DDTHH:MM:SS + Z for kml
+ dt_str_begin = dt.isoformat()+'Z'
+ dt_str_end = (data[idx+1]['dt']).isoformat()+'Z'
+
+ histpms.append(
+ KML.Placemark(
+ # ISO time stamp to be displayed with marker
+ KML.name(dt_str_begin),
+ # surface report data (not for history placemarks
+ # KML.description(d['description']),
+ KML.styleUrl("#histPosnIcon"),
+ KML.Point(
+ KML.altitudeMode("absolute"),
+ KML.coordinates("%f,%f,%f" % (d['lon'], d['lat'], 0.))
+ ),
+ KML.TimeSpan(
+ KML.begin(dt_str_begin),
+ KML.end(dt_str_end)
+ )
+ )
+ )
+
+
+ d=data[-1]
+ last_pm = KML.Placemark(
+ # short time stamp
+ KML.name(d['name']),
+ # surface report data
+ KML.description(d['description']),
+ KML.styleUrl("#lastPosnIcon"),
+ KML.Point(
+ KML.altitudeMode("absolute"),
+ KML.coordinates("%f,%f,%f" % (d['lon'], d['lat'], 0.))
+ )
+ )
+ if d['wlon'] and d['wlat']:
+ wp_pm = KML.Placemark(
+ KML.name('Target Waypoint'),
+ # surface report data
+ KML.description(''),
+ KML.styleUrl("#lastWayPosnIcon"),
+ KML.Point(
+ KML.altitudeMode("absolute"),
+ KML.coordinates("%f,%f,%f" % (d['wlon'], d['wlat'], 0.))
+ )
+ )
+ track_folder = KML.Folder(
+ KML.name(d['glider']),
+ track_line,
+ )
+ for pm in pms:
+ track_folder.append(pm)
+ for hpm in histpms:
+ track_folder.append(hpm)
+ track_folder.append(last_pm)
+ if d['wlon'] and d['wlat']:
+ track_folder.append(wp_pm)
+ doc.Document.append(
+ track_folder
+ )
+ track_kml = lxml.etree.tostring(doc, pretty_print=True)
+ return track_kml
+