From b300518bd12a23c00969c6b16699605c181ca8c1 Mon Sep 17 00:00:00 2001 From: William Toth Date: Tue, 6 Dec 2022 23:58:09 -0600 Subject: [PATCH] [hal] Add CAN Stream API to Java through JNI bindings (#4193) The CAN Stream API allows defining an buffer to receive an arbitrary set of CAN messages, based on an ID and a mask. Messages are added to this queue separate of other CAN APIs. This means the messages can be receive without impacting other APIs such as vendor APIs. This enables things like detection of what devices are on the bus, or custom decoding, without using vendor APIs. Co-authored-by: Thad House --- .../edu/wpi/first/hal/CANStreamMessage.java | 35 ++++++++ .../java/edu/wpi/first/hal/can/CANJNI.java | 8 ++ hal/src/main/native/cpp/jni/CANJNI.cpp | 86 +++++++++++++++++++ hal/src/main/native/cpp/jni/HALUtil.cpp | 14 +++ hal/src/main/native/cpp/jni/HALUtil.h | 4 + 5 files changed, 147 insertions(+) create mode 100644 hal/src/main/java/edu/wpi/first/hal/CANStreamMessage.java diff --git a/hal/src/main/java/edu/wpi/first/hal/CANStreamMessage.java b/hal/src/main/java/edu/wpi/first/hal/CANStreamMessage.java new file mode 100644 index 0000000000..bdb2112854 --- /dev/null +++ b/hal/src/main/java/edu/wpi/first/hal/CANStreamMessage.java @@ -0,0 +1,35 @@ +// 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. + +package edu.wpi.first.hal; + +public class CANStreamMessage { + @SuppressWarnings("MemberName") + public final byte[] data = new byte[8]; + + @SuppressWarnings("MemberName") + public int length; + + @SuppressWarnings("MemberName") + public long timestamp; + + @SuppressWarnings("MemberName") + public int messageID; + + /** + * API used from JNI to set the data. + * + * @param length Length of packet in bytes. + * @param messageID CAN message ID of the message. + * @param timestamp CAN frame timestamp in microseconds. + * @return Buffer containing CAN frame. + */ + @SuppressWarnings("PMD.MethodReturnsInternalArray") + public byte[] setStreamData(int length, int messageID, long timestamp) { + this.messageID = messageID; + this.length = length; + this.timestamp = timestamp; + return data; + } +} diff --git a/hal/src/main/java/edu/wpi/first/hal/can/CANJNI.java b/hal/src/main/java/edu/wpi/first/hal/can/CANJNI.java index 6a498a6faf..e0734dd369 100644 --- a/hal/src/main/java/edu/wpi/first/hal/can/CANJNI.java +++ b/hal/src/main/java/edu/wpi/first/hal/can/CANJNI.java @@ -4,6 +4,7 @@ package edu.wpi.first.hal.can; +import edu.wpi.first.hal.CANStreamMessage; import edu.wpi.first.hal.JNIWrapper; import java.nio.ByteBuffer; import java.nio.IntBuffer; @@ -24,4 +25,11 @@ public class CANJNI extends JNIWrapper { IntBuffer messageID, int messageIDMask, ByteBuffer timeStamp); public static native void getCANStatus(CANStatus status); + + public static native int openCANStreamSession(int messageID, int messageIDMask, int maxMessages); + + public static native void closeCANStreamSession(int sessionHandle); + + public static native int readCANStreamSession( + int sessionHandle, CANStreamMessage[] messages, int messagesToRead); } diff --git a/hal/src/main/native/cpp/jni/CANJNI.cpp b/hal/src/main/native/cpp/jni/CANJNI.cpp index d69786ed63..4ad2a74e04 100644 --- a/hal/src/main/native/cpp/jni/CANJNI.cpp +++ b/hal/src/main/native/cpp/jni/CANJNI.cpp @@ -91,4 +91,90 @@ Java_edu_wpi_first_hal_can_CANJNI_getCANStatus txFullCount, receiveErrorCount, transmitErrorCount); } +/* + * Class: edu_wpi_first_hal_can_CANJNI + * Method: openCANStreamSession + * Signature: (III)I + */ +JNIEXPORT jint JNICALL +Java_edu_wpi_first_hal_can_CANJNI_openCANStreamSession + (JNIEnv* env, jclass, jint messageID, jint messageIDMask, jint maxMessages) +{ + uint32_t handle = 0; + int32_t status = 0; + HAL_CAN_OpenStreamSession(&handle, static_cast(messageID), + static_cast(messageIDMask), + static_cast(maxMessages), &status); + + if (!CheckStatus(env, status)) { + return static_cast(0); + } + + return static_cast(handle); +} + +/* + * Class: edu_wpi_first_hal_can_CANJNI + * Method: closeCANStreamSession + * Signature: (I)V + */ +JNIEXPORT void JNICALL +Java_edu_wpi_first_hal_can_CANJNI_closeCANStreamSession + (JNIEnv* env, jclass, jint sessionHandle) +{ + HAL_CAN_CloseStreamSession(static_cast(sessionHandle)); +} + +/* + * Class: edu_wpi_first_hal_can_CANJNI + * Method: readCANStreamSession + * Signature: (I[Ljava/lang/Object;I)I + */ +JNIEXPORT jint JNICALL +Java_edu_wpi_first_hal_can_CANJNI_readCANStreamSession + (JNIEnv* env, jclass, jint sessionHandle, jobjectArray messages, + jint messagesToRead) +{ + uint32_t handle = static_cast(sessionHandle); + uint32_t messagesRead = 0; + + wpi::SmallVector messageBuffer; + messageBuffer.resize_for_overwrite(messagesToRead); + + int32_t status = 0; + + HAL_CAN_ReadStreamSession(handle, messageBuffer.begin(), + static_cast(messagesToRead), + &messagesRead, &status); + + if (status == HAL_ERR_CANSessionMux_MessageNotFound || messagesRead == 0) { + return 0; + } + + if (!CheckStatus(env, status)) { + return 0; + } + + for (int i = 0; i < static_cast(messagesRead); i++) { + struct HAL_CANStreamMessage* msg = &messageBuffer[i]; + JLocal elem{ + env, static_cast(env->GetObjectArrayElement(messages, i))}; + if (!elem) { + // TODO decide if should throw + continue; + } + JLocal toSetArray{ + env, SetCANStreamObject(env, elem, msg->dataSize, msg->messageID, + msg->timeStamp)}; + auto javaLen = env->GetArrayLength(toSetArray); + if (javaLen < msg->dataSize) { + msg->dataSize = javaLen; + } + env->SetByteArrayRegion(toSetArray, 0, msg->dataSize, + reinterpret_cast(msg->data)); + } + + return static_cast(messagesRead); +} + } // extern "C" diff --git a/hal/src/main/native/cpp/jni/HALUtil.cpp b/hal/src/main/native/cpp/jni/HALUtil.cpp index 5d5a9588b4..a2f080b872 100644 --- a/hal/src/main/native/cpp/jni/HALUtil.cpp +++ b/hal/src/main/native/cpp/jni/HALUtil.cpp @@ -52,6 +52,7 @@ static JClass canStatusCls; static JClass matchInfoDataCls; static JClass accumulatorResultCls; static JClass canDataCls; +static JClass canStreamMessageCls; static JClass halValueCls; static JClass baseStoreCls; static JClass revPHVersionCls; @@ -64,6 +65,7 @@ static const JClassInit classes[] = { {"edu/wpi/first/hal/MatchInfoData", &matchInfoDataCls}, {"edu/wpi/first/hal/AccumulatorResult", &accumulatorResultCls}, {"edu/wpi/first/hal/CANData", &canDataCls}, + {"edu/wpi/first/hal/CANStreamMessage", &canStreamMessageCls}, {"edu/wpi/first/hal/HALValue", &halValueCls}, {"edu/wpi/first/hal/DMAJNISample$BaseStore", &baseStoreCls}, {"edu/wpi/first/hal/REVPHVersion", &revPHVersionCls}}; @@ -303,6 +305,18 @@ jbyteArray SetCANDataObject(JNIEnv* env, jobject canData, int32_t length, return retVal; } +jbyteArray SetCANStreamObject(JNIEnv* env, jobject canStreamData, + int32_t length, uint32_t messageID, + uint64_t timestamp) { + static jmethodID func = + env->GetMethodID(canStreamMessageCls, "setStreamData", "(IIJ)[B"); + + jbyteArray retVal = static_cast(env->CallObjectMethod( + canStreamData, func, static_cast(length), + static_cast(messageID), static_cast(timestamp))); + return retVal; +} + jobject CreateHALValue(JNIEnv* env, const HAL_Value& value) { static jmethodID fromNative = env->GetStaticMethodID( halValueCls, "fromNative", "(IJD)Ledu/wpi/first/hal/HALValue;"); diff --git a/hal/src/main/native/cpp/jni/HALUtil.h b/hal/src/main/native/cpp/jni/HALUtil.h index cf3956c7f3..9c9487cd30 100644 --- a/hal/src/main/native/cpp/jni/HALUtil.h +++ b/hal/src/main/native/cpp/jni/HALUtil.h @@ -78,6 +78,10 @@ void SetAccumulatorResultObject(JNIEnv* env, jobject accumulatorResult, jbyteArray SetCANDataObject(JNIEnv* env, jobject canData, int32_t length, uint64_t timestamp); +jbyteArray SetCANStreamObject(JNIEnv* env, jobject canStreamData, + int32_t length, uint32_t messageID, + uint64_t timestamp); + jobject CreateHALValue(JNIEnv* env, const HAL_Value& value); jobject CreateDMABaseStore(JNIEnv* env, jint valueType, jint index);