diff options
-rw-r--r-- | .gitignore | 20 | ||||
-rw-r--r-- | Makefile | 19 | ||||
-rw-r--r-- | build.sbt | 9 | ||||
-rw-r--r-- | src/main/c/com_github_jodersky_flow_NativeSerial.h | 73 | ||||
-rw-r--r-- | src/main/c/flow-aio.c | 149 | ||||
-rw-r--r-- | src/main/c/flow-epoll.c | 220 | ||||
-rw-r--r-- | src/main/c/flow-signal.c | 153 | ||||
-rw-r--r-- | src/main/c/flow.c | 292 | ||||
-rw-r--r-- | src/main/java/com/github/jodersky/flow/NativeSerial.java | 49 | ||||
-rw-r--r-- | src/main/scala/com/github/jodersky/flow/Main.scala | 26 | ||||
-rw-r--r-- | src/main/scala/com/github/jodersky/flow/Serial.scala | 63 | ||||
-rw-r--r-- | src/main/scala/com/github/jodersky/flow/exceptions.scala | 7 |
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 |