Coverage for ion/agents/instrumentagents/helper_NMEA0183 : 69.81%
Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
|
#!/usr/bin/env python
@file ion/agents/instrumentagents/helper_NMEA0183.py @author Alon Yaari @brief Helper code for working with NMEA0183 devices and parsing NMEA0183 strings """
# Constants
""" Additional error codes unique to NMEA strings. """
""" Instructions to parse known NMEA strings. """
{1: 'GPS Fix (SPS)'}, {2: 'DGPS Fix'}, {3: 'PPS Fix'}, {4: 'Real Time Kinematic'}, {5: 'Float RTK'}, {6: 'Estimated (dead reckon)'}, {7: 'Manual Input Mode'}, {8: 'Simulation Mode'})
'D': 'Differential', 'E': 'Estimated', 'N': 'Not Valid', 'S': 'Simulated'}
# NMEA_CD string 5-character NMEA data type code # DESC string Short description of the NMEA sentence # UTC_HMS int ot float UTC time on a 24hr clock as HHMMSS.S (1hz GPS as HHMMSS)ex: 123519.2 = 12:35:19.2 # UTC_DMY int 6-digit date as ddmmyy ex: 250411 # RAW_LAT float Lat as ddmm.mmm, needs converting to decimal degrees ex: 4807.038 = 48deg 7.038min # LAT_DIR char N or S (if S, lat is negative) # RAW_LON float Lon as dddmm.sss, needs converting to decimal degrees ex: 01131.051 = 11deg 31.051min # LON_DIR char E or W (if W, lon is negative) # FIX_QUA int See fixQuality list above ex: 2 = DGPS Fix # NUM_SAT int Number of tracked satellites (<10 may have leading 0) ex: 08 = 8 satellites # HOR_DOP float Relative accuracy of horizontal position (in m) ex: 0.9 = .9 meters HDOP # ALT_MSL float Altitude above mean sea level (MSL) ex: 545.4 = 545.4 alt MSL # MSLUNIT char Units for altitude mean sea level, M = meters # ALT_GEO float Altitude above mean geoid (usually WGS84?) ex: 342.7 = 342.7 alt MSL # GEOUNIT char Units for altitude geoid, M = meters # DATA_AC char See dataActive list above ex: A # STATUS char A=Valid position V=NVA error ex: A # SPD_GND float Speed over ground in knots ex: 22.4 # TRK_DEG float Track angle in degrees relative to true north ex: 95.3 # RAW_MAG float Magnetic variation in degrees ex: 003.3 # MAG_DIR char Direction of magnetic variation E=east W=west ex: E # WEEK_NO int GPS week number (0 to 1023) # SECONDS int GPS seconds (0 to 604799) # LEAPSEC int GPS leap second count
'PGRMO': ['Output Sentence Enable/Disable', #0 'TARGET', #1 'PGRMODE'], #2
'PGRMC': ['Garmin Sensor Configuration Information', # 0 'FIX_MODE', # 1 'ALT_MSL', # 2 'E_DATUM', # 3 'SM_AXIS', # 4 'DATUMIFF', # 5 'DATUM_DX', # 6 'DATUM_DY', # 7 'DATUM_DZ', # 8 'DIFFMODE', # 9 'BAUD_RT', # 10 'VEL_FILT', # 11 'MP_OUT', # 12 'MP_LEN', # 13 'DED_REC']} # 14
'GPGGA': {'Parsing': ['GPS Fix Data', #0 'UTC_HMS', #1 'RAW_LAT', #2 'LAT_DIR', #3 'RAW_LON', #4 'LON_DIR', #5 'FIX_QUA', #6 'NUM_SAT', #7 'HOR_DOP', #8 'ALT_MSL', #9 'MSLUNIT', #10 'ALT_GEO', #11 'GEOUNIT'], #12 'Output': ['NMEA_CD', 'DESC', 'HOUR', 'MIN', 'SEC', 'MS', # Only if GPS > 1hz 'GPS_LAT', 'GPS_LON', 'FIX_QUA', 'NUM_SAT', 'HDOP', 'ALT_MSL', 'MSLUNIT', 'ALT_GEO', 'GEOUNIT', 'DATA_AC']}, 'OOIXX': {'Parsing': ['OOI Custom Sentence', #0 'UTC_HMS', #1 'RAW_LAT', #2 'LAT_DIR', #3 'RAW_LON', #4 'LON_DIR', #5 'FIX_QUA', #6 'NUM_SAT', #7 'HOR_DOP', #8 'ALT_MSL', #9 'MSLUNIT', #10 'COURSE', #11 'SPD_MPS'], #12 'Output': ['NMEA_CD', 'DESC', 'HOUR', 'MIN', 'SEC', 'MS', # Only if GPS > 1hz 'GPS_LAT', 'GPS_LON', 'FIX_QUA', 'NUM_SAT', 'HDOP', 'ALT_MSL', 'MSLUNIT', 'ALT_GEO', 'COURSE', 'SPD_MPS', 'SPD_KPH']}, 'XXXXX': {'Parsing': ['Dummy Heartbeat', # 0 'IGNORE'], # 1 'Output': ['NMEA_CD']}, 'GPGLL': {'Parsing': ['GPS Latitude and Longitude', # 0 'RAW_LAT', # 1 'LAT_DIR', # 2 'RAW_LON', # 3 'LON_DIR', # 4 'UTC_HMS', # 5 'DATA_AC'], # 6 'Output': ['NMEA_CD', 'DESC', 'GPS_LAT', 'GPS_LON', 'HOUR', 'MIN', 'SEC', 'MS', # Only if GPS > 1hz 'DATA_AC']}, 'GPRMC': {'Parsing': ['Recommended Minimum Senence C', # 0 'UTC_HMS', # 1 'STATUS', # 2 'RAW_LAT', # 3 'LAT_DIR', # 4 'RAW_LON', # 5 'LON_DIR', # 6 'SPD_KTS', # 7 'TRK_DEG', # 8 'DATE', # 9 'RAW_MAG' # 10 'MAG_DIR'], # 11 'Output': ['HOUR', 'MIN', 'SEC', 'MS', # Only if GPS > 1hz 'STATUS', 'GPS_LAT', 'GPS_LON', 'SPD_KTS', 'SPD_MPS', 'TRK_DEG', 'DAY', 'MONTH', 'YEAR', 'MAG_VAR']}, 'PGRMC': {'Parsing': ['Garmin Sensor Configuration Information', # 0 'FIX_MODE', # 1 'ALT_MSL', # 2 'E_DATUM', # 3 'SM_AXIS', # 4 'DATUMIFF', # 5 'DATUM_DX', # 6 'DATUM_DY', # 7 'DATUM_DZ', # 8 'DIFFMODE', # 9 'BAUD_RT', # 10 'IGNORE', # 11 'MP_OUT', # 12 'MP_LEN', # 13 'DED_REC'], # 14 'Output': ['FIX_MODE', 'ALT_MSL', 'E_DATUM', 'SM_AXIS', 'DATUMIFF', 'DATUM_DX', 'DATUM_DY' 'DATUM_DZ' 'DIFFMODE', 'BAUD_RT', 'MP_OUT', 'MP_LEN', 'DED_REC']}, 'PGRMF': {'Parsing': ['Garmin GPS Fix Data Sentence', # 0 'WEEK_NO', # 1 'SECONDS', # 2 'UTC_DMY', # 3 'UTC_HMS', # 4 'LEAPSEC', # 5 'RAW_LAT', # 6 'LAT_DIR', # 7 'RAW_LON', # 8 'LON_DIR', # 9 'GPSMODE', # 10 'FIXTYPE', # 11 'SPD_KPH', # 12 'COURSE', # 13 'PDOP', # 14 'TDOP'], # 15 'Output': ['DAY', 'MONTH', 'YEAR', 'HOUR', 'MIN', 'SEC', 'MS', # Only if GPS > 1hz 'GPS_LAT', 'GPS_LON', 'GPSMODE', 'FIXTYPE', 'SPD_KTS', 'SPD_MPS', 'COURSE', 'PDOP', 'TDOP']}}
""" Representation of a single ASCII NMEA command (sent to the NMEA device). """
""" Takes the input NMEA string through the parsing process. """
""" Checks that an NMEA string is valid. @retval OK if valid NMEA string, otherwise relevant error code """
# Rules for a valid input NMEA string: # - Must always start with '$' # - Must always end with <CR>, <LF>, or <CR><LF> # - Maximum 82 characters, inclusive of '$' and <CR><LF> # - Must have 5 chars immediately after '$' # - Therefore, minimum string is "$XXXXX<CR><LF>" len = 8
# Validate NMEA string length return NMEAErrorCode.INVALID_NMEA_STRING
# Validate '$'
# Strip off <CR>, <LF>, and <CR><LF>
# Verify that there is at least one data element to parse # 0 1 2 # $ X ,<end> minimum possible NMEA string except ValueError: return NMEAErrorCode.INVALID_NMEA_STRING
# Strip out NMEA type code and verify it is known to this parser return NMEAErrorCode.UNKNOWN_NMEA_CODE
return NMEAErrorCode.is_error (self._valid)
""" If the NMEA string was valid, returns the parsed data. @retval Dict of GPS data, otherwise error code """
return parsedOK
""" Main parsing routine for an NMEA input string. @retval Dict of nmeaDefs and parsed values or error relevant code """
return NMEAErrorCode.UNKNOWN_NMEA_CODE
# Must have enough data elements to parse return NMEAErrorCode.INVALID_DATA_ITEMS
# NMEA_CD string 5-character NMEA data type code
# DESC string Short description of the NMEA sentence
# TARGET string For $PFRMO, the sentence to turn on or off
# PGRMODE int Target sentence mode
# FIX_MODE char Combine of FIX_TYPE and GPS_MODE # A = Automatic, 3 = 3D only self._dataOut['FIX_MODE'] = item
# E_DATUM int Earth datum ID number self._dataOut['E_DATUM'] = item
# SM_AXIS Relevant only if E_DATUM == USERDEF pass
# DATUMIFF Relevant only if E_DATUM == USERDEF pass
# DATUM_DX Relevant only if E_DATUM == USERDEF pass
# DATUM_DY Relevant only if E_DATUM == USERDEF pass
# DATUM_DZ Relevant only if E_DATUM == USERDEF pass
# DIFFMODE char Differential mode self._dataOut['DIFFMODE'] = item
# BAUD_RT int NMEA 0183 Baud Rate self._dataOut['BAUD_RT'] = item
# MP_OUT int Measurement Pulse Output self._dataOut['MP_OUT'] = item
# MP_LEN int Measurement Pulse Output pulse length ((n+1)* 20ms) self._dataOut['MP_LEN'] = item
# DED_REC float Ded. Reckoning valid time 0.2 to 30.0 sec self._dataOut['DED_REC'] = item
""" Representation of a single ASCII NMEA string (from the NMEA device). """
""" Takes the NMEA string through the entire parsing process. @param nmeaString Complete NMEA line from $ to <CR><LF> """
""" Reports on the validity of the NMEA string. @retval OK, otherwise relevant error code """
""" If the NMEA string was valid, returns the parsed data. @retval Dict of GPS data, otherwise error code """
return parsedOK
""" Calculates NMEA checksum and compares it with the in-string value. @param nmeaCS Only the characters between '$' and '*' exclusive @param checkH left (most sig.) hex nibble of the NMEA's checksum @param checkL right (least sig.) hex nibble of the NMEA's checksum @retval OK if checksums match, otherwise relevant error code """
# Validate the hex nibbles return NMEAErrorCode.INVALID_NMEA_STRING
# Calculate checksum # checksum = 8-bit XOR of all chars in string # result is an 8-bit number (0 to 255) high += 7;
# Validate calculated against what the NMEA string said it should be
""" Checks that an NMEA string is valid. @retval OK if valid NMEA string, otherwise relevant error code """
# Rules for a valid NMEA string: # - Must always start with '$' # - Must always end with <CR><LF> # - Maximum 82 characters, inclusive of '$' and <CR><LF> # - Must have 5 chars immediately after '$' # - Therefore, minimum string is "$XXXXX<CR><LF>" len = 8 # - Optional checksum: # - Indicated with a '*' after last data element, before <CR><LF> # - two-printable chars between '*' and <CR><LF> # - Checksum = 8-bit XOR of all chars between $ and * exclusive
# Validate NMEA string length return NMEAErrorCode.INVALID_NMEA_STRING
# Validate '$'
# Properly formed NMEA strings must end in <CR><LF> # Reality is that some devices don't do it or that middleware changes # it to some variant. It is also convenient in testing to not have # them in place. # Therefore, this code does not enforce the presence of <CR><LF> and # in fact allows any combination of <CR>, <LF>, both, or none.
# Verify that there is at least one data element to parse # 0 1 2 # $ X ,<end> minimum possible NMEA string
# Strip out NMEA type code and verify it is known to this parser
# Validate CEHCKSUM (if there is one) # Since <CR><LF> was stripped off, '*' marking the # checksum will always be at location [-3] # -3 -2 -1 # * F F<end> self.nmeaStr[-2], self.nmeaStr[-1])
else:
""" Converts string to float for NMEA string processing. @param inStr Input float as a string @retval Float value or gpsNAN on not a float value error """
""" Converts DDMM.MMMM or DDDMM.MMMM into DD.DDDDDDDD. @param inFloat Input float value @retval Float in decimal degrees """
""" Main parsing routine for an NMEA string. @retval Dict of nmeaDefs and parsed values or error relevant code """
return NMEAErrorCode.UNKNOWN_NMEA_CODE return NMEAErrorCode.INTERNAL_ERROR
# Must have enough data elements to parse return NMEAErrorCode.INVALID_DATA_ITEMS
# Break apart the data values and deal with each type
# NMEA_CD string 5-character NMEA data type code
# DESC string Short description of the NMEA sentence
# UTC_HMS double UTC time on a 24hr clock as HHMMSS.S # (1hz GPS as HHMMSS) # ex: 123519.2 = 12:35:19.2 # Valid time is HHMMSS or HHMMSS.S
# RAW_LAT double Lat as ddmm.mmm, needs converting to dec. deg. # ex: 4807.038 = 48deg 7.038min
# LAT_DIR char N or S (if S, lat is negative)
# RAW_LON double Lon as dddmm.sss, needs converting to dec. deg. # ex: 01131.051 = 11deg 31.051min
# LON_DIR char E or W (if W, lon is negative)
# FIX_QUA int See fixQuality list above # ex: 2 = DGPS Fix
# NUM_SAT int Number of tracked satellites (<10 may have leading 0) # ex: 08 = 8 satellites
# HOR_DOP double Relative accuracy of horizontal position (in m) # ex: 0.9 = .9 meters HDOP
# ALT_MSL double Altitude above mean sea level (MSL) # ex: 545.4 = 545.4 alt MSL
# MSLUNIT char Units for altitude mean sea level # M = meters
# ALT_GEO double Altitude above mean geoid (usually WGS84?) # ex: 342.7 = 342.7 alt MSL dataOut['ALT_GEO'] = rawAlt
# GEOUNIT char Units for altitude geoid # M = meters
# DATA_AC char See dataActive list above # ex: A da = NMEADefs.dataActive.get (item, 'NODT') if da != 'NODT': dataOut['DATA_AC'] = da
# GPSMODE char Mode is manual or automatic if item == 'M': dataOut['GPSMODE'] = item else: dataOut['GPSMODE'] = 'A'
# FIXTYPE char Whether fixed # fixed to 2D or has 3D fix if item == '3': dataOut['FIXTYPE'] = '3D' elif item == '2': dataOut['FIXTYPE'] = '2D' else: dataOut['FIXTYPE'] = 'NOFIX'
# SPD_KPH double Speed over ground in kph kph = self.NMEAStrToFloat (item) if kph != gpsNAN: mps = kph * 0.277777778 dataOut['SPD_KPH'] = kph dataOut['SPD_MPS'] = mps
# SPEEDMS double Speed over ground in meters per second mps = self.NMEAStrToFloat(item) if mps != gpsNAN: kph = mps / 0.277777778 dataOut['SPD_KPH'] = kph dataOut['SPD_MPS'] = mps
# COURSE int Track heading # 0-359 degrees if item.isdigit(): course = int (item) if course >= 0 and course <= 359: dataOut['COURSE'] = course
# PDOP int Position dilution of precision # 0 to 9 rounded to nearest int if item.isdigit(): dop = int (item) if dop >= 0 and dop <= 359: dataOut['PDOP'] = dop
# TDOP int Time dilution of precision # 0 to 9 rounded to nearest int if item.isdigit(): dop = int (item) if dop >= 0 and dop <= 359: dataOut['TDOP'] = dop
# FIX_MODE char Combine of FIX_TYPE and GPS_MODE # A = Automatic, 3 = 3D only if item == '3': dataOut['FIX_MODE'] = '3D' else: dataOut['FIX_MODE'] = 'AUTO'
# E_DATUM int Earth datum ID number if item.isdigit(): datum = int (item) if datum == 96: dataOut['E_DATUM'] = 'USERDEF' if datum == 100: dataOut['E_DATUM'] = 'WGS84' else: dataOut['E_DATUM'] = 'NOT_WGS84'
# SM_AXIS Relevant only if E_DATUM == USERDEF pass
# DATUMIFF Relevant only if E_DATUM == USERDEF pass
# DATUM_DX Relevant only if E_DATUM == USERDEF pass
# DATUM_DY Relevant only if E_DATUM == USERDEF pass
# DATUM_DZ Relevant only if E_DATUM == USERDEF pass
# DIFFMODE char Differential mode if item == 'A': dataOut['DIFFMODE'] = 'AUTO' else: dataOut['DIFFMODE'] = 'DIFF_ONLY'
# BAUD_RT int NMEA 0183 Baud Rate if item.isdigit(): baud = int (item) if baud == 3: dataOut['BAUD_RT'] = '4800' elif baud == 4: dataOut['BAUD_RT'] = '9600' elif baud == 5: dataOut['BAUD_RT'] = '19200' elif baud == 6: dataOut['BAUD_RT'] = '300' elif baud == 7: dataOut['BAUD_RT'] = '600' elif baud == 8: dataOut['BAUD_RT'] = '38400'
# MP_OUT int Measurement Pulse Output if item.isdigit(): mpo = int (item) if mpo == 2: dataOut['MP_OUT'] = 'ENABLED' else: dataOut['MP_OUT'] = 'DISABLED'
# MP_LEN int Measurement Pulse Output pulse length ((n+1)* 20ms) if item.isdigit(): dataOut['MP_LEN'] = (1 + int (item)) * 20
# DED_REC float Ded. Reckoning valid time 0.2 to 30.0 sec dr = self.NMEAStrToFloat (item) if dr >= 0.2 or dr <= 30.0: dataOut['DED_REC'] = dr
|