"fetch from upstream"

This commit is contained in:
JoseAndresMR 2018-06-25 16:25:17 +02:00
commit 6db5ea8023
12 changed files with 456 additions and 322 deletions

View File

@ -3,7 +3,7 @@ The Python Mode-S Decoder (2.0-dev)
Python library for Mode-S message decoding. Support Downlink Formats (DF) are: Python library for Mode-S message decoding. Support Downlink Formats (DF) are:
**Automatic Dependent Surveillance - Broadcast (ADS-B) (DF17)** **Automatic Dependent Surveillance - Broadcast (ADS-B) (DF 17/18)**
- TC=1-4 / BDS 0,8: Aircraft identification and category - TC=1-4 / BDS 0,8: Aircraft identification and category
- TC=5-8 / BDS 0,6: Surface position - TC=5-8 / BDS 0,6: Surface position
@ -40,7 +40,7 @@ New features in v2.0
--------------------- ---------------------
- New structure of the libraries - New structure of the libraries
- ADS-B and Comm-B data streaming - ADS-B and Comm-B data streaming
- Active aircraft viewing (terminal cursor) - Active aircraft viewing (terminal curses)
- Improved BDS identification - Improved BDS identification
- Optimizing decoding speed - Optimizing decoding speed
@ -65,6 +65,28 @@ To install latest development version (dev-2.0) from the GitHub:
pip install git+https://github.com/junzis/pyModeS pip install git+https://github.com/junzis/pyModeS
Live view traffic (pmslive)
----------------------------------------------------
Supports **Mode-S Beast** and **AVR** raw stream
::
pmslive --server [server_address] --port [tcp_port] --rawtype [beast_or_avr] --latlon [lat] [lon]
Arguments:
-h, --help show this help message and exit
--server SERVER server address or IP
--port PORT raw data port
--rawtype RAWTYPE beast or avr
--latlon LAT LON receiver position
Example screen shot:
.. image:: https://github.com/junzis/pyModeS/raw/master/doc/pmslive-screenshot.png
:width: 700px
Use the library Use the library
--------------- ---------------
@ -147,18 +169,20 @@ Common Mode-S functions
pms.icao(msg) # Infer the ICAO address from the message pms.icao(msg) # Infer the ICAO address from the message
pms.bds.infer(msg) # Infer the Modes-S BDS code pms.bds.infer(msg) # Infer the Modes-S BDS code
pms.bds.is10(msg) # check if BDS is 1,0 explicitly
pms.bds.is17(msg) # check if BDS is 1,7 explicitly
pms.bds.is20(msg) # check if BDS is 2,0 explicitly
pms.bds.is30(msg) # check if BDS is 3,0 explicitly
pms.bds.is40(msg) # check if BDS is 4,0 explicitly
pms.bds.is44(msg) # check if BDS is 4,4 explicitly
pms.bds.is50(msg) # check if BDS is 5,0 explicitly
pms.bds.is60(msg) # check if BDS is 6,0 explicitly
# check if BDS is 5,0 or 6,0, give reference spd, trk, alt (from ADS-B) # check if BDS is 5,0 or 6,0, give reference spd, trk, alt (from ADS-B)
pms.bds.is50or60(msg, spd_ref, trk_ref, alt_ref) pms.bds.is50or60(msg, spd_ref, trk_ref, alt_ref)
# check each BDS explicitly
pms.bds.bds10.is10(msg)
pms.bds.bds17.is17(msg)
pms.bds.bds20.is20(msg)
pms.bds.bds30.is30(msg)
pms.bds.bds40.is40(msg)
pms.bds.bds44.is44(msg)
pms.bds.bds50.is50(msg)
pms.bds.bds60.is60(msg)
Mode-S elementary surveillance (ELS) Mode-S elementary surveillance (ELS)
************************************* *************************************
@ -206,6 +230,7 @@ Meteorological routine air report (MRAR) [Experimental]
pms.commb.p44(msg, rev=False) # pressure (hPa) pms.commb.p44(msg, rev=False) # pressure (hPa)
pms.commb.hum44(msg, rev=False) # humidity (%) pms.commb.hum44(msg, rev=False) # humidity (%)
Developement Developement
------------ ------------
To perform unit tests. First install ``tox`` through pip, Then, run the following commands: To perform unit tests. First install ``tox`` through pip, Then, run the following commands:

BIN
doc/pmslive-screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

21
pyModeS/decoder/acas.py Normal file
View File

