FERS 0.1.0
The Flexible Extensible Radar Simulator
Loading...
Searching...
No Matches
simulationProgressStore.test.ts
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-only
2// Copyright (c) 2026-present FERS Contributors (see AUTHORS.md).
3
4import { describe, expect, test } from 'bun:test';
5import {
6 normalizeSimulationOutputMetadata,
7 type RawSimulationOutputMetadata,
8 type SimulationOutputMetadata,
9} from './simulationProgressStore';
10
11const baseMetadata = {
12 schema_version: 1,
13 simulation_name: 'Metadata Fixture',
14 output_directory: '/tmp/fers',
15 start_time: 0,
16 end_time: 1,
17 sampling_rate: 1e6,
18 oversample_ratio: 1,
19} satisfies Omit<SimulationOutputMetadata, 'files'>;
20
21describe('simulation output metadata', () => {
22 test('keeps file-backed FMCW waveform metadata', () => {
23 const metadata: SimulationOutputMetadata = {
24 ...baseMetadata,
25 files: [
26 {
27 receiver_id: 1,
28 receiver_name: 'File FMCW Rx',
29 mode: 'fmcw',
30 path: '/tmp/fers/file-fmcw.h5',
31 sampling_rate: 1e6,
32 total_samples: 4,
33 sample_start: 0,
34 sample_end_exclusive: 4,
35 pulse_count: 0,
36 min_pulse_length_samples: 0,
37 max_pulse_length_samples: 0,
38 uniform_pulse_length: true,
39 chunks: [],
40 streaming_segments: [],
41 fmcw: {
42 waveform_shape: 'file',
43 chirp_bandwidth: 0,
44 chirp_duration: 0,
45 chirp_rate: 0,
46 start_frequency_offset: 0,
47 sampled_duration: 250e-6,
48 sampled_count: 8,
49 },
50 fmcw_sources: [],
51 sfcw_sources: [],
52 },
53 ],
54 };
55
56 expect(
57 normalizeSimulationOutputMetadata(metadata).files[0].fmcw
58 ).toMatchObject({
59 waveform_shape: 'file',
60 sampled_duration: 250e-6,
61 sampled_count: 8,
62 });
63 });
64
65 test('keeps FMCW metadata and streaming segments', () => {
66 const metadata: SimulationOutputMetadata = {
67 ...baseMetadata,
68 files: [
69 {
70 receiver_id: 10,
71 receiver_name: 'FMCW Rx',
72 mode: 'fmcw',
73 path: '/tmp/fers/fmcw.h5',
74 sampling_rate: 1e6,
75 total_samples: 128,
76 sample_start: 0,
77 sample_end_exclusive: 128,
78 pulse_count: 0,
79 min_pulse_length_samples: 0,
80 max_pulse_length_samples: 0,
81 uniform_pulse_length: true,
82 chunks: [],
83 streaming_segments: [
84 {
85 start_time: 0,
86 end_time: 0.001,
87 sample_count: 128,
88 sample_start: 0,
89 sample_end_exclusive: 128,
90 first_chirp_start_time: 0,
91 emitted_chirp_count: 4,
92 },
93 ],
94 fmcw: {
95 chirp_bandwidth: 20e6,
96 chirp_duration: 250e-6,
97 chirp_period: 500e-6,
98 chirp_rate: 80e9,
99 chirp_rate_signed: -80e9,
100 chirp_direction: 'down',
101 start_frequency_offset: 0,
102 chirp_count: 4,
103 },
104 fmcw_sources: [
105 {
106 transmitter_id: 11,
107 transmitter_name: 'FMCW Tx',
108 waveform_id: 12,
109 waveform_name: 'FMCW Wave',
110 carrier_frequency: 10e9,
111 chirp_bandwidth: 20e6,
112 chirp_duration: 250e-6,
113 chirp_period: 500e-6,
114 chirp_rate: 80e9,
115 chirp_rate_signed: -80e9,
116 chirp_direction: 'down',
117 start_frequency_offset: 0,
118 chirp_count: 4,
119 segments: [
120 {
121 start_time: 0,
122 end_time: 0.001,
123 first_chirp_start_time: 0,
124 emitted_chirp_count: 4,
125 },
126 ],
127 },
128 ],
129 sfcw_sources: [],
130 },
131 ],
132 };
133
134 expect(
135 normalizeSimulationOutputMetadata(metadata).files[0]
136 ).toMatchObject({
137 mode: 'fmcw',
138 streaming_segments: [
139 {
140 first_chirp_start_time: 0,
141 emitted_chirp_count: 4,
142 },
143 ],
144 fmcw: {
145 chirp_bandwidth: 20e6,
146 chirp_duration: 250e-6,
147 chirp_period: 500e-6,
148 chirp_direction: 'down',
149 },
150 fmcw_sources: [
151 {
152 transmitter_id: 11,
153 waveform_id: 12,
154 segments: [
155 {
156 first_chirp_start_time: 0,
157 emitted_chirp_count: 4,
158 },
159 ],
160 },
161 ],
162 });
163 });
164
165 test('adapts legacy cw_segments metadata to streaming_segments', () => {
166 const legacyMetadata = {
167 ...baseMetadata,
168 files: [
169 {
170 receiver_id: 20,
171 receiver_name: 'CW Rx',
172 mode: 'cw',
173 path: '/tmp/fers/cw.h5',
174 sampling_rate: 1e6,
175 total_samples: 64,
176 sample_start: 0,
177 sample_end_exclusive: 64,
178 pulse_count: 0,
179 min_pulse_length_samples: 0,
180 max_pulse_length_samples: 0,
181 uniform_pulse_length: true,
182 chunks: [],
183 cw_segments: [
184 {
185 start_time: 0,
186 end_time: 0.001,
187 sample_count: 64,
188 sample_start: 0,
189 sample_end_exclusive: 64,
190 },
191 ],
192 },
193 ],
194 } satisfies RawSimulationOutputMetadata;
195
196 const normalizedFile =
197 normalizeSimulationOutputMetadata(legacyMetadata).files[0];
198
199 expect(normalizedFile.streaming_segments).toHaveLength(1);
200 expect(normalizedFile.fmcw_sources).toEqual([]);
201 expect('cw_segments' in normalizedFile).toBe(false);
202 });
203
204 test('normalizes mixed sampling-rate metadata', () => {
205 const metadata = {
206 ...baseMetadata,
207 sampling_rate: null,
208 sampling_rates: [1e6, 2e6],
209 files: [
210 {
211 receiver_id: 30,
212 receiver_name: 'Rx 1',
213 mode: 'cw',
214 path: '/tmp/fers/rx1.h5',
215 sampling_rate: 1e6,
216 total_samples: 64,
217 sample_start: 0,
218 sample_end_exclusive: 64,
219 pulse_count: 0,
220 min_pulse_length_samples: 0,
221 max_pulse_length_samples: 0,
222 uniform_pulse_length: true,
223 chunks: [],
224 streaming_segments: [],
225 },
226 {
227 receiver_id: 31,
228 receiver_name: 'Rx 2',
229 mode: 'fmcw',
230 path: '/tmp/fers/rx2.h5',
231 sampling_rate: 2e6,
232 total_samples: 128,
233 sample_start: 0,
234 sample_end_exclusive: 128,
235 pulse_count: 0,
236 min_pulse_length_samples: 0,
237 max_pulse_length_samples: 0,
238 uniform_pulse_length: true,
239 chunks: [],
240 streaming_segments: [],
241 fmcw_dechirp_mode: 'none',
242 fmcw_dechirp_reference_source: 'none',
243 },
244 ],
245 } satisfies RawSimulationOutputMetadata;
246
247 const normalized = normalizeSimulationOutputMetadata(metadata);
248
249 expect(normalized.sampling_rate).toBeNull();
250 expect(normalized.sampling_rates).toEqual([1e6, 2e6]);
251 expect(normalized.files.map((file) => file.sampling_rate)).toEqual([
252 1e6, 2e6,
253 ]);
254 });
255
256 test('preserves VITA49 metadata section', () => {
257 const metadata = {
258 ...baseMetadata,
259 files: [],
260 vita49: {
261 endpoint: '127.0.0.1:4991',
262 endpoint_host: '127.0.0.1',
263 endpoint_port: 4991,
264 epoch_unix_nanoseconds: '1700000000123456789',
265 class_id: '0xFA52530001000101',
266 adc_fullscale: 1,
267 max_udp_payload: 1400,
268 queue_depth: 1024,
269 streams: [
270 {
271 receiver_id: 10,
272 receiver_name: 'Rx',
273 stream_id: 1234,
274 sample_rate: 1e6,
275 reference_frequency: 9.6e9,
276 packets_emitted: 4,
277 samples_emitted: 512,
278 packets_dropped: 0,
279 samples_dropped: 0,
280 over_range_count: 0,
281 late_data_packet_count: 0,
282 late_context_packet_count: 0,
283 context_packet_count: 2,
284 first_sample_time: 0,
285 end_sample_time: 10,
286 first_timestamp: {
287 integer_seconds: 1700000000,
288 fractional_picoseconds: 123456789000,
289 },
290 end_timestamp: {
291 integer_seconds: 1700000010,
292 fractional_picoseconds: 123456789000,
293 },
294 },
295 ],
296 },
297 } satisfies RawSimulationOutputMetadata;
298
299 expect(normalizeSimulationOutputMetadata(metadata).vita49).toEqual(
300 metadata.vita49
301 );
302 });
303});