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); + } +}