BDS08 and BDS09 encoders
This commit is contained in:
parent
5286355bf6
commit
aff0f75de2
68
pyModeS/encoder/__init__.py
Normal file
68
pyModeS/encoder/__init__.py
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
from .bds.bds08 import me08
|
||||||
|
from .bds.bds09 import me09
|
||||||
|
from pyModeS import common
|
||||||
|
|
||||||
|
|
||||||
|
def encode_adsb(**kwargs):
|
||||||
|
"""Encode ADS-B message.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
icao (string): Transponder ICAO address (6 hexdigits)
|
||||||
|
capability (int): Transponder capability, between 0 and 7
|
||||||
|
typecode (int): Typecode, less than 32
|
||||||
|
|
||||||
|
callsign (string): Callsign (6 hexdigits)
|
||||||
|
category (int): Aircraft category, between 0 and 7, Default to 0.
|
||||||
|
|
||||||
|
speed (int): Speed in knots.
|
||||||
|
angle (float): Track angle or heading angle in degrees.
|
||||||
|
vertical_rate (int): vertical rate in feet/minute
|
||||||
|
intent_change (int): Intent change flag, 0 or 1. Default to 0.
|
||||||
|
ifr_capability (int): IFR capability flag, 0 or 1. Default to 1.
|
||||||
|
navigation_quality (int): NUC (ver 0) or NACv (ver 1, 2), between 0 and 7.
|
||||||
|
Default to 0.
|
||||||
|
supersonic (bool): Is this a supersonic flight? Default to False.
|
||||||
|
speed_type (str): Speed type: GS, IAS, or TAS. Default to GS.
|
||||||
|
vertical_rate_source (str): GNSS or BARO. Default to BARO.
|
||||||
|
gnss_baro_alt_diff (int): Different between GNSS and barometric altitude in feet.
|
||||||
|
Negative value indicates GNSS altitude below barometric altitude. Default to 0
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
string: 28 hexdigits raw message
|
||||||
|
|
||||||
|
"""
|
||||||
|
tc = kwargs.get("typecode")
|
||||||
|
|
||||||
|
if 1 <= tc <= 4:
|
||||||
|
me = me08(**kwargs)
|
||||||
|
elif tc == 19:
|
||||||
|
me = me09(**kwargs)
|
||||||
|
|
||||||
|
msg = _constuct(**dict(kwargs, me=me))
|
||||||
|
return msg
|
||||||
|
|
||||||
|
|
||||||
|
def _constuct(**kwargs):
|
||||||
|
icao = kwargs.get("icao")
|
||||||
|
me = kwargs.get("me")
|
||||||
|
capability = kwargs.get("capability", 6)
|
||||||
|
|
||||||
|
if icao is None or len(icao) != 6:
|
||||||
|
raise Exception("Transponder address must be 6 hexadecimal characters.")
|
||||||
|
|
||||||
|
if me is None or len(me) != 14:
|
||||||
|
raise Exception("Message be 14 hexadecimal characters.")
|
||||||
|
|
||||||
|
if capability > 6:
|
||||||
|
raise Exception("Transponder capability must be smaller than 7.")
|
||||||
|
|
||||||
|
header_bin = "10001" + "{0:03b}".format(capability)
|
||||||
|
header_hex = "{0:02X}".format(int(header_bin, 2))
|
||||||
|
|
||||||
|
msg = header_hex + icao + me + "000000"
|
||||||
|
|
||||||
|
pi = common.crc(msg, encode=True)
|
||||||
|
pi_hex = "{0:06X}".format(pi)
|
||||||
|
|
||||||
|
msg = msg[:-6] + pi_hex
|
||||||
|
return msg
|
0
pyModeS/encoder/bds/__init__.py
Normal file
0
pyModeS/encoder/bds/__init__.py
Normal file
5
pyModeS/encoder/bds/bds05.py
Normal file
5
pyModeS/encoder/bds/bds05.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# ------------------------------------------
|
||||||
|
# BDS 0,5
|
||||||
|
# ADS-B TC=9-18
|
||||||
|
# Airborn position
|
||||||
|
# ------------------------------------------
|
5
pyModeS/encoder/bds/bds06.py
Normal file
5
pyModeS/encoder/bds/bds06.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# ------------------------------------------
|
||||||
|
# BDS 0,6
|
||||||
|
# ADS-B TC=5-8
|
||||||
|
# Surface position
|
||||||
|
# ------------------------------------------
|
40
pyModeS/encoder/bds/bds08.py
Normal file
40
pyModeS/encoder/bds/bds08.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# ------------------------------------------
|
||||||
|
# BDS 0,8
|
||||||
|
# ADS-B TC=1-4
|
||||||
|
# Aircraft identitification and category
|
||||||
|
# ------------------------------------------
|
||||||
|
|
||||||
|
from pyModeS import common
|
||||||
|
|
||||||
|
charmap = "#ABCDEFGHIJKLMNOPQRSTUVWXYZ##### ###############0123456789######"
|
||||||
|
|
||||||
|
|
||||||
|
def me08(callsign, **kwargs):
|
||||||
|
cs = callsign
|
||||||
|
tc = kwargs.get("typecode")
|
||||||
|
cat = kwargs.get("category", 0)
|
||||||
|
|
||||||
|
if len(cs) > 8:
|
||||||
|
raise Exception("callsign must contain less than 9 characters")
|
||||||
|
|
||||||
|
if tc > 4:
|
||||||
|
raise Exception("typecode must be less 5")
|
||||||
|
|
||||||
|
if cat > 7:
|
||||||
|
raise Exception("category must be less 8")
|
||||||
|
|
||||||
|
if not cs.isalnum():
|
||||||
|
raise Exception("callsign must only contain alphanumeric characters")
|
||||||
|
|
||||||
|
cs = "{:<8}".format(cs.upper())
|
||||||
|
|
||||||
|
idx = [charmap.index(c) for c in cs]
|
||||||
|
me_bin = (
|
||||||
|
"{0:05b}".format(tc)
|
||||||
|
+ "{0:03b}".format(cat)
|
||||||
|
+ "".join("{0:06b}".format(i) for i in idx)
|
||||||
|
)
|
||||||
|
|
||||||
|
me_hex = "{0:04X}".format(int(me_bin, 2))
|
||||||
|
|
||||||
|
return me_hex
|
119
pyModeS/encoder/bds/bds09.py
Normal file
119
pyModeS/encoder/bds/bds09.py
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
# ------------------------------------------
|
||||||
|
# BDS 0,9
|
||||||
|
# ADS-B TC=19
|
||||||
|
# Aircraft Airborn velocity
|
||||||
|
# ------------------------------------------
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
|
def me09(speed, angle, vertical_rate, **kwargs):
|
||||||
|
spd = speed
|
||||||
|
agl = angle
|
||||||
|
vr = vertical_rate
|
||||||
|
|
||||||
|
tc = kwargs.get("typecode")
|
||||||
|
intent = kwargs.get("intent_change", 0)
|
||||||
|
ifr = kwargs.get("ifr_capability", 1)
|
||||||
|
navq = kwargs.get("navigation_quality", 0)
|
||||||
|
supersonic = kwargs.get("supersonic", False)
|
||||||
|
spd_type = kwargs.get("speed_type", "gs").lower()
|
||||||
|
vr_source = kwargs.get("vertical_rate_source", "baro").lower()
|
||||||
|
alt_diff = kwargs.get("gnss_baro_alt_diff", 0)
|
||||||
|
|
||||||
|
if tc != 19:
|
||||||
|
raise Exception("Typecode must be 19.")
|
||||||
|
|
||||||
|
if intent not in (0, 1):
|
||||||
|
raise Exception("Intent change flag must be 0 or 1.")
|
||||||
|
|
||||||
|
if ifr not in (0, 1):
|
||||||
|
raise Exception("IFR capability flag must be 0 or 1.")
|
||||||
|
|
||||||
|
if type(supersonic) != bool:
|
||||||
|
raise Exception("Subsonic flag must be True or False.")
|
||||||
|
|
||||||
|
if navq > 7:
|
||||||
|
raise Exception("Navigation quality indicator must be smaller than 8.")
|
||||||
|
|
||||||
|
if spd_type not in ["gs", "tas"]:
|
||||||
|
raise Exception("Speed type must be 'gs', 'ias', or 'tas'.")
|
||||||
|
|
||||||
|
if vr_source not in ["baro", "gnss"]:
|
||||||
|
raise Exception("Vertical rate source must be 'baro' or 'gnss'.")
|
||||||
|
|
||||||
|
me_bin = ""
|
||||||
|
|
||||||
|
# typecode
|
||||||
|
me_bin += "{0:05b}".format(tc)
|
||||||
|
|
||||||
|
# sub-type
|
||||||
|
if supersonic:
|
||||||
|
if spd_type == "gs":
|
||||||
|
me_bin += "010"
|
||||||
|
else:
|
||||||
|
me_bin += "100"
|
||||||
|
else:
|
||||||
|
if spd_type == "gs":
|
||||||
|
me_bin += "001"
|
||||||
|
else:
|
||||||
|
me_bin += "011"
|
||||||
|
|
||||||
|
# intent, ifr, navigation quality
|
||||||
|
me_bin += str(intent) + str(ifr) + "{0:03b}".format(navq)
|
||||||
|
|
||||||
|
# speed and angle part
|
||||||
|
if spd_type == "gs":
|
||||||
|
vx = spd * np.sin(np.radians(agl))
|
||||||
|
vy = spd * np.cos(np.radians(agl))
|
||||||
|
|
||||||
|
if supersonic:
|
||||||
|
vx /= 4
|
||||||
|
vy /= 4
|
||||||
|
|
||||||
|
vx = int(round(vx))
|
||||||
|
vy = int(round(vy))
|
||||||
|
|
||||||
|
sew = "0" if vx >= 0 else "1"
|
||||||
|
sns = "0" if vy >= 0 else "1"
|
||||||
|
vew = "{0:010b}".format(min(abs(vx), 1023) + 1)
|
||||||
|
vns = "{0:010b}".format(min(abs(vy), 1023) + 1)
|
||||||
|
|
||||||
|
me_bin += sew + vew + sns + vns
|
||||||
|
|
||||||
|
elif spd_type == "ias" or spd_type == "tas":
|
||||||
|
hdg = int(round(agl * 1024 / 360))
|
||||||
|
hdg = min(hdg, 1023)
|
||||||
|
|
||||||
|
air_type = "1" if spd_type == "tas" else "0"
|
||||||
|
|
||||||
|
if supersonic:
|
||||||
|
spd /= 4
|
||||||
|
|
||||||
|
spd = min(int(round(spd)), 1023)
|
||||||
|
|
||||||
|
me_bin += "1" + "{0:010b}".format(hdg) + air_type + "{0:010b}".format(spd)
|
||||||
|
|
||||||
|
# vertical rate source
|
||||||
|
me_bin += "1" if vr_source == "baro" else "0"
|
||||||
|
|
||||||
|
# vertical rate
|
||||||
|
me_bin += "0" if vr > 0 else "1"
|
||||||
|
vr = int(round((abs(vr) / 64 + 1)))
|
||||||
|
vr = min(vr, 511)
|
||||||
|
me_bin += "{0:09b}".format(vr)
|
||||||
|
|
||||||
|
# reserved
|
||||||
|
me_bin += "00"
|
||||||
|
|
||||||
|
# altitude difference
|
||||||
|
me_bin += "1" if alt_diff < 0 else "0"
|
||||||
|
alt_diff = int(round(abs(alt_diff) / 25 + 1))
|
||||||
|
alt_diff = min(alt_diff, 127)
|
||||||
|
me_bin += "{0:07b}".format(alt_diff)
|
||||||
|
print(me_bin)
|
||||||
|
|
||||||
|
# convert to hexdigits
|
||||||
|
me_hex = "{0:04X}".format(int(me_bin, 2))
|
||||||
|
|
||||||
|
return me_hex
|
23
tests/test_encoder.py
Normal file
23
tests/test_encoder.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
from pyModeS import encoder
|
||||||
|
|
||||||
|
|
||||||
|
def test_identification():
|
||||||
|
msg = encoder.encode_adsb(
|
||||||
|
icao="406B90", typecode=4, capability=5, category=0, callsign="EZY85MH"
|
||||||
|
)
|
||||||
|
assert msg == "8D406B902015A678D4D220AA4BDA"
|
||||||
|
|
||||||
|
|
||||||
|
def test_speed():
|
||||||
|
msg = encoder.encode_adsb(
|
||||||
|
icao="485020",
|
||||||
|
typecode=19,
|
||||||
|
capability=5,
|
||||||
|
speed_type="gs",
|
||||||
|
speed=159,
|
||||||
|
angle=182.88,
|
||||||
|
vertical_rate=-832,
|
||||||
|
vertical_rate_source="gnss",
|
||||||
|
gnss_baro_alt_diff=550,
|
||||||
|
)
|
||||||
|
assert msg == "8D485020994409940838175B284F"
|
Loading…
Reference in New Issue
Block a user