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:
2026-05-07 16:00:21 +10:00
commit 295abb37ee
122 changed files with 38142 additions and 0 deletions
+394
View File
@@ -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;