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 ==
"file")
85 if (fmcw.sampled_duration.has_value())
87 result[
"sampled_duration"] = *fmcw.sampled_duration;
89 if (fmcw.sampled_count.has_value())
91 result[
"sampled_count"] = *fmcw.sampled_count;
94 else if (fmcw.waveform_shape ==
"triangle")
96 if (fmcw.triangle_period.has_value())
98 result[
"triangle_period"] = *fmcw.triangle_period;
100 if (fmcw.triangle_count.has_value())
102 result[
"triangle_count"] = *fmcw.triangle_count;
112 if (
segment.first_chirp_start_time.has_value())
114 result[
"first_chirp_start_time"] = *
segment.first_chirp_start_time;
116 if (
segment.emitted_chirp_count.has_value())
118 result[
"emitted_chirp_count"] = *
segment.emitted_chirp_count;
120 if (
segment.first_triangle_start_time.has_value())
122 result[
"first_triangle_start_time"] = *
segment.first_triangle_start_time;
124 if (
segment.emitted_triangle_count.has_value())
126 result[
"emitted_triangle_count"] = *
segment.emitted_triangle_count;
134 nlohmann::json segments = nlohmann::json::array();
135 for (
const auto&
segment : source.segments)
140 nlohmann::json
result = {{
"transmitter_id", source.transmitter_id},
141 {
"transmitter_name", source.transmitter_name},
142 {
"waveform_id", source.waveform_id},
143 {
"waveform_name", source.waveform_name},
144 {
"carrier_frequency", source.carrier_frequency},
145 {
"segments", segments}};
150 nlohmann::json
sfcwToJson(
const SfcwMetadata& sfcw)
152 nlohmann::json
result = {{
"carrier_frequency", sfcw.carrier_frequency},
153 {
"start_frequency_offset", sfcw.start_frequency_offset},
154 {
"step_size", sfcw.step_size},
155 {
"step_count", sfcw.step_count},
156 {
"dwell_time", sfcw.dwell_time},
157 {
"step_period", sfcw.step_period},
158 {
"first_frequency", sfcw.first_frequency},
159 {
"last_frequency", sfcw.last_frequency},
160 {
"frequency_span", sfcw.frequency_span},
161 {
"effective_bandwidth", sfcw.effective_bandwidth},
162 {
"range_resolution", sfcw.range_resolution},
163 {
"unambiguous_range", sfcw.unambiguous_range}};
164 if (sfcw.sweep_count.has_value())
166 result[
"sweep_count"] = *sfcw.sweep_count;
174 if (
segment.first_step_start_time.has_value())
176 result[
"first_step_start_time"] = *
segment.first_step_start_time;
178 if (
segment.emitted_step_count.has_value())
187 nlohmann::json segments = nlohmann::json::array();
188 for (
const auto&
segment : source.segments)
193 nlohmann::json
result = {{
"transmitter_id", source.transmitter_id},
194 {
"transmitter_name", source.transmitter_name},
195 {
"waveform_id", source.waveform_id},
196 {
"waveform_name", source.waveform_name},
197 {
"segments", segments}};
202 template <
typename T>
205 if (value.has_value())
213 addOptional(
result,
"fmcw_dechirp_reference_transmitter_id", file.fmcw_dechirp_reference_transmitter_id);
215 file.fmcw_dechirp_reference_transmitter_name);
216 addOptional(
result,
"fmcw_dechirp_reference_waveform_id", file.fmcw_dechirp_reference_waveform_id);
217 addOptional(
result,
"fmcw_dechirp_reference_waveform_name", file.fmcw_dechirp_reference_waveform_name);
218 if (file.fmcw_dechirp_reference_waveform.has_value())
220 result[
"fmcw_dechirp_reference_waveform"] =
fmcwToJson(*file.fmcw_dechirp_reference_waveform);
226 result[
"fmcw_if_decimation_enabled"] = file.fmcw_if_decimation_enabled;
227 result[
"fmcw_if_legacy_full_rate"] = file.fmcw_if_legacy_full_rate;
228 addOptional(
result,
"fmcw_if_requested_sample_rate", file.fmcw_if_requested_sample_rate);
230 addOptional(
result,
"fmcw_if_input_sample_rate", file.fmcw_if_input_sample_rate);
231 addOptional(
result,
"fmcw_if_resample_numerator", file.fmcw_if_resample_numerator);
232 addOptional(
result,
"fmcw_if_resample_denominator", file.fmcw_if_resample_denominator);
233 addOptional(
result,
"fmcw_if_decimation_factor", file.fmcw_if_decimation_factor);
235 addOptional(
result,
"fmcw_if_filter_transition_width", file.fmcw_if_filter_transition_width);
237 addOptional(
result,
"fmcw_if_filter_group_delay_seconds", file.fmcw_if_filter_group_delay_seconds);
239 file.fmcw_if_compensated_integer_delay_samples);
241 file.fmcw_if_compensated_fractional_delay_samples);
242 addOptional(
result,
"fmcw_if_warmup_discard_samples", file.fmcw_if_warmup_discard_samples);
244 addOptional(
result,
"fmcw_if_timing_error_seconds", file.fmcw_if_timing_error_seconds);
245 addOptional(
result,
"fmcw_if_phase_error_radians", file.fmcw_if_phase_error_radians);
247 result[
"fmcw_if_group_delay_compensated"] = file.fmcw_if_group_delay_compensated;
251 nlohmann::json
fileToJson(
const OutputFileMetadata& file)
253 nlohmann::json chunks = nlohmann::json::array();
254 for (
const auto&
chunk : file.chunks)
259 nlohmann::json streaming_segments = nlohmann::json::array();
260 for (
const auto&
segment : file.streaming_segments)
265 nlohmann::json fmcw_sources = nlohmann::json::array();
266 for (
const auto& source : file.fmcw_sources)
271 nlohmann::json sfcw_sources = nlohmann::json::array();
272 for (
const auto& source : file.sfcw_sources)
277 nlohmann::json
result = {{
"receiver_id", file.receiver_id},
278 {
"receiver_name", file.receiver_name},
281 {
"sampling_rate", file.sampling_rate},
282 {
"total_samples", file.total_samples},
283 {
"sample_start", file.sample_start},
284 {
"sample_end_exclusive", file.sample_end_exclusive},
285 {
"pulse_count", file.pulse_count},
286 {
"min_pulse_length_samples", file.min_pulse_length_samples},
287 {
"max_pulse_length_samples", file.max_pulse_length_samples},
288 {
"uniform_pulse_length", file.uniform_pulse_length},
290 {
"streaming_segments", streaming_segments},
291 {
"fmcw_sources", fmcw_sources},
292 {
"sfcw_sources", sfcw_sources},
293 {
"fmcw_dechirp_mode", file.fmcw_dechirp_mode},
294 {
"fmcw_dechirp_reference_source", file.fmcw_dechirp_reference_source}};
295 if (file.fmcw.has_value())
299 if (file.sfcw.has_value())
311 auto timestampToJson = [](
const std::optional<Vita49Timestamp>& timestamp) -> nlohmann::json
313 if (!timestamp.has_value())
317 return {{
"integer_seconds", timestamp->integer_seconds},
318 {
"fractional_picoseconds", timestamp->fractional_picoseconds}};
321 return {{
"receiver_id", stream.receiver_id},
322 {
"receiver_name", stream.receiver_name},
323 {
"stream_id", stream.stream_id},
324 {
"mode", stream.mode},
325 {
"sample_rate", stream.sample_rate},
326 {
"reference_frequency", stream.reference_frequency},
327 {
"packets_emitted", stream.packets_emitted},
328 {
"samples_emitted", stream.samples_emitted},
329 {
"packets_dropped", stream.packets_dropped},
330 {
"samples_dropped", stream.samples_dropped},
331 {
"over_range_count", stream.over_range_count},
332 {
"late_data_packet_count", stream.late_data_packet_count},
333 {
"late_context_packet_count", stream.late_context_packet_count},
334 {
"context_packet_count", stream.context_packet_count},
335 {
"first_sample_time",
336 stream.first_sample_time.has_value() ? nlohmann::json(*stream.first_sample_time)
339 stream.end_sample_time.has_value() ? nlohmann::json(*stream.end_sample_time)
346 nlohmann::json
vita49ToJson(
const Vita49OutputMetadata& vita49)
348 nlohmann::json streams = nlohmann::json::array();
349 for (
const auto& stream : vita49.streams)
354 nlohmann::json
result = {{
"endpoint", vita49.endpoint_host +
":" + std::to_string(vita49.endpoint_port)},
355 {
"endpoint_host", vita49.endpoint_host},
356 {
"endpoint_port", vita49.endpoint_port},
357 {
"epoch_unix_nanoseconds",
nullptr},
358 {
"class_id", vita49.class_id},
359 {
"adc_fullscale", vita49.adc_fullscale},
360 {
"max_udp_payload", vita49.max_udp_payload},
361 {
"queue_depth", vita49.queue_depth},
362 {
"streams", streams}};
363 if (vita49.epoch_unix_nanoseconds.has_value())
365 result[
"epoch_unix_nanoseconds"] = std::to_string(*vita49.epoch_unix_nanoseconds);
373 nlohmann::json files = nlohmann::json::array();
375 for (
const auto& file : metadata.files)
381 if (rate == file.sampling_rate)
393 nlohmann::json
result = {{
"schema_version", metadata.schema_version},
394 {
"simulation_name", metadata.simulation_name},
395 {
"output_directory", metadata.output_directory},
396 {
"start_time", metadata.start_time},
397 {
"end_time", metadata.end_time},
398 {
"oversample_ratio", metadata.oversample_ratio},
402 result[
"sampling_rate"] = metadata.sampling_rate;
410 result[
"sampling_rate"] =
nullptr;
413 if (metadata.vita49.has_value())
421 OutputMetadataCollector::OutputMetadataCollector(std::string output_dir)
423 _metadata.output_directory = std::move(output_dir);
433 std::scoped_lock
const lock(_mutex);
434 _metadata.files.push_back(std::move(file_metadata));
439 std::scoped_lock
const lock(_mutex);
453 .endpoint_port =
config.port,
454 .epoch_unix_nanoseconds =
config.epoch_unix_nanoseconds,
455 .adc_fullscale =
config.adc_fullscale,
456 .max_udp_payload =
config.max_udp_payload,
457 .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.