def rise_set_yydoy(df_tle: pd.DataFrame, yydoy: str, dir_tle: str, logger: logging.Logger) -> pd.DataFrame: """ rise_set_yydoy calculates the rise/set times for GNSS PRNs """ cFuncName = colored(os.path.basename(__file__), 'yellow') + ' - ' + colored(sys._getframe().f_code.co_name, 'green') # get the datetime that corresponds to yydoy date_yydoy = datetime.strptime(yydoy, '%y%j') logger.info('{func:s}: calculating rise / set times for {date:s} ({yy:s}/{doy:s})'.format(date=colored(date_yydoy.strftime('%d-%m-%Y'), 'green'), yy=yydoy[:2], doy=yydoy[2:], func=cFuncName)) # load a time scale and set RMA as Topo # loader = sf.Loader(dir_tle, expire=True) # loads the needed data files into the tle dir ts = sf.load.timescale() RMA = sf.Topos('50.8438 N', '4.3928 E') logger.info('{func:s}: Earth station RMA = {topo!s}'.format(topo=colored(RMA, 'green'), func=cFuncName)) t0 = ts.utc(int(date_yydoy.strftime('%Y')), int(date_yydoy.strftime('%m')), int(date_yydoy.strftime('%d'))) date_tomorrow = date_yydoy + timedelta(days=1) t1 = ts.utc(int(date_tomorrow.strftime('%Y')), int(date_tomorrow.strftime('%m')), int(date_tomorrow.strftime('%d'))) # go over the PRN / NORADs that have TLE corresponding to the requested date for row, prn in enumerate(df_tle['PRN']): logger.info('{func:s}: for NORAD {norad:s} ({prn:s})'.format(norad=colored(df_tle['NORAD'][row], 'green'), prn=colored(prn, 'green'), func=cFuncName)) # create a EarthSatellites from the TLE lines for this PRN gnss_sv = EarthSatellite(df_tle['TLE1'][row], df_tle['TLE2'][row]) logger.info('{func:s}: created earth satellites {sat!s}'.format(sat=colored(gnss_sv, 'green'), func=cFuncName)) t, events = gnss_sv.find_events(RMA, t0, t1, altitude_degrees=5.0) for ti, event in zip(t, events): name = ('rise above 5d', 'culminate', 'set below 5d')[event] logger.info('{func:s}: {jpl!s} -- {name!s}'.format(jpl=ti.utc_jpl(), name=name, func=cFuncName))
class SatTracker: """Satellite tracker for observer.""" def __init__(self, lat, lon, norad_id=None, horizon=10.0): self.eph = skyfield_load("de421.bsp") self.timescale = skyfield_load.timescale() self.horizon = horizon tle = get_tle(norad_id) self.observer = Topos(latitude_degrees=lat, longitude_degrees=lon) self.satellite = EarthSatellite(tle[1], tle[2], tle[0], self.timescale) def next_passes(self, days=7, visible_only=False): passes = [] now = self.timescale.now() t0, t1 = now, self.timescale.utc(now.utc_datetime() + timedelta(days=days)) # Find satellite events for observer times, events = self.satellite.find_events( self.observer, t0, t1, altitude_degrees=self.horizon) # Each pass is composed by 3 events (rise, culmination, set) # Start arrays on next first pass offset = len(events) % 3 times = times[offset:] events = events[offset:] # Loop for each pass (3 events) for pass_times, pass_events in zip(chunked(times, 3), chunked(events, 3)): full_pass = self.serialize_pass(pass_times, pass_events) full_pass["visible"] = any(event["visible"] for event in full_pass.values()) passes.append(full_pass) # Filter visible ones if visible_only: passes = [p for p in passes if p["visible"]] return passes def serialize_pass(self, pass_times, pass_events): full_pass = {} observer_barycenter = self.eph["earth"] + self.observer for time, event_type in zip(pass_times, pass_events): geometric_sat = (self.satellite - self.observer).at(time) geometric_sun = (self.eph["sun"] - observer_barycenter).at(time) sat_alt, sat_az, sat_d = geometric_sat.altaz() sun_alt, sun_az, sun_d = geometric_sun.altaz() is_sunlit = geometric_sat.is_sunlit(self.eph) event = ('rise', 'culmination', 'set')[event_type] full_pass[event] = { "alt": f"{sat_alt.degrees:.2f}", "az": f"{sat_az.degrees:.2f}", "az_octant": az_to_octant(sat_az.degrees), "utc_datetime": str(time.utc_datetime()), "utc_timestamp": int(time.utc_datetime().timestamp()), "is_sunlit": bool(is_sunlit), "visible": -18 <= int(sun_alt.degrees) <= -6 and bool(is_sunlit) } return full_pass
from skyfield.api import EarthSatellite, Topos, load import math import numpy ts = load.timescale(builtin=True) satname = "USA 224" line1 = "1 37348U 11002A 20053.50800700 .00010600 00000-0 95354-4 0 09" line2 = "2 37348 97.9000 166.7120 0540467 271.5258 235.8003 14.76330431 04" satellite = EarthSatellite(line1, line2, satname, ts) st = ts.utc(2020, 4, 22, 0, 0, 0) et = ts.utc(2020, 4, 24, 0, 0, 0) #visibility intervals target = Topos('35.234722 N', '53.920833 E') t, events = satellite.find_events(target, st, et, altitude_degrees=0.0) print("Target visibility intervals") for ti, event in zip(t, events): name = ('rise', 'culminate', 'set')[event] print(ti.utc_jpl(), name) #downlink intervals gs = Topos('64.977488 N', '147.510697 W') t, events = satellite.find_events(gs, st, et, altitude_degrees=5.0) print("Ground station passes") for ti, event in zip(t, events): name = ('rise', 'culminate', 'set')[event] print(ti.utc_jpl(), name)
def tle_rise_set_times(prn: int, df_tle: pd.DataFrame, marker: sf.Topos, t0: sf.Time, t1: sf.Time, elev_min: int, obs_int: float, logger: logging.Logger) -> Tuple[list, list, list, list]: """ tle_rise_set_info calculates for a PRN based on TLEs the rise and set times and theoreticlal number of observations. """ cFuncName = colored(os.path.basename(__file__), 'yellow') + ' - ' + colored(sys._getframe().f_code.co_name, 'green') # create the to be returned lists dt_tle_rise = [] dt_tle_set = [] dt_tle_cul = [] tle_arc_count = [] # check with the TLEs what the theoretical rise / set times should be try: row = df_tle.index[df_tle['PRN'] == prn].tolist()[0] logger.info('{func:s}: for NORAD {norad:s} ({prn:s})'.format(norad=colored(df_tle['NORAD'][row], 'green'), prn=colored(prn, 'green'), func=cFuncName)) # create a EarthSatellites from the TLE lines for this PRN gnss_sv = EarthSatellite(df_tle['TLE1'][row], df_tle['TLE2'][row]) logger.info('{func:s}: created earth satellite {sat!s}'.format(sat=colored(gnss_sv, 'green'), func=cFuncName)) # find rise:set/cul times t, events = gnss_sv.find_events(marker, t0, t1, altitude_degrees=elev_min) # create a list for setting for each rise-culminate-set sequence the datetime values tle_events = [t0.utc_datetime(), np.NaN, t1.utc_datetime()] event_latest = -1 # event: 0=RISE, 1=Culminate, 2=SET for i, (ti, event) in enumerate(zip(t, events)): tle_events[event] = ti.utc_datetime() event_latest = event if event == 2: # PRN below cutoff dt_tle_rise.append(tle_events[0].time().replace(microsecond=0)) dt_tle_set.append(tle_events[2].time().replace(microsecond=0)) if isinstance(tle_events[1], float): dt_tle_cul.append(np.NaN) else: dt_tle_cul.append(tle_events[1].time().replace(microsecond=0)) tle_events = [t0.utc_datetime(), np.NaN, t1.utc_datetime()] # add the final events detected if event_latest != 2: dt_tle_rise.append(tle_events[0].time().replace(microsecond=0)) dt_tle_set.append(tle_events[2].time().replace(microsecond=0)) if isinstance(tle_events[1], float): dt_tle_cul.append(np.NaN) else: dt_tle_cul.append(tle_events[1].time().replace(microsecond=0)) # check whether a set time is "00:00:00" and change to "23:59:59" midnight = time(hour=0, minute=0, second=0, microsecond=0) for i, tle_set in enumerate(dt_tle_set): if tle_set == midnight: dt_tle_set[i] = time(hour=23, minute=59, second=59, microsecond=0) for tle_rise, tle_set in zip(dt_tle_rise, dt_tle_set): print('type tle_rise {!s}'.format(type(tle_rise))) rise_sec = int(timedelta(hours=tle_rise.hour, minutes=tle_rise.minute, seconds=tle_rise.second).total_seconds()) set_sec = int(timedelta(hours=tle_set.hour, minutes=tle_set.minute, seconds=tle_set.second).total_seconds()) tle_arc_count.append((set_sec - rise_sec) / obs_int) # inform the user logger.info('{func:s}: TLE based times for {prn:s}'.format(prn=colored(prn, 'green'), func=cFuncName)) for i, (stdt, culdt, enddt) in enumerate(zip(dt_tle_rise, dt_tle_cul, dt_tle_set)): if isinstance(culdt, float): str_culdt = 'N/A' else: str_culdt = culdt.strftime('%H:%M:%S') logger.info('{func:s}: arc[{nr:d}]: {stdt:s} -> {culdt:s} -> {enddt:s}'.format(nr=i, stdt=colored(stdt.strftime('%H:%M:%S'), 'yellow'), culdt=colored(str_culdt, 'yellow'), enddt=colored(enddt.strftime('%H:%M:%S'), 'yellow'), func=cFuncName)) except IndexError: logger.info('{func:s}: No NARAD TLE file present for {prn:s}'.format(prn=colored(prn, 'red'), func=cFuncName)) return dt_tle_rise, dt_tle_set, dt_tle_cul, tle_arc_count
from spacetrack import SpaceTrackClient from skyfield.api import EarthSatellite, Topos, load from .stconfig.auth import USR, PASS if __name__ == "__main__": st = SpaceTrackClient(USR, PASS) tlelines = [st.tle_latest(norad_cat_id=45438)[0]["TLE_LINE1"], st.tle_latest(norad_cat_id=45438)[0]["TLE_LINE2"]] bcn = Topos(41.414850, 2.165036) ts = load.timescale() t0 = ts.utc(2020,12,23) t1 = ts.utc(2020,12,30) satellite = EarthSatellite(*tlelines, "STARKLINK-61", ts) t, events = satellite.find_events(bcn, t0, t1, altitude_degrees=30.0) for ti, event in zip(t, events): name = ('aos @ 30°', 'culminate', 'los @ < 30°')[event] if (ti.utc.hour >18 ):#and (event == 0): print(ti.utc_strftime('%Y %b %d %H:%M:%S'), name) difference = satellite - bcn topocentric = difference.at(ti) alt, az, distance = topocentric.altaz() print(" ",alt) print(" ",az) print(" ",int(distance.km), 'km')
def get_all_passes(tle: [str], lat_deg: float, long_deg: float, start_datetime_utc: datetime, end_datetime_utc: datetime, approved_passes: [OrbitalPass] = None, elev_m: float = 0.0, horizon_deg: float = 0.0, min_duration_s: int = 0) -> [OrbitalPass]: """ Get a list of all passes for a satellite and location for a time span. Wrapper for Skyfield TLE ground station pass functions that produces an OrbitalPass object list of possible passes. Parameters ---------- tle : [str] Can be [tle_line1, tle_line2] or [tle_header, tle_line1, tle_line2] lat_deg : float latitude of ground station in degrees long_deg : float longitude of ground station in degrees start_datetime_utc : datetime The start datetime wanted. end_datetime_utc : datetime The end datetime wanted. approved_passes : [OrbitalPass] A list of OrbitalPass objects for existing approved passes. elev_m : float elevation of ground station in meters horizon_deg : float Minimum horizon degrees min_duration_s : int Minimum duration wanted Raises ------ ValueError If the tle list is incorrect. Returns ------- [OrbitalPass] A list of OrbitalPass. """ pass_list = [] load = Loader('/tmp', verbose=False) ts = load.timescale() t0 = ts.utc(start_datetime_utc.replace(tzinfo=timezone.utc)) t1 = ts.utc(end_datetime_utc.replace(tzinfo=timezone.utc)) # make topocentric object loc = Topos(lat_deg, long_deg, elev_m) loc = Topos(latitude_degrees=lat_deg, longitude_degrees=long_deg, elevation_m=elev_m) # make satellite object from TLE if len(tle) == 2: satellite = EarthSatellite(tle[0], tle[1], "", ts) elif len(tle) == 3: satellite = EarthSatellite(tle[1], tle[2], tle[0], ts) else: raise ValueError("Invalid tle string list\n") # find all events t, events = satellite.find_events(loc, t0, t1, horizon_deg) # make a list of datetimes for passes for x in range(0, len(events)-3, 3): aos_utc = t[x].utc_datetime() los_utc = t[x+2].utc_datetime() duration_s = (los_utc - aos_utc).total_seconds() if duration_s > min_duration_s: new_pass = OrbitalPass(gs_latitude_deg=lat_deg, gs_longitude_deg=long_deg, aos_utc=aos_utc.replace(tzinfo=None), los_utc=los_utc.replace(tzinfo=None), gs_elevation_m=elev_m, horizon_deg=horizon_deg) if not pass_overlap(new_pass, approved_passes): pass_list.append(new_pass) # add pass to list return pass_list
class Satellite: def __init__(self, tle_1, tle_2, gs_lat, gs_long, target_lat, target_long, T0, duration): self.current_mode = "data_downlink" self.possible_modes = [ "sun_point", "imaging", "data_downlink", "wheel_desaturate" ] self.telemetry = { "batt": { "percent": 80, "temp": 25 }, "panels": { "illuminated": True }, "comms": { "pwr": False, "temp": 25 }, "obc": { "disk": 0, "temp": 25 }, "adcs": { "mode": None, "temp": 25, "whl_rpm": [0, 0, 0], "mag_pwr": [False, False, False] }, "cam": { "pwr": False, "temp": 25 } } self.collected_data = 0 self.start_time = datetime(T0[0], T0[1], T0[2], tzinfo=timezone.utc) self.end_time = self.start_time + timedelta(hours=duration) self.duration = duration self.ts = load.timescale() self.groundstation = Topos(gs_lat, gs_long) self.target = Topos(target_lat, target_long) self.satellite = EarthSatellite(tle_1, tle_2) self.ephemeris = load('de421.bsp') # Calculate groundstation passes in the time window gs_min_alt_degrees = 5.0 gs_t, gs_events = self.satellite.find_events( self.groundstation, self.ts.utc(self.start_time), self.ts.utc(self.end_time), altitude_degrees=gs_min_alt_degrees) if len(gs_events) == 0: raise (ValueError( "No ground station passes in the given time window.")) gs_sunlit = self._satellite_sunlit(times=gs_t) # print("\nExpected Ground Station Passses\n##############") aos = [] los = [] for ti, event, is_sunlit in zip(gs_t, gs_events, gs_sunlit): name = (f'rise above {gs_min_alt_degrees}°', 'culminate', f'set below {gs_min_alt_degrees}°')[event] # print(ti.utc_datetime().strftime("%Y-%m-%dT%H:%M:%SZ"), # name, "Satellite Illuminated:", is_sunlit) if event == 0: aos.append(ti) elif event == 2: los.append(ti) self.gs_passes = [] for aos_t in aos: for los_t in los: if los_t.utc_datetime() > aos_t.utc_datetime(): self.gs_passes.append([aos_t, los_t]) break # Calculate target passes in the time window target_min_alt_degrees = 5.0 target_t, target_events = self.satellite.find_events( self.target, self.ts.utc(self.start_time), self.ts.utc(self.end_time), altitude_degrees=target_min_alt_degrees) if len(target_events) == 0: raise (ValueError("No target passes in the given time window.")) # print("\nExpected Target Passses\n##############") target_aos = [] target_los = [] for ti, event in zip(target_t, target_events): name = (f'rise above {target_min_alt_degrees}°', 'culminate', f'set below {target_min_alt_degrees}°')[event] # print(ti.utc_datetime().strftime("%Y-%m-%dT%H:%M:%SZ"), # name, "Target Illuminated:", self._location_sunlit(ti, self.target)) if event == 0: target_aos.append(ti) elif event == 2: target_los.append(ti) self.target_passes = [] for aos_t in target_aos: for los_t in target_los: if los_t.utc_datetime() > aos_t.utc_datetime(): self.target_passes.append([aos_t, los_t]) break def execute_mission_plan(self, mission_plan): tlm_plottable = { "batt": { "percent": [], "temp": [] }, "panels": { "illuminated": [] }, "comms": { "pwr": [], "temp": [] }, "obc": { "disk": [], "temp": [] }, "adcs": { "mode": [], "temp": [], "whl_rpm": [], "mag_pwr": [] }, "cam": { "pwr": [], "temp": [] } } print("Executing mission plan.") time.sleep(1) # Construct iterator step_minutes = np.arange(0, self.duration * 60, 1.0) times = [] for idx, step in enumerate(step_minutes): times.append(self.start_time + timedelta(minutes=step)) # Calculate sunlit times sunlit = self._satellite_sunlit(times=self.ts.utc(times)) # Begin simulation for idx, current_time in enumerate(times): print(current_time.strftime("%Y-%m-%dT%H:%M:%SZ")) for entry in mission_plan: if current_time == entry[0]: self._mode_change(new_mode=entry[1], current_time=current_time) self._update_tlm(sunlit=sunlit[idx]) self._check_pass_validity(current_time=current_time) errors = self._check_telemetry() for subsystem in self.telemetry: for item in self.telemetry[subsystem]: tlm_plottable[subsystem][item].append( self.telemetry[subsystem][item]) if errors != []: raise (MissionFailure(errors)) time.sleep(0.005) if self.collected_data >= REQUIRED_DATA: # from matplotlib import pyplot as plt # plt.title('Temperatures') # plt.plot(times, tlm_plottable["batt"]["temp"], label="batt") # plt.plot(times, tlm_plottable["comms"]["temp"], label="comms") # plt.plot(times, tlm_plottable["obc"]["temp"], label="obs") # plt.plot(times, tlm_plottable["adcs"]["temp"], label="adcs") # plt.plot(times, tlm_plottable["cam"]["temp"], label="cam") # plt.title('Wheel Speeds') # plt.plot(times, tlm_plottable["adcs"]["whl_rpm"]) # plt.title('Percentages') # plt.plot(times, tlm_plottable["batt"]["percent"], label="batt %") # plt.plot(times, tlm_plottable["obc"]["disk"], label="obc disk") # plt.legend() # plt.show() return True else: raise ( MissionFailure("Data was not obtained within the time limit.")) def _update_tlm(self, sunlit): # Updates all the tlm based on current mode self.telemetry["panels"]["illuminated"] = sunlit # sun_point if self.current_mode == "sun_point": # batt if sunlit: if self.telemetry["batt"]["percent"] < 100: if (100 - self.telemetry["batt"]["percent"]) < 0.6: self.telemetry["batt"]["percent"] = 100 else: self.telemetry["batt"]["percent"] += 0.6 self._change_all_temps(step=0.1, upper=30) else: self._change_all_temps(step=-0.1) # comms self.telemetry["comms"]["pwr"] = False if self.telemetry["comms"]["temp"] > 25: self.telemetry["comms"]["temp"] += -0.2 # cam self.telemetry["cam"]["pwr"] = False if self.telemetry["cam"]["temp"] > 25: self.telemetry["cam"]["temp"] += -0.2 # adcs self.telemetry["adcs"]["mode"] = "track_sun" self.telemetry["adcs"]["mag_pwr"] = [False, False, False] self._change_wheel_speeds(step=10) self.telemetry["batt"]["percent"] += -.1 # none for obc elif self.current_mode == "imaging": # batt if sunlit: self._change_all_temps(step=0.2) else: self._change_all_temps(step=-0.1) # comms self.telemetry["comms"]["pwr"] = False if self.telemetry["comms"]["temp"] > 25: self.telemetry["comms"]["temp"] += -0.2 # cam self.telemetry["cam"]["pwr"] = True self.telemetry["batt"]["percent"] += -8 self.telemetry["cam"]["temp"] += 5 self.telemetry["batt"]["temp"] += 1 # adcs self.telemetry["adcs"]["mode"] = "target_track" self.telemetry["adcs"]["mag_pwr"] = [False, False, False] self._change_wheel_speeds(step=50) self.telemetry["batt"]["percent"] += -.1 # obc self.telemetry["obc"]["disk"] += 10 elif self.current_mode == "data_downlink": # batt if sunlit: self._change_all_temps(step=0.2) else: self._change_all_temps(step=-0.1) # comms self.telemetry["comms"]["pwr"] = True self.telemetry["batt"]["percent"] += -10 self.telemetry["comms"]["temp"] += 7 self.telemetry["batt"]["temp"] += 1 # cam self.telemetry["cam"]["pwr"] = False if self.telemetry["cam"]["temp"] > 25: self.telemetry["cam"]["temp"] += -0.2 # adcs self.telemetry["adcs"]["mode"] = "target_track" self.telemetry["adcs"]["mag_pwr"] = [False, False, False] self._change_wheel_speeds(step=50) self.telemetry["batt"]["percent"] += -.1 # obc and data collection if self.telemetry["obc"]["disk"] <= 20: self.collected_data += self.telemetry["obc"][ "disk"] / 100 * OBC_DISK_SIZE self.telemetry["obc"]["disk"] = 0 else: self.telemetry["obc"]["disk"] += -20 self.collected_data += 20 / 100 * OBC_DISK_SIZE elif self.current_mode == "wheel_desaturate": # batt if sunlit: self._change_all_temps(step=0.2) else: self._change_all_temps(step=-0.1) # comms self.telemetry["comms"]["pwr"] = False # cam self.telemetry["cam"]["pwr"] = False # adcs self.telemetry["adcs"]["mode"] = "desaturate" self.telemetry["adcs"]["mag_pwr"] = [True, True, True] self._change_wheel_speeds(step=-100) self.telemetry["batt"]["percent"] += -.2 # none for obc print(self.telemetry) print(f"Collected Data: {self.collected_data} bytes") def _mode_change(self, new_mode, current_time): if self.current_mode == new_mode: raise (ValueError(f"Already in '{new_mode}'")) elif new_mode not in self.possible_modes: raise (ValueError(f"Mode must be one of: {self.possible_modes}")) print(f"Changing mode to: {new_mode}") self.current_mode = new_mode def _check_pass_validity(self, current_time): valid_pass = False if self.current_mode == "imaging": for target_pass in self.target_passes: if target_pass[0].utc_datetime( ) < current_time and target_pass[1].utc_datetime( ) > current_time: valid_pass = True if not valid_pass: raise ( MissionFailure("ERROR: Target not in view. Cannot image.")) if not self._location_sunlit(time=self.ts.utc(current_time), location=self.target): raise (MissionFailure( "ERROR: Target location is not sunlit. Cannot image.")) elif self.current_mode == "data_downlink": for gs_pass in self.gs_passes: if gs_pass[0].utc_datetime( ) < current_time and gs_pass[1].utc_datetime() > current_time: valid_pass = True if not valid_pass: raise (MissionFailure( "ERROR: Ground station not in view. Cannot downlink data.") ) def _satellite_sunlit(self, times): # Calculate eclipse times for passes Re = 6378.137 earth = self.ephemeris['earth'] sun = self.ephemeris['sun'] sat = earth + self.satellite sunpos, earthpos, satpos = [ thing.at(times).position.km for thing in (sun, earth, sat) ] sunearth, sunsat = earthpos - sunpos, satpos - sunpos sunearthnorm, sunsatnorm = [ vec / np.sqrt((vec**2).sum(axis=0)) for vec in (sunearth, sunsat) ] angle = np.arccos((sunearthnorm * sunsatnorm).sum(axis=0)) sunearthdistance = np.sqrt((sunearth**2).sum(axis=0)) sunsatdistance = np.sqrt((sunsat**2).sum(axis=0)) limbangle = np.arctan2(Re, sunearthdistance) sunlit = [] for idx, value in enumerate(angle): sunlit.append(((angle[idx] > limbangle[idx]) or (sunsatdistance[idx] < sunearthdistance[idx]))) return sunlit def _location_sunlit(self, time, location): """ Returns a function that tells you if the sun is shining at a given time in a given location. """ func = almanac.sunrise_sunset(self.ephemeris, self.target) return func(time) def _change_all_temps(self, step, upper=None, lower=None): for subsystem in self.telemetry: for field in self.telemetry[subsystem]: if field == "temp": if upper: if self.telemetry[subsystem][field] < upper: self.telemetry[subsystem][field] += step elif lower: if self.telemetry[subsystem][field] > lower: self.telemetry[subsystem][field] += step else: self.telemetry[subsystem][field] += step def _change_wheel_speeds(self, step): x = randint(1, 100) y = randint(1, 100) z = randint(1, 100) total = x + y + z x = x / total * step y = y / total * step z = z / total * step if step < 0: if self.telemetry["adcs"]["whl_rpm"][0] <= abs(x): self.telemetry["adcs"]["whl_rpm"][0] = 0 x = 0 if self.telemetry["adcs"]["whl_rpm"][1] <= abs(y): self.telemetry["adcs"]["whl_rpm"][1] = 0 y = 0 if self.telemetry["adcs"]["whl_rpm"][2] <= abs(z): self.telemetry["adcs"]["whl_rpm"][2] = 0 z = 0 self.telemetry["adcs"]["whl_rpm"] = [ self.telemetry["adcs"]["whl_rpm"][0] + x, self.telemetry["adcs"]["whl_rpm"][1] + y, self.telemetry["adcs"]["whl_rpm"][2] + z ] def _check_telemetry(self): errors = [] for subsystem in self.telemetry: for field in self.telemetry[subsystem]: if field == "temp": if self.telemetry[subsystem][field] < TEMP_LOWER: errors.append( f"ERROR: {subsystem} is no longer operational due to low temp." ) elif self.telemetry[subsystem][field] > TEMP_UPPER: errors.append( f"ERROR: {subsystem} fried due to high temp.") for idx, axis in enumerate(self.telemetry['adcs']['whl_rpm']): if abs(axis) > ADCS_WHEEL_RPM: errors.append( f"ERROR: adcs wheel {idx} has exceeded max wheel speed.") if self.telemetry["obc"]["disk"] > OBC_DISK: errors.append("ERROR: Disk is full. Data partition is corrupted.") if self.telemetry["batt"]["percent"] < BATT_PERCENT: errors.append("ERROR: Battery level critical. Entering Safe Mode.") return errors
def get_next_satellite_pass_for_latlon( latitude: float, longitude: float, requested_date: datetime.datetime, tle_satellite_name: str, elevation: float = 0.0, number_of_results: int = 1, visible_passes_only: bool = False, altitude_degrees: float = 10.0, units: str = "metric", ): """ Determine the next pass of the ISS for a given set of coordinates for a certain date Parameters ========== latitude : 'float' Latitude value longitude : 'float' Longitude value requested_date: class 'datetime' Start-datestamp for the given calculation tle_satellite_name: 'str' Name of the satellite whose pass we want to calculate (see http://www.celestrak.com/NORAD/elements/amateur.txt) elevation : 'float' Elevation in meters above sea levels Default is 0 (sea level) number_of_results: int default: 1, supports up to 5 max results visible_passes_only: bool If True, then show only visible passes to the user altitude_degrees: float default: 10.0 degrees units: str units of measure, either metric or imperial Returns ======= success: bool False in case an error has occurred """ assert 1 <= number_of_results <= 5 assert units in ["metric", "imperial"] satellite_response_data = {} rise_time = ( rise_azimuth ) = maximum_time = maximum_altitude = set_time = set_azimuth = None # Try to get the satellite information from the dictionary # Return error settings if not found success, tle_satellite, tle_data_line1, tle_data_line2 = get_tle_data( satellite_id=tle_satellite_name) if success: ts = api.load.timescale() eph = api.load("de421.bsp") satellite = EarthSatellite(tle_data_line1, tle_data_line2, tle_satellite, ts) pos = api.Topos( latitude_degrees=latitude, longitude_degrees=longitude, elevation_m=elevation, ) today = requested_date tomorrow = requested_date + datetime.timedelta(days=10) # # t = ts.utc( # year=today.year, # month=today.month, # day=today.day, # hour=today.hour, # minute=today.minute, # second=today.second, # ) # days = t - satellite.epoch # logger.info(msg="{:.3f} days away from epoch".format(days)) t0 = ts.utc(today.year, today.month, today.day, today.hour, today.minute, today.second) t1 = ts.utc( tomorrow.year, tomorrow.month, tomorrow.day, tomorrow.hour, tomorrow.minute, tomorrow.second, ) t, events = satellite.find_events(pos, t0, t1, altitude_degrees=altitude_degrees) events_dictionary = {} found_rise = False for ti, event in zip(t, events): # name = ("rise above 10°", "culminate", "set below 10°")[event] # print(ti.utc_strftime("%Y %b %d %H:%M:%S"), name) # create a datetime object out of the skyfield date/time-stamp # we don't really need the microsecond information but keeping this data # should make our dictionary key unique :-) timestamp = datetime.datetime( year=ti.utc.year, month=ti.utc.month, day=ti.utc.day, hour=ti.utc.hour, minute=ti.utc.minute, second=floor(ti.utc.second), microsecond=floor(1000000 * (ti.utc.second - floor(ti.utc.second))), ) is_sunlit = satellite.at(ti).is_sunlit(eph) difference = satellite - pos topocentric = difference.at(ti) alt, az, distance = topocentric.altaz() above_horizon = True if alt.degrees > 0 else False # (re)calculate km distance in miles if the user has requested imperial units _div = 1.0 if units == "imperial": _div = 1.609 # change km to miles # 'event' values: '0' = rise above, '1' = culminate, '2' = set below # at the point in time for which the user has requested the data # there might happen a flyby (meaning that we receive a '1'/'2' # even as first event). We are going to skip those until we receive # the first '0' event if event == 0 or found_rise: events_dictionary[timestamp] = { "event": event, "above_horizon": above_horizon, "altitude": ceil(altitude_degrees), "azimuth": ceil(az.degrees), "distance": floor(distance.km / _div), # Change km to miles if necessary "is_sunlit": is_sunlit, } found_rise = True # We now have a dictionary that is a) in the correct order and b) starts with a '0' event # Try to process the data and build the dictionary that will contain # the blended data is_visible = False rise_date = culmination_date = set_date = datetime.datetime.min alt = az = dst = 0.0 count = 0 for event_datetime in events_dictionary: event_item = events_dictionary[event_datetime] event = event_item["event"] above_horizon = event_item["above_horizon"] altitude = event_item["altitude"] azimuth = event_item["azimuth"] distance = event_item["distance"] is_sunlit = event_item["is_sunlit"] if event == 0: # rise rise_date = event_datetime if is_sunlit: is_visible = True elif event == 1: # culmination culmination_date = event_datetime alt = altitude az = azimuth dst = distance if is_sunlit: is_visible = True elif event == 2: # set set_date = event_datetime if is_sunlit: is_visible = True # we should now have all of the required data for creating # a full entry. Now check if we need to add it if is_visible or not visible_passes_only: satellite_response_data[rise_date] = { "culmination_date": culmination_date, "set_date": set_date, "altitude": alt, "azimuth": az, "distance": dst, "is_visible": is_visible, } # Increase entry counter and end for loop # if we have enough results count = count + 1 if count >= number_of_results: break # Otherwise, we are going to reset our work variables for # the next loop that we are going to enter is_visible = False rise_date = culmination_date = set_date = datetime.datetime.min alt = az = dst = 0.0 return success, satellite_response_data
geocentric = satellite.at(t) print(geocentric.position.km) # 星下点 subpoint = geocentric.subpoint() print('Latitude:', subpoint.latitude) print('Longitude:', subpoint.longitude) print('Elevation (m):', int(subpoint.elevation.m)) # 卫星的高度超过地平线以上指定的度数 # 纬度:北纬为正数,南纬为负数。北纬(N)南纬(S) # 经度:东经为正数,西经为负数。东经(E)西经(W) bluffton = Topos('34.23053 N', '108.93425 E') t1 = ts.utc(2014, 1, 23) t2 = ts.utc(2014, 1, 24) t, events = satellite.find_events(bluffton, t1, t2, altitude_degrees=30.0) for ti, event in zip(t, events): name = ('rise above 30°', 'culminate', 'set below 30°')[event] print(ti.utc_strftime('%Y %b %d %H:%M:%S'), name) # 卫星相对于观察者位置 t = ts.utc(2014, 1, 21, 22, 23, 4) difference = satellite - bluffton topocentric = difference.at(t) alt, az, distance = topocentric.altaz() if alt.degrees > 0: print('The ISS is above the horizon') print(alt) print(az) print(int(distance.km), 'km')
def calculatePasses(params): try: ephem = load(params[13]) sun = ephem['sun'] earth = ephem['earth'] CityElevation = int(params[15]) Location = Topos(params[12].split(',')[0], params[12].split(',')[1], elevation_m=CityElevation) tle0 = params[0] tle1 = params[1] tle2 = params[2] NORADID = params[3] tleEpoch = params[4] City = params[5] YearFrom = params[6] MonthFrom = params[7] DayFrom = params[8] YearTo = params[9] MonthTo = params[10] DayTo = params[11] stdmag = params[14] ts = load.timescale() E_TLEEEPOCH = tleEpoch E_SATID = NORADID E_CITY = City E_UTCCALCDATE = ts.now() E_TLEEEPOCH = tleEpoch satellite = EarthSatellite(tle1, tle2, tle0, ts) difference = satellite - Location EarthLoc = (earth + Location) SunEarth = (sun - earth) obj = [] t0 = ts.utc(YearFrom, MonthFrom, DayFrom) t1 = ts.utc(YearTo, MonthTo, DayTo) lastevent = -1 times, events = satellite.find_events(Location, t0, t1, altitude_degrees=0) if len(events) == 0: return obj if events[len(events) - 1] != 2: t1 = ts.utc(YearTo, MonthTo, DayTo, 0, 40, 0) times, events = satellite.find_events(Location, t0, t1, altitude_degrees=0) for ti, event in zip(times, events): if lastevent == -1 and event > 0: continue else: lastevent = event if event == 0: E_UTC0R = None E_UTC0S = None E_UTC15R = None E_UTC15S = None E_UTC30R = None E_UTC30S = None E_UTCMAX = None E_MAXELEV = None E_SUNELEVAT0R = None E_SUNELEVAT0S = None E_SUNELEVAT15R = None E_SUNELEVAT15S = None E_SUNELEVAT30R = None E_SUNELEVAT30S = None E_SUNELEVATMAX = None E_ISSUNLIT0R = None E_ISSUNLIT0S = None E_ISSUNLIT15R = None E_ISSUNLIT15S = None E_ISSUNLIT30R = None E_ISSUNLIT30S = None E_ISSUNLITMAX = None E_UTCSHADOW = None E_ELEVATSHADOW = None E_MAG0R = None E_MAG0S = None E_MAG15R = None E_MAG15S = None E_MAG30R = None E_MAG30S = None E_MAGMAX = None E_MAGPRESHADOW = None E_AZ0R = None E_AZ15R = None E_AZ30R = None E_AZMAX = None E_AZ30S = None E_AZ15S = None E_AZ0S = None E_UTC0R = ti topocentric = difference.at(E_UTC0R) alt, az, distance = topocentric.altaz() E_AZ0R = az.degrees E_ISSUNLIT0R = satellite.at(E_UTC0R).is_sunlit(ephem) EarthLocSun = EarthLoc.at(E_UTC0R).observe(sun) altSun, azSun, distanceSun = EarthLocSun.apparent().altaz() E_SUNELEVAT0R = altSun.degrees if E_ISSUNLIT0R: E_MAG0R = calculateMag(E_UTC0R, difference, SunEarth, EarthLocSun, stdmag) if event == 1: E_UTCMAX = ti topocentric = difference.at(E_UTCMAX) alt, az, distance = topocentric.altaz() E_MAXELEV = alt.degrees E_AZMAX = az.degrees E_ISSUNLITMAX = satellite.at(E_UTCMAX).is_sunlit(ephem) EarthLocSun = EarthLoc.at(E_UTCMAX).observe(sun) altSun, azSun, distanceSun = EarthLocSun.apparent().altaz() E_SUNELEVATMAX = altSun.degrees if E_ISSUNLITMAX: E_MAGMAX = calculateMag(E_UTCMAX, difference, SunEarth, EarthLocSun, stdmag) if event == 2: E_UTC0S = ti topocentric = difference.at(E_UTC0S) alt, az, distance = topocentric.altaz() E_AZ0S = az.degrees E_ISSUNLIT0S = satellite.at(E_UTC0S).is_sunlit(ephem) EarthLocSun = EarthLoc.at(E_UTC0S).observe(sun) altSun, azSun, distanceSun = EarthLocSun.apparent().altaz() E_SUNELEVAT0S = altSun.degrees if E_ISSUNLIT0S: E_MAG0S = calculateMag(E_UTC0S, difference, SunEarth, EarthLocSun, stdmag) if E_MAXELEV > 15: times15, events15 = satellite.find_events( Location, E_UTC0R, E_UTC0S, altitude_degrees=15) for ti15, event15 in zip(times15, events15): if event15 == 0: E_UTC15R = ti15 topocentric = difference.at(E_UTC15R) alt, az, distance = topocentric.altaz() E_AZ15R = az.degrees E_ISSUNLIT15R = satellite.at(E_UTC15R).is_sunlit( ephem) EarthLocSun = EarthLoc.at(E_UTC15R).observe(sun) altSun, azSun, distanceSun = EarthLocSun.apparent( ).altaz() E_SUNELEVAT15R = altSun.degrees if E_ISSUNLIT15R: E_MAG15R = calculateMag( E_UTC15R, difference, SunEarth, EarthLocSun, stdmag) if event15 == 2: E_UTC15S = ti15 topocentric = difference.at(E_UTC15S) alt, az, distance = topocentric.altaz() E_AZ15S = az.degrees E_ISSUNLIT15S = satellite.at(E_UTC15S).is_sunlit( ephem) EarthLocSun = EarthLoc.at(E_UTC15S).observe(sun) altSun, azSun, distanceSun = EarthLocSun.apparent( ).altaz() E_SUNELEVAT15S = altSun.degrees if E_ISSUNLIT15S: E_MAG15S = calculateMag( E_UTC15S, difference, SunEarth, EarthLocSun, stdmag) if E_MAXELEV > 30: times30, events30 = satellite.find_events( Location, E_UTC15R, E_UTC15S, altitude_degrees=30) for ti30, event30 in zip(times30, events30): if event30 == 0: E_UTC30R = ti30 topocentric = difference.at(E_UTC30R) alt, az, distance = topocentric.altaz() E_AZ30R = az.degrees E_ISSUNLIT30R = satellite.at(E_UTC30R).is_sunlit( ephem) EarthLocSun = EarthLoc.at(E_UTC30R).observe(sun) altSun, azSun, distanceSun = EarthLocSun.apparent( ).altaz() E_SUNELEVAT30R = altSun.degrees if E_ISSUNLIT30R: E_MAG30R = calculateMag( E_UTC30R, difference, SunEarth, EarthLocSun, stdmag) if event30 == 2: E_UTC30S = ti30 topocentric = difference.at(E_UTC30S) alt, az, distance = topocentric.altaz() E_AZ30S = az.degrees E_ISSUNLIT30S = satellite.at(E_UTC30S).is_sunlit( ephem) EarthLocSun = EarthLoc.at(E_UTC30S).observe(sun) altSun, azSun, distanceSun = EarthLocSun.apparent( ).altaz() E_SUNELEVAT30S = altSun.degrees if E_ISSUNLIT30S: E_MAG30S = calculateMag( E_UTC30S, difference, SunEarth, EarthLocSun, stdmag) SHADOWDIRECTION = 0 if (E_ISSUNLIT0R and (not E_ISSUNLITMAX or not E_ISSUNLIT0S)): SHADOWDIRECTION = 1 if (not E_ISSUNLIT0R and (E_ISSUNLITMAX or E_ISSUNLIT0S)): SHADOWDIRECTION = -1 if SHADOWDIRECTION != 0: SatRiseTime = datetime.datetime.strptime( E_UTC0R.utc_iso(' '), '%Y-%m-%d %H:%M:%SZ') SatSetTime = datetime.datetime.strptime( E_UTC0S.utc_iso(' '), '%Y-%m-%d %H:%M:%SZ') SatTimeDiff = (SatSetTime - SatRiseTime).total_seconds() TimeIncrease = 1 if SHADOWDIRECTION > 0: MinLitTime = SatRiseTime MaxLitTime = SatSetTime TTC1 = SatRiseTime if SHADOWDIRECTION < 0: MinLitTime = SatRiseTime MaxLitTime = SatSetTime TTC1 = SatSetTime while TimeIncrease > 0: TimeIncrease = math.ceil(SatTimeDiff / 2) TTC1 = TTC1 + datetime.timedelta( seconds=SHADOWDIRECTION * TimeIncrease) TTC2 = TTC1 + datetime.timedelta( seconds=SHADOWDIRECTION) tsunlit1 = ts.utc(TTC1.year, TTC1.month, TTC1.day, TTC1.hour, TTC1.minute, TTC1.second) tsunlit2 = ts.utc(TTC2.year, TTC2.month, TTC2.day, TTC2.hour, TTC2.minute, TTC2.second) ShadowSunLit1 = satellite.at(tsunlit1).is_sunlit(ephem) ShadowSunLit2 = satellite.at(tsunlit2).is_sunlit(ephem) if ShadowSunLit1 != ShadowSunLit2: TimeIncrease = -1 else: if ShadowSunLit1 and SHADOWDIRECTION > 0: MinLitTime = TTC1 elif not ShadowSunLit1 and SHADOWDIRECTION > 0: MaxLitTime = TTC1 SHADOWDIRECTION = -1 elif ShadowSunLit1 and SHADOWDIRECTION < 0: MinLitTime = TTC1 SHADOWDIRECTION = 1 elif not ShadowSunLit1 and SHADOWDIRECTION < 0: MinLitTime = TTC1 SatTimeDiff = (MaxLitTime - MinLitTime).total_seconds() if SatTimeDiff <= 1: TimeIncrease = -1 topocentric = difference.at(tsunlit1) alt, az, distance = topocentric.altaz() E_ELEVATSHADOW = alt.degrees E_UTCSHADOW = tsunlit1 E_MAGPRESHADOW = calculateMag(E_UTCSHADOW, difference, SunEarth, EarthLocSun, stdmag) obj.append( fillValues(P_SATID=E_SATID, P_CITY=E_CITY, P_UTCCALCDATE=E_UTCCALCDATE, P_TLEEEPOCH=E_TLEEEPOCH, P_UTC0R=E_UTC0R, P_UTC0S=E_UTC0S, P_UTC15R=E_UTC15R, P_UTC15S=E_UTC15S, P_UTC30R=E_UTC30R, P_UTC30S=E_UTC30S, P_UTCMAX=E_UTCMAX, P_MAXELEV=E_MAXELEV, P_SUNELEVAT0R=E_SUNELEVAT0R, P_SUNELEVAT0S=E_SUNELEVAT0S, P_SUNELEVAT15R=E_SUNELEVAT15R, P_SUNELEVAT15S=E_SUNELEVAT15S, P_SUNELEVAT30R=E_SUNELEVAT30R, P_SUNELEVAT30S=E_SUNELEVAT30S, P_SUNELEVATMAX=E_SUNELEVATMAX, P_ISSUNLIT0R=E_ISSUNLIT0R, P_ISSUNLIT0S=E_ISSUNLIT0S, P_ISSUNLIT15R=E_ISSUNLIT15R, P_ISSUNLIT15S=E_ISSUNLIT15S, P_ISSUNLIT30R=E_ISSUNLIT30R, P_ISSUNLIT30S=E_ISSUNLIT30S, P_ISSUNLITMAX=E_ISSUNLITMAX, P_UTCSHADOW=E_UTCSHADOW, P_ELEVATSHADOW=E_ELEVATSHADOW, P_MAG0R=E_MAG0R, P_MAG0S=E_MAG0S, P_MAG15R=E_MAG15R, P_MAG15S=E_MAG15S, P_MAG30R=E_MAG30R, P_MAG30S=E_MAG30S, P_MAGMAX=E_MAGMAX, P_MAGPRESHADOW=E_MAGPRESHADOW, P_AZ0R=E_AZ0R, P_AZ15R=E_AZ15R, P_AZ30R=E_AZ30R, P_AZMAX=E_AZMAX, P_AZ30S=E_AZ30S, P_AZ15S=E_AZ15S, P_AZ0S=E_AZ0S)) return obj except Exception as e: if hasattr(e, 'message'): return "Error {0} processing values: {1}".format( e.message, ",".join(str(x) for x in params)) else: return "Error {0} processing values: {1}".format( str(e), ",".join(str(x) for x in params))
class Orbit: def __init__(self): self.tle = None self.id = None self.line1 = None self.line2 = None self.url = None self.satellites = None self.observer = None self.ts = load.timescale() self.alt = None self.az = None self.distance = None def __str__(self): temp = "TLE\n" temp += 69 * "-" temp += self.id temp += self.line1 temp += self.line2 return temp def loadTLE(self, tle): ''' :param tle: :return: ''' self.tle = tle self.id = self.tle[0] self.line1 = self.tle[1] self.line2 = self.tle[2] return self.id, self.line1, self.line2 def loadSatellites(self): ''' :return: ''' self.satellites = load.tle_file(self.url) return def loadSatfromTLE(self, tle): ''' :param tle: TLE EXAMPLE tle = """ GOCE 1 34602U 09013A 13314.96046236 .14220718 20669-5 50412-4 0 930 2 34602 096.5717 344.5256 0009826 296.2811 064.0942 16.58673376272979 """ :return: ''' lines = tle.strip().splitlines() self.satellites = EarthSatellite(lines[1], lines[2], lines[0]) return def loadObserver(self, lat, long): ''' :param lat: :param long: :return: ''' self.observer = wgs84.latlon(lat, long) return def setURL(self, url): ''' :param url: :return: ''' self.url = url return def currentTime(self): ''' :return: ''' return self.ts.now() def showSatellite(self): ''' :return: ''' print(self.satellite) return def setTime(self, year, month, day): ''' :param year: :param month: :param day: :return: ''' t = self.ts.utc(year, month, day) return t def findRiseSets(self, timedate_init, timedate_final, altitude_degrees): ''' :param timedate_init: :param timedate_final: :param altitude_degrees: :return: ''' t0 = self.setTime(timedate_init) t1 = self.setTime(timedate_final) t, events = self.satellites.find_events(self.observer, t0, t1, altitude_degrees) for ti, event in zip(t, events): name = ('rise above' + str(altitude_degrees), 'culminate', 'set below' + str(altitude_degrees))[event] print(ti.utc_strftime('%Y %b %d %H:%M:%S'), name) return def checkSatEpoch(self): ''' :return: ''' self.epoch = self.satellites.epoch.utc_jpl() print(self.epoch) return def checkSatellitePosition(self): ''' :return: ''' return self.satellites.at(self.currentTime()) def getSatfromYours(self): ''' :return: ''' return print(self.satellites - self.observer) def getObservation(self): ''' :return: ''' self.alt, self.az, self.distance = self.getSatfromYours() if self.alt.degrees > 0: print(str(self.satellites.name) + " is above the horizon") print(self.alt) print(self.az) print(self.int(self.distance.km), 'km') return