def TimeUtc(fields): seconds, fractional_seconds = FloatSplit(float(fields['seconds'])) microseconds = int(math.floor(fractional_seconds * 1e6)) fields['seconds'] = seconds fields['microseconds'] = microseconds fields['hours'] = util.MaybeToNumber(fields['hours']) fields['minutes'] = util.MaybeToNumber(fields['minutes']) when = datetime.time(fields['hours'], fields['minutes'], seconds, microseconds) fields['when'] = when
def Zda(line): try: fields = ZDA_RE.match(line).groupdict() except TypeError: return for field in ('year', 'month', 'day', 'hours', 'minutes', 'zone_hours', 'zone_minutes'): if fields[field] is not None and fields[field]: fields[field] = util.MaybeToNumber(fields[field]) seconds, fractional_seconds = FloatSplit(float(fields['seconds'])) microseconds = int(math.floor(fractional_seconds * 1e6)) when = datetime.datetime( fields['year'], fields['month'], fields['day'], fields['hours'], fields['minutes'], seconds, microseconds) # TODO(schwehr): Convert this to Unix UTC seconds. result = { 'msg': 'ZDA', 'talker': fields['talker'], 'datetime': when, 'zone_hours': fields['zone_hours'], 'zone_minutes': fields['zone_minutes'], } return result
def Txt(line): """Decode Text Transmission (TXT). TODO(schwehr): Handle encoded characters. e.g. ^21 is a '!'. Args: line: A string containing a NMEA TXT message. Return: A dictionary with the decoded fields. """ try: fields = TXT_RE.match(line).groupdict() except TypeError: return result = { 'msg': 'TXT', 'talker': fields['talker'], 'text': fields['text'], } for field in ('sen_tot', 'sen_num', 'seq_num'): result[field] = util.MaybeToNumber(fields[field]) return result
def Fsr(line): try: fields = FSR_RE.match(line).groupdict() except TypeError: return seconds, fractional_seconds = FloatSplit(float(fields['seconds'])) microseconds = int(math.floor(fractional_seconds * 1e6)) when = datetime.time( int(fields['hours']), int(fields['minutes']), seconds, microseconds ) result = { 'msg': 'FSR', 'id': fields['id'], 'chan': fields['chan'], 'time': when, } for field in ('slots_recv', 'slots_self', 'crc_fails', 'slots_reserved', 'slots_reserved_self', 'noise_db', 'slots_above_noise'): if fields[field] is not None and fields[field]: result[field] = util.MaybeToNumber(fields[field]) return result
def HandleGga(line): try: fields = GGA_RE.match(line).groupdict() except TypeError: return seconds, fractional_seconds = FloatSplit(float(fields['seconds'])) microseconds = int(math.floor(fractional_seconds * 1e6)) when = datetime.time(int(fields['hours']), int(fields['minutes']), seconds, microseconds) x = int(fields['lon_deg']) + float(fields['lon_min']) / 60.0 if fields['longitude_hemisphere'] == 'W': x = -x y = int(fields['lat_deg']) + float(fields['lat_min']) / 60.0 if fields['latitude_hemisphere'] == 'S': y = -y result = { 'message': 'GGA', 'time': when, 'longitude': x, 'latitude': y, } for field in ('gps_quality', 'satellites', 'hdop', 'antenna_height', 'antenna_height_units', 'geoidal_height', 'geoidal_height_units', 'differential_ref_station', 'differential_age_sec'): if fields[field] is not None and fields[field]: result[field] = util.MaybeToNumber(fields[field]) return result
def Parse(data): """Unpack a TAG Block line or return None. Makes sure that the line matches the regex and the checksum matches. Args: data: Line of text or a dict from at TAG_BLOCK_RE. Returns: A NMEA TAG Block dict or None if the line would not parse or has an invalid checksum. """ if isinstance(data, str): try: result = TAG_BLOCK_RE.search(data).groupdict() except AttributeError: return elif isinstance(data, dict): result = data else: return result.update({k: util.MaybeToNumber(v) for k, v in six.iteritems(result) if k in NUMERIC_FIELDS}) actual = nmea.Checksum(result['metadata']) expected = result['tag_checksum'].upper() if actual != expected: return return result
def Abk(line): """Decode AIS Addressed and Binary Broadcast Acknowledgement (ABK).""" try: fields = ABK_RE.match(line).groupdict() except TypeError: return result = { 'msg': 'ABK', 'talker': fields['talker'], 'chan': fields['chan'], } for field in ('mmsi', 'msg_id', 'seq_num', 'ack_type'): result[field] = util.MaybeToNumber(fields[field]) return result
def Bbm(line): """Decode Binary Broadcast Message (BBM) sentence.""" try: fields = BBM_RE.match(line).groupdict() except TypeError: return result = { 'msg': 'BBM', 'talker': fields['talker'], 'body': fields['body'], } for field in ('sen_tot', 'sen_num', 'seq_num', 'chan', 'msg_id', 'fill_bits'): result[field] = util.MaybeToNumber(fields[field]) return result
def HandleAds(line): """Decode Automatic Device Status (ADS).""" try: fields = ADS_RE.match(line).groupdict() except TypeError: return TimeUtc(fields) return { 'message': 'ADS', 'talker': fields['talker'], 'id': fields['id'], 'alarm': fields['alarm'], 'time_sync_method': util.MaybeToNumber(fields['time_sync_method']), 'pos_src': fields['pos_src'], 'time_src': fields['time_src'], 'when': fields['when'], }
def Parse(data): """Unpack a USCG old metadata format line or return None. Makes sure that the line matches the regex and the checksum matches. Args: data: Line of text. Returns: A vdm dict or None and a metadata dict or None. """ try: result = USCG_RE.search(data).groupdict() except AttributeError: return None result.update({k: util.MaybeToNumber(v) for k, v in six.iteritems(result) if k in NUMERIC_FIELDS}) return result
def Parse(data): """Unpack a NMEA VDM AIS message line(s).""" if not isinstance(data, six.string_types): raise NotImplementedError try: result = VDM_RE.search(data).groupdict() except AttributeError: return result.update({ k: util.MaybeToNumber(v) for k, v in six.iteritems(result) if k in NUMERIC_FIELDS }) actual = nmea.Checksum(result['vdm']) expected = result['checksum'] if actual != expected: return return result
def put(self, line, line_num=None): if line_num is not None: self.line_num = line_num else: self.line_num += 1 line = line.rstrip() metadata_match = Parse(line) match = vdm.Parse(line) if not match: logging.info('not match') msg = { 'line_nums': [self.line_num], 'lines': [line], } if metadata_match: msg['match'] = metadata_match Queue.Queue.put(self, msg) return if not metadata_match: logging.info('not metadata match') self.unknown_queue.put(line) if not self.unknown_queue.empty(): msg = Queue.Queue.get() self.put(msg) return match.update(metadata_match) if 'station' not in match: match['station'] = 'rUnknown' sentence_tot = int(match['sen_tot']) if sentence_tot == 1: body = match['body'] fill_bits = int(match['fill_bits']) try: decoded = ais.decode(body, fill_bits) except ais.DecodeError as error: logging.error( 'Unable to decode message: %s\n %d %s', error, self.line_num, line) return decoded['md5'] = hashlib.md5(body.encode('utf-8')).hexdigest() Queue.Queue.put(self, { 'line_nums': [line_num], 'lines': [line], 'decoded': decoded, 'matches': [match] }) return station = match['station'] or 'rUnknown' sentence_num = int(match['sen_num']) sequence_id = match['seq_id'] or '' group_id = station + str(sequence_id) time = util.MaybeToNumber(match['time']) if group_id not in self.groups: self.groups[group_id] = [] if not self.groups[group_id]: if sentence_num != 1: # Drop a partial AIS message. return if sentence_num == 1: self.groups[group_id] = { 'line_nums': [self.line_num], 'lines': [line], 'matches': [match], 'times': [time], } return entry = self.groups[group_id] entry['line_nums'].append(self.line_num) entry['lines'].append(line) entry['matches'].append(match) entry['times'].append(time) if sentence_num != sentence_tot: # Found the middle part of a message. return decoded = DecodeMultiple(entry) if decoded: entry['decoded'] = decoded else: logging.info('Unable to process: %s', entry) Queue.Queue.put(self, entry) self.groups.pop(group_id)
def put(self, line, line_num=None): if line_num is not None: self.line_num = line_num else: self.line_num += 1 line = line.rstrip() match = Parse(line) if not match: Queue.Queue.put(self, { 'line_nums': [self.line_num], 'lines': [line], }) return time = util.MaybeToNumber(match['time']) if not match['group']: msg = { 'line_nums': [self.line_num], 'lines': [line], 'matches': [match], 'times': [time], } decoded = DecodeTagSingle(msg) if decoded: msg['decoded'] = decoded else: logging.info('Unable to decode. Passing without decoded block.') decoded = nmea_messages.Decode(match['payload']) if decoded: msg['decoded'] = decoded else: logging.info('No NMEA match for line: %d, %s', line_num, line) Queue.Queue.put(self, msg) return sentence_num = int(match['sentence_num']) sentence_total = int(match['sentence_tot']) group_id = int(match['group_id']) if sentence_num == 1: self.groups[group_id] = { 'line_nums': [self.line_num], 'lines': [line], 'matches': [match], 'times': [time], } return if group_id not in self.groups: logging.error('group_id not in groups: %d', group_id) return entry = self.groups[group_id] entry['line_nums'].append(self.line_num) entry['lines'].append(line) entry['matches'].append(match) entry['times'].append(time) if sentence_num != sentence_total: # Found the middle part of a message. return # Found the final message in a group. decoded = DecodeTagMultiple(entry) if decoded: entry['decoded'] = decoded else: logging.info('Unable to process: %s', entry) Queue.Queue.put(self, entry) self.groups.pop(group_id)
def testMaybeToNumber(self): self.assertEqual(util.MaybeToNumber(None), None) self.assertEqual(util.MaybeToNumber([]), []) self.assertEqual(util.MaybeToNumber({}), {}) self.assertEqual(util.MaybeToNumber('a'), 'a') self.assertEqual(util.MaybeToNumber(1), 1) self.assertEqual(util.MaybeToNumber(-3.12), -3.12) self.assertEqual(util.MaybeToNumber('-1'), -1) self.assertIsInstance(util.MaybeToNumber('-1'), int) self.assertEqual(util.MaybeToNumber('42.0'), 42.0) self.assertIsInstance(util.MaybeToNumber('42.0'), float) value = 9999999999999999999999999 value_str = '9999999999999999999999999' self.assertEqual(util.MaybeToNumber(value_str), value) self.assertIsInstance(util.MaybeToNumber(value_str), six.integer_types) self.assertEqual( util.MaybeToNumber('1e99999999999999999999999'), float('inf')) self.assertEqual( util.MaybeToNumber('-1e99999999999999999999999'), float('-inf'))