Package: sedaro
Sedaro Python ClientA python client for interacting with the Sedaro API using intuitive classes and methods.This client is intended to be used alongside ourOpenAPI Specification. Please refer to this documentation for detailed information on the names, attributes, and relationships of each Sedaro Block. See docstrings on classes and their methods for further instructions and explanations.It is recommended that you are familiar withModeling in Sedaroas a prerequisite to using this client.Package release versions correspond to the Sedaro application version at the time of package updates.InstallationpipinstallsedaroGetting StartedInstantiateSedaroApiClientand get aBranch# Generate an API key in the Sedaro Management Console.sedaro=SedaroApiClient(api_key=API_KEY)# Get an agent template branchagent_template_branch=sedaro.agent_template('NShL_CIU9iuufSII49xm-')# Get a scenario branchscenario_branch=sedaro.scenario('NXKwd2xSSPo-V2ivlIr8k')# If using a dedicated enterprise Sedaro instance, overwrite the default `host` kwarg.HOST='url-to-my-sedaro-instance.com'sedaro=SedaroApiClient(api_key=API_KEY,host=HOST)Block CRUDUse theAgentTemplateBranchorScenarioBranchto instantiate and utilize theBlockTypeclass. ABlockTypeobject is used to create and access Sedaro Blocks of the respective class.branch.BatteryCellbranch.Componentbranch.Subsystem# ...etc.ValidBlockTypes for Agent Template Branches and Scenario Branches can be found in our redocsOpenAPI Specification, by viewing the valid classes in theblockskey for theTemplatePATCHroute. In code editors that support it, intellisense will suggest names forBlockTypes.ABlockTypehas several methods:branch.Subsystem.create(name='Structure')branch.Subsystem.get(block_id)# ID of desired Subsystembranch.Subsystem.get_all_ids()branch.Subsystem.get_all()branch.Subsystem.get_where()branch.Subsystem.get_first()branch.Subsystem.get_last()These methods (except forget_all_ids) return a single or list ofBlock(s). ABlockhas several methods and properties.subsystem=branch.Subsystem.create(name='Structure')subsystem.update(name='Structure 2.0')subsystem.delete()ABlockwill always be equal to and in sync with all otherBlocks referencing the same Sedaro Block:subsystem=branch.Subsystem.create(name='Structure')subsystem_2=subsystem.update(name='Structure 2.0')subsystem_3=branch.Subsystem.get(subsystem.id)assertsubsystem==subsystem_2==subsystem_3Thereprof aBlockwill show you the corresponding Sedaro Block's data:repr(subsystem)>>>Subsystem(>>>category='CUSTOM'>>>components=[]>>>id='NShHxZwUh1JGRfZKDvqdA'>>>name='Structure 2.0'>>>type='Subsystem'>>>)Keying into any field existing on the corresponding Sedaro Block will return the value.subsystem.name>>>'Structure 2.0'Keying into relationship fields returnsBlocks corresponding to the related SedaroBlocks as follows:OneSide: aBlockManySide: alistofBlocksDataSide: a dictionary withBlocks as keys and relationship data as valuessubsystem.components[0]>>>SolarPanel(id='NShKPImRZHxGAXqkPsluk')Note that this allows for traversing via chained relationship fields.solar_panel.cell.panels[-1].subsystem.components[0].delete()Full ExamplefromsedaroimportSedaroApiClientfromsedaro.exceptionsimportNonexistantBlockErrorAPI_KEY='api_key_generated_by_sedaro'AGENT_TEMPLATE_ID='NShL_CIU9iuufSII49xm-'sedaro=SedaroApiClient(api_key=API_KEY)branch=sedaro.agent_template(AGENT_TEMPLATE_ID)solar_cell=branch.SolarCell.create(partNumber="987654321",manufacturer='Sedaro Corporation',openCircuitVoltage=3.95,shortCircuitCurrent=0.36,maxPowerVoltage=3.54,maxPowerCurrent=0.345,numJunctions=3,)sc_id=solar_cell.idsolar_cell.update(partNumber="123456789")solar_cell.delete()try:solar_cell.update(partNumber="987654321")exceptNonexistantBlockErrorase:assertstr(e)==f'The referenced Block with ID:{sc_id}no longer exists.'Multi-Block CRUDThecrudmethod is also available for performing operations on multiple Sedaro blocks and/or root at the same time using kwargs as follows:root: update fields on the root by passing a dictionaryblocks: create/update 1+ blocks by passing a list of dictionaries. If anidis present, the corresponding block will be updated. If anidisn't present, a new block will be created. Thetypeis always required.delete: delete 1+ blocks by passing a list of their blockids.In this method, relationship fields can point at existingBlockID's or "ref id"s. A "ref id" is similar to a
json "reference" and is used as follows:It is any string starting with'$'.It must be in theidfield of a singleBlockdictionary created in this transaction.It can be referenced in any relationship field on root or anyBlockdictionary in this transaction.All instances of the "ref id" will be resolved to the corresponding createdBlock's id.branch.crud(root={"field":"value"},# update fields on rootblocks=[{"id":"NXKzb4gSdLyThwudHSR4k","type":"Modem","field":"value"},# update block{"type":"SolarCell","field":"value",...},# create block],delete=["NTF8-90Sh93mPKxJkq6z-"]# delete block)The response from this method is used to update the blocks in theBranchthe method was called on. The content of the response is also returned, as follows:{"crud":{"blocks":[],# ids of all Blocks created or updated"delete":[],# ids of all Blocks deleted},"branch":{# whole branch dictionary}}SimulationAccess aSimulationvia thesimulationattribute on aScenarioBranch.sim=sedaro.scenario('NShL7J0Rni63llTcEUp4F').simulation# Start simulationsimulation_handle=sim.start()# This will return imediately after queueing the simulation job# To instead wait for simulation to enter the RUNNING state, pass `wait=True`# simulation_handle = sim.start(wait=True)# See simulation statussimulation_handle=sim.status()# Poll simulation, and return results when complete (progress will be printed until ready)results=sim.results_poll()# If you know it's complete, query for results directlyresults=sim.results()# Terminate running simulationsim.terminate()Thestatus,results,results_poll, andterminatemethods can all optionally take ajob_id, otherwise they operate on the latest (most recently started/finished) simulation.Forresultsandresults_poll, you may also provide the optional kwargstreams. This triggers narrowing results to fetch only specific streams that you specify. See doc strings for theresultsmethod for details on how to use thestreamskwarg.Simulation HandleThe followingSimulationmethods are also available on theSimulationHandlereturned bysim.start()andsim.status():simulation_handle.status()simulation_handle.results_poll()simulation_handle.results()simulation_handle.results_plain(...)simulation_handle.terminate()TheSimulationHandlecan also be used to access the attributes of the running simulation. For example:simulation_handle['id']simulation_handle['status']...ResultsAny object in the results API will provide a descriptive summary of its contents when the.summarizemethod is called. See theresults_api_demonotebook in themodsim notebooksrepository for more examples.Fetch Raw DataYou may also fetch results directly as a plain dictionary with additional arguments to customize the result.sim=sedaro.scenario('NShL7J0Rni63llTcEUp4F').simulation# Run simulationsim.start(wait=True)# After finished... get raw dataselected_streams=[('foo',),('bar','Thermal'),('bar','Power')]data=sim.results_plain(start=65000,stop=65001,sampleRank=1,streams=selected_streams,)### alternative:data=sim.results_plain(start=65000,stop=65001,sampleRank=8,streams=selected_streams,)See doc string in theresults_plainfor details on use of the arguments.Bulk DownloadUse the following method to download larger datasets more efficiently. See the doc string for details on use of the arguments.sim.download()This will produce a ZIP archive calleddata.zipin your working directory. Inside the ZIP archive, there will be one JSON file for each Agent, with the name<agent UUID>.json.Send RequestsUse the built-in method to send custom requests to the host. SeeOpenAPI Specificationfor documentation on resource paths and body params.Through therequestproperty, you can accessget,post,put,patch, anddeletemethods.# get a branchsedaro.request.get(f'/models/branches/{AGENT_TEMPLATE_ID}')# create a celestial target in a branchsun={'name':'Sun','type':'CelestialTarget'}sedaro.request.patch(f'/models/branches/{AGENT_TEMPLATE_ID}/template/',{'blocks':[sun]})Note that requests sent this way to CRUD Sedaro Blocks won't automatically update already instantiatedBranchorBlockobjects.External Simulation State DependenciesThe following API is exposed to enable the integration of external software with a Sedaro simulation during runtime. Read more about "Cosimulation" in Sedarohere.Warning:The following documentation is a work in progress as we continue to evolve this feature. It is recommended that you reach out to Sedaro Application Engineering for assistance using this capability while we mature the documentation for it.SetupDefineExternalStateblock(s) on aScenarioto facilitate in-the-loop connections from external client(s) (i.e.Cosimulation). The existance of these blocks determines whether or not the external interface is enabled and active during a simulation. These blocks will also be version controlled just as any other block in a Sedaro model.# Per Round External State Block{"id":"NZ2SGPWRnmdJhwUT4GD5k","type":"PerRoundExternalState","produced":[{"root":"velocity"}],# Implicit QuantityKind"consumed":[{"prev.root.position.as":"Position.eci"}],# Explicit QuantityKind"engineIndex":0,# 0: GNC, 1: C&DH, 2: Power, 3: Thermal"agents":["NSghFfVT8ieam0ydeZGX-"]}# Spontaneous External State Block{"id":"NZ2SHUkS95z1GtmMZ0CTk","type":"SpontaneousExternalState","produced":[{"root":"activeOpMode"}],"consumed":[{"prev.root.position.as":"Position.eci"}],"engineIndex":0,# 0: GNC, 1: C&DH, 2: Power, 3: Thermal"agents":["NSghFfVT8ieam0ydeZGX-"]}Deploy (i.e. Initialize)sim_client=sedaro.scenario('NShL7J0Rni63llTcEUp4F').simulation# Start the simulation# Note that when `sim_client.start()` returns, the simulation job has entered your Workspace queue to be built and run.# Passing `wait=True` to start() will wait until the simulation has entered the RUNNING state before returning.# At this time, the simulation is ready for external state production/consumptionsimulation_handle=sim_client.start(wait=True)Consumeagent_id=...# The ID of the relevant simulation Agentper_round_external_state_id=...# The ID of the relevant ExternalState blockspontaneous_external_state_id=...# The ID of the relevant ExternalState blocktime=60050.0137# Time in MJD# Query the simulation for the state defined on the ExternalState block at the optionally given time# This blocks until the state is available from the simulationstate=simulasimulation_handletion.consume(agent_id,per_round_external_state_id)print(state)state=simulation_handle.consume(agent_id,spontaneous_external_state_id,time=time)# Optionally provide timeprint(state)Note:Callingconsumewith atimevalue that the simulation hasn't reached yet will block until the simulation catches up. This is currently subject to a 10 minute timeout. If the request fails after 10 minutes, it is recommended that it be reattempted.Similarly, callingconsumewith atimethat too far lags the current simulation might result in an error as the value has been garbage collected from the simulation caches and is no longer available for retrieval. If this is the case, please fetch the data from the Data Service (via the Results API) instead.Producestate=([7000,0,0],# Position as ECI (km)[12,0,14.1,14.3,7,0],# Thruster thrusts)simulation_handle.produce(agent_id,per_round_external_state_id,state)state=([0,0,0,1],# Commanded Attitude as Quaternion)simulation_handle.produce(agent_id,spontaneous_external_state_id,state,timestamp=60050.2)# `timestamp` is optional.  If not provided, the `time` at which the simulation receives the spontaneous state is used# Note: `timestamp` can be used to intentionally inject latency between the time a command is sent and when it is to take effect.  This allows for more accurately modeling communications latency on various comms buses.TeardownA simulation that terminates on its own will clean up all external state interfaces. Manually terminating the simulation will do the same:simulation_handle.terminate()Sedaro Base ClientThe Sedaro client is a wrapper around the Swagger generated OpenAPI client. When this package is installed, the auto-generated, lower-level clients and methods are also available undersedaro_base_client.fromsedaro_base_clientimport...Community, Support, DiscussionIf you have any issues using the package or any suggestions, please start by reaching out:Open an issue onGitHubJoin the Sedaro CommunitySlackEmail us atsupport@sedarotech.comPlease note that while emails are always welcome, we prefer the first two options as this allows for others to benefit from the discourse in the threads. That said, if the matter is specific to your use case or sensitive in nature, don't hesitate to shoot us an email instead.