mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-08-02 10:16:11 +00:00
feat: replace advert scan with active node discovery in NearbyScreen
NearbyScreen now sends CTL_TYPE_NODE_DISCOVER_REQ (same protocol as the companion app) instead of a plain self-advert. Nearby repeaters, sensors and room servers respond with their pub_key and type. Known contacts get their lastmod refreshed; unknown nodes appear as temporary entries (e.g. "Repeater") in the list until their advert is received. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
9427fa1999
commit
9cabcd5ac7
@@ -764,7 +764,58 @@ bool MyMesh::onContactPathRecv(ContactInfo& contact, uint8_t* in_path, uint8_t i
|
|||||||
return BaseChatMesh::onContactPathRecv(contact, in_path, in_path_len, out_path, out_path_len, extra_type, extra, extra_len);
|
return BaseChatMesh::onContactPathRecv(contact, in_path, in_path_len, out_path, out_path_len, extra_type, extra, extra_len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define CTL_TYPE_NODE_DISCOVER_REQ 0x80
|
||||||
|
#define CTL_TYPE_NODE_DISCOVER_RESP 0x90
|
||||||
|
|
||||||
|
void MyMesh::sendNodeDiscoverReq() {
|
||||||
|
uint8_t data[10];
|
||||||
|
data[0] = CTL_TYPE_NODE_DISCOVER_REQ;
|
||||||
|
data[1] = (1 << ADV_TYPE_REPEATER) | (1 << ADV_TYPE_SENSOR) | (1 << ADV_TYPE_ROOM);
|
||||||
|
getRNG()->random(&data[2], 4);
|
||||||
|
memcpy(&_pending_node_discover_tag, &data[2], 4);
|
||||||
|
_pending_node_discover_until = futureMillis(8000);
|
||||||
|
_discovered_count = 0;
|
||||||
|
uint32_t since = 0;
|
||||||
|
memcpy(&data[6], &since, 4);
|
||||||
|
auto pkt = createControlData(data, sizeof(data));
|
||||||
|
if (pkt) sendZeroHop(pkt);
|
||||||
|
}
|
||||||
|
|
||||||
|
int MyMesh::getDiscoveredNodes(DiscoveredEntry dest[], int max_count) {
|
||||||
|
int n = min(_discovered_count, max_count);
|
||||||
|
memcpy(dest, _discovered, n * sizeof(DiscoveredEntry));
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
void MyMesh::onControlDataRecv(mesh::Packet *packet) {
|
void MyMesh::onControlDataRecv(mesh::Packet *packet) {
|
||||||
|
// handle node discovery responses when in standalone UI mode (no companion app connected)
|
||||||
|
if (!_serial->isBLEConnected() &&
|
||||||
|
(packet->payload[0] & 0xF0) == CTL_TYPE_NODE_DISCOVER_RESP &&
|
||||||
|
packet->payload_len >= 6 + PUB_KEY_SIZE &&
|
||||||
|
_pending_node_discover_tag != 0 &&
|
||||||
|
!millisHasNowPassed(_pending_node_discover_until)) {
|
||||||
|
uint32_t tag;
|
||||||
|
memcpy(&tag, &packet->payload[2], 4);
|
||||||
|
if (tag == _pending_node_discover_tag) {
|
||||||
|
uint8_t node_type = packet->payload[0] & 0x0F;
|
||||||
|
const uint8_t* pub_key = &packet->payload[6];
|
||||||
|
// check if already in contacts — update lastmod so it shows as fresh
|
||||||
|
ContactInfo* known = lookupContactByPubKey(pub_key, PUB_KEY_SIZE);
|
||||||
|
if (known) {
|
||||||
|
known->lastmod = getRTCClock()->getCurrentTime();
|
||||||
|
}
|
||||||
|
// if unknown, add to temporary discovered list
|
||||||
|
if (!known && _discovered_count < DISCOVERED_NODES_MAX) {
|
||||||
|
DiscoveredEntry& e = _discovered[_discovered_count++];
|
||||||
|
memcpy(e.pub_key_prefix, pub_key, 4);
|
||||||
|
e.type = node_type;
|
||||||
|
e.timestamp = getRTCClock()->getCurrentTime();
|
||||||
|
}
|
||||||
|
if (_ui) _ui->notify(UIEventType::newContactMessage);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (packet->payload_len + 4 > sizeof(out_frame)) {
|
if (packet->payload_len + 4 > sizeof(out_frame)) {
|
||||||
MESH_DEBUG_PRINTLN("onControlDataRecv(), payload_len too long: %d", packet->payload_len);
|
MESH_DEBUG_PRINTLN("onControlDataRecv(), payload_len too long: %d", packet->payload_len);
|
||||||
return;
|
return;
|
||||||
@@ -862,6 +913,9 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
|
|||||||
dirty_contacts_expiry = 0;
|
dirty_contacts_expiry = 0;
|
||||||
memset(advert_paths, 0, sizeof(advert_paths));
|
memset(advert_paths, 0, sizeof(advert_paths));
|
||||||
memset(send_scope.key, 0, sizeof(send_scope.key));
|
memset(send_scope.key, 0, sizeof(send_scope.key));
|
||||||
|
_discovered_count = 0;
|
||||||
|
_pending_node_discover_tag = 0;
|
||||||
|
_pending_node_discover_until = 0;
|
||||||
|
|
||||||
// defaults
|
// defaults
|
||||||
memset(&_prefs, 0, sizeof(_prefs));
|
memset(&_prefs, 0, sizeof(_prefs));
|
||||||
|
|||||||
@@ -85,6 +85,12 @@ struct AdvertPath {
|
|||||||
uint8_t path[MAX_PATH_SIZE];
|
uint8_t path[MAX_PATH_SIZE];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct DiscoveredEntry {
|
||||||
|
uint8_t pub_key_prefix[4];
|
||||||
|
uint8_t type; // ADV_TYPE_REPEATER / ADV_TYPE_SENSOR / ADV_TYPE_ROOM
|
||||||
|
uint32_t timestamp; // RTC timestamp of discovery
|
||||||
|
};
|
||||||
|
|
||||||
#define EXPECTED_ACK_TABLE_SIZE 8
|
#define EXPECTED_ACK_TABLE_SIZE 8
|
||||||
|
|
||||||
class MyMesh : public BaseChatMesh, public DataStoreHost {
|
class MyMesh : public BaseChatMesh, public DataStoreHost {
|
||||||
@@ -101,9 +107,11 @@ public:
|
|||||||
void loop();
|
void loop();
|
||||||
void handleCmdFrame(size_t len);
|
void handleCmdFrame(size_t len);
|
||||||
bool advert();
|
bool advert();
|
||||||
|
void sendNodeDiscoverReq();
|
||||||
void enterCLIRescue();
|
void enterCLIRescue();
|
||||||
|
|
||||||
int getRecentlyHeard(AdvertPath dest[], int max_num);
|
int getRecentlyHeard(AdvertPath dest[], int max_num);
|
||||||
|
int getDiscoveredNodes(DiscoveredEntry dest[], int max_count);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
float getAirtimeBudgetFactor() const override;
|
float getAirtimeBudgetFactor() const override;
|
||||||
@@ -263,6 +271,12 @@ private:
|
|||||||
|
|
||||||
#define ADVERT_PATH_TABLE_SIZE 16
|
#define ADVERT_PATH_TABLE_SIZE 16
|
||||||
AdvertPath advert_paths[ADVERT_PATH_TABLE_SIZE]; // circular table
|
AdvertPath advert_paths[ADVERT_PATH_TABLE_SIZE]; // circular table
|
||||||
|
|
||||||
|
#define DISCOVERED_NODES_MAX 8
|
||||||
|
DiscoveredEntry _discovered[DISCOVERED_NODES_MAX];
|
||||||
|
int _discovered_count;
|
||||||
|
uint32_t _pending_node_discover_tag;
|
||||||
|
unsigned long _pending_node_discover_until;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern MyMesh the_mesh;
|
extern MyMesh the_mesh;
|
||||||
|
|||||||
@@ -22,8 +22,9 @@ class NearbyScreen : public UIScreen {
|
|||||||
int32_t lat_e6, lon_e6;
|
int32_t lat_e6, lon_e6;
|
||||||
float dist_km;
|
float dist_km;
|
||||||
uint8_t type;
|
uint8_t type;
|
||||||
int contact_idx;
|
int contact_idx; // -1 for discovered-but-unknown nodes
|
||||||
uint32_t lastmod; // our RTC timestamp of last received packet
|
uint32_t lastmod;
|
||||||
|
bool is_discovered; // true = found via CTL_TYPE_NODE_DISCOVER_RESP, not in contacts[]
|
||||||
};
|
};
|
||||||
|
|
||||||
static const int MAX_NEARBY = 32;
|
static const int MAX_NEARBY = 32;
|
||||||
@@ -101,7 +102,7 @@ class NearbyScreen : public UIScreen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void startScan() {
|
void startScan() {
|
||||||
the_mesh.advert();
|
the_mesh.sendNodeDiscoverReq();
|
||||||
_scanning = true;
|
_scanning = true;
|
||||||
_scan_started_ms = millis();
|
_scan_started_ms = millis();
|
||||||
}
|
}
|
||||||
@@ -139,6 +140,26 @@ class NearbyScreen : public UIScreen {
|
|||||||
e.type = ci.type;
|
e.type = ci.type;
|
||||||
e.contact_idx = i;
|
e.contact_idx = i;
|
||||||
e.lastmod = ci.lastmod;
|
e.lastmod = ci.lastmod;
|
||||||
|
e.is_discovered = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add nodes discovered via CTL_TYPE_NODE_DISCOVER_RESP that are not in contacts[]
|
||||||
|
if (_filter != 0) { // Fav filter never shows anonymous discovered nodes
|
||||||
|
DiscoveredEntry disc[DISCOVERED_NODES_MAX];
|
||||||
|
int disc_count = the_mesh.getDiscoveredNodes(disc, DISCOVERED_NODES_MAX);
|
||||||
|
for (int i = 0; i < disc_count && _count < MAX_NEARBY; i++) {
|
||||||
|
if (_filter >= 2 && disc[i].type != FILTER_TYPES[_filter]) continue;
|
||||||
|
Entry& e = _entries[_count++];
|
||||||
|
strncpy(e.name, typeName(disc[i].type), sizeof(e.name) - 1);
|
||||||
|
e.name[sizeof(e.name) - 1] = '\0';
|
||||||
|
e.lat_e6 = 0;
|
||||||
|
e.lon_e6 = 0;
|
||||||
|
e.dist_km = -1.0f;
|
||||||
|
e.type = disc[i].type;
|
||||||
|
e.contact_idx = -1;
|
||||||
|
e.lastmod = disc[i].timestamp;
|
||||||
|
e.is_discovered = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// sort by distance ascending; nodes without GPS (-1) go to the end
|
// sort by distance ascending; nodes without GPS (-1) go to the end
|
||||||
@@ -242,7 +263,7 @@ public:
|
|||||||
// ── list view ────────────────────────────────────────────────────────────
|
// ── list view ────────────────────────────────────────────────────────────
|
||||||
display.setColor(DisplayDriver::LIGHT);
|
display.setColor(DisplayDriver::LIGHT);
|
||||||
if (_scanning) {
|
if (_scanning) {
|
||||||
display.drawTextCentered(display.width() / 2, 0, "ADVERTISING...");
|
display.drawTextCentered(display.width() / 2, 0, "DISCOVERING...");
|
||||||
} else {
|
} else {
|
||||||
char title[22];
|
char title[22];
|
||||||
snprintf(title, sizeof(title), "NEARBY[%s]", FILTER_LABELS[_filter]);
|
snprintf(title, sizeof(title), "NEARBY[%s]", FILTER_LABELS[_filter]);
|
||||||
@@ -252,7 +273,7 @@ public:
|
|||||||
|
|
||||||
if (_count == 0) {
|
if (_count == 0) {
|
||||||
display.drawTextCentered(display.width() / 2, 28, _scanning ? "Waiting..." : "No contacts found");
|
display.drawTextCentered(display.width() / 2, 28, _scanning ? "Waiting..." : "No contacts found");
|
||||||
display.drawTextCentered(display.width() / 2, 40, "[M]=Advert");
|
display.drawTextCentered(display.width() / 2, 40, "[M]=Discover");
|
||||||
} else {
|
} else {
|
||||||
for (int i = 0; i < VISIBLE && (_scroll + i) < _count; i++) {
|
for (int i = 0; i < VISIBLE && (_scroll + i) < _count; i++) {
|
||||||
int idx = _scroll + i;
|
int idx = _scroll + i;
|
||||||
@@ -323,7 +344,7 @@ public:
|
|||||||
if (c == KEY_CANCEL) { _task->gotoToolsScreen(); return true; }
|
if (c == KEY_CANCEL) { _task->gotoToolsScreen(); return true; }
|
||||||
if (c == KEY_CONTEXT_MENU) {
|
if (c == KEY_CONTEXT_MENU) {
|
||||||
_ctx_menu.begin("Options", 2);
|
_ctx_menu.begin("Options", 2);
|
||||||
_ctx_menu.addItem("Send my advert");
|
_ctx_menu.addItem("Discover nearby");
|
||||||
_ctx_menu.addItem("Back");
|
_ctx_menu.addItem("Back");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user