Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
21 changes: 21 additions & 0 deletions docs/cli_commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -905,6 +905,27 @@ region save

---

#### View or change GPS location blur (privacy)
**Usage:**
- `gps blur`
- `gps blur <digits>`

**Parameters:**
- `digits`: `0`–`6` decimal places to publish
- `0`: no blur (full precision, ~0.1 m)
- `1`: ~11 km grid
- `2`: ~1.1 km grid
- `3`: ~111 m grid (neighborhood)
- `4`: ~11 m grid
- `5`: ~1.1 m grid
- `6`: same as 0 (full precision)

**Default:** `0` (off)

**Note:** Applies to both advertised position and telemetry GPS data. Coordinates are truncated to the nearest grid cell, not rounded.

---

#### View or change the GPS advert policy
**Usage:**
- `gps advert`
Expand Down
65 changes: 62 additions & 3 deletions src/helpers/CommonCLI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
file.read((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166
file.read((uint8_t *)_prefs->owner_info, sizeof(_prefs->owner_info)); // 170
file.read((uint8_t *)&_prefs->rx_boosted_gain, sizeof(_prefs->rx_boosted_gain)); // 290
// next: 291
file.read((uint8_t *)&_prefs->gps_blur_digits, sizeof(_prefs->gps_blur_digits)); // 291
// next: 292

// sanitise bad pref values
_prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f);
Expand Down Expand Up @@ -118,6 +119,19 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {

// sanitise settings
_prefs->rx_boosted_gain = constrain(_prefs->rx_boosted_gain, 0, 1); // boolean
_prefs->gps_blur_digits = constrain(_prefs->gps_blur_digits, 0, 6);

_sensors->gps_blur_digits = _prefs->gps_blur_digits;

// derive per-device fuzz seeds: first half of pub_key → lat, second half → lon
{
const uint8_t* pk = _callbacks->getSelfId().pub_key;
uint32_t lat_seed = 0, lon_seed = 0;
for (int i = 0; i < 16; i++) lat_seed ^= ((uint32_t)pk[i] << ((i % 4) * 8));
for (int i = 16; i < 32; i++) lon_seed ^= ((uint32_t)pk[i] << ((i % 4) * 8));
_sensors->gps_fuzz_lat = lat_seed;
_sensors->gps_fuzz_lon = lon_seed;
}

file.close();
}
Expand Down Expand Up @@ -179,7 +193,8 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) {
file.write((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166
file.write((uint8_t *)_prefs->owner_info, sizeof(_prefs->owner_info)); // 170
file.write((uint8_t *)&_prefs->rx_boosted_gain, sizeof(_prefs->rx_boosted_gain)); // 290
// next: 291
file.write((uint8_t *)&_prefs->gps_blur_digits, sizeof(_prefs->gps_blur_digits)); // 291
// next: 292

file.close();
}
Expand All @@ -194,15 +209,28 @@ void CommonCLI::savePrefs() {
_callbacks->savePrefs();
}

static void applyGpsBlur(double& lat, double& lon, uint8_t digits, uint32_t lat_seed, uint32_t lon_seed) {
if (digits == 0 || digits > 6) return;
long grid = 1;
for (uint8_t i = 0; i < 6 - digits; i++) grid *= 10;
long lat_mic = (long)(lat * 1000000.0);
long lon_mic = (long)(lon * 1000000.0);
lat = (double)(((lat_mic / grid) * grid) + (long)(lat_seed % (uint32_t)grid) - grid / 2) / 1000000.0;
lon = (double)(((lon_mic / grid) * grid) + (long)(lon_seed % (uint32_t)grid) - grid / 2) / 1000000.0;
}

uint8_t CommonCLI::buildAdvertData(uint8_t node_type, uint8_t* app_data) {
if (_prefs->advert_loc_policy == ADVERT_LOC_NONE) {
AdvertDataBuilder builder(node_type, _prefs->node_name);
return builder.encodeTo(app_data);
} else if (_prefs->advert_loc_policy == ADVERT_LOC_SHARE) {
// node_lat/lon already blurred by EnvironmentSensorManager
AdvertDataBuilder builder(node_type, _prefs->node_name, _sensors->node_lat, _sensors->node_lon);
return builder.encodeTo(app_data);
} else {
AdvertDataBuilder builder(node_type, _prefs->node_name, _prefs->node_lat, _prefs->node_lon);
double lat = _prefs->node_lat, lon = _prefs->node_lon;
applyGpsBlur(lat, lon, _prefs->gps_blur_digits, _sensors->gps_fuzz_lat, _sensors->gps_fuzz_lon);
AdvertDataBuilder builder(node_type, _prefs->node_name, lat, lon);
return builder.encodeTo(app_data);
}
}
Expand Down Expand Up @@ -376,6 +404,37 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re
_prefs->node_lon = _sensors->node_lon;
savePrefs();
strcpy(reply, "ok");
} else if (memcmp(command, "gps blur", 8) == 0) {
if (strlen(command) == 8) {
if (_prefs->gps_blur_digits == 0) {
strcpy(reply, "> off");
} else {
uint8_t digits = _prefs->gps_blur_digits;
long grid = 1;
for (uint8_t i = 0; i < 6 - digits; i++) grid *= 10;
double lat_off = ((long)(_sensors->gps_fuzz_lat % (uint32_t)grid) - grid / 2) / 1000000.0;
double lon_off = ((long)(_sensors->gps_fuzz_lon % (uint32_t)grid) - grid / 2) / 1000000.0;
sprintf(reply, "> %d, offset lat%+.6f lon%+.6f", digits, lat_off, lon_off);
}
} else {
uint8_t digits = atoi(command + 9);
if (digits <= 6) {
_prefs->gps_blur_digits = digits;
_sensors->gps_blur_digits = digits;
savePrefs();
if (digits == 0) {
strcpy(reply, "ok");
} else {
long grid = 1;
for (uint8_t i = 0; i < 6 - digits; i++) grid *= 10;
double lat_off = ((long)(_sensors->gps_fuzz_lat % (uint32_t)grid) - grid / 2) / 1000000.0;
double lon_off = ((long)(_sensors->gps_fuzz_lon % (uint32_t)grid) - grid / 2) / 1000000.0;
sprintf(reply, "ok, offset lat%+.6f lon%+.6f", lat_off, lon_off);
}
} else {
strcpy(reply, "error: value must be 0-6");
}
}
} else if (memcmp(command, "gps advert", 10) == 0) {
if (strlen(command) == 10) {
switch (_prefs->advert_loc_policy) {
Expand Down
1 change: 1 addition & 0 deletions src/helpers/CommonCLI.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ struct NodePrefs { // persisted to file
uint8_t rx_boosted_gain; // power settings
uint8_t path_hash_mode; // which path mode to use when sending
uint8_t loop_detect;
uint8_t gps_blur_digits; // 0=off, 1-6: broadcast GPS coords rounded to N decimal places (~111km/11km/1.1km/111m/11m/1m)
};

class CommonCLICallbacks {
Expand Down
5 changes: 4 additions & 1 deletion src/helpers/SensorManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ class SensorManager {
public:
double node_lat, node_lon; // modify these, if you want to affect Advert location
double node_altitude; // altitude in meters
uint8_t gps_blur_digits = 0; // 0=off, 1-6: snap GPS coords to this many decimal places before publishing
uint32_t gps_fuzz_lat = 0; // derived from first half of pub_key; deterministic offset within blur grid cell
uint32_t gps_fuzz_lon = 0; // derived from second half of pub_key

SensorManager() { node_lat = 0; node_lon = 0; node_altitude = 0; }
SensorManager() { node_lat = 0; node_lon = 0; node_altitude = 0; gps_blur_digits = 0; gps_fuzz_lat = 0; gps_fuzz_lon = 0; }
virtual bool begin() { return false; }
virtual bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) { return false; }
virtual void loop() { }
Expand Down
17 changes: 11 additions & 6 deletions src/helpers/sensors/EnvironmentSensorManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -754,6 +754,13 @@ void EnvironmentSensorManager::stop_gps() {
#endif
}

static double blurCoord(long microdeg, uint8_t digits, uint32_t fuzz) {
if (digits == 0 || digits > 6) return microdeg / 1000000.0;
long grid = 1;
for (uint8_t i = 0; i < 6 - digits; i++) grid *= 10;
return (double)(((microdeg / grid) * grid) + (long)(fuzz % (uint32_t)grid) - grid / 2) / 1000000.0;
}

void EnvironmentSensorManager::loop() {
static long next_gps_update = 0;

Expand All @@ -766,17 +773,15 @@ void EnvironmentSensorManager::loop() {
if(gps_active){
#ifdef RAK_WISBLOCK_GPS
if ((i2cGPSFlag || serialGPSFlag) && _location->isValid()) {
node_lat = ((double)_location->getLatitude())/1000000.;
node_lon = ((double)_location->getLongitude())/1000000.;
MESH_DEBUG_PRINTLN("lat %f lon %f", node_lat, node_lon);
node_lat = blurCoord(_location->getLatitude(), gps_blur_digits, gps_fuzz_lat);
node_lon = blurCoord(_location->getLongitude(), gps_blur_digits, gps_fuzz_lon);
node_altitude = ((double)_location->getAltitude()) / 1000.0;
MESH_DEBUG_PRINTLN("lat %f lon %f alt %f", node_lat, node_lon, node_altitude);
}
#else
if (_location->isValid()) {
node_lat = ((double)_location->getLatitude())/1000000.;
node_lon = ((double)_location->getLongitude())/1000000.;
MESH_DEBUG_PRINTLN("lat %f lon %f", node_lat, node_lon);
node_lat = blurCoord(_location->getLatitude(), gps_blur_digits, gps_fuzz_lat);
node_lon = blurCoord(_location->getLongitude(), gps_blur_digits, gps_fuzz_lon);
node_altitude = ((double)_location->getAltitude()) / 1000.0;
MESH_DEBUG_PRINTLN("lat %f lon %f alt %f", node_lat, node_lon, node_altitude);
}
Expand Down