"fetch from upstream"
This commit is contained in:
commit
6db5ea8023
47
README.rst
47
README.rst
@ -3,7 +3,7 @@ The Python Mode-S Decoder (2.0-dev)
|
||||
|
||||
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=5-8 / BDS 0,6: Surface position
|
||||
@ -40,7 +40,7 @@ New features in v2.0
|
||||
---------------------
|
||||
- New structure of the libraries
|
||||
- ADS-B and Comm-B data streaming
|
||||
- Active aircraft viewing (terminal cursor)
|
||||
- Active aircraft viewing (terminal curses)
|
||||
- Improved BDS identification
|
||||
- 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
|
||||
|
||||
|
||||
|
||||
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
|
||||
---------------
|
||||
|
||||
@ -147,18 +169,20 @@ Common Mode-S functions
|
||||
pms.icao(msg) # Infer the ICAO address from the message
|
||||
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)
|
||||
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)
|
||||
*************************************
|
||||
@ -206,6 +230,7 @@ Meteorological routine air report (MRAR) [Experimental]
|
||||
pms.commb.p44(msg, rev=False) # pressure (hPa)
|
||||
pms.commb.hum44(msg, rev=False) # humidity (%)
|
||||
|
||||
|
||||
Developement
|
||||
------------
|
||||
To perform unit tests. First install ``tox`` through pip, Then, run the following commands:
|
||||
|
BIN
doc/pmslive-screenshot.png
Normal file
BIN
doc/pmslive-screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 62 KiB |
21
pyModeS/decoder/acas.py
Normal file
21
pyModeS/decoder/acas.py
Normal 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
|
@ -166,53 +166,6 @@ def speed_heading(msg):
|
||||
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):
|
||||
"""Check the odd/even flag. Bit 54, 0 for even, 1 for odd.
|
||||
Args:
|
||||
@ -223,22 +176,47 @@ def oe_flag(msg):
|
||||
msgbin = common.hex2bin(msg)
|
||||
return int(msgbin[53])
|
||||
|
||||
# Uncertainty & accuracy
|
||||
|
||||
def nic_v1(msg,nic_sup_b):
|
||||
"""Calculate NIC, navigation integrity category
|
||||
def version(msg):
|
||||
"""ADS-B Version
|
||||
|
||||
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:
|
||||
int: NIC number (from 0 to 11), -1 if not applicable
|
||||
"""
|
||||
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)
|
||||
|
||||
if nic_sup_b in ['0', '1']:
|
||||
nic_sup_b = int(nic_sup_b)
|
||||
|
||||
if tc in [0, 8, 18, 22]:
|
||||
nic = 0
|
||||
elif tc == 17:
|
||||
@ -277,19 +255,34 @@ def nic_v1(msg,nic_sup_b):
|
||||
nic = -1
|
||||
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:
|
||||
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:
|
||||
int: NIC number (from 0 to 11), -1 if not applicable
|
||||
"""
|
||||
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)
|
||||
|
||||
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]:
|
||||
nic = 0
|
||||
elif tc == 17:
|
||||
@ -343,59 +336,67 @@ def nic_v2(msg,nic_a,nic_b,nic_c):
|
||||
return nic
|
||||
|
||||
|
||||
|
||||
def nic_s(msg):
|
||||
"""Calculate NICs, navigation integrity category supplement
|
||||
"""Obtain NIC supplement bit, TC=31 message
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
|
||||
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)
|
||||
|
||||
msgbin = common.hex2bin(msg)
|
||||
nic_s = common.bin2int(msgbin[75])
|
||||
nic_s = int(msgbin[75])
|
||||
|
||||
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:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
|
||||
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)
|
||||
|
||||
msgbin = common.hex2bin(msg)
|
||||
nic_a = common.bin2int(msgbin[75])
|
||||
nic_c = common.bin2int(msgbin[51])
|
||||
nic_a = int(msgbin[75])
|
||||
nic_c = int(msgbin[51])
|
||||
|
||||
return nic_a, nic_c
|
||||
|
||||
|
||||
def nic_b(msg):
|
||||
"""Calculate NICb, navigation integrity category supplement
|
||||
"""Obtain NICb, navigation integrity category supplement-b
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string
|
||||
|
||||
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)
|
||||
|
||||
if tc < 9 or tc > 18:
|
||||
raise RuntimeError("%s: Not a airborne position message, expecting 8<TC<19" % msg)
|
||||
|
||||
msgbin = common.hex2bin(msg)
|
||||
nic_b = common.bin2int(msgbin[39])
|
||||
nic_b = int(msgbin[39])
|
||||
|
||||
return nic_b
|
||||
|
||||
|
||||
def nac_p(msg):
|
||||
"""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
|
||||
|
||||
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]:
|
||||
raise RuntimeError("%s: Not a target state and status message neither operation status message, expecting TC = 29 or 31" % msg)
|
||||
tc = typecode(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)
|
||||
tc = typecode(msg)
|
||||
|
||||
if tc == 29:
|
||||
nac_p = common.bin2int(msgbin[71:75])
|
||||
elif tc == 31:
|
||||
nac_p = common.bin2int(msgbin[76:80])
|
||||
else:
|
||||
nac_p = -1
|
||||
|
||||
return nac_p
|
||||
|
||||
|
||||
@ -428,63 +431,44 @@ def nac_v(msg):
|
||||
Returns:
|
||||
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)
|
||||
|
||||
msgbin = common.hex2bin(msg)
|
||||
tc = typecode(msg)
|
||||
if tc == 19:
|
||||
nac_v = common.bin2int(msgbin[42:45])
|
||||
else:
|
||||
nac_v = -1
|
||||
return nac_v
|
||||
|
||||
def sil(msg,version):
|
||||
|
||||
def sil(msg, version):
|
||||
"""Calculate SIL, Surveillance Integrity Level
|
||||
|
||||
Args:
|
||||
msg (string): 28 bytes hexadecimal message string with TC = 29, 31
|
||||
|
||||
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]:
|
||||
raise RuntimeError("%s: Not a target state and status message neither operation status message, expecting TC = 29 or 31" % msg)
|
||||
tc = typecode(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)
|
||||
tc = typecode(msg)
|
||||
|
||||
if tc == 29:
|
||||
sil = common.bin2int(msgbin[76:78])
|
||||
elif tc == 31:
|
||||
sil = common.bin2int(msg[82:84])
|
||||
else:
|
||||
sil = -1
|
||||
|
||||
sil_sup = None
|
||||
|
||||
if version == 2:
|
||||
if typecode(msg) == 29:
|
||||
sils = common.bin2int(msgbin[39])
|
||||
elif typecode(msg) == 31:
|
||||
sils = common.bin2int(msgbin[86])
|
||||
else:
|
||||
sils = -1
|
||||
if version == 29:
|
||||
sil_sup = common.bin2int(msgbin[39])
|
||||
elif version == 31:
|
||||
sil_sup = common.bin2int(msgbin[86])
|
||||
|
||||
return sil, sils
|
||||
|
||||
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
|
||||
return sil, sil_sup
|
||||
|
21
pyModeS/decoder/allcall.py
Normal file
21
pyModeS/decoder/allcall.py
Normal 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
21
pyModeS/decoder/surv.py
Normal 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
|
@ -2,6 +2,7 @@
|
||||
Stream beast raw data from a TCP server, convert to mode-s messages
|
||||
'''
|
||||
from __future__ import print_function, division
|
||||
import os
|
||||
import sys
|
||||
import socket
|
||||
import time
|
||||
@ -13,12 +14,15 @@ else:
|
||||
PY_VERSION = 2
|
||||
|
||||
class BaseClient(Thread):
|
||||
def __init__(self, host, port):
|
||||
def __init__(self, host, port, rawtype):
|
||||
Thread.__init__(self)
|
||||
self.host = host
|
||||
self.port = port
|
||||
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):
|
||||
while True:
|
||||
@ -33,6 +37,33 @@ class BaseClient(Thread):
|
||||
print("Socket connection error: %s. reconnecting..." % err)
|
||||
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):
|
||||
'''
|
||||
<esc> "1" : 6 byte MLAT timestamp, 1 byte signal level,
|
||||
@ -91,6 +122,8 @@ class BaseClient(Thread):
|
||||
# extract messages
|
||||
messages = []
|
||||
for mm in messages_mlat:
|
||||
ts = time.time()
|
||||
|
||||
msgtype = mm[0]
|
||||
# print(''.join('%02X' % i for i in mm))
|
||||
|
||||
@ -108,11 +141,10 @@ class BaseClient(Thread):
|
||||
# incomplete message
|
||||
continue
|
||||
|
||||
ts = time.time()
|
||||
|
||||
messages.append([msg, ts])
|
||||
return messages
|
||||
|
||||
|
||||
def handle_messages(self, messages):
|
||||
"""re-implement this method to handle the messages"""
|
||||
for msg, t in messages:
|
||||
@ -136,7 +168,10 @@ class BaseClient(Thread):
|
||||
# continue
|
||||
# -- Removed!! Cause delay in low data rate scenario --
|
||||
|
||||
if self.rawtype == 'beast':
|
||||
messages = self.read_beast_buffer()
|
||||
elif self.rawtype == 'avr':
|
||||
messages = self.read_avr_buffer()
|
||||
|
||||
if not messages:
|
||||
continue
|
0
pyModeS/streamer/__init__.py
Normal file
0
pyModeS/streamer/__init__.py
Normal file
49
pyModeS/streamer/pmstream.py → pyModeS/streamer/pmslive
Normal file → Executable file
49
pyModeS/streamer/pmstream.py → pyModeS/streamer/pmslive
Normal file → Executable file
@ -1,37 +1,39 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from __future__ import print_function, division
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
import curses
|
||||
import numpy as np
|
||||
import time
|
||||
from threading import Lock
|
||||
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.screen import Screen
|
||||
|
||||
LOCK = Lock()
|
||||
ADSB_MSG = []
|
||||
ADSB_TS = []
|
||||
EHS_MSG = []
|
||||
EHS_TS = []
|
||||
COMMB_MSG = []
|
||||
COMMB_TS = []
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--server', help='server address or IP', required=True)
|
||||
parser.add_argument('--port', help='Raw beast port', required=True)
|
||||
parser.add_argument('--lat0', help='Latitude of receiver', required=True)
|
||||
parser.add_argument('--lon0', help='Longitude of receiver', required=True)
|
||||
parser.add_argument('--port', help='raw data port', required=True)
|
||||
parser.add_argument('--rawtype', help='beast or avr', required=True)
|
||||
parser.add_argument('--latlon', help='receiver position', nargs=2, metavar=('LAT', 'LON'), required=True)
|
||||
args = parser.parse_args()
|
||||
|
||||
SERVER = args.server
|
||||
PORT = int(args.port)
|
||||
LAT0 = float(args.lat0) # 51.9899 for TU Delft
|
||||
LON0 = float(args.lon0) # 4.3754
|
||||
RAWTYPE = args.rawtype
|
||||
LAT0 = float(args.latlon[0])
|
||||
LON0 = float(args.latlon[1])
|
||||
|
||||
|
||||
class ModesClient(BaseClient):
|
||||
def __init__(self, host, port):
|
||||
super(ModesClient, self).__init__(host, port)
|
||||
def __init__(self, host, port, rawtype):
|
||||
super(ModesClient, self).__init__(host, port, rawtype)
|
||||
|
||||
def handle_messages(self, messages):
|
||||
local_buffer_adsb_msg = []
|
||||
@ -58,14 +60,15 @@ class ModesClient(BaseClient):
|
||||
LOCK.acquire()
|
||||
ADSB_MSG.extend(local_buffer_adsb_msg)
|
||||
ADSB_TS.extend(local_buffer_adsb_ts)
|
||||
EHS_MSG.extend(local_buffer_ehs_msg)
|
||||
EHS_TS.extend(local_buffer_ehs_ts)
|
||||
COMMB_MSG.extend(local_buffer_ehs_msg)
|
||||
COMMB_TS.extend(local_buffer_ehs_ts)
|
||||
LOCK.release()
|
||||
|
||||
|
||||
# redirect all stdout to null, avoiding messing up with the screen
|
||||
sys.stdout = open(os.devnull, 'w')
|
||||
|
||||
client = ModesClient(host=SERVER, port=PORT)
|
||||
client = ModesClient(host=SERVER, port=PORT, rawtype=RAWTYPE)
|
||||
client.daemon = True
|
||||
client.start()
|
||||
|
||||
@ -79,21 +82,21 @@ try:
|
||||
while True:
|
||||
if len(ADSB_MSG) > 200:
|
||||
LOCK.acquire()
|
||||
stream.process_raw(ADSB_TS, ADSB_MSG, EHS_TS, EHS_MSG)
|
||||
stream.process_raw(ADSB_TS, ADSB_MSG, COMMB_TS, COMMB_MSG)
|
||||
ADSB_MSG = []
|
||||
ADSB_TS = []
|
||||
EHS_MSG = []
|
||||
EHS_TS = []
|
||||
COMMB_MSG = []
|
||||
COMMB_TS = []
|
||||
LOCK.release()
|
||||
|
||||
acs = stream.get_aircraft()
|
||||
# try:
|
||||
try:
|
||||
screen.update_data(acs)
|
||||
screen.update()
|
||||
# except KeyboardInterrupt:
|
||||
# raise
|
||||
# except:
|
||||
# continue
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
continue
|
||||
|
||||
except KeyboardInterrupt:
|
||||
sys.exit(0)
|
@ -1,16 +1,35 @@
|
||||
from __future__ import print_function, division
|
||||
import os
|
||||
import curses
|
||||
import numpy as np
|
||||
import time
|
||||
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):
|
||||
def __init__(self):
|
||||
Thread.__init__(self)
|
||||
self.screen = curses.initscr()
|
||||
curses.noecho()
|
||||
curses.mousemask(1)
|
||||
self.screen.keypad(True)
|
||||
self.y = 3
|
||||
self.x = 1
|
||||
@ -27,7 +46,7 @@ class Screen(Thread):
|
||||
|
||||
def draw_frame(self):
|
||||
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):
|
||||
if len(self.acs) == 0:
|
||||
@ -44,14 +63,17 @@ class Screen(Thread):
|
||||
|
||||
row = 1
|
||||
|
||||
header = 'icao'
|
||||
for c in COLUMNS:
|
||||
c = 'updated' if c=='t' else c
|
||||
header += '%10s' % c
|
||||
header = ' icao'
|
||||
for c, cw in COLUMNS:
|
||||
header += (cw-len(c))*' ' + c
|
||||
|
||||
# fill end with spaces
|
||||
header += (self.scr_w - 2 - len(header)) * ' '
|
||||
|
||||
if len(header) > self.scr_w - 2:
|
||||
header = header[:self.scr_w-3] + '>'
|
||||
|
||||
|
||||
self.screen.addstr(row, 1, header)
|
||||
|
||||
row +=1
|
||||
@ -74,19 +96,23 @@ class Screen(Thread):
|
||||
|
||||
line += icao
|
||||
|
||||
for c in COLUMNS:
|
||||
|
||||
if c == 't':
|
||||
val = str(int(ac[c]))
|
||||
line += '%12s' % val
|
||||
for c, cw in COLUMNS:
|
||||
if c=='live':
|
||||
val = int(time.time() - ac[c])
|
||||
elif ac[c] is None:
|
||||
val = ''
|
||||
else:
|
||||
val = '' if ac[c] is None else ac[c]
|
||||
line += '%10s' % val
|
||||
val = ac[c]
|
||||
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:
|
||||
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)
|
||||
elif row == self.y:
|
||||
self.screen.addstr(row, 1, line, curses.A_BOLD)
|
||||
@ -108,7 +134,10 @@ class Screen(Thread):
|
||||
while True:
|
||||
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.y = 1
|
||||
elif c == curses.KEY_NPAGE:
|
||||
@ -131,3 +160,6 @@ class Screen(Thread):
|
||||
self.y = y_intent
|
||||
elif c == curses.KEY_ENTER or c == 10 or c == 13:
|
||||
self.lock_icao = (self.screen.instr(self.y, 1, 6)).decode()
|
||||
elif c == curses.KEY_F5:
|
||||
self.screen.refresh()
|
||||
self.draw_frame()
|
||||
|
@ -1,7 +1,7 @@
|
||||
from __future__ import absolute_import, print_function, division
|
||||
import numpy as np
|
||||
import time
|
||||
from pyModeS.decoder import adsb, ehs
|
||||
import pyModeS as pms
|
||||
|
||||
class Stream():
|
||||
def __init__(self, lat0, lon0):
|
||||
@ -18,8 +18,8 @@ class Stream():
|
||||
self.cache_timeout = 60 # seconds
|
||||
|
||||
|
||||
def process_raw(self, adsb_ts, adsb_msgs, ehs_ts, ehs_msgs, tnow=None):
|
||||
"""process a chunk of adsb and ehs messages recieved in the same
|
||||
def process_raw(self, adsb_ts, adsb_msgs, commb_ts, commb_msgs, tnow=None):
|
||||
"""process a chunk of adsb and commb messages recieved in the same
|
||||
time period.
|
||||
"""
|
||||
if tnow is None:
|
||||
@ -31,11 +31,12 @@ class Stream():
|
||||
|
||||
# process adsb message
|
||||
for t, msg in zip(adsb_ts, adsb_msgs):
|
||||
icao = adsb.icao(msg)
|
||||
tc = adsb.typecode(msg)
|
||||
icao = pms.icao(msg)
|
||||
tc = pms.adsb.typecode(msg)
|
||||
|
||||
if icao not in self.acs:
|
||||
self.acs[icao] = {
|
||||
'live': None,
|
||||
'lat': None,
|
||||
'lon': None,
|
||||
'alt': None,
|
||||
@ -46,20 +47,20 @@ class Stream():
|
||||
'ias': None,
|
||||
'mach': None,
|
||||
'hdg': None,
|
||||
'adsb_version' : None,
|
||||
'nic_s' : None,
|
||||
'nic_a' : None,
|
||||
'nic_b' : None,
|
||||
'nic_c' : None
|
||||
'ver' : None,
|
||||
'NIC' : None,
|
||||
'NACp' : None,
|
||||
'NACv' : None,
|
||||
'SIL' : None
|
||||
}
|
||||
|
||||
self.acs[icao]['t'] = t
|
||||
self.acs[icao]['live'] = int(t)
|
||||
|
||||
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):
|
||||
vdata = adsb.velocity(msg)
|
||||
vdata = pms.adsb.velocity(msg)
|
||||
if vdata is None:
|
||||
continue
|
||||
|
||||
@ -75,7 +76,7 @@ class Stream():
|
||||
self.acs[icao]['tv'] = t
|
||||
|
||||
if (5 <= tc <= 18):
|
||||
oe = adsb.oe_flag(msg)
|
||||
oe = pms.adsb.oe_flag(msg)
|
||||
self.acs[icao][oe] = msg
|
||||
self.acs[icao]['t'+str(oe)] = t
|
||||
|
||||
@ -83,21 +84,21 @@ class Stream():
|
||||
# use single message decoding
|
||||
rlat = self.acs[icao]['lat']
|
||||
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 \
|
||||
(abs(self.acs[icao]['t0'] - self.acs[icao]['t1']) < 10):
|
||||
# use multi message decoding
|
||||
try:
|
||||
latlon = adsb.position(
|
||||
# try:
|
||||
latlon = pms.adsb.position(
|
||||
self.acs[icao][0],
|
||||
self.acs[icao][1],
|
||||
self.acs[icao]['t0'],
|
||||
self.acs[icao]['t1'],
|
||||
self.lat0, self.lon0
|
||||
)
|
||||
except:
|
||||
# mix of surface and airborne position message
|
||||
continue
|
||||
# except:
|
||||
# # mix of surface and airborne position message
|
||||
# continue
|
||||
else:
|
||||
latlon = None
|
||||
|
||||
@ -105,71 +106,60 @@ class Stream():
|
||||
self.acs[icao]['tpos'] = t
|
||||
self.acs[icao]['lat'] = latlon[0]
|
||||
self.acs[icao]['lon'] = latlon[1]
|
||||
<<<<<<< HEAD
|
||||
self.acs[icao]['alt'] = adsb.altitude(msg)
|
||||
# local_updated_acs_buffer.append(icao)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
|
||||
if (5 <= tc <= 8):
|
||||
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.nic_a,self.acs[icao]['nic_b'],self.acs[icao]['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'])
|
||||
ac = self.acs[icao]
|
||||
|
||||
if (5 <= tc <= 8) or (9 <= tc <= 18) or (20 <= tc <= 22):
|
||||
if (ac['ver'] == 1) and ('nic_s' in ac.keys()):
|
||||
self.acs[icao]['NIC'] = pms.adsb.nic_v1(msg, ac['nic_s'])
|
||||
elif (ac['ver'] == 2) and ('nic_a' in ac.keys()) and ('nic_b' in ac.keys()):
|
||||
self.acs[icao]['NIC'] = pms.adsb.nic_v2(msg, ac['nic_a'], ac['nic_b'], ac['nic_c'])
|
||||
if tc == 19:
|
||||
self.acs[icao]['nac_v'] = adsb.nac_v(msg)
|
||||
if (20 <= tc <= 22):
|
||||
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 ac['ver'] in [1, 2]:
|
||||
self.acs[icao]['NACv'] = pms.adsb.nac_v(msg)
|
||||
if tc == 29:
|
||||
if self.acs[icao]['adsb_version'] != None:
|
||||
self.acs[icao]['sil'] = adsb.sil(msg,self.acs[icao]['adsb_version'])
|
||||
self.acs[icao]['nac_p'] = adsb.nac_p(msg)
|
||||
if ac['ver'] != None:
|
||||
self.acs[icao]['SIL'], self.acs[icao]['sil_s'] = pms.adsb.sil(msg, ac['ver'])
|
||||
self.acs[icao]['NACp'] = pms.adsb.nac_p(msg)
|
||||
if tc == 31:
|
||||
self.acs[icao]['adsb_version'] = adsb.version(msg)
|
||||
self.acs[icao]['sil'] = adsb.version(msg)
|
||||
self.acs[icao]['nac_p'] = adsb.nac_p(msg)
|
||||
if self.acs[icao]['adsb_version'] == 1:
|
||||
self.acs[icao]['nic_s'] = adsb.nic_s(msg)
|
||||
elif self.acs[icao]['adsb_version'] == 2:
|
||||
self.acs[icao]['nic_a'] , self.acs[icao]['nic_c'] = adsb.nic_a_and_c(msg)
|
||||
self.acs[icao]['ver'] = pms.adsb.version(msg)
|
||||
self.acs[icao]['SIL'] = pms.adsb.version(msg)
|
||||
self.acs[icao]['NACp'] = pms.adsb.nac_p(msg)
|
||||
if self.acs[icao]['ver'] == 1:
|
||||
self.acs[icao]['nic_s'] = pms.adsb.nic_s(msg)
|
||||
elif self.acs[icao]['ver'] == 2:
|
||||
self.acs[icao]['nic_a'], self.acs[icao]['nic_c'] = pms.adsb.nic_a_c(msg)
|
||||
|
||||
|
||||
|
||||
|
||||
# process ehs message
|
||||
for t, msg in zip(ehs_ts, ehs_msgs):
|
||||
icao = ehs.icao(msg)
|
||||
# process commb message
|
||||
for t, msg in zip(commb_ts, commb_msgs):
|
||||
icao = pms.icao(msg)
|
||||
|
||||
if icao not in self.acs:
|
||||
continue
|
||||
|
||||
bds = ehs.BDS(msg)
|
||||
bds = pms.bds.infer(msg)
|
||||
|
||||
if bds == 'BDS50':
|
||||
tas = ehs.tas50(msg)
|
||||
tas = pms.commb.tas50(msg)
|
||||
|
||||
if tas:
|
||||
self.acs[icao]['t50'] = t
|
||||
self.acs[icao]['tas'] = tas
|
||||
|
||||
elif bds == 'BDS60':
|
||||
ias = ehs.ias60(msg)
|
||||
hdg = ehs.hdg60(msg)
|
||||
mach = ehs.mach60(msg)
|
||||
ias = pms.commb.ias60(msg)
|
||||
hdg = pms.commb.hdg60(msg)
|
||||
mach = pms.commb.mach60(msg)
|
||||
|
||||
if ias or hdg or mach:
|
||||
self.acs[icao]['t60'] = t
|
||||
@ -182,7 +172,7 @@ class Stream():
|
||||
|
||||
# clear up old data
|
||||
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]
|
||||
continue
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user