295abb37ee
Firmware for an autonomous wave-measurement buoy (ATmega2560-based WII5 v2 board). Reads wave motion from a Sparton AHRS-M1/M2 IMU, samples GPS and battery state, and reports back over Iridium SBD satellite telemetry. Originally developed 2012-2024. This is the first public release. Code, documentation, and field-tested operating modes (Capture, Sleep, Position, ManualTest, SelfTest, LowBattery) are licensed under Apache 2.0 — see LICENSE and NOTICE. See README.md for an overview and build instructions, CONTRIBUTING.md for how to contribute, and DEPLOYMENTS.md for the field-deployment log.
395 lines
11 KiB
C++
395 lines
11 KiB
C++
// SPDX-License-Identifier: Apache-2.0
|
|
// Copyright (c) 2012-2024 Scott Penrose <scottp@dd.com.au> 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 <Arduino.h>
|
|
#include <WII5.h>
|
|
#include <WII5GPS.h>
|
|
#include <WII5Sh3dConsole.h>
|
|
|
|
// 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;
|