@ -0,0 +1,21 @@
# Copyright (C) 2018 Junzi Sun (TU Delft)
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Decoding Air-Air Surveillance (ACAS) DF=0/16
"""
from __future__ import absolute_import, print_function, division
from pyModeS.decoder import common

View File

@ -166,53 +166,6 @@ def speed_heading(msg):
return spd, trk_or_hdg return spd, trk_or_hdg
def nic(msg):
"""Calculate NIC, navigation integrity category
Args:
msg (string): 28 bytes hexadecimal message string
Returns:
int: NIC number (from 0 to 11), -1 if not applicable
"""
if typecode(msg) < 9 or typecode(msg) > 18:
raise RuntimeError("%s: Not a airborne position message, expecting 8<TC<19" % msg)
msgbin = common.hex2bin(msg)
tc = typecode(msg)
nic_sup_b = common.bin2int(msgbin[39])
if tc in [0, 18, 22]:
nic = 0
elif tc == 17:
nic = 1
elif tc == 16:
if nic_sup_b:
nic = 3
else:
nic = 2
elif tc == 15:
nic = 4
elif tc == 14:
nic = 5
elif tc == 13:
nic = 6
elif tc == 12:
nic = 7
elif tc == 11:
if nic_sup_b:
nic = 9
else:
nic = 8
elif tc in [10, 21]:
nic = 10
elif tc in [9, 20]:
nic = 11
else:
nic = -1
return nic
def oe_flag(msg): def oe_flag(msg):
"""Check the odd/even flag. Bit 54, 0 for even, 1 for odd. """Check the odd/even flag. Bit 54, 0 for even, 1 for odd.
Args: Args:
@ -223,22 +176,47 @@ def oe_flag(msg):
msgbin = common.hex2bin(msg) msgbin = common.hex2bin(msg)
return int(msgbin[53]) return int(msgbin[53])
# Uncertainty & accuracy
def nic_v1(msg,nic_sup_b): def version(msg):
"""Calculate NIC, navigation integrity category """ADS-B Version
Args: Args:
msg (string): 28 bytes hexadecimal message string, nic_sup_b (int): NIC supplement msg (string): 28 bytes hexadecimal message string, TC = 31
Returns:
int: version number
"""
tc = typecode(msg)
if tc != 31:
raise RuntimeError("%s: Not a status operation message, expecting TC = 31" % msg)
msgbin = common.hex2bin(msg)
version = common.bin2int(msgbin[72:75])
return version
def nic_v1(msg, nic_sup_b):
"""Calculate NIC, navigation integrity category for ADS-B version 1
Args:
msg (string): 28 bytes hexadecimal message string
nic_sup_b (int or string): NIC supplement
Returns: Returns:
int: NIC number (from 0 to 11), -1 if not applicable int: NIC number (from 0 to 11), -1 if not applicable
""" """
if typecode(msg) < 5 or typecode(msg) > 22: if typecode(msg) < 5 or typecode(msg) > 22:
raise RuntimeError("%s: Not a surface position message (5<TC<8, )airborne position message (8<TC<19), airborne position with GNSS height (20<TC<22)" % msg) raise RuntimeError("%s: Not a surface position message (5<TC<8), \
airborne position message (8<TC<19), \
or airborne position with GNSS height (20<TC<22)" % msg)
tc = typecode(msg) tc = typecode(msg)
if nic_sup_b in ['0', '1']:
nic_sup_b = int(nic_sup_b)
if tc in [0, 8, 18, 22]: if tc in [0, 8, 18, 22]:
nic = 0 nic = 0
elif tc == 17: elif tc == 17:
@ -277,19 +255,34 @@ def nic_v1(msg,nic_sup_b):
nic = -1 nic = -1
return nic return nic
def nic_v2(msg,nic_a,nic_b,nic_c):
"""Calculate NIC, navigation integrity category def nic_v2(msg, nic_a, nic_b, nic_c):
"""Calculate NIC, navigation integrity category, for ADS-B version 2
Args: Args:
msg (string): 28 bytes hexadecimal message string, nic_a (int): NIC supplement, nic_b (int): NIC supplement, nic_c (int): NIC supplement msg (string): 28 bytes hexadecimal message string
nic_a (int or string): NIC supplement
nic_b (int or srting): NIC supplement
nic_c (int or string): NIC supplement
Returns: Returns:
int: NIC number (from 0 to 11), -1 if not applicable int: NIC number (from 0 to 11), -1 if not applicable
""" """
if typecode(msg) < 5 or typecode(msg) > 22: if typecode(msg) < 5 or typecode(msg) > 22:
raise RuntimeError("%s: Not a surface position message (5<TC<8, )airborne position message (8<TC<19), airborne position with GNSS height (20<TC<22)" % msg) raise RuntimeError("%s: Not a surface position message (5<TC<8) \
airborne position message (8<TC<19), \
or airborne position with GNSS height (20<TC<22)" % msg)
tc = typecode(msg) tc = typecode(msg)
if nic_a in ['0', '1']:
nic_a = int(nic_a)
if nic_b in ['0', '1']:
nic_b = int(nic_b)
if nic_c in ['0', '1']:
nic_c = int(nic_c)
if tc in [0, 18, 22]: if tc in [0, 18, 22]:
nic = 0 nic = 0
elif tc == 17: elif tc == 17:
@ -343,59 +336,67 @@ def nic_v2(msg,nic_a,nic_b,nic_c):
return nic return nic
def nic_s(msg): def nic_s(msg):
"""Calculate NICs, navigation integrity category supplement """Obtain NIC supplement bit, TC=31 message
Args: Args:
msg (string): 28 bytes hexadecimal message string msg (string): 28 bytes hexadecimal message string
Returns: Returns:
int: NIC number (from 0 to 11), -1 if not applicable int: NICs number (0 or 1)
""" """
if typecode(msg) != 31: tc = typecode(msg)
if tc != 31:
raise RuntimeError("%s: Not a status operation message, expecting TC = 31" % msg) raise RuntimeError("%s: Not a status operation message, expecting TC = 31" % msg)
msgbin = common.hex2bin(msg) msgbin = common.hex2bin(msg)
nic_s = common.bin2int(msgbin[75]) nic_s = int(msgbin[75])
return nic_s return nic_s
def nic_a_and_c(msg):
"""Calculate NICa and NICc, navigation integrity category supplements def nic_a_c(msg):
"""Obtain NICa/c, navigation integrity category supplements a and c
Args: Args:
msg (string): 28 bytes hexadecimal message string msg (string): 28 bytes hexadecimal message string
Returns: Returns:
int: NIC number (from 0 to 11), -1 if not applicable (int, int): NICa and NICc number (0 or 1)
""" """
if typecode(msg) != 31: tc = typecode(msg)
if tc != 31:
raise RuntimeError("%s: Not a status operation message, expecting TC = 31" % msg) raise RuntimeError("%s: Not a status operation message, expecting TC = 31" % msg)
msgbin = common.hex2bin(msg) msgbin = common.hex2bin(msg)
nic_a = common.bin2int(msgbin[75]) nic_a = int(msgbin[75])
nic_c = common.bin2int(msgbin[51]) nic_c = int(msgbin[51])
return nic_a, nic_c return nic_a, nic_c
def nic_b(msg): def nic_b(msg):
"""Calculate NICb, navigation integrity category supplement """Obtain NICb, navigation integrity category supplement-b
Args: Args:
msg (string): 28 bytes hexadecimal message string msg (string): 28 bytes hexadecimal message string
Returns: Returns:
int: NIC number (from 0 to 11), -1 if not applicable int: NICb number (0 or 1)
""" """
if typecode(msg) < 9 or typecode(msg) > 18: tc = typecode(msg)
raise RuntimeError("%s: Not a airborne position message, expecting 8<TC<19" % msg)
if tc < 9 or tc > 18:
raise RuntimeError("%s: Not a airborne position message, expecting 8<TC<19" % msg)
msgbin = common.hex2bin(msg) msgbin = common.hex2bin(msg)
nic_b = common.bin2int(msgbin[39]) nic_b = int(msgbin[39])
return nic_b return nic_b
def nac_p(msg): def nac_p(msg):
"""Calculate NACp, Navigation Accuracy Category - Position """Calculate NACp, Navigation Accuracy Category - Position
@ -403,19 +404,21 @@ def nac_p(msg):
msg (string): 28 bytes hexadecimal message string, TC = 29 or 31 msg (string): 28 bytes hexadecimal message string, TC = 29 or 31
Returns: Returns:
int: NACp number (from 0 to 11), -1 if not applicable int: NACp number (0 or 1)
""" """
if typecode(msg) not in [29,31]: tc = typecode(msg)
raise RuntimeError("%s: Not a target state and status message neither operation status message, expecting TC = 29 or 31" % msg)
if tc not in [29, 31]:
raise RuntimeError("%s: Not a target state and status message, \
or operation status message, expecting TC = 29 or 31" % msg)
msgbin = common.hex2bin(msg) msgbin = common.hex2bin(msg)
tc = typecode(msg)
if tc == 29: if tc == 29:
nac_p = common.bin2int(msgbin[71:75]) nac_p = common.bin2int(msgbin[71:75])
elif tc == 31: elif tc == 31:
nac_p = common.bin2int(msgbin[76:80]) nac_p = common.bin2int(msgbin[76:80])
else:
nac_p = -1
return nac_p return nac_p
@ -428,63 +431,44 @@ def nac_v(msg):
Returns: Returns:
int: NACv number (from 0 to 4), -1 if not applicable int: NACv number (from 0 to 4), -1 if not applicable
""" """
if typecode(msg) != 19: tc = typecode(msg)
if tc != 19:
raise RuntimeError("%s: Not an airborne velocity message, expecting TC = 19" % msg) raise RuntimeError("%s: Not an airborne velocity message, expecting TC = 19" % msg)
msgbin = common.hex2bin(msg) msgbin = common.hex2bin(msg)
tc = typecode(msg) nac_v = common.bin2int(msgbin[42:45])
if tc == 19:
nac_v = common.bin2int(msgbin[42:45])
else:
nac_v = -1
return nac_v return nac_v
def sil(msg,version):
"""Calculate SIL, Surveillance Integrity Level def sil(msg, version):
"""Calculate SIL, Surveillance Integrity Level
Args: Args:
msg (string): 28 bytes hexadecimal message string with TC = 29, 31 msg (string): 28 bytes hexadecimal message string with TC = 29, 31
Returns: Returns:
int: sil number, -1 if not applicable (int, int): sil number and sil supplement (only for v2)
""" """
if typecode(msg) not in [29,31]: tc = typecode(msg)
raise RuntimeError("%s: Not a target state and status message neither operation status message, expecting TC = 29 or 31" % msg)
if tc not in [29, 31]:
raise RuntimeError("%s: Not a target state and status messag, \
or operation status message, expecting TC = 29 or 31" % msg)
msgbin = common.hex2bin(msg) msgbin = common.hex2bin(msg)
tc = typecode(msg)
if tc == 29: if tc == 29:
sil = common.bin2int(msgbin[76:78]) sil = common.bin2int(msgbin[76:78])
elif tc == 31: elif tc == 31:
sil = common.bin2int(msg[82:84]) sil = common.bin2int(msg[82:84])
else:
sil = -1 sil_sup = None
if version == 2: if version == 2:
if typecode(msg) == 29: if version == 29:
sils = common.bin2int(msgbin[39]) sil_sup = common.bin2int(msgbin[39])
elif typecode(msg) == 31: elif version == 31:
sils = common.bin2int(msgbin[86]) sil_sup = common.bin2int(msgbin[86])
else:
sils = -1
return sil, sils return sil, sil_sup
def version(msg):
"""ADS-B Version
Args:
msg (string): 28 bytes hexadecimal message string, TC = 31
Returns:
int: version number
"""
msgbin = common.hex2bin(msg)
if typecode(msg) not in [29,31]:
raise RuntimeError("%s: Not a target state and status message neither operation status message, expecting TC = 29 or 31" % msg)
if typecode(msg) in [29,31]:
version = common.bin2int(msgbin[72:75])
else:
version = -1
return version

View File

@ -0,0 +1,21 @@
# Copyright (C) 2018 Junzi Sun (TU Delft)
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Decoding all call replies DF=11
"""
from __future__ import absolute_import, print_function, division
from pyModeS.decoder import common

