diff --git a/EmptySketch/OPC.pde b/EmptySketch/OPC.pde new file mode 100755 index 0000000..5df534d --- /dev/null +++ b/EmptySketch/OPC.pde @@ -0,0 +1,370 @@ +/* + * Simple Open Pixel Control client for Processing, + * designed to sample each LED's color from some point on the canvas. + * + * Micah Elizabeth Scott, 2013 + * This file is released into the public domain. + */ + +import java.net.*; +import java.util.Arrays; + +public class OPC implements Runnable +{ + Thread thread; + Socket socket; + OutputStream output, pending; + String host; + int port; + + int[] pixelLocations; + byte[] packetData; + byte firmwareConfig; + String colorCorrection; + boolean enableShowLocations; + + OPC(PApplet parent, String host, int port) + { + this.host = host; + this.port = port; + thread = new Thread(this); + thread.start(); + this.enableShowLocations = true; + parent.registerMethod("draw", this); + } + + // Set the location of a single LED + void led(int index, int x, int y) + { + // For convenience, automatically grow the pixelLocations array. We do want this to be an array, + // instead of a HashMap, to keep draw() as fast as it can be. + if (pixelLocations == null) { + pixelLocations = new int[index + 1]; + } else if (index >= pixelLocations.length) { + pixelLocations = Arrays.copyOf(pixelLocations, index + 1); + } + + pixelLocations[index] = x + width * y; + } + + // Set the location of several LEDs arranged in a strip. + // Angle is in radians, measured clockwise from +X. + // (x,y) is the center of the strip. + void ledStrip(int index, int count, float x, float y, float spacing, float angle, boolean reversed) + { + float s = sin(angle); + float c = cos(angle); + for (int i = 0; i < count; i++) { + led(reversed ? (index + count - 1 - i) : (index + i), + (int)(x + (i - (count-1)/2.0) * spacing * c + 0.5), + (int)(y + (i - (count-1)/2.0) * spacing * s + 0.5)); + } + } + + // Set the locations of a ring of LEDs. The center of the ring is at (x, y), + // with "radius" pixels between the center and each LED. The first LED is at + // the indicated angle, in radians, measured clockwise from +X. + void ledRing(int index, int count, float x, float y, float radius, float angle) + { + for (int i = 0; i < count; i++) { + float a = angle + i * 2 * PI / count; + led(index + i, (int)(x - radius * cos(a) + 0.5), + (int)(y - radius * sin(a) + 0.5)); + } + } + + // Set the location of several LEDs arranged in a grid. The first strip is + // at 'angle', measured in radians clockwise from +X. + // (x,y) is the center of the grid. + void ledGrid(int index, int stripLength, int numStrips, float x, float y, + float ledSpacing, float stripSpacing, float angle, boolean zigzag) + { + float s = sin(angle + HALF_PI); + float c = cos(angle + HALF_PI); + for (int i = 0; i < numStrips; i++) { + ledStrip(index + stripLength * i, stripLength, + x + (i - (numStrips-1)/2.0) * stripSpacing * c, + y + (i - (numStrips-1)/2.0) * stripSpacing * s, ledSpacing, + angle, zigzag && (i % 2) == 1); + } + } + + // Set the location of 64 LEDs arranged in a uniform 8x8 grid. + // (x,y) is the center of the grid. + void ledGrid8x8(int index, float x, float y, float spacing, float angle, boolean zigzag) + { + ledGrid(index, 8, 8, x, y, spacing, spacing, angle, zigzag); + } + + // Should the pixel sampling locations be visible? This helps with debugging. + // Showing locations is enabled by default. You might need to disable it if our drawing + // is interfering with your processing sketch, or if you'd simply like the screen to be + // less cluttered. + void showLocations(boolean enabled) + { + enableShowLocations = enabled; + } + + // Enable or disable dithering. Dithering avoids the "stair-stepping" artifact and increases color + // resolution by quickly jittering between adjacent 8-bit brightness levels about 400 times a second. + // Dithering is on by default. + void setDithering(boolean enabled) + { + if (enabled) + firmwareConfig &= ~0x01; + else + firmwareConfig |= 0x01; + sendFirmwareConfigPacket(); + } + + // Enable or disable frame interpolation. Interpolation automatically blends between consecutive frames + // in hardware, and it does so with 16-bit per channel resolution. Combined with dithering, this helps make + // fades very smooth. Interpolation is on by default. + void setInterpolation(boolean enabled) + { + if (enabled) + firmwareConfig &= ~0x02; + else + firmwareConfig |= 0x02; + sendFirmwareConfigPacket(); + } + + // Put the Fadecandy onboard LED under automatic control. It blinks any time the firmware processes a packet. + // This is the default configuration for the LED. + void statusLedAuto() + { + firmwareConfig &= 0x0C; + sendFirmwareConfigPacket(); + } + + // Manually turn the Fadecandy onboard LED on or off. This disables automatic LED control. + void setStatusLed(boolean on) + { + firmwareConfig |= 0x04; // Manual LED control + if (on) + firmwareConfig |= 0x08; + else + firmwareConfig &= ~0x08; + sendFirmwareConfigPacket(); + } + + // Set the color correction parameters + void setColorCorrection(float gamma, float red, float green, float blue) + { + colorCorrection = "{ \"gamma\": " + gamma + ", \"whitepoint\": [" + red + "," + green + "," + blue + "]}"; + sendColorCorrectionPacket(); + } + + // Set custom color correction parameters from a string + void setColorCorrection(String s) + { + colorCorrection = s; + sendColorCorrectionPacket(); + } + + // Send a packet with the current firmware configuration settings + void sendFirmwareConfigPacket() + { + if (pending == null) { + // We'll do this when we reconnect + return; + } + + byte[] packet = new byte[9]; + packet[0] = (byte)0x00; // Channel (reserved) + packet[1] = (byte)0xFF; // Command (System Exclusive) + packet[2] = (byte)0x00; // Length high byte + packet[3] = (byte)0x05; // Length low byte + packet[4] = (byte)0x00; // System ID high byte + packet[5] = (byte)0x01; // System ID low byte + packet[6] = (byte)0x00; // Command ID high byte + packet[7] = (byte)0x02; // Command ID low byte + packet[8] = (byte)firmwareConfig; + + try { + pending.write(packet); + } catch (Exception e) { + dispose(); + } + } + + // Send a packet with the current color correction settings + void sendColorCorrectionPacket() + { + if (colorCorrection == null) { + // No color correction defined + return; + } + if (pending == null) { + // We'll do this when we reconnect + return; + } + + byte[] content = colorCorrection.getBytes(); + int packetLen = content.length + 4; + byte[] header = new byte[8]; + header[0] = (byte)0x00; // Channel (reserved) + header[1] = (byte)0xFF; // Command (System Exclusive) + header[2] = (byte)(packetLen >> 8); // Length high byte + header[3] = (byte)(packetLen & 0xFF); // Length low byte + header[4] = (byte)0x00; // System ID high byte + header[5] = (byte)0x01; // System ID low byte + header[6] = (byte)0x00; // Command ID high byte + header[7] = (byte)0x01; // Command ID low byte + + try { + pending.write(header); + pending.write(content); + } catch (Exception e) { + dispose(); + } + } + + // Automatically called at the end of each draw(). + // This handles the automatic Pixel to LED mapping. + // If you aren't using that mapping, this function has no effect. + // In that case, you can call setPixelCount(), setPixel(), and writePixels() + // separately. + void draw() + { + if (pixelLocations == null) { + // No pixels defined yet + return; + } + if (output == null) { + return; + } + + int numPixels = pixelLocations.length; + int ledAddress = 4; + + setPixelCount(numPixels); + loadPixels(); + + for (int i = 0; i < numPixels; i++) { + int pixelLocation = pixelLocations[i]; + int pixel = pixels[pixelLocation]; + + packetData[ledAddress] = (byte)(pixel >> 16); + packetData[ledAddress + 1] = (byte)(pixel >> 8); + packetData[ledAddress + 2] = (byte)pixel; + ledAddress += 3; + + if (enableShowLocations) { + pixels[pixelLocation] = 0xFFFFFF ^ pixel; + } + } + + writePixels(); + + if (enableShowLocations) { + updatePixels(); + } + } + + // Change the number of pixels in our output packet. + // This is normally not needed; the output packet is automatically sized + // by draw() and by setPixel(). + void setPixelCount(int numPixels) + { + int numBytes = 3 * numPixels; + int packetLen = 4 + numBytes; + if (packetData == null || packetData.length != packetLen) { + // Set up our packet buffer + packetData = new byte[packetLen]; + packetData[0] = (byte)0x00; // Channel + packetData[1] = (byte)0x00; // Command (Set pixel colors) + packetData[2] = (byte)(numBytes >> 8); // Length high byte + packetData[3] = (byte)(numBytes & 0xFF); // Length low byte + } + } + + // Directly manipulate a pixel in the output buffer. This isn't needed + // for pixels that are mapped to the screen. + void setPixel(int number, color c) + { + int offset = 4 + number * 3; + if (packetData == null || packetData.length < offset + 3) { + setPixelCount(number + 1); + } + + packetData[offset] = (byte) (c >> 16); + packetData[offset + 1] = (byte) (c >> 8); + packetData[offset + 2] = (byte) c; + } + + // Read a pixel from the output buffer. If the pixel was mapped to the display, + // this returns the value we captured on the previous frame. + color getPixel(int number) + { + int offset = 4 + number * 3; + if (packetData == null || packetData.length < offset + 3) { + return 0; + } + return (packetData[offset] << 16) | (packetData[offset + 1] << 8) | packetData[offset + 2]; + } + + // Transmit our current buffer of pixel values to the OPC server. This is handled + // automatically in draw() if any pixels are mapped to the screen, but if you haven't + // mapped any pixels to the screen you'll want to call this directly. + void writePixels() + { + if (packetData == null || packetData.length == 0) { + // No pixel buffer + return; + } + if (output == null) { + return; + } + + try { + output.write(packetData); + } catch (Exception e) { + dispose(); + } + } + + void dispose() + { + // Destroy the socket. Called internally when we've disconnected. + // (Thread continues to run) + if (output != null) { + println("Disconnected from OPC server"); + } + socket = null; + output = pending = null; + } + + public void run() + { + // Thread tests server connection periodically, attempts reconnection. + // Important for OPC arrays; faster startup, client continues + // to run smoothly when mobile servers go in and out of range. + for(;;) { + + if(output == null) { // No OPC connection? + try { // Make one! + socket = new Socket(host, port); + socket.setTcpNoDelay(true); + pending = socket.getOutputStream(); // Avoid race condition... + println("Connected to OPC server"); + sendColorCorrectionPacket(); // These write to 'pending' + sendFirmwareConfigPacket(); // rather than 'output' before + output = pending; // rest of code given access. + // pending not set null, more config packets are OK! + } catch (ConnectException e) { + dispose(); + } catch (IOException e) { + dispose(); + } + } + + // Pause thread to avoid massive CPU load + try { + Thread.sleep(500); + } + catch(InterruptedException e) { + } + } + } +} diff --git a/IndividualColor/IndividualColor.pde b/IndividualColor/IndividualColor.pde index ba55c46..a98e7fc 100644 --- a/IndividualColor/IndividualColor.pde +++ b/IndividualColor/IndividualColor.pde @@ -10,7 +10,7 @@ void setup() { void draw() { background(0); - + for (int i = 0; i < 8; i++) { opc.setPixel(i+8*0, color(255,0,0)); opc.setPixel(i+8*1, color(0,255,0)); @@ -23,6 +23,11 @@ void draw() { opc.setPixel(i+8*6, color(100,100,100)); opc.setPixel(i+8*7, color(200,200,200)); } + + + //opc.setPixel(5, color(255,0,0)); + //opc.setPixel(25, color(0,255,0)); + //opc.setPixel(40, color(0,0,255)); opc.writePixels(); diff --git a/IndividualColor/OPC.pde b/IndividualColor/OPC.pde new file mode 100755 index 0000000..5df534d --- /dev/null +++ b/IndividualColor/OPC.pde @@ -0,0 +1,370 @@ +/* + * Simple Open Pixel Control client for Processing, + * designed to sample each LED's color from some point on the canvas. + * + * Micah Elizabeth Scott, 2013 + * This file is released into the public domain. + */ + +import java.net.*; +import java.util.Arrays; + +public class OPC implements Runnable +{ + Thread thread; + Socket socket; + OutputStream output, pending; + String host; + int port; + + int[] pixelLocations; + byte[] packetData; + byte firmwareConfig; + String colorCorrection; + boolean enableShowLocations; + + OPC(PApplet parent, String host, int port) + { + this.host = host; + this.port = port; + thread = new Thread(this); + thread.start(); + this.enableShowLocations = true; + parent.registerMethod("draw", this); + } + + // Set the location of a single LED + void led(int index, int x, int y) + { + // For convenience, automatically grow the pixelLocations array. We do want this to be an array, + // instead of a HashMap, to keep draw() as fast as it can be. + if (pixelLocations == null) { + pixelLocations = new int[index + 1]; + } else if (index >= pixelLocations.length) { + pixelLocations = Arrays.copyOf(pixelLocations, index + 1); + } + + pixelLocations[index] = x + width * y; + } + + // Set the location of several LEDs arranged in a strip. + // Angle is in radians, measured clockwise from +X. + // (x,y) is the center of the strip. + void ledStrip(int index, int count, float x, float y, float spacing, float angle, boolean reversed) + { + float s = sin(angle); + float c = cos(angle); + for (int i = 0; i < count; i++) { + led(reversed ? (index + count - 1 - i) : (index + i), + (int)(x + (i - (count-1)/2.0) * spacing * c + 0.5), + (int)(y + (i - (count-1)/2.0) * spacing * s + 0.5)); + } + } + + // Set the locations of a ring of LEDs. The center of the ring is at (x, y), + // with "radius" pixels between the center and each LED. The first LED is at + // the indicated angle, in radians, measured clockwise from +X. + void ledRing(int index, int count, float x, float y, float radius, float angle) + { + for (int i = 0; i < count; i++) { + float a = angle + i * 2 * PI / count; + led(index + i, (int)(x - radius * cos(a) + 0.5), + (int)(y - radius * sin(a) + 0.5)); + } + } + + // Set the location of several LEDs arranged in a grid. The first strip is + // at 'angle', measured in radians clockwise from +X. + // (x,y) is the center of the grid. + void ledGrid(int index, int stripLength, int numStrips, float x, float y, + float ledSpacing, float stripSpacing, float angle, boolean zigzag) + { + float s = sin(angle + HALF_PI); + float c = cos(angle + HALF_PI); + for (int i = 0; i < numStrips; i++) { + ledStrip(index + stripLength * i, stripLength, + x + (i - (numStrips-1)/2.0) * stripSpacing * c, + y + (i - (numStrips-1)/2.0) * stripSpacing * s, ledSpacing, + angle, zigzag && (i % 2) == 1); + } + } + + // Set the location of 64 LEDs arranged in a uniform 8x8 grid. + // (x,y) is the center of the grid. + void ledGrid8x8(int index, float x, float y, float spacing, float angle, boolean zigzag) + { + ledGrid(index, 8, 8, x, y, spacing, spacing, angle, zigzag); + } + + // Should the pixel sampling locations be visible? This helps with debugging. + // Showing locations is enabled by default. You might need to disable it if our drawing + // is interfering with your processing sketch, or if you'd simply like the screen to be + // less cluttered. + void showLocations(boolean enabled) + { + enableShowLocations = enabled; + } + + // Enable or disable dithering. Dithering avoids the "stair-stepping" artifact and increases color + // resolution by quickly jittering between adjacent 8-bit brightness levels about 400 times a second. + // Dithering is on by default. + void setDithering(boolean enabled) + { + if (enabled) + firmwareConfig &= ~0x01; + else + firmwareConfig |= 0x01; + sendFirmwareConfigPacket(); + } + + // Enable or disable frame interpolation. Interpolation automatically blends between consecutive frames + // in hardware, and it does so with 16-bit per channel resolution. Combined with dithering, this helps make + // fades very smooth. Interpolation is on by default. + void setInterpolation(boolean enabled) + { + if (enabled) + firmwareConfig &= ~0x02; + else + firmwareConfig |= 0x02; + sendFirmwareConfigPacket(); + } + + // Put the Fadecandy onboard LED under automatic control. It blinks any time the firmware processes a packet. + // This is the default configuration for the LED. + void statusLedAuto() + { + firmwareConfig &= 0x0C; + sendFirmwareConfigPacket(); + } + + // Manually turn the Fadecandy onboard LED on or off. This disables automatic LED control. + void setStatusLed(boolean on) + { + firmwareConfig |= 0x04; // Manual LED control + if (on) + firmwareConfig |= 0x08; + else + firmwareConfig &= ~0x08; + sendFirmwareConfigPacket(); + } + + // Set the color correction parameters + void setColorCorrection(float gamma, float red, float green, float blue) + { + colorCorrection = "{ \"gamma\": " + gamma + ", \"whitepoint\": [" + red + "," + green + "," + blue + "]}"; + sendColorCorrectionPacket(); + } + + // Set custom color correction parameters from a string + void setColorCorrection(String s) + { + colorCorrection = s; + sendColorCorrectionPacket(); + } + + // Send a packet with the current firmware configuration settings + void sendFirmwareConfigPacket() + { + if (pending == null) { + // We'll do this when we reconnect + return; + } + + byte[] packet = new byte[9]; + packet[0] = (byte)0x00; // Channel (reserved) + packet[1] = (byte)0xFF; // Command (System Exclusive) + packet[2] = (byte)0x00; // Length high byte + packet[3] = (byte)0x05; // Length low byte + packet[4] = (byte)0x00; // System ID high byte + packet[5] = (byte)0x01; // System ID low byte + packet[6] = (byte)0x00; // Command ID high byte + packet[7] = (byte)0x02; // Command ID low byte + packet[8] = (byte)firmwareConfig; + + try { + pending.write(packet); + } catch (Exception e) { + dispose(); + } + } + + // Send a packet with the current color correction settings + void sendColorCorrectionPacket() + { + if (colorCorrection == null) { + // No color correction defined + return; + } + if (pending == null) { + // We'll do this when we reconnect + return; + } + + byte[] content = colorCorrection.getBytes(); + int packetLen = content.length + 4; + byte[] header = new byte[8]; + header[0] = (byte)0x00; // Channel (reserved) + header[1] = (byte)0xFF; // Command (System Exclusive) + header[2] = (byte)(packetLen >> 8); // Length high byte + header[3] = (byte)(packetLen & 0xFF); // Length low byte + header[4] = (byte)0x00; // System ID high byte + header[5] = (byte)0x01; // System ID low byte + header[6] = (byte)0x00; // Command ID high byte + header[7] = (byte)0x01; // Command ID low byte + + try { + pending.write(header); + pending.write(content); + } catch (Exception e) { + dispose(); + } + } + + // Automatically called at the end of each draw(). + // This handles the automatic Pixel to LED mapping. + // If you aren't using that mapping, this function has no effect. + // In that case, you can call setPixelCount(), setPixel(), and writePixels() + // separately. + void draw() + { + if (pixelLocations == null) { + // No pixels defined yet + return; + } + if (output == null) { + return; + } + + int numPixels = pixelLocations.length; + int ledAddress = 4; + + setPixelCount(numPixels); + loadPixels(); + + for (int i = 0; i < numPixels; i++) { + int pixelLocation = pixelLocations[i]; + int pixel = pixels[pixelLocation]; + + packetData[ledAddress] = (byte)(pixel >> 16); + packetData[ledAddress + 1] = (byte)(pixel >> 8); + packetData[ledAddress + 2] = (byte)pixel; + ledAddress += 3; + + if (enableShowLocations) { + pixels[pixelLocation] = 0xFFFFFF ^ pixel; + } + } + + writePixels(); + + if (enableShowLocations) { + updatePixels(); + } + } + + // Change the number of pixels in our output packet. + // This is normally not needed; the output packet is automatically sized + // by draw() and by setPixel(). + void setPixelCount(int numPixels) + { + int numBytes = 3 * numPixels; + int packetLen = 4 + numBytes; + if (packetData == null || packetData.length != packetLen) { + // Set up our packet buffer + packetData = new byte[packetLen]; + packetData[0] = (byte)0x00; // Channel + packetData[1] = (byte)0x00; // Command (Set pixel colors) + packetData[2] = (byte)(numBytes >> 8); // Length high byte + packetData[3] = (byte)(numBytes & 0xFF); // Length low byte + } + } + + // Directly manipulate a pixel in the output buffer. This isn't needed + // for pixels that are mapped to the screen. + void setPixel(int number, color c) + { + int offset = 4 + number * 3; + if (packetData == null || packetData.length < offset + 3) { + setPixelCount(number + 1); + } + + packetData[offset] = (byte) (c >> 16); + packetData[offset + 1] = (byte) (c >> 8); + packetData[offset + 2] = (byte) c; + } + + // Read a pixel from the output buffer. If the pixel was mapped to the display, + // this returns the value we captured on the previous frame. + color getPixel(int number) + { + int offset = 4 + number * 3; + if (packetData == null || packetData.length < offset + 3) { + return 0; + } + return (packetData[offset] << 16) | (packetData[offset + 1] << 8) | packetData[offset + 2]; + } + + // Transmit our current buffer of pixel values to the OPC server. This is handled + // automatically in draw() if any pixels are mapped to the screen, but if you haven't + // mapped any pixels to the screen you'll want to call this directly. + void writePixels() + { + if (packetData == null || packetData.length == 0) { + // No pixel buffer + return; + } + if (output == null) { + return; + } + + try { + output.write(packetData); + } catch (Exception e) { + dispose(); + } + } + + void dispose() + { + // Destroy the socket. Called internally when we've disconnected. + // (Thread continues to run) + if (output != null) { + println("Disconnected from OPC server"); + } + socket = null; + output = pending = null; + } + + public void run() + { + // Thread tests server connection periodically, attempts reconnection. + // Important for OPC arrays; faster startup, client continues + // to run smoothly when mobile servers go in and out of range. + for(;;) { + + if(output == null) { // No OPC connection? + try { // Make one! + socket = new Socket(host, port); + socket.setTcpNoDelay(true); + pending = socket.getOutputStream(); // Avoid race condition... + println("Connected to OPC server"); + sendColorCorrectionPacket(); // These write to 'pending' + sendFirmwareConfigPacket(); // rather than 'output' before + output = pending; // rest of code given access. + // pending not set null, more config packets are OK! + } catch (ConnectException e) { + dispose(); + } catch (IOException e) { + dispose(); + } + } + + // Pause thread to avoid massive CPU load + try { + Thread.sleep(500); + } + catch(InterruptedException e) { + } + } + } +} diff --git a/Presentation.pptx b/Presentation.pptx new file mode 100644 index 0000000..4987271 Binary files /dev/null and b/Presentation.pptx differ diff --git a/README.md b/README.md index e6f82c3..af08745 100644 --- a/README.md +++ b/README.md @@ -100,10 +100,10 @@ Lets try and set up an empty sketch using the template below: OPC opc; void setup() { - size(500, 500, P3D); + size(500, 500, P3D); - opc = new OPC(this, "127.0.0.1", 7890); - opc.ledGrid8x8(0, width/2, height/2, height / 16.0, 0, false); + opc = new OPC(this, "127.0.0.1", 7890); + opc.ledGrid8x8(0, width/2, height/2, height / 16.0, 0, false); } void draw() {