2.8: NodeDB shrink, decoupling, and restructuring (#10413)
* 2.8: NodeDB refactor to decouple satellite entries and decrease size * Regen * Refactor node mute handling to use dedicated functions for clarity and consistency * Develop ref * Fix NodeDB review follow-ups Agent-Logs-Url: https://github.com/meshtastic/firmware/sessions/6b1d6cf6-ed6b-43b6-95cb-8e141757664e Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> * Address review validation nits Agent-Logs-Url: https://github.com/meshtastic/firmware/sessions/6b1d6cf6-ed6b-43b6-95cb-8e141757664e Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> * Trunk * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Extract legacy NodeDatabase migration * Fix remaining NodeDB review issues Agent-Logs-Url: https://github.com/meshtastic/firmware/sessions/c76b9a5a-7244-4fbc-9ef0-98091d8caaea Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> * Fixes * Trunk * Fix latest review compile follow-ups Agent-Logs-Url: https://github.com/meshtastic/firmware/sessions/5198da01-ec4c-4c16-8a09-68b8e6d5d410 Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> * Fix cppcheck style warnings Agent-Logs-Url: https://github.com/meshtastic/firmware/sessions/e60287ba-4ece-46e0-83d8-a6d89664c0bb Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> * Change pointer type for mesh node in set_favorite function * Change pointer types for mesh node references to const in multiple applets * Add NodeDB layout v25 documentation and migration guidelines * Remove tests for uninitialized PacketHistory state due to undefined behavior * Fix code block formatting in copilot instructions --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
co-authored by
thebentern
Copilot Autofix powered by AI
copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
parent
1bcabb893b
commit
94bb21ecc7
@@ -135,6 +135,95 @@ On top of authorization, any remote admin message that **mutates** state (not a
|
|||||||
- **Channel 0 PSK change** → every peer must re-learn the channel hash; cached NodeInfo becomes temporarily unreachable until the next broadcast.
|
- **Channel 0 PSK change** → every peer must re-learn the channel hash; cached NodeInfo becomes temporarily unreachable until the next broadcast.
|
||||||
- **`security.private_key` blanked via admin** → regenerates both halves (unless in Ham mode) and propagates the new public key via NodeInfo.
|
- **`security.private_key` blanked via admin** → regenerates both halves (unless in Ham mode) and propagates the new public key via NodeInfo.
|
||||||
|
|
||||||
|
## NodeDB Layout (v25)
|
||||||
|
|
||||||
|
`DEVICESTATE_CUR_VER = 25`, `DEVICESTATE_MIN_VER = 24`. The on-device NodeDB was split in v25 into a slim header table plus four optional satellite stores. Older v24 saves auto-migrate at boot. Old training-data instincts (`node->user.long_name`, `node->position.latitude_i`, `node->is_favorite`, `node->device_metrics.battery_level`) are wrong now — the fields aren't there. Read this section before touching anything that walks `nodeDB->meshNodes`.
|
||||||
|
|
||||||
|
### Slim `NodeInfoLite`
|
||||||
|
|
||||||
|
`UserLite` is flattened onto `NodeInfoLite` (no nested sub-message); `position` and `device_metrics` are removed entirely (tags reserved). MAC address is dropped. Long names are capped at 25 chars (`max_size:25` in `deviceonly.options`); `hw_model` and `role` are `int_size:8`. Encoded size dropped from ~166 B → ~105 B per node.
|
||||||
|
|
||||||
|
Booleans are bit-packed into `NodeInfoLite.bitfield`. **Do not read or write the bits directly** — use the inline helpers in `src/mesh/NodeDB.h`:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
nodeInfoLiteHasUser(n) // bit 5 — user fields populated
|
||||||
|
nodeInfoLiteIsFavorite(n) // bit 3
|
||||||
|
nodeInfoLiteIsIgnored(n) // bit 4
|
||||||
|
nodeInfoLiteIsMuted(n) // bit 1
|
||||||
|
nodeInfoLiteIsLicensed(n) // bit 6 — Ham mode peer
|
||||||
|
nodeInfoLiteIsKeyManuallyVerified(n) // bit 0
|
||||||
|
nodeInfoLiteHasIsUnmessagable(n) // bit 8 — "is_unmessagable was sent"
|
||||||
|
nodeInfoLiteIsUnmessagable(n) // bit 7
|
||||||
|
// via_mqtt is bit 2 (mask exposed; predicate uses the mask directly)
|
||||||
|
|
||||||
|
nodeInfoLiteSetBit(n, NODEINFO_BITFIELD_IS_FAVORITE_MASK, true); // setter
|
||||||
|
```
|
||||||
|
|
||||||
|
### Satellite stores
|
||||||
|
|
||||||
|
Four `std::unordered_map<NodeNum, …>` members on `NodeDB`, each gated by its own build flag:
|
||||||
|
|
||||||
|
| Map | Value type | Build flag |
|
||||||
|
| ----------------- | ------------------------------- | ---------------------------------- |
|
||||||
|
| `nodePositions` | `meshtastic_PositionLite` | `MESHTASTIC_EXCLUDE_POSITIONDB` |
|
||||||
|
| `nodeTelemetry` | `meshtastic_DeviceMetrics` | `MESHTASTIC_EXCLUDE_TELEMETRYDB` |
|
||||||
|
| `nodeEnvironment` | `meshtastic_EnvironmentMetrics` | `MESHTASTIC_EXCLUDE_ENVIRONMENTDB` |
|
||||||
|
| `nodeStatus` | `meshtastic_StatusMessage` | `MESHTASTIC_EXCLUDE_STATUSDB` |
|
||||||
|
|
||||||
|
Defaults are ON (i.e., maps **excluded**) for STM32WL only — see `src/mesh/mesh-pb-constants.h`. On every other arch all four maps are present. When excluded, the map member is absent and the corresponding accessors return `false`.
|
||||||
|
|
||||||
|
All four maps are guarded by **`mutable concurrency::Lock satelliteMutex`** — concurrent access from receive threads, the phone API state machine, and the renderer is the rule, not the exception.
|
||||||
|
|
||||||
|
### Accessor convention
|
||||||
|
|
||||||
|
**Never hand out pointers into the maps.** Use the copy-out accessors on `NodeDB`:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
bool copyNodePosition(NodeNum, meshtastic_PositionLite &out) const;
|
||||||
|
bool copyNodeTelemetry(NodeNum, meshtastic_DeviceMetrics &out) const;
|
||||||
|
bool copyNodeEnvironment(NodeNum, meshtastic_EnvironmentMetrics &out) const;
|
||||||
|
bool copyNodeStatus(NodeNum, meshtastic_StatusMessage &out) const;
|
||||||
|
```
|
||||||
|
|
||||||
|
Each takes the lock, copies the value if present, returns `false` if the entry is absent or the DB is excluded. Pass-by-out-param is deliberate — pointer-style accessors would invite UAF and lock-leak bugs across the renderer. The "has any X" convenience predicates (`hasValidPosition` etc.) are implemented in terms of these.
|
||||||
|
|
||||||
|
Writers go through `setNodeStatus`, `updatePosition`, `updateTelemetry` (which dispatches on `which_variant` for device vs environment metrics) — these own the lock and the eviction hooks.
|
||||||
|
|
||||||
|
### Eviction
|
||||||
|
|
||||||
|
Every code path that drops a node from the header table must also evict the satellites. The single chokepoint is `eraseNodeSatellites(NodeNum)`; it's already called from `getOrCreateMeshNode`'s oldest-boring eviction, `removeNodeByNum`, both branches of `resetNodes`, `cleanupMeshDB`, `addFromContact`'s ignored-branch, and `AdminModule`'s `set_ignored_node`. Add new eviction sites here, not by calling `.erase()` directly.
|
||||||
|
|
||||||
|
### Gradient sync (opt-in via special nonces)
|
||||||
|
|
||||||
|
`client_capabilities` is **not** a thing in this branch. Phone clients opt into the new sync flow by sending one of two values in the `ToRadio.want_config_id`:
|
||||||
|
|
||||||
|
- `SPECIAL_NONCE_GRADIENT_SYNC` (69422) — full config + thin NodeInfo + replay phases.
|
||||||
|
- `SPECIAL_NONCE_GRADIENT_ONLY_NODES` (69423) — skip config segments, NodeInfo + replay only.
|
||||||
|
|
||||||
|
`PhoneAPI::clientWantsGradientSync()` is the single switch. When true, `STATE_SEND_OTHER_NODEINFOS` is followed by:
|
||||||
|
|
||||||
|
```text
|
||||||
|
STATE_REPLAY_POSITIONS → STATE_REPLAY_TELEMETRY → STATE_REPLAY_ENVIRONMENT → STATE_REPLAY_STATUS
|
||||||
|
```
|
||||||
|
|
||||||
|
Each replay phase walks the corresponding satellite map and emits synthetic `MeshPacket`s on the matching portnum (`POSITION_APP`, `TELEMETRY_APP` for both device + environment variants, `STATUS_MESSAGE_APP`). Legacy clients (no special nonce) get the bundled-NodeInfo path with position/device_metrics joined back in by `ConvertToNodeInfo(lite, pos*, dm*)` — wire bytes are byte-identical to pre-v25 for them.
|
||||||
|
|
||||||
|
`ConvertToNodeInfoThin(lite)` is the gradient-sync emitter (no position/telemetry).
|
||||||
|
|
||||||
|
### v24 → v25 migration
|
||||||
|
|
||||||
|
The legacy migration code lives in **`src/mesh/NodeDBLegacyMigration.cpp`**, not in `NodeDB.cpp`. It owns the `meshtastic_NodeDatabase_Legacy` callback and `NodeDB::migrateLegacyNodeDatabase()`. The legacy proto descriptor is `protobufs/meshtastic/deviceonly_legacy.proto` (only included by the migration TU). The boot path peeks the file's leading version tag, runs the migration if `version < 25`, then re-saves in v25 layout. The legacy descriptor is scheduled for removal once `DEVICESTATE_MIN_VER` is bumped.
|
||||||
|
|
||||||
|
### Read-site rules of thumb
|
||||||
|
|
||||||
|
- Never `node->position.X` / `node->device_metrics.X` — those fields no longer exist. Pull from the satellite map via `copyNodePosition` / `copyNodeTelemetry`.
|
||||||
|
- Never `node->user.long_name` — `long_name`, `short_name`, `public_key`, `hw_model`, `role`, `macaddr` (gone), `is_licensed`, `is_unmessagable` are flat on `NodeInfoLite`.
|
||||||
|
- Never `node->is_favorite` / `node->is_ignored` / `node->via_mqtt` / `node->is_key_manually_verified` — use the bitfield helpers.
|
||||||
|
- Never assume `nodeDB->getMeshNode(num)->position.time` — call `copyNodePosition` and check the return.
|
||||||
|
- Don't lock `satelliteMutex` yourself in renderer code; the copy-out accessors already do.
|
||||||
|
|
||||||
|
Unit tests for the conversion layer live in `test/test_type_conversions/test_main.cpp` (Unity) — bitfield round-trips, `long_name` truncation, thin-vs-full conversions. Add cases there when extending the schema.
|
||||||
|
|
||||||
## Project Structure
|
## Project Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
+1
-1
Submodule protobufs updated: 1d6f1a71ff...149586802f
+3
-6
@@ -53,8 +53,7 @@ class GPSStatus : public Status
|
|||||||
int32_t getLatitude() const
|
int32_t getLatitude() const
|
||||||
{
|
{
|
||||||
if (config.position.fixed_position) {
|
if (config.position.fixed_position) {
|
||||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
return localPosition.latitude_i;
|
||||||
return node->position.latitude_i;
|
|
||||||
} else {
|
} else {
|
||||||
return p.latitude_i;
|
return p.latitude_i;
|
||||||
}
|
}
|
||||||
@@ -63,8 +62,7 @@ class GPSStatus : public Status
|
|||||||
int32_t getLongitude() const
|
int32_t getLongitude() const
|
||||||
{
|
{
|
||||||
if (config.position.fixed_position) {
|
if (config.position.fixed_position) {
|
||||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
return localPosition.longitude_i;
|
||||||
return node->position.longitude_i;
|
|
||||||
} else {
|
} else {
|
||||||
return p.longitude_i;
|
return p.longitude_i;
|
||||||
}
|
}
|
||||||
@@ -73,8 +71,7 @@ class GPSStatus : public Status
|
|||||||
int32_t getAltitude() const
|
int32_t getAltitude() const
|
||||||
{
|
{
|
||||||
if (config.position.fixed_position) {
|
if (config.position.fixed_position) {
|
||||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
return localPosition.altitude;
|
||||||
return node->position.altitude;
|
|
||||||
} else {
|
} else {
|
||||||
return p.altitude;
|
return p.altitude;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1285,7 +1285,7 @@ void Screen::setFrames(FrameFocus focus)
|
|||||||
|
|
||||||
for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
|
for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
|
||||||
const meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i);
|
const meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i);
|
||||||
if (n && n->num != nodeDB->getNodeNum() && n->is_favorite) {
|
if (n && n->num != nodeDB->getNodeNum() && nodeInfoLiteIsFavorite(n)) {
|
||||||
favoriteFrames.push_back(graphics::UIRenderer::drawFavoriteNode);
|
favoriteFrames.push_back(graphics::UIRenderer::drawFavoriteNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1670,7 +1670,7 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet)
|
|||||||
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet->from);
|
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet->from);
|
||||||
const meshtastic_Channel channel =
|
const meshtastic_Channel channel =
|
||||||
channels.getByIndex(packet->channel ? packet->channel : channels.getPrimaryIndex());
|
channels.getByIndex(packet->channel ? packet->channel : channels.getPrimaryIndex());
|
||||||
const char *longName = (node && node->has_user) ? node->user.long_name : nullptr;
|
const char *longName = nodeInfoLiteHasUser(node) ? node->long_name : nullptr;
|
||||||
|
|
||||||
const char *msgRaw = reinterpret_cast<const char *>(packet->decoded.payload.bytes);
|
const char *msgRaw = reinterpret_cast<const char *>(packet->decoded.payload.bytes);
|
||||||
|
|
||||||
|
|||||||
@@ -845,10 +845,10 @@ void menuHandler::messageViewModeMenu()
|
|||||||
// Encode peers
|
// Encode peers
|
||||||
for (size_t i = 0; i < uniquePeers.size(); ++i) {
|
for (size_t i = 0; i < uniquePeers.size(); ++i) {
|
||||||
uint32_t peer = uniquePeers[i];
|
uint32_t peer = uniquePeers[i];
|
||||||
auto node = nodeDB->getMeshNode(peer);
|
const auto *node = nodeDB->getMeshNode(peer);
|
||||||
std::string name;
|
std::string name;
|
||||||
if (node && node->has_user)
|
if (nodeInfoLiteHasUser(node))
|
||||||
name = sanitizeString(node->user.long_name).substr(0, 15);
|
name = sanitizeString(node->long_name).substr(0, 15);
|
||||||
else {
|
else {
|
||||||
char buf[20];
|
char buf[20];
|
||||||
snprintf(buf, sizeof(buf), "Node %08X", peer);
|
snprintf(buf, sizeof(buf), "Node %08X", peer);
|
||||||
@@ -1355,14 +1355,14 @@ void menuHandler::manageNodeMenu()
|
|||||||
static int optionsEnumArray[enumEnd] = {Back};
|
static int optionsEnumArray[enumEnd] = {Back};
|
||||||
int options = 1;
|
int options = 1;
|
||||||
|
|
||||||
if (node->is_favorite) {
|
if (nodeInfoLiteIsFavorite(node)) {
|
||||||
optionsArray[options] = "Unfavorite";
|
optionsArray[options] = "Unfavorite";
|
||||||
} else {
|
} else {
|
||||||
optionsArray[options] = "Favorite";
|
optionsArray[options] = "Favorite";
|
||||||
}
|
}
|
||||||
optionsEnumArray[options++] = Favorite;
|
optionsEnumArray[options++] = Favorite;
|
||||||
|
|
||||||
bool isMuted = (node->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) != 0;
|
bool isMuted = nodeInfoLiteIsMuted(node);
|
||||||
if (isMuted) {
|
if (isMuted) {
|
||||||
optionsArray[options] = "Unmute Notifications";
|
optionsArray[options] = "Unmute Notifications";
|
||||||
} else {
|
} else {
|
||||||
@@ -1376,7 +1376,7 @@ void menuHandler::manageNodeMenu()
|
|||||||
optionsArray[options] = "Key Verification";
|
optionsArray[options] = "Key Verification";
|
||||||
optionsEnumArray[options++] = KeyVerification;
|
optionsEnumArray[options++] = KeyVerification;
|
||||||
|
|
||||||
if (node->is_ignored) {
|
if (nodeInfoLiteIsIgnored(node)) {
|
||||||
optionsArray[options] = "Unignore Node";
|
optionsArray[options] = "Unignore Node";
|
||||||
} else {
|
} else {
|
||||||
optionsArray[options] = "Ignore Node";
|
optionsArray[options] = "Ignore Node";
|
||||||
@@ -1386,8 +1386,8 @@ void menuHandler::manageNodeMenu()
|
|||||||
BannerOverlayOptions bannerOptions;
|
BannerOverlayOptions bannerOptions;
|
||||||
|
|
||||||
std::string title = "";
|
std::string title = "";
|
||||||
if (node->has_user && node->user.long_name && node->user.long_name[0]) {
|
if (nodeInfoLiteHasUser(node) && node->long_name[0]) {
|
||||||
title += sanitizeString(node->user.long_name).substr(0, 15);
|
title += sanitizeString(node->long_name).substr(0, 15);
|
||||||
} else {
|
} else {
|
||||||
char buf[20];
|
char buf[20];
|
||||||
snprintf(buf, sizeof(buf), "%08X", (unsigned int)node->num);
|
snprintf(buf, sizeof(buf), "%08X", (unsigned int)node->num);
|
||||||
@@ -1409,7 +1409,7 @@ void menuHandler::manageNodeMenu()
|
|||||||
if (!n) {
|
if (!n) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (n->is_favorite) {
|
if (nodeInfoLiteIsFavorite(n)) {
|
||||||
LOG_INFO("Removing node %08X from favorites", menuHandler::pickedNodeNum);
|
LOG_INFO("Removing node %08X from favorites", menuHandler::pickedNodeNum);
|
||||||
nodeDB->set_favorite(false, menuHandler::pickedNodeNum);
|
nodeDB->set_favorite(false, menuHandler::pickedNodeNum);
|
||||||
} else {
|
} else {
|
||||||
@@ -1426,13 +1426,9 @@ void menuHandler::manageNodeMenu()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (n->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) {
|
const bool wasMuted = nodeInfoLiteIsMuted(n);
|
||||||
n->bitfield &= ~NODEINFO_BITFIELD_IS_MUTED_MASK;
|
nodeInfoLiteSetBit(n, NODEINFO_BITFIELD_IS_MUTED_MASK, !wasMuted);
|
||||||
LOG_INFO("Unmuted node %08X", menuHandler::pickedNodeNum);
|
LOG_INFO(wasMuted ? "Unmuted node %08X" : "Muted node %08X", menuHandler::pickedNodeNum);
|
||||||
} else {
|
|
||||||
n->bitfield |= NODEINFO_BITFIELD_IS_MUTED_MASK;
|
|
||||||
LOG_INFO("Muted node %08X", menuHandler::pickedNodeNum);
|
|
||||||
}
|
|
||||||
nodeDB->notifyObservers(true);
|
nodeDB->notifyObservers(true);
|
||||||
nodeDB->saveToDisk();
|
nodeDB->saveToDisk();
|
||||||
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
|
screen->setFrames(graphics::Screen::FOCUS_PRESERVE);
|
||||||
@@ -1461,11 +1457,11 @@ void menuHandler::manageNodeMenu()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (n->is_ignored) {
|
if (nodeInfoLiteIsIgnored(n)) {
|
||||||
n->is_ignored = false;
|
nodeInfoLiteSetBit(n, NODEINFO_BITFIELD_IS_IGNORED_MASK, false);
|
||||||
LOG_INFO("Unignoring node %08X", menuHandler::pickedNodeNum);
|
LOG_INFO("Unignoring node %08X", menuHandler::pickedNodeNum);
|
||||||
} else {
|
} else {
|
||||||
n->is_ignored = true;
|
nodeInfoLiteSetBit(n, NODEINFO_BITFIELD_IS_IGNORED_MASK, true);
|
||||||
LOG_INFO("Ignoring node %08X", menuHandler::pickedNodeNum);
|
LOG_INFO("Ignoring node %08X", menuHandler::pickedNodeNum);
|
||||||
}
|
}
|
||||||
nodeDB->notifyObservers(true);
|
nodeDB->notifyObservers(true);
|
||||||
@@ -2079,9 +2075,9 @@ void menuHandler::removeFavoriteMenu()
|
|||||||
static const char *optionsArray[] = {"Back", "Yes"};
|
static const char *optionsArray[] = {"Back", "Yes"};
|
||||||
BannerOverlayOptions bannerOptions;
|
BannerOverlayOptions bannerOptions;
|
||||||
std::string message = "Unfavorite This Node?\n";
|
std::string message = "Unfavorite This Node?\n";
|
||||||
auto node = nodeDB->getMeshNode(graphics::UIRenderer::currentFavoriteNodeNum);
|
const auto *node = nodeDB->getMeshNode(graphics::UIRenderer::currentFavoriteNodeNum);
|
||||||
if (node && node->has_user) {
|
if (nodeInfoLiteHasUser(node)) {
|
||||||
message += sanitizeString(node->user.long_name).substr(0, 15);
|
message += sanitizeString(node->long_name).substr(0, 15);
|
||||||
}
|
}
|
||||||
bannerOptions.message = message.c_str();
|
bannerOptions.message = message.c_str();
|
||||||
bannerOptions.optionsArrayPtr = optionsArray;
|
bannerOptions.optionsArrayPtr = optionsArray;
|
||||||
|
|||||||
@@ -462,8 +462,8 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
|||||||
}
|
}
|
||||||
case ThreadMode::DIRECT: {
|
case ThreadMode::DIRECT: {
|
||||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(currentPeer);
|
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(currentPeer);
|
||||||
if (node && node->has_user && node->user.short_name[0]) {
|
if (nodeInfoLiteHasUser(node) && node->short_name[0]) {
|
||||||
snprintf(titleStr, sizeof(titleStr), "@%s", node->user.short_name);
|
snprintf(titleStr, sizeof(titleStr), "@%s", node->short_name);
|
||||||
} else {
|
} else {
|
||||||
snprintf(titleStr, sizeof(titleStr), "@%08x", currentPeer);
|
snprintf(titleStr, sizeof(titleStr), "@%08x", currentPeer);
|
||||||
}
|
}
|
||||||
@@ -585,11 +585,11 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
|||||||
meshtastic_NodeInfoLite *node_recipient = nodeDB->getMeshNode(m.dest);
|
meshtastic_NodeInfoLite *node_recipient = nodeDB->getMeshNode(m.dest);
|
||||||
|
|
||||||
char senderName[64] = "";
|
char senderName[64] = "";
|
||||||
if (node && node->has_user) {
|
if (nodeInfoLiteHasUser(node)) {
|
||||||
if (node->user.long_name[0]) {
|
if (node->long_name[0]) {
|
||||||
strncpy(senderName, node->user.long_name, sizeof(senderName) - 1);
|
strncpy(senderName, node->long_name, sizeof(senderName) - 1);
|
||||||
} else if (node->user.short_name[0]) {
|
} else if (node->short_name[0]) {
|
||||||
strncpy(senderName, node->user.short_name, sizeof(senderName) - 1);
|
strncpy(senderName, node->short_name, sizeof(senderName) - 1);
|
||||||
}
|
}
|
||||||
senderName[sizeof(senderName) - 1] = '\0';
|
senderName[sizeof(senderName) - 1] = '\0';
|
||||||
}
|
}
|
||||||
@@ -599,18 +599,17 @@ void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16
|
|||||||
|
|
||||||
// If this is *our own* message, override senderName to who the recipient was
|
// If this is *our own* message, override senderName to who the recipient was
|
||||||
bool mine = (m.sender == nodeDB->getNodeNum());
|
bool mine = (m.sender == nodeDB->getNodeNum());
|
||||||
if (mine && node_recipient && node_recipient->has_user) {
|
if (mine && nodeInfoLiteHasUser(node_recipient)) {
|
||||||
if (node_recipient->user.long_name[0]) {
|
if (node_recipient->long_name[0]) {
|
||||||
strncpy(senderName, node_recipient->user.long_name, sizeof(senderName) - 1);
|
strncpy(senderName, node_recipient->long_name, sizeof(senderName) - 1);
|
||||||
senderName[sizeof(senderName) - 1] = '\0';
|
senderName[sizeof(senderName) - 1] = '\0';
|
||||||
} else if (node_recipient->user.short_name[0]) {
|
} else if (node_recipient->short_name[0]) {
|
||||||
strncpy(senderName, node_recipient->user.short_name, sizeof(senderName) - 1);
|
strncpy(senderName, node_recipient->short_name, sizeof(senderName) - 1);
|
||||||
senderName[sizeof(senderName) - 1] = '\0';
|
senderName[sizeof(senderName) - 1] = '\0';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If recipient info is missing/empty, prefer a recipient identifier for outbound messages.
|
// If recipient info is missing/empty, prefer a recipient identifier for outbound messages.
|
||||||
if (mine && (!node_recipient || !node_recipient->has_user ||
|
if (mine && (!nodeInfoLiteHasUser(node_recipient) || (!node_recipient->long_name[0] && !node_recipient->short_name[0]))) {
|
||||||
(!node_recipient->user.long_name[0] && !node_recipient->user.short_name[0]))) {
|
|
||||||
snprintf(senderName, sizeof(senderName), "(%08x)", m.dest);
|
snprintf(senderName, sizeof(senderName), "(%08x)", m.dest);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1073,12 +1072,12 @@ void handleNewMessage(OLEDDisplay *display, const StoredMessage &sm, const mesht
|
|||||||
// Banner logic
|
// Banner logic
|
||||||
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet.from);
|
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(packet.from);
|
||||||
char longName[64] = "?";
|
char longName[64] = "?";
|
||||||
if (node && node->has_user) {
|
if (nodeInfoLiteHasUser(node)) {
|
||||||
if (node->user.long_name[0]) {
|
if (node->long_name[0]) {
|
||||||
strncpy(longName, node->user.long_name, sizeof(longName) - 1);
|
strncpy(longName, node->long_name, sizeof(longName) - 1);
|
||||||
longName[sizeof(longName) - 1] = '\0';
|
longName[sizeof(longName) - 1] = '\0';
|
||||||
} else if (node->user.short_name[0]) {
|
} else if (node->short_name[0]) {
|
||||||
strncpy(longName, node->user.short_name, sizeof(longName) - 1);
|
strncpy(longName, node->short_name, sizeof(longName) - 1);
|
||||||
longName[sizeof(longName) - 1] = '\0';
|
longName[sizeof(longName) - 1] = '\0';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,29 +98,23 @@ std::string getSafeNodeName(OLEDDisplay *display, meshtastic_NodeInfoLite *node,
|
|||||||
// 1) Choose target candidate (long vs short) only if present
|
// 1) Choose target candidate (long vs short) only if present
|
||||||
const char *raw = nullptr;
|
const char *raw = nullptr;
|
||||||
|
|
||||||
#if !MESHTASTIC_EXCLUDE_STATUS
|
#if !MESHTASTIC_EXCLUDE_STATUS && !MESHTASTIC_EXCLUDE_STATUSDB
|
||||||
// If long-name mode is enabled, and we have a recent status for this node,
|
// If long-name mode is enabled, and we have a recent status for this node,
|
||||||
// prefer "(short_name) statusText" as the raw candidate.
|
// prefer "(short_name) statusText" as the raw candidate. Pull straight out
|
||||||
|
// of NodeDB's per-NodeNum cache instead of scanning a FIFO.
|
||||||
std::string composedFromStatus;
|
std::string composedFromStatus;
|
||||||
if (config.display.use_long_node_name && node && node->has_user && statusMessageModule) {
|
if (config.display.use_long_node_name && nodeInfoLiteHasUser(node) && nodeDB) {
|
||||||
const auto &recent = statusMessageModule->getRecentReceived();
|
meshtastic_StatusMessage cachedStatus;
|
||||||
const StatusMessageModule::RecentStatus *found = nullptr;
|
if (nodeDB->copyNodeStatus(node->num, cachedStatus) && cachedStatus.status[0]) {
|
||||||
for (auto it = recent.rbegin(); it != recent.rend(); ++it) {
|
const char *shortName = node->short_name;
|
||||||
if (it->fromNodeId == node->num && !it->statusText.empty()) {
|
const size_t statusLen = std::strlen(cachedStatus.status);
|
||||||
found = &(*it);
|
composedFromStatus.reserve(4 + (shortName ? std::strlen(shortName) : 0) + 1 + statusLen);
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (found) {
|
|
||||||
const char *shortName = node->user.short_name;
|
|
||||||
composedFromStatus.reserve(4 + (shortName ? std::strlen(shortName) : 0) + 1 + found->statusText.size());
|
|
||||||
composedFromStatus += "(";
|
composedFromStatus += "(";
|
||||||
if (shortName && *shortName) {
|
if (shortName && *shortName) {
|
||||||
composedFromStatus += shortName;
|
composedFromStatus += shortName;
|
||||||
}
|
}
|
||||||
composedFromStatus += ") ";
|
composedFromStatus += ") ";
|
||||||
composedFromStatus += found->statusText;
|
composedFromStatus += cachedStatus.status;
|
||||||
|
|
||||||
raw = composedFromStatus.c_str(); // safe for now; we'll sanitize immediately into std::string
|
raw = composedFromStatus.c_str(); // safe for now; we'll sanitize immediately into std::string
|
||||||
}
|
}
|
||||||
@@ -129,8 +123,8 @@ std::string getSafeNodeName(OLEDDisplay *display, meshtastic_NodeInfoLite *node,
|
|||||||
|
|
||||||
// If we didn't compose from status, use normal long/short selection
|
// If we didn't compose from status, use normal long/short selection
|
||||||
if (!raw) {
|
if (!raw) {
|
||||||
if (node && node->has_user) {
|
if (nodeInfoLiteHasUser(node)) {
|
||||||
raw = config.display.use_long_node_name ? node->user.long_name : node->user.short_name;
|
raw = config.display.use_long_node_name ? node->long_name : node->short_name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,7 +212,7 @@ void drawScrollbar(OLEDDisplay *display, int visibleNodeRows, int totalEntries,
|
|||||||
static inline void applyFavoriteNodeNameColor(OLEDDisplay *display, const meshtastic_NodeInfoLite *node, const char *nodeName,
|
static inline void applyFavoriteNodeNameColor(OLEDDisplay *display, const meshtastic_NodeInfoLite *node, const char *nodeName,
|
||||||
int16_t nameX, int16_t y, int nameMaxWidth)
|
int16_t nameX, int16_t y, int nameMaxWidth)
|
||||||
{
|
{
|
||||||
if (!display || !node || !node->is_favorite || !isTFTColoringEnabled() || !nodeName) {
|
if (!display || !node || !nodeInfoLiteIsFavorite(node) || !isTFTColoringEnabled() || !nodeName) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,7 +253,7 @@ void drawEntryLastHeard(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int
|
|||||||
#if GRAPHICS_TFT_COLORING_ENABLED
|
#if GRAPHICS_TFT_COLORING_ENABLED
|
||||||
applyFavoriteNodeNameColor(display, node, nodeName, nameX, y, nameMaxWidth);
|
applyFavoriteNodeNameColor(display, node, nodeName, nameX, y, nameMaxWidth);
|
||||||
#endif
|
#endif
|
||||||
bool isMuted = (node->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) != 0;
|
bool isMuted = nodeInfoLiteIsMuted(node);
|
||||||
|
|
||||||
char timeStr[10];
|
char timeStr[10];
|
||||||
uint32_t seconds = sinceLastSeen(node);
|
uint32_t seconds = sinceLastSeen(node);
|
||||||
@@ -279,14 +273,14 @@ void drawEntryLastHeard(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int
|
|||||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
display->setFont(FONT_SMALL);
|
display->setFont(FONT_SMALL);
|
||||||
UIRenderer::drawStringWithEmotes(display, nameX, y, nodeName, FONT_HEIGHT_SMALL, 1, false);
|
UIRenderer::drawStringWithEmotes(display, nameX, y, nodeName, FONT_HEIGHT_SMALL, 1, false);
|
||||||
if (node->is_favorite) {
|
if (nodeInfoLiteIsFavorite(node)) {
|
||||||
if (currentResolution == ScreenResolution::High) {
|
if (currentResolution == ScreenResolution::High) {
|
||||||
drawScaledXBitmap16x16(x, y + 6, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint, display);
|
drawScaledXBitmap16x16(x, y + 6, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint, display);
|
||||||
} else {
|
} else {
|
||||||
display->drawXbm(x, y + 5, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint);
|
display->drawXbm(x, y + 5, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (node->is_ignored || isMuted) {
|
if (nodeInfoLiteIsIgnored(node) || isMuted) {
|
||||||
if (currentResolution == ScreenResolution::High) {
|
if (currentResolution == ScreenResolution::High) {
|
||||||
display->drawLine(x + 8, y + 8, (isLeftCol ? 0 : x - 4) + nameMaxWidth - 17, y + 8);
|
display->drawLine(x + 8, y + 8, (isLeftCol ? 0 : x - 4) + nameMaxWidth - 17, y + 8);
|
||||||
} else {
|
} else {
|
||||||
@@ -321,20 +315,20 @@ void drawEntryHopSignal(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int
|
|||||||
#if GRAPHICS_TFT_COLORING_ENABLED
|
#if GRAPHICS_TFT_COLORING_ENABLED
|
||||||
applyFavoriteNodeNameColor(display, node, nodeName, nameX, y, nameMaxWidth);
|
applyFavoriteNodeNameColor(display, node, nodeName, nameX, y, nameMaxWidth);
|
||||||
#endif
|
#endif
|
||||||
bool isMuted = (node->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) != 0;
|
bool isMuted = nodeInfoLiteIsMuted(node);
|
||||||
|
|
||||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
display->setFont(FONT_SMALL);
|
display->setFont(FONT_SMALL);
|
||||||
|
|
||||||
UIRenderer::drawStringWithEmotes(display, nameX, y, nodeName, FONT_HEIGHT_SMALL, 1, false);
|
UIRenderer::drawStringWithEmotes(display, nameX, y, nodeName, FONT_HEIGHT_SMALL, 1, false);
|
||||||
if (node->is_favorite) {
|
if (nodeInfoLiteIsFavorite(node)) {
|
||||||
if (currentResolution == ScreenResolution::High) {
|
if (currentResolution == ScreenResolution::High) {
|
||||||
drawScaledXBitmap16x16(x, y + 6, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint, display);
|
drawScaledXBitmap16x16(x, y + 6, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint, display);
|
||||||
} else {
|
} else {
|
||||||
display->drawXbm(x, y + 5, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint);
|
display->drawXbm(x, y + 5, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (node->is_ignored || isMuted) {
|
if (nodeInfoLiteIsIgnored(node) || isMuted) {
|
||||||
if (currentResolution == ScreenResolution::High) {
|
if (currentResolution == ScreenResolution::High) {
|
||||||
display->drawLine(x + 8, y + 8, (isLeftCol ? 0 : x - 4) + nameMaxWidth - 17, y + 8);
|
display->drawLine(x + 8, y + 8, (isLeftCol ? 0 : x - 4) + nameMaxWidth - 17, y + 8);
|
||||||
} else {
|
} else {
|
||||||
@@ -401,15 +395,19 @@ void drawNodeDistance(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
|
|||||||
#if GRAPHICS_TFT_COLORING_ENABLED
|
#if GRAPHICS_TFT_COLORING_ENABLED
|
||||||
applyFavoriteNodeNameColor(display, node, nodeName, nameX, y, nameMaxWidth);
|
applyFavoriteNodeNameColor(display, node, nodeName, nameX, y, nameMaxWidth);
|
||||||
#endif
|
#endif
|
||||||
bool isMuted = (node->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) != 0;
|
bool isMuted = nodeInfoLiteIsMuted(node);
|
||||||
char distStr[10] = "";
|
char distStr[10] = "";
|
||||||
|
|
||||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
const meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||||
if (nodeDB->hasValidPosition(ourNode) && nodeDB->hasValidPosition(node)) {
|
meshtastic_PositionLite ourPos;
|
||||||
double lat1 = ourNode->position.latitude_i * 1e-7;
|
meshtastic_PositionLite theirPos;
|
||||||
double lon1 = ourNode->position.longitude_i * 1e-7;
|
const bool haveOurPos = ourNode && nodeDB->copyNodePosition(ourNode->num, ourPos);
|
||||||
double lat2 = node->position.latitude_i * 1e-7;
|
const bool haveTheirPos = nodeDB->copyNodePosition(node->num, theirPos);
|
||||||
double lon2 = node->position.longitude_i * 1e-7;
|
if (nodeDB->hasValidPosition(ourNode) && nodeDB->hasValidPosition(node) && haveOurPos && haveTheirPos) {
|
||||||
|
double lat1 = ourPos.latitude_i * 1e-7;
|
||||||
|
double lon1 = ourPos.longitude_i * 1e-7;
|
||||||
|
double lat2 = theirPos.latitude_i * 1e-7;
|
||||||
|
double lon2 = theirPos.longitude_i * 1e-7;
|
||||||
|
|
||||||
double earthRadiusKm = 6371.0;
|
double earthRadiusKm = 6371.0;
|
||||||
double dLat = (lat2 - lat1) * DEG_TO_RAD;
|
double dLat = (lat2 - lat1) * DEG_TO_RAD;
|
||||||
@@ -455,14 +453,14 @@ void drawNodeDistance(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
|
|||||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
display->setFont(FONT_SMALL);
|
display->setFont(FONT_SMALL);
|
||||||
UIRenderer::drawStringWithEmotes(display, nameX, y, nodeName, FONT_HEIGHT_SMALL, 1, false);
|
UIRenderer::drawStringWithEmotes(display, nameX, y, nodeName, FONT_HEIGHT_SMALL, 1, false);
|
||||||
if (node->is_favorite) {
|
if (nodeInfoLiteIsFavorite(node)) {
|
||||||
if (currentResolution == ScreenResolution::High) {
|
if (currentResolution == ScreenResolution::High) {
|
||||||
drawScaledXBitmap16x16(x, y + 6, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint, display);
|
drawScaledXBitmap16x16(x, y + 6, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint, display);
|
||||||
} else {
|
} else {
|
||||||
display->drawXbm(x, y + 5, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint);
|
display->drawXbm(x, y + 5, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (node->is_ignored || isMuted) {
|
if (nodeInfoLiteIsIgnored(node) || isMuted) {
|
||||||
if (currentResolution == ScreenResolution::High) {
|
if (currentResolution == ScreenResolution::High) {
|
||||||
display->drawLine(x + 8, y + 8, (isLeftCol ? 0 : x - 4) + nameMaxWidth - 17, y + 8);
|
display->drawLine(x + 8, y + 8, (isLeftCol ? 0 : x - 4) + nameMaxWidth - 17, y + 8);
|
||||||
} else {
|
} else {
|
||||||
@@ -509,19 +507,19 @@ void drawEntryCompass(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
|
|||||||
#if GRAPHICS_TFT_COLORING_ENABLED
|
#if GRAPHICS_TFT_COLORING_ENABLED
|
||||||
applyFavoriteNodeNameColor(display, node, nodeName, nameX, y, nameMaxWidth);
|
applyFavoriteNodeNameColor(display, node, nodeName, nameX, y, nameMaxWidth);
|
||||||
#endif
|
#endif
|
||||||
bool isMuted = (node->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) != 0;
|
bool isMuted = nodeInfoLiteIsMuted(node);
|
||||||
|
|
||||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
display->setFont(FONT_SMALL);
|
display->setFont(FONT_SMALL);
|
||||||
UIRenderer::drawStringWithEmotes(display, nameX, y, nodeName, FONT_HEIGHT_SMALL, 1, false);
|
UIRenderer::drawStringWithEmotes(display, nameX, y, nodeName, FONT_HEIGHT_SMALL, 1, false);
|
||||||
if (node->is_favorite) {
|
if (nodeInfoLiteIsFavorite(node)) {
|
||||||
if (currentResolution == ScreenResolution::High) {
|
if (currentResolution == ScreenResolution::High) {
|
||||||
drawScaledXBitmap16x16(x, y + 6, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint, display);
|
drawScaledXBitmap16x16(x, y + 6, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint, display);
|
||||||
} else {
|
} else {
|
||||||
display->drawXbm(x, y + 5, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint);
|
display->drawXbm(x, y + 5, smallbulletpoint_width, smallbulletpoint_height, smallbulletpoint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (node->is_ignored || isMuted) {
|
if (nodeInfoLiteIsIgnored(node) || isMuted) {
|
||||||
if (currentResolution == ScreenResolution::High) {
|
if (currentResolution == ScreenResolution::High) {
|
||||||
display->drawLine(x + 8, y + 8, (isLeftCol ? 0 : x - 4) + nameMaxWidth - 17, y + 8);
|
display->drawLine(x + 8, y + 8, (isLeftCol ? 0 : x - 4) + nameMaxWidth - 17, y + 8);
|
||||||
} else {
|
} else {
|
||||||
@@ -542,8 +540,11 @@ void drawCompassArrow(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
|
|||||||
int centerX = x + columnWidth - arrowXOffset;
|
int centerX = x + columnWidth - arrowXOffset;
|
||||||
int centerY = y + FONT_HEIGHT_SMALL / 2;
|
int centerY = y + FONT_HEIGHT_SMALL / 2;
|
||||||
|
|
||||||
double nodeLat = node->position.latitude_i * 1e-7;
|
meshtastic_PositionLite nodePos;
|
||||||
double nodeLon = node->position.longitude_i * 1e-7;
|
if (!nodeDB->copyNodePosition(node->num, nodePos))
|
||||||
|
return;
|
||||||
|
double nodeLat = nodePos.latitude_i * 1e-7;
|
||||||
|
double nodeLon = nodePos.longitude_i * 1e-7;
|
||||||
float bearing = GeoCoord::bearing(userLat, userLon, nodeLat, nodeLon);
|
float bearing = GeoCoord::bearing(userLat, userLon, nodeLat, nodeLon);
|
||||||
float relativeBearing = CompassRenderer::adjustBearingForCompassMode(bearing, myHeadingRadian);
|
float relativeBearing = CompassRenderer::adjustBearingForCompassMode(bearing, myHeadingRadian);
|
||||||
float relativeBearingDeg = CompassRenderer::radiansToDegrees360(relativeBearing);
|
float relativeBearingDeg = CompassRenderer::radiansToDegrees360(relativeBearing);
|
||||||
@@ -652,7 +653,7 @@ void drawNodeListScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
|
|||||||
continue;
|
continue;
|
||||||
if (n->num == nodeDB->getNodeNum())
|
if (n->num == nodeDB->getNodeNum())
|
||||||
continue;
|
continue;
|
||||||
if (locationScreen && !n->has_position)
|
if (locationScreen && !nodeDB->hasNodePosition(n->num))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
drawList.push_back(n->num);
|
drawList.push_back(n->num);
|
||||||
@@ -883,14 +884,15 @@ void drawDistanceScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
|
|||||||
void drawNodeListWithCompasses(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
void drawNodeListWithCompasses(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||||
{
|
{
|
||||||
float headingRadian = 0.0f;
|
float headingRadian = 0.0f;
|
||||||
auto ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
const auto *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||||
if (!ourNode || !nodeDB->hasValidPosition(ourNode)) {
|
meshtastic_PositionLite ourSelfPos;
|
||||||
|
if (!ourNode || !nodeDB->hasValidPosition(ourNode) || !nodeDB->copyNodePosition(ourNode->num, ourSelfPos)) {
|
||||||
drawNodeListScreen(display, state, x, y, "Bearings", drawEntryCompass, drawCompassUnknown, headingRadian, 0.0, 0.0);
|
drawNodeListScreen(display, state, x, y, "Bearings", drawEntryCompass, drawCompassUnknown, headingRadian, 0.0, 0.0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
double lat = DegD(ourNode->position.latitude_i);
|
double lat = DegD(ourSelfPos.latitude_i);
|
||||||
double lon = DegD(ourNode->position.longitude_i);
|
double lon = DegD(ourSelfPos.longitude_i);
|
||||||
|
|
||||||
#if defined(M5STACK_UNITC6L)
|
#if defined(M5STACK_UNITC6L)
|
||||||
display->clear();
|
display->clear();
|
||||||
|
|||||||
@@ -317,12 +317,12 @@ void NotificationRenderer::drawNodePicker(OLEDDisplay *display, OLEDDisplayUiSta
|
|||||||
for (int i = firstOptionToShow; i < alertBannerOptions && linesShown < visibleTotalLines; i++, linesShown++) {
|
for (int i = firstOptionToShow; i < alertBannerOptions && linesShown < visibleTotalLines; i++, linesShown++) {
|
||||||
char tempName[48] = {0};
|
char tempName[48] = {0};
|
||||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i + 1);
|
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i + 1);
|
||||||
if (node && node->has_user) {
|
if (nodeInfoLiteHasUser(node)) {
|
||||||
const char *rawName = nullptr;
|
const char *rawName = nullptr;
|
||||||
if (node->user.long_name[0]) {
|
if (node->long_name[0]) {
|
||||||
rawName = node->user.long_name;
|
rawName = node->long_name;
|
||||||
} else if (node->user.short_name[0]) {
|
} else if (node->short_name[0]) {
|
||||||
rawName = node->user.short_name;
|
rawName = node->short_name;
|
||||||
}
|
}
|
||||||
if (rawName) {
|
if (rawName) {
|
||||||
const int arrowWidth = (currentResolution == ScreenResolution::High)
|
const int arrowWidth = (currentResolution == ScreenResolution::High)
|
||||||
|
|||||||
@@ -464,11 +464,11 @@ static bool computeBottomCompassPlacement(OLEDDisplay *display, int16_t xOffset,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void drawTruncatedStatusLine(OLEDDisplay *display, int16_t x, int16_t y, const std::string &statusText)
|
static void drawTruncatedStatusLine(OLEDDisplay *display, int16_t x, int16_t y, const char *statusText)
|
||||||
{
|
{
|
||||||
// Fixed-buffer truncate helper replaces iterative std::string chopping to keep code size down.
|
// Fixed-buffer truncate helper replaces iterative std::string chopping to keep code size down.
|
||||||
char rawStatus[96];
|
char rawStatus[96];
|
||||||
snprintf(rawStatus, sizeof(rawStatus), " Status: %s", statusText.c_str());
|
snprintf(rawStatus, sizeof(rawStatus), " Status: %s", statusText ? statusText : "");
|
||||||
|
|
||||||
char clippedStatus[96];
|
char clippedStatus[96];
|
||||||
UIRenderer::truncateStringWithEmotes(display, rawStatus, clippedStatus, sizeof(clippedStatus), display->getWidth());
|
UIRenderer::truncateStringWithEmotes(display, rawStatus, clippedStatus, sizeof(clippedStatus), display->getWidth());
|
||||||
@@ -495,7 +495,7 @@ void graphics::UIRenderer::rebuildFavoritedNodes()
|
|||||||
meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i);
|
meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i);
|
||||||
if (!n || n->num == nodeDB->getNodeNum())
|
if (!n || n->num == nodeDB->getNodeNum())
|
||||||
continue;
|
continue;
|
||||||
if (n->is_favorite)
|
if (nodeInfoLiteIsFavorite(n))
|
||||||
favoritedNodes.push_back(n);
|
favoritedNodes.push_back(n);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -750,7 +750,7 @@ void UIRenderer::drawFavoriteNode(OLEDDisplay *display, OLEDDisplayUiState *stat
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
meshtastic_NodeInfoLite *node = favoritedNodes[nodeIndex];
|
meshtastic_NodeInfoLite *node = favoritedNodes[nodeIndex];
|
||||||
if (!node || node->num == nodeDB->getNodeNum() || !node->is_favorite)
|
if (!node || node->num == nodeDB->getNodeNum() || !nodeInfoLiteIsFavorite(node))
|
||||||
return;
|
return;
|
||||||
display->clear();
|
display->clear();
|
||||||
#if defined(M5STACK_UNITC6L)
|
#if defined(M5STACK_UNITC6L)
|
||||||
@@ -763,7 +763,7 @@ void UIRenderer::drawFavoriteNode(OLEDDisplay *display, OLEDDisplayUiState *stat
|
|||||||
#endif
|
#endif
|
||||||
currentFavoriteNodeNum = node->num;
|
currentFavoriteNodeNum = node->num;
|
||||||
// === Create the shortName and title string ===
|
// === Create the shortName and title string ===
|
||||||
const char *shortName = (node->has_user && node->user.short_name[0]) ? node->user.short_name : "Node";
|
const char *shortName = (nodeInfoLiteHasUser(node) && node->short_name[0]) ? node->short_name : "Node";
|
||||||
char titlestr[40];
|
char titlestr[40];
|
||||||
snprintf(titlestr, sizeof(titlestr), "*%s*", shortName);
|
snprintf(titlestr, sizeof(titlestr), "*%s*", shortName);
|
||||||
|
|
||||||
@@ -781,9 +781,9 @@ void UIRenderer::drawFavoriteNode(OLEDDisplay *display, OLEDDisplayUiState *stat
|
|||||||
// === 1. Long Name (always try to show first) ===
|
// === 1. Long Name (always try to show first) ===
|
||||||
const char *username;
|
const char *username;
|
||||||
if (currentResolution == ScreenResolution::UltraLow) {
|
if (currentResolution == ScreenResolution::UltraLow) {
|
||||||
username = (node->has_user && node->user.long_name[0]) ? node->user.short_name : nullptr;
|
username = (nodeInfoLiteHasUser(node) && node->long_name[0]) ? node->short_name : nullptr;
|
||||||
} else {
|
} else {
|
||||||
username = (node->has_user && node->user.long_name[0]) ? node->user.long_name : nullptr;
|
username = (nodeInfoLiteHasUser(node) && node->long_name[0]) ? node->long_name : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print node's long name (e.g. "Backpack Node")
|
// Print node's long name (e.g. "Backpack Node")
|
||||||
@@ -796,23 +796,14 @@ void UIRenderer::drawFavoriteNode(OLEDDisplay *display, OLEDDisplayUiState *stat
|
|||||||
UIRenderer::drawStringWithEmotes(display, x, getTextPositions(display)[line++], username, FONT_HEIGHT_SMALL, 1, false);
|
UIRenderer::drawStringWithEmotes(display, x, getTextPositions(display)[line++], username, FONT_HEIGHT_SMALL, 1, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !MESHTASTIC_EXCLUDE_STATUS
|
#if !MESHTASTIC_EXCLUDE_STATUS && !MESHTASTIC_EXCLUDE_STATUSDB
|
||||||
// === Optional: Last received StatusMessage line for this node ===
|
// === Optional: Last received StatusMessage line for this node ===
|
||||||
// Display it directly under the username line (if we have one).
|
// Display it directly under the username line (if we have one). The cache
|
||||||
if (statusMessageModule) {
|
// lives on NodeDB now, keyed by NodeNum, so this is an O(1) lookup.
|
||||||
const auto &recent = statusMessageModule->getRecentReceived();
|
if (nodeDB) {
|
||||||
const StatusMessageModule::RecentStatus *found = nullptr;
|
meshtastic_StatusMessage cachedStatus;
|
||||||
|
if (nodeDB->copyNodeStatus(node->num, cachedStatus) && cachedStatus.status[0]) {
|
||||||
// Search newest-to-oldest
|
drawTruncatedStatusLine(display, x, getTextPositions(display)[line++], cachedStatus.status);
|
||||||
for (auto it = recent.rbegin(); it != recent.rend(); ++it) {
|
|
||||||
if (it->fromNodeId == node->num && !it->statusText.empty()) {
|
|
||||||
found = &(*it);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (found) {
|
|
||||||
drawTruncatedStatusLine(display, x, getTextPositions(display)[line++], found->statusText);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -971,25 +962,30 @@ void UIRenderer::drawFavoriteNode(OLEDDisplay *display, OLEDDisplayUiState *stat
|
|||||||
#if !defined(M5STACK_UNITC6L)
|
#if !defined(M5STACK_UNITC6L)
|
||||||
// === 4. Uptime (only show if metric is present) ===
|
// === 4. Uptime (only show if metric is present) ===
|
||||||
char uptimeStr[32] = "";
|
char uptimeStr[32] = "";
|
||||||
if (node->has_device_metrics && node->device_metrics.has_uptime_seconds) {
|
meshtastic_DeviceMetrics nodeMetrics;
|
||||||
|
const bool haveNodeMetrics = nodeDB->copyNodeTelemetry(node->num, nodeMetrics);
|
||||||
|
if (haveNodeMetrics && nodeMetrics.has_uptime_seconds) {
|
||||||
char upPrefix[12]; // enough for leftSideSpacing + "Up:"
|
char upPrefix[12]; // enough for leftSideSpacing + "Up:"
|
||||||
snprintf(upPrefix, sizeof(upPrefix), "%sUp:", leftSideSpacing);
|
snprintf(upPrefix, sizeof(upPrefix), "%sUp:", leftSideSpacing);
|
||||||
getUptimeStr(node->device_metrics.uptime_seconds * 1000, upPrefix, uptimeStr, sizeof(uptimeStr));
|
getUptimeStr(nodeMetrics.uptime_seconds * 1000, upPrefix, uptimeStr, sizeof(uptimeStr));
|
||||||
}
|
}
|
||||||
if (uptimeStr[0]) {
|
if (uptimeStr[0]) {
|
||||||
display->drawString(x, getTextPositions(display)[line++], uptimeStr);
|
display->drawString(x, getTextPositions(display)[line++], uptimeStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// === 5. Distance (only if both nodes have GPS position) ===
|
// === 5. Distance (only if both nodes have GPS position) ===
|
||||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
const meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||||
char distStr[24] = ""; // Make buffer big enough for any string
|
char distStr[24] = ""; // Make buffer big enough for any string
|
||||||
bool haveDistance = false;
|
bool haveDistance = false;
|
||||||
|
|
||||||
if (nodeDB->hasValidPosition(ourNode) && nodeDB->hasValidPosition(node)) {
|
meshtastic_PositionLite nodePos;
|
||||||
|
meshtastic_PositionLite ourPos;
|
||||||
|
const bool haveNodePos = nodeDB->copyNodePosition(node->num, nodePos);
|
||||||
|
const bool haveOurPos = ourNode && nodeDB->copyNodePosition(ourNode->num, ourPos);
|
||||||
|
if (nodeDB->hasValidPosition(ourNode) && nodeDB->hasValidPosition(node) && haveNodePos && haveOurPos) {
|
||||||
// Use shared meter conversion, then format display units with lightweight integer rounding.
|
// Use shared meter conversion, then format display units with lightweight integer rounding.
|
||||||
const float distanceMeters =
|
const float distanceMeters = GeoCoord::latLongToMeter(DegD(nodePos.latitude_i), DegD(nodePos.longitude_i),
|
||||||
GeoCoord::latLongToMeter(DegD(node->position.latitude_i), DegD(node->position.longitude_i),
|
DegD(ourPos.latitude_i), DegD(ourPos.longitude_i));
|
||||||
DegD(ourNode->position.latitude_i), DegD(ourNode->position.longitude_i));
|
|
||||||
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
|
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
|
||||||
const int feet = static_cast<int>((distanceMeters * METERS_TO_FEET) + 0.5f);
|
const int feet = static_cast<int>((distanceMeters * METERS_TO_FEET) + 0.5f);
|
||||||
if (feet > 0 && feet < 1000) {
|
if (feet > 0 && feet < 1000) {
|
||||||
@@ -1024,19 +1020,19 @@ void UIRenderer::drawFavoriteNode(OLEDDisplay *display, OLEDDisplayUiState *stat
|
|||||||
char batLine[32] = "";
|
char batLine[32] = "";
|
||||||
bool haveBatLine = false;
|
bool haveBatLine = false;
|
||||||
|
|
||||||
if (node->has_device_metrics) {
|
if (haveNodeMetrics) {
|
||||||
bool hasPct = node->device_metrics.has_battery_level;
|
bool hasPct = nodeMetrics.has_battery_level;
|
||||||
bool hasVolt = node->device_metrics.has_voltage && node->device_metrics.voltage > 0.001f;
|
bool hasVolt = nodeMetrics.has_voltage && nodeMetrics.voltage > 0.001f;
|
||||||
|
|
||||||
int pct = 0;
|
int pct = 0;
|
||||||
float volt = 0.0f;
|
float volt = 0.0f;
|
||||||
|
|
||||||
if (hasPct) {
|
if (hasPct) {
|
||||||
pct = (int)node->device_metrics.battery_level;
|
pct = (int)nodeMetrics.battery_level;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasVolt) {
|
if (hasVolt) {
|
||||||
volt = node->device_metrics.voltage;
|
volt = nodeMetrics.voltage;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasPct && pct > 0 && pct <= 100) {
|
if (hasPct && pct > 0 && pct <= 100) {
|
||||||
@@ -1076,11 +1072,11 @@ void UIRenderer::drawFavoriteNode(OLEDDisplay *display, OLEDDisplayUiState *stat
|
|||||||
const bool hasNodePositionFix = nodeDB->hasValidPosition(node);
|
const bool hasNodePositionFix = nodeDB->hasValidPosition(node);
|
||||||
const char *statusLine1 = nullptr;
|
const char *statusLine1 = nullptr;
|
||||||
const char *statusLine2 = nullptr;
|
const char *statusLine2 = nullptr;
|
||||||
if (hasOwnPositionFix && hasNodePositionFix) {
|
if (hasOwnPositionFix && hasNodePositionFix && haveOurPos && haveNodePos) {
|
||||||
const auto &op = ourNode->position;
|
const auto &op = ourPos;
|
||||||
showCompass = CompassRenderer::getHeadingRadians(DegD(op.latitude_i), DegD(op.longitude_i), myHeading);
|
showCompass = CompassRenderer::getHeadingRadians(DegD(op.latitude_i), DegD(op.longitude_i), myHeading);
|
||||||
if (showCompass) {
|
if (showCompass) {
|
||||||
const auto &p = node->position;
|
const auto &p = nodePos;
|
||||||
bearing = GeoCoord::bearing(DegD(op.latitude_i), DegD(op.longitude_i), DegD(p.latitude_i), DegD(p.longitude_i));
|
bearing = GeoCoord::bearing(DegD(op.latitude_i), DegD(op.longitude_i), DegD(p.latitude_i), DegD(p.longitude_i));
|
||||||
bearing = CompassRenderer::adjustBearingForCompassMode(bearing, myHeading);
|
bearing = CompassRenderer::adjustBearingForCompassMode(bearing, myHeading);
|
||||||
} else {
|
} else {
|
||||||
@@ -1130,7 +1126,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
|
|||||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||||
display->setFont(FONT_SMALL);
|
display->setFont(FONT_SMALL);
|
||||||
int line = 1;
|
int line = 1;
|
||||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
const meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||||
|
|
||||||
// === Header ===
|
// === Header ===
|
||||||
if (currentResolution == ScreenResolution::UltraLow) {
|
if (currentResolution == ScreenResolution::UltraLow) {
|
||||||
@@ -1270,7 +1266,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
|
|||||||
int textWidth = 0;
|
int textWidth = 0;
|
||||||
int nameX = 0;
|
int nameX = 0;
|
||||||
int yOffset = (currentResolution == ScreenResolution::High) ? 0 : 5;
|
int yOffset = (currentResolution == ScreenResolution::High) ? 0 : 5;
|
||||||
const char *longName = (ourNode && ourNode->has_user && ourNode->user.long_name[0]) ? ourNode->user.long_name : "";
|
const char *longName = (nodeInfoLiteHasUser(ourNode) && ourNode->long_name[0]) ? ourNode->long_name : "";
|
||||||
const char *shortName = owner.short_name ? owner.short_name : "";
|
const char *shortName = owner.short_name ? owner.short_name : "";
|
||||||
char combinedName[96];
|
char combinedName[96];
|
||||||
if (longName[0] && shortName[0]) {
|
if (longName[0] && shortName[0]) {
|
||||||
@@ -1597,7 +1593,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
|
|||||||
geoCoord.updateCoords(int32_t(gpsStatus->getLatitude()), int32_t(gpsStatus->getLongitude()),
|
geoCoord.updateCoords(int32_t(gpsStatus->getLatitude()), int32_t(gpsStatus->getLongitude()),
|
||||||
int32_t(gpsStatus->getAltitude()));
|
int32_t(gpsStatus->getAltitude()));
|
||||||
|
|
||||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
const meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||||
const bool hasOwnPositionFix = (ourNode && nodeDB->hasValidPosition(ourNode));
|
const bool hasOwnPositionFix = (ourNode && nodeDB->hasValidPosition(ourNode));
|
||||||
const bool hasLiveGpsFix =
|
const bool hasLiveGpsFix =
|
||||||
(gpsStatus && gpsStatus->getHasLock() && (gpsStatus->getLatitude() != 0 || gpsStatus->getLongitude() != 0));
|
(gpsStatus && gpsStatus->getHasLock() && (gpsStatus->getLatitude() != 0 || gpsStatus->getLongitude() != 0));
|
||||||
@@ -1613,9 +1609,11 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
|
|||||||
headingLat = DegD(gpsStatus->getLatitude());
|
headingLat = DegD(gpsStatus->getLatitude());
|
||||||
headingLon = DegD(gpsStatus->getLongitude());
|
headingLon = DegD(gpsStatus->getLongitude());
|
||||||
} else if (hasOwnPositionFix) {
|
} else if (hasOwnPositionFix) {
|
||||||
const auto &op = ourNode->position;
|
meshtastic_PositionLite ownPos;
|
||||||
headingLat = DegD(op.latitude_i);
|
if (nodeDB->copyNodePosition(ourNode->num, ownPos)) {
|
||||||
headingLon = DegD(op.longitude_i);
|
headingLat = DegD(ownPos.latitude_i);
|
||||||
|
headingLon = DegD(ownPos.longitude_i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
validHeading = CompassRenderer::getHeadingRadians(headingLat, headingLon, heading);
|
validHeading = CompassRenderer::getHeadingRadians(headingLat, headingLon, heading);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -362,8 +362,8 @@ std::string InkHUD::Applet::parseShortName(meshtastic_NodeInfoLite *node)
|
|||||||
assert(node);
|
assert(node);
|
||||||
|
|
||||||
// Use the true shortname if known, and doesn't contain any unprintable characters (emoji, etc.)
|
// Use the true shortname if known, and doesn't contain any unprintable characters (emoji, etc.)
|
||||||
if (node->has_user) {
|
if (nodeInfoLiteHasUser(node)) {
|
||||||
std::string parsed = parse(node->user.short_name);
|
std::string parsed = parse(node->short_name);
|
||||||
if (isPrintable(parsed))
|
if (isPrintable(parsed))
|
||||||
return parsed;
|
return parsed;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,9 +136,10 @@ void InkHUD::MapApplet::onRender(bool full)
|
|||||||
printAt(vertBarX + (bottomLabelW / 2) + 1, bottomLabelY + (bottomLabelH / 2), vertBottomLabel, CENTER, MIDDLE);
|
printAt(vertBarX + (bottomLabelW / 2) + 1, bottomLabelY + (bottomLabelH / 2), vertBottomLabel, CENTER, MIDDLE);
|
||||||
|
|
||||||
// Draw our node LAST with full white fill + outline
|
// Draw our node LAST with full white fill + outline
|
||||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
const meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||||
if (ourNode && nodeDB->hasValidPosition(ourNode)) {
|
meshtastic_PositionLite ourSelfPos;
|
||||||
Marker self = calculateMarker(ourNode->position.latitude_i * 1e-7, ourNode->position.longitude_i * 1e-7, false, 0);
|
if (ourNode && nodeDB->hasValidPosition(ourNode) && nodeDB->copyNodePosition(ourNode->num, ourSelfPos)) {
|
||||||
|
Marker self = calculateMarker(ourSelfPos.latitude_i * 1e-7, ourSelfPos.longitude_i * 1e-7, false, 0);
|
||||||
|
|
||||||
int16_t centerX = X(0.5) + (self.eastMeters * metersToPx);
|
int16_t centerX = X(0.5) + (self.eastMeters * metersToPx);
|
||||||
int16_t centerY = Y(0.5) - (self.northMeters * metersToPx);
|
int16_t centerY = Y(0.5) - (self.northMeters * metersToPx);
|
||||||
@@ -168,10 +169,11 @@ void InkHUD::MapApplet::onRender(bool full)
|
|||||||
void InkHUD::MapApplet::getMapCenter(float *lat, float *lng)
|
void InkHUD::MapApplet::getMapCenter(float *lat, float *lng)
|
||||||
{
|
{
|
||||||
// If we have a valid position for our own node, use that as the anchor
|
// If we have a valid position for our own node, use that as the anchor
|
||||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
const meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||||
if (ourNode && nodeDB->hasValidPosition(ourNode)) {
|
meshtastic_PositionLite ourSelfPos;
|
||||||
*lat = ourNode->position.latitude_i * 1e-7;
|
if (ourNode && nodeDB->hasValidPosition(ourNode) && nodeDB->copyNodePosition(ourNode->num, ourSelfPos)) {
|
||||||
*lng = ourNode->position.longitude_i * 1e-7;
|
*lat = ourSelfPos.latitude_i * 1e-7;
|
||||||
|
*lng = ourSelfPos.longitude_i * 1e-7;
|
||||||
} else {
|
} else {
|
||||||
// Find mean lat long coords
|
// Find mean lat long coords
|
||||||
// ============================
|
// ============================
|
||||||
@@ -201,9 +203,13 @@ void InkHUD::MapApplet::getMapCenter(float *lat, float *lng)
|
|||||||
if (!shouldDrawNode(node))
|
if (!shouldDrawNode(node))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
meshtastic_PositionLite pos;
|
||||||
|
if (!nodeDB->copyNodePosition(node->num, pos))
|
||||||
|
continue;
|
||||||
|
|
||||||
// Latitude and Longitude of node, in radians
|
// Latitude and Longitude of node, in radians
|
||||||
float latRad = node->position.latitude_i * (1e-7) * DEG_TO_RAD;
|
float latRad = pos.latitude_i * (1e-7) * DEG_TO_RAD;
|
||||||
float lngRad = node->position.longitude_i * (1e-7) * DEG_TO_RAD;
|
float lngRad = pos.longitude_i * (1e-7) * DEG_TO_RAD;
|
||||||
|
|
||||||
// Convert to cartesian points, with center of earth at 0, 0, 0
|
// Convert to cartesian points, with center of earth at 0, 0, 0
|
||||||
// Exact distance from center is irrelevant, as we're only interested in the vector
|
// Exact distance from center is irrelevant, as we're only interested in the vector
|
||||||
@@ -300,13 +306,17 @@ void InkHUD::MapApplet::getMapCenter(float *lat, float *lng)
|
|||||||
if (!shouldDrawNode(node))
|
if (!shouldDrawNode(node))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
meshtastic_PositionLite pos;
|
||||||
|
if (!nodeDB->copyNodePosition(node->num, pos))
|
||||||
|
continue;
|
||||||
|
|
||||||
// Check for a new top or bottom latitude
|
// Check for a new top or bottom latitude
|
||||||
float latNode = node->position.latitude_i * 1e-7;
|
float latNode = pos.latitude_i * 1e-7;
|
||||||
northernmost = max(northernmost, latNode);
|
northernmost = max(northernmost, latNode);
|
||||||
southernmost = min(southernmost, latNode);
|
southernmost = min(southernmost, latNode);
|
||||||
|
|
||||||
// Longitude is trickier
|
// Longitude is trickier
|
||||||
float lngNode = node->position.longitude_i * 1e-7;
|
float lngNode = pos.longitude_i * 1e-7;
|
||||||
float degEastward = fmod(((lngNode - lngCenter) + 360), 360); // Degrees traveled east from lngCenter to reach node
|
float degEastward = fmod(((lngNode - lngCenter) + 360), 360); // Degrees traveled east from lngCenter to reach node
|
||||||
float degWestward = abs(fmod(((lngNode - lngCenter) - 360), 360)); // Degrees traveled west from lngCenter to reach node
|
float degWestward = abs(fmod(((lngNode - lngCenter) - 360), 360)); // Degrees traveled west from lngCenter to reach node
|
||||||
if (degEastward < degWestward)
|
if (degEastward < degWestward)
|
||||||
@@ -372,10 +382,13 @@ void InkHUD::MapApplet::drawLabeledMarker(meshtastic_NodeInfoLite *node)
|
|||||||
{
|
{
|
||||||
// Find x and y position based on node's position in nodeDB
|
// Find x and y position based on node's position in nodeDB
|
||||||
assert(nodeDB->hasValidPosition(node));
|
assert(nodeDB->hasValidPosition(node));
|
||||||
Marker m = calculateMarker(node->position.latitude_i * 1e-7, // Lat, converted from Meshtastic's internal int32 style
|
meshtastic_PositionLite pos;
|
||||||
node->position.longitude_i * 1e-7, // Long, converted from Meshtastic's internal int32 style
|
const bool hasPos = nodeDB->copyNodePosition(node->num, pos);
|
||||||
node->has_hops_away, // Is the hopsAway number valid
|
assert(hasPos);
|
||||||
node->hops_away // Hops away
|
Marker m = calculateMarker(pos.latitude_i * 1e-7, // Lat, converted from Meshtastic's internal int32 style
|
||||||
|
pos.longitude_i * 1e-7, // Long, converted from Meshtastic's internal int32 style
|
||||||
|
node->has_hops_away, // Is the hopsAway number valid
|
||||||
|
node->hops_away // Hops away
|
||||||
);
|
);
|
||||||
|
|
||||||
// Convert to pixel coords
|
// Convert to pixel coords
|
||||||
@@ -516,13 +529,16 @@ void InkHUD::MapApplet::calculateAllMarkers()
|
|||||||
if (node->num == nodeDB->getNodeNum())
|
if (node->num == nodeDB->getNodeNum())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
meshtastic_PositionLite pos;
|
||||||
|
if (!nodeDB->copyNodePosition(node->num, pos))
|
||||||
|
continue;
|
||||||
|
|
||||||
// Calculate marker and store it
|
// Calculate marker and store it
|
||||||
markers.push_back(
|
markers.push_back(calculateMarker(pos.latitude_i * 1e-7, // Lat, converted from Meshtastic's internal int32 style
|
||||||
calculateMarker(node->position.latitude_i * 1e-7, // Lat, converted from Meshtastic's internal int32 style
|
pos.longitude_i * 1e-7, // Long, converted from Meshtastic's internal int32 style
|
||||||
node->position.longitude_i * 1e-7, // Long, converted from Meshtastic's internal int32 style
|
node->has_hops_away, // Is the hopsAway number valid
|
||||||
node->has_hops_away, // Is the hopsAway number valid
|
node->hops_away // Hops away
|
||||||
node->hops_away // Hops away
|
));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,21 +50,23 @@ ProcessMessage InkHUD::NodeListApplet::handleReceived(const meshtastic_MeshPacke
|
|||||||
c.signal = getSignalStrength(mp.rx_snr, mp.rx_rssi);
|
c.signal = getSignalStrength(mp.rx_snr, mp.rx_rssi);
|
||||||
|
|
||||||
// Assemble info: from nodeDB (needed to detect changes)
|
// Assemble info: from nodeDB (needed to detect changes)
|
||||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(c.nodeNum);
|
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(c.nodeNum);
|
||||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
const meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||||
if (node) {
|
if (node) {
|
||||||
if (node->has_hops_away)
|
if (node->has_hops_away)
|
||||||
c.hopsAway = node->hops_away;
|
c.hopsAway = node->hops_away;
|
||||||
|
|
||||||
if (nodeDB->hasValidPosition(node) && nodeDB->hasValidPosition(ourNode)) {
|
if (nodeDB->hasValidPosition(node) && nodeDB->hasValidPosition(ourNode)) {
|
||||||
// Get lat and long as float
|
meshtastic_PositionLite ourPos;
|
||||||
// Meshtastic stores these as integers internally
|
meshtastic_PositionLite theirPos;
|
||||||
float ourLat = ourNode->position.latitude_i * 1e-7;
|
if (nodeDB->copyNodePosition(ourNode->num, ourPos) && nodeDB->copyNodePosition(node->num, theirPos)) {
|
||||||
float ourLong = ourNode->position.longitude_i * 1e-7;
|
float ourLat = ourPos.latitude_i * 1e-7;
|
||||||
float theirLat = node->position.latitude_i * 1e-7;
|
float ourLong = ourPos.longitude_i * 1e-7;
|
||||||
float theirLong = node->position.longitude_i * 1e-7;
|
float theirLat = theirPos.latitude_i * 1e-7;
|
||||||
|
float theirLong = theirPos.longitude_i * 1e-7;
|
||||||
|
|
||||||
c.distanceMeters = (int32_t)GeoCoord::latLongToMeter(theirLat, theirLong, ourLat, ourLong);
|
c.distanceMeters = (int32_t)GeoCoord::latLongToMeter(theirLat, theirLong, ourLat, ourLong);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,8 +181,8 @@ void InkHUD::NodeListApplet::onRender(bool full)
|
|||||||
// -- Longname --
|
// -- Longname --
|
||||||
// Parse special chars in long name
|
// Parse special chars in long name
|
||||||
// Use node id if unknown
|
// Use node id if unknown
|
||||||
if (node && node->has_user)
|
if (nodeInfoLiteHasUser(node))
|
||||||
longName = parse(node->user.long_name); // Found in nodeDB
|
longName = parse(node->long_name); // Found in nodeDB
|
||||||
else {
|
else {
|
||||||
// Not found in nodeDB, show a hex nodeid instead
|
// Not found in nodeDB, show a hex nodeid instead
|
||||||
longName = hexifyNodeNum(nodeNum);
|
longName = hexifyNodeNum(nodeNum);
|
||||||
|
|||||||
@@ -2090,7 +2090,7 @@ void InkHUD::MenuApplet::populateRecipientPage()
|
|||||||
|
|
||||||
// Count favorites
|
// Count favorites
|
||||||
for (uint32_t i = 0; i < nodeCount; i++) {
|
for (uint32_t i = 0; i < nodeCount; i++) {
|
||||||
if (nodeDB->getMeshNodeByIndex(i)->is_favorite)
|
if (nodeInfoLiteIsFavorite(nodeDB->getMeshNodeByIndex(i)))
|
||||||
favoriteCount++;
|
favoriteCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2098,10 +2098,10 @@ void InkHUD::MenuApplet::populateRecipientPage()
|
|||||||
// Don't want some monstrous list that takes 100 clicks to reach exit
|
// Don't want some monstrous list that takes 100 clicks to reach exit
|
||||||
if (favoriteCount < 20) {
|
if (favoriteCount < 20) {
|
||||||
for (uint32_t i = 0; i < nodeCount; i++) {
|
for (uint32_t i = 0; i < nodeCount; i++) {
|
||||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
|
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
|
||||||
|
|
||||||
// Skip node if not a favorite
|
// Skip node if not a favorite
|
||||||
if (!node->is_favorite)
|
if (!nodeInfoLiteIsFavorite(node))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
CannedMessages::RecipientItem r;
|
CannedMessages::RecipientItem r;
|
||||||
@@ -2111,8 +2111,8 @@ void InkHUD::MenuApplet::populateRecipientPage()
|
|||||||
|
|
||||||
// Set a label for the menu item
|
// Set a label for the menu item
|
||||||
r.label = "DM: ";
|
r.label = "DM: ";
|
||||||
if (node->has_user)
|
if (nodeInfoLiteHasUser(node))
|
||||||
r.label += parse(node->user.long_name);
|
r.label += parse(node->long_name);
|
||||||
else
|
else
|
||||||
r.label += hexifyNodeNum(node->num); // Unsure if it's possible to favorite a node without NodeInfo?
|
r.label += hexifyNodeNum(node->num); // Unsure if it's possible to favorite a node without NodeInfo?
|
||||||
|
|
||||||
|
|||||||
@@ -241,7 +241,7 @@ std::string InkHUD::NotificationApplet::getNotificationText(uint16_t widthAvaila
|
|||||||
text += msgIsBroadcast ? "From:" : "DM: ";
|
text += msgIsBroadcast ? "From:" : "DM: ";
|
||||||
|
|
||||||
// Sender id
|
// Sender id
|
||||||
if (node && node->has_user)
|
if (nodeInfoLiteHasUser(node))
|
||||||
text += parseShortName(node);
|
text += parseShortName(node);
|
||||||
else
|
else
|
||||||
text += hexifyNodeNum(message->sender);
|
text += hexifyNodeNum(message->sender);
|
||||||
@@ -255,7 +255,7 @@ std::string InkHUD::NotificationApplet::getNotificationText(uint16_t widthAvaila
|
|||||||
text += msgIsBroadcast ? "Msg from " : "DM from ";
|
text += msgIsBroadcast ? "Msg from " : "DM from ";
|
||||||
|
|
||||||
// Sender id
|
// Sender id
|
||||||
if (node && node->has_user)
|
if (nodeInfoLiteHasUser(node))
|
||||||
text += parseShortName(node);
|
text += parseShortName(node);
|
||||||
else
|
else
|
||||||
text += hexifyNodeNum(message->sender);
|
text += hexifyNodeNum(message->sender);
|
||||||
|
|||||||
@@ -70,10 +70,10 @@ void InkHUD::AllMessageApplet::onRender(bool full)
|
|||||||
// - short name and long name, if available, or
|
// - short name and long name, if available, or
|
||||||
// - node id
|
// - node id
|
||||||
meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(message->sender);
|
meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(message->sender);
|
||||||
if (sender && sender->has_user) {
|
if (nodeInfoLiteHasUser(sender)) {
|
||||||
header += parseShortName(sender); // May be last-four of node if unprintable (emoji, etc)
|
header += parseShortName(sender); // May be last-four of node if unprintable (emoji, etc)
|
||||||
header += " (";
|
header += " (";
|
||||||
header += parse(sender->user.long_name);
|
header += parse(sender->long_name);
|
||||||
header += ")";
|
header += ")";
|
||||||
} else
|
} else
|
||||||
header += hexifyNodeNum(message->sender);
|
header += hexifyNodeNum(message->sender);
|
||||||
|
|||||||
@@ -66,10 +66,10 @@ void InkHUD::DMApplet::onRender(bool full)
|
|||||||
// - shortname and long name, if available, or
|
// - shortname and long name, if available, or
|
||||||
// - node id
|
// - node id
|
||||||
meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(latestMessage->dm.sender);
|
meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(latestMessage->dm.sender);
|
||||||
if (sender && sender->has_user) {
|
if (nodeInfoLiteHasUser(sender)) {
|
||||||
header += parseShortName(sender); // May be last-four of node if unprintable (emoji, etc)
|
header += parseShortName(sender); // May be last-four of node if unprintable (emoji, etc)
|
||||||
header += " (";
|
header += " (";
|
||||||
header += parse(sender->user.long_name);
|
header += parse(sender->long_name);
|
||||||
header += ")";
|
header += ")";
|
||||||
} else
|
} else
|
||||||
header += hexifyNodeNum(latestMessage->dm.sender);
|
header += hexifyNodeNum(latestMessage->dm.sender);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ using namespace NicheGraphics;
|
|||||||
bool InkHUD::FavoritesMapApplet::shouldDrawNode(meshtastic_NodeInfoLite *node)
|
bool InkHUD::FavoritesMapApplet::shouldDrawNode(meshtastic_NodeInfoLite *node)
|
||||||
{
|
{
|
||||||
// Keep our own node available as map anchor/center; all others must be favorited.
|
// Keep our own node available as map anchor/center; all others must be favorited.
|
||||||
return node && (node->num == nodeDB->getNodeNum() || node->is_favorite);
|
return node && (node->num == nodeDB->getNodeNum() || nodeInfoLiteIsFavorite(node));
|
||||||
}
|
}
|
||||||
|
|
||||||
void InkHUD::FavoritesMapApplet::onRender(bool full)
|
void InkHUD::FavoritesMapApplet::onRender(bool full)
|
||||||
@@ -25,7 +25,7 @@ void InkHUD::FavoritesMapApplet::onRender(bool full)
|
|||||||
|
|
||||||
// Draw our latest "node of interest" as a special marker.
|
// Draw our latest "node of interest" as a special marker.
|
||||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(lastFrom);
|
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(lastFrom);
|
||||||
if (node && node->is_favorite && nodeDB->hasValidPosition(node) && enoughMarkers())
|
if (node && nodeInfoLiteIsFavorite(node) && nodeDB->hasValidPosition(node) && enoughMarkers())
|
||||||
drawLabeledMarker(node);
|
drawLabeledMarker(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@ ProcessMessage InkHUD::FavoritesMapApplet::handleReceived(const meshtastic_MeshP
|
|||||||
} else {
|
} else {
|
||||||
// For non-local packets, this applet only reacts to favorited nodes.
|
// For non-local packets, this applet only reacts to favorited nodes.
|
||||||
const meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(mp.from);
|
const meshtastic_NodeInfoLite *sender = nodeDB->getMeshNode(mp.from);
|
||||||
if (!sender || !sender->is_favorite)
|
if (!nodeInfoLiteIsFavorite(sender))
|
||||||
return ProcessMessage::CONTINUE;
|
return ProcessMessage::CONTINUE;
|
||||||
|
|
||||||
// Check if this position is from someone different than our previous position packet.
|
// Check if this position is from someone different than our previous position packet.
|
||||||
|
|||||||
@@ -80,8 +80,8 @@ void InkHUD::HeardApplet::populateFromNodeDB()
|
|||||||
ordered.resize(maxCards());
|
ordered.resize(maxCards());
|
||||||
|
|
||||||
// Create card info for these (stale) node observations
|
// Create card info for these (stale) node observations
|
||||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
const meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||||
for (meshtastic_NodeInfoLite *node : ordered) {
|
for (const meshtastic_NodeInfoLite *node : ordered) {
|
||||||
CardInfo c;
|
CardInfo c;
|
||||||
c.nodeNum = node->num;
|
c.nodeNum = node->num;
|
||||||
|
|
||||||
@@ -89,14 +89,16 @@ void InkHUD::HeardApplet::populateFromNodeDB()
|
|||||||
c.hopsAway = node->hops_away;
|
c.hopsAway = node->hops_away;
|
||||||
|
|
||||||
if (nodeDB->hasValidPosition(node) && nodeDB->hasValidPosition(ourNode)) {
|
if (nodeDB->hasValidPosition(node) && nodeDB->hasValidPosition(ourNode)) {
|
||||||
// Get lat and long as float
|
meshtastic_PositionLite ourPos;
|
||||||
// Meshtastic stores these as integers internally
|
meshtastic_PositionLite theirPos;
|
||||||
float ourLat = ourNode->position.latitude_i * 1e-7;
|
if (nodeDB->copyNodePosition(ourNode->num, ourPos) && nodeDB->copyNodePosition(node->num, theirPos)) {
|
||||||
float ourLong = ourNode->position.longitude_i * 1e-7;
|
float ourLat = ourPos.latitude_i * 1e-7;
|
||||||
float theirLat = node->position.latitude_i * 1e-7;
|
float ourLong = ourPos.longitude_i * 1e-7;
|
||||||
float theirLong = node->position.longitude_i * 1e-7;
|
float theirLat = theirPos.latitude_i * 1e-7;
|
||||||
|
float theirLong = theirPos.longitude_i * 1e-7;
|
||||||
|
|
||||||
c.distanceMeters = (int32_t)GeoCoord::latLongToMeter(theirLat, theirLong, ourLat, ourLong);
|
c.distanceMeters = (int32_t)GeoCoord::latLongToMeter(theirLat, theirLong, ourLat, ourLong);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert into the card collection (member of base class)
|
// Insert into the card collection (member of base class)
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ bool CryptoEngine::ensurePkiKeys(meshtastic_Config_SecurityConfig &security, mes
|
|||||||
* @param bytes Buffer containing plaintext input.
|
* @param bytes Buffer containing plaintext input.
|
||||||
* @param bytesOut Output buffer to be populated with encrypted ciphertext.
|
* @param bytesOut Output buffer to be populated with encrypted ciphertext.
|
||||||
*/
|
*/
|
||||||
bool CryptoEngine::encryptCurve25519(uint32_t toNode, uint32_t fromNode, meshtastic_UserLite_public_key_t remotePublic,
|
bool CryptoEngine::encryptCurve25519(uint32_t toNode, uint32_t fromNode, meshtastic_NodeInfoLite_public_key_t remotePublic,
|
||||||
uint64_t packetNum, size_t numBytes, const uint8_t *bytes, uint8_t *bytesOut)
|
uint64_t packetNum, size_t numBytes, const uint8_t *bytes, uint8_t *bytesOut)
|
||||||
{
|
{
|
||||||
uint8_t *auth;
|
uint8_t *auth;
|
||||||
@@ -152,7 +152,7 @@ bool CryptoEngine::encryptCurve25519(uint32_t toNode, uint32_t fromNode, meshtas
|
|||||||
* @param bytes Buffer containing ciphertext input.
|
* @param bytes Buffer containing ciphertext input.
|
||||||
* @param bytesOut Output buffer to be populated with decrypted plaintext.
|
* @param bytesOut Output buffer to be populated with decrypted plaintext.
|
||||||
*/
|
*/
|
||||||
bool CryptoEngine::decryptCurve25519(uint32_t fromNode, meshtastic_UserLite_public_key_t remotePublic, uint64_t packetNum,
|
bool CryptoEngine::decryptCurve25519(uint32_t fromNode, meshtastic_NodeInfoLite_public_key_t remotePublic, uint64_t packetNum,
|
||||||
size_t numBytes, const uint8_t *bytes, uint8_t *bytesOut)
|
size_t numBytes, const uint8_t *bytes, uint8_t *bytesOut)
|
||||||
{
|
{
|
||||||
const uint8_t *auth = bytes + numBytes - 12; // set to last 8 bytes of text?
|
const uint8_t *auth = bytes + numBytes - 12; // set to last 8 bytes of text?
|
||||||
|
|||||||
@@ -40,9 +40,12 @@ class CryptoEngine
|
|||||||
|
|
||||||
#endif
|
#endif
|
||||||
void setDHPrivateKey(uint8_t *_private_key);
|
void setDHPrivateKey(uint8_t *_private_key);
|
||||||
virtual bool encryptCurve25519(uint32_t toNode, uint32_t fromNode, meshtastic_UserLite_public_key_t remotePublic,
|
// The remotePublic key parameter takes the public_key bytes container from
|
||||||
|
// a stored node header. NodeInfoLite is the on-device storage type since
|
||||||
|
// the slim refactor flattened UserLite into it.
|
||||||
|
virtual bool encryptCurve25519(uint32_t toNode, uint32_t fromNode, meshtastic_NodeInfoLite_public_key_t remotePublic,
|
||||||
uint64_t packetNum, size_t numBytes, const uint8_t *bytes, uint8_t *bytesOut);
|
uint64_t packetNum, size_t numBytes, const uint8_t *bytes, uint8_t *bytesOut);
|
||||||
virtual bool decryptCurve25519(uint32_t fromNode, meshtastic_UserLite_public_key_t remotePublic, uint64_t packetNum,
|
virtual bool decryptCurve25519(uint32_t fromNode, meshtastic_NodeInfoLite_public_key_t remotePublic, uint64_t packetNum,
|
||||||
size_t numBytes, const uint8_t *bytes, uint8_t *bytesOut);
|
size_t numBytes, const uint8_t *bytes, uint8_t *bytesOut);
|
||||||
virtual bool setDHPublicKey(uint8_t *publicKey);
|
virtual bool setDHPublicKey(uint8_t *publicKey);
|
||||||
virtual void hash(uint8_t *bytes, size_t numBytes);
|
virtual void hash(uint8_t *bytes, size_t numBytes);
|
||||||
|
|||||||
+12
-12
@@ -94,8 +94,9 @@ int MeshService::handleFromRadio(const meshtastic_MeshPacket *mp)
|
|||||||
mp->decoded.portnum == meshtastic_PortNum_TELEMETRY_APP && mp->decoded.request_id > 0) {
|
mp->decoded.portnum == meshtastic_PortNum_TELEMETRY_APP && mp->decoded.request_id > 0) {
|
||||||
LOG_DEBUG("Received telemetry response. Skip sending our NodeInfo");
|
LOG_DEBUG("Received telemetry response. Skip sending our NodeInfo");
|
||||||
// ignore our request for its NodeInfo
|
// ignore our request for its NodeInfo
|
||||||
} else if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag && !nodeDB->getMeshNode(mp->from)->has_user &&
|
} else if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
|
||||||
nodeInfoModule && !isPreferredRebroadcaster && !nodeDB->isFull()) {
|
!nodeInfoLiteHasUser(nodeDB->getMeshNode(mp->from)) && nodeInfoModule && !isPreferredRebroadcaster &&
|
||||||
|
!nodeDB->isFull()) {
|
||||||
if (airTime->isTxAllowedChannelUtil(true)) {
|
if (airTime->isTxAllowedChannelUtil(true)) {
|
||||||
const int8_t hopsUsed = getHopsAway(*mp, config.lora.hop_limit);
|
const int8_t hopsUsed = getHopsAway(*mp, config.lora.hop_limit);
|
||||||
if (hopsUsed > (int32_t)(config.lora.hop_limit + 2)) {
|
if (hopsUsed > (int32_t)(config.lora.hop_limit + 2)) {
|
||||||
@@ -392,19 +393,16 @@ meshtastic_NodeInfoLite *MeshService::refreshLocalMeshNode()
|
|||||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||||
assert(node);
|
assert(node);
|
||||||
|
|
||||||
// We might not have a position yet for our local node, in that case, at least try to send the time
|
|
||||||
if (!node->has_position) {
|
|
||||||
memset(&node->position, 0, sizeof(node->position));
|
|
||||||
node->has_position = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
meshtastic_PositionLite &position = node->position;
|
|
||||||
|
|
||||||
// Update our local node info with our time (even if we don't decide to update anyone else)
|
// Update our local node info with our time (even if we don't decide to update anyone else)
|
||||||
node->last_heard =
|
node->last_heard =
|
||||||
getValidTime(RTCQualityFromNet); // This nodedb timestamp might be stale, so update it if our clock is kinda valid
|
getValidTime(RTCQualityFromNet); // This nodedb timestamp might be stale, so update it if our clock is kinda valid
|
||||||
|
|
||||||
position.time = getValidTime(RTCQualityFromNet);
|
#if !MESHTASTIC_EXCLUDE_POSITIONDB
|
||||||
|
// Make sure our own NodeNum has a slot in the position map so subsequent
|
||||||
|
// updates (and the bundled NodeInfo emission to the phone) have somewhere
|
||||||
|
// to read from. Insert a default-zero entry on first call.
|
||||||
|
nodeDB->touchNodePositionTime(node->num, getValidTime(RTCQualityFromNet));
|
||||||
|
#endif
|
||||||
|
|
||||||
if (powerStatus->getHasBattery() == 1) {
|
if (powerStatus->getHasBattery() == 1) {
|
||||||
updateBatteryLevel(powerStatus->getBatteryChargePercent());
|
updateBatteryLevel(powerStatus->getBatteryChargePercent());
|
||||||
@@ -432,7 +430,9 @@ int MeshService::onGPSChanged(const meshtastic::GPSStatus *newStatus)
|
|||||||
// Used fixed position if configured regardless of GPS lock
|
// Used fixed position if configured regardless of GPS lock
|
||||||
if (config.position.fixed_position) {
|
if (config.position.fixed_position) {
|
||||||
LOG_WARN("Use fixed position");
|
LOG_WARN("Use fixed position");
|
||||||
pos = TypeConversions::ConvertToPosition(node->position);
|
meshtastic_PositionLite fixedSlot;
|
||||||
|
if (nodeDB->copyNodePosition(node->num, fixedSlot))
|
||||||
|
pos = TypeConversions::ConvertToPosition(fixedSlot);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a fresh timestamp
|
// Add a fresh timestamp
|
||||||
|
|||||||
+553
-125
@@ -22,6 +22,7 @@
|
|||||||
#include "error.h"
|
#include "error.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "mesh-pb-constants.h"
|
#include "mesh-pb-constants.h"
|
||||||
|
#include "mesh/generated/meshtastic/deviceonly_legacy.pb.h"
|
||||||
#include "meshUtils.h"
|
#include "meshUtils.h"
|
||||||
#include "modules/NeighborInfoModule.h"
|
#include "modules/NeighborInfoModule.h"
|
||||||
#include <ErriezCRC32.h>
|
#include <ErriezCRC32.h>
|
||||||
@@ -151,24 +152,103 @@ uint32_t get_st7789_id(uint8_t cs, uint8_t sck, uint8_t mosi, uint8_t dc, uint8_
|
|||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
bool meshtastic_NodeDatabase_callback(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_iter_t *field)
|
bool meshtastic_NodeDatabase_callback(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_t *field)
|
||||||
{
|
{
|
||||||
if (ostream) {
|
const auto *iter = reinterpret_cast<const pb_field_iter_t *>(field);
|
||||||
std::vector<meshtastic_NodeInfoLite> const *vec = (std::vector<meshtastic_NodeInfoLite> *)field->pData;
|
switch (iter->tag) {
|
||||||
for (auto item : *vec) {
|
case meshtastic_NodeDatabase_nodes_tag: {
|
||||||
if (!pb_encode_tag_for_field(ostream, field))
|
if (ostream) {
|
||||||
return false;
|
const auto *vec = static_cast<const std::vector<meshtastic_NodeInfoLite> *>(iter->pData);
|
||||||
pb_encode_submessage(ostream, meshtastic_NodeInfoLite_fields, &item);
|
for (auto item : *vec) {
|
||||||
|
if (!pb_encode_tag_for_field(ostream, iter))
|
||||||
|
return false;
|
||||||
|
if (!pb_encode_submessage(ostream, meshtastic_NodeInfoLite_fields, &item))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (istream) {
|
||||||
|
meshtastic_NodeInfoLite node;
|
||||||
|
auto *vec = static_cast<std::vector<meshtastic_NodeInfoLite> *>(iter->pData);
|
||||||
|
if (istream->bytes_left && pb_decode(istream, meshtastic_NodeInfoLite_fields, &node))
|
||||||
|
vec->push_back(node);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
if (istream) {
|
case meshtastic_NodeDatabase_positions_tag: {
|
||||||
meshtastic_NodeInfoLite node; // this gets good data
|
if (ostream) {
|
||||||
std::vector<meshtastic_NodeInfoLite> *vec = (std::vector<meshtastic_NodeInfoLite> *)field->pData;
|
const auto *vec = static_cast<const std::vector<meshtastic_NodePositionEntry> *>(iter->pData);
|
||||||
|
for (auto item : *vec) {
|
||||||
if (istream->bytes_left && pb_decode(istream, meshtastic_NodeInfoLite_fields, &node))
|
if (!pb_encode_tag_for_field(ostream, iter))
|
||||||
vec->push_back(node);
|
return false;
|
||||||
|
if (!pb_encode_submessage(ostream, meshtastic_NodePositionEntry_fields, &item))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (istream) {
|
||||||
|
meshtastic_NodePositionEntry entry;
|
||||||
|
auto *vec = static_cast<std::vector<meshtastic_NodePositionEntry> *>(iter->pData);
|
||||||
|
if (istream->bytes_left && pb_decode(istream, meshtastic_NodePositionEntry_fields, &entry))
|
||||||
|
vec->push_back(entry);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case meshtastic_NodeDatabase_telemetry_tag: {
|
||||||
|
if (ostream) {
|
||||||
|
const auto *vec = static_cast<const std::vector<meshtastic_NodeTelemetryEntry> *>(iter->pData);
|
||||||
|
for (auto item : *vec) {
|
||||||
|
if (!pb_encode_tag_for_field(ostream, iter))
|
||||||
|
return false;
|
||||||
|
if (!pb_encode_submessage(ostream, meshtastic_NodeTelemetryEntry_fields, &item))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (istream) {
|
||||||
|
meshtastic_NodeTelemetryEntry entry;
|
||||||
|
auto *vec = static_cast<std::vector<meshtastic_NodeTelemetryEntry> *>(iter->pData);
|
||||||
|
if (istream->bytes_left && pb_decode(istream, meshtastic_NodeTelemetryEntry_fields, &entry))
|
||||||
|
vec->push_back(entry);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case meshtastic_NodeDatabase_status_tag: {
|
||||||
|
if (ostream) {
|
||||||
|
const auto *vec = static_cast<const std::vector<meshtastic_NodeStatusEntry> *>(iter->pData);
|
||||||
|
for (auto item : *vec) {
|
||||||
|
if (!pb_encode_tag_for_field(ostream, iter))
|
||||||
|
return false;
|
||||||
|
if (!pb_encode_submessage(ostream, meshtastic_NodeStatusEntry_fields, &item))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (istream) {
|
||||||
|
meshtastic_NodeStatusEntry entry;
|
||||||
|
auto *vec = static_cast<std::vector<meshtastic_NodeStatusEntry> *>(iter->pData);
|
||||||
|
if (istream->bytes_left && pb_decode(istream, meshtastic_NodeStatusEntry_fields, &entry))
|
||||||
|
vec->push_back(entry);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case meshtastic_NodeDatabase_environment_tag: {
|
||||||
|
if (ostream) {
|
||||||
|
const auto *vec = static_cast<const std::vector<meshtastic_NodeEnvironmentEntry> *>(iter->pData);
|
||||||
|
for (auto item : *vec) {
|
||||||
|
if (!pb_encode_tag_for_field(ostream, iter))
|
||||||
|
return false;
|
||||||
|
if (!pb_encode_submessage(ostream, meshtastic_NodeEnvironmentEntry_fields, &item))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (istream) {
|
||||||
|
meshtastic_NodeEnvironmentEntry entry;
|
||||||
|
auto *vec = static_cast<std::vector<meshtastic_NodeEnvironmentEntry> *>(iter->pData);
|
||||||
|
if (istream->bytes_left && pb_decode(istream, meshtastic_NodeEnvironmentEntry_fields, &entry))
|
||||||
|
vec->push_back(entry);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The current change # for radio settings. Starts at 0 on boot and any time the radio settings
|
/** The current change # for radio settings. Starts at 0 on boot and any time the radio settings
|
||||||
@@ -298,8 +378,7 @@ NodeDB::NodeDB()
|
|||||||
#endif
|
#endif
|
||||||
// Include our owner in the node db under our nodenum
|
// Include our owner in the node db under our nodenum
|
||||||
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(getNodeNum());
|
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(getNodeNum());
|
||||||
info->user = TypeConversions::ConvertToUserLite(owner);
|
TypeConversions::CopyUserToNodeInfoLite(info, owner);
|
||||||
info->has_user = true;
|
|
||||||
|
|
||||||
// If node database has not been saved for the first time, save it now
|
// If node database has not been saved for the first time, save it now
|
||||||
#ifdef FSCom
|
#ifdef FSCom
|
||||||
@@ -442,8 +521,12 @@ NodeDB::NodeDB()
|
|||||||
#if defined(USERPREFS_FIXED_GPS_LAT) && defined(USERPREFS_FIXED_GPS_LON)
|
#if defined(USERPREFS_FIXED_GPS_LAT) && defined(USERPREFS_FIXED_GPS_LON)
|
||||||
fixedGPS.location_source = meshtastic_Position_LocSource_LOC_MANUAL;
|
fixedGPS.location_source = meshtastic_Position_LocSource_LOC_MANUAL;
|
||||||
config.has_position = true;
|
config.has_position = true;
|
||||||
info->has_position = true;
|
#if !MESHTASTIC_EXCLUDE_POSITIONDB
|
||||||
info->position = TypeConversions::ConvertToPositionLite(fixedGPS);
|
{
|
||||||
|
concurrency::LockGuard guard(&satelliteMutex);
|
||||||
|
nodePositions[info->num] = TypeConversions::ConvertToPositionLite(fixedGPS);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
nodeDB->setLocalPosition(fixedGPS);
|
nodeDB->setLocalPosition(fixedGPS);
|
||||||
config.position.fixed_position = true;
|
config.position.fixed_position = true;
|
||||||
#endif
|
#endif
|
||||||
@@ -479,6 +562,29 @@ bool isBroadcast(uint32_t dest)
|
|||||||
return dest == NODENUM_BROADCAST || dest == NODENUM_BROADCAST_NO_LORA;
|
return dest == NODENUM_BROADCAST || dest == NODENUM_BROADCAST_NO_LORA;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
template <typename Map, typename Value> bool copySatelliteEntry(const Map &map, NodeNum n, Value &out)
|
||||||
|
{
|
||||||
|
auto it = map.find(n);
|
||||||
|
if (it == map.end())
|
||||||
|
return false;
|
||||||
|
out = it->second;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Map> std::vector<NodeNum> snapshotSatelliteNodeNums(const Map &map, NodeNum exclude)
|
||||||
|
{
|
||||||
|
std::vector<NodeNum> result;
|
||||||
|
result.reserve(map.size());
|
||||||
|
for (const auto &kv : map) {
|
||||||
|
if (kv.first != exclude)
|
||||||
|
result.push_back(kv.first);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
void NodeDB::resetRadioConfig(bool is_fresh_install)
|
void NodeDB::resetRadioConfig(bool is_fresh_install)
|
||||||
{
|
{
|
||||||
if (is_fresh_install) {
|
if (is_fresh_install) {
|
||||||
@@ -548,6 +654,19 @@ void NodeDB::installDefaultNodeDatabase()
|
|||||||
nodeDatabase.nodes = std::vector<meshtastic_NodeInfoLite>(MAX_NUM_NODES);
|
nodeDatabase.nodes = std::vector<meshtastic_NodeInfoLite>(MAX_NUM_NODES);
|
||||||
numMeshNodes = 0;
|
numMeshNodes = 0;
|
||||||
meshNodes = &nodeDatabase.nodes;
|
meshNodes = &nodeDatabase.nodes;
|
||||||
|
concurrency::LockGuard satelliteGuard(&satelliteMutex);
|
||||||
|
#if !MESHTASTIC_EXCLUDE_POSITIONDB
|
||||||
|
nodePositions.clear();
|
||||||
|
#endif
|
||||||
|
#if !MESHTASTIC_EXCLUDE_TELEMETRYDB
|
||||||
|
nodeTelemetry.clear();
|
||||||
|
#endif
|
||||||
|
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTDB
|
||||||
|
nodeEnvironment.clear();
|
||||||
|
#endif
|
||||||
|
#if !MESHTASTIC_EXCLUDE_STATUSDB
|
||||||
|
nodeStatus.clear();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void NodeDB::installDefaultConfig(bool preserveKey = false)
|
void NodeDB::installDefaultConfig(bool preserveKey = false)
|
||||||
@@ -1019,12 +1138,14 @@ void NodeDB::resetNodes(bool keepFavorites)
|
|||||||
{
|
{
|
||||||
if (!config.position.fixed_position)
|
if (!config.position.fixed_position)
|
||||||
clearLocalPosition();
|
clearLocalPosition();
|
||||||
|
NodeNum ourNum = getNodeNum();
|
||||||
numMeshNodes = 1;
|
numMeshNodes = 1;
|
||||||
if (keepFavorites) {
|
if (keepFavorites) {
|
||||||
LOG_INFO("Clearing node database - preserving favorites");
|
LOG_INFO("Clearing node database - preserving favorites");
|
||||||
for (size_t i = 0; i < meshNodes->size(); i++) {
|
for (size_t i = 0; i < meshNodes->size(); i++) {
|
||||||
meshtastic_NodeInfoLite &node = meshNodes->at(i);
|
meshtastic_NodeInfoLite &node = meshNodes->at(i);
|
||||||
if (i > 0 && !node.is_favorite) {
|
if (i > 0 && !nodeInfoLiteIsFavorite(&node)) {
|
||||||
|
eraseNodeSatellites(node.num);
|
||||||
node = meshtastic_NodeInfoLite();
|
node = meshtastic_NodeInfoLite();
|
||||||
} else {
|
} else {
|
||||||
numMeshNodes += 1;
|
numMeshNodes += 1;
|
||||||
@@ -1032,8 +1153,14 @@ void NodeDB::resetNodes(bool keepFavorites)
|
|||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
LOG_INFO("Clearing node database - removing favorites");
|
LOG_INFO("Clearing node database - removing favorites");
|
||||||
|
for (size_t i = 1; i < meshNodes->size(); i++) {
|
||||||
|
const NodeNum gone = meshNodes->at(i).num;
|
||||||
|
if (gone)
|
||||||
|
eraseNodeSatellites(gone);
|
||||||
|
}
|
||||||
std::fill(nodeDatabase.nodes.begin() + 1, nodeDatabase.nodes.end(), meshtastic_NodeInfoLite());
|
std::fill(nodeDatabase.nodes.begin() + 1, nodeDatabase.nodes.end(), meshtastic_NodeInfoLite());
|
||||||
}
|
}
|
||||||
|
(void)ourNum;
|
||||||
devicestate.has_rx_text_message = false;
|
devicestate.has_rx_text_message = false;
|
||||||
devicestate.has_rx_waypoint = false;
|
devicestate.has_rx_waypoint = false;
|
||||||
saveNodeDatabaseToDisk();
|
saveNodeDatabaseToDisk();
|
||||||
@@ -1054,36 +1181,173 @@ void NodeDB::removeNodeByNum(NodeNum nodeNum)
|
|||||||
numMeshNodes -= removed;
|
numMeshNodes -= removed;
|
||||||
std::fill(nodeDatabase.nodes.begin() + numMeshNodes, nodeDatabase.nodes.begin() + numMeshNodes + 1,
|
std::fill(nodeDatabase.nodes.begin() + numMeshNodes, nodeDatabase.nodes.begin() + numMeshNodes + 1,
|
||||||
meshtastic_NodeInfoLite());
|
meshtastic_NodeInfoLite());
|
||||||
|
if (removed)
|
||||||
|
eraseNodeSatellites(nodeNum);
|
||||||
LOG_DEBUG("NodeDB::removeNodeByNum purged %d entries. Save changes", removed);
|
LOG_DEBUG("NodeDB::removeNodeByNum purged %d entries. Save changes", removed);
|
||||||
saveNodeDatabaseToDisk();
|
saveNodeDatabaseToDisk();
|
||||||
}
|
}
|
||||||
|
|
||||||
void NodeDB::clearLocalPosition()
|
void NodeDB::clearLocalPosition()
|
||||||
{
|
{
|
||||||
meshtastic_NodeInfoLite *node = getMeshNode(nodeDB->getNodeNum());
|
#if !MESHTASTIC_EXCLUDE_POSITIONDB
|
||||||
node->position.latitude_i = 0;
|
concurrency::LockGuard guard(&satelliteMutex);
|
||||||
node->position.longitude_i = 0;
|
nodePositions.erase(getNodeNum());
|
||||||
node->position.altitude = 0;
|
#endif
|
||||||
node->position.time = 0;
|
|
||||||
setLocalPosition(meshtastic_Position_init_default);
|
setLocalPosition(meshtastic_Position_init_default);
|
||||||
localPositionUpdatedSinceBoot = false;
|
localPositionUpdatedSinceBoot = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool NodeDB::copyNodePosition(NodeNum n, meshtastic_PositionLite &out) const
|
||||||
|
{
|
||||||
|
#if MESHTASTIC_EXCLUDE_POSITIONDB
|
||||||
|
(void)n;
|
||||||
|
(void)out;
|
||||||
|
return false;
|
||||||
|
#else
|
||||||
|
concurrency::LockGuard guard(&satelliteMutex);
|
||||||
|
return copySatelliteEntry(nodePositions, n, out);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NodeDB::copyNodeTelemetry(NodeNum n, meshtastic_DeviceMetrics &out) const
|
||||||
|
{
|
||||||
|
#if MESHTASTIC_EXCLUDE_TELEMETRYDB
|
||||||
|
(void)n;
|
||||||
|
(void)out;
|
||||||
|
return false;
|
||||||
|
#else
|
||||||
|
concurrency::LockGuard guard(&satelliteMutex);
|
||||||
|
return copySatelliteEntry(nodeTelemetry, n, out);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NodeDB::copyNodeEnvironment(NodeNum n, meshtastic_EnvironmentMetrics &out) const
|
||||||
|
{
|
||||||
|
#if MESHTASTIC_EXCLUDE_ENVIRONMENTDB
|
||||||
|
(void)n;
|
||||||
|
(void)out;
|
||||||
|
return false;
|
||||||
|
#else
|
||||||
|
concurrency::LockGuard guard(&satelliteMutex);
|
||||||
|
return copySatelliteEntry(nodeEnvironment, n, out);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NodeDB::copyNodeStatus(NodeNum n, meshtastic_StatusMessage &out) const
|
||||||
|
{
|
||||||
|
#if MESHTASTIC_EXCLUDE_STATUSDB
|
||||||
|
(void)n;
|
||||||
|
(void)out;
|
||||||
|
return false;
|
||||||
|
#else
|
||||||
|
concurrency::LockGuard guard(&satelliteMutex);
|
||||||
|
return copySatelliteEntry(nodeStatus, n, out);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<NodeNum> NodeDB::snapshotPositionNodeNums(NodeNum exclude) const
|
||||||
|
{
|
||||||
|
#if MESHTASTIC_EXCLUDE_POSITIONDB
|
||||||
|
(void)exclude;
|
||||||
|
return {};
|
||||||
|
#else
|
||||||
|
concurrency::LockGuard guard(&satelliteMutex);
|
||||||
|
return snapshotSatelliteNodeNums(nodePositions, exclude);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<NodeNum> NodeDB::snapshotTelemetryNodeNums(NodeNum exclude) const
|
||||||
|
{
|
||||||
|
#if MESHTASTIC_EXCLUDE_TELEMETRYDB
|
||||||
|
(void)exclude;
|
||||||
|
return {};
|
||||||
|
#else
|
||||||
|
concurrency::LockGuard guard(&satelliteMutex);
|
||||||
|
return snapshotSatelliteNodeNums(nodeTelemetry, exclude);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<NodeNum> NodeDB::snapshotEnvironmentNodeNums(NodeNum exclude) const
|
||||||
|
{
|
||||||
|
#if MESHTASTIC_EXCLUDE_ENVIRONMENTDB
|
||||||
|
(void)exclude;
|
||||||
|
return {};
|
||||||
|
#else
|
||||||
|
concurrency::LockGuard guard(&satelliteMutex);
|
||||||
|
return snapshotSatelliteNodeNums(nodeEnvironment, exclude);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<NodeNum> NodeDB::snapshotStatusNodeNums(NodeNum exclude) const
|
||||||
|
{
|
||||||
|
#if MESHTASTIC_EXCLUDE_STATUSDB
|
||||||
|
(void)exclude;
|
||||||
|
return {};
|
||||||
|
#else
|
||||||
|
concurrency::LockGuard guard(&satelliteMutex);
|
||||||
|
return snapshotSatelliteNodeNums(nodeStatus, exclude);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void NodeDB::setNodeStatus(NodeNum n, const meshtastic_StatusMessage &status)
|
||||||
|
{
|
||||||
|
#if MESHTASTIC_EXCLUDE_STATUSDB
|
||||||
|
(void)n;
|
||||||
|
(void)status;
|
||||||
|
#else
|
||||||
|
concurrency::LockGuard guard(&satelliteMutex);
|
||||||
|
nodeStatus[n] = status;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void NodeDB::touchNodePositionTime(NodeNum n, uint32_t time)
|
||||||
|
{
|
||||||
|
#if MESHTASTIC_EXCLUDE_POSITIONDB
|
||||||
|
(void)n;
|
||||||
|
(void)time;
|
||||||
|
#else
|
||||||
|
concurrency::LockGuard guard(&satelliteMutex);
|
||||||
|
nodePositions[n].time = time;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void NodeDB::eraseNodeSatellites(NodeNum n)
|
||||||
|
{
|
||||||
|
concurrency::LockGuard guard(&satelliteMutex);
|
||||||
|
#if !MESHTASTIC_EXCLUDE_POSITIONDB
|
||||||
|
nodePositions.erase(n);
|
||||||
|
#endif
|
||||||
|
#if !MESHTASTIC_EXCLUDE_TELEMETRYDB
|
||||||
|
nodeTelemetry.erase(n);
|
||||||
|
#endif
|
||||||
|
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTDB
|
||||||
|
nodeEnvironment.erase(n);
|
||||||
|
#endif
|
||||||
|
#if !MESHTASTIC_EXCLUDE_STATUSDB
|
||||||
|
nodeStatus.erase(n);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
void NodeDB::cleanupMeshDB()
|
void NodeDB::cleanupMeshDB()
|
||||||
{
|
{
|
||||||
int newPos = 0, removed = 0;
|
int newPos = 0, removed = 0;
|
||||||
for (int i = 0; i < numMeshNodes; i++) {
|
for (int i = 0; i < numMeshNodes; i++) {
|
||||||
if (meshNodes->at(i).has_user) {
|
meshtastic_NodeInfoLite &n = meshNodes->at(i);
|
||||||
if (meshNodes->at(i).user.public_key.size > 0) {
|
if (nodeInfoLiteHasUser(&n)) {
|
||||||
if (memfll(meshNodes->at(i).user.public_key.bytes, 0, meshNodes->at(i).user.public_key.size)) {
|
if (n.public_key.size > 0) {
|
||||||
meshNodes->at(i).user.public_key.size = 0;
|
if (memfll(n.public_key.bytes, 0, n.public_key.size)) {
|
||||||
|
n.public_key.size = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (newPos != i)
|
if (newPos != i)
|
||||||
meshNodes->at(newPos++) = meshNodes->at(i);
|
meshNodes->at(newPos++) = n;
|
||||||
else
|
else
|
||||||
newPos++;
|
newPos++;
|
||||||
} else {
|
} else {
|
||||||
|
// No user info - drop this node and its satellites
|
||||||
|
const NodeNum gone = n.num;
|
||||||
|
if (gone)
|
||||||
|
eraseNodeSatellites(gone);
|
||||||
removed++;
|
removed++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1141,14 +1405,22 @@ void NodeDB::pickNewNodeNum()
|
|||||||
nodeNum = (ourMacAddr[2] << 24) | (ourMacAddr[3] << 16) | (ourMacAddr[4] << 8) | ourMacAddr[5];
|
nodeNum = (ourMacAddr[2] << 24) | (ourMacAddr[3] << 16) | (ourMacAddr[4] << 8) | ourMacAddr[5];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Identity check via public key (or "empty slot?" when no keys yet);
|
||||||
|
// macaddr no longer lives on the slim header.
|
||||||
|
auto isOurOwnEntry = [&](const meshtastic_NodeInfoLite *n) -> bool {
|
||||||
|
if (!n)
|
||||||
|
return false;
|
||||||
|
if (owner.public_key.size == 32 && n->public_key.size == 32)
|
||||||
|
return memcmp(n->public_key.bytes, owner.public_key.bytes, 32) == 0;
|
||||||
|
return !nodeInfoLiteHasUser(n);
|
||||||
|
};
|
||||||
|
|
||||||
meshtastic_NodeInfoLite *found;
|
meshtastic_NodeInfoLite *found;
|
||||||
while (((found = getMeshNode(nodeNum)) && memcmp(found->user.macaddr, ourMacAddr, sizeof(ourMacAddr)) != 0) ||
|
while (((found = getMeshNode(nodeNum)) && !isOurOwnEntry(found)) ||
|
||||||
(nodeNum == NODENUM_BROADCAST || nodeNum < NUM_RESERVED)) {
|
(nodeNum == NODENUM_BROADCAST || nodeNum < NUM_RESERVED)) {
|
||||||
NodeNum candidate = random(NUM_RESERVED, LONG_MAX); // try a new random choice
|
NodeNum candidate = random(NUM_RESERVED, LONG_MAX); // try a new random choice
|
||||||
if (found)
|
if (found)
|
||||||
LOG_WARN("NOTE! Our desired nodenum 0x%x is invalid or in use, by MAC ending in 0x%02x%02x vs our 0x%02x%02x, so "
|
LOG_WARN("NOTE! Our desired nodenum 0x%x is invalid or in use, picking 0x%x", nodeNum, candidate);
|
||||||
"trying for 0x%x",
|
|
||||||
nodeNum, found->user.macaddr[4], found->user.macaddr[5], ourMacAddr[4], ourMacAddr[5], candidate);
|
|
||||||
nodeNum = candidate;
|
nodeNum = candidate;
|
||||||
}
|
}
|
||||||
LOG_DEBUG("Use nodenum 0x%x ", nodeNum);
|
LOG_DEBUG("Use nodenum 0x%x ", nodeNum);
|
||||||
@@ -1169,7 +1441,8 @@ LoadFileResult NodeDB::loadProto(const char *filename, size_t protoSize, size_t
|
|||||||
if (f) {
|
if (f) {
|
||||||
LOG_INFO("Load %s", filename);
|
LOG_INFO("Load %s", filename);
|
||||||
pb_istream_t stream = {&readcb, &f, protoSize};
|
pb_istream_t stream = {&readcb, &f, protoSize};
|
||||||
if (fields != &meshtastic_NodeDatabase_msg) // contains a vector object
|
if (fields != &meshtastic_NodeDatabase_msg &&
|
||||||
|
fields != &meshtastic_NodeDatabase_Legacy_msg) // both NodeDatabase descriptors contain std::vector members
|
||||||
memset(dest_struct, 0, objSize);
|
memset(dest_struct, 0, objSize);
|
||||||
if (!pb_decode(&stream, fields, dest_struct)) {
|
if (!pb_decode(&stream, fields, dest_struct)) {
|
||||||
LOG_ERROR("Error: can't decode protobuf %s", PB_GET_ERROR(&stream));
|
LOG_ERROR("Error: can't decode protobuf %s", PB_GET_ERROR(&stream));
|
||||||
@@ -1242,9 +1515,57 @@ void NodeDB::loadFromDisk()
|
|||||||
if (nodeDatabase.version < DEVICESTATE_MIN_VER) {
|
if (nodeDatabase.version < DEVICESTATE_MIN_VER) {
|
||||||
LOG_WARN("NodeDatabase %d is old, discard", nodeDatabase.version);
|
LOG_WARN("NodeDatabase %d is old, discard", nodeDatabase.version);
|
||||||
installDefaultNodeDatabase();
|
installDefaultNodeDatabase();
|
||||||
|
} else if (nodeDatabase.version < DEVICESTATE_CUR_VER) {
|
||||||
|
if (migrateLegacyNodeDatabase())
|
||||||
|
saveNodeDatabaseToDisk();
|
||||||
|
else
|
||||||
|
installDefaultNodeDatabase();
|
||||||
} else {
|
} else {
|
||||||
meshNodes = &nodeDatabase.nodes;
|
meshNodes = &nodeDatabase.nodes;
|
||||||
numMeshNodes = nodeDatabase.nodes.size();
|
numMeshNodes = nodeDatabase.nodes.size();
|
||||||
|
// Hydrate the satellite maps; the on-disk vectors stay empty in steady
|
||||||
|
// state and are repopulated only at save time.
|
||||||
|
concurrency::LockGuard guard(&satelliteMutex);
|
||||||
|
#if !MESHTASTIC_EXCLUDE_POSITIONDB
|
||||||
|
nodePositions.clear();
|
||||||
|
nodePositions.reserve(nodeDatabase.positions.size());
|
||||||
|
for (const auto &entry : nodeDatabase.positions) {
|
||||||
|
if (entry.has_position)
|
||||||
|
nodePositions[entry.num] = entry.position;
|
||||||
|
}
|
||||||
|
nodeDatabase.positions.clear();
|
||||||
|
nodeDatabase.positions.shrink_to_fit();
|
||||||
|
#endif
|
||||||
|
#if !MESHTASTIC_EXCLUDE_TELEMETRYDB
|
||||||
|
nodeTelemetry.clear();
|
||||||
|
nodeTelemetry.reserve(nodeDatabase.telemetry.size());
|
||||||
|
for (const auto &entry : nodeDatabase.telemetry) {
|
||||||
|
if (entry.has_device_metrics)
|
||||||
|
nodeTelemetry[entry.num] = entry.device_metrics;
|
||||||
|
}
|
||||||
|
nodeDatabase.telemetry.clear();
|
||||||
|
nodeDatabase.telemetry.shrink_to_fit();
|
||||||
|
#endif
|
||||||
|
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTDB
|
||||||
|
nodeEnvironment.clear();
|
||||||
|
nodeEnvironment.reserve(nodeDatabase.environment.size());
|
||||||
|
for (const auto &entry : nodeDatabase.environment) {
|
||||||
|
if (entry.has_environment_metrics)
|
||||||
|
nodeEnvironment[entry.num] = entry.environment_metrics;
|
||||||
|
}
|
||||||
|
nodeDatabase.environment.clear();
|
||||||
|
nodeDatabase.environment.shrink_to_fit();
|
||||||
|
#endif
|
||||||
|
#if !MESHTASTIC_EXCLUDE_STATUSDB
|
||||||
|
nodeStatus.clear();
|
||||||
|
nodeStatus.reserve(nodeDatabase.status.size());
|
||||||
|
for (const auto &entry : nodeDatabase.status) {
|
||||||
|
if (entry.has_status)
|
||||||
|
nodeStatus[entry.num] = entry.status;
|
||||||
|
}
|
||||||
|
nodeDatabase.status.clear();
|
||||||
|
nodeDatabase.status.shrink_to_fit();
|
||||||
|
#endif
|
||||||
LOG_INFO("Loaded saved nodedatabase version %d, with nodes count: %d", nodeDatabase.version, nodeDatabase.nodes.size());
|
LOG_INFO("Loaded saved nodedatabase version %d, with nodes count: %d", nodeDatabase.version, nodeDatabase.nodes.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1272,16 +1593,16 @@ void NodeDB::loadFromDisk()
|
|||||||
|
|
||||||
// Attempt recovery of owner fields from our own NodeDB entry if available.
|
// Attempt recovery of owner fields from our own NodeDB entry if available.
|
||||||
meshtastic_NodeInfoLite *us = getMeshNode(getNodeNum());
|
meshtastic_NodeInfoLite *us = getMeshNode(getNodeNum());
|
||||||
if (us && us->has_user) {
|
if (nodeInfoLiteHasUser(us)) {
|
||||||
LOG_WARN("Restoring owner fields (long_name/short_name/is_licensed/is_unmessagable) from NodeDB for our node 0x%08x",
|
LOG_WARN("Restoring owner fields (long_name/short_name/is_licensed/is_unmessagable) from NodeDB for our node 0x%08x",
|
||||||
us->num);
|
us->num);
|
||||||
memcpy(owner.long_name, us->user.long_name, sizeof(owner.long_name));
|
memcpy(owner.long_name, us->long_name, sizeof(owner.long_name));
|
||||||
owner.long_name[sizeof(owner.long_name) - 1] = '\0';
|
owner.long_name[sizeof(owner.long_name) - 1] = '\0';
|
||||||
memcpy(owner.short_name, us->user.short_name, sizeof(owner.short_name));
|
memcpy(owner.short_name, us->short_name, sizeof(owner.short_name));
|
||||||
owner.short_name[sizeof(owner.short_name) - 1] = '\0';
|
owner.short_name[sizeof(owner.short_name) - 1] = '\0';
|
||||||
owner.is_licensed = us->user.is_licensed;
|
owner.is_licensed = nodeInfoLiteIsLicensed(us);
|
||||||
owner.has_is_unmessagable = us->user.has_is_unmessagable;
|
owner.has_is_unmessagable = nodeInfoLiteHasIsUnmessagable(us);
|
||||||
owner.is_unmessagable = us->user.is_unmessagable;
|
owner.is_unmessagable = nodeInfoLiteIsUnmessagable(us);
|
||||||
|
|
||||||
// Save the recovered owner to device state on disk
|
// Save the recovered owner to device state on disk
|
||||||
saveToDisk(SEGMENT_DEVICESTATE);
|
saveToDisk(SEGMENT_DEVICESTATE);
|
||||||
@@ -1528,9 +1849,75 @@ bool NodeDB::saveNodeDatabaseToDisk()
|
|||||||
FSCom.mkdir("/prefs");
|
FSCom.mkdir("/prefs");
|
||||||
spiLock->unlock();
|
spiLock->unlock();
|
||||||
#endif
|
#endif
|
||||||
|
// Project the maps into the on-disk vectors just before encoding; cleared
|
||||||
|
// again on the way out so we don't carry duplicate state.
|
||||||
|
concurrency::LockGuard guard(&satelliteMutex);
|
||||||
|
#if !MESHTASTIC_EXCLUDE_POSITIONDB
|
||||||
|
nodeDatabase.positions.clear();
|
||||||
|
nodeDatabase.positions.reserve(nodePositions.size());
|
||||||
|
for (const auto &kv : nodePositions) {
|
||||||
|
meshtastic_NodePositionEntry entry = meshtastic_NodePositionEntry_init_default;
|
||||||
|
entry.num = kv.first;
|
||||||
|
entry.has_position = true;
|
||||||
|
entry.position = kv.second;
|
||||||
|
nodeDatabase.positions.push_back(entry);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
nodeDatabase.positions.clear();
|
||||||
|
#endif
|
||||||
|
#if !MESHTASTIC_EXCLUDE_TELEMETRYDB
|
||||||
|
nodeDatabase.telemetry.clear();
|
||||||
|
nodeDatabase.telemetry.reserve(nodeTelemetry.size());
|
||||||
|
for (const auto &kv : nodeTelemetry) {
|
||||||
|
meshtastic_NodeTelemetryEntry entry = meshtastic_NodeTelemetryEntry_init_default;
|
||||||
|
entry.num = kv.first;
|
||||||
|
entry.has_device_metrics = true;
|
||||||
|
entry.device_metrics = kv.second;
|
||||||
|
nodeDatabase.telemetry.push_back(entry);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
nodeDatabase.telemetry.clear();
|
||||||
|
#endif
|
||||||
|
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTDB
|
||||||
|
nodeDatabase.environment.clear();
|
||||||
|
nodeDatabase.environment.reserve(nodeEnvironment.size());
|
||||||
|
for (const auto &kv : nodeEnvironment) {
|
||||||
|
meshtastic_NodeEnvironmentEntry entry = meshtastic_NodeEnvironmentEntry_init_default;
|
||||||
|
entry.num = kv.first;
|
||||||
|
entry.has_environment_metrics = true;
|
||||||
|
entry.environment_metrics = kv.second;
|
||||||
|
nodeDatabase.environment.push_back(entry);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
nodeDatabase.environment.clear();
|
||||||
|
#endif
|
||||||
|
#if !MESHTASTIC_EXCLUDE_STATUSDB
|
||||||
|
nodeDatabase.status.clear();
|
||||||
|
nodeDatabase.status.reserve(nodeStatus.size());
|
||||||
|
for (const auto &kv : nodeStatus) {
|
||||||
|
meshtastic_NodeStatusEntry entry = meshtastic_NodeStatusEntry_init_default;
|
||||||
|
entry.num = kv.first;
|
||||||
|
entry.has_status = true;
|
||||||
|
entry.status = kv.second;
|
||||||
|
nodeDatabase.status.push_back(entry);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
nodeDatabase.status.clear();
|
||||||
|
#endif
|
||||||
|
|
||||||
size_t nodeDatabaseSize;
|
size_t nodeDatabaseSize;
|
||||||
pb_get_encoded_size(&nodeDatabaseSize, meshtastic_NodeDatabase_fields, &nodeDatabase);
|
pb_get_encoded_size(&nodeDatabaseSize, meshtastic_NodeDatabase_fields, &nodeDatabase);
|
||||||
return saveProto(nodeDatabaseFileName, nodeDatabaseSize, &meshtastic_NodeDatabase_msg, &nodeDatabase, false);
|
bool ok = saveProto(nodeDatabaseFileName, nodeDatabaseSize, &meshtastic_NodeDatabase_msg, &nodeDatabase, false);
|
||||||
|
|
||||||
|
nodeDatabase.positions.clear();
|
||||||
|
nodeDatabase.positions.shrink_to_fit();
|
||||||
|
nodeDatabase.telemetry.clear();
|
||||||
|
nodeDatabase.telemetry.shrink_to_fit();
|
||||||
|
nodeDatabase.environment.clear();
|
||||||
|
nodeDatabase.environment.shrink_to_fit();
|
||||||
|
nodeDatabase.status.clear();
|
||||||
|
nodeDatabase.status.shrink_to_fit();
|
||||||
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NodeDB::saveToDiskNoRetry(int saveWhat)
|
bool NodeDB::saveToDiskNoRetry(int saveWhat)
|
||||||
@@ -1699,7 +2086,7 @@ size_t NodeDB::getNumOnlineMeshNodes(bool localOnly)
|
|||||||
|
|
||||||
// FIXME this implementation is kinda expensive
|
// FIXME this implementation is kinda expensive
|
||||||
for (int i = 0; i < numMeshNodes; i++) {
|
for (int i = 0; i < numMeshNodes; i++) {
|
||||||
if (localOnly && meshNodes->at(i).via_mqtt)
|
if (localOnly && nodeInfoLiteViaMqtt(&meshNodes->at(i)))
|
||||||
continue;
|
continue;
|
||||||
if (sinceLastSeen(&meshNodes->at(i)) < NUM_ONLINE_SECS)
|
if (sinceLastSeen(&meshNodes->at(i)) < NUM_ONLINE_SECS)
|
||||||
numseen++;
|
numseen++;
|
||||||
@@ -1736,62 +2123,94 @@ void NodeDB::updatePosition(uint32_t nodeId, const meshtastic_Position &p, RxSou
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if MESHTASTIC_EXCLUDE_POSITIONDB
|
||||||
|
// Build flag opted out: header still tracks last_heard via updateFrom; we
|
||||||
|
// simply don't cache the position payload anywhere on this device.
|
||||||
if (src == RX_SRC_LOCAL) {
|
if (src == RX_SRC_LOCAL) {
|
||||||
// Local packet, fully authoritative
|
LOG_INFO("updatePosition LOCAL (PositionDB excluded) time=%u lat=%d lon=%d alt=%d", p.time, p.latitude_i, p.longitude_i,
|
||||||
LOG_INFO("updatePosition LOCAL pos@%x time=%u lat=%d lon=%d alt=%d", p.timestamp, p.time, p.latitude_i, p.longitude_i,
|
|
||||||
p.altitude);
|
p.altitude);
|
||||||
|
|
||||||
setLocalPosition(p);
|
setLocalPosition(p);
|
||||||
info->position = TypeConversions::ConvertToPositionLite(p);
|
|
||||||
} else if ((p.time > 0) && !p.latitude_i && !p.longitude_i && !p.timestamp && !p.location_source) {
|
|
||||||
// FIXME SPECIAL TIME SETTING PACKET FROM EUD TO RADIO
|
|
||||||
// (stop-gap fix for issue #900)
|
|
||||||
LOG_DEBUG("updatePosition SPECIAL time setting time=%u", p.time);
|
|
||||||
info->position.time = p.time;
|
|
||||||
} else {
|
|
||||||
// Be careful to only update fields that have been set by the REMOTE sender
|
|
||||||
// A lot of position reports don't have time populated. In that case, be careful to not blow away the time we
|
|
||||||
// recorded based on the packet rxTime
|
|
||||||
//
|
|
||||||
// FIXME perhaps handle RX_SRC_USER separately?
|
|
||||||
LOG_INFO("updatePosition REMOTE node=0x%x time=%u lat=%d lon=%d", nodeId, p.time, p.latitude_i, p.longitude_i);
|
|
||||||
|
|
||||||
// First, back up fields that we want to protect from overwrite
|
|
||||||
uint32_t tmp_time = info->position.time;
|
|
||||||
|
|
||||||
// Next, update atomically
|
|
||||||
info->position = TypeConversions::ConvertToPositionLite(p);
|
|
||||||
|
|
||||||
// Last, restore any fields that may have been overwritten
|
|
||||||
if (!info->position.time)
|
|
||||||
info->position.time = tmp_time;
|
|
||||||
}
|
}
|
||||||
info->has_position = true;
|
(void)nodeId;
|
||||||
|
updateGUIforNode = info;
|
||||||
|
notifyObservers(true);
|
||||||
|
#else
|
||||||
|
{
|
||||||
|
concurrency::LockGuard guard(&satelliteMutex);
|
||||||
|
meshtastic_PositionLite &slot = nodePositions[nodeId]; // creates default-zero entry if missing
|
||||||
|
|
||||||
|
if (src == RX_SRC_LOCAL) {
|
||||||
|
// Local packet, fully authoritative
|
||||||
|
LOG_INFO("updatePosition LOCAL pos@%x time=%u lat=%d lon=%d alt=%d", p.timestamp, p.time, p.latitude_i, p.longitude_i,
|
||||||
|
p.altitude);
|
||||||
|
|
||||||
|
setLocalPosition(p);
|
||||||
|
slot = TypeConversions::ConvertToPositionLite(p);
|
||||||
|
} else if ((p.time > 0) && !p.latitude_i && !p.longitude_i && !p.timestamp && !p.location_source) {
|
||||||
|
// FIXME SPECIAL TIME SETTING PACKET FROM EUD TO RADIO
|
||||||
|
// (stop-gap fix for issue #900)
|
||||||
|
LOG_DEBUG("updatePosition SPECIAL time setting time=%u", p.time);
|
||||||
|
slot.time = p.time;
|
||||||
|
} else {
|
||||||
|
// Be careful to only update fields that have been set by the REMOTE sender
|
||||||
|
// A lot of position reports don't have time populated. In that case, be careful to not blow away the time we
|
||||||
|
// recorded based on the packet rxTime
|
||||||
|
//
|
||||||
|
// FIXME perhaps handle RX_SRC_USER separately?
|
||||||
|
LOG_INFO("updatePosition REMOTE node=0x%x time=%u lat=%d lon=%d", nodeId, p.time, p.latitude_i, p.longitude_i);
|
||||||
|
|
||||||
|
// First, back up fields that we want to protect from overwrite
|
||||||
|
uint32_t tmp_time = slot.time;
|
||||||
|
|
||||||
|
// Next, update atomically
|
||||||
|
slot = TypeConversions::ConvertToPositionLite(p);
|
||||||
|
|
||||||
|
// Last, restore any fields that may have been overwritten
|
||||||
|
if (!slot.time)
|
||||||
|
slot.time = tmp_time;
|
||||||
|
}
|
||||||
|
}
|
||||||
updateGUIforNode = info;
|
updateGUIforNode = info;
|
||||||
notifyObservers(true); // Force an update whether or not our node counts have changed
|
notifyObservers(true); // Force an update whether or not our node counts have changed
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Update telemetry info for this node based on received metrics
|
/** Update telemetry info for this node based on received metrics. Stores
|
||||||
* We only care about device telemetry here
|
* device_metrics and environment_metrics into their respective satellite
|
||||||
|
* maps; other variants (air_quality, power, local_stats, health) are
|
||||||
|
* intentionally not retained per-node.
|
||||||
*/
|
*/
|
||||||
void NodeDB::updateTelemetry(uint32_t nodeId, const meshtastic_Telemetry &t, RxSource src)
|
void NodeDB::updateTelemetry(uint32_t nodeId, const meshtastic_Telemetry &t, RxSource src)
|
||||||
{
|
{
|
||||||
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(nodeId);
|
meshtastic_NodeInfoLite *info = getOrCreateMeshNode(nodeId);
|
||||||
// Environment metrics should never go to NodeDb but we'll safegaurd anyway
|
if (!info)
|
||||||
if (!info || t.which_variant != meshtastic_Telemetry_device_metrics_tag) {
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if (src == RX_SRC_LOCAL) {
|
if (t.which_variant == meshtastic_Telemetry_device_metrics_tag) {
|
||||||
// Local packet, fully authoritative
|
if (src == RX_SRC_LOCAL) {
|
||||||
LOG_DEBUG("updateTelemetry LOCAL");
|
LOG_DEBUG("updateTelemetry LOCAL device");
|
||||||
|
} else {
|
||||||
|
LOG_DEBUG("updateTelemetry REMOTE device node=0x%x", nodeId);
|
||||||
|
}
|
||||||
|
#if !MESHTASTIC_EXCLUDE_TELEMETRYDB
|
||||||
|
concurrency::LockGuard guard(&satelliteMutex);
|
||||||
|
nodeTelemetry[nodeId] = t.variant.device_metrics;
|
||||||
|
#endif
|
||||||
|
} else if (t.which_variant == meshtastic_Telemetry_environment_metrics_tag) {
|
||||||
|
if (src == RX_SRC_LOCAL) {
|
||||||
|
LOG_DEBUG("updateTelemetry LOCAL env");
|
||||||
|
} else {
|
||||||
|
LOG_DEBUG("updateTelemetry REMOTE env node=0x%x", nodeId);
|
||||||
|
}
|
||||||
|
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTDB
|
||||||
|
concurrency::LockGuard guard(&satelliteMutex);
|
||||||
|
nodeEnvironment[nodeId] = t.variant.environment_metrics;
|
||||||
|
#endif
|
||||||
} else {
|
} else {
|
||||||
LOG_DEBUG("updateTelemetry REMOTE node=0x%x ", nodeId);
|
return; // air_quality / power / local_stats / health: not stored per-node
|
||||||
}
|
}
|
||||||
info->device_metrics = t.variant.device_metrics;
|
|
||||||
info->has_device_metrics = true;
|
|
||||||
updateGUIforNode = info;
|
updateGUIforNode = info;
|
||||||
notifyObservers(true); // Force an update whether or not our node counts have changed
|
notifyObservers(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1806,24 +2225,22 @@ void NodeDB::addFromContact(meshtastic_SharedContact contact)
|
|||||||
// If the local node has this node marked as manually verified
|
// If the local node has this node marked as manually verified
|
||||||
// and the client does not, do not allow the client to update the
|
// and the client does not, do not allow the client to update the
|
||||||
// saved public key.
|
// saved public key.
|
||||||
if ((info->bitfield & NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK) && !contact.manually_verified) {
|
if (nodeInfoLiteIsKeyManuallyVerified(info) && !contact.manually_verified) {
|
||||||
if (contact.user.public_key.size != info->user.public_key.size ||
|
if (contact.user.public_key.size != info->public_key.size ||
|
||||||
memcmp(contact.user.public_key.bytes, info->user.public_key.bytes, info->user.public_key.size) != 0) {
|
memcmp(contact.user.public_key.bytes, info->public_key.bytes, info->public_key.size) != 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
info->num = contact.node_num;
|
info->num = contact.node_num;
|
||||||
info->has_user = true;
|
TypeConversions::CopyUserToNodeInfoLite(info, contact.user);
|
||||||
info->user = TypeConversions::ConvertToUserLite(contact.user);
|
|
||||||
if (contact.should_ignore) {
|
if (contact.should_ignore) {
|
||||||
// If should_ignore is set,
|
// If should_ignore is set,
|
||||||
// we need to clear the public key and other cruft, in addition to setting the node as ignored
|
// we need to clear the public key and other cruft, in addition to setting the node as ignored
|
||||||
info->is_ignored = true;
|
nodeInfoLiteSetBit(info, NODEINFO_BITFIELD_IS_IGNORED_MASK, true);
|
||||||
info->is_favorite = false;
|
nodeInfoLiteSetBit(info, NODEINFO_BITFIELD_IS_FAVORITE_MASK, false);
|
||||||
info->has_device_metrics = false;
|
eraseNodeSatellites(contact.node_num);
|
||||||
info->has_position = false;
|
info->public_key.size = 0;
|
||||||
info->user.public_key.size = 0;
|
memset(info->public_key.bytes, 0, sizeof(info->public_key.bytes));
|
||||||
memset(info->user.public_key.bytes, 0, sizeof(info->user.public_key.bytes));
|
|
||||||
} else {
|
} else {
|
||||||
/* Clients are sending add_contact before every text message DM (because clients may hold a larger node database with
|
/* Clients are sending add_contact before every text message DM (because clients may hold a larger node database with
|
||||||
* public keys than the radio holds). However, we don't want to update last_heard just because we sent someone a DM!
|
* public keys than the radio holds). However, we don't want to update last_heard just because we sent someone a DM!
|
||||||
@@ -1842,12 +2259,12 @@ void NodeDB::addFromContact(meshtastic_SharedContact contact)
|
|||||||
} else {
|
} else {
|
||||||
// Normal case: set is_favorite to prevent expiration.
|
// Normal case: set is_favorite to prevent expiration.
|
||||||
// last_heard will remain as-is (or remain 0 if this entry wasn't in the nodeDB).
|
// last_heard will remain as-is (or remain 0 if this entry wasn't in the nodeDB).
|
||||||
info->is_favorite = true;
|
nodeInfoLiteSetBit(info, NODEINFO_BITFIELD_IS_FAVORITE_MASK, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// As the clients will begin sending the contact with DMs, we want to strictly check if the node is manually verified
|
// As the clients will begin sending the contact with DMs, we want to strictly check if the node is manually verified
|
||||||
if (contact.manually_verified) {
|
if (contact.manually_verified) {
|
||||||
info->bitfield |= NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK;
|
nodeInfoLiteSetBit(info, NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK, true);
|
||||||
}
|
}
|
||||||
// Mark the node's key as manually verified to indicate trustworthiness.
|
// Mark the node's key as manually verified to indicate trustworthiness.
|
||||||
updateGUIforNode = info;
|
updateGUIforNode = info;
|
||||||
@@ -1887,9 +2304,9 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (info->user.public_key.size == 32) { // if we have a key for this user already, don't overwrite with a new one
|
if (info->public_key.size == 32) { // if we have a key for this user already, don't overwrite with a new one
|
||||||
// if the key doesn't match, don't update nodeDB at all.
|
// if the key doesn't match, don't update nodeDB at all.
|
||||||
if (p.public_key.size != 32 || (memcmp(p.public_key.bytes, info->user.public_key.bytes, 32) != 0)) {
|
if (p.public_key.size != 32 || (memcmp(p.public_key.bytes, info->public_key.bytes, 32) != 0)) {
|
||||||
LOG_WARN("Public Key mismatch, dropping NodeInfo");
|
LOG_WARN("Public Key mismatch, dropping NodeInfo");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1902,19 +2319,22 @@ bool NodeDB::updateUser(uint32_t nodeId, meshtastic_User &p, uint8_t channelInde
|
|||||||
// Always ensure user.id is derived from nodeId, regardless of what was received
|
// Always ensure user.id is derived from nodeId, regardless of what was received
|
||||||
snprintf(p.id, sizeof(p.id), "!%08x", nodeId);
|
snprintf(p.id, sizeof(p.id), "!%08x", nodeId);
|
||||||
|
|
||||||
// Both of info->user and p start as filled with zero so I think this is okay
|
meshtastic_NodeInfoLite before = *info;
|
||||||
auto lite = TypeConversions::ConvertToUserLite(p);
|
TypeConversions::CopyUserToNodeInfoLite(info, p);
|
||||||
bool changed = memcmp(&info->user, &lite, sizeof(info->user)) || (info->channel != channelIndex);
|
bool changed =
|
||||||
|
(memcmp(before.long_name, info->long_name, sizeof(info->long_name)) != 0) ||
|
||||||
|
(memcmp(before.short_name, info->short_name, sizeof(info->short_name)) != 0) || (before.hw_model != info->hw_model) ||
|
||||||
|
(before.role != info->role) || (before.public_key.size != info->public_key.size) ||
|
||||||
|
(info->public_key.size > 0 && memcmp(before.public_key.bytes, info->public_key.bytes, info->public_key.size) != 0) ||
|
||||||
|
(before.bitfield != info->bitfield) || (info->channel != channelIndex);
|
||||||
|
|
||||||
info->user = lite;
|
if (info->public_key.size == 32) {
|
||||||
if (info->user.public_key.size == 32) {
|
printBytes("Saved Pubkey: ", info->public_key.bytes, 32);
|
||||||
printBytes("Saved Pubkey: ", info->user.public_key.bytes, 32);
|
|
||||||
}
|
}
|
||||||
if (nodeId != getNodeNum())
|
if (nodeId != getNodeNum())
|
||||||
info->channel = channelIndex; // Set channel we need to use to reach this node (but don't set our own channel)
|
info->channel = channelIndex; // Set channel we need to use to reach this node (but don't set our own channel)
|
||||||
LOG_DEBUG("Update changed=%d user %s/%s, id=0x%08x, channel=%d", changed, info->user.long_name, info->user.short_name, nodeId,
|
LOG_DEBUG("Update changed=%d user %s/%s, id=0x%08x, channel=%d", changed, info->long_name, info->short_name, nodeId,
|
||||||
info->channel);
|
info->channel);
|
||||||
info->has_user = true;
|
|
||||||
|
|
||||||
if (changed) {
|
if (changed) {
|
||||||
updateGUIforNode = info;
|
updateGUIforNode = info;
|
||||||
@@ -1956,7 +2376,8 @@ void NodeDB::updateFrom(const meshtastic_MeshPacket &mp)
|
|||||||
if (mp.rx_snr)
|
if (mp.rx_snr)
|
||||||
info->snr = mp.rx_snr; // keep the most recent SNR we received for this node.
|
info->snr = mp.rx_snr; // keep the most recent SNR we received for this node.
|
||||||
|
|
||||||
info->via_mqtt = mp.via_mqtt; // Store if we received this packet via MQTT
|
nodeInfoLiteSetBit(info, NODEINFO_BITFIELD_VIA_MQTT_MASK,
|
||||||
|
mp.via_mqtt); // Store if we received this packet via MQTT
|
||||||
|
|
||||||
// If hopStart was set and there wasn't someone messing with the limit in the middle, add hopsAway
|
// If hopStart was set and there wasn't someone messing with the limit in the middle, add hopsAway
|
||||||
const int8_t hopsAway = getHopsAway(mp);
|
const int8_t hopsAway = getHopsAway(mp);
|
||||||
@@ -1971,8 +2392,8 @@ void NodeDB::updateFrom(const meshtastic_MeshPacket &mp)
|
|||||||
void NodeDB::set_favorite(bool is_favorite, uint32_t nodeId)
|
void NodeDB::set_favorite(bool is_favorite, uint32_t nodeId)
|
||||||
{
|
{
|
||||||
meshtastic_NodeInfoLite *lite = getMeshNode(nodeId);
|
meshtastic_NodeInfoLite *lite = getMeshNode(nodeId);
|
||||||
if (lite && lite->is_favorite != is_favorite) {
|
if (lite && nodeInfoLiteIsFavorite(lite) != is_favorite) {
|
||||||
lite->is_favorite = is_favorite;
|
nodeInfoLiteSetBit(lite, NODEINFO_BITFIELD_IS_FAVORITE_MASK, is_favorite);
|
||||||
sortMeshDB();
|
sortMeshDB();
|
||||||
saveNodeDatabaseToDisk();
|
saveNodeDatabaseToDisk();
|
||||||
}
|
}
|
||||||
@@ -1986,10 +2407,10 @@ bool NodeDB::isFavorite(uint32_t nodeId)
|
|||||||
if (nodeId == NODENUM_BROADCAST)
|
if (nodeId == NODENUM_BROADCAST)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
meshtastic_NodeInfoLite *lite = getMeshNode(nodeId);
|
const meshtastic_NodeInfoLite *lite = getMeshNode(nodeId);
|
||||||
|
|
||||||
if (lite) {
|
if (lite) {
|
||||||
return lite->is_favorite;
|
return nodeInfoLiteIsFavorite(lite);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -2014,14 +2435,14 @@ bool NodeDB::isFromOrToFavoritedNode(const meshtastic_MeshPacket &p)
|
|||||||
lite = &meshNodes->at(i);
|
lite = &meshNodes->at(i);
|
||||||
|
|
||||||
if (lite->num == p.from) {
|
if (lite->num == p.from) {
|
||||||
if (lite->is_favorite)
|
if (nodeInfoLiteIsFavorite(lite))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
seenFrom = true;
|
seenFrom = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lite->num == p.to) {
|
if (lite->num == p.to) {
|
||||||
if (lite->is_favorite)
|
if (nodeInfoLiteIsFavorite(lite))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
seenTo = true;
|
seenTo = true;
|
||||||
@@ -2057,10 +2478,10 @@ void NodeDB::sortMeshDB()
|
|||||||
// TODO: Look for at(i-1) also matching own node num, and throw the DB in the trash
|
// TODO: Look for at(i-1) also matching own node num, and throw the DB in the trash
|
||||||
std::swap(meshNodes->at(i), meshNodes->at(i - 1));
|
std::swap(meshNodes->at(i), meshNodes->at(i - 1));
|
||||||
changed = true;
|
changed = true;
|
||||||
} else if (meshNodes->at(i).is_favorite && !meshNodes->at(i - 1).is_favorite) {
|
} else if (nodeInfoLiteIsFavorite(&meshNodes->at(i)) && !nodeInfoLiteIsFavorite(&meshNodes->at(i - 1))) {
|
||||||
std::swap(meshNodes->at(i), meshNodes->at(i - 1));
|
std::swap(meshNodes->at(i), meshNodes->at(i - 1));
|
||||||
changed = true;
|
changed = true;
|
||||||
} else if (!meshNodes->at(i).is_favorite && meshNodes->at(i - 1).is_favorite) {
|
} else if (!nodeInfoLiteIsFavorite(&meshNodes->at(i)) && nodeInfoLiteIsFavorite(&meshNodes->at(i - 1))) {
|
||||||
// noop
|
// noop
|
||||||
} else if (meshNodes->at(i).last_heard > meshNodes->at(i - 1).last_heard) {
|
} else if (meshNodes->at(i).last_heard > meshNodes->at(i - 1).last_heard) {
|
||||||
std::swap(meshNodes->at(i), meshNodes->at(i - 1));
|
std::swap(meshNodes->at(i), meshNodes->at(i - 1));
|
||||||
@@ -2120,17 +2541,18 @@ meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n)
|
|||||||
int oldestIndex = -1;
|
int oldestIndex = -1;
|
||||||
int oldestBoringIndex = -1;
|
int oldestBoringIndex = -1;
|
||||||
for (int i = 1; i < numMeshNodes; i++) {
|
for (int i = 1; i < numMeshNodes; i++) {
|
||||||
|
const meshtastic_NodeInfoLite *cand = &meshNodes->at(i);
|
||||||
|
const bool isFavoriteNode = nodeInfoLiteIsFavorite(cand);
|
||||||
|
const bool isIgnored = nodeInfoLiteIsIgnored(cand);
|
||||||
|
const bool isVerified = nodeInfoLiteIsKeyManuallyVerified(cand);
|
||||||
// Simply the oldest non-favorite, non-ignored, non-verified node
|
// Simply the oldest non-favorite, non-ignored, non-verified node
|
||||||
if (!meshNodes->at(i).is_favorite && !meshNodes->at(i).is_ignored &&
|
if (!isFavoriteNode && !isIgnored && !isVerified && cand->last_heard < oldest) {
|
||||||
!(meshNodes->at(i).bitfield & NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK) &&
|
oldest = cand->last_heard;
|
||||||
meshNodes->at(i).last_heard < oldest) {
|
|
||||||
oldest = meshNodes->at(i).last_heard;
|
|
||||||
oldestIndex = i;
|
oldestIndex = i;
|
||||||
}
|
}
|
||||||
// The oldest "boring" node
|
// The oldest "boring" node
|
||||||
if (!meshNodes->at(i).is_favorite && !meshNodes->at(i).is_ignored && meshNodes->at(i).user.public_key.size == 0 &&
|
if (!isFavoriteNode && !isIgnored && cand->public_key.size == 0 && cand->last_heard < oldestBoring) {
|
||||||
meshNodes->at(i).last_heard < oldestBoring) {
|
oldestBoring = cand->last_heard;
|
||||||
oldestBoring = meshNodes->at(i).last_heard;
|
|
||||||
oldestBoringIndex = i;
|
oldestBoringIndex = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2140,6 +2562,7 @@ meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (oldestIndex != -1) {
|
if (oldestIndex != -1) {
|
||||||
|
eraseNodeSatellites(meshNodes->at(oldestIndex).num);
|
||||||
// Shove the remaining nodes down the chain
|
// Shove the remaining nodes down the chain
|
||||||
for (int i = oldestIndex; i < numMeshNodes - 1; i++) {
|
for (int i = oldestIndex; i < numMeshNodes - 1; i++) {
|
||||||
meshNodes->at(i) = meshNodes->at(i + 1);
|
meshNodes->at(i) = meshNodes->at(i + 1);
|
||||||
@@ -2163,18 +2586,23 @@ meshtastic_NodeInfoLite *NodeDB::getOrCreateMeshNode(NodeNum n)
|
|||||||
/// valid lat/lon
|
/// valid lat/lon
|
||||||
bool NodeDB::hasValidPosition(const meshtastic_NodeInfoLite *n)
|
bool NodeDB::hasValidPosition(const meshtastic_NodeInfoLite *n)
|
||||||
{
|
{
|
||||||
return n->has_position && (n->position.latitude_i != 0 || n->position.longitude_i != 0);
|
if (!n)
|
||||||
|
return false;
|
||||||
|
if (n->num == getNodeNum()) {
|
||||||
|
return localPosition.latitude_i != 0 || localPosition.longitude_i != 0;
|
||||||
|
}
|
||||||
|
meshtastic_PositionLite pos;
|
||||||
|
return copyNodePosition(n->num, pos) && (pos.latitude_i != 0 || pos.longitude_i != 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If we have a node / user and they report is_licensed = true
|
/// If we have a node / user and they report is_licensed = true
|
||||||
/// we consider them licensed
|
/// we consider them licensed
|
||||||
UserLicenseStatus NodeDB::getLicenseStatus(uint32_t nodeNum)
|
UserLicenseStatus NodeDB::getLicenseStatus(uint32_t nodeNum)
|
||||||
{
|
{
|
||||||
meshtastic_NodeInfoLite *info = getMeshNode(nodeNum);
|
const meshtastic_NodeInfoLite *info = getMeshNode(nodeNum);
|
||||||
if (!info || !info->has_user) {
|
if (!nodeInfoLiteHasUser(info))
|
||||||
return UserLicenseStatus::NotKnown;
|
return UserLicenseStatus::NotKnown;
|
||||||
}
|
return nodeInfoLiteIsLicensed(info) ? UserLicenseStatus::Licensed : UserLicenseStatus::NotLicensed;
|
||||||
return info->user.is_licensed ? UserLicenseStatus::Licensed : UserLicenseStatus::NotLicensed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !defined(MESHTASTIC_EXCLUDE_PKI)
|
#if !defined(MESHTASTIC_EXCLUDE_PKI)
|
||||||
|
|||||||
+134
-4
@@ -6,10 +6,12 @@
|
|||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <pb_encode.h>
|
#include <pb_encode.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "MeshTypes.h"
|
#include "MeshTypes.h"
|
||||||
#include "NodeStatus.h"
|
#include "NodeStatus.h"
|
||||||
|
#include "concurrency/Lock.h"
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include "mesh-pb-constants.h"
|
#include "mesh-pb-constants.h"
|
||||||
#include "mesh/generated/meshtastic/mesh.pb.h" // For CriticalErrorCode
|
#include "mesh/generated/meshtastic/mesh.pb.h" // For CriticalErrorCode
|
||||||
@@ -82,7 +84,9 @@ DeviceState versions used to be defined in the .proto file but really only this
|
|||||||
#define SEGMENT_CHANNELS 8
|
#define SEGMENT_CHANNELS 8
|
||||||
#define SEGMENT_NODEDATABASE 16
|
#define SEGMENT_NODEDATABASE 16
|
||||||
|
|
||||||
#define DEVICESTATE_CUR_VER 24
|
#define DEVICESTATE_CUR_VER 25
|
||||||
|
// Lowest on-disk version we still know how to load. v24 saves are migrated
|
||||||
|
// at boot via the parallel deviceonly_legacy descriptor and re-saved as v25.
|
||||||
#define DEVICESTATE_MIN_VER 24
|
#define DEVICESTATE_MIN_VER 24
|
||||||
|
|
||||||
extern meshtastic_DeviceState devicestate;
|
extern meshtastic_DeviceState devicestate;
|
||||||
@@ -166,6 +170,21 @@ class NodeDB
|
|||||||
Observable<const meshtastic::NodeStatus *> newStatus;
|
Observable<const meshtastic::NodeStatus *> newStatus;
|
||||||
pb_size_t numMeshNodes;
|
pb_size_t numMeshNodes;
|
||||||
|
|
||||||
|
// Satellite per-NodeNum maps for data we used to inline into NodeInfoLite,
|
||||||
|
// gated by MESHTASTIC_EXCLUDE_*DB so STM32WL can omit them.
|
||||||
|
#if !MESHTASTIC_EXCLUDE_POSITIONDB
|
||||||
|
std::unordered_map<NodeNum, meshtastic_PositionLite> nodePositions;
|
||||||
|
#endif
|
||||||
|
#if !MESHTASTIC_EXCLUDE_TELEMETRYDB
|
||||||
|
std::unordered_map<NodeNum, meshtastic_DeviceMetrics> nodeTelemetry;
|
||||||
|
#endif
|
||||||
|
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTDB
|
||||||
|
std::unordered_map<NodeNum, meshtastic_EnvironmentMetrics> nodeEnvironment;
|
||||||
|
#endif
|
||||||
|
#if !MESHTASTIC_EXCLUDE_STATUSDB
|
||||||
|
std::unordered_map<NodeNum, meshtastic_StatusMessage> nodeStatus;
|
||||||
|
#endif
|
||||||
|
|
||||||
bool keyIsLowEntropy = false;
|
bool keyIsLowEntropy = false;
|
||||||
bool hasWarned = false;
|
bool hasWarned = false;
|
||||||
|
|
||||||
@@ -277,6 +296,43 @@ class NodeDB
|
|||||||
virtual meshtastic_NodeInfoLite *getMeshNode(NodeNum n);
|
virtual meshtastic_NodeInfoLite *getMeshNode(NodeNum n);
|
||||||
size_t getNumMeshNodes() { return numMeshNodes; }
|
size_t getNumMeshNodes() { return numMeshNodes; }
|
||||||
|
|
||||||
|
// Thread-safe satellite-map accessors. Return false if absent or the
|
||||||
|
// corresponding DB is compiled out.
|
||||||
|
bool copyNodePosition(NodeNum n, meshtastic_PositionLite &out) const;
|
||||||
|
bool copyNodeTelemetry(NodeNum n, meshtastic_DeviceMetrics &out) const;
|
||||||
|
bool copyNodeEnvironment(NodeNum n, meshtastic_EnvironmentMetrics &out) const;
|
||||||
|
bool copyNodeStatus(NodeNum n, meshtastic_StatusMessage &out) const;
|
||||||
|
std::vector<NodeNum> snapshotPositionNodeNums(NodeNum exclude) const;
|
||||||
|
std::vector<NodeNum> snapshotTelemetryNodeNums(NodeNum exclude) const;
|
||||||
|
std::vector<NodeNum> snapshotEnvironmentNodeNums(NodeNum exclude) const;
|
||||||
|
std::vector<NodeNum> snapshotStatusNodeNums(NodeNum exclude) const;
|
||||||
|
|
||||||
|
void setNodeStatus(NodeNum n, const meshtastic_StatusMessage &status);
|
||||||
|
void touchNodePositionTime(NodeNum n, uint32_t time);
|
||||||
|
|
||||||
|
bool hasNodePosition(NodeNum n) const
|
||||||
|
{
|
||||||
|
meshtastic_PositionLite scratch;
|
||||||
|
return copyNodePosition(n, scratch);
|
||||||
|
}
|
||||||
|
bool hasNodeTelemetry(NodeNum n) const
|
||||||
|
{
|
||||||
|
meshtastic_DeviceMetrics scratch;
|
||||||
|
return copyNodeTelemetry(n, scratch);
|
||||||
|
}
|
||||||
|
bool hasNodeEnvironment(NodeNum n) const
|
||||||
|
{
|
||||||
|
meshtastic_EnvironmentMetrics scratch;
|
||||||
|
return copyNodeEnvironment(n, scratch);
|
||||||
|
}
|
||||||
|
bool hasNodeStatus(NodeNum n) const
|
||||||
|
{
|
||||||
|
meshtastic_StatusMessage scratch;
|
||||||
|
return copyNodeStatus(n, scratch);
|
||||||
|
}
|
||||||
|
|
||||||
|
void eraseNodeSatellites(NodeNum n);
|
||||||
|
|
||||||
UserLicenseStatus getLicenseStatus(uint32_t nodeNum);
|
UserLicenseStatus getLicenseStatus(uint32_t nodeNum);
|
||||||
|
|
||||||
size_t getMaxNodesAllocatedSize()
|
size_t getMaxNodesAllocatedSize()
|
||||||
@@ -285,7 +341,11 @@ class NodeDB
|
|||||||
emptyNodeDatabase.version = DEVICESTATE_CUR_VER;
|
emptyNodeDatabase.version = DEVICESTATE_CUR_VER;
|
||||||
size_t nodeDatabaseSize;
|
size_t nodeDatabaseSize;
|
||||||
pb_get_encoded_size(&nodeDatabaseSize, meshtastic_NodeDatabase_fields, &emptyNodeDatabase);
|
pb_get_encoded_size(&nodeDatabaseSize, meshtastic_NodeDatabase_fields, &emptyNodeDatabase);
|
||||||
return nodeDatabaseSize + (MAX_NUM_NODES * meshtastic_NodeInfoLite_size);
|
// Always include satellite slots so backups from higher-cap peers
|
||||||
|
// decode without truncation, even when our build excludes the DBs.
|
||||||
|
return nodeDatabaseSize + (MAX_NUM_NODES * meshtastic_NodeInfoLite_size) +
|
||||||
|
(MAX_NUM_NODES * meshtastic_NodePositionEntry_size) + (MAX_NUM_NODES * meshtastic_NodeTelemetryEntry_size) +
|
||||||
|
(MAX_NUM_NODES * meshtastic_NodeEnvironmentEntry_size) + (MAX_NUM_NODES * meshtastic_NodeStatusEntry_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns true if the maximum number of nodes is reached or we are running low on memory
|
// returns true if the maximum number of nodes is reached or we are running low on memory
|
||||||
@@ -329,6 +389,7 @@ class NodeDB
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
mutable concurrency::Lock satelliteMutex;
|
||||||
bool duplicateWarned = false;
|
bool duplicateWarned = false;
|
||||||
bool localPositionUpdatedSinceBoot = false;
|
bool localPositionUpdatedSinceBoot = false;
|
||||||
uint32_t lastNodeDbSave = 0; // when we last saved our db to flash
|
uint32_t lastNodeDbSave = 0; // when we last saved our db to flash
|
||||||
@@ -363,6 +424,11 @@ class NodeDB
|
|||||||
bool saveDeviceStateToDisk();
|
bool saveDeviceStateToDisk();
|
||||||
bool saveNodeDatabaseToDisk();
|
bool saveNodeDatabaseToDisk();
|
||||||
void sortMeshDB();
|
void sortMeshDB();
|
||||||
|
|
||||||
|
// Defined in NodeDBLegacyMigration.cpp. Decodes /prefs/nodes.proto via
|
||||||
|
// the legacy descriptor and copies entries into the v25 layout. Caller
|
||||||
|
// is responsible for save / install-default on the result.
|
||||||
|
bool migrateLegacyNodeDatabase();
|
||||||
};
|
};
|
||||||
|
|
||||||
extern NodeDB *nodeDB;
|
extern NodeDB *nodeDB;
|
||||||
@@ -397,10 +463,74 @@ extern meshtastic_CriticalErrorCode error_code;
|
|||||||
* A numeric error address (nonzero if available)
|
* A numeric error address (nonzero if available)
|
||||||
*/
|
*/
|
||||||
extern uint32_t error_address;
|
extern uint32_t error_address;
|
||||||
|
// Bit assignments for meshtastic_NodeInfoLite.bitfield.
|
||||||
#define NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_SHIFT 0
|
#define NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_SHIFT 0
|
||||||
#define NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK (1 << NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_SHIFT)
|
#define NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK (1u << NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_SHIFT)
|
||||||
#define NODEINFO_BITFIELD_IS_MUTED_SHIFT 1
|
#define NODEINFO_BITFIELD_IS_MUTED_SHIFT 1
|
||||||
#define NODEINFO_BITFIELD_IS_MUTED_MASK (1 << NODEINFO_BITFIELD_IS_MUTED_SHIFT)
|
#define NODEINFO_BITFIELD_IS_MUTED_MASK (1u << NODEINFO_BITFIELD_IS_MUTED_SHIFT)
|
||||||
|
#define NODEINFO_BITFIELD_VIA_MQTT_SHIFT 2
|
||||||
|
#define NODEINFO_BITFIELD_VIA_MQTT_MASK (1u << NODEINFO_BITFIELD_VIA_MQTT_SHIFT)
|
||||||
|
#define NODEINFO_BITFIELD_IS_FAVORITE_SHIFT 3
|
||||||
|
#define NODEINFO_BITFIELD_IS_FAVORITE_MASK (1u << NODEINFO_BITFIELD_IS_FAVORITE_SHIFT)
|
||||||
|
#define NODEINFO_BITFIELD_IS_IGNORED_SHIFT 4
|
||||||
|
#define NODEINFO_BITFIELD_IS_IGNORED_MASK (1u << NODEINFO_BITFIELD_IS_IGNORED_SHIFT)
|
||||||
|
#define NODEINFO_BITFIELD_HAS_USER_SHIFT 5
|
||||||
|
#define NODEINFO_BITFIELD_HAS_USER_MASK (1u << NODEINFO_BITFIELD_HAS_USER_SHIFT)
|
||||||
|
#define NODEINFO_BITFIELD_IS_LICENSED_SHIFT 6
|
||||||
|
#define NODEINFO_BITFIELD_IS_LICENSED_MASK (1u << NODEINFO_BITFIELD_IS_LICENSED_SHIFT)
|
||||||
|
#define NODEINFO_BITFIELD_IS_UNMESSAGABLE_SHIFT 7
|
||||||
|
#define NODEINFO_BITFIELD_IS_UNMESSAGABLE_MASK (1u << NODEINFO_BITFIELD_IS_UNMESSAGABLE_SHIFT)
|
||||||
|
#define NODEINFO_BITFIELD_HAS_IS_UNMESSAGABLE_SHIFT 8
|
||||||
|
#define NODEINFO_BITFIELD_HAS_IS_UNMESSAGABLE_MASK (1u << NODEINFO_BITFIELD_HAS_IS_UNMESSAGABLE_SHIFT)
|
||||||
|
// Bits 9..31 reserved for future single-bit flags.
|
||||||
|
|
||||||
|
// Convenience accessors so call sites read like the old struct fields.
|
||||||
|
inline bool nodeInfoLiteHasUser(const meshtastic_NodeInfoLite *n)
|
||||||
|
{
|
||||||
|
return n && (n->bitfield & NODEINFO_BITFIELD_HAS_USER_MASK);
|
||||||
|
}
|
||||||
|
inline bool nodeInfoLiteViaMqtt(const meshtastic_NodeInfoLite *n)
|
||||||
|
{
|
||||||
|
return n && (n->bitfield & NODEINFO_BITFIELD_VIA_MQTT_MASK);
|
||||||
|
}
|
||||||
|
inline bool nodeInfoLiteIsFavorite(const meshtastic_NodeInfoLite *n)
|
||||||
|
{
|
||||||
|
return n && (n->bitfield & NODEINFO_BITFIELD_IS_FAVORITE_MASK);
|
||||||
|
}
|
||||||
|
inline bool nodeInfoLiteIsIgnored(const meshtastic_NodeInfoLite *n)
|
||||||
|
{
|
||||||
|
return n && (n->bitfield & NODEINFO_BITFIELD_IS_IGNORED_MASK);
|
||||||
|
}
|
||||||
|
inline bool nodeInfoLiteIsLicensed(const meshtastic_NodeInfoLite *n)
|
||||||
|
{
|
||||||
|
return n && (n->bitfield & NODEINFO_BITFIELD_IS_LICENSED_MASK);
|
||||||
|
}
|
||||||
|
inline bool nodeInfoLiteHasIsUnmessagable(const meshtastic_NodeInfoLite *n)
|
||||||
|
{
|
||||||
|
return n && (n->bitfield & NODEINFO_BITFIELD_HAS_IS_UNMESSAGABLE_MASK);
|
||||||
|
}
|
||||||
|
inline bool nodeInfoLiteIsUnmessagable(const meshtastic_NodeInfoLite *n)
|
||||||
|
{
|
||||||
|
return n && (n->bitfield & NODEINFO_BITFIELD_IS_UNMESSAGABLE_MASK);
|
||||||
|
}
|
||||||
|
inline bool nodeInfoLiteIsMuted(const meshtastic_NodeInfoLite *n)
|
||||||
|
{
|
||||||
|
return n && (n->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK);
|
||||||
|
}
|
||||||
|
inline bool nodeInfoLiteIsKeyManuallyVerified(const meshtastic_NodeInfoLite *n)
|
||||||
|
{
|
||||||
|
return n && (n->bitfield & NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void nodeInfoLiteSetBit(meshtastic_NodeInfoLite *n, uint32_t mask, bool value)
|
||||||
|
{
|
||||||
|
if (!n)
|
||||||
|
return;
|
||||||
|
if (value)
|
||||||
|
n->bitfield |= mask;
|
||||||
|
else
|
||||||
|
n->bitfield &= ~mask;
|
||||||
|
}
|
||||||
|
|
||||||
#define Module_Config_size \
|
#define Module_Config_size \
|
||||||
(ModuleConfig_CannedMessageConfig_size + ModuleConfig_ExternalNotificationConfig_size + ModuleConfig_MQTTConfig_size + \
|
(ModuleConfig_CannedMessageConfig_size + ModuleConfig_ExternalNotificationConfig_size + ModuleConfig_MQTTConfig_size + \
|
||||||
|
|||||||
@@ -0,0 +1,129 @@
|
|||||||
|
// Migration from the legacy (pre-v25) NodeDatabase shape to the slim header +
|
||||||
|
// satellite maps layout. Lives in its own translation unit so the bulk
|
||||||
|
// fixed-shape decode + field-by-field copy doesn't clutter NodeDB.cpp.
|
||||||
|
//
|
||||||
|
// Caller (NodeDB::loadFromDisk) decides what to do with the result:
|
||||||
|
// - true -> persist via saveNodeDatabaseToDisk()
|
||||||
|
// - false -> reset via installDefaultNodeDatabase()
|
||||||
|
//
|
||||||
|
// This file (and the deviceonly_legacy proto) can be removed once
|
||||||
|
// DEVICESTATE_MIN_VER advances past 24.
|
||||||
|
|
||||||
|
#include "NodeDB.h"
|
||||||
|
#include "concurrency/LockGuard.h"
|
||||||
|
#include "configuration.h"
|
||||||
|
#include "mesh-pb-constants.h"
|
||||||
|
#include "mesh/generated/meshtastic/deviceonly_legacy.pb.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
|
#include <pb_decode.h>
|
||||||
|
#include <pb_encode.h>
|
||||||
|
|
||||||
|
bool meshtastic_NodeDatabase_Legacy_callback(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_t *field)
|
||||||
|
{
|
||||||
|
const auto *iter = reinterpret_cast<const pb_field_iter_t *>(field);
|
||||||
|
if (ostream) {
|
||||||
|
const auto *vec = static_cast<const std::vector<meshtastic_NodeInfoLite_Legacy> *>(iter->pData);
|
||||||
|
for (auto item : *vec) {
|
||||||
|
if (!pb_encode_tag_for_field(ostream, iter))
|
||||||
|
return false;
|
||||||
|
if (!pb_encode_submessage(ostream, meshtastic_NodeInfoLite_Legacy_fields, &item))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (istream) {
|
||||||
|
meshtastic_NodeInfoLite_Legacy node;
|
||||||
|
auto *vec = static_cast<std::vector<meshtastic_NodeInfoLite_Legacy> *>(iter->pData);
|
||||||
|
if (istream->bytes_left && pb_decode(istream, meshtastic_NodeInfoLite_Legacy_fields, &node))
|
||||||
|
vec->push_back(node);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NodeDB::migrateLegacyNodeDatabase()
|
||||||
|
{
|
||||||
|
LOG_WARN("NodeDatabase v%u: migrating to v%u", nodeDatabase.version, DEVICESTATE_CUR_VER);
|
||||||
|
|
||||||
|
// _init_zero brace-inits the embedded std::vector via its explicit
|
||||||
|
// (size_type, allocator) ctor, so default-construct instead.
|
||||||
|
meshtastic_NodeDatabase_Legacy legacyDb{};
|
||||||
|
legacyDb.version = 0;
|
||||||
|
meshtastic_NodeDatabase_Legacy legacyEmpty{};
|
||||||
|
legacyEmpty.version = DEVICESTATE_CUR_VER;
|
||||||
|
size_t legacyEmptyEncoded = 0;
|
||||||
|
pb_get_encoded_size(&legacyEmptyEncoded, meshtastic_NodeDatabase_Legacy_fields, &legacyEmpty);
|
||||||
|
const size_t legacyBufSize = legacyEmptyEncoded + (MAX_NUM_NODES * meshtastic_NodeInfoLite_Legacy_size);
|
||||||
|
auto legacyState = loadProto(nodeDatabaseFileName, legacyBufSize, sizeof(meshtastic_NodeDatabase_Legacy),
|
||||||
|
&meshtastic_NodeDatabase_Legacy_msg, &legacyDb);
|
||||||
|
if (legacyState != LoadFileResult::LOAD_SUCCESS) {
|
||||||
|
LOG_ERROR("Failed to load NodeDatabase via legacy descriptor; installing default");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeDatabase.nodes.clear();
|
||||||
|
const size_t maxToMigrate = std::min<size_t>(legacyDb.nodes.size(), MAX_NUM_NODES);
|
||||||
|
nodeDatabase.nodes.reserve(maxToMigrate);
|
||||||
|
size_t posCount = 0, telCount = 0;
|
||||||
|
{
|
||||||
|
concurrency::LockGuard guard(&satelliteMutex);
|
||||||
|
for (size_t i = 0; i < maxToMigrate; ++i) {
|
||||||
|
const auto &legacy = legacyDb.nodes[i];
|
||||||
|
meshtastic_NodeInfoLite slim = meshtastic_NodeInfoLite_init_default;
|
||||||
|
slim.num = legacy.num;
|
||||||
|
slim.snr = legacy.snr;
|
||||||
|
slim.last_heard = legacy.last_heard;
|
||||||
|
slim.channel = legacy.channel;
|
||||||
|
slim.has_hops_away = legacy.has_hops_away;
|
||||||
|
slim.hops_away = legacy.hops_away;
|
||||||
|
slim.next_hop = legacy.next_hop;
|
||||||
|
slim.bitfield = legacy.bitfield;
|
||||||
|
if (legacy.via_mqtt)
|
||||||
|
slim.bitfield |= NODEINFO_BITFIELD_VIA_MQTT_MASK;
|
||||||
|
if (legacy.is_favorite)
|
||||||
|
slim.bitfield |= NODEINFO_BITFIELD_IS_FAVORITE_MASK;
|
||||||
|
if (legacy.is_ignored)
|
||||||
|
slim.bitfield |= NODEINFO_BITFIELD_IS_IGNORED_MASK;
|
||||||
|
if (legacy.has_user) {
|
||||||
|
slim.bitfield |= NODEINFO_BITFIELD_HAS_USER_MASK;
|
||||||
|
strncpy(slim.long_name, legacy.user.long_name, sizeof(slim.long_name));
|
||||||
|
slim.long_name[sizeof(slim.long_name) - 1] = '\0';
|
||||||
|
strncpy(slim.short_name, legacy.user.short_name, sizeof(slim.short_name));
|
||||||
|
slim.short_name[sizeof(slim.short_name) - 1] = '\0';
|
||||||
|
slim.hw_model = legacy.user.hw_model;
|
||||||
|
slim.role = legacy.user.role;
|
||||||
|
if (legacy.user.is_licensed)
|
||||||
|
slim.bitfield |= NODEINFO_BITFIELD_IS_LICENSED_MASK;
|
||||||
|
slim.public_key.size = legacy.user.public_key.size;
|
||||||
|
memcpy(slim.public_key.bytes, legacy.user.public_key.bytes, sizeof(slim.public_key.bytes));
|
||||||
|
if (legacy.user.has_is_unmessagable) {
|
||||||
|
slim.bitfield |= NODEINFO_BITFIELD_HAS_IS_UNMESSAGABLE_MASK;
|
||||||
|
if (legacy.user.is_unmessagable)
|
||||||
|
slim.bitfield |= NODEINFO_BITFIELD_IS_UNMESSAGABLE_MASK;
|
||||||
|
}
|
||||||
|
// macaddr deprecated since 1.2.11 - dropped from slim header.
|
||||||
|
}
|
||||||
|
nodeDatabase.nodes.push_back(slim);
|
||||||
|
#if !MESHTASTIC_EXCLUDE_POSITIONDB
|
||||||
|
if (legacy.has_position)
|
||||||
|
nodePositions[legacy.num] = legacy.position;
|
||||||
|
#endif
|
||||||
|
#if !MESHTASTIC_EXCLUDE_TELEMETRYDB
|
||||||
|
if (legacy.has_device_metrics)
|
||||||
|
nodeTelemetry[legacy.num] = legacy.device_metrics;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#if !MESHTASTIC_EXCLUDE_POSITIONDB
|
||||||
|
posCount = nodePositions.size();
|
||||||
|
#endif
|
||||||
|
#if !MESHTASTIC_EXCLUDE_TELEMETRYDB
|
||||||
|
telCount = nodeTelemetry.size();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
nodeDatabase.version = DEVICESTATE_CUR_VER;
|
||||||
|
meshNodes = &nodeDatabase.nodes;
|
||||||
|
numMeshNodes = nodeDatabase.nodes.size();
|
||||||
|
LOG_INFO("Migrated %u nodes from legacy -> v%u (positions: %u, telemetry: %u)", (unsigned)numMeshNodes, DEVICESTATE_CUR_VER,
|
||||||
|
(unsigned)posCount, (unsigned)telCount);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
+419
-7
@@ -62,7 +62,7 @@ void PhoneAPI::handleStartConfig()
|
|||||||
onConfigStart();
|
onConfigStart();
|
||||||
|
|
||||||
// even if we were already connected - restart our state machine
|
// even if we were already connected - restart our state machine
|
||||||
if (config_nonce == SPECIAL_NONCE_ONLY_NODES) {
|
if (config_nonce == SPECIAL_NONCE_ONLY_NODES || config_nonce == SPECIAL_NONCE_GRADIENT_ONLY_NODES) {
|
||||||
// If client only wants node info, jump directly to sending nodes
|
// If client only wants node info, jump directly to sending nodes
|
||||||
state = STATE_SEND_OWN_NODEINFO;
|
state = STATE_SEND_OWN_NODEINFO;
|
||||||
LOG_INFO("Client only wants node info, skipping other config");
|
LOG_INFO("Client only wants node info, skipping other config");
|
||||||
@@ -81,6 +81,15 @@ void PhoneAPI::handleStartConfig()
|
|||||||
concurrency::LockGuard guard(&nodeInfoMutex);
|
concurrency::LockGuard guard(&nodeInfoMutex);
|
||||||
nodeInfoForPhone = {};
|
nodeInfoForPhone = {};
|
||||||
nodeInfoQueue.clear();
|
nodeInfoQueue.clear();
|
||||||
|
replayQueue.clear();
|
||||||
|
replayPositionOrder.clear();
|
||||||
|
replayTelemetryOrder.clear();
|
||||||
|
replayEnvironmentOrder.clear();
|
||||||
|
replayStatusOrder.clear();
|
||||||
|
replayPositionIndex = 0;
|
||||||
|
replayTelemetryIndex = 0;
|
||||||
|
replayEnvironmentIndex = 0;
|
||||||
|
replayStatusIndex = 0;
|
||||||
}
|
}
|
||||||
resetReadIndex();
|
resetReadIndex();
|
||||||
}
|
}
|
||||||
@@ -120,6 +129,15 @@ void PhoneAPI::close()
|
|||||||
concurrency::LockGuard guard(&nodeInfoMutex);
|
concurrency::LockGuard guard(&nodeInfoMutex);
|
||||||
nodeInfoForPhone = {};
|
nodeInfoForPhone = {};
|
||||||
nodeInfoQueue.clear();
|
nodeInfoQueue.clear();
|
||||||
|
replayQueue.clear();
|
||||||
|
replayPositionOrder.clear();
|
||||||
|
replayTelemetryOrder.clear();
|
||||||
|
replayEnvironmentOrder.clear();
|
||||||
|
replayStatusOrder.clear();
|
||||||
|
replayPositionIndex = 0;
|
||||||
|
replayTelemetryIndex = 0;
|
||||||
|
replayEnvironmentIndex = 0;
|
||||||
|
replayStatusIndex = 0;
|
||||||
}
|
}
|
||||||
packetForPhone = NULL;
|
packetForPhone = NULL;
|
||||||
filesManifest.clear();
|
filesManifest.clear();
|
||||||
@@ -302,7 +320,7 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
|
|||||||
nodeInfoForPhone.num = 0;
|
nodeInfoForPhone.num = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (config_nonce == SPECIAL_NONCE_ONLY_NODES) {
|
if (config_nonce == SPECIAL_NONCE_ONLY_NODES || config_nonce == SPECIAL_NONCE_GRADIENT_ONLY_NODES) {
|
||||||
// If client only wants node info, jump directly to sending nodes
|
// If client only wants node info, jump directly to sending nodes
|
||||||
state = STATE_SEND_OTHER_NODEINFOS;
|
state = STATE_SEND_OTHER_NODEINFOS;
|
||||||
onNowHasData(0);
|
onNowHasData(0);
|
||||||
@@ -529,8 +547,124 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
|
|||||||
LOG_DEBUG("Done sending %d of %d nodeinfos millis=%u", readIndex, nodeDB->getNumMeshNodes(), millis());
|
LOG_DEBUG("Done sending %d of %d nodeinfos millis=%u", readIndex, nodeDB->getNumMeshNodes(), millis());
|
||||||
concurrency::LockGuard guard(&nodeInfoMutex);
|
concurrency::LockGuard guard(&nodeInfoMutex);
|
||||||
nodeInfoQueue.clear();
|
nodeInfoQueue.clear();
|
||||||
|
// Replay states no-op for legacy clients / excluded DBs.
|
||||||
|
state = STATE_REPLAY_POSITIONS;
|
||||||
|
return getFromRadio(buf);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case STATE_REPLAY_POSITIONS: {
|
||||||
|
if (replayPositionOrder.empty() && replayPositionIndex == 0)
|
||||||
|
beginReplayPositions();
|
||||||
|
prefetchReplayPositions();
|
||||||
|
|
||||||
|
meshtastic_MeshPacket pkt = {};
|
||||||
|
bool havePkt = false;
|
||||||
|
{
|
||||||
|
concurrency::LockGuard guard(&nodeInfoMutex);
|
||||||
|
if (!replayQueue.empty()) {
|
||||||
|
pkt = replayQueue.front();
|
||||||
|
replayQueue.pop_front();
|
||||||
|
havePkt = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (havePkt) {
|
||||||
|
fromRadioScratch.which_payload_variant = meshtastic_FromRadio_packet_tag;
|
||||||
|
fromRadioScratch.packet = pkt;
|
||||||
|
} else {
|
||||||
|
LOG_DEBUG("Done replaying positions count=%u millis=%u", (unsigned)replayPositionIndex, millis());
|
||||||
|
state = STATE_REPLAY_TELEMETRY;
|
||||||
|
return getFromRadio(buf);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case STATE_REPLAY_TELEMETRY: {
|
||||||
|
if (replayTelemetryOrder.empty() && replayTelemetryIndex == 0)
|
||||||
|
beginReplayTelemetry();
|
||||||
|
prefetchReplayTelemetry();
|
||||||
|
|
||||||
|
meshtastic_MeshPacket pkt = {};
|
||||||
|
bool havePkt = false;
|
||||||
|
{
|
||||||
|
concurrency::LockGuard guard(&nodeInfoMutex);
|
||||||
|
if (!replayQueue.empty()) {
|
||||||
|
pkt = replayQueue.front();
|
||||||
|
replayQueue.pop_front();
|
||||||
|
havePkt = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (havePkt) {
|
||||||
|
fromRadioScratch.which_payload_variant = meshtastic_FromRadio_packet_tag;
|
||||||
|
fromRadioScratch.packet = pkt;
|
||||||
|
} else {
|
||||||
|
LOG_DEBUG("Done replaying telemetry count=%u millis=%u", (unsigned)replayTelemetryIndex, millis());
|
||||||
|
state = STATE_REPLAY_ENVIRONMENT;
|
||||||
|
return getFromRadio(buf);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case STATE_REPLAY_ENVIRONMENT: {
|
||||||
|
if (replayEnvironmentOrder.empty() && replayEnvironmentIndex == 0)
|
||||||
|
beginReplayEnvironment();
|
||||||
|
prefetchReplayEnvironment();
|
||||||
|
|
||||||
|
meshtastic_MeshPacket pkt = {};
|
||||||
|
bool havePkt = false;
|
||||||
|
{
|
||||||
|
concurrency::LockGuard guard(&nodeInfoMutex);
|
||||||
|
if (!replayQueue.empty()) {
|
||||||
|
pkt = replayQueue.front();
|
||||||
|
replayQueue.pop_front();
|
||||||
|
havePkt = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (havePkt) {
|
||||||
|
fromRadioScratch.which_payload_variant = meshtastic_FromRadio_packet_tag;
|
||||||
|
fromRadioScratch.packet = pkt;
|
||||||
|
} else {
|
||||||
|
LOG_DEBUG("Done replaying environment count=%u millis=%u", (unsigned)replayEnvironmentIndex, millis());
|
||||||
|
state = STATE_REPLAY_STATUS;
|
||||||
|
return getFromRadio(buf);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case STATE_REPLAY_STATUS: {
|
||||||
|
if (replayStatusOrder.empty() && replayStatusIndex == 0)
|
||||||
|
beginReplayStatus();
|
||||||
|
prefetchReplayStatus();
|
||||||
|
|
||||||
|
meshtastic_MeshPacket pkt = {};
|
||||||
|
bool havePkt = false;
|
||||||
|
{
|
||||||
|
concurrency::LockGuard guard(&nodeInfoMutex);
|
||||||
|
if (!replayQueue.empty()) {
|
||||||
|
pkt = replayQueue.front();
|
||||||
|
replayQueue.pop_front();
|
||||||
|
havePkt = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (havePkt) {
|
||||||
|
fromRadioScratch.which_payload_variant = meshtastic_FromRadio_packet_tag;
|
||||||
|
fromRadioScratch.packet = pkt;
|
||||||
|
} else {
|
||||||
|
LOG_DEBUG("Done replaying status count=%u millis=%u", (unsigned)replayStatusIndex, millis());
|
||||||
|
replayPositionOrder.clear();
|
||||||
|
replayPositionOrder.shrink_to_fit();
|
||||||
|
replayTelemetryOrder.clear();
|
||||||
|
replayTelemetryOrder.shrink_to_fit();
|
||||||
|
replayEnvironmentOrder.clear();
|
||||||
|
replayEnvironmentOrder.shrink_to_fit();
|
||||||
|
replayStatusOrder.clear();
|
||||||
|
replayStatusOrder.shrink_to_fit();
|
||||||
state = STATE_SEND_FILEMANIFEST;
|
state = STATE_SEND_FILEMANIFEST;
|
||||||
// Go ahead and send that ID right now
|
|
||||||
return getFromRadio(buf);
|
return getFromRadio(buf);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -538,9 +672,9 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
|
|||||||
|
|
||||||
case STATE_SEND_FILEMANIFEST: {
|
case STATE_SEND_FILEMANIFEST: {
|
||||||
LOG_DEBUG("FromRadio=STATE_SEND_FILEMANIFEST");
|
LOG_DEBUG("FromRadio=STATE_SEND_FILEMANIFEST");
|
||||||
// last element
|
// ONLY_NODES variants skip the manifest.
|
||||||
if (config_state == filesManifest.size() ||
|
if (config_state == filesManifest.size() || config_nonce == SPECIAL_NONCE_ONLY_NODES ||
|
||||||
config_nonce == SPECIAL_NONCE_ONLY_NODES) { // also handles an empty filesManifest
|
config_nonce == SPECIAL_NONCE_GRADIENT_ONLY_NODES) {
|
||||||
config_state = 0;
|
config_state = 0;
|
||||||
filesManifest.clear();
|
filesManifest.clear();
|
||||||
// Skip to complete packet
|
// Skip to complete packet
|
||||||
@@ -653,6 +787,7 @@ void PhoneAPI::prefetchNodeInfos()
|
|||||||
{
|
{
|
||||||
bool added = false;
|
bool added = false;
|
||||||
bool wasEmpty = false;
|
bool wasEmpty = false;
|
||||||
|
const bool gradient = clientWantsGradientSync();
|
||||||
// Keep the queue topped up so BLE reads stay responsive even if DB fetches take a moment.
|
// Keep the queue topped up so BLE reads stay responsive even if DB fetches take a moment.
|
||||||
{
|
{
|
||||||
concurrency::LockGuard guard(&nodeInfoMutex);
|
concurrency::LockGuard guard(&nodeInfoMutex);
|
||||||
@@ -662,7 +797,8 @@ void PhoneAPI::prefetchNodeInfos()
|
|||||||
if (!nextNode)
|
if (!nextNode)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
auto info = TypeConversions::ConvertToNodeInfo(nextNode);
|
auto info =
|
||||||
|
gradient ? TypeConversions::ConvertToNodeInfoThin(nextNode) : TypeConversions::ConvertToNodeInfo(nextNode);
|
||||||
bool isUs = info.num == nodeDB->getNodeNum();
|
bool isUs = info.num == nodeDB->getNodeNum();
|
||||||
info.hops_away = isUs ? 0 : info.hops_away;
|
info.hops_away = isUs ? 0 : info.hops_away;
|
||||||
info.last_heard = isUs ? getValidTime(RTCQualityFromNet) : info.last_heard;
|
info.last_heard = isUs ? getValidTime(RTCQualityFromNet) : info.last_heard;
|
||||||
@@ -682,6 +818,257 @@ void PhoneAPI::prefetchNodeInfos()
|
|||||||
onNowHasData(0);
|
onNowHasData(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
meshtastic_MeshPacket PhoneAPI::makeReplayPositionPacket(NodeNum num, const meshtastic_PositionLite &pos)
|
||||||
|
{
|
||||||
|
meshtastic_MeshPacket pkt = meshtastic_MeshPacket_init_default;
|
||||||
|
pkt.from = num;
|
||||||
|
pkt.to = nodeDB->getNodeNum();
|
||||||
|
pkt.id = generatePacketId();
|
||||||
|
pkt.rx_time = pos.time;
|
||||||
|
pkt.which_payload_variant = meshtastic_MeshPacket_decoded_tag;
|
||||||
|
pkt.decoded.portnum = meshtastic_PortNum_POSITION_APP;
|
||||||
|
meshtastic_Position fullPos = TypeConversions::ConvertToPosition(pos);
|
||||||
|
size_t len =
|
||||||
|
pb_encode_to_bytes(pkt.decoded.payload.bytes, sizeof(pkt.decoded.payload.bytes), &meshtastic_Position_msg, &fullPos);
|
||||||
|
pkt.decoded.payload.size = (pb_size_t)len;
|
||||||
|
return pkt;
|
||||||
|
}
|
||||||
|
|
||||||
|
meshtastic_MeshPacket PhoneAPI::makeReplayTelemetryPacket(NodeNum num, const meshtastic_DeviceMetrics &metrics)
|
||||||
|
{
|
||||||
|
meshtastic_MeshPacket pkt = meshtastic_MeshPacket_init_default;
|
||||||
|
pkt.from = num;
|
||||||
|
pkt.to = nodeDB->getNodeNum();
|
||||||
|
pkt.id = generatePacketId();
|
||||||
|
// No native timestamp on telemetry packets here; use last_heard.
|
||||||
|
const meshtastic_NodeInfoLite *header = nodeDB->getMeshNode(num);
|
||||||
|
pkt.rx_time = header ? header->last_heard : 0;
|
||||||
|
pkt.which_payload_variant = meshtastic_MeshPacket_decoded_tag;
|
||||||
|
pkt.decoded.portnum = meshtastic_PortNum_TELEMETRY_APP;
|
||||||
|
meshtastic_Telemetry fullTel = meshtastic_Telemetry_init_default;
|
||||||
|
fullTel.time = pkt.rx_time;
|
||||||
|
fullTel.which_variant = meshtastic_Telemetry_device_metrics_tag;
|
||||||
|
fullTel.variant.device_metrics = metrics;
|
||||||
|
size_t len =
|
||||||
|
pb_encode_to_bytes(pkt.decoded.payload.bytes, sizeof(pkt.decoded.payload.bytes), &meshtastic_Telemetry_msg, &fullTel);
|
||||||
|
pkt.decoded.payload.size = (pb_size_t)len;
|
||||||
|
return pkt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhoneAPI::beginReplayPositions()
|
||||||
|
{
|
||||||
|
#if MESHTASTIC_EXCLUDE_POSITIONDB
|
||||||
|
// Build excluded entirely - leave the order list empty so the state arm
|
||||||
|
// immediately drains and advances.
|
||||||
|
replayPositionOrder.clear();
|
||||||
|
replayPositionIndex = 0;
|
||||||
|
#else
|
||||||
|
if (!clientWantsGradientSync()) {
|
||||||
|
replayPositionOrder.clear();
|
||||||
|
replayPositionIndex = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Snapshot the keyset at phase start so concurrent inserts/erases on the
|
||||||
|
// map don't invalidate iteration. Skip our own node - the phone already
|
||||||
|
// got our position bundled in STATE_SEND_OWN_NODEINFO.
|
||||||
|
replayPositionOrder = nodeDB->snapshotPositionNodeNums(nodeDB->getNodeNum());
|
||||||
|
replayPositionIndex = 0;
|
||||||
|
LOG_INFO("Begin position replay: %u entries millis=%u", (unsigned)replayPositionOrder.size(), millis());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhoneAPI::prefetchReplayPositions()
|
||||||
|
{
|
||||||
|
#if MESHTASTIC_EXCLUDE_POSITIONDB
|
||||||
|
return;
|
||||||
|
#else
|
||||||
|
if (!clientWantsGradientSync())
|
||||||
|
return;
|
||||||
|
bool added = false;
|
||||||
|
bool wasEmpty = false;
|
||||||
|
{
|
||||||
|
concurrency::LockGuard guard(&nodeInfoMutex);
|
||||||
|
wasEmpty = replayQueue.empty();
|
||||||
|
while (replayQueue.size() < kReplayPrefetchDepth && replayPositionIndex < replayPositionOrder.size()) {
|
||||||
|
NodeNum num = replayPositionOrder[replayPositionIndex++];
|
||||||
|
meshtastic_PositionLite pos;
|
||||||
|
if (!nodeDB->copyNodePosition(num, pos))
|
||||||
|
continue; // entry was evicted between snapshot and now
|
||||||
|
replayQueue.push_back(makeReplayPositionPacket(num, pos));
|
||||||
|
added = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (added && wasEmpty)
|
||||||
|
onNowHasData(0);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhoneAPI::beginReplayTelemetry()
|
||||||
|
{
|
||||||
|
#if MESHTASTIC_EXCLUDE_TELEMETRYDB
|
||||||
|
replayTelemetryOrder.clear();
|
||||||
|
replayTelemetryIndex = 0;
|
||||||
|
#else
|
||||||
|
if (!clientWantsGradientSync()) {
|
||||||
|
replayTelemetryOrder.clear();
|
||||||
|
replayTelemetryIndex = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
replayTelemetryOrder = nodeDB->snapshotTelemetryNodeNums(nodeDB->getNodeNum());
|
||||||
|
replayTelemetryIndex = 0;
|
||||||
|
LOG_INFO("Begin telemetry replay: %u entries millis=%u", (unsigned)replayTelemetryOrder.size(), millis());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhoneAPI::prefetchReplayTelemetry()
|
||||||
|
{
|
||||||
|
#if MESHTASTIC_EXCLUDE_TELEMETRYDB
|
||||||
|
return;
|
||||||
|
#else
|
||||||
|
if (!clientWantsGradientSync())
|
||||||
|
return;
|
||||||
|
bool added = false;
|
||||||
|
bool wasEmpty = false;
|
||||||
|
{
|
||||||
|
concurrency::LockGuard guard(&nodeInfoMutex);
|
||||||
|
wasEmpty = replayQueue.empty();
|
||||||
|
while (replayQueue.size() < kReplayPrefetchDepth && replayTelemetryIndex < replayTelemetryOrder.size()) {
|
||||||
|
NodeNum num = replayTelemetryOrder[replayTelemetryIndex++];
|
||||||
|
meshtastic_DeviceMetrics dm;
|
||||||
|
if (!nodeDB->copyNodeTelemetry(num, dm))
|
||||||
|
continue;
|
||||||
|
replayQueue.push_back(makeReplayTelemetryPacket(num, dm));
|
||||||
|
added = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (added && wasEmpty)
|
||||||
|
onNowHasData(0);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
meshtastic_MeshPacket PhoneAPI::makeReplayEnvironmentPacket(uint32_t num, const meshtastic_EnvironmentMetrics &env)
|
||||||
|
{
|
||||||
|
meshtastic_MeshPacket pkt = meshtastic_MeshPacket_init_default;
|
||||||
|
pkt.from = num;
|
||||||
|
pkt.to = nodeDB->getNodeNum();
|
||||||
|
pkt.id = generatePacketId();
|
||||||
|
const meshtastic_NodeInfoLite *header = nodeDB->getMeshNode(num);
|
||||||
|
pkt.rx_time = header ? header->last_heard : 0;
|
||||||
|
pkt.which_payload_variant = meshtastic_MeshPacket_decoded_tag;
|
||||||
|
pkt.decoded.portnum = meshtastic_PortNum_TELEMETRY_APP;
|
||||||
|
meshtastic_Telemetry fullTel = meshtastic_Telemetry_init_default;
|
||||||
|
fullTel.time = pkt.rx_time;
|
||||||
|
fullTel.which_variant = meshtastic_Telemetry_environment_metrics_tag;
|
||||||
|
fullTel.variant.environment_metrics = env;
|
||||||
|
size_t len =
|
||||||
|
pb_encode_to_bytes(pkt.decoded.payload.bytes, sizeof(pkt.decoded.payload.bytes), &meshtastic_Telemetry_msg, &fullTel);
|
||||||
|
pkt.decoded.payload.size = (pb_size_t)len;
|
||||||
|
return pkt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhoneAPI::beginReplayEnvironment()
|
||||||
|
{
|
||||||
|
#if MESHTASTIC_EXCLUDE_ENVIRONMENTDB
|
||||||
|
replayEnvironmentOrder.clear();
|
||||||
|
replayEnvironmentIndex = 0;
|
||||||
|
#else
|
||||||
|
if (!clientWantsGradientSync()) {
|
||||||
|
replayEnvironmentOrder.clear();
|
||||||
|
replayEnvironmentIndex = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
replayEnvironmentOrder = nodeDB->snapshotEnvironmentNodeNums(nodeDB->getNodeNum());
|
||||||
|
replayEnvironmentIndex = 0;
|
||||||
|
LOG_INFO("Begin environment replay: %u entries millis=%u", (unsigned)replayEnvironmentOrder.size(), millis());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhoneAPI::prefetchReplayEnvironment()
|
||||||
|
{
|
||||||
|
#if MESHTASTIC_EXCLUDE_ENVIRONMENTDB
|
||||||
|
return;
|
||||||
|
#else
|
||||||
|
if (!clientWantsGradientSync())
|
||||||
|
return;
|
||||||
|
bool added = false;
|
||||||
|
bool wasEmpty = false;
|
||||||
|
{
|
||||||
|
concurrency::LockGuard guard(&nodeInfoMutex);
|
||||||
|
wasEmpty = replayQueue.empty();
|
||||||
|
while (replayQueue.size() < kReplayPrefetchDepth && replayEnvironmentIndex < replayEnvironmentOrder.size()) {
|
||||||
|
NodeNum num = replayEnvironmentOrder[replayEnvironmentIndex++];
|
||||||
|
meshtastic_EnvironmentMetrics env;
|
||||||
|
if (!nodeDB->copyNodeEnvironment(num, env))
|
||||||
|
continue;
|
||||||
|
replayQueue.push_back(makeReplayEnvironmentPacket(num, env));
|
||||||
|
added = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (added && wasEmpty)
|
||||||
|
onNowHasData(0);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
meshtastic_MeshPacket PhoneAPI::makeReplayStatusPacket(uint32_t num, const meshtastic_StatusMessage &status)
|
||||||
|
{
|
||||||
|
meshtastic_MeshPacket pkt = meshtastic_MeshPacket_init_default;
|
||||||
|
pkt.from = num;
|
||||||
|
pkt.to = nodeDB->getNodeNum();
|
||||||
|
pkt.id = generatePacketId();
|
||||||
|
// StatusMessage has no native timestamp; use last_heard.
|
||||||
|
const meshtastic_NodeInfoLite *header = nodeDB->getMeshNode(num);
|
||||||
|
pkt.rx_time = header ? header->last_heard : 0;
|
||||||
|
pkt.which_payload_variant = meshtastic_MeshPacket_decoded_tag;
|
||||||
|
pkt.decoded.portnum = meshtastic_PortNum_NODE_STATUS_APP;
|
||||||
|
size_t len =
|
||||||
|
pb_encode_to_bytes(pkt.decoded.payload.bytes, sizeof(pkt.decoded.payload.bytes), &meshtastic_StatusMessage_msg, &status);
|
||||||
|
pkt.decoded.payload.size = (pb_size_t)len;
|
||||||
|
return pkt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhoneAPI::beginReplayStatus()
|
||||||
|
{
|
||||||
|
#if MESHTASTIC_EXCLUDE_STATUSDB
|
||||||
|
replayStatusOrder.clear();
|
||||||
|
replayStatusIndex = 0;
|
||||||
|
#else
|
||||||
|
if (!clientWantsGradientSync()) {
|
||||||
|
replayStatusOrder.clear();
|
||||||
|
replayStatusIndex = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
replayStatusOrder = nodeDB->snapshotStatusNodeNums(nodeDB->getNodeNum());
|
||||||
|
replayStatusIndex = 0;
|
||||||
|
LOG_INFO("Begin status replay: %u entries millis=%u", (unsigned)replayStatusOrder.size(), millis());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void PhoneAPI::prefetchReplayStatus()
|
||||||
|
{
|
||||||
|
#if MESHTASTIC_EXCLUDE_STATUSDB
|
||||||
|
return;
|
||||||
|
#else
|
||||||
|
if (!clientWantsGradientSync())
|
||||||
|
return;
|
||||||
|
bool added = false;
|
||||||
|
bool wasEmpty = false;
|
||||||
|
{
|
||||||
|
concurrency::LockGuard guard(&nodeInfoMutex);
|
||||||
|
wasEmpty = replayQueue.empty();
|
||||||
|
while (replayQueue.size() < kReplayPrefetchDepth && replayStatusIndex < replayStatusOrder.size()) {
|
||||||
|
NodeNum num = replayStatusOrder[replayStatusIndex++];
|
||||||
|
meshtastic_StatusMessage status;
|
||||||
|
if (!nodeDB->copyNodeStatus(num, status) || status.status[0] == '\0')
|
||||||
|
continue;
|
||||||
|
replayQueue.push_back(makeReplayStatusPacket(num, status));
|
||||||
|
added = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (added && wasEmpty)
|
||||||
|
onNowHasData(0);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
void PhoneAPI::releaseMqttClientProxyPhonePacket()
|
void PhoneAPI::releaseMqttClientProxyPhonePacket()
|
||||||
{
|
{
|
||||||
if (mqttClientProxyMessageForPhone) {
|
if (mqttClientProxyMessageForPhone) {
|
||||||
@@ -728,6 +1115,31 @@ bool PhoneAPI::available()
|
|||||||
PREFETCH_NODEINFO:
|
PREFETCH_NODEINFO:
|
||||||
prefetchNodeInfos();
|
prefetchNodeInfos();
|
||||||
return true;
|
return true;
|
||||||
|
case STATE_REPLAY_POSITIONS: {
|
||||||
|
// Prime the iterator if we haven't yet, then top up the queue.
|
||||||
|
if (replayPositionOrder.empty() && replayPositionIndex == 0)
|
||||||
|
beginReplayPositions();
|
||||||
|
prefetchReplayPositions();
|
||||||
|
return true; // Always advance state machine; arm itself transitions when drained
|
||||||
|
}
|
||||||
|
case STATE_REPLAY_TELEMETRY: {
|
||||||
|
if (replayTelemetryOrder.empty() && replayTelemetryIndex == 0)
|
||||||
|
beginReplayTelemetry();
|
||||||
|
prefetchReplayTelemetry();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case STATE_REPLAY_ENVIRONMENT: {
|
||||||
|
if (replayEnvironmentOrder.empty() && replayEnvironmentIndex == 0)
|
||||||
|
beginReplayEnvironment();
|
||||||
|
prefetchReplayEnvironment();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case STATE_REPLAY_STATUS: {
|
||||||
|
if (replayStatusOrder.empty() && replayStatusIndex == 0)
|
||||||
|
beginReplayStatus();
|
||||||
|
prefetchReplayStatus();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
case STATE_SEND_PACKETS: {
|
case STATE_SEND_PACKETS: {
|
||||||
if (!queueStatusPacketForPhone)
|
if (!queueStatusPacketForPhone)
|
||||||
queueStatusPacketForPhone = service->getQueueStatusForPhone();
|
queueStatusPacketForPhone = service->getQueueStatusForPhone();
|
||||||
|
|||||||
+44
-1
@@ -4,12 +4,16 @@
|
|||||||
#include "concurrency/Lock.h"
|
#include "concurrency/Lock.h"
|
||||||
#include "mesh-pb-constants.h"
|
#include "mesh-pb-constants.h"
|
||||||
#include "meshtastic/portnums.pb.h"
|
#include "meshtastic/portnums.pb.h"
|
||||||
|
#include <cstdint>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
// NodeNum stored as raw uint32_t below; including MeshTypes.h here breaks the
|
||||||
|
// LOG_WARN include order via Arduino.h/MemoryPool.h.
|
||||||
|
|
||||||
// Make sure that we never let our packets grow too large for one BLE packet
|
// Make sure that we never let our packets grow too large for one BLE packet
|
||||||
#define MAX_TO_FROM_RADIO_SIZE 512
|
#define MAX_TO_FROM_RADIO_SIZE 512
|
||||||
|
|
||||||
@@ -22,6 +26,9 @@
|
|||||||
|
|
||||||
#define SPECIAL_NONCE_ONLY_CONFIG 69420
|
#define SPECIAL_NONCE_ONLY_CONFIG 69420
|
||||||
#define SPECIAL_NONCE_ONLY_NODES 69421 // ( ͡° ͜ʖ ͡°)
|
#define SPECIAL_NONCE_ONLY_NODES 69421 // ( ͡° ͜ʖ ͡°)
|
||||||
|
// Gradient sync: phone sends one of these to opt into thin-header + replay.
|
||||||
|
#define SPECIAL_NONCE_GRADIENT_SYNC 69422
|
||||||
|
#define SPECIAL_NONCE_GRADIENT_ONLY_NODES 69423
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides our protobuf based API which phone/PC clients can use to talk to our device
|
* Provides our protobuf based API which phone/PC clients can use to talk to our device
|
||||||
@@ -45,7 +52,13 @@ class PhoneAPI
|
|||||||
STATE_SEND_CONFIG, // Replacement for the old Radioconfig
|
STATE_SEND_CONFIG, // Replacement for the old Radioconfig
|
||||||
STATE_SEND_MODULECONFIG, // Send Module specific config
|
STATE_SEND_MODULECONFIG, // Send Module specific config
|
||||||
STATE_SEND_OTHER_NODEINFOS, // states progress in this order as the device sends to to the client
|
STATE_SEND_OTHER_NODEINFOS, // states progress in this order as the device sends to to the client
|
||||||
STATE_SEND_FILEMANIFEST, // Send file manifest
|
// Drain satellite DBs as synthetic POSITION_APP / TELEMETRY_APP /
|
||||||
|
// NODE_STATUS_APP packets when the phone opted into gradient sync.
|
||||||
|
STATE_REPLAY_POSITIONS,
|
||||||
|
STATE_REPLAY_TELEMETRY,
|
||||||
|
STATE_REPLAY_ENVIRONMENT,
|
||||||
|
STATE_REPLAY_STATUS,
|
||||||
|
STATE_SEND_FILEMANIFEST, // Send file manifest
|
||||||
STATE_SEND_COMPLETE_ID,
|
STATE_SEND_COMPLETE_ID,
|
||||||
STATE_SEND_PACKETS // send packets or debug strings
|
STATE_SEND_PACKETS // send packets or debug strings
|
||||||
};
|
};
|
||||||
@@ -88,6 +101,20 @@ class PhoneAPI
|
|||||||
// Protect nodeInfoForPhone + nodeInfoQueue because NimBLE callbacks run in a separate FreeRTOS task.
|
// Protect nodeInfoForPhone + nodeInfoQueue because NimBLE callbacks run in a separate FreeRTOS task.
|
||||||
concurrency::Lock nodeInfoMutex;
|
concurrency::Lock nodeInfoMutex;
|
||||||
|
|
||||||
|
// Synthetic-packet replay queue (paced via prefetch).
|
||||||
|
std::deque<meshtastic_MeshPacket> replayQueue;
|
||||||
|
static constexpr size_t kReplayPrefetchDepth = 4;
|
||||||
|
// NodeNum snapshots taken at each replay phase start so concurrent
|
||||||
|
// satellite-map mutations don't invalidate iteration.
|
||||||
|
std::vector<uint32_t> replayPositionOrder;
|
||||||
|
std::vector<uint32_t> replayTelemetryOrder;
|
||||||
|
std::vector<uint32_t> replayEnvironmentOrder;
|
||||||
|
std::vector<uint32_t> replayStatusOrder;
|
||||||
|
size_t replayPositionIndex = 0;
|
||||||
|
size_t replayTelemetryIndex = 0;
|
||||||
|
size_t replayEnvironmentIndex = 0;
|
||||||
|
size_t replayStatusIndex = 0;
|
||||||
|
|
||||||
meshtastic_ToRadio toRadioScratch = {
|
meshtastic_ToRadio toRadioScratch = {
|
||||||
0}; // this is a static scratch object, any data must be copied elsewhere before returning
|
0}; // this is a static scratch object, any data must be copied elsewhere before returning
|
||||||
|
|
||||||
@@ -137,6 +164,10 @@ class PhoneAPI
|
|||||||
|
|
||||||
bool isConnected() { return state != STATE_SEND_NOTHING; }
|
bool isConnected() { return state != STATE_SEND_NOTHING; }
|
||||||
bool isSendingPackets() { return state == STATE_SEND_PACKETS; }
|
bool isSendingPackets() { return state == STATE_SEND_PACKETS; }
|
||||||
|
bool clientWantsGradientSync() const
|
||||||
|
{
|
||||||
|
return config_nonce == SPECIAL_NONCE_GRADIENT_SYNC || config_nonce == SPECIAL_NONCE_GRADIENT_ONLY_NODES;
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/// Our fromradio packet while it is being assembled
|
/// Our fromradio packet while it is being assembled
|
||||||
@@ -185,6 +216,18 @@ class PhoneAPI
|
|||||||
void releaseQueueStatusPhonePacket();
|
void releaseQueueStatusPhonePacket();
|
||||||
|
|
||||||
void prefetchNodeInfos();
|
void prefetchNodeInfos();
|
||||||
|
void beginReplayPositions();
|
||||||
|
void prefetchReplayPositions();
|
||||||
|
void beginReplayTelemetry();
|
||||||
|
void prefetchReplayTelemetry();
|
||||||
|
void beginReplayEnvironment();
|
||||||
|
void prefetchReplayEnvironment();
|
||||||
|
void beginReplayStatus();
|
||||||
|
void prefetchReplayStatus();
|
||||||
|
meshtastic_MeshPacket makeReplayPositionPacket(uint32_t num, const meshtastic_PositionLite &pos);
|
||||||
|
meshtastic_MeshPacket makeReplayTelemetryPacket(uint32_t num, const meshtastic_DeviceMetrics &metrics);
|
||||||
|
meshtastic_MeshPacket makeReplayEnvironmentPacket(uint32_t num, const meshtastic_EnvironmentMetrics &env);
|
||||||
|
meshtastic_MeshPacket makeReplayStatusPacket(uint32_t num, const meshtastic_StatusMessage &status);
|
||||||
|
|
||||||
void releaseMqttClientProxyPhonePacket();
|
void releaseMqttClientProxyPhonePacket();
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ template <class T> class ProtobufModule : protected SinglePortModule
|
|||||||
const char *getSenderShortName(const meshtastic_MeshPacket &mp)
|
const char *getSenderShortName(const meshtastic_MeshPacket &mp)
|
||||||
{
|
{
|
||||||
auto node = nodeDB->getMeshNode(getFrom(&mp));
|
auto node = nodeDB->getMeshNode(getFrom(&mp));
|
||||||
const char *sender = (node) ? node->user.short_name : "???";
|
const char *sender = (node && nodeInfoLiteHasUser(node) && node->short_name[0]) ? node->short_name : "???";
|
||||||
return sender;
|
return sender;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas
|
|||||||
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, 0);
|
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel, 0);
|
||||||
}
|
}
|
||||||
} else if (p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag && p->channel == 0 &&
|
} else if (p->which_payload_variant == meshtastic_MeshPacket_encrypted_tag && p->channel == 0 &&
|
||||||
(nodeDB->getMeshNode(p->from) == nullptr || nodeDB->getMeshNode(p->from)->user.public_key.size == 0)) {
|
(nodeDB->getMeshNode(p->from) == nullptr || nodeDB->getMeshNode(p->from)->public_key.size == 0)) {
|
||||||
LOG_INFO("PKI packet from unknown node, send PKI_UNKNOWN_PUBKEY");
|
LOG_INFO("PKI packet from unknown node, send PKI_UNKNOWN_PUBKEY");
|
||||||
sendAckNak(meshtastic_Routing_Error_PKI_UNKNOWN_PUBKEY, getFrom(p), p->id, channels.getPrimaryIndex(),
|
sendAckNak(meshtastic_Routing_Error_PKI_UNKNOWN_PUBKEY, getFrom(p), p->id, channels.getPrimaryIndex(),
|
||||||
routingModule->getHopLimitForResponse(*p));
|
routingModule->getHopLimitForResponse(*p));
|
||||||
|
|||||||
+16
-16
@@ -120,17 +120,17 @@ bool Router::shouldDecrementHopLimit(const meshtastic_MeshPacket *p)
|
|||||||
if (!node)
|
if (!node)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Check 1: is_favorite (cheapest - single bool)
|
// Check 1: is_favorite (cheapest - single bit test)
|
||||||
if (!node->is_favorite)
|
if (!nodeInfoLiteIsFavorite(node))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Check 2: has_user (cheap - single bool)
|
// Check 2: has_user (cheap - single bit test)
|
||||||
if (!node->has_user)
|
if (!nodeInfoLiteHasUser(node))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
// Check 3: role check (moderate cost - multiple comparisons)
|
// Check 3: role check (moderate cost - multiple comparisons)
|
||||||
if (!IS_ONE_OF(node->user.role, meshtastic_Config_DeviceConfig_Role_ROUTER,
|
if (!IS_ONE_OF(node->role, meshtastic_Config_DeviceConfig_Role_ROUTER, meshtastic_Config_DeviceConfig_Role_ROUTER_LATE,
|
||||||
meshtastic_Config_DeviceConfig_Role_ROUTER_LATE, meshtastic_Config_DeviceConfig_Role_CLIENT_BASE)) {
|
meshtastic_Config_DeviceConfig_Role_CLIENT_BASE)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -433,7 +433,7 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p)
|
|||||||
concurrency::LockGuard g(cryptLock);
|
concurrency::LockGuard g(cryptLock);
|
||||||
|
|
||||||
if (config.device.rebroadcast_mode == meshtastic_Config_DeviceConfig_RebroadcastMode_KNOWN_ONLY &&
|
if (config.device.rebroadcast_mode == meshtastic_Config_DeviceConfig_RebroadcastMode_KNOWN_ONLY &&
|
||||||
(nodeDB->getMeshNode(p->from) == NULL || !nodeDB->getMeshNode(p->from)->has_user)) {
|
!nodeInfoLiteHasUser(nodeDB->getMeshNode(p->from))) {
|
||||||
LOG_DEBUG("Node 0x%x not in nodeDB-> Rebroadcast mode KNOWN_ONLY will ignore packet", p->from);
|
LOG_DEBUG("Node 0x%x not in nodeDB-> Rebroadcast mode KNOWN_ONLY will ignore packet", p->from);
|
||||||
return DecodeState::DECODE_FAILURE;
|
return DecodeState::DECODE_FAILURE;
|
||||||
}
|
}
|
||||||
@@ -451,11 +451,11 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p)
|
|||||||
#if !(MESHTASTIC_EXCLUDE_PKI)
|
#if !(MESHTASTIC_EXCLUDE_PKI)
|
||||||
// Attempt PKI decryption first
|
// Attempt PKI decryption first
|
||||||
if (p->channel == 0 && isToUs(p) && p->to > 0 && !isBroadcast(p->to) && nodeDB->getMeshNode(p->from) != nullptr &&
|
if (p->channel == 0 && isToUs(p) && p->to > 0 && !isBroadcast(p->to) && nodeDB->getMeshNode(p->from) != nullptr &&
|
||||||
nodeDB->getMeshNode(p->from)->user.public_key.size > 0 && nodeDB->getMeshNode(p->to)->user.public_key.size > 0 &&
|
nodeDB->getMeshNode(p->from)->public_key.size > 0 && nodeDB->getMeshNode(p->to) != nullptr &&
|
||||||
rawSize > MESHTASTIC_PKC_OVERHEAD) {
|
nodeDB->getMeshNode(p->to)->public_key.size > 0 && rawSize > MESHTASTIC_PKC_OVERHEAD) {
|
||||||
LOG_DEBUG("Attempt PKI decryption");
|
LOG_DEBUG("Attempt PKI decryption");
|
||||||
|
|
||||||
if (crypto->decryptCurve25519(p->from, nodeDB->getMeshNode(p->from)->user.public_key, p->id, rawSize, p->encrypted.bytes,
|
if (crypto->decryptCurve25519(p->from, nodeDB->getMeshNode(p->from)->public_key, p->id, rawSize, p->encrypted.bytes,
|
||||||
bytes)) {
|
bytes)) {
|
||||||
LOG_INFO("PKI Decryption worked!");
|
LOG_INFO("PKI Decryption worked!");
|
||||||
|
|
||||||
@@ -467,7 +467,7 @@ DecodeState perhapsDecode(meshtastic_MeshPacket *p)
|
|||||||
decrypted = true;
|
decrypted = true;
|
||||||
LOG_INFO("Packet decrypted using PKI!");
|
LOG_INFO("Packet decrypted using PKI!");
|
||||||
p->pki_encrypted = true;
|
p->pki_encrypted = true;
|
||||||
memcpy(&p->public_key.bytes, nodeDB->getMeshNode(p->from)->user.public_key.bytes, 32);
|
memcpy(&p->public_key.bytes, nodeDB->getMeshNode(p->from)->public_key.bytes, 32);
|
||||||
p->public_key.size = 32;
|
p->public_key.size = 32;
|
||||||
p->decoded = decodedtmp;
|
p->decoded = decodedtmp;
|
||||||
p->which_payload_variant = meshtastic_MeshPacket_decoded_tag; // change type to decoded
|
p->which_payload_variant = meshtastic_MeshPacket_decoded_tag; // change type to decoded
|
||||||
@@ -665,18 +665,18 @@ meshtastic_Routing_Error perhapsEncode(meshtastic_MeshPacket *p)
|
|||||||
if (numbytes + MESHTASTIC_HEADER_LENGTH + MESHTASTIC_PKC_OVERHEAD > MAX_LORA_PAYLOAD_LEN)
|
if (numbytes + MESHTASTIC_HEADER_LENGTH + MESHTASTIC_PKC_OVERHEAD > MAX_LORA_PAYLOAD_LEN)
|
||||||
return meshtastic_Routing_Error_TOO_LARGE;
|
return meshtastic_Routing_Error_TOO_LARGE;
|
||||||
// Check for a known public key for the destination
|
// Check for a known public key for the destination
|
||||||
if (node == nullptr || node->user.public_key.size != 32) {
|
if (node == nullptr || node->public_key.size != 32) {
|
||||||
LOG_WARN("Unknown public key for destination node 0x%08x (portnum %d), refusing to send legacy DM", p->to,
|
LOG_WARN("Unknown public key for destination node 0x%08x (portnum %d), refusing to send legacy DM", p->to,
|
||||||
p->decoded.portnum);
|
p->decoded.portnum);
|
||||||
return meshtastic_Routing_Error_PKI_SEND_FAIL_PUBLIC_KEY;
|
return meshtastic_Routing_Error_PKI_SEND_FAIL_PUBLIC_KEY;
|
||||||
}
|
}
|
||||||
if (p->pki_encrypted && !memfll(p->public_key.bytes, 0, 32) &&
|
if (p->pki_encrypted && !memfll(p->public_key.bytes, 0, 32) &&
|
||||||
memcmp(p->public_key.bytes, node->user.public_key.bytes, 32) != 0) {
|
memcmp(p->public_key.bytes, node->public_key.bytes, 32) != 0) {
|
||||||
LOG_WARN("Client public key differs from requested: 0x%02x, stored key begins 0x%02x", *p->public_key.bytes,
|
LOG_WARN("Client public key differs from requested: 0x%02x, stored key begins 0x%02x", *p->public_key.bytes,
|
||||||
*node->user.public_key.bytes);
|
*node->public_key.bytes);
|
||||||
return meshtastic_Routing_Error_PKI_FAILED;
|
return meshtastic_Routing_Error_PKI_FAILED;
|
||||||
}
|
}
|
||||||
crypto->encryptCurve25519(p->to, getFrom(p), node->user.public_key, p->id, numbytes, bytes, p->encrypted.bytes);
|
crypto->encryptCurve25519(p->to, getFrom(p), node->public_key, p->id, numbytes, bytes, p->encrypted.bytes);
|
||||||
numbytes += MESHTASTIC_PKC_OVERHEAD;
|
numbytes += MESHTASTIC_PKC_OVERHEAD;
|
||||||
p->channel = 0;
|
p->channel = 0;
|
||||||
p->pki_encrypted = true;
|
p->pki_encrypted = true;
|
||||||
@@ -860,7 +860,7 @@ void Router::perhapsHandleReceived(meshtastic_MeshPacket *p)
|
|||||||
}
|
}
|
||||||
|
|
||||||
meshtastic_NodeInfoLite const *node = nodeDB->getMeshNode(p->from);
|
meshtastic_NodeInfoLite const *node = nodeDB->getMeshNode(p->from);
|
||||||
if (node != NULL && node->is_ignored) {
|
if (nodeInfoLiteIsIgnored(node)) {
|
||||||
LOG_DEBUG("Ignore msg, 0x%x is ignored", p->from);
|
LOG_DEBUG("Ignore msg, 0x%x is ignored", p->from);
|
||||||
packetPool.release(p);
|
packetPool.release(p);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -3,7 +3,9 @@
|
|||||||
#include "mesh/generated/meshtastic/mesh.pb.h"
|
#include "mesh/generated/meshtastic/mesh.pb.h"
|
||||||
#include "meshUtils.h"
|
#include "meshUtils.h"
|
||||||
|
|
||||||
meshtastic_NodeInfo TypeConversions::ConvertToNodeInfo(const meshtastic_NodeInfoLite *lite)
|
meshtastic_NodeInfo TypeConversions::ConvertToNodeInfo(const meshtastic_NodeInfoLite *lite,
|
||||||
|
const meshtastic_PositionLite *position,
|
||||||
|
const meshtastic_DeviceMetrics *deviceMetrics)
|
||||||
{
|
{
|
||||||
meshtastic_NodeInfo info = meshtastic_NodeInfo_init_default;
|
meshtastic_NodeInfo info = meshtastic_NodeInfo_init_default;
|
||||||
|
|
||||||
@@ -11,42 +13,56 @@ meshtastic_NodeInfo TypeConversions::ConvertToNodeInfo(const meshtastic_NodeInfo
|
|||||||
info.snr = lite->snr;
|
info.snr = lite->snr;
|
||||||
info.last_heard = lite->last_heard;
|
info.last_heard = lite->last_heard;
|
||||||
info.channel = lite->channel;
|
info.channel = lite->channel;
|
||||||
info.via_mqtt = lite->via_mqtt;
|
info.via_mqtt = nodeInfoLiteViaMqtt(lite);
|
||||||
info.is_favorite = lite->is_favorite;
|
info.is_favorite = nodeInfoLiteIsFavorite(lite);
|
||||||
info.is_ignored = lite->is_ignored;
|
info.is_ignored = nodeInfoLiteIsIgnored(lite);
|
||||||
info.is_key_manually_verified = lite->bitfield & NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK;
|
info.is_key_manually_verified = nodeInfoLiteIsKeyManuallyVerified(lite);
|
||||||
info.is_muted = lite->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK;
|
info.is_muted = nodeInfoLiteIsMuted(lite);
|
||||||
|
|
||||||
if (lite->has_hops_away) {
|
if (lite->has_hops_away) {
|
||||||
info.has_hops_away = true;
|
info.has_hops_away = true;
|
||||||
info.hops_away = lite->hops_away;
|
info.hops_away = lite->hops_away;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lite->has_position) {
|
if (position) {
|
||||||
info.has_position = true;
|
info.has_position = true;
|
||||||
if (lite->position.latitude_i != 0)
|
if (position->latitude_i != 0)
|
||||||
info.position.has_latitude_i = true;
|
info.position.has_latitude_i = true;
|
||||||
info.position.latitude_i = lite->position.latitude_i;
|
info.position.latitude_i = position->latitude_i;
|
||||||
if (lite->position.longitude_i != 0)
|
if (position->longitude_i != 0)
|
||||||
info.position.has_longitude_i = true;
|
info.position.has_longitude_i = true;
|
||||||
info.position.longitude_i = lite->position.longitude_i;
|
info.position.longitude_i = position->longitude_i;
|
||||||
if (lite->position.altitude != 0)
|
if (position->altitude != 0)
|
||||||
info.position.has_altitude = true;
|
info.position.has_altitude = true;
|
||||||
info.position.altitude = lite->position.altitude;
|
info.position.altitude = position->altitude;
|
||||||
info.position.location_source = lite->position.location_source;
|
info.position.location_source = position->location_source;
|
||||||
info.position.time = lite->position.time;
|
info.position.time = position->time;
|
||||||
}
|
}
|
||||||
if (lite->has_user) {
|
if (nodeInfoLiteHasUser(lite)) {
|
||||||
info.has_user = true;
|
info.has_user = true;
|
||||||
info.user = ConvertToUser(lite->num, lite->user);
|
info.user = ConvertToUser(lite);
|
||||||
}
|
}
|
||||||
if (lite->has_device_metrics) {
|
if (deviceMetrics) {
|
||||||
info.has_device_metrics = true;
|
info.has_device_metrics = true;
|
||||||
info.device_metrics = lite->device_metrics;
|
info.device_metrics = *deviceMetrics;
|
||||||
}
|
}
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
meshtastic_NodeInfo TypeConversions::ConvertToNodeInfo(const meshtastic_NodeInfoLite *lite)
|
||||||
|
{
|
||||||
|
meshtastic_PositionLite posScratch;
|
||||||
|
meshtastic_DeviceMetrics dmScratch;
|
||||||
|
const meshtastic_PositionLite *pos = (nodeDB && nodeDB->copyNodePosition(lite->num, posScratch)) ? &posScratch : nullptr;
|
||||||
|
const meshtastic_DeviceMetrics *dm = (nodeDB && nodeDB->copyNodeTelemetry(lite->num, dmScratch)) ? &dmScratch : nullptr;
|
||||||
|
return ConvertToNodeInfo(lite, pos, dm);
|
||||||
|
}
|
||||||
|
|
||||||
|
meshtastic_NodeInfo TypeConversions::ConvertToNodeInfoThin(const meshtastic_NodeInfoLite *lite)
|
||||||
|
{
|
||||||
|
return ConvertToNodeInfo(lite, nullptr, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
meshtastic_PositionLite TypeConversions::ConvertToPositionLite(meshtastic_Position position)
|
meshtastic_PositionLite TypeConversions::ConvertToPositionLite(meshtastic_Position position)
|
||||||
{
|
{
|
||||||
meshtastic_PositionLite lite = meshtastic_PositionLite_init_default;
|
meshtastic_PositionLite lite = meshtastic_PositionLite_init_default;
|
||||||
@@ -77,46 +93,50 @@ meshtastic_Position TypeConversions::ConvertToPosition(meshtastic_PositionLite l
|
|||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
meshtastic_UserLite TypeConversions::ConvertToUserLite(meshtastic_User user)
|
void TypeConversions::CopyUserToNodeInfoLite(meshtastic_NodeInfoLite *lite, const meshtastic_User &user)
|
||||||
{
|
{
|
||||||
meshtastic_UserLite lite = meshtastic_UserLite_init_default;
|
if (!lite)
|
||||||
|
return;
|
||||||
|
|
||||||
strncpy(lite.long_name, user.long_name, sizeof(lite.long_name));
|
strncpy(lite->long_name, user.long_name, sizeof(lite->long_name));
|
||||||
lite.long_name[sizeof(lite.long_name) - 1] = '\0';
|
lite->long_name[sizeof(lite->long_name) - 1] = '\0';
|
||||||
sanitizeUtf8(lite.long_name, sizeof(lite.long_name));
|
sanitizeUtf8(lite->long_name, sizeof(lite->long_name));
|
||||||
strncpy(lite.short_name, user.short_name, sizeof(lite.short_name));
|
strncpy(lite->short_name, user.short_name, sizeof(lite->short_name));
|
||||||
lite.short_name[sizeof(lite.short_name) - 1] = '\0';
|
lite->short_name[sizeof(lite->short_name) - 1] = '\0';
|
||||||
sanitizeUtf8(lite.short_name, sizeof(lite.short_name));
|
sanitizeUtf8(lite->short_name, sizeof(lite->short_name));
|
||||||
lite.hw_model = user.hw_model;
|
lite->hw_model = user.hw_model;
|
||||||
lite.role = user.role;
|
lite->role = user.role;
|
||||||
lite.is_licensed = user.is_licensed;
|
memcpy(lite->public_key.bytes, user.public_key.bytes, sizeof(lite->public_key.bytes));
|
||||||
memcpy(lite.macaddr, user.macaddr, sizeof(lite.macaddr));
|
lite->public_key.size = user.public_key.size;
|
||||||
memcpy(lite.public_key.bytes, user.public_key.bytes, sizeof(lite.public_key.bytes));
|
|
||||||
lite.public_key.size = user.public_key.size;
|
nodeInfoLiteSetBit(lite, NODEINFO_BITFIELD_IS_LICENSED_MASK, user.is_licensed);
|
||||||
lite.has_is_unmessagable = user.has_is_unmessagable;
|
nodeInfoLiteSetBit(lite, NODEINFO_BITFIELD_HAS_IS_UNMESSAGABLE_MASK, user.has_is_unmessagable);
|
||||||
lite.is_unmessagable = user.is_unmessagable;
|
nodeInfoLiteSetBit(lite, NODEINFO_BITFIELD_IS_UNMESSAGABLE_MASK, user.has_is_unmessagable && user.is_unmessagable);
|
||||||
return lite;
|
nodeInfoLiteSetBit(lite, NODEINFO_BITFIELD_HAS_USER_MASK, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
meshtastic_User TypeConversions::ConvertToUser(uint32_t nodeNum, meshtastic_UserLite lite)
|
meshtastic_User TypeConversions::ConvertToUser(const meshtastic_NodeInfoLite *lite)
|
||||||
{
|
{
|
||||||
meshtastic_User user = meshtastic_User_init_default;
|
meshtastic_User user = meshtastic_User_init_default;
|
||||||
|
if (!lite)
|
||||||
|
return user;
|
||||||
|
|
||||||
snprintf(user.id, sizeof(user.id), "!%08x", nodeNum);
|
snprintf(user.id, sizeof(user.id), "!%08x", lite->num);
|
||||||
strncpy(user.long_name, lite.long_name, sizeof(user.long_name));
|
strncpy(user.long_name, lite->long_name, sizeof(user.long_name));
|
||||||
user.long_name[sizeof(user.long_name) - 1] = '\0';
|
user.long_name[sizeof(user.long_name) - 1] = '\0';
|
||||||
sanitizeUtf8(user.long_name, sizeof(user.long_name));
|
sanitizeUtf8(user.long_name, sizeof(user.long_name));
|
||||||
strncpy(user.short_name, lite.short_name, sizeof(user.short_name));
|
strncpy(user.short_name, lite->short_name, sizeof(user.short_name));
|
||||||
user.short_name[sizeof(user.short_name) - 1] = '\0';
|
user.short_name[sizeof(user.short_name) - 1] = '\0';
|
||||||
sanitizeUtf8(user.short_name, sizeof(user.short_name));
|
sanitizeUtf8(user.short_name, sizeof(user.short_name));
|
||||||
user.hw_model = lite.hw_model;
|
user.hw_model = lite->hw_model;
|
||||||
user.role = lite.role;
|
user.role = lite->role;
|
||||||
user.is_licensed = lite.is_licensed;
|
user.is_licensed = nodeInfoLiteIsLicensed(lite);
|
||||||
memcpy(user.macaddr, lite.macaddr, sizeof(user.macaddr));
|
memcpy(user.public_key.bytes, lite->public_key.bytes, sizeof(user.public_key.bytes));
|
||||||
memcpy(user.public_key.bytes, lite.public_key.bytes, sizeof(user.public_key.bytes));
|
user.public_key.size = lite->public_key.size;
|
||||||
user.public_key.size = lite.public_key.size;
|
user.has_is_unmessagable = nodeInfoLiteHasIsUnmessagable(lite);
|
||||||
user.has_is_unmessagable = lite.has_is_unmessagable;
|
user.is_unmessagable = nodeInfoLiteIsUnmessagable(lite);
|
||||||
user.is_unmessagable = lite.is_unmessagable;
|
// macaddr is gone from the slim header; zero-fill for old clients that read it.
|
||||||
|
memset(user.macaddr, 0, sizeof(user.macaddr));
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
@@ -7,9 +7,16 @@
|
|||||||
class TypeConversions
|
class TypeConversions
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
// Either pointer may be null; the corresponding has_* fields stay unset.
|
||||||
|
static meshtastic_NodeInfo ConvertToNodeInfo(const meshtastic_NodeInfoLite *lite, const meshtastic_PositionLite *position,
|
||||||
|
const meshtastic_DeviceMetrics *deviceMetrics);
|
||||||
static meshtastic_NodeInfo ConvertToNodeInfo(const meshtastic_NodeInfoLite *lite);
|
static meshtastic_NodeInfo ConvertToNodeInfo(const meshtastic_NodeInfoLite *lite);
|
||||||
|
// Identity + link-state only; satellite payloads are replayed afterward.
|
||||||
|
static meshtastic_NodeInfo ConvertToNodeInfoThin(const meshtastic_NodeInfoLite *lite);
|
||||||
|
|
||||||
static meshtastic_PositionLite ConvertToPositionLite(meshtastic_Position position);
|
static meshtastic_PositionLite ConvertToPositionLite(meshtastic_Position position);
|
||||||
static meshtastic_Position ConvertToPosition(meshtastic_PositionLite lite);
|
static meshtastic_Position ConvertToPosition(meshtastic_PositionLite lite);
|
||||||
static meshtastic_UserLite ConvertToUserLite(meshtastic_User user);
|
|
||||||
static meshtastic_User ConvertToUser(uint32_t nodeNum, meshtastic_UserLite lite);
|
static void CopyUserToNodeInfoLite(meshtastic_NodeInfoLite *lite, const meshtastic_User &user);
|
||||||
|
static meshtastic_User ConvertToUser(const meshtastic_NodeInfoLite *lite);
|
||||||
};
|
};
|
||||||
@@ -18,6 +18,18 @@ PB_BIND(meshtastic_NodeInfoLite, meshtastic_NodeInfoLite, AUTO)
|
|||||||
PB_BIND(meshtastic_DeviceState, meshtastic_DeviceState, 2)
|
PB_BIND(meshtastic_DeviceState, meshtastic_DeviceState, 2)
|
||||||
|
|
||||||
|
|
||||||
|
PB_BIND(meshtastic_NodePositionEntry, meshtastic_NodePositionEntry, AUTO)
|
||||||
|
|
||||||
|
|
||||||
|
PB_BIND(meshtastic_NodeTelemetryEntry, meshtastic_NodeTelemetryEntry, AUTO)
|
||||||
|
|
||||||
|
|
||||||
|
PB_BIND(meshtastic_NodeEnvironmentEntry, meshtastic_NodeEnvironmentEntry, AUTO)
|
||||||
|
|
||||||
|
|
||||||
|
PB_BIND(meshtastic_NodeStatusEntry, meshtastic_NodeStatusEntry, AUTO)
|
||||||
|
|
||||||
|
|
||||||
PB_BIND(meshtastic_NodeDatabase, meshtastic_NodeDatabase, AUTO)
|
PB_BIND(meshtastic_NodeDatabase, meshtastic_NodeDatabase, AUTO)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -63,43 +63,35 @@ typedef struct _meshtastic_UserLite {
|
|||||||
bool is_unmessagable;
|
bool is_unmessagable;
|
||||||
} meshtastic_UserLite;
|
} meshtastic_UserLite;
|
||||||
|
|
||||||
|
typedef PB_BYTES_ARRAY_T(32) meshtastic_NodeInfoLite_public_key_t;
|
||||||
typedef struct _meshtastic_NodeInfoLite {
|
typedef struct _meshtastic_NodeInfoLite {
|
||||||
/* The node number */
|
/* The node number */
|
||||||
uint32_t num;
|
uint32_t num;
|
||||||
/* The user info for this node */
|
|
||||||
bool has_user;
|
|
||||||
meshtastic_UserLite user;
|
|
||||||
/* This position data. Note: before 1.2.14 we would also store the last time we've heard from this node in position.time, that is no longer true.
|
|
||||||
Position.time now indicates the last time we received a POSITION from that node. */
|
|
||||||
bool has_position;
|
|
||||||
meshtastic_PositionLite position;
|
|
||||||
/* Returns the Signal-to-noise ratio (SNR) of the last received message,
|
/* Returns the Signal-to-noise ratio (SNR) of the last received message,
|
||||||
as measured by the receiver. Return SNR of the last received message in dB */
|
as measured by the receiver. Return SNR of the last received message in dB */
|
||||||
float snr;
|
float snr;
|
||||||
/* Set to indicate the last time we received a packet from this node */
|
/* Set to indicate the last time we received a packet from this node */
|
||||||
uint32_t last_heard;
|
uint32_t last_heard;
|
||||||
/* The latest device metrics for the node. */
|
|
||||||
bool has_device_metrics;
|
|
||||||
meshtastic_DeviceMetrics device_metrics;
|
|
||||||
/* local channel index we heard that node on. Only populated if its not the default channel. */
|
/* local channel index we heard that node on. Only populated if its not the default channel. */
|
||||||
uint8_t channel;
|
uint8_t channel;
|
||||||
/* True if we witnessed the node over MQTT instead of LoRA transport */
|
|
||||||
bool via_mqtt;
|
|
||||||
/* Number of hops away from us this node is (0 if direct neighbor) */
|
/* Number of hops away from us this node is (0 if direct neighbor) */
|
||||||
bool has_hops_away;
|
bool has_hops_away;
|
||||||
uint8_t hops_away;
|
uint8_t hops_away;
|
||||||
/* True if node is in our favorites list
|
|
||||||
Persists between NodeDB internal clean ups */
|
|
||||||
bool is_favorite;
|
|
||||||
/* True if node is in our ignored list
|
|
||||||
Persists between NodeDB internal clean ups */
|
|
||||||
bool is_ignored;
|
|
||||||
/* Last byte of the node number of the node that should be used as the next hop to reach this node. */
|
/* Last byte of the node number of the node that should be used as the next hop to reach this node. */
|
||||||
uint8_t next_hop;
|
uint8_t next_hop;
|
||||||
/* Bitfield for storing booleans.
|
/* Bitfield for storing booleans. See NODEINFO_BITFIELD_* in src/mesh/NodeDB.h. */
|
||||||
LSB 0 is_key_manually_verified
|
|
||||||
LSB 1 is_muted */
|
|
||||||
uint32_t bitfield;
|
uint32_t bitfield;
|
||||||
|
/* A full name for this user, i.e. "Kevin Hester". */
|
||||||
|
char long_name[25];
|
||||||
|
/* A VERY short name, ideally two characters or an emoji.
|
||||||
|
Suitable for a tiny OLED screen. */
|
||||||
|
char short_name[5];
|
||||||
|
/* Hardware model the user's device is running. */
|
||||||
|
meshtastic_HardwareModel hw_model;
|
||||||
|
/* The user's role in the mesh. */
|
||||||
|
meshtastic_Config_DeviceConfig_Role role;
|
||||||
|
/* The public key of the user's device, for PKI-based encrypted DMs. */
|
||||||
|
meshtastic_NodeInfoLite_public_key_t public_key;
|
||||||
} meshtastic_NodeInfoLite;
|
} meshtastic_NodeInfoLite;
|
||||||
|
|
||||||
/* This message is never sent over the wire, but it is used for serializing DB
|
/* This message is never sent over the wire, but it is used for serializing DB
|
||||||
@@ -143,6 +135,30 @@ typedef struct _meshtastic_DeviceState {
|
|||||||
meshtastic_NodeRemoteHardwarePin node_remote_hardware_pins[12];
|
meshtastic_NodeRemoteHardwarePin node_remote_hardware_pins[12];
|
||||||
} meshtastic_DeviceState;
|
} meshtastic_DeviceState;
|
||||||
|
|
||||||
|
typedef struct _meshtastic_NodePositionEntry {
|
||||||
|
uint32_t num;
|
||||||
|
bool has_position;
|
||||||
|
meshtastic_PositionLite position;
|
||||||
|
} meshtastic_NodePositionEntry;
|
||||||
|
|
||||||
|
typedef struct _meshtastic_NodeTelemetryEntry {
|
||||||
|
uint32_t num;
|
||||||
|
bool has_device_metrics;
|
||||||
|
meshtastic_DeviceMetrics device_metrics;
|
||||||
|
} meshtastic_NodeTelemetryEntry;
|
||||||
|
|
||||||
|
typedef struct _meshtastic_NodeEnvironmentEntry {
|
||||||
|
uint32_t num;
|
||||||
|
bool has_environment_metrics;
|
||||||
|
meshtastic_EnvironmentMetrics environment_metrics;
|
||||||
|
} meshtastic_NodeEnvironmentEntry;
|
||||||
|
|
||||||
|
typedef struct _meshtastic_NodeStatusEntry {
|
||||||
|
uint32_t num;
|
||||||
|
bool has_status;
|
||||||
|
meshtastic_StatusMessage status;
|
||||||
|
} meshtastic_NodeStatusEntry;
|
||||||
|
|
||||||
typedef struct _meshtastic_NodeDatabase {
|
typedef struct _meshtastic_NodeDatabase {
|
||||||
/* A version integer used to invalidate old save files when we make
|
/* A version integer used to invalidate old save files when we make
|
||||||
incompatible changes This integer is set at build time and is private to
|
incompatible changes This integer is set at build time and is private to
|
||||||
@@ -150,6 +166,12 @@ typedef struct _meshtastic_NodeDatabase {
|
|||||||
uint32_t version;
|
uint32_t version;
|
||||||
/* New lite version of NodeDB to decrease memory footprint */
|
/* New lite version of NodeDB to decrease memory footprint */
|
||||||
std::vector<meshtastic_NodeInfoLite> nodes;
|
std::vector<meshtastic_NodeInfoLite> nodes;
|
||||||
|
/* Per-NodeNum satellite arrays. Constrained platforms (e.g. STM32WL) omit
|
||||||
|
these via MESHTASTIC_EXCLUDE_*DB build flags. */
|
||||||
|
std::vector<meshtastic_NodePositionEntry> positions;
|
||||||
|
std::vector<meshtastic_NodeTelemetryEntry> telemetry;
|
||||||
|
std::vector<meshtastic_NodeStatusEntry> status;
|
||||||
|
std::vector<meshtastic_NodeEnvironmentEntry> environment;
|
||||||
} meshtastic_NodeDatabase;
|
} meshtastic_NodeDatabase;
|
||||||
|
|
||||||
/* The on-disk saved channels */
|
/* The on-disk saved channels */
|
||||||
@@ -191,16 +213,24 @@ extern "C" {
|
|||||||
/* Initializer values for message structs */
|
/* Initializer values for message structs */
|
||||||
#define meshtastic_PositionLite_init_default {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN}
|
#define meshtastic_PositionLite_init_default {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN}
|
||||||
#define meshtastic_UserLite_init_default {{0}, "", "", _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}, false, 0}
|
#define meshtastic_UserLite_init_default {{0}, "", "", _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}, false, 0}
|
||||||
#define meshtastic_NodeInfoLite_init_default {0, false, meshtastic_UserLite_init_default, false, meshtastic_PositionLite_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, false, 0, 0, 0, 0, 0}
|
#define meshtastic_NodeInfoLite_init_default {0, 0, 0, 0, false, 0, 0, 0, "", "", _meshtastic_HardwareModel_MIN, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}}
|
||||||
#define meshtastic_DeviceState_init_default {false, meshtastic_MyNodeInfo_init_default, false, meshtastic_User_init_default, 0, {meshtastic_MeshPacket_init_default}, false, meshtastic_MeshPacket_init_default, 0, 0, 0, false, meshtastic_MeshPacket_init_default, 0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}}
|
#define meshtastic_DeviceState_init_default {false, meshtastic_MyNodeInfo_init_default, false, meshtastic_User_init_default, 0, {meshtastic_MeshPacket_init_default}, false, meshtastic_MeshPacket_init_default, 0, 0, 0, false, meshtastic_MeshPacket_init_default, 0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}}
|
||||||
#define meshtastic_NodeDatabase_init_default {0, {0}}
|
#define meshtastic_NodePositionEntry_init_default {0, false, meshtastic_PositionLite_init_default}
|
||||||
|
#define meshtastic_NodeTelemetryEntry_init_default {0, false, meshtastic_DeviceMetrics_init_default}
|
||||||
|
#define meshtastic_NodeEnvironmentEntry_init_default {0, false, meshtastic_EnvironmentMetrics_init_default}
|
||||||
|
#define meshtastic_NodeStatusEntry_init_default {0, false, meshtastic_StatusMessage_init_default}
|
||||||
|
#define meshtastic_NodeDatabase_init_default {0, {0}, {0}, {0}, {0}, {0}}
|
||||||
#define meshtastic_ChannelFile_init_default {0, {meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default}, 0}
|
#define meshtastic_ChannelFile_init_default {0, {meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default, meshtastic_Channel_init_default}, 0}
|
||||||
#define meshtastic_BackupPreferences_init_default {0, 0, false, meshtastic_LocalConfig_init_default, false, meshtastic_LocalModuleConfig_init_default, false, meshtastic_ChannelFile_init_default, false, meshtastic_User_init_default}
|
#define meshtastic_BackupPreferences_init_default {0, 0, false, meshtastic_LocalConfig_init_default, false, meshtastic_LocalModuleConfig_init_default, false, meshtastic_ChannelFile_init_default, false, meshtastic_User_init_default}
|
||||||
#define meshtastic_PositionLite_init_zero {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN}
|
#define meshtastic_PositionLite_init_zero {0, 0, 0, 0, _meshtastic_Position_LocSource_MIN}
|
||||||
#define meshtastic_UserLite_init_zero {{0}, "", "", _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}, false, 0}
|
#define meshtastic_UserLite_init_zero {{0}, "", "", _meshtastic_HardwareModel_MIN, 0, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}, false, 0}
|
||||||
#define meshtastic_NodeInfoLite_init_zero {0, false, meshtastic_UserLite_init_zero, false, meshtastic_PositionLite_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, false, 0, 0, 0, 0, 0}
|
#define meshtastic_NodeInfoLite_init_zero {0, 0, 0, 0, false, 0, 0, 0, "", "", _meshtastic_HardwareModel_MIN, _meshtastic_Config_DeviceConfig_Role_MIN, {0, {0}}}
|
||||||
#define meshtastic_DeviceState_init_zero {false, meshtastic_MyNodeInfo_init_zero, false, meshtastic_User_init_zero, 0, {meshtastic_MeshPacket_init_zero}, false, meshtastic_MeshPacket_init_zero, 0, 0, 0, false, meshtastic_MeshPacket_init_zero, 0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}}
|
#define meshtastic_DeviceState_init_zero {false, meshtastic_MyNodeInfo_init_zero, false, meshtastic_User_init_zero, 0, {meshtastic_MeshPacket_init_zero}, false, meshtastic_MeshPacket_init_zero, 0, 0, 0, false, meshtastic_MeshPacket_init_zero, 0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}}
|
||||||
#define meshtastic_NodeDatabase_init_zero {0, {0}}
|
#define meshtastic_NodePositionEntry_init_zero {0, false, meshtastic_PositionLite_init_zero}
|
||||||
|
#define meshtastic_NodeTelemetryEntry_init_zero {0, false, meshtastic_DeviceMetrics_init_zero}
|
||||||
|
#define meshtastic_NodeEnvironmentEntry_init_zero {0, false, meshtastic_EnvironmentMetrics_init_zero}
|
||||||
|
#define meshtastic_NodeStatusEntry_init_zero {0, false, meshtastic_StatusMessage_init_zero}
|
||||||
|
#define meshtastic_NodeDatabase_init_zero {0, {0}, {0}, {0}, {0}, {0}}
|
||||||
#define meshtastic_ChannelFile_init_zero {0, {meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero}, 0}
|
#define meshtastic_ChannelFile_init_zero {0, {meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero, meshtastic_Channel_init_zero}, 0}
|
||||||
#define meshtastic_BackupPreferences_init_zero {0, 0, false, meshtastic_LocalConfig_init_zero, false, meshtastic_LocalModuleConfig_init_zero, false, meshtastic_ChannelFile_init_zero, false, meshtastic_User_init_zero}
|
#define meshtastic_BackupPreferences_init_zero {0, 0, false, meshtastic_LocalConfig_init_zero, false, meshtastic_LocalModuleConfig_init_zero, false, meshtastic_ChannelFile_init_zero, false, meshtastic_User_init_zero}
|
||||||
|
|
||||||
@@ -219,18 +249,17 @@ extern "C" {
|
|||||||
#define meshtastic_UserLite_public_key_tag 7
|
#define meshtastic_UserLite_public_key_tag 7
|
||||||
#define meshtastic_UserLite_is_unmessagable_tag 9
|
#define meshtastic_UserLite_is_unmessagable_tag 9
|
||||||
#define meshtastic_NodeInfoLite_num_tag 1
|
#define meshtastic_NodeInfoLite_num_tag 1
|
||||||
#define meshtastic_NodeInfoLite_user_tag 2
|
|
||||||
#define meshtastic_NodeInfoLite_position_tag 3
|
|
||||||
#define meshtastic_NodeInfoLite_snr_tag 4
|
#define meshtastic_NodeInfoLite_snr_tag 4
|
||||||
#define meshtastic_NodeInfoLite_last_heard_tag 5
|
#define meshtastic_NodeInfoLite_last_heard_tag 5
|
||||||
#define meshtastic_NodeInfoLite_device_metrics_tag 6
|
|
||||||
#define meshtastic_NodeInfoLite_channel_tag 7
|
#define meshtastic_NodeInfoLite_channel_tag 7
|
||||||
#define meshtastic_NodeInfoLite_via_mqtt_tag 8
|
|
||||||
#define meshtastic_NodeInfoLite_hops_away_tag 9
|
#define meshtastic_NodeInfoLite_hops_away_tag 9
|
||||||
#define meshtastic_NodeInfoLite_is_favorite_tag 10
|
|
||||||
#define meshtastic_NodeInfoLite_is_ignored_tag 11
|
|
||||||
#define meshtastic_NodeInfoLite_next_hop_tag 12
|
#define meshtastic_NodeInfoLite_next_hop_tag 12
|
||||||
#define meshtastic_NodeInfoLite_bitfield_tag 13
|
#define meshtastic_NodeInfoLite_bitfield_tag 13
|
||||||
|
#define meshtastic_NodeInfoLite_long_name_tag 14
|
||||||
|
#define meshtastic_NodeInfoLite_short_name_tag 15
|
||||||
|
#define meshtastic_NodeInfoLite_hw_model_tag 16
|
||||||
|
#define meshtastic_NodeInfoLite_role_tag 17
|
||||||
|
#define meshtastic_NodeInfoLite_public_key_tag 18
|
||||||
#define meshtastic_DeviceState_my_node_tag 2
|
#define meshtastic_DeviceState_my_node_tag 2
|
||||||
#define meshtastic_DeviceState_owner_tag 3
|
#define meshtastic_DeviceState_owner_tag 3
|
||||||
#define meshtastic_DeviceState_receive_queue_tag 5
|
#define meshtastic_DeviceState_receive_queue_tag 5
|
||||||
@@ -240,8 +269,20 @@ extern "C" {
|
|||||||
#define meshtastic_DeviceState_did_gps_reset_tag 11
|
#define meshtastic_DeviceState_did_gps_reset_tag 11
|
||||||
#define meshtastic_DeviceState_rx_waypoint_tag 12
|
#define meshtastic_DeviceState_rx_waypoint_tag 12
|
||||||
#define meshtastic_DeviceState_node_remote_hardware_pins_tag 13
|
#define meshtastic_DeviceState_node_remote_hardware_pins_tag 13
|
||||||
|
#define meshtastic_NodePositionEntry_num_tag 1
|
||||||
|
#define meshtastic_NodePositionEntry_position_tag 2
|
||||||
|
#define meshtastic_NodeTelemetryEntry_num_tag 1
|
||||||
|
#define meshtastic_NodeTelemetryEntry_device_metrics_tag 2
|
||||||
|
#define meshtastic_NodeEnvironmentEntry_num_tag 1
|
||||||
|
#define meshtastic_NodeEnvironmentEntry_environment_metrics_tag 2
|
||||||
|
#define meshtastic_NodeStatusEntry_num_tag 1
|
||||||
|
#define meshtastic_NodeStatusEntry_status_tag 2
|
||||||
#define meshtastic_NodeDatabase_version_tag 1
|
#define meshtastic_NodeDatabase_version_tag 1
|
||||||
#define meshtastic_NodeDatabase_nodes_tag 2
|
#define meshtastic_NodeDatabase_nodes_tag 2
|
||||||
|
#define meshtastic_NodeDatabase_positions_tag 3
|
||||||
|
#define meshtastic_NodeDatabase_telemetry_tag 4
|
||||||
|
#define meshtastic_NodeDatabase_status_tag 5
|
||||||
|
#define meshtastic_NodeDatabase_environment_tag 6
|
||||||
#define meshtastic_ChannelFile_channels_tag 1
|
#define meshtastic_ChannelFile_channels_tag 1
|
||||||
#define meshtastic_ChannelFile_version_tag 2
|
#define meshtastic_ChannelFile_version_tag 2
|
||||||
#define meshtastic_BackupPreferences_version_tag 1
|
#define meshtastic_BackupPreferences_version_tag 1
|
||||||
@@ -275,23 +316,19 @@ X(a, STATIC, OPTIONAL, BOOL, is_unmessagable, 9)
|
|||||||
|
|
||||||
#define meshtastic_NodeInfoLite_FIELDLIST(X, a) \
|
#define meshtastic_NodeInfoLite_FIELDLIST(X, a) \
|
||||||
X(a, STATIC, SINGULAR, UINT32, num, 1) \
|
X(a, STATIC, SINGULAR, UINT32, num, 1) \
|
||||||
X(a, STATIC, OPTIONAL, MESSAGE, user, 2) \
|
|
||||||
X(a, STATIC, OPTIONAL, MESSAGE, position, 3) \
|
|
||||||
X(a, STATIC, SINGULAR, FLOAT, snr, 4) \
|
X(a, STATIC, SINGULAR, FLOAT, snr, 4) \
|
||||||
X(a, STATIC, SINGULAR, FIXED32, last_heard, 5) \
|
X(a, STATIC, SINGULAR, FIXED32, last_heard, 5) \
|
||||||
X(a, STATIC, OPTIONAL, MESSAGE, device_metrics, 6) \
|
|
||||||
X(a, STATIC, SINGULAR, UINT32, channel, 7) \
|
X(a, STATIC, SINGULAR, UINT32, channel, 7) \
|
||||||
X(a, STATIC, SINGULAR, BOOL, via_mqtt, 8) \
|
|
||||||
X(a, STATIC, OPTIONAL, UINT32, hops_away, 9) \
|
X(a, STATIC, OPTIONAL, UINT32, hops_away, 9) \
|
||||||
X(a, STATIC, SINGULAR, BOOL, is_favorite, 10) \
|
|
||||||
X(a, STATIC, SINGULAR, BOOL, is_ignored, 11) \
|
|
||||||
X(a, STATIC, SINGULAR, UINT32, next_hop, 12) \
|
X(a, STATIC, SINGULAR, UINT32, next_hop, 12) \
|
||||||
X(a, STATIC, SINGULAR, UINT32, bitfield, 13)
|
X(a, STATIC, SINGULAR, UINT32, bitfield, 13) \
|
||||||
|
X(a, STATIC, SINGULAR, STRING, long_name, 14) \
|
||||||
|
X(a, STATIC, SINGULAR, STRING, short_name, 15) \
|
||||||
|
X(a, STATIC, SINGULAR, UENUM, hw_model, 16) \
|
||||||
|
X(a, STATIC, SINGULAR, UENUM, role, 17) \
|
||||||
|
X(a, STATIC, SINGULAR, BYTES, public_key, 18)
|
||||||
#define meshtastic_NodeInfoLite_CALLBACK NULL
|
#define meshtastic_NodeInfoLite_CALLBACK NULL
|
||||||
#define meshtastic_NodeInfoLite_DEFAULT NULL
|
#define meshtastic_NodeInfoLite_DEFAULT NULL
|
||||||
#define meshtastic_NodeInfoLite_user_MSGTYPE meshtastic_UserLite
|
|
||||||
#define meshtastic_NodeInfoLite_position_MSGTYPE meshtastic_PositionLite
|
|
||||||
#define meshtastic_NodeInfoLite_device_metrics_MSGTYPE meshtastic_DeviceMetrics
|
|
||||||
|
|
||||||
#define meshtastic_DeviceState_FIELDLIST(X, a) \
|
#define meshtastic_DeviceState_FIELDLIST(X, a) \
|
||||||
X(a, STATIC, OPTIONAL, MESSAGE, my_node, 2) \
|
X(a, STATIC, OPTIONAL, MESSAGE, my_node, 2) \
|
||||||
@@ -312,13 +349,49 @@ X(a, STATIC, REPEATED, MESSAGE, node_remote_hardware_pins, 13)
|
|||||||
#define meshtastic_DeviceState_rx_waypoint_MSGTYPE meshtastic_MeshPacket
|
#define meshtastic_DeviceState_rx_waypoint_MSGTYPE meshtastic_MeshPacket
|
||||||
#define meshtastic_DeviceState_node_remote_hardware_pins_MSGTYPE meshtastic_NodeRemoteHardwarePin
|
#define meshtastic_DeviceState_node_remote_hardware_pins_MSGTYPE meshtastic_NodeRemoteHardwarePin
|
||||||
|
|
||||||
|
#define meshtastic_NodePositionEntry_FIELDLIST(X, a) \
|
||||||
|
X(a, STATIC, SINGULAR, UINT32, num, 1) \
|
||||||
|
X(a, STATIC, OPTIONAL, MESSAGE, position, 2)
|
||||||
|
#define meshtastic_NodePositionEntry_CALLBACK NULL
|
||||||
|
#define meshtastic_NodePositionEntry_DEFAULT NULL
|
||||||
|
#define meshtastic_NodePositionEntry_position_MSGTYPE meshtastic_PositionLite
|
||||||
|
|
||||||
|
#define meshtastic_NodeTelemetryEntry_FIELDLIST(X, a) \
|
||||||
|
X(a, STATIC, SINGULAR, UINT32, num, 1) \
|
||||||
|
X(a, STATIC, OPTIONAL, MESSAGE, device_metrics, 2)
|
||||||
|
#define meshtastic_NodeTelemetryEntry_CALLBACK NULL
|
||||||
|
#define meshtastic_NodeTelemetryEntry_DEFAULT NULL
|
||||||
|
#define meshtastic_NodeTelemetryEntry_device_metrics_MSGTYPE meshtastic_DeviceMetrics
|
||||||
|
|
||||||
|
#define meshtastic_NodeEnvironmentEntry_FIELDLIST(X, a) \
|
||||||
|
X(a, STATIC, SINGULAR, UINT32, num, 1) \
|
||||||
|
X(a, STATIC, OPTIONAL, MESSAGE, environment_metrics, 2)
|
||||||
|
#define meshtastic_NodeEnvironmentEntry_CALLBACK NULL
|
||||||
|
#define meshtastic_NodeEnvironmentEntry_DEFAULT NULL
|
||||||
|
#define meshtastic_NodeEnvironmentEntry_environment_metrics_MSGTYPE meshtastic_EnvironmentMetrics
|
||||||
|
|
||||||
|
#define meshtastic_NodeStatusEntry_FIELDLIST(X, a) \
|
||||||
|
X(a, STATIC, SINGULAR, UINT32, num, 1) \
|
||||||
|
X(a, STATIC, OPTIONAL, MESSAGE, status, 2)
|
||||||
|
#define meshtastic_NodeStatusEntry_CALLBACK NULL
|
||||||
|
#define meshtastic_NodeStatusEntry_DEFAULT NULL
|
||||||
|
#define meshtastic_NodeStatusEntry_status_MSGTYPE meshtastic_StatusMessage
|
||||||
|
|
||||||
#define meshtastic_NodeDatabase_FIELDLIST(X, a) \
|
#define meshtastic_NodeDatabase_FIELDLIST(X, a) \
|
||||||
X(a, STATIC, SINGULAR, UINT32, version, 1) \
|
X(a, STATIC, SINGULAR, UINT32, version, 1) \
|
||||||
X(a, CALLBACK, REPEATED, MESSAGE, nodes, 2)
|
X(a, CALLBACK, REPEATED, MESSAGE, nodes, 2) \
|
||||||
|
X(a, CALLBACK, REPEATED, MESSAGE, positions, 3) \
|
||||||
|
X(a, CALLBACK, REPEATED, MESSAGE, telemetry, 4) \
|
||||||
|
X(a, CALLBACK, REPEATED, MESSAGE, status, 5) \
|
||||||
|
X(a, CALLBACK, REPEATED, MESSAGE, environment, 6)
|
||||||
extern bool meshtastic_NodeDatabase_callback(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_t *field);
|
extern bool meshtastic_NodeDatabase_callback(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_t *field);
|
||||||
#define meshtastic_NodeDatabase_CALLBACK meshtastic_NodeDatabase_callback
|
#define meshtastic_NodeDatabase_CALLBACK meshtastic_NodeDatabase_callback
|
||||||
#define meshtastic_NodeDatabase_DEFAULT NULL
|
#define meshtastic_NodeDatabase_DEFAULT NULL
|
||||||
#define meshtastic_NodeDatabase_nodes_MSGTYPE meshtastic_NodeInfoLite
|
#define meshtastic_NodeDatabase_nodes_MSGTYPE meshtastic_NodeInfoLite
|
||||||
|
#define meshtastic_NodeDatabase_positions_MSGTYPE meshtastic_NodePositionEntry
|
||||||
|
#define meshtastic_NodeDatabase_telemetry_MSGTYPE meshtastic_NodeTelemetryEntry
|
||||||
|
#define meshtastic_NodeDatabase_status_MSGTYPE meshtastic_NodeStatusEntry
|
||||||
|
#define meshtastic_NodeDatabase_environment_MSGTYPE meshtastic_NodeEnvironmentEntry
|
||||||
|
|
||||||
#define meshtastic_ChannelFile_FIELDLIST(X, a) \
|
#define meshtastic_ChannelFile_FIELDLIST(X, a) \
|
||||||
X(a, STATIC, REPEATED, MESSAGE, channels, 1) \
|
X(a, STATIC, REPEATED, MESSAGE, channels, 1) \
|
||||||
@@ -345,6 +418,10 @@ extern const pb_msgdesc_t meshtastic_PositionLite_msg;
|
|||||||
extern const pb_msgdesc_t meshtastic_UserLite_msg;
|
extern const pb_msgdesc_t meshtastic_UserLite_msg;
|
||||||
extern const pb_msgdesc_t meshtastic_NodeInfoLite_msg;
|
extern const pb_msgdesc_t meshtastic_NodeInfoLite_msg;
|
||||||
extern const pb_msgdesc_t meshtastic_DeviceState_msg;
|
extern const pb_msgdesc_t meshtastic_DeviceState_msg;
|
||||||
|
extern const pb_msgdesc_t meshtastic_NodePositionEntry_msg;
|
||||||
|
extern const pb_msgdesc_t meshtastic_NodeTelemetryEntry_msg;
|
||||||
|
extern const pb_msgdesc_t meshtastic_NodeEnvironmentEntry_msg;
|
||||||
|
extern const pb_msgdesc_t meshtastic_NodeStatusEntry_msg;
|
||||||
extern const pb_msgdesc_t meshtastic_NodeDatabase_msg;
|
extern const pb_msgdesc_t meshtastic_NodeDatabase_msg;
|
||||||
extern const pb_msgdesc_t meshtastic_ChannelFile_msg;
|
extern const pb_msgdesc_t meshtastic_ChannelFile_msg;
|
||||||
extern const pb_msgdesc_t meshtastic_BackupPreferences_msg;
|
extern const pb_msgdesc_t meshtastic_BackupPreferences_msg;
|
||||||
@@ -354,6 +431,10 @@ extern const pb_msgdesc_t meshtastic_BackupPreferences_msg;
|
|||||||
#define meshtastic_UserLite_fields &meshtastic_UserLite_msg
|
#define meshtastic_UserLite_fields &meshtastic_UserLite_msg
|
||||||
#define meshtastic_NodeInfoLite_fields &meshtastic_NodeInfoLite_msg
|
#define meshtastic_NodeInfoLite_fields &meshtastic_NodeInfoLite_msg
|
||||||
#define meshtastic_DeviceState_fields &meshtastic_DeviceState_msg
|
#define meshtastic_DeviceState_fields &meshtastic_DeviceState_msg
|
||||||
|
#define meshtastic_NodePositionEntry_fields &meshtastic_NodePositionEntry_msg
|
||||||
|
#define meshtastic_NodeTelemetryEntry_fields &meshtastic_NodeTelemetryEntry_msg
|
||||||
|
#define meshtastic_NodeEnvironmentEntry_fields &meshtastic_NodeEnvironmentEntry_msg
|
||||||
|
#define meshtastic_NodeStatusEntry_fields &meshtastic_NodeStatusEntry_msg
|
||||||
#define meshtastic_NodeDatabase_fields &meshtastic_NodeDatabase_msg
|
#define meshtastic_NodeDatabase_fields &meshtastic_NodeDatabase_msg
|
||||||
#define meshtastic_ChannelFile_fields &meshtastic_ChannelFile_msg
|
#define meshtastic_ChannelFile_fields &meshtastic_ChannelFile_msg
|
||||||
#define meshtastic_BackupPreferences_fields &meshtastic_BackupPreferences_msg
|
#define meshtastic_BackupPreferences_fields &meshtastic_BackupPreferences_msg
|
||||||
@@ -364,7 +445,11 @@ extern const pb_msgdesc_t meshtastic_BackupPreferences_msg;
|
|||||||
#define meshtastic_BackupPreferences_size 2432
|
#define meshtastic_BackupPreferences_size 2432
|
||||||
#define meshtastic_ChannelFile_size 718
|
#define meshtastic_ChannelFile_size 718
|
||||||
#define meshtastic_DeviceState_size 1737
|
#define meshtastic_DeviceState_size 1737
|
||||||
#define meshtastic_NodeInfoLite_size 196
|
#define meshtastic_NodeEnvironmentEntry_size 170
|
||||||
|
#define meshtastic_NodeInfoLite_size 105
|
||||||
|
#define meshtastic_NodePositionEntry_size 36
|
||||||
|
#define meshtastic_NodeStatusEntry_size 89
|
||||||
|
#define meshtastic_NodeTelemetryEntry_size 35
|
||||||
#define meshtastic_PositionLite_size 28
|
#define meshtastic_PositionLite_size 28
|
||||||
#define meshtastic_UserLite_size 98
|
#define meshtastic_UserLite_size 98
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
/* Automatically generated nanopb constant definitions */
|
||||||
|
/* Generated by nanopb-0.4.9.1 */
|
||||||
|
|
||||||
|
#include "meshtastic/deviceonly_legacy.pb.h"
|
||||||
|
#if PB_PROTO_HEADER_VERSION != 40
|
||||||
|
#error Regenerate this file with the current version of nanopb generator.
|
||||||
|
#endif
|
||||||
|
|
||||||
|
PB_BIND(meshtastic_NodeInfoLite_Legacy, meshtastic_NodeInfoLite_Legacy, AUTO)
|
||||||
|
|
||||||
|
|
||||||
|
PB_BIND(meshtastic_NodeDatabase_Legacy, meshtastic_NodeDatabase_Legacy, AUTO)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
/* Automatically generated nanopb header */
|
||||||
|
/* Generated by nanopb-0.4.9.1 */
|
||||||
|
|
||||||
|
#ifndef PB_MESHTASTIC_MESHTASTIC_DEVICEONLY_LEGACY_PB_H_INCLUDED
|
||||||
|
#define PB_MESHTASTIC_MESHTASTIC_DEVICEONLY_LEGACY_PB_H_INCLUDED
|
||||||
|
#include <pb.h>
|
||||||
|
#include <vector>
|
||||||
|
#include "meshtastic/deviceonly.pb.h"
|
||||||
|
#include "meshtastic/telemetry.pb.h"
|
||||||
|
|
||||||
|
#if PB_PROTO_HEADER_VERSION != 40
|
||||||
|
#error Regenerate this file with the current version of nanopb generator.
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Struct definitions */
|
||||||
|
/* Legacy NodeInfoLite descriptor used only to decode pre-split
|
||||||
|
/prefs/nodes.proto saves during the v24 -> v25 migration boot.
|
||||||
|
This preserves the original NodeInfoLite-compatible field numbers needed
|
||||||
|
to parse old wire bytes cleanly, including user (2), position (3),
|
||||||
|
device_metrics (6), and the legacy-only compatibility fields via_mqtt (8),
|
||||||
|
is_favorite (10), and is_ignored (11). Steady-state code does not use
|
||||||
|
this struct; it is dropped after migration completes. This file should be
|
||||||
|
removed once DEVICESTATE_MIN_VER advances past 24. */
|
||||||
|
typedef struct _meshtastic_NodeInfoLite_Legacy {
|
||||||
|
uint32_t num;
|
||||||
|
bool has_user;
|
||||||
|
meshtastic_UserLite user;
|
||||||
|
bool has_position;
|
||||||
|
meshtastic_PositionLite position;
|
||||||
|
float snr;
|
||||||
|
uint32_t last_heard;
|
||||||
|
bool has_device_metrics;
|
||||||
|
meshtastic_DeviceMetrics device_metrics;
|
||||||
|
uint8_t channel;
|
||||||
|
bool via_mqtt;
|
||||||
|
bool has_hops_away;
|
||||||
|
uint8_t hops_away;
|
||||||
|
bool is_favorite;
|
||||||
|
bool is_ignored;
|
||||||
|
uint8_t next_hop;
|
||||||
|
uint32_t bitfield;
|
||||||
|
} meshtastic_NodeInfoLite_Legacy;
|
||||||
|
|
||||||
|
/* Legacy NodeDatabase shape: one repeated array of fat NodeInfoLite_Legacy
|
||||||
|
with no satellite position/telemetry arrays. */
|
||||||
|
typedef struct _meshtastic_NodeDatabase_Legacy {
|
||||||
|
uint32_t version;
|
||||||
|
std::vector<meshtastic_NodeInfoLite_Legacy> nodes;
|
||||||
|
} meshtastic_NodeDatabase_Legacy;
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Initializer values for message structs */
|
||||||
|
#define meshtastic_NodeInfoLite_Legacy_init_default {0, false, meshtastic_UserLite_init_default, false, meshtastic_PositionLite_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, false, 0, 0, 0, 0, 0}
|
||||||
|
#define meshtastic_NodeDatabase_Legacy_init_default {0, {0}}
|
||||||
|
#define meshtastic_NodeInfoLite_Legacy_init_zero {0, false, meshtastic_UserLite_init_zero, false, meshtastic_PositionLite_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, false, 0, 0, 0, 0, 0}
|
||||||
|
#define meshtastic_NodeDatabase_Legacy_init_zero {0, {0}}
|
||||||
|
|
||||||
|
/* Field tags (for use in manual encoding/decoding) */
|
||||||
|
#define meshtastic_NodeInfoLite_Legacy_num_tag 1
|
||||||
|
#define meshtastic_NodeInfoLite_Legacy_user_tag 2
|
||||||
|
#define meshtastic_NodeInfoLite_Legacy_position_tag 3
|
||||||
|
#define meshtastic_NodeInfoLite_Legacy_snr_tag 4
|
||||||
|
#define meshtastic_NodeInfoLite_Legacy_last_heard_tag 5
|
||||||
|
#define meshtastic_NodeInfoLite_Legacy_device_metrics_tag 6
|
||||||
|
#define meshtastic_NodeInfoLite_Legacy_channel_tag 7
|
||||||
|
#define meshtastic_NodeInfoLite_Legacy_via_mqtt_tag 8
|
||||||
|
#define meshtastic_NodeInfoLite_Legacy_hops_away_tag 9
|
||||||
|
#define meshtastic_NodeInfoLite_Legacy_is_favorite_tag 10
|
||||||
|
#define meshtastic_NodeInfoLite_Legacy_is_ignored_tag 11
|
||||||
|
#define meshtastic_NodeInfoLite_Legacy_next_hop_tag 12
|
||||||
|
#define meshtastic_NodeInfoLite_Legacy_bitfield_tag 13
|
||||||
|
#define meshtastic_NodeDatabase_Legacy_version_tag 1
|
||||||
|
#define meshtastic_NodeDatabase_Legacy_nodes_tag 2
|
||||||
|
|
||||||
|
/* Struct field encoding specification for nanopb */
|
||||||
|
#define meshtastic_NodeInfoLite_Legacy_FIELDLIST(X, a) \
|
||||||
|
X(a, STATIC, SINGULAR, UINT32, num, 1) \
|
||||||
|
X(a, STATIC, OPTIONAL, MESSAGE, user, 2) \
|
||||||
|
X(a, STATIC, OPTIONAL, MESSAGE, position, 3) \
|
||||||
|
X(a, STATIC, SINGULAR, FLOAT, snr, 4) \
|
||||||
|
X(a, STATIC, SINGULAR, FIXED32, last_heard, 5) \
|
||||||
|
X(a, STATIC, OPTIONAL, MESSAGE, device_metrics, 6) \
|
||||||
|
X(a, STATIC, SINGULAR, UINT32, channel, 7) \
|
||||||
|
X(a, STATIC, SINGULAR, BOOL, via_mqtt, 8) \
|
||||||
|
X(a, STATIC, OPTIONAL, UINT32, hops_away, 9) \
|
||||||
|
X(a, STATIC, SINGULAR, BOOL, is_favorite, 10) \
|
||||||
|
X(a, STATIC, SINGULAR, BOOL, is_ignored, 11) \
|
||||||
|
X(a, STATIC, SINGULAR, UINT32, next_hop, 12) \
|
||||||
|
X(a, STATIC, SINGULAR, UINT32, bitfield, 13)
|
||||||
|
#define meshtastic_NodeInfoLite_Legacy_CALLBACK NULL
|
||||||
|
#define meshtastic_NodeInfoLite_Legacy_DEFAULT NULL
|
||||||
|
#define meshtastic_NodeInfoLite_Legacy_user_MSGTYPE meshtastic_UserLite
|
||||||
|
#define meshtastic_NodeInfoLite_Legacy_position_MSGTYPE meshtastic_PositionLite
|
||||||
|
#define meshtastic_NodeInfoLite_Legacy_device_metrics_MSGTYPE meshtastic_DeviceMetrics
|
||||||
|
|
||||||
|
#define meshtastic_NodeDatabase_Legacy_FIELDLIST(X, a) \
|
||||||
|
X(a, STATIC, SINGULAR, UINT32, version, 1) \
|
||||||
|
X(a, CALLBACK, REPEATED, MESSAGE, nodes, 2)
|
||||||
|
extern bool meshtastic_NodeDatabase_Legacy_callback(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_t *field);
|
||||||
|
#define meshtastic_NodeDatabase_Legacy_CALLBACK meshtastic_NodeDatabase_Legacy_callback
|
||||||
|
#define meshtastic_NodeDatabase_Legacy_DEFAULT NULL
|
||||||
|
#define meshtastic_NodeDatabase_Legacy_nodes_MSGTYPE meshtastic_NodeInfoLite_Legacy
|
||||||
|
|
||||||
|
extern const pb_msgdesc_t meshtastic_NodeInfoLite_Legacy_msg;
|
||||||
|
extern const pb_msgdesc_t meshtastic_NodeDatabase_Legacy_msg;
|
||||||
|
|
||||||
|
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
|
||||||
|
#define meshtastic_NodeInfoLite_Legacy_fields &meshtastic_NodeInfoLite_Legacy_msg
|
||||||
|
#define meshtastic_NodeDatabase_Legacy_fields &meshtastic_NodeDatabase_Legacy_msg
|
||||||
|
|
||||||
|
/* Maximum encoded size of messages (where known) */
|
||||||
|
/* meshtastic_NodeDatabase_Legacy_size depends on runtime parameters */
|
||||||
|
#define MESHTASTIC_MESHTASTIC_DEVICEONLY_LEGACY_PB_H_MAX_SIZE meshtastic_NodeInfoLite_Legacy_size
|
||||||
|
#define meshtastic_NodeInfoLite_Legacy_size 196
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
} /* extern "C" */
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -729,7 +729,7 @@ void handleNodes(HTTPRequest *req, HTTPResponse *res)
|
|||||||
uint32_t readIndex = 0;
|
uint32_t readIndex = 0;
|
||||||
const meshtastic_NodeInfoLite *tempNodeInfo = nodeDB->readNextMeshNode(readIndex);
|
const meshtastic_NodeInfoLite *tempNodeInfo = nodeDB->readNextMeshNode(readIndex);
|
||||||
while (tempNodeInfo != NULL) {
|
while (tempNodeInfo != NULL) {
|
||||||
if (tempNodeInfo->has_user) {
|
if (nodeInfoLiteHasUser(tempNodeInfo)) {
|
||||||
JSONObject node;
|
JSONObject node;
|
||||||
|
|
||||||
char id[16];
|
char id[16];
|
||||||
@@ -737,26 +737,26 @@ void handleNodes(HTTPRequest *req, HTTPResponse *res)
|
|||||||
|
|
||||||
node["id"] = new JSONValue(id);
|
node["id"] = new JSONValue(id);
|
||||||
node["snr"] = new JSONValue(tempNodeInfo->snr);
|
node["snr"] = new JSONValue(tempNodeInfo->snr);
|
||||||
node["via_mqtt"] = new JSONValue(BoolToString(tempNodeInfo->via_mqtt));
|
node["via_mqtt"] = new JSONValue(BoolToString(nodeInfoLiteViaMqtt(tempNodeInfo)));
|
||||||
node["last_heard"] = new JSONValue((int)tempNodeInfo->last_heard);
|
node["last_heard"] = new JSONValue((int)tempNodeInfo->last_heard);
|
||||||
node["position"] = new JSONValue();
|
node["position"] = new JSONValue();
|
||||||
|
|
||||||
if (nodeDB->hasValidPosition(tempNodeInfo)) {
|
if (nodeDB->hasValidPosition(tempNodeInfo)) {
|
||||||
JSONObject position;
|
meshtastic_PositionLite posLite;
|
||||||
position["latitude"] = new JSONValue((float)tempNodeInfo->position.latitude_i * 1e-7);
|
if (nodeDB->copyNodePosition(tempNodeInfo->num, posLite)) {
|
||||||
position["longitude"] = new JSONValue((float)tempNodeInfo->position.longitude_i * 1e-7);
|
JSONObject position;
|
||||||
position["altitude"] = new JSONValue((int)tempNodeInfo->position.altitude);
|
position["latitude"] = new JSONValue((float)posLite.latitude_i * 1e-7);
|
||||||
node["position"] = new JSONValue(position);
|
position["longitude"] = new JSONValue((float)posLite.longitude_i * 1e-7);
|
||||||
|
position["altitude"] = new JSONValue((int)posLite.altitude);
|
||||||
|
node["position"] = new JSONValue(position);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
node["long_name"] = new JSONValue(tempNodeInfo->user.long_name);
|
node["long_name"] = new JSONValue(tempNodeInfo->long_name);
|
||||||
node["short_name"] = new JSONValue(tempNodeInfo->user.short_name);
|
node["short_name"] = new JSONValue(tempNodeInfo->short_name);
|
||||||
char macStr[18];
|
// mac_address dropped from NodeInfoLite as part of the slim refactor; emit zeros.
|
||||||
snprintf(macStr, sizeof(macStr), "%02X:%02X:%02X:%02X:%02X:%02X", tempNodeInfo->user.macaddr[0],
|
node["mac_address"] = new JSONValue("00:00:00:00:00:00");
|
||||||
tempNodeInfo->user.macaddr[1], tempNodeInfo->user.macaddr[2], tempNodeInfo->user.macaddr[3],
|
node["hw_model"] = new JSONValue(tempNodeInfo->hw_model);
|
||||||
tempNodeInfo->user.macaddr[4], tempNodeInfo->user.macaddr[5]);
|
|
||||||
node["mac_address"] = new JSONValue(macStr);
|
|
||||||
node["hw_model"] = new JSONValue(tempNodeInfo->user.hw_model);
|
|
||||||
|
|
||||||
nodesArray.push_back(new JSONValue(node));
|
nodesArray.push_back(new JSONValue(node));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,9 +37,43 @@
|
|||||||
#define MAX_RX_NOTIFICATION_TOPHONE 2
|
#define MAX_RX_NOTIFICATION_TOPHONE 2
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/// Verify baseline assumption of node size. If it increases, we need to reevaluate
|
/// Tighten this when the slim header shrinks; loosen only with deliberate
|
||||||
/// the impact of its memory footprint, notably on MAX_NUM_NODES.
|
/// awareness of MAX_NUM_NODES impact per platform.
|
||||||
static_assert(sizeof(meshtastic_NodeInfoLite) <= 200, "NodeInfoLite size increased. Reconsider impact on MAX_NUM_NODES.");
|
static_assert(sizeof(meshtastic_NodeInfoLite) <= 130, "NodeInfoLite size increased. Reconsider impact on MAX_NUM_NODES.");
|
||||||
|
|
||||||
|
// Compile satellite NodeDBs out on STM32WL (and the status DB also follows
|
||||||
|
// MESHTASTIC_EXCLUDE_STATUS).
|
||||||
|
#ifndef MESHTASTIC_EXCLUDE_POSITIONDB
|
||||||
|
#if defined(ARCH_STM32WL)
|
||||||
|
#define MESHTASTIC_EXCLUDE_POSITIONDB 1
|
||||||
|
#else
|
||||||
|
#define MESHTASTIC_EXCLUDE_POSITIONDB 0
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef MESHTASTIC_EXCLUDE_TELEMETRYDB
|
||||||
|
#if defined(ARCH_STM32WL)
|
||||||
|
#define MESHTASTIC_EXCLUDE_TELEMETRYDB 1
|
||||||
|
#else
|
||||||
|
#define MESHTASTIC_EXCLUDE_TELEMETRYDB 0
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef MESHTASTIC_EXCLUDE_ENVIRONMENTDB
|
||||||
|
#if defined(ARCH_STM32WL)
|
||||||
|
#define MESHTASTIC_EXCLUDE_ENVIRONMENTDB 1
|
||||||
|
#else
|
||||||
|
#define MESHTASTIC_EXCLUDE_ENVIRONMENTDB 0
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef MESHTASTIC_EXCLUDE_STATUSDB
|
||||||
|
#if defined(ARCH_STM32WL) || defined(MESHTASTIC_EXCLUDE_STATUS)
|
||||||
|
#define MESHTASTIC_EXCLUDE_STATUSDB 1
|
||||||
|
#else
|
||||||
|
#define MESHTASTIC_EXCLUDE_STATUSDB 0
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
/// max number of nodes allowed in the nodeDB
|
/// max number of nodes allowed in the nodeDB
|
||||||
#ifndef MAX_NUM_NODES
|
#ifndef MAX_NUM_NODES
|
||||||
|
|||||||
+15
-14
@@ -107,14 +107,14 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
|
|||||||
|
|
||||||
// Automatically favorite the node that is using the admin key
|
// Automatically favorite the node that is using the admin key
|
||||||
auto remoteNode = nodeDB->getMeshNode(mp.from);
|
auto remoteNode = nodeDB->getMeshNode(mp.from);
|
||||||
if (remoteNode && !remoteNode->is_favorite) {
|
if (remoteNode && !nodeInfoLiteIsFavorite(remoteNode)) {
|
||||||
if (config.device.role == meshtastic_Config_DeviceConfig_Role_CLIENT_BASE) {
|
if (config.device.role == meshtastic_Config_DeviceConfig_Role_CLIENT_BASE) {
|
||||||
// Special case for CLIENT_BASE: is_favorite has special meaning, and we don't want to automatically set it
|
// Special case for CLIENT_BASE: is_favorite has special meaning, and we don't want to automatically set it
|
||||||
// without the user doing so deliberately.
|
// without the user doing so deliberately.
|
||||||
LOG_INFO("PKC admin valid, but not auto-favoriting node %x because role==CLIENT_BASE", mp.from);
|
LOG_INFO("PKC admin valid, but not auto-favoriting node %x because role==CLIENT_BASE", mp.from);
|
||||||
} else {
|
} else {
|
||||||
LOG_INFO("PKC admin valid. Auto-favoriting node %x", mp.from);
|
LOG_INFO("PKC admin valid. Auto-favoriting node %x", mp.from);
|
||||||
remoteNode->is_favorite = true;
|
nodeInfoLiteSetBit(remoteNode, NODEINFO_BITFIELD_IS_FAVORITE_MASK, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -393,7 +393,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
|
|||||||
LOG_INFO("Client received set_favorite_node command");
|
LOG_INFO("Client received set_favorite_node command");
|
||||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(r->set_favorite_node);
|
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(r->set_favorite_node);
|
||||||
if (node != NULL) {
|
if (node != NULL) {
|
||||||
node->is_favorite = true;
|
nodeInfoLiteSetBit(node, NODEINFO_BITFIELD_IS_FAVORITE_MASK, true);
|
||||||
saveChanges(SEGMENT_NODEDATABASE, false);
|
saveChanges(SEGMENT_NODEDATABASE, false);
|
||||||
if (screen)
|
if (screen)
|
||||||
screen->setFrames(graphics::Screen::FOCUS_PRESERVE); // <-- Rebuild screens
|
screen->setFrames(graphics::Screen::FOCUS_PRESERVE); // <-- Rebuild screens
|
||||||
@@ -404,7 +404,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
|
|||||||
LOG_INFO("Client received remove_favorite_node command");
|
LOG_INFO("Client received remove_favorite_node command");
|
||||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(r->remove_favorite_node);
|
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(r->remove_favorite_node);
|
||||||
if (node != NULL) {
|
if (node != NULL) {
|
||||||
node->is_favorite = false;
|
nodeInfoLiteSetBit(node, NODEINFO_BITFIELD_IS_FAVORITE_MASK, false);
|
||||||
saveChanges(SEGMENT_NODEDATABASE, false);
|
saveChanges(SEGMENT_NODEDATABASE, false);
|
||||||
if (screen)
|
if (screen)
|
||||||
screen->setFrames(graphics::Screen::FOCUS_PRESERVE); // <-- Rebuild screens
|
screen->setFrames(graphics::Screen::FOCUS_PRESERVE); // <-- Rebuild screens
|
||||||
@@ -415,11 +415,10 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
|
|||||||
LOG_INFO("Client received set_ignored_node command");
|
LOG_INFO("Client received set_ignored_node command");
|
||||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(r->set_ignored_node);
|
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(r->set_ignored_node);
|
||||||
if (node != NULL) {
|
if (node != NULL) {
|
||||||
node->is_ignored = true;
|
nodeInfoLiteSetBit(node, NODEINFO_BITFIELD_IS_IGNORED_MASK, true);
|
||||||
node->has_device_metrics = false;
|
nodeDB->eraseNodeSatellites(node->num);
|
||||||
node->has_position = false;
|
node->public_key.size = 0;
|
||||||
node->user.public_key.size = 0;
|
memset(node->public_key.bytes, 0, sizeof(node->public_key.bytes));
|
||||||
memset(node->user.public_key.bytes, 0, sizeof(node->user.public_key.bytes));
|
|
||||||
saveChanges(SEGMENT_NODEDATABASE, false);
|
saveChanges(SEGMENT_NODEDATABASE, false);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -428,7 +427,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
|
|||||||
LOG_INFO("Client received remove_ignored_node command");
|
LOG_INFO("Client received remove_ignored_node command");
|
||||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(r->remove_ignored_node);
|
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(r->remove_ignored_node);
|
||||||
if (node != NULL) {
|
if (node != NULL) {
|
||||||
node->is_ignored = false;
|
nodeInfoLiteSetBit(node, NODEINFO_BITFIELD_IS_IGNORED_MASK, false);
|
||||||
saveChanges(SEGMENT_NODEDATABASE, false);
|
saveChanges(SEGMENT_NODEDATABASE, false);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -437,7 +436,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
|
|||||||
LOG_INFO("Client received toggle_muted_node command");
|
LOG_INFO("Client received toggle_muted_node command");
|
||||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(r->toggle_muted_node);
|
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(r->toggle_muted_node);
|
||||||
if (node != NULL) {
|
if (node != NULL) {
|
||||||
node->bitfield ^= (1 << NODEINFO_BITFIELD_IS_MUTED_SHIFT);
|
nodeInfoLiteSetBit(node, NODEINFO_BITFIELD_IS_MUTED_MASK, !nodeInfoLiteIsMuted(node));
|
||||||
saveChanges(SEGMENT_NODEDATABASE, false);
|
saveChanges(SEGMENT_NODEDATABASE, false);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -445,9 +444,11 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
|
|||||||
|
|
||||||
case meshtastic_AdminMessage_set_fixed_position_tag: {
|
case meshtastic_AdminMessage_set_fixed_position_tag: {
|
||||||
LOG_INFO("Client received set_fixed_position command");
|
LOG_INFO("Client received set_fixed_position command");
|
||||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||||
node->has_position = true;
|
// Route the fixed position through updatePosition so it lands in the
|
||||||
node->position = TypeConversions::ConvertToPositionLite(r->set_fixed_position);
|
// satellite map (or, on builds with PositionDB excluded, just sets
|
||||||
|
// localPosition for the local broadcast path).
|
||||||
|
nodeDB->updatePosition(node->num, r->set_fixed_position, RX_SRC_LOCAL);
|
||||||
nodeDB->setLocalPosition(r->set_fixed_position);
|
nodeDB->setLocalPosition(r->set_fixed_position);
|
||||||
config.position.fixed_position = true;
|
config.position.fixed_position = true;
|
||||||
saveChanges(SEGMENT_NODEDATABASE | SEGMENT_CONFIG, false);
|
saveChanges(SEGMENT_NODEDATABASE | SEGMENT_CONFIG, false);
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ void CannedMessageModule::LaunchFreetextWithDestination(NodeNum newDest, uint8_t
|
|||||||
static bool returnToCannedList = false;
|
static bool returnToCannedList = false;
|
||||||
bool hasKeyForNode(const meshtastic_NodeInfoLite *node)
|
bool hasKeyForNode(const meshtastic_NodeInfoLite *node)
|
||||||
{
|
{
|
||||||
return node && node->has_user && node->user.public_key.size > 0;
|
return nodeInfoLiteHasUser(node) && node->public_key.size > 0;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @brief Items in array this->messages will be set to be pointing on the right
|
* @brief Items in array this->messages will be set to be pointing on the right
|
||||||
@@ -262,10 +262,10 @@ void CannedMessageModule::updateDestinationSelectionList()
|
|||||||
|
|
||||||
for (size_t i = 0; i < numMeshNodes; ++i) {
|
for (size_t i = 0; i < numMeshNodes; ++i) {
|
||||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
|
meshtastic_NodeInfoLite *node = nodeDB->getMeshNodeByIndex(i);
|
||||||
if (!node || node->num == myNodeNum || !node->has_user || node->user.public_key.size != 32)
|
if (!node || node->num == myNodeNum || !nodeInfoLiteHasUser(node) || node->public_key.size != 32)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
const String &nodeName = node->user.long_name;
|
const String &nodeName = node->long_name;
|
||||||
|
|
||||||
if (searchQuery.length() == 0) {
|
if (searchQuery.length() == 0) {
|
||||||
this->filteredNodes.push_back({node, sinceLastSeen(node)});
|
this->filteredNodes.push_back({node, sinceLastSeen(node)});
|
||||||
@@ -1054,7 +1054,7 @@ void CannedMessageModule::sendText(NodeNum dest, ChannelIndex channel, const cha
|
|||||||
}
|
}
|
||||||
|
|
||||||
NodeNum myNodeNum = nodeDB->getNodeNum();
|
NodeNum myNodeNum = nodeDB->getNodeNum();
|
||||||
if (node && node->num != myNodeNum && node->has_user && node->user.public_key.size == 32) {
|
if (node && node->num != myNodeNum && nodeInfoLiteHasUser(node) && node->public_key.size == 32) {
|
||||||
p->pki_encrypted = true;
|
p->pki_encrypted = true;
|
||||||
p->channel = 0; // force PKI
|
p->channel = 0; // force PKI
|
||||||
}
|
}
|
||||||
@@ -1415,8 +1415,8 @@ const char *CannedMessageModule::getNodeName(NodeNum node)
|
|||||||
return "Broadcast";
|
return "Broadcast";
|
||||||
|
|
||||||
meshtastic_NodeInfoLite *info = nodeDB->getMeshNode(node);
|
meshtastic_NodeInfoLite *info = nodeDB->getMeshNode(node);
|
||||||
if (info && info->has_user && strlen(info->user.long_name) > 0) {
|
if (nodeInfoLiteHasUser(info) && strlen(info->long_name) > 0) {
|
||||||
return info->user.long_name;
|
return info->long_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
static char fallback[12];
|
static char fallback[12];
|
||||||
@@ -1723,17 +1723,17 @@ void CannedMessageModule::drawDestinationSelectionScreen(OLEDDisplay *display, O
|
|||||||
meshtastic_NodeInfoLite *node = this->filteredNodes[nodeIndex].node;
|
meshtastic_NodeInfoLite *node = this->filteredNodes[nodeIndex].node;
|
||||||
if (node) {
|
if (node) {
|
||||||
if (display->getWidth() <= 64) {
|
if (display->getWidth() <= 64) {
|
||||||
entryText = node->user.short_name;
|
entryText = node->short_name;
|
||||||
} else if (node->user.long_name[0]) {
|
} else if (node->long_name[0]) {
|
||||||
entryText = node->user.long_name;
|
entryText = node->long_name;
|
||||||
} else {
|
} else {
|
||||||
entryText = node->user.short_name;
|
entryText = node->short_name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int availWidth = display->getWidth() -
|
int availWidth = display->getWidth() -
|
||||||
((graphics::currentResolution == graphics::ScreenResolution::High) ? 40 : 20) -
|
((graphics::currentResolution == graphics::ScreenResolution::High) ? 40 : 20) -
|
||||||
((node && node->is_favorite) ? 10 : 0);
|
((nodeInfoLiteIsFavorite(node)) ? 10 : 0);
|
||||||
if (availWidth < 0)
|
if (availWidth < 0)
|
||||||
availWidth = 0;
|
availWidth = 0;
|
||||||
char truncatedEntry[96];
|
char truncatedEntry[96];
|
||||||
@@ -1742,7 +1742,7 @@ void CannedMessageModule::drawDestinationSelectionScreen(OLEDDisplay *display, O
|
|||||||
entryText = truncatedEntry;
|
entryText = truncatedEntry;
|
||||||
|
|
||||||
// Prepend "* " if this is a favorite
|
// Prepend "* " if this is a favorite
|
||||||
if (node && node->is_favorite) {
|
if (nodeInfoLiteIsFavorite(node)) {
|
||||||
entryText = "* " + entryText;
|
entryText = "* " + entryText;
|
||||||
}
|
}
|
||||||
graphics::UIRenderer::truncateStringWithEmotes(display, entryText.c_str(), truncatedEntry, sizeof(truncatedEntry),
|
graphics::UIRenderer::truncateStringWithEmotes(display, entryText.c_str(), truncatedEntry, sizeof(truncatedEntry),
|
||||||
|
|||||||
@@ -372,7 +372,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
|
|||||||
// If we receive a direct message and the receipent is us, apply DM mute setting
|
// If we receive a direct message and the receipent is us, apply DM mute setting
|
||||||
// Else we just handle it as not muted.
|
// Else we just handle it as not muted.
|
||||||
const bool isDmToUs = !isBroadcast(mp.to) && isToUs(&mp);
|
const bool isDmToUs = !isBroadcast(mp.to) && isToUs(&mp);
|
||||||
bool is_muted = isDmToUs ? (sender && ((sender->bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) != 0))
|
bool is_muted = isDmToUs ? nodeInfoLiteIsMuted(sender)
|
||||||
: (ch.settings.has_module_settings && ch.settings.module_settings.is_muted);
|
: (ch.settings.has_module_settings && ch.settings.module_settings.is_muted);
|
||||||
|
|
||||||
const bool buzzerModeIsDirectOnly =
|
const bool buzzerModeIsDirectOnly =
|
||||||
|
|||||||
@@ -10,6 +10,18 @@
|
|||||||
|
|
||||||
KeyVerificationModule *keyVerificationModule;
|
KeyVerificationModule *keyVerificationModule;
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
void copyNodeLongNameOrUnknown(char *dest, size_t destSize, const meshtastic_NodeInfoLite *node)
|
||||||
|
{
|
||||||
|
if (!dest || destSize == 0)
|
||||||
|
return;
|
||||||
|
const char *name = (node && nodeInfoLiteHasUser(node) && node->long_name[0]) ? node->long_name : "Unknown";
|
||||||
|
strncpy(dest, name, destSize - 1);
|
||||||
|
dest[destSize - 1] = '\0';
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
KeyVerificationModule::KeyVerificationModule()
|
KeyVerificationModule::KeyVerificationModule()
|
||||||
: ProtobufModule("KeyVerification", meshtastic_PortNum_KEY_VERIFICATION_APP, &meshtastic_KeyVerification_msg)
|
: ProtobufModule("KeyVerification", meshtastic_PortNum_KEY_VERIFICATION_APP, &meshtastic_KeyVerification_msg)
|
||||||
{
|
{
|
||||||
@@ -37,7 +49,8 @@ AdminMessageHandleResult KeyVerificationModule::handleAdminMessageForModule(cons
|
|||||||
} else if (request->key_verification.message_type == meshtastic_KeyVerificationAdmin_MessageType_DO_VERIFY &&
|
} else if (request->key_verification.message_type == meshtastic_KeyVerificationAdmin_MessageType_DO_VERIFY &&
|
||||||
request->key_verification.nonce == currentNonce) {
|
request->key_verification.nonce == currentNonce) {
|
||||||
auto remoteNodePtr = nodeDB->getMeshNode(currentRemoteNode);
|
auto remoteNodePtr = nodeDB->getMeshNode(currentRemoteNode);
|
||||||
remoteNodePtr->bitfield |= NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK;
|
if (remoteNodePtr)
|
||||||
|
remoteNodePtr->bitfield |= NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK;
|
||||||
resetToIdle();
|
resetToIdle();
|
||||||
} else if (request->key_verification.message_type == meshtastic_KeyVerificationAdmin_MessageType_DO_NOT_VERIFY) {
|
} else if (request->key_verification.message_type == meshtastic_KeyVerificationAdmin_MessageType_DO_NOT_VERIFY) {
|
||||||
resetToIdle();
|
resetToIdle();
|
||||||
@@ -72,9 +85,9 @@ bool KeyVerificationModule::handleReceivedProtobuf(const meshtastic_MeshPacket &
|
|||||||
sprintf(cn->message, "Enter Security Number for Key Verification");
|
sprintf(cn->message, "Enter Security Number for Key Verification");
|
||||||
cn->which_payload_variant = meshtastic_ClientNotification_key_verification_number_request_tag;
|
cn->which_payload_variant = meshtastic_ClientNotification_key_verification_number_request_tag;
|
||||||
cn->payload_variant.key_verification_number_request.nonce = currentNonce;
|
cn->payload_variant.key_verification_number_request.nonce = currentNonce;
|
||||||
strncpy(cn->payload_variant.key_verification_number_request.remote_longname, // should really check for nulls, etc
|
copyNodeLongNameOrUnknown(cn->payload_variant.key_verification_number_request.remote_longname,
|
||||||
nodeDB->getMeshNode(currentRemoteNode)->user.long_name,
|
sizeof(cn->payload_variant.key_verification_number_request.remote_longname),
|
||||||
sizeof(cn->payload_variant.key_verification_number_request.remote_longname));
|
nodeDB->getMeshNode(currentRemoteNode));
|
||||||
service->sendClientNotification(cn);
|
service->sendClientNotification(cn);
|
||||||
LOG_INFO("Received hash2");
|
LOG_INFO("Received hash2");
|
||||||
currentState = KEY_VERIFICATION_SENDER_AWAITING_NUMBER;
|
currentState = KEY_VERIFICATION_SENDER_AWAITING_NUMBER;
|
||||||
@@ -95,7 +108,8 @@ bool KeyVerificationModule::handleReceivedProtobuf(const meshtastic_MeshPacket &
|
|||||||
[=](int selected) {
|
[=](int selected) {
|
||||||
if (selected == 1) {
|
if (selected == 1) {
|
||||||
auto remoteNodePtr = nodeDB->getMeshNode(currentRemoteNode);
|
auto remoteNodePtr = nodeDB->getMeshNode(currentRemoteNode);
|
||||||
remoteNodePtr->bitfield |= NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK;
|
if (remoteNodePtr)
|
||||||
|
remoteNodePtr->bitfield |= NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
screen->showOverlayBanner(options);)
|
screen->showOverlayBanner(options);)
|
||||||
@@ -104,9 +118,9 @@ bool KeyVerificationModule::handleReceivedProtobuf(const meshtastic_MeshPacket &
|
|||||||
sprintf(cn->message, "Final confirmation for incoming manual key verification %s", message);
|
sprintf(cn->message, "Final confirmation for incoming manual key verification %s", message);
|
||||||
cn->which_payload_variant = meshtastic_ClientNotification_key_verification_final_tag;
|
cn->which_payload_variant = meshtastic_ClientNotification_key_verification_final_tag;
|
||||||
cn->payload_variant.key_verification_final.nonce = currentNonce;
|
cn->payload_variant.key_verification_final.nonce = currentNonce;
|
||||||
strncpy(cn->payload_variant.key_verification_final.remote_longname, // should really check for nulls, etc
|
copyNodeLongNameOrUnknown(cn->payload_variant.key_verification_final.remote_longname,
|
||||||
nodeDB->getMeshNode(currentRemoteNode)->user.long_name,
|
sizeof(cn->payload_variant.key_verification_final.remote_longname),
|
||||||
sizeof(cn->payload_variant.key_verification_final.remote_longname));
|
nodeDB->getMeshNode(currentRemoteNode));
|
||||||
cn->payload_variant.key_verification_final.isSender = false;
|
cn->payload_variant.key_verification_final.isSender = false;
|
||||||
service->sendClientNotification(cn);
|
service->sendClientNotification(cn);
|
||||||
|
|
||||||
@@ -202,9 +216,9 @@ meshtastic_MeshPacket *KeyVerificationModule::allocReply()
|
|||||||
currentSecurityNumber % 1000);
|
currentSecurityNumber % 1000);
|
||||||
cn->which_payload_variant = meshtastic_ClientNotification_key_verification_number_inform_tag;
|
cn->which_payload_variant = meshtastic_ClientNotification_key_verification_number_inform_tag;
|
||||||
cn->payload_variant.key_verification_number_inform.nonce = currentNonce;
|
cn->payload_variant.key_verification_number_inform.nonce = currentNonce;
|
||||||
strncpy(cn->payload_variant.key_verification_number_inform.remote_longname, // should really check for nulls, etc
|
copyNodeLongNameOrUnknown(cn->payload_variant.key_verification_number_inform.remote_longname,
|
||||||
nodeDB->getMeshNode(currentRemoteNode)->user.long_name,
|
sizeof(cn->payload_variant.key_verification_number_inform.remote_longname),
|
||||||
sizeof(cn->payload_variant.key_verification_number_inform.remote_longname));
|
nodeDB->getMeshNode(currentRemoteNode));
|
||||||
cn->payload_variant.key_verification_number_inform.security_number = currentSecurityNumber;
|
cn->payload_variant.key_verification_number_inform.security_number = currentSecurityNumber;
|
||||||
service->sendClientNotification(cn);
|
service->sendClientNotification(cn);
|
||||||
LOG_WARN("Security Number %04u, nonce %llu", currentSecurityNumber, currentNonce);
|
LOG_WARN("Security Number %04u, nonce %llu", currentSecurityNumber, currentNonce);
|
||||||
@@ -219,7 +233,7 @@ void KeyVerificationModule::processSecurityNumber(uint32_t incomingNumber)
|
|||||||
LOG_WARN("received security number: %u", incomingNumber);
|
LOG_WARN("received security number: %u", incomingNumber);
|
||||||
meshtastic_NodeInfoLite *remoteNodePtr = nullptr;
|
meshtastic_NodeInfoLite *remoteNodePtr = nullptr;
|
||||||
remoteNodePtr = nodeDB->getMeshNode(currentRemoteNode);
|
remoteNodePtr = nodeDB->getMeshNode(currentRemoteNode);
|
||||||
if (remoteNodePtr == nullptr || !remoteNodePtr->has_user || remoteNodePtr->user.public_key.size != 32) {
|
if (!remoteNodePtr || !nodeInfoLiteHasUser(remoteNodePtr) || remoteNodePtr->public_key.size != 32) {
|
||||||
currentState = KEY_VERIFICATION_IDLE;
|
currentState = KEY_VERIFICATION_IDLE;
|
||||||
return; // should we throw an error here?
|
return; // should we throw an error here?
|
||||||
}
|
}
|
||||||
@@ -232,7 +246,7 @@ void KeyVerificationModule::processSecurityNumber(uint32_t incomingNumber)
|
|||||||
hash.update(¤tRemoteNode, sizeof(currentRemoteNode));
|
hash.update(¤tRemoteNode, sizeof(currentRemoteNode));
|
||||||
hash.update(owner.public_key.bytes, owner.public_key.size);
|
hash.update(owner.public_key.bytes, owner.public_key.size);
|
||||||
|
|
||||||
hash.update(remoteNodePtr->user.public_key.bytes, remoteNodePtr->user.public_key.size);
|
hash.update(remoteNodePtr->public_key.bytes, remoteNodePtr->public_key.size);
|
||||||
hash.finalize(hash1, 32);
|
hash.finalize(hash1, 32);
|
||||||
|
|
||||||
hash.reset();
|
hash.reset();
|
||||||
@@ -265,9 +279,9 @@ void KeyVerificationModule::processSecurityNumber(uint32_t incomingNumber)
|
|||||||
sprintf(cn->message, "Final confirmation for outgoing manual key verification %s", message);
|
sprintf(cn->message, "Final confirmation for outgoing manual key verification %s", message);
|
||||||
cn->which_payload_variant = meshtastic_ClientNotification_key_verification_final_tag;
|
cn->which_payload_variant = meshtastic_ClientNotification_key_verification_final_tag;
|
||||||
cn->payload_variant.key_verification_final.nonce = currentNonce;
|
cn->payload_variant.key_verification_final.nonce = currentNonce;
|
||||||
strncpy(cn->payload_variant.key_verification_final.remote_longname, // should really check for nulls, etc
|
copyNodeLongNameOrUnknown(cn->payload_variant.key_verification_final.remote_longname,
|
||||||
nodeDB->getMeshNode(currentRemoteNode)->user.long_name,
|
sizeof(cn->payload_variant.key_verification_final.remote_longname),
|
||||||
sizeof(cn->payload_variant.key_verification_final.remote_longname));
|
nodeDB->getMeshNode(currentRemoteNode));
|
||||||
cn->payload_variant.key_verification_final.isSender = true;
|
cn->payload_variant.key_verification_final.isSender = true;
|
||||||
service->sendClientNotification(cn);
|
service->sendClientNotification(cn);
|
||||||
LOG_INFO(message);
|
LOG_INFO(message);
|
||||||
|
|||||||
@@ -183,8 +183,7 @@ meshtastic_MeshPacket *PositionModule::allocPositionPacket()
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
meshtastic_NodeInfoLite *node = service->refreshLocalMeshNode(); // should guarantee there is now a position
|
const meshtastic_NodeInfoLite *node = service->refreshLocalMeshNode(); // should guarantee there is now a position
|
||||||
assert(node->has_position);
|
|
||||||
|
|
||||||
// configuration of POSITION packet
|
// configuration of POSITION packet
|
||||||
// consider making this a function argument?
|
// consider making this a function argument?
|
||||||
@@ -194,7 +193,9 @@ meshtastic_MeshPacket *PositionModule::allocPositionPacket()
|
|||||||
meshtastic_Position p = meshtastic_Position_init_default; // Start with an empty structure
|
meshtastic_Position p = meshtastic_Position_init_default; // Start with an empty structure
|
||||||
// if localPosition is totally empty, put our last saved position (lite) in there
|
// if localPosition is totally empty, put our last saved position (lite) in there
|
||||||
if (localPosition.latitude_i == 0 && localPosition.longitude_i == 0) {
|
if (localPosition.latitude_i == 0 && localPosition.longitude_i == 0) {
|
||||||
nodeDB->setLocalPosition(TypeConversions::ConvertToPosition(node->position));
|
meshtastic_PositionLite cachedSelf;
|
||||||
|
if (nodeDB->copyNodePosition(node->num, cachedSelf))
|
||||||
|
nodeDB->setLocalPosition(TypeConversions::ConvertToPosition(cachedSelf));
|
||||||
}
|
}
|
||||||
localPosition.seq_number++;
|
localPosition.seq_number++;
|
||||||
|
|
||||||
@@ -420,7 +421,7 @@ int32_t PositionModule::runOnce()
|
|||||||
doDeepSleep(nightyNightMs, false, false);
|
doDeepSleep(nightyNightMs, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||||
if (node == nullptr)
|
if (node == nullptr)
|
||||||
return RUNONCE_INTERVAL;
|
return RUNONCE_INTERVAL;
|
||||||
|
|
||||||
@@ -445,8 +446,11 @@ int32_t PositionModule::runOnce()
|
|||||||
} else if (nodeDB->hasValidPosition(node)) {
|
} else if (nodeDB->hasValidPosition(node)) {
|
||||||
lastGpsSend = now;
|
lastGpsSend = now;
|
||||||
|
|
||||||
lastGpsLatitude = node->position.latitude_i;
|
meshtastic_PositionLite selfPos;
|
||||||
lastGpsLongitude = node->position.longitude_i;
|
if (nodeDB->copyNodePosition(node->num, selfPos)) {
|
||||||
|
lastGpsLatitude = selfPos.latitude_i;
|
||||||
|
lastGpsLongitude = selfPos.longitude_i;
|
||||||
|
}
|
||||||
|
|
||||||
if (transmitHistory)
|
if (transmitHistory)
|
||||||
transmitHistory->setLastSentToMesh(meshtastic_PortNum_POSITION_APP);
|
transmitHistory->setLastSentToMesh(meshtastic_PortNum_POSITION_APP);
|
||||||
@@ -461,7 +465,10 @@ int32_t PositionModule::runOnce()
|
|||||||
if (nodeDB->hasValidPosition(node2)) {
|
if (nodeDB->hasValidPosition(node2)) {
|
||||||
// The minimum time (in seconds) that would pass before we are able to send a new position packet.
|
// The minimum time (in seconds) that would pass before we are able to send a new position packet.
|
||||||
|
|
||||||
auto smartPosition = getDistanceTraveledSinceLastSend(node->position);
|
meshtastic_PositionLite selfPos;
|
||||||
|
if (!nodeDB->copyNodePosition(node->num, selfPos))
|
||||||
|
return RUNONCE_INTERVAL; // Defensive: hasValidPosition should imply this is non-null
|
||||||
|
auto smartPosition = getDistanceTraveledSinceLastSend(selfPos);
|
||||||
msSinceLastSend = now - lastGpsSend;
|
msSinceLastSend = now - lastGpsSend;
|
||||||
|
|
||||||
if (smartPosition.hasTraveledOverThreshold &&
|
if (smartPosition.hasTraveledOverThreshold &&
|
||||||
@@ -479,8 +486,8 @@ int32_t PositionModule::runOnce()
|
|||||||
msSinceLastSend, minimumTimeThreshold);
|
msSinceLastSend, minimumTimeThreshold);
|
||||||
|
|
||||||
// Set the current coords as our last ones, after we've compared distance with current and decided to send
|
// Set the current coords as our last ones, after we've compared distance with current and decided to send
|
||||||
lastGpsLatitude = node->position.latitude_i;
|
lastGpsLatitude = selfPos.latitude_i;
|
||||||
lastGpsLongitude = node->position.longitude_i;
|
lastGpsLongitude = selfPos.longitude_i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -563,11 +570,14 @@ struct SmartPosition PositionModule::getDistanceTraveledSinceLastSend(meshtastic
|
|||||||
|
|
||||||
void PositionModule::handleNewPosition()
|
void PositionModule::handleNewPosition()
|
||||||
{
|
{
|
||||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||||
const meshtastic_NodeInfoLite *node2 = service->refreshLocalMeshNode(); // should guarantee there is now a position
|
const meshtastic_NodeInfoLite *node2 = service->refreshLocalMeshNode(); // should guarantee there is now a position
|
||||||
// We limit our GPS broadcasts to a max rate
|
// We limit our GPS broadcasts to a max rate
|
||||||
if (nodeDB->hasValidPosition(node2)) {
|
if (nodeDB->hasValidPosition(node2)) {
|
||||||
auto smartPosition = getDistanceTraveledSinceLastSend(node->position);
|
meshtastic_PositionLite selfPos;
|
||||||
|
if (!nodeDB->copyNodePosition(node->num, selfPos))
|
||||||
|
return;
|
||||||
|
auto smartPosition = getDistanceTraveledSinceLastSend(selfPos);
|
||||||
uint32_t msSinceLastSend = millis() - lastGpsSend;
|
uint32_t msSinceLastSend = millis() - lastGpsSend;
|
||||||
if (smartPosition.hasTraveledOverThreshold &&
|
if (smartPosition.hasTraveledOverThreshold &&
|
||||||
Throttle::execute(
|
Throttle::execute(
|
||||||
@@ -583,8 +593,8 @@ void PositionModule::handleNewPosition()
|
|||||||
minimumTimeThreshold);
|
minimumTimeThreshold);
|
||||||
|
|
||||||
// Set the current coords as our last ones, after we've compared distance with current and decided to send
|
// Set the current coords as our last ones, after we've compared distance with current and decided to send
|
||||||
lastGpsLatitude = node->position.latitude_i;
|
lastGpsLatitude = selfPos.latitude_i;
|
||||||
lastGpsLongitude = node->position.longitude_i;
|
lastGpsLongitude = selfPos.longitude_i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -268,26 +268,36 @@ bool RangeTestModuleRadio::appendFile(const meshtastic_MeshPacket &mp)
|
|||||||
fileToAppend.printf("??:??:??,"); // Time
|
fileToAppend.printf("??:??:??,"); // Time
|
||||||
}
|
}
|
||||||
|
|
||||||
fileToAppend.printf("%d,", getFrom(&mp)); // From
|
fileToAppend.printf("%d,", getFrom(&mp)); // From
|
||||||
fileToAppend.printf("%s,", n->user.long_name); // Long Name
|
fileToAppend.printf("%s,", n ? n->long_name : ""); // Long Name
|
||||||
fileToAppend.printf("%f,", n->position.latitude_i * 1e-7); // Sender Lat
|
meshtastic_PositionLite senderPos;
|
||||||
fileToAppend.printf("%f,", n->position.longitude_i * 1e-7); // Sender Long
|
const bool haveSenderPos = nodeDB->copyNodePosition(getFrom(&mp), senderPos);
|
||||||
|
if (haveSenderPos) {
|
||||||
|
fileToAppend.printf("%f,", senderPos.latitude_i * 1e-7); // Sender Lat
|
||||||
|
fileToAppend.printf("%f,", senderPos.longitude_i * 1e-7); // Sender Long
|
||||||
|
} else {
|
||||||
|
fileToAppend.printf("0.0,0.0,");
|
||||||
|
}
|
||||||
if (gpsStatus->getIsConnected() || config.position.fixed_position) {
|
if (gpsStatus->getIsConnected() || config.position.fixed_position) {
|
||||||
fileToAppend.printf("%f,", gpsStatus->getLatitude() * 1e-7); // RX Lat
|
fileToAppend.printf("%f,", gpsStatus->getLatitude() * 1e-7); // RX Lat
|
||||||
fileToAppend.printf("%f,", gpsStatus->getLongitude() * 1e-7); // RX Long
|
fileToAppend.printf("%f,", gpsStatus->getLongitude() * 1e-7); // RX Long
|
||||||
fileToAppend.printf("%d,", gpsStatus->getAltitude()); // RX Altitude
|
fileToAppend.printf("%d,", gpsStatus->getAltitude()); // RX Altitude
|
||||||
} else {
|
} else {
|
||||||
// When the phone API is in use, the node info will be updated with position
|
// When the phone API is in use, the node info will be updated with position
|
||||||
meshtastic_NodeInfoLite *us = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
meshtastic_PositionLite usPos;
|
||||||
fileToAppend.printf("%f,", us->position.latitude_i * 1e-7); // RX Lat
|
if (nodeDB->copyNodePosition(nodeDB->getNodeNum(), usPos)) {
|
||||||
fileToAppend.printf("%f,", us->position.longitude_i * 1e-7); // RX Long
|
fileToAppend.printf("%f,", usPos.latitude_i * 1e-7); // RX Lat
|
||||||
fileToAppend.printf("%d,", us->position.altitude); // RX Altitude
|
fileToAppend.printf("%f,", usPos.longitude_i * 1e-7); // RX Long
|
||||||
|
fileToAppend.printf("%d,", usPos.altitude); // RX Altitude
|
||||||
|
} else {
|
||||||
|
fileToAppend.printf("0.0,0.0,0,");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fileToAppend.printf("%f,", mp.rx_snr); // RX SNR
|
fileToAppend.printf("%f,", mp.rx_snr); // RX SNR
|
||||||
|
|
||||||
if (n->position.latitude_i && n->position.longitude_i && gpsStatus->getLatitude() && gpsStatus->getLongitude()) {
|
if (haveSenderPos && senderPos.latitude_i && senderPos.longitude_i && gpsStatus->getLatitude() && gpsStatus->getLongitude()) {
|
||||||
float distance = GeoCoord::latLongToMeter(n->position.latitude_i * 1e-7, n->position.longitude_i * 1e-7,
|
float distance = GeoCoord::latLongToMeter(senderPos.latitude_i * 1e-7, senderPos.longitude_i * 1e-7,
|
||||||
gpsStatus->getLatitude() * 1e-7, gpsStatus->getLongitude() * 1e-7);
|
gpsStatus->getLatitude() * 1e-7, gpsStatus->getLongitude() * 1e-7);
|
||||||
fileToAppend.printf("%f,", distance); // Distance in meters
|
fileToAppend.printf("%f,", distance); // Distance in meters
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -17,8 +17,7 @@ bool RoutingModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mesh
|
|||||||
config.device.rebroadcast_mode == meshtastic_Config_DeviceConfig_RebroadcastMode_KNOWN_ONLY)) {
|
config.device.rebroadcast_mode == meshtastic_Config_DeviceConfig_RebroadcastMode_KNOWN_ONLY)) {
|
||||||
if (!maybePKI)
|
if (!maybePKI)
|
||||||
return false;
|
return false;
|
||||||
if ((nodeDB->getMeshNode(mp.from) == NULL || !nodeDB->getMeshNode(mp.from)->has_user) &&
|
if (!nodeInfoLiteHasUser(nodeDB->getMeshNode(mp.from)) && !nodeInfoLiteHasUser(nodeDB->getMeshNode(mp.to)))
|
||||||
(nodeDB->getMeshNode(mp.to) == NULL || !nodeDB->getMeshNode(mp.to)->has_user))
|
|
||||||
return false;
|
return false;
|
||||||
} else if (owner.is_licensed && ((nodeDB->getLicenseStatus(mp.from) == UserLicenseStatus::NotLicensed) ||
|
} else if (owner.is_licensed && ((nodeDB->getLicenseStatus(mp.from) == UserLicenseStatus::NotLicensed) ||
|
||||||
(nodeDB->getLicenseStatus(mp.to) == UserLicenseStatus::NotLicensed))) {
|
(nodeDB->getLicenseStatus(mp.to) == UserLicenseStatus::NotLicensed))) {
|
||||||
|
|||||||
@@ -250,9 +250,12 @@ int32_t SerialModule::runOnce()
|
|||||||
uint32_t readIndex = 0;
|
uint32_t readIndex = 0;
|
||||||
const meshtastic_NodeInfoLite *tempNodeInfo = nodeDB->readNextMeshNode(readIndex);
|
const meshtastic_NodeInfoLite *tempNodeInfo = nodeDB->readNextMeshNode(readIndex);
|
||||||
while (tempNodeInfo != NULL) {
|
while (tempNodeInfo != NULL) {
|
||||||
if (tempNodeInfo->has_user && nodeDB->hasValidPosition(tempNodeInfo)) {
|
if (nodeInfoLiteHasUser(tempNodeInfo) && nodeDB->hasValidPosition(tempNodeInfo)) {
|
||||||
printWPL(outbuf, sizeof(outbuf), tempNodeInfo->position, tempNodeInfo->user.long_name, true);
|
meshtastic_PositionLite pos;
|
||||||
serialPrint->printf("%s", outbuf);
|
if (nodeDB->copyNodePosition(tempNodeInfo->num, pos)) {
|
||||||
|
printWPL(outbuf, sizeof(outbuf), pos, tempNodeInfo->long_name, true);
|
||||||
|
serialPrint->printf("%s", outbuf);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
tempNodeInfo = nodeDB->readNextMeshNode(readIndex);
|
tempNodeInfo = nodeDB->readNextMeshNode(readIndex);
|
||||||
}
|
}
|
||||||
@@ -401,7 +404,7 @@ ProcessMessage SerialModuleRadio::handleReceived(const meshtastic_MeshPacket &mp
|
|||||||
serialPrint->write(p.payload.bytes, p.payload.size);
|
serialPrint->write(p.payload.bytes, p.payload.size);
|
||||||
} else if (moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_TEXTMSG) {
|
} else if (moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_TEXTMSG) {
|
||||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(getFrom(&mp));
|
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(getFrom(&mp));
|
||||||
const char *sender = (node && node->has_user) ? node->user.short_name : "???";
|
const char *sender = nodeInfoLiteHasUser(node) ? node->short_name : "???";
|
||||||
serialPrint->println();
|
serialPrint->println();
|
||||||
serialPrint->printf("%s: %s", sender, p.payload.bytes);
|
serialPrint->printf("%s: %s", sender, p.payload.bytes);
|
||||||
serialPrint->println();
|
serialPrint->println();
|
||||||
@@ -417,9 +420,13 @@ ProcessMessage SerialModuleRadio::handleReceived(const meshtastic_MeshPacket &mp
|
|||||||
decoded = &scratch;
|
decoded = &scratch;
|
||||||
}
|
}
|
||||||
// send position packet as WPL to the serial port
|
// send position packet as WPL to the serial port
|
||||||
printWPL(outbuf, sizeof(outbuf), *decoded, nodeDB->getMeshNode(getFrom(&mp))->user.long_name,
|
{
|
||||||
moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO);
|
meshtastic_NodeInfoLite *senderNode = nodeDB->getMeshNode(getFrom(&mp));
|
||||||
serialPrint->printf("%s", outbuf);
|
const char *senderName = senderNode ? senderNode->long_name : "";
|
||||||
|
printWPL(outbuf, sizeof(outbuf), *decoded, senderName,
|
||||||
|
moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO);
|
||||||
|
serialPrint->printf("%s", outbuf);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "StatusMessageModule.h"
|
#include "StatusMessageModule.h"
|
||||||
#include "MeshService.h"
|
#include "MeshService.h"
|
||||||
|
#include "NodeDB.h"
|
||||||
#include "ProtobufModule.h"
|
#include "ProtobufModule.h"
|
||||||
|
|
||||||
StatusMessageModule *statusMessageModule;
|
StatusMessageModule *statusMessageModule;
|
||||||
@@ -36,16 +37,8 @@ ProcessMessage StatusMessageModule::handleReceived(const meshtastic_MeshPacket &
|
|||||||
|
|
||||||
LOG_INFO("Received a NodeStatus message %s", incomingMessage.status);
|
LOG_INFO("Received a NodeStatus message %s", incomingMessage.status);
|
||||||
|
|
||||||
RecentStatus entry;
|
if (nodeDB)
|
||||||
entry.fromNodeId = mp.from;
|
nodeDB->setNodeStatus(mp.from, incomingMessage);
|
||||||
entry.statusText = incomingMessage.status;
|
|
||||||
|
|
||||||
recentReceived.push_back(std::move(entry));
|
|
||||||
|
|
||||||
// Keep only last MAX_RECENT_STATUSMESSAGES
|
|
||||||
if (recentReceived.size() > MAX_RECENT_STATUSMESSAGES) {
|
|
||||||
recentReceived.erase(recentReceived.begin()); // drop oldest
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ProcessMessage::CONTINUE;
|
return ProcessMessage::CONTINUE;
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
#if !MESHTASTIC_EXCLUDE_STATUS
|
#if !MESHTASTIC_EXCLUDE_STATUS
|
||||||
#include "SinglePortModule.h"
|
#include "SinglePortModule.h"
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
class StatusMessageModule : public SinglePortModule, private concurrency::OSThread
|
class StatusMessageModule : public SinglePortModule, private concurrency::OSThread
|
||||||
{
|
{
|
||||||
@@ -20,28 +18,15 @@ class StatusMessageModule : public SinglePortModule, private concurrency::OSThre
|
|||||||
this->setInterval(1000 * 12 * 60 * 60);
|
this->setInterval(1000 * 12 * 60 * 60);
|
||||||
}
|
}
|
||||||
// TODO: If we have a string, set the initial delay (15 minutes maybe)
|
// TODO: If we have a string, set the initial delay (15 minutes maybe)
|
||||||
|
|
||||||
// Keep vector from reallocating as we fill up to MAX_RECENT_STATUSMESSAGES
|
|
||||||
recentReceived.reserve(MAX_RECENT_STATUSMESSAGES);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual int32_t runOnce() override;
|
virtual int32_t runOnce() override;
|
||||||
|
|
||||||
struct RecentStatus {
|
|
||||||
uint32_t fromNodeId; // mp.from
|
|
||||||
std::string statusText; // incomingMessage.status
|
|
||||||
};
|
|
||||||
|
|
||||||
const std::vector<RecentStatus> &getRecentReceived() const { return recentReceived; }
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/** Called to handle a particular incoming message
|
/** Called to handle a particular incoming message. The cached most-recent
|
||||||
|
* status for each node lives on NodeDB; use nodeDB->copyNodeStatus(num, ...).
|
||||||
*/
|
*/
|
||||||
virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
|
virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
|
||||||
|
|
||||||
private:
|
|
||||||
static constexpr size_t MAX_RECENT_STATUSMESSAGES = 5;
|
|
||||||
std::vector<RecentStatus> recentReceived;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
extern StatusMessageModule *statusMessageModule;
|
extern StatusMessageModule *statusMessageModule;
|
||||||
|
|||||||
@@ -515,6 +515,10 @@ bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPac
|
|||||||
packetPool.release(lastMeasurementPacket);
|
packetPool.release(lastMeasurementPacket);
|
||||||
|
|
||||||
lastMeasurementPacket = packetPool.allocCopy(mp);
|
lastMeasurementPacket = packetPool.allocCopy(mp);
|
||||||
|
|
||||||
|
// Cache the latest env metrics per node on NodeDB so the phone can
|
||||||
|
// pull last-known values across reboots and replays.
|
||||||
|
nodeDB->updateTelemetry(getFrom(&mp), *t, RX_SRC_RADIO);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false; // Let others look at this message also if they want
|
return false; // Let others look at this message also if they want
|
||||||
|
|||||||
@@ -492,12 +492,12 @@ TraceRouteModule::TraceRouteModule()
|
|||||||
const char *TraceRouteModule::getNodeName(NodeNum node)
|
const char *TraceRouteModule::getNodeName(NodeNum node)
|
||||||
{
|
{
|
||||||
meshtastic_NodeInfoLite *info = nodeDB->getMeshNode(node);
|
meshtastic_NodeInfoLite *info = nodeDB->getMeshNode(node);
|
||||||
if (info && info->has_user) {
|
if (nodeInfoLiteHasUser(info)) {
|
||||||
if (strlen(info->user.short_name) > 0) {
|
if (strlen(info->short_name) > 0) {
|
||||||
return info->user.short_name;
|
return info->short_name;
|
||||||
}
|
}
|
||||||
if (strlen(info->user.long_name) > 0) {
|
if (strlen(info->long_name) > 0) {
|
||||||
return info->user.long_name;
|
return info->long_name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1226,9 +1226,9 @@ bool TrafficManagementModule::shouldRespondToNodeInfo(const meshtastic_MeshPacke
|
|||||||
// Fallback only when PSRAM cache is unavailable on this target.
|
// Fallback only when PSRAM cache is unavailable on this target.
|
||||||
// In this mode we use the node-wide table maintained by NodeInfoModule.
|
// In this mode we use the node-wide table maintained by NodeInfoModule.
|
||||||
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(p->to);
|
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(p->to);
|
||||||
if (!node || !node->has_user)
|
if (!nodeInfoLiteHasUser(node))
|
||||||
return false;
|
return false;
|
||||||
cachedUser = TypeConversions::ConvertToUser(node->num, node->user);
|
cachedUser = TypeConversions::ConvertToUser(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sendResponse)
|
if (!sendResponse)
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ void WaypointModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state,
|
|||||||
char distStr[20] = "";
|
char distStr[20] = "";
|
||||||
|
|
||||||
// Get our node, to use our own position
|
// Get our node, to use our own position
|
||||||
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
const meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
|
||||||
|
|
||||||
// Match compass sizing/placement to favorite node screen logic.
|
// Match compass sizing/placement to favorite node screen logic.
|
||||||
const int w = display->getWidth();
|
const int w = display->getWidth();
|
||||||
@@ -147,8 +147,10 @@ void WaypointModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state,
|
|||||||
const char *statusLine2 = nullptr;
|
const char *statusLine2 = nullptr;
|
||||||
|
|
||||||
// Distance only needs our own position fix; compass/bearing additionally needs heading.
|
// Distance only needs our own position fix; compass/bearing additionally needs heading.
|
||||||
if (hasOwnPositionFix) {
|
meshtastic_PositionLite ownPos;
|
||||||
const meshtastic_PositionLite &op = ourNode->position;
|
const bool haveOwnPos = ourNode && nodeDB->copyNodePosition(ourNode->num, ownPos);
|
||||||
|
if (hasOwnPositionFix && haveOwnPos) {
|
||||||
|
const meshtastic_PositionLite &op = ownPos;
|
||||||
const float d =
|
const float d =
|
||||||
GeoCoord::latLongToMeter(DegD(wp.latitude_i), DegD(wp.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i));
|
GeoCoord::latLongToMeter(DegD(wp.latitude_i), DegD(wp.longitude_i), DegD(op.latitude_i), DegD(op.longitude_i));
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -141,7 +141,7 @@ inline void onReceiveProto(char *topic, byte *payload, size_t length)
|
|||||||
const meshtastic_NodeInfoLite *rx = nodeDB->getMeshNode(p->to);
|
const meshtastic_NodeInfoLite *rx = nodeDB->getMeshNode(p->to);
|
||||||
// Only accept PKI messages to us, or if we have both the sender and receiver in our nodeDB, as then it's
|
// Only accept PKI messages to us, or if we have both the sender and receiver in our nodeDB, as then it's
|
||||||
// likely they discovered each other via a channel we have downlink enabled for
|
// likely they discovered each other via a channel we have downlink enabled for
|
||||||
if (isToUs(p.get()) || (tx && tx->has_user && rx && rx->has_user))
|
if (isToUs(p.get()) || (nodeInfoLiteHasUser(tx) && nodeInfoLiteHasUser(rx)))
|
||||||
router->enqueueReceivedMessage(p.release());
|
router->enqueueReceivedMessage(p.release());
|
||||||
} else if (router &&
|
} else if (router &&
|
||||||
perhapsDecode(p.get()) == DecodeState::DECODE_SUCCESS) // ignore messages if we don't have the channel key
|
perhapsDecode(p.get()) == DecodeState::DECODE_SUCCESS) // ignore messages if we don't have the channel key
|
||||||
|
|||||||
@@ -319,10 +319,14 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp,
|
|||||||
// Lambda function for adding a long name to the route
|
// Lambda function for adding a long name to the route
|
||||||
auto addToRoute = [](JSONArray *route, NodeNum num) {
|
auto addToRoute = [](JSONArray *route, NodeNum num) {
|
||||||
char long_name[40] = "Unknown";
|
char long_name[40] = "Unknown";
|
||||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(num);
|
const meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(num);
|
||||||
bool name_known = node ? node->has_user : false;
|
bool name_known = nodeInfoLiteHasUser(node);
|
||||||
if (name_known)
|
if (name_known) {
|
||||||
memcpy(long_name, node->user.long_name, sizeof(long_name));
|
const size_t copy_len =
|
||||||
|
(sizeof(node->long_name) < sizeof(long_name)) ? sizeof(node->long_name) : sizeof(long_name) - 1;
|
||||||
|
memcpy(long_name, node->long_name, copy_len);
|
||||||
|
long_name[copy_len] = '\0';
|
||||||
|
}
|
||||||
route->push_back(new JSONValue(long_name));
|
route->push_back(new JSONValue(long_name));
|
||||||
};
|
};
|
||||||
addToRoute(&route, mp->to); // Started at the original transmitter (destination of response)
|
addToRoute(&route, mp->to); // Started at the original transmitter (destination of response)
|
||||||
|
|||||||
@@ -292,9 +292,13 @@ std::string MeshPacketSerializer::JsonSerialize(const meshtastic_MeshPacket *mp,
|
|||||||
auto addToRoute = [](JsonArray *route, NodeNum num) {
|
auto addToRoute = [](JsonArray *route, NodeNum num) {
|
||||||
char long_name[40] = "Unknown";
|
char long_name[40] = "Unknown";
|
||||||
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(num);
|
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(num);
|
||||||
bool name_known = node ? node->has_user : false;
|
bool name_known = nodeInfoLiteHasUser(node);
|
||||||
if (name_known)
|
if (name_known) {
|
||||||
memcpy(long_name, node->user.long_name, sizeof(long_name));
|
const size_t copy_len =
|
||||||
|
(sizeof(node->long_name) < sizeof(long_name)) ? sizeof(node->long_name) : sizeof(long_name) - 1;
|
||||||
|
memcpy(long_name, node->long_name, copy_len);
|
||||||
|
long_name[copy_len] = '\0';
|
||||||
|
}
|
||||||
route->add(long_name);
|
route->add(long_name);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ void test_DH25519(void)
|
|||||||
void test_PKC(void)
|
void test_PKC(void)
|
||||||
{
|
{
|
||||||
uint8_t private_key[32];
|
uint8_t private_key[32];
|
||||||
meshtastic_UserLite_public_key_t public_key;
|
meshtastic_NodeInfoLite_public_key_t public_key;
|
||||||
uint8_t expected_shared[32];
|
uint8_t expected_shared[32];
|
||||||
uint8_t expected_decrypted[32];
|
uint8_t expected_decrypted[32];
|
||||||
uint8_t radioBytes[128] __attribute__((__aligned__));
|
uint8_t radioBytes[128] __attribute__((__aligned__));
|
||||||
|
|||||||
@@ -641,30 +641,12 @@ void test_sender_zero_substituted(void)
|
|||||||
TEST_ASSERT_TRUE(ph->wasSeenRecently(&p2, false));
|
TEST_ASSERT_TRUE(ph->wasSeenRecently(&p2, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
void test_uninitialized_wasSeenRecently(void)
|
// NOTE: Tests for the !initOk() short-circuit in wasSeenRecently / wasRelayer
|
||||||
{
|
// were removed: the only way to drive that branch from a test is to destruct
|
||||||
// Simulate uninitialized state — create a PacketHistory that looks uninitialized
|
// the object and then call methods on it, which is UB and trips ASAN under
|
||||||
// We can't easily make allocation fail, but we can test the initOk guard with a destructed one
|
// `pio test -e coverage`. In practice the guard can only fire if `new[]` throws
|
||||||
PacketHistory h(4);
|
// (in which case the constructor doesn't return), so the lost coverage is
|
||||||
TEST_ASSERT_TRUE(h.initOk()); // sanity check
|
// purely defensive.
|
||||||
h.~PacketHistory();
|
|
||||||
|
|
||||||
auto p = makePacket(0x1111, 100);
|
|
||||||
TEST_ASSERT_FALSE(h.wasSeenRecently(&p));
|
|
||||||
|
|
||||||
// Reconstruct in place to allow proper destruction
|
|
||||||
new (&h) PacketHistory(4);
|
|
||||||
}
|
|
||||||
|
|
||||||
void test_uninitialized_wasRelayer(void)
|
|
||||||
{
|
|
||||||
PacketHistory h(4);
|
|
||||||
h.~PacketHistory();
|
|
||||||
|
|
||||||
TEST_ASSERT_FALSE(h.wasRelayer(0xAA, 100, 0x1111));
|
|
||||||
|
|
||||||
new (&h) PacketHistory(4);
|
|
||||||
}
|
|
||||||
|
|
||||||
void test_multiple_instances_independent(void)
|
void test_multiple_instances_independent(void)
|
||||||
{
|
{
|
||||||
@@ -819,8 +801,6 @@ void setup()
|
|||||||
// Group 10 — Edge Cases
|
// Group 10 — Edge Cases
|
||||||
RUN_TEST(test_packet_id_zero_not_stored);
|
RUN_TEST(test_packet_id_zero_not_stored);
|
||||||
RUN_TEST(test_sender_zero_substituted);
|
RUN_TEST(test_sender_zero_substituted);
|
||||||
RUN_TEST(test_uninitialized_wasSeenRecently);
|
|
||||||
RUN_TEST(test_uninitialized_wasRelayer);
|
|
||||||
RUN_TEST(test_multiple_instances_independent);
|
RUN_TEST(test_multiple_instances_independent);
|
||||||
|
|
||||||
// Group 11 — Hash Table Stress
|
// Group 11 — Hash Table Stress
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ class MockNodeDB : public NodeDB
|
|||||||
hasCachedNode = true;
|
hasCachedNode = true;
|
||||||
cachedNodeNum = n;
|
cachedNodeNum = n;
|
||||||
cachedNode.num = n;
|
cachedNode.num = n;
|
||||||
cachedNode.has_user = true;
|
cachedNode.bitfield |= NODEINFO_BITFIELD_HAS_USER_MASK;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -494,9 +494,9 @@ static void test_tm_nodeinfo_directResponse_learnsRequestorNodeInfo(void)
|
|||||||
|
|
||||||
TEST_ASSERT_EQUAL_INT(static_cast<int>(ProcessMessage::STOP), static_cast<int>(result));
|
TEST_ASSERT_EQUAL_INT(static_cast<int>(ProcessMessage::STOP), static_cast<int>(result));
|
||||||
TEST_ASSERT_NOT_NULL(requestor);
|
TEST_ASSERT_NOT_NULL(requestor);
|
||||||
TEST_ASSERT_TRUE(requestor->has_user);
|
TEST_ASSERT_TRUE((requestor->bitfield & NODEINFO_BITFIELD_HAS_USER_MASK) != 0);
|
||||||
TEST_ASSERT_EQUAL_STRING("requester-long", requestor->user.long_name);
|
TEST_ASSERT_EQUAL_STRING("requester-long", requestor->long_name);
|
||||||
TEST_ASSERT_EQUAL_STRING("rq", requestor->user.short_name);
|
TEST_ASSERT_EQUAL_STRING("rq", requestor->short_name);
|
||||||
TEST_ASSERT_EQUAL_UINT8(request.channel, requestor->channel);
|
TEST_ASSERT_EQUAL_UINT8(request.channel, requestor->channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,406 @@
|
|||||||
|
// Tests for src/mesh/TypeConversions.cpp covering:
|
||||||
|
// - bitfield bit collapse on store + extraction round-trip
|
||||||
|
// - long_name / short_name truncation at the new max_size:25 / 5 boundaries
|
||||||
|
// - public_key / hw_model / role pass-through
|
||||||
|
// - thin vs bundled NodeInfo emission
|
||||||
|
//
|
||||||
|
// All exercised via the explicit-args overload of ConvertToNodeInfo so we don't
|
||||||
|
// touch the global nodeDB pointer (which isn't initialized in this test env).
|
||||||
|
|
||||||
|
#include "NodeDB.h"
|
||||||
|
#include "TestUtil.h"
|
||||||
|
#include "TypeConversions.h"
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
#include <unity.h>
|
||||||
|
|
||||||
|
void setUp(void) {}
|
||||||
|
void tearDown(void) {}
|
||||||
|
|
||||||
|
// ---------- helpers -----------------------------------------------------------
|
||||||
|
|
||||||
|
static meshtastic_User makeUser(const char *longName, const char *shortName)
|
||||||
|
{
|
||||||
|
meshtastic_User u = meshtastic_User_init_zero;
|
||||||
|
if (longName)
|
||||||
|
strncpy(u.long_name, longName, sizeof(u.long_name) - 1);
|
||||||
|
if (shortName)
|
||||||
|
strncpy(u.short_name, shortName, sizeof(u.short_name) - 1);
|
||||||
|
u.hw_model = meshtastic_HardwareModel_TBEAM;
|
||||||
|
u.role = meshtastic_Config_DeviceConfig_Role_CLIENT;
|
||||||
|
return u;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- has_user / id / macaddr ------------------------------------------
|
||||||
|
|
||||||
|
void test_copy_user_sets_has_user_bit(void)
|
||||||
|
{
|
||||||
|
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||||
|
meshtastic_User u = makeUser("Kevin Hester", "kh");
|
||||||
|
TEST_ASSERT_FALSE((lite.bitfield & NODEINFO_BITFIELD_HAS_USER_MASK) != 0);
|
||||||
|
TypeConversions::CopyUserToNodeInfoLite(&lite, u);
|
||||||
|
TEST_ASSERT_TRUE((lite.bitfield & NODEINFO_BITFIELD_HAS_USER_MASK) != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_convert_to_user_id_derived_from_nodenum(void)
|
||||||
|
{
|
||||||
|
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||||
|
lite.num = 0x12345678;
|
||||||
|
meshtastic_User u = TypeConversions::ConvertToUser(&lite);
|
||||||
|
TEST_ASSERT_EQUAL_STRING("!12345678", u.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_convert_to_user_zero_fills_macaddr(void)
|
||||||
|
{
|
||||||
|
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||||
|
lite.num = 0xCAFEBABE;
|
||||||
|
meshtastic_User u = TypeConversions::ConvertToUser(&lite);
|
||||||
|
uint8_t zeros[sizeof(u.macaddr)] = {0};
|
||||||
|
TEST_ASSERT_EQUAL_MEMORY(zeros, u.macaddr, sizeof(u.macaddr));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- long_name truncation ---------------------------------------------
|
||||||
|
|
||||||
|
void test_long_name_short_passes_through(void)
|
||||||
|
{
|
||||||
|
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||||
|
meshtastic_User u = makeUser("Kevin Hester", "kh"); // 12 chars, fits
|
||||||
|
TypeConversions::CopyUserToNodeInfoLite(&lite, u);
|
||||||
|
TEST_ASSERT_EQUAL_STRING("Kevin Hester", lite.long_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_long_name_exact_24_fits(void)
|
||||||
|
{
|
||||||
|
// 24 chars -> stored as 24 chars + NUL inside char[25].
|
||||||
|
const char *exact24 = "abcdefghijklmnopqrstuvwx";
|
||||||
|
TEST_ASSERT_EQUAL_INT(24, (int)strlen(exact24));
|
||||||
|
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||||
|
meshtastic_User u = makeUser(exact24, "ex");
|
||||||
|
TypeConversions::CopyUserToNodeInfoLite(&lite, u);
|
||||||
|
TEST_ASSERT_EQUAL_STRING(exact24, lite.long_name);
|
||||||
|
TEST_ASSERT_EQUAL_INT(24, (int)strlen(lite.long_name));
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_long_name_truncates_when_too_long(void)
|
||||||
|
{
|
||||||
|
// 33 chars in, must fit in 24 + NUL.
|
||||||
|
const char *tooLong = "North-County Search & Rescue Base";
|
||||||
|
TEST_ASSERT_EQUAL_INT(33, (int)strlen(tooLong));
|
||||||
|
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||||
|
meshtastic_User u = makeUser(tooLong, "nc");
|
||||||
|
TypeConversions::CopyUserToNodeInfoLite(&lite, u);
|
||||||
|
TEST_ASSERT_EQUAL_INT(24, (int)strlen(lite.long_name));
|
||||||
|
TEST_ASSERT_EQUAL_STRING_LEN(tooLong, lite.long_name, 24);
|
||||||
|
TEST_ASSERT_EQUAL_INT('\0', lite.long_name[24]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_long_name_round_trip_to_wire(void)
|
||||||
|
{
|
||||||
|
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||||
|
meshtastic_User in = makeUser("Mountain Repeater Site E", "mr"); // exactly 24
|
||||||
|
TypeConversions::CopyUserToNodeInfoLite(&lite, in);
|
||||||
|
meshtastic_User out = TypeConversions::ConvertToUser(&lite);
|
||||||
|
TEST_ASSERT_EQUAL_STRING(in.long_name, out.long_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_long_name_truncated_utf8_boundary_sanitized(void)
|
||||||
|
{
|
||||||
|
// Suffix the 24th byte with the start of a 4-byte emoji; truncation should
|
||||||
|
// leave the dangling bytes for sanitizeUtf8 to replace with '?'.
|
||||||
|
char input[40] = {0};
|
||||||
|
memset(input, 'a', 22); // 22 ASCII
|
||||||
|
input[22] = static_cast<char>(0xF0); // emoji lead byte at position 22
|
||||||
|
input[23] = static_cast<char>(0x9F); // continuation
|
||||||
|
input[24] = static_cast<char>(0xA4); // continuation - past the cap
|
||||||
|
input[25] = static_cast<char>(0x96); // continuation - past the cap
|
||||||
|
input[26] = '\0';
|
||||||
|
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||||
|
meshtastic_User u = makeUser(input, "u8");
|
||||||
|
TypeConversions::CopyUserToNodeInfoLite(&lite, u);
|
||||||
|
// 24 bytes survive plus NUL. sanitizeUtf8 should have turned the dangling
|
||||||
|
// multi-byte head into '?' since its continuation bytes were chopped.
|
||||||
|
TEST_ASSERT_EQUAL_INT(24, (int)strlen(lite.long_name));
|
||||||
|
for (int i = 0; i < 22; ++i)
|
||||||
|
TEST_ASSERT_EQUAL_INT('a', lite.long_name[i]);
|
||||||
|
// The 4-byte sequence got truncated mid-codepoint; sanitizeUtf8 replaces
|
||||||
|
// any invalid lead/continuation byte with '?'.
|
||||||
|
TEST_ASSERT_EQUAL_INT('?', lite.long_name[22]);
|
||||||
|
TEST_ASSERT_EQUAL_INT('?', lite.long_name[23]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- short_name truncation --------------------------------------------
|
||||||
|
|
||||||
|
void test_short_name_passes_through(void)
|
||||||
|
{
|
||||||
|
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||||
|
meshtastic_User u = makeUser("Test", "abcd"); // 4 chars, fits short_name[5]
|
||||||
|
TypeConversions::CopyUserToNodeInfoLite(&lite, u);
|
||||||
|
TEST_ASSERT_EQUAL_STRING("abcd", lite.short_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_short_name_truncates_when_too_long(void)
|
||||||
|
{
|
||||||
|
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||||
|
meshtastic_User u = makeUser("Test", "abcdefgh");
|
||||||
|
TypeConversions::CopyUserToNodeInfoLite(&lite, u);
|
||||||
|
TEST_ASSERT_EQUAL_INT(4, (int)strlen(lite.short_name));
|
||||||
|
TEST_ASSERT_EQUAL_INT('\0', lite.short_name[4]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- bitfield collapse + round-trip per bool --------------------------
|
||||||
|
|
||||||
|
void test_bitfield_is_licensed_round_trip(void)
|
||||||
|
{
|
||||||
|
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||||
|
meshtastic_User in = makeUser("a", "a");
|
||||||
|
in.is_licensed = true;
|
||||||
|
TypeConversions::CopyUserToNodeInfoLite(&lite, in);
|
||||||
|
TEST_ASSERT_TRUE((lite.bitfield & NODEINFO_BITFIELD_IS_LICENSED_MASK) != 0);
|
||||||
|
meshtastic_User out = TypeConversions::ConvertToUser(&lite);
|
||||||
|
TEST_ASSERT_TRUE(out.is_licensed);
|
||||||
|
|
||||||
|
in.is_licensed = false;
|
||||||
|
meshtastic_NodeInfoLite lite2 = meshtastic_NodeInfoLite_init_default;
|
||||||
|
TypeConversions::CopyUserToNodeInfoLite(&lite2, in);
|
||||||
|
TEST_ASSERT_FALSE((lite2.bitfield & NODEINFO_BITFIELD_IS_LICENSED_MASK) != 0);
|
||||||
|
meshtastic_User out2 = TypeConversions::ConvertToUser(&lite2);
|
||||||
|
TEST_ASSERT_FALSE(out2.is_licensed);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_bitfield_unmessagable_present_and_true(void)
|
||||||
|
{
|
||||||
|
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||||
|
meshtastic_User in = makeUser("a", "a");
|
||||||
|
in.has_is_unmessagable = true;
|
||||||
|
in.is_unmessagable = true;
|
||||||
|
TypeConversions::CopyUserToNodeInfoLite(&lite, in);
|
||||||
|
TEST_ASSERT_TRUE((lite.bitfield & NODEINFO_BITFIELD_HAS_IS_UNMESSAGABLE_MASK) != 0);
|
||||||
|
TEST_ASSERT_TRUE((lite.bitfield & NODEINFO_BITFIELD_IS_UNMESSAGABLE_MASK) != 0);
|
||||||
|
meshtastic_User out = TypeConversions::ConvertToUser(&lite);
|
||||||
|
TEST_ASSERT_TRUE(out.has_is_unmessagable);
|
||||||
|
TEST_ASSERT_TRUE(out.is_unmessagable);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_bitfield_unmessagable_present_but_false(void)
|
||||||
|
{
|
||||||
|
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||||
|
meshtastic_User in = makeUser("a", "a");
|
||||||
|
in.has_is_unmessagable = true;
|
||||||
|
in.is_unmessagable = false;
|
||||||
|
TypeConversions::CopyUserToNodeInfoLite(&lite, in);
|
||||||
|
TEST_ASSERT_TRUE((lite.bitfield & NODEINFO_BITFIELD_HAS_IS_UNMESSAGABLE_MASK) != 0);
|
||||||
|
TEST_ASSERT_FALSE((lite.bitfield & NODEINFO_BITFIELD_IS_UNMESSAGABLE_MASK) != 0);
|
||||||
|
meshtastic_User out = TypeConversions::ConvertToUser(&lite);
|
||||||
|
TEST_ASSERT_TRUE(out.has_is_unmessagable);
|
||||||
|
TEST_ASSERT_FALSE(out.is_unmessagable);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_bitfield_unmessagable_absent(void)
|
||||||
|
{
|
||||||
|
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||||
|
meshtastic_User in = makeUser("a", "a");
|
||||||
|
in.has_is_unmessagable = false;
|
||||||
|
in.is_unmessagable = true; // explicitly true to make sure absence still wins
|
||||||
|
TypeConversions::CopyUserToNodeInfoLite(&lite, in);
|
||||||
|
TEST_ASSERT_FALSE((lite.bitfield & NODEINFO_BITFIELD_HAS_IS_UNMESSAGABLE_MASK) != 0);
|
||||||
|
TEST_ASSERT_FALSE((lite.bitfield & NODEINFO_BITFIELD_IS_UNMESSAGABLE_MASK) != 0);
|
||||||
|
meshtastic_User out = TypeConversions::ConvertToUser(&lite);
|
||||||
|
TEST_ASSERT_FALSE(out.has_is_unmessagable);
|
||||||
|
TEST_ASSERT_FALSE(out.is_unmessagable);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_copy_user_preserves_unrelated_bits(void)
|
||||||
|
{
|
||||||
|
// Pre-set is_muted and is_key_manually_verified - CopyUserToNodeInfoLite
|
||||||
|
// must not stomp them.
|
||||||
|
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||||
|
lite.bitfield = NODEINFO_BITFIELD_IS_MUTED_MASK | NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK |
|
||||||
|
NODEINFO_BITFIELD_VIA_MQTT_MASK | NODEINFO_BITFIELD_IS_FAVORITE_MASK | NODEINFO_BITFIELD_IS_IGNORED_MASK;
|
||||||
|
meshtastic_User in = makeUser("a", "a");
|
||||||
|
TypeConversions::CopyUserToNodeInfoLite(&lite, in);
|
||||||
|
TEST_ASSERT_TRUE((lite.bitfield & NODEINFO_BITFIELD_IS_MUTED_MASK) != 0);
|
||||||
|
TEST_ASSERT_TRUE((lite.bitfield & NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK) != 0);
|
||||||
|
TEST_ASSERT_TRUE((lite.bitfield & NODEINFO_BITFIELD_VIA_MQTT_MASK) != 0);
|
||||||
|
TEST_ASSERT_TRUE((lite.bitfield & NODEINFO_BITFIELD_IS_FAVORITE_MASK) != 0);
|
||||||
|
TEST_ASSERT_TRUE((lite.bitfield & NODEINFO_BITFIELD_IS_IGNORED_MASK) != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_bitfield_bits_are_independent(void)
|
||||||
|
{
|
||||||
|
// Set just is_licensed, verify only that bit (and HAS_USER) is set.
|
||||||
|
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||||
|
meshtastic_User in = makeUser("a", "a");
|
||||||
|
in.is_licensed = true;
|
||||||
|
TypeConversions::CopyUserToNodeInfoLite(&lite, in);
|
||||||
|
const uint32_t expected = NODEINFO_BITFIELD_HAS_USER_MASK | NODEINFO_BITFIELD_IS_LICENSED_MASK;
|
||||||
|
TEST_ASSERT_EQUAL_HEX32(expected, lite.bitfield);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- public_key / hw_model / role pass-through ------------------------
|
||||||
|
|
||||||
|
void test_public_key_round_trip(void)
|
||||||
|
{
|
||||||
|
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||||
|
meshtastic_User in = makeUser("a", "a");
|
||||||
|
in.public_key.size = 32;
|
||||||
|
for (int i = 0; i < 32; ++i)
|
||||||
|
in.public_key.bytes[i] = (uint8_t)(i ^ 0xA5);
|
||||||
|
TypeConversions::CopyUserToNodeInfoLite(&lite, in);
|
||||||
|
TEST_ASSERT_EQUAL_INT(32, lite.public_key.size);
|
||||||
|
TEST_ASSERT_EQUAL_MEMORY(in.public_key.bytes, lite.public_key.bytes, 32);
|
||||||
|
meshtastic_User out = TypeConversions::ConvertToUser(&lite);
|
||||||
|
TEST_ASSERT_EQUAL_INT(32, out.public_key.size);
|
||||||
|
TEST_ASSERT_EQUAL_MEMORY(in.public_key.bytes, out.public_key.bytes, 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_hw_model_and_role_round_trip(void)
|
||||||
|
{
|
||||||
|
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||||
|
meshtastic_User in = makeUser("a", "a");
|
||||||
|
in.hw_model = meshtastic_HardwareModel_HELTEC_V3;
|
||||||
|
in.role = meshtastic_Config_DeviceConfig_Role_ROUTER;
|
||||||
|
TypeConversions::CopyUserToNodeInfoLite(&lite, in);
|
||||||
|
TEST_ASSERT_EQUAL_INT(meshtastic_HardwareModel_HELTEC_V3, lite.hw_model);
|
||||||
|
TEST_ASSERT_EQUAL_INT(meshtastic_Config_DeviceConfig_Role_ROUTER, lite.role);
|
||||||
|
meshtastic_User out = TypeConversions::ConvertToUser(&lite);
|
||||||
|
TEST_ASSERT_EQUAL_INT(meshtastic_HardwareModel_HELTEC_V3, out.hw_model);
|
||||||
|
TEST_ASSERT_EQUAL_INT(meshtastic_Config_DeviceConfig_Role_ROUTER, out.role);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- ConvertToNodeInfo (3-arg) ----------------------------------------
|
||||||
|
|
||||||
|
void test_convert_to_node_info_thin_omits_position_and_metrics(void)
|
||||||
|
{
|
||||||
|
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||||
|
lite.num = 0xAA;
|
||||||
|
meshtastic_NodeInfo info = TypeConversions::ConvertToNodeInfoThin(&lite);
|
||||||
|
TEST_ASSERT_FALSE(info.has_position);
|
||||||
|
TEST_ASSERT_FALSE(info.has_device_metrics);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_convert_to_node_info_3arg_with_position(void)
|
||||||
|
{
|
||||||
|
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||||
|
lite.num = 0xAA;
|
||||||
|
meshtastic_PositionLite pos = meshtastic_PositionLite_init_default;
|
||||||
|
pos.latitude_i = 374200000;
|
||||||
|
pos.longitude_i = -1221000000;
|
||||||
|
pos.altitude = 30;
|
||||||
|
pos.time = 12345;
|
||||||
|
meshtastic_NodeInfo info = TypeConversions::ConvertToNodeInfo(&lite, &pos, nullptr);
|
||||||
|
TEST_ASSERT_TRUE(info.has_position);
|
||||||
|
TEST_ASSERT_FALSE(info.has_device_metrics);
|
||||||
|
TEST_ASSERT_EQUAL_INT32(374200000, info.position.latitude_i);
|
||||||
|
TEST_ASSERT_EQUAL_INT32(-1221000000, info.position.longitude_i);
|
||||||
|
TEST_ASSERT_EQUAL_INT32(30, info.position.altitude);
|
||||||
|
TEST_ASSERT_EQUAL_UINT32(12345, info.position.time);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_convert_to_node_info_3arg_with_metrics(void)
|
||||||
|
{
|
||||||
|
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||||
|
lite.num = 0xAA;
|
||||||
|
meshtastic_DeviceMetrics dm = meshtastic_DeviceMetrics_init_default;
|
||||||
|
dm.battery_level = 88;
|
||||||
|
dm.has_battery_level = true;
|
||||||
|
dm.voltage = 3.71f;
|
||||||
|
dm.has_voltage = true;
|
||||||
|
meshtastic_NodeInfo info = TypeConversions::ConvertToNodeInfo(&lite, nullptr, &dm);
|
||||||
|
TEST_ASSERT_FALSE(info.has_position);
|
||||||
|
TEST_ASSERT_TRUE(info.has_device_metrics);
|
||||||
|
TEST_ASSERT_EQUAL_INT(88, info.device_metrics.battery_level);
|
||||||
|
TEST_ASSERT_TRUE(info.device_metrics.has_battery_level);
|
||||||
|
TEST_ASSERT_EQUAL_FLOAT(3.71f, info.device_metrics.voltage);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_convert_to_node_info_3arg_null_inputs(void)
|
||||||
|
{
|
||||||
|
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||||
|
lite.num = 0xAA;
|
||||||
|
meshtastic_NodeInfo info = TypeConversions::ConvertToNodeInfo(&lite, nullptr, nullptr);
|
||||||
|
TEST_ASSERT_FALSE(info.has_position);
|
||||||
|
TEST_ASSERT_FALSE(info.has_device_metrics);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_convert_to_node_info_extracts_bitfield_bools(void)
|
||||||
|
{
|
||||||
|
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||||
|
lite.num = 0xBB;
|
||||||
|
lite.bitfield = NODEINFO_BITFIELD_VIA_MQTT_MASK | NODEINFO_BITFIELD_IS_FAVORITE_MASK | NODEINFO_BITFIELD_IS_IGNORED_MASK |
|
||||||
|
NODEINFO_BITFIELD_IS_KEY_MANUALLY_VERIFIED_MASK | NODEINFO_BITFIELD_IS_MUTED_MASK;
|
||||||
|
meshtastic_NodeInfo info = TypeConversions::ConvertToNodeInfo(&lite, nullptr, nullptr);
|
||||||
|
TEST_ASSERT_TRUE(info.via_mqtt);
|
||||||
|
TEST_ASSERT_TRUE(info.is_favorite);
|
||||||
|
TEST_ASSERT_TRUE(info.is_ignored);
|
||||||
|
TEST_ASSERT_TRUE(info.is_key_manually_verified);
|
||||||
|
TEST_ASSERT_TRUE(info.is_muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_convert_to_node_info_extracts_bitfield_bools_none_set(void)
|
||||||
|
{
|
||||||
|
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||||
|
lite.num = 0xBB;
|
||||||
|
lite.bitfield = 0;
|
||||||
|
meshtastic_NodeInfo info = TypeConversions::ConvertToNodeInfo(&lite, nullptr, nullptr);
|
||||||
|
TEST_ASSERT_FALSE(info.via_mqtt);
|
||||||
|
TEST_ASSERT_FALSE(info.is_favorite);
|
||||||
|
TEST_ASSERT_FALSE(info.is_ignored);
|
||||||
|
TEST_ASSERT_FALSE(info.is_key_manually_verified);
|
||||||
|
TEST_ASSERT_FALSE(info.is_muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_convert_to_node_info_user_only_when_has_user_bit_set(void)
|
||||||
|
{
|
||||||
|
meshtastic_NodeInfoLite lite = meshtastic_NodeInfoLite_init_default;
|
||||||
|
lite.num = 0x01;
|
||||||
|
strcpy(lite.long_name, "Alpha");
|
||||||
|
strcpy(lite.short_name, "A");
|
||||||
|
// No HAS_USER bit -> user fields ignored on emit.
|
||||||
|
meshtastic_NodeInfo info = TypeConversions::ConvertToNodeInfo(&lite, nullptr, nullptr);
|
||||||
|
TEST_ASSERT_FALSE(info.has_user);
|
||||||
|
|
||||||
|
lite.bitfield |= NODEINFO_BITFIELD_HAS_USER_MASK;
|
||||||
|
meshtastic_NodeInfo info2 = TypeConversions::ConvertToNodeInfo(&lite, nullptr, nullptr);
|
||||||
|
TEST_ASSERT_TRUE(info2.has_user);
|
||||||
|
TEST_ASSERT_EQUAL_STRING("Alpha", info2.user.long_name);
|
||||||
|
TEST_ASSERT_EQUAL_STRING("A", info2.user.short_name);
|
||||||
|
TEST_ASSERT_EQUAL_STRING("!00000001", info2.user.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- entry point -------------------------------------------------------
|
||||||
|
|
||||||
|
void setup()
|
||||||
|
{
|
||||||
|
delay(10);
|
||||||
|
delay(2000);
|
||||||
|
initializeTestEnvironment();
|
||||||
|
UNITY_BEGIN();
|
||||||
|
RUN_TEST(test_copy_user_sets_has_user_bit);
|
||||||
|
RUN_TEST(test_convert_to_user_id_derived_from_nodenum);
|
||||||
|
RUN_TEST(test_convert_to_user_zero_fills_macaddr);
|
||||||
|
RUN_TEST(test_long_name_short_passes_through);
|
||||||
|
RUN_TEST(test_long_name_exact_24_fits);
|
||||||
|
RUN_TEST(test_long_name_truncates_when_too_long);
|
||||||
|
RUN_TEST(test_long_name_round_trip_to_wire);
|
||||||
|
RUN_TEST(test_long_name_truncated_utf8_boundary_sanitized);
|
||||||
|
RUN_TEST(test_short_name_passes_through);
|
||||||
|
RUN_TEST(test_short_name_truncates_when_too_long);
|
||||||
|
RUN_TEST(test_bitfield_is_licensed_round_trip);
|
||||||
|
RUN_TEST(test_bitfield_unmessagable_present_and_true);
|
||||||
|
RUN_TEST(test_bitfield_unmessagable_present_but_false);
|
||||||
|
RUN_TEST(test_bitfield_unmessagable_absent);
|
||||||
|
RUN_TEST(test_copy_user_preserves_unrelated_bits);
|
||||||
|
RUN_TEST(test_bitfield_bits_are_independent);
|
||||||
|
RUN_TEST(test_public_key_round_trip);
|
||||||
|
RUN_TEST(test_hw_model_and_role_round_trip);
|
||||||
|
RUN_TEST(test_convert_to_node_info_thin_omits_position_and_metrics);
|
||||||
|
RUN_TEST(test_convert_to_node_info_3arg_with_position);
|
||||||
|
RUN_TEST(test_convert_to_node_info_3arg_with_metrics);
|
||||||
|
RUN_TEST(test_convert_to_node_info_3arg_null_inputs);
|
||||||
|
RUN_TEST(test_convert_to_node_info_extracts_bitfield_bools);
|
||||||
|
RUN_TEST(test_convert_to_node_info_extracts_bitfield_bools_none_set);
|
||||||
|
RUN_TEST(test_convert_to_node_info_user_only_when_has_user_bit_set);
|
||||||
|
exit(UNITY_END());
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {}
|
||||||
Reference in New Issue
Block a user