STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
flight_e2e.c
Go to the documentation of this file.
1/*
2 * flight_e2e: the flight computer's data pipeline end to end, on a laptop.
3 *
4 * flight profile -> fake chips -> REAL chip drivers (chips/) -> REAL
5 * sensors_core (app/) -> REAL emmc_log (Core/) -> fake eMMC card ->
6 * independent log reader -> compared against the profile
7 * simulated MAX-M10S -> REAL ubx + gnss_frame + gnss_core -> same log
8 *
9 * Time is virtual. The harness plays the RTOS: it wakes the sensors core on
10 * data-ready edges (+37 us latency) or on the core's own deadlines, and runs
11 * the eMMC writer every 10 ms, exactly as sensors_task and the emmc writer
12 * task do on the board. Only the chips, the card and the clock are fake.
13 *
14 * Output, one line per check (parsed by ./flight_test):
15 * CHECK <id> PASS|FAIL <description> | <detail>
16 * Usage: flight_e2e [--list] [--only=id,id]
17 * Exit 0 only if every selected check passed.
18 */
19#include <stdarg.h>
20#include <stdbool.h>
21#include <stdint.h>
22#include <stdio.h>
23#include <stdlib.h>
24#include <string.h>
25
26#include "emmc_log.h"
27#include "fake_baro.h"
28#include "fake_gnss.h"
29#include "fake_regs.h"
30#include "gnss_core.h"
31#include "sensors_core.h"
32
33/* ------------------------------------------------------------------ */
34/* Flight profile: the truth every check compares against. */
35
36#define FLIGHT_US 20000000U
37#define T_LAUNCH_US 2000000U /* ARMED -> LAUNCHED */
38#define T_BURNOUT_US 4000000U /* boost ends */
39#define T_RECOVERY_US 10000000U
40
41enum { PAD, BOOST, COAST, DESCENT };
42
43static int phase_at(uint32_t t) {
44 if (t < T_LAUNCH_US)
45 return PAD;
46 if (t < T_BURNOUT_US)
47 return BOOST;
48 if (t < T_RECOVERY_US)
49 return COAST;
50 return DESCENT;
51}
52
53/* IMU raw: accel z by phase (1 g = 2049 LSB at 0.488 mg), the rest varies
54 * with time so a reordered or stale sample cannot pass. */
55static void imu_truth(uint32_t t, int16_t out[7]) {
56 static const int16_t accel_z[] = {2049, 16393, 0, 2049}; /* 1, 8, 0, 1 g */
57 uint32_t ms = t / 1000U;
58 out[0] = (int16_t)(ms % 256U); /* temp */
59 out[1] = (int16_t)(ms % 1000U); /* gyro x */
60 out[2] = (int16_t)-(int16_t)(ms % 700U);
61 out[3] = (int16_t)(t % 30011U);
62 out[4] = (int16_t)(ms % 97U); /* accel x */
63 out[5] = (int16_t)-(int16_t)(ms % 89U);
64 out[6] = accel_z[phase_at(t)];
65}
66
67static void highg_truth(uint32_t t, int8_t out[3]) {
68 static const int8_t z[] = {1, 10, 0, 1}; /* 780 mg/digit */
69 out[0] = (int8_t)((t / 2500U) % 50U);
70 out[1] = (int8_t)-(int8_t)((t / 2500U) % 40U);
71 out[2] = z[phase_at(t)];
72}
73
74/* Barometer: datasheet-verified (D1, D2) -> (TEMP, P) vectors per phase, so
75 * the expected values do not come from the code under test. */
76static const struct {
77 uint32_t d1, d2;
78 int32_t temp, p;
79} kBaro[] = {
80 {9085466, 8569150, 2007, 100009}, /* pad: datasheet p.8 example */
81 {8900000, 8330000, 1175, 94911}, /* boost: 2nd-order branch */
82 {8500000, 7300000, -3022, 79839}, /* coast: very-low branch */
83 {9085466, 8569150, 2007, 100009}, /* descent */
84};
85
86/* GPS truth: climbs during boost/coast, comes down after. */
87static void gps_truth(uint32_t t, ubx_pvt_t* v) {
88 v->itow_ms = 300000000U + t / 1000U;
89 v->fix_type = 3;
90 v->flags = 1;
91 v->num_sv = 10;
92 v->lat_e7 = 378719000 + (int32_t)(t / 100000U);
93 v->lon_e7 = -1222585000;
94 v->hmsl_mm = 100000 + (int32_t)(t / 50U);
95 v->vel_d_mms = phase_at(t) == DESCENT ? 8000 : -20000;
96 v->hacc_mm = 2000;
97 v->vacc_mm = 3000;
98 v->pdop_e2 = 140;
99}
100
101/* ------------------------------------------------------------------ */
102/* Fake eMMC card (emmc_port_t) with fault injection. */
103
104#define BS EMMC_BLOCK_SIZE
105#define DATA_START EMMC_LOG_DEFAULT_DATA_START
106#define CAPACITY (DATA_START + 8192U)
107
108typedef struct {
109 uint8_t* blocks;
110 uint32_t fail_from_ms, fail_until_ms; /* writes time out in this window */
111 uint32_t now_ms;
113} card_t;
114
115static emmc_status_t c_start(void* ctx, uint32_t* count) {
116 (void)ctx;
117 *count = CAPACITY;
118 return EMMC_OK;
119}
120static emmc_status_t c_read(void* ctx, uint32_t lba, void* buf, uint32_t n,
121 uint32_t budget) {
122 card_t* c = ctx;
123 (void)budget;
124 if (lba + n > CAPACITY)
125 return EMMC_ERR_PARAM;
126 memcpy(buf, c->blocks + (size_t)lba * BS, (size_t)n * BS);
127 return EMMC_OK;
128}
129static emmc_status_t c_write(void* ctx, uint32_t lba, const void* buf,
130 uint32_t n, uint32_t budget) {
131 card_t* c = ctx;
132 (void)budget;
133 if (lba + n > CAPACITY)
134 return EMMC_ERR_PARAM;
135 if (c->now_ms >= c->fail_from_ms && c->now_ms < c->fail_until_ms) {
136 c->failed_writes++;
137 return EMMC_ERR_TIMEOUT;
138 }
139 c->writes++;
140 memcpy(c->blocks + (size_t)lba * BS, buf, (size_t)n * BS);
141 return EMMC_OK;
142}
143
144/* ------------------------------------------------------------------ */
145/* Independent log reader (CRC-32 implemented here, not emmc_log_crc32) */
146
147typedef struct {
148 uint8_t type, len;
149 uint32_t t_ms;
150 uint8_t data[64];
151} rec_t;
152
153typedef struct {
156 unsigned n;
158} image_t;
159
160static uint32_t crc32_ref(const uint8_t* p, size_t n) {
161 uint32_t c = 0xFFFFFFFFU;
162 for (size_t i = 0; i < n; ++i) {
163 c ^= p[i];
164 for (int k = 0; k < 8; ++k)
165 c = (c >> 1) ^ (0xEDB88320U & -(c & 1U));
166 }
167 return ~c;
168}
169static uint32_t rd32(const uint8_t* p) {
170 return (uint32_t)p[0] | (uint32_t)p[1] << 8 | (uint32_t)p[2] << 16 |
171 (uint32_t)p[3] << 24;
172}
173
174static void read_image(const card_t* card, image_t* img, unsigned max) {
175 memset(img, 0, sizeof(*img));
176 img->recs = calloc(max, sizeof(rec_t));
177 const uint8_t* s = card->blocks;
178 img->super_ok =
179 rd32(s) == 0x31425353U && rd32(s + 508) == crc32_ref(s, 508);
180 if (!img->super_ok)
181 return;
182 uint32_t epoch = rd32(s + 8), last_session = 0;
183 for (uint32_t k = 0; DATA_START + k < CAPACITY; ++k) {
184 const uint8_t* b = card->blocks + (size_t)(DATA_START + k) * BS;
185 if (rd32(b) != 0x31474C53U || rd32(b + 4) != epoch || rd32(b + 8) != k)
186 break; /* end of the run */
187 if (rd32(b + 508) != crc32_ref(b, 508)) {
188 img->bad_blocks++;
189 break;
190 }
191 img->blocks++;
192 if (rd32(b + 12) != last_session) {
193 last_session = rd32(b + 12);
194 img->sessions++;
195 }
196 unsigned used = (unsigned)(b[18] | b[19] << 8);
197 if (b[16] != 1)
198 continue; /* pad block */
199 for (unsigned o = 0; o + 6 <= used && img->n < max;) {
200 const uint8_t* r = b + 20 + o;
201 rec_t* x = &img->recs[img->n++];
202 x->type = r[0];
203 x->len = r[1];
204 x->t_ms = rd32(r + 2);
205 memcpy(x->data, r + 6, x->len < 64 ? x->len : 64);
206 o += 6U + r[1];
207 }
208 }
209}
210
211/* ------------------------------------------------------------------ */
212/* The simulated board. */
213
214#define MAX_RECS 40000U
215#define WAKE_LATENCY_US 37U
216
217typedef struct {
218 uint32_t imu_fail_from, imu_fail_until; /* IMU bus dead in this window */
220} faults_t;
221
222typedef struct {
229 unsigned n_gnss;
230 uint32_t chunk_start_ms; /* first record of the chunk now filling */
231 uint32_t sealed_before_stall_ms; /* chunk_start_ms when the card stalled */
236 uint8_t scratch[BS];
239 rec_t* produced; /* everything the core emitted while recording */
240 unsigned n_produced, n_type[3];
241 uint32_t first_t[3], last_t[3], backwards[3];
242 unsigned appended, refused;
244} world_t;
245
246static void sink(void* ctx, uint8_t type, const void* data, uint8_t len) {
247 world_t* w = ctx;
248 int i = type - REC_IMU;
249 if (type == REC_GNSS)
250 w->n_gnss++;
251 if (i >= 0 && i < 3) {
252 uint32_t t;
253 memcpy(&t, data, 4);
254 if (w->n_type[i] == 0)
255 w->first_t[i] = t;
256 else if ((int32_t)(t - w->last_t[i]) <= 0)
257 w->backwards[i]++;
258 w->last_t[i] = t;
259 w->n_type[i]++;
260 }
261 uint32_t now_ms = w->now / 1000U;
262 if (!w->stall_seen && w->faults.card_fail_until_ms &&
263 now_ms >= w->faults.card_fail_from_ms) {
264 w->stall_seen = true;
266 }
267 bool had = w->log.has_fill;
268 uint8_t c = w->log.fill_chunk, blk = w->log.fill_block;
269 uint16_t off = w->log.fill_offset;
270 bool ok = emmc_log_append(&w->log, type, now_ms, data, len);
271 if (w->log.has_fill &&
272 (!had || w->log.fill_chunk != c || w->log.fill_block < blk ||
273 (w->log.fill_block == blk && w->log.fill_offset < off)))
274 w->chunk_start_ms = now_ms; /* this record opened a new chunk */
275 if (ok) {
276 w->appended++;
277 } else {
278 w->refused++;
279 }
280 if (w->n_produced < MAX_RECS) {
281 rec_t* r = &w->produced[w->n_produced++];
282 r->type = type;
283 r->len = len;
284 r->t_ms = w->now / 1000U;
285 memcpy(r->data, data, len);
286 }
287}
288
289static void set_chips(world_t* w, uint32_t t) {
290 int16_t v[7];
291 imu_truth(t, v);
292 for (int i = 0; i < 7; ++i) {
293 w->imu.regs[0x20 + 2 * i] = (uint8_t)v[i];
294 w->imu.regs[0x21 + 2 * i] = (uint8_t)((uint16_t)v[i] >> 8);
295 }
296 int8_t g[3];
297 highg_truth(t, g);
298 w->highg.regs[0x29] = (uint8_t)g[0];
299 w->highg.regs[0x2B] = (uint8_t)g[1];
300 w->highg.regs[0x2D] = (uint8_t)g[2];
301 w->baro.d1 = kBaro[phase_at(t)].d1;
302 w->baro.d2 = kBaro[phase_at(t)].d2;
303 bool imu_dead =
304 t >= w->faults.imu_fail_from && t < w->faults.imu_fail_until;
305 w->imu.fail_after = imu_dead ? (int)w->imu.transfers : -1;
306 /* SW_RESET (CTRL3_C bit 0) clears itself on the next read, every time. */
307 w->imu.self_clear_reg = 0x12;
308 w->imu.self_clear_bits = 0x01;
309 w->imu.clear_after_reads = 1;
310}
311
313 emmc_log_on_state(&w->log, w->state, to, w->now / 1000U);
314 w->state = to;
315}
316
317static void world_init(world_t* w, const faults_t* faults) {
318 memset(w, 0, sizeof(*w));
319 if (faults)
320 w->faults = *faults;
321 w->produced = calloc(MAX_RECS, sizeof(rec_t));
322 w->card.blocks = calloc(CAPACITY, BS);
325 fake_regs_init(&w->imu, false);
326 w->imu.regs[0x0F] = 0x6B;
327 fake_regs_init(&w->highg, true);
328 w->highg.regs[0x0F] = 0x32;
329 memcpy(w->baro.prom, fake_baro_datasheet_prom, sizeof(w->baro.prom));
330
331 static const emmc_port_t port = {c_start, c_read, c_write, NULL, NULL};
333 emmc_log_init(&w->log, &port, &w->card, &cfg, w->chunks, 4, w->scratch);
334
335 w->core.imu_bus = fake_regs_bus(&w->imu);
338 w->core.sink.record = sink;
339 w->core.sink.ctx = w;
340 set_chips(w, 0);
341 sensors_core_init(&w->core, 0);
342 fake_gnss_init(&w->gps, 9600, gps_truth); /* cold module (p.13) */
343 w->gnss.io = fake_gnss_io(&w->gps);
344 w->gnss.sink.record = sink;
345 w->gnss.sink.ctx = w;
346 gnss_core_init(&w->gnss, 0);
347 w->next_gps_us = 1000U;
348 w->next_pps_us = 1000000U;
349 w->state = FLIGHT_IDLE;
350 change_state(w, FLIGHT_ARMED); /* recording starts at ARMED */
353 w->next_service = 10000U;
354}
355
356static void service_card(world_t* w) {
357 w->card.now_ms = w->now / 1000U;
358 for (int i = 0; i < 4; ++i) { /* the writer loops while there is work */
359 emmc_log_service(&w->log, w->now / 1000U);
360 if (emmc_log_backlog(&w->log) == 0)
361 break;
362 }
363}
364
365static int32_t diff(uint32_t a, uint32_t b) {
366 return (int32_t)(a - b);
367}
368
369static void fly(world_t* w) {
370 while (diff(FLIGHT_US, w->now) > 0) {
371 uint32_t wake = w->now + sensors_core_wait_us(&w->core, w->now);
372 if (wake == w->now)
373 wake++;
374 if (diff(w->next_imu, wake) < 0)
375 wake = w->next_imu;
376 if (diff(w->next_highg, wake) < 0)
377 wake = w->next_highg;
378 if (diff(w->next_service, wake) < 0)
379 wake = w->next_service;
380 if (diff(w->next_gps_us, wake) < 0)
381 wake = w->next_gps_us;
382 uint32_t ev = 0;
383 if (diff(w->next_imu, wake) <= 0) {
384 ev |= SENSORS_EV_IMU;
385 w->edge_imu = w->next_imu;
387 }
388 if (diff(w->next_highg, wake) <= 0) {
389 ev |= SENSORS_EV_HIGHG;
390 w->edge_highg = w->next_highg;
392 }
393 uint32_t now = ev ? wake + WAKE_LATENCY_US : wake;
394 if (diff(now, w->now) > 0)
395 w->now = now;
396 /* Chips latch a sample at its data-ready edge. */
397 set_chips(w, ev & SENSORS_EV_IMU ? w->edge_imu : w->now);
398 if (phase_at(w->now) == BOOST && w->state == FLIGHT_ARMED)
400 if (phase_at(w->now) == DESCENT && w->state == FLIGHT_LAUNCHED)
402 sensors_core_step(&w->core, ev, w->edge_imu, w->edge_highg, w->now);
403 if (diff(w->now, w->next_gps_us) >= 0) { /* gnss_task, 1 ms */
404 uint8_t buf[512];
405 if (diff(w->now, w->next_pps_us) >= 0) {
407 w->next_pps_us += 1000000U;
408 }
409 size_t n = fake_gnss_emit(&w->gps, w->now, buf, sizeof(buf));
410 gnss_core_feed(&w->gnss, buf, n, w->now);
411 gnss_core_poll(&w->gnss, w->now);
412 w->next_gps_us += 1000U;
413 }
414 if (diff(w->now, w->next_service) >= 0) {
415 service_card(w);
416 w->next_service += 10000U;
417 }
418 }
419 change_state(w, FLIGHT_IDLE); /* session end + flush */
420 for (int i = 0; i < 64 && emmc_log_backlog(&w->log); ++i) {
421 w->now += 10000U;
422 service_card(w);
423 }
424}
425
426static void world_free(world_t* w) {
427 free(w->produced);
428 free(w->card.blocks);
429}
430
431/* ------------------------------------------------------------------ */
432/* Checks */
433
434static char detail[512];
435static bool ok;
436#define EXPECT(cond, ...) \
437 do { \
438 if (!(cond)) { \
439 snprintf(detail, sizeof(detail), __VA_ARGS__); \
440 ok = false; \
441 return; \
442 } \
443 } while (0)
444static void note(const char* fmt, ...) {
445 va_list ap;
446 va_start(ap, fmt);
447 vsnprintf(detail, sizeof(detail), fmt, ap);
448 va_end(ap);
449}
450
451static world_t W; /* the nominal flight, shared by the read-only checks */
453static bool flown;
454
455static void nominal(void) {
456 if (flown)
457 return;
458 world_init(&W, NULL);
459 fly(&W);
460#ifdef FLIGHT_TEST_CANARY
461 /* Test of the tests: corrupt one byte of one logged block. The
462 * conservation and round-trip checks must now fail. */
463 W.card.blocks[(size_t)(DATA_START + 3) * BS + 100] ^= 0x01;
464#endif
465 read_image(&W.card, &IMG, MAX_RECS);
466 flown = true;
467}
468
469static void check_sensor_rates(void) {
470 nominal();
471 double s = FLIGHT_US / 1e6;
472 double imu = W.n_type[0] / s, hg = W.n_type[1] / s, baro = W.n_type[2] / s;
473 EXPECT(imu > 833.3 * 0.99 && imu < 833.3 * 1.01, "IMU %.1f Hz", imu);
474 EXPECT(hg > 400 * 0.99 && hg < 400 * 1.01, "high-g %.1f Hz", hg);
475 EXPECT(baro > 49 && baro < 51, "baro %.1f Hz", baro);
476 note("IMU %.1f Hz, high-g %.1f Hz, baro %.1f Hz over %.0f s", imu, hg, baro,
477 s);
478}
479
480static void check_sample_values(void) {
481 nominal();
482 unsigned n = 0;
483 for (unsigned i = 0; i < W.n_produced; ++i) {
484 const rec_t* r = &W.produced[i];
485 uint32_t t;
486 memcpy(&t, r->data, 4);
487 if (r->type == REC_IMU) {
488 rec_imu_t s;
489 memcpy(&s, r->data, sizeof(s));
490 int16_t v[7];
491 imu_truth(t, v);
492 int16_t got[7] = {s.temp, s.gyro[0], s.gyro[1], s.gyro[2],
493 s.accel[0], s.accel[1], s.accel[2]};
494 EXPECT(memcmp(v, got, sizeof(v)) == 0,
495 "IMU sample at t=%u us differs from the profile", t);
496 } else if (r->type == REC_HIGHG) {
497 rec_highg_t s;
498 memcpy(&s, r->data, sizeof(s));
499 int8_t g[3];
500 highg_truth(t, g);
501 EXPECT(memcmp(g, s.accel, 3) == 0,
502 "high-g sample at t=%u us differs from the profile", t);
503 } else if (r->type == REC_BARO) {
504 rec_baro_t s;
505 memcpy(&s, r->data, sizeof(s));
506 int p = phase_at(t);
507 /* A D1/D2 pair that straddles a phase change (D1 before it, D2
508 * after) mixes two vectors; it must still be a sane pressure. */
509 bool exact =
510 s.temp_c100 == kBaro[p].temp && s.pressure_pa == kBaro[p].p;
511 bool boundary = phase_at(t + 2U * SENSORS_BARO_STEP_US) != p &&
512 s.pressure_pa >= 70000 && s.pressure_pa <= 110000;
513 EXPECT(exact || boundary,
514 "baro at t=%u us: %d.%02d C %d Pa, expected %d Pa", t,
515 s.temp_c100 / 100, abs(s.temp_c100 % 100), s.pressure_pa,
516 kBaro[p].p);
517 } else {
518 continue;
519 }
520 n++;
521 }
522 note("%u samples match the flight profile exactly", n);
523}
524
525static void check_timestamps(void) {
526 nominal();
527 for (int i = 0; i < 3; ++i)
528 EXPECT(W.backwards[i] == 0, "type 0x%02X went backwards %u times",
529 REC_IMU + i, W.backwards[i]);
530 /* IMU and high-g carry the data-ready edge time: on the period grid. */
531 for (unsigned i = 0; i < W.n_produced; ++i) {
532 const rec_t* r = &W.produced[i];
533 uint32_t t;
534 memcpy(&t, r->data, 4);
535 if (r->type == REC_IMU)
537 "IMU stamped %u us, not its edge", t);
538 if (r->type == REC_HIGHG)
540 "high-g stamped %u us, not its edge", t);
541 }
542 note("monotonic per sensor; IMU/high-g stamped at their data-ready edges");
543}
544
545static void check_conservation(void) {
546 nominal();
547 EXPECT(W.refused == 0, "%u records refused by the log", W.refused);
548 EXPECT(W.log.stats.dropped == 0, "log dropped %u", W.log.stats.dropped);
549 unsigned j = 0, matched = 0;
550 for (unsigned i = 0; i < IMG.n; ++i) {
551 const rec_t* got = &IMG.recs[i];
552 if (got->type < EMMC_REC_USER_FIRST)
553 continue; /* log's own */
554 EXPECT(j < W.n_produced, "card has more records than were produced");
555 const rec_t* want = &W.produced[j++];
556 EXPECT(got->type == want->type && got->len == want->len &&
557 got->t_ms == want->t_ms &&
558 memcmp(got->data, want->data, want->len) == 0,
559 "record %u on the card differs from what was produced", j);
560 matched++;
561 }
562 EXPECT(matched == W.n_produced, "%u produced, %u on the card", W.n_produced,
563 matched);
564 note("all %u records on the card, byte for byte, in order", matched);
565}
566
567static void check_emmc_roundtrip(void) {
568 nominal();
569 EXPECT(IMG.super_ok, "superblock missing or CRC bad");
570 EXPECT(IMG.bad_blocks == 0, "a log block failed its CRC");
571 EXPECT(IMG.sessions == 1, "%u sessions, expected 1", IMG.sessions);
572 unsigned starts = 0, ends = 0, states = 0;
573 for (unsigned i = 0; i < IMG.n; ++i) {
574 starts += IMG.recs[i].type == EMMC_REC_SESSION_START;
575 ends += IMG.recs[i].type == EMMC_REC_SESSION_END;
576 states += IMG.recs[i].type == EMMC_REC_STATE;
577 }
578 EXPECT(starts == 1 && ends == 1, "session start %u / end %u", starts, ends);
579 EXPECT(states == 2, "%u STATE records, expected 2 (launch, recovery)",
580 states);
581 EXPECT(W.log.disk == EMMC_DISK_MOUNTED, "disk state %d", W.log.disk);
582 note("%u blocks, superblock + every block CRC-valid, 1 session",
583 IMG.blocks);
584}
585
586static void check_emmc_stall(void) {
587 static world_t w;
588 faults_t f = {0, 0, 3000, 3500}; /* card times out for 0.5 s in boost */
589 world_init(&w, &f);
590 fly(&w);
591 EXPECT(w.card.failed_writes > 0, "the stall never hit a write");
592 EXPECT(w.log.stats.write_errors > 0, "log did not report the failures");
593 /* The sensors never waited on the card. */
594 EXPECT(w.n_type[0] > 833 * 20 * 99 / 100, "IMU fell to %u samples",
595 w.n_type[0]);
596 static image_t img;
598 EXPECT(img.super_ok && img.bad_blocks == 0, "card image damaged");
599 /* Every record in a chunk sealed before the stall is on the card. The
600 * chunk still open when the card stalled is not covered: the log latches
601 * FAULT and drops it (the eMMC log's owner is changing that to retry);
602 * its loss is reported in the detail. */
603 uint32_t cut = w.sealed_before_stall_ms;
604 EXPECT(w.stall_seen && cut >= 2000U,
605 "no sealed history before the stall (cut %u ms)", cut);
606 unsigned before = 0, on_card = 0, open_lost = 0;
607 for (unsigned i = 0; i < w.n_produced; ++i)
608 before += w.produced[i].t_ms < cut;
609 for (unsigned i = 0; i < img.n; ++i)
610 on_card +=
611 img.recs[i].type >= EMMC_REC_USER_FIRST && img.recs[i].t_ms < cut;
612 EXPECT(on_card == before, "%u of %u sealed pre-stall records on the card",
613 on_card, before);
614 for (unsigned i = 0; i < w.n_produced; ++i) {
615 uint32_t t = w.produced[i].t_ms;
616 if (t < cut || t >= f.card_fail_from_ms)
617 continue;
618 bool found = false;
619 for (unsigned j = 0; j < img.n && !found; ++j)
620 found =
621 img.recs[j].t_ms == t && img.recs[j].type == w.produced[i].type;
622 open_lost += !found;
623 }
624 note(
625 "%u writes timed out; sensors kept full rate; sealed data (< %u ms) "
626 "intact; open chunk lost %u records; disk %s, %u dropped, %u chunks "
627 "lost",
628 w.card.failed_writes, cut, open_lost,
629 w.log.disk == EMMC_DISK_FAULT ? "FAULT" : "mounted",
631 free(img.recs);
632 world_free(&w);
633}
634
635static void check_fault_imu_silent(void) {
636 static world_t w;
637 faults_t f = {5000000, 5500000, 0, 0}; /* IMU bus dead for 0.5 s */
638 world_init(&w, &f);
639 fly(&w);
640 EXPECT(w.core.imu.ok, "IMU never recovered");
641 EXPECT(w.core.imu.inits == 2, "IMU init attempts %u, expected 2",
642 w.core.imu.inits);
643 /* Longest IMU gap: the outage plus at most one retry interval. */
644 uint32_t prev = 0, gap = 0;
645 for (unsigned i = 0; i < w.n_produced; ++i) {
646 if (w.produced[i].type != REC_IMU)
647 continue;
648 uint32_t t;
649 memcpy(&t, w.produced[i].data, 4);
650 if (prev && t - prev > gap)
651 gap = t - prev;
652 prev = t;
653 }
654 EXPECT(gap <= 500000U + SENSORS_RETRY_US + 2 * SENSORS_IMU_PERIOD_US,
655 "IMU gap %u us", gap);
656 EXPECT(w.n_type[1] == 400 * 20, "high-g disturbed: %u samples",
657 w.n_type[1]);
658 EXPECT(w.n_type[2] >= 49 * 20, "baro disturbed: %u", w.n_type[2]);
659 note("IMU gap %.2f s, recovered after %u inits; high-g and baro unaffected",
660 gap / 1e6, w.core.imu.inits);
661 world_free(&w);
662}
663
664static void check_gps(void) {
665 static world_t w;
666 world_init(&w, NULL);
667 fly(&w);
668 EXPECT(w.gnss.state == GNSS_RUN, "GNSS never configured (state %d)",
669 w.gnss.state);
670 EXPECT(w.gnss.detected_baud == 9600 && w.gps.module_baud == 115200,
671 "baud: found %u, module at %u", w.gnss.detected_baud,
672 w.gps.module_baud);
673 EXPECT(w.gps.dynmodel == UBX_DYNMODEL_AIR4 && w.gps.rate_ms == 100 &&
674 !w.gps.nmea_on,
675 "module config: model %u rate %u ms NMEA %d", w.gps.dynmodel,
676 w.gps.rate_ms, w.gps.nmea_on);
677 EXPECT(w.n_gnss >= 180 && w.n_gnss <= 200, "%u fixes in 20 s", w.n_gnss);
678 unsigned checked = 0;
679 for (unsigned i = 0; i < w.n_produced; ++i) {
680 if (w.produced[i].type != REC_GNSS)
681 continue;
683 memcpy(&g, w.produced[i].data, sizeof(g));
684 ubx_pvt_t v; /* the module built this fix at the ms it was sent */
685 memset(&v, 0, sizeof(v));
686 gps_truth(g.t_us, &v);
687 EXPECT(g.lat_e7 == v.lat_e7 && g.lon_e7 == v.lon_e7 &&
688 g.hmsl_mm == v.hmsl_mm && g.vel_d_mms == v.vel_d_mms &&
689 g.itow_ms == v.itow_ms && g.pdop_e2 == v.pdop_e2,
690 "fix at t=%u us differs from the GPS truth", g.t_us);
691 EXPECT(g.fix_type == 3 && g.num_sv == 10, "fix type/SVs wrong");
692 EXPECT(g.t_us < 1000000U ||
693 (g.pps_us > 0 && g.t_us - g.pps_us < 1000000U),
694 "fix at %u us has a stale PPS %u", g.t_us, g.pps_us);
695 checked++;
696 }
697 note(
698 "cold 9600 -> 115200 -> 10 Hz NAV-PVT; %u fixes = truth, "
699 "PPS-stamped",
700 checked);
701 world_free(&w);
702}
703
704typedef struct {
705 const char* id;
706 const char* what;
707 void (*run)(void);
708} check_t;
709
710static const check_t checks[] = {
711 {"sensor_rates",
712 "IMU 833 Hz, high-g 400 Hz, baro ~50 Hz over a 20 s flight",
714 {"sample_values", "every sample equals the flight profile (raw, exact)",
716 {"timestamps", "monotonic; IMU/high-g stamped at the data-ready edge",
718 {"conservation", "every produced record is on the eMMC card, byte for byte",
720 {"emmc_roundtrip",
721 "card image: superblock, block CRCs, one session, states",
723 {"emmc_stall",
724 "card times out 0.5 s: sensors unaffected, sealed data intact",
726 {"gps", "GNSS cold start 9600 -> 115200, 10 Hz fixes = truth, PPS-stamped",
727 check_gps},
728 {"fault_imu_silent",
729 "IMU dead 0.5 s: dropped, retried, recovered; others ok",
731};
732
733int main(int argc, char** argv) {
734 const char* only = NULL;
735 for (int i = 1; i < argc; ++i) {
736 if (strcmp(argv[i], "--list") == 0) {
737 for (size_t c = 0; c < sizeof(checks) / sizeof(checks[0]); ++c)
738 printf("%s\t%s\n", checks[c].id, checks[c].what);
739 return 0;
740 }
741 if (strncmp(argv[i], "--only=", 7) == 0)
742 only = argv[i] + 7;
743 }
744 int failed = 0;
745 for (size_t c = 0; c < sizeof(checks) / sizeof(checks[0]); ++c) {
746 if (only) {
747 const char* hit = strstr(only, checks[c].id);
748 size_t len = strlen(checks[c].id);
749 if (!hit || (hit[len] != '\0' && hit[len] != ','))
750 continue;
751 }
752 ok = true;
753 detail[0] = '\0';
754 checks[c].run();
755 printf("CHECK %s %s %s | %s\n", checks[c].id, ok ? "PASS" : "FAIL",
756 checks[c].what, detail);
757 fflush(stdout);
758 failed += !ok;
759 }
760 return failed ? 1 : 0;
761}
#define max(a, b)
Definition Arduino.h:43
flight_state_t
@ FLIGHT_RECOVERY
@ FLIGHT_ARMED
@ FLIGHT_IDLE
@ FLIGHT_LAUNCHED
bool emmc_log_on_state(emmc_log_t *log, flight_state_t from, flight_state_t to, uint32_t now_ms)
Definition emmc_log.c:200
emmc_status_t emmc_log_service(emmc_log_t *log, uint32_t now_ms)
Definition emmc_log.c:424
emmc_status_t emmc_log_init(emmc_log_t *log, const emmc_port_t *port, void *context, const emmc_log_config_t *config, emmc_log_chunk_t *chunks, uint8_t chunk_count, uint8_t *scratch)
Definition emmc_log.c:177
uint8_t emmc_log_backlog(const emmc_log_t *log)
Definition emmc_log.c:252
bool emmc_log_append(emmc_log_t *log, uint8_t type, uint32_t t_ms, const void *data, uint8_t len)
Definition emmc_log.c:235
emmc_log_config_t emmc_log_default_config(void)
Definition emmc_log.c:167
#define EMMC_REC_USER_FIRST
Definition emmc_log.h:75
@ EMMC_DISK_FAULT
Definition emmc_log.h:96
@ EMMC_DISK_MOUNTED
Definition emmc_log.h:94
#define EMMC_REC_STATE
Definition emmc_log.h:72
#define EMMC_REC_SESSION_END
Definition emmc_log.h:73
#define EMMC_REC_SESSION_START
Definition emmc_log.h:71
emmc_status_t
Definition emmc_port.h:22
@ EMMC_ERR_PARAM
Definition emmc_port.h:27
@ EMMC_ERR_TIMEOUT
Definition emmc_port.h:25
@ EMMC_OK
Definition emmc_port.h:23
static emmc_card_info_t card
Definition emmc_stm32.c:55
static const emmc_port_t port
Definition emmc_stm32.c:210
const uint16_t fake_baro_datasheet_prom[8]
Definition fake_baro.c:5
dev_bus_t fake_baro_bus(fake_baro_t *f)
Definition fake_baro.c:41
size_t fake_gnss_emit(fake_gnss_t *f, uint32_t now_us, uint8_t *out, size_t cap)
Definition fake_gnss.c:177
gnss_io_t fake_gnss_io(fake_gnss_t *f)
Definition fake_gnss.c:136
void fake_gnss_init(fake_gnss_t *f, uint32_t baud, fake_gnss_truth_t truth)
Definition fake_gnss.c:118
dev_bus_t fake_regs_bus(fake_regs_t *f)
Definition fake_regs.c:59
void fake_regs_init(fake_regs_t *f, bool ms_bit)
Definition fake_regs.c:5
static ISM330DH_SPI imu
Definition main.cpp:7
static int32_t diff(uint32_t a, uint32_t b)
Definition flight_e2e.c:365
static void world_init(world_t *w, const faults_t *faults)
Definition flight_e2e.c:317
#define FLIGHT_US
Definition flight_e2e.c:36
static bool ok
Definition flight_e2e.c:435
static void check_fault_imu_silent(void)
Definition flight_e2e.c:635
static int phase_at(uint32_t t)
Definition flight_e2e.c:43
static void check_emmc_stall(void)
Definition flight_e2e.c:586
static uint32_t crc32_ref(const uint8_t *p, size_t n)
Definition flight_e2e.c:160
static void fly(world_t *w)
Definition flight_e2e.c:369
static emmc_status_t c_write(void *ctx, uint32_t lba, const void *buf, uint32_t n, uint32_t budget)
Definition flight_e2e.c:129
#define DATA_START
Definition flight_e2e.c:105
#define BS
Definition flight_e2e.c:104
#define T_RECOVERY_US
Definition flight_e2e.c:39
#define EXPECT(cond,...)
Definition flight_e2e.c:436
static void gps_truth(uint32_t t, ubx_pvt_t *v)
Definition flight_e2e.c:87
static void service_card(world_t *w)
Definition flight_e2e.c:356
static void nominal(void)
Definition flight_e2e.c:455
static void check_gps(void)
Definition flight_e2e.c:664
static void note(const char *fmt,...)
Definition flight_e2e.c:444
#define WAKE_LATENCY_US
Definition flight_e2e.c:215
uint32_t d1
Definition flight_e2e.c:77
static void highg_truth(uint32_t t, int8_t out[3])
Definition flight_e2e.c:67
static const struct @346030372276252055054377351274303231312311263244 kBaro[]
#define CAPACITY
Definition flight_e2e.c:106
int32_t p
Definition flight_e2e.c:78
static void check_emmc_roundtrip(void)
Definition flight_e2e.c:567
static void check_timestamps(void)
Definition flight_e2e.c:525
static void set_chips(world_t *w, uint32_t t)
Definition flight_e2e.c:289
static void imu_truth(uint32_t t, int16_t out[7])
Definition flight_e2e.c:55
static void world_free(world_t *w)
Definition flight_e2e.c:426
@ PAD
Definition flight_e2e.c:41
@ BOOST
Definition flight_e2e.c:41
@ COAST
Definition flight_e2e.c:41
@ DESCENT
Definition flight_e2e.c:41
static const check_t checks[]
Definition flight_e2e.c:710
#define MAX_RECS
Definition flight_e2e.c:214
static uint32_t rd32(const uint8_t *p)
Definition flight_e2e.c:169
static void check_conservation(void)
Definition flight_e2e.c:545
static void read_image(const card_t *card, image_t *img, unsigned max)
Definition flight_e2e.c:174
static bool flown
Definition flight_e2e.c:453
uint32_t d2
Definition flight_e2e.c:77
static emmc_status_t c_start(void *ctx, uint32_t *count)
Definition flight_e2e.c:115
static world_t W
Definition flight_e2e.c:451
static void change_state(world_t *w, flight_state_t to)
Definition flight_e2e.c:312
int32_t temp
Definition flight_e2e.c:78
static emmc_status_t c_read(void *ctx, uint32_t lba, void *buf, uint32_t n, uint32_t budget)
Definition flight_e2e.c:120
#define T_LAUNCH_US
Definition flight_e2e.c:37
static void sink(void *ctx, uint8_t type, const void *data, uint8_t len)
Definition flight_e2e.c:246
static image_t IMG
Definition flight_e2e.c:452
#define T_BURNOUT_US
Definition flight_e2e.c:38
static void check_sample_values(void)
Definition flight_e2e.c:480
static void check_sensor_rates(void)
Definition flight_e2e.c:469
void gnss_core_feed(gnss_core_t *c, const uint8_t *bytes, size_t n, uint32_t now_us)
Definition gnss_core.c:139
void gnss_core_set_pps(gnss_core_t *c, uint32_t edge_us)
Definition gnss_core.c:60
void gnss_core_init(gnss_core_t *c, uint32_t now_us)
Definition gnss_core.c:53
void gnss_core_poll(gnss_core_t *c, uint32_t now_us)
Definition gnss_core.c:149
@ GNSS_RUN
Definition gnss_core.h:48
#define NULL
Definition usbd_def.h:44
*brief create JSON pointer *sa reference_tokens const string_t const string_t & b
Definition json.hpp:13878
*brief create JSON pointer *sa reference_tokens const string_t & a
Definition json.hpp:13877
size_type count(const key_type &key) const
Definition json.hpp:19264
return o
Definition json.hpp:13897
detail namespace with internal helper functions
Definition json.hpp:249
*brief user defined string literal for JSON values *sa std::size_t n
Definition json.hpp:24474
Definition run.py:1
rec_gnss_t
Definition records.h:63
rec_baro_t
Definition records.h:51
rec_highg_t
Definition records.h:45
@ REC_HIGHG
Definition records.h:21
@ REC_GNSS
Definition records.h:23
@ REC_BARO
Definition records.h:22
@ REC_IMU
Definition records.h:20
rec_imu_t
Definition records.h:40
uint32_t sensors_core_wait_us(const sensors_core_t *c, uint32_t now_us)
void sensors_core_init(sensors_core_t *c, uint32_t now_us)
static void failed(sensors_device_t *d, int err, uint32_t now_us)
void sensors_core_step(sensors_core_t *c, uint32_t events, uint32_t imu_edge_us, uint32_t highg_edge_us, uint32_t now_us)
#define SENSORS_RETRY_US
#define SENSORS_BARO_STEP_US
#define SENSORS_HIGHG_PERIOD_US
#define SENSORS_IMU_PERIOD_US
#define SENSORS_EV_HIGHG
#define SENSORS_EV_IMU
uint32_t fail_until_ms
Definition flight_e2e.c:110
uint32_t fail_from_ms
Definition flight_e2e.c:110
unsigned failed_writes
Definition flight_e2e.c:112
uint32_t now_ms
Definition flight_e2e.c:111
unsigned writes
Definition flight_e2e.c:112
uint8_t * blocks
Definition flight_e2e.c:109
const char * what
Definition flight_e2e.c:706
const char * id
Definition flight_e2e.c:705
uint32_t chunks_lost
Definition emmc_log.h:103
uint32_t write_errors
Definition emmc_log.h:104
uint32_t dropped
Definition emmc_log.h:101
emmc_log_stats_t stats
Definition emmc_log.h:145
emmc_disk_state_t disk
Definition emmc_log.h:139
bool has_fill
Definition emmc_log.h:126
uint8_t fill_block
Definition emmc_log.h:128
uint16_t fill_offset
Definition emmc_log.h:129
uint8_t fill_chunk
Definition emmc_log.h:127
uint32_t d2
Definition fake_baro.h:15
uint16_t prom[8]
Definition fake_baro.h:14
uint32_t d1
Definition fake_baro.h:15
uint32_t module_baud
Definition fake_gnss.h:26
uint16_t rate_ms
Definition fake_gnss.h:28
uint8_t dynmodel
Definition fake_gnss.h:29
bool nmea_on
Definition fake_gnss.h:27
uint8_t self_clear_bits
Definition fake_regs.h:21
uint8_t regs[128]
Definition fake_regs.h:17
uint8_t self_clear_reg
Definition fake_regs.h:20
unsigned transfers
Definition fake_regs.h:25
int fail_after
Definition fake_regs.h:19
unsigned clear_after_reads
Definition fake_regs.h:22
uint32_t card_fail_from_ms
Definition flight_e2e.c:219
uint32_t imu_fail_until
Definition flight_e2e.c:218
uint32_t card_fail_until_ms
Definition flight_e2e.c:219
uint32_t imu_fail_from
Definition flight_e2e.c:218
gnss_io_t io
Definition gnss_core.h:51
uint32_t detected_baud
Definition gnss_core.h:61
gnss_state_t state
Definition gnss_core.h:54
record_sink_t sink
Definition gnss_core.h:52
bool super_ok
Definition flight_e2e.c:154
rec_t * recs
Definition flight_e2e.c:157
uint32_t sessions
Definition flight_e2e.c:155
uint32_t blocks
Definition flight_e2e.c:155
uint32_t bad_blocks
Definition flight_e2e.c:155
unsigned n
Definition flight_e2e.c:156
uint8_t len
Definition flight_e2e.c:148
uint8_t type
Definition flight_e2e.c:148
uint8_t data[64]
Definition flight_e2e.c:150
uint32_t t_ms
Definition flight_e2e.c:149
void * ctx
Definition records.h:32
void(* record)(void *ctx, uint8_t type, const void *data, uint8_t len)
Definition records.h:31
dev_bus_t imu_bus
dev_bus_t baro_bus
dev_bus_t highg_bus
sensors_device_t imu
sensors_sink_t sink
int32_t lon_e7
Definition ubx.h:71
uint16_t pdop_e2
Definition ubx.h:76
int32_t lat_e7
Definition ubx.h:71
uint8_t fix_type
Definition ubx.h:68
int32_t vel_d_mms
Definition ubx.h:74
uint32_t hacc_mm
Definition ubx.h:73
int32_t hmsl_mm
Definition ubx.h:72
uint8_t num_sv
Definition ubx.h:70
uint32_t itow_ms
Definition ubx.h:65
uint8_t flags
Definition ubx.h:69
uint32_t vacc_mm
Definition ubx.h:73
fake_baro_t baro
Definition flight_e2e.c:224
fake_regs_t imu
Definition flight_e2e.c:223
faults_t faults
Definition flight_e2e.c:243
uint32_t now
Definition flight_e2e.c:238
unsigned refused
Definition flight_e2e.c:242
emmc_log_t log
Definition flight_e2e.c:234
uint32_t next_gps_us
Definition flight_e2e.c:228
fake_gnss_t gps
Definition flight_e2e.c:226
uint32_t edge_imu
Definition flight_e2e.c:238
uint32_t next_pps_us
Definition flight_e2e.c:228
rec_t * produced
Definition flight_e2e.c:239
uint32_t last_t[3]
Definition flight_e2e.c:241
flight_state_t state
Definition flight_e2e.c:237
unsigned n_gnss
Definition flight_e2e.c:229
sensors_core_t core
Definition flight_e2e.c:225
uint32_t next_imu
Definition flight_e2e.c:238
uint32_t sealed_before_stall_ms
Definition flight_e2e.c:231
unsigned n_produced
Definition flight_e2e.c:240
emmc_log_chunk_t chunks[4]
Definition flight_e2e.c:235
uint8_t scratch[BS]
Definition flight_e2e.c:236
uint32_t next_service
Definition flight_e2e.c:238
uint32_t first_t[3]
Definition flight_e2e.c:241
unsigned n_type[3]
Definition flight_e2e.c:240
bool stall_seen
Definition flight_e2e.c:232
uint32_t next_highg
Definition flight_e2e.c:238
uint32_t chunk_start_ms
Definition flight_e2e.c:230
unsigned appended
Definition flight_e2e.c:242
gnss_core_t gnss
Definition flight_e2e.c:227
fake_regs_t highg
Definition flight_e2e.c:223
uint32_t backwards[3]
Definition flight_e2e.c:241
uint32_t edge_highg
Definition flight_e2e.c:238
card_t card
Definition flight_e2e.c:233
int main()
static ArmGate g(kMe)
static boot_report_t r
static image_t img
#define UBX_DYNMODEL_AIR4
Definition ubx.h:48