had OLED sleep ability and better SD card mount monitoring and ring buffer for error messages
This commit is contained in:
parent
a8a53518bb
commit
15a5dbe006
8 changed files with 223 additions and 40 deletions
|
|
@ -46,6 +46,10 @@
|
|||
#define FW_BUILD_UTC unknown
|
||||
#endif
|
||||
|
||||
#ifndef LOG_AP_IP_OCTET
|
||||
#define LOG_AP_IP_OCTET 23
|
||||
#endif
|
||||
|
||||
#define FIELD_QA_STR_INNER(x) #x
|
||||
#define FIELD_QA_STR(x) FIELD_QA_STR_INNER(x)
|
||||
|
||||
|
|
@ -59,7 +63,7 @@ static constexpr const char* kStorageName = "SD";
|
|||
static constexpr const char* kLogDir = "/logs";
|
||||
static constexpr const char* kLogApPrefix = "GPSQA-";
|
||||
static constexpr const char* kLogApPassword = "";
|
||||
static constexpr uint8_t kLogApIpOctet = 23;
|
||||
static constexpr uint8_t kLogApIpOctet = LOG_AP_IP_OCTET;
|
||||
static constexpr uint32_t kSerialDelayMs = 4000;
|
||||
static constexpr uint32_t kSamplePeriodMs = 1000;
|
||||
static constexpr uint32_t kLogFlushPeriodMs = 10000;
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ void DisplayManager::begin() {
|
|||
Wire.begin(OLED_SDA, OLED_SCL);
|
||||
m_oled.setI2CAddress(OLED_ADDR << 1);
|
||||
m_oled.begin();
|
||||
m_oled.setPowerSave(0);
|
||||
m_powerSave = false;
|
||||
}
|
||||
|
||||
void DisplayManager::drawLines(const char* l1,
|
||||
|
|
@ -40,15 +42,24 @@ void DisplayManager::drawLines(const char* l1,
|
|||
m_oled.sendBuffer();
|
||||
}
|
||||
|
||||
void DisplayManager::showBoot(const char* line2, const char* line3) {
|
||||
drawLines(kExerciseName, kFirmwareVersion, line2, line3);
|
||||
void DisplayManager::showBoot(const char* line2, const char* line3, const char* line4, const char* batteryText) {
|
||||
char header[24];
|
||||
if (batteryText && batteryText[0] != '\0') {
|
||||
snprintf(header, sizeof(header), "%s %s", kExerciseName, batteryText);
|
||||
drawLines(header, kFirmwareVersion, line2, line3, line4);
|
||||
} else {
|
||||
drawLines(kExerciseName, kFirmwareVersion, line2, line3, line4);
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayManager::showError(const char* line1, const char* line2) {
|
||||
drawLines(kExerciseName, "ERROR", line1, line2);
|
||||
}
|
||||
|
||||
void DisplayManager::showSample(const GnssSample& sample, const RunStats& stats, bool recording) {
|
||||
void DisplayManager::showSample(const GnssSample& sample,
|
||||
const RunStats& stats,
|
||||
bool recording,
|
||||
const char* batteryText) {
|
||||
char l1[24];
|
||||
char l2[20];
|
||||
char l3[20];
|
||||
|
|
@ -56,7 +67,11 @@ void DisplayManager::showSample(const GnssSample& sample, const RunStats& stats,
|
|||
char l5[20];
|
||||
char l6[20];
|
||||
|
||||
snprintf(l1, sizeof(l1), "%s", recording ? "*RECORDING" : "Halted");
|
||||
snprintf(l1,
|
||||
sizeof(l1),
|
||||
"%s %s",
|
||||
recording ? "*RECORDING" : "Halted",
|
||||
(batteryText && batteryText[0] != '\0') ? batteryText : "--.-v");
|
||||
snprintf(l2, sizeof(l2), "FIX: %s", fixTypeToString(sample.fixType));
|
||||
snprintf(l3, sizeof(l3), "USED: %d/%d", sample.satsUsed < 0 ? 0 : sample.satsUsed, sample.satsInView < 0 ? 0 : sample.satsInView);
|
||||
if (sample.validHdop) {
|
||||
|
|
@ -69,4 +84,16 @@ void DisplayManager::showSample(const GnssSample& sample, const RunStats& stats,
|
|||
drawLines(l1, l2, l3, l4, l5, l6);
|
||||
}
|
||||
|
||||
void DisplayManager::setPowerSave(bool enabled) {
|
||||
if (m_powerSave == enabled) {
|
||||
return;
|
||||
}
|
||||
m_oled.setPowerSave(enabled ? 1 : 0);
|
||||
m_powerSave = enabled;
|
||||
}
|
||||
|
||||
bool DisplayManager::powerSave() const {
|
||||
return m_powerSave;
|
||||
}
|
||||
|
||||
} // namespace field_qa
|
||||
|
|
|
|||
|
|
@ -10,9 +10,14 @@ namespace field_qa {
|
|||
class DisplayManager {
|
||||
public:
|
||||
void begin();
|
||||
void showBoot(const char* line2, const char* line3 = nullptr);
|
||||
void showBoot(const char* line2,
|
||||
const char* line3 = nullptr,
|
||||
const char* line4 = nullptr,
|
||||
const char* batteryText = nullptr);
|
||||
void showError(const char* line1, const char* line2 = nullptr);
|
||||
void showSample(const GnssSample& sample, const RunStats& stats, bool recording);
|
||||
void showSample(const GnssSample& sample, const RunStats& stats, bool recording, const char* batteryText);
|
||||
void setPowerSave(bool enabled);
|
||||
bool powerSave() const;
|
||||
|
||||
private:
|
||||
void drawLines(const char* l1,
|
||||
|
|
@ -23,6 +28,7 @@ class DisplayManager {
|
|||
const char* l6 = nullptr);
|
||||
|
||||
U8G2_SH1106_128X64_NONAME_F_HW_I2C m_oled{U8G2_R0, U8X8_PIN_NONE};
|
||||
bool m_powerSave = false;
|
||||
};
|
||||
|
||||
} // namespace field_qa
|
||||
|
|
|
|||
|
|
@ -265,13 +265,14 @@ bool StorageManager::appendLine(const String& line) {
|
|||
return appendBytes(record.c_str(), record.length());
|
||||
}
|
||||
|
||||
void StorageManager::appendSampleCsv(const GnssSample& sample,
|
||||
bool StorageManager::appendSampleCsv(const GnssSample& sample,
|
||||
uint32_t sampleSeq,
|
||||
uint32_t msSinceRunStart,
|
||||
const char* runId,
|
||||
const char* bootTimestampUtc) {
|
||||
if (!m_file) {
|
||||
return;
|
||||
m_lastError = "log file not open";
|
||||
return false;
|
||||
}
|
||||
if (m_file.size() == 0) {
|
||||
writeHeader(runId, bootTimestampUtc);
|
||||
|
|
@ -350,10 +351,10 @@ void StorageManager::appendSampleCsv(const GnssSample& sample,
|
|||
line += kLogFieldDelimiter;
|
||||
line += String(sample.longestNoFixMs);
|
||||
line += ",,,,,,,";
|
||||
(void)appendLine(line);
|
||||
return appendLine(line);
|
||||
}
|
||||
|
||||
void StorageManager::appendSatelliteCsv(const GnssSample& sample,
|
||||
bool StorageManager::appendSatelliteCsv(const GnssSample& sample,
|
||||
uint32_t sampleSeq,
|
||||
uint32_t msSinceRunStart,
|
||||
const SatelliteInfo* satellites,
|
||||
|
|
@ -361,7 +362,10 @@ void StorageManager::appendSatelliteCsv(const GnssSample& sample,
|
|||
const char* runId,
|
||||
const char* bootTimestampUtc) {
|
||||
if (!satellites || satelliteCount == 0 || !m_file) {
|
||||
return;
|
||||
if (!m_file) {
|
||||
m_lastError = "log file not open";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (m_file.size() == 0) {
|
||||
writeHeader(runId, bootTimestampUtc);
|
||||
|
|
@ -458,26 +462,30 @@ void StorageManager::appendSatelliteCsv(const GnssSample& sample,
|
|||
line += String(sat.snr);
|
||||
line += kLogFieldDelimiter;
|
||||
line += sat.usedInSolution ? "1" : "0";
|
||||
(void)appendLine(line);
|
||||
if (!appendLine(line)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void StorageManager::flush() {
|
||||
bool StorageManager::flush() {
|
||||
if (!m_file) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_bufferLengths[m_activeBuffer] > 0) {
|
||||
m_bufferPending[m_activeBuffer] = true;
|
||||
}
|
||||
if (!writePendingBuffer()) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
m_file.flush();
|
||||
return true;
|
||||
}
|
||||
|
||||
void StorageManager::close() {
|
||||
flush();
|
||||
(void)flush();
|
||||
if (m_file) {
|
||||
m_file.close();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,19 +17,19 @@ class StorageManager {
|
|||
bool fileOpen() const;
|
||||
size_t bufferedBytes() const;
|
||||
size_t logFileCount() const;
|
||||
void appendSampleCsv(const GnssSample& sample,
|
||||
bool appendSampleCsv(const GnssSample& sample,
|
||||
uint32_t sampleSeq,
|
||||
uint32_t msSinceRunStart,
|
||||
const char* runId,
|
||||
const char* bootTimestampUtc);
|
||||
void appendSatelliteCsv(const GnssSample& sample,
|
||||
bool appendSatelliteCsv(const GnssSample& sample,
|
||||
uint32_t sampleSeq,
|
||||
uint32_t msSinceRunStart,
|
||||
const SatelliteInfo* satellites,
|
||||
size_t satelliteCount,
|
||||
const char* runId,
|
||||
const char* bootTimestampUtc);
|
||||
void flush();
|
||||
bool flush();
|
||||
void close();
|
||||
void listFiles(Stream& out);
|
||||
void catFile(Stream& out, const char* path);
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ build_flags =
|
|||
-D NODE_LABEL=\"Amy\"
|
||||
-D NODE_SHORT=\"A\"
|
||||
-D NODE_SLOT_INDEX=0
|
||||
-D LOG_AP_IP_OCTET=23
|
||||
-D GNSS_CHIP_NAME=\"L76K\"
|
||||
|
||||
[env:bob]
|
||||
|
|
@ -50,6 +51,7 @@ build_flags =
|
|||
-D NODE_LABEL=\"Bob\"
|
||||
-D NODE_SHORT=\"B\"
|
||||
-D NODE_SLOT_INDEX=1
|
||||
-D LOG_AP_IP_OCTET=24
|
||||
-D GNSS_CHIP_NAME=\"L76K\"
|
||||
|
||||
[env:cy]
|
||||
|
|
@ -60,6 +62,7 @@ build_flags =
|
|||
-D NODE_LABEL=\"Cy\"
|
||||
-D NODE_SHORT=\"C\"
|
||||
-D NODE_SLOT_INDEX=2
|
||||
-D LOG_AP_IP_OCTET=25
|
||||
-D GNSS_CHIP_NAME=\"L76K\"
|
||||
|
||||
[env:dan]
|
||||
|
|
@ -70,6 +73,7 @@ build_flags =
|
|||
-D NODE_LABEL=\"Dan\"
|
||||
-D NODE_SHORT=\"D\"
|
||||
-D NODE_SLOT_INDEX=3
|
||||
-D LOG_AP_IP_OCTET=26
|
||||
-D GNSS_CHIP_NAME=\"L76K\"
|
||||
|
||||
[env:ed]
|
||||
|
|
@ -80,6 +84,7 @@ build_flags =
|
|||
-D NODE_LABEL=\"Ed\"
|
||||
-D NODE_SHORT=\"E\"
|
||||
-D NODE_SLOT_INDEX=4
|
||||
-D LOG_AP_IP_OCTET=27
|
||||
-D GNSS_CHIP_NAME=\"L76K\"
|
||||
|
||||
[env:flo]
|
||||
|
|
@ -90,6 +95,7 @@ build_flags =
|
|||
-D NODE_LABEL=\"Flo\"
|
||||
-D NODE_SHORT=\"F\"
|
||||
-D NODE_SLOT_INDEX=5
|
||||
-D LOG_AP_IP_OCTET=28
|
||||
-D GNSS_CHIP_NAME=\"L76K\"
|
||||
|
||||
[env:guy]
|
||||
|
|
@ -100,5 +106,6 @@ build_flags =
|
|||
-D NODE_LABEL=\"Guy\"
|
||||
-D NODE_SHORT=\"G\"
|
||||
-D NODE_SLOT_INDEX=6
|
||||
-D LOG_AP_IP_OCTET=29
|
||||
-D GNSS_CHIP_NAME=\"MAX-M10S\"
|
||||
-D GPS_UBLOX
|
||||
|
|
|
|||
|
|
@ -5,11 +5,13 @@
|
|||
#include <ctype.h>
|
||||
#include <SPI.h>
|
||||
#include <SD.h>
|
||||
#include <stdarg.h>
|
||||
#include <strings.h>
|
||||
#include <Wire.h>
|
||||
#include <WiFi.h>
|
||||
#include <WebServer.h>
|
||||
|
||||
#include "BatteryMonitor.h"
|
||||
#include "ClockDiscipline.h"
|
||||
#include "Config.h"
|
||||
#include "DisplayManager.h"
|
||||
|
|
@ -27,6 +29,7 @@ XPowersLibInterface* g_pmu = nullptr;
|
|||
DisplayManager g_display;
|
||||
GnssManager g_gnss;
|
||||
ClockDiscipline g_clock;
|
||||
BatteryMonitor g_battery;
|
||||
StorageManager g_storage;
|
||||
RunStats g_stats;
|
||||
StartupSdManager g_sd(Serial);
|
||||
|
|
@ -53,6 +56,9 @@ bool g_buttonHoldHandled = false;
|
|||
uint32_t g_buttonPressedMs = 0;
|
||||
uint32_t g_buttonConfirmDeadlineMs = 0;
|
||||
uint32_t g_buttonStopMessageUntilMs = 0;
|
||||
char g_lastHaltReason[32] = "none";
|
||||
bool g_displayAutoSleepEnabled = false;
|
||||
uint32_t g_displayWakeUntilMs = 0;
|
||||
|
||||
uint32_t g_lastSampleMs = 0;
|
||||
uint32_t g_lastFlushMs = 0;
|
||||
|
|
@ -64,11 +70,66 @@ volatile uint32_t g_ppsEdgeCount = 0;
|
|||
static constexpr uint32_t kButtonHoldPromptMs = 1500;
|
||||
static constexpr uint32_t kButtonConfirmWindowMs = 3000;
|
||||
static constexpr uint32_t kButtonStopMessageMs = 4000;
|
||||
static constexpr uint32_t kDisplayWakeMs = 120000;
|
||||
static constexpr size_t kEventLogCapacity = 128;
|
||||
static constexpr size_t kEventLogLineBytes = 96;
|
||||
char g_eventLog[kEventLogCapacity][kEventLogLineBytes] = {};
|
||||
size_t g_eventLogHead = 0;
|
||||
size_t g_eventLogCount = 0;
|
||||
|
||||
void IRAM_ATTR onPpsEdge() {
|
||||
++g_ppsEdgeCount;
|
||||
}
|
||||
|
||||
String htmlEscape(const String& in);
|
||||
|
||||
void recordEvent(const char* fmt, ...) {
|
||||
char msg[72];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(msg, sizeof(msg), fmt, args);
|
||||
va_end(args);
|
||||
|
||||
char line[kEventLogLineBytes];
|
||||
snprintf(line, sizeof(line), "%10lu %s", (unsigned long)millis(), msg);
|
||||
strlcpy(g_eventLog[g_eventLogHead], line, sizeof(g_eventLog[g_eventLogHead]));
|
||||
g_eventLogHead = (g_eventLogHead + 1) % kEventLogCapacity;
|
||||
if (g_eventLogCount < kEventLogCapacity) {
|
||||
++g_eventLogCount;
|
||||
}
|
||||
Serial.printf("EVENT: %s\n", msg);
|
||||
}
|
||||
|
||||
void setLastHaltReason(const char* reason) {
|
||||
strlcpy(g_lastHaltReason, (reason && reason[0] != '\0') ? reason : "none", sizeof(g_lastHaltReason));
|
||||
}
|
||||
|
||||
void wakeDisplay(uint32_t durationMs = kDisplayWakeMs) {
|
||||
g_display.setPowerSave(false);
|
||||
g_displayWakeUntilMs = millis() + durationMs;
|
||||
}
|
||||
|
||||
void updateDisplayPowerState() {
|
||||
if (!g_displayAutoSleepEnabled) {
|
||||
g_display.setPowerSave(false);
|
||||
return;
|
||||
}
|
||||
if ((int32_t)(millis() - g_displayWakeUntilMs) >= 0) {
|
||||
g_display.setPowerSave(true);
|
||||
}
|
||||
}
|
||||
|
||||
void appendRecentEventsHtml(String& html) {
|
||||
html += "<h2>Recent Events</h2><pre>";
|
||||
const size_t start = (g_eventLogCount < kEventLogCapacity) ? 0 : g_eventLogHead;
|
||||
for (size_t i = 0; i < g_eventLogCount; ++i) {
|
||||
const size_t idx = (start + i) % kEventLogCapacity;
|
||||
html += htmlEscape(String(g_eventLog[idx]));
|
||||
html += "\n";
|
||||
}
|
||||
html += "</pre>";
|
||||
}
|
||||
|
||||
String htmlEscape(const String& in) {
|
||||
String out;
|
||||
out.reserve(in.length() + 16);
|
||||
|
|
@ -151,6 +212,8 @@ void printSummary() {
|
|||
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_log_count=%u\n", (unsigned)g_logFileCount);
|
||||
Serial.printf("halt_reason=%s\n", g_lastHaltReason);
|
||||
Serial.printf("battery_voltage=%s\n", g_battery.voltageText());
|
||||
Serial.printf("web_ready=%s\n", g_webReady ? "yes" : "no");
|
||||
Serial.printf("web_url=http://192.168.%u.1/\n", (unsigned)kLogApIpOctet);
|
||||
}
|
||||
|
|
@ -207,7 +270,11 @@ bool ensureStorageReady() {
|
|||
g_sampleSeq = 0;
|
||||
g_runStartMs = millis();
|
||||
g_loggingEnabled = true;
|
||||
setLastHaltReason("none");
|
||||
g_displayAutoSleepEnabled = true;
|
||||
wakeDisplay();
|
||||
g_logFileCount = g_storage.logFileCount();
|
||||
recordEvent("logging_started path=%s", g_storage.currentPath());
|
||||
Serial.printf("logging started: %s\n", g_storage.currentPath());
|
||||
return true;
|
||||
}
|
||||
|
|
@ -216,10 +283,14 @@ void stopLoggingCleanly(const char* reason) {
|
|||
if (!g_loggingEnabled && !g_storageReady) {
|
||||
return;
|
||||
}
|
||||
g_storage.flush();
|
||||
(void)g_storage.flush();
|
||||
g_storage.close();
|
||||
g_storageReady = false;
|
||||
g_loggingEnabled = false;
|
||||
setLastHaltReason(reason);
|
||||
g_displayAutoSleepEnabled = true;
|
||||
wakeDisplay();
|
||||
recordEvent("logging_stopped reason=%s", g_lastHaltReason);
|
||||
if (reason && reason[0] != '\0') {
|
||||
Serial.printf("logging stopped: %s\n", reason);
|
||||
} else {
|
||||
|
|
@ -228,16 +299,36 @@ void stopLoggingCleanly(const char* reason) {
|
|||
g_buttonStopMessageUntilMs = millis() + kButtonStopMessageMs;
|
||||
}
|
||||
|
||||
void stopLoggingForStorageFailure(const char* phase) {
|
||||
g_storageReady = false;
|
||||
g_loggingEnabled = false;
|
||||
setLastHaltReason("sd_write_failed");
|
||||
g_displayAutoSleepEnabled = true;
|
||||
wakeDisplay();
|
||||
recordEvent("sd_write_failed phase=%s err=%s",
|
||||
phase ? phase : "unknown",
|
||||
g_storage.lastError()[0] ? g_storage.lastError() : "none");
|
||||
Serial.printf("ERROR: sd_write_failed phase=%s err=%s\n",
|
||||
phase ? phase : "unknown",
|
||||
g_storage.lastError()[0] ? g_storage.lastError() : "none");
|
||||
g_buttonStopMessageUntilMs = millis() + kButtonStopMessageMs;
|
||||
}
|
||||
|
||||
bool rescanSdCard() {
|
||||
g_storage.flush();
|
||||
recordEvent("sd_rescan requested");
|
||||
(void)g_storage.flush();
|
||||
g_storage.close();
|
||||
g_storageReady = false;
|
||||
g_loggingEnabled = false;
|
||||
const bool mounted = g_sd.forceRemount();
|
||||
g_storageMounted = g_sd.isMounted();
|
||||
wakeDisplay();
|
||||
if (mounted) {
|
||||
recordEvent("sd_rescan mounted");
|
||||
g_sd.printCardInfo();
|
||||
(void)ensureStorageReady();
|
||||
} else {
|
||||
recordEvent("sd_rescan failed");
|
||||
}
|
||||
return mounted;
|
||||
}
|
||||
|
|
@ -246,6 +337,10 @@ void pollStopButton() {
|
|||
const uint32_t now = millis();
|
||||
const bool pressed = (digitalRead(BUTTON_PIN) == LOW);
|
||||
|
||||
if (pressed) {
|
||||
wakeDisplay();
|
||||
}
|
||||
|
||||
if (g_buttonConfirmActive && (int32_t)(now - g_buttonConfirmDeadlineMs) >= 0) {
|
||||
g_buttonConfirmActive = false;
|
||||
}
|
||||
|
|
@ -254,7 +349,7 @@ void pollStopButton() {
|
|||
g_buttonPressedMs = now;
|
||||
g_buttonHoldHandled = false;
|
||||
if (g_buttonConfirmActive && g_loggingEnabled) {
|
||||
stopLoggingCleanly("button confirm");
|
||||
stopLoggingCleanly("button_confirm");
|
||||
g_buttonConfirmActive = false;
|
||||
}
|
||||
} else if (pressed && !g_buttonHoldHandled && !g_buttonConfirmActive && g_loggingEnabled &&
|
||||
|
|
@ -262,7 +357,8 @@ void pollStopButton() {
|
|||
g_buttonHoldHandled = true;
|
||||
g_buttonConfirmActive = true;
|
||||
g_buttonConfirmDeadlineMs = now + kButtonConfirmWindowMs;
|
||||
g_display.showBoot("Stop recording?", "Press again in 3s");
|
||||
recordEvent("button_prompt stop_recording");
|
||||
g_display.showBoot("Stop recording?", "Press again in 3s", nullptr, g_battery.voltageText());
|
||||
}
|
||||
|
||||
if (!pressed && g_buttonPrevPressed) {
|
||||
|
|
@ -276,11 +372,14 @@ void handleSdStateTransitions() {
|
|||
g_sd.update();
|
||||
if (g_sd.consumeMountedEvent()) {
|
||||
g_storageMounted = true;
|
||||
recordEvent("sd_mounted");
|
||||
Serial.println("SD mounted");
|
||||
g_sd.printCardInfo();
|
||||
(void)ensureStorageReady();
|
||||
}
|
||||
if (g_sd.consumeRemovedEvent()) {
|
||||
setLastHaltReason("sd_removed");
|
||||
recordEvent("sd_removed");
|
||||
Serial.println("SD removed");
|
||||
g_storageMounted = false;
|
||||
g_storage.close();
|
||||
|
|
@ -314,6 +413,9 @@ void attemptClockDiscipline(const GnssSample& sample) {
|
|||
|
||||
setRunIdentityFromClock(disciplinedUtc);
|
||||
g_clockDisciplined = true;
|
||||
g_displayAutoSleepEnabled = true;
|
||||
wakeDisplay();
|
||||
recordEvent("clock_disciplined run_id=%s", g_runId);
|
||||
Serial.printf("RTC disciplined to GPS: %s", g_bootTimestampUtc);
|
||||
if (hadPriorRtc) {
|
||||
Serial.printf(" drift=%+llds", (long long)driftSeconds);
|
||||
|
|
@ -324,6 +426,7 @@ void attemptClockDiscipline(const GnssSample& sample) {
|
|||
}
|
||||
|
||||
void sampleAndMaybeLog() {
|
||||
g_battery.update(millis());
|
||||
GnssSample sample = g_gnss.makeSample();
|
||||
g_stats.updateFromSample(sample, millis());
|
||||
sample.ttffMs = g_stats.ttffMs();
|
||||
|
|
@ -336,8 +439,12 @@ void sampleAndMaybeLog() {
|
|||
const uint32_t msSinceRunStart = millis() - g_runStartMs;
|
||||
SatelliteInfo sats[kMaxSatellites];
|
||||
const size_t satCount = g_gnss.copySatellites(sats, kMaxSatellites);
|
||||
g_storage.appendSampleCsv(sample, sampleSeq, msSinceRunStart, g_runId, g_bootTimestampUtc);
|
||||
g_storage.appendSatelliteCsv(sample, sampleSeq, msSinceRunStart, sats, satCount, g_runId, g_bootTimestampUtc);
|
||||
if (!g_storage.appendSampleCsv(sample, sampleSeq, msSinceRunStart, g_runId, g_bootTimestampUtc)) {
|
||||
stopLoggingForStorageFailure("append_sample");
|
||||
} else if (satCount > 0 &&
|
||||
!g_storage.appendSatelliteCsv(sample, sampleSeq, msSinceRunStart, sats, satCount, g_runId, g_bootTimestampUtc)) {
|
||||
stopLoggingForStorageFailure("append_satellite");
|
||||
}
|
||||
}
|
||||
|
||||
if (g_periodicSerialEnabled && (uint32_t)(millis() - g_lastStatusMs) >= kStatusPeriodMs) {
|
||||
|
|
@ -348,16 +455,20 @@ void sampleAndMaybeLog() {
|
|||
g_lastDisplayMs = millis();
|
||||
if (g_clockDisciplined) {
|
||||
if (g_buttonConfirmActive) {
|
||||
g_display.showBoot("Stop recording?", "Press again in 3s");
|
||||
g_display.showBoot("Stop recording?", "Press again in 3s", nullptr, g_battery.voltageText());
|
||||
} else if ((uint32_t)(millis() - g_buttonStopMessageUntilMs) < kButtonStopMessageMs) {
|
||||
g_display.showBoot("Halted", "Safe to power off");
|
||||
} else {
|
||||
g_display.showSample(sample, g_stats, g_loggingEnabled);
|
||||
g_display.showBoot("Halted", g_lastHaltReason, "Safe to power off", g_battery.voltageText());
|
||||
} else if (!g_display.powerSave()) {
|
||||
g_display.showSample(sample, g_stats, g_loggingEnabled, g_battery.voltageText());
|
||||
}
|
||||
} else {
|
||||
g_display.showBoot("Waiting for GPS UTC", sample.validTime ? "Awaiting PPS" : "No valid time yet");
|
||||
g_display.showBoot("Waiting for GPS UTC",
|
||||
sample.validTime ? "Awaiting PPS" : "No valid time yet",
|
||||
nullptr,
|
||||
g_battery.voltageText());
|
||||
}
|
||||
}
|
||||
updateDisplayPowerState();
|
||||
}
|
||||
|
||||
void buildFileTreeHtml(String& html, const char* path) {
|
||||
|
|
@ -430,7 +541,7 @@ bool normalizeWebPath(const String& input, String& out) {
|
|||
}
|
||||
|
||||
void handleWebIndex() {
|
||||
g_storage.flush();
|
||||
(void)g_storage.flush();
|
||||
String html;
|
||||
html.reserve(8192);
|
||||
html += "<!doctype html><html><head><meta charset='utf-8'><title>GPSQA ";
|
||||
|
|
@ -453,6 +564,10 @@ void handleWebIndex() {
|
|||
html += htmlEscape(String(g_storage.lastError()));
|
||||
html += "<br>Current log: ";
|
||||
html += htmlEscape(String(g_storage.currentPath()));
|
||||
html += "<br>Halt reason: ";
|
||||
html += htmlEscape(String(g_lastHaltReason));
|
||||
html += "<br>Battery: ";
|
||||
html += htmlEscape(String(g_battery.voltageText()));
|
||||
html += "</p>";
|
||||
html += "<p><a href='/cmd?status=1'>status</a> ";
|
||||
html += "<a href='/cmd?flush=1'>flush</a> ";
|
||||
|
|
@ -470,12 +585,13 @@ void handleWebIndex() {
|
|||
|
||||
html += "</ul><p>Web commands also accept query forms like ";
|
||||
html += "<code>/cmd?erase=/logs/20260406_093912_CY.csv</code></p>";
|
||||
appendRecentEventsHtml(html);
|
||||
html += "</body></html>";
|
||||
g_server.send(200, "text/html; charset=utf-8", html);
|
||||
}
|
||||
|
||||
void handleWebDownload() {
|
||||
g_storage.flush();
|
||||
(void)g_storage.flush();
|
||||
String pathArg = g_server.hasArg("path") ? g_server.arg("path") : g_server.arg("name");
|
||||
String fullPath;
|
||||
if (!normalizeWebPath(pathArg, fullPath)) {
|
||||
|
|
@ -513,7 +629,7 @@ void handleWebCommand() {
|
|||
String response;
|
||||
|
||||
if (g_server.hasArg("erase")) {
|
||||
g_storage.flush();
|
||||
(void)g_storage.flush();
|
||||
const String path = g_server.arg("erase");
|
||||
if (g_storage.eraseFile(path.c_str())) {
|
||||
g_storageReady = g_storage.ready();
|
||||
|
|
@ -526,7 +642,7 @@ void handleWebCommand() {
|
|||
response = String("erase failed: ") + g_storage.lastError();
|
||||
}
|
||||
} else if (g_server.hasArg("erase_logs")) {
|
||||
g_storage.flush();
|
||||
(void)g_storage.flush();
|
||||
g_storage.eraseLogs(Serial);
|
||||
g_storageReady = g_storage.ready();
|
||||
if (!g_storageReady) {
|
||||
|
|
@ -535,8 +651,7 @@ void handleWebCommand() {
|
|||
g_logFileCount = g_storage.logFileCount();
|
||||
response = "logs erased";
|
||||
} else if (g_server.hasArg("flush")) {
|
||||
g_storage.flush();
|
||||
response = "buffer flushed";
|
||||
response = g_storage.flush() ? "buffer flushed" : String("flush failed: ") + g_storage.lastError();
|
||||
} else if (g_server.hasArg("stop")) {
|
||||
stopLoggingCleanly("web stop");
|
||||
response = "logging stopped";
|
||||
|
|
@ -566,6 +681,8 @@ void handleWebCommand() {
|
|||
response += g_storageReady ? "yes" : "no";
|
||||
response += "\nsd_state=";
|
||||
response += g_storageMounted ? "mounted" : "absent";
|
||||
response += "\nhalt_reason=";
|
||||
response += g_lastHaltReason;
|
||||
} else {
|
||||
response = "commands: status flush start stop sd_rescan erase=<path> erase_logs=1";
|
||||
}
|
||||
|
|
@ -700,9 +817,11 @@ void setup() {
|
|||
Serial.println("WARNING: PMU init failed");
|
||||
}
|
||||
|
||||
g_battery.begin(g_pmu);
|
||||
g_display.begin();
|
||||
pinMode(BUTTON_PIN, INPUT_PULLUP);
|
||||
g_display.showBoot("Booting...", kBoardId);
|
||||
wakeDisplay();
|
||||
g_display.showBoot("Booting...", kBoardId, nullptr, g_battery.voltageText());
|
||||
g_stats.begin(millis());
|
||||
g_gnss.begin();
|
||||
(void)g_gnss.probeAtStartup(Serial);
|
||||
|
|
@ -731,8 +850,9 @@ void setup() {
|
|||
Serial.println("RTC invalid at boot");
|
||||
}
|
||||
|
||||
recordEvent("boot board=%s", kBoardId);
|
||||
printProvenance();
|
||||
g_display.showBoot("Waiting for GPS UTC", "No log before RTC set");
|
||||
g_display.showBoot("Waiting for GPS UTC", "No log before RTC set", nullptr, g_battery.voltageText());
|
||||
|
||||
g_lastSampleMs = millis();
|
||||
g_lastFlushMs = millis();
|
||||
|
|
@ -752,6 +872,8 @@ void loop() {
|
|||
}
|
||||
if (g_storageReady && (uint32_t)(now - g_lastFlushMs) >= kLogFlushPeriodMs) {
|
||||
g_lastFlushMs = now;
|
||||
g_storage.flush();
|
||||
if (!g_storage.flush()) {
|
||||
stopLoggingForStorageFailure("periodic_flush");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue