In [1]:
import json
import urllib.request
import pickle
import collections
import pathlib
import yaml

In [2]:
GR_SATELLITES_PATH = '/home/daniel/debian_testing_chroot/home/daniel/gr-satellites'
OUTPUT_DIR = '/tmp/satyamls'

In [3]:
satellites = json.load(urllib.request.urlopen('https://db.satnogs.org/api/satellites/?format=json'))
transmitters = json.load(urllib.request.urlopen('https://db.satnogs.org/api/transmitters/?format=json'))

In [4]:
modes = {'AFSK1k2' : ('AFSK', False, 1200), 'FSK19k2' : ('FSK', True, 19200), 'FSK1k2' : None, 'FSK2k4' : None, 'FSK4k8' : ('FSK', True, 4800),\
        'FSK9k6' : ('FSK', True, 9600), 'GFSK19k2' : ('FSK', True, 19200), 'GFSK1k2' : None, 'GFSK2k4' : None, 'GFSK4k8' : ('FSK', True, 4800),\
        'GFSK9k6' : ('FSK', True, 9600), 'GMSK19k2' : ('FSK', True, 19200), 'GMSK1k2' : None, 'GMSK2k4' : None, 'GMSK4k8' : ('FSK', True, 4800),\
        'GMSK9k6' : ('FSK', True, 9600), 'MSK19k2' : ('FSK', True, 19200), 'MSK1k2' : None, 'MSK2k4' : None, 'MSK4k8' : ('FSK', True, 4800),\
        'MSK9k6' : ('FSK', True, 9600),}

In [5]:
norad_list = set([t['norad_cat_id'] for t in transmitters if t['mode'] in modes.keys() and t['type'] == 'Transmitter'])

In [6]:
def get_ax25_headers(norad):
    tlm = json.load(urllib.request.urlopen(f'https://db.satnogs.org/api/telemetry/?format=json&satellite={norad}'))
    headers = [bytes.fromhex(t['frame'][:16*2]) for t in tlm]
    return collections.Counter(headers)

def to_address(header):
    return bytes([a >> 1 for a in header[:14]])

def load_info(file):
    with open(file, 'rb') as f:
        info = pickle.load(f)
    return info

def addresses(frames):
    return {to_address(a[0]):a[1] for a in frames.items()}

def sat_from_name(name):
    return [s for s in satellites if s['name'] == name][0]

def name(norad):
    return [s['name'] for s in satellites if s['norad_cat_id'] == norad][0]

def norad(name):
    return [s['norad_cat_id'] for s in satellites if s['name'] == name][0]

def addresses_look_ok(frame):
    if frame is None:
        return False
    addr = str(to_address(frame), encoding = 'ascii')
    return addr[:6].isprintable() and addr[7:7+6].isprintable()

def get_most_popular_header(headers):
    s = sorted(headers.items(), key = lambda a: a[1], reverse = True)
    if len(s) == 1:
        return s[0][0]
    if len(s) == 0:
        return None
    # the most popular needs to be 4 times more popular than the rest
    if s[0][1] > s[1][1] * 4:
        return s[0][0]
    else:
        return None

def baudrate_str(baud):
    return f'{baud//1000}k{(baud-baud//1000*1000)//100}'

def norad_from_satyaml(path):
    with open(path) as f:
        y = yaml.safe_load(f)
    return y['norad']

def good_filename(name):
    return name.replace('/', '_').replace(' ', '_').replace('.', '_') + '.yml'

def satyaml(sat):
    names = [s.strip() for s in sat['names'].replace('\r\n',',').split(',') if len(s.strip()) > 0]
    alternatives = '\nalternative_names:\n' + '\n'.join(['  - ' + s for s in names]) if len(names) else ''
    return f"""name: {sat['name']}{alternatives}
norad: {sat['norad_cat_id']}
data:
  &tlm Telemetry:
    telemetry: ax25
transmitters:
""" + '\n'.join([f"""  {baudrate_str(modes[t['mode']][2])} {modes[t['mode']][0]} downlink:
    frequency: {t['downlink_low']*1e-6:.03f}e+6
    modulation: {modes[t['mode']][0]}
    baudrate: {modes[t['mode']][2]}
    framing: AX.25{' G3RUH' if modes[t['mode']][1] else ''}
    data:
    - *tlm""" for t in transmitters if t['norad_cat_id'] == sat['norad_cat_id'] and t['mode'] in modes]) + '\n'

