diff --git a/build.gradle b/build.gradle index cfec65eb..c4166e5e 100644 --- a/build.gradle +++ b/build.gradle @@ -1,59 +1,93 @@ buildscript { - repositories { - jcenter() - maven { url "https://plugins.gradle.org/m2/" } - } - dependencies { - classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.+' - classpath "edu.wpi.first:GradleRIO:2019.4.1" - classpath "com.diffplug.spotless:spotless-plugin-gradle:3.24.2" - } + ext { + kotlinVersion = '1.3.60' + moshiVersion = '1.8.0' + jettyVersion = '9.4.19.v20190610' + okhttpVersion = '3.12.5' + slf4jVersion = '1.7.28' + logbackVersion = '1.2.3' + kotlinLoggingVersion = '1.7.6' + dokkaVersion = '0.9.18' + junitVersion = '5.+' + jsonAssert = '1.+' + assertJVersion = '3.+' + mockitoVersion = '2.+' + gradleRioVersion = '2019.4.1' + bintrayVersion = '1.+' + spotlessVersion = '3.+' + wpiVersion = '2019.4.1' + } + repositories { + jcenter() + maven { url "https://plugins.gradle.org/m2/" } + } + dependencies { + classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:$bintrayVersion" + classpath "edu.wpi.first:GradleRIO:$gradleRioVersion" + classpath "com.diffplug.spotless:spotless-plugin-gradle:$spotlessVersion" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" + classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokkaVersion" + } } +// applies to all sub-projects configure(subprojects) { - group = 'org.strykeforce.thirdcoast' - version = '19.4.1' + group = 'org.strykeforce.thirdcoast' + version = '19.5.0' - apply plugin: 'java-library' - apply plugin: 'idea' + apply plugin: 'java-library' + apply plugin: 'idea' - repositories { - jcenter() - } + sourceCompatibility = 11 - dependencies { - implementation 'org.slf4j:slf4j-api:1.7.25' + repositories { + jcenter() + } - compileOnly 'com.google.code.findbugs:jsr305:3.0.2' + dependencies { + implementation "org.slf4j:slf4j-api:$slf4jVersion" - // Testing - testImplementation "org.junit.jupiter:junit-jupiter-params:5.+" - testImplementation "org.junit.jupiter:junit-jupiter-api:5.+" - testImplementation "org.assertj:assertj-core:3.+" - testImplementation "org.mockito:mockito-junit-jupiter:2.+" - testRuntime "org.junit.jupiter:junit-jupiter-engine:5.+" - testRuntime 'ch.qos.logback:logback-classic:1.2.3' - } + // Testing + testImplementation "org.junit.jupiter:junit-jupiter-params:$junitVersion" + testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion" + testImplementation "org.assertj:assertj-core:$assertJVersion" + testImplementation "org.mockito:mockito-junit-jupiter:$mockitoVersion" + testRuntime "org.junit.jupiter:junit-jupiter-engine:$junitVersion" + testRuntime "ch.qos.logback:logback-classic:$logbackVersion" + } - idea { - module { - downloadJavadoc = true - downloadSources = true - sourceDirs += files('build/generated/source/kaptKotlin/main') - generatedSourceDirs += files('build/generated/source/kaptKotlin/main') - } + idea { + module { + downloadJavadoc = true + downloadSources = true } + } - test { - useJUnitPlatform() - } + test { + useJUnitPlatform() + } } +// applies to all sub-projects except for deadeye configure(subprojects - project(":deadeye")) { - apply plugin: "edu.wpi.first.GradleRIO" + apply plugin: "edu.wpi.first.GradleRIO" - dependencies { - compile wpi.deps.wpilib() - compile wpi.deps.vendor.java() - } + dependencies { + implementation wpi.deps.wpilib() + implementation wpi.deps.vendor.java() + } +} + +// applies to all Kotlin sub-projects +configure(subprojects - project(":swerve")) { + apply plugin: "kotlin" + apply plugin: "kotlin-kapt" + apply plugin: 'org.jetbrains.dokka' + + dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion" + implementation "io.github.microutils:kotlin-logging:$kotlinLoggingVersion" + implementation "com.squareup.moshi:moshi:$moshiVersion" + kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshiVersion" + } } diff --git a/deadeye/build.gradle b/deadeye/build.gradle index ee90cde7..98174227 100644 --- a/deadeye/build.gradle +++ b/deadeye/build.gradle @@ -1,28 +1,23 @@ -apply plugin: 'com.diffplug.gradle.spotless' - -sourceCompatibility = 11 - -dependencies { - - // https://mvnrepository.com/artifact/io.reactivex.rxjava2/rxjava - implementation group: 'io.reactivex.rxjava2', name: 'rxjava', version: '2.2.4' - - // https://mvnrepository.com/artifact/com.jakewharton.rxrelay2/rxrelay - implementation group: 'com.jakewharton.rxrelay2', name: 'rxrelay', version: '2.1.0' - - implementation 'javax.inject:javax.inject:1' -} - -task javadocJar(type: Jar, dependsOn: javadoc) { - classifier = 'javadoc' - from javadoc.destinationDir +repositories { + maven { url = "http://first.wpi.edu/FRC/roborio/maven/release" } } -spotless { - java { - googleJavaFormat() - } +dependencies { + implementation("edu.wpi.first.ntcore:ntcore-java:$wpiVersion") } - -apply from: "${rootDir}/gradle/publish.gradle" // needs to come after javadocJar +// +//task dokkaJavadoc(type: org.jetbrains.dokka.gradle.DokkaTask) { +// includes = ['packages.md'] +// outputDirectory = javadoc.destinationDir +// reportUndocumented = false +//} +// +// +//task javadocJar(type: Jar, dependsOn: dokkaJavadoc) { +// classifier = 'javadoc' +// from javadoc.destinationDir +//} +// +// +//apply from: "${rootDir}/gradle/publish.gradle" // needs to come after javadocJar diff --git a/deadeye/packages.md b/deadeye/packages.md new file mode 100644 index 00000000..e69de29b diff --git a/deadeye/src/main/java/org/strykeforce/thirdcoast/deadeye/ConnectionEvent.java b/deadeye/src/main/java/org/strykeforce/thirdcoast/deadeye/ConnectionEvent.java deleted file mode 100644 index 31620f5e..00000000 --- a/deadeye/src/main/java/org/strykeforce/thirdcoast/deadeye/ConnectionEvent.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.strykeforce.thirdcoast.deadeye; - -public enum ConnectionEvent { - CONNECTED, - DISCONNECTED; -} diff --git a/deadeye/src/main/java/org/strykeforce/thirdcoast/deadeye/DeadeyeMessage.java b/deadeye/src/main/java/org/strykeforce/thirdcoast/deadeye/DeadeyeMessage.java deleted file mode 100644 index 1559d98f..00000000 --- a/deadeye/src/main/java/org/strykeforce/thirdcoast/deadeye/DeadeyeMessage.java +++ /dev/null @@ -1,71 +0,0 @@ -package org.strykeforce.thirdcoast.deadeye; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.Arrays; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class DeadeyeMessage { - - public static final ByteOrder BYTE_ORDER = ByteOrder.LITTLE_ENDIAN; - - public static final byte ERROR_BYTE = (byte) 0; - public static final byte HEARTBEAT_BYTE = (byte) 1; - public static final byte[] HEARTBEAT_BYTES = {HEARTBEAT_BYTE}; - public static final byte DATA_BYTE = (byte) 2; - public static final byte NODATA_BYTE = (byte) 3; - - public final Type type; - - public final int latency; - public final double[] data = new double[4]; - - private final Logger logger = LoggerFactory.getLogger(this.getClass()); - - public DeadeyeMessage(byte[] bytes) { - - byte type = bytes.length > 0 ? bytes[0] : ERROR_BYTE; - - switch (type) { - case HEARTBEAT_BYTE: - this.type = Type.HEARTBEAT; - break; - case DATA_BYTE: - this.type = Type.DATA; - ByteBuffer buffer = ByteBuffer.wrap(bytes); - buffer.order(BYTE_ORDER); - buffer.position(Integer.BYTES); // skip over 1 integer - latency = buffer.getInt(); - for (int i = 0; i < 4; i++) { - data[i] = buffer.getDouble(); - } - return; - case NODATA_BYTE: - this.type = Type.NODATA; - break; - default: - this.type = Type.ERROR; - } - latency = 0; - } - - @Override - public String toString() { - return "DeadeyeMessage{" - + "type=" - + type - + ", latency=" - + latency - + ", data=" - + Arrays.toString(data) - + '}'; - } - - public enum Type { - HEARTBEAT, - DATA, - NODATA, - ERROR - } -} diff --git a/deadeye/src/main/java/org/strykeforce/thirdcoast/deadeye/DeadeyeService.java b/deadeye/src/main/java/org/strykeforce/thirdcoast/deadeye/DeadeyeService.java deleted file mode 100644 index ba7d1015..00000000 --- a/deadeye/src/main/java/org/strykeforce/thirdcoast/deadeye/DeadeyeService.java +++ /dev/null @@ -1,87 +0,0 @@ -package org.strykeforce.thirdcoast.deadeye; - -import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static org.strykeforce.thirdcoast.deadeye.ConnectionEvent.CONNECTED; -import static org.strykeforce.thirdcoast.deadeye.ConnectionEvent.DISCONNECTED; -import static org.strykeforce.thirdcoast.deadeye.DeadeyeMessage.Type.HEARTBEAT; - -import io.reactivex.Observable; -import io.reactivex.disposables.Disposable; -import io.reactivex.schedulers.Timed; -import java.net.DatagramPacket; -import java.net.InetSocketAddress; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.strykeforce.thirdcoast.deadeye.rx.RxUdp; - -public class DeadeyeService { - - private static final int PING_INTERVAL = 100; - private static final int PONG_LIMIT = PING_INTERVAL * 4; - private static final int PORT = 5555; - - private final Logger logger = LoggerFactory.getLogger(this.getClass()); - - // this IP address is hardcoded for tethering in Android 6.0 Marshmallow - private final InetSocketAddress ADDRESS = new InetSocketAddress("192.168.42.129", PORT); - - private final Observable> pongs; - private final Observable messageObservable; - private final Observable> heartbeat; - private final Observable connectionEventObservable; - // private final Disposable disposable; - - private Disposable connectionEventDisposable; - - public DeadeyeService() { - logger.info("starting pings to {}:{} every {} ms", ADDRESS.getHostName(), PORT, PING_INTERVAL); - - // send pings - Observable.interval(PING_INTERVAL, MILLISECONDS) - .map(i -> DeadeyeMessage.HEARTBEAT_BYTES) - .subscribe(RxUdp.observerSendingTo(ADDRESS)); - - // monitor pongs - logger.info("listening for pongs on port {} with limit {} ms.", PORT, PONG_LIMIT); - - // TODO: make defensive copy of byte[] in RxUDP, debug print sizeof(Data) - messageObservable = - RxUdp.observableReceivingFrom(PORT) - .map(DatagramPacket::getData) - .map(DeadeyeMessage::new) - .share(); - - pongs = - messageObservable - .filter(deadeyeMessage -> deadeyeMessage.type == HEARTBEAT) - .timestamp(MILLISECONDS); - - heartbeat = Observable.interval(PING_INTERVAL / 2, MILLISECONDS).timestamp(MILLISECONDS); - - connectionEventObservable = - Observable.combineLatest(pongs, heartbeat, (p, h) -> h.time() - p.time()) - .distinctUntilChanged(time -> time > PONG_LIMIT) - .map(time -> time > PONG_LIMIT ? DISCONNECTED : CONNECTED) - .startWith(DISCONNECTED) - .share(); - } - - public void enableConnectionEventLogging(boolean enable) { - if (connectionEventDisposable != null) connectionEventDisposable.dispose(); - - if (enable) { - connectionEventDisposable = - connectionEventObservable - .map(ConnectionEvent::toString) - .subscribe(logger::info, t -> logger.error("connection event logging", t)); - } - } - - public Observable getConnectionEventObservable() { - return connectionEventObservable; - } - - public Observable getMessageObservable() { - return messageObservable; - } -} diff --git a/deadeye/src/main/java/org/strykeforce/thirdcoast/deadeye/Debug.java b/deadeye/src/main/java/org/strykeforce/thirdcoast/deadeye/Debug.java deleted file mode 100644 index 5cdcaf52..00000000 --- a/deadeye/src/main/java/org/strykeforce/thirdcoast/deadeye/Debug.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.strykeforce.thirdcoast.deadeye; - -import java.net.DatagramPacket; -import java.nio.ByteBuffer; -import java.util.Arrays; - -public class Debug { - private static void debugDatagramPacket(DatagramPacket p) { - byte[] b = Arrays.copyOf(p.getData(), p.getLength()); - debugByteArray(b); - } - - private static void debugByteBuffer(ByteBuffer b) { - b.rewind(); - byte[] bytes = new byte[b.remaining()]; - b.get(bytes); - debugByteArray(bytes); - } - - private static void debugByteArray(byte[] b) { - System.out.println("Bytes = " + Arrays.toString(b)); - } -} diff --git a/deadeye/src/main/java/org/strykeforce/thirdcoast/deadeye/rx/RxBus.java b/deadeye/src/main/java/org/strykeforce/thirdcoast/deadeye/rx/RxBus.java deleted file mode 100644 index b2cbac37..00000000 --- a/deadeye/src/main/java/org/strykeforce/thirdcoast/deadeye/rx/RxBus.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.strykeforce.thirdcoast.deadeye.rx; - -import com.jakewharton.rxrelay2.PublishRelay; -import com.jakewharton.rxrelay2.Relay; -import io.reactivex.BackpressureStrategy; -import io.reactivex.Flowable; -import javax.inject.Inject; -import javax.inject.Singleton; - -// courtesy: https://github.com/kaushikgopal/RxJava-Android-Samples -@Singleton -public class RxBus { - - private final Relay bus = PublishRelay.create().toSerialized(); - - @Inject - public RxBus() {} - - public void send(Object o) { - bus.accept(o); - } - - public Flowable asFlowable() { - return bus.toFlowable(BackpressureStrategy.LATEST); - } - - public boolean hasObservers() { - return bus.hasObservers(); - } -} diff --git a/deadeye/src/main/java/org/strykeforce/thirdcoast/deadeye/rx/RxUdp.java b/deadeye/src/main/java/org/strykeforce/thirdcoast/deadeye/rx/RxUdp.java deleted file mode 100644 index 1637cfdd..00000000 --- a/deadeye/src/main/java/org/strykeforce/thirdcoast/deadeye/rx/RxUdp.java +++ /dev/null @@ -1,140 +0,0 @@ -package org.strykeforce.thirdcoast.deadeye.rx; - -import io.reactivex.Observable; -import io.reactivex.Observer; -import io.reactivex.disposables.Disposable; -import io.reactivex.observers.DisposableObserver; -import io.reactivex.schedulers.Schedulers; -import java.io.IOException; -import java.net.DatagramPacket; -import java.net.DatagramSocket; -import java.net.SocketAddress; -import java.net.SocketException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class RxUdp { - - static final int UDP_SIZE = 512; - - private final DatagramSocket socket; - - private RxUdp() { - try { - socket = new DatagramSocket(); - } catch (SocketException e) { - throw new RuntimeException(e); - } - } - - public static Observer observerSendingTo(SocketAddress address) { - return new UdpObserver(address); - } - - public static Observable observableReceivingFrom(int port) { - return Observable.create( - emitter -> { - DatagramSocket socket = new DatagramSocket(port); - emitter.setCancellable(socket::close); - while (!emitter.isDisposed()) { - byte[] buf = new byte[UDP_SIZE]; - DatagramPacket packet = new DatagramPacket(buf, buf.length); - try { - socket.receive(packet); - } catch (IOException ioe) { - if (socket.isClosed()) { - // socket close called, don't send exception to global exception handler - break; - } else { - emitter.tryOnError(ioe); - break; - } - } - emitter.onNext(packet); - } - }) - .subscribeOn(Schedulers.io()); - } - - public static DisposableObserver observerSendingDatagramPacket() { - - return new DisposableObserver() { - private final Logger logger = LoggerFactory.getLogger(this.getClass()); - DatagramSocket socket; - - @Override - protected void onStart() { - try { - logger.debug("initializing observerSendingDatagramPacket socket"); - socket = new DatagramSocket(); - } catch (Exception e) { - logger.error("error initializing DatagramSocket", e); - } - } - - @Override - public void onNext(DatagramPacket datagramPacket) { - try { - socket.send(datagramPacket); - } catch (Exception e) { - logger.error("error sending UDP packet", e); - } - } - - @Override - public void onError(Throwable e) { - logger.error("received error from upstream Observable", e); - } - - @Override - public void onComplete() { - logger.warn("upstream Observable is completed"); - } - }; - } - - private static class UdpObserver implements Observer { - - private final Logger logger = LoggerFactory.getLogger(this.getClass()); - - private final SocketAddress address; - private DatagramSocket socket; - private Disposable sub; - - UdpObserver(SocketAddress address) { - this.address = address; - } - - @Override - public void onSubscribe(Disposable d) { - sub = d; - try { - socket = new DatagramSocket(); - } catch (SocketException e) { - logger.error("can't create UDP socket", e); - sub.dispose(); - } - } - - @Override - public void onNext(byte[] buf) { - DatagramPacket packet = new DatagramPacket(buf, buf.length, address); - try { - socket.send(packet); - } catch (IOException e) { - logger.error("can't send UDP packet", e); - sub.dispose(); - } - } - - @Override - public void onError(Throwable e) { - logger.error("onError called", e); - } - - @Override - public void onComplete() { - logger.debug("onComplete called"); - } - } -} diff --git a/deadeye/src/main/kotlin/org/strykeforce/deadeye/Camera.kt b/deadeye/src/main/kotlin/org/strykeforce/deadeye/Camera.kt new file mode 100644 index 00000000..7a8987f9 --- /dev/null +++ b/deadeye/src/main/kotlin/org/strykeforce/deadeye/Camera.kt @@ -0,0 +1,27 @@ +package org.strykeforce.deadeye + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonClass +import okio.Buffer + +interface Camera { + val id: String + var enabled: Boolean + val error: Boolean + val config: Config + val stream: Stream + var targetDataListener: TargetDataListener? + var jsonAdapter: JsonAdapter + fun parse(buffer: Buffer) + + @JsonClass(generateAdapter = true) + data class Config( + @Json(name = "sn") val serial: Int, val exposure: Double, + val hue: List, @Json(name = "sat") val saturation: List, @Json(name = "val") val value: List + ) + + @JsonClass(generateAdapter = true) + data class Stream(@Json(name = "sn") val serial: Int, val contour: String, val view: String, val url: String) + +} diff --git a/deadeye/src/main/kotlin/org/strykeforce/deadeye/CameraImpl.kt b/deadeye/src/main/kotlin/org/strykeforce/deadeye/CameraImpl.kt new file mode 100644 index 00000000..3c40ce10 --- /dev/null +++ b/deadeye/src/main/kotlin/org/strykeforce/deadeye/CameraImpl.kt @@ -0,0 +1,54 @@ +package org.strykeforce.deadeye + +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonDataException +import com.squareup.moshi.Moshi +import edu.wpi.first.networktables.NetworkTable +import edu.wpi.first.networktables.NetworkTableInstance +import okio.Buffer + +private const val ON = "On" +private const val OFF = "Off" +private const val ERROR = "Error" +private const val CONFIG = "Config" +private const val STREAM = "Stream" + +internal class CameraImpl(override val id: String) : Camera { + + override var targetDataListener: TargetDataListener? = null + + @Suppress("MemberVisibilityCanBePrivate") + var targetData: TargetData = TargetData("") + + private val table: NetworkTable by lazy { NetworkTableInstance.getDefault().getTable("/Deadeye/${id[0]}/${id[1]}") } + + private val moshi: Moshi by lazy { Moshi.Builder().build() } + + override lateinit var jsonAdapter: JsonAdapter + + override var enabled: Boolean + set(value) { + table.getEntry(if (value) ON else OFF).apply { setBoolean(true) } + } + get() = table.getEntry(ON).getBoolean(false) + + override val error: Boolean + get() = table.getEntry(ERROR).getBoolean(false) + + override val config: Camera.Config + get() = with(table.getEntry(CONFIG).getString("{}")) { + Camera_ConfigJsonAdapter(moshi).fromJson(this) ?: throw JsonDataException("Config: $this") + } + + override val stream: Camera.Stream + get() = with(table.getEntry(STREAM).getString("{}")) { + Camera_StreamJsonAdapter(moshi).fromJson(this) ?: throw JsonDataException("Stream: $this") + } + + override fun parse(buffer: Buffer) { + var targetData = jsonAdapter.fromJson(buffer) ?: throw JsonDataException("parse error") + this.targetData = targetData + targetDataListener?.onTargetData(targetData) + } + +} diff --git a/deadeye/src/main/kotlin/org/strykeforce/deadeye/CenterTargetData.kt b/deadeye/src/main/kotlin/org/strykeforce/deadeye/CenterTargetData.kt new file mode 100644 index 00000000..70310819 --- /dev/null +++ b/deadeye/src/main/kotlin/org/strykeforce/deadeye/CenterTargetData.kt @@ -0,0 +1,12 @@ +package org.strykeforce.deadeye + +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +class CenterTargetData( + id: String = "NA", + sn: Int = 0, + valid: Boolean = false, + val x: Double = 0.0, + val y: Double = 0.0 +) : TargetData(id, sn, valid) diff --git a/deadeye/src/main/kotlin/org/strykeforce/deadeye/Deadeye.kt b/deadeye/src/main/kotlin/org/strykeforce/deadeye/Deadeye.kt new file mode 100644 index 00000000..a53b76a1 --- /dev/null +++ b/deadeye/src/main/kotlin/org/strykeforce/deadeye/Deadeye.kt @@ -0,0 +1,67 @@ +package org.strykeforce.deadeye + +import com.squareup.moshi.JsonClass +import com.squareup.moshi.JsonDataException +import com.squareup.moshi.Moshi +import edu.wpi.first.networktables.NetworkTable +import edu.wpi.first.networktables.NetworkTableInstance +import okio.Buffer +import java.net.DatagramPacket +import java.net.DatagramSocket +import kotlin.concurrent.thread + +private const val LINK = "Config" + +object Deadeye { + private val cameraCache = mutableMapOf>() + + + private val cameraIds: List + get() = with(NetworkTableInstance.getDefault().getTable("Deadeye")) { + subTables.flatMap { unit -> + getSubTable(unit).subTables.map { "$unit$it" } + } + } + +// val cameras: Collection +// get() { +// cameraIds.filterNot { cameraCache.containsKey(it) }.forEach { cameraCache[it] = CameraImpl(it) } +// return cameraCache.values +// } + + private val table: NetworkTable by lazy { NetworkTableInstance.getDefault().getTable("/Deadeye") } + private val moshi: Moshi by lazy { Moshi.Builder().build() } + + var config: Config + get() = with(table.getEntry(LINK).getString("{}")) { + Deadeye_ConfigJsonAdapter(moshi).fromJson(this) ?: throw JsonDataException("Config: $this") + } + set(value) = with(table.getEntry(LINK)) { + setString(Deadeye_ConfigJsonAdapter(moshi).toJson(value)) + } + + init { + thread(isDaemon = true) { + val socket = DatagramSocket(5800) + val bytes = ByteArray(512) + val buffer = Buffer() + val packet = DatagramPacket(bytes, bytes.size) + + while (true) { + socket.receive(packet) + val id = String(packet.data, 0, 2) + val camera = cameraCache[id] ?: throw JsonDataException("Unrecognized camera id: $id") + buffer.write(packet.data, 2, packet.length - 2) + camera.parse(buffer) + } + } + } + + @Suppress("UNCHECKED_CAST") + fun getCamera(id: String): Camera = cameraCache.getOrPut(id) { CameraImpl(id) } as Camera + + + @JsonClass(generateAdapter = true) + data class Config(val address: String, val port: Int, val enabled: Boolean) + +} diff --git a/deadeye/src/main/kotlin/org/strykeforce/deadeye/TargetData.kt b/deadeye/src/main/kotlin/org/strykeforce/deadeye/TargetData.kt new file mode 100644 index 00000000..f8bc6a9f --- /dev/null +++ b/deadeye/src/main/kotlin/org/strykeforce/deadeye/TargetData.kt @@ -0,0 +1,10 @@ +package org.strykeforce.deadeye + +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +open class TargetData( + val id: String = "NA", + val sn: Int = 0, + val valid: Boolean = false +) diff --git a/deadeye/src/main/kotlin/org/strykeforce/deadeye/TargetDataListener.kt b/deadeye/src/main/kotlin/org/strykeforce/deadeye/TargetDataListener.kt new file mode 100644 index 00000000..4e91979e --- /dev/null +++ b/deadeye/src/main/kotlin/org/strykeforce/deadeye/TargetDataListener.kt @@ -0,0 +1,6 @@ +package org.strykeforce.deadeye + +@FunctionalInterface +interface TargetDataListener { + fun onTargetData(data: TargetData) +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 87b738cb..457aad0d 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 75b8c7c8..8ce242a1 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ +#Thu Nov 21 13:49:38 EST 2019 +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.0-bin.zip -zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/telemetry/build.gradle b/telemetry/build.gradle index a02eddfb..08c4d16a 100644 --- a/telemetry/build.gradle +++ b/telemetry/build.gradle @@ -1,58 +1,27 @@ -buildscript { - ext { - kotlin_version = '1.3.50' - junit_version = '5.2.0' - } - - repositories { - jcenter() - maven { url = "https://plugins.gradle.org/m2/" } - } - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jetbrains.dokka:dokka-gradle-plugin:0.9.18" - } -} - -apply plugin: "kotlin" -apply plugin: "kotlin-kapt" -apply plugin: 'org.jetbrains.dokka' - -sourceCompatibility = 11 - - -repositories { -} - dependencies { - // Kotlin - compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" - - // telemetry - implementation "org.eclipse.jetty:jetty-server:9.4.19.v20190610" - implementation "com.squareup.moshi:moshi:1.8.0" - kapt "com.squareup.moshi:moshi-kotlin-codegen:1.8.0" - implementation "com.squareup.okhttp3:okhttp:3.12.5" - implementation project(':swerve') - - // Logging - compile 'io.github.microutils:kotlin-logging:1.7.6' + // telemetry + implementation "org.eclipse.jetty:jetty-server:$jettyVersion" + implementation "com.squareup.okhttp3:okhttp:$okhttpVersion" + implementation project(':swerve') - // unit tests - testImplementation 'org.skyscreamer:jsonassert:1.+' + // unit tests + testImplementation "org.skyscreamer:jsonassert:$jsonAssert" } task dokkaJavadoc(type: org.jetbrains.dokka.gradle.DokkaTask) { - includes = ['packages.md'] - outputDirectory = javadoc.destinationDir - reportUndocumented = false + includes = ['packages.md'] + outputDirectory = javadoc.destinationDir + reportUndocumented = false + externalDocumentationLink { + url = new URL('https://first.wpi.edu/FRC/roborio/release/docs/java/') + } } task javadocJar(type: Jar, dependsOn: dokkaJavadoc) { - classifier = 'javadoc' - from javadoc.destinationDir + classifier = 'javadoc' + from javadoc.destinationDir } apply from: "${rootDir}/gradle/publish.gradle" // needs to come after javadocJar diff --git a/telemetry/src/main/kotlin/org/strykeforce/thirdcoast/telemetry/item/PowerDistributionPanelItem.kt b/telemetry/src/main/kotlin/org/strykeforce/thirdcoast/telemetry/item/PowerDistributionPanelItem.kt new file mode 100644 index 00000000..968576ea --- /dev/null +++ b/telemetry/src/main/kotlin/org/strykeforce/thirdcoast/telemetry/item/PowerDistributionPanelItem.kt @@ -0,0 +1,97 @@ +package org.strykeforce.thirdcoast.telemetry.item + +import edu.wpi.first.wpilibj.PowerDistributionPanel +import java.util.function.DoubleSupplier + +internal const val PDP_CURRENT0 = "PDP_CURRENT_00" +internal const val PDP_CURRENT1 = "PDP_CURRENT_01" +internal const val PDP_CURRENT2 = "PDP_CURRENT_02" +internal const val PDP_CURRENT3 = "PDP_CURRENT_03" +internal const val PDP_CURRENT4 = "PDP_CURRENT_04" +internal const val PDP_CURRENT5 = "PDP_CURRENT_05" +internal const val PDP_CURRENT6 = "PDP_CURRENT_06" +internal const val PDP_CURRENT7 = "PDP_CURRENT_07" +internal const val PDP_CURRENT8 = "PDP_CURRENT_08" +internal const val PDP_CURRENT9 = "PDP_CURRENT_09" +internal const val PDP_CURRENT10 = "PDP_CURRENT_10" +internal const val PDP_CURRENT11 = "PDP_CURRENT_11" +internal const val PDP_CURRENT12 = "PDP_CURRENT_12" +internal const val PDP_CURRENT13 = "PDP_CURRENT_13" +internal const val PDP_CURRENT14 = "PDP_CURRENT_14" +internal const val PDP_CURRENT15 = "PDP_CURRENT_15" +internal const val PDP_TEMP = "PDP_TEMP" +internal const val PDP_TOTAL_CURRENT = "PDP_TOTAL_CURRENT" +internal const val PDP_TOTAL_ENERGY = "PDP_TOTAL_ENERGY" +internal const val PDP_TOTAL_POWER = "PDP_TOTAL_POWER" +internal const val PDP_VOLTAGE = "PDP_VOLTAGE" + +/** Represents a `PowerDistributionPanel` telemetry-enable `Measurable` item. */ +class PowerDistributionPanelItem @JvmOverloads constructor( + private val pdp: PowerDistributionPanel, + override val description: String = "Power Distribution Panel" +) : Measurable { + + override val deviceId = 0 + override val type = "pdp" + override val measures = setOf( + Measure(PDP_CURRENT1, "Ch. 0 Current"), + Measure(PDP_CURRENT2, "Ch. 1 Current"), + Measure(PDP_CURRENT0, "Ch. 2 Current"), + Measure(PDP_CURRENT3, "Ch. 3 Current"), + Measure(PDP_CURRENT4, "Ch. 4 Current"), + Measure(PDP_CURRENT5, "Ch. 5 Current"), + Measure(PDP_CURRENT6, "Ch. 6 Current"), + Measure(PDP_CURRENT7, "Ch. 7 Current"), + Measure(PDP_CURRENT8, "Ch. 8 Current"), + Measure(PDP_CURRENT9, "Ch. 9 Current"), + Measure(PDP_CURRENT10, "Ch. 10 Current"), + Measure(PDP_CURRENT11, "Ch. 11 Current"), + Measure(PDP_CURRENT12, "Ch. 12 Current"), + Measure(PDP_CURRENT13, "Ch. 13 Current"), + Measure(PDP_CURRENT14, "Ch. 14 Current"), + Measure(PDP_CURRENT15, "Ch. 15 Current"), + Measure(PDP_TEMP, "Temperature"), + Measure(PDP_TOTAL_CURRENT, "Total Current"), + Measure(PDP_TOTAL_ENERGY, "Total Energy"), + Measure(PDP_TOTAL_POWER, "Total Power"), + Measure(PDP_VOLTAGE, "Input Voltage") + ) + + override fun measurementFor(measure: Measure): DoubleSupplier { + + return when (measure.name) { + PDP_CURRENT0 -> DoubleSupplier { pdp.getCurrent(0) } + PDP_CURRENT1 -> DoubleSupplier { pdp.getCurrent(1) } + PDP_CURRENT2 -> DoubleSupplier { pdp.getCurrent(2) } + PDP_CURRENT3 -> DoubleSupplier { pdp.getCurrent(3) } + PDP_CURRENT4 -> DoubleSupplier { pdp.getCurrent(4) } + PDP_CURRENT5 -> DoubleSupplier { pdp.getCurrent(5) } + PDP_CURRENT6 -> DoubleSupplier { pdp.getCurrent(6) } + PDP_CURRENT7 -> DoubleSupplier { pdp.getCurrent(7) } + PDP_CURRENT8 -> DoubleSupplier { pdp.getCurrent(8) } + PDP_CURRENT9 -> DoubleSupplier { pdp.getCurrent(9) } + PDP_CURRENT10 -> DoubleSupplier { pdp.getCurrent(10) } + PDP_CURRENT11 -> DoubleSupplier { pdp.getCurrent(11) } + PDP_CURRENT12 -> DoubleSupplier { pdp.getCurrent(12) } + PDP_CURRENT13 -> DoubleSupplier { pdp.getCurrent(13) } + PDP_CURRENT14 -> DoubleSupplier { pdp.getCurrent(14) } + PDP_CURRENT15 -> DoubleSupplier { pdp.getCurrent(15) } + PDP_TEMP -> DoubleSupplier { pdp.temperature } + PDP_TOTAL_CURRENT -> DoubleSupplier { pdp.totalCurrent } + PDP_TOTAL_ENERGY -> DoubleSupplier { pdp.totalEnergy } + PDP_TOTAL_POWER -> DoubleSupplier { pdp.totalPower } + PDP_VOLTAGE -> DoubleSupplier { pdp.voltage } + else -> DoubleSupplier { 2767.0 } + } + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + if (deviceId != (other as PowerDistributionPanelItem).deviceId) return false + return true + } + + override fun hashCode() = deviceId + +}