diff --git a/SatsDecoder/systems/__init__.py b/SatsDecoder/systems/__init__.py index a9bfba0..0bf5c22 100644 --- a/SatsDecoder/systems/__init__.py +++ b/SatsDecoder/systems/__init__.py @@ -14,6 +14,7 @@ from SatsDecoder.systems.roseycubesat import * from SatsDecoder.systems.sharjahsat import * from SatsDecoder.systems.sonate2 import * +from SatsDecoder.systems.starlink_vhf import * from SatsDecoder.systems.usp import * from SatsDecoder.systems.wtc_simba import * @@ -27,6 +28,7 @@ 'roseycubesat', 'sharjahsat', 'sonate2', + 'starlink_vhf', 'wtc_simba', ): m = getattr(sys.modules['SatsDecoder.systems'], i) diff --git a/SatsDecoder/systems/starlink_vhf.py b/SatsDecoder/systems/starlink_vhf.py new file mode 100644 index 0000000..030dbc7 --- /dev/null +++ b/SatsDecoder/systems/starlink_vhf.py @@ -0,0 +1,93 @@ +# Copyright (c) 2024. Alexander Baskikh +# +# MIT License (MIT), http://opensource.org/licenses/MIT +# Full license can be found in the LICENSE-MIT file +# +# SPDX-License-Identifier: MIT + +import construct + +from SatsDecoder import utils +from SatsDecoder.systems import common + +__all__ = 'StarlinkProtocol', + +proto_name = 'starlink-vhf' + + +""" +https://emitters.space/Starlink.pdf +""" + + +starlink_beacon = construct.Struct( + '_name' / construct.Computed('starlink_beacon'), + 'name' / construct.Computed('Beacon'), + + # header + 'msg_num' / construct.Int24ul, + 'sc_num' / construct.Hex(construct.Int16ul), + 'ptype' / construct.Hex(construct.Byte), + 'semi_fixed' / construct.Bytes(7), + 'Time' / common.UNIXTimestampAdapter(construct.Int32ul), + + # ephemeris + 'lat' / construct.Float32l, + 'lon' / construct.Float32l, + 'alt' / construct.Int32ul, + 'semi_fixed_tbd' / construct.Bytes(8), + + # telemetry-A + 'tbd_a' / construct.Bytes(15), + 'gps_week' / construct.Int16ul, + 'gps_sec' / common.LinearAdapter(100, construct.Int32ul), + 'gps_time' / construct.Computed(lambda ctx: utils.gps_to_utc(ctx.gps_week, ctx.gps_sec)), + + # telemetry-B + 'tbd_b' / construct.Bytes(29), +) + + +class StarlinkProtocol: + columns = 'ptype', + c_width = 40, + + tlm_table = { + 'starlink_beacon': { + 'table': ( + ('name', 'Name'), + ('msg_num', 'Message Number'), + ('sc_num', 'Spacecraft Number'), + ('ptype', 'Packet Type'), + ('semi_fixed', 'Semi-fixed'), + ('Time', 'Time'), + ('lat', 'Latitude'), + ('lon', 'Longitude'), + ('alt', 'Altitude'), + ('semi_fixed_tbd', 'Semi-fixed TBD'), + ('tbd_a', 'TBD-A'), + ('gps_week', 'GPS Week Number'), + ('gps_sec', 'GPS Seconds'), + ('gps_time', 'GPS Time'), + ('tbd_b', 'TBD-B'), + ), + }, + } + + def __init__(self, outdir): + self.ir = None + + def recognize(self, bb): + x = starlink_beacon.parse(bb) + if not x: + return + + yield 'tlm', 'Starlink #%i' % x.sc_num, '0x%02X' % x.ptype, (x, x) + + +if __name__ == '__main__': + a = 'ae803e5109cc0000d0eb4e4b036d765564313d06c262100f4319530600000098a31400043090fc21ba0403000082ca660e060800d408c0ba090318b601e58f2f4b1481d8bce93eb3fcff2b74f6ff674afbff1e10d2dc07' + b = bytes.fromhex(a) + x = starlink_beacon.parse(b) + print(x) + print('Starlink #%i' % x.sc_num) diff --git a/SatsDecoder/utils.py b/SatsDecoder/utils.py index 82303e1..ea9297c 100644 --- a/SatsDecoder/utils.py +++ b/SatsDecoder/utils.py @@ -5,6 +5,7 @@ # # SPDX-License-Identifier: MIT +import datetime as dt import enum import sys import tkinter as tk @@ -341,6 +342,57 @@ def bayer2rgb(data, mode, w, h): return np.dstack((r, g, b)).astype(in_dtype) +_GPS_BORN = dt.datetime(1980, 1, 6) + dt.timedelta(seconds=19) + +def gps_to_utc(week, sec): + """ + https://hpiers.obspm.fr/eop-pc/index.php?index=TAI-UTC_tab&lang=en + """ + + x = _GPS_BORN + dt.timedelta(weeks=week) + # leaked seconds + if x < dt.datetime(year=1981, month=7, day=1): + sec -= 19 + elif x < dt.datetime(year=1982, month=7, day=1): + sec -= 20 + elif x < dt.datetime(year=1983, month=7, day=1): + sec -= 21 + elif x < dt.datetime(year=1985, month=7, day=1): + sec -= 22 + elif x < dt.datetime(year=1988, month=1, day=1): + sec -= 23 + elif x < dt.datetime(year=1990, month=1, day=1): + sec -= 24 + elif x < dt.datetime(year=1991, month=1, day=1): + sec -= 25 + elif x < dt.datetime(year=1992, month=7, day=1): + sec -= 26 + elif x < dt.datetime(year=1993, month=7, day=1): + sec -= 27 + elif x < dt.datetime(year=1994, month=7, day=1): + sec -= 28 + elif x < dt.datetime(year=1996, month=1, day=1): + sec -= 29 + elif x < dt.datetime(year=1997, month=7, day=1): + sec -= 30 + elif x < dt.datetime(year=1999, month=1, day=1): + sec -= 31 + elif x < dt.datetime(year=2006, month=1, day=1): + sec -= 32 + elif x < dt.datetime(year=2009, month=1, day=1): + sec -= 33 + elif x < dt.datetime(year=2012, month=7, day=1): + sec -= 34 + elif x < dt.datetime(year=2015, month=7, day=1): + sec -= 35 + elif x < dt.datetime(year=2017, month=1, day=1): + sec -= 36 + else: + sec -= 37 + + return x + dt.timedelta(seconds=sec) + + seqs_map = { '\x72\x73\x32\x30\x73': 'aHR0cHM6Ly91cGxvYWQud2lraW1lZGlhLm9yZy93aWtpcGVkaWEvY29tbW9u' 'cy90aHVtYi80LzRhL0N1YmVTYXRfR2Vvc2Nhbi1FZGVsdmVpc19lbWJsZW0u'