Files
Ashin Walpola d107534eb2 Keep vanetza-idf in obu-firmware, so a plain clone builds the firmware
obu-firmware builds against the vanetza-idf C-ITS library, which until now
came from the colleague's microbu-esp32c5 tree beside the repository and was
not tracked here, so a clone of this repository could not build the firmware
it ships. The library alone is now part of obu-firmware, as
obu-firmware/external/vanetza-idf: their external/vanetza-idf at commit
cf4b99f, unchanged (9775 files; see its PROVENANCE.md). CMake takes it from
there by default; -DVANETZA_IDF_DIR still points the build elsewhere.

The rest of the colleague's tree (their own VAM firmware, PKI tooling,
station-link Python tools, the V2X2MAP bridge) stays out of this repository
and gitignored; nothing is pushed to their repository. NOTES.md, docs/06,
TODO.md and the pcap verifier's usage line point at the new location.
2026-09-24 10:56:05 +02:00

341 lines
12 KiB
C++

#include <gtest/gtest.h>
#include <vanetza/facilities/path_history.hpp>
#include <vanetza/asn1/type_traits.hpp>
#include <vanetza/asn1/its/Heading.h>
#include <vanetza/asn1/its/PathHistory.h>
#include <vanetza/asn1/its/ReferencePosition.h>
#include <vanetza/asn1/its/r2/Heading.h>
#include <vanetza/asn1/its/r2/PathHistory.h>
#include <vanetza/asn1/its/r2/ReferencePosition.h>
#include <vanetza/facilities/cam_functions.hpp>
#include <boost/units/cmath.hpp>
#include <boost/units/io.hpp>
#include <cmath>
namespace vanetza
{
namespace asn1
{
template<> struct asn1_type_traits<::PathHistory>
{
static asn_TYPE_descriptor_t& descriptor() { return ::asn_DEF_PathHistory; }
};
template<> struct asn1_type_traits<::Vanetza_ITS2_PathHistory>
{
static asn_TYPE_descriptor_t& descriptor() { return ::asn_DEF_Vanetza_ITS2_PathHistory; }
};
} // namespace asn1
} // namespace vanetza
using namespace vanetza;
using namespace vanetza::facilities;
using namespace vanetza::units;
constexpr long latitude(double degree, double arc_minute)
{
return std::round(1e7 * (degree + arc_minute / 60.0));
}
constexpr long longitude(double degree, double arc_minute)
{
return std::round(1e7 * (degree + arc_minute / 60.0));
}
::testing::AssertionResult NearDistance(const Length& a, const Length& b, Length delta)
{
using namespace boost::units;
const auto diff = abs(a - b);
if (diff < delta) {
return ::testing::AssertionSuccess();
} else {
return ::testing::AssertionFailure() << "actual difference " << diff << " exceeds delta of " << delta;
}
}
using HeadingTypes = ::testing::Types<Heading, Vanetza_ITS2_Heading>;
template<typename T>
class CamFunctionsHeading : public ::testing::Test
{
};
TYPED_TEST_SUITE(CamFunctionsHeading, HeadingTypes);
TEST(CamFunctionsHeading, similar_heading)
{
Angle a = 3 * si::radian;
Angle b = 2 * si::radian;
Angle limit = 0.5 * si::radian;
EXPECT_FALSE(similar_heading(a, b, limit));
EXPECT_FALSE(similar_heading(b, a, limit));
limit = 1.0 * si::radian;
EXPECT_TRUE(similar_heading(a, b, limit));
EXPECT_TRUE(similar_heading(b, a, limit));
a = 6.1 * si::radian;
b = 0.2 * si::radian;
limit = 0.4 * si::radian;
EXPECT_TRUE(similar_heading(a, b, limit));
EXPECT_TRUE(similar_heading(b, a, limit));
limit = 0.3 * si::radian;
EXPECT_FALSE(similar_heading(a, b, limit));
EXPECT_FALSE(similar_heading(b, a, limit));
limit = -1.0 * si::radian;
EXPECT_FALSE(similar_heading(a, a, limit));
}
TYPED_TEST(CamFunctionsHeading, similar_heading_unavailable1)
{
using SomeHeading = TypeParam;
SomeHeading a;
a.headingValue = HeadingValue_unavailable;
Angle b = 2.0 * si::radian;
Angle limit = 10 * si::radian;
EXPECT_FALSE(is_available(a));
EXPECT_FALSE(similar_heading(a, b, limit));
a.headingValue = 2 * HeadingValue_wgs84East;
EXPECT_TRUE(is_available(a));
EXPECT_TRUE(similar_heading(a, b, limit));
b = 0.0 * si::radian;
limit = 3.14 * si::radian;
EXPECT_FALSE(similar_heading(a, b, limit));
limit = 3.15 * si::radian;
EXPECT_TRUE(similar_heading(a, b, limit));
}
TYPED_TEST(CamFunctionsHeading, similar_heading_unavailable2)
{
using SomeHeading = TypeParam;
SomeHeading a;
a.headingValue = HeadingValue_unavailable;
SomeHeading b;
b.headingValue = HeadingValue_unavailable;
Angle limit = 10 * si::radian;
EXPECT_FALSE(is_available(a));
EXPECT_FALSE(is_available(b));
EXPECT_FALSE(similar_heading(a, b, limit));
b.headingValue = 200;
EXPECT_TRUE(is_available(b));
EXPECT_FALSE(similar_heading(a, b, limit));
EXPECT_FALSE(similar_heading(b, a, limit));
a.headingValue = 300;
EXPECT_TRUE(is_available(a));
EXPECT_TRUE(similar_heading(a, b, limit));
EXPECT_TRUE(similar_heading(b, a, limit));
}
using ReferencePositionTypes = ::testing::Types<ReferencePosition_t, Vanetza_ITS2_ReferencePosition_t>;
template<typename T>
class CamFunctionsReferencePosition : public ::testing::Test
{
};
TYPED_TEST_SUITE(CamFunctionsReferencePosition, ReferencePositionTypes);
TYPED_TEST(CamFunctionsReferencePosition, distance_reference_positions)
{
using SomeReferencePosition = TypeParam;
SomeReferencePosition pos1;
pos1.latitude = -latitude(6, 21.23);
pos1.longitude = -longitude(33, 22.12);
SomeReferencePosition pos2;
pos2.latitude = -latitude(6, 22.48);
pos2.longitude = -longitude(33, 22.55);
EXPECT_TRUE(NearDistance(distance(pos1, pos2), 2440.0 * si::meter , 10.0 * si::meter));
SomeReferencePosition pos3;
pos3.latitude = latitude(37, 17.3);
pos3.longitude = -longitude(0, 13.14);
SomeReferencePosition pos4;
pos4.latitude = latitude(37, 17.19);
pos4.longitude = longitude(0, 9.45);
EXPECT_TRUE(NearDistance(distance(pos3, pos4), 33390.0 * si::meter , 100.0 * si::meter));
SomeReferencePosition pos5;
pos5.latitude = -latitude(0, 19.24);
pos5.longitude = longitude(83, 37.32);
SomeReferencePosition pos6;
pos6.latitude = latitude(0, 27.15);
pos6.longitude = longitude(83, 04.45);
EXPECT_TRUE(NearDistance(distance(pos5, pos6), 105010.0 * si::meter , 300.0 * si::meter));
SomeReferencePosition pos7;
pos7.latitude = latitude(48, 45.56);
pos7.longitude = longitude(11, 26.01);
SomeReferencePosition pos8;
pos8.latitude = latitude(48, 45.566);
pos8.longitude = longitude(11, 26.04);
EXPECT_TRUE(NearDistance(distance(pos7, pos8), 38.0 * si::meter , 0.5 * si::meter));
}
TYPED_TEST(CamFunctionsReferencePosition, distance_refpos_latlon)
{
using SomeReferencePosition = TypeParam;
SomeReferencePosition refpos;
refpos.latitude = -latitude(6, 21.23);
refpos.longitude = -longitude(33, 22.12);
GeoAngle lat = -(6 + (22.48 / 60.0)) * degree;
GeoAngle lon = -(33 + (22.55 / 60.0)) * degree;
EXPECT_TRUE(NearDistance(distance(refpos, lat, lon), 2440.0 * si::meter , 10.0 * si::meter));
}
TYPED_TEST(CamFunctionsReferencePosition, distance_unavailable)
{
using SomeReferencePosition = TypeParam;
SomeReferencePosition pos1 {};
SomeReferencePosition pos2 {};
EXPECT_TRUE(is_available(pos1));
EXPECT_TRUE(is_available(pos2));
EXPECT_FALSE(std::isnan(distance(pos1, pos2).value()));
pos1.latitude = Latitude_unavailable;
EXPECT_FALSE(is_available(pos1));
EXPECT_TRUE(std::isnan(distance(pos1, pos2).value()));
EXPECT_TRUE(std::isnan(distance(pos2, pos1).value()));
pos1.latitude = 0;
pos1.longitude = Longitude_unavailable;
EXPECT_FALSE(is_available(pos1));
EXPECT_TRUE(std::isnan(distance(pos1, pos2).value()));
EXPECT_TRUE(std::isnan(distance(pos2, pos1).value()));
}
TYPED_TEST(CamFunctionsReferencePosition, copy)
{
using SomeReferencePosition = TypeParam;
PositionFix src;
src.latitude = 1.23 * vanetza::units::degree;
src.longitude = 4.56 * vanetza::units::degree;
src.confidence.orientation = vanetza::units::TrueNorth::from_value(10.0);
src.confidence.semi_major = 20 * vanetza::units::si::meter;
src.confidence.semi_minor = 15 * vanetza::units::si::meter;
SomeReferencePosition dest;
copy(src, dest);
EXPECT_EQ(dest.latitude, 123 * 100000);
EXPECT_EQ(dest.longitude, 456 * 100000);
EXPECT_EQ(dest.positionConfidenceEllipse.semiMajorConfidence, 20 * 100);
EXPECT_EQ(dest.positionConfidenceEllipse.semiMinorConfidence, 15 * 100);
EXPECT_EQ(dest.positionConfidenceEllipse.semiMajorOrientation, 10 * 10);
EXPECT_EQ(dest.altitude.altitudeValue, AltitudeValue_unavailable);
EXPECT_EQ(dest.altitude.altitudeConfidence, AltitudeConfidence_unavailable);
}
using PathHistoryTypes = ::testing::Types<::PathHistory, Vanetza_ITS2_PathHistory>;
template<typename T>
class CamFunctionsPathHistory : public ::testing::Test
{
protected:
vanetza::facilities::PathHistory path_history;
void add_sample(double lat, double lon, const std::string& time)
{
vanetza::facilities::PathPoint path_point;
path_point.latitude = lat * degree;
path_point.longitude = lon * degree;
path_point.time = boost::posix_time::from_iso_string(time);
path_history.addSample(path_point);
}
};
TYPED_TEST_SUITE(CamFunctionsPathHistory, PathHistoryTypes);
TYPED_TEST(CamFunctionsPathHistory, copy_path_history)
{
using SomePathHistory = TypeParam;
this->add_sample(40.906, 29.155, "20241027T031000");
this->add_sample(40.907, 29.156, "20241027T031010");
this->add_sample(40.908, 29.157, "20241027T031020");
SomePathHistory dest_path_history = {}; // zero-initialize struct
copy(this->path_history, dest_path_history);
int size = dest_path_history.list.count;
EXPECT_EQ(size, 2);
// incremental encoding: each point relative to the previous
EXPECT_EQ(dest_path_history.list.array[0]->pathPosition.deltaLatitude,
dest_path_history.list.array[1]->pathPosition.deltaLatitude);
EXPECT_EQ(*dest_path_history.list.array[0]->pathDeltaTime, 1000); // 10 s
EXPECT_EQ(*dest_path_history.list.array[1]->pathDeltaTime, 1000); // 10 s
for (int i = 0; i < size; i++) {
auto current_path_point = dest_path_history.list.array[i];
ASSERT_NE(current_path_point->pathDeltaTime, nullptr);
// check ASN.1 constraints
EXPECT_GE(*current_path_point->pathDeltaTime, 1);
EXPECT_LE(*current_path_point->pathDeltaTime, 65535);
EXPECT_GE(current_path_point->pathPosition.deltaLatitude, -131071);
EXPECT_LE(current_path_point->pathPosition.deltaLatitude, 131072);
EXPECT_GE(current_path_point->pathPosition.deltaLongitude, -131071);
EXPECT_LE(current_path_point->pathPosition.deltaLongitude, 131072);
EXPECT_GE(current_path_point->pathPosition.deltaAltitude, -12700);
EXPECT_LE(current_path_point->pathPosition.deltaAltitude, 12800);
}
asn1::reset(dest_path_history);
}
TYPED_TEST(CamFunctionsPathHistory, copy_clamps_stationary_delta_time)
{
using SomePathHistory = TypeParam;
this->add_sample(40.906, 29.155, "20241027T031000");
this->add_sample(40.907, 29.156, "20241027T031010");
this->add_sample(40.908, 29.157, "20241027T031020");
// parked ~20 min at the last position: reference far past the max PathDeltaTime
this->add_sample(40.908, 29.157, "20241027T033000");
SomePathHistory dest = {}; // zero-initialize struct
copy(this->path_history, dest);
ASSERT_EQ(dest.list.count, 3); // whole trace kept alive
ASSERT_NE(dest.list.array[0]->pathDeltaTime, nullptr);
EXPECT_EQ(*dest.list.array[0]->pathDeltaTime, 65535); // only the first point is clamped
for (int i = 1; i < dest.list.count; i++) {
ASSERT_NE(dest.list.array[i]->pathDeltaTime, nullptr);
EXPECT_LT(*dest.list.array[i]->pathDeltaTime, 65535); // later points keep their short gaps
}
asn1::reset(dest);
}
TYPED_TEST(CamFunctionsPathHistory, copy_truncates_after_long_stop)
{
using SomePathHistory = TypeParam;
// moved, parked ~21 min, then resumed: the stop is a >655 s gap in the middle
this->add_sample(40.9000, 29.1500, "20241027T031000"); // pre-stop trail
this->add_sample(40.9005, 29.1505, "20241027T031010");
this->add_sample(40.9010, 29.1510, "20241027T031020");
this->add_sample(40.9015, 29.1515, "20241027T033100"); // resumed ~21 min later
this->add_sample(40.9020, 29.1520, "20241027T033110");
this->add_sample(40.9025, 29.1525, "20241027T033120");
SomePathHistory dest = {}; // zero-initialize struct
copy(this->path_history, dest);
// trail is cut at the discontinuity, not carried across it with a clamped mid gap
ASSERT_GT(dest.list.count, 0);
for (int i = 0; i < dest.list.count; i++) {
ASSERT_NE(dest.list.array[i]->pathDeltaTime, nullptr);
EXPECT_LT(*dest.list.array[i]->pathDeltaTime, 65535);
}
asn1::reset(dest);
}