diff --git a/Config.ipynb b/Config.ipynb new file mode 100644 index 0000000..d01df7a --- /dev/null +++ b/Config.ipynb @@ -0,0 +1,284 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# SkyScan Config\n", + "Make temporary changes to a running SkyScan instance. It will revert back to the values in the environment file when restart." + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": {}, + "outputs": [], + "source": [ + "broker=\"192.168.1.47\" # update with the IP for the Raspberry PI" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Requirement already satisfied: paho-mqtt in /Users/lberndt/opt/anaconda3/lib/python3.8/site-packages (1.5.0)\r\n" + ] + } + ], + "source": [ + "!pip install paho-mqtt" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": {}, + "outputs": [], + "source": [ + "import paho.mqtt.client as mqtt\n", + "import json" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [], + "source": [ + "client = mqtt.Client(\"notebook-config\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Camera Zoom\n", + "This is how much the camera is zoomed in. It is an Int number between 0-9999 (max)" + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 69, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "client.connect(broker)\n", + "data = {}\n", + "data['cameraZoom'] = 9999 # Update this Value\n", + "json_data = json.dumps(data)\n", + "client.publish(\"skyscan/config/json\",json_data)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Camera Delay\n", + "Float value for the number of seconds to wait after sending the camera move API command, before sending the take picture API command." + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 58, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "client.connect(broker)\n", + "data = {}\n", + "data['cameraDelay'] = 0.25 # Update this Value\n", + "json_data = json.dumps(data)\n", + "client.publish(\"skyscan/config/json\",json_data)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Camera Move Speed\n", + "This is how fast the camea will move. It is an Int number between 0-99 (max)" + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 56, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "client.connect(broker)\n", + "data = {}\n", + "data['cameraMoveSpeed'] = 99 # Update this Value\n", + "json_data = json.dumps(data)\n", + "client.publish(\"skyscan/config/json\",json_data)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Camera Lead\n", + "This is how far the tracker should move the center point in front of the currently tracked plane. It is a float, and is measured in seconds, example: 0.25 . It is based on the planes current heading and how fast it is going. " + ] + }, + { + "cell_type": "code", + "execution_count": 70, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 70, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "client.connect(broker)\n", + "data = {}\n", + "data['cameraLead'] = 0.45 # Update this Value\n", + "json_data = json.dumps(data)\n", + "client.publish(\"skyscan/config/json\",json_data)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Camera Bearing\n", + "This is a float to correct the cameras heading to help it better align with True North. It can be from -180 to 180. " + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 65, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "client.connect(broker)\n", + "data = {}\n", + "data['cameraBearing'] = -2 # Update this Value\n", + "json_data = json.dumps(data)\n", + "client.publish(\"skyscan/config/json\",json_data)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Minimum Elevation\n", + "The minimum elevation above the horizon which the Tracker will follow an airplane. Int value between 0-90 degrees." + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 62, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "client.connect(broker)\n", + "data = {}\n", + "data['minElevation'] = 45 # Update this Value\n", + "json_data = json.dumps(data)\n", + "client.publish(\"skyscan/config/json\",json_data)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.3" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/axis-ptz/camera.py b/axis-ptz/camera.py index 2459dc0..c0ebc55 100755 --- a/axis-ptz/camera.py +++ b/axis-ptz/camera.py @@ -32,8 +32,10 @@ cameraZoom = None cameraMoveSpeed = None cameraDelay = None +cameraBearing = 0 object_topic = None flight_topic = None +config_topic = "skyscan/config/json" pan = 0 tilt = 0 actualPan = 0 @@ -75,14 +77,13 @@ def setXY(x,y): def setPan(bearing): global pan - camera_bearing = args.bearing - diff_heading = getHeadingDiff(bearing, camera_bearing) + #diff_heading = getHeadingDiff(bearing, cameraBearing) - if pan != bearing: #abs(pan - diff_heading) > 2: #only update the pan if there has been a big change - #logging.info("Heading Diff %d for Bearing %d & Camera Bearing: %d"% (diff_heading, bearing, camera_bearing)) + if pan != bearing: #bearing #abs(pan - diff_heading) > 2: #only update the pan if there has been a big change + #logging.info("Heading Diff %d for Bearing %d & Camera Bearing: %d"% (diff_heading, bearing, cameraBearing)) - pan = bearing #diff_heading + pan = bearing #logging.info("Setting Pan to: %d"%pan) return True @@ -124,7 +125,7 @@ def get_jpeg_request(): # 5.2.4.1 """ payload = { 'resolution': "1920x1080", - 'compression': 5, + 'compression': 0, 'camera': 1, } url = 'http://' + args.axis_ip + '/axis-cgi/jpg/image.cgi' @@ -233,6 +234,25 @@ def moveCamera(): # Sleep for a bit so we're not hammering the camera withupdates time.sleep(0.005) + +def update_config(config): + global cameraZoom + global cameraMoveSpeed + global cameraDelay + global cameraBearing + + if "cameraZoom" in config: + cameraZoom = int(config["cameraZoom"]) + logging.info("Setting Camera Zoom to: {}".format(cameraZoom)) + if "cameraDelay" in config: + cameraDelay = float(config["cameraDelay"]) + logging.info("Setting Camera Delay to: {}".format(cameraDelay)) + if "cameraMoveSpeed" in config: + cameraMoveSpeed = int(config["cameraMoveSpeed"]) + logging.info("Setting Camera Move Speed to: {}".format(cameraMoveSpeed)) + if "cameraBearing" in config: + cameraBearing = float(config["cameraBearing"]) + logging.info("Setting Bearing Correction to: {}".format(cameraBearing)) ############################################# ## MQTT Callback Function ## ############################################# @@ -265,6 +285,9 @@ def on_message(client, userdata, message): bearingGood = setPan(update["bearing"]) setTilt(update["elevation"]) currentPlane = update + elif message.topic == config_topic: + update_config(update) + logging.info("Config Message: {}".format(update)) else: logging.info("Message: {} Object: {} Flight: {}".format(message.topic, object_topic, flight_topic)) @@ -277,6 +300,7 @@ def main(): global cameraDelay global cameraMoveSpeed global cameraZoom + global cameraBearing global cameraConfig global flight_topic global object_topic @@ -319,6 +343,7 @@ def main(): cameraDelay = args.camera_delay cameraMoveSpeed = args.camera_move_speed cameraZoom = args.camera_zoom + cameraBearing = args.bearing cameraConfig = vapix_config.CameraConfiguration(args.axis_ip, args.axis_username, args.axis_password) threading.Thread(target = moveCamera, daemon = True).start() @@ -333,8 +358,9 @@ def main(): client.connect(args.mqtt_host) #connect to broker client.loop_start() #start the loop - client.subscribe(flight_topic+"/#") - client.subscribe(object_topic+"/#") + client.subscribe(flight_topic) + client.subscribe(object_topic) + client.subscribe(config_topic) client.publish("skyscan/registration", "skyscan-axis-ptz-camera-"+ID+" Registration", 0, False) ############################################# diff --git a/docker-compose.yml b/docker-compose.yml index 24d8b2f..1e2a3cc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,21 +4,23 @@ services: tracker: build: ./tracker - entrypoint: "./flighttracker.py -m mqtt -l ${LAT} -L ${LONG} -a ${ALT} -P skyscan/planes/json -T skyscan/flight/json -M ${MIN_ELEVATION} -c ${CAMERA_LEAD}" - depends_on: - - mqtt - restart: unless-stopped - - adsb-mqtt: - build: ./adsb-mqtt - entrypoint: "./adsb-mqtt.py -H piaware -m mqtt -t skyscan/planes/json" + entrypoint: "./flighttracker.py -m mqtt -H piaware -l ${LAT} -L ${LONG} -a ${ALT} -P skyscan/planes/json -T skyscan/flight/json -M ${MIN_ELEVATION} -c ${CAMERA_LEAD}" volumes: - ./data:/app/data depends_on: - mqtt - - piaware restart: unless-stopped +# adsb-mqtt: +# build: ./adsb-mqtt +# entrypoint: "./adsb-mqtt.py -H piaware -m mqtt -t skyscan/planes/json" +# volumes: +# - ./data:/app/data +# depends_on: +# - mqtt +# - piaware +# restart: unless-stopped + # pan-tilt-pi: # build: ./pan-tilt-pi # entrypoint: "./camera.py -m mqtt -t skyscan/flight/json" @@ -63,6 +65,22 @@ services: - DUMP1090_DEVICE=${RTL_DEV} restart: unless-stopped + tar1090: + image: mikenye/tar1090:latest + tty: true + container_name: tar1090 + restart: always + environment: + - TZ=${TZ} + - BEASTHOST=piaware + - MLATHOST=piaware + - LAT=${LAT} + - LONG=${LONG} + ports: + - 8078:80 + tmpfs: + - /run:exec,size=64M + - /var/log mqtt: build: ./mqtt ports: diff --git a/mqtt-message.md b/mqtt-message.md new file mode 100644 index 0000000..9978659 --- /dev/null +++ b/mqtt-message.md @@ -0,0 +1,10 @@ +Topic: "skyscan/config/json" + +The JSON blob has different config values. There are no required fields. The following Keys are used: +- cameraZoom - int value from 0 - 9999 +- cameraDelay - float value for the number of seconds to wait after sending the camera move API command, before sending the take picture API command. +- cameraMoveSpeed - This is how fast the camea will move. It is an Int number between 0-99 (max) +- cameraLead - This is how far the tracker should move the center point in front of the currently tracked plane. It is a float, and is measured in seconds, example: 0.25 . It is based on the planes current heading and how fast it is going. +- cameraBearing - This is a float to correct the cameras heading to help it better align with True North. It can be from -180 to 180. +- minElevation - The minimum elevation above the horizon which the Tracker will follow an airplane. Int value between 0-90 degrees. +- \ No newline at end of file diff --git a/pan-tilt-pi/camera.py b/pan-tilt-pi/camera.py index 519a57b..e9ac3ef 100755 --- a/pan-tilt-pi/camera.py +++ b/pan-tilt-pi/camera.py @@ -191,7 +191,7 @@ def main(): client.connect(args.mqtt_host) #connect to broker client.loop_start() #start the loop - client.subscribe(args.mqtt_topic+"/#") + client.subscribe(args.mqtt_topic) client.publish("skyscan/registration", "pan-tilt-pi-camera-"+ID+" Registration", 0, False) ############################################# ## Main Loop ## diff --git a/tracker/flighttracker.py b/tracker/flighttracker.py index 493b12b..468e4ee 100755 --- a/tracker/flighttracker.py +++ b/tracker/flighttracker.py @@ -48,10 +48,13 @@ # Clean out observations this often OBSERVATION_CLEAN_INTERVAL = 30 +# Socket read timeout +DUMP1090_SOCKET_TIMEOUT = 60 q=Queue() # Good writeup of how to pass messages from MQTT into classes, here: http://www.steves-internet-guide.com/mqtt-python-callbacks/ args = None camera_latitude = None plant_topic = None # the onMessage function needs to be outside the Class and it needs to get the Plane Topic, so it prob needs to be a global +config_topic = "skyscan/config/json" camera_longitude = None camera_altitude = None camera_lead = None @@ -114,16 +117,49 @@ class Observation(object): def __init__(self, sbs1msg): logging.info("%s appeared" % sbs1msg["icao24"]) + self.__icao24 = sbs1msg["icao24"] + self.__loggedDate = datetime.utcnow() # sbs1msg["loggedDate"] + self.__callsign = sbs1msg["callsign"] + self.__altitude = 0.3048 * float(sbs1msg["altitude"] or 0) # Altitude is in FEET, we convert it to METER. + self.__altitudeTime = datetime.utcnow() + self.__groundSpeed = sbs1msg["groundSpeed"] + self.__track = sbs1msg["track"] + self.__lat = sbs1msg["lat"] + self.__lon = sbs1msg["lon"] + self.__latLonTime = datetime.utcnow() + self.__verticalRate = sbs1msg["verticalRate"] + self.__operator = None + self.__registration = None + self.__type = None + self.__model = None + self.__manufacturer = None + self.__updated = True + plane = planes.loc[planes['icao24'] == self.__icao24.lower()] + + if plane.size == 27: + logging.info("{} {} {} {} {}".format(plane["registration"].values[0],plane["manufacturername"].values[0], plane["model"].values[0], plane["operator"].values[0], plane["owner"].values[0])) + + self.__registration = plane['registration'].values[0] + self.__type = str(plane['manufacturername'].values[0]) + " " + str(plane['model'].values[0]) + self.__manufacturer = plane['manufacturername'].values[0] + self.__model = plane['model'].values[0] + self.__operator = plane['operator'].values[0] + else: + if not self.__planedb_nagged: + self.__planedb_nagged = True + logging.error("icao24 %s not found in the database" % (self.__icao24)) + logging.error(plane) + def update(self, sbs1msg): oldData = dict(self.__dict__) - #self.__loggedDate = datetime.utcnow() + self.__loggedDate = datetime.utcnow() if sbs1msg["icao24"]: self.__icao24 = sbs1msg["icao24"] if sbs1msg["callsign"] and self.__callsign != sbs1msg["callsign"]: self.__callsign = sbs1msg["callsign"].rstrip() if sbs1msg["altitude"]: - self.__altitude = sbs1msg["altitude"] + self.__altitude = 0.3048 * float(sbs1msg["altitude"] or 0) # Altitude is in FEET, we convert it to METER. self.__altitudeTime = datetime.utcnow() if sbs1msg["groundSpeed"]: self.__groundSpeed = sbs1msg["groundSpeed"] @@ -139,25 +175,35 @@ def update(self, sbs1msg): self.__verticalRate = sbs1msg["verticalRate"] if not self.__verticalRate: self.__verticalRate = 0 - if sbs1msg["loggedDate"]: - self.__loggedDate = datetime.strptime(sbs1msg["loggedDate"], '%Y-%m-%d %H:%M:%S.%f') - if sbs1msg["registration"]: - self.__registration = sbs1msg["registration"] - if sbs1msg["manufacturer"]: - self.__manufacturer = sbs1msg["manufacturer"] - if sbs1msg["model"]: - self.__model = sbs1msg["model"] - if sbs1msg["operator"]: - self.__operator = sbs1msg["operator"] - if sbs1msg["type"]: - self.__type = sbs1msg["type"] - - # Calculates the distance from the cameras location to the airplane. The output is in METERS! - distance = utils.coordinate_distance(camera_latitude, camera_longitude, self.__lat, self.__lon) - #Not sure we want to... commented out for now -> Round off to nearest 100 meters - self.__distance = distance = distance #round(distance/100) * 100 - self.__bearing = utils.bearing(camera_latitude, camera_longitude, self.__lat, self.__lon) - self.__elevation = utils.elevation(distance, self.__altitude, camera_altitude) # Distance and Altitude are both in meters + #if sbs1msg["loggedDate"]: + # self.__loggedDate = datetime.strptime(sbs1msg["loggedDate"], '%Y-%m-%d %H:%M:%S.%f') + #if sbs1msg["generatedDate"]: + # self.__generatedDate = sbs1msg["generatedDate"] + #if sbs1msg["loggedDate"]: + # self.__loggedDate = sbs1msg["loggedDate"] + + if self.__planedb_nagged == False and self.__registration == None: + plane = planes.loc[planes['icao24'] == self.__icao24.lower()] + + if plane.size == 27: + logging.info("{} {} {} {} {}".format(plane["registration"].values[0],plane["manufacturername"].values[0], plane["model"].values[0], plane["operator"].values[0], plane["owner"].values[0])) + + self.__registration = plane['registration'].values[0] + self.__type = str(plane['manufacturername'].values[0]) + " " + str(plane['model'].values[0]) + self.__manufacturer = plane['manufacturername'].values[0] + self.__model = plane['model'].values[0] + self.__operator = plane['operator'].values[0] + else: + if not self.__planedb_nagged: + self.__planedb_nagged = True + logging.error("icao24 %s not found in the database" % (self.__icao24)) + if self.__lat and self.__lon and self.__altitude: + # Calculates the distance from the cameras location to the airplane. The output is in METERS! + distance = utils.coordinate_distance(camera_latitude, camera_longitude, self.__lat, self.__lon) + #Not sure we want to... commented out for now -> Round off to nearest 100 meters + self.__distance = distance = distance #round(distance/100) * 100 + self.__bearing = utils.bearing(camera_latitude, camera_longitude, self.__lat, self.__lon) + self.__elevation = utils.elevation(distance, self.__altitude, camera_altitude) # Distance and Altitude are both in meters # Check if observation was updated newData = dict(self.__dict__) @@ -183,7 +229,8 @@ def getElevation(self) -> int: def getLoggedDate(self) -> datetime: return self.__loggedDate - + def getLatLonTime(self) -> datetime: + return self.__latLonTime def getGroundSpeed(self) -> float: return self.__groundSpeed @@ -264,7 +311,16 @@ def dict(self): #d["loggedDate"] = "%s" % (d["_Observation__loggedDate"]) return d +def update_config(config): + global camera_lead + global min_elevation + if "cameraLead" in config: + camera_lead = float(config["cameraLead"]) + logging.info("Setting Camera Lead to: {}".format(camera_lead)) + if "minElevation" in config: + min_elevation = int(config["minElevation"]) + logging.info("Setting Min. Elevation to: {}".format(min_elevation)) def on_message(client, userdata, message): global camera_altitude @@ -282,13 +338,15 @@ def on_message(client, userdata, message): log.critical("onMessage - Value Error: {} ".format(e)) except: log.critical("onMessage - Caught it!") - if message.topic == plane_topic: - q.put(update) #put messages on queue - elif message.topic == "skyscan/egi": + + if message.topic == "skyscan/egi": logging.info(update) camera_longitude = float(update["long"]) camera_latitude = float(update["lat"]) camera_altitude = float(update["alt"]) + elif message.topic == config_topic: + update_config(update) + logging.info("Config Message: {}".format(update)) else: logging.info("Topic not processed: " + message.topic) @@ -303,8 +361,11 @@ class FlightTracker(object): __tracking_distance: int = 999999999 __next_clean: datetime = None __has_nagged: bool = False + __dump1090_host: str = "" + __dump1090_port: int = 0 + __dump1090_sock: socket.socket = None - def __init__(self, mqtt_broker: str, plane_topic: str, flight_topic: str, mqtt_port: int = 1883, ): + def __init__(self, dump1090_host: str, mqtt_broker: str, plane_topic: str, flight_topic: str, dump1090_port: int = 30003, mqtt_port: int = 1883, ): """Initialize the flight tracker Arguments: @@ -319,7 +380,8 @@ def __init__(self, mqtt_broker: str, plane_topic: str, flight_topic: str, mqtt_ dump1090_port {int} -- Override the dump1090 raw port (default: {30003}) mqtt_port {int} -- Override the MQTT default port (default: {1883}) """ - + self.__dump1090_host = dump1090_host + self.__dump1090_port = dump1090_port self.__mqtt_broker = mqtt_broker self.__mqtt_port = mqtt_port self.__sock = None @@ -348,7 +410,7 @@ def __publish_thread(self): if cur is None: continue - (lat, lon) = utils.calc_travel(cur.getLat(), cur.getLon(), cur.getLoggedDate(), cur.getGroundSpeed(), cur.getTrack(), camera_lead) + (lat, lon) = utils.calc_travel(cur.getLat(), cur.getLon(), cur.getLatLonTime(), cur.getGroundSpeed(), cur.getTrack(), camera_lead) distance = utils.coordinate_distance(camera_latitude, camera_longitude, lat, lon) # Round off to nearest 100 meters #distance = round(distance/100) * 100 @@ -373,6 +435,103 @@ def updateTrackingDistance(self): cur = self.__observations[self.__tracking_icao24] self.__tracking_distance = utils.coordinate_distance(camera_latitude, camera_longitude, cur.getLat(), cur.getLon()) + def dump1090Connect(self) -> bool: + """If not connected, connect to the dump1090 host + + Returns: + bool -- True if we are connected + """ + if self.__dump1090_sock == None: + try: + if not self.__has_nagged: + logging.info("Connecting to dump1090") + self.__dump1090_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.__dump1090_sock.connect((self.__dump1090_host, self.__dump1090_port)) + logging.info("ADSB connected") + self.__dump1090_sock.settimeout(DUMP1090_SOCKET_TIMEOUT) + self.__has_nagged = False + return True + except socket.error as e: + if not self.__has_nagged: + logging.critical("Failed to connect to ADSB receiver on %s:%s, retrying : %s" % (self.__dump1090_host, self.__dump1090_port, e)) + self.__has_nagged = True + self.__dump1090_sock = None + time.sleep(5) + return False + else: + return True + + + def dump1090Close(self): + """Close connection to dump1090 host. + """ + try: + self.__dump1090_sock.close() + except socket.error: + pass + self.__dump1090_sock = None + self.__has_nagged = False + logging.critical("Closing dump1090 connection") + + + def dump1090Read(self) -> str: + """Read a line from the dump1090 host. If the host went down, close the socket and return None + + Returns: + str -- An SBS1 message or None if disconnected or timeout + + Yields: + str -- An SBS1 message or None if disconnected or timeout + """ + try: + try: + buffer = self.__dump1090_sock.recv(4096) + except ConnectionResetError: + logging.critical("Connection Reset Error") + self.dump1090Close() + return None + except socket.error: + logging.critical("Socket Error") + self.dump1090Close() + return None + buffer = buffer.decode("utf-8") + buffering = True + if buffer == "": + logging.critical("Buffer Empty") + self.dump1090Close() + return None + while buffering: + if "\n" in buffer: + (line, buffer) = buffer.split("\r\n", 1) + yield line + else: + try: + more = self.__dump1090_sock.recv(4096) + except ConnectionResetError: + logging.critical("Connection Reset Error") + self.dump1090Close() + return None + except socket.error: + logging.critical("Socket Error") + self.dump1090Close() + return None + if not more: + buffering = False + else: + if not isinstance(more, str): + more = more.decode("utf-8") + if more == "": + logging.critical("Receive Empty") + self.dump1090Close() + return None + buffer += more + if buffer: + yield buffer + except socket.timeout: + return None + + + def run(self): """Run the flight tracker. @@ -386,36 +545,42 @@ def run(self): print("connected mqtt") self.__client.loop_start() #start the loop print("start MQTT") - self.__client.subscribe(self.__plane_topic) self.__client.subscribe("skyscan/egi") + self.__client.subscribe(config_topic) self.__client.publish("skyscan/registration", "skyscan-tracker-"+ID+" Registration", 0, False) print("subscribe mqtt") threading.Thread(target = self.__publish_thread, daemon = True).start() while True: - while not q.empty(): - m = q.get() - icao24 = m["icao24"] + if not self.dump1090Connect(): + continue + for data in self.dump1090Read(): + if data is None: + continue self.cleanObservations() - if icao24 not in self.__observations: - self.__observations[icao24] = Observation(m) - self.__observations[icao24].update(m) - if self.__observations[icao24].isPresentable(): - if not self.__tracking_icao24: - if self.__observations[icao24].getElevation() != None and self.__observations[icao24].getElevation() > min_elevation: - self.__tracking_icao24 = icao24 + m = sbs1.parse(data) + if m: + icao24 = m["icao24"] + self.cleanObservations() + if icao24 not in self.__observations: + self.__observations[icao24] = Observation(m) + self.__observations[icao24].update(m) + if self.__observations[icao24].isPresentable(): + if not self.__tracking_icao24: + if self.__observations[icao24].getElevation() != None and self.__observations[icao24].getElevation() > min_elevation: + self.__tracking_icao24 = icao24 + self.updateTrackingDistance() + logging.info("Tracking %s at %d elevation: %d" % (self.__tracking_icao24, self.__tracking_distance, self.__observations[icao24].getElevation())) + elif self.__tracking_icao24 == icao24: self.updateTrackingDistance() - logging.info("Tracking %s at %d elevation: %d" % (self.__tracking_icao24, self.__tracking_distance, self.__observations[icao24].getElevation())) - elif self.__tracking_icao24 == icao24: - self.updateTrackingDistance() - else: - distance = utils.coordinate_distance(camera_latitude, camera_longitude, self.__observations[icao24].getLat(), self.__observations[icao24].getLon()) - if distance < self.__tracking_distance and self.__observations[icao24].getElevation() > min_elevation: - self.__tracking_icao24 = icao24 - self.__tracking_distance = distance - logging.info("Tracking %s at %d elevation: %d" % (self.__tracking_icao24, self.__tracking_distance, self.__observations[icao24].getElevation())) - time.sleep(0.1) + else: + distance = utils.coordinate_distance(camera_latitude, camera_longitude, self.__observations[icao24].getLat(), self.__observations[icao24].getLon()) + if distance < self.__tracking_distance and self.__observations[icao24].getElevation() > min_elevation: + self.__tracking_icao24 = icao24 + self.__tracking_distance = distance + logging.info("Tracking %s at %d elevation: %d" % (self.__tracking_icao24, self.__tracking_distance, self.__observations[icao24].getElevation())) + time.sleep(0.01) def selectNearestObservation(self): """Select nearest presentable aircraft @@ -470,6 +635,7 @@ def main(): global camera_lead global plane_topic global min_elevation + global planes parser = argparse.ArgumentParser(description='A Dump 1090 to MQTT bridge') @@ -483,7 +649,9 @@ def main(): parser.add_argument('-P', '--plane-topic', dest='plane_topic', help="MQTT plane topic", default="skyscan/planes/json") parser.add_argument('-T', '--flight-topic', dest='flight_topic', help="MQTT flight tracking topic", default="skyscan/flight/json") parser.add_argument('-v', '--verbose', action="store_true", help="Verbose output") - + parser.add_argument('-H', '--dump1090-host', help="dump1090 hostname", default='127.0.0.1') + parser.add_argument('--dump1090-port', type=int, help="dump1090 port number (default 30003)", default=30003) + args = parser.parse_args() if not args.lat and not args.lon: @@ -513,9 +681,11 @@ def main(): '%(message)s') logging.info("---[ Starting %s ]---------------------------------------------" % sys.argv[0]) + planes = pd.read_csv("/app/data/aircraftDatabase.csv") #,index_col='icao24') + logging.info("Printing table") + logging.info(planes) - - tracker = FlightTracker( args.mqtt_host, args.plane_topic, args.flight_topic, mqtt_port = args.mqtt_port) + tracker = FlightTracker(args.dump1090_host, args.mqtt_host, args.plane_topic, args.flight_topic,dump1090_port = args.dump1090_port, mqtt_port = args.mqtt_port) tracker.run() # Never returns diff --git a/tracker/sbs1.py b/tracker/sbs1.py index 295a1c4..7a37c4d 100644 --- a/tracker/sbs1.py +++ b/tracker/sbs1.py @@ -9,6 +9,7 @@ from typing import * from datetime import datetime import logging +import re try: import dateutil.parser except ImportError as e: @@ -124,7 +125,8 @@ def __parseInt(array: List, index: int): """Parse int at given index in array Return int value or None if index is out of bounds or type casting failed""" try: - return int(array[index]) + numbers = re.findall('[0-9]+', array[index])[0] + return int(numbers) except ValueError as e: return None except TypeError as e: