// 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 WII5ModeCapture.cpp * @brief Capture mode: IMU/wave-motion data capture and SD logging. */ /* MDOECapture - the main event * Watch for our perfect time to start * Prepare SD card, Sparton etc for running * Trigger start / stop sparton * Store data to disk */ #include #include #include void WII5ModeCapture::reset() { step = WII5CAPTURE_OFF; wait = 0; stepCount = 0; stepLast = WII5CAPTURE_FINISH; captureType = WII5CAPTURETYPE_UNKNOWN; } void WII5ModeCapture::begin() { debug = true; step = WII5CAPTURE_OFF; wait = 0; stepCount = 0; captureType = WII5CAPTURETYPE_UNKNOWN; enableAtFile = false; enableGPS = true; enableMaths = true; enableComms = true; enableLiveSwap = true; // Playing with this being safe for 2024 } void WII5ModeCapture::start(bool force) { // If OFF or WAITING for OFF - Skipt if ( (step == WII5CAPTURE_OFF) || (step == WII5CAPTURE_WAIT5_OFF) || force ) { console.log(LOG_INFO, F("Capture: MANUAL START - Now")); captureType = WII5CAPTURETYPE_MANUAL; captureStart = now(); step = WII5CAPTURE_START; wait = 0; retry = 0; stepCount = 0; } else { // Switch to CANCEL Capture with restart now console.log(LOG_INFO, F("Capture: MANUAL START - Cancelling existing and restarting")); captureType = WII5CAPTURETYPE_MANUAL; captureStart = now(); step = WII5CAPTURE_CANCEL_RESTART; wait = 0; retry = 0; stepCount = 0; } } void WII5ModeCapture::stop(bool force) { if ((step != WII5CAPTURE_OFF) || force) { // Switch to CANCEL Capture with OFF (aka Wait) step = WII5CAPTURE_CANCEL_OFF; wait = 0; retry = 0; stepCount = 0; } else { // Manually just double check everything is off. (done above in the CANCEL_OFF stage) if (enableGPS) wii5Gps.off(); wii5Sparton.stop(); } captureType = WII5CAPTURETYPE_UNKNOWN; wait = 0; retry = 0; stepCount = 0; } elapsedMillis test1; void WII5ModeCapture::loop() { // Calculate seconds until next capture has to be started bool first = (stepLast != step); stepLast = step; switch (step) { case WII5CAPTURE_OFF: // Total minutes since midnight minutes = (hour() * 60) + minute(); // Automatically set to a max of 12 hours period = wii5Config.getCapturePeriod(); remaining = period - (minutes % period); if (first) { console.log(LOG_INFO, F("Capture: OFF, waiting")); sh3dNodeIO.led1Set( LED_SLOW ); } // else if ((wii5Gps.sinceOnLast > (WII5TIME_1HOUR * 1000)) && (!wii5Gps.isTimeValid())) // TODO 2024 Changed from 1 hour to 5 minutes - This should change to do a full auto every run // Note: that & isTimeValid would often be true... else if (wii5Gps.sinceOnLast > 300000) { console.log(LOG_INFO, F("Capture: Internal clock / time is not valid, turn on GPS")); step = WII5CAPTURE_TIME; wait = 0; } // NOTE: we have only 60 seconds else if ( (minutes % period) == 0) { console.log(LOG_INFO, F("Capture: Starting minutes match period min=%lu, period=%lu"), minutes, wii5Config.getCapturePeriod()); // TODO atData !!! step = WII5CAPTURE_START; wait = 0; retry = 0; stepCount = 0; captureType = WII5CAPTURETYPE_TIME; captureStart = now(); } // 20 or more mimutes /* else if (remaining > 30) { // Hmmm... lets shut down everything, get the power lower // TODO check Maths and Communications is off. if (!wii5Communications.isRunning()) wii5Communications.stop(); if (wii5Controller.getSD() != 0) wii5Controller.setSDOff(); } */ // 3 or more minutes else if (remaining > 3) { // Run temperature if more than 5 minutes old if (wii5Weather_18B20.age > 300000) { wii5Weather_18B20.temperatureRead(); wii5Battery.start(); } } // TODO // - Iridium: loop can do its own start/stop processing data // - Weather & Battery already do their own // - // TODO // If WAIT is > X minutes, sleep, or just low power mode? // GPS off, Iridium Off, Maths off, Sparton off, SD off // Power Consumption = X // AVR off as well // Power Consumption = X break; case WII5CAPTURE_START: // Output Manual vs Time start switch(captureType) { case WII5CAPTURETYPE_MANUAL: wii5Display.statusSend(F("CaptureMode"), F("start,manual")); wii5Display.atDataSend( F("CaptureCommand"), F("START,Manual") ); break; case WII5CAPTURETYPE_TIME: wii5Display.statusSend(F("CaptureMode"), F("start,timeperiod")); wii5Display.atDataSend( F("CaptureCommand"), F("START,Time,period=%lu,minutes=%lu"), wii5Config.getCapturePeriod(), minutes ); break; }; startTime = now(); step = WII5CAPTURE_PREPARE; wait = 0; retry = 0; stepCount = 0; break; case WII5CAPTURE_TIME: if (first) { // wii5Gps.autoTime(); wii5Gps.autoAccurate(); // TODO 2024 - Moved to full auto accurate instead } else if (wii5Gps.ready()) { step = WII5CAPTURE_OFF; wait = 0; } else if (wait > 300000) { step = WII5CAPTURE_OFF; wait = 0; } break; case WII5CAPTURE_PREPARE: // Experiment - start communications DURING a run wii5Communications.setAutoMode(); // Some emergency off, just in case - also handy as it does a reboot wii5Sparton.stop(); wii5Sparton.off(); step = WII5CAPTURE_PREPARE_ID; wait = 0; retry = 0; stepCount = 0; break; case WII5CAPTURE_PREPARE_ID: wii5Config.updateRecordCount(); step = WII5CAPTURE_PREPARE_SD; wait = 0; retry = 0; stepCount = 0; break; case WII5CAPTURE_PREPARE_SD: // TODO - consider power cylce and delays, and consequence on Maths // TODO - Maybe only if card not open? if (wii5Controller.getSD() == 1) { wii5Controller.setSD1(); } else { wii5Controller.setSD2(); } // TODO retry X times with power retry every second? // TODO 2024 ! ZOMG ! safeDelay ? or not care? (was 1500) delay(1000); // TODO Yes I know ! // TODO - If card not open? if (sdBlock.cardOpen(wii5Controller.getSD())) { wii5Display.statusSend(F("CaptureMode"), F("sdcard,current=%d"), wii5Controller.getSD()); wii5Display.atDataSend( F("CaptureCommand"), F("SDCard,current,%d"), wii5Controller.getSD() ); // open card - else - bugger ! if (sdBlock.dataOpen(wii5Config.getRecordCount())) { wii5Display.atDataSend( F("CaptureCommand"), F("SDCard,current,%d,recordCount=%lu"), wii5Controller.getSD(), wii5Config.getRecordCount() ); #ifdef WII5_GPS if (enableGPS) { wii5Gps.autoAccurate(); } #endif step = WII5CAPTURE_PREPARE_COMMS; wait = 0; retry = 0; stepCount = 0; } else { sh3dNodeIO.led1Set( LED_FAST ); console.log(LOG_FATAL, F("CAPTURE: Failed dataOpen cardNum=%d"), wii5Controller.getSD()); snprintf_P( wii5Commands.lastCommandMessage, sizeof(wii5Commands.lastCommandMessage), PSTR("@Err,sd,dataopen,%d"), wii5Controller.getSD() ); wii5Commands.lastCommandCmd = 0; wii5Communications.sendError(); // Flip SD for next time if (wii5Controller.getSD() == 1) wii5Controller.setSD2(); else wii5Controller.setSD1(); console.log(LOG_FATAL, F("CAPTURE: Cards swapped cardNum=%d"), wii5Controller.getSD()); step = WII5CAPTURE_WAIT5_OFF; wait = 0; retry = 0; stepCount = 0; } } else { sh3dNodeIO.led1Set( LED_FAST ); console.log(LOG_FATAL, F("CAPTURE: Failed cardOpen cardNum=%d"), wii5Controller.getSD()); snprintf_P( wii5Commands.lastCommandMessage, sizeof(wii5Commands.lastCommandMessage), PSTR("@Err,sd,cardopen,%d"), wii5Controller.getSD() ); wii5Commands.lastCommandCmd = 0; wii5Communications.sendError(); // Flip SD for next time if (wii5Controller.getSD() == 1) wii5Controller.setSD2(); else wii5Controller.setSD1(); console.log(LOG_FATAL, F("CAPTURE: Cards swapped cardNum=%d"), wii5Controller.getSD()); step = WII5CAPTURE_WAIT5_OFF; wait = 0; retry = 0; stepCount = 0; } break; case WII5CAPTURE_PREPARE_COMMS: // For now - skipping comms - leaving until the end step = WII5CAPTURE_PREPARE_SPARTON; wait = 0; retry = 0; stepCount = 0; break; // Prepare the sparton to go case WII5CAPTURE_PREPARE_SPARTON: #ifdef WII5_IMU_SPARTON wii5Display.statusSend(F("CaptureMode"), F("sparton,start")); // TODO Configuration wii5Sparton.setBinary(1); wii5Sparton.setHz(8); wii5Sparton.setRecords(wii5Config.getCaptureRecords()); wii5Sparton.setCapture(1); // Start // TODO 2024 - Do we need to wait? - or just start? When is commands sent to Sparton? wii5Sparton.start(); #endif step = WII5CAPTURE_INPROGRESS; wait = 0; retry = 0; stepCount = 0; break; // Capture is running - report progress? (might be done in IMU) case WII5CAPTURE_INPROGRESS: if (first) { sh3dNodeIO.led1Set( LED_TRIPPLE ); } else if (wii5Sparton.captureFinished()) { step = WII5CAPTURE_SHUTDOWN; stepCount = 0; wait = 0; } // 30 minutes else if (wait > 1800000) { console.log(LOG_FATAL, F("Capture: Failed - timeout after 30 minutes")); // TODO 2024 - sendError? sh3dNodeIO.led1Set( LED_FAST ); step = WII5CAPTURE_CANCEL_OFF; wait = 0; retry = 0; stepCount = 0; } break; // Shutdown the capture devices, IMU etc (should be internal IMIU) case WII5CAPTURE_SHUTDOWN: if (stepCount == 1) { // Shutdown sparton? } step = WII5CAPTURE_SEND_PREPARE; stepCount = 0; wait = 0; break; case WII5CAPTURE_SEND_PREPARE: step = WII5CAPTURE_PROCESS; stepCount = 0; wait = 0; break; case WII5CAPTURE_PROCESS: // TODO This here should be switching SD Cards if possible // - Stop SD // - Switch SD // - Start Maths // - This allows code to be processed right away. // TODO - if Maths is off // TODO - Switch SD Cards // TODO - store new one in wii5Controller.getSD() for next time - ie. shoulduse that even if hours away step = WII5CAPTURE_FINISH; stepCount = 0; wait = 0; break; // Three ways to cancel case WII5CAPTURE_FLIP: case WII5CAPTURE_CANCEL_RESTART: case WII5CAPTURE_CANCEL_OFF: case WII5CAPTURE_MANUAL_OFF: if (first) { console.log(LOG_FATAL, F("Capture: Manual Cancel - Turning off the devices.")); #ifdef WII5_GPS if (enableGPS) wii5Gps.off(); #endif wii5Sparton.stop(); if (step == WII5CAPTURE_FLIP) { console.log(LOG_INFO, F("CaptureMode: FLIP manually requested on SD Cards")); sdBlock.cardForceClose(); wii5Controller.setSDOff(); delay(500); // TODO Yes I know.... gah. TODO 2024 safeDelay - really 3 seconds TODO 2024 was 3000 if (wii5Controller.getSD() == 1) { wii5Controller.setSD2(); wii5Display.statusSend(F("CaptureMode"), F("sdcard,swap,2")); wii5Display.atDataSend( F("MathsProcess"), F("SD=2") ); } else { wii5Controller.setSD1(); wii5Display.statusSend(F("CaptureMode"), F("sdcard,swap,1")); wii5Display.atDataSend( F("MathsProcess"), F("SD=1") ); } } } // Maths ? if (wait > 2000) { if (step == WII5CAPTURE_CANCEL_RESTART) { step = WII5CAPTURE_START; wait = 0; retry = 0; stepCount = 0; } else { step = WII5CAPTURE_WAIT5_OFF; wait = 0; retry = 0; stepCount = 0; } } break; case WII5CAPTURE_WAIT5_OFF: if (stepCount == 1) console.log(LOG_DEBUG, F("ModeCapture: Waiting for 90 seconds after fatal error before retrying")); if (wait > 90000) { step = WII5CAPTURE_OFF; stepCount = 0; wait = 0; } break; // Capture is finished - what do we do? // - Need to Sleep. e.g. more than 1 hour to wait. // - Twiddlge thumbs waiting for Maths CPU to process and send Iridium // . Reduce power of all other devices // . Monitor Maths - don't let it run forever // . Monitor Iridium - don't let it run forever case WII5CAPTURE_FINISH: wii5Display.statusSend(F("CaptureMode"), F("sparton,end")); // Just sit here wii5Display.atDataSend( F("CaptureCommand"), F("END") ); // Close DATA sdBlock.dataClose(false); // Create empty single entry for results. sdBlock.dataOpen(wii5Config.getRecordCount()); sdBlock.dataPrepare(); sdBlock.dataWrite(); sdBlock.dataClose(true); // Trigger temperatureRead - safe place at ene of a read wii5Weather_18B20.temperatureRead(); #ifdef WII5_GPS if (wii5Gps.isError()) { // TODO report errors console.log(LOG_DEBUG, F("GPS: failure I think")); } else if (wii5Gps.ready()) { console.log(LOG_DEBUG, F("GPS: Success I think")); } else { console.log(LOG_DEBUG, F("GPS: UNKNOWN I think")); } wii5Gps.off(); #endif // TODO Check for errors sdBlock.metadataPrepare(); if (sizeof(WII5MetaDataObject) <= sizeof(sdBlock.metadata->data)) { console.log(LOG_DEBUG, F("Writing SDBlock Metadata - WII5 Size=%d, SDBlock Size=%d"), int(sizeof(WII5MetaDataObject)), int(sizeof(sdBlock.metadata->data)) ); wii5Display.updateMetadata(sdBlock.metadata->data); } else { console.log(LOG_FATAL, F("SDBlock and MetaData are not compatible sizes - WII5 Size=%d, SDBlock Size=%d"), int(sizeof(WII5MetaDataObject)), int(sizeof(sdBlock.metadata->data)) ); } sdBlock.metadataWrite(); console.log(LOG_DEBUG, F("SDBlock metadata write = %lu"), sdBlock.metadataLast); // Always close card sdBlock.cardClose(); if (debug) { delay(1000); sdBlock.cardOpen(wii5Controller.getSD()); sdBlock.metadataRead(0); uint32_t dataBlockStart = sdBlock.metadata->dataBlockStart; uint32_t dataBlocks = sdBlock.metadata->dataBlocks; uint32_t resultsBlockStart = sdBlock.metadata->resultsBlockStart; uint32_t resultsBlocks = sdBlock.metadata->resultsBlocks; console.printf(F("@Block,metadata,address=%lu,deviceId=%lu,recordId=%lu\r\n"), sdBlock.metadataLast, sdBlock.metadata->deviceId, sdBlock.metadata->recordId ); console.printf(F("@Block,metadata,dataBlockStart=%lu,dataBlocks=%lu\r\n"), dataBlockStart, dataBlocks ); console.printf(F("@Block,metadata,resultsBlockStart=%lu,resultsBlocks=%lu\r\n"), resultsBlockStart, resultsBlocks ); } // SAFETY // - Check maths is off, others leave this one live if (wii5Maths.isOff() || enableLiveSwap) { // TODO need more delays... // TODO - consider printing that we did Live Swap if (wii5Controller.getSD() == 1) { wii5Controller.setSD2(); wii5Display.statusSend(F("CaptureMode"), F("sdcard,swap,2")); wii5Display.atDataSend( F("MathsProcess"), F("SD=2") ); } else { wii5Controller.setSD1(); wii5Display.statusSend(F("CaptureMode"), F("sdcard,swap,1")); wii5Display.atDataSend( F("MathsProcess"), F("SD=1") ); } if (!sdBlock.cardOpen(wii5Controller.getSD())) { sh3dNodeIO.led1Set( LED_FAST ); console.log(LOG_ERROR, F("Error opening SD Card")); } // Signal we have swapped SD wii5Communications.signalSD(); } else { wii5Display.statusSend(F("CaptureMode"), F("sdcard,noswap")); wii5Display.atDataSend( F("MathsProcess"), F("noswap") ); } // TODO Calculate waiting time. // If Maths finished, and Iridium finished. // And we have to wait longer than X (30 minutes?) // Deep Sleep for 90% of that time. if(captureType == WII5CAPTURETYPE_MANUAL) { console.log(LOG_INFO, F("Manual run executed, switching back to Default Mode")); wii5Controller.setDefaultMode(); } if (enableMaths) wii5Maths.start(WII5MATHSMODE_AUTO); wii5Communications.start(); // In case we didn't get SD, still tell it to do the job step = WII5CAPTURE_WAIT_COMMS_MATHS; wait = 0; retry = 0; stepCount = 0; break; case WII5CAPTURE_WAIT_COMMS_MATHS: if (first) { console.log(LOG_DEBUG, F("Waiting for Maths and Communicatinos to Finish")); } // If maths and Comms is off, jumpt to OFF else if ( !wii5Communications.isRunning() && wii5Maths.isOff()) { console.log(LOG_INFO, F("CAPTURE: Maths and Comms finsihed")); step = WII5CAPTURE_OFF; wait = 0; retry = 0; stepCount = 0; } // Wait 3 minuts here quietly else if (wait > 180000) { console.log(LOG_INFO, F("CAPTURE: 3 minutes and Maths and Communications not finished. Mvoing on but leaving them on.")); step = WII5CAPTURE_OFF; wait = 0; retry = 0; stepCount = 0; } break; default: console.log(LOG_FATAL, F("CAPTURE: Default step... step=%d"), step); step = WII5CAPTURE_OFF; stepCount = 0; wait = 0; break; } stepCount++; stepTotal++; } void WII5ModeCapture::flip() { step = WII5CAPTURE_FLIP; wait = 0; stepCount = 0; } WII5ModeCapture wii5ModeCapture;