diff --git a/eclipse-plugins/edu.wpi.first.wpilib.plugins.core.feature/feature.xml b/eclipse-plugins/edu.wpi.first.wpilib.plugins.core.feature/feature.xml
index 3dc2c5c007..bb6555d64f 100644
--- a/eclipse-plugins/edu.wpi.first.wpilib.plugins.core.feature/feature.xml
+++ b/eclipse-plugins/edu.wpi.first.wpilib.plugins.core.feature/feature.xml
@@ -52,4 +52,11 @@
version="0.0.0"
unpack="false"/>
+
+
diff --git a/eclipse-plugins/edu.wpi.first.wpilib.plugins.riolog/META-INF/MANIFEST.MF b/eclipse-plugins/edu.wpi.first.wpilib.plugins.riolog/META-INF/MANIFEST.MF
new file mode 100644
index 0000000000..4e6a849bd8
--- /dev/null
+++ b/eclipse-plugins/edu.wpi.first.wpilib.plugins.riolog/META-INF/MANIFEST.MF
@@ -0,0 +1,11 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: WPILib_Riolog
+Bundle-SymbolicName: edu.wpi.first.wpilib.plugins.riolog;singleton:=true
+Bundle-Version: 0.1.0.qualifier
+Bundle-Activator: netconsole2.Activator
+Require-Bundle: org.eclipse.ui,
+ org.eclipse.core.runtime
+Bundle-RequiredExecutionEnvironment: JavaSE-1.8
+Bundle-ActivationPolicy: lazy
+Bundle-Vendor: WPI & FIRST
diff --git a/eclipse-plugins/edu.wpi.first.wpilib.plugins.riolog/README b/eclipse-plugins/edu.wpi.first.wpilib.plugins.riolog/README
new file mode 100644
index 0000000000..09ee25990a
--- /dev/null
+++ b/eclipse-plugins/edu.wpi.first.wpilib.plugins.riolog/README
@@ -0,0 +1,13 @@
+
+ Riolog
+
+This is an Eclipse plugin that receives and displays text from UDP port 6666. It
+defines a new view that displays the received text.
+
+To compile, open project in an Eclipse instance with the appropriate plugin
+development tools, and Export as a deployable plugin/fragment.
+
+To install, copy the resulting jar file into $eclipse/dropins/, and start
+Eclipse (-clean may be necessary).
+
+To enable, go to Window>Show View>Other... and choose General>Riolog.
diff --git a/eclipse-plugins/edu.wpi.first.wpilib.plugins.riolog/build.properties b/eclipse-plugins/edu.wpi.first.wpilib.plugins.riolog/build.properties
new file mode 100644
index 0000000000..caaec514ba
--- /dev/null
+++ b/eclipse-plugins/edu.wpi.first.wpilib.plugins.riolog/build.properties
@@ -0,0 +1,7 @@
+source.. = src/
+output.. = bin/
+bin.includes = plugin.xml,\
+ META-INF/,\
+ .,\
+ icons/,\
+ contexts.xml
diff --git a/eclipse-plugins/edu.wpi.first.wpilib.plugins.riolog/contexts.xml b/eclipse-plugins/edu.wpi.first.wpilib.plugins.riolog/contexts.xml
new file mode 100644
index 0000000000..c3ed6a6474
--- /dev/null
+++ b/eclipse-plugins/edu.wpi.first.wpilib.plugins.riolog/contexts.xml
@@ -0,0 +1,13 @@
+
+
+
+ This is the context help for the Riolog It was generated by a PDE template.
+
+
+
+
+
+
+
+
+
diff --git a/eclipse-plugins/edu.wpi.first.wpilib.plugins.riolog/icons/riolog.png b/eclipse-plugins/edu.wpi.first.wpilib.plugins.riolog/icons/riolog.png
new file mode 100644
index 0000000000..2d6bb56c0b
Binary files /dev/null and b/eclipse-plugins/edu.wpi.first.wpilib.plugins.riolog/icons/riolog.png differ
diff --git a/eclipse-plugins/edu.wpi.first.wpilib.plugins.riolog/plugin.xml b/eclipse-plugins/edu.wpi.first.wpilib.plugins.riolog/plugin.xml
new file mode 100644
index 0000000000..57fe851dc4
--- /dev/null
+++ b/eclipse-plugins/edu.wpi.first.wpilib.plugins.riolog/plugin.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/eclipse-plugins/edu.wpi.first.wpilib.plugins.riolog/pom.xml b/eclipse-plugins/edu.wpi.first.wpilib.plugins.riolog/pom.xml
new file mode 100644
index 0000000000..e246bd30e2
--- /dev/null
+++ b/eclipse-plugins/edu.wpi.first.wpilib.plugins.riolog/pom.xml
@@ -0,0 +1,15 @@
+
+
+ 4.0.0
+ edu.wpi.first.wpilib.plugins.riolog
+ eclipse-plugin
+
+
+ edu.wpi.first.wpilib.plugins
+ edu.wpi.first.wpilib.plugins
+ 0.1.0.qualifier
+ ..
+
+
+
diff --git a/eclipse-plugins/edu.wpi.first.wpilib.plugins.riolog/src/netconsole2/Activator.java b/eclipse-plugins/edu.wpi.first.wpilib.plugins.riolog/src/netconsole2/Activator.java
new file mode 100644
index 0000000000..60d069df71
--- /dev/null
+++ b/eclipse-plugins/edu.wpi.first.wpilib.plugins.riolog/src/netconsole2/Activator.java
@@ -0,0 +1,61 @@
+package netconsole2;
+
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.ui.plugin.AbstractUIPlugin;
+import org.osgi.framework.BundleContext;
+
+/**
+ * The activator class controls the plug-in life cycle
+ */
+public class Activator extends AbstractUIPlugin {
+
+ // The plug-in ID
+ public static final String PLUGIN_ID = "riolog"; //$NON-NLS-1$
+
+ // The shared instance
+ private static Activator plugin;
+
+ /**
+ * The constructor
+ */
+ public Activator() {
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext)
+ */
+ public void start(BundleContext context) throws Exception {
+ super.start(context);
+ plugin = this;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext)
+ */
+ public void stop(BundleContext context) throws Exception {
+ plugin = null;
+ super.stop(context);
+ }
+
+ /**
+ * Returns the shared instance
+ *
+ * @return the shared instance
+ */
+ public static Activator getDefault() {
+ return plugin;
+ }
+
+ /**
+ * Returns an image descriptor for the image file at the given
+ * plug-in relative path
+ *
+ * @param path the path
+ * @return the image descriptor
+ */
+ public static ImageDescriptor getImageDescriptor(String path) {
+ return imageDescriptorFromPlugin(PLUGIN_ID, path);
+ }
+}
diff --git a/eclipse-plugins/edu.wpi.first.wpilib.plugins.riolog/src/netconsole2/views/RiologView.java b/eclipse-plugins/edu.wpi.first.wpilib.plugins.riolog/src/netconsole2/views/RiologView.java
new file mode 100644
index 0000000000..29d36d7829
--- /dev/null
+++ b/eclipse-plugins/edu.wpi.first.wpilib.plugins.riolog/src/netconsole2/views/RiologView.java
@@ -0,0 +1,414 @@
+package netconsole2.views;
+
+import java.io.IOException;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetSocketAddress;
+import java.net.SocketException;
+import java.util.ArrayList;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.action.IMenuListener;
+import org.eclipse.jface.action.IMenuManager;
+import org.eclipse.jface.action.IToolBarManager;
+import org.eclipse.jface.action.MenuManager;
+import org.eclipse.jface.action.Separator;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.layout.FillLayout;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Menu;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.ui.IActionBars;
+import org.eclipse.ui.ISharedImages;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.part.ViewPart;
+
+
+public class RiologView extends ViewPart {
+ public static Action confAction(String name, String tooltip, String img,
+ Action e) {
+ e.setText(name);
+ e.setToolTipText(tooltip);
+ e.setImageDescriptor(PlatformUI.getWorkbench().getSharedImages()
+ .getImageDescriptor(img));
+ return e;
+ }
+
+ public static byte[] getPacket(DatagramSocket socket, DatagramPacket buf) {
+ try {
+ socket.receive(buf);
+ } catch (IOException e) {
+ return null;
+ }
+ byte[] ret = new byte[buf.getLength()];
+ System.arraycopy(buf.getData(), 0, ret, 0, ret.length);
+ return ret;
+ }
+
+ public static DatagramSocket makeRecvSocket() {
+ DatagramSocket socket = null;
+ try {
+ socket = new DatagramSocket(null);
+ socket.setReuseAddress(true);
+ socket.bind(new InetSocketAddress(6666));
+ } catch (SocketException e) {
+ e.printStackTrace();
+ socket.close();
+ return null;
+ }
+ return socket;
+ }
+
+ public static Thread startDaemonThread(Runnable r, String name) {
+ Thread t = new Thread(r, name);
+ t.setDaemon(true);
+ t.start();
+ return t;
+ }
+
+ /**
+ * The ID of the view as specified by the extension.
+ */
+ public static final String ID = "netconsole2.views.RiologView";
+
+ Text text;
+ Thread listener;
+ Thread transferer;
+
+ volatile DatagramSocket socket_hook = null;
+ volatile boolean discard = false;
+ volatile boolean paused = false;
+ volatile boolean cleanup = false;
+
+ private Action clearAction;
+ private Action pauseAction;
+ private Action discardAction;
+ private Action unpauseAction;
+ private Action undiscardAction;
+ private Button discardButton;
+ private Button pauseButton;
+
+ /**
+ * The constructor.
+ */
+ public RiologView() {
+ }
+
+ private void contributeToActionBars() {
+ IActionBars bars = getViewSite().getActionBars();
+ fillLocalPullDown(bars.getMenuManager());
+ fillLocalToolBar(bars.getToolBarManager());
+ }
+
+ /**
+ * This is a callback that will allow us to create the viewer and initialize
+ * it.
+ */
+ public void createPartControl(Composite parent) {
+ GridLayout glayout = new GridLayout();
+ glayout.numColumns = 1;
+ parent.setLayout(glayout);
+
+ text = new Text(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL
+ | SWT.READ_ONLY);
+ {
+ GridData gdata = new GridData();
+ gdata.grabExcessVerticalSpace = true;
+ gdata.grabExcessHorizontalSpace = true;
+ gdata.horizontalAlignment = SWT.FILL;
+ gdata.verticalAlignment = SWT.FILL;
+ text.setLayoutData(gdata);
+ }
+
+ Composite row = new Composite(parent, SWT.NONE);
+
+ row.setLayout(new FillLayout(SWT.HORIZONTAL));
+
+ {
+ GridData gdata = new GridData();
+ gdata.grabExcessVerticalSpace = false;
+ gdata.grabExcessHorizontalSpace = true;
+ gdata.horizontalAlignment = SWT.FILL;
+ gdata.verticalAlignment = SWT.CENTER;
+ row.setLayoutData(gdata);
+ }
+
+
+
+ // Create the help context id for the viewer's control
+ PlatformUI.getWorkbench().getHelpSystem()
+ .setHelp(parent, "netconsole2.text");
+ makeActions();
+ makeButtons(row);
+ hookContextMenu();
+ contributeToActionBars();
+ startListening();
+ }
+
+ public void makeButtons(Composite parent) {
+ pauseButton = new Button(parent, SWT.TOGGLE);
+ pauseButton.setText(pauseAction.getText());
+ pauseButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ if (pauseButton.getSelection()) {
+ pauseAction.run();
+ } else {
+ unpauseAction.run();
+ }
+ }
+ });
+ discardButton = new Button(parent, SWT.TOGGLE);
+ discardButton.setText(discardAction.getText());
+ discardButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ if (discardButton.getSelection()) {
+ discardAction.run();
+ } else {
+ undiscardAction.run();
+ }
+ }
+ });
+ Button clearButton = new Button(parent, SWT.PUSH);
+ clearButton.setText(clearAction.getText());
+ clearButton.addSelectionListener(new SelectionAdapter() {
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ clearAction.run();
+ }
+ });
+ }
+
+ @Override
+ public void dispose() {
+ stopListening();
+ super.dispose();
+ }
+
+ private void fillContextMenu(IMenuManager manager) {
+ manager.add(pauseAction);
+ manager.add(unpauseAction);
+ manager.add(new Separator());
+ manager.add(clearAction);
+ manager.add(new Separator());
+ manager.add(discardAction);
+ manager.add(undiscardAction);
+ manager.add(new Separator());
+ manager.add(confAction("Copy", "Copy selected text",
+ ISharedImages.IMG_TOOL_COPY, new Action() {
+ @Override
+ public void run() {
+ text.copy();
+ }
+ }));
+ manager.add(confAction("Select All", "Select all text", "",
+ new Action() {
+ @Override
+ public void run() {
+ text.selectAll();
+ }
+ }));
+ }
+
+ private void fillLocalPullDown(IMenuManager manager) {
+ manager.add(pauseAction);
+ manager.add(unpauseAction);
+ manager.add(new Separator());
+ manager.add(clearAction);
+ manager.add(new Separator());
+ manager.add(discardAction);
+ manager.add(undiscardAction);
+ }
+
+ private void fillLocalToolBar(IToolBarManager manager) {
+ manager.add(pauseAction);
+ manager.add(unpauseAction);
+ manager.add(new Separator());
+ manager.add(clearAction);
+ manager.add(new Separator());
+ manager.add(discardAction);
+ manager.add(undiscardAction);
+ }
+
+ private void hookContextMenu() {
+ MenuManager menuMgr = new MenuManager("#PopupMenu");
+ menuMgr.setRemoveAllWhenShown(true);
+ menuMgr.addMenuListener(new IMenuListener() {
+ public void menuAboutToShow(IMenuManager manager) {
+ RiologView.this.fillContextMenu(manager);
+ }
+ });
+ Menu menu = menuMgr.createContextMenu(text);
+ text.setMenu(menu);
+ }
+
+ private void makeActions() {
+ clearAction = confAction("Clear Log", "Empty the textbox",
+ ISharedImages.IMG_ETOOL_CLEAR, new Action() {
+ public void run() {
+ text.setText("");
+ }
+ });
+
+ pauseAction = confAction("Pause Display",
+ "Stop adding packets to the textbox",
+ ISharedImages.IMG_ELCL_STOP, new Action() {
+ public void run() {
+ pauseAction.setEnabled(false);
+ unpauseAction.setEnabled(true);
+ pauseButton.setSelection(true);
+ pauseButton.setText("Show 0 Packets");
+ paused = true;
+ }
+ });
+ pauseAction.setEnabled(true);
+ unpauseAction = confAction("Continue Display",
+ "Continue adding packets to the textbox",
+ ISharedImages.IMG_TOOL_FORWARD, new Action() {
+ public void run() {
+ paused = false;
+ transferer.interrupt();
+ pauseAction.setEnabled(true);
+ unpauseAction.setEnabled(false);
+ pauseButton.setSelection(false);
+ pauseButton.setText(pauseAction.getText());
+ }
+ });
+ unpauseAction.setEnabled(false);
+
+ discardAction = confAction("Discard Incoming",
+ "Drop all incoming packets", ISharedImages.IMG_ETOOL_DELETE,
+ new Action() {
+ public void run() {
+ discard = true;
+ discardAction.setEnabled(false);
+ undiscardAction.setEnabled(true);
+ discardButton.setSelection(true);
+ discardButton.setText(undiscardAction.getText());
+ }
+ });
+ discardAction.setEnabled(true);
+ undiscardAction = confAction("Accept Incoming",
+ "Accept all incoming packets", ISharedImages.IMG_OBJ_ADD,
+ new Action() {
+ public void run() {
+ discard = false;
+ discardAction.setEnabled(true);
+ undiscardAction.setEnabled(false);
+ discardButton.setSelection(false);
+ discardButton.setText(discardAction.getText());
+ }
+ });
+ undiscardAction.setEnabled(false);
+ }
+
+ /**
+ * Passing the focus request to the viewer's control.
+ */
+ public void setFocus() {
+ text.setFocus();
+ }
+
+ public static String drainToString(ArrayList arr) {
+ int netlength = 0;
+ for (byte[] b : arr) {
+ netlength += b.length;
+ }
+
+ byte[] sum = new byte[netlength];
+ int mark = 0;
+ for (int i=0;i queue = new LinkedBlockingQueue<>();
+ listener = startDaemonThread(new Runnable() {
+ @Override
+ public void run() {
+ DatagramSocket socket = makeRecvSocket();
+ if (socket == null)
+ return;
+ socket_hook = socket;
+ byte[] buf = new byte[4096];
+ DatagramPacket datagram = new DatagramPacket(buf, buf.length);
+ while (!Thread.interrupted()) {
+ byte[] s = getPacket(socket, datagram);
+ if (s != null && !discard) {
+ try {
+ queue.put(s);
+ } catch (InterruptedException e) {
+ socket.close();
+ return;
+ }
+ }
+ }
+ socket.close();
+ }
+ }, "Riolog-Listener");
+ transferer = startDaemonThread(new Runnable() {
+ @Override
+ public void run() {
+ final ArrayList temp = new ArrayList<>();
+ while (!cleanup) {
+ try {
+ temp.add(queue.take());
+ } catch (InterruptedException e) {
+ if (cleanup) {
+ return;
+ }
+ }
+ queue.drainTo(temp);
+ if (!paused) {
+ Display.getDefault().syncExec(new Runnable() {
+ @Override
+ public void run() {
+ if (text.isDisposed())
+ return;
+ text.append(drainToString(temp));
+ }
+ });
+ } else {
+ Display.getDefault().syncExec(new Runnable() {
+ @Override
+ public void run() {
+ if (paused) {
+ if (temp.size() == 1) {
+ pauseButton.setText("Show 1 Packet\u2002");
+ } else {
+ pauseButton.setText("Show " + String.valueOf(temp.size()) + " Packets");
+ }
+ }
+ }
+ });
+ }
+ }
+ }
+ }, "Riolog-Transfer");
+ }
+
+ void stopListening() {
+ cleanup = true;
+ if (socket_hook != null) {
+ socket_hook.close();
+ }
+ listener.interrupt();
+ transferer.interrupt();
+ }
+}
\ No newline at end of file
diff --git a/eclipse-plugins/pom.xml b/eclipse-plugins/pom.xml
index 34e4c6ba5c..ddd3a50d8a 100644
--- a/eclipse-plugins/pom.xml
+++ b/eclipse-plugins/pom.xml
@@ -14,12 +14,13 @@
edu.wpi.first.wpilib.plugins.cpp.feature
edu.wpi.first.wpilib.plugins.java
edu.wpi.first.wpilib.plugins.java.feature
+ edu.wpi.first.wpilib.plugins.riolog
edu.wpi.first.wpilib.plugins.updatesite
- 0.18.1
+ 0.21.0