Receiving Data in C++#
This how-to guide describes the concepts of receiving data from other satellites. It is recommended to read through the implementation tutorial of satellites in C++ to get a good understanding of implementing a basic satellite.
See also
For the basic concepts behind data transmission and processing in Constellation check the chapter in the operator’s guide.
Receiving data with a satellite written in C++ entails inheriting from the ReceiverSatellite
class.
This class provides all features necessary for an efficient reception and treatment of data.
The following sections details its functions and provide guidance in implementing data handling methods.
Preparation: Validating Output Paths#
In many cases, the receiving satellite will store data to a file on disk.
The ReceiverSatellite
class provides a few convenience methods which ease this task and provide additional functionality
to the user:
The validate_output_directory
should be called in the initializing method of the receiver satellite.
It will check for the existence and accessibility of the selected storage directory. A SatelliteError
exception is thrown
upon any issue detected. In addition, this method will register the DISKSPACE_FREE
metric for the selected storage
location, which will regularly broadcast the available space on the target storage device. In addition, log messages are
emitted on WARNING
level when the available storage falls below 10 GB, and on CRITICAL
level when the available disk
space is less than 3 GB.
An example for using the method is given below:
void MyWriterSatellite::initializing(Configuration& config) {
base_path_ = config.getPath("output_directory");
validate_output_directory(base_path_);
}
During the starting transition, also the final file name is known, which likely uses the current run
identifier as part of the file name. In order to ensure that the file can be created properly, no other data is overwritten,
and the storage is writable, the validate_output_file
should be used. It takes the base path, a file name and an extension
as arguments, making it convenient for assembling the final storage path. The function returns the validated canonical path
to the target file.
A possible application could be the following, where the output file path is validated and a binary file stream is opened:
void MyWriterSatellite::starting(std::string_view run_identifier) {
// Open target file
auto file_path = validate_output_file(base_path_, "data_" + std::string(run_identifier), "raw");
file_stream_ = std::ofstream(file_path, std::ios_base::binary);
}
Alternatively, the create_output_file
function can be used to validate the target file in the same manner, but directly
create the output file stream and return it. The additional function argument allows selecting whether the file is opened in
binary mode or not. This would reduce the above example to:
void MyWriterSatellite::starting(std::string_view run_identifier) {
// Open target file
file_stream_ = create_output_file(base_path_, "data_" + std::string(run_identifier), "raw", true);
}
Both functions will also update the registered metric DISKSPACE_FREE
to the latest target storage.
Handling Data: Callbacks#
Data are received through the callbacks receive_bor
, receive_data
and receive_eor
which are purely virtual in the
ReceiverSatellite
base class and therefore must be implemented by any receiver implementation.
As the names indicate, these callbacks separate between the three message types known to Constellation for data transfer. The information provided to the callbacks differs:
The
receive_bor
callback provides the message header information with user-defined tags alongside the decoded satellite configuration of the sending satellite as taken from the BOR message payload.The
receive_data
callback provides direct access to the data message, comprising the header with user-defined tags as well as the payload frames.Finally, the
receive_eor
callback provides access to the message header with user-defined tags and the decoded dictionary of run metadata assembled by the sending satellite.
A simplified example implementation of these callbacks can be found below. Here, the callbacks are merely logging information taken from the received messages, and discard the data.
void MyWriterSatellite::receive_bor(const CDTP1Message::Header& header, Configuration config) {
LOG(INFO) << "Received BOR from " << header.getSender() << " with config" << config.getDictionary().to_string();
}
void MyWriterSatellite::receive_data(CDTP1Message data_message) {
const auto& header = data_message.getHeader();
LOG(DEBUG) << "Received data message from " << header.getSender()
<< " with " << data_message.countPayloadFrames() << " frames";
}
void MyWriterSatellite::receive_eor(const CDTP1Message::Header& header, Dictionary run_metadata) {
LOG(INFO) << "Received EOR from " << header.getSender() << " with metadata" << run_metadata.to_string();
}