Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions core/src/main/java/edu/wpi/grip/core/GRIPCoreModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import edu.wpi.grip.core.sources.CameraSource;
import edu.wpi.grip.core.sources.ImageFileSource;
import edu.wpi.grip.core.sources.MultiImageFileSource;
import edu.wpi.grip.core.sources.NetworkValueSource;
import edu.wpi.grip.core.util.ExceptionWitness;
import edu.wpi.grip.core.util.GRIPMode;

Expand Down Expand Up @@ -119,6 +120,9 @@ public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) {
install(new FactoryModuleBuilder()
.implement(MultiImageFileSource.class, MultiImageFileSource.class)
.build(MultiImageFileSource.Factory.class));
install(new FactoryModuleBuilder()
.implement(NetworkValueSource.class, NetworkValueSource.class)
.build(NetworkValueSource.Factory.class));

install(new FactoryModuleBuilder().build(ExceptionWitness.Factory.class));
}
Expand Down
4 changes: 4 additions & 0 deletions core/src/main/java/edu/wpi/grip/core/Source.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import edu.wpi.grip.core.sources.CameraSource;
import edu.wpi.grip.core.sources.ImageFileSource;
import edu.wpi.grip.core.sources.MultiImageFileSource;
import edu.wpi.grip.core.sources.NetworkValueSource;
import edu.wpi.grip.core.util.ExceptionWitness;

import java.io.IOException;
Expand All @@ -28,12 +29,15 @@ public static class SourceFactoryImpl implements SourceFactory {
ImageFileSource.Factory imageFactory;
@Inject
MultiImageFileSource.Factory multiImageFactory;
@Inject
NetworkValueSource.Factory networkValueFactory;

@Override
public Source create(Class type, Properties properties) throws IOException {
if (type.isAssignableFrom(CameraSource.class)) return cameraFactory.create(properties);
else if (type.isAssignableFrom(ImageFileSource.class)) return imageFactory.create(properties);
else if (type.isAssignableFrom(MultiImageFileSource.class)) return multiImageFactory.create(properties);
else if (type.isAssignableFrom(NetworkValueSource.class)) return networkValueFactory.create(properties);
else throw new IllegalArgumentException(type + " was not a valid type");
}
}
Expand Down
7 changes: 7 additions & 0 deletions core/src/main/java/edu/wpi/grip/core/sockets/SocketHints.java
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,13 @@ public static SocketHint<Boolean> createBooleanSocketHint(final String identifie
public static SocketHint<Number> createNumberSocketHint(final String identifier, Number defaultValue) {
return createNumberSocketHintBuilder(identifier, defaultValue).build();
}

public static SocketHint<String> createTextSocketHint(final String identifier, final String defaultValue) {
return new SocketHint.Builder<>(String.class)
.identifier(identifier)
.initialValue(defaultValue)
.build();
}
}

