diff --git a/java/libraries/io/build.xml b/java/libraries/io/build.xml
index b13699db7..0e9e10234 100644
--- a/java/libraries/io/build.xml
+++ b/java/libraries/io/build.xml
@@ -29,4 +29,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/java/libraries/io/examples/I2CScreen/I2CScreen.pde b/java/libraries/io/examples/I2CScreen/I2CScreen.pde
new file mode 100644
index 000000000..71a761598
--- /dev/null
+++ b/java/libraries/io/examples/I2CScreen/I2CScreen.pde
@@ -0,0 +1,18 @@
+import processing.io.*;
+
+// 0.96" 128x64 OLED display ("SKU 346540")
+SSD1306 oled;
+
+void setup() {
+ size(128, 64);
+
+ // the display can be set to one of these two addresses: 0x3c (default) or 0x3d
+ // (they might be listed as 0x7a and 0x7b on the circuit board)
+ oled = new SSD1306(I2C.list()[0], 0x3c);
+}
+
+void draw() {
+ line(0, 0, 127, 63);
+ line(0, 63, 127, 0);
+ oled.sendImage(get());
+}
diff --git a/java/libraries/io/examples/I2CScreen/SSD1306.pde b/java/libraries/io/examples/I2CScreen/SSD1306.pde
new file mode 100644
index 000000000..6c9f343aa
--- /dev/null
+++ b/java/libraries/io/examples/I2CScreen/SSD1306.pde
@@ -0,0 +1,118 @@
+import processing.io.I2C;
+
+// SSD1306 is a small, inexpensive 128x64 pixels monochrome OLED display
+// available online as "0.96" 128x64 OLED display", SKU 346540
+// or from Adafruit
+// datasheet: https://www.adafruit.com/datasheets/SSD1306.pdf
+
+class SSD1306 extends I2C {
+ int address;
+
+ // there can be more than one device connected to the bus
+ // as long as they have different addresses
+ SSD1306(String dev, int address) {
+ super(dev);
+ this.address = address;
+ init();
+ }
+
+ protected void init() {
+ writeCommand(0xae); // turn display off
+ writeCommand(0xa8, 0x3f); // set multiplex ratio to the highest setting
+ writeCommand(0x8d, 0x14); // enable charge pump
+ writeCommand(0x20, 0x00); // set memory addressing mode to horizontal
+ writeCommand(0xd5, 0x80); // set display clock divide ratio & oscillator frequency to default
+ writeCommand(0xd3, 0x00); // no display offset
+ writeCommand(0x40 | 0x00); // set default display start line
+
+ // use the following two lines to flip the display
+ writeCommand(0xa0 | 0x01); // set segment re-map
+ writeCommand(0xc8); // set COM output scan direction
+
+ writeCommand(0xda, 0x12); // set COM pins hardware configuration
+ writeCommand(0xd9, 0xf1); // set pre-charge period to 241x DCLK
+ writeCommand(0xdB, 0x40); // set VCOMH deselect level
+ writeCommand(0xa4); // display RAM content (not all-on)
+ writeCommand(0xa6); // set normal (not-inverted) display
+
+ // set this since we don't have access to the OLED's reset pins (?)
+ writeCommand(0x21, 0, 127); // set column address
+ writeCommand(0x22, 0, 7); // set page address
+
+ writeCommand(0x81, 0xcf); // set contrast
+ writeCommand(0x2e); // deactivate scroll
+ writeCommand(0xaf); // turn display on
+ }
+
+ void invert(boolean inverted) {
+ if (inverted) {
+ writeCommand(0xa7);
+ } else {
+ writeCommand(0xa6);
+ }
+ }
+
+ void sendImage(PImage img) {
+ sendImage(img, 0, 0);
+ }
+
+ void sendImage(PImage img, int startX, int startY) {
+ byte[] frame = new byte[1024];
+ img.loadPixels();
+ for (int y=startY; y < height && y-startY < 64; y++) {
+ for (int x=startX; x < width && x-startX < 128; x++) {
+ if (brightness(img.pixels[y*img.width+x]) < 128) {
+ // this isn't the normal (scanline) mapping, but 8 pixels below each other at a time
+ // white pixels have their bit turned on
+ frame[x + (y/8)*128] |= (1 << (y % 8));
+ }
+ }
+ }
+ sendFramebuffer(frame);
+ }
+
+ void sendFramebuffer(byte[] buf) {
+ if (buf.length != 1024) {
+ System.err.println("The framebuffer should be 1024 bytes long, with one bit per pixel");
+ throw new IllegalArgumentException("Unexpected buffer size");
+ }
+
+ writeCommand(0x00 | 0x0); // set start address
+ writeCommand(0x10 | 0x0); // set higher column start address
+ writeCommand(0x40 | 0x0); // set start line
+
+ // send the frame buffer as 16 byte long packets
+ for (int i=0; i < buf.length/16; i++) {
+ super.beginTransmission(address);
+ super.write(0x40); // indicates data write
+ for (int j=0; j < 16; j++) {
+ super.write(buf[i*16+j]);
+ }
+ super.endTransmission();
+ }
+ }
+
+ protected void writeCommand(int arg1) {
+ super.beginTransmission(address);
+ super.write(0x00); // indicates command write
+ super.write(arg1);
+ super.endTransmission();
+ }
+
+ protected void writeCommand(int arg1, int arg2) {
+ super.beginTransmission(address);
+ super.write(0x00);
+ super.write(arg1);
+ super.write(arg2);
+ super.endTransmission();
+ }
+
+ protected void writeCommand(int arg1, int arg2, int arg3) {
+ super.beginTransmission(address);
+ super.write(0x00);
+ super.write(arg1);
+ super.write(arg2);
+ super.write(arg3);
+ super.endTransmission();
+ }
+}
diff --git a/java/libraries/io/examples/LedCounter/LedCounter.pde b/java/libraries/io/examples/LedCounter/LedCounter.pde
index 4bc41e60f..3cd440b19 100644
--- a/java/libraries/io/examples/LedCounter/LedCounter.pde
+++ b/java/libraries/io/examples/LedCounter/LedCounter.pde
@@ -22,9 +22,9 @@ 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);
+ leds[i].brightness(1.0);
} else {
- leds[i].set(0.0);
+ leds[i].brightness(0.0);
}
}
println(frameCount);
diff --git a/java/libraries/io/examples/SPIAnalogDigitalOOP8/MCP3008.pde b/java/libraries/io/examples/SPIAnalogDigitalOOP8/MCP3008.pde
new file mode 100644
index 000000000..5ae8d9ef0
--- /dev/null
+++ b/java/libraries/io/examples/SPIAnalogDigitalOOP8/MCP3008.pde
@@ -0,0 +1,27 @@
+import processing.io.SPI;
+
+// MCP3008 is a Analog-to-Digital converter using SPI
+// other than the MCP3001, this has 8 input channels
+// datasheet: http://ww1.microchip.com/downloads/en/DeviceDoc/21295d.pdf
+
+class MCP3008 extends SPI {
+
+ MCP3008(String dev) {
+ super(dev);
+ super.settings(500000, SPI.MSBFIRST, SPI.MODE0);
+ }
+
+ float getAnalog(int channel) {
+ if (channel < 0 || 7 < channel) {
+ System.err.println("The channel needs to be from 0 to 7");
+ throw new IllegalArgumentException("Unexpected channel");
+ }
+ byte[] out = { 0, 0, 0 };
+ // encode the channel number in the first byte
+ out[0] = (byte)(0x18 | channel);
+ byte[] in = super.transfer(out);
+ int val = ((in[1] & 0x03) << 8) | (in[2] & 0xff);
+ // val is between 0 and 1023
+ return val/1023.0;
+ }
+}
diff --git a/java/libraries/io/examples/SPIAnalogDigitalOOP8/SPIAnalogDigitalOOP8.pde b/java/libraries/io/examples/SPIAnalogDigitalOOP8/SPIAnalogDigitalOOP8.pde
new file mode 100644
index 000000000..58c496977
--- /dev/null
+++ b/java/libraries/io/examples/SPIAnalogDigitalOOP8/SPIAnalogDigitalOOP8.pde
@@ -0,0 +1,15 @@
+import processing.io.*;
+MCP3008 adc;
+
+// see setup.png in the sketch folder for wiring details
+
+void setup() {
+ //printArray(SPI.list());
+ adc = new MCP3008(SPI.list()[0]);
+}
+
+void draw() {
+ background(adc.getAnalog(0) * 255);
+ fill(adc.getAnalog(1) * 255);
+ ellipse(width/2, height/2, width * 0.75, width * 0.75);
+}
diff --git a/java/libraries/io/examples/SimpleResistorSensor/SimpleResistorSensor.pde b/java/libraries/io/examples/SimpleResistorSensor/SimpleResistorSensor.pde
new file mode 100644
index 000000000..874b699e9
--- /dev/null
+++ b/java/libraries/io/examples/SimpleResistorSensor/SimpleResistorSensor.pde
@@ -0,0 +1,56 @@
+import processing.io.*;
+
+// using a capacitor that gets charged and discharged, while
+// measuring the time it takes, is an inexpensive way to
+// read the value of an (analog) resistive sensor, such as
+// a photocell
+// kudos to ladyada for the original tutorial
+
+// see setup.png in the sketch folder for wiring details
+
+int max = 0;
+int min = 9999;
+
+void setup() {
+}
+
+void draw() {
+ int val = sensorRead(4);
+ println(val);
+
+ // track largest and smallest reading, to get a sense
+ // how we compare
+ if (max < val) {
+ max = val;
+ }
+ if (val < min) {
+ min = val;
+ }
+
+ // convert current reading into a number between 0.0 and 1.0
+ float frac = map(val, min, max, 0.0, 1.0);
+
+ background(255 * frac);
+}
+
+int sensorRead(int pin) {
+ // discharge the capacitor
+ GPIO.pinMode(pin, GPIO.OUTPUT);
+ GPIO.digitalWrite(pin, GPIO.LOW);
+ delay(100);
+ // now the capacitor should be empty
+
+ // measure the time takes to fill it
+ // up to ~ 1.4V again
+ GPIO.pinMode(pin, GPIO.INPUT);
+ int start = millis();
+ while (GPIO.digitalRead(pin) == GPIO.LOW) {
+ // wait
+ }
+
+ // return the time elapsed
+ // this will vary based on the value of the
+ // resistive sensor (lower resistance will
+ // make the capacitor charge faster)
+ return millis() - start;
+}
diff --git a/java/libraries/io/library.properties b/java/libraries/io/library.properties
index 6bf755571..519c34193 100644
--- a/java/libraries/io/library.properties
+++ b/java/libraries/io/library.properties
@@ -1,2 +1,9 @@
name = Hardware I/O
+authors = The Processing Foundation
+url = http://processing.org/reference/libraries/io/index.html
+categories = Hardware
+sentence = Access peripherals on the Raspberry Pi and other Linux-based computers.
+paragraph = For other platforms, this is solely provided in order to build and export sketches that require processing.io.
version = 1
+prettyVersion = 1
+minRevision = 247
diff --git a/java/libraries/io/src/processing/io/GPIO.java b/java/libraries/io/src/processing/io/GPIO.java
index f8b972785..a0b970955 100644
--- a/java/libraries/io/src/processing/io/GPIO.java
+++ b/java/libraries/io/src/processing/io/GPIO.java
@@ -177,6 +177,10 @@ public class GPIO {
public static int digitalRead(int pin) {
checkValidPin(pin);
+ if (NativeInterface.isSimulated()) {
+ return LOW;
+ }
+
String fn = String.format("/sys/class/gpio/gpio%d/value", pin);
byte in[] = new byte[2];
int ret = NativeInterface.readFile(fn, in);
@@ -222,6 +226,10 @@ public class GPIO {
throw new IllegalArgumentException("Illegal value");
}
+ if (NativeInterface.isSimulated()) {
+ return;
+ }
+
String fn = String.format("/sys/class/gpio/gpio%d/value", pin);
int ret = NativeInterface.writeFile(fn, out);
if (ret < 0) {
@@ -278,6 +286,10 @@ public class GPIO {
throw new IllegalArgumentException("Unknown mode");
}
+ if (NativeInterface.isSimulated()) {
+ return;
+ }
+
String fn = String.format("/sys/class/gpio/gpio%d/edge", pin);
int ret = NativeInterface.writeFile(fn, out);
if (ret < 0) {
@@ -325,6 +337,10 @@ public class GPIO {
public static void pinMode(int pin, int mode) {
checkValidPin(pin);
+ if (NativeInterface.isSimulated()) {
+ return;
+ }
+
// export pin through sysfs
String fn = "/sys/class/gpio/export";
int ret = NativeInterface.writeFile(fn, Integer.toString(pin));
@@ -409,6 +425,10 @@ public class GPIO {
public static void releasePin(int pin) {
checkValidPin(pin);
+ if (NativeInterface.isSimulated()) {
+ return;
+ }
+
String fn = "/sys/class/gpio/unexport";
int ret = NativeInterface.writeFile(fn, Integer.toString(pin));
if (ret < 0) {
@@ -451,6 +471,14 @@ public class GPIO {
protected static boolean waitForInterrupt(int pin, int timeout) {
checkValidPin(pin);
+ if (NativeInterface.isSimulated()) {
+ // pretend the interrupt happens after 200ms
+ try {
+ Thread.sleep(200);
+ } catch (InterruptedException e) {}
+ return true;
+ }
+
String fn = String.format("/sys/class/gpio/gpio%d/value", pin);
int ret = NativeInterface.pollDevice(fn, timeout);
if (ret < 0) {
diff --git a/java/libraries/io/src/processing/io/I2C.java b/java/libraries/io/src/processing/io/I2C.java
index 96b62767b..1d0bfac31 100644
--- a/java/libraries/io/src/processing/io/I2C.java
+++ b/java/libraries/io/src/processing/io/I2C.java
@@ -50,6 +50,11 @@ public class I2C {
public I2C(String dev) {
NativeInterface.loadLibrary();
this.dev = dev;
+
+ if (NativeInterface.isSimulated()) {
+ return;
+ }
+
handle = NativeInterface.openDevice("/dev/" + dev);
if (handle < 0) {
throw new RuntimeException(NativeInterface.getError(handle));
@@ -81,6 +86,10 @@ public class I2C {
* @webref
*/
public void close() {
+ if (NativeInterface.isSimulated()) {
+ return;
+ }
+
NativeInterface.closeDevice(handle);
handle = 0;
}
@@ -107,6 +116,10 @@ public class I2C {
return;
}
+ if (NativeInterface.isSimulated()) {
+ 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;
@@ -126,6 +139,11 @@ public class I2C {
* @webref
*/
public static String[] list() {
+ if (NativeInterface.isSimulated()) {
+ // as on the Raspberry Pi
+ return new String[]{ "i2c-1" };
+ }
+
ArrayList devs = new ArrayList();
File dir = new File("/dev");
File[] files = dir.listFiles();
@@ -159,6 +177,10 @@ public class I2C {
byte[] in = new byte[len];
+ if (NativeInterface.isSimulated()) {
+ return in;
+ }
+
int ret = NativeInterface.transferI2c(handle, slave, out, in);
transmitting = false;
out = null;
@@ -211,18 +233,30 @@ public class I2C {
/**
* Adds a byte to be written to the attached device
- * @param out single byte to be written (0-255)
+ * @param out single byte to be written, e.g. numeric literal (0 to 255, or -128 to 127)
* @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.");
+ if (out < -128 || 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, or -128 to 127.");
throw new RuntimeException("Argument does not fit into a single byte");
}
byte[] tmp = new byte[1];
tmp[0] = (byte)out;
write(tmp);
}
+
+ /**
+ * Adds a byte to be written to the attached device
+ * @param out single byte to be written
+ * @see beginTransmission
+ * @see read
+ * @see endTransmission
+ */
+ public void write(byte out) {
+ // cast to (unsigned) int
+ write(out & 0xff);
+ }
}
diff --git a/java/libraries/io/src/processing/io/LED.java b/java/libraries/io/src/processing/io/LED.java
index 8114e58ee..61635f4d0 100644
--- a/java/libraries/io/src/processing/io/LED.java
+++ b/java/libraries/io/src/processing/io/LED.java
@@ -53,6 +53,10 @@ public class LED {
NativeInterface.loadLibrary();
this.dev = dev;
+ if (NativeInterface.isSimulated()) {
+ return;
+ }
+
// read maximum brightness
try {
Path path = Paths.get("/sys/class/leds/" + dev + "/max_brightness");
@@ -105,11 +109,16 @@ public class LED {
* @webref
*/
public void brightness(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");
}
+
+ if (NativeInterface.isSimulated()) {
+ return;
+ }
+
+ String fn = "/sys/class/leds/" + dev + "/brightness";
int ret = NativeInterface.writeFile(fn, Integer.toString((int)(bright * maxBrightness)));
if (ret < 0) {
throw new RuntimeException(fn + ": " + NativeInterface.getError(ret));
@@ -122,6 +131,10 @@ public class LED {
* @webref
*/
public void close() {
+ if (NativeInterface.isSimulated()) {
+ return;
+ }
+
// restore previous settings
String fn = "/sys/class/leds/" + dev + "/brightness";
int ret = NativeInterface.writeFile(fn, Integer.toString(prevBrightness));
@@ -143,6 +156,11 @@ public class LED {
* @webref
*/
public static String[] list() {
+ if (NativeInterface.isSimulated()) {
+ // as on the Raspberry Pi
+ return new String[]{ "led0", "led1" };
+ }
+
ArrayList devs = new ArrayList();
File dir = new File("/sys/class/leds");
File[] files = dir.listFiles();
diff --git a/java/libraries/io/src/processing/io/NativeInterface.java b/java/libraries/io/src/processing/io/NativeInterface.java
index 5cb5105ad..ea32292c0 100644
--- a/java/libraries/io/src/processing/io/NativeInterface.java
+++ b/java/libraries/io/src/processing/io/NativeInterface.java
@@ -26,17 +26,28 @@ package processing.io;
public class NativeInterface {
protected static boolean loaded = false;
+ protected static boolean alwaysSimulate = 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");
+ if (isSimulated()) {
+ System.err.println("The Processing I/O library is not supported on this platform. Instead of values from actual hardware ports, your sketch will only receive stand-in values that allow you to test the remainder of its functionality.");
+ } else {
+ System.loadLibrary("processing-io");
}
- System.loadLibrary("processing-io");
loaded = true;
}
}
+ public static void alwaysSimulate() {
+ alwaysSimulate = true;
+ }
+
+ public static boolean isSimulated() {
+ return alwaysSimulate ||
+ !"Linux".equals(System.getProperty("os.name"));
+ }
+
public static native int openDevice(String fn);
public static native String getError(int errno);
diff --git a/java/libraries/io/src/processing/io/PWM.java b/java/libraries/io/src/processing/io/PWM.java
index 6ea050a11..455a659e4 100644
--- a/java/libraries/io/src/processing/io/PWM.java
+++ b/java/libraries/io/src/processing/io/PWM.java
@@ -57,6 +57,10 @@ public class PWM {
chip = channel.substring(0, pos);
this.channel = Integer.parseInt(channel.substring(pos+4));
+ if (NativeInterface.isSimulated()) {
+ return;
+ }
+
// export channel through sysfs
String fn = "/sys/class/pwm/" + chip + "/export";
int ret = NativeInterface.writeFile(fn, Integer.toString(this.channel));
@@ -89,6 +93,10 @@ public class PWM {
* @webref
*/
public void clear() {
+ if (NativeInterface.isSimulated()) {
+ return;
+ }
+
String fn = String.format("/sys/class/pwm/%s/pwm%d/enable", chip, channel);
int ret = NativeInterface.writeFile(fn, "0");
if (ret < 0) {
@@ -102,6 +110,10 @@ public class PWM {
* @webref
*/
public void close() {
+ if (NativeInterface.isSimulated()) {
+ return;
+ }
+
// XXX: implicit clear()?
// XXX: also check GPIO
@@ -124,6 +136,10 @@ public class PWM {
* @webref
*/
public static String[] list() {
+ if (NativeInterface.isSimulated()) {
+ return new String[]{ "pwmchip0/pwm0", "pwmchip0/pwm1" };
+ }
+
ArrayList devs = new ArrayList();
File dir = new File("/sys/class/pwm");
File[] chips = dir.listFiles();
@@ -155,6 +171,10 @@ public class PWM {
* @webref
*/
public void set(int period, float duty) {
+ if (NativeInterface.isSimulated()) {
+ return;
+ }
+
// set period
String fn = fn = String.format("/sys/class/pwm/%s/pwm%d/period", chip, channel);
// convert to nanoseconds
diff --git a/java/libraries/io/src/processing/io/SPI.java b/java/libraries/io/src/processing/io/SPI.java
index 83b9b7a4c..1b55134af 100644
--- a/java/libraries/io/src/processing/io/SPI.java
+++ b/java/libraries/io/src/processing/io/SPI.java
@@ -78,6 +78,11 @@ public class SPI {
public SPI(String dev) {
NativeInterface.loadLibrary();
this.dev = dev;
+
+ if (NativeInterface.isSimulated()) {
+ return;
+ }
+
handle = NativeInterface.openDevice("/dev/" + dev);
if (handle < 0) {
throw new RuntimeException(NativeInterface.getError(handle));
@@ -90,6 +95,10 @@ public class SPI {
* @webref
*/
public void close() {
+ if (NativeInterface.isSimulated()) {
+ return;
+ }
+
NativeInterface.closeDevice(handle);
handle = 0;
}
@@ -110,6 +119,11 @@ public class SPI {
* @webref
*/
public static String[] list() {
+ if (NativeInterface.isSimulated()) {
+ // as on the Raspberry Pi
+ return new String[]{ "spidev0.0", "spidev0.1" };
+ }
+
ArrayList devs = new ArrayList();
File dir = new File("/dev");
File[] files = dir.listFiles();
@@ -148,6 +162,10 @@ public class SPI {
* @webref
*/
public byte[] transfer(byte[] out) {
+ if (NativeInterface.isSimulated()) {
+ return new byte[out.length];
+ }
+
// track the current setting per device across multiple instances
String curSettings = maxSpeed + "-" + dataOrder + "-" + mode;
if (!curSettings.equals(settings.get(dev))) {
@@ -182,16 +200,27 @@ public class SPI {
/**
* Transfers data over the SPI bus
- * @param out single byte to send
+ * @param out single byte to send, e.g. numeric literal (0 to 255, or -128 to 127)
* @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.");
+ if (out < -128 || 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, or -128 to 127.");
throw new RuntimeException("Argument does not fit into a single byte");
}
byte[] tmp = new byte[1];
tmp[0] = (byte)out;
return transfer(tmp);
}
+
+
+ /**
+ * Transfers data over the SPI bus
+ * @param out single byte to send
+ * @return bytes read in (array is the same length as out)
+ */
+ public byte[] transfer(byte out) {
+ // cast to (unsigned) int
+ return transfer(out & 0xff);
+ }
}