def test_nmea_multipart_sentence_reassembly(self): """ test the ability to recieve multiple NMEA 0183 sentences and join them together into one AIS message """ expected = ('000101000011000111100001100010000000000' '10000001001001111000100100011001' '1010100001001100000111011010010000010000' '000011000100100010000010100110' '0001001001111100000100000100000100000100000' '10000010000010000010000010000010000010000010' '00000101100100101000000001100000111100001000' '010011100010011100000001001111000010000001010' '010010010000101001001010010001111100000100000' '100000100000100000100000100000100000100000100' '0001000001000000000') testsentences = [('!AIVDM,2,1,2,B,537QR042Ci8kD9PsB20HT' '@DhTv2222222222221I:0H?24pW0ChPDTQB,0*49'), '!AIVDM,2,2,2,B,DSp888888888880,2*7A'] testtracker = nmea.NMEAtracker() for sentence in testsentences: processed = testtracker.process_sentence(sentence) if processed: binarypayload = binary.ais_sentence_payload_binary(processed) self.assertEqual(expected, binarypayload)
def __init__(self): tkinter.Tk.__init__(self) self.nmeatracker = nmea.NMEAtracker() self.aistracker = ais.AISTracker() self.messagelog = allmessages.AISMessageLog() self.protocol("WM_DELETE_WINDOW", self.quit) self.title('PY AIS NMEA - ' + version.VERSION) self.statuslabel = tkinter.Label(self, text='', bg='light grey') self.statuslabel.pack(fill=tkinter.X) self.tabcontrol = TabControl(self) self.tabcontrol.pack(expand=1, fill='both') self.top_menu() self.mpq = multiprocessing.Queue() self.updateguithread = None self.refreshguithread = None self.serverprocess = None self.serverrunning = False self.stopevent = threading.Event() self.forwardsentences = tkinter.BooleanVar() self.forwardsentences.set(0) self.kmzlivemap = tkinter.BooleanVar() self.kmzlivemap.set(0) self.livemap = None self.timingsources = [] self.currentupdatethreadid = None self.currentrefreshthreadid = None
def test_nmea_stats(self): """ feed in NMEA sentences and check that stats are computed correctly """ testsentences = [ '!AIVDM,1,1,,A,13P6>F002lwce04NvkaT<CPGH<02,0*69', '!AIVDM,1,1,,A,13P6>F002jwceJDNvk1T<SPcH8Og,0*2F', '!AIVDM,1,1,,A,13P6>F002kwcf6dNvj0T=kQ?H@3Q,0*27', ('!AIVDM,2,1,5,A,53P6>F42;si4mPhOJ208Dr0mV0<Q8DF22222' '220t41H;==8cN<R1FDj0,0*39'), '!AIVDM,2,2,5,A,CH8888888888880,2*2A', '!AIVDM,1,1,,A,13P6>F002lwcfRPNviF4;CQUH8:s,0*72', '!AIVDM,1,1,,B,33P6>F002lwcfgDNvi4T>SQgH50S,0*40', '!AIVDM,1,1,,B,13P6>F002lwcg8`NvhLT>kP;HL02,0*3C' ] testtracker = nmea.NMEAtracker() for sentence in testsentences: testtracker.process_sentence(sentence) teststats = testtracker.nmea_stats() expectedstats = { 'Total Sentences Processed': 8, 'Multipart Messages Reassembled': 1, 'Messages Recieved on Channel': { 'A': 6, 'B': 2 } } self.assertDictEqual(teststats, expectedstats)
def test_empty_value_for_cog(self): """ this sentence doesn't appear to have a value for course over ground the binary module should raise a NoBinaryData exception """ testsentence = '!AIVDM,1,1,,A,3O>soN5MUNBoMdUdlh,0*64' nmeatracker = nmea.NMEAtracker() testdata = nmeatracker.process_sentence(testsentence) testbinarystr = binary.ais_sentence_payload_binary(testdata) with self.assertRaises(binary.NoBinaryData): binary.decode_sixbit_integer(testbinarystr[116:128]) / 10
def __init__(self, outputpath, kmzoutput=False): self.kmzoutput = kmzoutput self.outputpath = outputpath self.mpq = multiprocessing.Queue() self.serverprocess = None if not os.path.exists(outputpath): AISLOGGER.info('output path does not exist creating directories') os.makedirs(outputpath) self.netlinkpath = os.path.join(outputpath, 'open_this.kml') self.kmlpath = os.path.join(outputpath, 'livemapdata.kml') self.logpath = os.path.join(outputpath, 'nmea-sentence-log.txt') self.aistracker = ais.AISTracker() self.nmeatracker = nmea.NMEAtracker() if kmzoutput: self.copy_icons()
def aistracker_from_file(filepath, debug=False, timingsource=None): """ open a file, read all nmea sentences and return an ais.AISTracker object Note: if debug is set then individual messages are saved into the messagelog Args: filepath(str): full path to nmea file debug(bool): save all message payloads and decoded attributes into messagelog timingsource(list): MMSIs of the base stations you wish to use as a time reference, type 4 base station reports from this base station will be used for times. default is None and all base stations will be used for times. list of strings Raises: NoSuitableMessagesFound: if there are no AIS messages in the file Returns: aistracker(ais.AISTracker): object that keeps track of all the ships we have seen nmeatracker(nmea.NMEAtracker): object that organises the nmea sentences messagelog(allmessages.AISMessageLog): object with all the AIS messages """ messagelog = allmessages.AISMessageLog() aistracker = ais.AISTracker() aistracker.timingsource = timingsource nmeatracker = nmea.NMEAtracker() msgnumber = 1 for line in open_file_generator(filepath): try: payload = nmeatracker.process_sentence(line) if payload: msg = aistracker.process_message(payload) if debug: messagelog.store(msgnumber, payload, msg) msgnumber += 1 except (nmea.NMEAInvalidSentence, nmea.NMEACheckSumFailed, ais.UnknownMessageType, ais.InvalidMMSI, binary.NoBinaryData, IndexError) as err: AISLOGGER.debug(str(err)) continue if aistracker.messagesprocessed == 0: raise NoSuitableMessagesFound('No AIS messages detected in this file') return (aistracker, nmeatracker, messagelog)
def extract_time_data_from_file(filepath): """ find the base stations and timing data from NMEA text files Args: filepath(str): path to the nmea0183 text file Returns: timingchoices(dict): keys are numbers, values are MMSIs of base stns basestntable(list): list of lists, each list is a row for an AIS base stn with its MMSI, flag, total messages, first and last known timestamps Raises: NoSuitableMessagesFound: if there are no type 4 messages in the file there is no usable timestamps """ nmeatracker = nmea.NMEAtracker() basestntracker = ais.BaseStationTracker() for line in open_file_generator(filepath): try: payload = nmeatracker.process_sentence(line) if payload: basestntracker.process_message(payload) except (nmea.NMEAInvalidSentence, nmea.NMEACheckSumFailed, ais.UnknownMessageType, ais.InvalidMMSI, binary.NoBinaryData, IndexError) as err: AISLOGGER.debug(str(err)) continue if basestntracker.messagesprocessed == 0: raise NoSuitableMessagesFound('No AIS Base Stations detected') basestnmainheader = [ 'MMSI', 'Flag', 'Total Messages', 'First Known Time', 'Last Known Time' ] basestnposheader = ['Time'] basestntable = basestntracker.create_table_data( csvheader=basestnmainheader, posheaders=basestnposheader) basestntable[0].insert(0, 'Choice') timingchoices = {} stncount = 1 for stn in basestntracker.stations: timingchoices[str(stncount)] = stn basestntable[stncount].insert(0, stncount) stncount += 1 return timingchoices, basestntable
def read_from_file(filepath, outpath, everything=False, filetype='text', orderby='Types', region='A'): """ read AIS NMEA sentences from a text file and save to various output formats Note: a text file containing stats and a basic summary, a KMZ map, JSON + CSV containing details of AIS stations and JSONLINES + CSV of all AIS messages are generated by default Args: filepath(str): full path to the input file containing NMEA sentences outpath(str): path to save to excluding file extensions everything(bool): whether to output files for every individual station filetype(str): what type of file are we reading from options are text, csv or jsonlines orderby(str): order KML/KMZ output and Everything station folders by 'Types', 'Flags' or 'Class', default is 'Types' region(str): IALA region 'A' or 'B', default is 'A' """ if not os.path.exists(outpath): AISLOGGER.info('output path does not exist creating directories') os.makedirs(outpath) AISLOGGER.info('processed output will be saved in %s', outpath) AISLOGGER.info('reading nmea sentences from - %s', filepath) timesources = [] try: if filetype == 'text': try: AISLOGGER.info('importing as text file') basestnchoices, basestntable = extract_time_data_from_file( filepath) AISLOGGER.info('choose timing source') choiceconfirmed = False while not choiceconfirmed: print_table(basestntable) choice = input('enter timing source choice number: ') try: basestnmmsi = basestnchoices[choice.rstrip()] if basestnmmsi not in timesources: timesources.append(basestnmmsi) except KeyError: AISLOGGER.error('enter a choice no!') continue AISLOGGER.info('use %s as a time reference', basestnmmsi) yesno = input('Y/N: ') if yesno.rstrip() in ('Y', 'y', 'yes', 'YES'): AISLOGGER.info('timing sources to be used - %s', timesources) yesno2 = input('add another timing source? Y/N: ') if yesno2.rstrip() in ('N', 'n', 'no', 'NO'): basestntimingsource = timesources choiceconfirmed = True except NoSuitableMessagesFound as err: basestntimingsource = None AISLOGGER.error(str(err)) aistracker, nmeatracker, messagelog = aistracker_from_file( filepath, debug=True, timingsource=basestntimingsource) elif filetype == 'csv': AISLOGGER.info('importing as CSV file') aistracker, messagelog = aistracker_from_csv(filepath, debug=True) elif filetype == 'jsonlines': AISLOGGER.info('importing as JSON lines file') aistracker, messagelog = aistracker_from_json(filepath, debug=True) if filetype in ('csv', 'jsonlines'): nmeatracker = nmea.NMEAtracker() nmeatracker.sentencecount = 'N/A' nmeatracker.reassembled = 'N/A' except (FileNotFoundError, NoSuitableMessagesFound) as err: AISLOGGER.info(str(err)) sys.exit(1) export.export_overview(aistracker, nmeatracker, messagelog, outpath, printsummary=True, orderby=orderby, region=region) if everything: export.export_everything(aistracker, messagelog, outpath, orderby=orderby, region=region) AISLOGGER.info('Finished')