Skip to content
Closed
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
52 changes: 52 additions & 0 deletions boards/gat562_mesh_tracker_pro.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"build": {
"arduino": {
"ldscript": "nrf52840_s140_v6.ld"
},
"core": "nRF5",
"cpu": "cortex-m4",
"extra_flags": "-DARDUINO_NRF52840_FEATHER -DNRF52840_XXAA",
"f_cpu": "64000000L",
"hwids": [
["0x239A", "0x8029"],
["0x239A", "0x0029"],
["0x239A", "0x002A"],
["0x239A", "0x802A"]
],
"usb_product": "GAT562 Mesh Tracker Pro",
"mcu": "nrf52840",
"variant": "gat562_mesh_tracker_pro",
"bsp": {
"name": "adafruit"
},
"softdevice": {
"sd_flags": "-DS140",
"sd_name": "s140",
"sd_version": "6.1.1",
"sd_fwid": "0x00B6"
},
"bootloader": {
"settings_addr": "0xFF000"
}
},
"connectivity": ["bluetooth"],
"debug": {
"jlink_device": "nRF52840_xxAA",
"svd_path": "nrf52840.svd",
"openocd_target": "nrf52840-mdk-rs"
},
"frameworks": ["arduino", "freertos"],
"name": "GAT562 Mesh Tracker Pro",
"upload": {
"maximum_ram_size": 248832,
"maximum_size": 815104,
"speed": 115200,
"protocol": "nrfutil",
"protocols": ["jlink", "nrfjprog", "nrfutil", "stlink"],
"use_1200bps_touch": true,
"require_upload_port": true,
"wait_for_upload_port": true
},
"url": "http://www.gat-iot.com/",
"vendor": "GAT"
}
59 changes: 59 additions & 0 deletions src/gps/GPS.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,20 @@ static struct uBloxGnssModelInfo {
#define GPS_SOL_EXPIRY_MS 5000 // in millis. give 1 second time to combine different sentences. NMEA Frequency isn't higher anyway
#define NMEA_MSG_GXGSA "GNGSA" // GSA message (GPGSA, GNGSA etc)

// How long to listen for a streaming NMEA GNSS before giving up on the current baud rate
#define GPS_NMEA_PROBE_TIMEOUT_MS 500
Comment on lines +74 to +75

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- file locations ---'
fd -i 'GPS.cpp|GPS.h|.*L76.*|.*gps.*' src
printf '%s\n' '--- symbol locations ---'
rg -n -C 8 'GPS_NMEA_PROBE_TIMEOUT_MS|probeForNMEA|isKnownNMEASentence|No GNSS Module|PCAS03|NMEA' src/gps/GPS.cpp src/gps 2>/dev/null || true
printf '%s\n' '--- focused source ---'
sed -n '50,95p' src/gps/GPS.cpp
sed -n '780,860p' src/gps/GPS.cpp
sed -n '1360,1435p' src/gps/GPS.cpp
sed -n '1470,1525p' src/gps/GPS.cpp
sed -n '1535,1610p' src/gps/GPS.cpp

Repository: meshtastic/firmware

Length of output: 50375


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- probe state machine ---'
sed -n '1240,1520p' src/gps/GPS.cpp
printf '%s\n' '--- probe helpers and timing primitives ---'
sed -n '1515,1600p' src/gps/GPS.cpp
rg -n -C 4 'currentStep|probe\(|GNSS_MODEL_GENERIC_NMEA|serialSpeed' src/gps/GPS.cpp src/gps/GPS.h | head -220
printf '%s\n' '--- relevant history summary ---'
git diff --stat
git status --short

Repository: meshtastic/firmware

Length of output: 30262


🌐 Web query:

Quectel L76K protocol specification NMEA output interval 1 Hz PCAS03

💡 Result:

In the Quectel L76K GNSS module, the NMEA output interval (positioning frequency) is configured using the PCAS02 command, not PCAS03 [1][2]. The PCAS03 command is specifically used to configure the output type and frequency (rate) of individual NMEA sentences [1][3]. PCAS02 Command (Positioning Frequency) To set the positioning frequency, use the $PCAS02 command [2]. The interval is specified in milliseconds [1]: - 1000 = 1 Hz - 500 = 2 Hz - 200 = 5 Hz Example: To set the positioning frequency to 1 Hz, use $PCAS02,1000*XX (where XX is the checksum). PCAS03 Command (NMEA Sentence Output) The PCAS03 command configures which NMEA sentences are output and how often (every Nth fix) they appear [1][4]. Its format is: $PCAS03,,,,,,,,,,,,,,*CS Parameters for PCAS03: - through : Define the output frequency for each sentence type. Setting these to 1 means outputting the sentence every fix; 0 means no output [1][5]. - Values 1–9 indicate the sentence will be output once every N position fixes [1][4]. - Leaving a field blank (null) keeps the previous configuration [1][3]. Note: When setting a positioning frequency higher than 1 Hz (i.e., interval < 1000), it is recommended to increase the serial baud rate to 115200 bps to ensure stable data transmission [6].

Citations:


🏁 Script executed:

#!/bin/bash
set -e
python3 - <<'PY'
# Read-only timing model for probeForNMEA's [start, start + timeout)
interval_ms = 1000
timeouts = (500, 1000, 1001, 1100)
phases = (0, 100, 499, 500, 750, 999, 1000)

for timeout in timeouts:
    misses = []
    for phase in phases:
        # A sentence starts at phase and then every interval. The probe starts at t=0.
        detected = any(0 <= phase + n * interval_ms < timeout for n in range(-2, 4))
        if not detected:
            misses.append(phase)
    print(f"timeout={timeout}ms; missed phases={misses}")

# A phase just after the half-second window demonstrates the 500 ms failure.
phase = 750
timeout = 500
print(f"example: first sentence at {phase}ms, detected={phase < timeout}")
PY

Repository: meshtastic/firmware

Length of output: 350


Wait for at least one complete NMEA interval. When the L76K emits sentences at 1 Hz, a 500 ms window can expire before the next sentence arrives, causing the No GNSS Module path. Set GPS_NMEA_PROBE_TIMEOUT_MS above 1000 ms.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/gps/GPS.cpp` around lines 74 - 75, Update the GPS_NMEA_PROBE_TIMEOUT_MS
constant in GPS.cpp to a value greater than 1000 ms, ensuring the GNSS probe
waits through at least one complete 1 Hz NMEA sentence interval before reporting
no module.


// True if the 5 chars following '$' identify a common NMEA GNSS sentence (e.g. GNGGA, GPRMC, GLGSV).
// The first char is always 'G' for GNSS talkers (GP, GL, GA, GB, GN, BD, QZ, ...).
static bool isKnownNMEASentence(const char *id)
{
if (id[0] != 'G')
return false;
return strncmp(id + 2, "RMC", 3) == 0 || strncmp(id + 2, "GGA", 3) == 0 || strncmp(id + 2, "GSV", 3) == 0 ||
strncmp(id + 2, "GSA", 3) == 0 || strncmp(id + 2, "VTG", 3) == 0 || strncmp(id + 2, "GLL", 3) == 0 ||
strncmp(id + 2, "ZDA", 3) == 0;
Comment on lines +79 to +85

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Accept the non-G talker IDs listed by the helper.

isKnownNMEASentence() returns false when id[0] != 'G'. The comment lists BD and QZ as supported prefixes. Therefore $BDGGA and $QZRMC are ignored.

Proposed fix
-    if (id[0] != 'G')
+    const bool isKnownTalker = id[0] == 'G' || (id[0] == 'B' && id[1] == 'D') ||
+                                (id[0] == 'Q' && id[1] == 'Z');
+    if (!isKnownTalker)
         return false;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
static bool isKnownNMEASentence(const char *id)
{
if (id[0] != 'G')
return false;
return strncmp(id + 2, "RMC", 3) == 0 || strncmp(id + 2, "GGA", 3) == 0 || strncmp(id + 2, "GSV", 3) == 0 ||
strncmp(id + 2, "GSA", 3) == 0 || strncmp(id + 2, "VTG", 3) == 0 || strncmp(id + 2, "GLL", 3) == 0 ||
strncmp(id + 2, "ZDA", 3) == 0;
static bool isKnownNMEASentence(const char *id)
{
const bool isKnownTalker = id[0] == 'G' || (id[0] == 'B' && id[1] == 'D') ||
(id[0] == 'Q' && id[1] == 'Z');
if (!isKnownTalker)
return false;
return strncmp(id + 2, "RMC", 3) == 0 || strncmp(id + 2, "GGA", 3) == 0 || strncmp(id + 2, "GSV", 3) == 0 ||
strncmp(id + 2, "GSA", 3) == 0 || strncmp(id + 2, "VTG", 3) == 0 || strncmp(id + 2, "GLL", 3) == 0 ||
strncmp(id + 2, "ZDA", 3) == 0;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/gps/GPS.cpp` around lines 79 - 85, Update isKnownNMEASentence so it
accepts the supported BD and QZ talker prefixes in addition to G, while
preserving the existing sentence-type checks for RMC, GGA, GSV, GSA, VTG, GLL,
and ZDA.

}

