Skip to content

Instantly share code, notes, and snippets.

@abozzay

abozzay/OPC.pde

Last active Nov 5, 2017
Embed
What would you like to do?
WS2811 Pixel Control from Syphon Video via Fadecandy
{
"listen": ["127.0.0.1", 7890],
"verbose": true,
"color": {
"gamma": 2.5,
"whitepoint": [1.0, 1.0, 1.0],
"linearSlope": 1.0,
"linearCutoff": 0.0
},
"devices": [
{
"type": "fadecandy",
"serial": "GIVHQUDXLZYTODOF",
"led": null,
"dither": true,
"interpolate": true,
"map": [
[0, 0, 0, 50],
[0, 50, 64, 50],
[0, 100, 128, 50],
[0, 150, 192, 50],
[0, 200, 256, 50],
[0, 250, 320, 50],
[0, 300, 384, 50],
[0, 350, 448, 50]
]
},
{
"type": "fadecandy",
"serial": "IKYISOAUTVYZYHAD",
"led": null,
"dither": true,
"interpolate": true,
"map": [
[0, 400, 0, 50],
[0, 450, 64, 50],
[0, 500, 128, 50],
[0, 550, 192, 50],
[0, 600, 256, 50],
[0, 650, 320, 50],
[0, 700, 384, 50],
[0, 750, 448, 50]
]
},
{
"type": "fadecandy",
"serial": "UGQKPJMAESHZDLEV",
"led": null,
"dither": true,
"interpolate": true,
"map": [
[0, 800, 0, 50],
[0, 850, 64, 50],
[0, 900, 128, 50],
[0, 950, 192, 50],
[0, 1000, 256, 50],
[0, 1050, 320, 50],
[0, 1100, 384, 50],
[0, 1150, 448, 50]
]
},
{
"type": "fadecandy",
"serial": "VNVFSAAGUKFJJRWX",
"led": null,
"dither": true,
"interpolate": true,
"map": [
[0, 1200, 0, 50],
[0, 1250, 64, 50],
[0, 1300, 128, 50],
[0, 1350, 192, 50],
[0, 1400, 256, 50],
[0, 1450, 320, 50],
[0, 1500, 384, 50],
[0, 1550, 448, 50]
]
}
]
}
/*
* 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.
*
* abozzay, Nov 2017
* Updated for Processing 3
*/
import java.net.*;
import java.util.Arrays;
public class OPC
{
Socket socket;
OutputStream output;
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;
this.enableShowLocations = true;
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 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 (output == null) {
// We'll do this when we reconnect
return;
}
byte[] packet = new byte[9];
packet[0] = 0; // Channel (reserved)
packet[1] = (byte)0xFF; // Command (System Exclusive)
packet[2] = 0; // Length high byte
packet[3] = 5; // Length low byte
packet[4] = 0x00; // System ID high byte
packet[5] = 0x01; // System ID low byte
packet[6] = 0x00; // Command ID high byte
packet[7] = 0x02; // Command ID low byte
packet[8] = firmwareConfig;
try {
output.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 (output == 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] = 0; // Channel (reserved)
header[1] = (byte)0xFF; // Command (System Exclusive)
header[2] = (byte)(packetLen >> 8);
header[3] = (byte)(packetLen & 0xFF);
header[4] = 0x00; // System ID high byte
header[5] = 0x01; // System ID low byte
header[6] = 0x00; // Command ID high byte
header[7] = 0x01; // Command ID low byte
try {
output.write(header);
output.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) {
// Try to (re)connect
connect();
}
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] = 0; // Channel
packetData[1] = 0; // Command (Set pixel colors)
packetData[2] = (byte)(numBytes >> 8);
packetData[3] = (byte)(numBytes & 0xFF);
}
}
// 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) {
// Try to (re)connect
connect();
}
if (output == null) {
return;
}
try {
output.write(packetData);
} catch (Exception e) {
dispose();
}
}
void dispose()
{
// Destroy the socket. Called internally when we've disconnected.
if (output != null) {
println("Disconnected from OPC server");
}
socket = null;
output = null;
}
void connect()
{
// Try to connect to the OPC server. This normally happens automatically in draw()
try {
socket = new Socket(host, port);
socket.setTcpNoDelay(true);
output = socket.getOutputStream();
println("Connected to OPC server");
} catch (ConnectException e) {
dispose();
} catch (IOException e) {
dispose();
}
sendColorCorrectionPacket();
sendFirmwareConfigPacket();
}
}
import codeanticode.syphon.*;
OPC opc;
PImage img;
SyphonClient client;
void setup()
{
size(1344, 384, P2D);
surface.setAlwaysOnTop(true);
frameRate(30);
println("Press ESC to exit, d to list the connected server.\n");
// List Syphon Clients
println("Available Syphon servers:");
println(SyphonClient.listServers());
// Create syhpon client to receive frames
// from the first available running server:
client = new SyphonClient(this);
// Connect to the local instance of fcserver
opc = new OPC(this, "127.0.0.1", 7890);
// Draw the pixels on the screen
opc.showLocations(true);
/* ledGrid(index, stripLength, numStrips, x, y, ledSpacing, stripSpacing, angle, zigzag)
- Place a rigid grid of LEDs on the screen
- index: Number for the first LED in the grid, starting with zero
- stripLength: How long is each strip in the grid?
- numStrips: How many strips of LEDs make up the grid?
- x, y: Center location, in pixels
- ledSpacing: Spacing between LEDs, in pixels
- stripSpacing: Spacing between strips, in pixels
- angle: Angle, in radians. Positive is clockwise. 0 has pixels in a strip going left-to-right and strips going top-to-bottom.
- zigzag: true = Every other strip is reversed, false = All strips are non-reversed */
// 64 strips going right to left, each strip 50, 25 top to bottom 25 to top
opc.ledGrid(0, 25, 64, width/2, height/2, height/25, width/64, radians(90), true);
}
void draw()
{
background(0);
if (client.newFrame()) {
img = client.getImage(img);
if (img != null) {
image(img, 0, 0, width, height);
}
}
}
void keyPressed() {
if (key == ESC) {
client.stop();
} else if (key == 'd') {
println("Connected to:");
println(client.getServerName());
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment