diff --git a/examples/java/example/apps/CustomTransport.java b/examples/java/example/apps/CustomTransport.java new file mode 100644 index 00000000..5f54dc21 --- /dev/null +++ b/examples/java/example/apps/CustomTransport.java @@ -0,0 +1,51 @@ +package example.apps; + +import zcm.zcm.*; +import javazcm.types.example_t; +import java.io.IOException; + +public class CustomTransport implements ZCMSubscriber +{ + public void messageReceived(ZCM zcm, String channel, ZCMDataInputStream ins) + { + try { + example_t msg = new example_t(ins); + System.out.println("msg timestamp: " + msg.timestamp); + } catch (IOException ex) { + System.out.println("Exception: " + ex); + } + } + + public static void main(String[] args) throws IOException + { + CustomTransport t = new CustomTransport(); + + // Create a loopback SerialIO implementation for testing + LoopbackSerialIO serialIO = new LoopbackSerialIO(); + + // Create the ZCM transport using the SerialIO + ZCMTransport transport = new ZCMGenericSerialTransport(serialIO, 1 << 14, 1 << 17); + ZCM zcm = new ZCM(transport); + + String channel = "EXAMPLE"; + + zcm.subscribe(channel, t); + zcm.start(); + + example_t msg = new example_t(); + msg.position = new double[] { 1, 2, 3 }; + msg.orientation = new double[] { 1, 0, 0, 0 }; + msg.ranges = new short[] { + 0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 + }; + msg.num_ranges = msg.ranges.length; + msg.name = "example string"; + msg.enabled = true; + + while (true) { + msg.timestamp = System.currentTimeMillis() * 1000; + zcm.publish(channel, msg); + try { Thread.sleep(100); } catch (InterruptedException ex) {} + } + } +} diff --git a/examples/java/example/apps/LoopbackSerialIO.java b/examples/java/example/apps/LoopbackSerialIO.java new file mode 100644 index 00000000..2666eba9 --- /dev/null +++ b/examples/java/example/apps/LoopbackSerialIO.java @@ -0,0 +1,159 @@ +package example.apps; + +import zcm.zcm.SerialIO; +import java.nio.ByteBuffer; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +/** + * A loopback SerialIO implementation for testing purposes. + * This implementation uses internal queues to simulate serial communication, + * where data written to the "serial port" can be read back from it. + * This is useful for testing ZCM serial transport without actual hardware. + */ +public class LoopbackSerialIO implements SerialIO { + + private final BlockingQueue dataQueue; + private final int maxQueueSize; + + /** + * Create a new loopback SerialIO with default queue size. + */ + public LoopbackSerialIO() { + this(10000); // Default queue size of 10KB + } + + /** + * Create a new loopback SerialIO with specified queue size. + * + * @param maxQueueSize maximum number of bytes to buffer internally + */ + public LoopbackSerialIO(int maxQueueSize) { + this.maxQueueSize = maxQueueSize; + this.dataQueue = new LinkedBlockingQueue<>(maxQueueSize); + } + + /** + * Read data from the internal queue (simulating serial read). + * + * @param buffer ByteBuffer to store the read data + * @param maxLen maximum number of bytes to read + * @param timeoutMs timeout in milliseconds, 0 for non-blocking + * @return number of bytes actually read + */ + @Override + public int get(ByteBuffer buffer, int maxLen, int timeoutMs) { + if (buffer == null || maxLen <= 0) { + return 0; + } + + int bytesRead = 0; + + if (timeoutMs == 0) { + // Non-blocking mode - drain available bytes directly into buffer + while (bytesRead < maxLen && !dataQueue.isEmpty()) { + Byte b = dataQueue.poll(); + if (b != null) { + buffer.put(b); + bytesRead++; + } + } + } else { + // Blocking mode - wait for at least one byte, then drain available + try { + Byte firstByte = dataQueue.poll(timeoutMs, TimeUnit.MILLISECONDS); + if (firstByte != null) { + buffer.put(firstByte); + bytesRead = 1; + // Drain any additional available bytes (non-blocking) + while (bytesRead < maxLen && !dataQueue.isEmpty()) { + Byte b = dataQueue.poll(); + if (b != null) { + buffer.put(b); + bytesRead++; + } + } + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return 0; + } + } + + return bytesRead; + } + + /** + * Write data to the internal queue (simulating serial write). + * + * @param buffer ByteBuffer containing data to write + * @param len number of bytes to write from the buffer + * @param timeoutMs timeout in milliseconds, 0 for non-blocking + * @return number of bytes actually written + */ + @Override + public int put(ByteBuffer buffer, int len, int timeoutMs) { + if (buffer == null || len <= 0) { + return 0; + } + + int bytesWritten = 0; + int bytesToWrite = Math.min(len, buffer.remaining()); + + for (int i = 0; i < bytesToWrite; i++) { + byte b = buffer.get(); + boolean success = false; + + if (timeoutMs == 0) { + // Non-blocking mode + success = dataQueue.offer(b); + } else { + // Blocking mode with timeout + try { + success = dataQueue.offer(b, timeoutMs, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + // Put the byte back to the buffer since it wasn't written + buffer.position(buffer.position() - 1); + break; + } + } + + if (success) { + bytesWritten++; + } else { + // Queue is full or timeout, put the byte back and break + buffer.position(buffer.position() - 1); + break; + } + } + + return bytesWritten; + } + + /** + * Get the current number of bytes available for reading. + * + * @return number of bytes in the internal queue + */ + public int available() { + return dataQueue.size(); + } + + /** + * Clear all data from the internal queue. + */ + public void clear() { + dataQueue.clear(); + } + + /** + * Get the maximum queue size. + * + * @return maximum number of bytes this SerialIO can buffer + */ + public int getMaxQueueSize() { + return maxQueueSize; + } +} diff --git a/examples/java/example/apps/SerialIOTest.java b/examples/java/example/apps/SerialIOTest.java new file mode 100644 index 00000000..f403d832 --- /dev/null +++ b/examples/java/example/apps/SerialIOTest.java @@ -0,0 +1,96 @@ +package example.apps; + +import zcm.zcm.SerialIO; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; + +/** + * A simple test runner to demonstrate and validate SerialIO implementations + * without requiring the full ZCM infrastructure. + */ +public class SerialIOTest { + + public static void main(String[] args) { + System.out.println("ZCM SerialIO Test Suite"); + System.out.println("======================="); + + // Test LoopbackSerialIO + testLoopbackSerialIO(); + + System.out.println("\nAll tests completed!"); + } + + /** + * Test the LoopbackSerialIO implementation + */ + private static void testLoopbackSerialIO() { + System.out.println("\n--- Testing LoopbackSerialIO ---"); + + LoopbackSerialIO serialIO = new LoopbackSerialIO(1000); + + // Test data + String testMessage = "Hello ZCM Serial!"; + byte[] testData = testMessage.getBytes(StandardCharsets.UTF_8); + + // Test writing + ByteBuffer writeBuffer = ByteBuffer.allocateDirect(testData.length); + writeBuffer.put(testData); + writeBuffer.flip(); + + int bytesWritten = serialIO.put(writeBuffer, testData.length, 0); + System.out.println("Wrote " + bytesWritten + " bytes: \"" + testMessage + "\""); + + // Test reading + ByteBuffer readBuffer = ByteBuffer.allocateDirect(1000); + int bytesRead = serialIO.get(readBuffer, 1000, 0); + + if (bytesRead > 0) { + readBuffer.flip(); + byte[] readData = new byte[bytesRead]; + readBuffer.get(readData); + String receivedMessage = new String(readData, StandardCharsets.UTF_8); + System.out.println("Read " + bytesRead + " bytes: \"" + receivedMessage + "\""); + + if (testMessage.equals(receivedMessage)) { + System.out.println("LoopbackSerialIO test PASSED"); + } else { + System.out.println("LoopbackSerialIO test FAILED - data mismatch"); + } + } else { + System.out.println("LoopbackSerialIO test FAILED - no data read"); + } + + // Test queue status + System.out.println("Available bytes: " + serialIO.available()); + System.out.println("Max queue size: " + serialIO.getMaxQueueSize()); + } + + /** + * Utility method to demonstrate ByteBuffer operations + */ + private static void demonstrateByteBufferUsage() { + System.out.println("\n--- ByteBuffer Usage Demo ---"); + + // Create a direct ByteBuffer (recommended for SerialIO) + ByteBuffer buffer = ByteBuffer.allocateDirect(100); + System.out.println("Created direct buffer with capacity: " + buffer.capacity()); + + // Write some data + String data = "Sample data"; + buffer.put(data.getBytes(StandardCharsets.UTF_8)); + System.out.println("After writing, position: " + buffer.position() + ", remaining: " + buffer.remaining()); + + // Flip buffer for reading + buffer.flip(); + System.out.println("After flip, position: " + buffer.position() + ", limit: " + buffer.limit()); + + // Read the data back + byte[] readData = new byte[buffer.remaining()]; + buffer.get(readData); + System.out.println("Read back: \"" + new String(readData, StandardCharsets.UTF_8) + "\""); + + // Clear buffer for reuse + buffer.clear(); + System.out.println("After clear, position: " + buffer.position() + ", limit: " + buffer.limit()); + } +} diff --git a/examples/java/example/apps/SerialTransportExample.java b/examples/java/example/apps/SerialTransportExample.java new file mode 100644 index 00000000..470bd3ef --- /dev/null +++ b/examples/java/example/apps/SerialTransportExample.java @@ -0,0 +1,123 @@ +package example.apps; + +import zcm.zcm.*; +import javazcm.types.example_t; +import java.io.IOException; + +/** + * A comprehensive example demonstrating ZCM communication using a custom serial transport. + * This example shows both publishing and subscribing using a loopback SerialIO implementation, + * which allows testing serial transport functionality without actual hardware. + */ +public class SerialTransportExample { + + private static class ExampleSubscriber implements ZCMSubscriber { + @Override + public void messageReceived(ZCM zcm, String channel, ZCMDataInputStream ins) { + try { + example_t msg = new example_t(ins); + System.out.println("Received message on channel '" + channel + "':"); + System.out.println(" timestamp: " + msg.timestamp); + System.out.println(" position: [" + msg.position[0] + ", " + msg.position[1] + ", " + msg.position[2] + "]"); + System.out.println(" orientation: [" + msg.orientation[0] + ", " + msg.orientation[1] + ", " + msg.orientation[2] + ", " + msg.orientation[3] + "]"); + System.out.println(" name: " + msg.name); + System.out.println(" enabled: " + msg.enabled); + System.out.println(" num_ranges: " + msg.num_ranges); + System.out.print(" ranges: ["); + for (int i = 0; i < msg.num_ranges; i++) { + System.out.print(msg.ranges[i]); + if (i < msg.num_ranges - 1) System.out.print(", "); + } + System.out.println("]"); + System.out.println(); + } catch (IOException e) { + System.err.println("Error deserializing message: " + e.getMessage()); + } + } + } + + public static void main(String[] args) { + try { + // Create a loopback SerialIO implementation for testing + LoopbackSerialIO serialIO = new LoopbackSerialIO(50000); // 50KB buffer + + System.out.println("Creating ZCM transport with custom SerialIO..."); + + // Create the ZCM transport using the SerialIO + // MTU: 16KB, Buffer size: 128KB + ZCMTransport transport = new ZCMGenericSerialTransport(serialIO, 1 << 14, 1 << 17); + ZCM zcm = new ZCM(transport); + + System.out.println("Transport created successfully!"); + + // Subscribe to messages + System.out.println("Setting up subscriber..."); + ExampleSubscriber subscriber = new ExampleSubscriber(); + zcm.subscribe("EXAMPLE", subscriber); + + // Start ZCM in a separate thread for message handling + zcm.start(); + + System.out.println("Starting to publish messages..."); + + // Create and publish messages + example_t msg = new example_t(); + + int messageCount = 0; + while (messageCount < 10) { + // Update message with current data + msg.timestamp = System.currentTimeMillis() * 1000; + msg.position = new double[] { + 1.0 + messageCount * 0.1, + 2.0 + messageCount * 0.2, + 3.0 + messageCount * 0.3 + }; + msg.orientation = new double[] { 1, 0, 0, 0 }; + msg.ranges = new short[messageCount + 1]; + for (int i = 0; i <= messageCount; i++) { + msg.ranges[i] = (short) i; + } + msg.num_ranges = msg.ranges.length; + msg.name = "example string #" + messageCount; + msg.enabled = (messageCount % 2 == 0); + + System.out.println("Publishing message #" + messageCount + "..."); + zcm.publish("EXAMPLE", msg); + + messageCount++; + + // Wait a bit between messages + try { + Thread.sleep(1000); + } catch (InterruptedException ex) { + System.out.println("Interrupted, stopping..."); + break; + } + } + + // Give some time for the last messages to be processed + System.out.println("Waiting for message processing to complete..."); + Thread.sleep(2000); + + // Stop ZCM + zcm.stop(); + + // Clean up + if (transport instanceof AutoCloseable) { + ((AutoCloseable) transport).close(); + } + + System.out.println("Example completed successfully!"); + + } catch (IOException e) { + System.err.println("Failed to create ZCM transport: " + e.getMessage()); + e.printStackTrace(); + } catch (InterruptedException e) { + System.err.println("Thread interrupted: " + e.getMessage()); + e.printStackTrace(); + } catch (Exception e) { + System.err.println("Unexpected error: " + e.getMessage()); + e.printStackTrace(); + } + } +} diff --git a/examples/java/gst.sh b/examples/java/gst.sh new file mode 100755 index 00000000..c7bf2e6f --- /dev/null +++ b/examples/java/gst.sh @@ -0,0 +1,2 @@ +#!/bin/bash +java -ea example.apps.CustomTransport diff --git a/wscript b/wscript index a7dfb8a0..1c97d910 100644 --- a/wscript +++ b/wscript @@ -184,7 +184,7 @@ def process_zcm_configure_options(ctx): env.USING_CXXTEST = hasoptDev('use_cxxtest') and attempt_use_cxxtest(ctx) env.TRACK_TRAFFIC_TOPOLOGY = getattr(opt, 'track_traffic_topology') - ZMQ_REQUIRED = env.USING_TRANS_IPC or env.USING_TRANS_INPROC + ZMQ_REQUIRED = env.USING_TRANS_IPC if ZMQ_REQUIRED and not env.USING_ZMQ: raise WafError("Using ZeroMQ is required for some of the selected transports (--use-zmq)") diff --git a/zcm/java/jni/zcm_zcm_ZCMGenericSerialTransport.c b/zcm/java/jni/zcm_zcm_ZCMGenericSerialTransport.c index 367fabda..9cbd15d3 100644 --- a/zcm/java/jni/zcm_zcm_ZCMGenericSerialTransport.c +++ b/zcm/java/jni/zcm_zcm_ZCMGenericSerialTransport.c @@ -16,6 +16,8 @@ struct JavaSerialTransport { jobject javaObj; // Global reference to the Java object zcm_trans_t *transport; + bool weOwnTransportMemory; + // Method IDs for callbacks jmethodID getNativeGetMethodID; jmethodID getNativePutMethodID; @@ -52,7 +54,7 @@ static uint64_t getSystemTimestamp(void *usr) } // Get function callback - calls Java nativeGet method -static size_t javaGetCallback(uint8_t* data, size_t nData, void* usr) +static size_t javaGetCallback(uint8_t* data, size_t nData, uint32_t timeoutMs, void* usr) { JavaSerialTransport *jst = (JavaSerialTransport*)usr; @@ -80,8 +82,9 @@ static size_t javaGetCallback(uint8_t* data, size_t nData, void* usr) } // Call Java nativeGet method - // XXX (Bendes): Wrap the java gets and puts in a try/catch - jint bytesRead = (*env)->CallIntMethod(env, jst->javaObj, jst->getNativeGetMethodID, directBuffer, (jint)nData); + jint bytesRead = + (*env)->CallIntMethod(env, jst->javaObj, jst->getNativeGetMethodID, + directBuffer, (jint)nData, (jint)timeoutMs); // Validate return value if (bytesRead < 0 || bytesRead > nData) { @@ -99,7 +102,7 @@ static size_t javaGetCallback(uint8_t* data, size_t nData, void* usr) } // Put function callback - calls Java nativePut method -static size_t javaPutCallback(const uint8_t* data, size_t nData, void* usr) +static size_t javaPutCallback(const uint8_t* data, size_t nData, uint32_t timeoutMs, void* usr) { JavaSerialTransport *jst = (JavaSerialTransport*)usr; @@ -127,7 +130,9 @@ static size_t javaPutCallback(const uint8_t* data, size_t nData, void* usr) } // Call Java nativePut method - jint bytesWritten = (*env)->CallIntMethod(env, jst->javaObj, jst->getNativePutMethodID, directBuffer, (jint)nData); + jint bytesWritten = + (*env)->CallIntMethod(env, jst->javaObj, jst->getNativePutMethodID, + directBuffer, (jint)nData, (jint)timeoutMs); // Check for exceptions if ((*env)->ExceptionCheck(env)) { @@ -178,10 +183,12 @@ JNIEXPORT jboolean JNICALL Java_zcm_zcm_ZCMGenericSerialTransport_initializeNati return JNI_FALSE; } + jst->weOwnTransportMemory = true; + // Get method IDs for the callback methods jclass cls = (*env)->GetObjectClass(env, self); - jst->getNativeGetMethodID = (*env)->GetMethodID(env, cls, "nativeGet", "(Ljava/nio/ByteBuffer;I)I"); - jst->getNativePutMethodID = (*env)->GetMethodID(env, cls, "nativePut", "(Ljava/nio/ByteBuffer;I)I"); + jst->getNativeGetMethodID = (*env)->GetMethodID(env, cls, "nativeGet", "(Ljava/nio/ByteBuffer;II)I"); + jst->getNativePutMethodID = (*env)->GetMethodID(env, cls, "nativePut", "(Ljava/nio/ByteBuffer;II)I"); if (jst->getNativeGetMethodID == NULL || jst->getNativePutMethodID == NULL) { (*env)->DeleteGlobalRef(env, jst->javaObj); @@ -190,7 +197,7 @@ JNIEXPORT jboolean JNICALL Java_zcm_zcm_ZCMGenericSerialTransport_initializeNati } // Create the C transport - jst->transport = zcm_trans_generic_serial_create( + jst->transport = zcm_trans_generic_serial_blocking_create( javaGetCallback, // get function javaPutCallback, // put function jst, // user data for get/put @@ -227,57 +234,6 @@ JNIEXPORT jlong JNICALL Java_zcm_zcm_ZCMGenericSerialTransport_getTransportPtr return (jlong)(intptr_t)jst->transport; } -/* - * Class: zcm_zcm_ZCMGenericSerialTransport - * Method: updateRx - * Signature: ()I - */ -JNIEXPORT jint JNICALL Java_zcm_zcm_ZCMGenericSerialTransport_updateRx -(JNIEnv *env, jobject self) -{ - JavaSerialTransport *jst = getNativePtr(env, self); - if (jst == NULL || jst->transport == NULL) { - return -1; - } - return serial_update_rx(jst->transport); -} - -/* - * Class: zcm_zcm_ZCMGenericSerialTransport - * Method: updateTx - * Signature: ()I - */ -JNIEXPORT jint JNICALL Java_zcm_zcm_ZCMGenericSerialTransport_updateTx -(JNIEnv *env, jobject self) -{ - JavaSerialTransport *jst = getNativePtr(env, self); - if (jst == NULL || jst->transport == NULL) { - return -1; - } - return serial_update_tx(jst->transport); -} - -/* - * Class: zcm_zcm_ZCMGenericSerialTransport - * Method: update - * Signature: ()I - */ -JNIEXPORT jint JNICALL Java_zcm_zcm_ZCMGenericSerialTransport_update -(JNIEnv *env, jobject self) -{ - JavaSerialTransport *jst = getNativePtr(env, self); - if (jst == NULL || jst->transport == NULL) { - return -1; - } - - // Update both RX and TX - int rxRet = serial_update_rx(jst->transport); - int txRet = serial_update_tx(jst->transport); - - // Return the first error, or success if both succeeded - return rxRet == ZCM_EOK ? txRet : rxRet; -} - /* * Class: zcm_zcm_ZCMGenericSerialTransport * Method: destroy @@ -291,12 +247,11 @@ JNIEXPORT void JNICALL Java_zcm_zcm_ZCMGenericSerialTransport_destroy return; } - // Destroy the C transport if (jst->transport != NULL) { - zcm_trans_generic_serial_destroy(jst->transport); + if (jst->weOwnTransportMemory) + zcm_trans_generic_serial_destroy(jst->transport); jst->transport = NULL; } - // Release the global reference if (jst->javaObj != NULL) { (*env)->DeleteGlobalRef(env, jst->javaObj); @@ -309,3 +264,18 @@ JNIEXPORT void JNICALL Java_zcm_zcm_ZCMGenericSerialTransport_destroy // Clear the native pointer in the Java object setNativePtr(env, self, NULL); } + +/* + * Class: zcm_zcm_ZCMGenericSerialTransport + * Method: releaseNativeTransportMemoryToZcm + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_zcm_zcm_ZCMGenericSerialTransport_releaseNativeTransportMemoryToZcm +(JNIEnv *env, jobject self) +{ + JavaSerialTransport *jst = getNativePtr(env, self); + if (jst == NULL) { + return; + } + jst->weOwnTransportMemory = false; +} diff --git a/zcm/java/jni/zcm_zcm_ZCMGenericSerialTransport.h b/zcm/java/jni/zcm_zcm_ZCMGenericSerialTransport.h index 7989695d..31298904 100644 --- a/zcm/java/jni/zcm_zcm_ZCMGenericSerialTransport.h +++ b/zcm/java/jni/zcm_zcm_ZCMGenericSerialTransport.h @@ -23,30 +23,6 @@ JNIEXPORT jboolean JNICALL Java_zcm_zcm_ZCMGenericSerialTransport_initializeNati JNIEXPORT jlong JNICALL Java_zcm_zcm_ZCMGenericSerialTransport_getTransportPtr (JNIEnv *, jobject); -/* - * Class: zcm_zcm_ZCMGenericSerialTransport - * Method: updateRx - * Signature: ()I - */ -JNIEXPORT jint JNICALL Java_zcm_zcm_ZCMGenericSerialTransport_updateRx - (JNIEnv *, jobject); - -/* - * Class: zcm_zcm_ZCMGenericSerialTransport - * Method: updateTx - * Signature: ()I - */ -JNIEXPORT jint JNICALL Java_zcm_zcm_ZCMGenericSerialTransport_updateTx - (JNIEnv *, jobject); - -/* - * Class: zcm_zcm_ZCMGenericSerialTransport - * Method: update - * Signature: ()I - */ -JNIEXPORT jint JNICALL Java_zcm_zcm_ZCMGenericSerialTransport_update - (JNIEnv *, jobject); - /* * Class: zcm_zcm_ZCMGenericSerialTransport * Method: destroy @@ -58,4 +34,4 @@ JNIEXPORT void JNICALL Java_zcm_zcm_ZCMGenericSerialTransport_destroy #ifdef __cplusplus } #endif -#endif \ No newline at end of file +#endif diff --git a/zcm/java/jni/zcm_zcm_ZCMJNI.c b/zcm/java/jni/zcm_zcm_ZCMJNI.c index a53bf6f1..f660a7bc 100644 --- a/zcm/java/jni/zcm_zcm_ZCMJNI.c +++ b/zcm/java/jni/zcm_zcm_ZCMJNI.c @@ -99,8 +99,21 @@ JNIEXPORT jboolean JNICALL Java_zcm_zcm_ZCMJNI_initializeNativeFromTransport return ret == ZCM_EOK && I->zcm ? JNI_TRUE : JNI_FALSE; } -// XXX (Bendes): Destroy needs to delete I, doesn't it? -PASS_THROUGH_FUNC(destroy, destroy, void, ()V) +/* + * Class: zcm_zcm_ZCMJNI + * Method: destroy + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_zcm_zcm_ZCMJNI_destroy +(JNIEnv *env, jobject self) +{ + Internal *I = getNativePtr(env, self); + if (!I) return; + if (I->zcm) zcm_destroy(I->zcm); + free(I); + setNativePtr(env, self, NULL); +} + PASS_THROUGH_FUNC(start, start, void, ()V) PASS_THROUGH_FUNC(stop, stop, void, ()V) @@ -224,6 +237,21 @@ JNIEXPORT jint JNICALL Java_zcm_zcm_ZCMJNI_unsubscribe return ret; } +/* + * Class: zcm_zcm_ZCMJNI + * Method: getNativeZcmPtr + * Signature: ()J + */ +JNIEXPORT jlong JNICALL Java_zcm_zcm_ZCMJNI_getNativeZcmPtr +(JNIEnv *env, jobject self) +{ + Internal *I = getNativePtr(env, self); + if (I && I->zcm) { + return (jlong)(intptr_t)I->zcm; + } + return 0; +} + PASS_THROUGH_FUNC(flush, flush, void, ()V) PASS_THROUGH_FUNC(pause, pause, void, ()V) PASS_THROUGH_FUNC(resume, resume, void, ()V) diff --git a/zcm/java/jni/zcm_zcm_ZCMJNI.h b/zcm/java/jni/zcm_zcm_ZCMJNI.h index f5fbe791..172dfa2b 100644 --- a/zcm/java/jni/zcm_zcm_ZCMJNI.h +++ b/zcm/java/jni/zcm_zcm_ZCMJNI.h @@ -111,6 +111,14 @@ JNIEXPORT jint JNICALL Java_zcm_zcm_ZCMJNI_handle JNIEXPORT jint JNICALL Java_zcm_zcm_ZCMJNI_handleNonblock (JNIEnv *, jobject); +/* + * Class: zcm_zcm_ZCMJNI + * Method: getNativeZcmPtr + * Signature: ()J + */ +JNIEXPORT jlong JNICALL Java_zcm_zcm_ZCMJNI_getNativeZcmPtr + (JNIEnv *, jobject); + #ifdef __cplusplus } #endif diff --git a/zcm/java/zcm/zcm/SerialIO.java b/zcm/java/zcm/zcm/SerialIO.java index 488995d9..494e1df2 100644 --- a/zcm/java/zcm/zcm/SerialIO.java +++ b/zcm/java/zcm/zcm/SerialIO.java @@ -16,9 +16,10 @@ public interface SerialIO { * * @param buffer ByteBuffer to store the read data (direct buffer, zero-copy) * @param maxLen maximum number of bytes to read + * @param timeoutMs number of ms this call may block for. 0 indicates nonblocking * @return number of bytes actually read, or 0 if no data available */ - int get(ByteBuffer buffer, int maxLen); + int get(ByteBuffer buffer, int maxLen, int timeoutMs); /** * Write data to the serial interface. @@ -27,7 +28,8 @@ public interface SerialIO { * * @param buffer ByteBuffer containing data to write (direct buffer, zero-copy) * @param len number of bytes to write from the buffer + * @param timeoutMs number of ms this call may block for. 0 indicates nonblocking * @return number of bytes actually written */ - int put(ByteBuffer buffer, int len); + int put(ByteBuffer buffer, int len, int timeoutMs); } diff --git a/zcm/java/zcm/zcm/ZCM.java b/zcm/java/zcm/zcm/ZCM.java index 63df7d10..f5aaf1a1 100644 --- a/zcm/java/zcm/zcm/ZCM.java +++ b/zcm/java/zcm/zcm/ZCM.java @@ -70,18 +70,18 @@ public static ZCM getSingleton() * primarily provided for testing purposes and may be removed in * the future. **/ - public void publish(String channel, String s) throws IOException + public int publish(String channel, String s) throws IOException { if (this.closed) throw new IllegalStateException(); s = s + "\0"; byte[] b = s.getBytes(); - publish(channel, b, 0, b.length); + return publish(channel, b, 0, b.length); } /** Publish an ZCM-defined type on a channel. If more than one URL was * specified, the message will be sent on each. **/ - public synchronized void publish(String channel, ZCMEncodable e) + public synchronized int publish(String channel, ZCMEncodable e) { if (this.closed) throw new IllegalStateException(); @@ -90,21 +90,22 @@ public synchronized void publish(String channel, ZCMEncodable e) e.encode(encodeBuffer); - publish(channel, encodeBuffer.getBuffer(), 0, encodeBuffer.size()); + return publish(channel, encodeBuffer.getBuffer(), 0, encodeBuffer.size()); } catch (IOException ex) { System.err.println("ZCM publish fail: "+ex); } + return -1; } /** Publish raw data on a channel, bypassing the ZCM type * specification. If more than one URL was specified when the ZCM * object was created, the message will be sent on each. **/ - public void publish(String channel, byte[] data, int offset, int length) + public int publish(String channel, byte[] data, int offset, int length) throws IOException { if (this.closed) throw new IllegalStateException(); - zcmjni.publish(channel, data, offset, length); + return zcmjni.publish(channel, data, offset, length); } public Subscription subscribe(String channel, ZCMSubscriber sub) @@ -149,7 +150,14 @@ public void close() this.closed = true; } - + /** Get the native zcm_t* pointer for use in JNI code. + * @return native zcm_t* pointer as long, or 0 if not initialized + */ + public long getNativeZcmPtr() + { + if (this.closed) throw new IllegalStateException(); + return zcmjni.getNativeZcmPtr(); + } //////////////////////////////////////////////////////////////// diff --git a/zcm/java/zcm/zcm/ZCMGenericSerialTransport.java b/zcm/java/zcm/zcm/ZCMGenericSerialTransport.java index f44651ca..6bf61d2d 100644 --- a/zcm/java/zcm/zcm/ZCMGenericSerialTransport.java +++ b/zcm/java/zcm/zcm/ZCMGenericSerialTransport.java @@ -10,6 +10,10 @@ */ public class ZCMGenericSerialTransport implements ZCMTransport, AutoCloseable { + static { + ZCMNativeLoader.loadLibrary(); + } + private long nativePtr = 0; private SerialIO serialIO; @@ -32,7 +36,12 @@ public ZCMGenericSerialTransport(SerialIO serialIO, int mtu, int bufSize) throws throw new IllegalArgumentException("Buffer size must be positive"); } + if (!ZCMNativeLoader.isLibraryLoaded()) { + throw new AssertionError("Library not yet loaded"); + } + this.serialIO = serialIO; + if (!initializeNative(mtu, bufSize)) { throw new IOException("Failed to create ZCM Generic Serial Transport"); } @@ -70,12 +79,17 @@ public long getNativeTransport() { * Destroy the transport and free all resources. * This implements the ZCMTransport interface. */ - // XXX (Bendes): I don't think this model works because I think zcm is - // supposed to own the close behavior after zcm is constructed with the - // transport. I'm not positive though @Override public native void destroy(); + /** + * Signal that the internal C transport will be cleaned + * up by a ZCM instance. + * This implements the ZCMTransport interface. + */ + @Override + public native void releaseNativeTransportMemoryToZcm(); + /** * Close the transport and free all resources. * This implements the AutoCloseable interface for try-with-resources support. @@ -91,11 +105,12 @@ public void close() { * * @param buffer direct ByteBuffer wrapping the native data array * @param maxLen maximum number of bytes to read + * @param timeoutMs number of ms this call may block for. 0 indicates nonblocking * @return number of bytes actually read */ - private int nativeGet(ByteBuffer buffer, int maxLen) { + private int nativeGet(ByteBuffer buffer, int maxLen, int timeoutMs) { try { - return serialIO.get(buffer, maxLen); + return serialIO.get(buffer, maxLen, timeoutMs); } catch (Exception e) { // Don't let exceptions propagate through JNI System.err.println("Exception in SerialIO.get(): " + e.getMessage()); @@ -109,11 +124,12 @@ private int nativeGet(ByteBuffer buffer, int maxLen) { * * @param buffer direct ByteBuffer wrapping the native data array * @param len number of bytes to write + * @param timeoutMs number of ms this call may block for. 0 indicates nonblocking * @return number of bytes actually written */ - private int nativePut(ByteBuffer buffer, int len) { + private int nativePut(ByteBuffer buffer, int len, int timeoutMs) { try { - return serialIO.put(buffer, len); + return serialIO.put(buffer, len, timeoutMs); } catch (Exception e) { // Don't let exceptions propagate through JNI System.err.println("Exception in SerialIO.put(): " + e.getMessage()); diff --git a/zcm/java/zcm/zcm/ZCMJNI.java b/zcm/java/zcm/zcm/ZCMJNI.java index 9b6470c2..d2ac867d 100644 --- a/zcm/java/zcm/zcm/ZCMJNI.java +++ b/zcm/java/zcm/zcm/ZCMJNI.java @@ -4,7 +4,7 @@ class ZCMJNI { static { - System.loadLibrary("zcmjni"); + ZCMNativeLoader.loadLibrary(); } private long nativePtr = 0; @@ -14,9 +14,9 @@ class ZCMJNI public ZCMJNI(String url) throws IOException { if (!initializeNative(url)) { - String msg = (url != null) ? - "Failed to create ZCM for '" + url + "'" : - "Failed to create ZCM using the default url"; + String msg = (url != null) + ? "Failed to create ZCM for '" + url + "'" + : "Failed to create ZCM using the default url"; throw new IOException(msg); } } @@ -29,9 +29,9 @@ public ZCMJNI(ZCMTransport transport) throws IOException if (!initializeNativeFromTransport(transport.getNativeTransport())) { throw new IOException("Failed to create ZCM from transport"); } + transport.releaseNativeTransportMemoryToZcm(); } - // XXX (Bendes): Do we need to delete/destroy zcmjni here too? public native void destroy(); public native void start(); @@ -41,4 +41,6 @@ public ZCMJNI(ZCMTransport transport) throws IOException public native Object subscribe(String channel, ZCM zcm, Object usr); public native int unsubscribe(Object usr); + + public native long getNativeZcmPtr(); } diff --git a/zcm/java/zcm/zcm/ZCMNativeLoader.java b/zcm/java/zcm/zcm/ZCMNativeLoader.java new file mode 100644 index 00000000..a8223c7a --- /dev/null +++ b/zcm/java/zcm/zcm/ZCMNativeLoader.java @@ -0,0 +1,61 @@ +package zcm.zcm; + +/** + * Utility class for loading ZCM native libraries. + * This class ensures that the required native libraries are loaded exactly once + * and provides a centralized place for library loading logic. + */ +public final class ZCMNativeLoader { + + private static volatile boolean isLoaded = false; + private static final Object lock = new Object(); + + // Private constructor to prevent instantiation + private ZCMNativeLoader() { + throw new AssertionError("ZCMNativeLoader should not be instantiated"); + } + + /** + * Loads the ZCM native library if it hasn't been loaded already. + * This method is thread-safe and ensures the library is loaded exactly once. + * + * @throws UnsatisfiedLinkError if the native library cannot be loaded + */ + public static void loadLibrary() { + if (!isLoaded) { + synchronized (lock) { + if (!isLoaded) { + try { + System.loadLibrary("zcm"); + } catch (UnsatisfiedLinkError e) { + throw new UnsatisfiedLinkError( + "Failed to load ZCM native library 'zcm': " + e.getMessage()); + } + try { + System.loadLibrary("zcmjni"); + } catch (UnsatisfiedLinkError e) { + throw new UnsatisfiedLinkError( + "Failed to load ZCM native library 'zcmjni': " + e.getMessage()); + } + isLoaded = true; + } + } + } + } + + /** + * Returns whether the ZCM native library has been loaded. + * + * @return true if the library is loaded, false otherwise + */ + public static boolean isLibraryLoaded() { + return isLoaded; + } + + /** + * Static initializer to automatically load the library when this class is first referenced. + */ + static { + loadLibrary(); + } +} diff --git a/zcm/java/zcm/zcm/ZCMTransport.java b/zcm/java/zcm/zcm/ZCMTransport.java index 960bf7a7..2c68db14 100644 --- a/zcm/java/zcm/zcm/ZCMTransport.java +++ b/zcm/java/zcm/zcm/ZCMTransport.java @@ -20,4 +20,11 @@ public interface ZCMTransport { * After calling this method, the transport should not be used. */ void destroy(); + + /** + * Signal to the transport that the native transport pointer memory + * will be managed by ZCM. This is invoked as soon as a ZCM is created + * from this transport. + */ + void releaseNativeTransportMemoryToZcm(); } diff --git a/zcm/transport/generic_serial_transport.c b/zcm/transport/generic_serial_transport.c index d1ebb5d8..d4b1e97a 100644 --- a/zcm/transport/generic_serial_transport.c +++ b/zcm/transport/generic_serial_transport.c @@ -15,6 +15,8 @@ #define ASSERT(x) +#define PUT_TIMEOUT_MS (100) + // Framing (size = 9 + chan_len + data_len) // 0xCC // 0x00 @@ -40,6 +42,8 @@ struct zcm_trans_generic_serial_t size_t (*get)(uint8_t* data, size_t nData, void* usr); size_t (*put)(const uint8_t* data, size_t nData, void* usr); void* put_get_usr; + uint32_t timeoutPut; + uint32_t timeoutGet; uint64_t (*time)(void* usr); void* time_usr; @@ -239,6 +243,22 @@ int serial_update_rx(zcm_trans_t *_zt) return ZCM_EOK; } +static size_t get_blocking(uint8_t *data, size_t nData, void *usr) +{ + zcm_trans_generic_serial_t* zt = cast(usr); + int ret = ((size_t (*)(uint8_t* data, size_t nData, uint32_t timeoutMs, void* usr))zt->get) + (data, nData, zt->timeoutGet, zt->put_get_usr); + zt->timeoutGet = 0; + return ret; +} + +int serial_update_rx_blocking(zcm_trans_t *_zt) +{ + zcm_trans_generic_serial_t* zt = cast(_zt); + cb_flush_in(&zt->recvBuffer, get_blocking, zt); + return ZCM_EOK; +} + int serial_update_tx(zcm_trans_t *_zt) { zcm_trans_generic_serial_t* zt = cast(_zt); @@ -246,6 +266,22 @@ int serial_update_tx(zcm_trans_t *_zt) return ZCM_EOK; } +static size_t put_blocking(const uint8_t *data, size_t nData, void *usr) +{ + zcm_trans_generic_serial_t* zt = cast(usr); + int ret = ((size_t (*)(const uint8_t* data, size_t nData, uint32_t timeoutMs, void* usr))zt->put) + (data, nData, zt->timeoutPut, zt->put_get_usr); + zt->timeoutPut = 0; + return ret; +} + +int serial_update_tx_blocking(zcm_trans_t *_zt) +{ + zcm_trans_generic_serial_t* zt = cast(_zt); + cb_flush_out(&zt->sendBuffer, put_blocking, zt); + return ZCM_EOK; +} + /********************** STATICS **********************/ static size_t _serial_get_mtu(zcm_trans_t *zt) { return serial_get_mtu(cast(zt)); } @@ -266,6 +302,25 @@ static int _serial_update(zcm_trans_t *zt) return rxRet == ZCM_EOK ? txRet : rxRet; } +static int _serial_sendmsg_blocking(zcm_trans_t *_zt, zcm_msg_t msg) +{ + zcm_trans_generic_serial_t* zt = cast(_zt); + int ret = serial_sendmsg(zt, msg); + zt->timeoutPut = PUT_TIMEOUT_MS; + serial_update_tx_blocking(_zt); + return ret; +} + +static int _serial_recvmsg_blocking(zcm_trans_t *_zt, zcm_msg_t *msg, unsigned timeout) +{ + zcm_trans_generic_serial_t* zt = cast(_zt); + int ret = serial_recvmsg(zt, msg, timeout); + if (ret == ZCM_EOK) return ZCM_EOK; + zt->timeoutGet = timeout; + serial_update_rx_blocking(_zt); + return ret; +} + static zcm_trans_methods_t methods = { &_serial_get_mtu, &_serial_sendmsg, @@ -276,9 +331,19 @@ static zcm_trans_methods_t methods = { &zcm_trans_generic_serial_destroy, }; +static zcm_trans_methods_t methods_blocking = { + &_serial_get_mtu, + &_serial_sendmsg_blocking, + &_serial_recvmsg_enable, + &_serial_recvmsg_blocking, + NULL, // drops + NULL, // update is part of recvmsg + &zcm_trans_generic_serial_destroy, +}; + static zcm_trans_generic_serial_t *cast(zcm_trans_t *zt) { - assert(zt->vtbl == &methods); + assert(zt->vtbl == &methods || zt->vtbl == &methods_blocking); return (zcm_trans_generic_serial_t*)zt; } @@ -325,6 +390,25 @@ zcm_trans_t *zcm_trans_generic_serial_create( return (zcm_trans_t*) zt; } +zcm_trans_t *zcm_trans_generic_serial_blocking_create( + size_t (*get)(uint8_t* data, size_t nData, uint32_t timeoutMs, void* usr), + size_t (*put)(const uint8_t* data, size_t nData, uint32_t timeoutMs, void* usr), + void* put_get_usr, + uint64_t (*timestamp_now)(void* usr), + void* time_usr, + size_t MTU, + size_t bufSize) +{ + zcm_trans_t *zt = + zcm_trans_generic_serial_create( + (size_t (*)(uint8_t* data, size_t nData, void* usr))get, + (size_t (*)(const uint8_t* data, size_t nData, void* usr))put, + put_get_usr, timestamp_now, time_usr, MTU, bufSize); + zt->trans_type = ZCM_BLOCKING; + zt->vtbl = &methods_blocking; + return zt; +} + void zcm_trans_generic_serial_destroy(zcm_trans_t* _zt) { zcm_trans_generic_serial_t *zt = cast(_zt); diff --git a/zcm/transport/generic_serial_transport.h b/zcm/transport/generic_serial_transport.h index 87599503..4377e93e 100644 --- a/zcm/transport/generic_serial_transport.h +++ b/zcm/transport/generic_serial_transport.h @@ -18,6 +18,16 @@ zcm_trans_t *zcm_trans_generic_serial_create( void* time_usr, size_t MTU, size_t bufSize); +// Note that put and get can be called at the same time. +// The caller is responsible for thread safety inside those functions, if required +zcm_trans_t *zcm_trans_generic_serial_blocking_create( + size_t (*get)(uint8_t* data, size_t nData, uint32_t timeoutMs, void* usr), + size_t (*put)(const uint8_t* data, size_t nData, uint32_t timeoutMs, void* usr), + void* put_get_usr, + uint64_t (*timestamp_now)(void* usr), + void* time_usr, + size_t MTU, size_t bufSize); + // frees all resources inside of zt and frees zt itself void zcm_trans_generic_serial_destroy(zcm_trans_t* zt);