feat(companion): live location sharing via [LOC] messages

Share and track positions over the mesh through chat messages, separate
from (and parallel to) the 0-hop auto-advert.

Receive / tracking:
- geo::parseLocShare + [LOC] tag (sibling to [WAY]); LiveTrackStore (16
  slots, DM=pubkey/verified, channel=name/best-effort, 20-min expiry).
- MyMesh parses [LOC] on DM + channel receive → UITask live-track table.
- NearbyScreen overlays shares onto contacts (fresher pin) and lists
  non-contact senders; markers (*=DM, ~=chan) + "Loc:" age/verified line.

Send:
- Map menu "Share my pos" (one-shot) → Messages recipient picker.
- New Tools > Live Share tool: Receive (Track loc) + Send (Auto, Target,
  Move/Min gap/Heartbeat, Share now). Target chosen via the Messages
  picker (channel/DM). Movement-gated auto-send engine in UITask loop.

Map / navigation:
- Trail map shows tracked contacts as ◆ markers (ICON_MAP_CONTACT).
- Home "Map" page: mini-map preview + status line; ENTER opens the tool,
  Cancel returns Home.

Prefs: track_shared_loc + loc_share_* (NodePrefs schema 0xC0DE0012).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
MarekZegare4
2026-06-22 10:13:09 +02:00
co-authored by Claude Opus 4.8
parent 29deaaad44
commit d78e74d4cd
14 changed files with 816 additions and 21 deletions
+24
View File
@@ -1,5 +1,6 @@
#include "MyMesh.h"
#include "MsgExpand.h"
#include "GeoUtils.h"
#include "Features.h"
#include <Arduino.h> // needed for PlatformIO
@@ -615,6 +616,12 @@ void MyMesh::onMessageRecv(const ContactInfo &from, mesh::Packet *pkt, uint32_t
markConnectionActive(from); // in case this is from a server, and we have a connection
queueMessage(from, TXT_TYPE_PLAIN, pkt, sender_timestamp, NULL, 0, text);
// Live position share: a verified DM, so key the track by the sender's pubkey.
int32_t loc_lat, loc_lon;
if (_ui && geo::parseLocShare(text, loc_lat, loc_lon)) {
_ui->onSharedLocation(from.id.pub_key, from.name, loc_lat, loc_lon, sender_timestamp, true);
}
// hop count of the received message. getPathHashCount() (low 6 bits of path_len)
// is the number of repeaters traversed — the same value the mesh uses for flood
// retransmit priority. 0 = heard directly. (Raw path_len is a size/count
@@ -689,6 +696,23 @@ void MyMesh::onChannelMessageRecv(const mesh::GroupChannel &channel, mesh::Packe
channel_name = channel_details.name;
}
if (_ui) _ui->newMsg(path_len, channel_name, text, offline_queue_len, 0);
// Live position share on a channel. The sender's identity here is only the
// unsigned "name: msg" prefix (no pubkey), so track it by name — best-effort
// and unverified. parseLocShare requires an explicit [LOC] tag, so ordinary
// chatter is ignored.
int32_t loc_lat, loc_lon;
if (_ui && geo::parseLocShare(text, loc_lat, loc_lon)) {
char sender[32] = {0};
const char* sep = strstr(text, ": ");
if (sep && sep > text) {
int n = (int)(sep - text);
if (n > (int)sizeof(sender) - 1) n = sizeof(sender) - 1;
memcpy(sender, text, n);
sender[n] = '\0';
}
_ui->onSharedLocation(nullptr, sender[0] ? sender : "?", loc_lat, loc_lon, timestamp, false);
}
#endif
// hop count for !hops (see onMessageRecv); not the wire path_len above.