Source code for mds_api.mds_api

""" A python API for interactions between a client and the MDS."""

import json
import math
import re
import time
import numpy as np
from enum import Enum

from mds_api import client
from mds_api.NetworkConfiguration import NetworkConfiguration
from mds_api.mds_utils import *
from mds_api.mds_io import *


ground_asset_types = {}
ground_asset_types['Unknown'] = 0
ground_asset_types['Station'] = 1
ground_asset_types['Ship'] = 2
ground_asset_types['Sumbarine'] = 3
ground_asset_types['GroundVehicle'] = 4
ground_asset_types['Plane'] = 5

pointing_modes = {}
pointing_modes['Idle'] = 0
pointing_modes['Nadir'] = 1
pointing_modes['Sun'] = 2
pointing_modes['Earth'] = 3
pointing_modes['CustomTarget'] = 4
pointing_modes['ClosestStation'] = 5
pointing_modes['CustomQuaternion'] = 6

comms_laws = {}
comms_laws['Nearest'] = 0
comms_laws['AllAvailable'] = 1
comms_laws['Friendly'] = 2

class AccelerationType(Enum):
    PointMassGravity = 0
    SphericalHarmonicGravity = 1
    RadiationPressure = 2
    Aerodynamic = 3
    RelativisticCorrection = 4

class SphericalHarmonicGravityFieldModels(Enum):
    egm96 = 1       # Earth
    ggm02c = 2      # Earth
    ggm02s = 3      # Earth
    goco05c = 4     # Earth
    glgm3150 = 5    # Moon
    lpe200 = 6      # Moon
    gggrx1200 = 7   # Moon
    jgmro120d = 8   # Mars
    jgmess160a = 9  # Mercury
    shgj180u = 10   # Venus

class Integrators(Enum):
    Euler = 0
    RK4 = 1
    RKF12 = 11
    RKF45 = 12
    RKF56 = 13
    RKF78 = 14
    RK87_DormandPrince = 15
    RKF89 = 16
    rungeKuttaVerner89 = 17
    rungeKuttaFeagin108 = 18
    rungeKuttaFeagin1210 = 19
    rungeKuttaFeagin1412 = 20

class IntegratorOrderToUse(Enum):
    lower = 0
    higher = 1

class IAUConventions(Enum):
    iau_2000_a = 0
    iau_2000_b = 1
    iau_2006 = 2

class ReferenceFrames(Enum):
    J2000 = 0
    ECLIPJ2000 = 1

class RadiationSourceModelTypes(Enum):
    HighFidelity = 0
    Constant = 1

class GravityFieldVariationsModelTypes(Enum):
    Empty = -1
    Default = 0

class PropagatorTypes(Enum):
    cowell = 0
    encke = 1
    gauss_keplerian = 2
    gauss_modified_equinoctial = 3
    unified_state_model_quaternions = 4
    unified_state_model_modified_rodrigues_parameters = 5
    unified_state_model_exponential_map = 6

class PredefinedPropagationSettings(Enum):
    VLEO = 0
    LowLEO = 1
    HighLEO = 2
    MEO = 3
    GEO = 4

AvailableSphericalHarmonicsModels = {
    "Earth": {
        "EGM96": SphericalHarmonicGravityFieldModels.egm96,
        "GGM02c": SphericalHarmonicGravityFieldModels.ggm02c,
        "GGM02s": SphericalHarmonicGravityFieldModels.ggm02s,
        "GOCO05c": SphericalHarmonicGravityFieldModels.goco05c
    },
    
    "Moon": {
        "GLGM3150": SphericalHarmonicGravityFieldModels.glgm3150,
        "LPE200": SphericalHarmonicGravityFieldModels.lpe200,
        "GGGRX1200": SphericalHarmonicGravityFieldModels.gggrx1200
    },

    "Mars": {
        "JGMRO120d": SphericalHarmonicGravityFieldModels.jgmro120d
    },

    "Mercury": {
        "JGMESS160a": SphericalHarmonicGravityFieldModels.jgmess160a
    },

    "Venus": {
        "SHGJ180u": SphericalHarmonicGravityFieldModels.shgj180u
    }
}

def enable_reentry_check():
    data = {}
    data['enableReentryCheck'] = True
    json_data = json.dumps(data)
    response = client.ToggleReentryCheck(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, json_data)
    return response

def disable_reentry_check():
    data = {}
    data['enableReentryCheck'] = False
    json_data = json.dumps(data)
    response = client.ToggleReentryCheck(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, json_data)
    return response

def modify_reentry_altitude(satName: str, altitude: float):
    """
    Modify the re-entry altitude for all satellites.
    
    Args:
        satName (str): Name of the satellite.
        altitude (float): Re-entry altitude in km.
    Returns:
        Response from the server
    """
    data = {}
    data['satName'] = satName
    data['reentryAltitude'] = altitude
    json_data = json.dumps(data)
    response = client.ModifyReentryAltitude(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, json_data)
    return response

def setPredefinedPropagationSettings(satName: str, predefinedSettings: PredefinedPropagationSettings):
    data = {}
    data['satName'] = satName
    data['predefinedSettings'] = str(predefinedSettings.value)
    json_data = json.dumps(data)
    response = client.setPredefinedPropagationSettings(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, json_data)
    return response

def modify_integrator_type(satName: str, useVariableStepIntegrator: bool):
    data = {}
    data['satName'] = satName
    data['useVariableStepIntegrator'] = str(useVariableStepIntegrator)
    json_data = json.dumps(data)
    response = client.modifyIntegratorType(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, json_data)
    return response

def modify_integrator_numerical_scheme(satName: str, numericalScheme: Integrators, orderToUse: IntegratorOrderToUse = IntegratorOrderToUse.lower):
    data = {}
    data['satName'] = satName
    data['numericalScheme'] = str(numericalScheme.value)
    data['orderToUse'] = str(orderToUse.value)
    json_data = json.dumps(data)
    response = client.modifyIntegratorNumericalScheme(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, json_data)
    return response

def modify_variable_step_integrator_extra_parameters(satName: str, absoluteTolerance: float, relativeTolerance: float, minTimeStep: float, maxTimeStep: float, usePerElementStepSizeControl : bool = False):
    data = {}
    data['satName'] = satName
    data['absTol'] = str(absoluteTolerance)
    data['relTol'] = str(relativeTolerance)
    data['minTimeStep'] = str(minTimeStep)
    data['maxTimeStep'] = str(maxTimeStep)
    data['usePerElementStepSizeControl'] = str(usePerElementStepSizeControl)
    json_data = json.dumps(data)
    response = client.modifyVariableStepIntegratorExtraParameters(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, json_data)
    return response

def modify_translational_propagator_type(satName: str, propagatorType: PropagatorTypes):
    data = {}
    data['satName'] = satName
    data['propagatorType'] = str(propagatorType.value)
    json_data = json.dumps(data)
    response = client.modifyTranslationalPropagatorType(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, json_data)
    return response

def modify_simulation_reference_frame(satName: str, referenceFrame: ReferenceFrames):
    data = {}
    data['satName'] = satName
    data['referenceFrame'] = str(referenceFrame.value)
    json_data = json.dumps(data)
    response = client.modifySimulationReferenceFrame(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, json_data)
    return response

def modify_gravity_field_variations_model(satName: str, bodyName: str, gravityFieldVariationModelType: GravityFieldVariationsModelTypes):
    data = {}
    data['satName'] = satName
    data['bodyName'] = bodyName
    data['gravityFieldVariationModelType'] = str(gravityFieldVariationModelType.value)
    json_data = json.dumps(data)
    response = client.modifyGravityFieldVariationsModel(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, json_data)
    return response

def modify_central_body_radiation_source_model(satName: str, bodyName: str, radiationSourceModelType: RadiationSourceModelTypes, includeAlbedoRadiation: bool = True, includeThermalRadiation: bool = True):
    data = {}
    data['satName'] = satName
    data['bodyName'] = bodyName
    data['radiationSourceModelType'] = str(radiationSourceModelType.value)
    data['includeAlbedoRadiation'] = str(includeAlbedoRadiation)
    data['includeThermalRadiation'] = str(includeThermalRadiation)
    json_data = json.dumps(data)
    response = client.modifyCentralBodyRadiationSourceModel(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, json_data)
    return response

