mirror of
https://github.com/pelgraine/Meck.git
synced 2026-08-10 02:32:44 +02:00
implemented $PCAS04,7 - Multi-constellation mode (GPS, Galileo, BeiDou, and QZSS)
The default LilyGo/MeshOS firmware uses GPS-only ($PCAS04,1) & $PMTK869,1,1 - EASY predicted ephemeris
This commit is contained in:
@@ -2582,6 +2582,15 @@ void setup() {
|
||||
#ifdef PIN_GPS_EN
|
||||
digitalWrite(PIN_GPS_EN, GPS_EN_ACTIVE);
|
||||
#endif
|
||||
#if defined(LilyGo_TDeck_Pro_Max)
|
||||
// Speed up / improve the MAX GPS fix: enable multi-constellation
|
||||
// (GPS + GLONASS + BeiDou) and EASY predicted ephemeris (warm/hot starts
|
||||
// on subsequent boots). A module that doesn't recognise a given sentence
|
||||
// simply ignores it. Sent once here, after the module is powered.
|
||||
delay(300); // allow the module to finish booting before accepting config
|
||||
Serial2.print("$PCAS04,7*1E\r\n"); // GPS + GLONASS + BeiDou
|
||||
Serial2.print("$PMTK869,1,1*35\r\n"); // EASY predicted ephemeris ON
|
||||
#endif
|
||||
sensors.setSettingValue("gps", "1");
|
||||
} else {
|
||||
#ifdef PIN_GPS_EN
|
||||
@@ -2723,6 +2732,28 @@ void loop() {
|
||||
|
||||
sensors.loop();
|
||||
|
||||
// Satellite-count diagnostic (MAX): every 10s while GPS is on, print the
|
||||
// reported satellite count, fix state and NMEA rate so the multi-constellation
|
||||
// change can be watched climbing. Remove once confirmed if you don't want it.
|
||||
#if defined(LilyGo_TDeck_Pro_Max) && HAS_GPS
|
||||
{
|
||||
static unsigned long lastSatDiag = 0;
|
||||
if (the_mesh.getNodePrefs()->gps_enabled && millis() - lastSatDiag >= 10000) {
|
||||
lastSatDiag = millis();
|
||||
auto* lp = sensors.getLocationProvider();
|
||||
if (lp) {
|
||||
char sv[64];
|
||||
gpsStream.getInViewBreakdown(sv, sizeof(sv));
|
||||
Serial.printf("[GPS] inview=%u [%s] used=%d fix=%s nmea=%u/s total=%lu\n",
|
||||
gpsStream.getSatellitesInView(), sv,
|
||||
lp->satellitesCount(), lp->isValid() ? "Y" : "N",
|
||||
gpsStream.getSentencesPerSec(),
|
||||
(unsigned long)gpsStream.getSentenceCount());
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// GPS diagnostic — disabled to reduce serial noise (uncomment for debugging)
|
||||
// #if HAS_GPS
|
||||
// {
|
||||
|
||||
@@ -84,6 +84,21 @@
|
||||
// from the same mesh callback on the same thread.
|
||||
static bool s_lastMsgSuppressed = false;
|
||||
|
||||
#ifdef KB_BL_PIN
|
||||
// Drive the keyboard-backlight pin for the message/ring flash. On the T-Deck
|
||||
// Pro MAX the manual (both-shifts) keyboard-backlight toggle drives this pin
|
||||
// via LEDC (analogWrite); once LEDC is bound to a pin a later digitalWrite is
|
||||
// swallowed, so the flash must use analogWrite too or it silently does nothing
|
||||
// after the manual toggle has been used. Other boards keep the plain GPIO write.
|
||||
static inline void kbBacklightFlashWrite(bool on) {
|
||||
#if defined(LilyGo_TDeck_Pro_Max)
|
||||
analogWrite(KB_BL_PIN, on ? 255 : 0);
|
||||
#else
|
||||
digitalWrite(KB_BL_PIN, on ? HIGH : LOW);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
class SplashScreen : public UIScreen {
|
||||
UITask* _task;
|
||||
unsigned long dismiss_after;
|
||||
@@ -1747,7 +1762,7 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, i
|
||||
// Keyboard flash notification (suppress for room sync and muted channels)
|
||||
#ifdef KB_BL_PIN
|
||||
if (_node_prefs->kb_flash_notify && !isRoomMsg && !suppressNotif) {
|
||||
digitalWrite(KB_BL_PIN, HIGH);
|
||||
kbBacklightFlashWrite(true);
|
||||
_kb_flash_off_at = millis() + 200; // 200ms flash
|
||||
}
|
||||
#endif
|
||||
@@ -1994,10 +2009,10 @@ void UITask::loop() {
|
||||
#ifdef HAS_4G_MODEM
|
||||
// Don't turn off LED if incoming call flash is active
|
||||
if (!_incomingCallRinging) {
|
||||
digitalWrite(KB_BL_PIN, LOW);
|
||||
kbBacklightFlashWrite(false);
|
||||
}
|
||||
#else
|
||||
digitalWrite(KB_BL_PIN, LOW);
|
||||
kbBacklightFlashWrite(false);
|
||||
#endif
|
||||
_kb_flash_off_at = 0;
|
||||
}
|
||||
@@ -2025,7 +2040,7 @@ void UITask::loop() {
|
||||
_incomingCallRinging = false;
|
||||
// Only turn off LED if message flash isn't also active
|
||||
if (!_kb_flash_off_at) {
|
||||
digitalWrite(KB_BL_PIN, LOW);
|
||||
kbBacklightFlashWrite(false);
|
||||
}
|
||||
_callFlashState = false;
|
||||
}
|
||||
@@ -2035,7 +2050,7 @@ void UITask::loop() {
|
||||
unsigned long now = millis();
|
||||
if (now >= _nextCallFlash) {
|
||||
_callFlashState = !_callFlashState;
|
||||
digitalWrite(KB_BL_PIN, _callFlashState ? HIGH : LOW);
|
||||
kbBacklightFlashWrite(_callFlashState);
|
||||
// 250ms on, 250ms off — fast pulse to distinguish from single msg flash
|
||||
_nextCallFlash = now + 250;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
// Transparent Stream wrapper that counts NMEA sentences (newline-delimited)
|
||||
// flowing from the GPS serial port to the MicroNMEA parser.
|
||||
//
|
||||
// Usage: Instead of MicroNMEALocationProvider gps(Serial2, &rtc_clock);
|
||||
// Use: GPSStreamCounter gpsStream(Serial2);
|
||||
// MicroNMEALocationProvider gps(gpsStream, &rtc_clock);
|
||||
//
|
||||
// Every read() call passes through to the underlying stream; when a '\n'
|
||||
// is seen the sentence counter increments. This lets the UI display a
|
||||
// live "nmea" count so users can confirm the baud rate is correct and
|
||||
// the GPS module is actually sending data.
|
||||
//
|
||||
// MAX addition: as bytes pass through, each completed $xxGSV sentence is
|
||||
// tapped for its "satellites in view" field. The per-constellation talkers
|
||||
// (GP/GL/GA/BD/GB/...) are tracked separately and summed, so the total
|
||||
// reflects multi-constellation reception (used to confirm $PCAS04,7 took
|
||||
// effect). This costs only a small per-byte accumulation in read().
|
||||
|
||||
class GPSStreamCounter : public Stream {
|
||||
public:
|
||||
GPSStreamCounter(Stream& inner)
|
||||
: _inner(inner), _sentences(0), _sentences_snapshot(0),
|
||||
_last_snapshot(0), _sentences_per_sec(0), _line_len(0), _gsv_n(0) {}
|
||||
|
||||
// --- Stream read interface (passes through) ---
|
||||
int available() override { return _inner.available(); }
|
||||
int peek() override { return _inner.peek(); }
|
||||
|
||||
int read() override {
|
||||
int c = _inner.read();
|
||||
if (c < 0) return c;
|
||||
|
||||
// Accumulate the current sentence so completed $xxGSV lines can be tapped
|
||||
// for their satellites-in-view count.
|
||||
if (c == '$') {
|
||||
_line_len = 0;
|
||||
_line[_line_len++] = (char)c;
|
||||
} else if (c == '\n') {
|
||||
if (_line_len > 0) { _line[_line_len] = '\0'; parseGSV(_line); }
|
||||
_sentences++;
|
||||
_line_len = 0;
|
||||
} else if (c != '\r' && _line_len < (int)sizeof(_line) - 1) {
|
||||
_line[_line_len++] = (char)c;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
// --- Stream write interface (pass through for NMEA commands if needed) ---
|
||||
size_t write(uint8_t b) override { return _inner.write(b); }
|
||||
|
||||
// --- Sentence counting API ---
|
||||
|
||||
// Total sentences received since boot (or last reset)
|
||||
uint32_t getSentenceCount() const { return _sentences; }
|
||||
|
||||
// Sentences received per second (updated each time you call it,
|
||||
// with a 1-second rolling window)
|
||||
uint16_t getSentencesPerSec() {
|
||||
unsigned long now = millis();
|
||||
unsigned long elapsed = now - _last_snapshot;
|
||||
if (elapsed >= 1000) {
|
||||
uint32_t delta = _sentences - _sentences_snapshot;
|
||||
// Scale to per-second if interval wasn't exactly 1000ms
|
||||
_sentences_per_sec = (uint16_t)((delta * 1000UL) / elapsed);
|
||||
_sentences_snapshot = _sentences;
|
||||
_last_snapshot = now;
|
||||
}
|
||||
return _sentences_per_sec;
|
||||
}
|
||||
|
||||
// Total satellites in view across all constellations, summed from the latest
|
||||
// $xxGSV per talker. Talkers not heard in the last few seconds are dropped so
|
||||
// the figure tracks the live sky rather than accumulating stale entries.
|
||||
uint16_t getSatellitesInView() {
|
||||
uint16_t total = 0;
|
||||
unsigned long now = millis();
|
||||
for (int i = 0; i < _gsv_n; i++) {
|
||||
if (now - _gsv[i].seen <= 8000) total += _gsv[i].inview;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
// Per-talker in-view summary, e.g. "GP=11 GL=0 GA=5 BD=8", for diagnostics.
|
||||
// Only talkers heard in the last few seconds are listed.
|
||||
void getInViewBreakdown(char* buf, size_t len) {
|
||||
if (len == 0) return;
|
||||
buf[0] = '\0';
|
||||
size_t pos = 0;
|
||||
unsigned long now = millis();
|
||||
for (int i = 0; i < _gsv_n; i++) {
|
||||
if (now - _gsv[i].seen > 8000) continue;
|
||||
int n = snprintf(buf + pos, len - pos, "%s%c%c=%u",
|
||||
pos ? " " : "", _gsv[i].t0, _gsv[i].t1, _gsv[i].inview);
|
||||
if (n <= 0 || (size_t)n >= len - pos) break;
|
||||
pos += (size_t)n;
|
||||
}
|
||||
}
|
||||
|
||||
// Reset all counters (e.g. when GPS hardware power cycles)
|
||||
void resetCounters() {
|
||||
_sentences = 0;
|
||||
_sentences_snapshot = 0;
|
||||
_sentences_per_sec = 0;
|
||||
_last_snapshot = millis();
|
||||
_gsv_n = 0;
|
||||
_line_len = 0;
|
||||
}
|
||||
|
||||
private:
|
||||
Stream& _inner;
|
||||
volatile uint32_t _sentences;
|
||||
uint32_t _sentences_snapshot;
|
||||
unsigned long _last_snapshot;
|
||||
uint16_t _sentences_per_sec;
|
||||
|
||||
// Current sentence accumulation (for the GSV tap)
|
||||
char _line[100];
|
||||
int _line_len;
|
||||
|
||||
// Per-talker satellites-in-view, e.g. GP (GPS), GL (GLONASS), BD/GB (BeiDou),
|
||||
// GA (Galileo). Small fixed table; combined "GN" talkers are skipped so they
|
||||
// don't double-count the per-constellation sentences.
|
||||
struct GsvEntry { char t0; char t1; uint8_t inview; unsigned long seen; };
|
||||
GsvEntry _gsv[6];
|
||||
int _gsv_n;
|
||||
|
||||
void parseGSV(const char* s) {
|
||||
// Expect "$ttGSV,total,num,inview,..." where tt is the 2-char talker id.
|
||||
if (s[0] != '$' || s[1] == '\0' || s[2] == '\0') return;
|
||||
if (!(s[3] == 'G' && s[4] == 'S' && s[5] == 'V')) return;
|
||||
char t0 = s[1], t1 = s[2];
|
||||
if (t0 == 'G' && t1 == 'N') return; // combined talker - skip to avoid double count
|
||||
|
||||
// The satellites-in-view value is the field after the 3rd comma.
|
||||
const char* p = s;
|
||||
int commas = 0;
|
||||
while (*p && commas < 3) { if (*p == ',') commas++; p++; }
|
||||
if (commas < 3) return;
|
||||
int inview = atoi(p);
|
||||
|
||||
unsigned long now = millis();
|
||||
for (int i = 0; i < _gsv_n; i++) {
|
||||
if (_gsv[i].t0 == t0 && _gsv[i].t1 == t1) {
|
||||
_gsv[i].inview = (uint8_t)inview;
|
||||
_gsv[i].seen = now;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (_gsv_n < (int)(sizeof(_gsv) / sizeof(_gsv[0]))) {
|
||||
_gsv[_gsv_n].t0 = t0;
|
||||
_gsv[_gsv_n].t1 = t1;
|
||||
_gsv[_gsv_n].inview = (uint8_t)inview;
|
||||
_gsv[_gsv_n].seen = now;
|
||||
_gsv_n++;
|
||||
}
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user