aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakob Odersky <jodersky@gmail.com>2013-05-21 13:41:49 +0200
committerJakob Odersky <jodersky@gmail.com>2013-05-21 13:41:49 +0200
commit507e3a235ca5e7e62e105fd46df7186e8559ac98 (patch)
tree873eeab88f81427a27d5501dc9c74084b88b223c
downloadakka-serial-507e3a235ca5e7e62e105fd46df7186e8559ac98.tar.gz
akka-serial-507e3a235ca5e7e62e105fd46df7186e8559ac98.tar.bz2
akka-serial-507e3a235ca5e7e62e105fd46df7186e8559ac98.zip
initial commit
-rw-r--r--.gitignore20
-rw-r--r--Makefile19
-rw-r--r--build.sbt9
-rw-r--r--src/main/c/com_github_jodersky_flow_NativeSerial.h73
-rw-r--r--src/main/c/flow-aio.c149
-rw-r--r--src/main/c/flow-epoll.c220
-rw-r--r--src/main/c/flow-signal.c153
-rw-r--r--src/main/c/flow.c292
-rw-r--r--src/main/java/com/github/jodersky/flow/NativeSerial.java49
-rw-r--r--src/main/scala/com/github/jodersky/flow/Main.scala26
-rw-r--r--src/main/scala/com/github/jodersky/flow/Serial.scala63
-rw-r--r--src/main/scala/com/github/jodersky/flow/exceptions.scala7
12 files changed, 1080 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..20e1c2d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,20 @@
+*.class
+*.log
+
+# sbt specific
+dist/*
+target/
+lib_managed/
+src_managed/
+project/boot/
+project/plugins/project/
+
+# Scala-IDE specific
+.scala_dependencies
+.project
+.classpath
+.cache
+
+# Native
+*.o
+*.so
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..01c1b6a
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,19 @@
+TARGET=target
+SCALA_VERSION=2.10
+CLASSPATH=$(TARGET)/scala-$(SCALA_VERSION)/classes
+TARGETDIR=target
+
+all: flow.so
+
+javah: $(CLASSPATH)
+ javah -d src/main/c/ -classpath $(CLASSPATH) com.github.jodersky.flow.NativeSerial
+
+flow.o:
+ gcc -I$(JAVA_HOME)/include -I$(JAVA_HOME)/include/linux -fPIC -c src/main/c/flow.c -o flow.o
+
+flow.so: flow.o
+ gcc -shared -Wl,-soname,libflow.so.1 -o libflow.so $< -lc
+
+clean:
+ rm -f *.o
+ rm -f *.so
diff --git a/build.sbt b/build.sbt
new file mode 100644
index 0000000..0ae0f6f
--- /dev/null
+++ b/build.sbt
@@ -0,0 +1,9 @@
+name := "flow"
+
+scalaVersion := "2.10.1"
+
+fork := true
+
+connectInput in run := true
+
+javaOptions in run += "-Djava.library.path=." \ No newline at end of file
diff --git a/src/main/c/com_github_jodersky_flow_NativeSerial.h b/src/main/c/com_github_jodersky_flow_NativeSerial.h
new file mode 100644
index 0000000..e89652c
--- /dev/null
+++ b/src/main/c/com_github_jodersky_flow_NativeSerial.h
@@ -0,0 +1,73 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include <jni.h>
+/* Header for class com_github_jodersky_flow_NativeSerial */
+
+#ifndef _Included_com_github_jodersky_flow_NativeSerial
+#define _Included_com_github_jodersky_flow_NativeSerial
+#ifdef __cplusplus
+extern "C" {
+#endif
+#undef com_github_jodersky_flow_NativeSerial_E_PERMISSION
+#define com_github_jodersky_flow_NativeSerial_E_PERMISSION -1L
+#undef com_github_jodersky_flow_NativeSerial_E_OPEN
+#define com_github_jodersky_flow_NativeSerial_E_OPEN -2L
+#undef com_github_jodersky_flow_NativeSerial_E_BUSY
+#define com_github_jodersky_flow_NativeSerial_E_BUSY -3L
+#undef com_github_jodersky_flow_NativeSerial_E_BAUD
+#define com_github_jodersky_flow_NativeSerial_E_BAUD -4L
+#undef com_github_jodersky_flow_NativeSerial_E_PIPE
+#define com_github_jodersky_flow_NativeSerial_E_PIPE -5L
+#undef com_github_jodersky_flow_NativeSerial_E_MALLOC
+#define com_github_jodersky_flow_NativeSerial_E_MALLOC -6L
+#undef com_github_jodersky_flow_NativeSerial_E_POINTER
+#define com_github_jodersky_flow_NativeSerial_E_POINTER -7L
+#undef com_github_jodersky_flow_NativeSerial_E_POLL
+#define com_github_jodersky_flow_NativeSerial_E_POLL -8L
+#undef com_github_jodersky_flow_NativeSerial_E_IO
+#define com_github_jodersky_flow_NativeSerial_E_IO -9L
+#undef com_github_jodersky_flow_NativeSerial_E_CLOSE
+#define com_github_jodersky_flow_NativeSerial_E_CLOSE -10L
+/*
+ * Class: com_github_jodersky_flow_NativeSerial
+ * Method: open
+ * Signature: (Ljava/lang/String;I[J)I
+ */
+JNIEXPORT jint JNICALL Java_com_github_jodersky_flow_NativeSerial_open
+ (JNIEnv *, jclass, jstring, jint, jlongArray);
+
+/*
+ * Class: com_github_jodersky_flow_NativeSerial
+ * Method: read
+ * Signature: (J[B)I
+ */
+JNIEXPORT jint JNICALL Java_com_github_jodersky_flow_NativeSerial_read
+ (JNIEnv *, jclass, jlong, jbyteArray);
+
+/*
+ * Class: com_github_jodersky_flow_NativeSerial
+ * Method: write
+ * Signature: (J[B)I
+ */
+JNIEXPORT jint JNICALL Java_com_github_jodersky_flow_NativeSerial_write
+ (JNIEnv *, jclass, jlong, jbyteArray);
+
+/*
+ * Class: com_github_jodersky_flow_NativeSerial
+ * Method: close
+ * Signature: (J)V
+ */
+JNIEXPORT void JNICALL Java_com_github_jodersky_flow_NativeSerial_close
+ (JNIEnv *, jclass, jlong);
+
+/*
+ * Class: com_github_jodersky_flow_NativeSerial
+ * Method: debug
+ * Signature: (Z)V
+ */
+JNIEXPORT void JNICALL Java_com_github_jodersky_flow_NativeSerial_debug
+ (JNIEnv *, jclass, jboolean);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/src/main/c/flow-aio.c b/src/main/c/flow-aio.c
new file mode 100644
index 0000000..86e1747
--- /dev/null
+++ b/src/main/c/flow-aio.c
@@ -0,0 +1,149 @@
+#include <termios.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/signal.h>
+#include <sys/types.h>
+#include <sys/file.h>
+#include <errno.h>
+#include <sys/epoll.h>
+#include "com_github_jodersky_flow_NativeSerial.h"
+
+#define BUFSIZE 128
+
+/* return values:
+ * >0 fd
+ * -1 can't get file descriptor
+ * -2 device busy
+ * -3 invalid baudrate
+ * */
+int serial_open(const char* device, int baud) {
+
+ int fd = open(device, O_RDWR | O_NOCTTY);
+
+ if (fd < 0) {
+ perror(device);
+ return -1;
+ }
+
+ if (flock(fd, LOCK_EX | LOCK_NB) < 0) {
+ perror(device);
+ return -2;
+ }
+
+ speed_t bd;
+ switch (baud) {
+ case 50: bd = B50; break;
+ case 75: bd = B75; break;
+ case 110: bd = B110; break;
+ case 134: bd = B134; break;
+ case 150: bd = B150; break;
+ case 200: bd = B200; break;
+ case 300: bd = B300; break;
+ case 600: bd = B600; break;
+ case 1200: bd = B1200; break;
+ case 1800: bd = B1800; break;
+ case 2400: bd = B2400; break;
+ case 4800: bd = B4800; break;
+ case 9600: bd = B9600; break;
+ case 19200: bd = B19200; break;
+ case 38400: bd = B38400; break;
+ case 57600: bd = B57600; break;
+ case 115200: bd = B115200; break;
+ case 230400: bd = B230400; break;
+ default: puts("invalid baudrate"); return -3; break;
+ }
+
+ struct aiocb my_aiocb;
+
+ /* Zero out the aiocb structure (recommended) */
+ bzero( (char *)&my_aiocb, sizeof(struct aiocb) );
+
+ /* Allocate a data buffer for the aiocb request */
+ my_aiocb.aio_buf = malloc(BUFSIZE+1);
+ if (!my_aiocb.aio_buf) perror("malloc");
+
+ /* Initialize the necessary fields in the aiocb */
+ my_aiocb.aio_fildes = fd;
+ my_aiocb.aio_nbytes = BUFSIZE;
+ my_aiocb.aio_offset = 0;
+
+ /* configure new port settings */
+ struct termios newtio;
+ newtio.c_cflag &= ~PARENB;
+ newtio.c_cflag &= ~CSTOPB;
+ newtio.c_cflag &= ~CSIZE;
+ newtio.c_cflag |= CS8;
+ // no flow control
+ newtio.c_cflag &= ~CRTSCTS;
+ newtio.c_cflag &= ~HUPCL; // disable hang-up-on-close to avoid reset
+ newtio.c_cflag |= CREAD | CLOCAL; // turn on READ & ignore ctrl lines
+ newtio.c_iflag &= ~(IXON | IXOFF | IXANY); // turn off s/w flow ctrl
+ newtio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // make raw
+ newtio.c_oflag &= ~OPOST; // make raw
+
+ // see: http://unixwiz.net/techtips/termios-vmin-vtime.html
+ newtio.c_cc[VMIN] = 1;
+ newtio.c_cc[VTIME] = 2*10/baud;
+ cfsetspeed(&newtio, bd);
+
+ /* load new settings to port */
+ tcflush(fd, TCIFLUSH);
+ tcsetattr(fd,TCSANOW,&newtio);
+
+ int ret = aio_read( &my_aiocb );
+ if (ret < 0) perror("aio_read");
+
+
+ struct aiocb *cblist[1];
+ /* Clear the list. */
+ bzero( (char *)cblist, sizeof(cblist) );
+
+ /* Load one or more references into the list */
+ cblist[0] = &my_aiocb;
+ aio_suspend(cblist, 1, NULL);
+ //while ( aio_error( &my_aiocb ) == EINPROGRESS ) ;
+
+ if ((ret = aio_return(&my_aiocb )) > 0) {
+ /* got ret bytes on the read */
+ puts("yeah, got bytes");
+ } else {
+ puts("hmmmmmm, couldn't read bytes");
+ }
+
+ return fd;
+}
+
+void serial_close(int fd) {
+ if (flock(fd, LOCK_UN) < 0 ) {
+ perror("");
+ }
+ if (close(fd) < 0 ) {
+ perror("");
+ }
+}
+
+
+// JNI bindings
+// ============
+
+JNIEXPORT jint JNICALL Java_com_github_jodersky_flow_NativeSerial_open
+ (JNIEnv *env, jclass clazz, jstring device, jint baud, jobject reader)
+{
+ const char *dev = (*env)->GetStringUTFChars(env, device, 0);
+ int r = serial_open(dev, baud);
+ (*env)->ReleaseStringUTFChars(env, device, dev);
+ return r;
+}
+
+/*
+ * Class: com_github_jodersky_flow_NativeSerial
+ * Method: close
+ * Signature: (I)I
+ */
+JNIEXPORT void JNICALL Java_com_github_jodersky_flow_NativeSerial_close
+ (JNIEnv * env, jclass clazz, jint fd)
+{
+ serial_close(fd);
+}
diff --git a/src/main/c/flow-epoll.c b/src/main/c/flow-epoll.c
new file mode 100644
index 0000000..c9bb728
--- /dev/null
+++ b/src/main/c/flow-epoll.c
@@ -0,0 +1,220 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <termios.h>
+#include <fcntl.h>
+//#include <sys/signal.h>
+//#include <sys/types.h>
+//#include <sys/file.h>
+#include <sys/epoll.h>
+#include <sys/eventfd.h>
+#include "com_github_jodersky_flow_NativeSerial.h"
+
+//contains file descriptors used in managing a serial port
+struct serial_config {
+
+ int fd; //serial port
+ int efd; //event
+ int epfd; //file descriptor for epoll
+
+};
+
+/* return values:
+ * >0 fd
+ * -1 can't get file descriptor
+ * -2 device busy
+ * -3 invalid baudrate
+ * -4 can't open pipe for graceful closing
+ * -5 can't create epoll
+ * -6 can't add event to epoll
+ * */
+int serial_open(const char* device, int baud, struct serial_config** serial) {
+
+ int fd = open(device, O_RDWR | O_NOCTTY | O_NONBLOCK);
+
+ if (fd < 0) {
+ perror(device);
+ return -1;
+ }
+
+ if (flock(fd, LOCK_EX | LOCK_NB) < 0) {
+ perror(device);
+ return -2;
+ }
+
+ speed_t bd;
+ switch (baud) {
+ case 50: bd = B50; break;
+ case 75: bd = B75; break;
+ case 110: bd = B110; break;
+ case 134: bd = B134; break;
+ case 150: bd = B150; break;
+ case 200: bd = B200; break;
+ case 300: bd = B300; break;
+ case 600: bd = B600; break;
+ case 1200: bd = B1200; break;
+ case 1800: bd = B1800; break;
+ case 2400: bd = B2400; break;
+ case 4800: bd = B4800; break;
+ case 9600: bd = B9600; break;
+ case 19200: bd = B19200; break;
+ case 38400: bd = B38400; break;
+ case 57600: bd = B57600; break;
+ case 115200: bd = B115200; break;
+ case 230400: bd = B230400; break;
+ default: puts("invalid baudrate"); return -3; break;
+ }
+
+ /* configure new port settings */
+ struct termios newtio;
+ newtio.c_cflag &= ~PARENB;
+ newtio.c_cflag &= ~CSTOPB;
+ newtio.c_cflag &= ~CSIZE;
+ newtio.c_cflag |= CS8;
+ // no flow control
+ newtio.c_cflag &= ~CRTSCTS;
+ newtio.c_cflag &= ~HUPCL; // disable hang-up-on-close to avoid reset
+ newtio.c_cflag |= CREAD | CLOCAL; // turn on READ & ignore ctrl lines
+ newtio.c_iflag &= ~(IXON | IXOFF | IXANY); // turn off s/w flow ctrl
+ newtio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // make raw
+ newtio.c_oflag &= ~OPOST; // make raw
+
+ // see: http://unixwiz.net/techtips/termios-vmin-vtime.html
+ newtio.c_cc[VMIN] = 1;
+ newtio.c_cc[VTIME] = 2*10/baud;
+ cfsetspeed(&newtio, bd);
+
+ /* load new settings to port */
+ tcflush(fd, TCIFLUSH);
+ tcsetattr(fd,TCSANOW,&newtio);
+
+ int efd = eventfd(0, EFD_NONBLOCK);
+
+ int epfd = epoll_create(1);
+ if (epfd < 0) {
+ perror(device);
+ return -5;
+ }
+
+ struct epoll_event serial_event;
+ serial_event.data.fd = fd;
+ serial_event.events = EPOLLIN;
+
+ struct epoll_event efd_event = {0};
+ efd_event.data.fd = efd;
+ efd_event.events = EPOLLIN | EPOLLET | EPOLLONESHOT;
+
+
+ if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &serial_event) < 0 || epoll_ctl(epfd, EPOLL_CTL_ADD, efd, &efd_event) < 0) {
+ perror(device);
+ return -6;
+ }
+
+ struct serial_config* s = malloc(sizeof(s));
+ s->fd = fd;
+ s->efd = efd;
+ s->epfd = epfd;
+ (*serial) = s;
+
+ return 0;
+}
+/*
+int serial_pipe() {
+ int p = pipe();
+ if (p < 0) {
+ perror("create pipe");
+ }
+ return p;
+}
+
+int serial_add_epoll(int fd, int pipe) {
+ int epfd = epoll_create(1);
+
+ struct epoll_event event;
+ event.data.fd = fd;
+ event.events = EPOLLIN;
+
+ if (epoll_ctl (epfd, EPOLL_CTL_ADD, fd, &event)) {
+ perror (device);
+ }
+
+ return epfd;
+}
+
+*/
+void serial_close(struct serial_config* serial) {
+
+ if (serial == NULL) return;
+
+ //write to pipe to wake up any blocked read thread (self-pipe trick)
+ eventfd_write(serial->efd, 1);
+
+ close(serial->efd);
+ flock(serial->fd, LOCK_UN);
+ close(serial->fd);
+ close(serial->epfd);
+
+ free(serial);
+}
+
+int serial_read(struct serial_config* serial) {
+ struct epoll_event events[10];// = malloc (sizeof (struct epoll_event) * 10);
+ int nr_events = epoll_wait(serial->epfd, events, 10, -1);
+ if (nr_events < 0) {
+ perror("read");
+ }
+
+ int i;
+ for (i = 0; i < nr_events; i++) {
+ //printf ("event=%d on fd=%d\n", events[i].events, events[i].data.fd);
+
+ if (events[i].data.fd == serial->fd) puts("from serial");
+ else if (events[i].data.fd == serial->efd) puts("from pipe");
+ else puts("from ???");
+ }
+ /*
+ int buffer[256];
+ int x = read(fd, buffer, 255);
+ if (x < 0) {
+ perror("read");
+ }*/
+ return nr_events;
+}
+
+
+// JNI bindings
+// ============
+
+inline struct serial_config* j2s(jlong pointer) {
+ return (struct serial_config*) pointer;
+}
+
+inline jlong s2j(struct serial_config* pointer) {
+ return (jlong) pointer;
+}
+
+JNIEXPORT jlong JNICALL Java_com_github_jodersky_flow_NativeSerial_open
+ (JNIEnv *env, jclass clazz, jstring device, jint baud)
+{
+ const char *dev = (*env)->GetStringUTFChars(env, device, 0);
+
+ struct serial_config* serial;
+ serial_open(dev, baud, &serial);
+
+ (*env)->ReleaseStringUTFChars(env, device, dev);
+
+ return s2j(serial);
+}
+
+JNIEXPORT void JNICALL Java_com_github_jodersky_flow_NativeSerial_close
+ (JNIEnv * env, jclass clazz, jlong serial)
+{
+ serial_close(j2s(serial));
+}
+
+JNIEXPORT jint JNICALL Java_com_github_jodersky_flow_NativeSerial_read
+ (JNIEnv * env, jclass clazz, jlong serial)
+{
+ return serial_read(j2s(serial));
+}
diff --git a/src/main/c/flow-signal.c b/src/main/c/flow-signal.c
new file mode 100644
index 0000000..f3f6e7d
--- /dev/null
+++ b/src/main/c/flow-signal.c
@@ -0,0 +1,153 @@
+#include <termios.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/signal.h>
+#include <sys/types.h>
+#include <sys/file.h>
+#include "com_github_jodersky_flow_NativeSerial.h"
+
+
+static JavaVM *jvm = NULL;
+static jobject callback = NULL;
+
+void signal_handler(int signum) {
+ puts("got data");
+ if(jvm == NULL)
+ return ;
+ if(callback == NULL)
+ return ;
+
+ JNIEnv *env = NULL;
+ jint res;
+ res = (*jvm)->AttachCurrentThread(jvm, (void **)&env, NULL);
+ if(res < 0)
+ {
+ fprintf(stderr, "Attach VM Thread failed\n");
+ return ;
+ }
+
+ jclass cls = (*env)->GetObjectClass(env, callback);
+ jmethodID mid = (*env)->GetMethodID(env, cls, "onRead", "()V");
+ (*env)->CallVoidMethod(env, callback, mid);
+ (*jvm)->DetachCurrentThread(jvm);
+}
+
+/* return values:
+ * >0 fd
+ * -1 can't get file descriptor
+ * -2 device busy
+ * -3 invalid baudrate
+ * */
+int serial_open(const char* device, int baud) {
+
+ int fd = open(device, O_RDWR | O_NOCTTY | O_NONBLOCK);
+
+ if (fd < 0) {
+ perror(device);
+ return -1;
+ }
+
+ if (flock(fd, LOCK_EX | LOCK_NB) < 0) {
+ perror(device);
+ return -2;
+ }
+
+ speed_t bd;
+ switch (baud) {
+ case 50: bd = B50; break;
+ case 75: bd = B75; break;
+ case 110: bd = B110; break;
+ case 134: bd = B134; break;
+ case 150: bd = B150; break;
+ case 200: bd = B200; break;
+ case 300: bd = B300; break;
+ case 600: bd = B600; break;
+ case 1200: bd = B1200; break;
+ case 1800: bd = B1800; break;
+ case 2400: bd = B2400; break;
+ case 4800: bd = B4800; break;
+ case 9600: bd = B9600; break;
+ case 19200: bd = B19200; break;
+ case 38400: bd = B38400; break;
+ case 57600: bd = B57600; break;
+ case 115200: bd = B115200; break;
+ case 230400: bd = B230400; break;
+ default: puts("invalid baudrate"); return -3; break;
+ }
+
+ struct sigaction saio;
+ saio.sa_handler = signal_handler;
+ sigemptyset(&saio.sa_mask);
+ saio.sa_flags = 0;
+ saio.sa_restorer = NULL;
+ sigaction(SIGIO,&saio,NULL);
+
+ /* allow the process to receive SIGIO */
+ fcntl(fd, F_SETOWN, getpid());
+
+ /* send SIGIO whenever input or output becomes available */
+ fcntl(fd, F_SETFL, FASYNC);
+
+ /* configure new port settings */
+ struct termios newtio;
+ newtio.c_cflag &= ~PARENB;
+ newtio.c_cflag &= ~CSTOPB;
+ newtio.c_cflag &= ~CSIZE;
+ newtio.c_cflag |= CS8;
+ // no flow control
+ newtio.c_cflag &= ~CRTSCTS;
+ newtio.c_cflag &= ~HUPCL; // disable hang-up-on-close to avoid reset
+ newtio.c_cflag |= CREAD | CLOCAL; // turn on READ & ignore ctrl lines
+ newtio.c_iflag &= ~(IXON | IXOFF | IXANY); // turn off s/w flow ctrl
+ newtio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // make raw
+ newtio.c_oflag &= ~OPOST; // make raw
+
+ // see: http://unixwiz.net/techtips/termios-vmin-vtime.html
+ newtio.c_cc[VMIN] = 1;
+ newtio.c_cc[VTIME] = 2*10/baud;
+ cfsetspeed(&newtio, bd);
+
+ /* load new settings to port */
+ tcflush(fd, TCIFLUSH);
+ tcsetattr(fd,TCSANOW,&newtio);
+
+ return fd;
+}
+
+void serial_close(int fd) {
+ if (flock(fd, LOCK_UN) < 0 ) {
+ perror("");
+ }
+ if (close(fd) < 0 ) {
+ perror("");
+ }
+}
+
+
+// JNI bindings
+// ============
+
+JNIEXPORT jint JNICALL Java_com_github_jodersky_flow_NativeSerial_open
+ (JNIEnv *env, jclass clazz, jstring device, jint baud, jobject reader)
+{
+ const char *dev = (*env)->GetStringUTFChars(env, device, 0);
+ int r = serial_open(dev, baud);
+ (*env)->ReleaseStringUTFChars(env, device, dev);
+ (*env)->GetJavaVM(env, &jvm);
+ /* upgrade callback to global ref */
+ callback = (*env)->NewGlobalRef(env, reader);
+ return r;
+}
+
+/*
+ * Class: com_github_jodersky_flow_NativeSerial
+ * Method: close
+ * Signature: (I)I
+ */
+JNIEXPORT void JNICALL Java_com_github_jodersky_flow_NativeSerial_close
+ (JNIEnv * env, jclass clazz, jint fd)
+{
+ serial_close(fd);
+}
diff --git a/src/main/c/flow.c b/src/main/c/flow.c
new file mode 100644
index 0000000..3ed66b6
--- /dev/null
+++ b/src/main/c/flow.c
@@ -0,0 +1,292 @@
+/*
+ * Copyright (C) 2013 Jakob Odersky
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of the nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <errno.h>
+#include <termios.h>
+#include <fcntl.h>
+#include <poll.h>
+#include "com_github_jodersky_flow_NativeSerial.h"
+
+#define E_PERMISSION -1
+#define E_OPEN -2
+#define E_BUSY -3
+#define E_BAUD -4
+#define E_PIPE -5
+#define E_MALLOC -6
+#define E_POINTER -7
+#define E_POLL -8
+#define E_IO -9
+#define E_CLOSE -10
+
+
+static bool debug = false;
+#define DEBUG(f) if (debug) {f;}
+
+//contains file descriptors used in managing a serial port
+struct serial_config {
+
+ int fd; //serial port
+ int pipe_read; //event
+ int pipe_write; //event
+
+};
+
+/* return values:
+ * 0 ok
+ * E_PERMISSION don't have permission to open
+ * E_OPEN can't get file descriptor
+ * E_BUSY device busy
+ * E_BAUD invalid baudrate
+ * E_PIPE can't open pipe for graceful closing
+ * E_MALLOC malloc error
+ */
+int serial_open(const char* device, int baud, struct serial_config** serial) {
+
+ int fd = open(device, O_RDWR | O_NOCTTY | O_NONBLOCK);
+
+ if (fd < 0) {
+ DEBUG(perror(device));
+ if (errno == EACCES) return E_PERMISSION;
+ else return E_OPEN;
+ }
+
+ if (flock(fd, LOCK_EX | LOCK_NB) < 0) {
+ DEBUG(perror(device));
+ return E_BUSY;
+ }
+
+ speed_t bd;
+ switch (baud) {
+ case 50: bd = B50; break;
+ case 75: bd = B75; break;
+ case 110: bd = B110; break;
+ case 134: bd = B134; break;
+ case 150: bd = B150; break;
+ case 200: bd = B200; break;
+ case 300: bd = B300; break;
+ case 600: bd = B600; break;
+ case 1200: bd = B1200; break;
+ case 1800: bd = B1800; break;
+ case 2400: bd = B2400; break;
+ case 4800: bd = B4800; break;
+ case 9600: bd = B9600; break;
+ case 19200: bd = B19200; break;
+ case 38400: bd = B38400; break;
+ case 57600: bd = B57600; break;
+ case 115200: bd = B115200; break;
+ case 230400: bd = B230400; break;
+ default: return E_BAUD; break;
+ }
+
+ /* configure new port settings */
+ struct termios newtio;
+ newtio.c_cflag &= ~(PARENB | CSTOPB | CSIZE | CRTSCTS); // 8N1
+ newtio.c_cflag |= CS8 | CREAD | CLOCAL;
+ newtio.c_iflag &= ~(IXON | IXOFF | IXANY); // turn off s/w flow ctrl
+ newtio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // make raw
+ newtio.c_oflag &= ~OPOST; // make raw
+
+ // see: http://unixwiz.net/techtips/termios-vmin-vtime.html
+ //newtio.c_cc[VMIN] = 1;
+ //newtio.c_cc[VTIME] = 2*10/baud;
+ cfsetspeed(&newtio, bd);
+
+ /* load new settings to port */
+ tcflush(fd, TCIOFLUSH);
+ tcsetattr(fd,TCSANOW,&newtio);
+
+ int pipe_fd[2];
+ if (pipe2(pipe_fd, O_NONBLOCK) < 0) {
+ DEBUG(perror(device));
+ return E_PIPE;
+ }
+
+ struct serial_config* s = malloc(sizeof(s));
+ if (s == NULL) {
+ return E_MALLOC;
+ }
+
+ s->fd = fd;
+ s->pipe_read = pipe_fd[0];
+ s->pipe_write = pipe_fd[1];
+ (*serial) = s;
+
+ return 0;
+}
+
+void serial_close(struct serial_config* serial) {
+
+ if (serial == NULL) return;
+
+ int data = 0xffffffff;
+
+ //write to pipe to wake up any blocked read thread (self-pipe trick)
+ write(serial->pipe_write, &data, 1);
+
+ close(serial->pipe_write);
+ close(serial->pipe_read);
+
+ flock(serial->fd, LOCK_UN);
+ close(serial->fd);
+
+ free(serial);
+}
+
+/* return
+ * >0 number of bytes read
+ * E_POINTER invalid serial pointer
+ * E_POLL poll error
+ * E_IO read error
+ * E_CLOSE close request
+ */
+int serial_read(struct serial_config* serial, unsigned char * buffer, size_t size) {
+ if (serial == NULL) {
+ return E_POINTER;
+ }
+
+ struct pollfd sp; //serial poll
+ sp.fd = serial->fd;
+ sp.events = POLLIN;
+
+ struct pollfd pp; //pipe poll
+ pp.fd = serial->pipe_read;
+ pp.events = POLLIN;
+
+ struct pollfd poll_list[] = {sp, pp};
+
+ int n = poll(poll_list,(unsigned long)3,-1);
+ if (n < 0) {
+ DEBUG(perror("read"));
+ return E_IO;
+ }
+
+ if (sp.revents & POLLIN != 0) {
+ int r = read(sp.fd, buffer, size);
+
+ //treat 0 bytes read as an error to avoid problems on disconnect
+ //anyway, after a poll there should be more than 0 bytes available to read
+ if (r <= 0) {
+ if (r < 0) DEBUG(perror("read"));
+ return E_IO;
+ }
+ return r;
+ } else {
+ return E_CLOSE;
+ }
+}
+
+/*return
+ * >0 number of bytes written
+ * E_POINTER invalid serial config (null pointer)
+ * E_IO write error
+ */
+int serial_write(struct serial_config* serial, unsigned char* data, size_t size) {
+ if (serial == NULL) return E_POINTER;
+
+ int r = write(serial->fd, data, size);
+ if (r < 0) {
+ DEBUG(perror("write"));
+ return E_IO;
+ }
+ return r;
+}
+
+
+
+// JNI bindings
+// ============
+
+inline struct serial_config* j2s(jlong pointer) {
+ return (struct serial_config*) pointer;
+}
+
+inline jlong s2j(struct serial_config* pointer) {
+ return (jlong) pointer;
+}
+
+JNIEXPORT jint JNICALL Java_com_github_jodersky_flow_NativeSerial_open
+ (JNIEnv *env, jclass clazz, jstring device, jint baud, jlongArray jserialp)
+{
+ const char *dev = (*env)->GetStringUTFChars(env, device, 0);
+ struct serial_config* serial;
+ int r = serial_open(dev, baud, &serial);
+ (*env)->ReleaseStringUTFChars(env, device, dev);
+
+ long serialp = s2j(serial);
+ (*env)->SetLongArrayRegion(env, jserialp, 0, 1, &serialp);
+
+ return r;
+}
+
+JNIEXPORT void JNICALL Java_com_github_jodersky_flow_NativeSerial_close
+ (JNIEnv * env, jclass clazz, jlong serial)
+{
+ serial_close(j2s(serial));
+}
+
+JNIEXPORT jint JNICALL Java_com_github_jodersky_flow_NativeSerial_read
+ (JNIEnv * env, jclass clazz, jlong serial, jbyteArray jbuffer)
+{
+
+ jsize size = (*env)->GetArrayLength(env, jbuffer);
+
+ unsigned char buffer[size];
+ int n = serial_read(j2s(serial), buffer, size);
+ if (n < 0) {
+ return n;
+ }
+
+ (*env)->SetByteArrayRegion(env, jbuffer, 0, n, (signed char *) buffer);
+ return n;
+}
+
+JNIEXPORT jint JNICALL Java_com_github_jodersky_flow_NativeSerial_write
+ (JNIEnv * env, jclass clazz, jlong serial, jbyteArray jbuffer)
+{
+ unsigned char * buffer = (*env)->GetByteArrayElements(env, jbuffer, NULL);
+ int size = (*env)->GetArrayLength(env, jbuffer);
+ int r = serial_write(j2s(serial), buffer, size);
+
+ (*env)->ReleaseByteArrayElements(env, jbuffer, buffer, JNI_ABORT);
+
+ return r;
+}
+
+JNIEXPORT void JNICALL Java_com_github_jodersky_flow_NativeSerial_debug
+ (JNIEnv *env, jclass clazz, jboolean value)
+{
+ debug = (bool) value;
+}
+
diff --git a/src/main/java/com/github/jodersky/flow/NativeSerial.java b/src/main/java/com/github/jodersky/flow/NativeSerial.java
new file mode 100644
index 0000000..10a82c9
--- /dev/null
+++ b/src/main/java/com/github/jodersky/flow/NativeSerial.java
@@ -0,0 +1,49 @@
+package com.github.jodersky.flow;
+
+public class NativeSerial {
+
+ static {
+ System.loadLibrary("flow");
+ }
+
+ final static int E_PERMISSION = -1;
+ final static int E_OPEN = -2;
+ final static int E_BUSY = -3;
+ final static int E_BAUD = -4;
+ final static int E_PIPE = -5;
+ final static int E_MALLOC = -6;
+ final static int E_POINTER = -7;
+ final static int E_POLL = -8;
+ final static int E_IO = -9;
+ final static int E_CLOSE = -10;
+
+
+ /* return values:
+ * 0 ok
+ * E_PERMISSION don't have permission to open
+ * E_OPEN can't get file descriptor
+ * E_BUSY device busy
+ * E_BAUD invalid baudrate
+ * E_PIPE can't open pipe for graceful closing
+ * E_MALLOC malloc error */
+ native static int open(String device, int baud, long[] serial);
+
+ /* return
+ * >0 number of bytes read
+ * E_POINTER invalid serial pointer
+ * E_POLL poll error
+ * E_IO read error
+ * E_CLOSE close request */
+ native static int read(long serial, byte[] buffer);
+
+ /*return
+ * >0 number of bytes written
+ * E_POINTER invalid serial config (null pointer)
+ * E_IO write error */
+ native static int write(long serial, byte[] buffer);
+
+ native static void close(long serial);
+
+ native static void debug(boolean value);
+
+}
diff --git a/src/main/scala/com/github/jodersky/flow/Main.scala b/src/main/scala/com/github/jodersky/flow/Main.scala
new file mode 100644
index 0000000..feea51b
--- /dev/null
+++ b/src/main/scala/com/github/jodersky/flow/Main.scala
@@ -0,0 +1,26 @@
+package com.github.jodersky.flow
+
+import scala.concurrent._
+import scala.concurrent.ExecutionContext.Implicits.global
+
+object Main {
+
+ def main(args: Array[String]): Unit = {
+ println("opening")
+
+ Serial.debug(true)
+ val s = Serial.open("/dev/ttyACM0", 115200){ data =>
+ println(data.mkString("[",",","]"))
+ }
+ println("press enter to write a loooooooooong array of data")
+ Console.readLine()
+
+ val data: Array[Byte] = Array.fill(1000000)(42)
+ s.write(data).map(d => println("sent: " + d.mkString("[",",","]"))).recover{case t => println("write error")}
+
+ println("press enter to exit")
+ Console.readLine()
+ println("exiting")
+ Console.flush()
+ }
+} \ No newline at end of file
diff --git a/src/main/scala/com/github/jodersky/flow/Serial.scala b/src/main/scala/com/github/jodersky/flow/Serial.scala
new file mode 100644
index 0000000..7565cbd
--- /dev/null
+++ b/src/main/scala/com/github/jodersky/flow/Serial.scala
@@ -0,0 +1,63 @@
+package com.github.jodersky.flow
+
+import scala.concurrent._
+import scala.concurrent.ExecutionContext.Implicits._
+import java.io.IOException
+
+class Serial private (val port: String, private val pointer: Long, reader: Array[Byte] => Unit) {
+ future {
+ var n = 0
+ val buffer = new Array[Byte](100)
+ while (n >= 0) {
+ n = NativeSerial.read(pointer, buffer)
+ import NativeSerial._
+ n match {
+ case E_POINTER => throw new NullPointerException("pointer to native serial")
+ case E_POLL => throw new IOException(port + ": polling")
+ case E_IO => throw new IOException(port + ": reading")
+ case E_CLOSE => println("close request")
+ case x if x > 0 => reader(buffer.take(n))
+ }
+ }
+ }
+
+ private def writeBlock(data: Array[Byte]) = synchronized {
+ import NativeSerial._
+ NativeSerial.write(pointer, data) match {
+ case E_POINTER => throw new NullPointerException("pointer to native serial")
+ case E_IO => throw new IOException(port + ": writing")
+ case r => data.take(r)
+ }
+ }
+
+ def write(data: Array[Byte]) = future { writeBlock(data) }
+
+ def close() = synchronized {
+ NativeSerial.close(pointer)
+ }
+
+}
+
+object Serial {
+
+ def open(port: String, baud: Int)(reader: Array[Byte] => Unit) = synchronized {
+ val pointer = new Array[Long](1)
+ val result = NativeSerial.open(port, baud, pointer)
+
+ import NativeSerial._
+ result match {
+ case E_PERMISSION => throw new AccessDeniedException(port)
+ case E_OPEN => throw new NoSuchPortException(port)
+ case E_BUSY => throw new PortInUseException(port)
+ case E_BAUD => throw new IllegalArgumentException(
+ s"invalid baudrate ${baud}, use standard unix values")
+ case E_PIPE => throw new IOException("cannot create pipe")
+ case E_MALLOC => throw new IOException("cannot allocate memory")
+ case 0 => new Serial(port, pointer(0), reader)
+ case _ => throw new Exception("cannot open port")
+ }
+ }
+
+ def debug(value: Boolean) = NativeSerial.debug(value)
+
+} \ No newline at end of file
diff --git a/src/main/scala/com/github/jodersky/flow/exceptions.scala b/src/main/scala/com/github/jodersky/flow/exceptions.scala
new file mode 100644
index 0000000..a360079
--- /dev/null
+++ b/src/main/scala/com/github/jodersky/flow/exceptions.scala
@@ -0,0 +1,7 @@
+package com.github.jodersky.flow
+
+import java.io.IOException
+
+class NoSuchPortException(message: String) extends IOException(message)
+class PortInUseException(message: String) extends IOException(message)
+class AccessDeniedException(message: String) extends IOException(message) \ No newline at end of file