diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7d5ac6e..76d4170 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,7 @@ jobs: build-host: env: - MACOSX_DEPLOYMENT_TARGET: 12 + MACOSX_DEPLOYMENT_TARGET: 13 strategy: fail-fast: false matrix: @@ -42,9 +42,9 @@ jobs: - os: windows-2022 artifact-name: Win64 architecture: x64 - - os: macos-12 + - os: macos-14 artifact-name: macOS - architecture: x64 + architecture: aarch64 name: "Build - ${{ matrix.artifact-name }}" runs-on: ${{ matrix.os }} steps: diff --git a/.gitignore b/.gitignore index a332fb2..afba9e5 100644 --- a/.gitignore +++ b/.gitignore @@ -158,3 +158,7 @@ bin/ # End of https://www.gitignore.io/api/c++,java,linux,macos,gradle,windows,visualstudiocode + +# clangd +/.cache +compile_commands.json diff --git a/URCL.json b/URCL.json index 33cc527..cd66abf 100644 --- a/URCL.json +++ b/URCL.json @@ -62,4 +62,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/build.gradle b/build.gradle index ca99b7b..48ff2ad 100644 --- a/build.gradle +++ b/build.gradle @@ -21,19 +21,19 @@ apply from: 'config.gradle' // Apply Java configuration dependencies { - implementation 'edu.wpi.first.cscore:cscore-java:2024.+' - implementation 'edu.wpi.first.cameraserver:cameraserver-java:2024.+' - implementation 'edu.wpi.first.ntcore:ntcore-java:2024.+' - implementation 'edu.wpi.first.wpilibj:wpilibj-java:2024.+' - implementation 'edu.wpi.first.wpiutil:wpiutil-java:2024.+' - implementation 'edu.wpi.first.wpimath:wpimath-java:2024.+' - implementation 'edu.wpi.first.wpiunits:wpiunits-java:2024.+' - implementation 'edu.wpi.first.hal:hal-java:2024.+' + implementation 'edu.wpi.first.cscore:cscore-java:2025.+' + implementation 'edu.wpi.first.cameraserver:cameraserver-java:2025.+' + implementation 'edu.wpi.first.ntcore:ntcore-java:2025.+' + implementation 'edu.wpi.first.wpilibj:wpilibj-java:2025.+' + implementation 'edu.wpi.first.wpiutil:wpiutil-java:2025.+' + implementation 'edu.wpi.first.wpimath:wpimath-java:2025.+' + implementation 'edu.wpi.first.wpiunits:wpiunits-java:2025.+' + implementation 'edu.wpi.first.hal:hal-java:2025.+' implementation "org.ejml:ejml-simple:0.43.1" implementation "com.fasterxml.jackson.core:jackson-annotations:2.12.4" implementation "com.fasterxml.jackson.core:jackson-core:2.12.4" implementation "com.fasterxml.jackson.core:jackson-databind:2.12.4" - implementation 'edu.wpi.first.thirdparty.frc2024.opencv:opencv-java:4.8.0-2' + // implementation 'edu.wpi.first.thirdparty.frc2024.opencv:opencv-java:4.8.0-2' } // Set up exports properly diff --git a/config.gradle b/config.gradle index 0c88342..a47e6d3 100644 --- a/config.gradle +++ b/config.gradle @@ -6,7 +6,7 @@ nativeUtils.withCrossRoboRIO() nativeUtils { wpi { configureDependencies { - wpiVersion = "2024.+" + wpiVersion = "2025.+" opencvYear = "frc2024" googleTestYear = "frc2024" niLibVersion = "2024.2.1" diff --git a/src/main/java/org/littletonrobotics/urcl/URCL.java b/src/main/java/org/littletonrobotics/urcl/URCL.java index 1025498..b90e562 100644 --- a/src/main/java/org/littletonrobotics/urcl/URCL.java +++ b/src/main/java/org/littletonrobotics/urcl/URCL.java @@ -15,18 +15,20 @@ import edu.wpi.first.networktables.NetworkTableInstance; import edu.wpi.first.networktables.RawPublisher; +import edu.wpi.first.util.datalog.DataLog; +import edu.wpi.first.util.datalog.RawLogEntry; import edu.wpi.first.wpilibj.DriverStation; import edu.wpi.first.wpilibj.Notifier; /** *

