mirror of
https://github.com/meshtastic/firmware.git
synced 2025-07-30 02:15:41 +00:00
Compare commits
3 Commits
6d678a5cae
...
35061c2d58
Author | SHA1 | Date | |
---|---|---|---|
![]() |
35061c2d58 | ||
![]() |
b5a8e8f51b | ||
![]() |
35c7390ff9 |
@ -224,9 +224,10 @@ ErrorCode Router::send(meshtastic_MeshPacket *p)
|
||||
if (!config.lora.override_duty_cycle && myRegion->dutyCycle < 100) {
|
||||
float hourlyTxPercent = airTime->utilizationTXPercent();
|
||||
if (hourlyTxPercent > myRegion->dutyCycle) {
|
||||
#ifdef DEBUG_PORT
|
||||
uint8_t silentMinutes = airTime->getSilentMinutes(hourlyTxPercent, myRegion->dutyCycle);
|
||||
|
||||
LOG_WARN("Duty cycle limit exceeded. Aborting send for now, you can send again in %d mins", silentMinutes);
|
||||
|
||||
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
|
||||
cn->has_reply_id = true;
|
||||
cn->reply_id = p->id;
|
||||
@ -234,7 +235,7 @@ ErrorCode Router::send(meshtastic_MeshPacket *p)
|
||||
cn->time = getValidTime(RTCQualityFromNet);
|
||||
sprintf(cn->message, "Duty cycle limit exceeded. You can send again in %d mins", silentMinutes);
|
||||
service->sendClientNotification(cn);
|
||||
#endif
|
||||
|
||||
meshtastic_Routing_Error err = meshtastic_Routing_Error_DUTY_CYCLE_LIMIT;
|
||||
if (isFromUs(p)) { // only send NAK to API, not to the mesh
|
||||
abortSendAndNak(err, p);
|
||||
|
@ -53,6 +53,7 @@ meshtastic_PositionLite TypeConversions::ConvertToPositionLite(meshtastic_Positi
|
||||
lite.altitude = position.altitude;
|
||||
lite.location_source = position.location_source;
|
||||
lite.time = position.time;
|
||||
lite.precision_bits = position.precision_bits;
|
||||
|
||||
return lite;
|
||||
}
|
||||
@ -71,6 +72,7 @@ meshtastic_Position TypeConversions::ConvertToPosition(meshtastic_PositionLite l
|
||||
position.altitude = lite.altitude;
|
||||
position.location_source = lite.location_source;
|
||||
position.time = lite.time;
|
||||
position.precision_bits = lite.precision_bits;
|
||||
|
||||
return position;
|
||||
}
|
||||
|
@ -33,6 +33,8 @@ typedef struct _meshtastic_PositionLite {
|
||||
uint32_t time;
|
||||
/* TODO: REPLACE */
|
||||
meshtastic_Position_LocSource location_source;
|
||||
/* Indicates the bits of precision set by the sending node */
|
||||
uint32_t precision_bits;
|
||||
} meshtastic_PositionLite;
|
||||
|
||||
typedef PB_BYTES_ARRAY_T(32) meshtastic_UserLite_public_key_t;
|
||||
|
@ -41,6 +41,30 @@ PositionModule::PositionModule()
|
||||
}
|
||||
}
|
||||
|
||||
bool PositionModule::shouldUpdatePosition(const meshtastic_PositionLite &existingPos, const meshtastic_Position &incomingPos)
|
||||
{
|
||||
// Assume precision is 32 if not set (older firmware compatibility)
|
||||
uint32_t existingPrecision = existingPos.precision_bits ? existingPos.precision_bits : 32;
|
||||
uint32_t incomingPrecision = incomingPos.precision_bits ? incomingPos.precision_bits : 32;
|
||||
|
||||
// Use the lower precision for position comparison
|
||||
uint32_t minPrecision = (incomingPrecision < existingPrecision) ? incomingPrecision : existingPrecision;
|
||||
|
||||
int32_t degradedExistingLat = existingPos.latitude_i & (0xFFFFFFFF << (32 - minPrecision));
|
||||
int32_t degradedExistingLon = existingPos.longitude_i & (0xFFFFFFFF << (32 - minPrecision));
|
||||
|
||||
int32_t degradedIncomingLat = incomingPos.latitude_i & (0xFFFFFFFF << (32 - minPrecision));
|
||||
int32_t degradedIncomingLon = incomingPos.longitude_i & (0xFFFFFFFF << (32 - minPrecision));
|
||||
|
||||
if (degradedExistingLat != degradedIncomingLat || degradedExistingLon != degradedIncomingLon) {
|
||||
// Position changed - always update regardless of precision
|
||||
return true;
|
||||
} else {
|
||||
// Same position - only update if precision is equal or higher
|
||||
return incomingPrecision >= existingPrecision;
|
||||
}
|
||||
}
|
||||
|
||||
bool PositionModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Position *pptr)
|
||||
{
|
||||
auto p = *pptr;
|
||||
@ -92,7 +116,27 @@ bool PositionModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes
|
||||
trySetRtc(p, isLocal, force);
|
||||
}
|
||||
|
||||
nodeDB->updatePosition(getFrom(&mp), p);
|
||||
// Smart position update logic with precision awareness:
|
||||
// Only update if position has actually changed to avoid unnecessary mesh traffic
|
||||
bool shouldUpdate = true;
|
||||
|
||||
meshtastic_NodeInfoLite *existingNode = nodeDB->getMeshNode(getFrom(&mp));
|
||||
if (existingNode && existingNode->has_position) {
|
||||
// Use precision-aware position comparison directly with PositionLite
|
||||
shouldUpdate = shouldUpdatePosition(existingNode->position, p);
|
||||
|
||||
if (shouldUpdate) {
|
||||
LOG_DEBUG("Accept position update: precision-aware logic approved (existing precision: %d, incoming: %d)",
|
||||
existingNode->position.precision_bits, p.precision_bits);
|
||||
} else {
|
||||
LOG_DEBUG("Reject position update: precision-aware logic rejected (preserving higher precision data)");
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldUpdate) {
|
||||
nodeDB->updatePosition(getFrom(&mp), p);
|
||||
}
|
||||
|
||||
if (channels.getByIndex(mp.channel).settings.has_module_settings) {
|
||||
precision = channels.getByIndex(mp.channel).settings.module_settings.position_precision;
|
||||
} else if (channels.getByIndex(mp.channel).role == meshtastic_Channel_Role_PRIMARY) {
|
||||
@ -338,7 +382,7 @@ void PositionModule::sendOurPosition()
|
||||
if (channels.getByIndex(channelNum).settings.has_module_settings &&
|
||||
channels.getByIndex(channelNum).settings.module_settings.position_precision != 0) {
|
||||
sendOurPosition(NODENUM_BROADCAST, requestReplies, channelNum);
|
||||
return;
|
||||
// Continue to next channel instead of returning
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -38,6 +38,14 @@ class PositionModule : public ProtobufModule<meshtastic_Position>, private concu
|
||||
|
||||
void handleNewPosition();
|
||||
|
||||
/**
|
||||
* Precision-aware position update logic for smart filtering
|
||||
* @param existingPos Existing position with latitude, longitude, and precision
|
||||
* @param incomingPos Incoming position with latitude, longitude, and precision
|
||||
* @return true if position should be updated, false otherwise
|
||||
*/
|
||||
static bool shouldUpdatePosition(const meshtastic_PositionLite &existingPos, const meshtastic_Position &incomingPos);
|
||||
|
||||
protected:
|
||||
/** Called to handle a particular incoming message
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
meshtastic_MeshPacket *ReplyModule::allocReply()
|
||||
{
|
||||
assert(currentRequest); // should always be !NULL
|
||||
#ifdef DEBUG_PORT
|
||||
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
|
||||
auto req = *currentRequest;
|
||||
auto &p = req.decoded;
|
||||
// The incoming message is in p.payload
|
||||
|
@ -121,7 +121,7 @@ int32_t AirQualityTelemetryModule::runOnce()
|
||||
bool AirQualityTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t)
|
||||
{
|
||||
if (t->which_variant == meshtastic_Telemetry_air_quality_metrics_tag) {
|
||||
#ifdef DEBUG_PORT
|
||||
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
|
||||
const char *sender = getSenderShortName(mp);
|
||||
|
||||
LOG_INFO("(Received from %s): pm10_standard=%i, pm25_standard=%i, pm100_standard=%i", sender,
|
||||
|
@ -49,7 +49,7 @@ bool DeviceTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &
|
||||
return false;
|
||||
|
||||
if (t->which_variant == meshtastic_Telemetry_device_metrics_tag) {
|
||||
#ifdef DEBUG_PORT
|
||||
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
|
||||
const char *sender = getSenderShortName(mp);
|
||||
|
||||
LOG_INFO("(Received from %s): air_util_tx=%f, channel_utilization=%f, battery_level=%i, voltage=%f", sender,
|
||||
|
@ -502,7 +502,7 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt
|
||||
bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t)
|
||||
{
|
||||
if (t->which_variant == meshtastic_Telemetry_environment_metrics_tag) {
|
||||
#ifdef DEBUG_PORT
|
||||
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
|
||||
const char *sender = getSenderShortName(mp);
|
||||
|
||||
LOG_INFO("(Received from %s): barometric_pressure=%f, current=%f, gas_resistance=%f, relative_humidity=%f, "
|
||||
|
@ -149,7 +149,7 @@ void HealthTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *
|
||||
bool HealthTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t)
|
||||
{
|
||||
if (t->which_variant == meshtastic_Telemetry_health_metrics_tag) {
|
||||
#ifdef DEBUG_PORT
|
||||
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
|
||||
const char *sender = getSenderShortName(mp);
|
||||
|
||||
LOG_INFO("(Received from %s): temperature=%f, heart_bpm=%d, spO2=%d,", sender, t->variant.health_metrics.temperature,
|
||||
|
@ -27,7 +27,7 @@ bool HostMetricsModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp,
|
||||
return false;
|
||||
|
||||
if (t->which_variant == meshtastic_Telemetry_host_metrics_tag) {
|
||||
#ifdef DEBUG_PORT
|
||||
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
|
||||
const char *sender = getSenderShortName(mp);
|
||||
if (t->variant.host_metrics.has_user_string)
|
||||
t->variant.host_metrics.user_string[sizeof(t->variant.host_metrics.user_string) - 1] = '\0';
|
||||
|
@ -168,7 +168,7 @@ void PowerTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *s
|
||||
bool PowerTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t)
|
||||
{
|
||||
if (t->which_variant == meshtastic_Telemetry_power_metrics_tag) {
|
||||
#ifdef DEBUG_PORT
|
||||
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
|
||||
const char *sender = getSenderShortName(mp);
|
||||
|
||||
LOG_INFO("(Received from %s): ch1_voltage=%.1f, ch1_current=%.1f, ch2_voltage=%.1f, ch2_current=%.1f, "
|
||||
|
@ -9,7 +9,7 @@ TextMessageModule *textMessageModule;
|
||||
|
||||
ProcessMessage TextMessageModule::handleReceived(const meshtastic_MeshPacket &mp)
|
||||
{
|
||||
#ifdef DEBUG_PORT
|
||||
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
|
||||
auto &p = mp.decoded;
|
||||
LOG_INFO("Received text msg from=0x%0x, id=0x%x, msg=%.*s", mp.from, mp.id, p.payload.size, p.payload.bytes);
|
||||
#endif
|
||||
|
@ -232,7 +232,7 @@ void TraceRouteModule::appendMyIDandSNR(meshtastic_RouteDiscovery *updated, floa
|
||||
|
||||
void TraceRouteModule::printRoute(meshtastic_RouteDiscovery *r, uint32_t origin, uint32_t dest, bool isTowardsDestination)
|
||||
{
|
||||
#ifdef DEBUG_PORT
|
||||
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
|
||||
std::string route = "Route traced:\n";
|
||||
route += vformat("0x%x --> ", origin);
|
||||
for (uint8_t i = 0; i < r->route_count; i++) {
|
||||
|
@ -16,7 +16,7 @@ WaypointModule *waypointModule;
|
||||
|
||||
ProcessMessage WaypointModule::handleReceived(const meshtastic_MeshPacket &mp)
|
||||
{
|
||||
#ifdef DEBUG_PORT
|
||||
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
|
||||
auto &p = mp.decoded;
|
||||
LOG_INFO("Received waypoint msg from=0x%0x, id=0x%x, msg=%.*s", mp.from, mp.id, p.payload.size, p.payload.bytes);
|
||||
#endif
|
||||
|
@ -32,3 +32,8 @@
|
||||
#define SX126X_DIO1 1001
|
||||
#define SX126X_RESET 1003
|
||||
#define SX126X_BUSY 1004
|
||||
|
||||
#if !defined(DEBUG_MUTE) && !defined(PIO_FRAMEWORK_ARDUINO_NANOLIB_FLOAT_PRINTF)
|
||||
#error \
|
||||
"You MUST enable PIO_FRAMEWORK_ARDUINO_NANOLIB_FLOAT_PRINTF if debug prints are enabled. printf will print uninitialized garbage instead of floats."
|
||||
#endif
|
@ -131,7 +131,7 @@ void initDeepSleep()
|
||||
support busted boards, assume button one was pressed wakeButtons = ((uint64_t)1) << buttons.gpios[0];
|
||||
*/
|
||||
|
||||
#ifdef DEBUG_PORT
|
||||
#if defined(DEBUG_PORT) && !defined(DEBUG_MUTE)
|
||||
// If we booted because our timer ran out or the user pressed reset, send those as fake events
|
||||
RESET_REASON hwReason = rtc_get_reset_reason(0);
|
||||
|
||||
|
255
test/test_position_precision/test_main.cpp
Normal file
255
test/test_position_precision/test_main.cpp
Normal file
@ -0,0 +1,255 @@
|
||||
#include "CryptoEngine.h"
|
||||
#include "TestUtil.h"
|
||||
#include "modules/PositionModule.h"
|
||||
#include "mesh/TypeConversions.h"
|
||||
#include <unity.h>
|
||||
#include <cstdint>
|
||||
|
||||
void setUp(void) {
|
||||
// Called before each test
|
||||
}
|
||||
|
||||
void tearDown(void) {
|
||||
// Called after each test
|
||||
}
|
||||
|
||||
void test_no_existing_data() {
|
||||
// Test with no existing position data - should always update
|
||||
int32_t lat = (int32_t)0x12345678;
|
||||
int32_t lon = (int32_t)0x87654321;
|
||||
|
||||
// Create position structs
|
||||
meshtastic_PositionLite existingPos = meshtastic_PositionLite_init_default;
|
||||
existingPos.latitude_i = 0;
|
||||
existingPos.longitude_i = 0;
|
||||
existingPos.precision_bits = 0;
|
||||
|
||||
meshtastic_Position incomingPos = meshtastic_Position_init_default;
|
||||
incomingPos.latitude_i = lat;
|
||||
incomingPos.longitude_i = lon;
|
||||
incomingPos.precision_bits = 16;
|
||||
|
||||
// Simulate no existing position by testing against invalid coordinates
|
||||
TEST_ASSERT_TRUE(PositionModule::shouldUpdatePosition(existingPos, incomingPos));
|
||||
}
|
||||
|
||||
void test_same_position_different_precision() {
|
||||
// Test: same physical location, different precision levels
|
||||
int32_t lat = (int32_t)0x075BCD15; // 123456789 in hex
|
||||
int32_t lon = (int32_t)0x3ADE68B1; // 987654321 in hex
|
||||
|
||||
// Create PositionLite structs for existing positions
|
||||
meshtastic_PositionLite highPrecPosLite = meshtastic_PositionLite_init_default;
|
||||
highPrecPosLite.latitude_i = lat;
|
||||
highPrecPosLite.longitude_i = lon;
|
||||
highPrecPosLite.precision_bits = 32;
|
||||
|
||||
meshtastic_PositionLite lowPrecPosLite = meshtastic_PositionLite_init_default;
|
||||
lowPrecPosLite.latitude_i = lat;
|
||||
lowPrecPosLite.longitude_i = lon;
|
||||
lowPrecPosLite.precision_bits = 13;
|
||||
|
||||
// Create Position structs for incoming positions
|
||||
meshtastic_Position lowPrecPos = meshtastic_Position_init_default;
|
||||
lowPrecPos.latitude_i = lat;
|
||||
lowPrecPos.longitude_i = lon;
|
||||
lowPrecPos.precision_bits = 13;
|
||||
|
||||
meshtastic_Position highPrecPos = meshtastic_Position_init_default;
|
||||
highPrecPos.latitude_i = lat;
|
||||
highPrecPos.longitude_i = lon;
|
||||
highPrecPos.precision_bits = 32;
|
||||
|
||||
// High precision -> Low precision: should NOT update (preserve high precision)
|
||||
TEST_ASSERT_FALSE(PositionModule::shouldUpdatePosition(highPrecPosLite, lowPrecPos));
|
||||
|
||||
// Low precision -> High precision: should update
|
||||
TEST_ASSERT_TRUE(PositionModule::shouldUpdatePosition(lowPrecPosLite, highPrecPos));
|
||||
|
||||
// Same precision: should update (refreshes timestamp)
|
||||
TEST_ASSERT_TRUE(PositionModule::shouldUpdatePosition(lowPrecPosLite, lowPrecPos));
|
||||
}
|
||||
|
||||
void test_movement_detection() {
|
||||
// Test movement detection with hex coordinates
|
||||
int32_t lat1 = (int32_t)0x12345678;
|
||||
int32_t lon1 = (int32_t)0x87654321;
|
||||
|
||||
// At 8-bit precision, change top byte to ensure detection
|
||||
int32_t lat2 = (int32_t)0x22345678; // Changed from 0x12 to 0x22
|
||||
int32_t lon2 = (int32_t)0x87654321; // Same longitude
|
||||
|
||||
// Create PositionLite structs for existing positions
|
||||
meshtastic_PositionLite pos1HighLite = meshtastic_PositionLite_init_default;
|
||||
pos1HighLite.latitude_i = lat1;
|
||||
pos1HighLite.longitude_i = lon1;
|
||||
pos1HighLite.precision_bits = 32;
|
||||
|
||||
meshtastic_PositionLite pos1LowLite = meshtastic_PositionLite_init_default;
|
||||
pos1LowLite.latitude_i = lat1;
|
||||
pos1LowLite.longitude_i = lon1;
|
||||
pos1LowLite.precision_bits = 8;
|
||||
|
||||
// Create Position structs for incoming positions
|
||||
meshtastic_Position pos2Low = meshtastic_Position_init_default;
|
||||
pos2Low.latitude_i = lat2;
|
||||
pos2Low.longitude_i = lon2;
|
||||
pos2Low.precision_bits = 8;
|
||||
|
||||
meshtastic_Position pos2High = meshtastic_Position_init_default;
|
||||
pos2High.latitude_i = lat2;
|
||||
pos2High.longitude_i = lon2;
|
||||
pos2High.precision_bits = 32;
|
||||
|
||||
// Different positions should always update, regardless of precision
|
||||
TEST_ASSERT_TRUE(PositionModule::shouldUpdatePosition(pos1HighLite, pos2Low));
|
||||
TEST_ASSERT_TRUE(PositionModule::shouldUpdatePosition(pos1LowLite, pos2High));
|
||||
TEST_ASSERT_TRUE(PositionModule::shouldUpdatePosition(pos1LowLite, pos2Low));
|
||||
}
|
||||
|
||||
void test_sar_scenario() {
|
||||
// Test the specific Search and Rescue use case
|
||||
|
||||
// Initial: High precision GPS position on private channel
|
||||
int32_t baseLat = (int32_t)0x075BCD15;
|
||||
int32_t baseLon = (int32_t)0x3ADE68B1;
|
||||
uint32_t privateChannelPrec = 32; // Full precision
|
||||
|
||||
// Later: Same location received on public channel (degraded precision)
|
||||
uint32_t publicChannelPrec = 13; // ~610m accuracy
|
||||
|
||||
// Create PositionLite struct for existing position
|
||||
meshtastic_PositionLite privatePosLite = meshtastic_PositionLite_init_default;
|
||||
privatePosLite.latitude_i = baseLat;
|
||||
privatePosLite.longitude_i = baseLon;
|
||||
privatePosLite.precision_bits = privateChannelPrec;
|
||||
|
||||
// Create Position structs for incoming positions
|
||||
meshtastic_Position publicPos = meshtastic_Position_init_default;
|
||||
publicPos.latitude_i = baseLat;
|
||||
publicPos.longitude_i = baseLon;
|
||||
publicPos.precision_bits = publicChannelPrec;
|
||||
|
||||
// Should NOT update - preserve the high precision data
|
||||
bool preserveHighPrec = PositionModule::shouldUpdatePosition(privatePosLite, publicPos);
|
||||
TEST_ASSERT_FALSE(preserveHighPrec);
|
||||
|
||||
// Now: Actual movement detected even with lower precision
|
||||
int32_t movedLat = baseLat + (int32_t)0x927C0; // +600000 (~6km)
|
||||
int32_t movedLon = baseLon + (int32_t)0x927C0;
|
||||
|
||||
meshtastic_Position movedPos = meshtastic_Position_init_default;
|
||||
movedPos.latitude_i = movedLat;
|
||||
movedPos.longitude_i = movedLon;
|
||||
movedPos.precision_bits = publicChannelPrec;
|
||||
|
||||
// Should update - movement detected despite lower precision
|
||||
bool detectMovement = PositionModule::shouldUpdatePosition(privatePosLite, movedPos);
|
||||
TEST_ASSERT_TRUE(detectMovement);
|
||||
}
|
||||
|
||||
void test_precision_bit_masking() {
|
||||
// Test the bit masking logic directly
|
||||
|
||||
// For precision=13: mask should clear bottom 19 bits
|
||||
uint32_t mask13 = 0xFFFFFFFF << (32 - 13);
|
||||
TEST_ASSERT_EQUAL_HEX32(0xFFF80000, mask13);
|
||||
|
||||
// Test masking effect on real coordinates
|
||||
int32_t original = (int32_t)0x075BCD15; // 123456789
|
||||
int32_t masked = original & mask13;
|
||||
TEST_ASSERT_EQUAL_HEX32(0x07580000, masked); // Bottom 19 bits cleared
|
||||
|
||||
// Verify different coordinates in same precision bucket are treated as same
|
||||
int32_t coord1 = (int32_t)0x075B0000;
|
||||
int32_t coord2 = (int32_t)0x075BFFFF; // Same precision bucket at precision=13
|
||||
|
||||
int32_t masked1 = coord1 & mask13;
|
||||
int32_t masked2 = coord2 & mask13;
|
||||
TEST_ASSERT_EQUAL(masked1, masked2); // Should be identical after masking
|
||||
}
|
||||
|
||||
void test_real_gps_coordinates() {
|
||||
// Test with realistic GPS coordinates in hex
|
||||
|
||||
// San Francisco: 37.7749° N, 122.4194° W
|
||||
// In int32 format: lat = 377749000 (0x1682F808), lon = -1224194000 (0xB6F64FB0)
|
||||
int32_t sfLat = (int32_t)0x1682F808;
|
||||
int32_t sfLon = (int32_t)0xB6F64FB0; // Negative value
|
||||
|
||||
// Small movement within same precision bucket - should still update at same precision
|
||||
int32_t nearbyLat = sfLat + 1000; // Small offset
|
||||
int32_t nearbyLon = sfLon + 1000;
|
||||
|
||||
// Create PositionLite struct for existing position
|
||||
meshtastic_PositionLite sfPosLite = meshtastic_PositionLite_init_default;
|
||||
sfPosLite.latitude_i = sfLat;
|
||||
sfPosLite.longitude_i = sfLon;
|
||||
sfPosLite.precision_bits = 13;
|
||||
|
||||
// Create Position struct for incoming position
|
||||
meshtastic_Position nearbyPos = meshtastic_Position_init_default;
|
||||
nearbyPos.latitude_i = nearbyLat;
|
||||
nearbyPos.longitude_i = nearbyLon;
|
||||
nearbyPos.precision_bits = 13;
|
||||
|
||||
TEST_ASSERT_TRUE(PositionModule::shouldUpdatePosition(sfPosLite, nearbyPos));
|
||||
|
||||
// Large movement - should always update
|
||||
int32_t distantLat = sfLat + (int32_t)0x100000; // Large offset
|
||||
int32_t distantLon = sfLon + (int32_t)0x100000;
|
||||
|
||||
meshtastic_PositionLite sfPosHighLite = meshtastic_PositionLite_init_default;
|
||||
sfPosHighLite.latitude_i = sfLat;
|
||||
sfPosHighLite.longitude_i = sfLon;
|
||||
sfPosHighLite.precision_bits = 32;
|
||||
|
||||
meshtastic_Position distantPos = meshtastic_Position_init_default;
|
||||
distantPos.latitude_i = distantLat;
|
||||
distantPos.longitude_i = distantLon;
|
||||
distantPos.precision_bits = 13;
|
||||
|
||||
TEST_ASSERT_TRUE(PositionModule::shouldUpdatePosition(sfPosHighLite, distantPos));
|
||||
}
|
||||
|
||||
void test_very_low_precision() {
|
||||
// Test 4-bit precision with clear bit differences
|
||||
int32_t lat1 = (int32_t)0x80000000; // High bit set
|
||||
int32_t lon1 = (int32_t)0x40000000;
|
||||
int32_t lat2 = (int32_t)0x90000000; // Different high bits
|
||||
int32_t lon2 = (int32_t)0x50000000;
|
||||
|
||||
// Create PositionLite struct for existing position
|
||||
meshtastic_PositionLite pos1Lite = meshtastic_PositionLite_init_default;
|
||||
pos1Lite.latitude_i = lat1;
|
||||
pos1Lite.longitude_i = lon1;
|
||||
pos1Lite.precision_bits = 4;
|
||||
|
||||
// Create Position struct for incoming position
|
||||
meshtastic_Position pos2 = meshtastic_Position_init_default;
|
||||
pos2.latitude_i = lat2;
|
||||
pos2.longitude_i = lon2;
|
||||
pos2.precision_bits = 4;
|
||||
|
||||
// At 4-bit precision, mask is 0xF0000000
|
||||
// 0x80000000 & 0xF0000000 = 0x80000000
|
||||
// 0x90000000 & 0xF0000000 = 0x90000000
|
||||
// These should be different, so should update
|
||||
TEST_ASSERT_TRUE(PositionModule::shouldUpdatePosition(pos1Lite, pos2));
|
||||
}
|
||||
|
||||
void setup() {
|
||||
initializeTestEnvironment();
|
||||
UNITY_BEGIN(); // IMPORTANT LINE!
|
||||
|
||||
// Run critical position precision tests
|
||||
RUN_TEST(test_no_existing_data);
|
||||
RUN_TEST(test_same_position_different_precision);
|
||||
RUN_TEST(test_movement_detection);
|
||||
RUN_TEST(test_sar_scenario);
|
||||
RUN_TEST(test_precision_bit_masking);
|
||||
|
||||
exit(UNITY_END()); // stop unit testing
|
||||
}
|
||||
|
||||
void loop() {}
|
Loading…
Reference in New Issue
Block a user