14 Commits
v1.2.0 ... 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
Junzi Sun
c708d57fcc Update README.rst 2017-09-19 11:19:54 +02:00
6 changed files with 157 additions and 131 deletions

View File

@@ -1,32 +1,26 @@
A Python Mode-S Decoder
=======================
The Python Mode-S Decoder
=========================
Python library for Mode-S message decoding. Two separate methods are
implemented to decode the following messages:
Python library for Mode-S message decoding. Support Downlink Formats (DF) are:
- Automatic Dependent Surveillance - Broadcast (ADS-B) (DF17)
- 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
A detailed manual on Mode-S decoding is published by the author, at:
http://adsb-decode-guide.readthedocs.io
Detailed manual on Mode-S decoding is published by the author, at:
http://mode-s.org/decode
Source code
@@ -37,21 +31,29 @@ https://github.com/junzis/pyModeS
API documentation at:
http://pymodes.readthedocs.io
Install
-------
Checkout source code, or install using pip:
The easiest installation is to use pip:
::
pip install pyModeS
pip install pyModeS
Usage
-----
To install latest devlopment version from the GitHub:
::
pip install git+https://github.com/junzis/pyModeS
Use the library
---------------
.. code:: python
import pyModeS as pms
import pyModeS as pms
Common functions:
@@ -59,15 +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.bin2gray(str) # Convert binary string to grey code
pms.gray2bin(str) # Convert grey code to binary string
pms.gray2int(str) # Convert grey code to interger
Core functions for ADS-B decoding:
@@ -75,26 +76,31 @@ 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)
Hint: When you have a fix position of the aircraft, it is convenient to
Note: When you have a fix position of the aircraft, it is convenient to
use `position_with_ref()` method to decode with only one position message
(either odd or even). This works with both airborne and surface position
messages. But the reference position shall be with in 180NM (airborne)
@@ -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 run tests, run the following commands:
```
$ tox
```
To perform unit tests. First install ``tox`` through pip, Then, run the following commands:
.. 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