def toggle_central_body_high_fidelity_atmosphere_model(satName: str, bodyName: str, useNRLMSISE: bool, spaceWeatherFilePath: str = ""):
    data = {}
    data['satName'] = satName
    data['bodyName'] = bodyName
    data['useNRLMSISE'] = useNRLMSISE
    if spaceWeatherFilePath != "":
        data['spaceWeatherFilePath'] = spaceWeatherFilePath
    
    json_data = json.dumps(data)
    response = client.toggleCentralBodyHighFidelityAtmosphereModel(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, json_data)
    return response

def toggle_central_body_high_fidelity_rotation_model(satName: str, bodyName: str, useHighFidelityRotationModel: bool, iauConvention: IAUConventions = IAUConventions.iau_2006):
    data = {}
    data['satName'] = satName
    data['bodyName'] = bodyName
    data['useHighFidelityRotationModel'] = useHighFidelityRotationModel
    data['iauConvention'] = str(iauConvention.value)
    json_data = json.dumps(data)
    response = client.toggleCentralBodyHighFidelityRotationModel(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, json_data)
    return response

def modify_spherical_harmonics_model(satName: str, bodyName: str, sphericalHarmonicsModel: SphericalHarmonicGravityFieldModels):
    data = {}
    data['satName'] = satName
    data['bodyName'] = bodyName
    data['sphericalHarmonicsModel'] = str(sphericalHarmonicsModel.value)
    json_data = json.dumps(data)
    response = client.modifySphericalHarmonicsModel(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, json_data)
    return response

def add_acceleration(satName:str, bodyName:str, acceleration_type:AccelerationType, spherical_harmonic_degree:int = -1, spherical_harmonic_order:int = -1):
    data = {}
    data['satName'] = satName
    data['bodyName'] = bodyName
    data['accelerationType'] = acceleration_type.value
    data['sphericalHarmonicDegree'] = spherical_harmonic_degree
    data['sphericalHarmonicOrder'] = spherical_harmonic_order
    json_data = json.dumps(data)
    response = client.addAcceleration(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, json_data)
    return response

def load_custom_model_for_satellite(sat_name: str, custom_model_path: str):
    data = {
        "satelliteName": sat_name,
        "customModelPath": custom_model_path
    }
    json_data = json.dumps(data)
    response = client.load_custom_model_for_satellite(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, json_data)
    return response

def attach_torch(sat_name: str, range: float, spot_angle: float, intensity: float, color: list[float], position: list[float], rotation: list[float]):
    data = {}
    data['name'] = sat_name
    data['range'] = range
    data['spotAngle'] = spot_angle
    data['intensity'] = intensity
    data['color'] = vector_to_string(color)
    data['position'] = vector_to_string(position)
    data['rotation'] = '(' + str(rotation[0]) + ', ' + str(rotation[1]) + ', ' + str(rotation[2]) + ', ' + str(rotation[3]) + ')'
    json_data = json.dumps(data)
    response = client.attach_torch(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, json_data)
    return response
def calculate_intersat_metrics():
    """call to take fleet-data and compute relative pos/vel/etc in LVLH

    Returns:
    """    
    data = {}
    json_data = json.dumps(data)
    response = client.calculate_intersat_metrics(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, json_data)
    return response
[docs] def calculate_coverage(resolution = 50000, data_index = 0): """call to take fleet-data, create an Earth-grid and perform a coverage calculation. Args: resolution (int, optional): total propagation time. Defaults to 50000. data_index (int, optional): the propagated data's time index to be used for the instantaneous coverage calculation. Defaults to 0. Returns: """ data = {} data['resolution'] = resolution data['dataIndex'] = data_index # data['name'] = sat_name json_data = json.dumps(data) response = client.calculate_coverage(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, json_data) return response
[docs] def calculate_gs_pass(prop_time = 3600, prop_time_step = 60): """call to do an offline fleet propagation and compute ground station passes between added satellites and available ground assests Args: prop_time (int, optional): total propagation time. Defaults to 3600. prop_time_step (int, optional): propagation timestep. Defaults to 60. Returns: """ data = {} data['timeStep'] = prop_time_step data['propTime'] = prop_time # data['name'] = sat_name json_data = json.dumps(data) response = client.calculate_gs_pass(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, json_data) return response
def deattach_torch(sat_name: str): data = {} data['name'] = sat_name json_data = json.dumps(data) response = client.deattach_torch(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, json_data) return response
[docs] def check_constraint(sat_name: str): """Check constraints on satellite for monitoring Args: sat_name (str): sat name """ data = {} data['name'] = sat_name data['constraint'] = {} data['constraint']['type'] = 1 json_data = json.dumps(data) response = client.check_constraint(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, json_data) return response
[docs] def set_constraint(sat_name: str, constraint_type: int, limits: list[float], target: str = ""): """Check constraints on satellite for monitoring Args: sat_name (str): sat name constraint_type (int ): enum type for constraint 1 - semi_major axis 2 - eccentricity 3 - inclination 4 - ltan limits (list[float]): [min, max] limits for constraint target (str, optional): when applicable - relative target. Defaults to "". Returns: """ data = {} data['name'] = sat_name data['constraint'] = {} data['constraint']['type'] = constraint_type data['constraint']['minValue'] = min(limits) data['constraint']['maxValue'] = max(limits) data['constraint']['target'] = target json_data = json.dumps(data) response = client.set_constraint(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, json_data) return response
def remove_constraint(sat_name: str, constraint_type: int, target: str = ""): """Check constraints on satellite for monitoring Args: sat_name (str): sat name constraint_type (int ): enum type for constraint 1 - semi_major axis 2 - eccentricity 3 - inclination 4 - ltan limits (list[float]): [min, max] limits for constraint target (str, optional): when applicable - relative target. Defaults to "". Returns: """ data = {} data['name'] = sat_name data['constraint'] = {} data['constraint']['type'] = constraint_type data['constraint']['target'] = target json_data = json.dumps(data) response = client.remove_constraint(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, json_data) return response
[docs] def set_sat_propagation_attributes(sat_name: str, mass: float = 1, area: float = 1, c_d: float = 2.2, c_srp = 1.2): """Alter satellite mass, surface area expsoed to drag/srp and drag/srp coefficients Args: sat_name (str): satellite name to select mass (float, optional): selected mass [kg]. Defaults to 1. area (float, optional): selected surface area [m^2]. Defaults to 1. c_d (float, optional): Drag coefficient [-]. Defaults to 2.2. c_srp (float, optional): Solar radiation pressure coefficient [-]. Defaults to 1.2. """ data = {} data['name'] = sat_name data['bodyType'] = 1 data['mass'] = mass data['coefficientDrag'] = c_d data['coefficientSRP'] = c_srp data['surfaceArea'] = area json_data = json.dumps([data]) response = client.update_config(json_data, NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) return response
def set_earth_light_intensity(intensity: float): ''' It's possible to set specificaly earth light intensity :param intensity: float from 0 to n works as color multiplier :return: ''' data = { 'intensity': intensity } json_data = json.dumps(data) response = client.set_earth_light_intensity(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, json_data) return response def set_satellite_light_intensity(name: str, intensity: float): ''' It's possible to set light intensity for specific satellite param: name (str): satellite name :param intensity: float from 0 to n works as color multiplier :return: ''' data = { 'name': name, 'intensity': intensity } json_data = json.dumps(data) response = client.set_satellite_light_intensity(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, json_data) return response def remove_entity(name: str): ''' :param name: removes created entity from the scene :return: ''' data = { 'name': name } json_data = json.dumps(data) response = client.remove_entity(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, json_data) return response def add_update_entity(name: str, position_ECI:list[float], radius: float, r: int, g: int, b: int, central_body: str, update_entity: bool=False): ''' Creates sphere in inertial coordinate system :param name: entity name :param position_ECI: position in intertial coordinate system :param radius: sphere radius :param r: color red :param g: color green :param b: color blue :param central_body: central body "Earth", "Moon", "Sun", etc :param update_entity: True or False. if True new entity will be added, if false existing entity will be updated :return: ''' data = { 'name': name, 'positionECI': vector_to_string(position_ECI), 'radius': radius, 'r': r, 'g': g, 'b': b, 'centralBody': central_body, 'mode': 'create_entity', } if (update_entity): data['mode'] = 'updateEntity' json_data = json.dumps(data) response = client.add_update_entity(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, json_data) return response def always_show_satellite_labels(show: bool, names: list[str]): ''' Turn on of satellite labels for specific satellites :param show: True or False, if true satellite label is always visible :param names: satellite list of names which will be affected :return: ''' data = {} data['show'] = show n = ','.join(names) data['names'] = n json_data = json.dumps(data) response = client.always_show_satellite_labels(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, json_data) return response
[docs] def set_mass(sat_name: str, mass: float): data = {} data['name'] = sat_name data['bodyType'] = 1 data['mass'] = mass json_data = json.dumps([data]) response = client.update_config(json_data, NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) return response
def set_velocity(sat_name: str, local_velocity: list[float]): data = {} data['name'] = sat_name data['bodyType'] = 1 data['localVelocity'] = vector_to_string(local_velocity) json_data = json.dumps([data]) response = client.update_config(json_data, NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) return response
[docs] def set_simulation_timestep(time_step): client.set_simulation_timestep(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, time_step)
[docs] def enable_API_synchronization(): client.enable_API_synchronization(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT)
[docs] def disable_API_synchronization(): client.disable_API_synchronization(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT)
def toggle_Tudat_Propagator( useTudatPropagator : bool ): data = {} data["handleTudatPropagator"] = useTudatPropagator json_data = json.dumps(data) response = client.toggle_Tudat_Propagator(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, json_data) return response
[docs] def clear_scene(): rval = client.clear_scene(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) return rval.decode()
[docs] def get_config() -> str: """ Retrieve the full current simulation config from MDS. """ cfg = client.save_config(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) return cfg.decode()
[docs] def get_config_part(part_name: str) -> tuple[float, str]: """ Retrieve a specified part of the simulation config from MDS. Args: part_name (str): the name of the CelestialBody, PropagatedBody or GroundStation to get the config of. """ jd, cfg_part = client.get_config_part([part_name], NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) return jd, cfg_part
[docs] def load_config(cfg_path: str) -> bytes: """ Order MDS to load a new configuration. Loads configurationn from a specified file and sends to MDS. Args: cfg_path (str): path to config file (on client machine) """ with open(cfg_path, 'r', encoding='utf-8') as f: json_str = f.read() response = client.load_config(json_str, NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) return response
[docs] def get_celestial_body_data(*names: str) -> tuple[float, dict]: """ Get data about specified celestial bodies. Args: names (str): names of the celestial bodies. """ names = list(names) jd, jsonStr = client.get_config_part(names, NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) jsonDict = json.loads(jsonStr) return jd, jsonDict
[docs] def get_ground_asset_data(*names: str) -> tuple[float, dict]: """ Get data about a specified ground station(s). Args: names (str): names of the ground stations. """ names = list(names) jd, jsonStr = client.get_config_part(names, NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) jsonDict = json.loads(jsonStr) return jd, jsonDict
[docs] def get_sat_data(*names: str) -> tuple[float, dict]: """ Get data about a specified satellite/spacecraft. Args: names (str): names of the spacecraft """ names = list(names) jd, jsonStr = client.get_config_part(names, NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) jsonDict = { } if jsonStr != '': jsonDict = json.loads(jsonStr) return jd, jsonDict
[docs] def get_sat_names() -> list[str]: jsonStr = get_config() jsonDict = json.loads(jsonStr) prop_bodies = jsonDict['propagatedBodies'] sat_names = [] for sat in prop_bodies: sat_names.append(sat['name']) return sat_names
[docs] def get_sat_pos_vel(*names: str) -> tuple[float, list[float], list[float]]: """ Get local position and velocity of a specified satellite/spacecraft. Args: names (str): names of the spacecraft """ names = list(names) jd, jsonStr = client.get_config_part(names, NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) if jsonStr == '': return jd, [], [] jsonDict = json.loads(jsonStr) positions = [] velocities = [] if len(names) > 1: for dict in jsonDict: name, pos, vel = extract_pos_vel(dict) positions.append(pos) velocities.append(vel) else: name, positions, velocities = extract_pos_vel(jsonDict) return jd, positions, velocities
[docs] def get_sat_elements(*names: str) -> tuple[float, list[float]]: """ Get osculating Kepler elements of a specified satellite/spacecraft. Args: names (str): names of the spacecraft """ names = list(names) jd, jsonStr = client.get_config_part(names, NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) jsonDict = json.loads(jsonStr) elements = [] if len(names) > 1: for dict in jsonDict: name, elems = extract_elements(dict) elements.append(elems) else: elements = extract_elements(jsonDict) return jd, elements
[docs] def add_solar_sat(sat_name: str, mass: float, position: list[float], velocity: list[float]) -> bytes: """ Instantiate a new spacecraft with specified mass, global position and velocity. Args: sat_name (str): name of the spnew spacecraft mass (float): mass of the new spacecraft [kg]. position (list[float]): solar barycentric position of a spacecraft [m]. velocity (list[float]): velocity of the spacecraft in ICRF coordinates, [m/s] """ data = {} data['name'] = sat_name data['bodyType'] = 1 data['mass'] = mass # '(-149941742301.569, 1423841988.23641, 651518931.770543)' data['solarBarycentricPosition'] = '(' + str(position[0]) + ', ' + str(position[1]) + ', ' + str(position[2]) + ')' # '(505.265200853449, -26005.9285649072, -4509.1488673049)' data['solarBarycentricVelocity'] = '(' + str(velocity[0]) + ', ' + str(velocity[1]) + ', ' + str(velocity[2]) + ')' json_data = json.dumps([data]) response = client.update_config(json_data, NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) return response
[docs] def add_sat(sat_name: str, mass: float, local_pos: list[float], local_vel: list[float], central_body: str, referenceArea: float=1.0, coefficientDrag: float=2.2, coefficientSRP: float=1.2, attitude: list[float]=[0, 0, 0, 1], angularVelocity: list[float]=[0, 0, 0], max_ang_rate: float=2, max_ang_acc: float=0.1, rigidbody: bool=False, custom_model_path: str=None, model_rotation_offset: list[float]=[0, 0, 0, 1], print_debug: bool=False) -> bytes: """ Instantiate a satellite, orbiting around a specified celestial body. Args: sat_name (str): name of the new spacecraft to be instantiated. mass (float): mass of the spacecraft [kg] local_pos (list[float]): local position of the spacecraft, relative to the central body, in body centered inertial frame (ICRF aligned) [m]. local_vel (list[float]): velocity relative to central body, in body centered inertial frame (ICRF aligned) [m/s]. central_body (str): name of the celestial body, around which the spacecraft will be rotating. attitude (list[float], optional): attitude quaternion of the spacecraft, relative to ICRF. angularVelocity (list[float], optional): angular velocity [rad/s] of the spacecraft, relative to ICRF. max_ang_rate (float, optional): maximum angular rate for the spacecraft pointing maneuvers in degrees per second. defaults to 2 [rad/s]. max_ang_acc (float, optional): maximum angular acceleration for the spacecraft pointing maneuvers in degrees per second per second. defaults to 0.1 [rad/s/s]. rigidbody (bool, optional): flag to enable or disable rigidbody 6DOF simulation for this spacecraft. defaults to False. custom_model_path (str, optional): path to a custom .glb or .gltf 3D model. path can be absolute, or if it is not absolute, it will be interpreted as relative to /MDS_Solaris_Data/StreamingAssets/. model_rotation_offset (list[float], optional): orientation quaternion of the 3D model relative to the spacecraft. print_debug (bool, optional): flag to turn debug message printing on/off """ data = {} data['name'] = sat_name data['bodyType'] = 1 data['mass'] = mass data['localPosition'] = '(' + str(local_pos[0]) + ', ' + str(local_pos[1]) + ', ' + str(local_pos[2]) + ')' data['localVelocity'] = '(' + str(local_vel[0]) + ', ' + str(local_vel[1]) + ', ' + str(local_vel[2]) + ')' data['centralBody'] = central_body data['attitudeState'] = {} data['attitudeState']['attitude'] = '(' + str(attitude[0]) + ', ' + str(attitude[1]) + ', ' + str( attitude[2]) + ', ' + str(attitude[3]) + ')' data['attitudeState']['angularVelocity'] = vector_to_string(angularVelocity) data['attitudeState']['maxAngularSpeed'] = max_ang_rate data['attitudeState']['maxAngularAcceleration'] = max_ang_acc data['rigidBodyAttached'] = rigidbody data['modelRotation'] = '(' + str(model_rotation_offset[0]) + ', ' + str(model_rotation_offset[1]) + ', ' + str( model_rotation_offset[2]) + ', ' + str(model_rotation_offset[3]) + ')' data['referenceArea'] = referenceArea data['coefficientDrag'] = coefficientDrag data['coefficientSRP'] = coefficientSRP if custom_model_path != None: data['customModelPath'] = custom_model_path json_data = json.dumps([data]) if print_debug: print("updating config with:\n") print(json_data) response = client.update_config(json_data, NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) return response
def create_ground_asset_waypoint(name:str, longitudeDeg: float, latitudeDeg: float, altitudeMeters:float, speed:float = -1, ascendAndHold:bool = True) -> dict: """ Create a dict, representing a waypoint for a ground asset. Args: name: name for the waypoint (str). latitudeDeg: latitude, in degrees. Can be given as a float, or a string of format [deg]o[minutes]'[seconds]''[N/S]. For example "55o32'29''N". (float or str) longitudeDeg: longitude, in degrees. Can be given as float or a sting of format [deg]o[minutes]'[seconds]''[N/S]. For example "23o59'17''N". (float or str) altitudeMeters: altitude above sea level, in meters. (float) speed: maximum speed at which approacinh this waypoint is allowed. If set to -1, then the ground asset's own maximum speed is used. (float, default: -1); ascendAndHold: manner, in which the ground asset ascends to the waypoin't altitude. only affects ground assets that can fly. If ascendAndHold is true, then the asset ascends at maximum allowed ascent rate and continues to the waypoint at the waypoint's altitude. If this parameter is false, then the asset ascends/descends gradually to the waypoint. (bool, default=True) """ deg2rad = math.pi / 180 waypoint_dict = { "name": name, 'LonLatAltPosition': '(' + str(get_degrees(longitudeDeg) * deg2rad) + ', ' + \ str(get_degrees(latitudeDeg) * deg2rad) + ', ' + \ str(altitudeMeters) + ')', "speed": speed, "ascentMode": 0 if ascendAndHold else 1 } return waypoint_dict
[docs] def add_ground_asset(asset_name: str, centralBody: str, longitudeDeg: float | str, latitudeDeg: float | str, altitudeMeters: float, canFly: bool = True, maxSpeed:float = 250.0, maxAscentRate: float=12.0, loopWaypoints: bool=True, waypoints = None, currentWaypointIndex: int | None = None, tx_frequency: float=2047 * 1e6, rx_frequency: float=8250 * 1e6, radius: float=10, r: int=255, g: int=255, b: int=255, assetType: int = 1, print_debug:bool = False) -> bytes: """ Add ground asset Args: asset_name: name of your ground asset (string) longitudeDeg: longitude, in dgrees (float) latitudeDeg: latitude, in degrees (float) altitudeMeters: altitude in meters, above sea level (float) canFly: is the asset allowed to lift off above 0m altitude? (bool, default=True) maxSpeed: maximum speed in m/s (float, default=250) maxAscentRate: maximum speed in m/s, at which the asset can ascend/descend (float, default=12) loopWaypoints: Whether to loop waypoints. If True, then upon reachng the last waypoint will redirect to the first one. Otherwise will stop at the last waypoint. (bool, default=True) waypoints: list of waypoints or None. If you provide a list, waypoints will be updated. If you provide None, waypoints are not modified. Create each item in the list using the function create_ground_asset_waypoint. (list or None, default=None) currentWaypointIndex: index of waypoint that the ground asset is currently heading to. Place the index of waypoint, -1 to make the asset stop or None to not modify current value. (int or None, default=None) tx_frequency: Tranzsmission frequency in Hz (default = 2047 MHz) rx_frequency: reception frequency, in Hz (default = 8520 MHz) r,g,b: Color of the spherical marker, as byte from 0 to 255. (default = 255,255,255) assetType: ground asset type. Only affects the icon displayed, does not affect behaviour. Please see mds_api.ground_asset_types for possible values. (default = 1 (Station)) """ deg2rad = math.pi / 180 data = {} data['name'] = asset_name data['mass'] = 10000 # default, unused number data['bodyType'] = 2 data['centralBody'] = centralBody data['canFly'] = canFly data['maxSpeed'] = maxSpeed data['maxAscentRate'] = maxAscentRate data['loopWaypoints'] = loopWaypoints data['txFrequency'] = tx_frequency data['rxFrequency'] = rx_frequency data['radius'] = radius data['r'] = r data['g'] = g data['b'] = b data['assetType'] = assetType if currentWaypointIndex is not None: data['currentWaypointIndex'] = currentWaypointIndex if waypoints is not None: data['waypoints'] = waypoints data['positionLonLatAlt'] = '(' + str(get_degrees(longitudeDeg) * deg2rad) + \ ', ' + str(get_degrees(latitudeDeg) * deg2rad) + \ ', ' + str(altitudeMeters) + ')' json_data = json.dumps([data]) if print_debug: print("updating config with:\n") print(json_data) response = client.update_config(json_data, NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) return response
[docs] def get_camera_frame(sat_name: str, delay : bool = True) -> bytes: """ Get the current frame from a camera attached to satellite. Args: sat_name (str): name of the spacecraft with the camera of interest delay (bool): workaround in MDS1.3.0 to make sure camera image matches camera UI """ response = client.get_camera_frame(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, sat_name) if delay: time.sleep(0.2) response = client.get_camera_frame(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, sat_name) return response
def get_camera_depth_frame(sat_name: str) -> bytes: """ Get the current depth data frame from a camera attached to satellite. Args: sat_name (str): name of the spacecraft with the camera of interest """ response = client.get_camera_depth_frame(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, sat_name) return response
[docs] def add_sat_from_elements(sat_name: str, mass: float, a: float, e: float, i: float, Omega: float, omega: float, nu: float, central_body: str, referenceArea: float=1.0, coefficientDrag: float=2.2, coefficientSRP: float=1.2, attitude: list[float]=[0, 0, 0, 1], angular_velocity: list[float]=[0, 0, 0], max_ang_rate: float=2, max_ang_acc: float=0.1, rigidbody: bool=False, model_rotation_offset: list[float]=[0,0,0,1], custom_model_path: str=None) -> bytes: """ Instantiate a satellite with a custom 3d model, orbiting around a specified celestial body. Args: sat_name (str): name of the new spacecraft to be instantiated mass (float): mass of the spacecraft [kg] a (float): semi-major axis [m] e (float): eccentricity [-] i (float): inclination [deg] Omega (float): right ascension of the ascending node [deg] omega (float): argument of periapsis [deg] nu (float): true anomaly [deg] central_body (str): name of central body attitude (list[float], optional): attitude quaternion of the spacecraft, relative to ICRF. max_ang_rate (float, optional): maximum angular rate for the spacecraft pointing maneuvers in degrees per second. defaults to 2 [rad/s]. max_ang_acc (float, optional): maximum angular acceleration for the spacecraft pointing maneuvers in degrees per second per second. defaults to 0.01 [rad/s/s]. rigidbody (bool, optional): flag to enable or disable rigidbody 6DOF simulation for this spacecraft. defaults to False. custom_model_path (str, optional): path to a custom .glb or .gltf 3D model. path can be absolute, or if it is not absolute, it will be interpreted as relative to /MDS_Solaris_Data/StreamingAssets/. model_rotation_offset (list[float], optional): orientation quaternion of the 3D model relative to the spacecraft. """ data = {} data['name'] = sat_name data['bodyType'] = 1 data['mass'] = mass elements = {} elements['semiMajorAxis'] = a elements['eccentricity'] = e elements['inclination'] = math.radians(i) elements['ascendingNodeLongitude'] = math.radians(Omega) elements['periapsisArgument'] = math.radians(omega) elements['trueAnomaly'] = math.radians(nu) data['orbitalElements'] = elements data['centralBody'] = central_body data['attitudeState'] = {} data['attitudeState']['attitude'] = '(' + str(attitude[0]) + ', ' + str(attitude[1]) + ', ' + str( attitude[2]) + ', ' + str(attitude[3]) + ')' data['attitudeState']['angularVelocity'] = vector_to_string(angular_velocity) data['attitudeState']['maxAngularSpeed'] = max_ang_rate data['attitudeState']['maxAngularAcceleration'] = max_ang_acc data['rigidBodyAttached'] = rigidbody data['modelRotation'] = '(' + str(model_rotation_offset[0]) + ', ' + str(model_rotation_offset[1]) + ', ' + str( model_rotation_offset[2]) + ', ' + str(model_rotation_offset[3]) + ')' data['referenceArea'] = referenceArea data['coefficientDrag'] = coefficientDrag data['coefficientSRP'] = coefficientSRP if custom_model_path != None: data['customModelPath'] = custom_model_path json_data = json.dumps([data]) response = client.update_config(json_data, NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) return response
def add_battery(sat_name: str, charger_efficiency: float, capacity: float, charge_fraction: float) -> bytes: """ Add a battery to a spacecraft. Args: sat_name (str): name of the spacecraft, for which to add the battery. charger_efficiency (float): efficiency of the battery charging process [-]. capacity (float): maximum capacity of the battery [Wh]. charge_fraction (float): current charge level as a fraction of the maximum capacity [-]. """ jd, jsonStr = client.get_config_part([sat_name], NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) jsonDict = json.loads(jsonStr) battery_count = len(jsonDict['spacecraftData']['batteries']) + 1 # print(f"battery_count = {battery_count}") solar_panel_count = len(jsonDict['spacecraftData']['solarPanels']) # print(f"solar_panel_count = {solar_panel_count}") if solar_panel_count < battery_count: print("no solar panel to attach battery to") return 0 else: structure_id = jsonDict['spacecraftData']['solarPanels'][battery_count-1]['structureId'] battery = {} battery['structureId'] = structure_id battery['chargerEfficiency'] = charger_efficiency battery['capacityWattHours'] = capacity battery['chargeFloat'] = charge_fraction jsonDict['spacecraftData']['batteries'].append(battery) json_data = json.dumps([jsonDict]) response = client.update_config(json_data, NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) return response
[docs] def add_solar_panel(sat_name: str, position: list[float], rotation: list[float], area: float, max_power: float, efficiency: float, display_axis: bool=True) -> bytes: """ Add a solar panel to the spacecraft. Args: sat_name (str): name of the spacecraft to which the panel shall be added. position (list): local position vector of the solar panel's center, w.r.t. spacecraft body frame origin [m]. rotation (list): local rotation quaternion of the solar panel w.r.t. spacecraft body z-axis. max_power (float): maximum power that can be generated by this solar panel [W]. area (float): area of the solar panel [m^2]. efficiency (float): efficiency of the solar cells [-]. display_axis (bool, optional): flag to turn the visual aid axis on/off. default True. """ jd, jsonStr = client.get_config_part([sat_name], NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) jsonDict = json.loads(jsonStr) structure_id = jsonDict['spacecraftData']['structure'][-1]['id'] + 1 structure = {} structure['name'] = 'solar panel' structure['id'] = structure_id structure['parentID'] = 0 structure['localPosition'] = vector_to_string(position) structure['localRotation'] = vector_to_string(rotation) structure['size'] = vector_to_string([1, 1, 1]) panel = {} panel['structureId'] = structure_id panel['powerEfficiency'] = efficiency panel['area'] = area panel['maxPower'] = max_power panel['displayAxis'] = display_axis jsonDict['spacecraftData']['structure'].append(structure) jsonDict['spacecraftData']['solarPanels'].append(panel) json_data = json.dumps([jsonDict]) response = client.update_config(json_data, NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) return response
[docs] def add_sensor(sensor_name: str, sat_name: str, sensor_type: str, orientation: list[float], fov: list[float], pixel_dims: list[float], range: float, line_color_r: int=0,line_color_g: int=0,line_color_b: int=255,line_color_a: int=255, cone_color_r:int=0,cone_color_g:int=0,cone_color_b:int=255,cone_color_a:int=60, replace:bool=False) -> bytes: """ Add a sensor to the spacecraft. Args: sat_name (str): name of the spacecraft, for which to add the sensor. sensor_type (str): type of the sensor. currently only 'Optical'. orientation (list[float]): orientation quaternion w.r.t. the spacecraft body axes. fov (list[float]): field of view angles [deg]. pixel_dims (list[int]): horizontal and vertical pixel dimensions. range (float): range of the sensor [m]. """ jd, jsonStr = client.get_config_part([sat_name], NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) jsonDict = json.loads(jsonStr) sensor = {} sensor['name'] = sensor_name sensor['FoV'] = fov sensor['PixelDimensions'] = pixel_dims sensor['range'] = range sensor['orientation'] = '(' + str(orientation[0]) + ', ' + str(orientation[1]) + ', ' + str( orientation[2]) + ', ' + str(orientation[3]) + ')' sensor['sensorType'] = sensor_type sensor['Ranges'] = [] sensor['LinesOfSight'] = [] sensor['AngleMeasurements'] = [] sensor['PixelCoordinates'] = [] sensor['lineColorR'] = line_color_r sensor['lineColorG'] = line_color_g sensor['lineColorB'] = line_color_b sensor['lineColorA'] = line_color_a sensor['coneColorR'] = cone_color_r sensor['coneColorG'] = cone_color_g sensor['coneColorB'] = cone_color_b sensor['coneColorA'] = cone_color_a data = {} data['name'] = sat_name data['bodyType'] = 1 data['spacecraftData'] = jsonDict['spacecraftData'] if replace: if "sensors" in data['spacecraftData']: s = data['spacecraftData']['sensors'] count = len(s) index = 0 while count > 0: if sensor_name == data['spacecraftData']['sensors'][index]['name']: data['spacecraftData']['sensors'][index] = sensor break count -= 1 index += 1 else: data['spacecraftData']['sensors'].append(sensor) json_data = json.dumps([data]) response = client.update_config(json_data, NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) return response
[docs] def add_radio(sat_name: str, orientation: list[float], antenna_input_power: float, tx_frequency: float, rx_frequency: float, comms_law: str="Nearest", antenna_pattern_path: str="", friendlyStations: list[str]=[], friendlySpacecraft: list[str]=[]) -> bytes: """ Add a radio subsystem to a spacecraft. Args: sat_name (str): name of the spacecraft, for which to add the radio. orientation (list[float]): orientation quaternion w.r.t. the spacecraft. antenna_input_power (float): the power fed into the antenna input [W]. tx_frequency (float): transmission frequency [Hz]. rx_frequency (float): receive frequency [Hz]. antenna_pattern_path (str, optional): path to an antenna radiation pattern csv file. path can be absolute, or if it is not absolute, it will be interpreted as relative to /MDS_Solaris_Data/StreamingAssets/. friendlyStations (list[str], optional): list of friendly station names. is only relevant if comms_law is "Friendly". friendlySpacecraft (list[str], optional): list of friendly spacecraft names. is only relevant if comms_law is "Friendly". """ jd, jsonStr = client.get_config_part([sat_name], NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) jsonDict = json.loads(jsonStr) radio = {} radio['antennaInputPower'] = antenna_input_power radio['orientation'] = '(' + str(orientation[0]) + ', ' + str(orientation[1]) + ', ' + str( orientation[2]) + ', ' + str(orientation[3]) + ')' radio['txFrequency'] = tx_frequency radio['rxFrequency'] = rx_frequency radio['friendlyStations'] = friendlyStations radio['friendlySpacecraft'] = friendlySpacecraft radio['CommsLaw'] = comms_laws[comms_law] if len(antenna_pattern_path) > 0: radio['radiationPatternPath'] = antenna_pattern_path jsonDict['spacecraftData']['radios'].append(radio) json_data = json.dumps([jsonDict]) response = client.update_config(json_data, NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) return response
[docs] def add_polygons_from_csv(csv_path: str, r: float, g: float, b: float, a: float): """ Add polygons from a CSV file. Args: csv_path (str): path to a csv file (on client computer). r (float): red component of the polygon color. g (float): green component of the polygon color. b (float): blue component of the polygon color. a (float): alpha component of the polygon color. """ file = open(csv_path, 'r') content = file.read() file.close() response = client.load_polygons_from_csv(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, content, r, g, b, a) return response
[docs] def toggle_celestial_vector(enabled: bool, celestial_body: str) -> bytes: """ Toggle celestial vector for a specified celestial body. CelestialBodies values: Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune, Pluto, Moon, Sun Args: enabled (bool): flag that enables/disables celestial vector. celestial_body (str): name of the celestial body that the celestial vector shall point to. """ response = client.toggle_celestial_vectors(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, enabled, celestial_body) return response
[docs] def clear_sensors(sat_name: str) -> bytes: """ Remove all sensors from a satellite. Args: sat_name (str): name of the satellite, from which all sensors will be removed. """ data = {} data['name'] = sat_name data['bodyType'] = 1 data["spacecraftData"] = {} data["spacecraftData"]["sensors"] = [] json_data = json.dumps([data]) print(json_data) response = client.update_config(json_data, NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) return response
[docs] def schedule_maneuver_at_date(sat_name: str, julian_date: float, delta_v: list[float], burn_time: float) -> bytes: """ Schedule a maneuver for a specified date. Args: sat_name (str): name of the spacecraft, for which the maneuver will be scheduled. julian_date (float): julian date, for which the maneuver is scheduled. delta_v (list[float]): a vector of 3 components - delta V in inertial frame [m/s]. burn_time (float): time, which wil be taken to complete the maneuver [s].""" data = {} data['name'] = sat_name data['bodyType'] = 1 maneuver = {} maneuver['julianDate'] = julian_date maneuver['deltaV'] = '(' + str(delta_v[0]) + ', ' + str(delta_v[1]) + ', ' + str(delta_v[2]) + ')' maneuver['burnDuration'] = burn_time maneuver['useDate'] = True data['maneuvers'] = [maneuver] json_data = json.dumps([data]) response = client.update_config(json_data, NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) return response
[docs] def apply_torque(sat_name: str, torque: list[float], inertial: bool=False) -> bytes: """ Apply a torque Args: sat_name (str): name of the spacecraft, for which the torque will be applied. torque (list[float]): torque vector in the spacecraft body frame [Nm]. inertial (bool): if True, torque is applied in the inertial ICRF frame. """ data = {} data['name'] = sat_name data['bodyType'] = 1 if inertial: data['inertialTorque'] = "(" + str(torque[0]) + ", " + str(torque[1]) + ", " + str(torque[2]) + ")" else: data['torque'] = "(" + str(torque[0]) + ", " + str(torque[1]) + ", " + str(torque[2]) + ")" json_data = json.dumps([data]) response = client.update_config(json_data, NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) return response
[docs] def apply_thrust(sat_name: str, thrust: list[float], inertial: bool=False) -> bytes: """ Apply a thrust. Args: sat_name (str): name of the spacecraft to which the thrust should be applied. thrust (list): thrust vector in ICRF, to be applied during the next simulation step [N]. inertial (bool): if True, thrust is applied in the inertial ICRF frame. """ data = {} data['name'] = sat_name data['bodyType'] = 1 if inertial: data['inertialThrust'] = "(" + str(thrust[0]) + ", " + str(thrust[1]) + ", " + str(thrust[2]) + ")" else: data['thrust'] = "(" + str(thrust[0]) + ", " + str(thrust[1]) + ", " + str(thrust[2]) + ")" json_data = json.dumps([data]) response = client.update_config(json_data, NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) return response
[docs] def apply_thrust_torque(sat_name: str, thrust: list[float], torque: list[float], inertial: bool=False) -> bytes: """ Apply a thrust and a torque. Args: sat_name (str): name of the spacecraft to which the thrust and torque should be applied. thrust (list): thrust vector in spacecraft body frame, to be applied during the next simulation step [N]. torque (list): torque vector in spacecraft body frame, to be applied during the next simulation step [Nm]. inertial (bool): if True, thrust and torque are applied in the inertial ICRF frame. """ data = {} data['name'] = sat_name data['bodyType'] = 1 if inertial: data['inertialThrust'] = vector_to_string(thrust) data['inertialTorque'] = vector_to_string(torque) else: data['thrust'] = vector_to_string(thrust) data['torque'] = vector_to_string(torque) json_data = json.dumps([data]) response = client.update_config(json_data, NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) return response
def set_sat_velocity(sat_name: str, velocity: list[float]) -> bytes: """ Hard set the position of a spacecraft. Args: sat_name (str): name of the spacecraft. velocity (list[float]): the velocity vector, specifying spacecraft's velocity in body-centered inertial frame, with origin at the central body's c.o.g. and axes aligned with ICRF. """ data = {} data['name'] = sat_name data['bodyType'] = 1 data['localVelocity'] = vector_to_string(velocity) json_data = json.dumps([data]) response = client.update_config(json_data, NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) return response
[docs] def set_sat_position(sat_name: str, position: list[float]) -> bytes: """ Hard set the position of a spacecraft. Args: sat_name (str): name of the spacecraft. position (list[float]): the position vector, specifying spacecraft's position in body-centered inertial frame, with origin at the central body's c.o.g. and axes aligned with ICRF. """ data = {} data['name'] = sat_name data['bodyType'] = 1 data['localPosition'] = vector_to_string(position) json_data = json.dumps([data]) response = client.update_config(json_data, NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) return response
[docs] def set_sat_attitude(sat_name: str, quaternion: list[float]) -> bytes: """ Hard set the attitude of a spacecraft. Args: sat_name (str): name of the spacecraft. quaternion (list[float]): the attitude quaternion, specifying spacecraft's body frame rotation from the inertial ICRF frame. in [x,y,z,w] format. """ q_norm = np.linalg.norm(quaternion) if not np.isclose(q_norm, 1, rtol = 1e-4, atol = 1e-4): quaternion = np.array(quaternion) / q_norm data = {} data['name'] = sat_name data['bodyType'] = 1 data['attitudeState'] = {} data['attitudeState']['attitude'] = '(' + str(quaternion[0]) + ', ' + str(quaternion[1]) + ', ' + str( quaternion[2]) + ', ' + str(quaternion[3]) + ')' json_data = json.dumps([data]) response = client.update_config(json_data, NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) return response
[docs] def set_sat_angular_velocity(sat_name: str, angular_velocity: list[float], attitude: list[float] = None, convert_to_world: bool = False) -> bytes: """ Hard set the angular velocity of a spacecraft. Args: sat_name (str): name of the spacecraft. angular_velocity (list[float]): rad/s the angular velocity vector, specifying the desired angular velocity of the target in either its body frame (if convert_to_world is set to True, default behaviour) or the ICRF world frame (if convert_to_world is set to False). attitude (optional, list[float]): the attitude quaternion, specifying spacecraft's body frame rotation from the inertial ICRF frame. in [x,y,z,w] format. Necessary only when convert_to_world is set to True to convert from body frame to ICRF frame. convert_to_world (optional, bool): Whether or not to convert the given angular velocity from body frame to inertial ICRF frame. True by default because MDS expects the angular velocity to be specified in the inertial ICRF frame. If set to False then the attitude parameter can be left out also. """ data = {} data['name'] = sat_name data['bodyType'] = 1 data['attitudeState'] = {} if convert_to_world: if attitude is None: raise ValueError("If convert_to_world is set to True then the satellite attitude must be " \ "provided to convert the angular velocity from body to inertial ICRF world frame.\n" \ "Either convert the angular velocity yourself or provide the attitude quaternion to this method.") # Using own implementation to rotate angular velocity vector into world frame # (not relying on scipy) angular_velocity_world = rotate_vector_by_quaternion(angular_velocity, attitude) else: angular_velocity_world = angular_velocity data['attitudeState']['angularVelocity'] = vector_to_string(angular_velocity_world) json_data = json.dumps([data]) response = client.update_config(json_data, NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) return response
[docs] def set_state_covariance(sat_name: str, covariance_matrix: list[list[float]]) -> bytes: """ Set the state covariance matrix of a spacecraft. Args: sat_name (str): name of the spacecraft, for which the state covariance belongs. covariance_matrix (list[list[float]]): 6x6 covariance matrix specified as list of lists. """ data = {} data['name'] = sat_name data['bodyType'] = 1 data['stateCovariance'] = covariance_matrix json_data = json.dumps([data]) response = client.update_config(json_data, NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) return response
def set_inertia_tensor(sat_name: str, inertia_tensor: list[float]) -> bytes: data = {} data['name'] = sat_name data['bodyType'] = 1 data['inertiaTensor'] = vector_to_string(inertia_tensor) json_data = json.dumps([data]) response = client.update_config(json_data, NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) return response
[docs] def set_attitude_covariance(sat_name: str, covariance_matrix: list[list[float]]) -> bytes: """ Set the attitude covariance matrix of a spacecraft. Args: sat_name (str): name of the spacecraft, for which the attitude covariance belongs. covariance_matrix (list[list[float]]): 4x4 covariance matrix specified as list of lists. """ data = {} data['name'] = sat_name data['bodyType'] = 1 data['attitudeState'] = {} data['attitudeState']['attitudeCovariance'] = covariance_matrix json_data = json.dumps([data]) response = client.update_config(json_data, NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) return response
[docs] def set_attitude_parameters(sat_name: str, max_rotation_rate: float, max_rotation_accel: float) -> bytes: """ Set the attitude control constraints for a spacecraft. Args: sat_name (str): name of the spacecraft, for which to set the attitude parameters. max_rotation_rate (float): maximum possible rotation rate magnitude in [deg/s]. max_rotation_accel (float): maximum possible angular acceleration magnitude in [deg/s/s]. """ data = {} data['name'] = sat_name data['bodyType'] = 1 data['attitudeState'] = {} data['attitudeState']['maxAngularSpeed'] = max_rotation_rate data['attitudeState']['maxAngularAcceleration'] = max_rotation_accel json_data = json.dumps([data]) response = client.update_config(json_data, NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) return response
[docs] def set_pointing_mode(sat_name: str, mode_name: str) -> bytes: """ Set the pointing mode of satellite. Args: sat_name (str): name of the spacecraft. mode_name (str): a string that represents the pointing mode. available modes are: Idle, Nadir, Sun, Earth, ClosestStation. """ data = {} data['name'] = sat_name data['bodyType'] = 1 data['pointingMode'] = pointing_modes[mode_name] json_data = json.dumps([data]) response = client.update_config(json_data, NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) return response
[docs] def attach_camera_to_sat(sat_name: str, attach: bool=True, focal_length: float=20.78461, distance_from_satellite: float=0.0, near_plane: float=0.3, far_plane: float=1000000.0, sensor_size_x: float=36.0, sensor_size_y: float=24.0, lens_shift_x: float=0.0, lens_shift_y: float=0.0, gate_fit: str="Horizontal", iso: int=200, shutter_speed: float=100, focus_distance: float=5.0, aperture: float=16, lens_distortion_intensity: float=0.0, bloom_intensity: float=0.0, bloom_scatter: float=0.0, color_grain_intensity: float=0.0, color_grain_response: float=0.0, chromatic_aberration: float=0.0, enable_lens_flare: bool=False, texture_width: int=1024, texture_height: int=1024, enable_capture: bool=False, capture_interval: float=1.0, camera_quaternion: list[float]=[0, 0, 0, 1], python_server_ip: str=None, python_server_port: int=None, enable_depth_of_field: bool=True): """ Attach a camera to a spacecraft. Args: sat_name (str): name of the spacecraft, to which the camera will be attached. attach (bool, optional): True attaches the camera, False detaches it. focal_length (float, optional): focal length of the camera [mm]. distance_from_satellite (float, optional): distance from the satellite [m]. near_plane (float, optional): near clipping plane of the camera [m]. far_plane (float, optional): far clipping plane of the camera [m]. sensor_size_x (float, optional): sensor size in the horizontal dimension [mm]. sensor_size_y (float, optional): sensor size in the vertical dimension [mm]. lens_shift_x (float, optional): The lens offset of the camera in the x direction. The lens shift is relative to the sensor size. For example, a lens shift of 0.5 offsets the sensor by half its horizontal size. lens_shift_y (float, optional): The lens offset of the camera in the y direction. gate_fit (str, optional): Enum used to specify how the sensor gate (sensor frame) defined by sensor_size fits into the resolution gate (render frame). Options are: "Vertical", "Horizontal", "Fill", "Overscan", "None". enable_capture (bool, optional): Flag to turn frame capture on and off. When True, frames are saved to a local directory every capture_interval. Default is False. camera_quaternion (list[float], optional): quaternion of the camera orientation relative to X axis capture_interval (float, optional): interval at which to capture frames, if enable_capture is True [s]. python_server_ip (str, optional): IP of a python server to send the frames to. Default is None. python_server_port (int, optional): port of a python server to send the frames to. Default is None. """ response = client.attach_camera_to_satellite(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, sat_name, attach, focal_length, distance_from_satellite, near_plane, far_plane, sensor_size_x, sensor_size_y, lens_shift_x, lens_shift_y, gate_fit, iso, shutter_speed, focus_distance, aperture, lens_distortion_intensity, bloom_intensity, bloom_scatter, color_grain_intensity, color_grain_response,chromatic_aberration,enable_lens_flare, texture_width, texture_height, enable_capture, capture_interval, quaternion_to_string(camera_quaternion), python_server_ip, python_server_port, enable_depth_of_field) return response
[docs] def set_pointing_target(sat_name: str, quaternion: list[float]) -> bytes: """ Set a custom pointing target quaternion for a spacecraft. Args: sat_name (str): name of spacecraft quaternion ([float]): list of [x,y,z,w] quaternion values representing ICRF to body frame rotation.""" data = {} data['name'] = sat_name data['bodyType'] = 1 data['pointingMode'] = 'CustomQuaternion' data['CustomPointingQuaternion'] = '(' + str(quaternion[0]) + ', ' + str(quaternion[1]) + ', ' + str( quaternion[2]) + ', ' + str(quaternion[3]) + ')' json_data = json.dumps([data]) response = client.update_config(json_data, NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) return response
def set_power_consumption(sat_name: str, power_consumption: float): """ Set spacecraft's power consumption. Args: sat_name (str): name of the spacecraft, for which to set the power consumption. power_consumption (float): total power consumption of the spacecraft [W]. """ data = {} data['name'] = sat_name data['bodyType'] = 1 scData = {} scData['PowerConsumption'] = power_consumption data['spacecraftData'] = scData json_data = json.dumps([data]) response = client.update_config(json_data, NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) return response
[docs] def set_sat_track(sat_name: str, enabled: bool, new_track_interval: int, r: float, g: float, b: float, a: float): """ Attach a camera to a spacecraft. Args: sat_name (str): name of the spacecraft, to which the camera will be attached. enabled (bool): True enables the sensor track, False disables it. new_track_interval (int): simulation interval at which to produce track mark [s]. r,g,b,a (float): RGBA values ranging from 0 to 1 that set the color of the track.""" response = client.set_satellite_track(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, sat_name, enabled, new_track_interval, r, g, b, a) return response
[docs] def clear_objects(object_name_list: list[str]): """ Remove objects from the simulation. Args: object_name_list (list[str]): list of object names to be removed. """ response = client.delete_object(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, object_name_list) return response
[docs] def set_utc_date(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int): """ Set the simulation date as UTC TDB date-time. Args: year (int): the year. month (int): the month. day (int): the day. hour (int): the hour. minute (int): the minute. second (int): the second. millisecond (int): the millisecond. """ utc_string = str(year).zfill(4) + "-" + str(month).zfill(2) + "-" + str(day).zfill(2) + "T" + str(hour).zfill( 2) + ":" + str(minute).zfill(2) + ":" + str(second).zfill(2) + "." + str(millisecond).zfill(3) + "Z" print(utc_string) response = client.set_simulation_date(utc_string, NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) return response
[docs] def get_julian_date(): """ Get the current Julian date TDB of the simulation. """ response = client.get_config_part([], NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) jd = response[0] return jd
[docs] def get_utc_date(): """ Get the current UTC TDB date of the simulation. """ jd, utcdate = client.get_config_part(['utcDateTime'], NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) # print(utcdate) jsonDict = json.loads(utcdate) return jsonDict
[docs] def set_julian_date(julian_date: float): """ Set the simulation date as Julian date TDB. """ response = client.set_simulation_date(str(julian_date), NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) return response
[docs] def get_time_scale(): """ Get the timescale of simulation. """ jd, time_scale = get_config_part('timeScale') return float(time_scale)
[docs] def set_time_scale(scale: float): """ Set the timescale of simulation. Args: scale (float): time multiplier """ response = client.set_simulation_timescale(str(scale), NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) return response
[docs] def step_sim(steps = 1, waitForPropagationEnd = False): """ Step the simulation. """ response = client.step_simulation(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, steps, waitForPropagationEnd) return response
[docs] def select_satellite(name): data = { "name" : name } json_data = json.dumps(data) response = client.select_satellite(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, json_data) return response
def disable_sat_thruster_effects(name): data = { "name" : name } json_data = json.dumps(data) response = client.disable_sat_thruster_effects(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, json_data) return response def enable_sat_thruster_effects(name): data = { "name" : name } json_data = json.dumps(data) response = client.enable_sat_thruster_effects(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, json_data) return response
[docs] def load_orbits_from_csv(csv_path: str): """ Load a list of spacecraft from a CSV file. Args: csv_path (str): a path to the csv file, on client computer. """ json_data = "" with open(csv_path, 'r', encoding='utf-8') as f: next(f) for line in f: elems = line.split(',') data = {} data['name'] = elems[0] data['bodyType'] = 1 data['mass'] = float(elems[7]) elements = {} rp = float(elems[1]) ra = float(elems[2]) elements['semiMajorAxis'] = (rp + ra) * 1000 / 2 elements['eccentricity'] = (ra - rp) / (ra + rp) elements['inclination'] = float(elems[3]) * math.pi / 180.0 elements['ascendingNodeLongitude'] = float(elems[4]) * math.pi / 180.0 elements['periapsisArgument'] = float(elems[5]) * math.pi / 180.0 elements['trueAnomaly'] = float(elems[6]) * math.pi / 180.0 data['orbitalElements'] = elements data['centralBody'] = elems[8] json_data += "," + json.dumps(data) json_data = json_data.lstrip(',') json_data = "[" + json_data + "]" response = client.update_config(json_data, NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) return response
# response = client.load_orbits_from_csv(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, content) # return response
[docs] def visualize_thrusters(sat_name, thrust_body, thrust_min=0): """visualizes the thrusters by converting body-frame thrust to booleans of whether the positive/negative values exeed the thrust_min_shown threshold Args: thrust_body (array): body-frame thrust [N] thrust_min (float): minimum thrust to visualize [N] """ response = client.set_manual_thrust_torque(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, sat_name, thrustRight=bool(thrust_body[0]>thrust_min), thrustLeft=bool(thrust_body[0]<-thrust_min), thrustUp=bool(thrust_body[1]>thrust_min), thrustDown=bool(thrust_body[1]<-thrust_min), thrustForward=bool(thrust_body[2]>thrust_min), thrustBackward=bool(thrust_body[2]<-thrust_min), torqueLeft = False, torqueRight = False, torqueUp = False, torqueDown = False, torqueForward = False, torqueBackward = False ) return response
def set_manual_thrust_torque(name : str, thrustRight : bool = False, thrustLeft : bool = False, thrustUp : bool = False, thrustDown : bool = False, thrustForward : bool = False, thrustBackward : bool = False, torqueLeft :bool = False, torqueRight : bool = False, torqueUp : bool = False, torqueDown : bool = False, torqueForward : bool = False, torqueBackward : bool = False): response = client.set_manual_thrust_torque(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, name, thrustRight, thrustLeft, thrustUp, thrustDown, thrustForward, thrustBackward, torqueLeft, torqueRight, torqueUp, torqueDown, torqueForward, torqueBackward) return response def enable_collisions(): response = client.enable_collisions(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) return response def disable_collisions(): response = client.disable_collisions(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) return response def attach_bodies(parent_sat_name, child_sat_name): data = {} data['parent'] = parent_sat_name data['child'] = child_sat_name _, sat_data = get_sat_data(parent_sat_name, child_sat_name) r_parent = np.fromstring(sat_data[0]['localPosition'].strip('()'), sep=', ') r_child = np.fromstring(sat_data[1]['localPosition'].strip('()'), sep=', ') data['offsetVector'] = vector_to_string(r_child - r_parent) json_data = json.dumps(data) response = client.attach_bodies(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, json_data) return response def de_attach_bodies(parent_sat_name, child_sat_name): data = {} data['parent'] = parent_sat_name data['child'] = child_sat_name json_data = json.dumps(data) response = client.de_attach_bodies(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, json_data) return response
[docs] def optimize_transfer(sat_name: str, a: float, e: float, i: float, Omega: float, omega: float, alpha1: float | None = None, alpha2: float | None = None, mode: str='OPTIMIZE_DELTAV', maxDeltaT: float | None = None, maxDeltaV: float | None = None): """Order an optimal orbit transfer. Args: sat_name (str): name of the spacecraft, which will perform the transfer. a (float): desired semi-major axis in meters. e (float): desired eccentricity. i (float): desired inclination in degrees. Omega (float): desired ascending node longitude in degrees. omega (float): desired argument of periapsis in degrees. alpha1 (float): initial orbit exit point true anomaly in degrees (TA in your first orbit where you apply maneuver) alpha2 (float): target orbit entry point true anomaly in degrees (TA in transfer orbit, where the second maneuver will be applied) mode (enum): desired optimization goal: (default) 'OPTIMIZE_DELTAV', 'OPTIMIZE_TRANSFER_TIME', 'OPTIMIZE_TIME'. maxDeltaT (float): DeltaV constraint: Maximum allowed DeltaV. Used when you do time optimization. maxDeltaT (float): Time constraint: Maximum allowed time for transfer, when you optimize for deltaV. """ data = {} data['name'] = sat_name target_elements = {} target_elements['semiMajorAxis'] = a target_elements['eccentricity'] = e target_elements['inclination'] = i target_elements['ascendingNodeLongitude'] = Omega target_elements['periapsisArgument'] = omega data['orbit'] = target_elements data['mode'] = mode data['alpha1'] = alpha1 data['alpha2'] = alpha2 data['maxDeltaT'] = maxDeltaT data['maxDeltaV'] = maxDeltaV json_data = json.dumps(data) response = client.optimize_transfer(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, json_data) return response
[docs] def send_log_message(message: str): """ Send a message to be inserted to the mission log. Args: message (string): message to be added to the mission log. """ response = client.send_log_comment(message, NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT) return response
def extract_elements(dict): """ :meta private: """ name = dict["name"] elems = dict["orbitalElements"] a = elems['semiMajorAxis'] e = elems['eccentricity'] i = elems["inclination"] raan = elems['ascendingNodeLongitude'] omega = elems['periapsisArgument'] nu = elems["trueAnomaly"] # print(name + ":") # print("Semi-major axis = " + str(a) + " m") # print("Eccentricity = " + str(e)) # print("Inclination = " + str(i * 180 / math.pi) + " deg") # print("Ascending node longitude = " + str(raan * 180 / math.pi) + " deg") # print("Periapsis argument = " + str(omega * 180 / math.pi) + " deg") # print("True anomaly = " + str(nu * 180 / math.pi) + " deg") return name, elems def rename_spacecraft(name, rename_to): """ Rename a spacecraft. :param name: original spacecraft name :param rename_to: new spacecraft name """ data = {} data['name'] = name data['new_name'] = rename_to json_data = json.dumps(data) response = client.rename_spacecraft(NetworkConfiguration.MDS_HOST, NetworkConfiguration.MDS_HOST_PORT, json_data) return response