fix(mesh): coerce coordinate traffic to the position channel on event builds (#11545)

Under USERPREFS_BLOCK_POSITION_ON_EVENT_CHANNEL every coordinate packet a
client aimed at the event channel was rejected with the "Location sharing is
disabled on this channel" notification - including the phone's own location
feed. Both apps hand a GPS-less node its fix as a POSITION_APP packet
addressed to the node itself on channel 0; that packet never leaves the
device (Router::sendLocal delivers it locally) but resolved to the event
channel and was dropped before PositionModule saw it. Result: the toast on
every location tick, and nodes without a GPS never learned a position to
share on their private channel.

Position traffic now converges on the position channel - findPositionChannel(),
the first channel with non-zero on-wire precision, which is never the event
channel:

- From-us-to-us coordinate packets are exempt from the event block.
- Local coordinate sends aimed at the event channel (phone share-location,
  request-position, waypoints, any module/UI originator) are moved onto the
  position channel in Router::sendLocal and PhoneAPI instead of rejected. The
  client notification is only sent when no channel carries positions at all.
- A position request DM'd to us on the event channel is answered on the
  position channel at that channel's precision (request_id preserved, same
  reply throttle); the requester's coordinates are still not stored,
  forwarded, relayed or published. want_response from the bitfield is merged
  before the event-channel decode short-circuit so such requests are seen.
- PositionModule::sendOurPosition, positionUnchangedSinceLastSend and
  MeshService::trySendPosition use the shared helper instead of three copies
  of the same walk.

Non-event builds are unaffected: the coercion compiles out and the helper
matches the previous walk.

Tests: coverage-event-policy (test_event_channel_phone_api,
test_event_channel_router, test_position_precision, test_mqtt,
test_nexthop_routing) and the same suites with the policy off.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Ben MeadorsandClaude Fable 5 authored and GitHub committed 2026-08-18 21:58:51 +00:00
1 parent 8fe246e250
commit a5fc95f774
11 files changed
+411 -40

No files matched your search

+28 -10
View File
@@ -289,6 +289,27 @@ meshtastic_MeshPacket *PositionModule::allocReply()
return reply;
}
void PositionModule::replyOnPositionChannel(const meshtastic_MeshPacket &req)
{
uint8_t positionChannel;
if (!findPositionChannel(positionChannel)) {
LOG_DEBUG("Skip position reply to 0x%08x: position sharing disabled on all channels", getFrom(&req));
return;
}
if (!service)
return;
precision = getPositionPrecisionForChannel(positionChannel);
meshtastic_MeshPacket *reply = allocReply(); // reply throttle + precision-0/no-fix guards live here
if (!reply)
return;
setReplyTo(reply, req);
reply->channel = positionChannel; // not the channel the request came in on
LOG_INFO("Reply to position request from 0x%08x on position channel %u", getFrom(&req), positionChannel);
service->sendToMesh(reply);
}
meshtastic_MeshPacket *PositionModule::allocAtakPli()
{
LOG_INFO("Send TAK V2 PLI packet");
@@ -374,12 +395,11 @@ void PositionModule::sendOurPosition()
currentGeneration = radioGeneration;
// If we changed channels, ask everyone else for their latest info
for (uint8_t channelNum = 0; channelNum < 8; channelNum++) {
if (getPositionPrecisionForChannel(channelNum) != 0) {
LOG_INFO("Send pos@%x:6 to mesh (wantReplies=%d)", localPosition.timestamp, requestReplies);
sendOurPosition(NODENUM_BROADCAST, requestReplies, channelNum);
return;
}
uint8_t positionChannel;
if (findPositionChannel(positionChannel)) {
LOG_INFO("Send pos@%x:6 to mesh (wantReplies=%d)", localPosition.timestamp, requestReplies);
sendOurPosition(NODENUM_BROADCAST, requestReplies, positionChannel);
return;
}
LOG_INFO("Skip pos@%x:6 broadcast; position sharing disabled on all channels", localPosition.timestamp);
}
@@ -467,12 +487,10 @@ bool PositionModule::positionUnchangedSinceLastSend(const meshtastic_PositionLit
// precision). Default nodes gauge movement at that on-wire (public-clamped) resolution;
// trackers use their own configured (unclamped) precision so finer moves still count.
uint32_t precisionBits = 0;
for (uint8_t ch = 0; ch < 8; ch++) {
if (getPositionPrecisionForChannel(ch) == 0)
continue;
uint8_t ch;
if (findPositionChannel(ch)) {
precisionBits =
useConfiguredPrecision ? getPositionPrecisionForChannel(channels.getByIndex(ch)) : getPositionPrecisionForChannel(ch);
break;
}
return positionWithinPrecisionCell(selfPos.latitude_i, selfPos.longitude_i, lastGpsLatitude, lastGpsLongitude, precisionBits);