13 Commits
v1.2.1 ... 1.x

Author SHA1 Message Date
Junzi Sun
972ffe264e Update README.rst 2018-06-20 16:29:36 +02:00
Junzi Sun
7c52db318d release version 1.2.2 2018-03-28 13:49:46 +02:00
Junzi Sun
e692d30d66 fix vertical rate 2018-03-09 13:19:12 +01:00
Junzi Sun
fd8bb8386f fix vertical rate bug in BDS60 2018-03-08 16:36:09 +01:00
Junzi Sun
2b1f2a5878 rename spd to trk 2017-12-12 23:06:03 +01:00
Junzi Sun
3538645e22 Update README.rst 2017-12-12 21:48:25 +01:00
Junzi Sun
a9887d6238 minor fix in bit sequence 2017-11-18 22:14:09 +01:00
Junzi Sun
7c8fd74db7 fix velocity message with no data; and update some comments 2017-11-15 22:33:06 +01:00
Junzi Sun
35b0d63fa9 update readme 2017-11-01 11:53:10 +01:00
Junzi Sun
2ae7bf4c19 Merge pull request #13 from hv92/patch-6
update identification function for BDS44
2017-10-24 17:59:17 +02:00
Huy Vû
e8449154ca Update ehs.py
Additional checks BDS4,4
2017-10-24 13:02:08 +02:00
Junzi Sun
fbe5b63286 Merge pull request #12 from hv92/patch-2
Temperature requirement
2017-10-11 20:34:39 +02:00
Huy Vû
45c32cd7aa Temperature requirement
Temperature range [-80,60] is a requirement in Annex 3
2017-10-11 11:38:26 +02:00
6 changed files with 146 additions and 120 deletions

View File

