// SPDX-License-Identifier: Apache-2.0 // Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors // // This file is part of WII5 Buoy firmware. // See LICENSE for full terms. /** * @file WII5GPS.cpp * @brief GPS driver: NMEA parsing via TinyGPS++, time/position updates. */ /* WII5 GPS */ #include #include #include #include // HARDWARE begin - Just once to configure void WII5GPS::begin() { enableNmea(); powerSetPin(POWER_GPS_PIN, POWER_GPS_ON); powerOff(true); // Forced off at boot #ifdef WII5_BUFFER_GPS setBuffer(wii5BufferGPS, WII5_BUFFER_GPS); #endif step = WII5GPS_OFF; stepWait = 0; running = false; minutes = DEFAULT_MINUTES; processMode = WII5SERIALPARSER_LINE; lastTime = 864000000; // 10 days ago sinceOnLast = 864000000; }; // True if last GPS waas more than 12 hours ago bool WII5GPS::old() { return (lastTime > (WII5TIME_12HOUR * 1000)); } void WII5GPS::off() { step = WII5GPS_OFF; stepWait = 0; stop(true); } void WII5GPS::on() { step = WII5GPS_ON; stepWait = 0; start(true); sinceOnLast = 0; } void WII5GPS::autoTime() { // Set mode to turn off after a Time, then step = OFF step = WII5GPS_TIME; stepWait = 0; start(true); } void WII5GPS::autoPos() { // Set mode to turn off after a position, then step = OFF step = WII5GPS_POS; stepWait = 0; start(true); } void WII5GPS::autoAccurate() { // Set mode to turn off after a more accurate position, then step = OFF step = WII5GPS_ACCURATE; stepWait = 0; start(true); } void WII5GPS::autoRepeat() { // Set mode to control GPS power on/off every N minutes to get a good time and position // keep step here. step = WII5GPS_REPEAT; stepWait = 0; start(true); } // start - and Stop - can be manually called but might get overwritten by modes in loop void WII5GPS::start(bool force) { if (!running || !stream || force) { powerOn(force); SerialGPS.begin(SerialGPS_Baud); stream = &SerialGPS; // waitTime = UPDATE_TIME; // Update as soon as ready waitTime = 0; // TODO 2024 - Change to 0 and shorter UPDATE TIME beginSerialManager(); // NOTE: This requires modified TinyGPS++ now /* TODO 2024 Removed - how to change? gps->encodedCharCount = 0; gps->sentencesWithFixCount = 0; gps->failedChecksumCount = 0; gps->passedChecksumCount = 0; */ finished = false; running = true; lastError = WII5ERROR_UNKNOWN; runTime = 0; sinceOnLast = 0; // Kept how long even with errors } } // stop void WII5GPS::stop(bool force) { if (running || force) { stream = NULL; SerialGPS.end(); endSerialManager(); powerOff(force); running = false; } } // set and get minutes void WII5GPS::setMinutes(uint8_t newMinutes) { minutes = newMinutes; } uint8_t WII5GPS::getMinutes() { return minutes; } // loop void WII5GPS::loop() { if (running) { WII5SerialManager::loop(); if ( // Wait - usually 5 minutes before updating time again (waitTime > UPDATE_TIME) && (gps->date.isValid()) && (gps->date.age() < 5000) && (gps->time.isValid()) && (gps->time.age() < 5000) && (gps->satellites.isValid()) && (gps->satellites.value() > 4) // 2024 moved back to 4 ? 2024 - Moved from 4 to 2, aka 5 satellites down to 3 ) { /* TODO 2024 console.log(LOG_INFO, F("GPS: set time before %d-%d-%d %d:%d:%d"), gps->date.year(), gps->date.month(), gps->date.day(), gps->time.hour(), gps->time.minute(), gps->time.second() ); */ console.log(LOG_INFO, F("GPS: Before setting time loop")); when = now(); setTime( gps->time.hour(), gps->time.minute(), gps->time.second(), gps->date.day(), gps->date.month(), gps->date.year() ); console.log(LOG_INFO, F("GPS: After setting time loop")); // Yay - we have a valid time - lets set this lastTime = 0; // Ignore anything less than 1 second (is there a way to do before setTime) (10 secconds) // TODO configurable... 10 seconds safe? // TODO abs - does it work with long //if ( ( (now() - when) > 10 ) || ( when - now() > 10) ) { #ifdef WII5_RTC wii5RTC.setRTC(); #endif //} waitTime = 0; } } first = (step != stepLast); stepLast = step; switch (step) { // WII5GPS_OFF - Completely off, powered down, disabled. case WII5GPS_OFF: // NOTE: This is called every time, but does nothing if already stoppped. Just safety if (first) stop(); // When to do more ? // - e..g no DateTime so trigger GPS break; // WII5GPS_QUIET - Turn on GPS and capture data normally, but don't print anything // (passthrough now separate) case WII5GPS_ON: if (first) start(); break; case WII5GPS_TIME: if (first) start(); // Valid and age is < 10 seconds??? if ( (stepWait > 5000) && gps->date.isValid() && (gps->date.age() < 10000) && gps->time.isValid() && (gps->time.age() < 10000) && (gps->satellites.isValid()) && (gps->satellites.value() > 4) // 2024 moved from 4 to 2 ) { lastError = WII5ERROR_NONE; // Callback or other things? finished = true; lastRunTime = (uint32_t)(runTime / 1000); // TODO Log and Display step = WII5GPS_OFF; stepWait = 0; } else if (stepWait > (wii5Config.getGpsTimeout() * 1000)) { lastError = WII5ERROR_TIMEOUT; finished = true; lastRunTime = (uint32_t)(runTime / 1000); step = WII5GPS_OFF; stepWait = 0; } break; case WII5GPS_POS: if (first) start(); // Valid and age is < 30 seconds??? if ( (stepWait > 5000) && gps->location.isValid() && (gps->time.age() < 20000) && (gps->satellites.isValid()) && (gps->satellites.value() >= 4) // 2024 Keep 3 for position ) { lastError = WII5ERROR_NONE; // Callback or other things? finished = true; lastRunTime = (uint32_t)(runTime / 1000); // TODO Log and Display step = WII5GPS_OFF; stepWait = 0; } else if (stepWait > (wii5Config.getGpsTimeout() * 1000)) { lastError = WII5ERROR_TIMEOUT; finished = true; lastRunTime = (uint32_t)(runTime / 1000); step = WII5GPS_OFF; stepWait = 0; } break; case WII5GPS_ACCURATE: if (first) start(); if ( // TODO HDOP, VDOP, TDOP - and possibly other average (stepWait > 5000) && gps->location.isValid() && (gps->date.isValid()) && (gps->date.age() < 5000) && (gps->time.isValid()) && (gps->time.age() < 5000) && (gps->satellites.isValid()) && (gps->satellites.value() >= 4) // 2024 - moved from >4 to >=4 aka 5 to 4 for accurate ) { lastError = WII5ERROR_NONE; // Callback or other things? finished = true; lastRunTime = (uint32_t)(runTime / 1000); // TODO Log and Display console.log(LOG_INFO, F("GPS: Accurate Complete")); console.log(LOG_INFO, F("GPS: Before setting time accurate")); when = now(); setTime( gps->time.hour(), gps->time.minute(), gps->time.second(), gps->date.day(), gps->date.month(), gps->date.year() ); console.log(LOG_INFO, F("GPS: After setting time accurate")); lastTime = 0; waitTime = 0; step = WII5GPS_OFF; stepWait = 0; } else if (stepWait > (wii5Config.getGpsTimeout() * 1000)) { lastError = WII5ERROR_TIMEOUT; finished = true; lastRunTime = (uint32_t)(runTime / 1000); step = WII5GPS_OFF; stepWait = 0; } break; case WII5GPS_REPEAT: if (first) start(); if ( (stepWait > 60000) && gps->location.isValid() && (gps->time.age() < 20000) && (gps->satellites.isValid()) && (gps->satellites.value() >= 4) // 2024 - moved from >4 to >=4 ) { stepWait = 0; // TODO Log and Display } break; default: step = WII5GPS_OFF; stepWait = 0; break; } return; } bool WII5GPS::isTimeValid() { // Less than 12 hours since we had a good lock? return ( (lastTime < (WII5TIME_12HOUR * 1000)) && (year() >= 2019) // TODO EWWWW YEAH EWWW I KNOW ! && (year() < 2045) ); } bool WII5GPS::ready() { return finished; } bool WII5GPS::isError() { return (lastError != WII5ERROR_NONE); } bool WII5GPS::isRunning() { return (step != WII5GPS_OFF); } void WII5GPS::dump(bool toConsole, Print* toOther) { // Error if (isError()) { snprintf_P( wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT, PSTR("@Metadata,gps,error=timeout\r\n") ); } else { snprintf_P( wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT, PSTR("@Metadata,gps,error=none\r\n") ); } if (toOther) toOther->print(wii5BufferConsolePrint); if (toConsole) console.print(wii5BufferConsolePrint); // Position snprintf_P( wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT, PSTR("@Metadata,gps,position,%ld,%ld,%ld,%lu\r\n"), long(gps->location.lat() * GPS_POS_MULT), long(gps->location.lng() * GPS_POS_MULT), long(gps->altitude.meters() * GPS_ALT_MULT), gps->location.age() ); if (toOther) toOther->print(wii5BufferConsolePrint); if (toConsole) console.print(wii5BufferConsolePrint); // Speed and Course snprintf_P( wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT, PSTR("@Metadata,gps,direction,%ld,%ld,%lu\r\n"), long(gps->course.deg() * 100), long(gps->speed.kmph() * 100), gps->speed.age() ); if (toOther) toOther->print(wii5BufferConsolePrint); if (toConsole) console.print(wii5BufferConsolePrint); // Datetime snprintf_P( wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT, PSTR("@Metadata,gps,datetime,%04d-%02d-%02dT%02d:%02d:%02d,%lu,%lu\r\n"), int(gps->date.year()), int(gps->date.month()), int(gps->date.day()), int(gps->time.hour()), int(gps->time.minute()), int(gps->time.second()), gps->date.value(), gps->date.age() ); if (toOther) toOther->print(wii5BufferConsolePrint); if (toConsole) console.print(wii5BufferConsolePrint); // Quality snprintf_P( wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT, PSTR("@Metadata,gps,quality,satellites=%lu,hdop=%lu,age=%lu\r\n"), gps->satellites.value(), gps->hdop.value(), gps->date.age() ); if (toOther) toOther->print(wii5BufferConsolePrint); if (toConsole) console.print(wii5BufferConsolePrint); // Stats snprintf_P( wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT, PSTR("@Metadata,gps,stats,locktime=%lu,chars=%lu,sentences=%lu\r\n"), lastRunTime, gps->charsProcessed(), gps->sentencesWithFix() ); if (toOther) toOther->print(wii5BufferConsolePrint); if (toConsole) console.print(wii5BufferConsolePrint); } WII5GPS wii5Gps;