diff --git a/docs/faq.md b/docs/faq.md index ade59bf0090..ba73b0264f1 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -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) @@ -665,6 +666,25 @@ Both the Windows and Mac versions of the client app are fully unlocked and are f - MeshCore Advantages by MCarper: - MeshCore vs Meshtastic by austinmesh.org: +### 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 +``` + --- diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 6112b2dbb17..e05c0479727 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -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); diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 944790df922..6d46f5306e1 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -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"); diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index 3cceed56668..646d5139257 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -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) @@ -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) { } diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 839d4958336..e1abe95b97b 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -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 diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 0dc892c3a52..c702c7b792d 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -1,6 +1,12 @@ #include "EnvironmentSensorManager.h" #include +#if ENV_GPS_PIN_CONFIG + #include // RADIOLIB_NC in variant pin defines + #ifdef ESP32 + #include + #endif +#endif #if ENV_PIN_SDA && ENV_PIN_SCL #define TELEM_WIRE &Wire1 // Use Wire1 as the I2C bus for Environment Sensors @@ -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; } @@ -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; } @@ -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; } @@ -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(); diff --git a/src/helpers/sensors/EnvironmentSensorManager.h b/src/helpers/sensors/EnvironmentSensorManager.h index 17706cbc492..19b73e9e4aa 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.h +++ b/src/helpers/sensors/EnvironmentSensorManager.h @@ -4,6 +4,11 @@ #include #include +// Not for RAK WisBlock GPS (same condition as RAK_WISBLOCK_GPS in the .cpp), which probes sockets instead +#if ENV_INCLUDE_GPS && !(defined(RAK_BOARD) && !defined(RAK_WISMESH_TAG)) + #define ENV_GPS_PIN_CONFIG 1 +#endif + class EnvironmentSensorManager : public SensorManager { protected: static const int MAX_ACTIVE_SENSORS = 16; @@ -25,6 +30,17 @@ class EnvironmentSensorManager : public SensorManager { #if ENV_INCLUDE_GPS LocationProvider* _location; + #if ENV_GPS_PIN_CONFIG + // Runtime overrides of PIN_GPS_RX/TX/EN and GPS_BAUD_RATE (-1 / 0 = variant default). + // Only exposed as settings once the app calls configureGPS(), since it must persist them. + bool gps_cfg_enabled = false; + int8_t gps_pin_rx = -1; + int8_t gps_pin_tx = -1; + int8_t gps_pin_en = -1; + uint32_t gps_baud = 0; + mutable char gps_cfg_val[4][12]; + bool isGPSPinAllowed(int pin, bool output) const; + #endif void start_gps(); void stop_gps(); void initBasicGPS(); @@ -39,6 +55,14 @@ class EnvironmentSensorManager : public SensorManager { EnvironmentSensorManager(LocationProvider &location): _location(&location){}; LocationProvider* getLocationProvider() { return _location; } bool isGPSDetected() const override { return gps_detected; } + #if ENV_GPS_PIN_CONFIG + // Call before begin(). Invalid values fall back to the variant defaults. + void configureGPS(int rx, int tx, int en, uint32_t baud); + // Current overrides (-1 / 0 = board default), for persisting after a setting change. + void getGPSOverrides(int8_t& rx, int8_t& tx, int8_t& en, uint32_t& baud) const { + rx = gps_pin_rx; tx = gps_pin_tx; en = gps_pin_en; baud = gps_baud; + } + #endif #else EnvironmentSensorManager(){}; #endif diff --git a/src/helpers/sensors/LocationProvider.h b/src/helpers/sensors/LocationProvider.h index bc4f1fe7648..03e038ce172 100644 --- a/src/helpers/sensors/LocationProvider.h +++ b/src/helpers/sensors/LocationProvider.h @@ -26,6 +26,8 @@ class LocationProvider { virtual void stop() = 0; virtual void loop() = 0; virtual bool isEnabled() = 0; + // Override the power/enable pin chosen at construction (-1 = none). Call before begin(). + virtual void setEnablePin(int pin) { } // Format compact diagnostics in the caller-provided buffer. Providers that // have more information (for example UART counters) can override this. diff --git a/src/helpers/sensors/MicroNMEALocationProvider.h b/src/helpers/sensors/MicroNMEALocationProvider.h index 37ef5762c48..bd30d073b40 100644 --- a/src/helpers/sensors/MicroNMEALocationProvider.h +++ b/src/helpers/sensors/MicroNMEALocationProvider.h @@ -147,6 +147,18 @@ public : release(); } + void setEnablePin(int pin) override { + if (pin == _pin_en) return; + if (_pin_en != -1) { + pinMode(_pin_en, INPUT); // release the variant's default pin + } + _pin_en = pin; + if (_pin_en != -1) { + pinMode(_pin_en, OUTPUT); + digitalWrite(_pin_en, !GPS_EN_ACTIVE); + } + } + bool isEnabled() override { // directly read the enable pin if present as gps can be // activated/deactivated outside of here ...