Skip to content

Commit

Permalink
Update logging format
Browse files Browse the repository at this point in the history
Separate keys for persistent and periodic
Add device alias map
  • Loading branch information
jwbonner committed Feb 19, 2024
1 parent 0d1b419 commit d5e8995
Show file tree
Hide file tree
Showing 9 changed files with 249 additions and 60 deletions.
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,24 @@ By default, this project builds against the latest WPILib development build. To

## Data Format

### Revision 2

- "Raw/Persistent"
- Buffer Length, Not Published (uint32)
- Persistent Messages (8 bytes each)
- Short Message ID (uint16)
- Data (6 bytes)
- "Raw/Periodic"
- Buffer Length, Not Published (uint32)
- Periodic Messages (14 bytes each)
- Timestamp MS (uint32)
- Short Message ID (uint16)
- Data (8 bytes)
- "Raw/Aliases"
- JSON object of (CAN ID string -> alias string)

### Revision 1

- Buffer Size, Not Published (uint32)
- Persistent Message Count (uint32)
- Periodic Message Count (uint32)
Expand Down
40 changes: 23 additions & 17 deletions src/main/driver/cpp/URCLDriver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ constexpr int periodicMessageId =
constexpr int periodicMessageIdMask = 0x1ffffc00;

bool running = false;
char* sharedBuffer;
char* persistentBuffer;
char* periodicBuffer;
int32_t halStatus;
uint32_t timeOffsetMillis;
uint32_t firmwareStreamHandle;
Expand All @@ -57,16 +58,24 @@ uint64_t devicesModelReceived = 0;
uint64_t devicesCANReady = 0;
HAL_CANHandle devicesCANHandles[64];

char* URCLDriver_start() {
if (running) return sharedBuffer;
void URCLDriver_start() {
if (running) return;
running = true;
sharedBuffer = (char*) malloc(bufferSize);
persistentBuffer = (char*) malloc(persistentSize);
periodicBuffer = (char*) malloc(periodicSize);
uint32_t fpgaMillis = (HAL_GetFPGATime(&halStatus) / 1000ull) & 0xffffffff;
timeOffsetMillis = fpgaMillis - HAL_GetCANPacketBaseTime();
HAL_CAN_OpenStreamSession(&firmwareStreamHandle, firmwareMessageId, firmwareMessageIdMask, maxPersistentMessages, &halStatus);
HAL_CAN_OpenStreamSession(&modelStreamHandle, modelMessageId, modelMessageIdMask, maxPersistentMessages, &halStatus);
HAL_CAN_OpenStreamSession(&periodicStreamHandle, periodicMessageId, periodicMessageIdMask, maxPeriodicMessages, &halStatus);
return sharedBuffer;
}

char* URCLDriver_getPersistentBuffer() {
return persistentBuffer;
}

char* URCLDriver_getPeriodicBuffer() {
return periodicBuffer;
}

/**
Expand All @@ -76,8 +85,9 @@ void writeMessagePersistent(HAL_CANStreamMessage message) {
uint16_t messageIdShort = message.messageID & 0xffff;
uint32_t index = 0;
while (index < persistentMessageCount) {
char* messageBuffer = sharedBuffer + startSize + (index * persistentMessageSize);
uint16_t existingMessageId = *messageBuffer;
char* messageBuffer = persistentBuffer + 4 + (index * persistentMessageSize);
uint16_t existingMessageId;
std::memcpy(&existingMessageId, messageBuffer, 2);
if (messageIdShort == existingMessageId) {
std::memcpy(messageBuffer + 2, &message.data, 6);
return;
Expand All @@ -87,7 +97,7 @@ void writeMessagePersistent(HAL_CANStreamMessage message) {

if (persistentMessageCount < maxPersistentMessages) {
persistentMessageCount++;
char* messageBuffer = sharedBuffer + startSize + (index * persistentMessageSize);
char* messageBuffer = persistentBuffer + 4 + (index * persistentMessageSize);
std::memcpy(messageBuffer, &messageIdShort, 2);
std::memcpy(messageBuffer + 2, &message.data, 6);
}
Expand All @@ -97,9 +107,7 @@ void writeMessagePersistent(HAL_CANStreamMessage message) {
* Write a periodic message to the buffer at the specified index.
*/
void writeMessagePeriodic(HAL_CANStreamMessage message, uint32_t index) {
char* messageBuffer = sharedBuffer + startSize +
(persistentMessageCount * persistentMessageSize) +
(index * periodicMessageSize);
char* messageBuffer = periodicBuffer + 4 + (index * periodicMessageSize);
uint32_t timestamp = message.timeStamp + timeOffsetMillis;
uint16_t messageIdShort = message.messageID & 0xffff;
std::memcpy(messageBuffer + 0, &timestamp, 4);
Expand Down Expand Up @@ -166,12 +174,10 @@ void URCLDriver_read() {
}

// Write sizes
uint32_t bufferSize = startSize - 4 +
(persistentMessageCount * persistentMessageSize) +
(messageCount * periodicMessageSize);
std::memcpy(sharedBuffer, &bufferSize, 4);
std::memcpy(sharedBuffer + 4, &persistentMessageCount, 4);
std::memcpy(sharedBuffer + 8, &messageCount, 4);
uint32_t persistentSize = persistentMessageCount * persistentMessageSize;
uint32_t periodicSize = messageCount * periodicMessageSize;
std::memcpy(persistentBuffer, &persistentSize, 4);
std::memcpy(periodicBuffer, &periodicSize, 4);
}

}
14 changes: 12 additions & 2 deletions src/main/driver/cpp/URCLJNI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,19 @@ JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEXPORT void JNICALL
JNI_OnUnload(JavaVM* vm, void* reserved) {}

