From f3f70cb13dfe0dbcf95f02068ec83f07853f6cd5 Mon Sep 17 00:00:00 2001 From: Gautier Hattenberger Date: Fri, 11 May 2018 10:52:58 +0200 Subject: [PATCH] [python] get air traffic from opensky-network and display in GCS (#2259) --- conf/control_panel_example.xml | 1 + sw/ground_segment/cockpit/intruders.ml | 4 +- .../python/opensky-network/opensky.py | 176 ++++++++++++++++++ 3 files changed, 179 insertions(+), 2 deletions(-) create mode 100755 sw/ground_segment/python/opensky-network/opensky.py diff --git a/conf/control_panel_example.xml b/conf/control_panel_example.xml index c3b284e2e1..32bc748dab 100644 --- a/conf/control_panel_example.xml +++ b/conf/control_panel_example.xml @@ -65,6 +65,7 @@ + diff --git a/sw/ground_segment/cockpit/intruders.ml b/sw/ground_segment/cockpit/intruders.ml index c7d520cca1..d7a72c1798 100644 --- a/sw/ground_segment/cockpit/intruders.ml +++ b/sw/ground_segment/cockpit/intruders.ml @@ -51,11 +51,11 @@ let update_intruder = fun id wgs84 heading alt speed climb time -> let intruder_exist = fun id -> Hashtbl.mem intruders id -(* remove old intruders after 10s *) +(* remove old intruders after 20s *) let remove_old_intruders = fun () -> Hashtbl.iter (fun id i -> - if (Unix.gettimeofday () -. i.last_update) > 10.0 then + if (Unix.gettimeofday () -. i.last_update) > 20.0 then remove_intruder id ) intruders diff --git a/sw/ground_segment/python/opensky-network/opensky.py b/sw/ground_segment/python/opensky-network/opensky.py new file mode 100755 index 0000000000..1a1fcb5a63 --- /dev/null +++ b/sw/ground_segment/python/opensky-network/opensky.py @@ -0,0 +1,176 @@ +#!/usr/bin/env python +# +# Copyright (C) 2018 Gautier Hattenberger +# +# This file is part of paparazzi. +# +# paparazzi 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 2, or (at your option) +# any later version. +# +# paparazzi 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 paparazzi; see the file COPYING. If not, see +# . +# + +''' +Get air traffic information from the opensky-network system (https://opensky-network.org/) + +The python API is required and should be installed using the instruction +from https://github.com/openskynetwork/opensky-api + +Positions of the UAVs are used to request for the surrounding aircraft +and INTRUDER messages are sent to the IVY bus for display in the GCS +''' + + +from __future__ import print_function + +import sys +from os import path, getenv +from time import sleep, time + +from opensky_api import OpenSkyApi + +# if PAPARAZZI_HOME not set, then assume the tree containing this +# file is a reasonable substitute +PPRZ_HOME = getenv("PAPARAZZI_HOME", path.normpath(path.join(path.dirname(path.abspath(__file__)), '../../../..'))) +sys.path.append(PPRZ_HOME + "/var/lib/python") + +from pprzlink.ivy import IvyMessagesInterface +from pprzlink.message import PprzMessage + + +class OpenSkyTraffic(object): + def __init__(self, period=10., margin=1., timeout=60., verbose=False): + self.period = period + self.margin = margin + self.timeout = timeout + self.last_receive = time() + self.verbose = verbose + self._interface = IvyMessagesInterface("OpenSkyTraffic") + self._opensky = OpenSkyApi() + self.ac_list = {} + self.intruder_list = {} + self.running = False + if self.verbose: + print("Starting opensky interface...") + + # subscribe to FLIGHT_PARAM ground message + self._interface.subscribe(self.update_ac, PprzMessage("ground", "FLIGHT_PARAM")) + + def stop(self): + if self.verbose: + print("Shutting down opensky interface...") + self.running = False + if self._interface is not None: + self._interface.shutdown() + + def __del__(self): + self.stop() + + def update_ac(self, ac_id, msg): + # update A/C info + self.last_receive = time() + self.ac_list[ac_id] = (self.last_receive, float(msg['lat']), float(msg['long'])) + + def filter_ac(self): + # purge timeout A/C + timeout = time() - self.timeout + for ac, (t, lat, lon) in self.ac_list.items(): + if t < timeout: + del self.ac_list[ac] + + def compute_bbox_list(self): + bbox = [] + # for now assume that all UAVs are close enough, so merge all boxes into one + # future improvement could handle groups of UAVs far appart + lat_min, lat_max, lon_min, lon_max = None, None, None, None + for ac, (t, lat, lon) in self.ac_list.items(): + if lat_min is None: + lat_min = lat + lat_max = lat + lon_min = lon + lon_max = lon + else: + lat_min = min(lat, lat_min) + lat_max = max(lat, lat_max) + lon_min = min(lon, lon_min) + lon_max = max(lon, lon_max) + + if lat_min is not None: + bbox.append((lat_min-self.margin, lat_max+self.margin, lon_min-self.margin, lon_max+self.margin)) + return bbox + + def get_intruders(self): + self.filter_ac() + bbox = self.compute_bbox_list() + # request surounding aircraft to opensky-network + self.intruder_list = {} + for bb in bbox: + states = self._opensky.get_states(bbox=bb) + if states is not None: + for s in states.states: + self.intruder_list[s.callsign] = s + + def send_intruder_msgs(self): + self.get_intruders() + def val_or_default(val, default): + if val is None: + return default + else: + return val + for i in self.intruder_list: + intruder = self.intruder_list[i] + if intruder.callsign is not None and len(intruder.callsign) > 0: + msg = PprzMessage("ground", "INTRUDER") + msg['id'] = intruder.icao24 + msg['name'] = intruder.callsign.replace(" ", "") + msg['lat'] = int(intruder.latitude * 1e7) + msg['lon'] = int(intruder.longitude * 1e7) + msg['alt'] = int(val_or_default(intruder.geo_altitude, 0.) * 1000.) + msg['course'] = val_or_default(intruder.heading, 0.) + msg['speed'] = val_or_default(intruder.velocity, 0.) + msg['climb'] = val_or_default(intruder.vertical_rate, 0.) + msg['itow'] = intruder.time_position + if self.verbose: + print(msg) + self._interface.send(msg) + + def run(self): + try: + self.running = True + t = 0. + while self.running: + t = t + 0.1 # increment timer until next update time is reach + if t > self.period: + self.send_intruder_msgs() + t = 0. + sleep(0.1) # wait a short time + except KeyboardInterrupt: + self.stop() + except Exception as e: + print("Failing with: "+str(e)) + self.stop() + + + +if __name__ == '__main__': + import argparse + + parser = argparse.ArgumentParser(description="OpenSky-Network traffic information") + parser.add_argument('-p', '--period', dest='period', default=10., type=float, help="update period") + parser.add_argument('-m', '--margin', dest='margin', default=1., type=float, help="margin in degree to define the bounding box") + parser.add_argument('-t', '--timeout', dest='timeout', default=60., type=float, help="timeout to remove A/C") + parser.add_argument('-v', '--verbose', dest='verbose', default=False, action='store_true', help="display debug messages") + args = parser.parse_args() + + osn = OpenSkyTraffic(args.period, args.margin, args.timeout, args.verbose) + osn.run() +