In [7]:
#for norad in norad_list:
#    with open(f'satnogsdb/ax25_addresses/{norad}', 'wb') as f:
#        try:
#            headers = get_ax25_headers(norad)
#        except:
#            print(f'{name(norad)} {norad} failed')
#        pickle.dump(headers, f)

In [8]:
frames = {int(f.name) : load_info(f) for f in pathlib.Path('satnogsdb/ax25_addresses/').glob('*')}

In [9]:
headers = {name(f[0]) : get_most_popular_header(f[1]) for f in frames.items()}
suspicious_addresses = {h[0] : to_address(h[1]) for h in headers.items() if h[1] is not None and not addresses_look_ok(h[1])}
ok_addresses = {h[0] : to_address(h[1]) for h in headers.items() if h[1] is not None and addresses_look_ok(h[1])}

In [10]:
suspicious_addresses


Out[10]:
{'AALTO-2': b'%Iz\x17\x073\x178bQ9WiP',
 'AOBA VELOX-IV': b'\x16\x0b\x04k:){\x08!9\x06c"Y',
 'BEESAT-2': b'\x01*\x02\x00\x00\x00\x00\x00\x1f\x00"(\x18!',
 'BEESAT-3': b'\x01*\x02\x00\x00\x00\x00\x00\x1f\x00"(\x18!',
 'BUGSAT-1': b'CQ    0LU7AA\x00p',
 'ENDUROSAT ONE': b'1Z|-b \rcFV\tS\x08\x00',
 'IRVINE-01': b'\x00\x00\x00\x00\x00\x00\x00KK6CVB\x01',
 'InnoSat 2': b'\x00@SA\x01-h&\x1d\x00\x00\x00m\x00',
 'LQSAT': b'P?=Y\x1a\x01q\x17s\x1c\x7fY',
 'M-CUBED & EXP-1 PRIME': b'\x199\x02\x01%\x1b&)*\x16\x18\x10\x04\x10',
 'NUSAT 1': b'K3%\x047K#\x07=\x1eHNzp',
 'NUSAT 2': b"QG'kHsq\x13R",
 'PEGASUS': b")''\x18\x19 *CC2C\x004\x00",
 'QBEE': b'\'\'\x18\x18)"\x00\'\'\x18\x18)"\x00',
 'SNUGLITE': b'")\x18"$\x00\x00")\x18"$\x00\x00',
 'TECHNOSAT': b'\x00>\x02\x00\x00\x00\x00\x00\x19\x07"(\x18*'}

In [11]:
ok_addresses


