Skip to content

Commit 640cf32

Browse files
committed
Add NetworkTableSource
1 parent 58635d3 commit 640cf32

25 files changed

+453
-34
lines changed

core/src/main/java/edu/wpi/grip/core/GripCoreModule.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import edu.wpi.grip.core.sources.HttpSource;
1212
import edu.wpi.grip.core.sources.ImageFileSource;
1313
import edu.wpi.grip.core.sources.MultiImageFileSource;
14+
import edu.wpi.grip.core.sources.NetworkTableEntrySource;
1415
import edu.wpi.grip.core.util.ExceptionWitness;
1516
import edu.wpi.grip.core.util.GripMode;
1617

@@ -144,6 +145,9 @@ public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) {
144145
install(new FactoryModuleBuilder()
145146
.implement(HttpSource.class, HttpSource.class)
146147
.build(HttpSource.Factory.class));
148+
install(new FactoryModuleBuilder()
149+
.implement(NetworkTableEntrySource.class, NetworkTableEntrySource.class)
150+
.build(NetworkTableEntrySource.Factory.class));
147151

148152
install(new FactoryModuleBuilder().build(ExceptionWitness.Factory.class));
149153
}

core/src/main/java/edu/wpi/grip/core/Source.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import edu.wpi.grip.core.sources.HttpSource;
66
import edu.wpi.grip.core.sources.ImageFileSource;
77
import edu.wpi.grip.core.sources.MultiImageFileSource;
8+
import edu.wpi.grip.core.sources.NetworkTableEntrySource;
89
import edu.wpi.grip.core.util.ExceptionWitness;
910

