First cut at an Open Pixel Control server
authorMicah Elizabeth Scott <micah@scanlime.org>
Sat, 8 Mar 2014 05:09:12 +0000 (21:09 -0800)
committerMicah Elizabeth Scott <micah@scanlime.org>
Sat, 8 Mar 2014 05:09:12 +0000 (21:09 -0800)
.gitignore
OpenPixelControl.pde [new file with mode: 0644]
SugarCubes.pde

index 10dda951ee749e167ecdc28a2aba8059861a083a..9c861b353a2185977b912616c532a8df92d38f18 100755 (executable)
@@ -1,3 +1,3 @@
 .DS_Store
 data/presets.txt
-
+openpixelcontrol-layout.json
diff --git a/OpenPixelControl.pde b/OpenPixelControl.pde
new file mode 100644 (file)
index 0000000..118eb2a
--- /dev/null
@@ -0,0 +1,145 @@
+import processing.net.*;
+
+// Server for Open Pixel Control patterns (http://openpixelcontrol.org/)
+class OpenPixelControl extends SCPattern {
+
+    int port = 7890;
+
+    Server server;
+    byte[] buffer;
+    int bufferedByteCount;
+
+    public OpenPixelControl(LX lx, PApplet parent) {
+        super(lx);
+        server = new Server(parent, port);
+        println("Listening for Open Pixel Control data on port " + port);
+
+        // Buffer space for two frames, worst case
+        buffer = new byte[0x10004 * 2];
+        bufferedByteCount = 0;
+
+        // Save a JSON layout file that some Open Pixel Control clients can use
+        writeMappingFile("openpixelcontrol-layout.json");
+    }
+
+    public void run(double deltaMs) {
+        readFromClient();
+    }
+
+    void readFromClient() {
+        Client client = server.available();
+
+        if (client == null) {
+            // No client; flush any stored partial frames
+            bufferedByteCount = 0;
+            return;
+        }
+
+        while (true) {
+            int available = client.available();
+            if (available <= 0) {
+                return;
+            }
+
+            if (bufferedByteCount == 0) {
+                // Read directly to buffer
+                bufferedByteCount = client.readBytes(buffer);
+            } else {
+                // Append to an earlier partial frame
+                byte[] additional = client.readBytes();
+                arrayCopy(additional, 0, buffer, bufferedByteCount, additional.length);
+                bufferedByteCount += additional.length;
+            }
+
+            // Extract OPC packets from buffer
+            int offset = 0;
+            while (bufferedByteCount - offset >= 4) {
+                int channel = buffer[offset + 0] & 0xFF;
+                int command = buffer[offset + 1] & 0xFF;
+                int length = ((buffer[offset + 2] & 0xFF) << 8) | (buffer[offset + 3] & 0xFF);
+
+                if (bufferedByteCount - offset < length + 4) {
+                    // Not enough data for a full packet yet
+                    break;
+                }
+
+                // Handle the packet in-place
+                offset += 4;
+                opcPacket(channel, command, offset, length);
+                offset += length;
+            }
+
+            // If we didn't use the whole buffer, save remainder for later
+            bufferedByteCount -= offset;
+            arrayCopy(buffer, offset, buffer, 0, bufferedByteCount);
+        }
+    }
+
+    void opcPacket(int channel, int command, int offset, int length) {
+        // Only look at "Set Pixel Colors" to channel 0.
+        if (channel == 0 && command == 0) {
+
+            // Unpack colors directly into framebuffer
+            for (int i = 0; i < length / 3; i++) {
+                if (i >= colors.length) {
+                    break;
+                }
+
+                colors[i] =
+                    ((buffer[offset + 0] & 0xFF) << 16) |
+                    ((buffer[offset + 1] & 0xFF) << 8)  |
+                     (buffer[offset + 2] & 0xFF) ;
+
+                offset += 3;
+            }
+        }
+    }
+
+    void writeMappingFile(String filename) {
+        PrintWriter output;
+        output = createWriter(filename);
+
+        // Rearrange points by color buffer index
+        LXPoint[] orderedPoints;
+        int maxIndex = 0;
+        for (LXPoint p : model.points) {
+            maxIndex = max(maxIndex, p.index);
+        }
+        orderedPoints = new LXPoint[maxIndex + 1];
+        for (LXPoint p : model.points) {
+            orderedPoints[p.index] = p;
+        }
+
+        float xCenter = (model.xMax + model.xMin) / 2.0;
+        float yCenter = (model.yMax + model.yMin) / 2.0;
+        float zCenter = (model.zMax + model.zMin) / 2.0;
+        float xSize = model.xMax - model.xMin;
+        float zSize = model.zMax - model.zMin;
+        float maxSize = max(xSize, zSize);
+        float scale = 4.0 / maxSize;
+
+        output.print("[");
+        for (int i = 0; i < orderedPoints.length; i++) {
+            boolean isLast = i == orderedPoints.length - 1;
+            String comma = isLast ? "" : ",";
+            LXPoint p = orderedPoints[i];
+
+            if (p == null) {
+                // Unused index
+                output.print("null" + comma);
+            } else {
+                // Transform coordinates to make sense in the OPC visualizer
+                float x = (p.x - xCenter) * scale;
+                float y = (p.z - zCenter) * scale;
+                float z = (p.y - yCenter) * scale;
+
+                output.print("{\"point\":[" + x + "," + y + "," + z + "]}" + comma);
+            }
+        }
+        output.print("]");
+
+        output.flush();
+        output.close();
+        println("Saved OpenPixelControl model to " + filename);
+    }
+};
index 9d72c5dfd49a6c0b2443d61e199ef611f82bca4c..f218ccc3d3979e9f2e0064d330c8092f7e0d62a9 100644 (file)
@@ -119,6 +119,9 @@ LXPattern[] patterns(LX lx) {
     // Micah
     new Rings(lx),
 
+    // Open pixel control server
+    new OpenPixelControl(lx, this),
+
     // Basic test patterns for reference, not art    
     new TestCubePattern(lx),
     new TestTowerPattern(lx),