@@ -7,25 +7,20 @@ Python library for Mode-S message decoding. Support Downlink Formats (DF) are:
- aircraft information that contains: ICAO address, position, altitude, velocity (ground speed), callsign, etc.
- Mode-S Elementary Surveillance (ELS) (DF4 and DF5).
- DF4: Altitude
- DF5: Squawk code
- Mode-S Enhanced Surveillance (EHS) (DF20 and DF21). Additional information in response to SSR interrogation, such as: true airspeed, indicated airspeed, mach number, wind, temperature, etc.
- Mode-S Comm-B replies :Additional information in response to SSR interrogation, such as true airspeed, indicated airspeed, mach number, wind, temperature, etc.
- DF20: Altitude
- DF21: Squawk code
- BDS 2,0 Aircraft identification
- BDS 2,1 Aircraft and airline registration markings
- BDS 4,0 Selected vertical intention
- BDS 4,4 Meteorological routine air report
- BDS 5,0 Track and turn report
- BDS 5,3 Air-referenced state vector
- BDS 6,0 Heading and speed report
- DF20/21 BDS 2,0 Aircraft identification
- DF20/21 BDS 2,1 Aircraft and airline registration markings
- DF20/21 BDS 4,0 Selected vertical intention
- DF20/21 BDS 4,4 Meteorological routine air report
- DF20/21 BDS 5,0 Track and turn report
- DF20/21 BDS 5,3 Air-referenced state vector
- DF20/21 BDS 6,0 Heading and speed report
Detailed manual on Mode-S decoding is published by the author, at:
http://adsb-decode-guide.readthedocs.io
http://mode-s.org/decode
Source code
@@ -44,7 +39,13 @@ The easiest installation is to use pip:
::
pip install pyModeS
pip install pyModeS
To install latest devlopment version from the GitHub:
::
pip install git+https://github.com/junzis/pyModeS
Use the library
@@ -52,7 +53,7 @@ Use the library
.. code:: python
import pyModeS as pms
import pyModeS as pms
Common functions:
@@ -60,14 +61,14 @@ Common functions:
.. code:: python
pms.df(msg) # Downlink Format
pms.crc(msg, encode=False) # Perform CRC or generate parity bit
pms.df(msg) # Downlink Format
pms.crc(msg, encode=False) # Perform CRC or generate parity bit
pms.hex2bin(str) # Convert hexadecimal string to binary string
pms.bin2int(str) # Convert binary string to integer
pms.hex2int(str) # Convert hexadecimal string to integer
pms.hex2bin(str) # Convert hexadecimal string to binary string
pms.bin2int(str) # Convert binary string to integer
pms.hex2int(str) # Convert hexadecimal string to integer
pms.gray2int(str) # Convert grey code to interger
pms.gray2int(str) # Convert grey code to interger
Core functions for ADS-B decoding:
@@ -75,23 +76,28 @@ Core functions for ADS-B decoding:
.. code:: python
pms.adsb.icao(msg)
pms.adsb.callsign(msg)
pms.adsb.icao(msg)
pms.adsb.typecode(msg)
pms.adsb.position(msg_even, msg_odd, t_even, t_odd, lat_ref=None, lon_ref=None)
pms.adsb.airborne_position(msg_even, msg_odd, t_even, t_odd)
pms.adsb.surface_position(msg_even, msg_odd, t_even, t_odd, lat_ref, lon_ref)
# typecode 1-4
pms.adsb.callsign(msg)
pms.adsb.position_with_ref(msg, lat_ref, lon_ref)
pms.adsb.airborne_position_with_ref(msg, lat_ref, lon_ref)
pms.adsb.surface_position_with_ref(msg, lat_ref, lon_ref)
# typecode 5-8 (surface) and 9-18 (airborne)
pms.adsb.position(msg_even, msg_odd, t_even, t_odd, lat_ref=None, lon_ref=None)
pms.adsb.airborne_position(msg_even, msg_odd, t_even, t_odd)
pms.adsb.surface_position(msg_even, msg_odd, t_even, t_odd, lat_ref, lon_ref)
pms.adsb.altitude(msg)
pms.adsb.position_with_ref(msg, lat_ref, lon_ref)
pms.adsb.airborne_position_with_ref(msg, lat_ref, lon_ref)
pms.adsb.surface_position_with_ref(msg, lat_ref, lon_ref)
pms.adsb.velocity(msg) # handles both surface & airborne messages
pms.adsb.speed_heading(msg) # handles both surface & airborne messages
pms.adsb.surface_velocity(msg)
pms.adsb.airborne_velocity(msg)
pms.adsb.altitude(msg)
# typecode: 19
pms.adsb.velocity(msg) # handles both surface & airborne messages
pms.adsb.speed_heading(msg) # handles both surface & airborne messages
pms.adsb.surface_velocity(msg)
pms.adsb.airborne_velocity(msg)
Note: When you have a fix position of the aircraft, it is convenient to
@@ -105,9 +111,9 @@ Core functions for ELS decoding:
.. code:: python
pms.els.icao(msg) # ICAO address
pms.els.df4alt(msg) # Altitude from any DF4 message
pms.ehs.df5id(msg) # Squawk code from any DF5 message
pms.els.icao(msg) # ICAO address
pms.els.df4alt(msg) # Altitude from any DF4 message
pms.ehs.df5id(msg) # Squawk code from any DF5 message
Core functions for EHS decoding:
@@ -115,56 +121,57 @@ Core functions for EHS decoding:
.. code:: python
pms.ehs.icao(msg) # ICAO address
pms.ehs.df20alt(msg) # Altitude from any DF20 message
pms.ehs.df21id(msg) # Squawk code from any DF21 message
pms.ehs.icao(msg) # ICAO address
pms.ehs.df20alt(msg) # Altitude from any DF20 message
pms.ehs.df21id(msg) # Squawk code from any DF21 message
pms.ehs.BDS(msg) # Comm-B Data Selector Version
pms.ehs.BDS(msg) # Comm-B Data Selector Version
# for BDS version 2,0
pms.ehs.isBDS20(msg) # Check if message is BDS 2,0
pms.ehs.callsign(msg) # Aircraft callsign
# for BDS version 2,0
pms.ehs.isBDS20(msg) # Check if message is BDS 2,0
pms.ehs.callsign(msg) # Aircraft callsign
# for BDS version 4,0
pms.ehs.isBDS40(msg) # Check if message is BDS 4,0
pms.ehs.alt40mcp(msg) # MCP/FCU selected altitude (ft)
pms.ehs.alt40fms(msg) # FMS selected altitude (ft)
pms.ehs.p40baro(msg) # Barometric pressure (mb)
# for BDS version 4,0
pms.ehs.isBDS40(msg) # Check if message is BDS 4,0
pms.ehs.alt40mcp(msg) # MCP/FCU selected altitude (ft)
pms.ehs.alt40fms(msg) # FMS selected altitude (ft)
pms.ehs.p40baro(msg) # Barometric pressure (mb)
# for BDS version 4,4
pms.ehs.isBDS44(msg, rev=False) # Check if message is BDS 4,4
pms.ehs.wind44(msg, rev=False) # wind speed (kt) and heading (deg)
pms.ehs.temp44(msg, rev=False) # temperature (C)
pms.ehs.p44(msg, rev=False) # pressure (hPa)
pms.ehs.hum44(msg, rev=False) # humidity (%)
# for BDS version 4,4
pms.ehs.isBDS44(msg, rev=False) # Check if message is BDS 4,4
pms.ehs.wind44(msg, rev=False) # wind speed (kt) and heading (deg)
pms.ehs.temp44(msg, rev=False) # temperature (C)
pms.ehs.p44(msg, rev=False) # pressure (hPa)
pms.ehs.hum44(msg, rev=False) # humidity (%)
# for BDS version 5,0
pms.ehs.isBDS50(msg) # Check if message is BDS 5,0
pms.ehs.roll50(msg) # roll angle (deg)
pms.ehs.trk50(msg) # track angle (deg)
pms.ehs.gs50(msg) # ground speed (kt)
pms.ehs.rtrk50(msg) # track angle rate (deg/sec)
pms.ehs.tas50(msg) # true airspeed (kt)
# for BDS version 5,0
pms.ehs.isBDS50(msg) # Check if message is BDS 5,0
pms.ehs.roll50(msg) # roll angle (deg)
pms.ehs.trk50(msg) # track angle (deg)
pms.ehs.gs50(msg) # ground speed (kt)
pms.ehs.rtrk50(msg) # track angle rate (deg/sec)
pms.ehs.tas50(msg) # true airspeed (kt)
# for BDS version 5,3
pms.ehs.isBDS53(msg) # Check if message is BDS 5,3
pms.ehs.hdg53(msg) # magnetic heading (deg)
pms.ehs.ias53(msg) # indicated airspeed (kt)
pms.ehs.mach53(msg) # MACH number
pms.ehs.tas53(msg) # true airspeed (kt)
pms.ehs.vr53(msg) # vertical rate (fpm)
# for BDS version 5,3
pms.ehs.isBDS53(msg) # Check if message is BDS 5,3
pms.ehs.hdg53(msg) # magnetic heading (deg)
pms.ehs.ias53(msg) # indicated airspeed (kt)
pms.ehs.mach53(msg) # MACH number
pms.ehs.tas53(msg) # true airspeed (kt)
pms.ehs.vr53(msg) # vertical rate (fpm)
# for BDS version 6,0
pms.ehs.isBDS60(msg) # Check if message is BDS 6,0
pms.ehs.hdg60(msg) # heading (deg)
pms.ehs.ias60(msg) # indicated airspeed (kt)
pms.ehs.mach60(msg) # MACH number
pms.ehs.vr60baro(msg) # barometric altitude rate (ft/min)
pms.ehs.vr60ins(msg) # inertial vertical speed (ft/min)
# for BDS version 6,0
pms.ehs.isBDS60(msg) # Check if message is BDS 6,0
pms.ehs.hdg60(msg) # heading (deg)
pms.ehs.ias60(msg) # indicated airspeed (kt)
pms.ehs.mach60(msg) # MACH number
pms.ehs.vr60baro(msg) # barometric altitude rate (ft/min)
pms.ehs.vr60ins(msg) # inertial vertical speed (ft/min)
Developement
------------
To perform unit tests. First install ``tox`` through pip, Then, run the following commands:
```
$ tox
```
.. code:: bash
$ tox

View File

@@ -266,10 +266,9 @@ def position_with_ref(msg, lat_ref, lon_ref):
of the true position.
Args:
msg0 (string): even message (28 bytes hexadecimal string)
msg1 (string): odd message (28 bytes hexadecimal string)
t0 (int): timestamps for the even message
t1 (int): timestamps for the odd message
msg (string): even message (28 bytes hexadecimal string)
lat_ref: previous known latitude
lon_ref: previous known longitude
Returns:
(float, float): (latitude, longitude) of the aircraft
@@ -546,7 +545,7 @@ def velocity(msg):
msg (string): 28 bytes hexadecimal message string
Returns:
(int, float, int, string): speed (kt), heading (degree),
(int, float, int, string): speed (kt), ground track or heading (degree),
rate of climb/descend (ft/min), and speed type
('GS' for ground speed, 'AS' for airspeed)
"""
@@ -561,27 +560,27 @@ def velocity(msg):
raise RuntimeError("incorrect or inconsistant message types, expecting 4<TC<9 or TC=19")
def speed_heading(msg):
"""Get speed and heading only from the velocity message
"""Get speed and ground track (or heading) from the velocity message
(handles both airborne or surface message)
Args:
msg (string): 28 bytes hexadecimal message string
Returns:
(int, float): speed (kt), heading (degree)
(int, float): speed (kt), ground track or heading (degree)
"""
spd, hdg, rocd, tag = velocity(msg)
return spd, hdg
spd, trk_or_hdg, rocd, tag = velocity(msg)
return spd, trk_or_hdg
def airborne_velocity(msg):
"""Calculate the speed, heading, and vertical rate
"""Calculate the speed, track (or heading), and vertical rate
Args:
msg (string): 28 bytes hexadecimal message string
Returns:
(int, float, int, string): speed (kt), heading (degree),
(int, float, int, string): speed (kt), ground track or heading (degree),
rate of climb/descend (ft/min), and speed type
('GS' for ground speed, 'AS' for airspeed)
"""
@@ -593,6 +592,9 @@ def airborne_velocity(msg):
subtype = util.bin2int(msgbin[37:40])
if util.bin2int(msgbin[46:56]) == 0 or util.bin2int(msgbin[57:67]) == 0:
return None
if subtype in (1, 2):
v_ew_sign = -1 if int(msgbin[45]) else 1
v_ew = util.bin2int(msgbin[46:56]) - 1 # east-west velocity
@@ -600,28 +602,31 @@ def airborne_velocity(msg):
v_ns_sign = -1 if int(msgbin[56]) else 1
v_ns = util.bin2int(msgbin[57:67]) - 1 # north-south velocity
v_we = v_ew_sign * v_ew
v_sn = v_ns_sign * v_ns
spd = math.sqrt(v_sn*v_sn + v_we*v_we) # unit in kts
hdg = math.atan2(v_we, v_sn)
hdg = math.degrees(hdg) # convert to degrees
hdg = hdg if hdg >= 0 else hdg + 360 # no negative val
trk = math.atan2(v_we, v_sn)
trk = math.degrees(trk) # convert to degrees
trk = trk if trk >= 0 else trk + 360 # no negative val
tag = 'GS'
trk_or_hdg = trk
else:
hdg = util.bin2int(msgbin[46:56]) / 1024.0 * 360.0
spd = util.bin2int(msgbin[57:67])
tag = 'AS'
trk_or_hdg = hdg
vr_sign = -1 if int(msgbin[68]) else 1
vr = (util.bin2int(msgbin[69:78]) - 1) * 64 # vertical rate, fpm
rocd = vr_sign * vr
return int(spd), round(hdg, 1), int(rocd), tag
return int(spd), round(trk_or_hdg, 1), int(rocd), tag
def surface_velocity(msg):
@@ -630,7 +635,7 @@ def surface_velocity(msg):
msg (string): 28 bytes hexadecimal message string
Returns:
(int, float, int, string): speed (kt), heading (degree),
(int, float, int, string): speed (kt), ground track (degree),
rate of climb/descend (ft/min), and speed type
('GS' for ground speed, 'AS' for airspeed)
"""
@@ -640,13 +645,13 @@ def surface_velocity(msg):
msgbin = util.hex2bin(msg)
# heading
hdg_status = int(msgbin[44])
if hdg_status == 1:
hdg = util.bin2int(msgbin[45:52]) * 360.0 / 128.0
hdg = round(hdg, 1)
# ground track
trk_status = int(msgbin[44])
if trk_status == 1:
trk = util.bin2int(msgbin[45:52]) * 360.0 / 128.0
trk = round(trk, 1)
else:
hdg = None
trk = None
# ground movment / speed
mov = util.bin2int(msgbin[37:44])
@@ -665,7 +670,7 @@ def surface_velocity(msg):
spd = kts[i-1] + (mov-movs[i-1]) * step
spd = round(spd, 2)
return spd, hdg, 0, 'GS'
return spd, trk, 0, 'GS'
def altitude_diff(msg):
"""Decode the differece between GNSS and barometric altitude

View File

@@ -325,12 +325,17 @@ def isBDS44(msg, rev=False):
result = result & checkbits(d, 5, 6, 23) \
& checkbits(d, 35, 36, 46) & checkbits(d, 47, 48, 49) \
& checkbits(d, 50, 51, 56)
# Bits 1-4 indicate source, values > 4 reserved and should not occur
if util.bin2int(d[0:4]) > 4:
result &= False
else:
# status bit 5, 15, 24, 36, 49
result = result & checkbits(d, 5, 6, 14) \
& checkbits(d, 15, 16, 23) & checkbits(d, 24, 25, 35) \
& checkbits(d, 36, 37, 47) & checkbits(d, 49, 50, 56)
# Bits 1-4 are reserved and should be zero
if util.bin2int(d[0:4]) != 0:
result &= False
if not result:
return False
@@ -339,9 +344,11 @@ def isBDS44(msg, rev=False):
if vw is not None and vw[0] > 250:
result &= False
# if temp44(msg):
# if temp44(msg) > 60 or temp44(msg) < -80:
# result &= False
if temp44(msg):
if temp44(msg) > 60 or temp44(msg) < -80:
result &= False
elif temp44(msg) == 0:
result &= False
return result
@@ -614,7 +621,7 @@ def rtrk50(msg):
if d[36:45] == "111111111":
return None
sign = int(d[35]) # 1 -> minus
sign = int(d[35]) # 1 -> negative value, two's complement
value = util.bin2int(d[36:45])
if sign:
value = value - 512
@@ -787,12 +794,15 @@ def vr53(msg):
if d[46] == '0':
return None
sign = d[47] # 1 -> minus
sign = int(d[47]) # 1 -> negative value, two's complement
value = util.bin2int(d[48:56])
if sign:
value = value - 256
if value == 0 or value == 255: # all zeros or all ones
return 0
value = value - 256 if sign else value
roc = value * 64 # feet/min
return roc
@@ -918,11 +928,13 @@ def vr60baro(msg):
if d[34] == '0':
return None
sign = d[35] # 1 -> minus
sign = int(d[35]) # 1 -> negative value, two's complement
value = util.bin2int(d[36:45])
if sign:
value = value - 512
if value == 0 or value == 511: # all zeros or all ones
return 0
value = value - 512 if sign else value
roc = value * 32 # feet/min
return roc
@@ -942,11 +954,13 @@ def vr60ins(msg):
if d[45] == '0':
return None
sign = d[46] # 1 -> minus
sign = int(d[46]) # 1 -> negative value, two's complement
value = util.bin2int(d[47:56])
if sign:
value = value - 512
if value == 0 or value == 511: # all zeros or all ones
return 0
value = value - 512 if sign else value
roc = value * 32 # feet/min
return roc

View File

@@ -30,7 +30,7 @@ setup(
# Versions should comply with PEP440. For a discussion on single-sourcing
# the version across setup.py and the project code, see
# https://packaging.python.org/en/latest/single_source_version.html
version='1.2.0',
version='1.2.2',
description='Python Mode-S Decoder',
long_description=long_description,

View File

@@ -14,7 +14,7 @@ def test_df20alt():
def test_ehs_BDS():
assert ehs.BDS("A0001838201584F23468207CDFA5") == 'BDS20'
assert ehs.BDS("A0001839CA3800315800007448D9") == 'BDS40'
assert ehs.BDS("A000031DBAA9DD18622C441330E9") == 'BDS44'
# assert ehs.BDS("A000031DBAA9DD18622C441330E9") == 'BDS44'
assert ehs.BDS("A000139381951536E024D4CCF6B5") == 'BDS50'
assert ehs.BDS("A00004128F39F91A7E27C46ADC21") == 'BDS60'

View File

@@ -1,6 +1,6 @@
[tox]
toxworkdir=/tmp/tox
envlist = py26,py27,py35
envlist = py2,py3
[testenv]
deps=pytest
commands=py.test