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
20 changes: 20 additions & 0 deletions docs/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ A list of frequently-asked questions and answers for MeshCore
- [5.14. Q: Are there projects built around MeshCore?](#514-q-are-there-projects-built-around-meshcore)
- [5.15. Q: Are there client applications for Windows or Mac?](#515-q-are-there-client-applications-for-windows-or-mac)
- [5.16. Q: Are there any resources that compare MeshCore to other LoRa systems?](#516-q-are-there-any-resources-that-compare-meshcore-to-other-lora-systems)
- [5.17. Q: My external GPS is wired to different pins than the firmware expects. Do I need a custom build?](#517-q-my-external-gps-is-wired-to-different-pins-than-the-firmware-expects-do-i-need-a-custom-build)
- [6. Troubleshooting](#6-troubleshooting)
- [6.1. Q: My client says another client or a repeater or a room server was last seen many, many days ago.](#61-q-my-client-says-another-client-or-a-repeater-or-a-room-server-was-last-seen-many-many-days-ago)
- [6.2. Q: A repeater or a client or a room server I expect to see on my discover list (on T-Deck) or contact list (on a smart device client) are not listed.](#62-q-a-repeater-or-a-client-or-a-room-server-i-expect-to-see-on-my-discover-list-on-t-deck-or-contact-list-on-a-smart-device-client-are-not-listed)
Expand Down Expand Up @@ -665,6 +666,25 @@ Both the Windows and Mac versions of the client app are fully unlocked and are f
- MeshCore Advantages by MCarper: <https://github.com/mikecarper/meshfirmware/blob/main/MeshCoreAdvantages.md>
- MeshCore vs Meshtastic by austinmesh.org: <https://www.austinmesh.org/learn/meshcore-vs-meshtastic>

### 5.17. Q: My external GPS is wired to different pins than the firmware expects. Do I need a custom build?
**A:** Not on companion firmware. The GPS pins and baud rate can be changed at runtime through these custom variables (in the app, or with `meshcore-cli`):

| Variable | Meaning | Default |
|------------|------------------------------------------------------------------|---------|
| `gps_tx` | MCU pin connected to the GPS module's **TX** (MCU receives here) | board's `PIN_GPS_TX` |
| `gps_rx` | MCU pin connected to the GPS module's **RX** (MCU transmits here) | board's `PIN_GPS_RX` |
| `gps_en` | MCU pin that powers/enables the GPS (driven high when GPS is on) | board's `PIN_GPS_EN` (`-1` = none) |
| `gps_baud` | GPS UART baud rate (4800, 9600, 19200, 38400, 57600, 115200, 230400) | board's `GPS_BAUD_RATE`, else 9600 |

The variables always show the value in effect, so on a stock Heltec V3 they read `gps_rx:47`, `gps_tx:48`, `gps_en:26`, `gps_baud:9600`. Setting a variable to the board's built-in value (or to `-1`, or `0` for `gps_baud`) makes it follow the board default again. The values are saved and applied on the **next reboot**. They are listed even when no GPS was detected, so a wrong setting can always be fixed. Once the GPS is found, the usual `gps` on/off variable appears.

Pins already used by the board (LoRa, display I2C, button, Vext, LED, and on ESP32 the flash/PSRAM and USB serial pins) are rejected. Pins the board definition doesn't declare can't be checked, so double-check your wiring. Not available on RAK WisBlock GPS boards, which detect the GPS socket automatically.

Example for a Heltec V3 with the GPS on GPIO34 (TX) / GPIO33 (RX), powered from GPIO42:
```
meshcli -s /dev/ttyUSB0 set gps_tx 34 set gps_rx 33 set gps_en 42 set gps_baud 9600 reboot
```


---

Expand Down
8 changes: 8 additions & 0 deletions examples/companion_radio/MyMesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1885,6 +1885,14 @@ void MyMesh::handleCmdFrame(size_t len) {
savePrefs();
}
#endif
#if ENV_GPS_PIN_CONFIG
// pin/baud overrides (validated and normalised by the sensor manager); applied on next boot
if (strcmp(sp, "gps_rx") == 0 || strcmp(sp, "gps_tx") == 0 ||
strcmp(sp, "gps_en") == 0 || strcmp(sp, "gps_baud") == 0) {
sensors.getGPSOverrides(_prefs.gps_pin_rx, _prefs.gps_pin_tx, _prefs.gps_pin_en, _prefs.gps_baud);
savePrefs();
}
#endif
writeOKFrame();
} else {
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
Expand Down
7 changes: 7 additions & 0 deletions examples/companion_radio/MyMesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,13 @@ class MyMesh : public BaseChatMesh, public DataStoreHost {
_prefs.clearDirty();
}

#if ENV_GPS_PIN_CONFIG
// must run before sensors.begin(), which opens the GPS UART
void applyGpsPinPrefs() {
sensors.configureGPS(_prefs.gps_pin_rx, _prefs.gps_pin_tx, _prefs.gps_pin_en, _prefs.gps_baud);
}
#endif

#if ENV_INCLUDE_GPS == 1
void applyGpsPrefs() {
sensors.setSettingValue("gps", _prefs.gps_enabled ? "1" : "0");
Expand Down
8 changes: 8 additions & 0 deletions examples/companion_radio/NodePrefs.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ 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
int8_t gps_pin_rx = -1; // GPS pin overrides, -1 = variant default (applied at boot)
int8_t gps_pin_tx = -1;
int8_t gps_pin_en = -1;
uint32_t gps_baud = 0; // 0 = variant default
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 @@ -130,6 +134,10 @@ class NodePrefs : public ConfigSerializer { // persisted to file
def("en", _parent->gps_enabled); // boolean
def("int", _parent->gps_interval); // interval in seconds
def("adv_loc", _parent->advert_loc_policy);
def("pin_rx", _parent->gps_pin_rx);
def("pin_tx", _parent->gps_pin_tx);
def("pin_en", _parent->gps_pin_en);
def("baud", _parent->gps_baud);
}
public:
GPSPrefs(NodePrefs* parent) : _parent(parent) { }
Expand Down
3 changes: 3 additions & 0 deletions examples/companion_radio/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,9 @@ void setup() {
#endif

the_mesh.startInterface(interface_manager);
#if ENV_GPS_PIN_CONFIG
the_mesh.applyGpsPinPrefs();
#endif
sensors.begin();

#if ENV_INCLUDE_GPS == 1
Expand Down
224 changes: 219 additions & 5 deletions src/helpers/sensors/EnvironmentSensorManager.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
#include "EnvironmentSensorManager.h"

#include <Wire.h>
#if ENV_GPS_PIN_CONFIG
#include <RadioLib.h> // RADIOLIB_NC in variant pin defines
#ifdef ESP32
#include <driver/gpio.h>
#endif
#endif

#if ENV_PIN_SDA && ENV_PIN_SCL
#define TELEM_WIRE &Wire1 // Use Wire1 as the I2C bus for Environment Sensors
Expand Down Expand Up @@ -699,11 +705,50 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen
}


#if ENV_GPS_PIN_CONFIG
#ifdef PIN_GPS_RX
#define GPS_DEFAULT_RX PIN_GPS_RX
#else
#define GPS_DEFAULT_RX -1
#endif
#ifdef PIN_GPS_TX
#define GPS_DEFAULT_TX PIN_GPS_TX
#else
#define GPS_DEFAULT_TX -1
#endif
#ifdef PIN_GPS_EN
#define GPS_DEFAULT_EN PIN_GPS_EN
#else
#define GPS_DEFAULT_EN -1
#endif
#ifdef GPS_BAUD_RATE
#define GPS_DEFAULT_BAUD GPS_BAUD_RATE
#else
#define GPS_DEFAULT_BAUD 9600
#endif

// Listed even when no GPS was detected, so wrong pins can be fixed from the app.
// Values shown are the effective ones (override or board default). Changes take effect after reboot.
static const char* const GPS_CFG_NAMES[] = { "gps_rx", "gps_tx", "gps_en", "gps_baud" };
static const int GPS_CFG_COUNT = sizeof(GPS_CFG_NAMES) / sizeof(GPS_CFG_NAMES[0]);

// Whole decimal number within [lo, hi]. Rejects empty or trailing text, so "abc" isn't read as 0
// and out-of-range pins can't wrap when stored in int8_t.
static bool parseGPSCfg(const char* s, long lo, long hi, long& out) {
char* end;
out = strtol(s, &end, 10);
return end != s && *end == 0 && out >= lo && out <= hi;
}
#endif

int EnvironmentSensorManager::getNumSettings() const {
int settings = 0;
#if ENV_INCLUDE_GPS
if (gps_detected) settings++; // only show GPS setting if GPS is detected
#endif
#if ENV_GPS_PIN_CONFIG
if (gps_cfg_enabled) settings += GPS_CFG_COUNT;
#endif
return settings;
}

Expand All @@ -714,6 +759,12 @@ const char* EnvironmentSensorManager::getSettingName(int i) const {
return "gps";
}
#endif
#if ENV_GPS_PIN_CONFIG
if (gps_cfg_enabled && i >= settings && i < settings + GPS_CFG_COUNT) {
return GPS_CFG_NAMES[i - settings];
}
if (gps_cfg_enabled) settings += GPS_CFG_COUNT; // keep the index right for settings listed after these
#endif
return NULL;
}

Expand All @@ -724,6 +775,18 @@ const char* EnvironmentSensorManager::getSettingValue(int i) const {
return gps_active ? "1" : "0";
}
#endif
#if ENV_GPS_PIN_CONFIG
if (gps_cfg_enabled && i >= settings && i < settings + GPS_CFG_COUNT) {
int k = i - settings;
long v = k == 0 ? (gps_pin_rx != -1 ? gps_pin_rx : GPS_DEFAULT_RX)
: k == 1 ? (gps_pin_tx != -1 ? gps_pin_tx : GPS_DEFAULT_TX)
: k == 2 ? (gps_pin_en != -1 ? gps_pin_en : GPS_DEFAULT_EN)
: (long)(gps_baud != 0 ? gps_baud : GPS_DEFAULT_BAUD);
snprintf(gps_cfg_val[k], sizeof(gps_cfg_val[k]), "%ld", v);
return gps_cfg_val[k];
}
if (gps_cfg_enabled) settings += GPS_CFG_COUNT;
#endif
return NULL;
}

Expand All @@ -743,19 +806,170 @@ bool EnvironmentSensorManager::setSettingValue(const char* name, const char* val
return true;
}
#endif
#if ENV_GPS_PIN_CONFIG
if (gps_cfg_enabled) {
if (strcmp(name, "gps_baud") == 0) {
long baud;
if (!parseGPSCfg(value, 0, 230400, baud)) return false;
if (baud != 0 && baud != 4800 && baud != 9600 && baud != 19200 && baud != 38400 &&
baud != 57600 && baud != 115200 && baud != 230400) {
return false;
}
gps_baud = baud == GPS_DEFAULT_BAUD ? 0 : baud;
return true;
}
int8_t* target = strcmp(name, "gps_rx") == 0 ? &gps_pin_rx
: strcmp(name, "gps_tx") == 0 ? &gps_pin_tx
: strcmp(name, "gps_en") == 0 ? &gps_pin_en : NULL;
if (target) {
long pin;
if (!parseGPSCfg(value, -1, INT8_MAX, pin)) return false;
int def = target == &gps_pin_rx ? GPS_DEFAULT_RX : target == &gps_pin_tx ? GPS_DEFAULT_TX : GPS_DEFAULT_EN;
if (pin == def) pin = -1; // the board default (or -1) means: follow the board default
if (pin != -1) {
// MCU drives the GPS RX line and the enable pin; GPS TX is input-only on our side
if (!isGPSPinAllowed(pin, target != &gps_pin_tx)) return false;
// no sharing between overrides (defaults not checked, so lines can be swapped one at a time)
if (target != &gps_pin_rx && pin == gps_pin_rx) return false;
if (target != &gps_pin_tx && pin == gps_pin_tx) return false;
if (target != &gps_pin_en && pin == gps_pin_en) return false;
}
*target = pin;
return true;
}
}
#endif
return false; // not supported
}

#if ENV_INCLUDE_GPS
void EnvironmentSensorManager::initBasicGPS() {
#if ENV_GPS_PIN_CONFIG
void EnvironmentSensorManager::configureGPS(int rx, int tx, int en, uint32_t baud) {
gps_cfg_enabled = true;
char buf[12];
// route through setSettingValue() so stored values get the same validation as app input
snprintf(buf, sizeof(buf), "%d", rx);
if (!setSettingValue("gps_rx", buf)) gps_pin_rx = -1;
snprintf(buf, sizeof(buf), "%d", tx);
if (!setSettingValue("gps_tx", buf)) gps_pin_tx = -1;
snprintf(buf, sizeof(buf), "%d", en);
if (!setSettingValue("gps_en", buf)) gps_pin_en = -1;
snprintf(buf, sizeof(buf), "%lu", (unsigned long)baud);
if (!setSettingValue("gps_baud", buf)) gps_baud = 0;
}

Serial1.setPins(PIN_GPS_TX, PIN_GPS_RX);
// Reject pins the board already uses, so a typo can't take out the radio, display or flash.
bool EnvironmentSensorManager::isGPSPinAllowed(int pin, bool output) const {
if (pin < 0) return false;
static const int reserved[] = {
#ifdef P_LORA_NSS
P_LORA_NSS,
#endif
#ifdef P_LORA_DIO_1
P_LORA_DIO_1,
#endif
#ifdef P_LORA_RESET
P_LORA_RESET,
#endif
#ifdef P_LORA_BUSY
P_LORA_BUSY,
#endif
#ifdef P_LORA_SCLK
P_LORA_SCLK,
#endif
#ifdef P_LORA_MISO
P_LORA_MISO,
#endif
#ifdef P_LORA_MOSI
P_LORA_MOSI,
#endif
#ifdef P_LORA_TX_LED
P_LORA_TX_LED,
#endif
#ifdef PIN_BOARD_SDA
PIN_BOARD_SDA,
#endif
#ifdef PIN_BOARD_SCL
PIN_BOARD_SCL,
#endif
#ifdef ENV_PIN_SDA
ENV_PIN_SDA,
#endif
#ifdef ENV_PIN_SCL
ENV_PIN_SCL,
#endif
#ifdef PIN_USER_BTN
PIN_USER_BTN,
#endif
#ifdef PIN_VEXT_EN
PIN_VEXT_EN,
#endif
#ifdef PIN_BUZZER
PIN_BUZZER,
#endif
#ifdef SERIAL_RX
SERIAL_RX,
#endif
#ifdef SERIAL_TX
SERIAL_TX,
#endif
-1
};
for (size_t i = 0; i < sizeof(reserved) / sizeof(reserved[0]); i++) {
if (pin == reserved[i]) return false;
}
#if defined(ESP32)
if (pin >= SOC_GPIO_PIN_COUNT) return false; // GPIO_IS_VALID_* shift by pin unchecked
if (!(output ? GPIO_IS_VALID_OUTPUT_GPIO(pin) : GPIO_IS_VALID_GPIO(pin))) return false;
#if defined(CONFIG_IDF_TARGET_ESP32S3)
if (pin >= 27 && pin <= 32) return false; // SPI flash
#ifdef BOARD_HAS_PSRAM
if (pin == 26) return false; // PSRAM CS
#endif
#if CONFIG_SPIRAM_MODE_OCT
if (pin >= 33 && pin <= 37) return false; // octal PSRAM
#endif
#if ARDUINO_USB_CDC_ON_BOOT
if (pin == 19 || pin == 20) return false; // native USB
#else
if (pin == 43 || pin == 44) return false; // UART0 (USB bridge)
#endif
#elif defined(CONFIG_IDF_TARGET_ESP32)
if (pin >= 6 && pin <= 11) return false; // SPI flash
if (pin == 1 || pin == 3) return false; // UART0 (USB bridge)
#endif
#elif defined(PINS_COUNT)
if (pin >= (int)PINS_COUNT) return false;
#endif
return true;
}
#endif

#if ENV_INCLUDE_GPS
void EnvironmentSensorManager::initBasicGPS() {
#ifdef PIN_GPS_RX
int pin_rx = PIN_GPS_RX;
#else
int pin_rx = -1;
#endif
#ifdef PIN_GPS_TX
int pin_tx = PIN_GPS_TX;
#else
int pin_tx = -1;
#endif
#ifdef GPS_BAUD_RATE
Serial1.begin(GPS_BAUD_RATE);
uint32_t baud = GPS_BAUD_RATE;
#else
Serial1.begin(9600);
uint32_t baud = 9600;
#endif
#if ENV_GPS_PIN_CONFIG
if (gps_pin_rx != -1) pin_rx = gps_pin_rx;
if (gps_pin_tx != -1) pin_tx = gps_pin_tx;
if (gps_baud != 0) baud = gps_baud;
if (gps_pin_en != -1) _location->setEnablePin(gps_pin_en);
#endif

Serial1.setPins(pin_tx, pin_rx);
Serial1.begin(baud);

// Try to detect if GPS is physically connected to determine if we should expose the setting
_location->begin();
Expand Down
Loading