Modified FiveTalk with GPS coordinates, Power has OLED display at outset

This commit is contained in:
John Poole 2026-02-19 21:22:25 -08:00
commit 38b80f97e5
4 changed files with 423 additions and 4 deletions

View file

@ -120,14 +120,21 @@ struct DateTime {
struct GpsState {
bool sawAnySentence = false;
bool hasValidUtc = false;
bool hasValidPosition = false;
bool hasValidAltitude = false;
uint8_t satsUsed = 0;
uint8_t satsInView = 0;
uint32_t lastUtcMs = 0;
DateTime utc{};
double latitudeDeg = 0.0;
double longitudeDeg = 0.0;
float altitudeM = 0.0f;
};
static GpsState g_gps;
static void parsePayloadCoords(const char* msg, char* latOut, size_t latLen, char* lonOut, size_t lonLen, char* altOut, size_t altLen);
enum class AppPhase : uint8_t {
WAIT_SD = 0,
WAIT_DISCIPLINE,
@ -320,11 +327,46 @@ static bool parseUInt2(const char* s, uint8_t& out) {
return true;
}
static bool parseNmeaCoordToDecimal(const char* raw, const char* hemi, bool isLat, double& outDeg) {
if (!raw || !hemi || raw[0] == '\0' || hemi[0] == '\0') return false;
int degDigits = isLat ? 2 : 3;
size_t n = strlen(raw);
if (n <= (size_t)degDigits + 2) return false;
for (int i = 0; i < degDigits; ++i) {
if (!isdigit((unsigned char)raw[i])) return false;
}
char degBuf[4] = {0};
memcpy(degBuf, raw, degDigits);
int deg = atoi(degBuf);
const char* minPtr = raw + degDigits;
double minutes = atof(minPtr);
if (minutes < 0.0 || minutes >= 60.0) return false;
double dec = (double)deg + (minutes / 60.0);
char h = (char)toupper((unsigned char)hemi[0]);
if (h == 'S' || h == 'W') {
dec = -dec;
} else if (h != 'N' && h != 'E') {
return false;
}
outDeg = dec;
return true;
}
static void parseRmc(char* fields[], int count) {
if (count <= 9) return;
const char* utc = fields[1];
const char* status = fields[2];
const char* latRaw = (count > 3) ? fields[3] : nullptr;
const char* latHem = (count > 4) ? fields[4] : nullptr;
const char* lonRaw = (count > 5) ? fields[5] : nullptr;
const char* lonHem = (count > 6) ? fields[6] : nullptr;
const char* date = fields[9];
if (!status || status[0] != 'A') return;
@ -343,12 +385,39 @@ static void parseRmc(char* fields[], int count) {
g_gps.utc.year = (uint16_t)(2000U + yy);
g_gps.hasValidUtc = true;
g_gps.lastUtcMs = millis();
double lat = 0.0;
double lon = 0.0;
if (parseNmeaCoordToDecimal(latRaw, latHem, true, lat) &&
parseNmeaCoordToDecimal(lonRaw, lonHem, false, lon)) {
g_gps.latitudeDeg = lat;
g_gps.longitudeDeg = lon;
g_gps.hasValidPosition = true;
}
}
static void parseGga(char* fields[], int count) {
if (count <= 7) return;
const char* latRaw = (count > 2) ? fields[2] : nullptr;
const char* latHem = (count > 3) ? fields[3] : nullptr;
const char* lonRaw = (count > 4) ? fields[4] : nullptr;
const char* lonHem = (count > 5) ? fields[5] : nullptr;
int sats = atoi(fields[7]);
if (sats >= 0 && sats <= 255) g_gps.satsUsed = (uint8_t)sats;
if (count > 9 && fields[9] && fields[9][0] != '\0') {
g_gps.altitudeM = (float)atof(fields[9]);
g_gps.hasValidAltitude = true;
}
double lat = 0.0;
double lon = 0.0;
if (parseNmeaCoordToDecimal(latRaw, latHem, true, lat) &&
parseNmeaCoordToDecimal(lonRaw, lonHem, false, lon)) {
g_gps.latitudeDeg = lat;
g_gps.longitudeDeg = lon;
g_gps.hasValidPosition = true;
}
}
static void parseGsv(char* fields[], int count) {
@ -632,6 +701,20 @@ static bool openSessionLogs() {
return true;
}
static void gpsFieldStrings(char* latOut, size_t latLen, char* lonOut, size_t lonLen, char* altOut, size_t altLen) {
if (latOut && latLen > 0) latOut[0] = '\0';
if (lonOut && lonLen > 0) lonOut[0] = '\0';
if (altOut && altLen > 0) altOut[0] = '\0';
if (g_gps.hasValidPosition) {
if (latOut && latLen > 0) snprintf(latOut, latLen, "%.6f", g_gps.latitudeDeg);
if (lonOut && lonLen > 0) snprintf(lonOut, lonLen, "%.6f", g_gps.longitudeDeg);
}
if (g_gps.hasValidAltitude) {
if (altOut && altLen > 0) snprintf(altOut, altLen, "%.2f", g_gps.altitudeM);
}
}
static void writeSentLog(int64_t epoch, const DateTime& dt) {
if (!g_sessionReady || !g_sentFile) return;
@ -642,11 +725,17 @@ static void writeSentLog(int64_t epoch, const DateTime& dt) {
char human[32];
formatUtcHuman(dt, human, sizeof(human));
g_sentFile.printf("epoch=%lld\tutc=%s\tunit=%s\tmsg=%s\ttx_count=%lu\tbatt_present=%u\tbatt_v=%.3f\n",
char lat[24], lon[24], alt[24];
gpsFieldStrings(lat, sizeof(lat), lon, sizeof(lon), alt, sizeof(alt));
g_sentFile.printf("epoch=%lld\tutc=%s\tunit=%s\tmsg=%s\tlat=%s\tlon=%s\talt_m=%s\ttx_count=%lu\tbatt_present=%u\tbatt_v=%.3f\n",
(long long)epoch,
human,
NODE_SHORT,
NODE_SHORT,
lat,
lon,
alt,
(unsigned long)g_txCount,
battPresent ? 1U : 0U,
battV);
@ -663,11 +752,17 @@ static void writeRecvLog(int64_t epoch, const DateTime& dt, const char* msg, flo
char human[32];
formatUtcHuman(dt, human, sizeof(human));
g_recvFile.printf("epoch=%lld\tutc=%s\tunit=%s\trx_msg=%s\trssi=%.1f\tsnr=%.1f\tbatt_present=%u\tbatt_v=%.3f\n",
char lat[24], lon[24], alt[24];
parsePayloadCoords(msg, lat, sizeof(lat), lon, sizeof(lon), alt, sizeof(alt));
g_recvFile.printf("epoch=%lld\tutc=%s\tunit=%s\trx_msg=%s\trx_lat=%s\trx_lon=%s\trx_alt_m=%s\trssi=%.1f\tsnr=%.1f\tbatt_present=%u\tbatt_v=%.3f\n",
(long long)epoch,
human,
NODE_SHORT,
msg ? msg : "",
lat,
lon,
alt,
rssi,
snr,
battPresent ? 1U : 0U,
@ -675,6 +770,41 @@ static void writeRecvLog(int64_t epoch, const DateTime& dt, const char* msg, flo
g_recvFile.flush();
}
static void buildTxPayload(char* out, size_t outLen) {
if (!out || outLen == 0) return;
out[0] = '\0';
char lat[24], lon[24], alt[24];
gpsFieldStrings(lat, sizeof(lat), lon, sizeof(lon), alt, sizeof(alt));
snprintf(out, outLen, "%s,%s,%s,%s", NODE_SHORT, lat, lon, alt);
}
static void parsePayloadCoords(const char* msg, char* latOut, size_t latLen, char* lonOut, size_t lonLen, char* altOut, size_t altLen) {
if (latOut && latLen > 0) latOut[0] = '\0';
if (lonOut && lonLen > 0) lonOut[0] = '\0';
if (altOut && altLen > 0) altOut[0] = '\0';
if (!msg || msg[0] == '\0') return;
char buf[128];
size_t n = strlen(msg);
if (n >= sizeof(buf)) n = sizeof(buf) - 1;
memcpy(buf, msg, n);
buf[n] = '\0';
char* saveptr = nullptr;
char* token = strtok_r(buf, ",", &saveptr); // unit label
(void)token;
token = strtok_r(nullptr, ",", &saveptr); // lat
if (token && latOut && latLen > 0) snprintf(latOut, latLen, "%s", token);
token = strtok_r(nullptr, ",", &saveptr); // lon
if (token && lonOut && lonLen > 0) snprintf(lonOut, lonLen, "%s", token);
token = strtok_r(nullptr, ",", &saveptr); // alt
if (token && altOut && altLen > 0) snprintf(altOut, altLen, "%s", token);
}
static void onLoRaDio1Rise() {
g_rxFlag = true;
}
@ -725,11 +855,13 @@ static void runTxScheduler() {
g_rxFlag = false;
g_radio.clearDio1Action();
int tx = g_radio.transmit(NODE_SHORT);
char payload[96];
buildTxPayload(payload, sizeof(payload));
int tx = g_radio.transmit(payload);
if (tx == RADIOLIB_ERR_NONE) {
g_txCount++;
writeSentLog(epoch, now);
logf("TX %s count=%lu", NODE_SHORT, (unsigned long)g_txCount);
logf("TX %s count=%lu payload=%s", NODE_SHORT, (unsigned long)g_txCount, payload);
} else {
logf("TX failed code=%d", tx);
}