// 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 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 #include #include 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;