21
pyModeS/decoder/surv.py Normal file
View File

@ -0,0 +1,21 @@
# Copyright (C) 2018 Junzi Sun (TU Delft)
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Warpper for short roll call surveillance replies DF=4/5
"""
from __future__ import absolute_import, print_function, division
from pyModeS.decoder import common

View File

@ -2,6 +2,7 @@
Stream beast raw data from a TCP server, convert to mode-s messages Stream beast raw data from a TCP server, convert to mode-s messages
''' '''
from __future__ import print_function, division from __future__ import print_function, division
import os
import sys import sys
import socket import socket
import time import time
@ -13,12 +14,15 @@ else:
PY_VERSION = 2 PY_VERSION = 2
class BaseClient(Thread): class BaseClient(Thread):
def __init__(self, host, port): def __init__(self, host, port, rawtype):
Thread.__init__(self) Thread.__init__(self)
self.host = host self.host = host
self.port = port self.port = port
self.buffer = [] self.buffer = []
self.rawtype = rawtype
if self.rawtype not in ['avr', 'beast']:
print("rawtype must be either avr or beast")
os._exit(1)
def connect(self): def connect(self):
while True: while True:
@ -33,6 +37,33 @@ class BaseClient(Thread):
print("Socket connection error: %s. reconnecting..." % err) print("Socket connection error: %s. reconnecting..." % err)
time.sleep(3) time.sleep(3)
def read_avr_buffer(self):
# -- testing --
# for b in self.buffer:
# print(chr(b), b)
# Append message with 0-9,A-F,a-f, until stop sign
messages = []
msg_stop = False
for b in self.buffer:
if b == 59:
msg_stop = True
ts = time.time()
messages.append([self.current_msg, ts])
if b == 42:
msg_stop = False
self.current_msg = ''
if (not msg_stop) and (48<=b<=57 or 65<=b<=70 or 97<=b<=102):
self.current_msg = self.current_msg + chr(b)
self.buffer = []
return messages
def read_beast_buffer(self): def read_beast_buffer(self):
''' '''
<esc> "1" : 6 byte MLAT timestamp, 1 byte signal level, <esc> "1" : 6 byte MLAT timestamp, 1 byte signal level,
@ -91,6 +122,8 @@ class BaseClient(Thread):
# extract messages # extract messages
messages = [] messages = []
for mm in messages_mlat: for mm in messages_mlat:
ts = time.time()
msgtype = mm[0] msgtype = mm[0]
# print(''.join('%02X' % i for i in mm)) # print(''.join('%02X' % i for i in mm))
@ -108,11 +141,10 @@ class BaseClient(Thread):
# incomplete message # incomplete message
continue continue
ts = time.time()
messages.append([msg, ts]) messages.append([msg, ts])
return messages return messages
def handle_messages(self, messages): def handle_messages(self, messages):
"""re-implement this method to handle the messages""" """re-implement this method to handle the messages"""
for msg, t in messages: for msg, t in messages:
@ -136,7 +168,10 @@ class BaseClient(Thread):
# continue # continue
# -- Removed!! Cause delay in low data rate scenario -- # -- Removed!! Cause delay in low data rate scenario --
messages = self.read_beast_buffer() if self.rawtype == 'beast':
messages = self.read_beast_buffer()
elif self.rawtype == 'avr':
messages = self.read_avr_buffer()
if not messages: if not messages:
continue continue

View File

207
pyModeS/streamer/pmstream.py → pyModeS/streamer/pmslive Normal file → Executable file
View File

@ -1,102 +1,105 @@
from __future__ import print_function, division #!/usr/bin/env python
import os
import sys from __future__ import print_function, division
import argparse import os
import curses import sys
import numpy as np import argparse
import time import curses
from threading import Lock from threading import Lock
import pyModeS as pms import pyModeS as pms
from pyModeS.extra.beastclient import BaseClient from pyModeS.extra.tcpclient import BaseClient
from pyModeS.streamer.stream import Stream from pyModeS.streamer.stream import Stream
from pyModeS.streamer.screen import Screen from pyModeS.streamer.screen import Screen
LOCK = Lock() LOCK = Lock()
ADSB_MSG = [] ADSB_MSG = []
ADSB_TS = [] ADSB_TS = []
EHS_MSG = [] COMMB_MSG = []
EHS_TS = [] COMMB_TS = []
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('--server', help='server address or IP', required=True) parser.add_argument('--server', help='server address or IP', required=True)
parser.add_argument('--port', help='Raw beast port', required=True) parser.add_argument('--port', help='raw data port', required=True)
parser.add_argument('--lat0', help='Latitude of receiver', required=True) parser.add_argument('--rawtype', help='beast or avr', required=True)
parser.add_argument('--lon0', help='Longitude of receiver', required=True) parser.add_argument('--latlon', help='receiver position', nargs=2, metavar=('LAT', 'LON'), required=True)
args = parser.parse_args() args = parser.parse_args()
SERVER = args.server SERVER = args.server
PORT = int(args.port) PORT = int(args.port)
LAT0 = float(args.lat0) # 51.9899 for TU Delft RAWTYPE = args.rawtype
LON0 = float(args.lon0) # 4.3754 LAT0 = float(args.latlon[0])
LON0 = float(args.latlon[1])
class ModesClient(BaseClient):
def __init__(self, host, port):
super(ModesClient, self).__init__(host, port) class ModesClient(BaseClient):
def __init__(self, host, port, rawtype):
def handle_messages(self, messages): super(ModesClient, self).__init__(host, port, rawtype)
local_buffer_adsb_msg = []
local_buffer_adsb_ts = [] def handle_messages(self, messages):
local_buffer_ehs_msg = [] local_buffer_adsb_msg = []
local_buffer_ehs_ts = [] local_buffer_adsb_ts = []
local_buffer_ehs_msg = []
for msg, t in messages: local_buffer_ehs_ts = []
if len(msg) < 28: # only process long messages
continue for msg, t in messages:
if len(msg) < 28: # only process long messages
df = pms.df(msg) continue
if df == 17 or df == 18: df = pms.df(msg)
local_buffer_adsb_msg.append(msg)
local_buffer_adsb_ts.append(t) if df == 17 or df == 18:
elif df == 20 or df == 21: local_buffer_adsb_msg.append(msg)
local_buffer_ehs_msg.append(msg) local_buffer_adsb_ts.append(t)
local_buffer_ehs_ts.append(t) elif df == 20 or df == 21:
else: local_buffer_ehs_msg.append(msg)
continue local_buffer_ehs_ts.append(t)
else:
continue
LOCK.acquire()
ADSB_MSG.extend(local_buffer_adsb_msg)
ADSB_TS.extend(local_buffer_adsb_ts) LOCK.acquire()
EHS_MSG.extend(local_buffer_ehs_msg) ADSB_MSG.extend(local_buffer_adsb_msg)
EHS_TS.extend(local_buffer_ehs_ts) ADSB_TS.extend(local_buffer_adsb_ts)
LOCK.release() COMMB_MSG.extend(local_buffer_ehs_msg)
COMMB_TS.extend(local_buffer_ehs_ts)
LOCK.release()
sys.stdout = open(os.devnull, 'w')
client = ModesClient(host=SERVER, port=PORT) # redirect all stdout to null, avoiding messing up with the screen
client.daemon = True sys.stdout = open(os.devnull, 'w')
client.start()
client = ModesClient(host=SERVER, port=PORT, rawtype=RAWTYPE)
stream = Stream(lat0=LAT0, lon0=LON0) client.daemon = True
client.start()
try:
screen = Screen() stream = Stream(lat0=LAT0, lon0=LON0)
screen.daemon = True
screen.start() try:
screen = Screen()
while True: screen.daemon = True
if len(ADSB_MSG) > 200: screen.start()
LOCK.acquire()
stream.process_raw(ADSB_TS, ADSB_MSG, EHS_TS, EHS_MSG) while True:
ADSB_MSG = [] if len(ADSB_MSG) > 200:
ADSB_TS = [] LOCK.acquire()
EHS_MSG = [] stream.process_raw(ADSB_TS, ADSB_MSG, COMMB_TS, COMMB_MSG)
EHS_TS = [] ADSB_MSG = []
LOCK.release() ADSB_TS = []
COMMB_MSG = []
acs = stream.get_aircraft() COMMB_TS = []
# try: LOCK.release()
screen.update_data(acs)
screen.update() acs = stream.get_aircraft()
# except KeyboardInterrupt: try:
# raise screen.update_data(acs)
# except: screen.update()
# continue except KeyboardInterrupt:
raise
except KeyboardInterrupt: except:
sys.exit(0) continue
finally: except KeyboardInterrupt:
curses.endwin() sys.exit(0)
finally:
curses.endwin()

View File

@ -1,16 +1,35 @@
from __future__ import print_function, division from __future__ import print_function, division
import os
import curses import curses
import numpy as np import numpy as np
import time import time
from threading import Thread from threading import Thread
COLUMNS = ['lat', 'lon', 'alt', 'gs', 'tas', 'ias', 'mach', 'roc', 'trk', 'hdg', 't'] COLUMNS = [
('lat', 10),
('lon', 10),
('alt', 7),
('gs', 5),
('tas', 5),
('ias', 5),
('mach', 7),
('roc', 7),
('trk', 10),
('hdg', 10),
('ver', 4),
('NIC', 5),
('NACv', 5),
('NACp', 5),
('SIL', 5),
('live', 6),
]
class Screen(Thread): class Screen(Thread):
def __init__(self): def __init__(self):
Thread.__init__(self) Thread.__init__(self)
self.screen = curses.initscr() self.screen = curses.initscr()
curses.noecho() curses.noecho()
curses.mousemask(1)
self.screen.keypad(True) self.screen.keypad(True)
self.y = 3 self.y = 3
self.x = 1 self.x = 1
@ -27,7 +46,7 @@ class Screen(Thread):
def draw_frame(self): def draw_frame(self):
self.screen.border(0) self.screen.border(0)
self.screen.addstr(0, 2, "Online aircraft ('crtl+c' to exit, 'enter' to select)") self.screen.addstr(0, 2, "Online aircraft ('ESC' to exit, 'Enter' to lock one)")
def update(self): def update(self):
if len(self.acs) == 0: if len(self.acs) == 0:
@ -44,14 +63,17 @@ class Screen(Thread):
row = 1 row = 1
header = 'icao' header = ' icao'
for c in COLUMNS: for c, cw in COLUMNS:
c = 'updated' if c=='t' else c header += (cw-len(c))*' ' + c
header += '%10s' % c
# fill end with spaces
header += (self.scr_w - 2 - len(header)) * ' '
if len(header) > self.scr_w - 2: if len(header) > self.scr_w - 2:
header = header[:self.scr_w-3] + '>' header = header[:self.scr_w-3] + '>'
self.screen.addstr(row, 1, header) self.screen.addstr(row, 1, header)
row +=1 row +=1
@ -74,19 +96,23 @@ class Screen(Thread):
line += icao line += icao
for c in COLUMNS: for c, cw in COLUMNS:
if c=='live':
if c == 't': val = int(time.time() - ac[c])
val = str(int(ac[c])) elif ac[c] is None:
line += '%12s' % val val = ''
else: else:
val = '' if ac[c] is None else ac[c] val = ac[c]
line += '%10s' % val val_str = str(val)
line += (cw-len(val_str))*' ' + val_str
# fill end with spaces
line += (self.scr_w - 2 - len(line)) * ' '
if len(line) > self.scr_w - 2: if len(line) > self.scr_w - 2:
line = line[:self.scr_w-3] + '>' line = line[:self.scr_w-3] + '>'
if self.lock_icao == icao: if (icao is not None) and (self.lock_icao == icao):
self.screen.addstr(row, 1, line, curses.A_STANDOUT) self.screen.addstr(row, 1, line, curses.A_STANDOUT)
elif row == self.y: elif row == self.y:
self.screen.addstr(row, 1, line, curses.A_BOLD) self.screen.addstr(row, 1, line, curses.A_BOLD)
@ -108,7 +134,10 @@ class Screen(Thread):
while True: while True:
c = self.screen.getch() c = self.screen.getch()
if c == curses.KEY_HOME: if c == 27:
curses.endwin()
os._exit(1)
elif c == curses.KEY_HOME:
self.x = 1 self.x = 1
self.y = 1 self.y = 1
elif c == curses.KEY_NPAGE: elif c == curses.KEY_NPAGE:
@ -131,3 +160,6 @@ class Screen(Thread):
self.y = y_intent self.y = y_intent
elif c == curses.KEY_ENTER or c == 10 or c == 13: elif c == curses.KEY_ENTER or c == 10 or c == 13:
self.lock_icao = (self.screen.instr(self.y, 1, 6)).decode() self.lock_icao = (self.screen.instr(self.y, 1, 6)).decode()
elif c == curses.KEY_F5:
self.screen.refresh()
self.draw_frame()

View File

@ -1,7 +1,7 @@
from __future__ import absolute_import, print_function, division from __future__ import absolute_import, print_function, division
import numpy as np import numpy as np
import time import time
from pyModeS.decoder import adsb, ehs import pyModeS as pms
class Stream(): class Stream():
def __init__(self, lat0, lon0): def __init__(self, lat0, lon0):
@ -18,8 +18,8 @@ class Stream():
self.cache_timeout = 60 # seconds self.cache_timeout = 60 # seconds
def process_raw(self, adsb_ts, adsb_msgs, ehs_ts, ehs_msgs, tnow=None): def process_raw(self, adsb_ts, adsb_msgs, commb_ts, commb_msgs, tnow=None):
"""process a chunk of adsb and ehs messages recieved in the same """process a chunk of adsb and commb messages recieved in the same
time period. time period.
""" """
if tnow is None: if tnow is None:
@ -31,11 +31,12 @@ class Stream():
# process adsb message # process adsb message
for t, msg in zip(adsb_ts, adsb_msgs): for t, msg in zip(adsb_ts, adsb_msgs):
icao = adsb.icao(msg) icao = pms.icao(msg)
tc = adsb.typecode(msg) tc = pms.adsb.typecode(msg)
if icao not in self.acs: if icao not in self.acs:
self.acs[icao] = { self.acs[icao] = {
'live': None,
'lat': None, 'lat': None,
'lon': None, 'lon': None,
'alt': None, 'alt': None,
@ -46,20 +47,20 @@ class Stream():
'ias': None, 'ias': None,
'mach': None, 'mach': None,
'hdg': None, 'hdg': None,
'adsb_version' : None, 'ver' : None,
'nic_s' : None, 'NIC' : None,
'nic_a' : None, 'NACp' : None,
'nic_b' : None, 'NACv' : None,
'nic_c' : None 'SIL' : None
} }
self.acs[icao]['t'] = t self.acs[icao]['live'] = int(t)
if 1 <= tc <= 4: if 1 <= tc <= 4:
self.acs[icao]['callsign'] = adsb.callsign(msg) self.acs[icao]['callsign'] = pms.adsb.callsign(msg)
if (5 <= tc <= 8) or (tc == 19): if (5 <= tc <= 8) or (tc == 19):
vdata = adsb.velocity(msg) vdata = pms.adsb.velocity(msg)
if vdata is None: if vdata is None:
continue continue
@ -75,7 +76,7 @@ class Stream():
self.acs[icao]['tv'] = t self.acs[icao]['tv'] = t
if (5 <= tc <= 18): if (5 <= tc <= 18):
oe = adsb.oe_flag(msg) oe = pms.adsb.oe_flag(msg)
self.acs[icao][oe] = msg self.acs[icao][oe] = msg
self.acs[icao]['t'+str(oe)] = t self.acs[icao]['t'+str(oe)] = t
@ -83,21 +84,21 @@ class Stream():
# use single message decoding # use single message decoding
rlat = self.acs[icao]['lat'] rlat = self.acs[icao]['lat']
rlon = self.acs[icao]['lon'] rlon = self.acs[icao]['lon']
latlon = adsb.position_with_ref(msg, rlat, rlon) latlon = pms.adsb.position_with_ref(msg, rlat, rlon)
elif ('t0' in self.acs[icao]) and ('t1' in self.acs[icao]) and \ elif ('t0' in self.acs[icao]) and ('t1' in self.acs[icao]) and \
(abs(self.acs[icao]['t0'] - self.acs[icao]['t1']) < 10): (abs(self.acs[icao]['t0'] - self.acs[icao]['t1']) < 10):
# use multi message decoding # use multi message decoding
try: # try:
latlon = adsb.position( latlon = pms.adsb.position(
self.acs[icao][0], self.acs[icao][0],
self.acs[icao][1], self.acs[icao][1],
self.acs[icao]['t0'], self.acs[icao]['t0'],
self.acs[icao]['t1'], self.acs[icao]['t1'],
self.lat0, self.lon0 self.lat0, self.lon0
) )
except: # except:
# mix of surface and airborne position message # # mix of surface and airborne position message
continue # continue
else: else:
latlon = None latlon = None
@ -105,71 +106,60 @@ class Stream():
self.acs[icao]['tpos'] = t self.acs[icao]['tpos'] = t
self.acs[icao]['lat'] = latlon[0] self.acs[icao]['lat'] = latlon[0]
self.acs[icao]['lon'] = latlon[1] self.acs[icao]['lon'] = latlon[1]
<<<<<<< HEAD
self.acs[icao]['alt'] = adsb.altitude(msg) self.acs[icao]['alt'] = adsb.altitude(msg)
# local_updated_acs_buffer.append(icao)acs[icao]['adsb_version'] # local_updated_acs_buffer.append(icao)acs[icao]['adsb_version']
local_updated_acs_buffer.append(acs[icao]['adsb_version']) local_updated_acs_buffer.append(acs[icao]['adsb_version'])
=======
self.acs[icao]['alt'] = pms.adsb.altitude(msg)
local_updated_acs_buffer.append(icao)
>>>>>>> upstream/master
# Uncertainty & accuracy # Uncertainty & accuracy
if (5 <= tc <= 8): ac = self.acs[icao]
if self.acs[icao]['adsb_version'] == 1:
if self.acs[icao]['nic_s'] != None: if (5 <= tc <= 8) or (9 <= tc <= 18) or (20 <= tc <= 22):
self.nic = adsb.nic_v1(msg,self.acs[icao]['nic_s']) if (ac['ver'] == 1) and ('nic_s' in ac.keys()):
elif self.acs[icao]['adsb_version'] == 2: self.acs[icao]['NIC'] = pms.adsb.nic_v1(msg, ac['nic_s'])
if self.acs[icao]['nic_a'] != None and self.acs[icao]['nic_b'] != None: elif (ac['ver'] == 2) and ('nic_a' in ac.keys()) and ('nic_b' in ac.keys()):
self.nic = adsb.nic_v2(msg,self.nic_a,self.acs[icao]['nic_b'],self.acs[icao]['nic_c']) self.acs[icao]['NIC'] = pms.adsb.nic_v2(msg, ac['nic_a'], ac['nic_b'], ac['nic_c'])
if (9 <= tc <= 18):
if self.acs[icao]['adsb_version'] == 1:
if self.acs[icao]['nic_s'] != None:
self.nic = adsb.nic_v1(msg,self.acs[icao]['nic_s'])
elif self.acs[icao]['adsb_version'] == 2:
self.acs[icao]['nic_b'] = adsb.nic_b(msg)
if self.acs[icao]['nic_a'] != None and self.acs[icao]['nic_b'] != None:
self.nic = adsb.nic_v2(msg,self.acs[icao]['nic_a'],self.nic_b,self.acs[icao]['nic_c'])
if tc == 19: if tc == 19:
self.acs[icao]['nac_v'] = adsb.nac_v(msg) if ac['ver'] in [1, 2]:
if (20 <= tc <= 22): self.acs[icao]['NACv'] = pms.adsb.nac_v(msg)
if self.acs[icao]['adsb_version'] == 1:
if self.acs[icao]['nic_s'] != None:
self.nic = adsb.nic_v1(msg,self.acs[icao]['nic_s'])
elif self.acs[icao]['adsb_version'] == 2:
if self.acs[icao]['nic_a'] != None and self.acs[icao]['nic_b'] != None:
self.nic = adsb.nic_v2(msg,self.acs[icao]['nic_a'],self.acs[icao]['nic_b'],self.acs[icao]['nic_c'])
if tc == 29: if tc == 29:
if self.acs[icao]['adsb_version'] != None: if ac['ver'] != None:
self.acs[icao]['sil'] = adsb.sil(msg,self.acs[icao]['adsb_version']) self.acs[icao]['SIL'], self.acs[icao]['sil_s'] = pms.adsb.sil(msg, ac['ver'])
self.acs[icao]['nac_p'] = adsb.nac_p(msg) self.acs[icao]['NACp'] = pms.adsb.nac_p(msg)
if tc == 31: if tc == 31:
self.acs[icao]['adsb_version'] = adsb.version(msg) self.acs[icao]['ver'] = pms.adsb.version(msg)
self.acs[icao]['sil'] = adsb.version(msg) self.acs[icao]['SIL'] = pms.adsb.version(msg)
self.acs[icao]['nac_p'] = adsb.nac_p(msg) self.acs[icao]['NACp'] = pms.adsb.nac_p(msg)
if self.acs[icao]['adsb_version'] == 1: if self.acs[icao]['ver'] == 1:
self.acs[icao]['nic_s'] = adsb.nic_s(msg) self.acs[icao]['nic_s'] = pms.adsb.nic_s(msg)
elif self.acs[icao]['adsb_version'] == 2: elif self.acs[icao]['ver'] == 2:
self.acs[icao]['nic_a'] , self.acs[icao]['nic_c'] = adsb.nic_a_and_c(msg) self.acs[icao]['nic_a'], self.acs[icao]['nic_c'] = pms.adsb.nic_a_c(msg)
# process commb message
for t, msg in zip(commb_ts, commb_msgs):
# process ehs message icao = pms.icao(msg)
for t, msg in zip(ehs_ts, ehs_msgs):
icao = ehs.icao(msg)
if icao not in self.acs: if icao not in self.acs:
continue continue
bds = ehs.BDS(msg) bds = pms.bds.infer(msg)
if bds == 'BDS50': if bds == 'BDS50':
tas = ehs.tas50(msg) tas = pms.commb.tas50(msg)
if tas: if tas:
self.acs[icao]['t50'] = t self.acs[icao]['t50'] = t
self.acs[icao]['tas'] = tas self.acs[icao]['tas'] = tas
elif bds == 'BDS60': elif bds == 'BDS60':
ias = ehs.ias60(msg) ias = pms.commb.ias60(msg)
hdg = ehs.hdg60(msg) hdg = pms.commb.hdg60(msg)
mach = ehs.mach60(msg) mach = pms.commb.mach60(msg)
if ias or hdg or mach: if ias or hdg or mach:
self.acs[icao]['t60'] = t self.acs[icao]['t60'] = t
@ -182,7 +172,7 @@ class Stream():
# clear up old data # clear up old data
for icao in list(self.acs.keys()): for icao in list(self.acs.keys()):
if self.t - self.acs[icao]['t'] > self.cache_timeout: if self.t - self.acs[icao]['live'] > self.cache_timeout:
del self.acs[icao] del self.acs[icao]
continue continue
@ -214,4 +204,4 @@ class Stream():
def reset_new_aircraft(self): def reset_new_aircraft(self):
"""reset the updated icao buffer once been read""" """reset the updated icao buffer once been read"""
self.__new_acs = set() self.__new_acs = set()

View File

@ -117,4 +117,6 @@ setup(
# 'sample=sample:main', # 'sample=sample:main',
# ], # ],
# }, # },
scripts=['pyModeS/streamer/pmslive'],
) )