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
+651
View File
@@ -0,0 +1,651 @@
// 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 WII5ModeSelfTest.cpp
* @brief Self test mode: automated boot-time hardware checks.
*/
/*
WII5 SelfTest
TODO:
* Show values at the end
* Maths loop back test
* Out of range Temperature / Voltage ?
* Not detecting data Iridium even though it is there
* Not detecting data Sparton even though it is there
MODE Default:
* Standard tests (including LED/Buzzer/Button)
* But don't expect user feedback (aka MANUAL for results)
* Turn on Maths, wait for boot and Hello, Shutdown maths
MODE FromMaths:
* Standard tests (including LED/Buzzer/Button)
* No Maths tests at all
MODE NoMaths:
* Existing code, expect user to check voltages etc
*/
#include <Arduino.h>
#include <TimeLib.h>
#include <WII5.h>
void WII5ModeSelfTest::reset() {
step = WII5ST_START; stepWait = 0;
}
void WII5ModeSelfTest::begin() {
step = WII5ST_START;
mode = WII5SM_DEFAULT;
}
WII5SELFTEST_MODE WII5ModeSelfTest::getMode() {
return mode;
}
void WII5ModeSelfTest::setMode(WII5SELFTEST_MODE m) {
mode = m;
}
void WII5ModeSelfTest::dump(bool toConsole, Print* toOther, bool hideNone) {
count_passed = 0;
count_nonenc = 0;
count_failed = 0;
// TODO Temp hack - BOARD should always be success
results[WII5STD_BOARD] = WII5STS_SUCCESS;
for (WII5SELFTEST_DEVICES i = 0; i < WII5STD_DEVICES; i = i + 1) {
switch(results[i]) {
case WII5STS_NONE:
case WII5STS_NC:
count_nonenc++;
break;
case WII5STS_SUCCESS:
case WII5STS_HUMAN:
case WII5STS_SLOW:
count_passed++;
break;
default:
count_failed++;
};
if ( !hideNone && ( (results[i] == WII5STS_NONE) || (results[i] == WII5STS_NC)) ) {
// Hidden this time
}
else {
snprintf_P(
wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT,
PSTR("@Metadata,selftest,%s,"),
wii5Strings.strSelfTestDevice(i)
);
if (toOther) toOther->print(wii5BufferConsolePrint);
if (toConsole) console.print(wii5BufferConsolePrint);
snprintf_P(
wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT,
PSTR("status=%s,"),
wii5Strings.strSelfTestStatus(results[i])
);
if (toOther) toOther->print(wii5BufferConsolePrint);
if (toConsole) console.print(wii5BufferConsolePrint);
snprintf_P(
wii5BufferConsolePrint, WII5_BUFFER_CONSOLE_PRINT,
PSTR("description=%s\r\n"),
wii5Strings.strSelfTestStatusLong(results[i])
);
if (toOther) toOther->print(wii5BufferConsolePrint);
if (toConsole) console.print(wii5BufferConsolePrint);
}
}
}
void WII5ModeSelfTest::loop() {
first = (step != stepLast);
stepLast = step;
if (first)
count++;
if (console.available()) {
// Exit Test gracefully - show report
if (console.getCommand() == 'X') {
step = WII5ST_REPORT; stepWait = 0; first = true;
}
// Restart existing test
else if (console.getCommand() == 'S') {
step = WII5ST_START; stepWait = 0; first = true;
}
// Force WDT Test and exit
else if (console.getCommand() == 'W') {
console.printf(F("WARNING - Disabled external WDT reset pin - runCount=%lu\r\n"), sh3dNodeConfig.getRunCount());
wii5Controller.tempDisableWDT = true;
wii5Controller.setDefaultMode(); // Back to default mode now? so no reboot/report
return;
}
}
switch (step) {
case WII5ST_START:
if (first) {
count = 0;
for (WII5SELFTEST_DEVICES i = 0; i < WII5STD_DEVICES; i = i + 1) {
results[i] = WII5STS_NONE;
}
results[WII5STD_BOARD] = WII5STS_SUCCESS;
console.log(LOG_INFO, F("SelfTest %d/%d: Start"), count, WII5ST_MAX);
}
step = WII5ST_SHUTDOWN; stepWait = 0;
break;
case WII5ST_SHUTDOWN: // Shutdown the existing bits (e.g. stopping anything we need to)
if (first) {
console.log(LOG_INFO, F("SelfTest %d/%d: Shutdown old processes"), count, WII5ST_MAX);
}
step = WII5ST_LED; stepWait = 0;
break;
case WII5ST_LED:
if (first) {
console.log(LOG_INFO, F("SelfTest %d/%d: LED (check for fast, irregular then back to normal) y; | n; to confirm/reject"), count, WII5ST_MAX);
sh3dNodeIO.led1Set(LED_FAST);
stepCount = 0;
}
if (console.available() && (console.getCommand() == 'y')) {
sh3dNodeIO.led1Set(LED_SLOW);
console.log(LOG_INFO, F("SelfTest: success"));
results[WII5STD_LED] = WII5STS_SUCCESS;
step = WII5ST_BUZZER; stepWait = 0;
}
else if (console.available() && (console.getCommand() == 'n')) {
sh3dNodeIO.led1Set(LED_SLOW);
console.log(LOG_INFO, F("SelfTest: failed"));
results[WII5STD_LED] = WII5STS_UNKNOWN;
step = WII5ST_BUZZER; stepWait = 0;
}
if (stepWait > 20000) {
sh3dNodeIO.led1Set(LED_SLOW);
console.log(LOG_INFO, F("SelfTest: timeout"));
results[WII5STD_LED] = WII5STS_TIMEOUT;
step = WII5ST_BUZZER; stepWait = 0;
}
else if (stepWait > 5000) {
if (stepCount == 1) {
sh3dNodeIO.led1Set(LED_DOUBLE_LONG);
stepCount++;
}
}
else if (stepWait > 2500) {
if (stepCount == 0) {
sh3dNodeIO.led1Set(LED_DOUBLE);
stepCount++;
}
}
break;
case WII5ST_BUZZER:
if (first) {
console.log(LOG_INFO, F("SelfTest %d/%d: BUZZER (check for fast, irregular then back to normal) y; | n; to confirm/reject"), count, WII5ST_MAX);
sh3dNodeIO.led2Set(LED_FAST);
stepCount = 0;
}
if (console.available() && (console.getCommand() == 'y')) {
sh3dNodeIO.led2Set(LED_OFF);
results[WII5STD_BUZZER] = WII5STS_SUCCESS;
step = WII5ST_BUTTON; stepWait = 0;
}
else if (console.available() && (console.getCommand() == 'n')) {
sh3dNodeIO.led2Set(LED_OFF);
results[WII5STD_BUZZER] = WII5STS_UNKNOWN;
step = WII5ST_BUTTON; stepWait = 0;
}
if (stepWait > 20000) {
sh3dNodeIO.led2Set(LED_OFF);
results[WII5STD_BUZZER] = WII5STS_TIMEOUT;
step = WII5ST_BUTTON; stepWait = 0;
}
else if (stepWait > 5000) {
if (stepCount == 1) {
sh3dNodeIO.led2Set(LED_DOUBLE_LONG);
stepCount++;
}
}
else if (stepWait > 2500) {
if (stepCount == 0) {
sh3dNodeIO.led2Set(LED_DOUBLE);
stepCount++;
}
}
break;
case WII5ST_BUTTON:
if (first) {
console.log(LOG_INFO, F("SelfTest %d/%d: Button. Toggle button at least twice"), count, WII5ST_MAX);
wii5Commands.disableButtons = 0;
stepCount = 0;
}
else if (sh3dNodeIO.button1->isClicked()) {
stepCount++;
}
else if (stepCount >= 2) {
console.log(LOG_INFO, F("SelfTest: Button detected 2 or more clicks success"));
results[WII5STD_BUTTON] = WII5STS_SUCCESS;
step = WII5ST_INFORMATION; stepWait = 0;
}
else if (console.available() && (console.getCommand() == 'y')) {
console.log(LOG_INFO, F("SelfTest: Button user clicked 'y' manually - human yes"));
results[WII5STD_BUTTON] = WII5STS_HUMAN;
step = WII5ST_INFORMATION; stepWait = 0;
}
else if (console.available() && (console.getCommand() == 'n')) {
console.log(LOG_INFO, F("SelfTest: Button user clicked 'n' manually - failed"));
results[WII5STD_BUTTON] = WII5STS_UNKNOWN;
step = WII5ST_INFORMATION; stepWait = 0;
}
else if (stepWait > 20000) {
console.log(LOG_INFO, F("SelfTest: Button timeout"));
results[WII5STD_BUTTON] = WII5STS_TIMEOUT;
step = WII5ST_INFORMATION; stepWait = 0;
}
break;
case WII5ST_INFORMATION: // What do we know: our ID: our run count etc
if (first) {
console.log(LOG_INFO, F("SelfTest %d/%d: Information"), count, WII5ST_MAX);
}
step = WII5ST_CONFIG; stepWait = 0;
break;
case WII5ST_CONFIG: // Check we can read and write config
if (first) {
console.log(LOG_INFO, F("SelfTest %d/%d: Config"), count, WII5ST_MAX);
}
if ( (wii5Config.getDeviceId() < 1) ) {
results[WII5STD_EEPROM] = WII5STS_UNKNOWN;
}
else if (
(wii5Config.getDeviceId() > 1000)
&& (wii5Config.getDeviceId() < 99999)
) {
results[WII5STD_EEPROM] = WII5STS_SUCCESS;
}
else {
results[WII5STD_EEPROM] = WII5STS_OUTOFRANGE;
}
step = WII5ST_GPS_RECEIVE; stepWait = 0;
break;
case WII5ST_GPS_RECEIVE: // Turn on GPS and receive some NMEA
#ifdef WII5_GPS
if (first) {
console.log(LOG_INFO, F("SelfTest %d/%d: GPS Started, please wait"), count, WII5ST_MAX);
wii5Gps.autoAccurate();
}
if (
(stepWait > 10000)
|| (wii5Gps.isError())
|| (wii5Gps.ready())
) {
console.log(LOG_INFO, F("SelfTest %d/%d: GPS complete"), count, WII5ST_MAX);
step = WII5ST_GPS_OFF; stepWait = 0;
}
#else
step = WII5ST_SD0; stepWait = 0;
results[WII5STD_GPS] = WII5STS_NC;
#endif
break;
case WII5ST_GPS_OFF: // Turn off: and check GPS is ok
#ifdef WII5_GPS
if (first) {
wii5Gps.off(); // TODO inconsistent OFF vs STOP ?
console.log(LOG_INFO, F("SelfTest %d/%d: GPS Off"), count, WII5ST_MAX);
}
if(wii5Gps.isError()) {
results[WII5STD_GPS] = WII5STS_UNKNOWN;
}
else if (wii5Gps.ready()) {
results[WII5STD_GPS] = WII5STS_SUCCESS;
}
else if (wii5Gps.gps->charsProcessed() > 300) {
results[WII5STD_GPS] = WII5STS_SLOW;
}
else {
results[WII5STD_GPS] = WII5STS_TIMEOUT;
}
step = WII5ST_SD0; stepWait = 0;
break;
#else
step = WII5ST_SD0; stepWait = 0;
#endif
// TODO GPS power testing
case WII5ST_SD0: // Make sure SD are both turned off
if (first) {
console.log(LOG_INFO, F("SelfTest %d/%d: SD0 - powering on but not configured"), count, WII5ST_MAX);
}
if (stepWait > 1000) {
step = WII5ST_SD1; stepWait = 0;
}
break;
case WII5ST_SD1: // Open SD1: write a file with unique id/datetime
if (first) {
console.log(LOG_INFO, F("SelfTest %d/%d: SD1"), count, WII5ST_MAX);
results[WII5STD_SD1] = WII5STS_SUCCESS;
step = WII5ST_SD2; stepWait = 0;
}
break;
case WII5ST_SD2: // Open SD2 and write a new file
if (first) {
console.log(LOG_INFO, F("SelfTest %d/%d: SD2"), count, WII5ST_MAX);
// TODO See above
results[WII5STD_SD2] = WII5STS_SUCCESS;
step = WII5ST_SD_OFF; stepWait = 0;
}
break;
// TODO: SD1_B / SD2_B verification — re-enable once data round-trip
// checks are implemented (similar to the SD Card tester).
case WII5ST_SD_OFF: // Shut it down
if (first) {
console.log(LOG_INFO, F("SelfTest %d/%d: SD_OFF"), count, WII5ST_MAX);
}
step = WII5ST_IRIDIUM; stepWait = 0;
break;
case WII5ST_IRIDIUM: // Turn on Iridium and wait for some responses
#ifdef WII5_COMMS_IRIDIUM
if (first) {
console.log(LOG_INFO, F("SelfTest %d/%d: Iridium (takes time, Console loggging enabled)"), count, WII5ST_MAX);
wii5Iridium.setPassthrough(false);
wii5Iridium.requestFirmware();
}
// Found IMEI - Yay Iridium !
else if (strlen(wii5Iridium.imei) > 3) {
wii5Iridium.stop();
results[WII5STD_IRIDIUM] = WII5STS_SUCCESS;
step = WII5ST_BATTERY_1; stepWait = 0;
}
else if (stepWait > 10000) {
wii5Iridium.stop();
results[WII5STD_IRIDIUM] = WII5STS_TIMEOUT;
step = WII5ST_BATTERY_1; stepWait = 0;
}
#else
results[WII5STD_IRIDIUM] = WII5STS_NC;
step = WII5ST_BATTERY_1; stepWait = 0;
#endif
break;
case WII5ST_BATTERY_1: // Check battery 1 has volts and doesn't jitter too much
if (first) {
console.log(LOG_INFO, F("SelfTest %d/%d: Battery 1"), count, WII5ST_MAX);
wii5Battery.start();
}
else if (stepWait > 5000) {
results[WII5STD_BATTERY] = WII5STS_TIMEOUT;
wii5Battery.stop();
step = WII5ST_WEATHER; stepWait = 0;
}
else if (! wii5Battery.isRunning()) {
if (
(wii5Battery.value < 1500)
&& (wii5Battery.value > 700)
) {
results[WII5STD_BATTERY] = WII5STS_SUCCESS;
}
else {
results[WII5STD_BATTERY] = WII5STS_OUTOFRANGE;
}
wii5Battery.stop();
step = WII5ST_WEATHER; stepWait = 0;
}
break;
/*
case WII5ST_BATTERY_2: // If it exists
if (first) {
console.log(LOG_INFO, F("SelfTest %d/%d: Battery 2"), count, WII5ST_MAX);
}
step = WII5ST_MATHS_START; stepWait = 0;
break;
*/
case WII5ST_WEATHER:
if (first) {
console.log(LOG_INFO, F("SelfTest %d/%d: Read Temperature"), count, WII5ST_MAX);
wii5Weather_18B20.temperatureRead();
if (
(wii5Weather_18B20.value > 100)
&& (wii5Weather_18B20.value < 5000)
&& (wii5Weather_18B20.value < 2000)
) {
results[WII5STD_18B20] = WII5STS_SUCCESS;
}
else if (
(wii5Weather_18B20.value)
) {
// OR slow ?
results[WII5STD_18B20] = WII5STS_OUTOFRANGE;
}
else {
results[WII5STD_18B20] = WII5STS_UNKNOWN;
}
}
step = WII5ST_5VOLT_START; stepWait = 0;
break;
case WII5ST_5VOLT_START:
if (first) {
if (mode == WII5SM_FROMMATHS) {
console.log(LOG_INFO, F("SelfTest %d/%d: From Maths - skipping 5V and Maths"), count, WII5ST_MAX);
step = WII5ST_RTC; stepWait = 0;
}
else {
console.log(LOG_INFO, F("SelfTest %d/%d: 5VOLT Power On for 30 seconds"), count, WII5ST_MAX);
}
}
else if (console.available() && (console.getCommand() == 'y')) {
step = WII5ST_5VOLT_OFF; stepWait = 0;
}
else if (console.available() && (console.getCommand() == 'n')) {
step = WII5ST_RTC; stepWait = 0;
}
else if (stepWait > 30000) {
step = WII5ST_5VOLT_OFF; stepWait = 0;
}
break;
case WII5ST_5VOLT_OFF:
if (first) {
console.log(LOG_INFO, F("SelfTest %d/%d: 5VOLT Off for 30 seconds"), count, WII5ST_MAX);
}
else if (console.available() && (console.getCommand() == 'y')) {
results[WII5STD_5VOLT] = WII5STS_SUCCESS;
step = WII5ST_MATHS_START; stepWait = 0;
}
else if (console.available() && (console.getCommand() == 'n')) {
results[WII5STD_5VOLT] = WII5STS_UNKNOWN;
step = WII5ST_RTC; stepWait = 0;
}
else if (stepWait > 30000) {
results[WII5STD_5VOLT] = WII5STS_TIMEOUT;
step = WII5ST_MATHS_START; stepWait = 0;
}
break;
case WII5ST_MATHS_START: // Turn it on ! and make sure we get response
if (first) {
console.log(LOG_INFO, F("SelfTest %d/%d: Maths Power On for 30 seconds"), count, WII5ST_MAX);
wii5Maths.powerOn();
}
else if (console.available() && (console.getCommand() == 'y')) {
step = WII5ST_MATHS_OFF; stepWait = 0;
}
else if (console.available() && (console.getCommand() == 'n')) {
step = WII5ST_RTC; stepWait = 0;
}
else if (stepWait > 30000) {
step = WII5ST_MATHS_OFF; stepWait = 0;
}
break;
case WII5ST_MATHS_OFF: // Turn it off: and make sure it is off
if (first) {
console.log(LOG_INFO, F("SelfTest %d/%d: Maths Off for 30 seconds"), count, WII5ST_MAX);
wii5Maths.powerOff();
}
else if (console.available() && (console.getCommand() == 'y')) {
results[WII5STD_MATHS] = WII5STS_SUCCESS;
step = WII5ST_RTC; stepWait = 0;
}
else if (console.available() && (console.getCommand() == 'n')) {
results[WII5STD_MATHS] = WII5STS_UNKNOWN;
step = WII5ST_RTC; stepWait = 0;
}
else if (stepWait > 30000) {
results[WII5STD_MATHS] = WII5STS_TIMEOUT;
step = WII5ST_RTC; stepWait = 0;
}
break;
case WII5ST_RTC: // Do we have one: what version: what details
if (first) {
#ifdef WII5_RTC
console.log(LOG_INFO, F("SelfTest %d/%d: RTC - Checking Temperature"), count, WII5ST_MAX);
int t = int(wii5RTC.getTemperature());
console.log(LOG_INFO, F("SelfTest RTC temperature = %d"), t);
if (t > 1 && t < 50) {
results[WII5STD_RTC] = WII5STS_SUCCESS;
}
else {
results[WII5STD_RTC] = WII5STS_OUTOFRANGE;
}
#else
console.log(LOG_INFO, F("SelfTest %d/%d: RTC - NOT ENABLED"), count, WII5ST_MAX);
results[WII5STD_RTC] = WII5STS_NC;
#endif
}
step = WII5ST_SPARTON; stepWait = 0;
break;
case WII5ST_SPARTON: // Turn it on: get some values: tun it off
if (first) {
console.log(LOG_INFO, F("SelfTest %d/%d: Sparton"), count, WII5ST_MAX);
wii5Sparton.automatic(64, 180, false);
}
else if (wii5Sparton.captureFinished()) {
wii5Sparton.stop();
if (
1
// (wii5Sparton.lastAccelZ > 900)
// && (wii5Sparton.lastAccelZ < 1100)
) {
results[WII5STD_SPARTON] = WII5STS_SUCCESS;
}
else {
results[WII5STD_SPARTON] = WII5STS_OUTOFRANGE;
}
step = WII5ST_REPORT; stepWait = 0;
}
else if (stepWait > 30000) {
wii5Sparton.stop();
results[WII5STD_SPARTON] = WII5STS_TIMEOUT;
step = WII5ST_REPORT; stepWait = 0;
}
break;
case WII5ST_REPORT: // Report what happened
if (first) {
console.log(LOG_INFO, F("SelfTest %d/%d: COMPLETE - Running Report (console only)"), count, WII5ST_MAX);
// step = WII5ST_SHUTDOWN;
// wii5Metadata.setPassthrough(true);
// wii5Metadata.setCapture(false);
// wii5Metadata.report();
/*
Human readalbe
* ID
* Number of passes / fails - %
* List of fails
*/
console.printf(F("# SelfTest: Passed=%d, None/NC=%d, Failed=%d\r\n"),
count_passed, count_nonenc, count_failed
);
for (WII5SELFTEST_DEVICES i = 0; i < WII5STD_DEVICES; i = i + 1) {
switch(results[i]) {
case WII5STS_NONE:
case WII5STS_NC:
case WII5STS_SUCCESS:
case WII5STS_HUMAN:
case WII5STS_SLOW:
break;
default:
console.printf(F("# FAILED = %s"),
wii5Strings.strSelfTestDevice(i)
);
console.printf(F(" status=%s"),
wii5Strings.strSelfTestStatus(results[i])
);
console.printf(F(" description=%s\r\n"),
wii5Strings.strSelfTestStatusLong(results[i])
);
break;
};
}
// Manual output numbers we know....
console.printf(F("# VALUES: Temperature=%d\r\n"), wii5Weather_18B20.value);
console.printf(F("# VALUES: Battery=%d\r\n"), wii5Battery.value);
if (mode == WII5SM_FROMMATHS) {
console.printf(F("# 60 seconds until back to default mode\r\n"));
}
else {
console.printf(F("# Commands (or 60 seconds until reboot)\r\n"));
console.printf(F("# .;=back to default, W;=disable WDT, R;=restart.\r\n"));
}
}
else if (stepWait > 60000) {
if (mode == WII5SM_FROMMATHS) {
wii5Controller.setDefaultMode();
}
else {
wii5Controller.setDefaultMode(); // Back to default mode now?
sh3dNodeUtil.reset();
}
}
break;
default:
step = WII5ST_START;
}
}
WII5ModeSelfTest wii5ModeSelfTest;