Initial public release of WII5 Buoy firmware
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.
This commit is contained in:
+394
@@ -0,0 +1,394 @@
|
||||
// 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;
|
||||
Reference in New Issue
Block a user