SD init now happens before Wi-Fi AP startup.
Startup timing is made slower and more forgiving: recoveryRailOffMs = 400 recoveryRailOnSettleMs = 1200 startupWarmupMs = 2500 If the initial cold-boot mount still fails, it now does one immediate second-chance remount after a short delay before proceeding.
This commit is contained in:
parent
ca639b2640
commit
dba0c9477f
4 changed files with 108 additions and 7 deletions
|
|
@ -84,6 +84,8 @@ static constexpr float kExcellentHdop = 1.5f;
|
||||||
static constexpr size_t kBufferedSamples = 10;
|
static constexpr size_t kBufferedSamples = 10;
|
||||||
static constexpr size_t kMaxSatellites = 64;
|
static constexpr size_t kMaxSatellites = 64;
|
||||||
static constexpr size_t kStorageBufferBytes = 4096;
|
static constexpr size_t kStorageBufferBytes = 4096;
|
||||||
|
static constexpr uint8_t kStorageWriteRetryCount = 3;
|
||||||
|
static constexpr uint32_t kStorageWriteRetryDelayMs = 25;
|
||||||
static constexpr uint32_t kClockDisciplineRetryMs = 5000;
|
static constexpr uint32_t kClockDisciplineRetryMs = 5000;
|
||||||
static constexpr uint32_t kClockPpsWaitTimeoutMs = 1500;
|
static constexpr uint32_t kClockPpsWaitTimeoutMs = 1500;
|
||||||
static constexpr uint32_t kClockFreshSampleMs = 2000;
|
static constexpr uint32_t kClockFreshSampleMs = 2000;
|
||||||
|
|
|
||||||
|
|
@ -178,6 +178,17 @@ bool StorageManager::openFile() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool StorageManager::reopenFileForAppend() {
|
||||||
|
if (m_file) {
|
||||||
|
m_file.close();
|
||||||
|
}
|
||||||
|
m_file = SD.open(m_path.c_str(), FILE_WRITE);
|
||||||
|
if (!m_file) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void StorageManager::writeHeader(const char* runId, const char* bootTimestampUtc) {
|
void StorageManager::writeHeader(const char* runId, const char* bootTimestampUtc) {
|
||||||
if (!m_file || !m_newFile) {
|
if (!m_file || !m_newFile) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -206,9 +217,7 @@ bool StorageManager::writePendingBuffer() {
|
||||||
if (!m_bufferPending[i] || m_bufferLengths[i] == 0) {
|
if (!m_bufferPending[i] || m_bufferLengths[i] == 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const size_t wrote = m_file.write((const uint8_t*)m_buffers[i], m_bufferLengths[i]);
|
if (!writeFully((const uint8_t*)m_buffers[i], m_bufferLengths[i], "buffer")) {
|
||||||
if (wrote != m_bufferLengths[i]) {
|
|
||||||
m_lastError = "SD.write failed";
|
|
||||||
m_ready = false;
|
m_ready = false;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -226,9 +235,7 @@ bool StorageManager::appendBytes(const char* data, size_t len) {
|
||||||
if (!writePendingBuffer()) {
|
if (!writePendingBuffer()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const size_t wrote = m_file.write((const uint8_t*)data, len);
|
if (!writeFully((const uint8_t*)data, len, "large")) {
|
||||||
if (wrote != len) {
|
|
||||||
m_lastError = "SD.write large block failed";
|
|
||||||
m_ready = false;
|
m_ready = false;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -256,6 +263,57 @@ bool StorageManager::appendBytes(const char* data, size_t len) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool StorageManager::writeFully(const uint8_t* data, size_t len, const char* context) {
|
||||||
|
if (!m_file || !data || len == 0) {
|
||||||
|
m_lastError = "writeFully invalid state";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t totalWrote = 0;
|
||||||
|
for (uint8_t attempt = 0; attempt <= kStorageWriteRetryCount; ++attempt) {
|
||||||
|
const size_t filePos = (size_t)m_file.position();
|
||||||
|
const size_t fileSize = (size_t)m_file.size();
|
||||||
|
const size_t wrote = m_file.write(data + totalWrote, len - totalWrote);
|
||||||
|
if (wrote > 0) {
|
||||||
|
totalWrote += wrote;
|
||||||
|
if (totalWrote >= len) {
|
||||||
|
if (attempt > 0) {
|
||||||
|
m_lastError = "";
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attempt >= kStorageWriteRetryCount) {
|
||||||
|
const bool stillMounted = mounted();
|
||||||
|
char err[192];
|
||||||
|
snprintf(err,
|
||||||
|
sizeof(err),
|
||||||
|
"SD.write failed ctx=%s wrote=%u/%u pos=%u size=%u mounted=%s attempts=%u",
|
||||||
|
context ? context : "?",
|
||||||
|
(unsigned)totalWrote,
|
||||||
|
(unsigned)len,
|
||||||
|
(unsigned)filePos,
|
||||||
|
(unsigned)fileSize,
|
||||||
|
stillMounted ? "yes" : "no",
|
||||||
|
(unsigned)(attempt + 1));
|
||||||
|
m_lastError = err;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
delay(kStorageWriteRetryDelayMs);
|
||||||
|
|
||||||
|
if (mounted()) {
|
||||||
|
if (!reopenFileForAppend()) {
|
||||||
|
delay(kStorageWriteRetryDelayMs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_lastError = "SD.write retry loop exhausted";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool StorageManager::appendLine(const String& line) {
|
bool StorageManager::appendLine(const String& line) {
|
||||||
if (line.endsWith("\n")) {
|
if (line.endsWith("\n")) {
|
||||||
return appendBytes(line.c_str(), line.length());
|
return appendBytes(line.c_str(), line.length());
|
||||||
|
|
|
||||||
|
|
@ -40,11 +40,13 @@ class StorageManager {
|
||||||
private:
|
private:
|
||||||
bool ensureDir();
|
bool ensureDir();
|
||||||
bool openFile();
|
bool openFile();
|
||||||
|
bool reopenFileForAppend();
|
||||||
void writeHeader(const char* runId, const char* bootTimestampUtc);
|
void writeHeader(const char* runId, const char* bootTimestampUtc);
|
||||||
String makeFilePath(const char* runId) const;
|
String makeFilePath(const char* runId) const;
|
||||||
bool appendLine(const String& line);
|
bool appendLine(const String& line);
|
||||||
bool appendBytes(const char* data, size_t len);
|
bool appendBytes(const char* data, size_t len);
|
||||||
bool writePendingBuffer();
|
bool writePendingBuffer();
|
||||||
|
bool writeFully(const uint8_t* data, size_t len, const char* context);
|
||||||
size_t countLogsRecursive(const char* path) const;
|
size_t countLogsRecursive(const char* path) const;
|
||||||
void listFilesRecursive(File& dir, Stream& out);
|
void listFilesRecursive(File& dir, Stream& out);
|
||||||
void eraseLogsRecursive(File& dir);
|
void eraseLogsRecursive(File& dir);
|
||||||
|
|
|
||||||
|
|
@ -140,6 +140,25 @@ void setLastHaltReason(const char* reason) {
|
||||||
strlcpy(g_lastHaltReason, (reason && reason[0] != '\0') ? reason : "none", sizeof(g_lastHaltReason));
|
strlcpy(g_lastHaltReason, (reason && reason[0] != '\0') ? reason : "none", sizeof(g_lastHaltReason));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char* currentLoggingState() {
|
||||||
|
if (!g_clockDisciplined) {
|
||||||
|
return "waiting_clock";
|
||||||
|
}
|
||||||
|
if (!g_storageMounted) {
|
||||||
|
return "sd_absent";
|
||||||
|
}
|
||||||
|
if (g_loggingEnabled && g_storageReady) {
|
||||||
|
return "recording";
|
||||||
|
}
|
||||||
|
if (g_lastHaltReason[0] != '\0' && strcmp(g_lastHaltReason, "none") != 0) {
|
||||||
|
return "halted";
|
||||||
|
}
|
||||||
|
if (!g_storageReady) {
|
||||||
|
return "storage_not_ready";
|
||||||
|
}
|
||||||
|
return "idle";
|
||||||
|
}
|
||||||
|
|
||||||
void wakeDisplay(uint32_t durationMs = kDisplayWakeMs) {
|
void wakeDisplay(uint32_t durationMs = kDisplayWakeMs) {
|
||||||
g_display.setPowerSave(false);
|
g_display.setPowerSave(false);
|
||||||
g_displayWakeUntilMs = millis() + durationMs;
|
g_displayWakeUntilMs = millis() + durationMs;
|
||||||
|
|
@ -248,6 +267,7 @@ void printSummary() {
|
||||||
Serial.printf("storage_used_bytes=%u\n", g_storageMounted ? (unsigned)SD.usedBytes() : 0U);
|
Serial.printf("storage_used_bytes=%u\n", g_storageMounted ? (unsigned)SD.usedBytes() : 0U);
|
||||||
Serial.printf("storage_buffered_bytes=%u\n", (unsigned)g_storage.bufferedBytes());
|
Serial.printf("storage_buffered_bytes=%u\n", (unsigned)g_storage.bufferedBytes());
|
||||||
Serial.printf("storage_log_count=%u\n", (unsigned)g_logFileCount);
|
Serial.printf("storage_log_count=%u\n", (unsigned)g_logFileCount);
|
||||||
|
Serial.printf("logging_state=%s\n", currentLoggingState());
|
||||||
Serial.printf("halt_reason=%s\n", g_lastHaltReason);
|
Serial.printf("halt_reason=%s\n", g_lastHaltReason);
|
||||||
Serial.printf("battery_voltage=%s\n", g_battery.voltageText());
|
Serial.printf("battery_voltage=%s\n", g_battery.voltageText());
|
||||||
Serial.printf("web_ready=%s\n", g_webReady ? "yes" : "no");
|
Serial.printf("web_ready=%s\n", g_webReady ? "yes" : "no");
|
||||||
|
|
@ -495,6 +515,10 @@ void sampleAndMaybeLog() {
|
||||||
g_display.showBoot("Stop recording?", "Press again in 3s", nullptr, g_battery.voltageText());
|
g_display.showBoot("Stop recording?", "Press again in 3s", nullptr, g_battery.voltageText());
|
||||||
} else if ((uint32_t)(millis() - g_buttonStopMessageUntilMs) < kButtonStopMessageMs) {
|
} else if ((uint32_t)(millis() - g_buttonStopMessageUntilMs) < kButtonStopMessageMs) {
|
||||||
g_display.showBoot("Halted", g_lastHaltReason, "Safe to power off", g_battery.voltageText());
|
g_display.showBoot("Halted", g_lastHaltReason, "Safe to power off", g_battery.voltageText());
|
||||||
|
} else if (!g_storageMounted) {
|
||||||
|
g_display.showBoot("No logging", "SD not mounted", g_runId, g_battery.voltageText());
|
||||||
|
} else if (!g_storageReady && !g_loggingEnabled) {
|
||||||
|
g_display.showBoot("No logging", "Storage not ready", g_runId, g_battery.voltageText());
|
||||||
} else if (!g_display.powerSave()) {
|
} else if (!g_display.powerSave()) {
|
||||||
g_display.showSample(sample, g_stats, g_loggingEnabled, g_battery.voltageText());
|
g_display.showSample(sample, g_stats, g_loggingEnabled, g_battery.voltageText());
|
||||||
}
|
}
|
||||||
|
|
@ -601,6 +625,8 @@ void handleWebIndex() {
|
||||||
html += htmlEscape(String(g_storage.lastError()));
|
html += htmlEscape(String(g_storage.lastError()));
|
||||||
html += "<br>Current log: ";
|
html += "<br>Current log: ";
|
||||||
html += htmlEscape(String(g_storage.currentPath()));
|
html += htmlEscape(String(g_storage.currentPath()));
|
||||||
|
html += "<br>Logging state: ";
|
||||||
|
html += htmlEscape(String(currentLoggingState()));
|
||||||
html += "<br>Halt reason: ";
|
html += "<br>Halt reason: ";
|
||||||
html += htmlEscape(String(g_lastHaltReason));
|
html += htmlEscape(String(g_lastHaltReason));
|
||||||
html += "<br>Battery: ";
|
html += "<br>Battery: ";
|
||||||
|
|
@ -718,6 +744,8 @@ void handleWebCommand() {
|
||||||
response += g_storageReady ? "yes" : "no";
|
response += g_storageReady ? "yes" : "no";
|
||||||
response += "\nsd_state=";
|
response += "\nsd_state=";
|
||||||
response += g_storageMounted ? "mounted" : "absent";
|
response += g_storageMounted ? "mounted" : "absent";
|
||||||
|
response += "\nlogging_state=";
|
||||||
|
response += currentLoggingState();
|
||||||
response += "\nhalt_reason=";
|
response += "\nhalt_reason=";
|
||||||
response += g_lastHaltReason;
|
response += g_lastHaltReason;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -862,17 +890,28 @@ void setup() {
|
||||||
g_stats.begin(millis());
|
g_stats.begin(millis());
|
||||||
g_gnss.begin();
|
g_gnss.begin();
|
||||||
(void)g_gnss.probeAtStartup(Serial);
|
(void)g_gnss.probeAtStartup(Serial);
|
||||||
startWebServer();
|
|
||||||
|
|
||||||
SdWatcherConfig sdCfg;
|
SdWatcherConfig sdCfg;
|
||||||
|
sdCfg.recoveryRailOffMs = 400;
|
||||||
|
sdCfg.recoveryRailOnSettleMs = 1200;
|
||||||
|
sdCfg.startupWarmupMs = 2500;
|
||||||
if (!g_sd.begin(sdCfg)) {
|
if (!g_sd.begin(sdCfg)) {
|
||||||
Serial.println("WARNING: SD watcher init failed");
|
Serial.println("WARNING: SD watcher init failed");
|
||||||
}
|
}
|
||||||
g_storageMounted = g_sd.isMounted();
|
g_storageMounted = g_sd.isMounted();
|
||||||
|
if (!g_storageMounted) {
|
||||||
|
Serial.println("INFO: cold-boot SD second-chance remount");
|
||||||
|
delay(750);
|
||||||
|
if (g_sd.forceRemount()) {
|
||||||
|
g_storageMounted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (g_storageMounted) {
|
if (g_storageMounted) {
|
||||||
g_sd.printCardInfo();
|
g_sd.printCardInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
startWebServer();
|
||||||
|
|
||||||
#ifdef GPS_1PPS_PIN
|
#ifdef GPS_1PPS_PIN
|
||||||
attachInterrupt(digitalPinToInterrupt(GPS_1PPS_PIN), onPpsEdge, RISING);
|
attachInterrupt(digitalPinToInterrupt(GPS_1PPS_PIN), onPpsEdge, RISING);
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue