2023-10-01 13:59:15 -07:00
|
|
|
// Copyright (c) FIRST and other WPILib contributors.
|
|
|
|
|
// Open Source Software; you can modify and/or share it under the terms of
|
|
|
|
|
// the WPILib BSD license file in the root directory of this project.
|
|
|
|
|
|
|
|
|
|
#include "DataLogManager.h"
|
|
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
#include <ctime>
|
|
|
|
|
#include <random>
|
2024-05-12 14:09:43 -07:00
|
|
|
#include <string>
|
2024-09-20 17:43:39 -07:00
|
|
|
#include <vector>
|
2023-10-01 13:59:15 -07:00
|
|
|
|
|
|
|
|
#include <fmt/chrono.h>
|
|
|
|
|
#include <fmt/format.h>
|
2025-11-07 19:57:55 -05:00
|
|
|
|
|
|
|
|
#include "wpi/datalog/DataLogBackgroundWriter.hpp"
|
|
|
|
|
#include "wpi/datalog/FileLogger.hpp"
|
2025-11-07 19:56:21 -05:00
|
|
|
#include "wpi/nt/NetworkTableInstance.hpp"
|
|
|
|
|
#include "wpi/util/SafeThread.hpp"
|
|
|
|
|
#include "wpi/util/StringExtras.hpp"
|
|
|
|
|
#include "wpi/util/fs.hpp"
|
|
|
|
|
#include "wpi/util/print.hpp"
|
2023-10-01 13:59:15 -07:00
|
|
|
|
|
|
|
|
using namespace wpi;
|
|
|
|
|
|
|
|
|
|
/** Shims to keep the code as similar as possible to wpilibc */
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
namespace warn {
|
|
|
|
|
static constexpr int Warning = 16;
|
|
|
|
|
} // namespace warn
|
|
|
|
|
|
|
|
|
|
namespace frc {
|
|
|
|
|
void ReportErrorV(int32_t status, const char* fileName, int lineNumber,
|
|
|
|
|
const char* funcName, fmt::string_view format,
|
|
|
|
|
fmt::format_args args) {
|
2025-06-02 16:42:56 -07:00
|
|
|
// TODO when we get a low level interface
|
|
|
|
|
// #ifdef __FRC_SYSTEMCORE__
|
|
|
|
|
// if (status == 0) {
|
|
|
|
|
// return;
|
|
|
|
|
// }
|
|
|
|
|
// fmt::memory_buffer out;
|
|
|
|
|
// fmt::format_to(fmt::appender{out}, "Warning: ");
|
|
|
|
|
// fmt::vformat_to(fmt::appender{out}, format, args);
|
|
|
|
|
// out.push_back('\0');
|
|
|
|
|
// FRC_NetworkCommunication_sendError(status < 0, status, 0, out.data(),
|
|
|
|
|
// "DataLogManager", "");
|
|
|
|
|
// #endif
|
2023-10-01 13:59:15 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
template <typename... Args>
|
|
|
|
|
inline void ReportError(int32_t status, const char* fileName, int lineNumber,
|
|
|
|
|
const char* funcName, fmt::string_view format,
|
|
|
|
|
Args&&... args) {
|
|
|
|
|
ReportErrorV(status, fileName, lineNumber, funcName, format,
|
|
|
|
|
fmt::make_format_args(args...));
|
|
|
|
|
}
|
|
|
|
|
} // namespace frc
|
|
|
|
|
|
2024-12-26 17:14:02 -08:00
|
|
|
#define FRC_ReportError(status, format, ...) \
|
|
|
|
|
do { \
|
|
|
|
|
if ((status) != 0) { \
|
|
|
|
|
::frc::ReportError(status, __FILE__, __LINE__, __FUNCTION__, \
|
|
|
|
|
format __VA_OPT__(, ) __VA_ARGS__); \
|
|
|
|
|
} \
|
2023-10-01 13:59:15 -07:00
|
|
|
} while (0)
|
|
|
|
|
|
|
|
|
|
namespace RobotController {
|
|
|
|
|
inline bool IsSystemTimeValid() {
|
2025-06-02 16:42:56 -07:00
|
|
|
#ifdef __FRC_SYSTEMCORE__
|
|
|
|
|
// TODO when we get a proper low level library, and time setting
|
|
|
|
|
return false;
|
|
|
|
|
// uint8_t timeWasSet = 0;
|
|
|
|
|
// FRC_NetworkCommunication_getTimeWasSet(&timeWasSet);
|
|
|
|
|
// return timeWasSet != 0;
|
2023-10-01 13:59:15 -07:00
|
|
|
#else
|
|
|
|
|
return true;
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
} // namespace RobotController
|
|
|
|
|
|
|
|
|
|
namespace filesystem {
|
|
|
|
|
inline std::string GetOperatingDirectory() {
|
2025-06-02 16:42:56 -07:00
|
|
|
#ifdef __FRC_SYSTEMCORE__
|
|
|
|
|
return "/home/systemcore";
|
2023-10-01 13:59:15 -07:00
|
|
|
#else
|
|
|
|
|
return fs::current_path().string();
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
} // namespace filesystem
|
|
|
|
|
|
|
|
|
|
namespace DriverStation {
|
2025-06-02 16:42:56 -07:00
|
|
|
// #ifdef __FRC_SYSTEMCORE__
|
|
|
|
|
// using MatchType = MatchType_t;
|
|
|
|
|
// constexpr int kNone = kMatchType_none;
|
|
|
|
|
// constexpr int kPractice = kMatchType_practice;
|
|
|
|
|
// constexpr int kQualification = kMatchType_qualification;
|
|
|
|
|
// constexpr int kElimination = kMatchType_elimination;
|
|
|
|
|
// char gEventName[128];
|
|
|
|
|
// MatchType_t gMatchType;
|
|
|
|
|
// uint16_t gMatchNumber;
|
|
|
|
|
// uint8_t gReplayNumber;
|
|
|
|
|
// uint8_t gGameSpecificMessage[16];
|
|
|
|
|
// uint16_t gGameSpecificMessageSize;
|
|
|
|
|
// #else
|
2023-10-01 13:59:15 -07:00
|
|
|
enum MatchType { kNone, kPractice, kQualification, kElimination };
|
2025-06-02 16:42:56 -07:00
|
|
|
// #endif
|
2023-10-01 13:59:15 -07:00
|
|
|
|
|
|
|
|
inline void UpdateMatchInfo() {
|
2025-06-02 16:42:56 -07:00
|
|
|
// #ifdef __FRC_SYSTEMCORE__
|
|
|
|
|
// gGameSpecificMessageSize = sizeof(gGameSpecificMessage);
|
|
|
|
|
// FRC_NetworkCommunication_getMatchInfo(gEventName, &gMatchType,
|
|
|
|
|
// &gMatchNumber,
|
|
|
|
|
// &gReplayNumber,
|
|
|
|
|
// gGameSpecificMessage,
|
|
|
|
|
// &gGameSpecificMessageSize);
|
|
|
|
|
// #endif
|
2023-10-01 13:59:15 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline MatchType GetMatchType() {
|
2025-06-02 16:42:56 -07:00
|
|
|
// #ifdef __FRC_SYSTEMCORE__
|
|
|
|
|
// return gMatchType;
|
|
|
|
|
// #else
|
2023-10-01 13:59:15 -07:00
|
|
|
return kNone;
|
2025-06-02 16:42:56 -07:00
|
|
|
// #endif
|
2023-10-01 13:59:15 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline std::string_view GetEventName() {
|
2025-06-02 16:42:56 -07:00
|
|
|
// #ifdef __FRC_SYSTEMCORE__
|
|
|
|
|
// return gEventName;
|
|
|
|
|
// #else
|
2023-10-01 13:59:15 -07:00
|
|
|
return "";
|
2025-06-02 16:42:56 -07:00
|
|
|
// #endif
|
2023-10-01 13:59:15 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline uint16_t GetMatchNumber() {
|
2025-06-02 16:42:56 -07:00
|
|
|
// #ifdef __FRC_SYSTEMCORE__
|
|
|
|
|
// return gMatchNumber;
|
|
|
|
|
// #else
|
2023-10-01 13:59:15 -07:00
|
|
|
return 0;
|
2025-06-02 16:42:56 -07:00
|
|
|
// #endif
|
2023-10-01 13:59:15 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline bool IsDSAttached() {
|
2025-06-02 16:42:56 -07:00
|
|
|
// #ifdef __FRC_SYSTEMCORE__
|
|
|
|
|
// struct ControlWord_t cw;
|
|
|
|
|
// FRC_NetworkCommunication_getControlWord(&cw);
|
|
|
|
|
// return cw.dsAttached;
|
|
|
|
|
// #else
|
2023-10-01 13:59:15 -07:00
|
|
|
return true;
|
2025-06-02 16:42:56 -07:00
|
|
|
// #endif
|
2023-10-01 13:59:15 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline bool IsFMSAttached() {
|
2025-06-02 16:42:56 -07:00
|
|
|
// #ifdef __FRC_SYSTEMCORE__
|
|
|
|
|
// struct ControlWord_t cw;
|
|
|
|
|
// FRC_NetworkCommunication_getControlWord(&cw);
|
|
|
|
|
// return cw.fmsAttached;
|
|
|
|
|
// #else
|
2023-10-01 13:59:15 -07:00
|
|
|
return false;
|
2025-06-02 16:42:56 -07:00
|
|
|
// #endif
|
2023-10-01 13:59:15 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
WPI_EventHandle gNewDataEvent;
|
|
|
|
|
|
|
|
|
|
inline void ProvideRefreshedDataEventHandle(WPI_EventHandle event) {
|
|
|
|
|
gNewDataEvent = event;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline void RemoveRefreshedDataEventHandle(WPI_EventHandle event) {}
|
|
|
|
|
|
|
|
|
|
} // namespace DriverStation
|
|
|
|
|
|
2025-06-02 16:42:56 -07:00
|
|
|
// #ifdef __FRC_SYSTEMCORE__
|
|
|
|
|
// static constexpr int kRoboRIO = 0;
|
|
|
|
|
// namespace RobotBase {
|
|
|
|
|
// inline int GetRuntimeType() {
|
|
|
|
|
// nLoadOut::tTargetClass targetClass = nLoadOut::getTargetClass();
|
|
|
|
|
// if (targetClass == nLoadOut::kTargetClass_RoboRIO2) {
|
|
|
|
|
// return 1;
|
|
|
|
|
// } else {
|
|
|
|
|
// return 0;
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
// } // namespace RobotBase
|
|
|
|
|
// #endif
|
2023-10-01 13:59:15 -07:00
|
|
|
|
2025-11-07 20:00:05 -05:00
|
|
|
struct Thread final : public wpi::util::SafeThread {
|
2023-10-01 13:59:15 -07:00
|
|
|
Thread(std::string_view dir, std::string_view filename, double period);
|
|
|
|
|
~Thread() override;
|
|
|
|
|
|
|
|
|
|
void Main() final;
|
|
|
|
|
|
|
|
|
|
void StartNTLog();
|
|
|
|
|
void StopNTLog();
|
2024-09-13 01:13:06 -04:00
|
|
|
void StartConsoleLog();
|
|
|
|
|
void StopConsoleLog();
|
2023-10-01 13:59:15 -07:00
|
|
|
|
|
|
|
|
std::string m_logDir;
|
|
|
|
|
bool m_filenameOverride;
|
2024-05-12 14:09:43 -07:00
|
|
|
wpi::log::DataLogBackgroundWriter m_log;
|
2023-10-01 13:59:15 -07:00
|
|
|
bool m_ntLoggerEnabled = false;
|
|
|
|
|
NT_DataLogger m_ntEntryLogger = 0;
|
|
|
|
|
NT_ConnectionDataLogger m_ntConnLogger = 0;
|
2024-09-13 01:13:06 -04:00
|
|
|
bool m_consoleLoggerEnabled = false;
|
2025-02-19 23:08:17 -06:00
|
|
|
wpi::log::FileLogger m_consoleLogger;
|
2023-10-01 13:59:15 -07:00
|
|
|
wpi::log::StringLogEntry m_messageLog;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct Instance {
|
|
|
|
|
Instance(std::string_view dir, std::string_view filename, double period);
|
2025-11-07 20:00:05 -05:00
|
|
|
wpi::util::SafeThreadOwner<Thread> owner;
|
2023-10-01 13:59:15 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
// if less than this much free space, delete log files until there is this much
|
|
|
|
|
// free space OR there are this many files remaining.
|
|
|
|
|
static constexpr uintmax_t kFreeSpaceThreshold = 50000000;
|
|
|
|
|
static constexpr int kFileCountThreshold = 10;
|
|
|
|
|
|
|
|
|
|
static std::string MakeLogDir(std::string_view dir) {
|
|
|
|
|
if (!dir.empty()) {
|
|
|
|
|
return std::string{dir};
|
|
|
|
|
}
|
2025-06-02 16:42:56 -07:00
|
|
|
#ifdef __FRC_SYSTEMCORE__
|
2023-10-01 13:59:15 -07:00
|
|
|
// prefer a mounted USB drive if one is accessible
|
|
|
|
|
std::error_code ec;
|
2023-11-30 23:13:51 -08:00
|
|
|
auto s = fs::status("/u", ec);
|
2023-10-01 13:59:15 -07:00
|
|
|
if (!ec && fs::is_directory(s) &&
|
|
|
|
|
(s.permissions() & fs::perms::others_write) != fs::perms::none) {
|
2023-11-30 23:13:51 -08:00
|
|
|
fs::create_directory("/u/logs", ec);
|
|
|
|
|
return "/u/logs";
|
2023-10-01 13:59:15 -07:00
|
|
|
}
|
2025-06-02 16:42:56 -07:00
|
|
|
fs::create_directory("/home/systemcore/logs", ec);
|
|
|
|
|
return "/home/systemcore/logs";
|
2023-11-14 12:20:51 -08:00
|
|
|
#else
|
2023-12-11 20:21:06 -08:00
|
|
|
std::string logDir = filesystem::GetOperatingDirectory() + "/logs";
|
|
|
|
|
std::error_code ec;
|
|
|
|
|
fs::create_directory(logDir, ec);
|
|
|
|
|
return logDir;
|
2023-11-14 12:20:51 -08:00
|
|
|
#endif
|
2023-10-01 13:59:15 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static std::string MakeLogFilename(std::string_view filenameOverride) {
|
|
|
|
|
if (!filenameOverride.empty()) {
|
|
|
|
|
return std::string{filenameOverride};
|
|
|
|
|
}
|
|
|
|
|
static std::random_device dev;
|
|
|
|
|
static std::mt19937 rng(dev());
|
|
|
|
|
std::uniform_int_distribution<int> dist(0, 15);
|
|
|
|
|
const char* v = "0123456789abcdef";
|
|
|
|
|
std::string filename = "FRC_TBD_";
|
|
|
|
|
for (int i = 0; i < 16; i++) {
|
|
|
|
|
filename += v[dist(rng)];
|
|
|
|
|
}
|
|
|
|
|
filename += ".wpilog";
|
|
|
|
|
return filename;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Thread::Thread(std::string_view dir, std::string_view filename, double period)
|
|
|
|
|
: m_logDir{dir},
|
|
|
|
|
m_filenameOverride{!filename.empty()},
|
|
|
|
|
m_log{dir, MakeLogFilename(filename), period},
|
|
|
|
|
m_messageLog{m_log, "messages"} {
|
|
|
|
|
StartNTLog();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Thread::~Thread() {
|
|
|
|
|
StopNTLog();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Thread::Main() {
|
|
|
|
|
// based on free disk space, scan for "old" FRC_*.wpilog files and remove
|
|
|
|
|
{
|
2023-11-03 20:34:43 -07:00
|
|
|
std::error_code ec;
|
|
|
|
|
uintmax_t freeSpace;
|
|
|
|
|
auto freeSpaceInfo = fs::space(m_logDir, ec);
|
|
|
|
|
if (!ec) {
|
|
|
|
|
freeSpace = freeSpaceInfo.available;
|
|
|
|
|
} else {
|
|
|
|
|
freeSpace = UINTMAX_MAX;
|
|
|
|
|
}
|
2023-10-01 13:59:15 -07:00
|
|
|
if (freeSpace < kFreeSpaceThreshold) {
|
|
|
|
|
// Delete oldest FRC_*.wpilog files (ignore FRC_TBD_*.wpilog as we just
|
|
|
|
|
// created one)
|
|
|
|
|
std::vector<fs::directory_entry> entries;
|
|
|
|
|
for (auto&& entry : fs::directory_iterator{m_logDir, ec}) {
|
|
|
|
|
auto stem = entry.path().stem().string();
|
2025-11-07 20:00:05 -05:00
|
|
|
if (wpi::util::starts_with(stem, "FRC_") &&
|
2023-10-01 13:59:15 -07:00
|
|
|
entry.path().extension() == ".wpilog" &&
|
2025-11-07 20:00:05 -05:00
|
|
|
!wpi::util::starts_with(stem, "FRC_TBD_")) {
|
2023-10-01 13:59:15 -07:00
|
|
|
entries.emplace_back(entry);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
std::sort(entries.begin(), entries.end(),
|
|
|
|
|
[](const auto& a, const auto& b) {
|
|
|
|
|
return a.last_write_time() < b.last_write_time();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
int count = entries.size();
|
|
|
|
|
for (auto&& entry : entries) {
|
|
|
|
|
--count;
|
|
|
|
|
if (count < kFileCountThreshold) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
auto size = entry.file_size();
|
|
|
|
|
if (fs::remove(entry.path(), ec)) {
|
|
|
|
|
FRC_ReportError(warn::Warning, "DataLogManager: Deleted {}",
|
|
|
|
|
entry.path().string());
|
|
|
|
|
freeSpace += size;
|
|
|
|
|
if (freeSpace >= kFreeSpaceThreshold) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2025-11-07 20:00:05 -05:00
|
|
|
wpi::util::print(stderr, "DataLogManager: could not delete {}\n",
|
2023-10-01 13:59:15 -07:00
|
|
|
entry.path().string());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if (freeSpace < 2 * kFreeSpaceThreshold) {
|
|
|
|
|
FRC_ReportError(
|
|
|
|
|
warn::Warning,
|
|
|
|
|
"DataLogManager: Log storage device has {} MB of free space "
|
|
|
|
|
"remaining! Logs will get deleted below {} MB of free space. "
|
|
|
|
|
"Consider deleting logs off the storage device.",
|
|
|
|
|
freeSpace / 1000000, kFreeSpaceThreshold / 1000000);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int timeoutCount = 0;
|
|
|
|
|
bool paused = false;
|
|
|
|
|
int dsAttachCount = 0;
|
|
|
|
|
int fmsAttachCount = 0;
|
|
|
|
|
bool dsRenamed = m_filenameOverride;
|
|
|
|
|
bool fmsRenamed = m_filenameOverride;
|
|
|
|
|
int sysTimeCount = 0;
|
|
|
|
|
wpi::log::IntegerLogEntry sysTimeEntry{
|
|
|
|
|
m_log, "systemTime",
|
|
|
|
|
"{\"source\":\"DataLogManager\",\"format\":\"time_t_us\"}"};
|
|
|
|
|
|
2025-11-07 20:00:05 -05:00
|
|
|
wpi::util::Event newDataEvent;
|
2023-10-01 13:59:15 -07:00
|
|
|
DriverStation::ProvideRefreshedDataEventHandle(newDataEvent.GetHandle());
|
|
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
|
bool timedOut = false;
|
|
|
|
|
bool newData =
|
2025-11-07 20:00:05 -05:00
|
|
|
wpi::util::WaitForObject(newDataEvent.GetHandle(), 0.25, &timedOut);
|
2023-10-01 13:59:15 -07:00
|
|
|
if (!m_active) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if (!newData) {
|
|
|
|
|
++timeoutCount;
|
|
|
|
|
// pause logging after being disconnected for 10 seconds
|
|
|
|
|
if (timeoutCount > 40 && !paused) {
|
|
|
|
|
timeoutCount = 0;
|
|
|
|
|
paused = true;
|
|
|
|
|
m_log.Pause();
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
// when we connect to the DS, resume logging
|
|
|
|
|
timeoutCount = 0;
|
|
|
|
|
if (paused) {
|
|
|
|
|
paused = false;
|
|
|
|
|
m_log.Resume();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!dsRenamed) {
|
|
|
|
|
// track DS attach
|
|
|
|
|
if (DriverStation::IsDSAttached()) {
|
|
|
|
|
++dsAttachCount;
|
|
|
|
|
} else {
|
|
|
|
|
dsAttachCount = 0;
|
|
|
|
|
}
|
|
|
|
|
if (dsAttachCount > 50) { // 1 second
|
|
|
|
|
if (RobotController::IsSystemTimeValid()) {
|
|
|
|
|
std::time_t now = std::time(nullptr);
|
|
|
|
|
auto tm = std::gmtime(&now);
|
|
|
|
|
m_log.SetFilename(fmt::format("FRC_{:%Y%m%d_%H%M%S}.wpilog", *tm));
|
|
|
|
|
dsRenamed = true;
|
|
|
|
|
} else {
|
|
|
|
|
dsAttachCount = 0; // wait a bit and try again
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!fmsRenamed) {
|
|
|
|
|
// track FMS attach
|
|
|
|
|
if (DriverStation::IsFMSAttached()) {
|
|
|
|
|
++fmsAttachCount;
|
|
|
|
|
} else {
|
|
|
|
|
fmsAttachCount = 0;
|
|
|
|
|
}
|
|
|
|
|
if (fmsAttachCount > 250) { // 5 seconds
|
|
|
|
|
// match info comes through TCP, so we need to double-check we've
|
|
|
|
|
// actually received it
|
|
|
|
|
DriverStation::UpdateMatchInfo();
|
|
|
|
|
auto matchType = DriverStation::GetMatchType();
|
|
|
|
|
if (matchType != DriverStation::kNone) {
|
|
|
|
|
// rename per match info
|
|
|
|
|
char matchTypeChar;
|
|
|
|
|
switch (matchType) {
|
|
|
|
|
case DriverStation::kPractice:
|
|
|
|
|
matchTypeChar = 'P';
|
|
|
|
|
break;
|
|
|
|
|
case DriverStation::kQualification:
|
|
|
|
|
matchTypeChar = 'Q';
|
|
|
|
|
break;
|
|
|
|
|
case DriverStation::kElimination:
|
|
|
|
|
matchTypeChar = 'E';
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
matchTypeChar = '_';
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
std::time_t now = std::time(nullptr);
|
|
|
|
|
m_log.SetFilename(
|
|
|
|
|
fmt::format("FRC_{:%Y%m%d_%H%M%S}_{}_{}{}.wpilog",
|
|
|
|
|
*std::gmtime(&now), DriverStation::GetEventName(),
|
|
|
|
|
matchTypeChar, DriverStation::GetMatchNumber()));
|
|
|
|
|
fmsRenamed = true;
|
|
|
|
|
dsRenamed = true; // don't override FMS rename
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Write system time every ~5 seconds
|
|
|
|
|
++sysTimeCount;
|
|
|
|
|
if (sysTimeCount >= 250) {
|
|
|
|
|
sysTimeCount = 0;
|
|
|
|
|
if (RobotController::IsSystemTimeValid()) {
|
2025-11-07 20:00:05 -05:00
|
|
|
sysTimeEntry.Append(wpi::util::GetSystemTime(), wpi::util::Now());
|
2023-10-01 13:59:15 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
DriverStation::RemoveRefreshedDataEventHandle(newDataEvent.GetHandle());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Thread::StartNTLog() {
|
|
|
|
|
if (!m_ntLoggerEnabled) {
|
|
|
|
|
m_ntLoggerEnabled = true;
|
|
|
|
|
auto inst = nt::NetworkTableInstance::GetDefault();
|
|
|
|
|
m_ntEntryLogger = inst.StartEntryDataLog(m_log, "", "NT:");
|
|
|
|
|
m_ntConnLogger = inst.StartConnectionDataLog(m_log, "NTConnection");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Thread::StopNTLog() {
|
|
|
|
|
if (m_ntLoggerEnabled) {
|
|
|
|
|
m_ntLoggerEnabled = false;
|
|
|
|
|
nt::NetworkTableInstance::StopEntryDataLog(m_ntEntryLogger);
|
|
|
|
|
nt::NetworkTableInstance::StopConnectionDataLog(m_ntConnLogger);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-13 01:13:06 -04:00
|
|
|
void Thread::StartConsoleLog() {
|
|
|
|
|
if (!m_consoleLoggerEnabled) {
|
|
|
|
|
m_consoleLoggerEnabled = true;
|
2025-06-02 16:42:56 -07:00
|
|
|
m_consoleLogger = {"/home/systemcore/FRC_UserProgram.log", m_log, "output"};
|
2024-09-13 01:13:06 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Thread::StopConsoleLog() {
|
|
|
|
|
if (m_consoleLoggerEnabled) {
|
|
|
|
|
m_consoleLoggerEnabled = false;
|
|
|
|
|
m_consoleLogger = {};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-01 13:59:15 -07:00
|
|
|
Instance::Instance(std::string_view dir, std::string_view filename,
|
|
|
|
|
double period) {
|
|
|
|
|
// Delete all previously existing FRC_TBD_*.wpilog files. These only exist
|
|
|
|
|
// when the robot never connects to the DS, so they are very unlikely to
|
|
|
|
|
// have useful data and just clutter the filesystem.
|
|
|
|
|
auto logDir = MakeLogDir(dir);
|
|
|
|
|
std::error_code ec;
|
|
|
|
|
for (auto&& entry : fs::directory_iterator{logDir, ec}) {
|
2025-11-07 20:00:05 -05:00
|
|
|
if (wpi::util::starts_with(entry.path().stem().string(), "FRC_TBD_") &&
|
2023-10-01 13:59:15 -07:00
|
|
|
entry.path().extension() == ".wpilog") {
|
|
|
|
|
if (!fs::remove(entry, ec)) {
|
2025-11-07 20:00:05 -05:00
|
|
|
wpi::util::print(stderr, "DataLogManager: could not delete {}\n",
|
2023-10-01 13:59:15 -07:00
|
|
|
entry.path().string());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
owner.Start(logDir, filename, period);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static Instance& GetInstance(std::string_view dir = "",
|
|
|
|
|
std::string_view filename = "",
|
|
|
|
|
double period = 0.25) {
|
|
|
|
|
static Instance instance(dir, filename, period);
|
2023-11-03 20:34:43 -07:00
|
|
|
if (!instance.owner) {
|
|
|
|
|
instance.owner.Start(MakeLogDir(dir), filename, period);
|
|
|
|
|
}
|
2023-10-01 13:59:15 -07:00
|
|
|
return instance;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DataLogManager::Start(std::string_view dir, std::string_view filename,
|
|
|
|
|
double period) {
|
|
|
|
|
GetInstance(dir, filename, period);
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-03 20:34:43 -07:00
|
|
|
void DataLogManager::Stop() {
|
|
|
|
|
auto& inst = GetInstance();
|
|
|
|
|
inst.owner.GetThread()->m_log.Stop();
|
2023-11-10 21:03:30 -08:00
|
|
|
inst.owner.Join();
|
2023-11-03 20:34:43 -07:00
|
|
|
}
|
|
|
|
|
|
2023-10-01 13:59:15 -07:00
|
|
|
void DataLogManager::Log(std::string_view message) {
|
|
|
|
|
GetInstance().owner.GetThread()->m_messageLog.Append(message);
|
2025-11-07 20:00:05 -05:00
|
|
|
wpi::util::print("{}\n", message);
|
2023-10-01 13:59:15 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
wpi::log::DataLog& DataLogManager::GetLog() {
|
|
|
|
|
return GetInstance().owner.GetThread()->m_log;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string_view DataLogManager::GetLogDir() {
|
|
|
|
|
return GetInstance().owner.GetThread()->m_logDir;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DataLogManager::LogNetworkTables(bool enabled) {
|
|
|
|
|
if (auto thr = GetInstance().owner.GetThread()) {
|
|
|
|
|
if (enabled) {
|
|
|
|
|
thr->StartNTLog();
|
|
|
|
|
} else if (!enabled) {
|
|
|
|
|
thr->StopNTLog();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-13 01:13:06 -04:00
|
|
|
void DataLogManager::LogConsoleOutput(bool enabled) {
|
|
|
|
|
if (auto thr = GetInstance().owner.GetThread()) {
|
|
|
|
|
if (enabled) {
|
|
|
|
|
thr->StartConsoleLog();
|
|
|
|
|
} else if (!enabled) {
|
|
|
|
|
thr->StopConsoleLog();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-01 13:59:15 -07:00
|
|
|
void DataLogManager::SignalNewDSDataOccur() {
|
2025-11-07 20:00:05 -05:00
|
|
|
wpi::util::SetSignalObject(DriverStation::gNewDataEvent);
|
2023-10-01 13:59:15 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
extern "C" {
|
|
|
|
|
|
2024-11-07 14:19:37 -08:00
|
|
|
void DLM_Start(const struct WPI_String* dir, const struct WPI_String* filename,
|
|
|
|
|
double period) {
|
2025-11-07 20:00:05 -05:00
|
|
|
DataLogManager::Start(wpi::util::to_string_view(dir), wpi::util::to_string_view(filename),
|
2024-11-07 14:19:37 -08:00
|
|
|
period);
|
2023-10-01 13:59:15 -07:00
|
|
|
}
|
|
|
|
|
|
2023-11-03 20:34:43 -07:00
|
|
|
void DLM_Stop(void) {
|
|
|
|
|
DataLogManager::Stop();
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-07 14:19:37 -08:00
|
|
|
void DLM_Log(const struct WPI_String* message) {
|
2025-11-07 20:00:05 -05:00
|
|
|
DataLogManager::Log(wpi::util::to_string_view(message));
|
2023-10-01 13:59:15 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
WPI_DataLog* DLM_GetLog(void) {
|
|
|
|
|
return reinterpret_cast<WPI_DataLog*>(&DataLogManager::GetLog());
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-07 14:19:37 -08:00
|
|
|
void DLM_GetLogDir(struct WPI_String* value) {
|
|
|
|
|
auto logDir = DataLogManager::GetLogDir();
|
|
|
|
|
char* write = WPI_AllocateString(value, logDir.size());
|
|
|
|
|
std::memcpy(write, logDir.data(), logDir.size());
|
2023-10-01 13:59:15 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DLM_LogNetworkTables(int enabled) {
|
|
|
|
|
DataLogManager::LogNetworkTables(enabled);
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-13 01:13:06 -04:00
|
|
|
void DLM_LogConsoleOutput(int enabled) {
|
|
|
|
|
DataLogManager::LogConsoleOutput(enabled);
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-01 13:59:15 -07:00
|
|
|
void DLM_SignalNewDSDataOccur(void) {
|
|
|
|
|
DataLogManager::SignalNewDSDataOccur();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // extern "C"
|