1011
import com.google.common.collect.ImmutableList;
@@ -111,6 +112,8 @@ public static class SourceFactoryImpl implements SourceFactory {
111112
MultiImageFileSource.Factory multiImageFactory;
112113
@Inject
113114
HttpSource.Factory httpFactory;
115+
@Inject
116+
NetworkTableEntrySource.Factory networkTableEntryFactory;
114117

115118
@Override
116119
public Source create(Class<?> type, Properties properties) throws IOException {
@@ -122,6 +125,8 @@ public Source create(Class<?> type, Properties properties) throws IOException {
122125
return multiImageFactory.create(properties);
123126
} else if (type.isAssignableFrom(HttpSource.class)) {
124127
return httpFactory.create(properties);
128+
} else if (type.isAssignableFrom(NetworkTableEntrySource.class)) {
129+
return networkTableEntryFactory.create(properties);
125130
} else {
126131
throw new IllegalArgumentException(type + " was not a valid type");
127132
}

core/src/main/java/edu/wpi/grip/core/operations/network/GripNetworkModule.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,10 @@ protected void configure() {
3434
bind(ROSNetworkPublisherFactory.class)
3535
.annotatedWith(Names.named("rosManager"))
3636
.to(ROSManager.class);
37+
38+
// Network receiver bindings
39+
bind(MapNetworkReceiverFactory.class)
40+
.annotatedWith(Names.named("ntManager"))
41+
.to(NTManager.class);
3742
}
3843
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package edu.wpi.grip.core.operations.network;
2+
3+
4+
/**
5+
* A factory to create {@link NetworkReceiver NetworkRecievers}.
6+
*/
7+
@FunctionalInterface
8+
public interface MapNetworkReceiverFactory {
9+
NetworkReceiver create(String path);
10+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package edu.wpi.grip.core.operations.network;
2+
3+
import static com.google.common.base.Preconditions.checkArgument;
4+
5+
/**
6+
* Manages the interface between the {@link PublishAnnotatedOperation} and the actual network
7+
* protocol implemented by a specific {@link Manager}.
8+
*/
9+
public abstract class NetworkReceiver implements AutoCloseable {
10+
11+
protected final String path;
12+
13+
/**
14+
* Create a new NetworkReceiver with the specified path.
15+
*
16+
* @param path The path of the object to get
17+
*/
18+
public NetworkReceiver(String path) {
19+
checkArgument(!path.isEmpty(), "Name cannot be an empty string");
20+
this.path = path;
21+
}
22+
23+
/**
24+
* Get the value of the object.
25+
*
26+
* @return The value of this NetworkReceiver
27+
*/
28+
public abstract Object getValue();
29+
30+
/**
31+
* Close the network reciever. This should not throw an exception.
32+
*/
33+
public abstract void close();
34+
}

core/src/main/java/edu/wpi/grip/core/operations/network/networktables/NTManager.java

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import edu.wpi.grip.core.operations.network.Manager;
66
import edu.wpi.grip.core.operations.network.MapNetworkPublisher;
77
import edu.wpi.grip.core.operations.network.MapNetworkPublisherFactory;
8+
import edu.wpi.grip.core.operations.network.MapNetworkReceiverFactory;
9+
import edu.wpi.grip.core.operations.network.NetworkReceiver;
810
import edu.wpi.grip.core.settings.ProjectSettings;
911
import edu.wpi.grip.core.util.GripMode;
1012

@@ -25,7 +27,7 @@
2527
import java.util.concurrent.atomic.AtomicInteger;
2628
import java.util.logging.Level;
2729
import java.util.logging.Logger;
28-
30+
import javafx.beans.property.SimpleObjectProperty;
2931
import javax.inject.Inject;
3032

3133
import static com.google.common.base.Preconditions.checkNotNull;
@@ -34,11 +36,11 @@
3436
* This class encapsulates the way we map various settings to the global NetworkTables state.
3537
*/
3638
@Singleton
37-
public class NTManager implements Manager, MapNetworkPublisherFactory {
39+
public class NTManager implements Manager, MapNetworkPublisherFactory, MapNetworkReceiverFactory {
3840
/*
3941
* Nasty hack that is unavoidable because of how NetworkTables works.
4042
*/
41-
private static final AtomicInteger publisherCount = new AtomicInteger(0);
43+
private static final AtomicInteger count = new AtomicInteger(0);
4244

4345
/**
4446
* Information from: https://github.com/PeterJohnson/ntcore/blob/master/src/Log.h and
@@ -120,11 +122,64 @@ public void updateSettings(ProjectSettingsChangedEvent event) {
120122

121123
@Override
122124
public <P> MapNetworkPublisher<P> create(Set<String> keys) {
123-
// Keep track of ever publisher created.
124-
publisherCount.getAndAdd(1);
125+
// Keep track of every publisher created.
126+
count.getAndAdd(1);
125127
return new NTPublisher<>(keys);
126128
}
127129

130+
@Override
131+
public NetworkReceiver create(String path) {
132+
count.getAndAdd(1);
133+
return new NTReceiver(path);
134+
}
135+
136+
private static final class NTReceiver extends NetworkReceiver {
137+
138+
private int entryListenerFunctionUid;
139+
private final SimpleObjectProperty objectProperty = new SimpleObjectProperty();
140+
141+
protected NTReceiver(String path) {
142+
super(path);
143+
NetworkTablesJNI.addConnectionListener((uid, connected, conn) -> {
144+
if (connected) {
145+
addListener();
146+
NetworkTablesJNI.removeConnectionListener(uid);
147+
}
148+
}, true);
149+
synchronized (NetworkTable.class) {
150+
NetworkTable.initialize();
151+
}
152+
}
153+
154+
private void addListener() {
155+
entryListenerFunctionUid = NetworkTablesJNI.addEntryListener(path,
156+
(uid, key, value, flags) -> objectProperty.set(value),
157+
ITable.NOTIFY_IMMEDIATE
158+
| ITable.NOTIFY_NEW
159+
| ITable.NOTIFY_UPDATE
160+
| ITable.NOTIFY_DELETE
161+
| ITable.NOTIFY_LOCAL);
162+
}
163+
164+
@Override
165+
public Object getValue() {
166+
return objectProperty.getValue();
167+
}
168+
169+
@Override
170+
public void close() {
171+
NetworkTablesJNI.removeEntryListener(entryListenerFunctionUid);
172+
173+
synchronized (NetworkTable.class) {
174+
// This publisher is no longer used.
175+
if (NTManager.count.addAndGet(-1) == 0) {
176+
// We are the last publisher so shut it down
177+
NetworkTable.shutdown();
178+
}
179+
}
180+
}
181+
}
182+
128183
private static final class NTPublisher<P> extends MapNetworkPublisher<P> {
129184
private final ImmutableSet<String> keys;
130185
private Optional<String> name = Optional.empty();
@@ -184,7 +239,7 @@ public void close() {
184239
}
185240
synchronized (NetworkTable.class) {
186241
// This publisher is no longer used.
187-
if (NTManager.publisherCount.addAndGet(-1) == 0) {
242+
if (NTManager.count.addAndGet(-1) == 0) {
188243
// We are the last publisher so shut it down
189244
NetworkTable.shutdown();
190245
}

core/src/main/java/edu/wpi/grip/core/sockets/SocketHints.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,5 +193,13 @@ public static SocketHint<Number> createNumberSocketHint(final String identifier,
193193
defaultValue) {
194194
return createNumberSocketHintBuilder(identifier, defaultValue).build();
195195
}
196+
197+
public static SocketHint<String> createStringSocketHint(final String identifier,
198+
String defaultValue) {
199+
return new SocketHint.Builder<String>(String.class)
200+
.identifier(identifier)
201+
.initialValue(defaultValue)
202+
.build();
203+
}
196204
}
197205
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
package edu.wpi.grip.core.sources;
2+
3+
import edu.wpi.grip.core.Source;
4+
import edu.wpi.grip.core.events.SourceRemovedEvent;
5+
import edu.wpi.grip.core.operations.network.MapNetworkReceiverFactory;
6+
import edu.wpi.grip.core.operations.network.NetworkReceiver;
7+
import edu.wpi.grip.core.sockets.OutputSocket;
8+
import edu.wpi.grip.core.sockets.SocketHint;
9+
import edu.wpi.grip.core.sockets.SocketHints;
10+
import edu.wpi.grip.core.util.ExceptionWitness;
11+
12+
import com.google.common.collect.ImmutableList;
13+
import com.google.common.eventbus.Subscribe;
14+
import com.google.inject.assistedinject.Assisted;
15+
import com.google.inject.assistedinject.AssistedInject;
16+
import com.google.inject.name.Named;
17+
import com.thoughtworks.xstream.annotations.XStreamAlias;
18+
19+
import java.util.List;
20+
import java.util.Properties;
21+
22+
/**
23+
* Provides a way to get a {@link Types Type} from a NetworkTable that GRIP is connected to.
24+
*/
25+
@XStreamAlias("grip:NetworkValue")
26+
public class NetworkTableEntrySource extends Source {
27+
28+
private static final String PATH_PROPERTY = "networktable_path";
29+
private static final String TYPE_PROPERTY = "BOOLEAN";
30+
31+
private final OutputSocket output;
32+
private final String path;
33+
private final Types type;
34+
private NetworkReceiver networkReceiver;
35+
36+
public interface Factory {
37+
NetworkTableEntrySource create(Properties properties);
38+
39+
NetworkTableEntrySource create(String path, Types type);
40+
}
41+
42+
public enum Types {
43+
BOOLEAN, NUMBER, STRING;
44+
45+
@Override
46+
public String toString() {
47+
return super.toString().charAt(0) + super.toString().substring(1).toLowerCase();
48+
}
49+
50+
}
51+
52+
@AssistedInject
53+
NetworkTableEntrySource(
54+
ExceptionWitness.Factory exceptionWitnessFactory,
55+
OutputSocket.Factory osf,
56+
@Named("ntManager") MapNetworkReceiverFactory networkReceiverFactory,
57+
@Assisted Properties properties) {
58+
this(exceptionWitnessFactory,
59+
osf,
60+
networkReceiverFactory,
61+
properties.getProperty(PATH_PROPERTY),
62+
Types.valueOf(properties.getProperty(TYPE_PROPERTY)));
63+
}
64+
65+
@AssistedInject
66+
NetworkTableEntrySource(
67+
ExceptionWitness.Factory exceptionWitnessFactory,
68+
OutputSocket.Factory osf,
69+
@Named("ntManager") MapNetworkReceiverFactory networkReceiverFactory,
70+
@Assisted String path,
71+
@Assisted Types type) {
72+
super(exceptionWitnessFactory);
73+
this.path = path;
74+
this.type = type;
75+
networkReceiver = networkReceiverFactory.create(path);
76+
output = osf.create(createOutputSocket(type));
77+
}
78+
79+
@Override
80+
public String getName() {
81+
return path;
82+
}
83+
84+
@Override
85+
protected List<OutputSocket> createOutputSockets() {
86+
return ImmutableList.of(
87+
output
88+
);
89+
}
90+
91+
@Override
92+
protected boolean updateOutputSockets() {
93+
try {
94+
output.setValue(networkReceiver.getValue());
95+
} catch (ClassCastException ex) {
96+
getExceptionWitness().flagException(ex, getName() + " is not of type "
97+
+ output.getSocketHint().getTypeLabel());
98+
return false;
99+
}
100+
return true;
101+
}
102+
103+
@Override
104+
public Properties getProperties() {
105+
Properties properties = new Properties();
106+
properties.setProperty(PATH_PROPERTY, path);
107+
properties.setProperty(TYPE_PROPERTY, type.toString().toUpperCase());
108+
return properties;
109+
}
110+
111+
@Override
112+
public void initialize() {
113+
updateOutputSockets();
114+
}
115+
116+
@Subscribe
117+
public void onSourceRemovedEvent(SourceRemovedEvent event) {
118+
if (event.getSource() == this) {
119+
networkReceiver.close();
120+
}
121+
}
122+
123+
/**
124+
* Create a SocketHint from the given type.
125+
*
126+
* @param type The type of SocketHint to create
127+
*/
128+
private static SocketHint createOutputSocket(Types type) {
129+
switch (type) {
130+
case BOOLEAN:
131+
return SocketHints.Outputs.createBooleanSocketHint(Types.BOOLEAN.toString(), false);
132+
case NUMBER:
133+
return SocketHints.Outputs.createNumberSocketHint(Types.NUMBER.toString(), 0.0);
134+
case STRING:
135+
return SocketHints.Outputs.createStringSocketHint(Types.STRING.toString(), "");
136+
default:
137+
throw new IllegalArgumentException("Invalid NetworkTable source type");
138+
}
139+
}
140+
}

core/src/test/java/edu/wpi/grip/core/PipelineTest.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import edu.wpi.grip.core.events.ConnectionRemovedEvent;
55
import edu.wpi.grip.core.events.SourceAddedEvent;
66
import edu.wpi.grip.core.events.SourceRemovedEvent;
7+
import edu.wpi.grip.core.operations.network.MockGripNetworkModule;
78
import edu.wpi.grip.core.sockets.InputSocket;
89
import edu.wpi.grip.core.sockets.MockInputSocket;
910
import edu.wpi.grip.core.sockets.MockOutputSocket;
@@ -15,6 +16,7 @@
1516
import com.google.common.eventbus.EventBus;
1617
import com.google.inject.Guice;
1718
import com.google.inject.Injector;
19+
import com.google.inject.util.Modules;
1820

1921
import org.junit.After;
2022
import org.junit.Before;
@@ -43,7 +45,8 @@ public class PipelineTest {
4345
public void setUp() {
4446
testModule = new GripCoreTestModule();
4547
testModule.setUp();
46-
final Injector injector = Guice.createInjector(testModule);
48+
final Injector injector = Guice.createInjector(Modules.override(testModule)
49+
.with(new MockGripNetworkModule()));
4750
stepFactory = injector.getInstance(Step.Factory.class);
4851
eventBus = injector.getInstance(EventBus.class);
4952
pipeline = injector.getInstance(Pipeline.class);

0 commit comments

Comments
 (0)