diff --git a/build/build.xml b/build/build.xml
index c37280abb..b5127f0ff 100644
--- a/build/build.xml
+++ b/build/build.xml
@@ -341,6 +341,7 @@
+
@@ -363,7 +364,12 @@
-
+
+
+
+
+
+
@@ -759,7 +765,7 @@
-
+
@@ -1296,7 +1302,7 @@ remove the spaces for depth since it should be double dash, but screws up commen
-
+
diff --git a/java/libraries/io/build.xml b/java/libraries/io/build.xml
new file mode 100644
index 000000000..f1fcb4008
--- /dev/null
+++ b/java/libraries/io/build.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/java/libraries/io/examples/I2CCompass/I2CCompass.pde b/java/libraries/io/examples/I2CCompass/I2CCompass.pde
new file mode 100644
index 000000000..c4f9e0cc8
--- /dev/null
+++ b/java/libraries/io/examples/I2CCompass/I2CCompass.pde
@@ -0,0 +1,42 @@
+import processing.io.*;
+I2C i2c;
+
+// HMC6352 is a digital compass module using I2C
+// datasheet: https://www.sparkfun.com/datasheets/Components/HMC6352.pdf
+
+void setup() {
+ //printArray(I2C.list());
+ i2c = new I2C(I2C.list()[0]);
+ setHeadingMode();
+}
+
+void draw() {
+ background(255);
+ float deg = getHeading();
+ println(deg + " degrees");
+ line(width/2, height/2, width/2+sin(radians(deg))*width/2, height/2-cos(radians(deg))*height/2);
+}
+
+void setHeadingMode() {
+ i2c.beginTransmission(0x21);
+ // command byte for writing to EEPROM
+ i2c.write(0x77);
+ // address of the output data control byte
+ i2c.write(0x4e);
+ // give us the plain heading
+ i2c.write(0x00);
+ i2c.endTransmission();
+}
+
+float getHeading() {
+ i2c.beginTransmission(0x21);
+ // command byte for reading the data
+ i2c.write(0x41);
+ byte[] in = i2c.read(2);
+ i2c.endTransmission();
+ // put bytes together to tenth of degrees
+ // & 0xff makes sure the byte is not interpreted as a negative value
+ int deg = (in[0] & 0xff) << 8 | (in[1] & 0xff);
+ // return degrees
+ return deg / 10.0;
+}
diff --git a/java/libraries/io/examples/I2CDigitalAnalog/I2CDigitalAnalog.pde b/java/libraries/io/examples/I2CDigitalAnalog/I2CDigitalAnalog.pde
new file mode 100644
index 000000000..66f034f61
--- /dev/null
+++ b/java/libraries/io/examples/I2CDigitalAnalog/I2CDigitalAnalog.pde
@@ -0,0 +1,27 @@
+import processing.io.*;
+I2C i2c;
+
+// MCP4725 is a Digital-to-Analog converter using I2C
+// datasheet: http://ww1.microchip.com/downloads/en/DeviceDoc/22039d.pdf
+
+void setup() {
+ //printArray(I2C.list());
+ i2c = new I2C(I2C.list()[0]);
+}
+
+void draw() {
+ background(map(mouseX, 0, width, 0, 255));
+ setAnalog(map(mouseX, 0, width, 0.0, 1.0));
+}
+
+// outputs voltages from 0V to the supply voltage
+// (works with 3.3V and 5V)
+void setAnalog(float fac) {
+ fac = constrain(fac, 0.0, 1.0);
+ // convert to 12 bit value
+ int val = int(4095 * fac);
+ i2c.beginTransmission(0x60);
+ i2c.write(val >> 8);
+ i2c.write(val & 255);
+ i2c.endTransmission();
+}
diff --git a/java/libraries/io/examples/I2CDigitalAnalogOOP/I2CDigitalAnalogOOP.pde b/java/libraries/io/examples/I2CDigitalAnalogOOP/I2CDigitalAnalogOOP.pde
new file mode 100644
index 000000000..2c69daec4
--- /dev/null
+++ b/java/libraries/io/examples/I2CDigitalAnalogOOP/I2CDigitalAnalogOOP.pde
@@ -0,0 +1,12 @@
+import processing.io.*;
+MCP4725 dac;
+
+void setup() {
+ //printArray(I2C.list());
+ dac = new MCP4725(I2C.list()[0], 0x60);
+}
+
+void draw() {
+ background(map(mouseX, 0, width, 0, 255));
+ dac.setAnalog(map(mouseX, 0, width, 0.0, 1.0));
+}
diff --git a/java/libraries/io/examples/I2CDigitalAnalogOOP/MCP4725.pde b/java/libraries/io/examples/I2CDigitalAnalogOOP/MCP4725.pde
new file mode 100644
index 000000000..67c50175f
--- /dev/null
+++ b/java/libraries/io/examples/I2CDigitalAnalogOOP/MCP4725.pde
@@ -0,0 +1,27 @@
+import processing.io.I2C;
+
+// MCP4725 is a Digital-to-Analog converter using I2C
+// datasheet: http://ww1.microchip.com/downloads/en/DeviceDoc/22039d.pdf
+
+class MCP4725 extends I2C {
+ int address;
+
+ // there can be more than one device connected to the bus
+ // as long as they have different addresses
+ MCP4725(String dev, int address) {
+ super(dev);
+ this.address = address;
+ }
+
+ // outputs voltages from 0V to the supply voltage
+ // (works with 3.3V and 5V)
+ void setAnalog(float fac) {
+ fac = constrain(fac, 0.0, 1.0);
+ // convert to 12 bit value
+ int val = int(4095 * fac);
+ beginTransmission(address);
+ write(val >> 8);
+ write(val & 255);
+ endTransmission();
+ }
+}
diff --git a/java/libraries/io/examples/Interrupt/Interrupt.pde b/java/libraries/io/examples/Interrupt/Interrupt.pde
new file mode 100644
index 000000000..9781fb0bc
--- /dev/null
+++ b/java/libraries/io/examples/Interrupt/Interrupt.pde
@@ -0,0 +1,25 @@
+import processing.io.*;
+color bgcolor = 0;
+
+// RPI.PIN7 refers to the physical pin 7 on the Raspberry Pi's
+// pin header, which is located on the fourth row, above one of
+// the Ground pins
+
+void setup() {
+ GPIO.pinMode(RPI.PIN7, GPIO.INPUT);
+ GPIO.attachInterrupt(RPI.PIN7, this, "pinEvent", GPIO.RISING);
+}
+
+void draw() {
+ background(bgcolor);
+}
+
+// this function will be called whenever pin 7 is brought from LOW to HIGH
+void pinEvent(int pin) {
+ println("Received interrupt");
+ if (bgcolor == 0) {
+ bgcolor = color(255);
+ } else {
+ bgcolor = color(0);
+ }
+}
diff --git a/java/libraries/io/examples/LedCounter/LedCounter.pde b/java/libraries/io/examples/LedCounter/LedCounter.pde
new file mode 100644
index 000000000..4bc41e60f
--- /dev/null
+++ b/java/libraries/io/examples/LedCounter/LedCounter.pde
@@ -0,0 +1,39 @@
+import processing.io.*;
+LED leds[];
+
+// the Raspberry Pi has two build-in LEDs we can control
+// led0 (green) and led1 (red)
+
+void setup() {
+ String available[] = LED.list();
+ print("Available: ");
+ println(available);
+
+ // create an object for each LED and store it in an array
+ leds = new LED[available.length];
+ for (int i=0; i < available.length; i++) {
+ leds[i] = new LED(available[i]);
+ }
+
+ frameRate(1);
+}
+
+void draw() {
+ // make the LEDs count in binary
+ for (int i=0; i < leds.length; i++) {
+ if ((frameCount & (1 << i)) != 0) {
+ leds[i].set(1.0);
+ } else {
+ leds[i].set(0.0);
+ }
+ }
+ println(frameCount);
+}
+
+void keyPressed() {
+ // cleanup
+ for (int i=0; i < leds.length; i++) {
+ leds[i].close();
+ }
+ exit();
+}
diff --git a/java/libraries/io/examples/SPIAnalogDigital/SPIAnalogDigital.pde b/java/libraries/io/examples/SPIAnalogDigital/SPIAnalogDigital.pde
new file mode 100644
index 000000000..4e8ee3b4a
--- /dev/null
+++ b/java/libraries/io/examples/SPIAnalogDigital/SPIAnalogDigital.pde
@@ -0,0 +1,21 @@
+import processing.io.*;
+SPI spi;
+
+// MCP3001 is a Analog-to-Digital converter using SPI
+// datasheet: http://ww1.microchip.com/downloads/en/DeviceDoc/21293C.pdf
+
+void setup() {
+ //printArray(SPI.list());
+ spi = new SPI(SPI.list()[0]);
+ spi.settings(500000, SPI.MSBFIRST, SPI.MODE0);
+}
+
+void draw() {
+ // dummy write, actual values don't matter
+ byte[] out = { 0, 0 };
+ byte[] in = spi.transfer(out);
+ // some input bit shifting according to the datasheet p. 16
+ int val = ((in[0] & 0x1f) << 5) | ((in[1] & 0xf8) >> 3);
+ // val is between 0 and 1023
+ background(map(val, 0, 1023, 0, 255));
+}
diff --git a/java/libraries/io/examples/SPIAnalogDigitalOOP/MCP3001.pde b/java/libraries/io/examples/SPIAnalogDigitalOOP/MCP3001.pde
new file mode 100644
index 000000000..e25869633
--- /dev/null
+++ b/java/libraries/io/examples/SPIAnalogDigitalOOP/MCP3001.pde
@@ -0,0 +1,22 @@
+import processing.io.SPI;
+
+// MCP3001 is a Analog-to-Digital converter using SPI
+// datasheet: http://ww1.microchip.com/downloads/en/DeviceDoc/21293C.pdf
+
+class MCP3001 extends SPI {
+
+ MCP3001(String dev) {
+ super(dev);
+ super.settings(500000, SPI.MSBFIRST, SPI.MODE0);
+ }
+
+ float getAnalog() {
+ // dummy write, actual values don't matter
+ byte[] out = { 0, 0 };
+ byte[] in = super.transfer(out);
+ // some input bit shifting according to the datasheet p. 16
+ int val = ((in[0] & 0x1f) << 5) | ((in[1] & 0xf8) >> 3);
+ // val is between 0 and 1023
+ return val/1023.0;
+ }
+}
diff --git a/java/libraries/io/examples/SPIAnalogDigitalOOP/SPIAnalogDigitalOOP.pde b/java/libraries/io/examples/SPIAnalogDigitalOOP/SPIAnalogDigitalOOP.pde
new file mode 100644
index 000000000..85be661a6
--- /dev/null
+++ b/java/libraries/io/examples/SPIAnalogDigitalOOP/SPIAnalogDigitalOOP.pde
@@ -0,0 +1,11 @@
+import processing.io.*;
+MCP3001 adc;
+
+void setup() {
+ //printArray(SPI.list());
+ adc = new MCP3001(SPI.list()[0]);
+}
+
+void draw() {
+ background(adc.getAnalog() * 255);
+}
diff --git a/java/libraries/io/examples/SimpleInput/SimpleInput.pde b/java/libraries/io/examples/SimpleInput/SimpleInput.pde
new file mode 100644
index 000000000..be1c78344
--- /dev/null
+++ b/java/libraries/io/examples/SimpleInput/SimpleInput.pde
@@ -0,0 +1,22 @@
+import processing.io.*;
+
+// RPI.PIN7 refers to the physical pin 7 on the Raspberry Pi's
+// pin header, which is located on the fourth row, above one of
+// the Ground pins
+
+void setup() {
+ GPIO.pinMode(RPI.PIN7, GPIO.INPUT);
+ // this is equivalent to addressing the pin with its GPIO number:
+ // GPIO.pinMode(4, GPIO.INPUT);
+}
+
+void draw() {
+ // sense the input pin
+ if (GPIO.digitalRead(RPI.PIN7) == GPIO.HIGH) {
+ fill(255);
+ } else {
+ fill(204);
+ }
+ stroke(255);
+ ellipse(width/2, height/2, width*0.75, height*0.75);
+}
diff --git a/java/libraries/io/examples/SimpleOutput/SimpleOutput.pde b/java/libraries/io/examples/SimpleOutput/SimpleOutput.pde
new file mode 100644
index 000000000..84c22bcdf
--- /dev/null
+++ b/java/libraries/io/examples/SimpleOutput/SimpleOutput.pde
@@ -0,0 +1,27 @@
+import processing.io.*;
+boolean ledOn = false;
+
+// RPI.PIN7 refers to the physical pin 7 on the Raspberry Pi's
+// pin header, which is located on the fourth row, above one of
+// the Ground pins
+
+void setup() {
+ GPIO.pinMode(RPI.PIN7, GPIO.OUTPUT);
+ // this is equivalent to addressing the pin with its GPIO number:
+ // GPIO.pinMode(4, GPIO.OUTPUT);
+ frameRate(0.5);
+}
+
+void draw() {
+ // make the LED blink
+ ledOn = !ledOn;
+ if (ledOn) {
+ GPIO.digitalWrite(RPI.PIN7, GPIO.LOW);
+ fill(204);
+ } else {
+ GPIO.digitalWrite(RPI.PIN7, GPIO.HIGH);
+ fill(255);
+ }
+ stroke(255);
+ ellipse(width/2, height/2, width*0.75, height*0.75);
+}
diff --git a/java/libraries/io/library.properties b/java/libraries/io/library.properties
new file mode 100644
index 000000000..9b276f0ca
--- /dev/null
+++ b/java/libraries/io/library.properties
@@ -0,0 +1,2 @@
+name = I/O
+version = 1
diff --git a/java/libraries/io/library/export.txt b/java/libraries/io/library/export.txt
new file mode 100644
index 000000000..0e434e57f
--- /dev/null
+++ b/java/libraries/io/library/export.txt
@@ -0,0 +1 @@
+name = I/O for Raspberry Pi and other Linux-based computers
diff --git a/java/libraries/io/src/native/Makefile b/java/libraries/io/src/native/Makefile
new file mode 100644
index 000000000..101a20ee5
--- /dev/null
+++ b/java/libraries/io/src/native/Makefile
@@ -0,0 +1,17 @@
+TARGET := libprocessing-io.so
+OBJS := impl.o
+CC := gcc
+
+CFLAGS := -std=c99 -fPIC -g
+CFLAGS += -I$(shell dirname $(shell realpath $(shell which javac)))/../include
+CFLAGS += -I$(shell dirname $(shell realpath $(shell which javac)))/../include/linux
+LDFLAGS := -shared
+
+$(TARGET): $(OBJS)
+ $(CC) $(CFLAGS) $(LDFLAGS) $^ -o $@
+
+iface.h:
+ javah -classpath .. -o iface.h processing.io.NativeInterface
+
+clean:
+ rm -f $(TARGET) $(OBJS)
diff --git a/java/libraries/io/src/native/iface.h b/java/libraries/io/src/native/iface.h
new file mode 100644
index 000000000..0cda66685
--- /dev/null
+++ b/java/libraries/io/src/native/iface.h
@@ -0,0 +1,85 @@
+/* DO NOT EDIT THIS FILE - it is machine generated */
+#include
+/* Header for class processing_io_NativeInterface */
+
+#ifndef _Included_processing_io_NativeInterface
+#define _Included_processing_io_NativeInterface
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * Class: processing_io_NativeInterface
+ * Method: openDevice
+ * Signature: (Ljava/lang/String;)I
+ */
+JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_openDevice
+ (JNIEnv *, jclass, jstring);
+
+/*
+ * Class: processing_io_NativeInterface
+ * Method: getError
+ * Signature: (I)Ljava/lang/String;
+ */
+JNIEXPORT jstring JNICALL Java_processing_io_NativeInterface_getError
+ (JNIEnv *, jclass, jint);
+
+/*
+ * Class: processing_io_NativeInterface
+ * Method: closeDevice
+ * Signature: (I)I
+ */
+JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_closeDevice
+ (JNIEnv *, jclass, jint);
+
+/*
+ * Class: processing_io_NativeInterface
+ * Method: readFile
+ * Signature: (Ljava/lang/String;[B)I
+ */
+JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_readFile
+ (JNIEnv *, jclass, jstring, jbyteArray);
+
+/*
+ * Class: processing_io_NativeInterface
+ * Method: writeFile
+ * Signature: (Ljava/lang/String;[B)I
+ */
+JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_writeFile
+ (JNIEnv *, jclass, jstring, jbyteArray);
+
+/*
+ * Class: processing_io_NativeInterface
+ * Method: pollDevice
+ * Signature: (Ljava/lang/String;I)I
+ */
+JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_pollDevice
+ (JNIEnv *, jclass, jstring, jint);
+
+/*
+ * Class: processing_io_NativeInterface
+ * Method: transferI2c
+ * Signature: (II[B[B)I
+ */
+JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_transferI2c
+ (JNIEnv *, jclass, jint, jint, jbyteArray, jbyteArray);
+
+/*
+ * Class: processing_io_NativeInterface
+ * Method: setSpiSettings
+ * Signature: (IIII)I
+ */
+JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_setSpiSettings
+ (JNIEnv *, jclass, jint, jint, jint, jint);
+
+/*
+ * Class: processing_io_NativeInterface
+ * Method: transferSpi
+ * Signature: (I[B[B)I
+ */
+JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_transferSpi
+ (JNIEnv *, jclass, jint, jbyteArray, jbyteArray);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/java/libraries/io/src/native/impl.c b/java/libraries/io/src/native/impl.c
new file mode 100644
index 000000000..86dcf50bb
--- /dev/null
+++ b/java/libraries/io/src/native/impl.c
@@ -0,0 +1,241 @@
+/*
+ Copyright (c) The Processing Foundation 2015
+ I/O library developed by Gottfried Haider as part of GSOC 2015
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General
+ Public License along with this library; if not, write to the
+ Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ Boston, MA 02111-1307 USA
+*/
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "iface.h"
+
+
+JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_openDevice
+ (JNIEnv *env, jclass cls, jstring _fn)
+{
+ const char *fn = (*env)->GetStringUTFChars(env, _fn, JNI_FALSE);
+ int file = open(fn, O_RDWR);
+ (*env)->ReleaseStringUTFChars(env, _fn, fn);
+ if (file < 0) {
+ return -errno;
+ } else {
+ return file;
+ }
+}
+
+
+JNIEXPORT jstring JNICALL Java_processing_io_NativeInterface_getError
+ (JNIEnv *env, jclass cls, jint _errno)
+{
+ char *msg = strerror(abs(_errno));
+ if (msg) {
+ return (*env)->NewStringUTF(env, msg);
+ } else {
+ return NULL;
+ }
+}
+
+
+JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_closeDevice
+ (JNIEnv *env, jclass cls, jint handle)
+{
+ if (close(handle) < 0) {
+ return -errno;
+ } else {
+ return 0;
+ }
+}
+
+
+JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_readFile
+ (JNIEnv *env, jclass cls, jstring _fn, jbyteArray _in)
+{
+ const char *fn = (*env)->GetStringUTFChars(env, _fn, JNI_FALSE);
+ int file = open(fn, O_RDONLY);
+ (*env)->ReleaseStringUTFChars(env, _fn, fn);
+ if (file < 0) {
+ return -errno;
+ }
+
+ jbyte *in = (*env)->GetByteArrayElements(env, _in, NULL);
+ int len = read(file, in, (*env)->GetArrayLength(env, _in));
+ if (len < 0) {
+ len = -errno;
+ }
+ (*env)->ReleaseByteArrayElements(env, _in, in, 0);
+
+ close(file);
+ return len;
+}
+
+
+JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_writeFile
+ (JNIEnv *env, jclass cls, jstring _fn, jbyteArray _out)
+{
+ const char *fn = (*env)->GetStringUTFChars(env, _fn, JNI_FALSE);
+ int file = open(fn, O_WRONLY);
+ (*env)->ReleaseStringUTFChars(env, _fn, fn);
+ if (file < 0) {
+ return -errno;
+ }
+
+ jbyte *out = (*env)->GetByteArrayElements(env, _out, JNI_FALSE);
+ int len = write(file, out, (*env)->GetArrayLength(env, _out));
+ if (len < 0) {
+ len = -errno;
+ }
+ (*env)->ReleaseByteArrayElements(env, _out, out, JNI_ABORT);
+
+ close(file);
+ return len;
+}
+
+
+JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_pollDevice
+ (JNIEnv *env, jclass cls, jstring _fn, jint timeout)
+{
+ const char *fn = (*env)->GetStringUTFChars(env, _fn, JNI_FALSE);
+ int file = open(fn, O_RDONLY|O_NONBLOCK);
+ (*env)->ReleaseStringUTFChars(env, _fn, fn);
+ if (file < 0) {
+ return -errno;
+ }
+
+ // dummy read
+ char tmp;
+ while (0 < read(file, &tmp, 1));
+
+ struct pollfd fds[1];
+ memset(fds, 0, sizeof(fds));
+ fds[0].fd = file;
+ fds[0].events = POLLPRI|POLLERR;
+
+ // and wait
+ int ret = poll(fds, 1, timeout);
+ close(file);
+
+ if (ret < 0) {
+ return -errno;
+ } else if (ret == 0) {
+ // timeout
+ return 0;
+ } else if (fds[0].revents & POLLPRI) {
+ // interrupt
+ return 1;
+ } else {
+ // POLLERR?
+ return -ENOMSG;
+ }
+}
+
+
+JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_transferI2c
+ (JNIEnv *env, jclass cls, jint handle, jint slave, jbyteArray _out, jbyteArray _in)
+{
+ struct i2c_rdwr_ioctl_data packets;
+ struct i2c_msg msgs[2];
+ jbyte *out, *in;
+
+ packets.msgs = msgs;
+
+ msgs[0].addr = slave;
+ msgs[0].flags = 0;
+ msgs[0].len = (*env)->GetArrayLength(env, _out);
+ out = (*env)->GetByteArrayElements(env, _out, NULL);
+ msgs[0].buf = out;
+ if (_in != NULL) {
+ in = (*env)->GetByteArrayElements(env, _in, NULL);
+ msgs[1].addr = slave;
+ msgs[1].flags = I2C_M_RD; // I2C_M_RECV_LEN is not supported
+ msgs[1].len = (*env)->GetArrayLength(env, _in);
+ msgs[1].buf = in;
+ packets.nmsgs = 2;
+ } else {
+ packets.nmsgs = 1;
+ }
+
+ int ret = ioctl(handle, I2C_RDWR, &packets);
+ if (ret < 0) {
+ ret = -errno;
+ }
+
+ (*env)->ReleaseByteArrayElements(env, _out, out, JNI_ABORT);
+ if (_in != NULL) {
+ (*env)->ReleaseByteArrayElements(env, _in, in, 0);
+ }
+
+ return ret;
+}
+
+
+JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_setSpiSettings
+ (JNIEnv *env, jclass cls, jint handle, jint _maxSpeed, jint dataOrder, jint mode)
+{
+ uint8_t tmp;
+ uint32_t maxSpeed;
+
+ tmp = (uint8_t)mode;
+ int ret = ioctl(handle, SPI_IOC_WR_MODE, &tmp);
+ if (ret < 0) {
+ return ret;
+ }
+
+ tmp = (uint8_t)dataOrder;
+ ret = ioctl(handle, SPI_IOC_WR_LSB_FIRST, &tmp);
+ if (ret < 0) {
+ return ret;
+ }
+
+ maxSpeed = (uint32_t)_maxSpeed;
+ ret = ioctl(handle, SPI_IOC_WR_MAX_SPEED_HZ, &maxSpeed);
+ if (ret < 0) {
+ return ret;
+ }
+
+ return 0;
+}
+
+
+JNIEXPORT jint JNICALL Java_processing_io_NativeInterface_transferSpi
+ (JNIEnv *env, jclass cls, jint handle, jbyteArray _out, jbyteArray _in)
+{
+ jbyte* out = (*env)->GetByteArrayElements(env, _out, NULL);
+ jbyte* in = (*env)->GetByteArrayElements(env, _in, NULL);
+
+ struct spi_ioc_transfer xfer = {
+ .tx_buf = (unsigned long)out,
+ .rx_buf = (unsigned long)in,
+ .len = MIN((*env)->GetArrayLength(env, _out), (*env)->GetArrayLength(env, _in)),
+ };
+
+ int ret = ioctl(handle, SPI_IOC_MESSAGE(1), &xfer);
+
+ (*env)->ReleaseByteArrayElements(env, _out, out, JNI_ABORT);
+ (*env)->ReleaseByteArrayElements(env, _in, in, 0);
+
+ return ret;
+}
diff --git a/java/libraries/io/src/processing/io/GPIO.java b/java/libraries/io/src/processing/io/GPIO.java
new file mode 100644
index 000000000..76893a9ea
--- /dev/null
+++ b/java/libraries/io/src/processing/io/GPIO.java
@@ -0,0 +1,545 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+ Copyright (c) The Processing Foundation 2015
+ I/O library developed by Gottfried Haider as part of GSOC 2015
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General
+ Public License along with this library; if not, write to the
+ Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ Boston, MA 02111-1307 USA
+*/
+
+package processing.io;
+
+import processing.core.*;
+import processing.io.NativeInterface;
+
+import java.lang.reflect.Method;
+import java.util.BitSet;
+import java.util.HashMap;
+import java.util.Map;
+
+
+public class GPIO {
+
+ // those constants are generally the same as in Arduino.h
+ public static final int INPUT = 0;
+ public static final int OUTPUT = 1;
+ public static final int INPUT_PULLUP = 2;
+ public static final int INPUT_PULLDOWN = 3;
+
+ public static final int LOW = 0;
+ public static final int HIGH = 1;
+
+ public static final int NONE = 0;
+ public static final int CHANGE = 1; // trigger when level changes
+ public static final int FALLING = 2; // trigger when level changes from high to low
+ public static final int RISING = 3; // trigger when level changes from low to high
+
+ protected static Map irqThreads = new HashMap();
+ protected static boolean serveInterrupts = true;
+ protected static BitSet values = new BitSet();
+
+
+ static {
+ NativeInterface.loadLibrary();
+ }
+
+
+ public static void analogWrite(int pin, int value) {
+ // currently this can't be done in a non-platform-specific way
+ // the best way forward would be implementing a generic, "soft"
+ // PWM in the kernel that uses high resolution timers, similiar
+ // to the patch Bill Gatliff posted, which unfortunately didn't
+ // get picked up, see
+ // https://dev.openwrt.org/browser/trunk/target/linux/generic/files/drivers/pwm/gpio-pwm.c?rev=35328
+
+ // additionally, there currently doesn't seem to be a way to link
+ // a PWM channel back to the GPIO pin it is associated with
+
+ // alternatively, this could be implemented in user-space to some
+ // degree
+ // see http://stackoverflow.com/a/13371570/3030124
+ // see http://raspberrypi.stackexchange.com/a/304
+ throw new RuntimeException("Not yet implemented");
+ }
+
+
+ /**
+ * Calls a function when the value of an INPUT pin changes
+ *
+ * Don't use enableInterrupt() and waitForInterrupt() in combination with
+ * this function, as they are orthogonal. The sketch method provided must
+ * accept a single integer (int) parameter, which is the number of the GPIO
+ * pin that the interrupt occured on.
+ * @param pin GPIO pin
+ * @param parent this
+ * @param method name of sketch method to call
+ * @param mode when to call: GPIO.CHANGE, GPIO.FALLING or GPIO.RISING
+ * @see noInterrupts
+ * @see interrupts
+ * @see releaseInterrupt
+ */
+ public static void attachInterrupt(int pin, PApplet parent, String method, int mode) {
+ if (irqThreads.containsKey(pin)) {
+ throw new RuntimeException("You must call releaseInterrupt before attaching another interrupt on the same pin");
+ }
+
+ enableInterrupt(pin, mode);
+
+ final int irqPin = pin;
+ final PApplet irqObject = parent;
+ final Method irqMethod;
+ try {
+ irqMethod = parent.getClass().getMethod(method, int.class);
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException("Method " + method + " does not exist");
+ }
+
+ // it might be worth checking how Java threads compare to pthreads in terms
+ // of latency
+ Thread t = new Thread(new Runnable() {
+ public void run() {
+ boolean gotInterrupt = false;
+ try {
+ do {
+ try {
+ if (waitForInterrupt(irqPin, 100)) {
+ gotInterrupt = true;
+ }
+ if (gotInterrupt && serveInterrupts) {
+ irqMethod.invoke(irqObject, irqPin);
+ gotInterrupt = false;
+ }
+ // if we received an interrupt while interrupts were disabled
+ // we still deliver it the next time interrupts get enabled
+ // not sure if everyone agrees with this logic though
+ } catch (RuntimeException e) {
+ // make sure we're not busy spinning on error
+ Thread.sleep(100);
+ }
+ } while (!Thread.currentThread().isInterrupted());
+ } catch (Exception e) {
+ // terminate the thread on any unexpected exception that might occur
+ System.err.println("Terminating interrupt handling for pin " + irqPin + " after catching: " + e.getMessage());
+ }
+ }
+ }, "GPIO" + pin + " IRQ");
+
+ t.setPriority(Thread.MAX_PRIORITY);
+ t.start();
+
+ irqThreads.put(pin, t);
+ }
+
+
+ /**
+ * Checks if the GPIO pin number can be valid
+ *
+ * Board-specific classes, such as RPI, assign -1 to pins that carry power,
+ * ground and the like.
+ * @param pin GPIO pin
+ */
+ protected static void checkValidPin(int pin) {
+ if (pin < 0) {
+ throw new RuntimeException("Operation not supported on this pin");
+ }
+ }
+
+
+ /**
+ * Pauses the execution of the sketch
+ *
+ * Calling this function will have an influence on the framerate
+ * the sketch is going to achieve.
+ * @param ms milliseconds to pause
+ */
+ protected static void delay(int ms) {
+ try {
+ Thread.sleep(ms);
+ } catch (InterruptedException e) {
+ }
+ }
+
+
+ /**
+ * Pauses the execution of the sketch
+ *
+ * Note: Both the operating system, as well as Processing, are not what is
+ * called "hard real-time" systems. In other words: there are many factors,
+ * outside of the control of the programmer, which can influence the execution
+ * of the program in minute ways. Those are generally not an issue, or even
+ * noticeable using a desktop operating system, but they can be a factor, when
+ * the timing of a particular sequence of events is critical. For example, one
+ * might to wait a very specific number amount of time after receiving an
+ * interrupt before changing the state of an output pin. When programming with
+ * micro-controllers, as found on the Arduino Uno, there very little between
+ * your code and the actual hardware, and multiple executions of the same
+ * sketch will probably match each other almost to the very tick of a clock
+ * (which happens at the speed of 16 MHz). Systems running full-fledged
+ * desktop operating systems, such as Linux, are generally multi-tasking,
+ * which means that the operating system allocates small slices of time to
+ * the many different processes that run concurrently. The effect of this is
+ * often offset by the sheer clock speeds that such computers run. But
+ * regardless: if you require your sketch to adhere to a very specific timing,
+ * you might be disappointed.
+ * @param us microseconds to pause
+ */
+ protected static void delayMicroseconds(int us) {
+ int ms = (int)(us / 1000);
+ int ns = (us - (ms * 1000)) * 1000;
+ try {
+ Thread.sleep(ms, ns);
+ } catch (InterruptedException e) {
+ }
+ }
+
+
+ /**
+ * Returns the value of an input pin
+ *
+ * You need to set the pin to INPUT by calling pinMode before calling
+ * this function.
+ * @param pin GPIO pin
+ * @return GPIO.HIGH (1) or GPIO.LOW (0)
+ * @see pinMode
+ * @see digitalWrite
+ */
+ public static int digitalRead(int pin) {
+ checkValidPin(pin);
+
+ String fn = String.format("/sys/class/gpio/gpio%d/value", pin);
+ byte in[] = new byte[2];
+ int ret = NativeInterface.readFile(fn, in);
+ if (ret < 0) {
+ throw new RuntimeException(NativeInterface.getError(ret));
+ } else if (1 <= ret && in[0] == '0') {
+ return LOW;
+ } else if (1 <= ret && in[0] == '1') {
+ return HIGH;
+ } else {
+ System.err.print("Read " + ret + " bytes");
+ if (0 < ret) {
+ System.err.format(", first byte is 0x%02x" + in[0]);
+ }
+ System.err.println();
+ throw new RuntimeException("Unexpected value");
+ }
+ }
+
+
+ /**
+ * Sets an output pin to HIGH or LOW
+ *
+ * You need set the pin to OUTPUT by calling pinMode before calling this
+ * function. It is not possible to enable or disable internal pull-up
+ * resistors for inputs using this function, which is something that's
+ * supported on Arduino.
+ * @param pin GPIO pin
+ * @param value GPIO.HIGH or GPIO.LOW
+ * @see pinMode
+ * @see digitalRead
+ */
+ public static void digitalWrite(int pin, int value) {
+ checkValidPin(pin);
+
+ String out;
+ if (value == LOW) {
+ // values are also stored in a bitmap to make it possible to set a
+ // default level per pin before enabling the output
+ values.clear(pin);
+ out = "0";
+ } else if (value == HIGH) {
+ values.set(pin);
+ out = "1";
+ } else {
+ System.err.println("Only GPIO.LOW and GPIO.HIGH, 0 and 1, or true and false, can be used.");
+ throw new IllegalArgumentException("Illegal value");
+ }
+
+ String fn = String.format("/sys/class/gpio/gpio%d/value", pin);
+ int ret = NativeInterface.writeFile(fn, out);
+ if (ret < 0) {
+ if (ret != -2) { // ENOENT, pin might not yet be exported
+ throw new RuntimeException(NativeInterface.getError(ret));
+ }
+ }
+ }
+
+
+ /**
+ * Sets an output pin to HIGH or LOW
+ *
+ * You need set the pin to OUTPUT by calling pinMode before calling this
+ * function. It is not possible to enable or disable internal pull-up
+ * resistors for inputs using this function, which is something that's
+ * supported on Arduino.
+ * @param pin GPIO pin
+ * @param value true or false
+ * @see pinMode
+ * @see digitalRead
+ */
+ public static void digitalWrite(int pin, boolean value) {
+ if (value) {
+ digitalWrite(pin, HIGH);
+ } else {
+ digitalWrite(pin, LOW);
+ }
+ }
+
+
+ /**
+ * Disables an interrupt for an INPUT pin
+ *
+ * Use this function only in combination with enableInterrupt() and
+ * waitForInterrupt(). This should not be called when attachInterrupt()
+ * is being used.
+ * @param pin GPIO pin
+ * @see enableInterrupt
+ * @see waitForInterrupt
+ */
+ public static void disableInterrupt(int pin) {
+ enableInterrupt(pin, NONE);
+ }
+
+
+ /**
+ * Enables an interrupt for an INPUT pin
+ *
+ * Use this function only when calling waitForInterrupt(). This should not
+ * be called when attachInterrupt() is being used.
+ * @param pin GPIO pin
+ * @param mode what to wait for: GPIO.CHANGE, GPIO.FALLING or GPIO.RISING
+ * @see waitForInterrupt
+ * @see disableInterrupt
+ */
+ public static void enableInterrupt(int pin, int mode) {
+ checkValidPin(pin);
+
+ String out;
+ if (mode == NONE) {
+ out = "none";
+ } else if (mode == CHANGE) {
+ out = "both";
+ } else if (mode == FALLING) {
+ out = "falling";
+ } else if (mode == RISING) {
+ out = "rising";
+ } else {
+ throw new IllegalArgumentException("Unknown mode");
+ }
+
+ String fn = String.format("/sys/class/gpio/gpio%d/edge", pin);
+ int ret = NativeInterface.writeFile(fn, out);
+ if (ret < 0) {
+ if (ret == -2) { // ENOENT
+ System.err.println("Make sure your called pinMode on the input pin");
+ }
+ throw new RuntimeException(NativeInterface.getError(ret));
+ }
+ }
+
+
+ /**
+ * Allows interrupts to happen
+ *
+ * You can use noInterrupts() and interrupts() in tandem to make sure no interrupts
+ * are occuring while your sketch is doing a particular task. This is only relevant
+ * when using attachInterrupt(), not for waitForInterrupt(). By default, interrupts
+ * are enabled.
+ * @see attachInterrupt
+ * @see noInterrupts
+ * @see releaseInterrupt
+ */
+ public static void interrupts() {
+ serveInterrupts = true;
+ }
+
+
+ /**
+ * Prevents interrupts from happpening
+ *
+ * You can use noInterrupts() and interrupts() in tandem to make sure no interrupts
+ * are occuring while your sketch is doing a particular task. This is only relevant
+ * when using attachInterrupt(), not for waitForInterrupt(). By default, interrupts
+ * are enabled.
+ * @see attachInterrupt
+ * @see interrupts
+ * @see releaseInterrupt
+ */
+ public static void noInterrupts() {
+ serveInterrupts = false;
+ }
+
+
+ /**
+ * Sets a pin to INPUT or OUTPUT
+ *
+ * While pins are implicitly set to input by default on Arduino, it is
+ * necessary to call this function for any pin you want to access later,
+ * including input pins.
+ * @param pin GPIO pin
+ * @param mode GPIO.INPUT or GPIO.OUTPUT
+ * @see digitalRead
+ * @see digitalWrite
+ * @see releasePin
+ */
+ public static void pinMode(int pin, int mode) {
+ checkValidPin(pin);
+
+ // export pin through sysfs
+ String fn = "/sys/class/gpio/export";
+ int ret = NativeInterface.writeFile(fn, Integer.toString(pin));
+ if (ret < 0) {
+ if (ret == -2) { // ENOENT
+ System.err.println("Make sure your kernel is compiled with GPIO_SYSFS enabled");
+ }
+ if (ret == -22) { // EINVAL
+ System.err.println("GPIO pin " + pin + " does not seem to be available on your platform");
+ }
+ if (ret != -16) { // EBUSY, returned when the pin is already exported
+ throw new RuntimeException(fn + ": " + NativeInterface.getError(ret));
+ }
+ }
+
+ // delay to give udev a chance to change the file permissions behind our back
+ // there should really be a cleaner way for this
+ try {
+ Thread.sleep(500);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+
+ // set direction and default level for outputs
+ fn = String.format("/sys/class/gpio/gpio%d/direction", pin);
+ String out;
+ if (mode == INPUT) {
+ out = "in";
+ } else if (mode == OUTPUT) {
+ if (values.get(pin)) {
+ out = "high";
+ } else {
+ out = "low";
+ }
+ } else if (mode == INPUT_PULLUP || mode == INPUT_PULLDOWN) {
+ // currently this can't be done in a non-platform-specific way, see
+ // http://lists.infradead.org/pipermail/linux-rpi-kernel/2015-August/002146.html
+ throw new RuntimeException("Not yet implemented");
+ } else {
+ throw new IllegalArgumentException("Unknown mode");
+ }
+ ret = NativeInterface.writeFile(fn, out);
+ if (ret < 0) {
+ throw new RuntimeException(fn + ": " + NativeInterface.getError(ret));
+ }
+ }
+
+
+ /**
+ * Stops listening for interrupts on an INPUT pin
+ *
+ * Use this function only in combination with attachInterrupt(). This should
+ * not be called when enableInterrupt() and waitForInterrupt() are being used.
+ * @param pin GPIO pin
+ * @see attachInterrupt
+ * @see noInterrupts
+ * @see interrupts
+ */
+ public static void releaseInterrupt(int pin) {
+ Thread t = irqThreads.get(pin);
+ if (t == null) {
+ return;
+ }
+
+ t.interrupt();
+ try {
+ t.join();
+ } catch (InterruptedException e) {
+ System.err.println("Error joining thread in releaseInterrupt: " + e.getMessage());
+ }
+ t = null;
+ irqThreads.remove(pin);
+
+ disableInterrupt(pin);
+ }
+
+
+ /**
+ * Gives ownership of a pin back to the operating system
+ *
+ * Without calling this function the pin will remain in the current
+ * state even after the sketch has been closed.
+ * @param pin GPIO pin
+ * @see pinMode
+ */
+ public static void releasePin(int pin) {
+ checkValidPin(pin);
+
+ String fn = "/sys/class/gpio/unexport";
+ int ret = NativeInterface.writeFile(fn, Integer.toString(pin));
+ if (ret < 0) {
+ if (ret == -2) { // ENOENT
+ System.err.println("Make sure your kernel is compiled with GPIO_SYSFS enabled");
+ }
+ // EINVAL is returned when trying to unexport pins that weren't exported to begin with, ignore this case
+ if (ret != -22) {
+ throw new RuntimeException(NativeInterface.getError(ret));
+ }
+ }
+ }
+
+
+ /**
+ * Waits for the value of an INPUT pin to change
+ *
+ * Make sure to setup the interrupt with enableInterrupt() before calling
+ * this function. A timeout value of -1 waits indefinitely.
+ * @param timeout don't wait more than timeout milliseconds
+ * @return true if the interrupt occured, false if the timeout occured
+ * @see enableInterrupt
+ * @see disableInterrupt
+ */
+ public static boolean waitForInterrupt(int pin, int timeout) {
+ checkValidPin(pin);
+
+ String fn = String.format("/sys/class/gpio/gpio%d/value", pin);
+ int ret = NativeInterface.pollDevice(fn, timeout);
+ if (ret < 0) {
+ if (ret == -2) { // ENOENT
+ System.err.println("Make sure your called pinMode on the input pin");
+ }
+ throw new RuntimeException(NativeInterface.getError(ret));
+ } else if (ret == 0) {
+ // timeout
+ return false;
+ } else {
+ // interrupt
+ return true;
+ }
+ }
+
+
+ /**
+ * Waits for the value of an INPUT pin to change
+ *
+ * Make sure to setup the interrupt with enableInterrupt() before calling
+ * this function. This function will wait indefinitely for an interrupt
+ * to occur.
+ * @see enableInterrupt
+ * @see disableInterrupt
+ */
+ public static void waitForInterrupt(int pin) {
+ waitForInterrupt(pin, -1);
+ }
+}
diff --git a/java/libraries/io/src/processing/io/I2C.java b/java/libraries/io/src/processing/io/I2C.java
new file mode 100644
index 000000000..6a006af5d
--- /dev/null
+++ b/java/libraries/io/src/processing/io/I2C.java
@@ -0,0 +1,250 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+ Copyright (c) The Processing Foundation 2015
+ I/O library developed by Gottfried Haider as part of GSOC 2015
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General
+ Public License along with this library; if not, write to the
+ Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ Boston, MA 02111-1307 USA
+*/
+
+package processing.io;
+
+import processing.io.NativeInterface;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+
+public class I2C {
+
+ protected String dev;
+ protected int handle;
+ protected int slave;
+ protected byte[] out;
+ protected boolean transmitting;
+
+
+ /**
+ * Opens an I2C device as master
+ *
+ * @param dev device name
+ * @see list
+ */
+ public I2C(String dev) {
+ NativeInterface.loadLibrary();
+ this.dev = dev;
+ handle = NativeInterface.openDevice("/dev/" + dev);
+ if (handle < 0) {
+ throw new RuntimeException(NativeInterface.getError(handle));
+ }
+ }
+
+
+ /**
+ * Close the I2C device
+ */
+ public void close() {
+ NativeInterface.closeDevice(handle);
+ handle = 0;
+ }
+
+
+ protected void finalize() throws Throwable {
+ try {
+ close();
+ } finally {
+ super.finalize();
+ }
+ }
+
+
+ /**
+ * Begins a transmission to an attached device
+ *
+ * I2C addresses consist of 7 bits plus one bit that indicates whether
+ * the device is being read from or written to. Some datasheets list
+ * the address in an 8 bit form (7 address bits + R/W bit), while others
+ * provide the address in a 7 bit form, with the address in the lower
+ * 7 bits. This function expects the address in the lower 7 bits, the
+ * same way as in Arduino's Wire library, and as shown in the output
+ * of the i2cdetect tool.
+ * If the address provided in a datasheet is greater than 127 (hex 0x7f)
+ * or there are separate addresses for read and write operations listed,
+ * which vary exactly by one, then you want to shif the this number by
+ * one bit to the right before passing it as an argument to this function.
+ * @param slave 7 bit address of slave device
+ * @see write
+ * @see read
+ * @see endTransmission
+ */
+ public void beginTransmission(int slave) {
+ // addresses 120 (0x78) to 127 are additionally reserved
+ if (0x78 <= slave) {
+ System.err.println("beginTransmission expects a 7 bit address, try shifting one bit to the right");
+ throw new IllegalArgumentException("Illegal address");
+ }
+ this.slave = slave;
+ transmitting = true;
+ out = null;
+ }
+
+
+ /**
+ * Ends the current transmissions
+ *
+ * This executes any queued writes.
+ * @see beginTransmission
+ * @see write
+ */
+ public void endTransmission() {
+ if (!transmitting) {
+ // silently ignore this case
+ return;
+ }
+
+ // implement these flags if needed: https://github.com/raspberrypi/linux/blob/rpi-patches/Documentation/i2c/i2c-protocol
+ int ret = NativeInterface.transferI2c(handle, slave, out, null);
+ transmitting = false;
+ out = null;
+ if (ret < 0) {
+ if (ret == -5) { // EIO
+ System.err.println("The device did not respond. Check the cabling and whether you are using the correct address.");
+ }
+ throw new RuntimeException(NativeInterface.getError(ret));
+ }
+ }
+
+
+ /**
+ * Lists all available I2C devices
+ * @return String array
+ */
+ public static String[] list() {
+ ArrayList devs = new ArrayList();
+ File dir = new File("/dev");
+ File[] files = dir.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ if (file.getName().startsWith("i2c-")) {
+ devs.add(file.getName());
+ }
+ }
+ }
+ // listFiles() does not guarantee ordering
+ String[] tmp = devs.toArray(new String[devs.size()]);
+ Arrays.sort(tmp);
+ return tmp;
+ }
+
+
+ /**
+ * Reads bytes from the attached device
+ *
+ * You must call beginTransmission() before calling this function. This function
+ * also ends the current transmisison and sends any data that was queued using
+ * write() before.
+ * @param len number of bytes to read
+ * @return bytes read from device
+ * @see beginTransmission
+ * @see write
+ * @see endTransmission
+ */
+ public byte[] read(int len) {
+ if (!transmitting) {
+ throw new RuntimeException("beginTransmisson has not been called");
+ }
+
+ byte[] in = new byte[len];
+
+ int ret = NativeInterface.transferI2c(handle, slave, out, in);
+ transmitting = false;
+ out = null;
+ if (ret < 0) {
+ if (ret == -5) { // EIO
+ System.err.println("The device did not respond. Check the cabling and whether you are using the correct address.");
+ }
+ throw new RuntimeException(NativeInterface.getError(ret));
+ }
+
+ return in;
+ }
+
+
+ /**
+ * Adds bytes to be written to the device
+ *
+ * You must call beginTransmission() before calling this function.
+ * The actual writing takes part when read() or endTransmission() is being
+ * called.
+ * @param out bytes to be written
+ * @see beginTransmission
+ * @see read
+ * @see endTransmission
+ */
+ public void write(byte[] out) {
+ if (!transmitting) {
+ throw new RuntimeException("beginTransmisson has not been called");
+ }
+
+ if (this.out == null) {
+ this.out = out;
+ } else {
+ byte[] tmp = new byte[this.out.length + out.length];
+ System.arraycopy(this.out, 0, tmp, 0, this.out.length);
+ System.arraycopy(out, 0, tmp, this.out.length, out.length);
+ this.out = tmp;
+ }
+ }
+
+
+ /**
+ * Adds bytes to be written to the device
+ *
+ * You must call beginTransmission() before calling this function.
+ * The actual writing takes part when read() or endTransmission() is being
+ * called.
+ * @param out string to be written
+ * @see beginTransmission
+ * @see read
+ * @see endTransmission
+ */
+ public void write(String out) {
+ write(out.getBytes());
+ }
+
+
+ /**
+ * Adds a byte to be written to the device
+ *
+ * You must call beginTransmission() before calling this function.
+ * The actual writing takes part when read() or endTransmission() is being
+ * called.
+ * @param out single byte to be written (0-255)
+ * @see beginTransmission
+ * @see read
+ * @see endTransmission
+ */
+ public void write(int out) {
+ if (out < 0 || 255 < out) {
+ System.err.println("The write function can only operate on a single byte at a time. Call it with a value from 0 to 255.");
+ throw new RuntimeException("Argument does not fit into a single byte");
+ }
+ byte[] tmp = new byte[1];
+ tmp[0] = (byte)out;
+ write(tmp);
+ }
+}
diff --git a/java/libraries/io/src/processing/io/LED.java b/java/libraries/io/src/processing/io/LED.java
new file mode 100644
index 000000000..7de5fb458
--- /dev/null
+++ b/java/libraries/io/src/processing/io/LED.java
@@ -0,0 +1,155 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+ Copyright (c) The Processing Foundation 2015
+ I/O library developed by Gottfried Haider as part of GSOC 2015
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General
+ Public License along with this library; if not, write to the
+ Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ Boston, MA 02111-1307 USA
+*/
+
+package processing.io;
+
+import processing.io.NativeInterface;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+
+public class LED {
+
+ protected String dev;
+ protected int maxBrightness;
+ protected int prevBrightness;
+ protected String prevTrigger;
+
+
+ /**
+ * Opens a LED device
+ * @param dev device name
+ * @see list
+ */
+ public LED(String dev) {
+ NativeInterface.loadLibrary();
+ this.dev = dev;
+
+ // read maximum brightness
+ try {
+ Path path = Paths.get("/sys/class/leds/" + dev + "/max_brightness");
+ String tmp = new String(Files.readAllBytes(path));
+ maxBrightness = Integer.parseInt(tmp.trim());
+ } catch (Exception e) {
+ System.err.println(e.getMessage());
+ throw new RuntimeException("Unable to read maximum brightness");
+ }
+
+ // read current trigger setting to be able to restore it later
+ try {
+ Path path = Paths.get("/sys/class/leds/" + dev + "/trigger");
+ String tmp = new String(Files.readAllBytes(path));
+ int start = tmp.indexOf('[');
+ int end = tmp.indexOf(']', start);
+ if (start != -1 && end != -1) {
+ prevTrigger = tmp.substring(start+1, end);
+ }
+ } catch (Exception e) {
+ System.err.println(e.getMessage());
+ throw new RuntimeException("Unable to read trigger setting");
+ }
+
+ // read current brightness to be able to restore it later
+ try {
+ Path path = Paths.get("/sys/class/leds/" + dev + "/brightness");
+ String tmp = new String(Files.readAllBytes(path));
+ prevBrightness = Integer.parseInt(tmp.trim());
+ } catch (Exception e) {
+ System.err.println(e.getMessage());
+ throw new RuntimeException("Unable to read current brightness");
+ }
+
+ // disable trigger
+ String fn = "/sys/class/leds/" + dev + "/trigger";
+ int ret = NativeInterface.writeFile(fn, "none");
+ if (ret < 0) {
+ if (ret == -13) { // EACCES
+ System.err.println("You might need to install a custom udev rule to allow regular users to modify /sys/class/leds/*.");
+ }
+ throw new RuntimeException(NativeInterface.getError(ret));
+ }
+ }
+
+
+ /**
+ * Restores the previous state
+ *
+ * Without calling this function the LED will remain in the current
+ * state even after the sketch has been closed.
+ */
+ public void close() {
+ // restore previous settings
+ String fn = "/sys/class/leds/" + dev + "/brightness";
+ int ret = NativeInterface.writeFile(fn, Integer.toString(prevBrightness));
+ if (ret < 0) {
+ throw new RuntimeException(fn + ": " + NativeInterface.getError(ret));
+ }
+
+ fn = "/sys/class/leds/" + dev + "/trigger";
+ ret = NativeInterface.writeFile(fn, prevTrigger);
+ if (ret < 0) {
+ throw new RuntimeException(fn + ": " + NativeInterface.getError(ret));
+ }
+ }
+
+
+ /**
+ * Lists all available LED devices
+ * @return String array
+ */
+ public static String[] list() {
+ ArrayList devs = new ArrayList();
+ File dir = new File("/sys/class/leds");
+ File[] files = dir.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ devs.add(file.getName());
+ }
+ }
+ // listFiles() does not guarantee ordering
+ String[] tmp = devs.toArray(new String[devs.size()]);
+ Arrays.sort(tmp);
+ return tmp;
+ }
+
+
+ /**
+ * Sets the brightness
+ * @param bright 0.0 (off) to 1.0 (maximum)
+ */
+ public void set(float bright) {
+ String fn = "/sys/class/leds/" + dev + "/brightness";
+ if (bright < 0.0 || 1.0 < bright) {
+ System.err.println("Brightness must be between 0.0 and 1.0.");
+ throw new IllegalArgumentException("Illegal argument");
+ }
+ int ret = NativeInterface.writeFile(fn, Integer.toString((int)(bright * maxBrightness)));
+ if (ret < 0) {
+ throw new RuntimeException(fn + ": " + NativeInterface.getError(ret));
+ }
+ }
+}
diff --git a/java/libraries/io/src/processing/io/NativeInterface.java b/java/libraries/io/src/processing/io/NativeInterface.java
new file mode 100644
index 000000000..b2dc64f3c
--- /dev/null
+++ b/java/libraries/io/src/processing/io/NativeInterface.java
@@ -0,0 +1,60 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+ Copyright (c) The Processing Foundation 2015
+ I/O library developed by Gottfried Haider as part of GSOC 2015
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General
+ Public License along with this library; if not, write to the
+ Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ Boston, MA 02111-1307 USA
+*/
+
+package processing.io;
+
+
+public class NativeInterface {
+
+ protected static boolean loaded = false;
+
+ public static void loadLibrary() {
+ if (!loaded) {
+ if (!"Linux".equals(System.getProperty("os.name"))) {
+ throw new RuntimeException("The Processing I/O library is only supported on Linux");
+ }
+ System.loadLibrary("processing-io");
+ loaded = true;
+ }
+ }
+
+
+ public static native int openDevice(String fn);
+ public static native String getError(int errno);
+ public static native int closeDevice(int handle);
+
+ // the following two functions were done in native code to get access to the
+ // specifc error number (errno) that might occur
+ public static native int readFile(String fn, byte[] in);
+ public static native int writeFile(String fn, byte[] out);
+ public static int writeFile(String fn, String out) {
+ return writeFile(fn, out.getBytes());
+ }
+
+ /* GPIO */
+ public static native int pollDevice(String fn, int timeout);
+ /* I2C */
+ public static native int transferI2c(int handle, int slave, byte[] out, byte[] in);
+ /* SPI */
+ public static native int setSpiSettings(int handle, int maxSpeed, int dataOrder, int mode);
+ public static native int transferSpi(int handle, byte[] out, byte[] in);
+}
diff --git a/java/libraries/io/src/processing/io/PWM.java b/java/libraries/io/src/processing/io/PWM.java
new file mode 100644
index 000000000..641738959
--- /dev/null
+++ b/java/libraries/io/src/processing/io/PWM.java
@@ -0,0 +1,191 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+ Copyright (c) The Processing Foundation 2015
+ I/O library developed by Gottfried Haider as part of GSOC 2015
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General
+ Public License along with this library; if not, write to the
+ Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ Boston, MA 02111-1307 USA
+*/
+
+package processing.io;
+
+import processing.io.NativeInterface;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+
+public class PWM {
+
+ int channel;
+ String chip;
+
+
+ /**
+ * Opens a PWM channel
+ * @param channel PWM channel
+ * @see list
+ */
+ public PWM(String channel) {
+ NativeInterface.loadLibrary();
+
+ int pos = channel.indexOf("/pwm");
+ if (pos == -1) {
+ throw new IllegalArgumentException("Unsupported channel");
+ }
+ chip = channel.substring(0, pos);
+ this.channel = Integer.parseInt(channel.substring(pos+4));
+
+ // export channel through sysfs
+ String fn = "/sys/class/pwm/" + chip + "/export";
+ int ret = NativeInterface.writeFile(fn, Integer.toString(this.channel));
+ if (ret < 0) {
+ if (ret == -2) { // ENOENT
+ System.err.println("Make sure your kernel is compiled with PWM_SYSFS enabled and you have the necessary PWM driver for your platform");
+ }
+ // XXX: check
+ if (ret == -22) { // EINVAL
+ System.err.println("PWM channel " + channel + " does not seem to be available on your platform");
+ }
+ // XXX: check
+ if (ret != -16) { // EBUSY, returned when the pin is already exported
+ throw new RuntimeException(fn + ": " + NativeInterface.getError(ret));
+ }
+ }
+
+ // delay to give udev a chance to change the file permissions behind our back
+ // there should really be a cleaner way for this
+ try {
+ Thread.sleep(500);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+
+
+ /**
+ * Disables the output
+ */
+ public void clear() {
+ String fn = String.format("/sys/class/pwm/%s/gpio%d/enable", chip, channel);
+ int ret = NativeInterface.writeFile(fn, "0");
+ if (ret < 0) {
+ throw new RuntimeException(NativeInterface.getError(ret));
+ }
+ }
+
+
+ /**
+ * Gives ownership of a channel back to the operating system
+ *
+ * Without calling this function the channel will remain in the current
+ * state even after the sketch has been closed.
+ */
+ public void close() {
+ // XXX: implicit clear()?
+ // XXX: also check GPIO
+
+ String fn = "/sys/class/pwm/" + chip + "/export";
+ int ret = NativeInterface.writeFile(fn, Integer.toString(channel));
+ if (ret < 0) {
+ if (ret == -2) { // ENOENT
+ System.err.println("Make sure your kernel is compiled with PWM_SYSFS enabled and you have the necessary PWM driver for your platform");
+ }
+ // XXX: check
+ // EINVAL is also returned when trying to unexport pins that weren't exported to begin with
+ throw new RuntimeException(NativeInterface.getError(ret));
+ }
+ }
+
+
+ /**
+ * Lists all available PWM channels
+ * @return String array
+ */
+ public static String[] list() {
+ ArrayList devs = new ArrayList();
+ File dir = new File("/sys/class/pwm");
+ File[] chips = dir.listFiles();
+ if (chips != null) {
+ for (File chip : chips) {
+ // get the number of supported channels
+ try {
+ Path path = Paths.get("/sys/class/pwm/" + chip.getName() + "/npwm");
+ String tmp = new String(Files.readAllBytes(path));
+ int npwm = Integer.parseInt(tmp.trim());
+ for (int i=0; i < npwm; i++) {
+ devs.add(chip.getName() + "/pwm" + i);
+ }
+ } catch (Exception e) {
+ }
+ }
+ }
+ // listFiles() does not guarantee ordering
+ String[] tmp = devs.toArray(new String[devs.size()]);
+ Arrays.sort(tmp);
+ return tmp;
+ }
+
+
+ /**
+ * Enables the PWM output
+ * @param period cycle period in Hz
+ * @param duty duty cycle, 0.0 (always off) to 1.0 (always on)
+ */
+ public void set(int period, float duty) {
+ // set period
+ String fn = fn = String.format("/sys/class/pwm/%s/gpio%d/period", chip, channel);
+ int ret = NativeInterface.writeFile(fn, String.format("%d", (int)(1000000000 / period)));
+ if (ret < 0) {
+ throw new RuntimeException(fn + ": " + NativeInterface.getError(ret));
+ }
+
+ // set duty cycle
+ fn = fn = String.format("/sys/class/pwm/%s/gpio%d/duty", chip, channel);
+ if (duty < 0.0 || 1.0 < duty) {
+ System.err.println("Duty cycle must be between 0.0 and 1.0.");
+ throw new IllegalArgumentException("Illegal argument");
+ }
+ ret = NativeInterface.writeFile(fn, String.format("%d", (int)((1000000000 * duty) / period)));
+ if (ret < 0) {
+ throw new RuntimeException(fn + ": " + NativeInterface.getError(ret));
+ }
+
+ // enable output
+ fn = String.format("/sys/class/pwm/%s/gpio%d/enable", chip, channel);
+ ret = NativeInterface.writeFile(fn, "1");
+ if (ret < 0) {
+ throw new RuntimeException(fn + ": " + NativeInterface.getError(ret));
+ }
+ }
+
+
+ /**
+ * Enables the PWM output with a preset period of 1 kHz
+ *
+ * This period approximately matches the dedicated PWM pins on
+ * the Arduino Uno, which have a frequency of 980 Hz.
+ * It is recommended to use set(period, duty) instead.
+ * @param duty duty cycle, 0.0 (always off) to 1.0 (always on)
+ */
+ public void set(float duty) {
+ set(1000, duty);
+ }
+}
diff --git a/java/libraries/io/src/processing/io/RPI.java b/java/libraries/io/src/processing/io/RPI.java
new file mode 100644
index 000000000..a5dae98bc
--- /dev/null
+++ b/java/libraries/io/src/processing/io/RPI.java
@@ -0,0 +1,85 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+ Copyright (c) The Processing Foundation 2015
+ I/O library developed by Gottfried Haider as part of GSOC 2015
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General
+ Public License along with this library; if not, write to the
+ Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ Boston, MA 02111-1307 USA
+*/
+
+package processing.io;
+
+
+public class RPI {
+
+ /*
+ * The Raspberry Pi has a 2x20 pin header for connecting various peripherals.
+ * The following constants describe how the pins of this header correspond
+ * to the pin numbering used by the CPU and by software ("GPIO pin number").
+ *
+ * You can use this class to refer to a pin by its location on the header:
+ * e.g. GPIO.digitalWrite(RPI.PIN7, GPIO.HIGH)
+ *
+ * Alternatively, if you know a pins "true" pin number (GPIO number), you
+ * can use it directly. The following is equivalent to the example above:
+ * GPIO.digitalWrite(4, GPIO.HIGH)
+ *
+ * PIN1 is located on the "top left" of the column of pins, close to the two
+ * LEDs. PIN2 is next to it. PIN3 is below PIN1, and it goes on like this.
+ * See also: http://pi.gadgetoid.com/pinout
+ */
+
+ public static final int PIN1 = -1; /* 3v3 Power, can source up to 50 mA */
+ public static final int PIN2 = -1; /* 5v Power, connected to input power */
+ public static final int PIN3 = 2; /* GPIO 2, also: I2C data */
+ public static final int PIN4 = -1; /* 5v Power, connected to input power */
+ public static final int PIN5 = 3; /* GPIO 3, also: I2C clock */
+ public static final int PIN6 = -1; /* Ground */
+ public static final int PIN7 = 4; /* GPIO 4 */
+ public static final int PIN8 = 14; /* GPIO 14, also: Serial TX */
+ public static final int PIN9 = -1; /* Ground */
+ public static final int PIN10 = 15; /* GPIO 15, also: Serial RX */
+ public static final int PIN11 = 17; /* GPIO 17 */
+ public static final int PIN12 = 18; /* GPIO 18 */
+ public static final int PIN13 = 27; /* GPIO 27 */
+ public static final int PIN14 = -1; /* Ground */
+ public static final int PIN15 = 22; /* GPIO 22 */
+ public static final int PIN16 = 23; /* GPIO 23 */
+ public static final int PIN17 = -1; /* 3v3 Power, can source up to 50 mA */
+ public static final int PIN18 = 24; /* GPIO 24 */
+ public static final int PIN19 = 10; /* GPIO 10, also: SPI MOSI */
+ public static final int PIN20 = -1; /* Ground */
+ public static final int PIN21 = 9; /* GPIO 9, also: SPI MISO */
+ public static final int PIN22 = 25; /* GPIO 25 */
+ public static final int PIN23 = 11; /* GPIO 11, also: SPI SCLK */
+ public static final int PIN24 = 8; /* GPIO 8, also: SPI Chip Select 0 */
+ public static final int PIN25 = -1; /* Ground */
+ public static final int PIN26 = 7; /* GPIO 7, also: SPI Chip Select 1 */
+ public static final int PIN27 = 0; /* GPIO 0, also: HAT I2C data, reserved [currenly not accessible] */
+ public static final int PIN28 = 1; /* GPIO 1, also HAT I2C data, reserved [currenly not accessible] */
+ public static final int PIN29 = 5; /* GPIO 5 */
+ public static final int PIN30 = -1; /* Ground */
+ public static final int PIN31 = 6; /* GPIO 6 */
+ public static final int PIN32 = 12; /* GPIO 12 */
+ public static final int PIN33 = 13; /* GPIO 13 */
+ public static final int PIN34 = -1; /* Ground */
+ public static final int PIN35 = 19; /* GPIO 19, also: SPI MISO [currenly not accessible] */
+ public static final int PIN36 = 16; /* GPIO 16 */
+ public static final int PIN37 = 26; /* GPIO 26 */
+ public static final int PIN38 = 20; /* GPIO 20, also: SPI MISO [currenly not accessible] */
+ public static final int PIN39 = -1; /* Ground */
+ public static final int PIN40 = 21; /* GPIO 21, also: SPI CLK [currenly not accessible] */
+}
diff --git a/java/libraries/io/src/processing/io/SPI.java b/java/libraries/io/src/processing/io/SPI.java
new file mode 100644
index 000000000..4a4b08352
--- /dev/null
+++ b/java/libraries/io/src/processing/io/SPI.java
@@ -0,0 +1,183 @@
+/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
+
+/*
+ Copyright (c) The Processing Foundation 2015
+ I/O library developed by Gottfried Haider as part of GSOC 2015
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General
+ Public License along with this library; if not, write to the
+ Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ Boston, MA 02111-1307 USA
+*/
+
+package processing.io;
+
+import processing.io.NativeInterface;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+
+public class SPI {
+
+ public static final int MODE0 = 0; // CPOL=0, CPHA=0, most common
+ public static final int MODE1 = 1; // CPOL=0, CPHA=1
+ public static final int MODE2 = 2; // CPOL=1, CPHA=0
+ public static final int MODE3 = 3; // CPOL=1, CPHA=1
+ public static final int MSBFIRST = 0; // most significant bit first, most common
+ public static final int LSBFIRST = 1; // least significant bit first
+
+ protected int dataOrder = 0;
+ protected String dev;
+ protected int handle;
+ protected int maxSpeed = 500000;
+ protected int mode = 0;
+ protected static Map settings = new HashMap();
+
+
+ /**
+ * Opens an SPI interface
+ * @param dev device name
+ * @see list
+ */
+ public SPI(String dev) {
+ NativeInterface.loadLibrary();
+ this.dev = dev;
+ handle = NativeInterface.openDevice("/dev/" + dev);
+ if (handle < 0) {
+ throw new RuntimeException(NativeInterface.getError(handle));
+ }
+ }
+
+
+ /**
+ * Closes the I2C interface
+ */
+ public void close() {
+ NativeInterface.closeDevice(handle);
+ handle = 0;
+ }
+
+
+ protected void finalize() throws Throwable {
+ try {
+ close();
+ } finally {
+ super.finalize();
+ }
+ }
+
+
+ /**
+ * Lists all available SPI interfaces
+ * @return String array
+ */
+ public static String[] list() {
+ ArrayList devs = new ArrayList();
+ File dir = new File("/dev");
+ File[] files = dir.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ if (file.getName().startsWith("spidev")) {
+ devs.add(file.getName());
+ }
+ }
+ }
+ // listFiles() does not guarantee ordering
+ String[] tmp = devs.toArray(new String[devs.size()]);
+ Arrays.sort(tmp);
+ return tmp;
+ }
+
+
+ /**
+ * Configures the SPI interface
+ * @param maxSpeed maximum transmission rate in Hz, 500000 (500 kHz) is a resonable default
+ * @param dataOrder whether data is send with the first- or least significant bit first (SPI.MSBFIRST or SPI.LSBFIRST, the former is more common)
+ * @param mode SPI.MODE0 to SPI.MODE3 (see https://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus#Clock_polarity_and_phase)
+ */
+ public void settings(int maxSpeed, int dataOrder, int mode) {
+ this.maxSpeed = maxSpeed;
+ this.dataOrder = dataOrder;
+ this.mode = mode;
+ }
+
+
+ /**
+ * Transfers data over the SPI bus
+ *
+ * With SPI, data is simultaneously being exchanged between the master device
+ * and the slave device. For every byte that is being sent out, there's also
+ * one byte being read in.
+ * @param out bytes to send
+ * @return bytes read in (array is the same length as out)
+ */
+ public byte[] transfer(byte[] out) {
+ // track the current setting per device across multiple instances
+ String curSettings = maxSpeed + "-" + dataOrder + "-" + mode;
+ if (!curSettings.equals(settings.get(dev))) {
+ int ret = NativeInterface.setSpiSettings(handle, maxSpeed, dataOrder, mode);
+ if (ret < 0) {
+ System.err.println(NativeInterface.getError(handle));
+ throw new RuntimeException("Error updating device configuration");
+ }
+ settings.put(dev, curSettings);
+ }
+
+ byte[] in = new byte[out.length];
+ int transferred = NativeInterface.transferSpi(handle, out, in);
+ if (transferred < 0) {
+ throw new RuntimeException(NativeInterface.getError(transferred));
+ } else if (transferred < out.length) {
+ throw new RuntimeException("Fewer bytes transferred than requested: " + transferred);
+ }
+ return in;
+ }
+
+
+ /**
+ * Transfers data over the SPI bus
+ *
+ * With SPI, data is simultaneously being exchanged between the master device
+ * and the slave device. For every byte that is being sent out, there's also
+ * one byte being read in.
+ * @param out string to send
+ * @return bytes read in (array is the same length as out)
+ */
+ public byte[] transfer(String out) {
+ return transfer(out.getBytes());
+ }
+
+
+ /**
+ * Transfers data over the SPI bus
+ *
+ * With SPI, data is simultaneously being exchanged between the master device
+ * and the slave device. For every byte that is being sent out, there's also
+ * one byte being read in.
+ * @param out single byte to send
+ * @return bytes read in (array is the same length as out)
+ */
+ public byte[] transfer(int out) {
+ if (out < 0 || 255 < out) {
+ System.err.println("The transfer function can only operate on a single byte at a time. Call it with a value from 0 to 255.");
+ throw new RuntimeException("Argument does not fit into a single byte");
+ }
+ byte[] tmp = new byte[1];
+ tmp[0] = (byte)out;
+ return transfer(tmp);
+ }
+}