diff --git a/Pipfile b/Pipfile index fa71ffa..032f728 100644 --- a/Pipfile +++ b/Pipfile @@ -17,6 +17,7 @@ discord-webhook = "*" selenium = "*" opensky-api = {editable = true, git = "https://github.com/openskynetwork/opensky-api.git", subdirectory = "python"} webdriver-manager = "*" +shapely = "*" [requires] python_version = "3.9" diff --git a/Pipfile.lock b/Pipfile.lock index ced5459..05d5dac 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "d41ac7cee51c5e9792798cbe020d3a1231ee1e0af3555771bd296c9afbd77d72" + "sha256": "5131229ab384051accd51e665cc63da8e2d08a651ad0c9df09041fca9f306977" }, "pipfile-spec": 6, "requires": { @@ -23,13 +23,13 @@ ], "version": "==2021.5.30" }, - "chardet": { + "charset-normalizer": { "hashes": [ - "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa", - "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5" + "sha256:0c8911edd15d19223366a194a513099a302055a962bca2cec0f54b8b63175d8b", + "sha256:f23667ebe1084be45f6ae0538e4a5a865206544097e4e8bbcacf42cd02a348f3" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==4.0.0" + "markers": "python_version >= '3'", + "version": "==2.0.4" }, "colorama": { "hashes": [ @@ -71,19 +71,19 @@ }, "geopy": { "hashes": [ - "sha256:4db8a2b79a2b3358a7d020ea195be639251a831a1b429c0d1b20c9f00c67c788", - "sha256:892b219413e7955587b029949af3a1949c6fbac9d5ad17b79d850718f6a9550f" + "sha256:58b7edf526b8c32e33126570b5f4fcdfaa29d4416506064777ae8d84cd103fdd", + "sha256:8f1f949082b964385de61fcc3a667a6a9a6e242beb1ae8972449f164b2ba0e89" ], "index": "pypi", - "version": "==2.1.0" + "version": "==2.2.0" }, "idna": { "hashes": [ - "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", - "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" + "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a", + "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.10" + "markers": "python_version >= '3'", + "version": "==3.2" }, "oauthlib": { "hashes": [ @@ -114,10 +114,12 @@ "sha256:2ee77c14a0299d0541d26f3d8500bb57e081233e3fa915fa35abd02c51fa7fae", "sha256:37730f6e68bdc6a3f02d2079c34c532330d206429f3cee651aab6b66839a9f0e", "sha256:3f08bd8d785204149b5b33e3b5f0ebbfe2190ea58d1a051c578e29e39bfd2367", + "sha256:479ab11cbd69612acefa8286481f65c5dece2002ffaa4f9db62682379ca3bb77", "sha256:4bc3c7ef940eeb200ca65bd83005eb3aae8083d47e8fcbf5f0943baa50726856", "sha256:660a87085925c61a0dcc80efb967512ac34dbb256ff7dd2b9b4ee8dbdab58cf4", "sha256:67b3666b544b953a2777cb3f5a922e991be73ab32635666ee72e05876b8a92de", "sha256:70af7d222df0ff81a2da601fab42decb009dc721545ed78549cb96e3a1c5f0c8", + "sha256:75e09042a3b39e0ea61ce37e941221313d51a9c26b8e54e12b3ececccb71718a", "sha256:8960a8a9f4598974e4c2aeb1bff9bdd5db03ee65fd1fce8adf3223721aa2a636", "sha256:9364c81b252d8348e9cc0cb63e856b8f7c1b340caba6ee7a7a65c968312f7dab", "sha256:969cc558cca859cadf24f890fc009e1bce7d7d0386ba7c0478641a60199adf79", @@ -126,14 +128,17 @@ "sha256:a2f381932dca2cf775811a008aa3027671ace723b7a38838045b1aee8669fdcf", "sha256:a4eef1ff2d62676deabf076f963eda4da34b51bc0517c70239fafed1d5b51500", "sha256:c088a000dfdd88c184cc7271bfac8c5b82d9efa8637cd2b68183771e3cf56f04", + "sha256:c0e0550a404c69aab1e04ae89cca3e2a042b56ab043f7f729d984bf73ed2a093", "sha256:c11003197f908878164f0e6da15fce22373ac3fc320cda8c9d16e6bba105b844", "sha256:c2a5ff58751670292b406b9f06e07ed1446a4b13ffced6b6cab75b857485cbc8", "sha256:c35d09db702f4185ba22bb33ef1751ad49c266534339a5cebeb5159d364f6f82", "sha256:c379425c2707078dfb6bfad2430728831d399dc95a7deeb92015eb4c92345eaf", "sha256:cc866706d56bd3a7dbf8bac8660c6f6462f2f2b8a49add2ba617bc0c54473d83", "sha256:d0da39795049a9afcaadec532e7b669b5ebbb2a9134576ebcc15dd5bdae33cc0", + "sha256:f156d6ecfc747ee111c167f8faf5f4953761b5e66e91a4e6767e548d0f80129c", "sha256:f4ebde71785f8bceb39dcd1e7f06bcc5d5c3cf48b9f69ab52636309387b097c8", "sha256:fc214a6b75d2e0ea7745488da7da3c381f41790812988c7a92345978414fad37", + "sha256:fd7eef578f5b2200d066db1b50c4aa66410786201669fb76d5238b007918fb24", "sha256:ff04c373477723430dce2e9d024c708a047d44cf17166bf16e604b379bf0ca14" ], "index": "pypi", @@ -176,11 +181,11 @@ "socks" ], "hashes": [ - "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804", - "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e" + "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24", + "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==2.25.1" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", + "version": "==2.26.0" }, "requests-oauthlib": { "hashes": [ @@ -198,6 +203,35 @@ "index": "pypi", "version": "==3.141.0" }, + "shapely": { + "hashes": [ + "sha256:052eb5b9ba756808a7825e8a8020fb146ec489dd5c919e7d139014775411e688", + "sha256:1641724c1055459a7e2b8bbe47ba25bdc89554582e62aec23cb3f3ca25f9b129", + "sha256:17df66e87d0fe0193910aeaa938c99f0b04f67b430edb8adae01e7be557b141b", + "sha256:182716ffb500d114b5d1b75d7fd9d14b7d3414cef3c38c0490534cc9ce20981a", + "sha256:2df5260d0f2983309776cb41bfa85c464ec07018d88c0ecfca23d40bfadae2f1", + "sha256:35be1c5d869966569d3dfd4ec31832d7c780e9df760e1fe52131105685941891", + "sha256:46da0ea527da9cf9503e66c18bab6981c5556859e518fe71578b47126e54ca93", + "sha256:4c10f317e379cc404f8fc510cd9982d5d3e7ba13a9cfd39aa251d894c6366798", + "sha256:4f3c59f6dbf86a9fc293546de492f5e07344e045f9333f3a753f2dda903c45d1", + "sha256:60e5b2282619249dbe8dc5266d781cc7d7fb1b27fa49f8241f2167672ad26719", + "sha256:617bf046a6861d7c6b44d2d9cb9e2311548638e684c2cd071d8945f24a926263", + "sha256:6593026cd3f5daaea12bcc51ae5c979318070fefee210e7990cb8ac2364e79a1", + "sha256:6871acba8fbe744efa4f9f34e726d070bfbf9bffb356a8f6d64557846324232b", + "sha256:791477edb422692e7dc351c5ed6530eb0e949a31b45569946619a0d9cd5f53cb", + "sha256:8e7659dd994792a0aad8fb80439f59055a21163e236faf2f9823beb63a380e19", + "sha256:8f15b6ce67dcc05b61f19c689b60f3fe58550ba994290ff8332f711f5aaa9840", + "sha256:90a3e2ae0d6d7d50ff2370ba168fbd416a53e7d8448410758c5d6a5920646c1d", + "sha256:a3774516c8a83abfd1ddffb8b6ec1b0935d7fe6ea0ff5c31a18bfdae567b4eba", + "sha256:a5c3a50d823c192f32615a2a6920e8c046b09e07a58eba220407335a9cd2e8ea", + "sha256:b40cc7bb089ae4aa9ddba1db900b4cd1bce3925d2a4b5837b639e49de054784f", + "sha256:da38ed3d65b8091447dc3717e5218cc336d20303b77b0634b261bc5c1aa2bae8", + "sha256:de618e67b64a51a0768d26a9963ecd7d338a2cf6e9e7582d2385f88ad005b3d1", + "sha256:e3afccf0437edc108eef1e2bb9cc4c7073e7705924eb4cd0bf7715cd1ef0ce1b" + ], + "index": "pypi", + "version": "==1.7.1" + }, "six": { "hashes": [ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", diff --git a/__main__.py b/__main__.py index c17fe39..7bf188a 100644 --- a/__main__.py +++ b/__main__.py @@ -165,7 +165,7 @@ try: from defOpenSky import pull_opensky planeData, failed = pull_opensky(planes) if failed == False: - if planeData.states != []: + if planeData != None and planeData.states != []: # print(planeData.time) for key, obj in planes.items(): has_data = False diff --git a/calculate_headings.py b/calculate_headings.py new file mode 100644 index 0000000..0ef3205 --- /dev/null +++ b/calculate_headings.py @@ -0,0 +1,34 @@ +def calculate_from_bearing(frm, to): + '''Calculate inital bearing from one coordinate to next (two tuples of coordinates(lat/lng) in degrees in, returns single bearing)''' + #https://gis.stackexchange.com/questions/228656/finding-compass-direction-between-two-distant-gps-points + from math import atan2, cos, radians, sin, degrees + frm = (radians(frm[0]), radians(frm[1])) + to = (radians(to[0]), radians(to[1])) + y = sin(to[1]- frm[1]) * cos(to[0]) + x = cos(frm[0]) * sin(to[0]) - sin(frm[0]) * cos(to[0]) * cos(to[1]-frm[1]) + from_bearing = degrees(atan2(y, x)) + if from_bearing < 0: + from_bearing += 360 + return from_bearing +def calculate_cardinal(d): + '''Finds cardinal direction from bearing degree''' + dirs = ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW'] + ix = int(round(d / (360. / len(dirs)))) + card = dirs[ix % len(dirs)] + print(card) + return card +def calculate_deg_change(new_heading, original_heading): + '''Calculates change between two headings, returns negative degree if change is left, positive if right''' + normal = abs(original_heading-new_heading) + across_inital = 360 - abs(original_heading-new_heading) + if across_inital < normal: + direction = "left" if original_heading < new_heading else "right" + track_change = across_inital + else: + direction = "right" if original_heading < new_heading else "left" + track_change = normal + if direction == "left": + track_change *= -1 + print(f"Track change of {track_change}° which is {direction}") + return track_change + diff --git a/planeClass.py b/planeClass.py index df76dc1..9665c59 100644 --- a/planeClass.py +++ b/planeClass.py @@ -28,9 +28,7 @@ class Plane: self.landing_plausible = False self.nav_modes = None self.last_nav_modes = None - self.recheck_to = None self.speed = None - self.nearest_airport_dict = None self.recent_ra_types = {} self.db_flags = None self.sel_nav_alt = None @@ -38,6 +36,11 @@ class Plane: self.squawk = None self.emergency_already_triggered = None self.last_emergency = None + self.recheck_route_time = None + self.known_to_airport = None + self.track = None + self.last_track = None + self.circle_history = None if self.config.has_option('DATA', 'DATA_LOSS_MINS'): self.data_loss_mins = self.config.getint('DATA', 'DATA_LOSS_MINS') else: @@ -57,7 +60,7 @@ class Plane: self.printheader("head") #print (Fore.YELLOW + "OpenSky Sourced Data: ", ac_dict) try: - self.__dict__.update({'icao' : ac_dict.icao24.upper(), 'callsign' : ac_dict.callsign, 'latitude' : ac_dict.latitude, 'longitude' : ac_dict.longitude, 'on_ground' : bool(ac_dict.on_ground), 'squawk' : ac_dict.squawk}) + self.__dict__.update({'icao' : ac_dict.icao24.upper(), 'callsign' : ac_dict.callsign, 'latitude' : ac_dict.latitude, 'longitude' : ac_dict.longitude, 'on_ground' : bool(ac_dict.on_ground), 'squawk' : ac_dict.squawk, 'track' : float(ac_dict.heading)}) if ac_dict.baro_altitude != None: self.alt_ft = round(float(ac_dict.baro_altitude) * 3.281) elif self.on_ground: @@ -77,7 +80,7 @@ class Plane: #print (Fore.YELLOW +"ADSBX Sourced Data: ", ac_dict, Style.RESET_ALL) try: #postime is divided by 1000 to get seconds from milliseconds, from timestamp expects secs. - self.__dict__.update({'icao' : ac_dict['icao'].upper(), 'callsign' : ac_dict['call'], 'reg' : ac_dict['reg'], 'latitude' : float(ac_dict['lat']), 'longitude' : float(ac_dict['lon']), 'alt_ft' : int(ac_dict['alt']), 'on_ground' : bool(int(ac_dict["gnd"])), 'squawk' : ac_dict['sqk']}) + self.__dict__.update({'icao' : ac_dict['icao'].upper(), 'callsign' : ac_dict['call'], 'reg' : ac_dict['reg'], 'latitude' : float(ac_dict['lat']), 'longitude' : float(ac_dict['lon']), 'alt_ft' : int(ac_dict['alt']), 'on_ground' : bool(int(ac_dict["gnd"])), 'squawk' : ac_dict['sqk'], 'track' : float(ac_dict["trak"])}) if self.on_ground: self.alt_ft = 0 self.last_pos_datetime = datetime.fromtimestamp(int(ac_dict['postime'])/1000) @@ -120,6 +123,8 @@ class Plane: else: self.nav_modes[idx] = self.nav_modes[idx].capitalize() self.squawk = ac_dict.get('squawk') + if "track" in ac_dict: + self.track = ac_dict['track'] if "nav_altitude_fms" in ac_dict: self.sel_nav_alt = ac_dict['nav_altitude_fms'] elif "nav_altitude_mcp" in ac_dict: @@ -181,27 +186,55 @@ class Plane: overlays = "" return overlays def route_info(self): - from lookup_route import lookup_route - extra_route_info = lookup_route(self.reg, (self.latitude, self.longitude), self.type, self.alt_ft) - if extra_route_info is None and not self.recheck_to: - self.recheck_to = True - route_to = None - elif extra_route_info is not None: + from lookup_route import lookup_route, clean_data + def route_format(extra_route_info, type): from defAirport import get_airport_by_icao - to_airport = get_airport_by_icao(extra_route_info['apdstic']) + to_airport = get_airport_by_icao(self.known_to_airport) code = to_airport['iata_code'] if to_airport['iata_code'] != "" else to_airport['icao'] airport_text = f"{code}, {to_airport['name']}" - if 'arrivalRelative' in extra_route_info.keys() and "In" in extra_route_info['arrivalRelative']: - arrival_rel = "in ~" + extra_route_info['arrivalRelative'].strip("In ") + if 'time_to' in extra_route_info.keys() and type != "divert": + arrival_rel = "in ~" + extra_route_info['time_to'] else: arrival_rel = None - if extra_route_info['apdstic'] != self.nearest_airport_dict['icao']: + if self.known_to_airport != self.nearest_from_airport: + if type == "inital": + header = "Going to" + elif type == "change": + header = "Now going to" + elif type == "divert": + header = "Now diverting to" area = f"{to_airport['municipality']}, {to_airport['region']}, {to_airport['iso_country']}" - route_to = f"Going to {area} ({airport_text})" + f" arriving {arrival_rel}" if arrival_rel is not None else "" + route_to = f"{header} {area} ({airport_text})" + (f" arriving {arrival_rel}" if arrival_rel is not None else "") else: - route_to = f"Will be returning to {airport_text}" + f" {arrival_rel}" if arrival_rel is not None else "" - else: - route_to = None + if type == "inital": + header = "Will be returning to" + elif type == "change": + header = "Now returning to" + elif type == "divert": + header = "Now diverting back to" + route_to = f"{header} {airport_text}" + (f" {arrival_rel}" if arrival_rel is not None else "") + return route_to + extra_route_info = clean_data(lookup_route(self.reg, (self.latitude, self.longitude), self.type, self.alt_ft)) + route_to = None + if extra_route_info is None: + pass + elif extra_route_info is not None: + #Diversion + if "divert_icao" in extra_route_info.keys(): + if self.known_to_airport != extra_route_info["divert_icao"]: + self.known_to_airport = extra_route_info['divert_icao'] + route_to = route_format(extra_route_info, "divert") + #Destination + elif "dest_icao" in extra_route_info.keys(): + #Inital Destination Found + if self.known_to_airport is None: + self.known_to_airport = extra_route_info['dest_icao'] + route_to = route_format(extra_route_info, "inital") + #Destination Change + elif self.known_to_airport != extra_route_info["dest_icao"]: + self.known_to_airport = extra_route_info['dest_icao'] + route_to = route_format(extra_route_info, "change") + return route_to def run_empty(self): self.printheader("head") @@ -332,8 +365,12 @@ class Plane: landed_time_msg = None #Proprietary Route Lookup if ENABLE_ROUTE_LOOKUP: - self.nearest_airport_dict = nearest_airport_dict + self.nearest_from_airport = nearest_airport_dict['icao'] route_to = self.route_info() + if route_to is None: + self.recheck_route_time = 1 + else: + self.recheck_route_time = 10 elif self.landed and self.takeoff_time != None: landed_time = datetime.utcnow() - self.takeoff_time if trigger_type == "data loss": @@ -382,11 +419,14 @@ class Plane: self.tweet_api.create_media_metadata(media_id= twitter_media_map_obj.media_id, alt_text= alt_text) self.tweet_api.update_status(status = ((self.twitter_title + " " + message).strip()), media_ids=[twitter_media_map_obj.media_id]) os.remove(self.map_file_name) - #Recheck Proprietary Route Lookup a minute later if infomation was not available on takeoff. - if self.recheck_to and self.takeoff_time is not None and (datetime.utcnow() - self.takeoff_time).total_seconds() > 60: + if self.landed: + self.recheck_route_time = None + self.known_to_airport = None + self.nearest_from_airport = None + #Recheck Proprietary Route Info. + if self.takeoff_time is not None and self.recheck_route_time is not None and (datetime.utcnow() - self.takeoff_time).total_seconds() > 60 * self.recheck_route_time: + self.recheck_route_time += 10 route_to = self.route_info() - self.recheck_to = False - self.nearest_takeoff_airport = None if route_to != None: print(route_to) #Discord @@ -399,6 +439,16 @@ class Plane: tweet = self.tweet_api.user_timeline(count = 1)[0] self.tweet_api.update_status(status = f"{self.twitter_title} {route_to}".strip(), in_reply_to_status_id = tweet.id) + if self.circle_history is not None: + #Expires traces for circles + if self.circle_history["traces"] != []: + for trace in self.circle_history["traces"]: + if (datetime.now() - datetime.fromtimestamp(trace[0])).total_seconds() >= 20*60: + print("Trace Expire, removed") + self.circle_history["traces"].remove(trace) + #Expire touchngo + if "touchngo" in self.circle_history.keys() and (datetime.now() - datetime.fromtimestamp(self.circle_history['touchngo'])).total_seconds() >= 10*60: + self.circle_history.pop("touchngo") if self.feeding: #Squawks emergency_squawks ={"7500" : "Hijacking", "7600" :"Radio Failure", "7700" : "General Emergency"} @@ -453,6 +503,73 @@ class Plane: if self.config.getboolean('DISCORD', 'ENABLE'): dis_message = (self.dis_title + " Sel. alt. " + str("{:,} ft".format(self.sel_nav_alt))) sendDis(dis_message,self.config) + #Circling + if self.last_track is not None: + import time + if self.circle_history is None: + self.circle_history = {"traces" : [], "triggered" : False} + #Add touchngo + if self.on_ground or self.alt_ft <= 400: + self.circle_history["touchngo"] = time.time() + #Add a Trace + if self.on_ground is False: + from calculate_headings import calculate_deg_change + track_change = calculate_deg_change(self.track, self.last_track) + track_change = round(track_change, 3) + self.circle_history["traces"].append((time.time(), self.latitude, self.longitude, track_change)) + + total_change = 0 + coords = [] + for trace in self.circle_history["traces"]: + total_change += float(trace[3]) + coords.append((float(trace[1]), float(trace[2]))) + + print("Total Bearing Change", round(total_change, 3)) + #Check Centroid when Bearing change meets req + if abs(total_change) >= 720 and self.circle_history['triggered'] is False: + print("Circling Bearing Change Met") + from shapely.geometry import MultiPoint + from geopy.distance import geodesic + aircraft_coords = (self.latitude, self.longitude) + points = MultiPoint(coords) + cent = (points.centroid) #True centroid, not necessarily an existing point + #rp = (points.representative_point()) #A represenative point, not centroid, + print(cent) + #print(rp) + distance_to_centroid = geodesic(aircraft_coords, cent.coords).mi + print(f"Distance to centroid of circling coordinates {distance_to_centroid} miles") + if distance_to_centroid <= 15: + print("Within 15 miles of centroid, CIRCLING") + from defAirport import getClosestAirport + nearest_airport_dict = getClosestAirport(self.latitude, self.longitude, ["medium_airport", "large_airport"]) + from calculate_headings import calculate_from_bearing, calculate_cardinal + from_bearing = calculate_from_bearing((float(nearest_airport_dict['latitude_deg']), float(nearest_airport_dict['longitude_deg'])), (self.latitude, self.longitude)) + cardinal = calculate_cardinal(from_bearing) + from defSS import get_adsbx_screenshot + url_params = f"icao={self.icao}&zoom=10&largeMode=2&hideButtons&hideSidebar&mapDim=0&overlays={self.get_adsbx_map_overlays()}" + get_adsbx_screenshot(self.map_file_name, url_params) + if nearest_airport_dict['distance_mi'] < 3: + if "touchngo" in self.circle_history.keys(): + message = f"Doing touch and goes at {nearest_airport_dict['icao']}" + else: + message = f"Circling over {nearest_airport_dict['icao']} at {self.alt_ft}ft" + else: + message = f"Circling {round(nearest_airport_dict['distance_mi'], 2)}mi {cardinal} of {nearest_airport_dict['icao']}, {nearest_airport_dict['name']} at {self.alt_ft}ft" + print(message) + if self.config.getboolean('DISCORD', 'ENABLE'): + role_id = self.config.get('DISCORD', 'ROLE_ID') if self.config.has_option('DISCORD', 'ROLE_ID') else None + sendDis(message, self.config, self.map_file_name, role_id) + if self.config.getboolean('TWITTER', 'ENABLE'): + twitter_media_map_obj = self.tweet_api.media_upload(self.map_file_name) + alt_text = f"Distance to centroid: {distance_to_centroid}, Total change: {total_change}" + self.tweet_api.create_media_metadata(media_id= twitter_media_map_obj.media_id, alt_text= alt_text) + tweet = self.tweet_api.user_timeline(count = 1)[0] + self.tweet_api.update_status(status = f"{self.twitter_title} {message}".strip(), in_reply_to_status_id = tweet.id, media_ids=[twitter_media_map_obj.media_id]) + + self.circle_history['triggered'] = True + elif abs(total_change) <= 360 and self.circle_history["triggered"]: + print("No Longer Circling, trigger cleared") + self.circle_history['triggered'] = False # #Power Up # if self.last_feeding == False and self.speed == 0 and self.on_ground: # if self.config.getboolean('DISCORD', 'ENABLE'): @@ -461,6 +578,7 @@ class Plane: #Set Variables to compare to next check + self.last_track = self.track self.last_feeding = self.feeding self.last_on_ground = self.on_ground self.last_below_desired_ft = self.below_desired_ft