FT8 Telemetry Datagram Decoder, written in python
#!/usr/bin/python # decode my ft8 data telegram format # (c) 2021 Thomas dl9sau <dl9sau@darc.de> # License: gpl import sys def i_base36_to_char(n): if not ( ( chr(n)>= 0 and chr(n) <= '9' ) or ( chr(n)>= 'A' or chr(n) <= 'Z' ) ) : raise TypeError("Error whle parsing input: only digits and numbers are valid.") if n <10: return chr(ord('0') + n) else: return chr(ord('A') + n-10) #----------------------------------------------------------------------------- def char_base36_to_i(c): if not ( ( c>= '0' and c <= '9' ) or ( c>= 'A' or c <= 'Z' ) ) : raise TypeError("Error whle parsing input: only digits and numbers are valid.") if c <= '9': return ord(c) - ord('0') else: return ord(c) - ord('A') + 10 #----------------------------------------------------------------------------- def decode_telegram(msgnum, data): global last_msgnum, locatorA, locatorB, locatorC, altitudeA, call_ssid, last_call_ssid if last_msgnum>= msgnum or not call_ssid == last_call_ssid: locatorA = "" locatorB = "" locatorC = "" altitudeA="?" last_msgnum = msgnum if msgnum == 0: locatorA = data print "Locator: %sxxxxxxxx" % locatorA elif msgnum == 1: locatorB = data if locatorA == "": tmpA = "xxxx" else: tmpA = locatorA print "Locator: %s%sxxxx" % (tmpA, locatorB) elif msgnum == 2: if locatorA == "": tmpA = "xxxx" else: tmpA = locatorA if locatorB == "": tmpB = "xxxx" else: tmpB = locatorB locatorC = data print "Locator: %s%s%s" % ( tmpA, tmpB, locatorC ) elif msgnum == 3: sats = ord(data[0]) - ord('A') print "sats: %d" % (sats % 13) if sats> 12: print "sat signal not valid" else: print "sat signal valid" txc = char_base36_to_i(data[1]) *36 + char_base36_to_i(data[2]) print "Telegram blocks sent: %d" % txc alt_c = data[3] if alt_c == 'A': print "Altitude: 100.000 feet + value from next frame (4)" altitudeA="1" elif alt_c == 'B': print "Negative altitute follows (or invalid measurement (BZZZZ)" altitudeA="-" elif alt_c == '0': print "Altitude: <10.000 feet" altitudeA="" else: print "Altitude: %c * 10.000 feet + value from next frame (4)" % alt_c altitudeA=alt_c elif msgnum == 4: if (altitudeA == "" or altitudeA == "-") and data == "ZZZZ": print "Altitude: invalid measurement (Keyword BZZZ)" else: print "Altitude: %s%s feet" % (altitudeA , data) if not altitudeA == "": print " ^came frome previous telegram" elif msgnum == 5: print "Speed: %d kn" % int(data[0:2]) if data[2:] == "ZZ": print "Course: invaild measurement" else: print "Course: %d Deg" % (char_base36_to_i(data[2]) * 36 + char_base36_to_i(data[3])) elif msgnum == 6: print "Pressure: %d hPa" % (char_base36_to_i(data[0]) * 36 + char_base36_to_i(data[1])) c = int( (char_base36_to_i(data[2]) * 36 + char_base36_to_i(data[3])) ) c = (c - 695) * 1.25 cA = c / 10 cB = c % 10 print "Temperature: %d.%d C" % ( cA, cB ) elif msgnum == 7: v = int( (char_base36_to_i(data[0]) * 36 + char_base36_to_i(data[1])) ) * 2 vA = v / 100 vB = v % 100 print "Voltage: %d.%2.2d V" % (vA, vB) print "PEP: %d dBm" % int(data[2:]) #----------------------------------------------------------------------------- def ft8_hex_to_string(buf, outlen): s = "" if not buf or buf == "": return s u32 = int(buf, 16) for i in range(0, outlen): n = u32 % 37 if n == 36: s = " " + s else: s = i_base36_to_char(n) + s u32 = u32 / 37 return s #----------------------------------------------------------------------------- def process_input(msg): global last_msgnum, locatorA, locatorB, locatorC, altitudeA, call_ssid, last_call_ssid call = "" hexcall = "" aprs_symbol = "" aprs_alt_table = False ssid = 0 msgnum = 0 data = "" hexdata = "" plain_text__do_not_process = False if msg.find(' ')> 0: msgnum = int(msg[2]) data, call = msg.split() if not len(data) == 6 or not (data[0] == 'Q' or data[0] == '0'): if len(data) == 4 and data[0]> 9 and data[0] <= 'R': print "Locator: %s" % data locatorA = data locatorB = "" locatorC = "" altitudeA = "?" last_msgnum = 0 plain_text__do_not_process = True else: if (msg[0] == 'Q'): ssid = 1 data = data[1] + data[3:] elif len(msg) == 18 and msg.find(' ') == -1: hexcall = msg[0:8] call = ft8_hex_to_string(hexcall, 6) ssid = int(msg[8], 16) i = int(msg[9:11], 16) if (i> 127): aprs_alt_table = True i = i-128 aprs_symbol = chr(i) msgnum = int(msg[17], 16) hexdata = msg[11:17] # pos 0: upper 3 bits re-used. 2 of them for protocol version ft8_protocol_version = ((int(hexdata[0], 16)>> 1) % 4 + 1) print "ft protocol version: %d" % ft8_protocol_version c = "%d" % (int(hexdata[0], 16) & 0x01) hexdata = c + hexdata[1:] data = ft8_hex_to_string(hexdata, 4) call_ssid = "%s-%d" % (call, ssid) print "hexcall: " + hexcall print "call: " + call print "ssid: %d " % ssid print "aprs symbol: %s" % aprs_symbol if aprs_alt_table: print "aprs alt table" else: print "aprs primary table" print "msgnum: %d" % msgnum print "hexdata: " + hexdata print "data: " + data if not plain_text__do_not_process: decode_telegram(msgnum, data) print "EOM" print last_call_ssid = call_ssid #----------------------------------------------------------------------------- # main() last_msgnum = -1 locatorA = "" locatorB = "" locatorC = "" altitudeA = "?" call_ssid = "" last_call_ssid = "" argv = sys.argv[1:] if len(argv)> 0: while len(argv)> 0: msg = argv[0] process_input(msg) argv = argv[1:] else: while True: msg = sys.stdin.readline().strip() if msg == "": break process_input(msg)