Path: blob/master/libraries/AP_ExternalAHRS/tests/test_sensaition_parser.cpp
13810 views
#include <AP_gtest.h>1#include <AP_ExternalAHRS/AP_ExternalAHRS_SensAItion_Parser.h>23const AP_HAL::HAL& hal = AP_HAL::get_HAL();45namespace6{7// Units definitions Kebni8constexpr float UG_PER_MSS = 1e6f / 9.80665f;9constexpr float UDEGS_PER_RADS = 1e6f * 180.0f / 3.1415926f;10constexpr float MHPA_PER_PA = 1e3f / 100.0f;1112using Parser = AP_ExternalAHRS_SensAItion_Parser;13}1415// --- HELPER FUNCTIONS ---16static bool is_equal(const float f1, const float f2, const float eps)17{18return (fabs(f1 - f2) < eps);19}2021static void fill_be32(uint8_t* data, size_t& loc, int32_t val)22{23data[loc++] = (val >> 24) & 0xFF;24data[loc++] = (val >> 16) & 0xFF;25data[loc++] = (val >> 8) & 0xFF;26data[loc++] = val & 0xFF;27}2829static void fill_be16(uint8_t* data, size_t& loc, int16_t val)30{31data[loc++] = (val >> 8) & 0xFF;32data[loc++] = val & 0xFF;33}3435static void fill_u8(uint8_t* data, size_t& loc, uint8_t val)36{37data[loc++] = val;38}3940// --- UPDATED STRUCT (Matches Parser + Date/Week Support) ---41struct Measurement {42Parser::MeasurementType type;4344// Packet 0 (IMU) Data45Vector3f acceleration_mss;46Vector3f angular_velocity_rads;47Vector3f magnetic_field_mgauss;48float temperature_degc;49float air_pressure_p;5051// Packet 1 (Orientation) Data52Quaternion orientation;5354// Packet 2 (INS) Data55Location location; // Lat/Lon/Alt56Vector3f velocity_ned; // North/East/Down m/s5758// Accuracy Metrics (Vectors)59Vector3f pos_accuracy; // X=Lat, Y=Lon, Z=Alt (meters)60Vector3f vel_accuracy; // X=VelN, Y=VelE, Z=VelD (m/s)6162// Status & Health Flags63uint8_t alignment_status; // 1 = Align OK64uint8_t gnss1_fix;65uint8_t gnss2_fix;6667uint8_t num_sats_gnss1;68uint8_t num_sats_gnss2;6970// Time & Date (Input for Generator)71uint32_t time_itow_ms;72uint16_t year;73uint8_t month;74uint8_t day;7576// The Calculated Result (Output from Parser)77uint16_t gps_week;7879uint32_t error_flags; // Bitmask from sensor80uint8_t sensor_valid; // Byte 49 (New in v5)81};8283// --- UPDATED GENERATOR (Matches 69-Byte Parser Layout) ---84static void fill_simulated_packet(uint8_t* data, size_t& data_length,85const Measurement& m,86Parser::ConfigMode mode)87{88size_t idx = 0;8990// 1. Header & ID91data[idx++] = 0xFA;92if (mode == Parser::ConfigMode::INTERLEAVED_INS) {93switch (m.type) {94case Parser::MeasurementType::IMU:95data[idx++] = 0x00;96break;97case Parser::MeasurementType::AHRS:98data[idx++] = 0x01;99break;100case Parser::MeasurementType::INS:101data[idx++] = 0x02;102break;103default:104break;105}106}107108// 2. Payload109if (m.type == Parser::MeasurementType::IMU) {110fill_be32(data, idx, m.acceleration_mss.x * UG_PER_MSS);111fill_be32(data, idx, m.acceleration_mss.y * UG_PER_MSS);112fill_be32(data, idx, m.acceleration_mss.z * UG_PER_MSS);113fill_be32(data, idx, m.angular_velocity_rads.x * UDEGS_PER_RADS);114fill_be32(data, idx, m.angular_velocity_rads.y * UDEGS_PER_RADS);115fill_be32(data, idx, m.angular_velocity_rads.z * UDEGS_PER_RADS);116fill_be16(data, idx, (int16_t)((m.temperature_degc - 20.0f) / 0.008f));117fill_be16(data, idx, m.magnetic_field_mgauss.x);118fill_be16(data, idx, m.magnetic_field_mgauss.y);119fill_be16(data, idx, m.magnetic_field_mgauss.z);120fill_be32(data, idx, m.air_pressure_p * MHPA_PER_PA);121122} else if (m.type == Parser::MeasurementType::AHRS) {123fill_be32(data, idx, m.orientation.q1 * 1e6);124fill_be32(data, idx, m.orientation.q2 * 1e6);125fill_be32(data, idx, m.orientation.q3 * 1e6);126fill_be32(data, idx, m.orientation.q4 * 1e6);127128} else if (m.type == Parser::MeasurementType::INS) {129// --- 69-BYTE LAYOUT ---130131// 0-3: Sats (Big Endian of 2 shorts)132// GNSS2 is first Short, GNSS1 is second Short133// We put values in the LSB of each Short: [00][Count]134fill_u8(data, idx, 0); // GNSS2 Hi135fill_u8(data, idx, m.num_sats_gnss2); // GNSS2 Lo136fill_u8(data, idx, 0); // GNSS1 Hi137fill_u8(data, idx, m.num_sats_gnss1); // GNSS1 Lo138139// 4-7: Flags140fill_be32(data, idx, m.error_flags);141142// 8: Valid143fill_u8(data, idx, m.sensor_valid);144145// 9-32: Nav Data146fill_be32(data, idx, m.location.lat);147fill_be32(data, idx, m.location.lng);148149// Velocity N, E, D150fill_be32(data, idx, m.velocity_ned.x * 1000);151fill_be32(data, idx, m.velocity_ned.y * 1000);152fill_be32(data, idx, m.velocity_ned.z * 1000);153154// Altitude155fill_be32(data, idx, m.location.alt * 10); // cm -> mm156157// 33: Alignment158fill_u8(data, idx, m.alignment_status);159160// 34-37: iTOW161fill_be32(data, idx, m.time_itow_ms);162163// 38-39: GNSS Fix (Mask 5 -> 2 Bytes)164// Wire: [GNSS2][GNSS1]165fill_u8(data, idx, m.gnss2_fix);166fill_u8(data, idx, m.gnss1_fix);167168// 40-43: UTC Date (Mask F -> 4 Bytes)169// Wire: [Y_MSB][Y_LSB][Pad][Month]170fill_u8(data, idx, (m.year >> 8) & 0xFF);171fill_u8(data, idx, m.year & 0xFF);172fill_u8(data, idx, 0); // Pad173fill_u8(data, idx, m.month);174175// 44: UTC Time (Mask 8 -> 1 Byte)176// Wire: [Day]177fill_u8(data, idx, m.day);178179// 45-68: Accuracy (6 x 4 Bytes)180// Order: Lat, Lon, VelN, VelE, VelD, PosD181fill_be32(data, idx, m.pos_accuracy.x * 1000); // Lat Acc182fill_be32(data, idx, m.pos_accuracy.y * 1000); // Lon Acc183fill_be32(data, idx, m.vel_accuracy.x * 1000); // Vel N184fill_be32(data, idx, m.vel_accuracy.y * 1000); // Vel E185fill_be32(data, idx, m.vel_accuracy.z * 1000); // Vel D186fill_be32(data, idx, m.pos_accuracy.z * 1000); // Pos D187}188189// 4. CRC190uint8_t checksum = 0;191for (size_t i = 1; i < idx; ++i) {192checksum ^= data[i];193}194data[idx++] = checksum;195196data_length = idx;197}198199// --- DEFAULT FACTORY ---200static Measurement default_measurement(Parser::MeasurementType type)201{202Measurement m = {};203m.type = type;204if (type == Parser::MeasurementType::IMU) {205m.acceleration_mss.x = 4.0f;206m.acceleration_mss.y = 5.0f;207m.acceleration_mss.z = 6.0f;208m.angular_velocity_rads.x = 0.1f;209m.angular_velocity_rads.y = 0.2f;210m.angular_velocity_rads.z = 0.3f;211m.temperature_degc = 56.0f;212m.magnetic_field_mgauss.x = 31.0f;213m.magnetic_field_mgauss.y = 41.0f;214m.magnetic_field_mgauss.z = 51.0f;215m.air_pressure_p = 1235.0f;216}217if (type == Parser::MeasurementType::INS) {218m.location.lat = 590000000;219m.location.lng = 180000000;220m.location.alt = 5000;221m.velocity_ned = Vector3f(1, 0, 0);222223m.pos_accuracy = Vector3f(0.5f, 0.5f, 0.8f);224m.vel_accuracy = Vector3f(0.1f, 0.1f, 0.1f);225226m.alignment_status = 1;227m.gnss1_fix = 3;228m.gnss2_fix = 0;229m.num_sats_gnss1 = 12;230m.time_itow_ms = 1000;231m.year = 2025;232m.month = 12;233m.day = 10;234m.sensor_valid = true;235}236return m;237}238static bool cmp_packages(Measurement& in, Parser::Measurement& out)239{240if (in.type != out.type) {241return false;242}243//244245//246switch (in.type) {247case Parser::MeasurementType::IMU:248if (!is_equal(in.acceleration_mss[0], out.acceleration_mss[0], 0.00001f) ||249!is_equal(in.acceleration_mss[1], out.acceleration_mss[1], 0.00001f) ||250!is_equal(in.acceleration_mss[2], out.acceleration_mss[2], 0.00001f) ||251!is_equal(in.angular_velocity_rads[0], out.angular_velocity_rads[0], 0.000001f) ||252!is_equal(in.angular_velocity_rads[1], out.angular_velocity_rads[1], 0.000001f) ||253!is_equal(in.angular_velocity_rads[2], out.angular_velocity_rads[2], 0.000001f) ||254!is_equal(in.magnetic_field_mgauss[0], out.magnetic_field_mgauss[0]) ||255!is_equal(in.magnetic_field_mgauss[1], out.magnetic_field_mgauss[1]) ||256!is_equal(in.magnetic_field_mgauss[2], out.magnetic_field_mgauss[2]) ||257!is_equal(in.temperature_degc, out.temperature_degc) ||258!is_equal(in.air_pressure_p, out.air_pressure_p)) {259return false;260}261break;262case Parser::MeasurementType::AHRS:263if (!is_equal(in.orientation.q1, out.orientation.q1) ||264!is_equal(in.orientation.q2, out.orientation.q2) ||265!is_equal(in.orientation.q3, out.orientation.q3) ||266!is_equal(in.orientation.q4, out.orientation.q4)) {267return false;268}269break;270case Parser::MeasurementType::INS:271if (in.location.lat != out.location.lat ||272in.location.lng != out.location.lng ||273in.location.alt != out.location.alt ||274!is_equal(in.velocity_ned[0], out.velocity_ned[0]) ||275!is_equal(in.velocity_ned[1], out.velocity_ned[1]) ||276!is_equal(in.velocity_ned[2], out.velocity_ned[2]) ||277!is_equal(in.pos_accuracy[0], out.pos_accuracy[0]) ||278!is_equal(in.pos_accuracy[1], out.pos_accuracy[1]) ||279!is_equal(in.pos_accuracy[2], out.pos_accuracy[2]) ||280!is_equal(in.vel_accuracy[0], out.vel_accuracy[0]) ||281!is_equal(in.vel_accuracy[1], out.vel_accuracy[1]) ||282!is_equal(in.vel_accuracy[2], out.vel_accuracy[2]) ||283in.alignment_status != out.alignment_status ||284in.gnss1_fix != out.gnss1_fix ||285in.gnss2_fix != out.gnss2_fix ||286in.num_sats_gnss1 != out.num_sats_gnss1 ||287in.num_sats_gnss2 != out.num_sats_gnss2 ||288in.time_itow_ms != out.time_itow_ms ||289out.gps_week != 2396 ||290in.error_flags != out.error_flags ||291in.sensor_valid != out.sensor_valid) {292return false;293}294break;295default:296return false;297}298return true;299}300301// ---------------------------------------------------------------------------302// LEGACY MODE TESTS303// ---------------------------------------------------------------------------304305TEST(SensAItionParser, Legacy_IMU_HappyPath)306{307Parser parser(Parser::ConfigMode::IMU);308auto in = default_measurement(Parser::MeasurementType::IMU);309uint8_t buffer[100];310size_t len = 100;311fill_simulated_packet(buffer, len, in, Parser::ConfigMode::IMU);312EXPECT_EQ(len, 38u);313314Parser::Measurement meas;315const auto parsed_bytes = parser.parse_stream(buffer, len, meas);316317EXPECT_EQ(parsed_bytes, 38u);318EXPECT_EQ(meas.type, Parser::MeasurementType::IMU);319EXPECT_TRUE(cmp_packages(in, meas));320}321322TEST(SensAItionParser, Legacy_RejectsInvalidChecksum)323{324Parser parser(Parser::ConfigMode::IMU);325auto in = default_measurement(Parser::MeasurementType::IMU);326uint8_t buffer[100];327size_t len = 100;328fill_simulated_packet(buffer, len, in, Parser::ConfigMode::IMU);329buffer[len - 1] += 1;330uint32_t err_start = parser.get_parse_errors();331332Parser::Measurement meas;333parser.parse_stream(buffer, len, meas);334335EXPECT_GT(parser.get_parse_errors(), err_start);336}337338TEST(SensAItionParser, Legacy_RejectsTooSmallBuffer)339{340Parser parser(Parser::ConfigMode::IMU);341auto in = default_measurement(Parser::MeasurementType::IMU);342uint8_t buffer[100];343size_t len = 100;344fill_simulated_packet(buffer, len, in, Parser::ConfigMode::IMU);345uint32_t valid_start = parser.get_valid_packets();346347Parser::Measurement meas;348parser.parse_stream(buffer, len - 5, meas);349350EXPECT_EQ(parser.get_valid_packets(), valid_start);351}352353TEST(SensAItionParser, Legacy_ValidPacketsCount)354{355Parser parser(Parser::ConfigMode::IMU);356auto in = default_measurement(Parser::MeasurementType::IMU);357uint8_t buffer[100];358size_t len = 100;359fill_simulated_packet(buffer, len, in, Parser::ConfigMode::IMU);360uint32_t valid_start = parser.get_valid_packets();361362Parser::Measurement meas;363for (int i = 0; i < 5; i++) {364parser.parse_stream(buffer, len, meas);365}366367EXPECT_EQ(parser.get_valid_packets(), valid_start + 5);368}369370TEST(SensAItionParser, Legacy_FalseHeaderInPayload)371{372Parser parser(Parser::ConfigMode::IMU);373auto in = default_measurement(Parser::MeasurementType::IMU);374uint8_t packet[38];375size_t len = 38;376fill_simulated_packet(packet, len, in, Parser::ConfigMode::IMU);377packet[5] = 0xFA;378uint8_t checksum = 0;379for (size_t i = 1; i < len - 1; ++i) {380checksum ^= packet[i];381}382packet[len - 1] = checksum;383uint32_t start_valid = parser.get_valid_packets();384385Parser::Measurement meas;386for (size_t i = 0; i < len; i++) {387parser.parse_stream(&packet[i], 1, meas);388}389390EXPECT_EQ(parser.get_valid_packets(), start_valid + 1);391}392393TEST(SensAItionParser, Legacy_FragmentedHeaderRecovery)394{395Parser parser(Parser::ConfigMode::IMU);396auto in = default_measurement(Parser::MeasurementType::IMU);397uint8_t valid[38];398size_t len = 38;399fill_simulated_packet(valid, len, in, Parser::ConfigMode::IMU);400uint8_t stream[120];401size_t slen = 0;402stream[slen++] = 0xFA; stream[slen++] = 0x00;403memcpy(&stream[slen], valid, 38);404slen += 38;405memcpy(&stream[slen], valid, 38);406slen += 38;407uint32_t start_valid = parser.get_valid_packets();408409Parser::Measurement meas;410for (size_t i = 0; i < slen; i++) {411parser.parse_stream(&stream[i], 1, meas);412}413414EXPECT_GE(parser.get_valid_packets() - start_valid, 1u);415}416417// ---------------------------------------------------------------------------418// INTERLEAVED MODE TESTS419// ---------------------------------------------------------------------------420421TEST(SensAItionParser, Interleaved_IMU_HappyPath)422{423Parser parser(Parser::ConfigMode::INTERLEAVED_INS);424auto in = default_measurement(Parser::MeasurementType::IMU);425in.acceleration_mss.x = 2.5f;426uint8_t buffer[64];427size_t len = 64;428fill_simulated_packet(buffer, len, in, Parser::ConfigMode::INTERLEAVED_INS);429EXPECT_EQ(len, 39u);430431Parser::Measurement meas;432const auto parsed_bytes = parser.parse_stream(buffer, len, meas);433434EXPECT_EQ(parsed_bytes, 39u);435EXPECT_EQ(meas.type, Parser::MeasurementType::IMU);436EXPECT_NEAR(meas.acceleration_mss.x, 2.5f, 0.01f);437EXPECT_TRUE(cmp_packages(in, meas));438}439440TEST(SensAItionParser, Interleaved_INS_HappyPath)441{442Parser parser(Parser::ConfigMode::INTERLEAVED_INS);443auto in = default_measurement(Parser::MeasurementType::INS);444uint8_t buffer[100];445size_t len = 100;446fill_simulated_packet(buffer, len, in, Parser::ConfigMode::INTERLEAVED_INS);447EXPECT_EQ(len, 72u); // Verified448449Parser::Measurement meas;450parser.parse_stream(buffer, len, meas);451452EXPECT_EQ(meas.type, Parser::MeasurementType::INS);453EXPECT_TRUE(cmp_packages(in, meas));454}455456TEST(SensAItionParser, Interleaved_InvalidID)457{458Parser parser(Parser::ConfigMode::INTERLEAVED_INS);459uint8_t bad[] = { 0xFA, 0x99, 0x00, 0x00 };460uint32_t start_err = parser.get_parse_errors();461462Parser::Measurement meas;463parser.parse_stream(bad, 4, meas);464465EXPECT_GT(parser.get_parse_errors(), start_err);466}467468TEST(SensAItionParser, Handle_Interleaved_Packets)469{470Parser parser(Parser::ConfigMode::INTERLEAVED_INS);471472// Create a buffer with one INS and IMU packet back-to-back473uint8_t stream[200];474size_t len = 0;475476// Create INS Packet477auto m_ins = default_measurement(Parser::MeasurementType::INS);478m_ins.location.lat = 123456789; // Unique marker479size_t ins_len = 0;480fill_simulated_packet(&stream[0], ins_len, m_ins, Parser::ConfigMode::INTERLEAVED_INS);481len += ins_len;482483// Create IMU Packet immediately after484auto m_imu = default_measurement(Parser::MeasurementType::IMU);485m_imu.acceleration_mss.z = -15.0f; // Unique marker486size_t imu_len = 0;487fill_simulated_packet(&stream[len], imu_len, m_imu, Parser::ConfigMode::INTERLEAVED_INS);488len += imu_len;489490// Parse first packet491Parser::Measurement meas;492auto parsed_bytes = parser.parse_stream(stream, len, meas);493494EXPECT_EQ(parsed_bytes, ins_len);495EXPECT_EQ(meas.type, Parser::MeasurementType::INS);496EXPECT_TRUE(cmp_packages(m_ins, meas));497498// Parse second packet499parsed_bytes = parser.parse_stream(&stream[parsed_bytes], len - parsed_bytes, meas);500EXPECT_EQ(parsed_bytes, imu_len);501EXPECT_EQ(meas.type, Parser::MeasurementType::IMU);502EXPECT_TRUE(cmp_packages(m_imu, meas));503}504505TEST(SensAItionParser, Interleaved_MixedStream_Transitions)506{507Parser parser(Parser::ConfigMode::INTERLEAVED_INS);508auto m_imu = default_measurement(Parser::MeasurementType::IMU);509auto m_ahrs = default_measurement(Parser::MeasurementType::AHRS);510auto m_ins = default_measurement(Parser::MeasurementType::INS);511uint8_t stream[200];512size_t len = 0;513size_t part_len;514515part_len = 200 - len;516fill_simulated_packet(&stream[len], part_len, m_imu, Parser::ConfigMode::INTERLEAVED_INS);517len += part_len;518part_len = 200 - len;519fill_simulated_packet(&stream[len], part_len, m_ahrs, Parser::ConfigMode::INTERLEAVED_INS);520len += part_len;521part_len = 200 - len;522fill_simulated_packet(&stream[len], part_len, m_ins, Parser::ConfigMode::INTERLEAVED_INS);523len += part_len;524525Parser::Measurement meas;526auto parsed_bytes = parser.parse_stream(stream, len, meas);527EXPECT_TRUE(cmp_packages(m_imu, meas));528529parsed_bytes += parser.parse_stream(&stream[parsed_bytes], len - parsed_bytes, meas);530EXPECT_TRUE(cmp_packages(m_ahrs, meas));531532parsed_bytes += parser.parse_stream(&stream[parsed_bytes], len - parsed_bytes, meas);533EXPECT_EQ(parsed_bytes, len);534EXPECT_TRUE(cmp_packages(m_ins, meas));535}536537TEST(SensAItionParser, Interleaved_INS_Fragmentation)538{539Parser parser(Parser::ConfigMode::INTERLEAVED_INS);540auto in = default_measurement(Parser::MeasurementType::INS);541uint8_t packet[100];542size_t len = 100;543fill_simulated_packet(packet, len, in, Parser::ConfigMode::INTERLEAVED_INS);544545Parser::Measurement meas;546// Parse all but one byte547auto parsed_bytes = parser.parse_stream(packet, len - 1, meas);548EXPECT_EQ(meas.type, Parser::MeasurementType::UNINITIALIZED);549550// Then parse the finishing byte of the message551parsed_bytes += parser.parse_stream(&packet[parsed_bytes], len - parsed_bytes, meas);552EXPECT_EQ(meas.type, Parser::MeasurementType::INS);553}554555TEST(SensAItionParser, Interleaved_Data_StressTest)556{557Parser parser(Parser::ConfigMode::INTERLEAVED_INS);558auto in = default_measurement(Parser::MeasurementType::INS);559in.location.lat = -593293230;560in.velocity_ned.x = -15.5f;561uint8_t buffer[100];562size_t len = 100;563fill_simulated_packet(buffer, len, in, Parser::ConfigMode::INTERLEAVED_INS);564565Parser::Measurement meas;566parser.parse_stream(buffer, len, meas);567EXPECT_EQ(meas.location.lat, -593293230);568EXPECT_NEAR(meas.velocity_ned.x, -15.5f, 0.01f);569}570571TEST(SensAItionParser, Interleaved_NoiseRecovery)572{573Parser parser(Parser::ConfigMode::INTERLEAVED_INS);574auto in = default_measurement(Parser::MeasurementType::INS);575uint8_t valid[100];576size_t len = 100;577fill_simulated_packet(valid, len, in, Parser::ConfigMode::INTERLEAVED_INS);578uint8_t stream[200];579memset(stream, 0xEE, 20);580memcpy(&stream[20], valid, len);581582Parser::Measurement meas;583parser.parse_stream(stream, len + 20, meas);584585EXPECT_EQ(meas.type, Parser::MeasurementType::INS);586}587588TEST(SensAItionParser, Legacy_IMU_PartialStream)589{590Parser parser(Parser::ConfigMode::IMU);591auto in = default_measurement(Parser::MeasurementType::IMU);592uint8_t packet[100];593memset(packet, 0x00, 100); // Ensure known content after the packet594size_t chunk_size = 5;595size_t len = 20 * chunk_size;596fill_simulated_packet(packet, len, in, Parser::ConfigMode::IMU);597598Parser::Measurement meas;599for (size_t i = 0; i < len; i += chunk_size) {600parser.parse_stream(&packet[i], chunk_size, meas);601}602603EXPECT_EQ(meas.type, Parser::MeasurementType::IMU);604}605606TEST(SensAItionParser, Interleaved_INS_FullFieldVerification)607{608Parser parser(Parser::ConfigMode::INTERLEAVED_INS);609auto in = default_measurement(Parser::MeasurementType::INS);610611// Setup Distinct Values612in.num_sats_gnss1 = 22;613in.num_sats_gnss2 = 18;614in.error_flags = 0xCAFEBABE;615in.sensor_valid = true;616in.location.lat = -593293230;617in.location.lng = 180685810;618in.location.alt = 1500; // cm619in.velocity_ned = Vector3f(-5.5f, 2.2f, 0.5f);620in.alignment_status = 1;621in.time_itow_ms = 987654321;622in.gnss1_fix = 3;623in.gnss2_fix = 2;624625// Time -> Week Calculation Check626// 2025-12-10 is GPS Week 2396627in.year = 2025;628in.month = 12;629in.day = 10;630uint16_t expected_week = 2396;631632// Vector Accuracy633in.pos_accuracy = Vector3f(0.1f, 0.2f, 0.3f); // Lat, Lon, Alt634in.vel_accuracy = Vector3f(0.4f, 0.5f, 0.6f); // N, E, D635636// Generate & Parse637uint8_t buffer[100];638size_t len = 100;639fill_simulated_packet(buffer, len, in, Parser::ConfigMode::INTERLEAVED_INS);640641Parser::Measurement meas;642parser.parse_stream(buffer, len, meas);643644// VERIFICATION645auto& out = meas;646EXPECT_EQ(out.type, Parser::MeasurementType::INS);647EXPECT_EQ(out.num_sats_gnss1, in.num_sats_gnss1);648EXPECT_EQ(out.num_sats_gnss2, in.num_sats_gnss2);649EXPECT_EQ(out.error_flags, in.error_flags);650EXPECT_EQ(out.sensor_valid, in.sensor_valid);651652EXPECT_EQ(out.location.lat, in.location.lat);653EXPECT_EQ(out.location.lng, in.location.lng);654EXPECT_EQ(out.location.alt, in.location.alt);655EXPECT_NEAR(out.velocity_ned.x, in.velocity_ned.x, 0.001f);656657EXPECT_EQ(out.alignment_status, in.alignment_status);658EXPECT_EQ(out.time_itow_ms, in.time_itow_ms);659EXPECT_EQ(out.gnss1_fix, in.gnss1_fix);660EXPECT_EQ(out.gnss2_fix, in.gnss2_fix);661662// Week Verify663EXPECT_EQ(out.gps_week, expected_week) << "GPS Week Calc Failed";664665// Vector Verify666EXPECT_NEAR(out.pos_accuracy.x, in.pos_accuracy.x, 0.001f);667EXPECT_NEAR(out.pos_accuracy.y, in.pos_accuracy.y, 0.001f);668EXPECT_NEAR(out.pos_accuracy.z, in.pos_accuracy.z, 0.001f);669670EXPECT_NEAR(out.vel_accuracy.x, in.vel_accuracy.x, 0.001f);671EXPECT_NEAR(out.vel_accuracy.y, in.vel_accuracy.y, 0.001f);672EXPECT_NEAR(out.vel_accuracy.z, in.vel_accuracy.z, 0.001f);673}674675TEST(SensAItionParser, GPS_Week_Calculation_EdgeCases)676{677Parser parser(Parser::ConfigMode::INTERLEAVED_INS);678auto in = default_measurement(Parser::MeasurementType::INS);679uint8_t buffer[100];680size_t len = 100;681Parser::Measurement meas;682683// CASE 1: Leap Year (Feb 29 2024)684// 2024-02-29 -> Week 2303685in.year = 2024; in.month = 2; in.day = 29;686fill_simulated_packet(buffer, len, in, Parser::ConfigMode::INTERLEAVED_INS);687parser.parse_stream(buffer, len, meas);688EXPECT_EQ(meas.gps_week, 2303) << "Failed Leap Year Calc";689690// CASE 2: No Fix -> Week Should be 0691in.gnss1_fix = 0; // Lost fix692in.year = 2025; in.month = 1; in.day = 1;693fill_simulated_packet(buffer, len, in, Parser::ConfigMode::INTERLEAVED_INS);694parser.parse_stream(buffer, len, meas);695EXPECT_EQ(meas.gps_week, 0) << "Week should be 0 when fix is lost";696697// CASE 3: Year 2000 (translates to 00 in BCD format) should be correctly handled698in = default_measurement(Parser::MeasurementType::INS);699in.year = 2000; in.month = 1; in.day = 31;700fill_simulated_packet(buffer, len, in, Parser::ConfigMode::INTERLEAVED_INS);701parser.parse_stream(buffer, len, meas);702EXPECT_EQ(meas.gps_week, 1047) << "Failed converting the year 2000 to GPS week";703}704705AP_GTEST_MAIN()706707708