Permalink
Find file
238dbfe Jul 12, 2016
@ThomasJClark @SamCarlberg
139 lines (115 sloc) 5.64 KB
package edu.wpi.grip.smartdashboard;
import edu.wpi.first.smartdashboard.gui.StaticWidget;
import edu.wpi.first.smartdashboard.properties.IntegerProperty;
import edu.wpi.first.smartdashboard.properties.Property;
import edu.wpi.first.smartdashboard.properties.StringProperty;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.io.*;
import java.net.Socket;
import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
import java.util.logging.StreamHandler;
public class GRIPExtension extends StaticWidget {
public final static String NAME = "GRIP Output Viewer";
private final static Logger logger = Logger.getLogger(GRIPExtension.class.getName());
static {
Logger.getGlobal().addHandler(new StreamHandler(new FileOutputStream(FileDescriptor.err), new SimpleFormatter()));
}
private final static int PORT = 1180;
private final static byte[] MAGIC_NUMBERS = {0x01, 0x00, 0x00, 0x00};
private final static int HW_COMPRESSION = -1;
private final static int SIZE_640x480 = 0;
public final IntegerProperty fpsProperty = new IntegerProperty(this, "FPS", 30);
public final StringProperty addressProperty = new StringProperty(this, "GRIP Address", "localhost");
private final GRIPImage gripImage = new GRIPImage();
private final GRIPReportList gripReportList = new GRIPReportList();
private boolean shutdownThread = false;
private final Thread thread = new Thread(() -> {
byte[] magic = new byte[4];
byte[] imageBuffer = new byte[64 * 1024];
// Loop until the widget is removed or SmartDashboard is closed. The outer loop only completes an
// iteration when the thread is interrupted or an exception happens.
while (!shutdownThread) {
try {
try (Socket socket = new Socket(addressProperty.getValue(), PORT);
DataInputStream inputStream = new DataInputStream(socket.getInputStream());
DataOutputStream outputStream = new DataOutputStream(socket.getOutputStream())) {
logger.info("Established connection to " + socket.getInetAddress());
// In the FRC dashboard protocol, the client (us) starts out by sending three 32-bit integers
// (FPS, compression level, and a size enum). FPS is the only one actually recognized by
// GRIP.
outputStream.writeInt(fpsProperty.getValue());
outputStream.writeInt(HW_COMPRESSION);
outputStream.writeInt(SIZE_640x480);
while (!Thread.currentThread().isInterrupted()) {
// Each frame in the FRC dashboard image protocol starts with a 4 magic numbers. If we
// don't get those four numbers, something's wrong.
inputStream.readFully(magic);
if (!Arrays.equals(magic, MAGIC_NUMBERS)) {
throw new IOException("Invalid stream (wrong magic numbers: " + Arrays.toString(magic) + ")");
}
// Next, the server sends a 32-bit number indicating the number of bytes in this frame,
// then the raw bytes.
int imageSize = inputStream.readInt();
imageBuffer = growIfNecessary(imageBuffer, imageSize);
inputStream.readFully(imageBuffer, 0, imageSize);
// Decode the image and redraw
gripImage.setImage(ImageIO.read(new ByteArrayInputStream(imageBuffer, 0, imageSize)));
}
} catch (IOException e) {
logger.warning(e.getMessage());
gripImage.setError(e.getMessage());
} finally {
Thread.sleep(1000); // Wait a second before trying again
}
} catch (InterruptedException e) {
// The main thread will interrupt the capture thread to indicate that properties have changed, or
// possibly that the thread should shut down.
logger.log(Level.INFO, "Capture thread interrupted");
}
}
}, "Capture");
@Override
public void init() {
JSplitPane splitPane = new JSplitPane();
splitPane.setContinuousLayout(true);
splitPane.setDividerSize(8);
splitPane.setResizeWeight(1.0);
splitPane.setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
splitPane.add(gripImage, JSplitPane.LEFT);
splitPane.add(gripReportList, JSplitPane.RIGHT);
setLayout(new BorderLayout());
add(splitPane, BorderLayout.CENTER);
gripImage.setReportList(gripReportList);
thread.start();
gripReportList.start();
}
/**
* Return an array big enough to hold least at least "capacity" elements. If the supplied buffer is big enough,
* it will be reused to avoid unnecessary allocations.
*/
private static byte[] growIfNecessary(byte[] buffer, int capacity) {
if (capacity > buffer.length) {
int newCapacity = buffer.length;
while (newCapacity < capacity) {
newCapacity *= 1.5;
}
logger.info("Growing to " + newCapacity);
return new byte[newCapacity];
}
return buffer;
}
@Override
public void disconnect() {
shutdownThread = true;
thread.interrupt();
}
@Override
public void propertyChanged(Property property) {
thread.interrupt();
}
}