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
+};