* Position: stream our own position to the phone/UI while mesh sharing is opt-in Position broadcasts became opt-in in 2.8 (#10929): with every public channel at position_precision 0, sendOurPosition() finds no eligible channel and returns without queueing anything, so the connected phone or on-device UI never sees the node's own GPS fix ("GPS looks dead" on standalone MUI devices even though the receiver has a lock). Mirror device telemetry's local delivery: once a minute, when the toPhone queue is idle, stream our own position to the connected client at full precision. The packet is handed straight to sendToPhone() and never touches the mesh, so the per-channel opt-in and the public-channel precision clamp still govern everything on the air. Also stop logging "Send pos ... to mesh" before the channel scan has found an eligible channel; when sharing is disabled everywhere the skip is now logged explicitly instead of pretending a send happened. The cadence gate is a pure static (shouldSendPositionToPhone) alongside the existing broadcast-policy helpers, with unit tests covering the first-send, cadence, gating, and millis() rollover cases. * Review: only advance phone cadence on a queued packet; drop the ms==0 sentinel sendOurPositionToPhone() now reports whether a packet actually reached the phone queue, and runOnce() stamps the cadence only on success, so a guard or allocation failure retries on the next tick instead of waiting out a minute. The never-sent state is a dedicated hasSentPositionToPhone flag rather than lastPhoneSendMs == 0, so a send stamped exactly at millis() == 0 still holds the cadence. New regression test covers that case; existing cases updated to the explicit flag. * Review: align the rollover test fixture with its documented elapsed times lastSent now sits exactly 30,000 ms before the uint32 wrap, so the two cases are precisely 70,000 ms (sends) and 40,000 ms (held) - the previous comments claimed 70s/20s against actual elapsed values of 70,001/40,001 ms.
113 lines
5.4 KiB
C++
113 lines
5.4 KiB
C++
#pragma once
|
|
#include "Default.h"
|
|
#include "ProtobufModule.h"
|
|
#include "concurrency/OSThread.h"
|
|
|
|
/**
|
|
* Position module for sending/receiving positions into the mesh
|
|
*/
|
|
class PositionModule : public ProtobufModule<meshtastic_Position>, private concurrency::OSThread
|
|
{
|
|
CallbackObserver<PositionModule, const meshtastic::Status *> nodeStatusObserver =
|
|
CallbackObserver<PositionModule, const meshtastic::Status *>(this, &PositionModule::handleStatusUpdate);
|
|
|
|
/// The id of the last packet we sent, to allow us to cancel it if we make something fresher
|
|
PacketId prevPacketId = 0;
|
|
|
|
/// We limit our GPS broadcasts to a max rate
|
|
uint32_t lastGpsSend = 0;
|
|
|
|
// Store the latest good lat / long
|
|
int32_t lastGpsLatitude = 0;
|
|
int32_t lastGpsLongitude = 0;
|
|
|
|
/// We force a rebroadcast if the radio settings change
|
|
uint32_t currentGeneration = 0;
|
|
|
|
public:
|
|
/** Constructor
|
|
* name is for debugging output
|
|
*/
|
|
PositionModule();
|
|
|
|
/**
|
|
* Send our position into the mesh
|
|
*/
|
|
void sendOurPosition(NodeNum dest, bool wantReplies = false, uint8_t channel = 0);
|
|
void sendOurPosition();
|
|
|
|
void handleNewPosition();
|
|
|
|
// Pure broadcast-policy helpers, split out so they're unit-testable without the module.
|
|
// True when two coordinates truncate to the same precision cell (so a re-broadcast would be a
|
|
// duplicate). precision 0 or >=32 returns false: no coarse cell to hold within, never suppress.
|
|
static bool positionWithinPrecisionCell(int32_t aLat, int32_t aLon, int32_t bLat, int32_t bLon, uint32_t precision);
|
|
// Effective min interval: stationary positions are held to stationaryFloorMs (when that is the
|
|
// longer of the two); otherwise the normal configured interval.
|
|
static uint32_t effectiveBroadcastIntervalMs(uint32_t configuredIntervalMs, bool stationary, uint32_t stationaryFloorMs);
|
|
// Pure local-play policy: stream our own position to the phone/UI when we have a valid
|
|
// position, the phone queue is idle, and the cadence has elapsed. everSentToPhone (rather
|
|
// than a lastSentMs sentinel) marks the never-sent state, so a send stamped at millis()==0
|
|
// still honors the cadence.
|
|
static bool shouldSendPositionToPhone(bool hasValidPosition, bool phoneQueueEmpty, bool everSentToPhone, uint32_t nowMs,
|
|
uint32_t lastSentMs, uint32_t intervalMs);
|
|
|
|
protected:
|
|
/** Called to handle a particular incoming message
|
|
|
|
@return true if you've guaranteed you've handled this message and no other handlers should be considered for it
|
|
*/
|
|
virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Position *p) override;
|
|
|
|
virtual void alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtastic_Position *p) override;
|
|
|
|
/** Messages can be received that have the want_response bit set. If set, this callback will be invoked
|
|
* so that subclasses can (optionally) send a response back to the original sender. */
|
|
virtual meshtastic_MeshPacket *allocReply() override;
|
|
|
|
/** Does our periodic broadcast */
|
|
virtual int32_t runOnce() override;
|
|
|
|
private:
|
|
meshtastic_MeshPacket *allocPositionPacket(uint32_t atPrecision);
|
|
// Streams our own position to the connected phone/UI at full precision without touching the
|
|
// mesh. Keeps the local view alive now that mesh position sharing is opt-in. Returns true
|
|
// only when a packet was handed to the phone queue.
|
|
bool sendOurPositionToPhone();
|
|
bool hasSentPositionToPhone = false;
|
|
uint32_t lastPhoneSendMs = 0;
|
|
static constexpr uint32_t sendToPhoneIntervalMs = 60 * 1000; // Matches telemetry's local cadence
|
|
struct SmartPosition getDistanceTraveledSinceLastSend(meshtastic_PositionLite currentPosition);
|
|
// True when our position is unchanged since the last broadcast: it truncates to the same
|
|
// precision grid cell, so re-sending would be a duplicate that traffic management dedups
|
|
// downstream anyway. Used to hold stationary broadcasts to a 12h floor. useConfiguredPrecision
|
|
// gauges movement at our own configured (unclamped) precision rather than the on-wire
|
|
// (public-clamped) precision - trackers report finer movement.
|
|
bool positionUnchangedSinceLastSend(const meshtastic_PositionLite &selfPos, bool useConfiguredPrecision);
|
|
meshtastic_MeshPacket *allocAtakPli();
|
|
void trySetRtc(meshtastic_Position p, bool isLocal, bool forceUpdate = false);
|
|
uint32_t precision;
|
|
void sendLostAndFoundText();
|
|
bool hasQualityTimesource();
|
|
bool hasGPS();
|
|
uint32_t lastSentReply = 0; // Last time we sent a position reply (used for reply throttling only)
|
|
|
|
#if USERPREFS_EVENT_MODE
|
|
// In event mode we want to prevent excessive position broadcasts
|
|
// we set the minimum interval to 5m
|
|
const uint32_t minimumTimeThreshold =
|
|
max(uint32_t(300000), Default::getConfiguredOrDefaultMs(config.position.broadcast_smart_minimum_interval_secs,
|
|
default_broadcast_smart_minimum_interval_secs));
|
|
#else
|
|
const uint32_t minimumTimeThreshold = Default::getConfiguredOrDefaultMs(config.position.broadcast_smart_minimum_interval_secs,
|
|
default_broadcast_smart_minimum_interval_secs);
|
|
#endif
|
|
};
|
|
|
|
struct SmartPosition {
|
|
float distanceTraveled;
|
|
uint32_t distanceThreshold;
|
|
bool hasTraveledOverThreshold;
|
|
};
|
|
|
|
extern PositionModule *positionModule; |