diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 00000000..00a51aff
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,6 @@
+#
+# https://help.github.com/articles/dealing-with-line-endings/
+#
+# These are explicitly windows files and should use crlf
+*.bat text eol=crlf
+
diff --git a/.gitignore b/.gitignore
index 61f25ce9..7a396920 100644
--- a/.gitignore
+++ b/.gitignore
@@ -136,7 +136,7 @@ $RECYCLE.BIN/
### Gradle ###
.gradle
-/build/
+build/
# Ignore Gradle GUI config
gradle-app.setting
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 59cc954f..e1384cd4 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -2,6 +2,7 @@
"java.configuration.updateBuildConfiguration": "automatic",
"editor.formatOnSave": true,
"editor.formatOnPaste": true,
+ "kotlin.compiler.jvm.target": "1.8",
"files.exclude": {
"**/.git": true,
"**/.svn": true,
@@ -16,4 +17,4 @@
"**/.settings": true,
"**/.factorypath": true
}
-}
+}
\ No newline at end of file
diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle
new file mode 100644
index 00000000..04d80d2a
--- /dev/null
+++ b/buildSrc/build.gradle
@@ -0,0 +1,13 @@
+/*
+ * This file was generated by the Gradle 'init' task.
+ */
+
+plugins {
+ // Support convention plugins written in Groovy. Convention plugins are build scripts in 'src/main' that automatically become available as plugins in the main build.
+ id 'groovy-gradle-plugin'
+}
+
+repositories {
+ // Use the plugin portal to apply community plugins in convention plugins.
+ gradlePluginPortal()
+}
diff --git a/buildSrc/src/main/groovy/com.chopshop166.chopshoplib.java-application-conventions.gradle b/buildSrc/src/main/groovy/com.chopshop166.chopshoplib.java-application-conventions.gradle
new file mode 100644
index 00000000..ba3c36df
--- /dev/null
+++ b/buildSrc/src/main/groovy/com.chopshop166.chopshoplib.java-application-conventions.gradle
@@ -0,0 +1,11 @@
+/*
+ * This file was generated by the Gradle 'init' task.
+ */
+
+plugins {
+ // Apply the common convention plugin for shared build configuration between library and application projects.
+ id 'com.chopshop166.chopshoplib.java-common-conventions'
+
+ // Apply the application plugin to add support for building a CLI application in Java.
+ id 'application'
+}
diff --git a/buildSrc/src/main/groovy/com.chopshop166.chopshoplib.java-common-conventions.gradle b/buildSrc/src/main/groovy/com.chopshop166.chopshoplib.java-common-conventions.gradle
new file mode 100644
index 00000000..6699282d
--- /dev/null
+++ b/buildSrc/src/main/groovy/com.chopshop166.chopshoplib.java-common-conventions.gradle
@@ -0,0 +1,21 @@
+/*
+ * This file was generated by the Gradle 'init' task.
+ */
+
+plugins {
+ // Apply the java Plugin to add support for Java.
+ id 'java'
+}
+
+dependencies {
+ // Use JUnit Jupiter API for testing.
+ testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.2'
+
+ // Use JUnit Jupiter Engine for testing.
+ testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
+}
+
+tasks.named('test') {
+ // Use junit platform for unit tests.
+ useJUnitPlatform()
+}
diff --git a/build.gradle b/buildSrc/src/main/groovy/com.chopshop166.chopshoplib.java-library-conventions.gradle
similarity index 58%
rename from build.gradle
rename to buildSrc/src/main/groovy/com.chopshop166.chopshoplib.java-library-conventions.gradle
index 261c7710..1c2a14e1 100644
--- a/build.gradle
+++ b/buildSrc/src/main/groovy/com.chopshop166.chopshoplib.java-library-conventions.gradle
@@ -1,15 +1,22 @@
+/*
+ * This file was generated by the Gradle 'init' task.
+ */
+
plugins {
- id "java-library"
- id "pmd"
+ // Apply the common convention plugin for shared build configuration between library and application projects.
+ id 'com.chopshop166.chopshoplib.java-common-conventions'
+
+ // Apply the java-library plugin for API and implementation separation.
+ id 'java-library'
+
+ id 'pmd'
id 'maven-publish'
- id "com.github.spotbugs" version "4.7.2"
- id "edu.wpi.first.GradleRIO" version "2021.3.1"
}
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
-group = 'com.chopshop166'
+group = 'com.chopshop166.chopshoplib'
repositories {
mavenCentral()
@@ -18,14 +25,6 @@ repositories {
dependencies {
- implementation wpi.deps.wpilib()
- nativeDesktopZip wpi.deps.wpilibJni(wpi.platforms.desktop)
-
- implementation wpi.deps.vendor.java()
- nativeDesktopZip wpi.deps.vendor.jni(wpi.platforms.desktop)
-
- implementation group: 'com.google.guava', name: 'guava', version: '30.1-jre'
-
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.2'
}
@@ -39,14 +38,6 @@ java {
withSourcesJar()
}
-publishing {
- publications {
- chopshoplib(MavenPublication) {
- from components.java
- }
- }
-}
-
spotbugsMain {
reports {
xml {
@@ -68,7 +59,3 @@ pmd {
ruleSets = []
ruleSetFiles = files("$rootProject.projectDir/config/pmd-ruleset.xml")
}
-
-wrapper {
- gradleVersion = '6.9'
-}
diff --git a/config/findbugs-ignore.xml b/config/findbugs-ignore.xml
index 42706963..439b7a5d 100644
--- a/config/findbugs-ignore.xml
+++ b/config/findbugs-ignore.xml
@@ -6,7 +6,7 @@
-
+
\ No newline at end of file
diff --git a/core/build.gradle b/core/build.gradle
new file mode 100644
index 00000000..94b417e0
--- /dev/null
+++ b/core/build.gradle
@@ -0,0 +1,21 @@
+plugins {
+ id 'com.chopshop166.chopshoplib.java-library-conventions'
+ id 'com.github.spotbugs' version '4.7.3'
+ id 'edu.wpi.first.GradleRIO' version '2021.3.1'
+}
+
+dependencies {
+
+ api wpi.deps.wpilib()
+ api wpi.deps.vendor.java()
+
+ implementation group: 'com.google.guava', name: 'guava', version: '30.1-jre'
+}
+
+publishing {
+ publications {
+ core(MavenPublication) {
+ from components.java
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/com/chopshop166/chopshoplib/Dashboard.kt b/core/src/main/java/com/chopshop166/chopshoplib/Dashboard.kt
new file mode 100644
index 00000000..a5b3580f
--- /dev/null
+++ b/core/src/main/java/com/chopshop166/chopshoplib/Dashboard.kt
@@ -0,0 +1,93 @@
+@file:OptIn(kotlin.experimental.ExperimentalTypeInference::class)
+package com.chopshop166.chopshoplib
+
+import edu.wpi.cscore.VideoSink
+import edu.wpi.cscore.VideoSource
+import edu.wpi.first.cameraserver.CameraServer
+import edu.wpi.first.wpilibj.Sendable
+import edu.wpi.first.wpilibj.shuffleboard.Shuffleboard
+import edu.wpi.first.wpilibj.shuffleboard.ShuffleboardContainer
+import edu.wpi.first.wpilibj.smartdashboard.SendableChooser
+import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard
+
+class DashboardWrapper internal constructor () {
+ operator fun set(name: String, value: Sendable) = (name displays value)
+ operator fun set(name: String, value: Boolean) = (name displays value)
+ operator fun set(name: String, value: String) = (name displays value)
+ operator fun set(name: String, value: Double) = (name displays value)
+ operator fun set(name: String, value: Int) = (name displays value)
+
+ operator fun get(name: String) = SmartDashboard.getData(name)
+
+ operator fun invoke(block: DashboardWrapper.() -> Unit = {}) = apply(block)
+
+ infix fun String.displays(value: Sendable) = SmartDashboard.putData(this, value)
+ infix fun String.displays(value: Boolean) = SmartDashboard.putBoolean(this, value)
+ infix fun String.displays(value: String) = SmartDashboard.putString(this, value)
+ infix fun String.displays(value: Double) = SmartDashboard.putNumber(this, value)
+ infix fun String.displays(value: Int) = SmartDashboard.putNumber(this, value.toDouble())
+
+ fun entry(name: String) = SmartDashboard.getEntry(name)
+}
+val dashboard = DashboardWrapper()
+
+class ShuffleboardContainerWrapper internal constructor (val con : ShuffleboardContainer) {
+
+ val title : String get() { return con.getTitle() }
+
+ operator fun set(name: String, value: Sendable) = name(value)
+ @OverloadResolutionByLambdaReturnType
+ @JvmName("setBoolean") operator fun set(name: String, value: () -> Boolean) = name(value)
+ @JvmName("setString") operator fun set(name: String, value: () -> String) = name(value)
+ @JvmName("setNumber") operator fun set(name: String, value: () -> Double) = name(value)
+ @JvmName("setInt") operator fun set(name: String, value: () -> Int) = name(value)
+
+ operator fun get(name: String) = ShuffleboardContainerWrapper(con.getLayout(name))
+ fun layout(name : String, block: ShuffleboardContainerWrapper.() -> Unit = {}) = this[name].run(block)
+
+ operator fun String.invoke(value: Sendable) = con.add(this, value)
+ @OverloadResolutionByLambdaReturnType
+ @JvmName("invokeBoolean") operator fun String.invoke(value: () -> Boolean) = con.addBoolean(this, value)
+ @JvmName("invokeString") operator fun String.invoke(value: () -> String) = con.addString(this, value)
+ @JvmName("invokeNumber") operator fun String.invoke(value: () -> Double) = con.addNumber(this, value)
+ @JvmName("invokeInt") operator fun String.invoke(value: () -> Int) = con.addNumber(this) { value().toDouble() }
+}
+fun shuffleboard(name : String, block: ShuffleboardContainerWrapper.() -> Unit = {}) = ShuffleboardContainerWrapper(Shuffleboard.getTab(name)).apply(block)
+
+class ChooserWrapper(val chooser: SendableChooser) {
+ infix fun String.default(value: T) { chooser.setDefaultOption(this, value) }
+ infix fun String.chooses(value: T) { chooser.addOption(this, value) }
+}
+
+inline fun chooser(block: ChooserWrapper.() -> Unit) = SendableChooser().apply {
+ ChooserWrapper(this).block()
+}
+
+class VideoSourceWrapper(val source: VideoSource) {
+ operator fun get(name: String) = source.getProperty(name).get()
+ operator fun set(name: String, value: Int) = source.getProperty(name).set(value)
+}
+
+class VideoSinkWrapper(val sink: VideoSink) {
+ operator fun get(name: String) = sink.getProperty(name).get()
+ operator fun set(name: String, value: Int) = sink.getProperty(name).set(value)
+}
+
+class CameraServerWrapper(private val base: CameraServer) {
+ fun server(block: VideoSinkWrapper.() -> Unit = {}) =
+ base.getServer().apply { VideoSinkWrapper(this).block() }
+ fun server(name: String, block: VideoSinkWrapper.() -> Unit = {}) =
+ base.getServer(name).apply { VideoSinkWrapper(this).block() }
+ fun source(block: VideoSourceWrapper.() -> Unit = {}) =
+ base.startAutomaticCapture().apply { VideoSourceWrapper(this).block() }
+ fun source(dev: Int, block: VideoSourceWrapper.() -> Unit = {}) =
+ base.startAutomaticCapture(dev).apply { VideoSourceWrapper(this).block() }
+ fun source(camera: VideoSource, block: VideoSinkWrapper.() -> Unit = {}) =
+ base.startAutomaticCapture(camera).apply { VideoSinkWrapper(this).block() }
+ fun source(name: String, dev: Int, block: VideoSourceWrapper.() -> Unit = {}) =
+ base.startAutomaticCapture(name, dev).apply { VideoSourceWrapper(this).block() }
+ fun source(name: String, path: String, block: VideoSourceWrapper.() -> Unit = {}) =
+ base.startAutomaticCapture(name, path).apply { VideoSourceWrapper(this).block() }
+}
+
+val cameraServer = CameraServerWrapper(CameraServer.getInstance())
diff --git a/src/main/java/com/chopshop166/chopshoplib/PersistenceCheck.java b/core/src/main/java/com/chopshop166/chopshoplib/PersistenceCheck.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/PersistenceCheck.java
rename to core/src/main/java/com/chopshop166/chopshoplib/PersistenceCheck.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/Resettable.java b/core/src/main/java/com/chopshop166/chopshoplib/Resettable.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/Resettable.java
rename to core/src/main/java/com/chopshop166/chopshoplib/Resettable.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/RobotUtils.java b/core/src/main/java/com/chopshop166/chopshoplib/RobotUtils.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/RobotUtils.java
rename to core/src/main/java/com/chopshop166/chopshoplib/RobotUtils.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/SampleBuffer.java b/core/src/main/java/com/chopshop166/chopshoplib/SampleBuffer.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/SampleBuffer.java
rename to core/src/main/java/com/chopshop166/chopshoplib/SampleBuffer.java
diff --git a/core/src/main/java/com/chopshop166/chopshoplib/Utils.kt b/core/src/main/java/com/chopshop166/chopshoplib/Utils.kt
new file mode 100644
index 00000000..51e7790d
--- /dev/null
+++ b/core/src/main/java/com/chopshop166/chopshoplib/Utils.kt
@@ -0,0 +1,18 @@
+package com.chopshop166.chopshoplib
+
+import kotlin.math.max
+import kotlin.math.min
+import kotlin.math.pow
+import kotlin.math.withSign
+
+fun Double.clamped(minVal : Double, maxVal : Double) = max(minVal, min(maxVal, this))
+fun Float.clamped(minVal : Float, maxVal : Float) = max(minVal, min(maxVal, this))
+fun Int.clamped(minVal : Int, maxVal : Int) = max(minVal, min(maxVal, this))
+fun Long.clamped(minVal : Long, maxVal : Long) = max(minVal, min(maxVal, this))
+
+fun Double.signPow(exp : Double) = pow(exp).withSign(this)
+
+typealias BoolFunc = ()->Boolean
+operator fun BoolFunc.not() = {!this()}
+infix fun BoolFunc.and(other : BoolFunc) = {this() && other()}
+infix fun BoolFunc.or(other : BoolFunc) = {this() || other()}
diff --git a/src/main/java/com/chopshop166/chopshoplib/commands/CommandBuilder.java b/core/src/main/java/com/chopshop166/chopshoplib/commands/CommandBuilder.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/commands/CommandBuilder.java
rename to core/src/main/java/com/chopshop166/chopshoplib/commands/CommandBuilder.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/commands/CommandRobot.java b/core/src/main/java/com/chopshop166/chopshoplib/commands/CommandRobot.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/commands/CommandRobot.java
rename to core/src/main/java/com/chopshop166/chopshoplib/commands/CommandRobot.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/commands/CommandUtils.java b/core/src/main/java/com/chopshop166/chopshoplib/commands/CommandUtils.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/commands/CommandUtils.java
rename to core/src/main/java/com/chopshop166/chopshoplib/commands/CommandUtils.java
diff --git a/core/src/main/java/com/chopshop166/chopshoplib/commands/Commands.kt b/core/src/main/java/com/chopshop166/chopshoplib/commands/Commands.kt
new file mode 100644
index 00000000..930a71ab
--- /dev/null
+++ b/core/src/main/java/com/chopshop166/chopshoplib/commands/Commands.kt
@@ -0,0 +1,271 @@
+package com.chopshop166.chopshoplib.commands
+
+import com.chopshop166.chopshoplib.Resettable
+import com.chopshop166.chopshoplib.commands.CommandRobot
+import com.chopshop166.chopshoplib.commands.CommandUtils
+import edu.wpi.first.wpilibj2.command.Command
+import edu.wpi.first.wpilibj2.command.CommandBase
+import edu.wpi.first.wpilibj2.command.CommandGroupBase
+import edu.wpi.first.wpilibj2.command.InstantCommand
+import edu.wpi.first.wpilibj2.command.RunCommand
+import edu.wpi.first.wpilibj2.command.StartEndCommand
+import edu.wpi.first.wpilibj2.command.Subsystem
+import edu.wpi.first.wpilibj2.command.SubsystemBase
+import edu.wpi.first.wpilibj2.command.WaitCommand
+import edu.wpi.first.wpilibj2.command.WaitUntilCommand
+import java.util.function.Supplier
+
+fun wait(time: Double) = WaitCommand(time)
+fun wait(until: () -> Boolean) = WaitUntilCommand(until)
+
+fun exec(block: () -> Unit) = InstantCommand(block)
+
+fun repeat(name: String = "", num: Int, block: () -> Command) =
+ CommandUtils.repeat(num, Supplier(block)).apply {
+ if (name != "") {
+ this.name = name
+ }
+ }
+
+@DslMarker
+annotation class CommandBuilderMarker
+
+@CommandBuilderMarker
+class CommandBuilder(var cmdName: String = "", private vararg val sys: Subsystem) {
+ private var onInit: Command.() -> Unit = {}
+ private var onExecute: Command.() -> Unit = {}
+ private var onEnd: Command.(Boolean) -> Unit = {}
+ private var isFinishedCheck: Command.() -> Boolean = { true }
+
+ fun initialize(onInit: Command.() -> Unit) { this.onInit = onInit }
+ fun execute(onExecute: Command.() -> Unit) { this.onExecute = onExecute }
+ fun end(onEnd: Command.(Boolean) -> Unit) { this.onEnd = onEnd }
+ fun isFinished(isFinishedCheck: Command.() -> Boolean) { this.isFinishedCheck = isFinishedCheck }
+
+ fun build() =
+ object : CommandBase() {
+ init {
+ if (cmdName != "") {
+ this.name = cmdName
+ }
+ addRequirements(*sys)
+ }
+ override fun initialize() = onInit()
+ override fun execute() = onExecute()
+ override fun end(interrupted: Boolean) = onEnd(interrupted)
+ override fun isFinished() = isFinishedCheck()
+ }
+}
+
+fun cmd(name: String = "", block: CommandBuilder.() -> Unit) =
+ CommandBuilder(name).apply(block).build()
+
+inline fun CommandRobot.getMapForName(
+ name: String,
+ pkg: String,
+ defaultValue: T
+) =
+ CommandRobot.getMapForName(name, T::class.java, pkg, defaultValue)
+
+fun Subsystem.cmd(name: String = "", block: CommandBuilder.() -> Unit) =
+ CommandBuilder(name, this).apply(block).build()
+
+/**
+ * Create an {@link InstantCommand}.
+ *
+ * @param name The name of the command.
+ * @param action The action to take.
+ * @return A new command.
+ */
+fun Subsystem.instant(name : String, action : () -> Unit) =
+ InstantCommand(action, this).withName(name)
+
+/**
+ * Create an {@link InstantCommand}.
+ *
+ * @param name The name of the command.
+ * @param action The action to take.
+ * @return A new command.
+ */
+fun instant(name : String, action : () -> Unit) =
+ InstantCommand(action).withName(name)
+
+/**
+ * Create a {@link RunCommand}.
+ *
+ * @param name The name of the command.
+ * @param action The action to take.
+ * @return A new command.
+ */
+fun Subsystem.running(name : String, action : () -> Unit) =
+ RunCommand(action, this).withName(name)
+
+/**
+ * Create a {@link RunCommand}.
+ *
+ * @param name The name of the command.
+ * @param action The action to take.
+ * @return A new command.
+ */
+fun running(name : String, action : () -> Unit) =
+ RunCommand(action).withName(name)
+
+/**
+ * Create a {@link StartEndCommand}.
+ *
+ * @param name The name of the command.
+ * @param onStart The action to take on start.
+ * @param onEnd The action to take on end.
+ * @return A new command.
+ */
+fun Subsystem.startEnd(name : String, onStart : () -> Unit, onEnd : () -> Unit) =
+ StartEndCommand(onStart, onEnd, this).withName(name)
+
+/**
+ * Create a {@link StartEndCommand}.
+ *
+ * @param name The name of the command.
+ * @param onStart The action to take on start.
+ * @param onEnd The action to take on end.
+ * @return A new command.
+ */
+fun startEnd(name : String, onStart : () -> Unit, onEnd : () -> Unit) =
+ StartEndCommand(onStart, onEnd,).withName(name)
+
+/**
+ * Run a {@link Runnable} and then wait until a condition is true.
+ *
+ * @param name The name of the command.
+ * @param init The action to take.
+ * @param until The condition to wait until.
+ * @return A new command.
+ */
+fun Subsystem.initAndWait(name : String, init : () -> Unit, until : () -> Boolean) =
+ parallel(name, InstantCommand(init, this), wait(until))
+
+/**
+ * Run a {@link Runnable} and then wait until a condition is true.
+ *
+ * @param name The name of the command.
+ * @param init The action to take.
+ * @param until The condition to wait until.
+ * @return A new command.
+ */
+fun initAndWait(name : String, init : () -> Unit, until : () -> Boolean) =
+ parallel(name, InstantCommand(init), wait(until))
+
+/**
+ * Create a command to call a consumer function.
+ *
+ * @param The type to wrap.
+ * @param name The name of the command.
+ * @param value The value to call the function with.
+ * @param func The function to call.
+ * @return A new command.
+ */
+fun Subsystem.setter(name : String, value : T, func : (T) -> Unit) =
+ instant(name) {
+ func(value);
+ }
+
+/**
+ * Create a command to call a consumer function.
+ *
+ * @param The type to wrap.
+ * @param name The name of the command.
+ * @param value The value to call the function with.
+ * @param func The function to call.
+ * @return A new command.
+ */
+fun setter(name : String, value : T, func : (T) -> Unit) =
+ instant(name) {
+ func(value);
+ }
+
+/**
+ * Create a command to call a consumer function and wait.
+ *
+ * @param The type to wrap.
+ * @param name The name of the command.
+ * @param value The value to call the function with.
+ * @param func The function to call.
+ * @param until The condition to wait until.
+ * @return A new command.
+ */
+fun Subsystem.callAndWait(name : String, value : T, func : (T) -> Unit, until : () -> Boolean) =
+ initAndWait(name, {
+ func(value);
+ }, until);
+
+/**
+ * Create a command to call a consumer function and wait.
+ *
+ * @param The type to wrap.
+ * @param name The name of the command.
+ * @param value The value to call the function with.
+ * @param func The function to call.
+ * @param until The condition to wait until.
+ * @return A new command.
+ */
+fun callAndWait(name : String, value : T, func : (T) -> Unit, until : () -> Boolean) =
+ initAndWait(name, {
+ func(value);
+ }, until);
+
+/**
+ * Create a command to reset the subsystem.
+ *
+ * @return A reset command.
+ */
+fun T.resetCmd() where T : SubsystemBase, T : Resettable =
+ instant("Reset " + this.name, this::reset)
+
+/**
+ * Cancel the currently running command.
+ *
+ * @return A cancel command.
+ */
+fun SubsystemBase.cancel() =
+ instant("Cancel " + this.name) {
+ }
+
+/**
+ * Create a sequential command group with a name.
+ *
+ * @param name The name of the command group.
+ * @param cmds The commands to sequence.
+ * @return A new command group.
+ */
+fun sequence(name : String, vararg cmds : Command) =
+ CommandGroupBase.sequence(*cmds).withName(name)
+
+/**
+ * Create a parallel command group with a name.
+ *
+ * @param name The name of the command group.
+ * @param cmds The commands to run in parallel.
+ * @return A new command group.
+ */
+fun parallel(name : String, vararg cmds : Command) =
+ CommandGroupBase.parallel(*cmds).withName(name)
+
+/**
+ * Create a racing command group with a name.
+ *
+ * @param name The name of the command group.
+ * @param racers The commands to race.
+ * @return A new command group.
+ */
+fun race(name : String, vararg racers : Command) =
+ CommandGroupBase.race(*racers).withName(name)
+
+/**
+ * Create a deadline-limited command group with a name.
+ *
+ * @param name The name of the command group.
+ * @param limiter The deadline command.
+ * @param cmds The commands to run until the deadline ends.
+ * @return A new command group.
+ */
+fun deadline(name : String, limiter : Command, vararg cmds : Command) =
+ CommandGroupBase.deadline(limiter, *cmds).withName(name)
\ No newline at end of file
diff --git a/src/main/java/com/chopshop166/chopshoplib/commands/PresetSubsystem.java b/core/src/main/java/com/chopshop166/chopshoplib/commands/PresetSubsystem.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/commands/PresetSubsystem.java
rename to core/src/main/java/com/chopshop166/chopshoplib/commands/PresetSubsystem.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/commands/RepeatWhileCommand.java b/core/src/main/java/com/chopshop166/chopshoplib/commands/RepeatWhileCommand.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/commands/RepeatWhileCommand.java
rename to core/src/main/java/com/chopshop166/chopshoplib/commands/RepeatWhileCommand.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/commands/SmartSubsystem.java b/core/src/main/java/com/chopshop166/chopshoplib/commands/SmartSubsystem.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/commands/SmartSubsystem.java
rename to core/src/main/java/com/chopshop166/chopshoplib/commands/SmartSubsystem.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/commands/SmartSubsystemBase.java b/core/src/main/java/com/chopshop166/chopshoplib/commands/SmartSubsystemBase.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/commands/SmartSubsystemBase.java
rename to core/src/main/java/com/chopshop166/chopshoplib/commands/SmartSubsystemBase.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/commands/package-info.java b/core/src/main/java/com/chopshop166/chopshoplib/commands/package-info.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/commands/package-info.java
rename to core/src/main/java/com/chopshop166/chopshoplib/commands/package-info.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/controls/ButtonJoystick.java b/core/src/main/java/com/chopshop166/chopshoplib/controls/ButtonJoystick.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/controls/ButtonJoystick.java
rename to core/src/main/java/com/chopshop166/chopshoplib/controls/ButtonJoystick.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/controls/ButtonXboxController.java b/core/src/main/java/com/chopshop166/chopshoplib/controls/ButtonXboxController.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/controls/ButtonXboxController.java
rename to core/src/main/java/com/chopshop166/chopshoplib/controls/ButtonXboxController.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/controls/CallbackUtils.java b/core/src/main/java/com/chopshop166/chopshoplib/controls/CallbackUtils.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/controls/CallbackUtils.java
rename to core/src/main/java/com/chopshop166/chopshoplib/controls/CallbackUtils.java
diff --git a/core/src/main/java/com/chopshop166/chopshoplib/controls/Controls.kt b/core/src/main/java/com/chopshop166/chopshoplib/controls/Controls.kt
new file mode 100644
index 00000000..55ef9d7f
--- /dev/null
+++ b/core/src/main/java/com/chopshop166/chopshoplib/controls/Controls.kt
@@ -0,0 +1,36 @@
+package com.chopshop166.chopshoplib.controls
+
+import com.chopshop166.chopshoplib.controls.ButtonXboxController
+import com.chopshop166.chopshoplib.triggers.XboxTrigger
+import edu.wpi.first.wpilibj.GenericHID.Hand
+import edu.wpi.first.wpilibj.XboxController.Button
+import edu.wpi.first.wpilibj2.command.button.JoystickButton
+import edu.wpi.first.wpilibj2.command.button.Trigger
+
+typealias TriggerSetup = Trigger.() -> Unit
+typealias ButtonSetup = JoystickButton.() -> Unit
+
+class ControllerConfigurer(val controller : ButtonXboxController) {
+
+ private fun button(btn : Button) = JoystickButton(controller, btn.value)
+
+ operator fun JoystickButton.invoke(block : ButtonSetup) = block()
+ operator fun XboxTrigger.invoke(block : TriggerSetup) = block()
+
+ val a = button(Button.kA)
+ val b = button(Button.kB)
+ val x = button(Button.kX)
+ val y = button(Button.kY)
+ val start = button(Button.kStart)
+ val back = button(Button.kBack)
+ val l = button(Button.kBumperLeft)
+ val r = button(Button.kBumperRight)
+ val stickLeft = button(Button.kStickLeft)
+ val stickRight = button(Button.kStickRight)
+ val trigger = mapOf(
+ Hand.kLeft to XboxTrigger(controller, Hand.kLeft),
+ Hand.kRight to XboxTrigger(controller, Hand.kRight)
+ )
+}
+
+fun ButtonXboxController.configure(block : ControllerConfigurer.() -> Unit) = ControllerConfigurer(this).block()
diff --git a/src/main/java/com/chopshop166/chopshoplib/controls/package-info.java b/core/src/main/java/com/chopshop166/chopshoplib/controls/package-info.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/controls/package-info.java
rename to core/src/main/java/com/chopshop166/chopshoplib/controls/package-info.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/maps/DifferentialDriveMap.java b/core/src/main/java/com/chopshop166/chopshoplib/maps/DifferentialDriveMap.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/maps/DifferentialDriveMap.java
rename to core/src/main/java/com/chopshop166/chopshoplib/maps/DifferentialDriveMap.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/maps/RobotMapFor.java b/core/src/main/java/com/chopshop166/chopshoplib/maps/RobotMapFor.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/maps/RobotMapFor.java
rename to core/src/main/java/com/chopshop166/chopshoplib/maps/RobotMapFor.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/maps/RobotMapForCollection.java b/core/src/main/java/com/chopshop166/chopshoplib/maps/RobotMapForCollection.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/maps/RobotMapForCollection.java
rename to core/src/main/java/com/chopshop166/chopshoplib/maps/RobotMapForCollection.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/maps/package-info.java b/core/src/main/java/com/chopshop166/chopshoplib/maps/package-info.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/maps/package-info.java
rename to core/src/main/java/com/chopshop166/chopshoplib/maps/package-info.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/outputs/DigitalOutputDutyCycle.java b/core/src/main/java/com/chopshop166/chopshoplib/outputs/DigitalOutputDutyCycle.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/outputs/DigitalOutputDutyCycle.java
rename to core/src/main/java/com/chopshop166/chopshoplib/outputs/DigitalOutputDutyCycle.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/outputs/IDSolenoid.java b/core/src/main/java/com/chopshop166/chopshoplib/outputs/IDSolenoid.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/outputs/IDSolenoid.java
rename to core/src/main/java/com/chopshop166/chopshoplib/outputs/IDSolenoid.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/outputs/IDigitalOutput.java b/core/src/main/java/com/chopshop166/chopshoplib/outputs/IDigitalOutput.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/outputs/IDigitalOutput.java
rename to core/src/main/java/com/chopshop166/chopshoplib/outputs/IDigitalOutput.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/outputs/ISolenoid.java b/core/src/main/java/com/chopshop166/chopshoplib/outputs/ISolenoid.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/outputs/ISolenoid.java
rename to core/src/main/java/com/chopshop166/chopshoplib/outputs/ISolenoid.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/outputs/MockDSolenoid.java b/core/src/main/java/com/chopshop166/chopshoplib/outputs/MockDSolenoid.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/outputs/MockDSolenoid.java
rename to core/src/main/java/com/chopshop166/chopshoplib/outputs/MockDSolenoid.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/outputs/MockDigitalOutput.java b/core/src/main/java/com/chopshop166/chopshoplib/outputs/MockDigitalOutput.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/outputs/MockDigitalOutput.java
rename to core/src/main/java/com/chopshop166/chopshoplib/outputs/MockDigitalOutput.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/outputs/MockSolenoid.java b/core/src/main/java/com/chopshop166/chopshoplib/outputs/MockSolenoid.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/outputs/MockSolenoid.java
rename to core/src/main/java/com/chopshop166/chopshoplib/outputs/MockSolenoid.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/outputs/MockSpeedController.java b/core/src/main/java/com/chopshop166/chopshoplib/outputs/MockSpeedController.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/outputs/MockSpeedController.java
rename to core/src/main/java/com/chopshop166/chopshoplib/outputs/MockSpeedController.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/outputs/Modifier.java b/core/src/main/java/com/chopshop166/chopshoplib/outputs/Modifier.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/outputs/Modifier.java
rename to core/src/main/java/com/chopshop166/chopshoplib/outputs/Modifier.java
diff --git a/core/src/main/java/com/chopshop166/chopshoplib/outputs/Motors.kt b/core/src/main/java/com/chopshop166/chopshoplib/outputs/Motors.kt
new file mode 100644
index 00000000..02b74513
--- /dev/null
+++ b/core/src/main/java/com/chopshop166/chopshoplib/outputs/Motors.kt
@@ -0,0 +1,76 @@
+package com.chopshop166.chopshoplib.outputs
+
+import com.chopshop166.chopshoplib.sensors.IEncoder
+import com.chopshop166.chopshoplib.sensors.MockEncoder
+import com.ctre.phoenix.motorcontrol.ControlMode
+import com.ctre.phoenix.motorcontrol.can.WPI_TalonFX
+import com.ctre.phoenix.motorcontrol.can.WPI_TalonSRX
+import com.revrobotics.CANSparkMax
+import com.revrobotics.ControlType
+import edu.wpi.first.wpilibj.Sendable
+import edu.wpi.first.wpilibj.SpeedController
+import edu.wpi.first.wpilibj.controller.PIDController
+
+enum class PIDControlType {
+ Velocity,
+ Position
+}
+
+fun SmartMotorController.toSmart() = this
+
+fun T.toSmart(encoder: IEncoder = MockEncoder(), vararg mods: Modifier) where
+T : Sendable,
+T : SpeedController = SmartMotorController(this, encoder, *mods)
+
+fun CANSparkMax.follow(thisObj: PIDSparkMax, inverted: Boolean = false) =
+ follow(thisObj.motorController, inverted)
+
+fun CANSparkMax.withPID(
+ controlType: PIDControlType = PIDControlType.Velocity,
+ block: PIDSparkMax.() -> Unit = {}
+) =
+ PIDSparkMax(this).apply {
+ this.controlType =
+ when (controlType) {
+ PIDControlType.Position -> ControlType.kPosition
+ PIDControlType.Velocity -> ControlType.kVelocity
+ }
+ block()
+ }
+
+fun WPI_TalonSRX.withPID(
+ controlType: PIDControlType = PIDControlType.Velocity,
+ block: WPI_TalonSRX.() -> Unit = {}
+) =
+ PIDTalonSRX(this).apply {
+ this.controlType =
+ when (controlType) {
+ PIDControlType.Position -> ControlMode.Position
+ PIDControlType.Velocity -> ControlMode.Velocity
+ }
+ block()
+ }
+
+fun WPI_TalonFX.withPID(
+ controlType: PIDControlType = PIDControlType.Velocity,
+ block: WPI_TalonFX.() -> Unit = {}
+) =
+ PIDTalonFX(this).apply {
+ this.controlType =
+ when (controlType) {
+ PIDControlType.Position -> ControlMode.Position
+ PIDControlType.Velocity -> ControlMode.Velocity
+ }
+ block()
+ }
+
+fun T.withPID(
+ pid: PIDController,
+ encoder: IEncoder,
+ controlType: PIDControlType = PIDControlType.Velocity,
+ block: SwPIDSpeedController.() -> Unit = {}
+) where T : SpeedController, T : Sendable =
+ when (controlType) {
+ PIDControlType.Position -> SwPIDSpeedController.position(this, pid, encoder)
+ PIDControlType.Velocity -> SwPIDSpeedController.velocity(this, pid, encoder)
+ }.apply(block)
diff --git a/src/main/java/com/chopshop166/chopshoplib/outputs/PIDSparkMax.java b/core/src/main/java/com/chopshop166/chopshoplib/outputs/PIDSparkMax.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/outputs/PIDSparkMax.java
rename to core/src/main/java/com/chopshop166/chopshoplib/outputs/PIDSparkMax.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/outputs/PIDTalonBase.java b/core/src/main/java/com/chopshop166/chopshoplib/outputs/PIDTalonBase.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/outputs/PIDTalonBase.java
rename to core/src/main/java/com/chopshop166/chopshoplib/outputs/PIDTalonBase.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/outputs/PIDTalonFX.java b/core/src/main/java/com/chopshop166/chopshoplib/outputs/PIDTalonFX.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/outputs/PIDTalonFX.java
rename to core/src/main/java/com/chopshop166/chopshoplib/outputs/PIDTalonFX.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/outputs/PIDTalonSRX.java b/core/src/main/java/com/chopshop166/chopshoplib/outputs/PIDTalonSRX.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/outputs/PIDTalonSRX.java
rename to core/src/main/java/com/chopshop166/chopshoplib/outputs/PIDTalonSRX.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/outputs/SmartMotorController.java b/core/src/main/java/com/chopshop166/chopshoplib/outputs/SmartMotorController.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/outputs/SmartMotorController.java
rename to core/src/main/java/com/chopshop166/chopshoplib/outputs/SmartMotorController.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/outputs/SwPIDSpeedController.java b/core/src/main/java/com/chopshop166/chopshoplib/outputs/SwPIDSpeedController.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/outputs/SwPIDSpeedController.java
rename to core/src/main/java/com/chopshop166/chopshoplib/outputs/SwPIDSpeedController.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/outputs/WDSolenoid.java b/core/src/main/java/com/chopshop166/chopshoplib/outputs/WDSolenoid.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/outputs/WDSolenoid.java
rename to core/src/main/java/com/chopshop166/chopshoplib/outputs/WDSolenoid.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/outputs/WSolenoid.java b/core/src/main/java/com/chopshop166/chopshoplib/outputs/WSolenoid.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/outputs/WSolenoid.java
rename to core/src/main/java/com/chopshop166/chopshoplib/outputs/WSolenoid.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/outputs/package-info.java b/core/src/main/java/com/chopshop166/chopshoplib/outputs/package-info.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/outputs/package-info.java
rename to core/src/main/java/com/chopshop166/chopshoplib/outputs/package-info.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/package-info.java b/core/src/main/java/com/chopshop166/chopshoplib/package-info.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/package-info.java
rename to core/src/main/java/com/chopshop166/chopshoplib/package-info.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/sensors/CTREEncoder.java b/core/src/main/java/com/chopshop166/chopshoplib/sensors/CTREEncoder.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/sensors/CTREEncoder.java
rename to core/src/main/java/com/chopshop166/chopshoplib/sensors/CTREEncoder.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/sensors/DigitalInputSource.java b/core/src/main/java/com/chopshop166/chopshoplib/sensors/DigitalInputSource.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/sensors/DigitalInputSource.java
rename to core/src/main/java/com/chopshop166/chopshoplib/sensors/DigitalInputSource.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/sensors/IAbsolutePosition.java b/core/src/main/java/com/chopshop166/chopshoplib/sensors/IAbsolutePosition.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/sensors/IAbsolutePosition.java
rename to core/src/main/java/com/chopshop166/chopshoplib/sensors/IAbsolutePosition.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/sensors/IColorSensor.java b/core/src/main/java/com/chopshop166/chopshoplib/sensors/IColorSensor.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/sensors/IColorSensor.java
rename to core/src/main/java/com/chopshop166/chopshoplib/sensors/IColorSensor.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/sensors/IEncoder.java b/core/src/main/java/com/chopshop166/chopshoplib/sensors/IEncoder.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/sensors/IEncoder.java
rename to core/src/main/java/com/chopshop166/chopshoplib/sensors/IEncoder.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/sensors/Lidar.java b/core/src/main/java/com/chopshop166/chopshoplib/sensors/Lidar.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/sensors/Lidar.java
rename to core/src/main/java/com/chopshop166/chopshoplib/sensors/Lidar.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/sensors/MockAccelerometer.java b/core/src/main/java/com/chopshop166/chopshoplib/sensors/MockAccelerometer.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/sensors/MockAccelerometer.java
rename to core/src/main/java/com/chopshop166/chopshoplib/sensors/MockAccelerometer.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/sensors/MockAnalogInput.java b/core/src/main/java/com/chopshop166/chopshoplib/sensors/MockAnalogInput.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/sensors/MockAnalogInput.java
rename to core/src/main/java/com/chopshop166/chopshoplib/sensors/MockAnalogInput.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/sensors/MockColorSensor.java b/core/src/main/java/com/chopshop166/chopshoplib/sensors/MockColorSensor.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/sensors/MockColorSensor.java
rename to core/src/main/java/com/chopshop166/chopshoplib/sensors/MockColorSensor.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/sensors/MockDigitalInput.java b/core/src/main/java/com/chopshop166/chopshoplib/sensors/MockDigitalInput.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/sensors/MockDigitalInput.java
rename to core/src/main/java/com/chopshop166/chopshoplib/sensors/MockDigitalInput.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/sensors/MockEncoder.java b/core/src/main/java/com/chopshop166/chopshoplib/sensors/MockEncoder.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/sensors/MockEncoder.java
rename to core/src/main/java/com/chopshop166/chopshoplib/sensors/MockEncoder.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/sensors/MockGyro.java b/core/src/main/java/com/chopshop166/chopshoplib/sensors/MockGyro.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/sensors/MockGyro.java
rename to core/src/main/java/com/chopshop166/chopshoplib/sensors/MockGyro.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/sensors/PigeonGyro.java b/core/src/main/java/com/chopshop166/chopshoplib/sensors/PigeonGyro.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/sensors/PigeonGyro.java
rename to core/src/main/java/com/chopshop166/chopshoplib/sensors/PigeonGyro.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/sensors/REVColorSensor.java b/core/src/main/java/com/chopshop166/chopshoplib/sensors/REVColorSensor.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/sensors/REVColorSensor.java
rename to core/src/main/java/com/chopshop166/chopshoplib/sensors/REVColorSensor.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/sensors/SparkMaxEncoder.java b/core/src/main/java/com/chopshop166/chopshoplib/sensors/SparkMaxEncoder.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/sensors/SparkMaxEncoder.java
rename to core/src/main/java/com/chopshop166/chopshoplib/sensors/SparkMaxEncoder.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/sensors/TalonEncoder.java b/core/src/main/java/com/chopshop166/chopshoplib/sensors/TalonEncoder.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/sensors/TalonEncoder.java
rename to core/src/main/java/com/chopshop166/chopshoplib/sensors/TalonEncoder.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/sensors/WDigitalInput.java b/core/src/main/java/com/chopshop166/chopshoplib/sensors/WDigitalInput.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/sensors/WDigitalInput.java
rename to core/src/main/java/com/chopshop166/chopshoplib/sensors/WDigitalInput.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/sensors/WEncoder.java b/core/src/main/java/com/chopshop166/chopshoplib/sensors/WEncoder.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/sensors/WEncoder.java
rename to core/src/main/java/com/chopshop166/chopshoplib/sensors/WEncoder.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/sensors/package-info.java b/core/src/main/java/com/chopshop166/chopshoplib/sensors/package-info.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/sensors/package-info.java
rename to core/src/main/java/com/chopshop166/chopshoplib/sensors/package-info.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/states/Direction.java b/core/src/main/java/com/chopshop166/chopshoplib/states/Direction.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/states/Direction.java
rename to core/src/main/java/com/chopshop166/chopshoplib/states/Direction.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/states/OpenClose.java b/core/src/main/java/com/chopshop166/chopshoplib/states/OpenClose.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/states/OpenClose.java
rename to core/src/main/java/com/chopshop166/chopshoplib/states/OpenClose.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/states/StateSubsystem.java b/core/src/main/java/com/chopshop166/chopshoplib/states/StateSubsystem.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/states/StateSubsystem.java
rename to core/src/main/java/com/chopshop166/chopshoplib/states/StateSubsystem.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/states/package-info.java b/core/src/main/java/com/chopshop166/chopshoplib/states/package-info.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/states/package-info.java
rename to core/src/main/java/com/chopshop166/chopshoplib/states/package-info.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/triggers/XboxTrigger.java b/core/src/main/java/com/chopshop166/chopshoplib/triggers/XboxTrigger.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/triggers/XboxTrigger.java
rename to core/src/main/java/com/chopshop166/chopshoplib/triggers/XboxTrigger.java
diff --git a/src/main/java/com/chopshop166/chopshoplib/triggers/package-info.java b/core/src/main/java/com/chopshop166/chopshoplib/triggers/package-info.java
similarity index 100%
rename from src/main/java/com/chopshop166/chopshoplib/triggers/package-info.java
rename to core/src/main/java/com/chopshop166/chopshoplib/triggers/package-info.java
diff --git a/src/test/java/com/chopshop166/chopshoplib/DigitalInputSourceTest.java b/core/src/test/java/com/chopshop166/chopshoplib/DigitalInputSourceTest.java
similarity index 100%
rename from src/test/java/com/chopshop166/chopshoplib/DigitalInputSourceTest.java
rename to core/src/test/java/com/chopshop166/chopshoplib/DigitalInputSourceTest.java
diff --git a/src/test/java/com/chopshop166/chopshoplib/maps/MapA.java b/core/src/test/java/com/chopshop166/chopshoplib/maps/MapA.java
similarity index 100%
rename from src/test/java/com/chopshop166/chopshoplib/maps/MapA.java
rename to core/src/test/java/com/chopshop166/chopshoplib/maps/MapA.java
diff --git a/src/test/java/com/chopshop166/chopshoplib/maps/MapB.java b/core/src/test/java/com/chopshop166/chopshoplib/maps/MapB.java
similarity index 100%
rename from src/test/java/com/chopshop166/chopshoplib/maps/MapB.java
rename to core/src/test/java/com/chopshop166/chopshoplib/maps/MapB.java
diff --git a/src/test/java/com/chopshop166/chopshoplib/maps/MapGetterTest.java b/core/src/test/java/com/chopshop166/chopshoplib/maps/MapGetterTest.java
similarity index 100%
rename from src/test/java/com/chopshop166/chopshoplib/maps/MapGetterTest.java
rename to core/src/test/java/com/chopshop166/chopshoplib/maps/MapGetterTest.java
diff --git a/src/test/java/com/chopshop166/chopshoplib/maps/RobotMap.java b/core/src/test/java/com/chopshop166/chopshoplib/maps/RobotMap.java
similarity index 100%
rename from src/test/java/com/chopshop166/chopshoplib/maps/RobotMap.java
rename to core/src/test/java/com/chopshop166/chopshoplib/maps/RobotMap.java
diff --git a/src/test/java/com/chopshop166/chopshoplib/states/Claw.java b/core/src/test/java/com/chopshop166/chopshoplib/states/Claw.java
similarity index 100%
rename from src/test/java/com/chopshop166/chopshoplib/states/Claw.java
rename to core/src/test/java/com/chopshop166/chopshoplib/states/Claw.java
diff --git a/src/test/java/com/chopshop166/chopshoplib/states/Claw2.java b/core/src/test/java/com/chopshop166/chopshoplib/states/Claw2.java
similarity index 100%
rename from src/test/java/com/chopshop166/chopshoplib/states/Claw2.java
rename to core/src/test/java/com/chopshop166/chopshoplib/states/Claw2.java
diff --git a/core/vendordeps/LightDrive.json b/core/vendordeps/LightDrive.json
new file mode 100644
index 00000000..f90f9c71
--- /dev/null
+++ b/core/vendordeps/LightDrive.json
@@ -0,0 +1,32 @@
+{
+ "fileName": "LightDrive.json",
+ "name": "MACH-LightDrive",
+ "version": "2019.2.19",
+ "uuid": "8e771444-0691-4b6d-8d09-8868c3298009",
+ "mavenUrls": [
+ "http://mach-engineering.com/products/maven/"
+ ],
+ "jsonUrl": "http://mach-engineering.com/products/maven/com/mach/LightDrive/LightDrive.json",
+ "javaDependencies": [
+ {
+ "groupId": "com.mach.LightDrive",
+ "artifactId": "LightDrive-java",
+ "version": "2019.2.19"
+ }
+ ],
+ "jniDependencies": [],
+ "cppDependencies": [
+ {
+ "groupId": "com.mach.LightDrive",
+ "artifactId": "LightDrive-cpp",
+ "version": "2019.2.13",
+ "libName": "mach",
+ "headerClassifier": "headers",
+ "sharedLibrary": false,
+ "skipInvalidPlatforms": true,
+ "binaryPlatforms": [
+ "linuxathena"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/vendordeps/Phoenix.json b/core/vendordeps/Phoenix.json
similarity index 100%
rename from vendordeps/Phoenix.json
rename to core/vendordeps/Phoenix.json
diff --git a/vendordeps/REVColorSensorV3.json b/core/vendordeps/REVColorSensorV3.json
similarity index 100%
rename from vendordeps/REVColorSensorV3.json
rename to core/vendordeps/REVColorSensorV3.json
diff --git a/vendordeps/REVRobotics.json b/core/vendordeps/REVRobotics.json
similarity index 100%
rename from vendordeps/REVRobotics.json
rename to core/vendordeps/REVRobotics.json
diff --git a/vendordeps/WPILibNewCommands.json b/core/vendordeps/WPILibNewCommands.json
similarity index 100%
rename from vendordeps/WPILibNewCommands.json
rename to core/vendordeps/WPILibNewCommands.json
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index d2ba48d7..549d8442 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
-distributionPath=permwrapper/dists
+distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.9-bin.zip
zipStoreBase=GRADLE_USER_HOME
-zipStorePath=permwrapper/dists
+zipStorePath=wrapper/dists
diff --git a/gradlew.bat b/gradlew.bat
index ac1b06f9..107acd32 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -1,89 +1,89 @@
-@rem
-@rem Copyright 2015 the original author or authors.
-@rem
-@rem Licensed under the Apache License, Version 2.0 (the "License");
-@rem you may not use this file except in compliance with the License.
-@rem You may obtain a copy of the License at
-@rem
-@rem https://www.apache.org/licenses/LICENSE-2.0
-@rem
-@rem Unless required by applicable law or agreed to in writing, software
-@rem distributed under the License is distributed on an "AS IS" BASIS,
-@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-@rem See the License for the specific language governing permissions and
-@rem limitations under the License.
-@rem
-
-@if "%DEBUG%" == "" @echo off
-@rem ##########################################################################
-@rem
-@rem Gradle startup script for Windows
-@rem
-@rem ##########################################################################
-
-@rem Set local scope for the variables with windows NT shell
-if "%OS%"=="Windows_NT" setlocal
-
-set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
-set APP_BASE_NAME=%~n0
-set APP_HOME=%DIRNAME%
-
-@rem Resolve any "." and ".." in APP_HOME to make it shorter.
-for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
-
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
-
-@rem Find java.exe
-if defined JAVA_HOME goto findJavaFromJavaHome
-
-set JAVA_EXE=java.exe
-%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto execute
-
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:findJavaFromJavaHome
-set JAVA_HOME=%JAVA_HOME:"=%
-set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-
-if exist "%JAVA_EXE%" goto execute
-
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
-
-goto fail
-
-:execute
-@rem Setup the command line
-
-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
-
-
-@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
-
-:end
-@rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
-
-:fail
-rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
-rem the _cmd.exe /c_ return code!
-if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
-
-:mainEnd
-if "%OS%"=="Windows_NT" endlocal
-
-:omega
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/kotlinext/build.gradle b/kotlinext/build.gradle
new file mode 100644
index 00000000..578241b2
--- /dev/null
+++ b/kotlinext/build.gradle
@@ -0,0 +1,31 @@
+plugins {
+ id "org.jetbrains.kotlin.jvm" version "1.5.30"
+ id 'com.chopshop166.chopshoplib.java-library-conventions'
+ id 'com.github.spotbugs' version '4.7.3'
+ id 'edu.wpi.first.GradleRIO' version '2021.3.1'
+}
+
+compileKotlin {
+ kotlinOptions {
+ jvmTarget = "11"
+ freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn"
+ }
+}
+
+dependencies {
+ // WPIlib core
+ api wpi.deps.wpilib()
+ // Vendor dependencies
+ wpi.deps.vendor.loadFrom(project(":core"))
+ api wpi.deps.vendor.java()
+ // Core dependencies
+ api project(':core')
+}
+
+publishing {
+ publications {
+ kotlinext(MavenPublication) {
+ from components.java
+ }
+ }
+}
diff --git a/kotlinext/src/main/java/com/chopshop166/chopshoplib/Dashboard.kt b/kotlinext/src/main/java/com/chopshop166/chopshoplib/Dashboard.kt
new file mode 100644
index 00000000..a5b3580f
--- /dev/null
+++ b/kotlinext/src/main/java/com/chopshop166/chopshoplib/Dashboard.kt
@@ -0,0 +1,93 @@
+@file:OptIn(kotlin.experimental.ExperimentalTypeInference::class)
+package com.chopshop166.chopshoplib
+
+import edu.wpi.cscore.VideoSink
+import edu.wpi.cscore.VideoSource
+import edu.wpi.first.cameraserver.CameraServer
+import edu.wpi.first.wpilibj.Sendable
+import edu.wpi.first.wpilibj.shuffleboard.Shuffleboard
+import edu.wpi.first.wpilibj.shuffleboard.ShuffleboardContainer
+import edu.wpi.first.wpilibj.smartdashboard.SendableChooser
+import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard
+
+class DashboardWrapper internal constructor () {
+ operator fun set(name: String, value: Sendable) = (name displays value)
+ operator fun set(name: String, value: Boolean) = (name displays value)
+ operator fun set(name: String, value: String) = (name displays value)
+ operator fun set(name: String, value: Double) = (name displays value)
+ operator fun set(name: String, value: Int) = (name displays value)
+
+ operator fun get(name: String) = SmartDashboard.getData(name)
+
+ operator fun invoke(block: DashboardWrapper.() -> Unit = {}) = apply(block)
+
+ infix fun String.displays(value: Sendable) = SmartDashboard.putData(this, value)
+ infix fun String.displays(value: Boolean) = SmartDashboard.putBoolean(this, value)
+ infix fun String.displays(value: String) = SmartDashboard.putString(this, value)
+ infix fun String.displays(value: Double) = SmartDashboard.putNumber(this, value)
+ infix fun String.displays(value: Int) = SmartDashboard.putNumber(this, value.toDouble())
+
+ fun entry(name: String) = SmartDashboard.getEntry(name)
+}
+val dashboard = DashboardWrapper()
+
+class ShuffleboardContainerWrapper internal constructor (val con : ShuffleboardContainer) {
+
+ val title : String get() { return con.getTitle() }
+
+ operator fun set(name: String, value: Sendable) = name(value)
+ @OverloadResolutionByLambdaReturnType
+ @JvmName("setBoolean") operator fun set(name: String, value: () -> Boolean) = name(value)
+ @JvmName("setString") operator fun set(name: String, value: () -> String) = name(value)
+ @JvmName("setNumber") operator fun set(name: String, value: () -> Double) = name(value)
+ @JvmName("setInt") operator fun set(name: String, value: () -> Int) = name(value)
+
+ operator fun get(name: String) = ShuffleboardContainerWrapper(con.getLayout(name))
+ fun layout(name : String, block: ShuffleboardContainerWrapper.() -> Unit = {}) = this[name].run(block)
+
+ operator fun String.invoke(value: Sendable) = con.add(this, value)
+ @OverloadResolutionByLambdaReturnType
+ @JvmName("invokeBoolean") operator fun String.invoke(value: () -> Boolean) = con.addBoolean(this, value)
+ @JvmName("invokeString") operator fun String.invoke(value: () -> String) = con.addString(this, value)
+ @JvmName("invokeNumber") operator fun String.invoke(value: () -> Double) = con.addNumber(this, value)
+ @JvmName("invokeInt") operator fun String.invoke(value: () -> Int) = con.addNumber(this) { value().toDouble() }
+}
+fun shuffleboard(name : String, block: ShuffleboardContainerWrapper.() -> Unit = {}) = ShuffleboardContainerWrapper(Shuffleboard.getTab(name)).apply(block)
+
+class ChooserWrapper(val chooser: SendableChooser) {
+ infix fun String.default(value: T) { chooser.setDefaultOption(this, value) }
+ infix fun String.chooses(value: T) { chooser.addOption(this, value) }
+}
+
+inline fun chooser(block: ChooserWrapper.() -> Unit) = SendableChooser().apply {
+ ChooserWrapper(this).block()
+}
+
+class VideoSourceWrapper(val source: VideoSource) {
+ operator fun get(name: String) = source.getProperty(name).get()
+ operator fun set(name: String, value: Int) = source.getProperty(name).set(value)
+}
+
+class VideoSinkWrapper(val sink: VideoSink) {
+ operator fun get(name: String) = sink.getProperty(name).get()
+ operator fun set(name: String, value: Int) = sink.getProperty(name).set(value)
+}
+
+class CameraServerWrapper(private val base: CameraServer) {
+ fun server(block: VideoSinkWrapper.() -> Unit = {}) =
+ base.getServer().apply { VideoSinkWrapper(this).block() }
+ fun server(name: String, block: VideoSinkWrapper.() -> Unit = {}) =
+ base.getServer(name).apply { VideoSinkWrapper(this).block() }
+ fun source(block: VideoSourceWrapper.() -> Unit = {}) =
+ base.startAutomaticCapture().apply { VideoSourceWrapper(this).block() }
+ fun source(dev: Int, block: VideoSourceWrapper.() -> Unit = {}) =
+ base.startAutomaticCapture(dev).apply { VideoSourceWrapper(this).block() }
+ fun source(camera: VideoSource, block: VideoSinkWrapper.() -> Unit = {}) =
+ base.startAutomaticCapture(camera).apply { VideoSinkWrapper(this).block() }
+ fun source(name: String, dev: Int, block: VideoSourceWrapper.() -> Unit = {}) =
+ base.startAutomaticCapture(name, dev).apply { VideoSourceWrapper(this).block() }
+ fun source(name: String, path: String, block: VideoSourceWrapper.() -> Unit = {}) =
+ base.startAutomaticCapture(name, path).apply { VideoSourceWrapper(this).block() }
+}
+
+val cameraServer = CameraServerWrapper(CameraServer.getInstance())
diff --git a/kotlinext/src/main/java/com/chopshop166/chopshoplib/Utils.kt b/kotlinext/src/main/java/com/chopshop166/chopshoplib/Utils.kt
new file mode 100644
index 00000000..51e7790d
--- /dev/null
+++ b/kotlinext/src/main/java/com/chopshop166/chopshoplib/Utils.kt
@@ -0,0 +1,18 @@
+package com.chopshop166.chopshoplib
+
+import kotlin.math.max
+import kotlin.math.min
+import kotlin.math.pow
+import kotlin.math.withSign
+
+fun Double.clamped(minVal : Double, maxVal : Double) = max(minVal, min(maxVal, this))
+fun Float.clamped(minVal : Float, maxVal : Float) = max(minVal, min(maxVal, this))
+fun Int.clamped(minVal : Int, maxVal : Int) = max(minVal, min(maxVal, this))
+fun Long.clamped(minVal : Long, maxVal : Long) = max(minVal, min(maxVal, this))
+
+fun Double.signPow(exp : Double) = pow(exp).withSign(this)
+
+typealias BoolFunc = ()->Boolean
+operator fun BoolFunc.not() = {!this()}
+infix fun BoolFunc.and(other : BoolFunc) = {this() && other()}
+infix fun BoolFunc.or(other : BoolFunc) = {this() || other()}
diff --git a/kotlinext/src/main/java/com/chopshop166/chopshoplib/commands/Commands.kt b/kotlinext/src/main/java/com/chopshop166/chopshoplib/commands/Commands.kt
new file mode 100644
index 00000000..930a71ab
--- /dev/null
+++ b/kotlinext/src/main/java/com/chopshop166/chopshoplib/commands/Commands.kt
@@ -0,0 +1,271 @@
+package com.chopshop166.chopshoplib.commands
+
+import com.chopshop166.chopshoplib.Resettable
+import com.chopshop166.chopshoplib.commands.CommandRobot
+import com.chopshop166.chopshoplib.commands.CommandUtils
+import edu.wpi.first.wpilibj2.command.Command
+import edu.wpi.first.wpilibj2.command.CommandBase
+import edu.wpi.first.wpilibj2.command.CommandGroupBase
+import edu.wpi.first.wpilibj2.command.InstantCommand
+import edu.wpi.first.wpilibj2.command.RunCommand
+import edu.wpi.first.wpilibj2.command.StartEndCommand
+import edu.wpi.first.wpilibj2.command.Subsystem
+import edu.wpi.first.wpilibj2.command.SubsystemBase
+import edu.wpi.first.wpilibj2.command.WaitCommand
+import edu.wpi.first.wpilibj2.command.WaitUntilCommand
+import java.util.function.Supplier
+
+fun wait(time: Double) = WaitCommand(time)
+fun wait(until: () -> Boolean) = WaitUntilCommand(until)
+
+fun exec(block: () -> Unit) = InstantCommand(block)
+
+fun repeat(name: String = "", num: Int, block: () -> Command) =
+ CommandUtils.repeat(num, Supplier(block)).apply {
+ if (name != "") {
+ this.name = name
+ }
+ }
+
+@DslMarker
+annotation class CommandBuilderMarker
+
+@CommandBuilderMarker
+class CommandBuilder(var cmdName: String = "", private vararg val sys: Subsystem) {
+ private var onInit: Command.() -> Unit = {}
+ private var onExecute: Command.() -> Unit = {}
+ private var onEnd: Command.(Boolean) -> Unit = {}
+ private var isFinishedCheck: Command.() -> Boolean = { true }
+
+ fun initialize(onInit: Command.() -> Unit) { this.onInit = onInit }
+ fun execute(onExecute: Command.() -> Unit) { this.onExecute = onExecute }
+ fun end(onEnd: Command.(Boolean) -> Unit) { this.onEnd = onEnd }
+ fun isFinished(isFinishedCheck: Command.() -> Boolean) { this.isFinishedCheck = isFinishedCheck }
+
+ fun build() =
+ object : CommandBase() {
+ init {
+ if (cmdName != "") {
+ this.name = cmdName
+ }
+ addRequirements(*sys)
+ }
+ override fun initialize() = onInit()
+ override fun execute() = onExecute()
+ override fun end(interrupted: Boolean) = onEnd(interrupted)
+ override fun isFinished() = isFinishedCheck()
+ }
+}
+
+fun cmd(name: String = "", block: CommandBuilder.() -> Unit) =
+ CommandBuilder(name).apply(block).build()
+
+inline fun CommandRobot.getMapForName(
+ name: String,
+ pkg: String,
+ defaultValue: T
+) =
+ CommandRobot.getMapForName(name, T::class.java, pkg, defaultValue)
+
+fun Subsystem.cmd(name: String = "", block: CommandBuilder.() -> Unit) =
+ CommandBuilder(name, this).apply(block).build()
+
+/**
+ * Create an {@link InstantCommand}.
+ *
+ * @param name The name of the command.
+ * @param action The action to take.
+ * @return A new command.
+ */
+fun Subsystem.instant(name : String, action : () -> Unit) =
+ InstantCommand(action, this).withName(name)
+
+/**
+ * Create an {@link InstantCommand}.
+ *
+ * @param name The name of the command.
+ * @param action The action to take.
+ * @return A new command.
+ */
+fun instant(name : String, action : () -> Unit) =
+ InstantCommand(action).withName(name)
+
+/**
+ * Create a {@link RunCommand}.
+ *
+ * @param name The name of the command.
+ * @param action The action to take.
+ * @return A new command.
+ */
+fun Subsystem.running(name : String, action : () -> Unit) =
+ RunCommand(action, this).withName(name)
+
+/**
+ * Create a {@link RunCommand}.
+ *
+ * @param name The name of the command.
+ * @param action The action to take.
+ * @return A new command.
+ */
+fun running(name : String, action : () -> Unit) =
+ RunCommand(action).withName(name)
+
+/**
+ * Create a {@link StartEndCommand}.
+ *
+ * @param name The name of the command.
+ * @param onStart The action to take on start.
+ * @param onEnd The action to take on end.
+ * @return A new command.
+ */
+fun Subsystem.startEnd(name : String, onStart : () -> Unit, onEnd : () -> Unit) =
+ StartEndCommand(onStart, onEnd, this).withName(name)
+
+/**
+ * Create a {@link StartEndCommand}.
+ *
+ * @param name The name of the command.
+ * @param onStart The action to take on start.
+ * @param onEnd The action to take on end.
+ * @return A new command.
+ */
+fun startEnd(name : String, onStart : () -> Unit, onEnd : () -> Unit) =
+ StartEndCommand(onStart, onEnd,).withName(name)
+
+/**
+ * Run a {@link Runnable} and then wait until a condition is true.
+ *
+ * @param name The name of the command.
+ * @param init The action to take.
+ * @param until The condition to wait until.
+ * @return A new command.
+ */
+fun Subsystem.initAndWait(name : String, init : () -> Unit, until : () -> Boolean) =
+ parallel(name, InstantCommand(init, this), wait(until))
+
+/**
+ * Run a {@link Runnable} and then wait until a condition is true.
+ *
+ * @param name The name of the command.
+ * @param init The action to take.
+ * @param until The condition to wait until.
+ * @return A new command.
+ */
+fun initAndWait(name : String, init : () -> Unit, until : () -> Boolean) =
+ parallel(name, InstantCommand(init), wait(until))
+
+/**
+ * Create a command to call a consumer function.
+ *
+ * @param The type to wrap.
+ * @param name The name of the command.
+ * @param value The value to call the function with.
+ * @param func The function to call.
+ * @return A new command.
+ */
+fun Subsystem.setter(name : String, value : T, func : (T) -> Unit) =
+ instant(name) {
+ func(value);
+ }
+
+/**
+ * Create a command to call a consumer function.
+ *
+ * @param The type to wrap.
+ * @param name The name of the command.
+ * @param value The value to call the function with.
+ * @param func The function to call.
+ * @return A new command.
+ */
+fun setter(name : String, value : T, func : (T) -> Unit) =
+ instant(name) {
+ func(value);
+ }
+
+/**
+ * Create a command to call a consumer function and wait.
+ *
+ * @param The type to wrap.
+ * @param name The name of the command.
+ * @param value The value to call the function with.
+ * @param func The function to call.
+ * @param until The condition to wait until.
+ * @return A new command.
+ */
+fun Subsystem.callAndWait(name : String, value : T, func : (T) -> Unit, until : () -> Boolean) =
+ initAndWait(name, {
+ func(value);
+ }, until);
+
+/**
+ * Create a command to call a consumer function and wait.
+ *
+ * @param The type to wrap.
+ * @param name The name of the command.
+ * @param value The value to call the function with.
+ * @param func The function to call.
+ * @param until The condition to wait until.
+ * @return A new command.
+ */
+fun callAndWait(name : String, value : T, func : (T) -> Unit, until : () -> Boolean) =
+ initAndWait(name, {
+ func(value);
+ }, until);
+
+/**
+ * Create a command to reset the subsystem.
+ *
+ * @return A reset command.
+ */
+fun T.resetCmd() where T : SubsystemBase, T : Resettable =
+ instant("Reset " + this.name, this::reset)
+
+/**
+ * Cancel the currently running command.
+ *
+ * @return A cancel command.
+ */
+fun SubsystemBase.cancel() =
+ instant("Cancel " + this.name) {
+ }
+
+/**
+ * Create a sequential command group with a name.
+ *
+ * @param name The name of the command group.
+ * @param cmds The commands to sequence.
+ * @return A new command group.
+ */
+fun sequence(name : String, vararg cmds : Command) =
+ CommandGroupBase.sequence(*cmds).withName(name)
+
+/**
+ * Create a parallel command group with a name.
+ *
+ * @param name The name of the command group.
+ * @param cmds The commands to run in parallel.
+ * @return A new command group.
+ */
+fun parallel(name : String, vararg cmds : Command) =
+ CommandGroupBase.parallel(*cmds).withName(name)
+
+/**
+ * Create a racing command group with a name.
+ *
+ * @param name The name of the command group.
+ * @param racers The commands to race.
+ * @return A new command group.
+ */
+fun race(name : String, vararg racers : Command) =
+ CommandGroupBase.race(*racers).withName(name)
+
+/**
+ * Create a deadline-limited command group with a name.
+ *
+ * @param name The name of the command group.
+ * @param limiter The deadline command.
+ * @param cmds The commands to run until the deadline ends.
+ * @return A new command group.
+ */
+fun deadline(name : String, limiter : Command, vararg cmds : Command) =
+ CommandGroupBase.deadline(limiter, *cmds).withName(name)
\ No newline at end of file
diff --git a/kotlinext/src/main/java/com/chopshop166/chopshoplib/controls/Controls.kt b/kotlinext/src/main/java/com/chopshop166/chopshoplib/controls/Controls.kt
new file mode 100644
index 00000000..55ef9d7f
--- /dev/null
+++ b/kotlinext/src/main/java/com/chopshop166/chopshoplib/controls/Controls.kt
@@ -0,0 +1,36 @@
+package com.chopshop166.chopshoplib.controls
+
+import com.chopshop166.chopshoplib.controls.ButtonXboxController
+import com.chopshop166.chopshoplib.triggers.XboxTrigger
+import edu.wpi.first.wpilibj.GenericHID.Hand
+import edu.wpi.first.wpilibj.XboxController.Button
+import edu.wpi.first.wpilibj2.command.button.JoystickButton
+import edu.wpi.first.wpilibj2.command.button.Trigger
+
+typealias TriggerSetup = Trigger.() -> Unit
+typealias ButtonSetup = JoystickButton.() -> Unit
+
+class ControllerConfigurer(val controller : ButtonXboxController) {
+
+ private fun button(btn : Button) = JoystickButton(controller, btn.value)
+
+ operator fun JoystickButton.invoke(block : ButtonSetup) = block()
+ operator fun XboxTrigger.invoke(block : TriggerSetup) = block()
+
+ val a = button(Button.kA)
+ val b = button(Button.kB)
+ val x = button(Button.kX)
+ val y = button(Button.kY)
+ val start = button(Button.kStart)
+ val back = button(Button.kBack)
+ val l = button(Button.kBumperLeft)
+ val r = button(Button.kBumperRight)
+ val stickLeft = button(Button.kStickLeft)
+ val stickRight = button(Button.kStickRight)
+ val trigger = mapOf(
+ Hand.kLeft to XboxTrigger(controller, Hand.kLeft),
+ Hand.kRight to XboxTrigger(controller, Hand.kRight)
+ )
+}
+
+fun ButtonXboxController.configure(block : ControllerConfigurer.() -> Unit) = ControllerConfigurer(this).block()
diff --git a/kotlinext/src/main/java/com/chopshop166/chopshoplib/outputs/Motors.kt b/kotlinext/src/main/java/com/chopshop166/chopshoplib/outputs/Motors.kt
new file mode 100644
index 00000000..02b74513
--- /dev/null
+++ b/kotlinext/src/main/java/com/chopshop166/chopshoplib/outputs/Motors.kt
@@ -0,0 +1,76 @@
+package com.chopshop166.chopshoplib.outputs
+
+import com.chopshop166.chopshoplib.sensors.IEncoder
+import com.chopshop166.chopshoplib.sensors.MockEncoder
+import com.ctre.phoenix.motorcontrol.ControlMode
+import com.ctre.phoenix.motorcontrol.can.WPI_TalonFX
+import com.ctre.phoenix.motorcontrol.can.WPI_TalonSRX
+import com.revrobotics.CANSparkMax
+import com.revrobotics.ControlType
+import edu.wpi.first.wpilibj.Sendable
+import edu.wpi.first.wpilibj.SpeedController
+import edu.wpi.first.wpilibj.controller.PIDController
+
+enum class PIDControlType {
+ Velocity,
+ Position
+}
+
+fun SmartMotorController.toSmart() = this
+
+fun T.toSmart(encoder: IEncoder = MockEncoder(), vararg mods: Modifier) where
+T : Sendable,
+T : SpeedController = SmartMotorController(this, encoder, *mods)
+
+fun CANSparkMax.follow(thisObj: PIDSparkMax, inverted: Boolean = false) =
+ follow(thisObj.motorController, inverted)
+
+fun CANSparkMax.withPID(
+ controlType: PIDControlType = PIDControlType.Velocity,
+ block: PIDSparkMax.() -> Unit = {}
+) =
+ PIDSparkMax(this).apply {
+ this.controlType =
+ when (controlType) {
+ PIDControlType.Position -> ControlType.kPosition
+ PIDControlType.Velocity -> ControlType.kVelocity
+ }
+ block()
+ }
+
+fun WPI_TalonSRX.withPID(
+ controlType: PIDControlType = PIDControlType.Velocity,
+ block: WPI_TalonSRX.() -> Unit = {}
+) =
+ PIDTalonSRX(this).apply {
+ this.controlType =
+ when (controlType) {
+ PIDControlType.Position -> ControlMode.Position
+ PIDControlType.Velocity -> ControlMode.Velocity
+ }
+ block()
+ }
+
+fun WPI_TalonFX.withPID(
+ controlType: PIDControlType = PIDControlType.Velocity,
+ block: WPI_TalonFX.() -> Unit = {}
+) =
+ PIDTalonFX(this).apply {
+ this.controlType =
+ when (controlType) {
+ PIDControlType.Position -> ControlMode.Position
+ PIDControlType.Velocity -> ControlMode.Velocity
+ }
+ block()
+ }
+
+fun T.withPID(
+ pid: PIDController,
+ encoder: IEncoder,
+ controlType: PIDControlType = PIDControlType.Velocity,
+ block: SwPIDSpeedController.() -> Unit = {}
+) where T : SpeedController, T : Sendable =
+ when (controlType) {
+ PIDControlType.Position -> SwPIDSpeedController.position(this, pid, encoder)
+ PIDControlType.Velocity -> SwPIDSpeedController.velocity(this, pid, encoder)
+ }.apply(block)
diff --git a/kotlinext/src/test/java/com/chopshop166/chopshoplib/Dashboard.kt b/kotlinext/src/test/java/com/chopshop166/chopshoplib/Dashboard.kt
new file mode 100644
index 00000000..f3736765
--- /dev/null
+++ b/kotlinext/src/test/java/com/chopshop166/chopshoplib/Dashboard.kt
@@ -0,0 +1,22 @@
+package com.chopshop166.chopshoplib
+
+// Compilation test
+fun testDashboard() {
+ dashboard {
+ "Foo" displays 1234.0
+ "Bar" displays 5678
+ "Baz" displays "Hello, world!"
+ }
+ dashboard["asdf"] = "qwerty"
+}
+
+fun testShuf() {
+ shuffleboard("tabName") {
+ "Foo" { 1234.0 }
+ "Bar" { 5678 }
+ "Baz" { "Hello, world!" }.withSize(2, 1)
+ layout("x") {
+ "y" { "z" }
+ }
+ }
+}
\ No newline at end of file
diff --git a/kotlinext/src/test/java/com/chopshop166/chopshoplib/commands/Commands.kt b/kotlinext/src/test/java/com/chopshop166/chopshoplib/commands/Commands.kt
new file mode 100644
index 00000000..f32d5d23
--- /dev/null
+++ b/kotlinext/src/test/java/com/chopshop166/chopshoplib/commands/Commands.kt
@@ -0,0 +1,31 @@
+package com.chopshop166.chopshoplib.commands
+
+import edu.wpi.first.wpilibj2.command.ConditionalCommand
+import edu.wpi.first.wpilibj2.command.PrintCommand
+import edu.wpi.first.wpilibj2.command.CommandGroupBase.sequence
+import edu.wpi.first.wpilibj2.command.CommandGroupBase.parallel
+import edu.wpi.first.wpilibj2.command.CommandGroupBase.race
+import edu.wpi.first.wpilibj2.command.CommandGroupBase.deadline
+import edu.wpi.first.wpilibj2.command.WaitCommand
+import java.util.function.BooleanSupplier
+
+fun testSequence() =
+ sequence(
+ PrintCommand("A"),
+ parallel(
+ PrintCommand("B"),
+ PrintCommand("C").withTimeout(2.0),
+ sequence(
+ PrintCommand("F1"),
+ wait(Math.PI),
+ PrintCommand("F2"),
+ exec {
+ System.out.println("2 + 2 = " + (2 + 2))
+ }
+ )
+ ),
+ PrintCommand("D"),
+ PrintCommand("E").withTimeout(3.0),
+ PrintCommand("G"),
+ ConditionalCommand(PrintCommand("It's true"), PrintCommand("It's false"), BooleanSupplier { -> true })
+ )
diff --git a/kotlinext/src/test/java/com/chopshop166/chopshoplib/controls/Controls.kt b/kotlinext/src/test/java/com/chopshop166/chopshoplib/controls/Controls.kt
new file mode 100644
index 00000000..5071a0d3
--- /dev/null
+++ b/kotlinext/src/test/java/com/chopshop166/chopshoplib/controls/Controls.kt
@@ -0,0 +1,18 @@
+package com.chopshop166.chopshoplib.controls
+
+import com.chopshop166.chopshoplib.controls.ButtonXboxController
+import edu.wpi.first.wpilibj.XboxController
+import edu.wpi.first.wpilibj2.command.PrintCommand
+
+fun configureTest(controller : ButtonXboxController) {
+ controller.configure {
+ a {
+ whenPressed(PrintCommand("A pressed"))
+ }
+ b {
+ whenPressed(PrintCommand("B pressed"))
+ whenReleased(PrintCommand("B released"))
+ }
+ x.whenPressed(PrintCommand("X pressed"))
+ }
+}
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 00000000..11408f88
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,2 @@
+rootProject.name = 'chopshoplib'
+include('core', 'kotlinext')