9#include <nlohmann/json.hpp>
21 return {{
"chunk_index",
chunk.chunk_index},
22 {
"i_dataset",
chunk.i_dataset},
23 {
"q_dataset",
chunk.q_dataset},
24 {
"start_time",
chunk.start_time},
25 {
"sample_count",
chunk.sample_count},
26 {
"sample_start",
chunk.sample_start},
27 {
"sample_end_exclusive",
chunk.sample_end_exclusive}};
35 {
"sample_count",
segment.sample_count},
36 {
"sample_start",
segment.sample_start},
37 {
"sample_end_exclusive",
segment.sample_end_exclusive}};
38 if (
segment.first_chirp_start_time.has_value())
40 result[
"first_chirp_start_time"] = *
segment.first_chirp_start_time;
42 if (
segment.emitted_chirp_count.has_value())
46 if (
segment.first_triangle_start_time.has_value())
48 result[
"first_triangle_start_time"] = *
segment.first_triangle_start_time;
50 if (
segment.emitted_triangle_count.has_value())
52 result[
"emitted_triangle_count"] = *
segment.emitted_triangle_count;
54 if (
segment.first_sfcw_step_start_time.has_value())
56 result[
"first_sfcw_step_start_time"] = *
segment.first_sfcw_step_start_time;
58 if (
segment.emitted_sfcw_step_count.has_value())
60 result[
"emitted_sfcw_step_count"] = *
segment.emitted_sfcw_step_count;
66 nlohmann::json
fmcwToJson(
const FmcwMetadata& fmcw)
68 nlohmann::json
result = {{
"chirp_bandwidth", fmcw.chirp_bandwidth},
69 {
"chirp_duration", fmcw.chirp_duration},
70 {
"waveform_shape", fmcw.waveform_shape},
71 {
"chirp_rate", fmcw.chirp_rate},
72 {
"start_frequency_offset", fmcw.start_frequency_offset}};
73 if (fmcw.waveform_shape ==
"linear")
75 result[
"chirp_period"] = fmcw.chirp_period;
76 result[
"chirp_rate_signed"] = fmcw.chirp_rate_signed;
77 result[
"chirp_direction"] = fmcw.chirp_direction;
78 if (fmcw.chirp_count.has_value())
80 result[
"chirp_count"] = *fmcw.chirp_count;
83 else if (fmcw.waveform_shape ==
"triangle")
85 if (fmcw.triangle_period.has_value())
87 result[
"triangle_period"] = *fmcw.triangle_period;
89 if (fmcw.triangle_count.has_value())
91 result[
"triangle_count"] = *fmcw.triangle_count;
101 if (
segment.first_chirp_start_time.has_value())
103 result[
"first_chirp_start_time"] = *
segment.first_chirp_start_time;
105 if (
segment.emitted_chirp_count.has_value())
107 result[
"emitted_chirp_count"] = *
segment.emitted_chirp_count;
109 if (
segment.first_triangle_start_time.has_value())
111 result[
"first_triangle_start_time"] = *
segment.first_triangle_start_time;
113 if (
segment.emitted_triangle_count.has_value())
115 result[
"emitted_triangle_count"] = *
segment.emitted_triangle_count;
123 nlohmann::json segments = nlohmann::json::array();
124 for (
const auto&
segment : source.segments)
129 nlohmann::json
result = {{
"transmitter_id", source.transmitter_id},
130 {
"transmitter_name", source.transmitter_name},
131 {
"waveform_id", source.waveform_id},
132 {
"waveform_name", source.waveform_name},
133 {
"carrier_frequency", source.carrier_frequency},
134 {
"segments", segments}};
139 nlohmann::json
sfcwToJson(
const SfcwMetadata& sfcw)
141 nlohmann::json
result = {{
"carrier_frequency", sfcw.carrier_frequency},
142 {
"start_frequency_offset", sfcw.start_frequency_offset},
143 {
"step_size", sfcw.step_size},
144 {
"step_count", sfcw.step_count},
145 {
"dwell_time", sfcw.dwell_time},
146 {
"step_period", sfcw.step_period},
147 {
"first_frequency", sfcw.first_frequency},
148 {
"last_frequency", sfcw.last_frequency},
149 {
"frequency_span", sfcw.frequency_span},
150 {
"effective_bandwidth", sfcw.effective_bandwidth},
151 {
"range_resolution", sfcw.range_resolution},
152 {
"unambiguous_range", sfcw.unambiguous_range}};
153 if (sfcw.sweep_count.has_value())
155 result[
"sweep_count"] = *sfcw.sweep_count;
163 if (
segment.first_step_start_time.has_value())
165 result[
"first_step_start_time"] = *
segment.first_step_start_time;
167 if (
segment.emitted_step_count.has_value())
176 nlohmann::json segments = nlohmann::json::array();
177 for (
const auto&
segment : source.segments)
182 nlohmann::json
result = {{
"transmitter_id", source.transmitter_id},
183 {
"transmitter_name", source.transmitter_name},
184 {
"waveform_id", source.waveform_id},
185 {
"waveform_name", source.waveform_name},
186 {
"segments", segments}};
191 template <
typename T>
194 if (value.has_value())
202 addOptional(
result,
"fmcw_dechirp_reference_transmitter_id",
file.fmcw_dechirp_reference_transmitter_id);
204 file.fmcw_dechirp_reference_transmitter_name);
206 addOptional(
result,
"fmcw_dechirp_reference_waveform_name",
file.fmcw_dechirp_reference_waveform_name);
207 if (
file.fmcw_dechirp_reference_waveform.has_value())
215 result[
"fmcw_if_decimation_enabled"] =
file.fmcw_if_decimation_enabled;
216 result[
"fmcw_if_legacy_full_rate"] =
file.fmcw_if_legacy_full_rate;
228 file.fmcw_if_compensated_integer_delay_samples);
230 file.fmcw_if_compensated_fractional_delay_samples);
236 result[
"fmcw_if_group_delay_compensated"] =
file.fmcw_if_group_delay_compensated;
242 nlohmann::json chunks = nlohmann::json::array();
248 nlohmann::json streaming_segments = nlohmann::json::array();
249 for (
const auto&
segment :
file.streaming_segments)
254 nlohmann::json fmcw_sources = nlohmann::json::array();
255 for (
const auto& source :
file.fmcw_sources)
260 nlohmann::json sfcw_sources = nlohmann::json::array();
261 for (
const auto& source :
file.sfcw_sources)
266 nlohmann::json
result = {{
"receiver_id",
file.receiver_id},
267 {
"receiver_name",
file.receiver_name},
270 {
"sampling_rate",
file.sampling_rate},
271 {
"total_samples",
file.total_samples},
272 {
"sample_start",
file.sample_start},
273 {
"sample_end_exclusive",
file.sample_end_exclusive},
274 {
"pulse_count",
file.pulse_count},
275 {
"min_pulse_length_samples",
file.min_pulse_length_samples},
276 {
"max_pulse_length_samples",
file.max_pulse_length_samples},
277 {
"uniform_pulse_length",
file.uniform_pulse_length},
279 {
"streaming_segments", streaming_segments},
280 {
"fmcw_sources", fmcw_sources},
281 {
"sfcw_sources", sfcw_sources},
282 {
"fmcw_dechirp_mode",
file.fmcw_dechirp_mode},
283 {
"fmcw_dechirp_reference_source",
file.fmcw_dechirp_reference_source}};
284 if (
file.fmcw.has_value())
288 if (
file.sfcw.has_value())
300 auto timestampToJson = [](
const std::optional<Vita49Timestamp>& timestamp) -> nlohmann::json
302 if (!timestamp.has_value())
306 return {{
"integer_seconds", timestamp->integer_seconds},
307 {
"fractional_picoseconds", timestamp->fractional_picoseconds}};
310 return {{
"receiver_id", stream.receiver_id},
311 {
"receiver_name", stream.receiver_name},
312 {
"stream_id", stream.stream_id},
313 {
"mode", stream.mode},
314 {
"sample_rate", stream.sample_rate},
315 {
"reference_frequency", stream.reference_frequency},
316 {
"packets_emitted", stream.packets_emitted},
317 {
"samples_emitted", stream.samples_emitted},
318 {
"packets_dropped", stream.packets_dropped},
319 {
"samples_dropped", stream.samples_dropped},
320 {
"over_range_count", stream.over_range_count},
321 {
"late_packet_count", stream.late_packet_count},
322 {
"context_packet_count", stream.context_packet_count},
323 {
"first_sample_time",
324 stream.first_sample_time.has_value() ? nlohmann::json(*stream.first_sample_time)
327 stream.end_sample_time.has_value() ? nlohmann::json(*stream.end_sample_time)
334 nlohmann::json
vita49ToJson(
const Vita49OutputMetadata& vita49)
336 nlohmann::json streams = nlohmann::json::array();
337 for (
const auto& stream : vita49.streams)
342 nlohmann::json
result = {{
"endpoint", vita49.endpoint_host +
":" + std::to_string(vita49.endpoint_port)},
343 {
"endpoint_host", vita49.endpoint_host},
344 {
"endpoint_port", vita49.endpoint_port},
345 {
"epoch_unix_nanoseconds",
nullptr},
346 {
"class_id", vita49.class_id},
347 {
"adc_fullscale", vita49.adc_fullscale},
348 {
"max_udp_payload", vita49.max_udp_payload},
349 {
"queue_depth", vita49.queue_depth},
350 {
"streams", streams}};
351 if (vita49.epoch_unix_nanoseconds.has_value())
353 result[
"epoch_unix_nanoseconds"] = std::to_string(*vita49.epoch_unix_nanoseconds);
361 nlohmann::json files = nlohmann::json::array();
363 for (
const auto&
file : metadata.files)
369 if (rate ==
file.sampling_rate)
381 nlohmann::json
result = {{
"schema_version", metadata.schema_version},
382 {
"simulation_name", metadata.simulation_name},
383 {
"output_directory", metadata.output_directory},
384 {
"start_time", metadata.start_time},
385 {
"end_time", metadata.end_time},
386 {
"oversample_ratio", metadata.oversample_ratio},
390 result[
"sampling_rate"] = metadata.sampling_rate;
398 result[
"sampling_rate"] =
nullptr;
401 if (metadata.vita49.has_value())
409 OutputMetadataCollector::OutputMetadataCollector(std::string output_dir)
411 _metadata.output_directory = std::move(output_dir);
421 std::scoped_lock
const lock(_mutex);
422 _metadata.files.push_back(std::move(file_metadata));
427 std::scoped_lock
const lock(_mutex);
441 .endpoint_port =
config.port,
442 .epoch_unix_nanoseconds =
config.epoch_unix_nanoseconds,
443 .adc_fullscale =
config.adc_fullscale,
444 .max_udp_payload =
config.max_udp_payload,
445 .queue_depth =
config.queue_depth};
Vita49OutputMetadata vita49MetadataFromConfig(const Vita49OutputConfig &config)
Builds the static VITA metadata section from runtime output configuration.
std::string outputFileMetadataToJsonString(const OutputFileMetadata &metadata)
Serializes one output-file metadata entry to JSON.
std::string outputMetadataToJsonString(const OutputMetadata &metadata)
Serializes a full simulation output metadata snapshot to JSON.
RealType endTime() noexcept
Get the end time for the simulation.
RealType rate() noexcept
Get the rendering sample rate.
RealType startTime() noexcept
Get the start time for the simulation.
unsigned oversampleRatio() noexcept
Get the oversampling ratio.
Parameters params
Global simulation parameter state.
Defines the Parameters struct and provides methods for managing simulation parameters.
std::string simulation_name
The name of the simulation, from the XML.