Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/mesh/mesh_wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,9 @@ int exportContactsFull(ContactInfo* out, int max) {
strncpy(out[n].name, c->name, 31);
out[n].name[31] = '\0';
out[n].type = c->type;
out[n].has_location = c->has_location;
out[n].latitude = c->latitude;
out[n].longitude = c->longitude;
out[n].rssi = c->last_rssi;
out[n].last_seen = c->last_seen;
n++;
Expand Down
3 changes: 3 additions & 0 deletions src/mesh/mesh_wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ struct MeshMessage {
struct ContactInfo {
char name[32];
uint8_t type; // ADV_TYPE_* (ADV_TYPE_CHAT=companion, ADV_TYPE_REPEATER, ADV_TYPE_ROOM, etc.)
bool has_location;
float latitude;
float longitude;
int rssi;
uint32_t last_seen;
};
Expand Down
17 changes: 17 additions & 0 deletions src/mesh/slop_mesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ struct SlopContact {
uint8_t secret[PUB_KEY_SIZE];
char name[32];
uint8_t type; // ADV_TYPE_* from the advert (CHAT, REPEATER, ROOM, etc.)
bool has_location; // true when the advert included GPS coordinates
float latitude;
float longitude;
uint32_t last_seen; // RTC timestamp of last advert
int last_rssi; // RSSI of last received packet (dBm)
uint8_t out_path_len; // OUT_PATH_UNKNOWN if no known direct path
Expand Down Expand Up @@ -137,12 +140,24 @@ class SlopMesh : public ::mesh::Mesh {
name = fallback;
}

auto update_location = [](SlopContact& c, const AdvertDataParser& p) {
c.has_location = p.hasLatLon();
if (c.has_location) {
c.latitude = (float)p.getLat();
c.longitude = (float)p.getLon();
} else {
c.latitude = 0.0f;
c.longitude = 0.0f;
}
};

// Deduplicate
for (int i = 0; i < _nContacts; i++)
if (_contacts[i].id.matches(id)) {
_contacts[i].last_seen = timestamp;
_contacts[i].last_rssi = (int)_radio->getLastRSSI();
_contacts[i].type = parser.getType();
update_location(_contacts[i], parser);
strncpy(_contacts[i].name, name, sizeof(_contacts[i].name) - 1);
_contacts[i].name[sizeof(_contacts[i].name) - 1] = '\0';
pushPacketLog(name, _contacts[i].last_rssi, pkt->getSNR(), "ADVERT");
Expand All @@ -161,6 +176,7 @@ class SlopMesh : public ::mesh::Mesh {
c.last_seen = timestamp;
c.last_rssi = (int)_radio->getLastRSSI();
c.type = parser.getType();
update_location(c, parser);
c.out_path_len = OUT_PATH_UNKNOWN;
self_id.calcSharedSecret(c.secret, id);
strncpy(c.name, name, sizeof(c.name) - 1);
Expand All @@ -174,6 +190,7 @@ class SlopMesh : public ::mesh::Mesh {
c.last_seen = timestamp;
c.last_rssi = (int)_radio->getLastRSSI();
c.type = parser.getType();
update_location(c, parser);
self_id.calcSharedSecret(c.secret, id);

strncpy(c.name, name, sizeof(c.name) - 1);
Expand Down
45 changes: 45 additions & 0 deletions test/test_mesh_messaging/test_mesh_messaging.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ struct TestContact {
char name[32];
uint32_t last_seen;
int last_rssi;
bool has_location;
float latitude;
float longitude;
bool valid;
};

Expand All @@ -51,16 +54,27 @@ struct ContactList {
contacts[i].valid = false;
contacts[i].last_seen = 0;
contacts[i].name[0] = '\0';
contacts[i].has_location = false;
contacts[i].latitude = 0.0f;
contacts[i].longitude = 0.0f;
}
}

// Returns true if contact was added/updated, false if dropped
bool on_advert(const char* name, uint32_t timestamp, int rssi) {
return on_advert(name, timestamp, rssi, false, 0.0f, 0.0f);
}

bool on_advert(const char* name, uint32_t timestamp, int rssi,
bool has_location, float lat, float lon) {
// Deduplicate
for (int i = 0; i < n_contacts; i++) {
if (strcmp(contacts[i].name, name) == 0) {
contacts[i].last_seen = timestamp;
contacts[i].last_rssi = rssi;
contacts[i].has_location = has_location;
contacts[i].latitude = has_location ? lat : 0.0f;
contacts[i].longitude = has_location ? lon : 0.0f;
return true;
}
}
Expand All @@ -76,6 +90,9 @@ struct ContactList {
c.valid = true;
c.last_seen = timestamp;
c.last_rssi = rssi;
c.has_location = has_location;
c.latitude = has_location ? lat : 0.0f;
c.longitude = has_location ? lon : 0.0f;
strncpy(c.name, name, sizeof(c.name) - 1);
c.name[sizeof(c.name) - 1] = '\0';
return true;
Expand All @@ -85,6 +102,9 @@ struct ContactList {
c.valid = true;
c.last_seen = timestamp;
c.last_rssi = rssi;
c.has_location = has_location;
c.latitude = has_location ? lat : 0.0f;
c.longitude = has_location ? lon : 0.0f;
strncpy(c.name, name, sizeof(c.name) - 1);
c.name[sizeof(c.name) - 1] = '\0';
return true;
Expand Down Expand Up @@ -466,6 +486,31 @@ TEST_F(ContactEvictionTest, DedupUpdatesLastSeen) {
EXPECT_EQ(cl.get(idx)->last_rssi, -80);
}

TEST_F(ContactEvictionTest, StoresAdvertLocationWhenPresent) {
EXPECT_TRUE(cl.on_advert("Toronto", 1000, -82, true, 43.6532f, -79.3832f));

int idx = cl.find("Toronto");
ASSERT_GE(idx, 0);
const TestContact* c = cl.get(idx);
ASSERT_NE(c, nullptr);
EXPECT_TRUE(c->has_location);
EXPECT_NEAR(c->latitude, 43.6532f, 0.0001f);
EXPECT_NEAR(c->longitude, -79.3832f, 0.0001f);
}

TEST_F(ContactEvictionTest, ClearsAdvertLocationWhenMissingOnUpdate) {
cl.on_advert("Mobile", 1000, -82, true, 43.6532f, -79.3832f);
cl.on_advert("Mobile", 2000, -78);

int idx = cl.find("Mobile");
ASSERT_GE(idx, 0);
const TestContact* c = cl.get(idx);
ASSERT_NE(c, nullptr);
EXPECT_FALSE(c->has_location);
EXPECT_FLOAT_EQ(c->latitude, 0.0f);
EXPECT_FLOAT_EQ(c->longitude, 0.0f);
}

TEST_F(ContactEvictionTest, FillsToMax) {
for (int i = 0; i < MAX_CONTACTS; i++) {
char name[16];
Expand Down
11 changes: 11 additions & 0 deletions test/test_mesh_wrapper/test_mesh_wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,17 @@ TEST_F(MeshWrapperTest, ContactInfoHasTypeField) {
EXPECT_EQ(sizeof(ci.type), sizeof(uint8_t));
}

TEST_F(MeshWrapperTest, ContactInfoHasLocationFields) {
slopos::mesh::ContactInfo ci{};
ci.has_location = true;
ci.latitude = 43.6532f;
ci.longitude = -79.3832f;

EXPECT_TRUE(ci.has_location);
EXPECT_NEAR(ci.latitude, 43.6532f, 0.0001f);
EXPECT_NEAR(ci.longitude, -79.3832f, 0.0001f);
}

// ── ADV_TYPE constants have expected values ────────────
TEST_F(MeshWrapperTest, AdvTypeConstants) {
EXPECT_EQ(ADV_TYPE_NONE, 0);
Expand Down
Loading