// For logging
static const char *getGPSPowerStateString(GPSPowerState state)
{
Expand Down Expand Up @@ -814,6 +828,11 @@ bool GPS::setup()
// enable RMC
_serial_gps->write("$CFGMSG,0,4,1,1*1F\r\n");
delay(250);
} else if (gnssModel == GNSS_MODEL_GENERIC_NMEA) {
// A module that auto-streams standard NMEA but ignores vendor probe commands (e.g. the GAT562's
// L76K in its default 9600 multi-GNSS mode). No chip-specific init is possible or needed: the
// streaming sentences are fed straight to the TinyGPS++ parser.
LOG_INFO("GNSS: using generic NMEA stream, skipping chip-specific init");
}
didSerialInit = true;
}
Expand Down Expand Up @@ -1388,6 +1407,12 @@ GnssModel_t GPS::probe(int serialSpeed)
// Check that the returned response class and message ID are correct
GPS_RESPONSE response = getACK(0x06, 0x08, 750);
if (response == GNSS_RESPONSE_NONE) {
if (probeForNMEA(GPS_NMEA_PROBE_TIMEOUT_MS)) {
LOG_INFO("GNSS: no probe response, but NMEA stream detected at %d baud - using generic NMEA driver",
serialSpeed);
currentStep = 0;
return GNSS_MODEL_GENERIC_NMEA;
}
Comment on lines +1410 to +1415

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- GPS.cpp relevant ranges ---'
sed -n '1280,1430p' src/gps/GPS.cpp
sed -n '1470,1610p' src/gps/GPS.cpp
printf '%s\n' '--- related declarations and constants ---'
rg -n -C 4 'GPS_NMEA_PROBE_TIMEOUT_MS|probeForNMEA|PCAS03|isKnownNMEASentence|GENERIC_NMEA|serialSpeed' src/gps/GPS.cpp src/gps
printf '%s\n' '--- repository status summary ---'
git diff --stat

Repository: meshtastic/firmware

Length of output: 36658


🌐 Web query:

Quectel L76K protocol specification PCAS03 output rate 0 no output

💡 Result:

According to the Quectel L76K GNSS Protocol Specification, the $PCAS03 command is used to configure the output type and frequency of NMEA sentences [1][2][3]. Regarding your query about the "0" output rate: - Setting a parameter to "0" for any NMEA sentence (GGA, GLL, GSA, GSV, RMC, VTG, ZDA, or ANT) in the $PCAS03 command explicitly disables the output of that specific sentence [1][4][3]. - If you set a field to "0", the module will no longer output the corresponding NMEA sentence [1][2]. - Leaving a field empty (e.g., $PCAS03,1,,,,,,,,,0,002) instead of providing a value generally indicates that the previous configuration for that specific sentence should be retained [1][4]. For example, if you send $PCAS03,0,0,0,0,0,0,0,0,0,0,,,,0,0xx, you would effectively disable all primary NMEA sentence outputs listed in the command structure [1][3]. If you are experiencing "no output" after configuring the device, verify that you have not inadvertently set all sentence frequencies to 0, or that you have not misconfigured the serial port baud rate using the $PCAS01 command [5]. If the module is not outputting any data at all, ensure the hardware connection is correct and check the baud rate settings, as the PCAS03 command only controls the NMEA sentence frequency, not the physical serial port transmission itself [5].

Citations:


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- probe call/state flow ---'
sed -n '500,545p' src/gps/GPS.cpp
sed -n '1385,1515p' src/gps/GPS.cpp
printf '%s\n' '--- PCAS03 references ---'
rg -n -C 3 '\$PCAS03|PCAS03' .
printf '%s\n' '--- NMEA predicate exact source ---'
sed -n '68,95p' src/gps/GPS.cpp

Repository: meshtastic/firmware

Length of output: 10022


Probe for generic NMEA before disabling output.

If a L76K accepts the all-zero $PCAS03 command at line 1332, it disables all NMEA sentences before probeForNMEA() runs at lines 1410 and 1504. Move generic detection before this command, or restore NMEA output before probing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/gps/GPS.cpp` around lines 1410 - 1415, Update the GPS detection flow
around probeForNMEA() so generic NMEA detection occurs before the L76K all-zero
$PCAS03 command can disable NMEA output, or restore NMEA output before each
generic probe. Preserve the existing GNSS_MODEL_GENERIC_NMEA return path and
apply the fix to both probeForNMEA() call sites.

LOG_WARN("No GNSS Module (baudrate %d)", serialSpeed);
currentDelay = 2000;
currentStep = 0;
Expand Down Expand Up @@ -1476,6 +1501,11 @@ GnssModel_t GPS::probe(int serialSpeed)
}
}
}
if (probeForNMEA(GPS_NMEA_PROBE_TIMEOUT_MS)) {
LOG_INFO("GNSS: no probe response, but NMEA stream detected at %d baud - using generic NMEA driver", serialSpeed);
currentStep = 0;
return GNSS_MODEL_GENERIC_NMEA;
}
LOG_WARN("No GNSS Module (baudrate %d)", serialSpeed);
currentDelay = 2000;
currentStep = 0;
Expand Down Expand Up @@ -1534,6 +1564,35 @@ GnssModel_t GPS::getProbeResponse(unsigned long timeout, const std::vector<ChipI
return GNSS_MODEL_UNKNOWN; // Return unknown on timeout
}

// Cheap listen for a live NMEA GNSS. Some modules (e.g. the GAT562's L76K in default 9600 multi-GNSS mode)
// auto-stream standard NMEA sentences but never answer vendor probe commands. We look for a known sentence
// type ($GNGGA, $GPRMC, $GPGSV, ...) within the listen window and treat that as a valid GNSS.
bool GPS::probeForNMEA(unsigned long timeout)
{
clearBuffer();
unsigned long start = millis();
char sentence[8] = {0};
uint8_t idx = 0;
while (millis() - start < timeout) {
if (_serial_gps->available()) {
char c = _serial_gps->read();
if (c == '$') {
idx = 0;
memset(sentence, 0, sizeof(sentence));
} else if (idx < 5) {
sentence[idx++] = c;
if (idx == 5 && isKnownNMEASentence(sentence)) {
#ifdef GPS_DEBUG
LOG_DEBUG("NMEA stream detected: %s", sentence);
#endif
return true;
}
}
}
}
return false;
}

std::unique_ptr<GPS> GPS::createGps()
{
int8_t _rx_gpio = config.position.rx_gpio;
Expand Down
6 changes: 5 additions & 1 deletion src/gps/GPS.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ typedef enum {
GNSS_MODEL_AG3335,
GNSS_MODEL_AG3352,
GNSS_MODEL_LS20031,
GNSS_MODEL_CM121
GNSS_MODEL_CM121,
GNSS_MODEL_GENERIC_NMEA
} GnssModel_t;

typedef enum {
Expand Down Expand Up @@ -254,6 +255,9 @@ class GPS : private concurrency::OSThread
// Get GNSS model
GnssModel_t probe(int serialSpeed);

// Cheap check for a live, streaming NMEA GNSS at the current baud rate
bool probeForNMEA(unsigned long timeout);

// delay counter to allow more sats before fixed position stops GPS thread
uint8_t fixeddelayCtr = 0;
};
Expand Down
15 changes: 15 additions & 0 deletions variants/nrf52840/gat562_mesh_tracker_pro/platformio.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
; GAT562 Mesh Tracker Pro with Trackball support
[env:gat562_mesh_tracker_pro]
extends = nrf52840_base
board = gat562_mesh_tracker_pro
board_level = extra
build_flags = ${nrf52840_base.build_flags}
-I variants/nrf52840/gat562_mesh_tracker_pro
-D GAT562_MESH_TRACKER_PRO
-DGPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely.
-DRADIOLIB_EXCLUDE_SX128X=1
-DRADIOLIB_EXCLUDE_SX127X=1
-DRADIOLIB_EXCLUDE_LR11X0=1
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/gat562_mesh_tracker_pro>
lib_deps =
${nrf52840_base.lib_deps}
54 changes: 54 additions & 0 deletions variants/nrf52840/gat562_mesh_tracker_pro/variant.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
Copyright (c) 2014-2015 Arduino LLC. All right reserved.
Copyright (c) 2016 Sandeep Mistry All right reserved.
Copyright (c) 2018, Adafruit Industries (adafruit.com)

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/

#include "variant.h"
#include "nrf.h"
#include "wiring_constants.h"
#include "wiring_digital.h"

const uint32_t g_ADigitalPinMap[] = {
// P0
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,

// P1
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};

void initVariant()
{
// LED1 & LED2
pinMode(PIN_LED1, OUTPUT);
ledOff(PIN_LED1);

pinMode(PIN_LED2, OUTPUT);
ledOff(PIN_LED2);

// 3V3 Power Rail
pinMode(PIN_3V3_EN, OUTPUT);
digitalWrite(PIN_3V3_EN, HIGH);

// Initialize trackball pins as inputs with pullup
#ifdef HAS_TRACKBALL
pinMode(TB_UP, INPUT_PULLUP);
pinMode(TB_DOWN, INPUT_PULLUP);
pinMode(TB_LEFT, INPUT_PULLUP);
pinMode(TB_RIGHT, INPUT_PULLUP);
pinMode(TB_PRESS, INPUT_PULLUP);
#endif
}
Loading