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;
58 nlohmann::json
fmcwToJson(
const FmcwMetadata& fmcw)
60 nlohmann::json
result = {{
"chirp_bandwidth", fmcw.chirp_bandwidth},
61 {
"chirp_duration", fmcw.chirp_duration},
62 {
"waveform_shape", fmcw.waveform_shape},
63 {
"chirp_rate", fmcw.chirp_rate},
64 {
"start_frequency_offset", fmcw.start_frequency_offset}};
65 if (fmcw.waveform_shape ==
"linear")
67 result[
"chirp_period"] = fmcw.chirp_period;
68 result[
"chirp_rate_signed"] = fmcw.chirp_rate_signed;
69 result[
"chirp_direction"] = fmcw.chirp_direction;
70 if (fmcw.chirp_count.has_value())
72 result[
"chirp_count"] = *fmcw.chirp_count;
75 else if (fmcw.waveform_shape ==
"triangle")
77 if (fmcw.triangle_period.has_value())
79 result[
"triangle_period"] = *fmcw.triangle_period;
81 if (fmcw.triangle_count.has_value())
83 result[
"triangle_count"] = *fmcw.triangle_count;
93 if (
segment.first_chirp_start_time.has_value())
95 result[
"first_chirp_start_time"] = *
segment.first_chirp_start_time;
97 if (
segment.emitted_chirp_count.has_value())
101 if (
segment.first_triangle_start_time.has_value())
103 result[
"first_triangle_start_time"] = *
segment.first_triangle_start_time;
105 if (
segment.emitted_triangle_count.has_value())
107 result[
"emitted_triangle_count"] = *
segment.emitted_triangle_count;
115 nlohmann::json segments = nlohmann::json::array();
116 for (
const auto&
segment : source.segments)
121 nlohmann::json
result = {{
"transmitter_id", source.transmitter_id},
122 {
"transmitter_name", source.transmitter_name},
123 {
"waveform_id", source.waveform_id},
124 {
"waveform_name", source.waveform_name},
125 {
"carrier_frequency", source.carrier_frequency},
126 {
"segments", segments}};
131 template <
typename T>
134 if (value.has_value())
142 addOptional(
result,
"fmcw_dechirp_reference_transmitter_id",
file.fmcw_dechirp_reference_transmitter_id);
144 file.fmcw_dechirp_reference_transmitter_name);
146 addOptional(
result,
"fmcw_dechirp_reference_waveform_name",
file.fmcw_dechirp_reference_waveform_name);
147 if (
file.fmcw_dechirp_reference_waveform.has_value())
155 result[
"fmcw_if_decimation_enabled"] =
file.fmcw_if_decimation_enabled;
156 result[
"fmcw_if_legacy_full_rate"] =
file.fmcw_if_legacy_full_rate;
168 file.fmcw_if_compensated_integer_delay_samples);
170 file.fmcw_if_compensated_fractional_delay_samples);
176 result[
"fmcw_if_group_delay_compensated"] =
file.fmcw_if_group_delay_compensated;
182 nlohmann::json chunks = nlohmann::json::array();
188 nlohmann::json streaming_segments = nlohmann::json::array();
189 for (
const auto&
segment :
file.streaming_segments)
194 nlohmann::json fmcw_sources = nlohmann::json::array();
195 for (
const auto& source :
file.fmcw_sources)
200 nlohmann::json
result = {{
"receiver_id",
file.receiver_id},
201 {
"receiver_name",
file.receiver_name},
204 {
"sampling_rate",
file.sampling_rate},
205 {
"total_samples",
file.total_samples},
206 {
"sample_start",
file.sample_start},
207 {
"sample_end_exclusive",
file.sample_end_exclusive},
208 {
"pulse_count",
file.pulse_count},
209 {
"min_pulse_length_samples",
file.min_pulse_length_samples},
210 {
"max_pulse_length_samples",
file.max_pulse_length_samples},
211 {
"uniform_pulse_length",
file.uniform_pulse_length},
213 {
"streaming_segments", streaming_segments},
214 {
"fmcw_sources", fmcw_sources},
215 {
"fmcw_dechirp_mode",
file.fmcw_dechirp_mode},
216 {
"fmcw_dechirp_reference_source",
file.fmcw_dechirp_reference_source}};
217 if (
file.fmcw.has_value())
229 auto timestampToJson = [](
const std::optional<Vita49Timestamp>& timestamp) -> nlohmann::json
231 if (!timestamp.has_value())
235 return {{
"integer_seconds", timestamp->integer_seconds},
236 {
"fractional_picoseconds", timestamp->fractional_picoseconds}};
239 return {{
"receiver_id", stream.receiver_id},
240 {
"receiver_name", stream.receiver_name},
241 {
"stream_id", stream.stream_id},
242 {
"mode", stream.mode},
243 {
"sample_rate", stream.sample_rate},
244 {
"reference_frequency", stream.reference_frequency},
245 {
"packets_emitted", stream.packets_emitted},
246 {
"samples_emitted", stream.samples_emitted},
247 {
"packets_dropped", stream.packets_dropped},
248 {
"samples_dropped", stream.samples_dropped},
249 {
"over_range_count", stream.over_range_count},
250 {
"late_packet_count", stream.late_packet_count},
251 {
"context_packet_count", stream.context_packet_count},
252 {
"first_sample_time",
253 stream.first_sample_time.has_value() ? nlohmann::json(*stream.first_sample_time)
256 stream.end_sample_time.has_value() ? nlohmann::json(*stream.end_sample_time)
263 nlohmann::json
vita49ToJson(
const Vita49OutputMetadata& vita49)
265 nlohmann::json streams = nlohmann::json::array();
266 for (
const auto& stream : vita49.streams)
271 nlohmann::json
result = {{
"endpoint", vita49.endpoint_host +
":" + std::to_string(vita49.endpoint_port)},
272 {
"endpoint_host", vita49.endpoint_host},
273 {
"endpoint_port", vita49.endpoint_port},
274 {
"epoch_unix_nanoseconds",
nullptr},
275 {
"class_id", vita49.class_id},
276 {
"adc_fullscale", vita49.adc_fullscale},
277 {
"max_udp_payload", vita49.max_udp_payload},
278 {
"queue_depth", vita49.queue_depth},
279 {
"streams", streams}};
280 if (vita49.epoch_unix_nanoseconds.has_value())
282 result[
"epoch_unix_nanoseconds"] = std::to_string(*vita49.epoch_unix_nanoseconds);
290 nlohmann::json files = nlohmann::json::array();
292 for (
const auto&
file : metadata.files)
298 if (rate ==
file.sampling_rate)
310 nlohmann::json
result = {{
"schema_version", metadata.schema_version},
311 {
"simulation_name", metadata.simulation_name},
312 {
"output_directory", metadata.output_directory},
313 {
"start_time", metadata.start_time},
314 {
"end_time", metadata.end_time},
315 {
"oversample_ratio", metadata.oversample_ratio},
319 result[
"sampling_rate"] = metadata.sampling_rate;
327 result[
"sampling_rate"] =
nullptr;
330 if (metadata.vita49.has_value())
338 OutputMetadataCollector::OutputMetadataCollector(std::string output_dir)
340 _metadata.output_directory = std::move(output_dir);
350 std::scoped_lock
const lock(_mutex);
351 _metadata.files.push_back(std::move(file_metadata));
356 std::scoped_lock
const lock(_mutex);
370 .endpoint_port =
config.port,
371 .epoch_unix_nanoseconds =
config.epoch_unix_nanoseconds,
372 .adc_fullscale =
config.adc_fullscale,
373 .max_udp_payload =
config.max_udp_payload,
374 .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.