Image for T-Beam is in good working shape, restructuring the Perl data importer to deal with the 44 columns using hashes rather than positions

This commit is contained in:
John Poole 2026-04-06 16:42:35 -07:00
commit d3043533ce
9 changed files with 174 additions and 41 deletions

View file

@ -38,6 +38,10 @@
#define GPS_TX_PIN 8
#endif
#ifndef BUTTON_PIN
#define BUTTON_PIN 0
#endif
#ifndef FW_BUILD_UTC
#define FW_BUILD_UTC unknown
#endif

View file

@ -48,7 +48,7 @@ void DisplayManager::showError(const char* line1, const char* line2) {
drawLines(kExerciseName, "ERROR", line1, line2);
}
void DisplayManager::showSample(const GnssSample& sample, const RunStats& stats) {
void DisplayManager::showSample(const GnssSample& sample, const RunStats& stats, bool recording) {
char l1[24];
char l2[20];
char l3[20];
@ -56,7 +56,7 @@ void DisplayManager::showSample(const GnssSample& sample, const RunStats& stats)
char l5[20];
char l6[20];
snprintf(l1, sizeof(l1), "%s %.5s", __DATE__, __TIME__);
snprintf(l1, sizeof(l1), "%s", recording ? "*RECORDING" : "Halted");
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) {
@ -70,4 +70,3 @@ void DisplayManager::showSample(const GnssSample& sample, const RunStats& stats)
}
} // namespace field_qa

View file

@ -12,7 +12,7 @@ class DisplayManager {
void begin();
void showBoot(const char* line2, const char* line3 = nullptr);
void showError(const char* line1, const char* line2 = nullptr);
void showSample(const GnssSample& sample, const RunStats& stats);
void showSample(const GnssSample& sample, const RunStats& stats, bool recording);
private:
void drawLines(const char* l1,
@ -26,4 +26,3 @@ class DisplayManager {
};
} // namespace field_qa

View file

@ -63,6 +63,7 @@ bool StorageManager::startLog(const char* runId, const char* bootTimestampUtc) {
m_ready = false;
m_lastError = "";
m_path = makeFilePath(runId);
m_newFile = !SD.exists(m_path.c_str());
if (!ensureDir() || !openFile()) {
return false;
}
@ -148,10 +149,24 @@ bool StorageManager::ensureDir() {
}
String StorageManager::makeFilePath(const char* runId) const {
char path[96];
char basePath[96];
char candidatePath[112];
const char* rid = (runId && runId[0] != '\0') ? runId : "run";
snprintf(path, sizeof(path), "%s/%s.csv", kLogDir, rid);
return String(path);
snprintf(basePath, sizeof(basePath), "%s/%s", kLogDir, rid);
snprintf(candidatePath, sizeof(candidatePath), "%s.csv", basePath);
if (!SD.exists(candidatePath)) {
return String(candidatePath);
}
for (uint16_t suffix = 2; suffix < 1000; ++suffix) {
snprintf(candidatePath, sizeof(candidatePath), "%s_%02u.csv", basePath, (unsigned)suffix);
if (!SD.exists(candidatePath)) {
return String(candidatePath);
}
}
snprintf(candidatePath, sizeof(candidatePath), "%s_overflow.csv", basePath);
return String(candidatePath);
}
bool StorageManager::openFile() {
@ -164,7 +179,7 @@ bool StorageManager::openFile() {
}
void StorageManager::writeHeader(const char* runId, const char* bootTimestampUtc) {
if (!m_file || m_file.size() > 0) {
if (!m_file || !m_newFile) {
return;
}
m_file.printf("# exercise: %s\n", kExerciseName);
@ -179,6 +194,7 @@ void StorageManager::writeHeader(const char* runId, const char* bootTimestampUtc
m_file.printf("# created_by: ChatGPT/Codex handoff\n");
m_file.print("record_type,timestamp_utc,sample_seq,ms_since_run_start,board_id,gnss_chip,firmware_exercise_name,firmware_version,boot_timestamp_utc,run_id,fix_type,fix_dimension,sats_in_view,sat_seen,sats_used,hdop,vdop,pdop,latitude,longitude,altitude_m,speed_mps,course_deg,pps_seen,quality_class,gps_count,galileo_count,glonass_count,beidou_count,navic_count,qzss_count,sbas_count,mean_cn0,max_cn0,age_of_fix_ms,ttff_ms,longest_no_fix_ms,sat_talker,sat_constellation,sat_prn,sat_elevation_deg,sat_azimuth_deg,sat_snr,sat_used_in_solution\n");
m_file.flush();
m_newFile = false;
}
bool StorageManager::writePendingBuffer() {
@ -241,14 +257,12 @@ bool StorageManager::appendBytes(const char* data, size_t len) {
}
bool StorageManager::appendLine(const String& line) {
if (!appendBytes(line.c_str(), line.length())) {
return false;
if (line.endsWith("\n")) {
return appendBytes(line.c_str(), line.length());
}
if (!line.endsWith("\n")) {
static const char newline = '\n';
return appendBytes(&newline, 1);
}
return true;
String record = line;
record += '\n';
return appendBytes(record.c_str(), record.length());
}
void StorageManager::appendSampleCsv(const GnssSample& sample,
@ -468,6 +482,7 @@ void StorageManager::close() {
m_file.close();
}
m_ready = false;
m_newFile = false;
}
bool StorageManager::normalizePath(const char* input, String& normalized) const {

View file

@ -50,6 +50,7 @@ class StorageManager {
void eraseLogsRecursive(File& dir);
bool m_ready = false;
bool m_newFile = false;
String m_path;
String m_lastError;
File m_file;

View file

@ -118,6 +118,25 @@ bool StartupSdManager::consumeRemovedEvent() {
return out;
}
bool StartupSdManager::forceRemount() {
logf("Watcher: manual rescan requested");
presentVotes_ = 0;
absentVotes_ = 0;
lastPollMs_ = 0;
lastFullScanMs_ = millis();
cycleSdRail(cfg_.recoveryRailOffMs, cfg_.recoveryRailOnSettleMs);
delay(cfg_.startupWarmupMs);
if (mountCardFullScan()) {
setStateMounted();
return true;
}
setStateAbsent();
return false;
}
void StartupSdManager::logf(const char* fmt, ...) {
char msg[196];
va_list args;

View file

@ -46,6 +46,7 @@ class StartupSdManager {
bool consumeMountedEvent();
bool consumeRemovedEvent();
bool forceRemount();
void printCardInfo();
bool ensureDirRecursive(const char* path);