Starting & Controlling a Satellite#

The following tutorial describes how to start a single satellite, how to discover it and change its state with a command line controller. For this purposes example satellites are used, a full list of satellites can be found here.

Hint

This tutorial assumes that both the C++ and Python implementations of Constellation are installed. If the C++ implementation is not installed, all appearances of SatelliteSputnik can be replaced with SatelliteMariner.

Starting a Satellite#

The SatelliteSputnik executable is used to start the example Sputnik satellite. It has two relevant command line arguments:

  • --name, -n: This is the name for satellite, which should be unique within the Constellation group (defaults to the host name of the PC).

  • --group, -g: This is the name of the Constellation group this satellite should be a part of.

SatelliteSputnik -n One -g edda

Controlling the Satellite#

A controller running in the same Constellation group is needed in order to control the satellite started in the first part of this tutorial. This section shows different options to perform this task.

Starting a Controller#

The Python implementation of Constellation provides a powerful command line interface controller using IPython. This can be installed with the cli component:

pip install --no-build-isolation -e .[cli]
pip install ConstellationDAQ[cli]

The controller can be started via the command Controller. It has one relevant command line argument:

  • --group, -g: This is the name of the Constellation group this controller should be a part of.

To control the satellite created in the first part of this tutorial, the controller needs to be in the same group.

Controller -g edda

The interactive command line provides the constellation object which holds all information about connected satellites and allows their control. Getting a dictionary containing the satellites could e.g. be performed by running:

edda > constellation.satellites
{'Sputnik.One': SatelliteCommLink(name=One, class=Sputnik)}

In order to obtain more information on a specific satellite, it can be directly addressed via its type and name and a command can be sent. The response is then printed on the terminal:

edda > constellation.Sputnik.One.get_name()
SatelliteResponse(msg='sputnik.one')

The controller supports tab completion, and suggestions for possible commands are displayed typing e.g. constellation.Sputnik.One. and hitting the tab key.

Since this is an interactive IPython console, of course also loops are possible and could look like this with two satellites connected:

edda > for sat in constellation.satellites.values():
  ...:     print(sat.get_name())
  ...:
'sputnik.one'
'mariner.nine'

Sending Commands to the Satellite#

Commands can either be sent to individual satellites, all satellites of a given type, or the entire constellation. All available commands for the constellation object are available via tab completion.

To initialize the satellite, it needs to be sent an initialize command, with a dictionary of configuration options as an argument. In the following example, this dictionary is empty ({}) and directly passed to the command.

edda > constellation.Sputnik.One.initialize({})
{'Sputnik.One': SatelliteResponse(msg='transition initialize is being initiated')}

All satellites can be initialized together by sending the command to the entire constellation:

edda > constellation.initialize({})
{'Sputnik.One': SatelliteResponse(msg='transition initialize is being initiated'),
 'Mariner.Nine': SatelliteResponse(msg='transitioning', payload=initialize)}

Whether the satellites have actually changed their state can be checked by retrieving the current state of an individual satellite or the entire constellation via:

edda > constellation.Sputnik.One.get_state()
SatelliteResponse(msg='init', payload=32,
                  meta={"last_changed": Timestamp(seconds=1734009498, nanoseconds=796949911)})
edda > constellation.get_state()
{'Sputnik.One': SatelliteResponse(
                   msg='init', payload=32,
                   meta={"last_changed": Timestamp(seconds=1734009498, nanoseconds=796949911)}),
 'Mariner.Nine': SatelliteResponse(
                    msg='init', payload=32,
                    meta={"last_changed": Timestamp(seconds=1734009498, nanoseconds=794958000),
                        "last_changed_iso": '2024-12-12T13:18:18.794958+00:00'})}

Similarly, all satellite states can be called. A full list of available commands, along with a description of the finite state machine can be found in the concepts chapter on satellites.

Loading a Configuration File#

Constellation configuration files are TOML files with the configuration key-value pairs for all satellites. The individual satellite configurations are sent to their satellites together with the initialize command as dictionary. Their basic structure and syntax is the following:

[satellites]
# General settings which apply to all satellites
confidentiality = "TOPSECRET"

[satellites.Sputnik]
# Settings which apply to all satellites of type "Sputnik"
interval = 1000

[satellites.Mariner.Nine]
# Settings which only apply to the satellite with name "Mariner.Nine"
voltage = 5.1

When starting the controller, a configuration file can be passed as optional command line argument:

Controller -g edda --config myconfiguration.toml

The configuration is then available as cfg object on the interactive command line and can be passed to the initialize function:

edda > constellation.initialize(cfg)
{'Sputnik.One': SatelliteResponse(msg='transition initialize is being initiated'),
 'Mariner.Nine': SatelliteResponse(msg='transitioning', payload=initialize)}

Alternatively, the configuration can be read and parsed in an already running interactive command line session using the load_config function. The resulting dictionary can be directly passed to the initialize method, the distribution of dictionaries to the individual satellites is taken care of by the controller.

edda > cfg = load_config("myconfiguration.toml")
edda > constellation.initialize(cfg)
{'Sputnik.One': SatelliteResponse(msg='transition initialize is being initiated'),
 'Mariner.Nine': SatelliteResponse(msg='transitioning', payload=initialize)}

Closing the Controller#

Controllers in Constellation do not possess state and can be closed and restarted at the discretion of the user without affecting the state of the satellites. When closing and starting the controller again, the satellites will still be in the INIT state from before.

The IPython CLI controller can be disconnected from the constellation using the command quit or by pressing Control-d twice.