public static <T extends Enum<T>> SocketHint<T> createEnumSocketHint(final String identifier,
Expand Down
199 changes: 199 additions & 0 deletions core/src/main/java/edu/wpi/grip/core/sources/NetworkValueSource.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
package edu.wpi.grip.core.sources;


import com.google.common.base.StandardSystemProperty;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import edu.wpi.first.wpilibj.networktables.NetworkTable;
import edu.wpi.first.wpilibj.tables.ITable;
import edu.wpi.grip.core.sockets.OutputSocket;
import edu.wpi.grip.core.sockets.SocketHint;
import edu.wpi.grip.core.sockets.SocketHints;
import edu.wpi.grip.core.Source;
import edu.wpi.grip.core.events.SourceHasPendingUpdateEvent;
import edu.wpi.grip.core.util.ExceptionWitness;
import edu.wpi.grip.core.util.service.LoggingListener;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


import java.io.IOException;
import java.util.Optional;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import java.util.Properties;
import java.util.logging.Logger;

/**
* Provides a way to get a value from NetworkTables.
*/
@XStreamAlias(value = "grip:NetworkValue")
public class NetworkValueSource extends Source {

private final static String KEY_PROPERTY = "key";
private final static String TYPE_PROPERTY = "type";
private static final Logger logger = Logger.getLogger(NetworkValueSource.class.getName());

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


private final String name;

private final EventBus eventBus;
private final Properties properties;

private final SocketHint<Number> numberOutputHint = SocketHints.createNumberSocketHint("Value", 0);
private final SocketHint<Boolean> booleanOutputHint = SocketHints.createBooleanSocketHint("Value", false);
private final SocketHint<String> stringOutputHint = SocketHints.Outputs.createTextSocketHint("Value", "");
private final OutputSocket<Number> numberOutputSocket;
private final OutputSocket<Boolean> booleanOutputSocket;
private final OutputSocket<String> stringOutputSocket;
private final OutputSocket outputSocket;
private final String key;
private volatile boolean booleanPrevValue = false;
private volatile double numberPrevValue = 0;
private volatile String stringPrevValue = "";
private ITable table = NetworkTable.getTable("GRIP");

public enum ValueType {
BOOLEAN,
NUMBER,
STRING,
}
private final ValueType valueType;

public interface Factory {
NetworkValueSource create(String key, ValueType valueType) throws IOException;

NetworkValueSource create(Properties properties) throws IOException;
}

/**
* Creates a camera source that can be used as an input to a pipeline
*
* @param eventBus The EventBus to attach to
* @param address A URL to stream video from an IP camera
*/
@AssistedInject
NetworkValueSource(
final EventBus eventBus,
final ExceptionWitness.Factory exceptionWitnessFactory,
@Assisted final String key,
@Assisted final ValueType valueType) throws IOException {
this(eventBus, exceptionWitnessFactory, createProperties(key, valueType));
}

/**
* Used for serialization
*/
@AssistedInject
NetworkValueSource(
final EventBus eventBus,
final ExceptionWitness.Factory exceptionWitnessFactory,
@Assisted final Properties properties) {
super(exceptionWitnessFactory);
this.eventBus = eventBus;
this.properties = properties;

final String keyProperty = properties.getProperty(KEY_PROPERTY);
if (keyProperty == null) {
throw new IllegalArgumentException("Cannot initialize NetworkValueSource without a key");
}
this.name = "Network Value " + keyProperty;
this.key = keyProperty;

final String typeProperty = properties.getProperty(TYPE_PROPERTY);
if (typeProperty == null) {
throw new IllegalArgumentException("Cannot initialize NetworkValueSource without a type");
}
if (typeProperty.equals("BOOLEAN")) {
this.booleanOutputSocket = new OutputSocket<>(eventBus, booleanOutputHint);
this.numberOutputSocket = null;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this.stringOutputSocket = null;
this.outputSocket = booleanOutputSocket;
this.valueType = ValueType.BOOLEAN;
} else if (typeProperty.equals("NUMBER")) {
this.booleanOutputSocket = null;
this.numberOutputSocket = new OutputSocket<>(eventBus, numberOutputHint);
this.stringOutputSocket = null;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this.outputSocket = numberOutputSocket;
this.valueType = ValueType.NUMBER;
} else if (typeProperty.equals("STRING")) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this.booleanOutputSocket = null;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this.numberOutputSocket = null;
this.stringOutputSocket = new OutputSocket<>(eventBus, stringOutputHint);
this.outputSocket = stringOutputSocket;
this.valueType = ValueType.STRING;
} else {
throw new IllegalArgumentException("NetworkValueSource does not support type " + typeProperty);
}

table.addTableListener(key, (source, key, value, isNew) -> {
eventBus.post(new SourceHasPendingUpdateEvent(this));
}, true);
}

@Override
public String getName() {
return this.name;
}

@Override
public OutputSocket[] createOutputSockets() {
return new OutputSocket[]{outputSocket};
}

@Override
protected boolean updateOutputSockets() {
if (valueType == ValueType.BOOLEAN) {
boolean newValue = table.getBoolean(key, false);
// We have a new value then we need to update the socket value
if (newValue != booleanPrevValue) {
booleanPrevValue = newValue;
// Update the value
booleanOutputSocket.setValue(newValue);
// We have updated output sockets
return true;
} else {
return false; // No output sockets were updated
}
} else if (valueType == ValueType.NUMBER) {
double newValue = table.getNumber(key, 0);
// We have a new value then we need to update the socket value
if (newValue != numberPrevValue) {
numberPrevValue = newValue;
// Update the value
numberOutputSocket.setValue(newValue);
// We have updated output sockets
return true;
} else {
return false; // No output sockets were updated
}
} else if (valueType == ValueType.STRING) {
String newValue = table.getString(key, "");
// We have a new value then we need to update the socket value
if (!newValue.equals(stringPrevValue)) {
stringPrevValue = newValue;
// Update the value
stringOutputSocket.setValue(newValue);
// We have updated output sockets
return true;
} else {
return false; // No output sockets were updated
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a lot of duplicate code here. Is there any way to generify it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking of refactoring the type-specific handling to an internal class to make it a bit cleaner and get rid of the null assignments, but I don't know how much code can be deduplicated because fundamentally there are different types and thus different function calls required in this section in particular.

} else {
return false;
}
}

@Override
public Properties getProperties() {
return this.properties;
}

@Override
public void initialize() {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

private static Properties createProperties(String key, ValueType valueType) {
final Properties properties = new Properties();
properties.setProperty(KEY_PROPERTY, key);
properties.setProperty(TYPE_PROPERTY, valueType.toString());
return properties;
}
}
59 changes: 55 additions & 4 deletions ui/src/main/java/edu/wpi/grip/ui/pipeline/AddSourceView.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@
import edu.wpi.grip.core.sources.CameraSource;
import edu.wpi.grip.core.sources.ImageFileSource;
import edu.wpi.grip.core.sources.MultiImageFileSource;
import edu.wpi.grip.core.sources.NetworkValueSource;
import edu.wpi.grip.ui.util.DPIUtility;
import edu.wpi.grip.ui.util.SupplierWithIO;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.event.EventHandler;
import javafx.scene.Parent;
import javafx.scene.control.*;
Expand Down Expand Up @@ -48,6 +50,7 @@ public class AddSourceView extends HBox {
private final MultiImageFileSource.Factory multiImageSourceFactory;
private final ImageFileSource.Factory imageSourceFactory;
private final CameraSource.Factory cameraSourceFactory;
private final NetworkValueSource.Factory networkValueSourceFactory;

private final Button webcamButton;
private final Button ipcamButton;
Expand All @@ -57,17 +60,27 @@ private static class SourceDialog extends Dialog<ButtonType> {
private final Text errorText = new Text();

private SourceDialog(final Parent root, Control inputField) {
this(root, inputField, null);
}

private SourceDialog(final Parent root, Control inputField1, Control inputField2) {
super();

this.getDialogPane().getStyleClass().add(SOURCE_DIALOG_STYLE_CLASS);

final GridPane gridContent = new GridPane();
gridContent.setMaxWidth(Double.MAX_VALUE);
GridPane.setHgrow(inputField, Priority.ALWAYS);
if (inputField2 != null) {
GridPane.setHgrow(inputField2, Priority.ALWAYS);
}
GridPane.setHgrow(inputField1, Priority.ALWAYS);
GridPane.setHgrow(errorText, Priority.NEVER);
errorText.wrappingWidthProperty().bind(inputField.widthProperty());
errorText.wrappingWidthProperty().bind(inputField1.widthProperty());
gridContent.add(errorText, 0, 0);
gridContent.add(inputField, 0, 1);
gridContent.add(inputField1, 0, 1);
if (inputField2 != null) {
gridContent.add(inputField2, 0, 2);
}

getDialogPane().setContent(gridContent);
getDialogPane().setStyle(root.getStyle());
Expand All @@ -84,11 +97,13 @@ public interface Factory {
AddSourceView(EventBus eventBus,
MultiImageFileSource.Factory multiImageSourceFactory,
ImageFileSource.Factory imageSourceFactory,
CameraSource.Factory cameraSourceFactory) {
CameraSource.Factory cameraSourceFactory,
NetworkValueSource.Factory networkValueSourceFactory) {
this.eventBus = eventBus;
this.multiImageSourceFactory = multiImageSourceFactory;
this.imageSourceFactory = imageSourceFactory;
this.cameraSourceFactory = cameraSourceFactory;
this.networkValueSourceFactory = networkValueSourceFactory;

this.setFillHeight(true);

Expand Down Expand Up @@ -201,6 +216,42 @@ public interface Factory {
dialog.errorText.setText(e.getMessage());
});
});

addButton("Add NT\nValue", getClass().getResource("/edu/wpi/grip/ui/icons/add-webcam.png"), mouseEvent -> {
final Parent root = this.getScene().getRoot();

// Show a dialog for the user to pick a NetworkTable key

final ChoiceBox type = new ChoiceBox(FXCollections.observableArrayList("Boolean", "Number", "String"));
final TextField key = new TextField();
final SourceDialog dialog = new SourceDialog(root, key, type);
key.setPromptText("Key");

dialog.setTitle("Add NetworkTable Value");
dialog.setHeaderText("Enter the NetworkTable type and key (under GRIP/)");

// If the user clicks OK, add a new source
activeDialog = Optional.of(dialog);
dialog.showAndWait().filter(Predicate.isEqual(ButtonType.OK)).ifPresent(result -> {
try {
final NetworkValueSource source;
if (type.getValue().equals("Boolean")) {
source = networkValueSourceFactory.create(key.getText(), NetworkValueSource.ValueType.BOOLEAN);
} else if (type.getValue().equals("Number")) {
source = networkValueSourceFactory.create(key.getText(), NetworkValueSource.ValueType.NUMBER);
} else if (type.getValue().equals("String")) {
source = networkValueSourceFactory.create(key.getText(), NetworkValueSource.ValueType.STRING);
} else {
throw new IOException("unrecognized type " + type.getValue());
}
source.initialize();
eventBus.post(new SourceAddedEvent(source));
} catch (IOException e) {
dialog.errorText.setText(e.getMessage());
}
});
activeDialog = Optional.empty();
});
}

/**
Expand Down