Newer
Older
/*
* 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.
*/
public class OPC
{
Socket socket;
OutputStream output;
int[] pixelLocations;
byte[] packetData;
byte firmwareConfig;
String colorCorrection;
boolean enableShowLocations;
OPC(PApplet parent, String host, int port)
{
this.host = host;
this.port = port;
parent.registerDraw(this);
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
}
// 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 zig-zag grid.
// (x,y) is the center of the grid.
void ledGrid8x8(int index, float x, float y, float spacing, float angle)
{
ledGrid(index, 8, 8, x, y, spacing, spacing, angle, true);
}
// Should the pixel sampling locations be visible? This helps with debugging.
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()
void draw()
{
if (pixelLocations == null) {
// No pixels defined yet
return;
}
if (output == null) {
connect();
}
if (output == null) {
return;
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
}
int numPixels = pixelLocations.length;
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);
}
loadPixels();
int ledAddress = 4;
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;
}
}
try {
output.write(packetData);
} catch (Exception e) {
dispose();
}
if (enableShowLocations) {
updatePixels();
}
}
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();