URCL (Unofficial REV-Compatible Logger)

- * + * * This unofficial logger enables automatic capture of CAN traffic from REV * motor controllers to NetworkTables, viewable using AdvantageScope. See the * corresponding * AdvantageScope documentation for more details. - * + * *

* As this library is not an official REV tool, support queries should be * directed to the URCL @@ -44,6 +46,9 @@ public class URCL { private static RawPublisher periodicPublisher; private static RawPublisher aliasesPublisher; private static Notifier notifier; + private static RawLogEntry persistentLogEntry; + private static RawLogEntry periodicLogEntry; + private static RawLogEntry aliasLogEntry; /** * Start capturing data from REV motor controllers to NetworkTables. This method @@ -53,10 +58,20 @@ public static void start() { start(Map.of()); } + /** + * Start capturing data from REV motor controllers to a Datalog. This method + * should only be called once. + * + * @param log the DataLog to log to. + */ + public static void start(DataLog log) { + start(Map.of(), log); + } + /** * Start capturing data from REV motor controllers to NetworkTables. This method * should only be called once. - * + * * @param aliases The set of aliases mapping CAN IDs to names. */ public static void start(Map aliases) { @@ -68,7 +83,7 @@ public static void start(Map aliases) { // Update aliases buffer updateAliasesBuffer(aliases); - + // Start driver URCLJNI.start(); persistentBuffer = URCLJNI.getPersistentBuffer(); @@ -96,12 +111,51 @@ public static void start(Map aliases) { notifier.startPeriodic(period); } + /** + * Start capturing data from REV motor controllers to a DataLog. This method + * should only be called once. + * + * @param aliases The set of aliases mapping CAN IDs to names. + * @param log the DataLog to log to. Note using a DataLog means it will not + * log to NetworkTables. + */ + public static void start(Map aliases, DataLog log) { + if (running) { + DriverStation.reportError("URCL cannot be started multiple times", true); + return; + } + running = true; + + // Update aliases buffer + updateAliasesBuffer(aliases); + + // Start driver + URCLJNI.start(); + persistentBuffer = URCLJNI.getPersistentBuffer(); + periodicBuffer = URCLJNI.getPeriodicBuffer(); + persistentBuffer.order(ByteOrder.LITTLE_ENDIAN); + periodicBuffer.order(ByteOrder.LITTLE_ENDIAN); + + persistentLogEntry = new RawLogEntry(log, "URCL/Raw/Persistent", "", + "URCLr2_persistent"); + periodicLogEntry = new RawLogEntry(log, "/URCL/Raw/Periodic", "", "URCLr2_periodic"); + aliasLogEntry = new RawLogEntry(log, "/URCL/Raw/Aliases", "", "URCLr2_aliases"); + notifier = new Notifier(() -> { + var data = getData(); + persistentLogEntry.update(data[0]); + periodicLogEntry.append(data[1]); + aliasLogEntry.update(data[2]); + }); + notifier.setName("URCL"); + notifier.startPeriodic(period); + } + /** * Start capturing data from REV motor controllers to an external framework like * AdvantageKit. This * method should only be called once. - * + * * @return The log supplier, to be called periodically */ public static Supplier startExternal() { @@ -113,7 +167,7 @@ public static Supplier startExternal() { * AdvantageKit. This * method should only be called once. - * + * * @param aliases The set of aliases mapping CAN IDs to names. * @return The log supplier, to be called periodically */ @@ -121,9 +175,9 @@ public static Supplier startExternal(Map aliases) if (running) { DriverStation.reportError("URCL cannot be started multiple times", true); ByteBuffer[] emptyOutput = new ByteBuffer[] { - ByteBuffer.allocate(0), - ByteBuffer.allocate(0), - ByteBuffer.allocate(0) + ByteBuffer.allocate(0), + ByteBuffer.allocate(0), + ByteBuffer.allocate(0) }; return () -> emptyOutput; } @@ -167,9 +221,9 @@ private static ByteBuffer[] getData() { int persistentSize = persistentBuffer.getInt(0); int periodicSize = periodicBuffer.getInt(0); return new ByteBuffer[] { - persistentBuffer.slice(4, persistentSize), - periodicBuffer.slice(4, periodicSize), - aliasesBuffer + persistentBuffer.slice(4, persistentSize), + periodicBuffer.slice(4, periodicSize), + aliasesBuffer }; } } diff --git a/src/main/java/org/littletonrobotics/urcl/URCLJNI.java b/src/main/java/org/littletonrobotics/urcl/URCLJNI.java index 8ad75d4..fb3ed3d 100644 --- a/src/main/java/org/littletonrobotics/urcl/URCLJNI.java +++ b/src/main/java/org/littletonrobotics/urcl/URCLJNI.java @@ -8,6 +8,7 @@ package org.littletonrobotics.urcl; import java.io.IOException; +import java.lang.System; import java.nio.ByteBuffer; import java.util.concurrent.atomic.AtomicBoolean; @@ -18,7 +19,6 @@ */ public class URCLJNI { static boolean libraryLoaded = false; - static RuntimeLoader loader = null; /** * Helper class for determining whether or not to load the driver on static @@ -29,7 +29,7 @@ public static class Helper { /** * Get whether to load the driver on static init. - * + * * @return true if the driver will load on static init */ public static boolean getExtractOnStaticLoad() { @@ -38,7 +38,7 @@ public static boolean getExtractOnStaticLoad() { /** * Set whether to load the driver on static init. - * + * * @param load the new value */ public static void setExtractOnStaticLoad(boolean load) { @@ -49,9 +49,7 @@ public static void setExtractOnStaticLoad(boolean load) { static { if (Helper.getExtractOnStaticLoad()) { try { - loader = new RuntimeLoader<>("URCLDriver", RuntimeLoader.getDefaultExtractionRoot(), - URCLJNI.class); - loader.loadLibrary(); + RuntimeLoader.loadLibrary("URCLDriver"); } catch (IOException ex) { ex.printStackTrace(); System.exit(1); @@ -62,32 +60,30 @@ public static void setExtractOnStaticLoad(boolean load) { /** * Force load the library. - * + * * @throws java.io.IOException thrown if the native library cannot be found */ public static synchronized void forceLoad() throws IOException { if (libraryLoaded) { return; } - loader = new RuntimeLoader<>("URCLDriver", RuntimeLoader.getDefaultExtractionRoot(), - URCLJNI.class); - loader.loadLibrary(); + RuntimeLoader.loadLibrary("URCLDriver"); libraryLoaded = true; } /** Start logging. */ public static native void start(); - /** + /** * Get the shared buffer with persistent data. - * + * * @return The shared buffer */ public static native ByteBuffer getPersistentBuffer(); - /** + /** * Get the shared buffer with periodic data. - * + * * @return The shared buffer */ public static native ByteBuffer getPeriodicBuffer(); diff --git a/src/main/native/cpp/URCL.cpp b/src/main/native/cpp/URCL.cpp index 965f1bf..b176139 100644 --- a/src/main/native/cpp/URCL.cpp +++ b/src/main/native/cpp/URCL.cpp @@ -8,36 +8,32 @@ #include "URCL.h" #include "URCLDriver.h" +#include +#include #include #include +#include +#include #include #include -#include -#include -#include -#include #include #include -#include -#include +#include +#include +#include +#include static constexpr auto period = 20_ms; bool URCL::running = false; -char* URCL::persistentBuffer = nullptr; -char* URCL::periodicBuffer = nullptr; -nt::RawPublisher URCL::persistentPublisher = - nt::NetworkTableInstance::GetDefault() - .GetRawTopic("/URCL/Raw/Persistent") - .Publish("URCLr2_persistent"); -nt::RawPublisher URCL::periodicPublisher = - nt::NetworkTableInstance::GetDefault() - .GetRawTopic("/URCL/Raw/Periodic") - .Publish("URCLr2_periodic"); -nt::RawPublisher URCL::aliasesPublisher = - nt::NetworkTableInstance::GetDefault() - .GetRawTopic("/URCL/Raw/Aliases") - .Publish("URCLr2_aliases"); +char *URCL::persistentBuffer = nullptr; +char *URCL::periodicBuffer = nullptr; +nt::RawPublisher URCL::persistentPublisher; +nt::RawPublisher URCL::periodicPublisher; +nt::RawPublisher URCL::aliasesPublisher; +wpi::log::RawLogEntry URCL::persistentLogEntry; +wpi::log::RawLogEntry URCL::periodicLogEntry; +wpi::log::RawLogEntry URCL::aliasesLogEntry; frc::Notifier URCL::notifier{URCL::Periodic}; void URCL::Start() { @@ -47,7 +43,8 @@ void URCL::Start() { void URCL::Start(std::map aliases) { if (running) { - FRC_ReportError(frc::err::Error, "{}", "URCL cannot be started multiple times"); + FRC_ReportError(frc::err::Error, "{}", + "URCL cannot be started multiple times"); return; } @@ -55,7 +52,7 @@ void URCL::Start(std::map aliases) { std::ostringstream aliasesBuilder; aliasesBuilder << "{"; bool firstEntry = true; - for (auto const& [key, value] : aliases) { + for (auto const &[key, value] : aliases) { if (!firstEntry) { aliasesBuilder << ","; } @@ -69,29 +66,105 @@ void URCL::Start(std::map aliases) { aliasesBuilder << "}"; std::string aliasesString = aliasesBuilder.str(); std::vector aliasesVector(aliasesString.size()); - std::memcpy(aliasesVector.data(), aliasesString.c_str(), aliasesString.size()); + std::memcpy(aliasesVector.data(), aliasesString.c_str(), + aliasesString.size()); + + // Start driver + URCLDriver_start(); + persistentBuffer = URCLDriver_getPersistentBuffer(); + periodicBuffer = URCLDriver_getPeriodicBuffer(); + + // Start publishers + persistentPublisher = nt::NetworkTableInstance::GetDefault() + .GetRawTopic("/URCL/Raw/Persistent") + .Publish("URCLr2_persistent"); + periodicPublisher = nt::NetworkTableInstance::GetDefault() + .GetRawTopic("/URCL/Raw/Periodic") + .Publish("URCLr2_periodic"); + aliasesPublisher = nt::NetworkTableInstance::GetDefault() + .GetRawTopic("/URCL/Raw/Aliases") + .Publish("URCLr2_aliases"); + aliasesPublisher.Set(aliasesVector); + // Start notifier + notifier.SetName("URCL"); + notifier.StartPeriodic(period); +} + +void URCL::Start(wpi::log::DataLog &log) { + std::map aliases; + URCL::Start(aliases, log); +} + +void URCL::Start(std::map aliases, + wpi::log::DataLog &log) { + if (running) { + FRC_ReportError(frc::err::Error, "{}", + "URCL cannot be started multiple times"); + return; + } + + // Publish aliases + std::ostringstream aliasesBuilder; + aliasesBuilder << "{"; + bool firstEntry = true; + for (auto const &[key, value] : aliases) { + if (!firstEntry) { + aliasesBuilder << ","; + } + firstEntry = false; + aliasesBuilder << "\""; + aliasesBuilder << key; + aliasesBuilder << "\":\""; + aliasesBuilder << value; + aliasesBuilder << "\""; + } + aliasesBuilder << "}"; + std::string aliasesString = aliasesBuilder.str(); + std::vector aliasesVector(aliasesString.size()); + std::memcpy(aliasesVector.data(), aliasesString.c_str(), + aliasesString.size()); + // Start driver URCLDriver_start(); persistentBuffer = URCLDriver_getPersistentBuffer(); periodicBuffer = URCLDriver_getPeriodicBuffer(); + persistentLogEntry = wpi::log::RawLogEntry{log, "/URCL/Raw/Persistent", "", + "URCLr2_persistent"}; + periodicLogEntry = + wpi::log::RawLogEntry{log, "/URCL/Raw/Periodic", "", "URCLr2_periodic"}; + aliasesLogEntry = + wpi::log::RawLogEntry{log, "/URCL/Raw/Aliases", "", "URCLr2_aliases"}; + + aliasesLogEntry.Append(aliasesVector); + // Start notifier notifier.SetName("URCL"); notifier.StartPeriodic(period); } void URCL::Periodic() { - URCLDriver_read(); - uint32_t persistentSize; - uint32_t periodicSize; - std::memcpy(&persistentSize, persistentBuffer, 4); - std::memcpy(&periodicSize, periodicBuffer, 4); - std::vector persistentVector(persistentSize); - std::vector periodicVector(periodicSize); - std::memcpy(persistentVector.data(), persistentBuffer + 4, persistentVector.size()); - std::memcpy(periodicVector.data(), periodicBuffer + 4, periodicVector.size()); - persistentPublisher.Set(persistentVector); - periodicPublisher.Set(periodicVector); -} \ No newline at end of file + URCLDriver_read(); + uint32_t persistentSize; + uint32_t periodicSize; + std::memcpy(&persistentSize, persistentBuffer, 4); + std::memcpy(&periodicSize, periodicBuffer, 4); + std::vector persistentVector(persistentSize); + std::vector periodicVector(periodicSize); + std::memcpy(persistentVector.data(), persistentBuffer + 4, + persistentVector.size()); + std::memcpy(periodicVector.data(), periodicBuffer + 4, + periodicVector.size()); + + if (persistentPublisher && periodicPublisher) { + persistentPublisher.Set(persistentVector); + periodicPublisher.Set(periodicVector); + } + + if (persistentLogEntry && periodicLogEntry) { + persistentLogEntry.Update(persistentVector); + periodicLogEntry.Update(periodicVector); + } +} diff --git a/src/main/native/include/URCL.h b/src/main/native/include/URCL.h index c514451..0d949cd 100644 --- a/src/main/native/include/URCL.h +++ b/src/main/native/include/URCL.h @@ -7,17 +7,19 @@ #pragma once +#include "frc/DataLogManager.h" +#include "wpi/DataLog.h" #include #include /** * URCL (Unofficial REV-Compatible Logger) - * + * * This unofficial logger enables automatic capture of CAN traffic from REV * motor controllers to NetworkTables, viewable using AdvantageScope. See the * corresponding AdvantageScope documentation for more details: * https://github.com/Mechanical-Advantage/AdvantageScope/blob/main/docs/REV-LOGGING.md - * + * * As this library is not an official REV tool, support queries should be * directed to the URCL issues page or software@team6328.org * rather than REV's support contact. @@ -32,14 +34,31 @@ class URCL final { */ static void Start(); + /** + * Start capturing data from REV motor controllers to a DataLog. This method + * should only be called once. + * + * @param log The DataLog object to log to. + */ + static void Start(wpi::log::DataLog& log); + /** * Start capturing data from REV motor controllers to NetworkTables. This method * should only be called once. - * + * * @param aliases The set of aliases mapping CAN IDs to names. */ static void Start(std::map aliases); + /** + * Start capturing data from REV motor controllers to a DataLog. This method + * should only be called once. + * + * @param aliases The set of aliases mapping CAN IDs to names. + * @param withNT Whether or not to run with NetworkTables. + */ + static void Start(std::map aliases, wpi::log::DataLog& log); + private: static void Periodic(); @@ -49,5 +68,8 @@ class URCL final { static nt::RawPublisher persistentPublisher; static nt::RawPublisher periodicPublisher; static nt::RawPublisher aliasesPublisher; + static wpi::log::RawLogEntry persistentLogEntry; + static wpi::log::RawLogEntry periodicLogEntry; + static wpi::log::RawLogEntry aliasesLogEntry; static frc::Notifier notifier; -}; \ No newline at end of file +};