Out[11]:
{'AALTO-1': b'OH2AGS\x00OH2A1S\x0b',
 'AAUSAT-II': b'UI    0OZ2CUB0',
 'ACRUX-1': b'NOCALL0CQ    p',
 'ARMADILLO': b'CQ    0BJ1SK 0',
 'ATLANTIS': b'CQ    0WD8DOXp',
 'Astrocast 0.1': b'CQ    0HB9GSF0',
 'Astrocast 0.2': b'CQ    0HB9GSF0',
 'BISONSAT': b'WH2XPM0N7SKC p',
 'BRICSat-2': b'APOFF 0USNAP11',
 'CAS-4A': b'CQ    0BJ1SK 0',
 'CAS-4B': b'CQ    0BJ1SL 0',
 'CHALLENGER': b'QBUS010CQ    p',
 'CHOMPTT': b'WI2XVT0WI2XVT0',
 'COLUMBIA': b'CQ    0KD8CJTp',
 'CSIM-FD': b'BCT   0CSIM  p',
 'CUBEBUG-2': b'CQ    0CUBEB2v',
 'CubeBel-1': b'EU1XX 0EU10S 0',
 'E-ST@R-II': b'ALLALLpESTAR 1',
 'ECAMSAT': b'UNDEF pKE6QLL0',
 'ELFIN-A': b'W6YRA90WJ2XNXp',
 'ELFIN-B': b'W6YRA90WJ2XOXp',
 'Eaglet-I': b'OHBROMpEAGLET0',
 'FIREBIRD 3': b'K7MSU 0K7MSU p',
 'FIREBIRD 4': b'K7MSU 0K7MSU p',
 'GRIFEX': b'KD8SPS0CQ    p',
 'INS-1C': b'INDUST:INDUSRz',
 'IRAZU': b'TI0TEC0TI0IRA0',
 'KrakSat': b'APDST46SR9KRA6',
 'LITUANICASAT-2': b'CQ    \x00LY0LS \x00',
 'LightSail-2': b'N6CP  \x01KK6HIT\n',
 'M6P': b'CQ    \x00LY0LS \x00',
 'MCUBED-2': b'CQ    0NOCALLp',
 'MINXSS': b'CQ    0MINXSSp',
 'MinXSS 2': b'CQ    \x00LY0LS \x00',
 'NEXUS': b'NEXUS 0JS1YAV0',
 'NIUSAT': b'NIUGSR:NIUOBTz',
 'NO-84': b'APRSON0PSAT  0',
 'NODES 1': b'CQ    0KE6QLL0',
 'NODES 2': b'CQ    0KE6QLL0',
 'NSIGHT-1': b'ZS1SCSpON02AZ0',
 'O/OREOS': b'UNDEF pKF6JBP0',
 'PHOENIX': b'NCKUGSpON01TW0',
 'PHONESAT 2.4': b'CQ    0KJ6KRW0',
 'POLYITAN-1': b'QST   pEM0UKP0',
 'RANGE A': b'CQ    \x00LY0LS \x00',
 'ROBUSTA-1B': b'F4KJE 0FX6FR 0',
 'SKCUBE': b'OM3KAApOM9SAT0',
 'SiriusSat-1': b'R2ANF 0RS13S 1',
 'SiriusSat-2': b'R2ANF 0RS14S 2',
 'Swiatowid': b'APDST46SR6SAT6',
 'TBEX-A': b'KD8SPS0KF6RFXp',
 'TBEX-B': b'KD8SPS0KF6RFXp',
 'TIGRISAT': b'CQ    0HNATIGp',
 'Tanusha-3': b'ALL   0RS8S  0',
 'UBAKUSAT': b'TA2MKApYM1RAS0',
 'UCLSAT': b'M3END zON03GB3',
 'UNISAT-6': b'CQ    0II0US p',
 'URSA MAIOR': b'CQ    0URSAMR0',
 'UWE-3': b'DD0UWE0DP0UWGp',
 'UWE-4': b'DD0UWE0DP0UWHp',
 'X-CUBESAT': b'TLM   \x00ON01FR\x00',
 'XW-2A': b'CQ    0BJ1SB 0',
 'XW-2B': b'CQ    0BJ1SC 0',
 'XW-2C': b'CQ    0BJ1SD 0',
 'XW-2D': b'CQ    0BJ1SE 0',
 'XW-2E': b'CQ    0BJ1SF 0',
 'XW-2F': b'CQ    0BJ1SG 0',
 'ZACUBE-1': b'012345p1234560',
 'al-Farabi-2': b'W6YRA90WJ2XNXp'}

In [12]:
blacklist = {'RANGE A' # LY0LS is another spacecraft
            }
suspicious_good = {'BUGSAT-1', 'ENDUROSAT ONE', 'IRVINE-01', 'QBEE', 'SNUGLITE'}

In [13]:
good_addresses = (ok_addresses.keys() - blacklist) | suspicious_good

In [14]:
gr_satellites_norads = {norad_from_satyaml(f) for f in (pathlib.Path(GR_SATELLITES_PATH) / 'python' / 'satyaml').glob('*.yml')}

In [21]:
gr_satellites_names = {n for n in good_addresses if norad(n) in gr_satellites_norads}
gr_satellites_names


Out[21]:
{'Astrocast 0.1', 'CHALLENGER', 'Swiatowid'}

In [23]:
for name in good_addresses - gr_satellites_names:
    with open(pathlib.Path(OUTPUT_DIR) / good_filename(name), 'w') as f:
        try:
            y = satyaml(sat_from_name(name))
        except:
            print('Error with', name)
        else:
            f.write(y)


Error with INS-1C
Error with NIUSAT
Error with AAUSAT-II