JNIEXPORT jobject JNICALL
JNIEXPORT void JNICALL
Java_org_littletonrobotics_urcl_URCLJNI_start(JNIEnv* env, jclass clazz) {
return env->NewDirectByteBuffer(URCLDriver_start(), bufferSize);
URCLDriver_start();
}

JNIEXPORT jobject JNICALL
Java_org_littletonrobotics_urcl_URCLJNI_getPersistentBuffer(JNIEnv* env, jclass clazz) {
return env->NewDirectByteBuffer(URCLDriver_getPersistentBuffer(), persistentSize);
}

JNIEXPORT jobject JNICALL
Java_org_littletonrobotics_urcl_URCLJNI_getPeriodicBuffer(JNIEnv* env, jclass clazz) {
return env->NewDirectByteBuffer(URCLDriver_getPeriodicBuffer(), periodicSize);
}

JNIEXPORT void JNICALL
Expand Down
13 changes: 7 additions & 6 deletions src/main/driver/include/URCLDriver.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,18 @@
extern "C" {
#endif

constexpr int startSize = 4 + 4 + 4;
constexpr int persistentMessageSize = 8;
constexpr int periodicMessageSize = 14;
constexpr int maxPersistentMessages = 200;
constexpr int maxPeriodicMessages = 500;
constexpr int bufferSize =
startSize +
(persistentMessageSize * maxPersistentMessages) +
(periodicMessageSize * maxPeriodicMessages);
constexpr int persistentSize = 4 + (persistentMessageSize * maxPersistentMessages);
constexpr int periodicSize = 4 + (periodicMessageSize * maxPeriodicMessages);

char* URCLDriver_start();
void URCLDriver_start();

char* URCLDriver_getPersistentBuffer();

char* URCLDriver_getPeriodicBuffer();

void URCLDriver_read();

Expand Down
4 changes: 4 additions & 0 deletions src/main/driver/symbols.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
JNI_OnLoad
JNI_OnUnload
URCLDriver_start
URCLDriver_getPersistentBuffer
URCLDriver_getPeriodicBuffer
URCLDriver_read
Java_org_littletonrobotics_urcl_URCLJNI_start
Java_org_littletonrobotics_urcl_URCLJNI_getPersistentBuffer
Java_org_littletonrobotics_urcl_URCLJNI_getPeriodicBuffer
Java_org_littletonrobotics_urcl_URCLJNI_read
118 changes: 101 additions & 17 deletions src/main/java/org/littletonrobotics/urcl/URCL.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.Charset;
import java.util.Map;
import java.util.function.Supplier;

import edu.wpi.first.networktables.NetworkTableInstance;
Expand All @@ -35,28 +37,61 @@ public class URCL {
private static final double period = 0.02;

private static boolean running = false;
private static ByteBuffer buffer;
private static RawPublisher publisher;
private static ByteBuffer persistentBuffer;
private static ByteBuffer periodicBuffer;
private static ByteBuffer aliasesBuffer;
private static RawPublisher persistentPublisher;
private static RawPublisher periodicPublisher;
private static RawPublisher aliasesPublisher;
private static Notifier notifier;

/**
* Start capturing data from REV motor controllers to NetworkTables. This method
* should only be called once.
*/
public static void start() {
start(Map.of());
}

/**
* 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<Integer, String> aliases) {
if (running) {
DriverStation.reportError("URCL cannot be started multiple times", true);
return;
}
running = true;

buffer = URCLJNI.start();
buffer.order(ByteOrder.LITTLE_ENDIAN);
publisher = NetworkTableInstance.getDefault()
.getRawTopic("/URCL")
.publish("URCL");
// 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);

notifier = new Notifier(() -> publisher.set(getData()));
// Start publishers
persistentPublisher = NetworkTableInstance.getDefault()
.getRawTopic("/URCL/Raw/Persistent")
.publish("URCLr2_persistent");
periodicPublisher = NetworkTableInstance.getDefault()
.getRawTopic("/URCL/Raw/Periodic")
.publish("URCLr2_periodic");
aliasesPublisher = NetworkTableInstance.getDefault()
.getRawTopic("/URCL/Raw/Aliases")
.publish("URCLr2_aliases");
notifier = new Notifier(() -> {
var data = getData();
persistentPublisher.set(data[0]);
periodicPublisher.set(data[1]);
aliasesPublisher.set(data[2]);
});
notifier.setName("URCL");
notifier.startPeriodic(period);
}
Expand All @@ -69,23 +104,72 @@ public static void start() {
*
* @return The log supplier, to be called periodically
*/
public static Supplier<ByteBuffer> startExternal() {
public static Supplier<ByteBuffer[]> startExternal() {
return startExternal(Map.of());
}

/**
* Start capturing data from REV motor controllers to an external framework like
* <a href=
* "https://github.com/Mechanical-Advantage/AdvantageKit">AdvantageKit</a>. 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
*/
public static Supplier<ByteBuffer[]> startExternal(Map<Integer, String> aliases) {
if (running) {
DriverStation.reportError("URCL cannot be started multiple times", true);
ByteBuffer dummyBuffer = ByteBuffer.allocate(0);
return () -> dummyBuffer;
ByteBuffer[] emptyOutput = new ByteBuffer[] {
ByteBuffer.allocate(0),
ByteBuffer.allocate(0),
ByteBuffer.allocate(0)
};
return () -> emptyOutput;
}
running = true;

buffer = URCLJNI.start();
buffer.order(ByteOrder.LITTLE_ENDIAN);
// 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);
return URCL::getData;
}

/** Refreshes and reads all data from the queue. */
private static ByteBuffer getData() {
/** Fills the alias data into the aliases buffer as JSON. */
private static void updateAliasesBuffer(Map<Integer, String> aliases) {
StringBuilder aliasesBuilder = new StringBuilder();
aliasesBuilder.append("{");
boolean firstEntry = true;
for (Map.Entry<Integer, String> entry : aliases.entrySet()) {
if (!firstEntry) {
aliasesBuilder.append(",");
}
firstEntry = false;
aliasesBuilder.append("\"");
aliasesBuilder.append(entry.getKey().toString());
aliasesBuilder.append("\":\"");
aliasesBuilder.append(entry.getValue());
aliasesBuilder.append("\"");
}
aliasesBuilder.append("}");
aliasesBuffer = Charset.forName("UTF-8").encode(aliasesBuilder.toString());
}

/** Refreshes and reads all data from the queues. */
private static ByteBuffer[] getData() {
URCLJNI.read();
int size = buffer.getInt(0);
return buffer.slice(4, size);
int persistentSize = persistentBuffer.getInt(0);
int periodicSize = periodicBuffer.getInt(0);
return new ByteBuffer[] {
persistentBuffer.slice(4, persistentSize),
periodicBuffer.slice(4, periodicSize),
aliasesBuffer
};
}
}
20 changes: 14 additions & 6 deletions src/main/java/org/littletonrobotics/urcl/URCLJNI.java
Original file line number Diff line number Diff line change
Expand Up @@ -75,15 +75,23 @@ public static synchronized void forceLoad() throws IOException {
libraryLoaded = true;
}

/**
* Start logging data from all devices.
/** Start logging. */
public static native void start();

/**
* Get the shared buffer with persistent data.
*
* @return The shared memory buffer for transferring data
* @return The shared buffer
*/
public static native ByteBuffer start();
public static native ByteBuffer getPersistentBuffer();

/**
* Reads all data from the queue to the shared memory buffer.
/**
* Get the shared buffer with periodic data.
*
* @return The shared buffer
*/
public static native ByteBuffer getPeriodicBuffer();

/** Read new data. */
public static native void read();
}
Loading

0 comments on commit d5e8995

Please sign in to comment.