Skip to content
Snippets Groups Projects
OPC.pde 11.2 KiB
Newer Older
  • Learn to ignore specific revisions
  • /*
     * 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
    
      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.registerDraw(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] = 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 {
    
          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] = 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 {
    
          pending.write(header);
          pending.write(content);
    
      // 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);
    
        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) {
          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;
    
        // 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) {
          }