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 = new byte[buffer.length - bufferedByteCount]; int additionalLength = client.readBytes(additional); arrayCopy(additional, 0, buffer, bufferedByteCount, additionalLength); bufferedByteCount += additionalLength; } // 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); } };