Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
df5a768
gps: add GpsDriver, handle the hardware and software switches in the …
fdlamotte Nov 2, 2026
fe6a47d
gps: move the gps CLI to GpsPrefs and convert all targets
fdlamotte Nov 2, 2026
36b53d8
gps: cache the last fix in LocationProvider (isValid vs hasValidFix),…
fdlamotte Nov 2, 2026
4ac1ed1
gps: periodic acquisition rules (timeout, sats and settle time), isEn…
fdlamotte Nov 3, 2026
fa7ca3f
rak_gps: simplify the rak driver, which now only handles i2c, for nme…
fdlamotte Nov 4, 2026
3fd36e7
gps: adapt ui-new and the sensor managers
fdlamotte Nov 4, 2026
d49621c
gps: fix_stats command
fdlamotte Nov 4, 2026
22ce208
muzi_base: fix GPS UART pins and re-enable ATGM336H NMEA output
nGoline Nov 4, 2026
d71a418
gps: serial pin and configuration fixes (nrf52, meshadventurer, track…
fdlamotte Nov 4, 2026
839bb16
gps: guard the clock sync against stalled and too old fixes
fdlamotte Nov 5, 2026
38fa18e
gps: move detection into LocationProvider, remove the legacy constructor
fdlamotte Nov 5, 2026
c06def2
gps: once command
fdlamotte Nov 7, 2026
6c9600e
gps: Rework the CLI
fdlamotte Nov 7, 2026
6eea5e7
ui_task: let user choose if enter in gps screen toggles or triggers gps
fdlamotte Nov 7, 2026
666949f
rak4631: let the serial GPS switch the WB_IO2 rail (opt-in)
Ve2mrx Nov 8, 2026
4069f48
NmeaGpsDriver: optional UART baud rate autodetection (GPS_BAUD_AUTODE…
AlexandrTyshchenko Nov 8, 2026
d644f0a
GPS: optional anti-spoofing guard for position and time (GPS_GUARD)
AlexandrTyshchenko Nov 8, 2026
181041a
GpsGuard: do not show a stale time ok without a current trusted fix
AlexandrTyshchenko Nov 9, 2026
eea64ff
GPS guard: drop time force, it goes to a separate PR
AlexandrTyshchenko Nov 9, 2026
fbd20ac
GpsGuard: let GPS correct a clock outside the plausible window
AlexandrTyshchenko Nov 9, 2026
cbfa66d
NmeaGpsDriver: baud rates to probe come from the board, not a build flag
AlexandrTyshchenko Nov 9, 2026
d72f67d
GPS guard: save gps fence in the GPS prefs
AlexandrTyshchenko Nov 9, 2026
519cf4c
Merge pull request #3 from AlexandrTyshchenko/feature/gps-baud-autode…
fdlamotte Nov 9, 2026
8c7e1a8
GPS guard: gps guard on/off, saved in the GPS prefs
AlexandrTyshchenko Nov 9, 2026
544514f
Merge pull request #4 from AlexandrTyshchenko/feature/gps-guard-refactor
fdlamotte Nov 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 76 additions & 4 deletions docs/cli_commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -999,19 +999,47 @@ region save
---
### GPS (When GPS support is compiled in)

#### View or change GPS state
#### View GPS state
**Usage:**
- `gps`

**Note:** Reports the requested state, receiver activity, last-fix status (last fix gives number of sats and how long ago was the fix), and mode. In periodic mode, `waiting` means the receiver is off between acquisitions; the last fix remains available. `off(hw)` means a hardware GPS switch is off.

---

#### Change GPS state
**Usage:**
- `gps <state>`

**Parameters:**
- `state`: `on`|`off`

**Default:** `off`

**Note:** Output format:
- `off` when the GPS hardware is disabled
- `on, {active|deactivated}, {fix|no fix}, {sat count} sats` when the GPS hardware is enabled
---

#### View or change the GPS acquisition period
**Usage:**
- `gps period`
- `gps period <value>`

**Parameters:**
- `value`: `0` for continuous tracking, or a period. The value can be postfixed with `s`, `m`, `h` or `d` to specify the unit (seconds by default).

**Note:** A periodic acquisition stops after receiving a valid fix, then resumes after the configured period.

---

#### View the last GPS fix
**Usage:**
- `gps fix`

**Output:**
- `> Fix (n sat): <lat>, <lon>`: the GPS currently has a fix.
- `> Last fix <age> ago (n sat)[, acq <time>]: <lat>, <lon>`: position of the last valid fix; `acq` is shown only while the receiver is running and shows how for how long the gps has been trying to get a fix.
- `> No fix[, acq <time>]`: no valid fix since the node started.

**Note:** Ages and durations are given in `s`, `m` or `h`.

---

Expand All @@ -1021,6 +1049,50 @@ region save

---

#### On demand GPS fix
**Usage:**
- `gps once`
- `gps once -s`

**Parameters:**
- `-s`: also sync this node's clock with GPS time (as `gps sync`)

**Note:** Starts an acquisition now. If the GPS is off, it is switched and kept on until a valid fix and then turned off.

**Reply:** `OK - turning on and acquiring` (GPS was off), `OK - waking gps` (periodic mode, receiver off between acquisitions) or `OK - gps already on`. `Error, gps hw switch off` if a hardware GPS switch is off, `Error, gps once not supported`.

---

#### View GPS time to first fix statistics
**Usage:**
- `gps fix_stats`

**Output:** `> ttf:<last>/<max> ok:<n> miss:<n>`: last and maximum time between a start of the GPS and its first valid fix, then the last count of consecutive starts with a fix (`ok`) or without one (`miss`).

---

#### View GPS diagnostics
**Usage:**
- `gps diag`

---

#### GPS anti-spoofing guard (When built with `GPS_GUARD`)
With `GPS_GUARD`, a fix becomes the node's last fix (position, telemetry, adverts) only after it passes plausibility checks: at least `GPS_GUARD_MIN_SATS` satellites, HDOP ≤ `GPS_GUARD_MAX_HDOP10`/10, inside the optional build-time region (`GPS_GUARD_LAT_MIN/MAX`, `GPS_GUARD_LON_MIN/MAX`), within the fence around the node's configured `lat`/`lon` (if set), no jump faster than `GPS_GUARD_MAX_SPEED_KMH`, and `GPS_GUARD_STABLE_FIXES` consistent fixes in a row. An unverified position is never reported.

GPS time is written to the clock only while such a fix is current, never earlier than the firmware build date, and, once the clock holds a plausible time (from the build date to 10 years after it), only within `GPS_GUARD_TIME_MAX_STEP` seconds for the first sync and within clock drift (`GPS_GUARD_TIME_DRIFT_BASE` s + `GPS_GUARD_TIME_DRIFT_PPM`) afterwards. Setting the clock manually (`time`, `clock sync`) resets that anchor. A clock outside that window (e.g. a bogus date in 2080) is corrected by the first trusted fix.

Covers serial NMEA receivers (`MicroNMEALocationProvider`).

**Usage:**
- `gps guard`: status, e.g. `> ok 7sat time ok +1s`, `> rej region time rej nofix`, or `... ALERT gps-clk +3600s 75m` when a trusted fix has disagreed with the clock by the same offset for `GPS_GUARD_ALERT_MIN` minutes. While the alert is active, the offset in hours is also reported in telemetry on the next free channel.
- `gps guard on`, `gps guard off`: turn the guard on or off (saved, default on). While off, every fix and GPS time is accepted and `gps guard` shows `off`.
- `gps guard accept`: while an alert is active, confirm that the GPS time is right and apply it to the clock.
- `gps fence`, `gps fence <metres>`: fence radius around the configured `lat`/`lon` (`0` = off). Default `GPS_GUARD_FENCE_M` (1000); a value set here is saved.
- `gps ref`: the reference position; follows `set lat`, `set lon` and `gps setloc`.

---

#### Set this node's location based on the GPS coordinates
**Usage:**
- `gps setloc`
Expand Down
93 changes: 93 additions & 0 deletions docs/companion_cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Since version 1.18 meshcore companion radio firmware has a CLI interface, which
- [Configuration](#configuration)
- [System](#system)
- [Radio](#radio)
- [GPS](#gps)
- [BLE](#ble)
- [Wifi](#wifi)
- [UI](#ui)
Expand Down Expand Up @@ -119,6 +120,84 @@ Radio parameters are common to all roles and handled by `CommonRadioPrefs`.

---

### GPS

#### View GPS state
**Usage:**
- `gps`

**Note:** Reports the requested state, receiver activity, last-fix status (last fix gives number of sats and how long ago was the fix), and mode. In periodic mode, `waiting` means the receiver is off between acquisitions; the last fix remains available. `off(hw)` means a hardware GPS switch is off.

---

#### Change GPS state
**Usage:**
- `gps <state>`

**Parameters:**
- `state`: `on`|`off`

---

#### View or change the GPS acquisition period
**Usage:**
- `gps period`
- `gps period <value>`

**Parameters:**
- `value`: `0` for continuous tracking, or a period. The value can be postfixed with `s`, `m`, `h` or `d` to specify the unit (seconds by default).

**Note:** A periodic acquisition stops after receiving a valid fix, then resumes after the configured period.

---

#### View the last GPS fix
**Usage:**
- `gps fix`

**Output:**
- `> Fix (n sat): <lat>, <lon>`: the GPS currently has a fix.
- `> Last fix <age> ago (n sat)[, acq <time>]: <lat>, <lon>`: position of the last valid fix; `acq` is shown only while the receiver is running and shows how for how long the gps has been trying to get a fix.
- `> No fix[, acq <time>]`: no valid fix since the node started.

**Note:** Ages and durations are given in `s`, `m` or `h`.

---

#### Sync this node's clock with GPS time
**Usage:**
- `gps sync`

---

#### On demand GPS fix
**Usage:**
- `gps once`
- `gps once -s`

**Parameters:**
- `-s`: also sync this node's clock with GPS time (as `gps sync`)

**Note:** Starts an acquisition now. If the GPS is off, it is switched and kept on until a valid fix and then turned off.

**Reply:** `OK - turning on and acquiring` (GPS was off), `OK - waking gps` (periodic mode, receiver off between acquisitions) or `OK - gps already on`. `Error, gps hw switch off` if a hardware GPS switch is off, `Error, gps once not supported`.

---

#### View GPS time to first fix statistics
**Usage:**
- `gps fix_stats`

**Output:** `> ttf:<last>/<max> ok:<n> miss:<n>`: last and maximum time between a start of the GPS and its first valid fix, then the last count of consecutive starts with a fix (`ok`) or without one (`miss`).

---

#### View GPS diagnostics
**Usage:**
- `gps diag`

---

#### View or change this node's rx boosted gain mode (SX12xx and LR1110)
**Usage:**
- `get radio.rxgain`
Expand Down Expand Up @@ -373,6 +452,20 @@ Radio parameters are common to all roles and handled by `CommonRadioPrefs`.

---

#### GPS key action
**Usage:**
- `set ui.gps_action <action>`
- `get ui.gps_action`

**Parameters:**
- `action`: `toggle` (or `0`) to switch the GPS on and off, `once` (or `trigger`, `1`) to acquire a fix on demand, as `gps once`

**Default:** `toggle`, or `once` on boards with a GPS switch

**Note:** Only used by the `ui-new` screens for now.

---

#### Node discovery screen shows nodes names
**Usage:**
- `set ui.nd_name <state>`
Expand Down
17 changes: 17 additions & 0 deletions examples/companion_radio/MyMesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2093,6 +2093,11 @@ bool MyMesh::handleCommand(const char* command, uint32_t sender_timestamp, char*
return true;
}

if (_prefs.getGpsPrefs()->handleCommand(command, sensors, reply)) {
if (_prefs.getGpsPrefs()->isDirty()) { savePrefs(); }
return true;
}

// hook for variant-specific CLI processing
if (board.handleCommand(command, sender_timestamp, reply)) {
if (_prefs.isDirty()) { savePrefs(); }
Expand Down Expand Up @@ -2286,6 +2291,18 @@ bool MyMesh::handleCommand(const char* command, uint32_t sender_timestamp, char*
sprintf(reply, "> ui.nd_name is now %d", _prefs.ui_nd_name);
return true;
}
if (memcmp(command, "set ui.gps_action", 17) == 0) {
const char* v = &command[17];
while (*v == ' ') v++;
_prefs.ui_gps_action = (memcmp(v, "once", 4) == 0 || memcmp(v, "trigger", 7) == 0 || *v == '1') ? 1 : 0; // anything else: toggle
savePrefs();
sprintf(reply, "> now %s", _prefs.ui_gps_action ? "once" : "toggle");
return true;
}
if (memcmp(command, "get ui.gps_action", 17) == 0) {
sprintf(reply, "> %s", _prefs.ui_gps_action ? "once" : "toggle");
return true;
}
if (memcmp(command, "get ui.show_clock", 17) == 0) {
sprintf(reply, "> %d", _prefs.ui_show_clock);
return true;
Expand Down
11 changes: 6 additions & 5 deletions examples/companion_radio/MyMesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -203,12 +203,13 @@ class MyMesh : public BaseChatMesh, public DataStoreHost {

#if ENV_INCLUDE_GPS == 1
void applyGpsPrefs() {
sensors.setSettingValue("gps", _prefs.gps_enabled ? "1" : "0");
if (_prefs.gps_interval > 0) {
char interval_str[12]; // Max: 24 hours = 86400 seconds (5 digits + null)
sprintf(interval_str, "%u", _prefs.gps_interval);
sensors.setSettingValue("gps_interval", interval_str);
if (_prefs.gps_interval > 0 &&
sensors.getSettingByKey("gps_interval") != nullptr) {
char interval[11];
snprintf(interval, sizeof(interval), "%lu", (unsigned long)_prefs.gps_interval);
sensors.setSettingValue("gps_interval", interval);
}
_prefs.getGpsPrefs()->apply(sensors);
}
#endif

Expand Down
37 changes: 34 additions & 3 deletions examples/companion_radio/NodePrefs.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include <cstdint> // For uint8_t, uint32_t
#include <helpers/ConfigSerializer.h>
#include <helpers/CommonRadioPrefs.h>
#include <helpers/CommonGpsPrefs.h>
#include <helpers/DynamicConfigSerializer.h>

#define TELEM_MODE_DENY 0
Expand Down Expand Up @@ -35,6 +36,9 @@ class NodePrefs : public ConfigSerializer { // persisted to file
uint8_t vibe_quiet = 0;
uint8_t gps_enabled = 0; // GPS enabled flag (0=disabled, 1=enabled)
uint32_t gps_interval = 0; // GPS read interval in seconds
uint32_t gps_period = 0; // periodic acquisition interval in seconds
int32_t gps_fence = -1; // GPS_GUARD fence in metres, 0 = off, -1 = build default
uint8_t gps_guard = 1; // GPS_GUARD on/off
uint8_t autoadd_config = 0; // bitmask for auto-add contacts config
uint8_t rx_boosted_gain = 0; // SX126x RX boosted gain mode (0=power saving, 1=boosted)
uint8_t radio_fem_rxgain = 0; // external LoRa FEM RX gain (LNA)
Expand Down Expand Up @@ -85,6 +89,11 @@ class NodePrefs : public ConfigSerializer { // persisted to file
uint8_t ui_auto_off_keep_msgs = 1;
uint8_t ui_max_partial = EINK_MAX_PARTIAL_REFRESH;
uint8_t ui_nd_name = 1;
#ifdef PIN_GPS_SWITCH
uint8_t ui_gps_action = 1;
#else
uint8_t ui_gps_action = 0;
#endif
#endif

private:
Expand Down Expand Up @@ -151,16 +160,36 @@ class NodePrefs : public ConfigSerializer { // persisted to file
};
RadioPrefs radio;

class GPSPrefs : public ConfigSerializer { // COPIED from CommonCLI (for now)
class GPSPrefs : public CommonGpsPrefs {
NodePrefs* _parent;
protected:
void structure() override {
def("en", _parent->gps_enabled); // boolean
def("int", _parent->gps_interval); // interval in seconds
def("period", _parent->gps_period); // periodic acquisition interval in seconds
def("adv_loc", _parent->advert_loc_policy);
#ifdef GPS_GUARD
def("fence", _parent->gps_fence);
def("guard", _parent->gps_guard);
#endif
}
public:
GPSPrefs(NodePrefs* parent) : _parent(parent) { }
bool isGpsEnabled() const override { return _parent->gps_enabled; }
void setGpsEnabled(bool enabled) override { _parent->gps_enabled = enabled; markDirty(); }
uint32_t getGpsPeriod() const override { return _parent->gps_period; }
void setGpsPeriod(uint32_t seconds) override { _parent->gps_period = seconds; markDirty(); }
bool getGuardFence(uint32_t& metres) const override {
if (_parent->gps_fence < 0) return false;
metres = (uint32_t)_parent->gps_fence;
return true;
}
void setGuardFence(uint32_t metres) override { _parent->gps_fence = (int32_t)metres; markDirty(); }
bool getGuardEnabled(bool& enabled) const override {
enabled = _parent->gps_guard != 0;
return true;
}
void setGuardEnabled(bool enabled) override { _parent->gps_guard = enabled ? 1 : 0; markDirty(); }
};
GPSPrefs gps;

Expand Down Expand Up @@ -231,6 +260,7 @@ class NodePrefs : public ConfigSerializer { // persisted to file
def("off_blank", _parent->ui_auto_off_blank);
def("off_keep_msgs", _parent->ui_auto_off_keep_msgs);
def("nd_name", _parent->ui_nd_name);
def("gps_act", _parent->ui_gps_action);
}
public:
UI_Prefs(NodePrefs* parent) : _parent(parent) {}
Expand Down Expand Up @@ -275,8 +305,9 @@ class NodePrefs : public ConfigSerializer { // persisted to file
void setRepeatEn(bool en) { repeat.disable_fwd = en ? 0 : 1; }

CommonRadioPrefs* getRadioPrefs() { return &radio; }
CommonGpsPrefs* getGpsPrefs() { return &gps; }
KeyValueStore* getCustom() { return &custom; }

bool isDirty() const override { return ConfigSerializer::isDirty() || radio.isDirty() || custom.isDirty(); }
void clearDirty() override { ConfigSerializer::clearDirty(); radio.clearDirty(); custom.clearDirty(); }
bool isDirty() const override { return ConfigSerializer::isDirty() || radio.isDirty() || gps.isDirty() || custom.isDirty(); }
void clearDirty() override { ConfigSerializer::clearDirty(); radio.clearDirty(); gps.clearDirty(); custom.clearDirty(); }
};
Loading
Loading