Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Ardupilot
GitHub Repository: Ardupilot/ardupilot
Path: blob/master/libraries/AP_ExternalAHRS/tests/test_sensaition_parser.cpp
13810 views
1
#include <AP_gtest.h>
2
#include <AP_ExternalAHRS/AP_ExternalAHRS_SensAItion_Parser.h>
3
4
const AP_HAL::HAL& hal = AP_HAL::get_HAL();
5
6
namespace
7
{
8
// Units definitions Kebni
9
constexpr float UG_PER_MSS = 1e6f / 9.80665f;
10
constexpr float UDEGS_PER_RADS = 1e6f * 180.0f / 3.1415926f;
11
constexpr float MHPA_PER_PA = 1e3f / 100.0f;
12
13
using Parser = AP_ExternalAHRS_SensAItion_Parser;
14
}
15
16
// --- HELPER FUNCTIONS ---
17
static bool is_equal(const float f1, const float f2, const float eps)
18
{
19
return (fabs(f1 - f2) < eps);
20
}
21
22
static void fill_be32(uint8_t* data, size_t& loc, int32_t val)
23
{
24
data[loc++] = (val >> 24) & 0xFF;
25
data[loc++] = (val >> 16) & 0xFF;
26
data[loc++] = (val >> 8) & 0xFF;
27
data[loc++] = val & 0xFF;
28
}
29
30
static void fill_be16(uint8_t* data, size_t& loc, int16_t val)
31
{
32
data[loc++] = (val >> 8) & 0xFF;
33
data[loc++] = val & 0xFF;
34
}
35
36
static void fill_u8(uint8_t* data, size_t& loc, uint8_t val)
37
{
38
data[loc++] = val;
39
}
40
41
// --- UPDATED STRUCT (Matches Parser + Date/Week Support) ---
42
struct Measurement {
43
Parser::MeasurementType type;
44
45
// Packet 0 (IMU) Data
46
Vector3f acceleration_mss;
47
Vector3f angular_velocity_rads;
48
Vector3f magnetic_field_mgauss;
49
float temperature_degc;
50
float air_pressure_p;
51
52
// Packet 1 (Orientation) Data
53
Quaternion orientation;
54
55
// Packet 2 (INS) Data
56
Location location; // Lat/Lon/Alt
57
Vector3f velocity_ned; // North/East/Down m/s
58
59
// Accuracy Metrics (Vectors)
60
Vector3f pos_accuracy; // X=Lat, Y=Lon, Z=Alt (meters)
61
Vector3f vel_accuracy; // X=VelN, Y=VelE, Z=VelD (m/s)
62
63
// Status & Health Flags
64
uint8_t alignment_status; // 1 = Align OK
65
uint8_t gnss1_fix;
66
uint8_t gnss2_fix;
67
68
uint8_t num_sats_gnss1;
69
uint8_t num_sats_gnss2;
70
71
// Time & Date (Input for Generator)
72
uint32_t time_itow_ms;
73
uint16_t year;
74
uint8_t month;
75
uint8_t day;
76
77
// The Calculated Result (Output from Parser)
78
uint16_t gps_week;
79
80
uint32_t error_flags; // Bitmask from sensor
81
uint8_t sensor_valid; // Byte 49 (New in v5)
82
};
83
84
// --- UPDATED GENERATOR (Matches 69-Byte Parser Layout) ---
85
static void fill_simulated_packet(uint8_t* data, size_t& data_length,
86
const Measurement& m,
87
Parser::ConfigMode mode)
88
{
89
size_t idx = 0;
90
91
// 1. Header & ID
92
data[idx++] = 0xFA;
93
if (mode == Parser::ConfigMode::INTERLEAVED_INS) {
94
switch (m.type) {
95
case Parser::MeasurementType::IMU:
96
data[idx++] = 0x00;
97
break;
98
case Parser::MeasurementType::AHRS:
99
data[idx++] = 0x01;
100
break;
101
case Parser::MeasurementType::INS:
102
data[idx++] = 0x02;
103
break;
104
default:
105
break;
106
}
107
}
108
109
// 2. Payload
110
if (m.type == Parser::MeasurementType::IMU) {
111
fill_be32(data, idx, m.acceleration_mss.x * UG_PER_MSS);
112
fill_be32(data, idx, m.acceleration_mss.y * UG_PER_MSS);
113
fill_be32(data, idx, m.acceleration_mss.z * UG_PER_MSS);
114
fill_be32(data, idx, m.angular_velocity_rads.x * UDEGS_PER_RADS);
115
fill_be32(data, idx, m.angular_velocity_rads.y * UDEGS_PER_RADS);
116
fill_be32(data, idx, m.angular_velocity_rads.z * UDEGS_PER_RADS);
117
fill_be16(data, idx, (int16_t)((m.temperature_degc - 20.0f) / 0.008f));
118
fill_be16(data, idx, m.magnetic_field_mgauss.x);
119
fill_be16(data, idx, m.magnetic_field_mgauss.y);
120
fill_be16(data, idx, m.magnetic_field_mgauss.z);
121
fill_be32(data, idx, m.air_pressure_p * MHPA_PER_PA);
122
123
} else if (m.type == Parser::MeasurementType::AHRS) {
124
fill_be32(data, idx, m.orientation.q1 * 1e6);
125
fill_be32(data, idx, m.orientation.q2 * 1e6);
126
fill_be32(data, idx, m.orientation.q3 * 1e6);
127
fill_be32(data, idx, m.orientation.q4 * 1e6);
128
129
} else if (m.type == Parser::MeasurementType::INS) {
130
// --- 69-BYTE LAYOUT ---
131
132
// 0-3: Sats (Big Endian of 2 shorts)
133
// GNSS2 is first Short, GNSS1 is second Short
134
// We put values in the LSB of each Short: [00][Count]
135
fill_u8(data, idx, 0); // GNSS2 Hi
136
fill_u8(data, idx, m.num_sats_gnss2); // GNSS2 Lo
137
fill_u8(data, idx, 0); // GNSS1 Hi
138
fill_u8(data, idx, m.num_sats_gnss1); // GNSS1 Lo
139
140
// 4-7: Flags
141
fill_be32(data, idx, m.error_flags);
142
143
// 8: Valid
144
fill_u8(data, idx, m.sensor_valid);
145
146
// 9-32: Nav Data
147
fill_be32(data, idx, m.location.lat);
148
fill_be32(data, idx, m.location.lng);
149
150
// Velocity N, E, D
151
fill_be32(data, idx, m.velocity_ned.x * 1000);
152
fill_be32(data, idx, m.velocity_ned.y * 1000);
153
fill_be32(data, idx, m.velocity_ned.z * 1000);
154
155
// Altitude
156
fill_be32(data, idx, m.location.alt * 10); // cm -> mm
157
158
// 33: Alignment
159
fill_u8(data, idx, m.alignment_status);
160
161
// 34-37: iTOW
162
fill_be32(data, idx, m.time_itow_ms);
163
164
// 38-39: GNSS Fix (Mask 5 -> 2 Bytes)
165
// Wire: [GNSS2][GNSS1]
166
fill_u8(data, idx, m.gnss2_fix);
167
fill_u8(data, idx, m.gnss1_fix);
168
169
// 40-43: UTC Date (Mask F -> 4 Bytes)
170
// Wire: [Y_MSB][Y_LSB][Pad][Month]
171
fill_u8(data, idx, (m.year >> 8) & 0xFF);
172
fill_u8(data, idx, m.year & 0xFF);
173
fill_u8(data, idx, 0); // Pad
174
fill_u8(data, idx, m.month);
175
176
// 44: UTC Time (Mask 8 -> 1 Byte)
177
// Wire: [Day]
178
fill_u8(data, idx, m.day);
179
180
// 45-68: Accuracy (6 x 4 Bytes)
181
// Order: Lat, Lon, VelN, VelE, VelD, PosD
182
fill_be32(data, idx, m.pos_accuracy.x * 1000); // Lat Acc
183
fill_be32(data, idx, m.pos_accuracy.y * 1000); // Lon Acc
184
fill_be32(data, idx, m.vel_accuracy.x * 1000); // Vel N
185
fill_be32(data, idx, m.vel_accuracy.y * 1000); // Vel E
186
fill_be32(data, idx, m.vel_accuracy.z * 1000); // Vel D
187
fill_be32(data, idx, m.pos_accuracy.z * 1000); // Pos D
188
}
189
190
// 4. CRC
191
uint8_t checksum = 0;
192
for (size_t i = 1; i < idx; ++i) {
193
checksum ^= data[i];
194
}
195
data[idx++] = checksum;
196
197
data_length = idx;
198
}
199
200
// --- DEFAULT FACTORY ---
201
static Measurement default_measurement(Parser::MeasurementType type)
202
{
203
Measurement m = {};
204
m.type = type;
205
if (type == Parser::MeasurementType::IMU) {
206
m.acceleration_mss.x = 4.0f;
207
m.acceleration_mss.y = 5.0f;
208
m.acceleration_mss.z = 6.0f;
209
m.angular_velocity_rads.x = 0.1f;
210
m.angular_velocity_rads.y = 0.2f;
211
m.angular_velocity_rads.z = 0.3f;
212
m.temperature_degc = 56.0f;
213
m.magnetic_field_mgauss.x = 31.0f;
214
m.magnetic_field_mgauss.y = 41.0f;
215
m.magnetic_field_mgauss.z = 51.0f;
216
m.air_pressure_p = 1235.0f;
217
}
218
if (type == Parser::MeasurementType::INS) {
219
m.location.lat = 590000000;
220
m.location.lng = 180000000;
221
m.location.alt = 5000;
222
m.velocity_ned = Vector3f(1, 0, 0);
223
224
m.pos_accuracy = Vector3f(0.5f, 0.5f, 0.8f);
225
m.vel_accuracy = Vector3f(0.1f, 0.1f, 0.1f);
226
227
m.alignment_status = 1;
228
m.gnss1_fix = 3;
229
m.gnss2_fix = 0;
230
m.num_sats_gnss1 = 12;
231
m.time_itow_ms = 1000;
232
m.year = 2025;
233
m.month = 12;
234
m.day = 10;
235
m.sensor_valid = true;
236
}
237
return m;
238
}
239
static bool cmp_packages(Measurement& in, Parser::Measurement& out)
240
{
241
if (in.type != out.type) {
242
return false;
243
}
244
//
245
246
//
247
switch (in.type) {
248
case Parser::MeasurementType::IMU:
249
if (!is_equal(in.acceleration_mss[0], out.acceleration_mss[0], 0.00001f) ||
250
!is_equal(in.acceleration_mss[1], out.acceleration_mss[1], 0.00001f) ||
251
!is_equal(in.acceleration_mss[2], out.acceleration_mss[2], 0.00001f) ||
252
!is_equal(in.angular_velocity_rads[0], out.angular_velocity_rads[0], 0.000001f) ||
253
!is_equal(in.angular_velocity_rads[1], out.angular_velocity_rads[1], 0.000001f) ||
254
!is_equal(in.angular_velocity_rads[2], out.angular_velocity_rads[2], 0.000001f) ||
255
!is_equal(in.magnetic_field_mgauss[0], out.magnetic_field_mgauss[0]) ||
256
!is_equal(in.magnetic_field_mgauss[1], out.magnetic_field_mgauss[1]) ||
257
!is_equal(in.magnetic_field_mgauss[2], out.magnetic_field_mgauss[2]) ||
258
!is_equal(in.temperature_degc, out.temperature_degc) ||
259
!is_equal(in.air_pressure_p, out.air_pressure_p)) {
260
return false;
261
}
262
break;
263
case Parser::MeasurementType::AHRS:
264
if (!is_equal(in.orientation.q1, out.orientation.q1) ||
265
!is_equal(in.orientation.q2, out.orientation.q2) ||
266
!is_equal(in.orientation.q3, out.orientation.q3) ||
267
!is_equal(in.orientation.q4, out.orientation.q4)) {
268
return false;
269
}
270
break;
271
case Parser::MeasurementType::INS:
272
if (in.location.lat != out.location.lat ||
273
in.location.lng != out.location.lng ||
274
in.location.alt != out.location.alt ||
275
!is_equal(in.velocity_ned[0], out.velocity_ned[0]) ||
276
!is_equal(in.velocity_ned[1], out.velocity_ned[1]) ||
277
!is_equal(in.velocity_ned[2], out.velocity_ned[2]) ||
278
!is_equal(in.pos_accuracy[0], out.pos_accuracy[0]) ||
279
!is_equal(in.pos_accuracy[1], out.pos_accuracy[1]) ||
280
!is_equal(in.pos_accuracy[2], out.pos_accuracy[2]) ||
281
!is_equal(in.vel_accuracy[0], out.vel_accuracy[0]) ||
282
!is_equal(in.vel_accuracy[1], out.vel_accuracy[1]) ||
283
!is_equal(in.vel_accuracy[2], out.vel_accuracy[2]) ||
284
in.alignment_status != out.alignment_status ||
285
in.gnss1_fix != out.gnss1_fix ||
286
in.gnss2_fix != out.gnss2_fix ||
287
in.num_sats_gnss1 != out.num_sats_gnss1 ||
288
in.num_sats_gnss2 != out.num_sats_gnss2 ||
289
in.time_itow_ms != out.time_itow_ms ||
290
out.gps_week != 2396 ||
291
in.error_flags != out.error_flags ||
292
in.sensor_valid != out.sensor_valid) {
293
return false;
294
}
295
break;
296
default:
297
return false;
298
}
299
return true;
300
}
301
302
// ---------------------------------------------------------------------------
303
// LEGACY MODE TESTS
304
// ---------------------------------------------------------------------------
305
306
TEST(SensAItionParser, Legacy_IMU_HappyPath)
307
{
308
Parser parser(Parser::ConfigMode::IMU);
309
auto in = default_measurement(Parser::MeasurementType::IMU);
310
uint8_t buffer[100];
311
size_t len = 100;
312
fill_simulated_packet(buffer, len, in, Parser::ConfigMode::IMU);
313
EXPECT_EQ(len, 38u);
314
315
Parser::Measurement meas;
316
const auto parsed_bytes = parser.parse_stream(buffer, len, meas);
317
318
EXPECT_EQ(parsed_bytes, 38u);
319
EXPECT_EQ(meas.type, Parser::MeasurementType::IMU);
320
EXPECT_TRUE(cmp_packages(in, meas));
321
}
322
323
TEST(SensAItionParser, Legacy_RejectsInvalidChecksum)
324
{
325
Parser parser(Parser::ConfigMode::IMU);
326
auto in = default_measurement(Parser::MeasurementType::IMU);
327
uint8_t buffer[100];
328
size_t len = 100;
329
fill_simulated_packet(buffer, len, in, Parser::ConfigMode::IMU);
330
buffer[len - 1] += 1;
331
uint32_t err_start = parser.get_parse_errors();
332
333
Parser::Measurement meas;
334
parser.parse_stream(buffer, len, meas);
335
336
EXPECT_GT(parser.get_parse_errors(), err_start);
337
}
338
339
TEST(SensAItionParser, Legacy_RejectsTooSmallBuffer)
340
{
341
Parser parser(Parser::ConfigMode::IMU);
342
auto in = default_measurement(Parser::MeasurementType::IMU);
343
uint8_t buffer[100];
344
size_t len = 100;
345
fill_simulated_packet(buffer, len, in, Parser::ConfigMode::IMU);
346
uint32_t valid_start = parser.get_valid_packets();
347
348
Parser::Measurement meas;
349
parser.parse_stream(buffer, len - 5, meas);
350
351
EXPECT_EQ(parser.get_valid_packets(), valid_start);
352
}
353
354
TEST(SensAItionParser, Legacy_ValidPacketsCount)
355
{
356
Parser parser(Parser::ConfigMode::IMU);
357
auto in = default_measurement(Parser::MeasurementType::IMU);
358
uint8_t buffer[100];
359
size_t len = 100;
360
fill_simulated_packet(buffer, len, in, Parser::ConfigMode::IMU);
361
uint32_t valid_start = parser.get_valid_packets();
362
363
Parser::Measurement meas;
364
for (int i = 0; i < 5; i++) {
365
parser.parse_stream(buffer, len, meas);
366
}
367
368
EXPECT_EQ(parser.get_valid_packets(), valid_start + 5);
369
}
370
371
TEST(SensAItionParser, Legacy_FalseHeaderInPayload)
372
{
373
Parser parser(Parser::ConfigMode::IMU);
374
auto in = default_measurement(Parser::MeasurementType::IMU);
375
uint8_t packet[38];
376
size_t len = 38;
377
fill_simulated_packet(packet, len, in, Parser::ConfigMode::IMU);
378
packet[5] = 0xFA;
379
uint8_t checksum = 0;
380
for (size_t i = 1; i < len - 1; ++i) {
381
checksum ^= packet[i];
382
}
383
packet[len - 1] = checksum;
384
uint32_t start_valid = parser.get_valid_packets();
385
386
Parser::Measurement meas;
387
for (size_t i = 0; i < len; i++) {
388
parser.parse_stream(&packet[i], 1, meas);
389
}
390
391
EXPECT_EQ(parser.get_valid_packets(), start_valid + 1);
392
}
393
394
TEST(SensAItionParser, Legacy_FragmentedHeaderRecovery)
395
{
396
Parser parser(Parser::ConfigMode::IMU);
397
auto in = default_measurement(Parser::MeasurementType::IMU);
398
uint8_t valid[38];
399
size_t len = 38;
400
fill_simulated_packet(valid, len, in, Parser::ConfigMode::IMU);
401
uint8_t stream[120];
402
size_t slen = 0;
403
stream[slen++] = 0xFA; stream[slen++] = 0x00;
404
memcpy(&stream[slen], valid, 38);
405
slen += 38;
406
memcpy(&stream[slen], valid, 38);
407
slen += 38;
408
uint32_t start_valid = parser.get_valid_packets();
409
410
Parser::Measurement meas;
411
for (size_t i = 0; i < slen; i++) {
412
parser.parse_stream(&stream[i], 1, meas);
413
}
414
415
EXPECT_GE(parser.get_valid_packets() - start_valid, 1u);
416
}
417
418
// ---------------------------------------------------------------------------
419
// INTERLEAVED MODE TESTS
420
// ---------------------------------------------------------------------------
421
422
TEST(SensAItionParser, Interleaved_IMU_HappyPath)
423
{
424
Parser parser(Parser::ConfigMode::INTERLEAVED_INS);
425
auto in = default_measurement(Parser::MeasurementType::IMU);
426
in.acceleration_mss.x = 2.5f;
427
uint8_t buffer[64];
428
size_t len = 64;
429
fill_simulated_packet(buffer, len, in, Parser::ConfigMode::INTERLEAVED_INS);
430
EXPECT_EQ(len, 39u);
431
432
Parser::Measurement meas;
433
const auto parsed_bytes = parser.parse_stream(buffer, len, meas);
434
435
EXPECT_EQ(parsed_bytes, 39u);
436
EXPECT_EQ(meas.type, Parser::MeasurementType::IMU);
437
EXPECT_NEAR(meas.acceleration_mss.x, 2.5f, 0.01f);
438
EXPECT_TRUE(cmp_packages(in, meas));
439
}
440
441
TEST(SensAItionParser, Interleaved_INS_HappyPath)
442
{
443
Parser parser(Parser::ConfigMode::INTERLEAVED_INS);
444
auto in = default_measurement(Parser::MeasurementType::INS);
445
uint8_t buffer[100];
446
size_t len = 100;
447
fill_simulated_packet(buffer, len, in, Parser::ConfigMode::INTERLEAVED_INS);
448
EXPECT_EQ(len, 72u); // Verified
449
450
Parser::Measurement meas;
451
parser.parse_stream(buffer, len, meas);
452
453
EXPECT_EQ(meas.type, Parser::MeasurementType::INS);
454
EXPECT_TRUE(cmp_packages(in, meas));
455
}
456
457
TEST(SensAItionParser, Interleaved_InvalidID)
458
{
459
Parser parser(Parser::ConfigMode::INTERLEAVED_INS);
460
uint8_t bad[] = { 0xFA, 0x99, 0x00, 0x00 };
461
uint32_t start_err = parser.get_parse_errors();
462
463
Parser::Measurement meas;
464
parser.parse_stream(bad, 4, meas);
465
466
EXPECT_GT(parser.get_parse_errors(), start_err);
467
}
468
469
TEST(SensAItionParser, Handle_Interleaved_Packets)
470
{
471
Parser parser(Parser::ConfigMode::INTERLEAVED_INS);
472
473
// Create a buffer with one INS and IMU packet back-to-back
474
uint8_t stream[200];
475
size_t len = 0;
476
477
// Create INS Packet
478
auto m_ins = default_measurement(Parser::MeasurementType::INS);
479
m_ins.location.lat = 123456789; // Unique marker
480
size_t ins_len = 0;
481
fill_simulated_packet(&stream[0], ins_len, m_ins, Parser::ConfigMode::INTERLEAVED_INS);
482
len += ins_len;
483
484
// Create IMU Packet immediately after
485
auto m_imu = default_measurement(Parser::MeasurementType::IMU);
486
m_imu.acceleration_mss.z = -15.0f; // Unique marker
487
size_t imu_len = 0;
488
fill_simulated_packet(&stream[len], imu_len, m_imu, Parser::ConfigMode::INTERLEAVED_INS);
489
len += imu_len;
490
491
// Parse first packet
492
Parser::Measurement meas;
493
auto parsed_bytes = parser.parse_stream(stream, len, meas);
494
495
EXPECT_EQ(parsed_bytes, ins_len);
496
EXPECT_EQ(meas.type, Parser::MeasurementType::INS);
497
EXPECT_TRUE(cmp_packages(m_ins, meas));
498
499
// Parse second packet
500
parsed_bytes = parser.parse_stream(&stream[parsed_bytes], len - parsed_bytes, meas);
501
EXPECT_EQ(parsed_bytes, imu_len);
502
EXPECT_EQ(meas.type, Parser::MeasurementType::IMU);
503
EXPECT_TRUE(cmp_packages(m_imu, meas));
504
}
505
506
TEST(SensAItionParser, Interleaved_MixedStream_Transitions)
507
{
508
Parser parser(Parser::ConfigMode::INTERLEAVED_INS);
509
auto m_imu = default_measurement(Parser::MeasurementType::IMU);
510
auto m_ahrs = default_measurement(Parser::MeasurementType::AHRS);
511
auto m_ins = default_measurement(Parser::MeasurementType::INS);
512
uint8_t stream[200];
513
size_t len = 0;
514
size_t part_len;
515
516
part_len = 200 - len;
517
fill_simulated_packet(&stream[len], part_len, m_imu, Parser::ConfigMode::INTERLEAVED_INS);
518
len += part_len;
519
part_len = 200 - len;
520
fill_simulated_packet(&stream[len], part_len, m_ahrs, Parser::ConfigMode::INTERLEAVED_INS);
521
len += part_len;
522
part_len = 200 - len;
523
fill_simulated_packet(&stream[len], part_len, m_ins, Parser::ConfigMode::INTERLEAVED_INS);
524
len += part_len;
525
526
Parser::Measurement meas;
527
auto parsed_bytes = parser.parse_stream(stream, len, meas);
528
EXPECT_TRUE(cmp_packages(m_imu, meas));
529
530
parsed_bytes += parser.parse_stream(&stream[parsed_bytes], len - parsed_bytes, meas);
531
EXPECT_TRUE(cmp_packages(m_ahrs, meas));
532
533
parsed_bytes += parser.parse_stream(&stream[parsed_bytes], len - parsed_bytes, meas);
534
EXPECT_EQ(parsed_bytes, len);
535
EXPECT_TRUE(cmp_packages(m_ins, meas));
536
}
537
538
TEST(SensAItionParser, Interleaved_INS_Fragmentation)
539
{
540
Parser parser(Parser::ConfigMode::INTERLEAVED_INS);
541
auto in = default_measurement(Parser::MeasurementType::INS);
542
uint8_t packet[100];
543
size_t len = 100;
544
fill_simulated_packet(packet, len, in, Parser::ConfigMode::INTERLEAVED_INS);
545
546
Parser::Measurement meas;
547
// Parse all but one byte
548
auto parsed_bytes = parser.parse_stream(packet, len - 1, meas);
549
EXPECT_EQ(meas.type, Parser::MeasurementType::UNINITIALIZED);
550
551
// Then parse the finishing byte of the message
552
parsed_bytes += parser.parse_stream(&packet[parsed_bytes], len - parsed_bytes, meas);
553
EXPECT_EQ(meas.type, Parser::MeasurementType::INS);
554
}
555
556
TEST(SensAItionParser, Interleaved_Data_StressTest)
557
{
558
Parser parser(Parser::ConfigMode::INTERLEAVED_INS);
559
auto in = default_measurement(Parser::MeasurementType::INS);
560
in.location.lat = -593293230;
561
in.velocity_ned.x = -15.5f;
562
uint8_t buffer[100];
563
size_t len = 100;
564
fill_simulated_packet(buffer, len, in, Parser::ConfigMode::INTERLEAVED_INS);
565
566
Parser::Measurement meas;
567
parser.parse_stream(buffer, len, meas);
568
EXPECT_EQ(meas.location.lat, -593293230);
569
EXPECT_NEAR(meas.velocity_ned.x, -15.5f, 0.01f);
570
}
571
572
TEST(SensAItionParser, Interleaved_NoiseRecovery)
573
{
574
Parser parser(Parser::ConfigMode::INTERLEAVED_INS);
575
auto in = default_measurement(Parser::MeasurementType::INS);
576
uint8_t valid[100];
577
size_t len = 100;
578
fill_simulated_packet(valid, len, in, Parser::ConfigMode::INTERLEAVED_INS);
579
uint8_t stream[200];
580
memset(stream, 0xEE, 20);
581
memcpy(&stream[20], valid, len);
582
583
Parser::Measurement meas;
584
parser.parse_stream(stream, len + 20, meas);
585
586
EXPECT_EQ(meas.type, Parser::MeasurementType::INS);
587
}
588
589
TEST(SensAItionParser, Legacy_IMU_PartialStream)
590
{
591
Parser parser(Parser::ConfigMode::IMU);
592
auto in = default_measurement(Parser::MeasurementType::IMU);
593
uint8_t packet[100];
594
memset(packet, 0x00, 100); // Ensure known content after the packet
595
size_t chunk_size = 5;
596
size_t len = 20 * chunk_size;
597
fill_simulated_packet(packet, len, in, Parser::ConfigMode::IMU);
598
599
Parser::Measurement meas;
600
for (size_t i = 0; i < len; i += chunk_size) {
601
parser.parse_stream(&packet[i], chunk_size, meas);
602
}
603
604
EXPECT_EQ(meas.type, Parser::MeasurementType::IMU);
605
}
606
607
TEST(SensAItionParser, Interleaved_INS_FullFieldVerification)
608
{
609
Parser parser(Parser::ConfigMode::INTERLEAVED_INS);
610
auto in = default_measurement(Parser::MeasurementType::INS);
611
612
// Setup Distinct Values
613
in.num_sats_gnss1 = 22;
614
in.num_sats_gnss2 = 18;
615
in.error_flags = 0xCAFEBABE;
616
in.sensor_valid = true;
617
in.location.lat = -593293230;
618
in.location.lng = 180685810;
619
in.location.alt = 1500; // cm
620
in.velocity_ned = Vector3f(-5.5f, 2.2f, 0.5f);
621
in.alignment_status = 1;
622
in.time_itow_ms = 987654321;
623
in.gnss1_fix = 3;
624
in.gnss2_fix = 2;
625
626
// Time -> Week Calculation Check
627
// 2025-12-10 is GPS Week 2396
628
in.year = 2025;
629
in.month = 12;
630
in.day = 10;
631
uint16_t expected_week = 2396;
632
633
// Vector Accuracy
634
in.pos_accuracy = Vector3f(0.1f, 0.2f, 0.3f); // Lat, Lon, Alt
635
in.vel_accuracy = Vector3f(0.4f, 0.5f, 0.6f); // N, E, D
636
637
// Generate & Parse
638
uint8_t buffer[100];
639
size_t len = 100;
640
fill_simulated_packet(buffer, len, in, Parser::ConfigMode::INTERLEAVED_INS);
641
642
Parser::Measurement meas;
643
parser.parse_stream(buffer, len, meas);
644
645
// VERIFICATION
646
auto& out = meas;
647
EXPECT_EQ(out.type, Parser::MeasurementType::INS);
648
EXPECT_EQ(out.num_sats_gnss1, in.num_sats_gnss1);
649
EXPECT_EQ(out.num_sats_gnss2, in.num_sats_gnss2);
650
EXPECT_EQ(out.error_flags, in.error_flags);
651
EXPECT_EQ(out.sensor_valid, in.sensor_valid);
652
653
EXPECT_EQ(out.location.lat, in.location.lat);
654
EXPECT_EQ(out.location.lng, in.location.lng);
655
EXPECT_EQ(out.location.alt, in.location.alt);
656
EXPECT_NEAR(out.velocity_ned.x, in.velocity_ned.x, 0.001f);
657
658
EXPECT_EQ(out.alignment_status, in.alignment_status);
659
EXPECT_EQ(out.time_itow_ms, in.time_itow_ms);
660
EXPECT_EQ(out.gnss1_fix, in.gnss1_fix);
661
EXPECT_EQ(out.gnss2_fix, in.gnss2_fix);
662
663
// Week Verify
664
EXPECT_EQ(out.gps_week, expected_week) << "GPS Week Calc Failed";
665
666
// Vector Verify
667
EXPECT_NEAR(out.pos_accuracy.x, in.pos_accuracy.x, 0.001f);
668
EXPECT_NEAR(out.pos_accuracy.y, in.pos_accuracy.y, 0.001f);
669
EXPECT_NEAR(out.pos_accuracy.z, in.pos_accuracy.z, 0.001f);
670
671
EXPECT_NEAR(out.vel_accuracy.x, in.vel_accuracy.x, 0.001f);
672
EXPECT_NEAR(out.vel_accuracy.y, in.vel_accuracy.y, 0.001f);
673
EXPECT_NEAR(out.vel_accuracy.z, in.vel_accuracy.z, 0.001f);
674
}
675
676
TEST(SensAItionParser, GPS_Week_Calculation_EdgeCases)
677
{
678
Parser parser(Parser::ConfigMode::INTERLEAVED_INS);
679
auto in = default_measurement(Parser::MeasurementType::INS);
680
uint8_t buffer[100];
681
size_t len = 100;
682
Parser::Measurement meas;
683
684
// CASE 1: Leap Year (Feb 29 2024)
685
// 2024-02-29 -> Week 2303
686
in.year = 2024; in.month = 2; in.day = 29;
687
fill_simulated_packet(buffer, len, in, Parser::ConfigMode::INTERLEAVED_INS);
688
parser.parse_stream(buffer, len, meas);
689
EXPECT_EQ(meas.gps_week, 2303) << "Failed Leap Year Calc";
690
691
// CASE 2: No Fix -> Week Should be 0
692
in.gnss1_fix = 0; // Lost fix
693
in.year = 2025; in.month = 1; in.day = 1;
694
fill_simulated_packet(buffer, len, in, Parser::ConfigMode::INTERLEAVED_INS);
695
parser.parse_stream(buffer, len, meas);
696
EXPECT_EQ(meas.gps_week, 0) << "Week should be 0 when fix is lost";
697
698
// CASE 3: Year 2000 (translates to 00 in BCD format) should be correctly handled
699
in = default_measurement(Parser::MeasurementType::INS);
700
in.year = 2000; in.month = 1; in.day = 31;
701
fill_simulated_packet(buffer, len, in, Parser::ConfigMode::INTERLEAVED_INS);
702
parser.parse_stream(buffer, len, meas);
703
EXPECT_EQ(meas.gps_week, 1047) << "Failed converting the year 2000 to GPS week";
704
}
705
706
AP_GTEST_MAIN()
707
708