While ancient and modern mariners alike look up to the stars for direction, oceanographers look to the sea. Our research in particular involves moving with the water wherever it may happen to take us since we want to measure how a patch of water develops and changes over time.
On my latest cruise I found myself put into the role of de facto computer tech in charge of the navigation program we use for our scientific gear. Throughout the cruise two pieces of gear are nearly constantly in the water (the sediment trap and the drifter array), and in order for our science to work we need to be within a few hundred meters (less than a mile) from them at all times. This requires up-to-date GPS fixes and a reliable system for relaying these fixes to the bridge.
Historically we’ve used a custom piece of Matlab programing to read out the GPS coordinates and to provide a visual map of where the gear is relative to the ship. Although the Matlab program would freeze up from time to time and require a restart, it provided the necessary information in a convenient package. This works, at least most of the time, until you get a captain who wants it done their way. Our captain was over it and wanted us to come up with a new solution of providing locations and the task fell to me. So enter NMEA.
The National Marine Electronics Association (NMEA) writes up specifications on how computers and electronics should communicate with one another aboard ships. By ensuring a single standard, ship captains can be sure that the data they’re receiving are accurate and reliable regardless of what combination or brands their navigational systems use.
The standard that NMEA came up with allows all these systems to communicate using standard NMEA sentences which are encoded messages that contain information such as the longitude and latitude of the ship. For example, here are a few taken from this wonderful site:
GPGSA,A,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1*39
GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47" def checksum(sentence): """ Remove any newlines """ if re.search("\n
", "") nmeadata, cksum = re.split('\*', sentence) print(nmeadata) calc_cksum = 0 for s in nmeadata: calc_cksum ^= ord(s) chk = str(hex(calc_cksum))[-2:] return "
", "100100"], ["U", "%", "100101"], ["V", "&", "100110"], ["W", "'", "100111"], ["`", "(", "101000"], ["a", ")", "101001"], ["b", "*", "101010"], ["c", "+", "101011"], ["d", ",", "101100"], ["e", "-", "101101"], ["f", ".", "101110"], ["g", "/", "101111"], ["h", "0", "110000"], ["i", "1", "110001"], ["j", "2", "110010"], ["k", "3", "110011"], ["l", "4", "110100"], ["m", "5", "110101"], ["n", "6", "110110"], ["o", "7", "110111"], ["p", "8", "111000"], ["q", "9", "111001"], ["r", ":", "111010"], ["s", ";", "111011"] ["t", "<", "111100"], ["u", "=", "111101"], ["v", ">", "111110"], ["w", "?", "111111"]] PT = "AIVDM" CHAN = "A" MT = '000001' RI = '00' FILL = '000000' NS = 2 def Invert(BinStr): # Oh shit! I'm a lame programmer and can't find a function to swap bits in binary! return BinStr.replace("0", "A").replace("1", "0").replace("A", "1") def convert(val,bits): # This magic function converts dec to bin in right format if (val < 0 ): val = -val val = (str(bin(~val)))[3:].zfill(bits) val = Invert (val) else: val = (str(bin(val)))[2:].zfill(bits) return str(val) def checksum(s): c = 0 for ch in s: c ^= ord(ch) c = hex(c).upper()[2:] return c def GenAis(PT, CHAN, MT, RI, MMSI, NS, ROT, SOG, PA, LON, LAT, COG, HDG, TS, FILL, CommState): # Preparing Variables MMSI = str((bin(MMSI)))[2:].zfill(30) NS = str(bin(NS))[2:].zfill(4) SOG = str(bin(SOG))[2:].zfill(10) # Converting Vars to bin LON = convert(LON,28) LAT = convert(LAT,27) HDG = convert(HDG,9) COG = convert(COG,12) ROT = convert(ROT,8) TS = convert(TS,6) CommState = convert(CommState,19) MESSAGE_BIN = MT + RI + MMSI + NS + ROT + SOG + PA + LON + LAT + COG + HDG + TS + FILL + CommState mess = MT + ' ' + RI + ' ' + MMSI + ' ' + NS + ' ' + ROT + ' ' + SOG + ' ' + PA + ' ' + LON + ' ' + LAT + ' ' + COG + ' ' + HDG + ' ' + TS + ' ' + FILL + ' ' + CommState print (mess) MESSENC = "" LEN = 28 #28 6-byte words = 168 bits P = 0 # Starting encoding binary string to 6-byte word: while P <= LEN: TMPSTR = MESSAGE_BIN[(6*P):(6*P+6)] # print (TMPSTR) P += 1 for PAIR in CharTable: if PAIR[2] == TMPSTR: MESSENC += PAIR[0] # print (MESSENC) MESSAIS = (PT + ',1,1,,' + CHAN + ',' + MESSENC + ',0') # CS = '4E' CS = checksum(MESSAIS) print(CS) if (len(CS) < 2): CS = '0' + CS return ("!" + MESSAIS + '*' + CS) # So here the program starts MMSI = 0 #MMSI = 000000000 PA = '0' #LON = -122.39253 #LAT = 37.803803 HDG = 511 COG = 3600 SOG = 1023 ROT = 0 ACC = 0 TS = 60 CommState = 67427 #print (GenAis(PT, CHAN, MT, RI, MMSI, NS, ROT, SOG, PA, LON, LAT, COG, HDG, TS, FILL, CommState)) while(1): a = open("/Users/euler/Desktop/OHM-MS-0001") temp = a.readline() temp = a.readline().split(",") print(temp) LON = int(temp[1]) LAT = int(temp[0]) #check = checksum(temp) MESSAGE = GenAis(PT, CHAN, MT, RI, MMSI, NS, ROT, SOG, PA, LON, LAT, COG, HDG, TS, FILL, CommState) MESSAGE = bytes(MESSAGE, 'utf-8') sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # UDP sock.sendto(MESSAGE, (UDP_IP, UDP_PORT)) print(MESSAGE) a.close() time.sleep(1)
Now what this script does is tell the navigation computer “Hey, drifter1 is at position X”. Although far from perfect, the script did work reliably for the duration of the cruise (about a month) and it allowed me to get some well needed sleep once in a while.