Skip to content

Commit 6c7a47c

Browse files
committed
Merge branch 'feature/deadeye'
2 parents a88db33 + 504490e commit 6c7a47c

File tree

10 files changed

+405
-10
lines changed

10 files changed

+405
-10
lines changed

build.gradle

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,26 +10,19 @@ buildscript {
1010
}
1111
}
1212

13-
subprojects {
13+
configure(subprojects) {
1414
group = 'org.strykeforce.thirdcoast'
15-
version = '18.7.4'
15+
version = '18.7.5'
1616

1717
apply plugin: 'java-library'
1818
apply plugin: 'idea'
1919
apply plugin: 'groovy'
20-
apply plugin: 'jaci.openrio.gradle.GradleRIO'
2120

2221
repositories {
2322
jcenter()
2423
}
2524

26-
wpi {
27-
ctreVersion = "5.7.1.0"
28-
}
29-
3025
dependencies {
31-
implementation wpilib()
32-
implementation ctre()
3326
implementation 'org.slf4j:slf4j-api:1.7.25'
3427

3528
compileOnly 'com.google.code.findbugs:jsr305:3.0.2'
@@ -47,3 +40,16 @@ subprojects {
4740
}
4841
}
4942
}
43+
44+
configure(subprojects - project(":deadeye")) {
45+
apply plugin: 'jaci.openrio.gradle.GradleRIO'
46+
47+
wpi {
48+
ctreVersion = "5.7.1.0"
49+
}
50+
51+
dependencies {
52+
implementation wpilib()
53+
implementation ctre()
54+
}
55+
}

deadeye/build.gradle

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
apply plugin: 'com.diffplug.gradle.spotless'
2+
3+
sourceCompatibility = 1.8
4+
5+
dependencies {
6+
7+
// https://mvnrepository.com/artifact/io.reactivex.rxjava2/rxjava
8+
implementation group: 'io.reactivex.rxjava2', name: 'rxjava', version: '2.2.4'
9+
10+
// https://mvnrepository.com/artifact/com.jakewharton.rxrelay2/rxrelay
11+
implementation group: 'com.jakewharton.rxrelay2', name: 'rxrelay', version: '2.1.0'
12+
13+
implementation 'javax.inject:javax.inject:1'
14+
}
15+
16+
task javadocJar(type: Jar, dependsOn: javadoc) {
17+
classifier = 'javadoc'
18+
from javadoc.destinationDir
19+
}
20+
21+
spotless {
22+
java {
23+
googleJavaFormat()
24+
}
25+
}
26+
27+
apply from: "${rootDir}/gradle/publish.gradle" // needs to come after javadocJar
28+

deadeye/gradle.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
projectDescription=Third Coast Deadeye library
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package org.strykeforce.thirdcoast.deadeye;
2+
3+
public enum ConnectionEvent {
4+
CONNECTED,
5+
DISCONNECTED;
6+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package org.strykeforce.thirdcoast.deadeye;
2+
3+
import org.slf4j.Logger;
4+
import org.slf4j.LoggerFactory;
5+
6+
import java.nio.ByteBuffer;
7+
import java.nio.ByteOrder;
8+
import java.util.Arrays;
9+
10+
public class DeadeyeMessage {
11+
12+
public static final ByteOrder BYTE_ORDER = ByteOrder.LITTLE_ENDIAN;
13+
14+
public static final byte ERROR_BYTE = (byte) 0;
15+
public static final byte HEARTBEAT_BYTE = (byte) 1;
16+
public static final byte[] HEARTBEAT_BYTES = {HEARTBEAT_BYTE};
17+
public static final byte DATA_BYTE = (byte) 2;
18+
public static final byte NODATA_BYTE = (byte) 3;
19+
20+
public final Type type;
21+
22+
public final int latency;
23+
public final double[] data = new double[4];
24+
25+
private final Logger logger = LoggerFactory.getLogger(this.getClass());
26+
27+
public DeadeyeMessage(byte[] bytes) {
28+
29+
byte type = bytes.length > 0 ? bytes[0] : ERROR_BYTE;
30+
31+
switch (type) {
32+
case HEARTBEAT_BYTE:
33+
this.type = Type.HEARTBEAT;
34+
break;
35+
case DATA_BYTE:
36+
this.type = Type.DATA;
37+
ByteBuffer buffer = ByteBuffer.wrap(bytes);
38+
buffer.order(BYTE_ORDER);
39+
buffer.position(Integer.BYTES); // skip over 1 integer
40+
latency = buffer.getInt();
41+
for (int i = 0; i < 4; i++) {
42+
data[i] = buffer.getDouble();
43+
}
44+
return;
45+
case NODATA_BYTE:
46+
this.type = Type.NODATA;
47+
break;
48+
default:
49+
this.type = Type.ERROR;
50+
}
51+
latency = 0;
52+
}
53+
54+
@Override
55+
public String toString() {
56+
return "DeadeyeMessage{"
57+
+ "type="
58+
+ type
59+
+ ", latency="
60+
+ latency
61+
+ ", data="
62+
+ Arrays.toString(data)
63+
+ '}';
64+
}
65+
66+
public enum Type {
67+
HEARTBEAT,
68+
DATA,
69+
NODATA,
70+
ERROR
71+
}
72+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package org.strykeforce.thirdcoast.deadeye;
2+
3+
import io.reactivex.Observable;
4+
import io.reactivex.disposables.Disposable;
5+
import io.reactivex.schedulers.Timed;
6+
import org.slf4j.Logger;
7+
import org.slf4j.LoggerFactory;
8+
import org.strykeforce.thirdcoast.deadeye.rx.RxUdp;
9+
10+
import java.net.DatagramPacket;
11+
import java.net.InetSocketAddress;
12+
13+
import static java.util.concurrent.TimeUnit.MILLISECONDS;
14+
import static org.strykeforce.thirdcoast.deadeye.ConnectionEvent.CONNECTED;
15+
import static org.strykeforce.thirdcoast.deadeye.ConnectionEvent.DISCONNECTED;
16+
import static org.strykeforce.thirdcoast.deadeye.DeadeyeMessage.Type.HEARTBEAT;
17+
18+
public class DeadeyeService {
19+
20+
private static final int PING_INTERVAL = 100;
21+
private static final int PONG_LIMIT = PING_INTERVAL * 4;
22+
private static final int PORT = 5555;
23+
24+
private final Logger logger = LoggerFactory.getLogger(this.getClass());
25+
26+
// this IP address is hardcoded for tethering in Android 6.0 Marshmallow
27+
private final InetSocketAddress ADDRESS = new InetSocketAddress("192.168.42.129", PORT);
28+
29+
private final Observable<Timed<DeadeyeMessage>> pongs;
30+
private final Observable<DeadeyeMessage> messageObservable;
31+
private final Observable<Timed<Long>> heartbeat;
32+
private final Observable<ConnectionEvent> connectionEventObservable;
33+
// private final Disposable disposable;
34+
35+
private Disposable connectionEventDisposable;
36+
37+
public DeadeyeService() {
38+
logger.info("starting pings to {}:{} every {} ms", ADDRESS.getHostName(), PORT, PING_INTERVAL);
39+
40+
// send pings
41+
Observable.interval(PING_INTERVAL, MILLISECONDS)
42+
.map(i -> DeadeyeMessage.HEARTBEAT_BYTES)
43+
.subscribe(RxUdp.observerSendingTo(ADDRESS));
44+
45+
// monitor pongs
46+
logger.info("listening for pongs on port {} with limit {} ms.", PORT, PONG_LIMIT);
47+
48+
// TODO: make defensive copy of byte[] in RxUDP, debug print sizeof(Data)
49+
messageObservable =
50+
RxUdp.observableReceivingFrom(PORT)
51+
.map(DatagramPacket::getData)
52+
.map(DeadeyeMessage::new)
53+
.share();
54+
55+
pongs =
56+
messageObservable
57+
.filter(deadeyeMessage -> deadeyeMessage.type == HEARTBEAT)
58+
.timestamp(MILLISECONDS);
59+
60+
heartbeat = Observable.interval(PING_INTERVAL / 2, MILLISECONDS).timestamp(MILLISECONDS);
61+
62+
connectionEventObservable =
63+
Observable.combineLatest(pongs, heartbeat, (p, h) -> h.time() - p.time())
64+
.distinctUntilChanged(time -> time > PONG_LIMIT)
65+
.map(time -> time > PONG_LIMIT ? DISCONNECTED : CONNECTED)
66+
.startWith(DISCONNECTED)
67+
.share();
68+
}
69+
70+
public void enableConnectionEventLogging(boolean enable) {
71+
if (connectionEventDisposable != null) connectionEventDisposable.dispose();
72+
73+
if (enable) {
74+
connectionEventDisposable =
75+
connectionEventObservable
76+
.map(ConnectionEvent::toString)
77+
.subscribe(logger::info, t -> logger.error("connection event logging", t));
78+
}
79+
}
80+
81+
public Observable<ConnectionEvent> getConnectionEventObservable() {
82+
return connectionEventObservable;
83+
}
84+
85+
public Observable<DeadeyeMessage> getMessageObservable() {
86+
return messageObservable;
87+
}
88+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package org.strykeforce.thirdcoast.deadeye;
2+
3+
import java.net.DatagramPacket;
4+
import java.nio.ByteBuffer;
5+
import java.util.Arrays;
6+
7+
public class Debug {
8+
private static void debugDatagramPacket(DatagramPacket p) {
9+
byte[] b = Arrays.copyOf(p.getData(), p.getLength());
10+
debugByteArray(b);
11+
}
12+
13+
private static void debugByteBuffer(ByteBuffer b) {
14+
b.rewind();
15+
byte[] bytes = new byte[b.remaining()];
16+
b.get(bytes);
17+
debugByteArray(bytes);
18+
}
19+
20+
private static void debugByteArray(byte[] b) {
21+
System.out.println("Bytes = " + Arrays.toString(b));
22+
}
23+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package org.strykeforce.thirdcoast.deadeye.rx;
2+
3+
import com.jakewharton.rxrelay2.PublishRelay;
4+
import com.jakewharton.rxrelay2.Relay;
5+
import io.reactivex.BackpressureStrategy;
6+
import io.reactivex.Flowable;
7+
import javax.inject.Inject;
8+
import javax.inject.Singleton;
9+
10+
// courtesy: https://github.com/kaushikgopal/RxJava-Android-Samples
11+
@Singleton
12+
public class RxBus {
13+
14+
private final Relay<Object> bus = PublishRelay.create().toSerialized();
15+
16+
@Inject
17+
public RxBus() {}
18+
19+
public void send(Object o) {
20+
bus.accept(o);
21+
}
22+
23+
public Flowable<Object> asFlowable() {
24+
return bus.toFlowable(BackpressureStrategy.LATEST);
25+
}
26+
27+
public boolean hasObservers() {
28+
return bus.hasObservers();
29+
}
30+
}

0 commit comments

Comments
 (0)